mirror of
https://github.com/telegramdesktop/tdesktop
synced 2025-02-05 14:47:39 +00:00
Add "Suggest emoji replacements" checkbox.
Also emoji suggestions insert an instant emoji replacement.
This commit is contained in:
parent
4b763a76df
commit
168a7ce2e5
@ -296,6 +296,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||
|
||||
"lng_settings_section_chat_settings" = "Chat Settings";
|
||||
"lng_settings_replace_emojis" = "Replace emoji";
|
||||
"lng_settings_suggest_emoji" = "Suggest emoji replacements";
|
||||
"lng_settings_suggest_by_emoji" = "Suggest popular stickers by emoji";
|
||||
"lng_settings_view_emojis" = "View list";
|
||||
"lng_settings_send_enter" = "Send by Enter";
|
||||
|
@ -346,12 +346,15 @@ void SuggestionsWidget::leaveEventHook(QEvent *e) {
|
||||
return TWidget::leaveEventHook(e);
|
||||
}
|
||||
|
||||
SuggestionsController::SuggestionsController(QWidget *parent, not_null<QTextEdit*> field) : QObject(nullptr)
|
||||
SuggestionsController::SuggestionsController(QWidget *parent, not_null<QTextEdit*> field)
|
||||
: QObject(nullptr)
|
||||
, _field(field)
|
||||
, _container(parent, st::emojiSuggestionsDropdown)
|
||||
, _suggestions(_container->setOwnedWidget(object_ptr<Ui::Emoji::SuggestionsWidget>(parent, st::emojiSuggestionsMenu))) {
|
||||
_container->setAutoHiding(false);
|
||||
|
||||
setReplaceCallback(nullptr);
|
||||
|
||||
_field->installEventFilter(this);
|
||||
connect(_field, &QTextEdit::textChanged, this, [this] { handleTextChange(); });
|
||||
connect(_field, &QTextEdit::cursorPositionChanged, this, [this] { handleCursorPositionChange(); });
|
||||
@ -363,6 +366,23 @@ SuggestionsController::SuggestionsController(QWidget *parent, not_null<QTextEdit
|
||||
handleTextChange();
|
||||
}
|
||||
|
||||
void SuggestionsController::setReplaceCallback(
|
||||
base::lambda<void(
|
||||
int from,
|
||||
int till,
|
||||
const QString &replacement)> callback) {
|
||||
if (callback) {
|
||||
_replaceCallback = std::move(callback);
|
||||
} else {
|
||||
_replaceCallback = [=](int from, int till, const QString &replacement) {
|
||||
auto cursor = _field->textCursor();
|
||||
cursor.setPosition(from);
|
||||
cursor.setPosition(till, QTextCursor::KeepAnchor);
|
||||
cursor.insertText(replacement);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void SuggestionsController::handleTextChange() {
|
||||
_ignoreCursorPositionChange = true;
|
||||
InvokeQueued(this, [this] { _ignoreCursorPositionChange = false; });
|
||||
@ -374,7 +394,7 @@ void SuggestionsController::handleTextChange() {
|
||||
}
|
||||
|
||||
QString SuggestionsController::getEmojiQuery() {
|
||||
if (!cReplaceEmojis()) {
|
||||
if (!Global::SuggestEmoji()) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
@ -471,23 +491,14 @@ QString SuggestionsController::getEmojiQuery() {
|
||||
}
|
||||
|
||||
void SuggestionsController::replaceCurrent(const QString &replacement) {
|
||||
auto cursor = _field->textCursor();
|
||||
auto suggestion = getEmojiQuery();
|
||||
if (suggestion.isEmpty()) {
|
||||
_suggestions->showWithQuery(QString());
|
||||
} else {
|
||||
cursor.setPosition(cursor.position() - suggestion.size(), QTextCursor::KeepAnchor);
|
||||
cursor.insertText(replacement);
|
||||
}
|
||||
|
||||
if (auto emoji = Find(replacement)) {
|
||||
if (emoji->hasVariants()) {
|
||||
auto it = cEmojiVariants().constFind(emoji->nonColoredId());
|
||||
if (it != cEmojiVariants().cend()) {
|
||||
emoji = emoji->variant(it.value());
|
||||
}
|
||||
}
|
||||
AddRecent(emoji);
|
||||
const auto cursor = _field->textCursor();
|
||||
const auto position = cursor.position();
|
||||
const auto from = position - suggestion.size();
|
||||
_replaceCallback(from, position, replacement);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -69,6 +69,10 @@ public:
|
||||
SuggestionsController(QWidget *parent, not_null<QTextEdit*> field);
|
||||
|
||||
void raise();
|
||||
void setReplaceCallback(base::lambda<void(
|
||||
int from,
|
||||
int till,
|
||||
const QString &replacement)> callback);
|
||||
|
||||
protected:
|
||||
bool eventFilter(QObject *object, QEvent *event) override;
|
||||
@ -88,6 +92,10 @@ private:
|
||||
bool _ignoreCursorPositionChange = false;
|
||||
bool _textChangeAfterKeyPress = false;
|
||||
QPointer<QTextEdit> _field;
|
||||
base::lambda<void(
|
||||
int from,
|
||||
int till,
|
||||
const QString &replacement)> _replaceCallback;
|
||||
object_ptr<InnerDropdown> _container;
|
||||
QPointer<SuggestionsWidget> _suggestions;
|
||||
|
||||
|
@ -133,6 +133,10 @@ MessageField::MessageField(QWidget *parent, not_null<Window::Controller*> contro
|
||||
Assert(emoji != nullptr);
|
||||
addInstantReplace(what, emoji->text());
|
||||
}
|
||||
enableInstantReplaces(Global::ReplaceEmoji());
|
||||
subscribe(Global::RefReplaceEmojiChanged(), [=] {
|
||||
enableInstantReplaces(Global::ReplaceEmoji());
|
||||
});
|
||||
}
|
||||
|
||||
bool MessageField::hasSendText() const {
|
||||
|
@ -553,7 +553,10 @@ struct Data {
|
||||
QByteArray DownloadPathBookmark;
|
||||
base::Observable<void> DownloadPathChanged;
|
||||
|
||||
bool ReplaceEmoji = true;
|
||||
bool SuggestEmoji = true;
|
||||
bool SuggestStickersByEmoji = true;
|
||||
base::Observable<void> ReplaceEmojiChanged;
|
||||
bool SoundNotify = true;
|
||||
bool DesktopNotify = true;
|
||||
bool RestoreSoundNotifyFromTray = false;
|
||||
@ -678,7 +681,10 @@ DefineVar(Global, QString, DownloadPath);
|
||||
DefineVar(Global, QByteArray, DownloadPathBookmark);
|
||||
DefineRefVar(Global, base::Observable<void>, DownloadPathChanged);
|
||||
|
||||
DefineVar(Global, bool, ReplaceEmoji);
|
||||
DefineVar(Global, bool, SuggestEmoji);
|
||||
DefineVar(Global, bool, SuggestStickersByEmoji);
|
||||
DefineRefVar(Global, base::Observable<void>, ReplaceEmojiChanged);
|
||||
DefineVar(Global, bool, SoundNotify);
|
||||
DefineVar(Global, bool, DesktopNotify);
|
||||
DefineVar(Global, bool, RestoreSoundNotifyFromTray);
|
||||
|
@ -361,7 +361,10 @@ DeclareVar(QString, DownloadPath);
|
||||
DeclareVar(QByteArray, DownloadPathBookmark);
|
||||
DeclareRefVar(base::Observable<void>, DownloadPathChanged);
|
||||
|
||||
DeclareVar(bool, ReplaceEmoji);
|
||||
DeclareVar(bool, SuggestEmoji);
|
||||
DeclareVar(bool, SuggestStickersByEmoji);
|
||||
DeclareRefVar(base::Observable<void>, ReplaceEmojiChanged);
|
||||
DeclareVar(bool, SoundNotify);
|
||||
DeclareVar(bool, DesktopNotify);
|
||||
DeclareVar(bool, RestoreSoundNotifyFromTray);
|
||||
|
@ -512,6 +512,12 @@ HistoryWidget::HistoryWidget(
|
||||
data->text());
|
||||
});
|
||||
_emojiSuggestions.create(this, _field.data());
|
||||
_emojiSuggestions->setReplaceCallback([=](
|
||||
int from,
|
||||
int till,
|
||||
const QString &replacement) {
|
||||
_field->commmitInstantReplacement(from, till, replacement);
|
||||
});
|
||||
updateFieldSubmitSettings();
|
||||
|
||||
_field->hide();
|
||||
|
@ -40,7 +40,6 @@ bool gRestartingUpdate = false, gRestarting = false, gRestartingToSettings = fal
|
||||
int32 gLastUpdateCheck = 0;
|
||||
bool gNoStartUpdate = false;
|
||||
bool gStartToSettings = false;
|
||||
bool gReplaceEmojis = true;
|
||||
|
||||
bool gCtrlEnter = false;
|
||||
|
||||
|
@ -100,7 +100,6 @@ DeclareSetting(bool, WriteProtected);
|
||||
DeclareSetting(int32, LastUpdateCheck);
|
||||
DeclareSetting(bool, NoStartUpdate);
|
||||
DeclareSetting(bool, StartToSettings);
|
||||
DeclareSetting(bool, ReplaceEmojis);
|
||||
DeclareReadSetting(bool, ManyInstance);
|
||||
|
||||
DeclareSetting(QByteArray, LocalSalt);
|
||||
|
@ -140,7 +140,8 @@ void ChatSettingsWidget::createControls() {
|
||||
style::margins marginSub(0, 0, 0, st::settingsSubSkip);
|
||||
style::margins slidedPadding(0, marginSub.bottom() / 2, 0, marginSub.bottom() - (marginSub.bottom() / 2));
|
||||
|
||||
createChildRow(_replaceEmoji, marginSmall, lang(lng_settings_replace_emojis), [this](bool) { toggleReplaceEmoji(); }, cReplaceEmojis());
|
||||
createChildRow(_replaceEmoji, marginSmall, lang(lng_settings_replace_emojis), [this](bool) { toggleReplaceEmoji(); }, Global::ReplaceEmoji());
|
||||
createChildRow(_suggestEmoji, marginSmall, lang(lng_settings_suggest_emoji), [this](bool) { toggleSuggestEmoji(); }, Global::SuggestEmoji());
|
||||
createChildRow(_suggestByEmoji, marginSkip, lang(lng_settings_suggest_by_emoji), [this](bool) { toggleSuggestStickersByEmoji(); }, Global::SuggestStickersByEmoji());
|
||||
|
||||
#ifndef OS_WIN_STORE
|
||||
@ -170,7 +171,13 @@ void ChatSettingsWidget::createControls() {
|
||||
}
|
||||
|
||||
void ChatSettingsWidget::toggleReplaceEmoji() {
|
||||
cSetReplaceEmojis(_replaceEmoji->checked());
|
||||
Global::SetReplaceEmoji(_replaceEmoji->checked());
|
||||
Global::RefReplaceEmojiChanged().notify();
|
||||
Local::writeUserSettings();
|
||||
}
|
||||
|
||||
void ChatSettingsWidget::toggleSuggestEmoji() {
|
||||
Global::SetSuggestEmoji(_suggestEmoji->checked());
|
||||
Local::writeUserSettings();
|
||||
}
|
||||
|
||||
|
@ -94,9 +94,11 @@ private:
|
||||
void createControls();
|
||||
|
||||
void toggleReplaceEmoji();
|
||||
void toggleSuggestEmoji();
|
||||
void toggleSuggestStickersByEmoji();
|
||||
|
||||
Ui::Checkbox *_replaceEmoji = nullptr;
|
||||
Ui::Checkbox *_suggestEmoji = nullptr;
|
||||
Ui::Checkbox *_suggestByEmoji = nullptr;
|
||||
Ui::Checkbox *_dontAskDownloadPath = nullptr;
|
||||
|
||||
|
@ -522,7 +522,7 @@ enum {
|
||||
// 0x10 reserved
|
||||
dbiDefaultAttach = 0x11,
|
||||
dbiCatsAndDogs = 0x12,
|
||||
dbiReplaceEmojis = 0x13,
|
||||
dbiReplaceEmoji = 0x13,
|
||||
dbiAskDownloadPath = 0x14,
|
||||
dbiDownloadPathOld = 0x15,
|
||||
dbiScale = 0x16,
|
||||
@ -575,6 +575,7 @@ enum {
|
||||
dbiConnectionType = 0x4f,
|
||||
dbiStickersFavedLimit = 0x50,
|
||||
dbiSuggestStickersByEmoji = 0x51,
|
||||
dbiSuggestEmoji = 0x52,
|
||||
|
||||
dbiEncryptedWithSalt = 333,
|
||||
dbiEncrypted = 444,
|
||||
@ -1019,14 +1020,6 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
|
||||
Global::SetSoundNotify(v == 1);
|
||||
} break;
|
||||
|
||||
case dbiSuggestStickersByEmoji: {
|
||||
qint32 v;
|
||||
stream >> v;
|
||||
if (!_checkStreamStatus(stream)) return false;
|
||||
|
||||
Global::SetSuggestStickersByEmoji(v == 1);
|
||||
} break;
|
||||
|
||||
case dbiAutoDownload: {
|
||||
qint32 photo, audio, gif;
|
||||
stream >> photo >> audio >> gif;
|
||||
@ -1434,12 +1427,28 @@ bool _readSetting(quint32 blockId, QDataStream &stream, int version, ReadSetting
|
||||
Global::RefLocalPasscodeChanged().notify();
|
||||
} break;
|
||||
|
||||
case dbiReplaceEmojis: {
|
||||
case dbiReplaceEmoji: {
|
||||
qint32 v;
|
||||
stream >> v;
|
||||
if (!_checkStreamStatus(stream)) return false;
|
||||
|
||||
cSetReplaceEmojis(v == 1);
|
||||
Global::SetReplaceEmoji(v == 1);
|
||||
} break;
|
||||
|
||||
case dbiSuggestEmoji: {
|
||||
qint32 v;
|
||||
stream >> v;
|
||||
if (!_checkStreamStatus(stream)) return false;
|
||||
|
||||
Global::SetSuggestEmoji(v == 1);
|
||||
} break;
|
||||
|
||||
case dbiSuggestStickersByEmoji: {
|
||||
qint32 v;
|
||||
stream >> v;
|
||||
if (!_checkStreamStatus(stream)) return false;
|
||||
|
||||
Global::SetSuggestStickersByEmoji(v == 1);
|
||||
} break;
|
||||
|
||||
case dbiDefaultAttach: {
|
||||
@ -1852,7 +1861,7 @@ void _writeUserSettings() {
|
||||
? userDataInstance->serialize()
|
||||
: QByteArray();
|
||||
|
||||
uint32 size = 22 * (sizeof(quint32) + sizeof(qint32));
|
||||
uint32 size = 23 * (sizeof(quint32) + sizeof(qint32));
|
||||
size += sizeof(quint32) + Serialize::stringSize(Global::AskDownloadPath() ? QString() : Global::DownloadPath()) + Serialize::bytearraySize(Global::AskDownloadPath() ? QByteArray() : Global::DownloadPathBookmark());
|
||||
|
||||
size += sizeof(quint32) + sizeof(qint32);
|
||||
@ -1877,7 +1886,9 @@ void _writeUserSettings() {
|
||||
data.stream << quint32(dbiTileBackground) << qint32(Window::Theme::Background()->tileForSave() ? 1 : 0);
|
||||
data.stream << quint32(dbiAdaptiveForWide) << qint32(Global::AdaptiveForWide() ? 1 : 0);
|
||||
data.stream << quint32(dbiAutoLock) << qint32(Global::AutoLock());
|
||||
data.stream << quint32(dbiReplaceEmojis) << qint32(cReplaceEmojis() ? 1 : 0);
|
||||
data.stream << quint32(dbiReplaceEmoji) << qint32(Global::ReplaceEmoji() ? 1 : 0);
|
||||
data.stream << quint32(dbiSuggestEmoji) << qint32(Global::SuggestEmoji() ? 1 : 0);
|
||||
data.stream << quint32(dbiSuggestStickersByEmoji) << qint32(Global::SuggestStickersByEmoji() ? 1 : 0);
|
||||
data.stream << quint32(dbiSoundNotify) << qint32(Global::SoundNotify());
|
||||
data.stream << quint32(dbiIncludeMuted) << qint32(Global::IncludeMuted());
|
||||
data.stream << quint32(dbiDesktopNotify) << qint32(Global::DesktopNotify());
|
||||
@ -1895,7 +1906,6 @@ void _writeUserSettings() {
|
||||
data.stream << quint32(dbiModerateMode) << qint32(Global::ModerateModeEnabled() ? 1 : 0);
|
||||
data.stream << quint32(dbiAutoPlay) << qint32(cAutoPlayGif() ? 1 : 0);
|
||||
data.stream << quint32(dbiUseExternalVideoPlayer) << qint32(cUseExternalVideoPlayer());
|
||||
data.stream << quint32(dbiSuggestStickersByEmoji) << qint32(Global::SuggestStickersByEmoji() ? 1 : 0);
|
||||
if (!userData.isEmpty()) {
|
||||
data.stream << quint32(dbiAuthSessionSettings) << userData;
|
||||
}
|
||||
|
@ -211,6 +211,10 @@ void FlatTextarea::addInstantReplace(
|
||||
accumulate_max(_instantReplaceMaxLength, int(what.size()));
|
||||
}
|
||||
|
||||
void FlatTextarea::enableInstantReplaces(bool enabled) {
|
||||
_instantReplacesEnabled = enabled;
|
||||
}
|
||||
|
||||
void FlatTextarea::updatePalette() {
|
||||
auto p = palette();
|
||||
p.setColor(QPalette::Text, _st.textColor->c);
|
||||
@ -1499,7 +1503,9 @@ void FlatTextarea::keyPressEvent(QKeyEvent *e) {
|
||||
}
|
||||
|
||||
void FlatTextarea::processInstantReplaces(const QString &text) {
|
||||
if (text.size() != 1 || !_instantReplaceMaxLength) {
|
||||
if (text.size() != 1
|
||||
|| !_instantReplaceMaxLength
|
||||
|| !_instantReplacesEnabled) {
|
||||
return;
|
||||
}
|
||||
const auto it = _reverseInstantReplaces.tail.find(text[0]);
|
||||
@ -1540,9 +1546,18 @@ void FlatTextarea::applyInstantReplace(
|
||||
} else if (position < length) {
|
||||
return;
|
||||
}
|
||||
commmitInstantReplacement(position - length, position, with, what);
|
||||
}
|
||||
|
||||
void FlatTextarea::commmitInstantReplacement(
|
||||
int from,
|
||||
int till,
|
||||
const QString &with,
|
||||
base::optional<QString> checkOriginal) {
|
||||
auto tags = QVector<TextWithTags::Tag>();
|
||||
const auto original = getTextPart(position - length, position, &tags);
|
||||
if (what.compare(original, Qt::CaseInsensitive) != 0) {
|
||||
const auto original = getTextPart(from, till, &tags);
|
||||
if (checkOriginal
|
||||
&& checkOriginal->compare(original, Qt::CaseInsensitive) != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1550,7 +1565,7 @@ void FlatTextarea::applyInstantReplace(
|
||||
auto emojiLength = 0;
|
||||
const auto emoji = Ui::Emoji::Find(with, &emojiLength);
|
||||
if (!emoji || with.size() != emojiLength) {
|
||||
return cursor.charFormat();
|
||||
return _defaultCharFormat;
|
||||
}
|
||||
const auto use = [&] {
|
||||
if (!emoji->hasVariants()) {
|
||||
@ -1562,6 +1577,7 @@ void FlatTextarea::applyInstantReplace(
|
||||
? emoji->variant(it.value())
|
||||
: emoji;
|
||||
}();
|
||||
Ui::Emoji::AddRecent(use);
|
||||
return PrepareEmojiFormat(use, _st.font);
|
||||
}();
|
||||
const auto replacement = format.isImageFormat()
|
||||
@ -1570,12 +1586,10 @@ void FlatTextarea::applyInstantReplace(
|
||||
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);
|
||||
auto cursor = textCursor();
|
||||
cursor.setPosition(from);
|
||||
cursor.setPosition(till, QTextCursor::KeepAnchor);
|
||||
cursor.insertText(replacement, format);
|
||||
}
|
||||
|
||||
bool FlatTextarea::revertInstantReplace() {
|
||||
|
@ -16,7 +16,7 @@ namespace Ui {
|
||||
|
||||
static UserData * const LookingUpInlineBot = SharedMemoryLocation<UserData, 0>();
|
||||
|
||||
class FlatTextarea : public TWidgetHelper<QTextEdit>, private base::Subscriber {
|
||||
class FlatTextarea : public TWidgetHelper<QTextEdit>, protected base::Subscriber {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
@ -32,7 +32,13 @@ public:
|
||||
void setMinHeight(int minHeight);
|
||||
void setMaxHeight(int maxHeight);
|
||||
|
||||
void enableInstantReplaces(bool enabled);
|
||||
void addInstantReplace(const QString &what, const QString &with);
|
||||
void commmitInstantReplacement(
|
||||
int from,
|
||||
int till,
|
||||
const QString &with,
|
||||
base::optional<QString> checkOriginal = base::none);
|
||||
|
||||
void setPlaceholder(base::lambda<QString()> placeholderFactory, int afterSymbols = 0);
|
||||
void updatePlaceholder();
|
||||
@ -230,6 +236,7 @@ private:
|
||||
|
||||
int _instantReplaceMaxLength = 0;
|
||||
InstantReplaceNode _reverseInstantReplaces;
|
||||
bool _instantReplacesEnabled = true;
|
||||
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user