Add "Suggest emoji replacements" checkbox.

Also emoji suggestions insert an instant emoji replacement.
This commit is contained in:
John Preston 2018-05-13 18:56:08 +03:00
parent 4b763a76df
commit 168a7ce2e5
14 changed files with 121 additions and 44 deletions

View File

@ -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";

View File

@ -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);
}
}

View File

@ -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;

View File

@ -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 {

View File

@ -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);

View File

@ -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);

View File

@ -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();

View File

@ -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;

View File

@ -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);

View File

@ -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();
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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() {

View File

@ -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;
};