mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-03-30 23:38:25 +00:00
parent
8764da787b
commit
4b763a76df
@ -12,7 +12,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
namespace Calls {
|
||||
namespace {
|
||||
|
||||
ushort Data[] = {
|
||||
const ushort Data[] = {
|
||||
0xd83d, 0xde09, 0xd83d, 0xde0d, 0xd83d, 0xde1b, 0xd83d, 0xde2d, 0xd83d, 0xde31, 0xd83d, 0xde21,
|
||||
0xd83d, 0xde0e, 0xd83d, 0xde34, 0xd83d, 0xde35, 0xd83d, 0xde08, 0xd83d, 0xde2c, 0xd83d, 0xde07,
|
||||
0xd83d, 0xde0f, 0xd83d, 0xdc6e, 0xd83d, 0xdc77, 0xd83d, 0xdc82, 0xd83d, 0xdc76, 0xd83d, 0xdc68,
|
||||
@ -69,7 +69,7 @@ ushort Data[] = {
|
||||
0x0030, 0x20e3, 0xd83d, 0xdd1f, 0x2757, 0x2753, 0x2665, 0x2666, 0xd83d, 0xdcaf, 0xd83d, 0xdd17,
|
||||
0xd83d, 0xdd31, 0xd83d, 0xdd34, 0xd83d, 0xdd35, 0xd83d, 0xdd36, 0xd83d, 0xdd37 };
|
||||
|
||||
ushort Offsets[] = {
|
||||
const ushort Offsets[] = {
|
||||
0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22,
|
||||
24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46,
|
||||
48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70,
|
||||
@ -119,7 +119,9 @@ std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call) {
|
||||
for (auto index = 0; index != EmojiCount; ++index) {
|
||||
auto offset = Offsets[index];
|
||||
auto size = Offsets[index + 1] - offset;
|
||||
auto string = QString::fromRawData(reinterpret_cast<QChar*>(Data + offset), size);
|
||||
auto string = QString::fromRawData(
|
||||
reinterpret_cast<const QChar*>(Data + offset),
|
||||
size);
|
||||
auto emoji = Ui::Emoji::Find(string);
|
||||
Assert(emoji != nullptr);
|
||||
}
|
||||
@ -131,7 +133,9 @@ std::vector<EmojiPtr> ComputeEmojiFingerprint(not_null<Call*> call) {
|
||||
auto index = value % EmojiCount;
|
||||
auto offset = Offsets[index];
|
||||
auto size = Offsets[index + 1] - offset;
|
||||
auto string = QString::fromRawData(reinterpret_cast<QChar*>(Data + offset), size);
|
||||
auto string = QString::fromRawData(
|
||||
reinterpret_cast<const QChar*>(Data + offset),
|
||||
size);
|
||||
auto emoji = Ui::Emoji::Find(string);
|
||||
Assert(emoji != nullptr);
|
||||
result.push_back(emoji);
|
||||
|
@ -14,11 +14,15 @@ namespace Ui {
|
||||
namespace Emoji {
|
||||
|
||||
inline utf16string QStringToUTF16(const QString &string) {
|
||||
return utf16string(reinterpret_cast<const utf16char*>(string.constData()), string.size());
|
||||
return utf16string(
|
||||
reinterpret_cast<const utf16char*>(string.constData()),
|
||||
string.size());
|
||||
}
|
||||
|
||||
inline QString QStringFromUTF16(utf16string string) {
|
||||
return QString::fromRawData(reinterpret_cast<const QChar*>(string.data()), string.size());
|
||||
return QString::fromRawData(
|
||||
reinterpret_cast<const QChar*>(string.data()),
|
||||
string.size());
|
||||
}
|
||||
|
||||
constexpr auto kSuggestionMaxLength = internal::kReplacementMaxLength;
|
||||
|
@ -11,6 +11,8 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
#include "base/qthelp_regex.h"
|
||||
#include "styles/style_history.h"
|
||||
#include "window/window_controller.h"
|
||||
#include "emoji_suggestions_data.h"
|
||||
#include "chat_helpers/emoji_suggestions_helper.h"
|
||||
#include "mainwindow.h"
|
||||
#include "auth_session.h"
|
||||
|
||||
@ -115,6 +117,22 @@ MessageField::MessageField(QWidget *parent, not_null<Window::Controller*> contro
|
||||
setMaxHeight(st::historyComposeFieldMaxHeight);
|
||||
|
||||
setTagMimeProcessor(std::make_unique<FieldTagMimeProcessor>());
|
||||
|
||||
addInstantReplace("--", QString(1, QChar(8212)));
|
||||
addInstantReplace("<<", QString(1, QChar(171)));
|
||||
addInstantReplace(">>", QString(1, QChar(187)));
|
||||
const auto &replacements = Ui::Emoji::internal::GetAllReplacements();
|
||||
for (const auto &one : replacements) {
|
||||
const auto with = Ui::Emoji::QStringFromUTF16(one.emoji);
|
||||
const auto what = Ui::Emoji::QStringFromUTF16(one.replacement);
|
||||
addInstantReplace(what, with);
|
||||
}
|
||||
const auto &pairs = Ui::Emoji::internal::GetReplacementPairs();
|
||||
for (const auto &[what, index] : pairs) {
|
||||
const auto emoji = Ui::Emoji::internal::ByIndex(index);
|
||||
Assert(emoji != nullptr);
|
||||
addInstantReplace(what, emoji->text());
|
||||
}
|
||||
}
|
||||
|
||||
bool MessageField::hasSendText() const {
|
||||
|
@ -57,7 +57,7 @@ Replace Replaces[] = {
|
||||
{ { 0xD83DDE22U }, ":'(" },
|
||||
{ { 0xD83DDE2DU }, ":_(" },
|
||||
{ { 0xD83DDE29U }, ":((" },
|
||||
{ { 0xD83DDE28U }, ":o" },
|
||||
// { { 0xD83DDE28U }, ":o" }, // Conflicts with typing :ok...
|
||||
{ { 0xD83DDE10U }, ":|" },
|
||||
{ { 0xD83DDE0CU }, "3-)" },
|
||||
{ { 0xD83DDE20U }, ">(" },
|
||||
|
@ -335,6 +335,10 @@ EmojiPtr FindReplace(const QChar *start, const QChar *end, int *outLength) {\n\
|
||||
return index ? &Items[index - 1] : nullptr;\n\
|
||||
}\n\
|
||||
\n\
|
||||
const std::vector<std::pair<QString, int>> GetReplacementPairs() {\n\
|
||||
return ReplacementPairs;\n\
|
||||
}\n\
|
||||
\n\
|
||||
EmojiPtr Find(const QChar *start, const QChar *end, int *outLength) {\n\
|
||||
auto index = FindIndex(start, end, outLength);\n\
|
||||
return index ? &Items[index - 1] : nullptr;\n\
|
||||
@ -389,6 +393,7 @@ inline bool IsReplaceEdge(const QChar *ch) {\n\
|
||||
// return false;\n\
|
||||
}\n\
|
||||
\n\
|
||||
const std::vector<std::pair<QString, int>> GetReplacementPairs();\n\
|
||||
EmojiPtr FindReplace(const QChar *ch, const QChar *end, int *outLength = nullptr);\n\
|
||||
\n";
|
||||
header->popNamespace().stream() << "\
|
||||
@ -591,6 +596,14 @@ EmojiPack GetSection(Section section) {\n\
|
||||
bool Generator::writeFindReplace() {
|
||||
source_->stream() << "\
|
||||
\n\
|
||||
const std::vector<std::pair<QString, int>> ReplacementPairs = {\n";
|
||||
for (const auto &[what, index] : data_.replaces) {
|
||||
source_->stream() << "\
|
||||
{ qsl(\"" << what << "\"), " << index << " },\n";
|
||||
}
|
||||
source_->stream() << "\
|
||||
};\n\
|
||||
\n\
|
||||
int FindReplaceIndex(const QChar *start, const QChar *end, int *outLength) {\n\
|
||||
auto ch = start;\n\
|
||||
\n";
|
||||
@ -783,6 +796,7 @@ struct Replacement {\n\
|
||||
constexpr auto kReplacementMaxLength = " << maxLength << ";\n\
|
||||
\n\
|
||||
void InitReplacements();\n\
|
||||
const std::vector<Replacement> &GetAllReplacements();\n\
|
||||
const std::vector<const Replacement*> *GetReplacements(utf16char first);\n\
|
||||
utf16string GetReplacementEmoji(utf16string replacement);\n\
|
||||
\n";
|
||||
@ -923,6 +937,10 @@ const std::vector<const Replacement*> *GetReplacements(utf16char first) {\n\
|
||||
return (it == ReplacementsMap.cend()) ? nullptr : &it->second;\n\
|
||||
}\n\
|
||||
\n\
|
||||
const std::vector<Replacement> &GetAllReplacements() {\n\
|
||||
return Replacements;\n\
|
||||
}\n\
|
||||
\n\
|
||||
utf16string GetReplacementEmoji(utf16string replacement) {\n\
|
||||
auto code = internal::countChecksum(replacement.data(), replacement.size() * sizeof(utf16char));\n\
|
||||
auto it = ReplacementsHash.find(code);\n\
|
||||
|
@ -2153,47 +2153,6 @@ void MovePartAndGoForward(TextWithEntities &result, int &to, int &from, int coun
|
||||
from += count;
|
||||
}
|
||||
|
||||
void ReplaceStringWithChar(const QLatin1String &from, QChar to, TextWithEntities &result, bool checkSpace = false) {
|
||||
Expects(from.size() > 1);
|
||||
auto len = from.size(), s = result.text.size(), offset = 0, length = 0;
|
||||
auto i = result.entities.begin(), e = result.entities.end();
|
||||
for (auto start = result.text.data(); offset < s;) {
|
||||
auto nextOffset = result.text.indexOf(from, offset);
|
||||
if (nextOffset < 0) {
|
||||
MovePartAndGoForward(result, length, offset, s - offset);
|
||||
break;
|
||||
}
|
||||
|
||||
if (checkSpace) {
|
||||
bool spaceBefore = (nextOffset > 0) && (start + nextOffset - 1)->isSpace();
|
||||
bool spaceAfter = (nextOffset + len < s) && (start + nextOffset + len)->isSpace();
|
||||
if (!spaceBefore && !spaceAfter) {
|
||||
MovePartAndGoForward(result, length, offset, nextOffset - offset + len + 1);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
auto skip = false;
|
||||
for (; i != e; ++i) { // find and check next finishing entity
|
||||
if (i->offset() + i->length() > nextOffset) {
|
||||
skip = (i->offset() < nextOffset + len);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (skip) {
|
||||
MovePartAndGoForward(result, length, offset, nextOffset - offset + len);
|
||||
continue;
|
||||
}
|
||||
|
||||
MovePartAndGoForward(result, length, offset, nextOffset - offset);
|
||||
|
||||
*(start + length) = to;
|
||||
++length;
|
||||
offset += len;
|
||||
}
|
||||
if (length < s) result.text.resize(length);
|
||||
}
|
||||
|
||||
void PrepareForSending(TextWithEntities &result, int32 flags) {
|
||||
ApplyServerCleaning(result);
|
||||
|
||||
@ -2201,14 +2160,6 @@ void PrepareForSending(TextWithEntities &result, int32 flags) {
|
||||
ParseEntities(result, flags);
|
||||
}
|
||||
|
||||
ReplaceStringWithChar(qstr("--"), QChar(8212), result, true);
|
||||
ReplaceStringWithChar(qstr("<<"), QChar(171), result);
|
||||
ReplaceStringWithChar(qstr(">>"), QChar(187), result);
|
||||
|
||||
if (cReplaceEmojis()) {
|
||||
Ui::Emoji::ReplaceInText(result);
|
||||
}
|
||||
|
||||
Trim(result);
|
||||
}
|
||||
|
||||
|
@ -19,6 +19,13 @@ namespace Ui {
|
||||
namespace {
|
||||
|
||||
constexpr auto kMaxUsernameLength = 32;
|
||||
constexpr auto kInstantReplaceRandomId = QTextFormat::UserProperty;
|
||||
constexpr auto kInstantReplaceWhatId = QTextFormat::UserProperty + 1;
|
||||
constexpr auto kInstantReplaceWithId = QTextFormat::UserProperty + 2;
|
||||
const auto kObjectReplacementCh = QChar(QChar::ObjectReplacementCharacter);
|
||||
const auto kObjectReplacement = QString::fromRawData(
|
||||
&kObjectReplacementCh,
|
||||
1);
|
||||
|
||||
template <typename InputClass>
|
||||
class InputStyle : public QCommonStyle {
|
||||
@ -61,6 +68,28 @@ private:
|
||||
template <typename InputClass>
|
||||
InputStyle<InputClass> *InputStyle<InputClass>::_instance = nullptr;
|
||||
|
||||
template <typename Iterator>
|
||||
QString AccumulateText(Iterator begin, Iterator end) {
|
||||
auto result = QString();
|
||||
result.reserve(end - begin);
|
||||
for (auto i = end; i != begin;) {
|
||||
result.push_back(*--i);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
QTextImageFormat PrepareEmojiFormat(EmojiPtr emoji, const style::font &f) {
|
||||
const auto factor = cIntRetinaFactor();
|
||||
const auto width = Ui::Emoji::Size() + st::emojiPadding * factor * 2;
|
||||
const auto height = f->height * factor;
|
||||
auto result = QTextImageFormat();
|
||||
result.setWidth(width / factor);
|
||||
result.setHeight(height / factor);
|
||||
result.setName(emoji->toUrl());
|
||||
result.setVerticalAlignment(QTextCharFormat::AlignBaseline);
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
QByteArray FlatTextarea::serializeTagsList(const TagList &tags) {
|
||||
@ -122,6 +151,7 @@ FlatTextarea::FlatTextarea(QWidget *parent, const style::FlatTextarea &st, base:
|
||||
, _placeholderVisible(!v.length())
|
||||
, _lastTextWithTags { v, tags }
|
||||
, _st(st) {
|
||||
_defaultCharFormat = textCursor().charFormat();
|
||||
|
||||
setCursor(style::cur_text);
|
||||
setAcceptRichText(false);
|
||||
@ -170,6 +200,17 @@ FlatTextarea::FlatTextarea(QWidget *parent, const style::FlatTextarea &st, base:
|
||||
}
|
||||
}
|
||||
|
||||
void FlatTextarea::addInstantReplace(
|
||||
const QString &what,
|
||||
const QString &with) {
|
||||
auto node = &_reverseInstantReplaces;
|
||||
for (const auto ch : base::reversed(what)) {
|
||||
node = &node->tail.emplace(ch, InstantReplaceNode()).first->second;
|
||||
}
|
||||
node->text = with;
|
||||
accumulate_max(_instantReplaceMaxLength, int(what.size()));
|
||||
}
|
||||
|
||||
void FlatTextarea::updatePalette() {
|
||||
auto p = palette();
|
||||
p.setColor(QPalette::Text, _st.textColor->c);
|
||||
@ -472,7 +513,7 @@ QString FlatTextarea::getMentionHashtagBotCommandPart(bool &start) const {
|
||||
int32 p = fr.position(), e = (p + fr.length());
|
||||
if (p >= pos || e < pos) continue;
|
||||
|
||||
QTextCharFormat f = fr.charFormat();
|
||||
const auto f = fr.charFormat();
|
||||
if (f.isImageFormat()) continue;
|
||||
|
||||
bool mentionInCommand = false;
|
||||
@ -559,11 +600,7 @@ void FlatTextarea::insertTag(const QString &text, QString tagId) {
|
||||
break;
|
||||
}
|
||||
if (tagId.isEmpty()) {
|
||||
QTextCharFormat format = cursor.charFormat();
|
||||
format.setAnchor(false);
|
||||
format.setAnchorName(QString());
|
||||
format.clearForeground();
|
||||
cursor.insertText(text + ' ', format);
|
||||
cursor.insertText(text + ' ', _defaultCharFormat);
|
||||
} else {
|
||||
_insertedTags.clear();
|
||||
_insertedTags.push_back({ 0, text.size(), tagId });
|
||||
@ -597,15 +634,18 @@ void FlatTextarea::getSingleEmojiFragment(QString &text, QTextFragment &fragment
|
||||
continue;
|
||||
}
|
||||
|
||||
QTextCharFormat f = fr.charFormat();
|
||||
QString t(fr.text());
|
||||
const auto f = fr.charFormat();
|
||||
auto t = fr.text();
|
||||
if (p < start) {
|
||||
t = t.mid(start - p, end - start);
|
||||
} else if (e > end) {
|
||||
t = t.mid(0, end - p);
|
||||
}
|
||||
if (f.isImageFormat() && !t.isEmpty() && t.at(0).unicode() == QChar::ObjectReplacementCharacter) {
|
||||
auto imageName = static_cast<QTextImageFormat*>(&f)->name();
|
||||
if (f.isImageFormat()
|
||||
&& !t.isEmpty()
|
||||
&& t[0] == kObjectReplacementCh) {
|
||||
const auto imageName = static_cast<const QTextImageFormat*>(
|
||||
&f)->name();
|
||||
if (Ui::Emoji::FromUrl(imageName)) {
|
||||
fragment = fr;
|
||||
text = t;
|
||||
@ -743,9 +783,9 @@ QString FlatTextarea::getTextPart(int start, int end, TagList *outTagsList, bool
|
||||
tagAccumulator.feed(fragment.charFormat().anchorName(), result.size());
|
||||
}
|
||||
|
||||
QTextCharFormat f = fragment.charFormat();
|
||||
const auto f = fragment.charFormat();
|
||||
QString emojiText;
|
||||
QString t(fragment.text());
|
||||
auto t = fragment.text();
|
||||
if (!full) {
|
||||
if (p < start) {
|
||||
t = t.mid(start - p, end - start);
|
||||
@ -767,8 +807,8 @@ QString FlatTextarea::getTextPart(int start, int end, TagList *outTagsList, bool
|
||||
} break;
|
||||
case QChar::ObjectReplacementCharacter: {
|
||||
if (emojiText.isEmpty() && f.isImageFormat()) {
|
||||
auto imageName = static_cast<QTextImageFormat*>(&f)->name();
|
||||
if (auto emoji = Ui::Emoji::FromUrl(imageName)) {
|
||||
const auto imageName = static_cast<const QTextImageFormat*>(&f)->name();
|
||||
if (const auto emoji = Ui::Emoji::FromUrl(imageName)) {
|
||||
emojiText = emoji->text();
|
||||
}
|
||||
}
|
||||
@ -929,20 +969,13 @@ void FlatTextarea::insertFromMimeData(const QMimeData *source) {
|
||||
}
|
||||
|
||||
void FlatTextarea::insertEmoji(EmojiPtr emoji, QTextCursor c) {
|
||||
QTextImageFormat imageFormat;
|
||||
auto ew = Ui::Emoji::Size() + st::emojiPadding * cIntRetinaFactor() * 2;
|
||||
auto eh = _st.font->height * cIntRetinaFactor();
|
||||
imageFormat.setWidth(ew / cIntRetinaFactor());
|
||||
imageFormat.setHeight(eh / cIntRetinaFactor());
|
||||
imageFormat.setName(emoji->toUrl());
|
||||
imageFormat.setVerticalAlignment(QTextCharFormat::AlignBaseline);
|
||||
auto format = PrepareEmojiFormat(emoji, _st.font);
|
||||
if (c.charFormat().isAnchor()) {
|
||||
imageFormat.setAnchor(true);
|
||||
imageFormat.setAnchorName(c.charFormat().anchorName());
|
||||
imageFormat.setForeground(st::defaultTextPalette.linkFg);
|
||||
format.setAnchor(true);
|
||||
format.setAnchorName(c.charFormat().anchorName());
|
||||
format.setForeground(st::defaultTextPalette.linkFg);
|
||||
}
|
||||
static QString objectReplacement(QChar::ObjectReplacementCharacter);
|
||||
c.insertText(objectReplacement, imageFormat);
|
||||
c.insertText(kObjectReplacement, format);
|
||||
}
|
||||
|
||||
QVariant FlatTextarea::loadResource(int type, const QUrl &name) {
|
||||
@ -1054,6 +1087,7 @@ struct FormattingAction {
|
||||
InsertEmoji,
|
||||
TildeFont,
|
||||
RemoveTag,
|
||||
ClearInstantReplace,
|
||||
};
|
||||
Type type = Type::Invalid;
|
||||
EmojiPtr emoji = nullptr;
|
||||
@ -1104,18 +1138,33 @@ void FlatTextarea::processFormatting(int insertPosition, int insertEnd) {
|
||||
break;
|
||||
}
|
||||
|
||||
auto charFormat = fragment.charFormat();
|
||||
auto format = fragment.charFormat();
|
||||
if (tildeFormatting) {
|
||||
isTildeFragment = (charFormat.fontFamily() == tildeFixedFont);
|
||||
isTildeFragment = (format.fontFamily() == tildeFixedFont);
|
||||
}
|
||||
|
||||
auto fragmentText = fragment.text();
|
||||
auto *textStart = fragmentText.constData();
|
||||
auto *textEnd = textStart + fragmentText.size();
|
||||
|
||||
const auto with = format.property(kInstantReplaceWithId);
|
||||
if (with.isValid()) {
|
||||
const auto string = with.toString();
|
||||
if (fragmentText != string) {
|
||||
action.type = ActionType::ClearInstantReplace;
|
||||
action.intervalStart = fragmentPosition
|
||||
+ (fragmentText.startsWith(string)
|
||||
? string.size()
|
||||
: 0);
|
||||
action.intervalEnd = fragmentPosition
|
||||
+ fragmentText.size();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!startTagFound) {
|
||||
startTagFound = true;
|
||||
auto tagName = charFormat.anchorName();
|
||||
auto tagName = format.anchorName();
|
||||
if (!tagName.isEmpty()) {
|
||||
breakTagOnNotLetter = wasInsertTillTheEndOfTag(block, fragmentIt, insertEnd);
|
||||
}
|
||||
@ -1193,6 +1242,8 @@ void FlatTextarea::processFormatting(int insertPosition, int insertEnd) {
|
||||
format.setFontFamily(action.isTilde ? tildeFixedFont : tildeRegularFont);
|
||||
c.mergeCharFormat(format);
|
||||
insertPosition = action.intervalEnd;
|
||||
} else if (action.type == ActionType::ClearInstantReplace) {
|
||||
c.setCharFormat(_defaultCharFormat);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
@ -1372,6 +1423,10 @@ void FlatTextarea::keyPressEvent(QKeyEvent *e) {
|
||||
start.movePosition(QTextCursor::StartOfLine);
|
||||
tc.setPosition(start.position(), QTextCursor::KeepAnchor);
|
||||
tc.removeSelectedText();
|
||||
} else if (e->key() == Qt::Key_Backspace
|
||||
&& e->modifiers() == 0
|
||||
&& revertInstantReplace()) {
|
||||
e->accept();
|
||||
} else if (enter && enterSubmit) {
|
||||
emit submitted(ctrl && shift);
|
||||
} else if (e->key() == Qt::Key_Escape) {
|
||||
@ -1394,39 +1449,174 @@ void FlatTextarea::keyPressEvent(QKeyEvent *e) {
|
||||
}
|
||||
#endif // Q_OS_MAC
|
||||
} else {
|
||||
QTextCursor tc(textCursor());
|
||||
const auto text = e->text();
|
||||
const auto key = e->key();
|
||||
auto cursor = textCursor();
|
||||
if (enter && ctrl) {
|
||||
e->setModifiers(e->modifiers() & ~Qt::ControlModifier);
|
||||
}
|
||||
bool spaceOrReturn = false;
|
||||
QString t(e->text());
|
||||
if (!t.isEmpty() && t.size() < 3) {
|
||||
if (t.at(0) == '\n' || t.at(0) == '\r' || t.at(0).isSpace() || t.at(0) == QChar::LineSeparator) {
|
||||
if (!text.isEmpty() && text.size() < 3) {
|
||||
const auto ch = text[0];
|
||||
if (ch == '\n'
|
||||
|| ch == '\r'
|
||||
|| ch.isSpace()
|
||||
|| ch == QChar::LineSeparator) {
|
||||
spaceOrReturn = true;
|
||||
}
|
||||
}
|
||||
QTextEdit::keyPressEvent(e);
|
||||
if (tc == textCursor()) {
|
||||
if (cursor == textCursor()) {
|
||||
bool check = false;
|
||||
if (e->key() == Qt::Key_PageUp || e->key() == Qt::Key_Up) {
|
||||
tc.movePosition(QTextCursor::Start, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
|
||||
if (key == Qt::Key_PageUp || key == Qt::Key_Up) {
|
||||
cursor.movePosition(
|
||||
QTextCursor::Start,
|
||||
(e->modifiers().testFlag(Qt::ShiftModifier)
|
||||
? QTextCursor::KeepAnchor
|
||||
: QTextCursor::MoveAnchor));
|
||||
check = true;
|
||||
} else if (e->key() == Qt::Key_PageDown || e->key() == Qt::Key_Down) {
|
||||
tc.movePosition(QTextCursor::End, e->modifiers().testFlag(Qt::ShiftModifier) ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
|
||||
} else if (key == Qt::Key_PageDown || key == Qt::Key_Down) {
|
||||
cursor.movePosition(
|
||||
QTextCursor::End,
|
||||
(e->modifiers().testFlag(Qt::ShiftModifier)
|
||||
? QTextCursor::KeepAnchor
|
||||
: QTextCursor::MoveAnchor));
|
||||
check = true;
|
||||
}
|
||||
if (check) {
|
||||
if (tc == textCursor()) {
|
||||
if (cursor == textCursor()) {
|
||||
e->ignore();
|
||||
} else {
|
||||
setTextCursor(tc);
|
||||
setTextCursor(cursor);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (spaceOrReturn) emit spacedReturnedPasted();
|
||||
processInstantReplaces(text);
|
||||
if (spaceOrReturn) {
|
||||
emit spacedReturnedPasted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void FlatTextarea::processInstantReplaces(const QString &text) {
|
||||
if (text.size() != 1 || !_instantReplaceMaxLength) {
|
||||
return;
|
||||
}
|
||||
const auto it = _reverseInstantReplaces.tail.find(text[0]);
|
||||
if (it == end(_reverseInstantReplaces.tail)) {
|
||||
return;
|
||||
}
|
||||
const auto position = textCursor().position();
|
||||
auto tags = QVector<TextWithTags::Tag>();
|
||||
const auto typed = getTextPart(
|
||||
std::max(position - _instantReplaceMaxLength, 0),
|
||||
position - 1,
|
||||
&tags);
|
||||
auto node = &it->second;
|
||||
auto i = typed.size();
|
||||
do {
|
||||
if (!node->text.isEmpty()) {
|
||||
applyInstantReplace(typed.mid(i) + text, node->text);
|
||||
return;
|
||||
} else if (!i) {
|
||||
return;
|
||||
}
|
||||
const auto it = node->tail.find(typed[--i]);
|
||||
if (it == end(node->tail)) {
|
||||
return;
|
||||
}
|
||||
node = &it->second;
|
||||
} while (true);
|
||||
}
|
||||
|
||||
void FlatTextarea::applyInstantReplace(
|
||||
const QString &what,
|
||||
const QString &with) {
|
||||
const auto length = int(what.size());
|
||||
const auto cursor = textCursor();
|
||||
const auto position = cursor.position();
|
||||
if (cursor.anchor() != position) {
|
||||
return;
|
||||
} else if (position < length) {
|
||||
return;
|
||||
}
|
||||
auto tags = QVector<TextWithTags::Tag>();
|
||||
const auto original = getTextPart(position - length, position, &tags);
|
||||
if (what.compare(original, Qt::CaseInsensitive) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto format = [&]() -> QTextCharFormat {
|
||||
auto emojiLength = 0;
|
||||
const auto emoji = Ui::Emoji::Find(with, &emojiLength);
|
||||
if (!emoji || with.size() != emojiLength) {
|
||||
return cursor.charFormat();
|
||||
}
|
||||
const auto use = [&] {
|
||||
if (!emoji->hasVariants()) {
|
||||
return emoji;
|
||||
}
|
||||
const auto nonColored = emoji->nonColoredId();
|
||||
const auto it = cEmojiVariants().constFind(nonColored);
|
||||
return (it != cEmojiVariants().cend())
|
||||
? emoji->variant(it.value())
|
||||
: emoji;
|
||||
}();
|
||||
return PrepareEmojiFormat(use, _st.font);
|
||||
}();
|
||||
const auto replacement = format.isImageFormat()
|
||||
? kObjectReplacement
|
||||
: with;
|
||||
format.setProperty(kInstantReplaceWhatId, original);
|
||||
format.setProperty(kInstantReplaceWithId, replacement);
|
||||
format.setProperty(kInstantReplaceRandomId, rand_value<uint32>());
|
||||
auto replaceCursor = cursor;
|
||||
replaceCursor.setPosition(position - length);
|
||||
replaceCursor.setPosition(position, QTextCursor::KeepAnchor);
|
||||
replaceCursor.insertText(
|
||||
replacement,
|
||||
format);
|
||||
}
|
||||
|
||||
bool FlatTextarea::revertInstantReplace() {
|
||||
const auto cursor = textCursor();
|
||||
const auto position = cursor.position();
|
||||
if (position <= 0 || cursor.anchor() != position) {
|
||||
return false;
|
||||
}
|
||||
const auto inside = position - 1;
|
||||
const auto block = document()->findBlock(inside);
|
||||
if (block == document()->end()) {
|
||||
return false;
|
||||
}
|
||||
for (auto i = block.begin(); !i.atEnd(); ++i) {
|
||||
const auto fragment = i.fragment();
|
||||
const auto fragmentStart = fragment.position();
|
||||
const auto fragmentEnd = fragmentStart + fragment.length();
|
||||
if (fragmentEnd <= inside) {
|
||||
continue;
|
||||
} else if (fragmentStart > inside || fragmentEnd != position) {
|
||||
return false;
|
||||
}
|
||||
const auto format = fragment.charFormat();
|
||||
const auto with = format.property(kInstantReplaceWithId);
|
||||
if (!with.isValid()) {
|
||||
return false;
|
||||
}
|
||||
const auto string = with.toString();
|
||||
if (fragment.text() != string) {
|
||||
return false;
|
||||
}
|
||||
auto replaceCursor = cursor;
|
||||
replaceCursor.setPosition(fragmentStart);
|
||||
replaceCursor.setPosition(fragmentEnd, QTextCursor::KeepAnchor);
|
||||
const auto what = format.property(kInstantReplaceWhatId).toString();
|
||||
replaceCursor.insertText(what, _defaultCharFormat);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void FlatTextarea::resizeEvent(QResizeEvent *e) {
|
||||
refreshPlaceholder();
|
||||
QTextEdit::resizeEvent(e);
|
||||
@ -2135,16 +2325,8 @@ bool InputArea::isRedoAvailable() const {
|
||||
}
|
||||
|
||||
void InputArea::insertEmoji(EmojiPtr emoji, QTextCursor c) {
|
||||
QTextImageFormat imageFormat;
|
||||
auto ew = Ui::Emoji::Size() + st::emojiPadding * cIntRetinaFactor() * 2;
|
||||
auto eh = _st.font->height * cIntRetinaFactor();
|
||||
imageFormat.setWidth(ew / cIntRetinaFactor());
|
||||
imageFormat.setHeight(eh / cIntRetinaFactor());
|
||||
imageFormat.setName(emoji->toUrl());
|
||||
imageFormat.setVerticalAlignment(QTextCharFormat::AlignBaseline);
|
||||
|
||||
static QString objectReplacement(QChar::ObjectReplacementCharacter);
|
||||
c.insertText(objectReplacement, imageFormat);
|
||||
const auto format = PrepareEmojiFormat(emoji, _st.font);
|
||||
c.insertText(kObjectReplacement, format);
|
||||
}
|
||||
|
||||
QVariant InputArea::Inner::loadResource(int type, const QUrl &name) {
|
||||
@ -2905,15 +3087,8 @@ bool InputField::isRedoAvailable() const {
|
||||
}
|
||||
|
||||
void InputField::insertEmoji(EmojiPtr emoji, QTextCursor c) {
|
||||
QTextImageFormat imageFormat;
|
||||
auto ew = Ui::Emoji::Size() + st::emojiPadding * cIntRetinaFactor() * 2, eh = _st.font->height * cIntRetinaFactor();
|
||||
imageFormat.setWidth(ew / cIntRetinaFactor());
|
||||
imageFormat.setHeight(eh / cIntRetinaFactor());
|
||||
imageFormat.setName(emoji->toUrl());
|
||||
imageFormat.setVerticalAlignment(QTextCharFormat::AlignBaseline);
|
||||
|
||||
static QString objectReplacement(QChar::ObjectReplacementCharacter);
|
||||
c.insertText(objectReplacement, imageFormat);
|
||||
const auto format = PrepareEmojiFormat(emoji, _st.font);
|
||||
c.insertText(kObjectReplacement, format);
|
||||
}
|
||||
|
||||
QVariant InputField::Inner::loadResource(int type, const QUrl &name) {
|
||||
|
@ -32,6 +32,8 @@ public:
|
||||
void setMinHeight(int minHeight);
|
||||
void setMaxHeight(int maxHeight);
|
||||
|
||||
void addInstantReplace(const QString &what, const QString &with);
|
||||
|
||||
void setPlaceholder(base::lambda<QString()> placeholderFactory, int afterSymbols = 0);
|
||||
void updatePlaceholder();
|
||||
void finishPlaceholder();
|
||||
@ -142,6 +144,10 @@ protected:
|
||||
void checkContentHeight();
|
||||
|
||||
private:
|
||||
struct InstantReplaceNode {
|
||||
QString text;
|
||||
std::map<QChar, InstantReplaceNode> tail;
|
||||
};
|
||||
void updatePalette();
|
||||
void refreshPlaceholder();
|
||||
|
||||
@ -160,6 +166,10 @@ private:
|
||||
// Rule 4 applies only if we inserted chars not in the middle of a tag (but at the end).
|
||||
void processFormatting(int changedPosition, int changedEnd);
|
||||
|
||||
void processInstantReplaces(const QString &text);
|
||||
void applyInstantReplace(const QString &what, const QString &with);
|
||||
bool revertInstantReplace();
|
||||
|
||||
bool heightAutoupdated();
|
||||
|
||||
int placeholderSkipWidth() const;
|
||||
@ -215,6 +225,12 @@ private:
|
||||
friend bool operator!=(const LinkRange &a, const LinkRange &b);
|
||||
using LinkRanges = QVector<LinkRange>;
|
||||
LinkRanges _links;
|
||||
|
||||
QTextCharFormat _defaultCharFormat;
|
||||
|
||||
int _instantReplaceMaxLength = 0;
|
||||
InstantReplaceNode _reverseInstantReplaces;
|
||||
|
||||
};
|
||||
|
||||
inline bool operator==(const FlatTextarea::LinkRange &a, const FlatTextarea::LinkRange &b) {
|
||||
|
Loading…
Reference in New Issue
Block a user