mirror of
https://github.com/AyuGram/AyuGramDesktop.git
synced 2025-06-05 06:33:57 +02:00
Initial chat-translation feature implementation.
This commit is contained in:
parent
02a0ca7112
commit
719466fcac
40 changed files with 1356 additions and 141 deletions
|
@ -756,6 +756,10 @@ PRIVATE
|
||||||
history/view/history_view_sticker_toast.h
|
history/view/history_view_sticker_toast.h
|
||||||
history/view/history_view_transcribe_button.cpp
|
history/view/history_view_transcribe_button.cpp
|
||||||
history/view/history_view_transcribe_button.h
|
history/view/history_view_transcribe_button.h
|
||||||
|
history/view/history_view_translate_bar.cpp
|
||||||
|
history/view/history_view_translate_bar.h
|
||||||
|
history/view/history_view_translate_tracker.cpp
|
||||||
|
history/view/history_view_translate_tracker.h
|
||||||
history/view/history_view_top_bar_widget.cpp
|
history/view/history_view_top_bar_widget.cpp
|
||||||
history/view/history_view_top_bar_widget.h
|
history/view/history_view_top_bar_widget.h
|
||||||
history/view/history_view_view_button.cpp
|
history/view/history_view_view_button.cpp
|
||||||
|
@ -782,6 +786,8 @@ PRIVATE
|
||||||
history/history_inner_widget.h
|
history/history_inner_widget.h
|
||||||
history/history_location_manager.cpp
|
history/history_location_manager.cpp
|
||||||
history/history_location_manager.h
|
history/history_location_manager.h
|
||||||
|
history/history_translation.cpp
|
||||||
|
history/history_translation.h
|
||||||
history/history_unread_things.cpp
|
history/history_unread_things.cpp
|
||||||
history/history_unread_things.h
|
history/history_unread_things.h
|
||||||
history/history_view_highlight_manager.cpp
|
history/history_view_highlight_manager.cpp
|
||||||
|
|
|
@ -31,6 +31,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "lang/lang_instance.h"
|
#include "lang/lang_instance.h"
|
||||||
#include "lang/lang_cloud_manager.h"
|
#include "lang/lang_cloud_manager.h"
|
||||||
#include "settings/settings_common.h"
|
#include "settings/settings_common.h"
|
||||||
|
#include "spellcheck/spellcheck_types.h"
|
||||||
#include "styles/style_layers.h"
|
#include "styles/style_layers.h"
|
||||||
#include "styles/style_boxes.h"
|
#include "styles/style_boxes.h"
|
||||||
#include "styles/style_info.h"
|
#include "styles/style_info.h"
|
||||||
|
@ -43,6 +44,16 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include <QtGui/QClipboard>
|
#include <QtGui/QClipboard>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<QLocale> SkipLocalesFromSettings() {
|
||||||
|
const auto list = Core::App().settings().skipTranslationLanguages();
|
||||||
|
return list
|
||||||
|
| ranges::views::transform(&LanguageId::locale)
|
||||||
|
| ranges::to_vector;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
using Language = Lang::Language;
|
using Language = Lang::Language;
|
||||||
using Languages = Lang::CloudManager::Languages;
|
using Languages = Lang::CloudManager::Languages;
|
||||||
|
@ -1138,19 +1149,19 @@ void LanguageBox::prepare() {
|
||||||
}),
|
}),
|
||||||
st::settingsButtonNoIcon);
|
st::settingsButtonNoIcon);
|
||||||
|
|
||||||
label->fire(Ui::Translate::LocalesFromSettings());
|
label->fire(SkipLocalesFromSettings());
|
||||||
translateSkip->setClickedCallback([=] {
|
translateSkip->setClickedCallback([=] {
|
||||||
Ui::BoxShow(this).showBox(
|
Ui::BoxShow(this).showBox(
|
||||||
Box(Ui::ChooseLanguageBox, [=](std::vector<QLocale> locales) {
|
Box(Ui::ChooseLanguageBox, [=](Locales locales) {
|
||||||
label->fire_copy(locales);
|
label->fire_copy(locales);
|
||||||
const auto result = ranges::views::all(
|
using namespace ranges::views;
|
||||||
|
Core::App().settings().setSkipTranslationLanguages(all(
|
||||||
locales
|
locales
|
||||||
) | ranges::views::transform([](const QLocale &l) {
|
) | transform([](const QLocale &l) {
|
||||||
return int(l.language());
|
return LanguageId{ l.language() };
|
||||||
}) | ranges::to_vector;
|
}) | ranges::to_vector);
|
||||||
Core::App().settings().setSkipTranslationForLanguages(result);
|
|
||||||
Core::App().saveSettingsDelayed();
|
Core::App().saveSettingsDelayed();
|
||||||
}, Ui::Translate::LocalesFromSettings()),
|
}, SkipLocalesFromSettings()),
|
||||||
Ui::LayerOption::KeepOther);
|
Ui::LayerOption::KeepOther);
|
||||||
});
|
});
|
||||||
Settings::AddSkip(topContainer);
|
Settings::AddSkip(topContainer);
|
||||||
|
|
|
@ -16,9 +16,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "mtproto/sender.h"
|
#include "mtproto/sender.h"
|
||||||
#include "settings/settings_common.h"
|
#include "settings/settings_common.h"
|
||||||
#ifndef TDESKTOP_DISABLE_SPELLCHECK
|
|
||||||
#include "spellcheck/platform/platform_language.h"
|
#include "spellcheck/platform/platform_language.h"
|
||||||
#endif
|
|
||||||
#include "ui/effects/loading_element.h"
|
#include "ui/effects/loading_element.h"
|
||||||
#include "ui/layers/generic_box.h"
|
#include "ui/layers/generic_box.h"
|
||||||
#include "ui/painter.h"
|
#include "ui/painter.h"
|
||||||
|
@ -252,29 +250,6 @@ rpl::producer<Qt::MouseButton> ShowButton::clicks() const {
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace Translate {
|
|
||||||
|
|
||||||
std::vector<QLocale> LocalesFromSettings() {
|
|
||||||
const auto langs = Core::App().settings().skipTranslationForLanguages();
|
|
||||||
if (langs.empty()) {
|
|
||||||
return { QLocale(QLocale::English) };
|
|
||||||
}
|
|
||||||
return ranges::views::all(
|
|
||||||
langs
|
|
||||||
) | ranges::view::transform([](int langId) {
|
|
||||||
const auto lang = QLocale::Language(langId);
|
|
||||||
return (lang == QLocale::English)
|
|
||||||
? QLocale(Lang::LanguageIdOrDefault(Lang::Id()))
|
|
||||||
: (lang == QLocale::C)
|
|
||||||
? QLocale(QLocale::English)
|
|
||||||
: QLocale(lang);
|
|
||||||
}) | ranges::to_vector;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Translate
|
|
||||||
|
|
||||||
using namespace Translate;
|
|
||||||
|
|
||||||
QString LanguageName(const QLocale &locale) {
|
QString LanguageName(const QLocale &locale) {
|
||||||
if (locale.language() == QLocale::English
|
if (locale.language() == QLocale::English
|
||||||
&& (locale.country() == QLocale::UnitedStates
|
&& (locale.country() == QLocale::UnitedStates
|
||||||
|
@ -297,7 +272,7 @@ void TranslateBox(
|
||||||
box->setWidth(st::boxWideWidth);
|
box->setWidth(st::boxWideWidth);
|
||||||
box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
|
box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
|
||||||
const auto container = box->verticalLayout();
|
const auto container = box->verticalLayout();
|
||||||
const auto defaultId = LocalesFromSettings().front().name().mid(0, 2);
|
const auto translateTo = Core::App().settings().translateTo().locale();
|
||||||
|
|
||||||
const auto api = box->lifetime().make_state<MTP::Sender>(
|
const auto api = box->lifetime().make_state<MTP::Sender>(
|
||||||
&peer->session().mtp());
|
&peer->session().mtp());
|
||||||
|
@ -316,7 +291,7 @@ void TranslateBox(
|
||||||
msgId = 0;
|
msgId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
using Flag = MTPmessages_translateText::Flag;
|
using Flag = MTPmessages_TranslateText::Flag;
|
||||||
const auto flags = msgId
|
const auto flags = msgId
|
||||||
? (Flag::f_peer | Flag::f_id)
|
? (Flag::f_peer | Flag::f_id)
|
||||||
: !text.text.isEmpty()
|
: !text.text.isEmpty()
|
||||||
|
@ -428,7 +403,7 @@ void TranslateBox(
|
||||||
: MTP_vector<MTPTextWithEntities>(1, MTP_textWithEntities(
|
: MTP_vector<MTPTextWithEntities>(1, MTP_textWithEntities(
|
||||||
MTP_string(text.text),
|
MTP_string(text.text),
|
||||||
MTP_vector<MTPMessageEntity>()))),
|
MTP_vector<MTPMessageEntity>()))),
|
||||||
MTP_string(toLang)
|
MTP_string(toLang.mid(0, 2))
|
||||||
)).done([=](const MTPmessages_TranslatedText &result) {
|
)).done([=](const MTPmessages_TranslatedText &result) {
|
||||||
const auto &data = result.data();
|
const auto &data = result.data();
|
||||||
const auto &list = data.vresult().v;
|
const auto &list = data.vresult().v;
|
||||||
|
@ -439,8 +414,8 @@ void TranslateBox(
|
||||||
showText(tr::lng_translate_box_error(tr::now));
|
showText(tr::lng_translate_box_error(tr::now));
|
||||||
}).send();
|
}).send();
|
||||||
};
|
};
|
||||||
send(defaultId);
|
send(translateTo.name());
|
||||||
state->locale.fire(QLocale(defaultId));
|
state->locale.fire_copy(translateTo);
|
||||||
|
|
||||||
box->addLeftButton(tr::lng_settings_language(), [=] {
|
box->addLeftButton(tr::lng_settings_language(), [=] {
|
||||||
if (loading->toggled()) {
|
if (loading->toggled()) {
|
||||||
|
@ -449,10 +424,10 @@ void TranslateBox(
|
||||||
Ui::BoxShow(box).showBox(Box(ChooseLanguageBox, [=](
|
Ui::BoxShow(box).showBox(Box(ChooseLanguageBox, [=](
|
||||||
std::vector<QLocale> locales) {
|
std::vector<QLocale> locales) {
|
||||||
const auto &locale = locales.front();
|
const auto &locale = locales.front();
|
||||||
|
send(locale.name());
|
||||||
state->locale.fire_copy(locale);
|
state->locale.fire_copy(locale);
|
||||||
loading->show(anim::type::instant);
|
loading->show(anim::type::instant);
|
||||||
translated->hide(anim::type::instant);
|
translated->hide(anim::type::instant);
|
||||||
send(locale.name().mid(0, 2));
|
|
||||||
}, std::vector<QLocale>()));
|
}, std::vector<QLocale>()));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -574,12 +549,9 @@ bool SkipTranslate(TextWithEntities textWithEntities) {
|
||||||
}
|
}
|
||||||
#ifndef TDESKTOP_DISABLE_SPELLCHECK
|
#ifndef TDESKTOP_DISABLE_SPELLCHECK
|
||||||
const auto result = Platform::Language::Recognize(text);
|
const auto result = Platform::Language::Recognize(text);
|
||||||
if (result.unknown) {
|
const auto skip = Core::App().settings().skipTranslationLanguages();
|
||||||
return false;
|
const auto test = (result == result);
|
||||||
}
|
return result.known() && ranges::contains(skip, result);
|
||||||
return ranges::any_of(LocalesFromSettings(), [&](const QLocale &l) {
|
|
||||||
return result.locale.language() == l.language();
|
|
||||||
});
|
|
||||||
#else
|
#else
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -10,9 +10,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
class PeerData;
|
class PeerData;
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
namespace Translate {
|
|
||||||
[[nodiscard]] std::vector<QLocale> LocalesFromSettings();
|
|
||||||
} // namespace Translate
|
|
||||||
|
|
||||||
class GenericBox;
|
class GenericBox;
|
||||||
|
|
||||||
|
|
|
@ -223,13 +223,13 @@ void EditLinkBox(
|
||||||
QObject::connect(text, &Ui::InputField::tabbed, [=] { url->setFocus(); });
|
QObject::connect(text, &Ui::InputField::tabbed, [=] { url->setFocus(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities StripSupportHashtag(TextWithEntities &&text) {
|
TextWithEntities StripSupportHashtag(TextWithEntities text) {
|
||||||
static const auto expression = QRegularExpression(
|
static const auto expression = QRegularExpression(
|
||||||
u"\\n?#tsf[a-z0-9_-]*[\\s#a-z0-9_-]*$"_q,
|
u"\\n?#tsf[a-z0-9_-]*[\\s#a-z0-9_-]*$"_q,
|
||||||
QRegularExpression::CaseInsensitiveOption);
|
QRegularExpression::CaseInsensitiveOption);
|
||||||
const auto match = expression.match(text.text);
|
const auto match = expression.match(text.text);
|
||||||
if (!match.hasMatch()) {
|
if (!match.hasMatch()) {
|
||||||
return std::move(text);
|
return text;
|
||||||
}
|
}
|
||||||
text.text.chop(match.capturedLength());
|
text.text.chop(match.capturedLength());
|
||||||
const auto length = text.text.size();
|
const auto length = text.text.size();
|
||||||
|
@ -246,7 +246,7 @@ TextWithEntities StripSupportHashtag(TextWithEntities &&text) {
|
||||||
}
|
}
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
return std::move(text);
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
|
@ -17,6 +17,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "media/player/media_player_instance.h"
|
#include "media/player/media_player_instance.h"
|
||||||
#include "ui/gl/gl_detection.h"
|
#include "ui/gl/gl_detection.h"
|
||||||
#include "calls/group/calls_group_common.h"
|
#include "calls/group/calls_group_common.h"
|
||||||
|
#include "spellcheck/spellcheck_types.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
namespace {
|
namespace {
|
||||||
|
@ -91,10 +92,13 @@ Settings::Settings()
|
||||||
, _dialogsWidthRatio(DefaultDialogsWidthRatio()) {
|
, _dialogsWidthRatio(DefaultDialogsWidthRatio()) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Settings::~Settings() = default;
|
||||||
|
|
||||||
QByteArray Settings::serialize() const {
|
QByteArray Settings::serialize() const {
|
||||||
const auto themesAccentColors = _themesAccentColors.serialize();
|
const auto themesAccentColors = _themesAccentColors.serialize();
|
||||||
const auto windowPosition = Serialize(_windowPosition);
|
const auto windowPosition = Serialize(_windowPosition);
|
||||||
const auto proxy = _proxy.serialize();
|
const auto proxy = _proxy.serialize();
|
||||||
|
const auto skipLanguages = _skipTranslationLanguages.current();
|
||||||
|
|
||||||
auto recentEmojiPreloadGenerated = std::vector<RecentEmojiPreload>();
|
auto recentEmojiPreloadGenerated = std::vector<RecentEmojiPreload>();
|
||||||
if (_recentEmojiPreload.empty()) {
|
if (_recentEmojiPreload.empty()) {
|
||||||
|
@ -152,7 +156,10 @@ QByteArray Settings::serialize() const {
|
||||||
+ Serialize::stringSize(_customDeviceModel.current())
|
+ Serialize::stringSize(_customDeviceModel.current())
|
||||||
+ sizeof(qint32) * 4
|
+ sizeof(qint32) * 4
|
||||||
+ (_accountsOrder.size() * sizeof(quint64))
|
+ (_accountsOrder.size() * sizeof(quint64))
|
||||||
+ sizeof(qint32) * 5;
|
+ sizeof(qint32) * 7
|
||||||
|
+ (skipLanguages.size() * sizeof(quint64))
|
||||||
|
+ sizeof(qint32)
|
||||||
|
+ sizeof(quint64);
|
||||||
|
|
||||||
auto result = QByteArray();
|
auto result = QByteArray();
|
||||||
result.reserve(size);
|
result.reserve(size);
|
||||||
|
@ -270,13 +277,17 @@ QByteArray Settings::serialize() const {
|
||||||
<< qint32(_translateButtonEnabled ? 1 : 0);
|
<< qint32(_translateButtonEnabled ? 1 : 0);
|
||||||
|
|
||||||
stream
|
stream
|
||||||
<< qint32(_skipTranslationForLanguages.size());
|
<< qint32(skipLanguages.size());
|
||||||
for (const auto &lang : _skipTranslationForLanguages) {
|
for (const auto &id : skipLanguages) {
|
||||||
stream << quint64(lang);
|
stream << quint64(id.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
stream
|
stream
|
||||||
<< qint32(_rememberedDeleteMessageOnlyForYou ? 1 : 0);
|
<< qint32(_rememberedDeleteMessageOnlyForYou ? 1 : 0);
|
||||||
|
|
||||||
|
stream
|
||||||
|
<< qint32(_translateChatEnabled.current() ? 1 : 0)
|
||||||
|
<< quint64(QLocale::Language(_translateToRaw.current()));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -371,9 +382,11 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||||
qint32 suggestAnimatedEmoji = _suggestAnimatedEmoji ? 1 : 0;
|
qint32 suggestAnimatedEmoji = _suggestAnimatedEmoji ? 1 : 0;
|
||||||
qint32 cornerReaction = _cornerReaction.current() ? 1 : 0;
|
qint32 cornerReaction = _cornerReaction.current() ? 1 : 0;
|
||||||
qint32 legacySkipTranslationForLanguage = _translateButtonEnabled ? 1 : 0;
|
qint32 legacySkipTranslationForLanguage = _translateButtonEnabled ? 1 : 0;
|
||||||
qint32 skipTranslationForLanguagesCount = 0;
|
qint32 skipTranslationLanguagesCount = 0;
|
||||||
std::vector<int> skipTranslationForLanguages;
|
std::vector<LanguageId> skipTranslationLanguages;
|
||||||
qint32 rememberedDeleteMessageOnlyForYou = _rememberedDeleteMessageOnlyForYou ? 1 : 0;
|
qint32 rememberedDeleteMessageOnlyForYou = _rememberedDeleteMessageOnlyForYou ? 1 : 0;
|
||||||
|
qint32 translateChatEnabled = _translateChatEnabled.current() ? 1 : 0;
|
||||||
|
quint64 translateToRaw = _translateToRaw.current();
|
||||||
|
|
||||||
stream >> themesAccentColors;
|
stream >> themesAccentColors;
|
||||||
if (!stream.atEnd()) {
|
if (!stream.atEnd()) {
|
||||||
|
@ -575,17 +588,24 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||||
stream >> legacySkipTranslationForLanguage;
|
stream >> legacySkipTranslationForLanguage;
|
||||||
}
|
}
|
||||||
if (!stream.atEnd()) {
|
if (!stream.atEnd()) {
|
||||||
stream >> skipTranslationForLanguagesCount;
|
stream >> skipTranslationLanguagesCount;
|
||||||
if (stream.status() == QDataStream::Ok) {
|
if (stream.status() == QDataStream::Ok) {
|
||||||
for (auto i = 0; i != skipTranslationForLanguagesCount; ++i) {
|
for (auto i = 0; i != skipTranslationLanguagesCount; ++i) {
|
||||||
quint64 language;
|
quint64 language;
|
||||||
stream >> language;
|
stream >> language;
|
||||||
skipTranslationForLanguages.emplace_back(language);
|
skipTranslationLanguages.push_back({
|
||||||
|
QLocale::Language(language)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stream >> rememberedDeleteMessageOnlyForYou;
|
stream >> rememberedDeleteMessageOnlyForYou;
|
||||||
}
|
}
|
||||||
|
if (!stream.atEnd()) {
|
||||||
|
stream
|
||||||
|
>> translateChatEnabled
|
||||||
|
>> translateToRaw;
|
||||||
|
}
|
||||||
if (stream.status() != QDataStream::Ok) {
|
if (stream.status() != QDataStream::Ok) {
|
||||||
LOG(("App Error: "
|
LOG(("App Error: "
|
||||||
"Bad data for Core::Settings::constructFromSerialized()"));
|
"Bad data for Core::Settings::constructFromSerialized()"));
|
||||||
|
@ -756,18 +776,21 @@ void Settings::addFromSerialized(const QByteArray &serialized) {
|
||||||
_suggestAnimatedEmoji = (suggestAnimatedEmoji == 1);
|
_suggestAnimatedEmoji = (suggestAnimatedEmoji == 1);
|
||||||
_cornerReaction = (cornerReaction == 1);
|
_cornerReaction = (cornerReaction == 1);
|
||||||
{ // Parse the legacy translation setting.
|
{ // Parse the legacy translation setting.
|
||||||
_skipTranslationForLanguages = skipTranslationForLanguages;
|
|
||||||
if (legacySkipTranslationForLanguage == 0) {
|
if (legacySkipTranslationForLanguage == 0) {
|
||||||
_translateButtonEnabled = false;
|
_translateButtonEnabled = false;
|
||||||
} else if (legacySkipTranslationForLanguage == 1) {
|
} else if (legacySkipTranslationForLanguage == 1) {
|
||||||
_translateButtonEnabled = true;
|
_translateButtonEnabled = true;
|
||||||
} else {
|
} else {
|
||||||
_translateButtonEnabled = (legacySkipTranslationForLanguage > 0);
|
_translateButtonEnabled = (legacySkipTranslationForLanguage > 0);
|
||||||
_skipTranslationForLanguages.push_back(
|
skipTranslationLanguages.push_back({
|
||||||
std::abs(legacySkipTranslationForLanguage));
|
QLocale::Language(std::abs(legacySkipTranslationForLanguage))
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
_skipTranslationLanguages = std::move(skipTranslationLanguages);
|
||||||
}
|
}
|
||||||
_rememberedDeleteMessageOnlyForYou = (rememberedDeleteMessageOnlyForYou == 1);
|
_rememberedDeleteMessageOnlyForYou = (rememberedDeleteMessageOnlyForYou == 1);
|
||||||
|
_translateChatEnabled = (translateChatEnabled == 1);
|
||||||
|
_translateToRaw = int(QLocale::Language(translateToRaw));
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Settings::getSoundPath(const QString &key) const {
|
QString Settings::getSoundPath(const QString &key) const {
|
||||||
|
@ -1080,14 +1103,76 @@ float64 Settings::DefaultDialogsWidthRatio() {
|
||||||
void Settings::setTranslateButtonEnabled(bool value) {
|
void Settings::setTranslateButtonEnabled(bool value) {
|
||||||
_translateButtonEnabled = value;
|
_translateButtonEnabled = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Settings::translateButtonEnabled() const {
|
bool Settings::translateButtonEnabled() const {
|
||||||
return _translateButtonEnabled;
|
return _translateButtonEnabled;
|
||||||
}
|
}
|
||||||
void Settings::setSkipTranslationForLanguages(std::vector<int> languages) {
|
|
||||||
_skipTranslationForLanguages = std::move(languages);
|
void Settings::setTranslateChatEnabled(bool value) {
|
||||||
|
_translateChatEnabled = value;
|
||||||
}
|
}
|
||||||
std::vector<int> Settings::skipTranslationForLanguages() const {
|
|
||||||
return _skipTranslationForLanguages;
|
bool Settings::translateChatEnabled() const {
|
||||||
|
return _translateChatEnabled.current();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<bool> Settings::translateChatEnabledValue() const {
|
||||||
|
return _translateChatEnabled.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] const std::vector<LanguageId> &DefaultSkipLanguages() {
|
||||||
|
using namespace Platform;
|
||||||
|
|
||||||
|
static auto Result = [&] {
|
||||||
|
auto list = std::vector<LanguageId>();
|
||||||
|
list.push_back({ LanguageId::FromName(Lang::Id()) });
|
||||||
|
const auto systemId = LanguageId::FromName(SystemLanguage());
|
||||||
|
if (list.back() != systemId) {
|
||||||
|
list.push_back(systemId);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ensures(!list.empty());
|
||||||
|
return list;
|
||||||
|
}();
|
||||||
|
return Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::vector<LanguageId> NonEmptySkipList(
|
||||||
|
std::vector<LanguageId> list) {
|
||||||
|
return list.empty() ? DefaultSkipLanguages() : list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::setTranslateTo(LanguageId id) {
|
||||||
|
_translateToRaw = int(id.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
LanguageId Settings::translateTo() const {
|
||||||
|
if (const auto raw = _translateToRaw.current()) {
|
||||||
|
return { QLocale::Language(raw) };
|
||||||
|
}
|
||||||
|
return DefaultSkipLanguages().front();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<LanguageId> Settings::translateToValue() const {
|
||||||
|
return _translateToRaw.value() | rpl::map([=](int raw) {
|
||||||
|
return raw
|
||||||
|
? LanguageId{ QLocale::Language(raw) }
|
||||||
|
: DefaultSkipLanguages().front();
|
||||||
|
}) | rpl::distinct_until_changed();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Settings::setSkipTranslationLanguages(
|
||||||
|
std::vector<LanguageId> languages) {
|
||||||
|
_skipTranslationLanguages = std::move(languages);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Settings::skipTranslationLanguages() const -> std::vector<LanguageId> {
|
||||||
|
return NonEmptySkipList(_skipTranslationLanguages.current());
|
||||||
|
}
|
||||||
|
|
||||||
|
auto Settings::skipTranslationLanguagesValue() const
|
||||||
|
-> rpl::producer<std::vector<LanguageId>> {
|
||||||
|
return _skipTranslationLanguages.value() | rpl::map(NonEmptySkipList);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Settings::setRememberedDeleteMessageOnlyForYou(bool value) {
|
void Settings::setRememberedDeleteMessageOnlyForYou(bool value) {
|
||||||
|
|
|
@ -15,6 +15,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "emoji.h"
|
#include "emoji.h"
|
||||||
|
|
||||||
enum class RectPart;
|
enum class RectPart;
|
||||||
|
struct LanguageId;
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
enum class InputSubmitSettings;
|
enum class InputSubmitSettings;
|
||||||
|
@ -99,6 +100,7 @@ public:
|
||||||
static constexpr auto kDefaultVolume = 0.9;
|
static constexpr auto kDefaultVolume = 0.9;
|
||||||
|
|
||||||
Settings();
|
Settings();
|
||||||
|
~Settings();
|
||||||
|
|
||||||
[[nodiscard]] rpl::producer<> saveDelayedRequests() const {
|
[[nodiscard]] rpl::producer<> saveDelayedRequests() const {
|
||||||
return _saveDelayed.events();
|
return _saveDelayed.events();
|
||||||
|
@ -724,8 +726,16 @@ public:
|
||||||
|
|
||||||
void setTranslateButtonEnabled(bool value);
|
void setTranslateButtonEnabled(bool value);
|
||||||
[[nodiscard]] bool translateButtonEnabled() const;
|
[[nodiscard]] bool translateButtonEnabled() const;
|
||||||
void setSkipTranslationForLanguages(std::vector<int> languages);
|
void setTranslateChatEnabled(bool value);
|
||||||
[[nodiscard]] std::vector<int> skipTranslationForLanguages() const;
|
[[nodiscard]] bool translateChatEnabled() const;
|
||||||
|
[[nodiscard]] rpl::producer<bool> translateChatEnabledValue() const;
|
||||||
|
void setTranslateTo(LanguageId id);
|
||||||
|
[[nodiscard]] LanguageId translateTo() const;
|
||||||
|
[[nodiscard]] rpl::producer<LanguageId> translateToValue() const;
|
||||||
|
void setSkipTranslationLanguages(std::vector<LanguageId> languages);
|
||||||
|
[[nodiscard]] std::vector<LanguageId> skipTranslationLanguages() const;
|
||||||
|
[[nodiscard]] auto skipTranslationLanguagesValue() const
|
||||||
|
-> rpl::producer<std::vector<LanguageId>>;
|
||||||
|
|
||||||
void setRememberedDeleteMessageOnlyForYou(bool value);
|
void setRememberedDeleteMessageOnlyForYou(bool value);
|
||||||
[[nodiscard]] bool rememberedDeleteMessageOnlyForYou() const;
|
[[nodiscard]] bool rememberedDeleteMessageOnlyForYou() const;
|
||||||
|
@ -845,7 +855,10 @@ private:
|
||||||
HistoryView::DoubleClickQuickAction _chatQuickAction =
|
HistoryView::DoubleClickQuickAction _chatQuickAction =
|
||||||
HistoryView::DoubleClickQuickAction();
|
HistoryView::DoubleClickQuickAction();
|
||||||
bool _translateButtonEnabled = false;
|
bool _translateButtonEnabled = false;
|
||||||
std::vector<int> _skipTranslationForLanguages;
|
rpl::variable<bool> _translateChatEnabled = true;
|
||||||
|
rpl::variable<int> _translateToRaw = 0;
|
||||||
|
rpl::variable<std::vector<LanguageId>> _skipTranslationLanguages;
|
||||||
|
rpl::event_stream<> _skipTranslationLanguagesChanges;
|
||||||
bool _rememberedDeleteMessageOnlyForYou = false;
|
bool _rememberedDeleteMessageOnlyForYou = false;
|
||||||
|
|
||||||
bool _tabbedReplacedWithInfo = false; // per-window
|
bool _tabbedReplacedWithInfo = false; // per-window
|
||||||
|
|
|
@ -56,52 +56,53 @@ struct PeerUpdate {
|
||||||
None = 0,
|
None = 0,
|
||||||
|
|
||||||
// Common flags
|
// Common flags
|
||||||
Name = (1ULL << 0),
|
Name = (1ULL << 0),
|
||||||
Username = (1ULL << 1),
|
Username = (1ULL << 1),
|
||||||
Photo = (1ULL << 2),
|
Photo = (1ULL << 2),
|
||||||
About = (1ULL << 3),
|
About = (1ULL << 3),
|
||||||
Notifications = (1ULL << 4),
|
Notifications = (1ULL << 4),
|
||||||
Migration = (1ULL << 5),
|
Migration = (1ULL << 5),
|
||||||
UnavailableReason = (1ULL << 6),
|
UnavailableReason = (1ULL << 6),
|
||||||
ChatThemeEmoji = (1ULL << 7),
|
ChatThemeEmoji = (1ULL << 7),
|
||||||
IsBlocked = (1ULL << 8),
|
IsBlocked = (1ULL << 8),
|
||||||
MessagesTTL = (1ULL << 9),
|
MessagesTTL = (1ULL << 9),
|
||||||
FullInfo = (1ULL << 10),
|
FullInfo = (1ULL << 10),
|
||||||
Usernames = (1ULL << 11),
|
Usernames = (1ULL << 11),
|
||||||
|
TranslationDisabled = (1ULL << 12),
|
||||||
|
|
||||||
// For users
|
// For users
|
||||||
CanShareContact = (1ULL << 12),
|
CanShareContact = (1ULL << 13),
|
||||||
IsContact = (1ULL << 13),
|
IsContact = (1ULL << 14),
|
||||||
PhoneNumber = (1ULL << 14),
|
PhoneNumber = (1ULL << 15),
|
||||||
OnlineStatus = (1ULL << 15),
|
OnlineStatus = (1ULL << 16),
|
||||||
BotCommands = (1ULL << 16),
|
BotCommands = (1ULL << 17),
|
||||||
BotCanBeInvited = (1ULL << 17),
|
BotCanBeInvited = (1ULL << 18),
|
||||||
BotStartToken = (1ULL << 18),
|
BotStartToken = (1ULL << 19),
|
||||||
CommonChats = (1ULL << 19),
|
CommonChats = (1ULL << 20),
|
||||||
HasCalls = (1ULL << 20),
|
HasCalls = (1ULL << 21),
|
||||||
SupportInfo = (1ULL << 21),
|
SupportInfo = (1ULL << 22),
|
||||||
IsBot = (1ULL << 22),
|
IsBot = (1ULL << 23),
|
||||||
EmojiStatus = (1ULL << 23),
|
EmojiStatus = (1ULL << 24),
|
||||||
|
|
||||||
// For chats and channels
|
// For chats and channels
|
||||||
InviteLinks = (1ULL << 24),
|
InviteLinks = (1ULL << 25),
|
||||||
Members = (1ULL << 25),
|
Members = (1ULL << 26),
|
||||||
Admins = (1ULL << 26),
|
Admins = (1ULL << 27),
|
||||||
BannedUsers = (1ULL << 27),
|
BannedUsers = (1ULL << 28),
|
||||||
Rights = (1ULL << 28),
|
Rights = (1ULL << 29),
|
||||||
PendingRequests = (1ULL << 29),
|
PendingRequests = (1ULL << 30),
|
||||||
Reactions = (1ULL << 30),
|
Reactions = (1ULL << 31),
|
||||||
|
|
||||||
// For channels
|
// For channels
|
||||||
ChannelAmIn = (1ULL << 31),
|
ChannelAmIn = (1ULL << 32),
|
||||||
StickersSet = (1ULL << 32),
|
StickersSet = (1ULL << 33),
|
||||||
ChannelLinkedChat = (1ULL << 33),
|
ChannelLinkedChat = (1ULL << 34),
|
||||||
ChannelLocation = (1ULL << 34),
|
ChannelLocation = (1ULL << 35),
|
||||||
Slowmode = (1ULL << 35),
|
Slowmode = (1ULL << 36),
|
||||||
GroupCall = (1ULL << 36),
|
GroupCall = (1ULL << 37),
|
||||||
|
|
||||||
// For iteration
|
// For iteration
|
||||||
LastUsedBit = (1ULL << 36),
|
LastUsedBit = (1ULL << 37),
|
||||||
};
|
};
|
||||||
using Flags = base::flags<Flag>;
|
using Flags = base::flags<Flag>;
|
||||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||||
|
@ -128,8 +129,10 @@ struct HistoryUpdate {
|
||||||
OutboxRead = (1U << 10),
|
OutboxRead = (1U << 10),
|
||||||
BotKeyboard = (1U << 11),
|
BotKeyboard = (1U << 11),
|
||||||
CloudDraft = (1U << 12),
|
CloudDraft = (1U << 12),
|
||||||
|
TranslateFrom = (1U << 13),
|
||||||
|
TranslatedTo = (1U << 14),
|
||||||
|
|
||||||
LastUsedBit = (1U << 12),
|
LastUsedBit = (1U << 14),
|
||||||
};
|
};
|
||||||
using Flags = base::flags<Flag>;
|
using Flags = base::flags<Flag>;
|
||||||
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
friend inline constexpr auto is_flag_type(Flag) { return true; }
|
||||||
|
|
|
@ -1040,6 +1040,7 @@ void ApplyChannelUpdate(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
channel->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
|
channel->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
|
||||||
|
channel->setTranslationDisabled(update.is_translations_disabled());
|
||||||
if (const auto allowed = update.vavailable_reactions()) {
|
if (const auto allowed = update.vavailable_reactions()) {
|
||||||
channel->setAllowedReactions(Data::Parse(*allowed));
|
channel->setAllowedReactions(Data::Parse(*allowed));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -470,6 +470,7 @@ void ApplyChatUpdate(not_null<ChatData*> chat, const MTPDchatFull &update) {
|
||||||
}
|
}
|
||||||
chat->checkFolder(update.vfolder_id().value_or_empty());
|
chat->checkFolder(update.vfolder_id().value_or_empty());
|
||||||
chat->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
|
chat->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
|
||||||
|
chat->setTranslationDisabled(update.is_translations_disabled());
|
||||||
if (const auto allowed = update.vavailable_reactions()) {
|
if (const auto allowed = update.vavailable_reactions()) {
|
||||||
chat->setAllowedReactions(Data::Parse(*allowed));
|
chat->setAllowedReactions(Data::Parse(*allowed));
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -655,6 +655,8 @@ ItemPreview MediaPhoto::toPreview(ToPreviewOptions options) const {
|
||||||
const auto type = tr::lng_in_dlg_photo(tr::now);
|
const auto type = tr::lng_in_dlg_photo(tr::now);
|
||||||
const auto caption = options.hideCaption
|
const auto caption = options.hideCaption
|
||||||
? TextWithEntities()
|
? TextWithEntities()
|
||||||
|
: options.translated
|
||||||
|
? parent()->translatedText()
|
||||||
: parent()->originalText();
|
: parent()->originalText();
|
||||||
const auto hasMiniImages = !images.empty();
|
const auto hasMiniImages = !images.empty();
|
||||||
return {
|
return {
|
||||||
|
@ -899,6 +901,8 @@ ItemPreview MediaFile::toPreview(ToPreviewOptions options) const {
|
||||||
}();
|
}();
|
||||||
const auto caption = options.hideCaption
|
const auto caption = options.hideCaption
|
||||||
? TextWithEntities()
|
? TextWithEntities()
|
||||||
|
: options.translated
|
||||||
|
? parent()->translatedText()
|
||||||
: parent()->originalText();
|
: parent()->originalText();
|
||||||
const auto hasMiniImages = !images.empty();
|
const auto hasMiniImages = !images.empty();
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -546,6 +546,32 @@ void PeerData::checkFolder(FolderId folderId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PeerData::setTranslationDisabled(bool disabled) {
|
||||||
|
const auto flag = disabled
|
||||||
|
? TranslationFlag::Disabled
|
||||||
|
: TranslationFlag::Enabled;
|
||||||
|
if (_translationFlag != flag) {
|
||||||
|
_translationFlag = flag;
|
||||||
|
session().changes().peerUpdated(
|
||||||
|
this,
|
||||||
|
UpdateFlag::TranslationDisabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PeerData::TranslationFlag PeerData::translationFlag() const {
|
||||||
|
return _translationFlag;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PeerData::saveTranslationDisabled(bool disabled) {
|
||||||
|
setTranslationDisabled(disabled);
|
||||||
|
|
||||||
|
using Flag = MTPmessages_TogglePeerTranslations::Flag;
|
||||||
|
session().api().request(MTPmessages_TogglePeerTranslations(
|
||||||
|
MTP_flags(disabled ? Flag::f_disabled : Flag()),
|
||||||
|
input
|
||||||
|
)).send();
|
||||||
|
}
|
||||||
|
|
||||||
void PeerData::setSettings(const MTPPeerSettings &data) {
|
void PeerData::setSettings(const MTPPeerSettings &data) {
|
||||||
data.match([&](const MTPDpeerSettings &data) {
|
data.match([&](const MTPDpeerSettings &data) {
|
||||||
_requestChatTitle = data.vrequest_chat_title().value_or_empty();
|
_requestChatTitle = data.vrequest_chat_title().value_or_empty();
|
||||||
|
|
|
@ -349,6 +349,15 @@ public:
|
||||||
return _requestChatDate;
|
return _requestChatDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class TranslationFlag : uchar {
|
||||||
|
Unknown,
|
||||||
|
Disabled,
|
||||||
|
Enabled,
|
||||||
|
};
|
||||||
|
void setTranslationDisabled(bool disabled);
|
||||||
|
[[nodiscard]] TranslationFlag translationFlag() const;
|
||||||
|
void saveTranslationDisabled(bool disabled);
|
||||||
|
|
||||||
void setSettings(const MTPPeerSettings &data);
|
void setSettings(const MTPPeerSettings &data);
|
||||||
|
|
||||||
enum class BlockStatus : char {
|
enum class BlockStatus : char {
|
||||||
|
@ -439,6 +448,7 @@ private:
|
||||||
Settings _settings = PeerSettings(PeerSetting::Unknown);
|
Settings _settings = PeerSettings(PeerSetting::Unknown);
|
||||||
BlockStatus _blockStatus = BlockStatus::Unknown;
|
BlockStatus _blockStatus = BlockStatus::Unknown;
|
||||||
LoadedStatus _loadedStatus = LoadedStatus::Not;
|
LoadedStatus _loadedStatus = LoadedStatus::Not;
|
||||||
|
TranslationFlag _translationFlag = TranslationFlag::Unknown;
|
||||||
bool _userpicHasVideo = false;
|
bool _userpicHasVideo = false;
|
||||||
|
|
||||||
QString _requestChatTitle;
|
QString _requestChatTitle;
|
||||||
|
|
|
@ -288,6 +288,9 @@ enum class MessageFlag : uint64 {
|
||||||
|
|
||||||
// Profile photo suggestion, views have special media type.
|
// Profile photo suggestion, views have special media type.
|
||||||
IsUserpicSuggestion = (1ULL << 33),
|
IsUserpicSuggestion = (1ULL << 33),
|
||||||
|
|
||||||
|
OnlyEmojiAndSpaces = (1ULL << 34),
|
||||||
|
OnlyEmojiAndSpacesSet = (1ULL << 35),
|
||||||
};
|
};
|
||||||
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
inline constexpr bool is_flag_type(MessageFlag) { return true; }
|
||||||
using MessageFlags = base::flags<MessageFlag>;
|
using MessageFlags = base::flags<MessageFlag>;
|
||||||
|
|
|
@ -423,6 +423,7 @@ void ApplyUserUpdate(not_null<UserData*> user, const MTPDuserFull &update) {
|
||||||
user->setCommonChatsCount(update.vcommon_chats_count().v);
|
user->setCommonChatsCount(update.vcommon_chats_count().v);
|
||||||
user->checkFolder(update.vfolder_id().value_or_empty());
|
user->checkFolder(update.vfolder_id().value_or_empty());
|
||||||
user->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
|
user->setThemeEmoji(qs(update.vtheme_emoticon().value_or_empty()));
|
||||||
|
user->setTranslationDisabled(update.is_translations_disabled());
|
||||||
|
|
||||||
if (const auto info = user->botInfo.get()) {
|
if (const auto info = user->botInfo.get()) {
|
||||||
const auto group = update.vbot_group_admin_rights()
|
const auto group = update.vbot_group_admin_rights()
|
||||||
|
|
|
@ -9,12 +9,13 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "history/view/history_view_element.h"
|
#include "history/view/history_view_element.h"
|
||||||
#include "history/view/history_view_item_preview.h"
|
#include "history/view/history_view_item_preview.h"
|
||||||
|
#include "dialogs/dialogs_indexed_list.h"
|
||||||
|
#include "history/history_inner_widget.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
#include "history/history_item_components.h"
|
#include "history/history_item_components.h"
|
||||||
#include "history/history_item_helpers.h"
|
#include "history/history_item_helpers.h"
|
||||||
#include "history/history_inner_widget.h"
|
#include "history/history_translation.h"
|
||||||
#include "history/history_unread_things.h"
|
#include "history/history_unread_things.h"
|
||||||
#include "dialogs/dialogs_indexed_list.h"
|
|
||||||
#include "dialogs/ui/dialogs_layout.h"
|
#include "dialogs/ui/dialogs_layout.h"
|
||||||
#include "data/notify/data_notify_settings.h"
|
#include "data/notify/data_notify_settings.h"
|
||||||
#include "data/stickers/data_stickers.h"
|
#include "data/stickers/data_stickers.h"
|
||||||
|
@ -44,6 +45,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "window/notifications_manager.h"
|
#include "window/notifications_manager.h"
|
||||||
#include "calls/calls_instance.h"
|
#include "calls/calls_instance.h"
|
||||||
|
#include "spellcheck/spellcheck_types.h"
|
||||||
#include "storage/localstorage.h"
|
#include "storage/localstorage.h"
|
||||||
#include "storage/storage_facade.h"
|
#include "storage/storage_facade.h"
|
||||||
#include "storage/storage_shared_media.h"
|
#include "storage/storage_shared_media.h"
|
||||||
|
@ -3456,6 +3458,46 @@ bool History::isTopPromoted() const {
|
||||||
return (_flags & Flag::IsTopPromoted);
|
return (_flags & Flag::IsTopPromoted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void History::translateOfferFrom(LanguageId id) {
|
||||||
|
if (!id) {
|
||||||
|
if (translatedTo()) {
|
||||||
|
_translation->offerFrom(id);
|
||||||
|
} else if (_translation) {
|
||||||
|
_translation = nullptr;
|
||||||
|
session().changes().historyUpdated(
|
||||||
|
this,
|
||||||
|
UpdateFlag::TranslateFrom);
|
||||||
|
}
|
||||||
|
} else if (!_translation) {
|
||||||
|
_translation = std::make_unique<HistoryTranslation>(this, id);
|
||||||
|
} else {
|
||||||
|
_translation->offerFrom(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LanguageId History::translateOfferedFrom() const {
|
||||||
|
return _translation ? _translation->offeredFrom() : LanguageId();
|
||||||
|
}
|
||||||
|
|
||||||
|
void History::translateTo(LanguageId id) {
|
||||||
|
if (!_translation) {
|
||||||
|
return;
|
||||||
|
} else if (!id && !translateOfferedFrom()) {
|
||||||
|
_translation = nullptr;
|
||||||
|
session().changes().historyUpdated(this, UpdateFlag::TranslatedTo);
|
||||||
|
} else {
|
||||||
|
_translation->translateTo(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LanguageId History::translatedTo() const {
|
||||||
|
return _translation ? _translation->translatedTo() : LanguageId();
|
||||||
|
}
|
||||||
|
|
||||||
|
HistoryTranslation *History::translation() const {
|
||||||
|
return _translation.get();
|
||||||
|
}
|
||||||
|
|
||||||
HistoryBlock::HistoryBlock(not_null<History*> history)
|
HistoryBlock::HistoryBlock(not_null<History*> history)
|
||||||
: _history(history) {
|
: _history(history) {
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,9 +18,11 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
class History;
|
class History;
|
||||||
class HistoryBlock;
|
class HistoryBlock;
|
||||||
|
class HistoryTranslation;
|
||||||
class HistoryItem;
|
class HistoryItem;
|
||||||
struct HistoryMessageMarkupData;
|
struct HistoryMessageMarkupData;
|
||||||
class HistoryMainElementDelegateMixin;
|
class HistoryMainElementDelegateMixin;
|
||||||
|
struct LanguageId;
|
||||||
|
|
||||||
namespace Main {
|
namespace Main {
|
||||||
class Session;
|
class Session;
|
||||||
|
@ -425,6 +427,13 @@ public:
|
||||||
|
|
||||||
[[nodiscard]] bool isTopPromoted() const;
|
[[nodiscard]] bool isTopPromoted() const;
|
||||||
|
|
||||||
|
void translateOfferFrom(LanguageId id);
|
||||||
|
[[nodiscard]] LanguageId translateOfferedFrom() const;
|
||||||
|
void translateTo(LanguageId id);
|
||||||
|
[[nodiscard]] LanguageId translatedTo() const;
|
||||||
|
|
||||||
|
[[nodiscard]] HistoryTranslation *translation() const;
|
||||||
|
|
||||||
const not_null<PeerData*> peer;
|
const not_null<PeerData*> peer;
|
||||||
|
|
||||||
// Still public data.
|
// Still public data.
|
||||||
|
@ -617,6 +626,7 @@ private:
|
||||||
HistoryBlock *block = nullptr;
|
HistoryBlock *block = nullptr;
|
||||||
};
|
};
|
||||||
std::unique_ptr<BuildingBlock> _buildingFrontBlock;
|
std::unique_ptr<BuildingBlock> _buildingFrontBlock;
|
||||||
|
std::unique_ptr<HistoryTranslation> _translation;
|
||||||
|
|
||||||
Data::HistoryDrafts _drafts;
|
Data::HistoryDrafts _drafts;
|
||||||
base::flat_map<MsgId, TimeId> _acceptCloudDraftsAfter;
|
base::flat_map<MsgId, TimeId> _acceptCloudDraftsAfter;
|
||||||
|
@ -628,6 +638,7 @@ private:
|
||||||
|
|
||||||
HistoryView::SendActionPainter _sendActionPainter;
|
HistoryView::SendActionPainter _sendActionPainter;
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class HistoryBlock {
|
class HistoryBlock {
|
||||||
|
|
|
@ -57,6 +57,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "chat_helpers/message_field.h"
|
#include "chat_helpers/message_field.h"
|
||||||
#include "chat_helpers/emoji_interactions.h"
|
#include "chat_helpers/emoji_interactions.h"
|
||||||
#include "history/history_widget.h"
|
#include "history/history_widget.h"
|
||||||
|
#include "history/view/history_view_translate_tracker.h"
|
||||||
#include "base/platform/base_platform_info.h"
|
#include "base/platform/base_platform_info.h"
|
||||||
#include "base/qt/qt_common_adapters.h"
|
#include "base/qt/qt_common_adapters.h"
|
||||||
#include "base/qt/qt_key_modifiers.h"
|
#include "base/qt/qt_key_modifiers.h"
|
||||||
|
@ -324,6 +325,7 @@ HistoryInner::HistoryInner(
|
||||||
&controller->session(),
|
&controller->session(),
|
||||||
[=](not_null<const Element*> view) { return itemTop(view); }))
|
[=](not_null<const Element*> view) { return itemTop(view); }))
|
||||||
, _migrated(history->migrateFrom())
|
, _migrated(history->migrateFrom())
|
||||||
|
, _translateTracker(std::make_unique<HistoryView::TranslateTracker>(history))
|
||||||
, _pathGradient(
|
, _pathGradient(
|
||||||
HistoryView::MakePathShiftGradient(
|
HistoryView::MakePathShiftGradient(
|
||||||
controller->chatStyle(),
|
controller->chatStyle(),
|
||||||
|
@ -340,6 +342,7 @@ HistoryInner::HistoryInner(
|
||||||
_history->delegateMixin()->setCurrent(this);
|
_history->delegateMixin()->setCurrent(this);
|
||||||
if (_migrated) {
|
if (_migrated) {
|
||||||
_migrated->delegateMixin()->setCurrent(this);
|
_migrated->delegateMixin()->setCurrent(this);
|
||||||
|
_migrated->translateTo(_history->translatedTo());
|
||||||
}
|
}
|
||||||
|
|
||||||
Window::ChatThemeValueFromPeer(
|
Window::ChatThemeValueFromPeer(
|
||||||
|
@ -431,7 +434,8 @@ HistoryInner::HistoryInner(
|
||||||
|
|
||||||
session().changes().historyUpdates(
|
session().changes().historyUpdates(
|
||||||
_history,
|
_history,
|
||||||
Data::HistoryUpdate::Flag::OutboxRead
|
(Data::HistoryUpdate::Flag::OutboxRead
|
||||||
|
| Data::HistoryUpdate::Flag::TranslatedTo)
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
update();
|
update();
|
||||||
}, lifetime());
|
}, lifetime());
|
||||||
|
@ -910,6 +914,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||||
|
|
||||||
const auto historyDisplayedEmpty = _history->isDisplayedEmpty()
|
const auto historyDisplayedEmpty = _history->isDisplayedEmpty()
|
||||||
&& (!_migrated || _migrated->isDisplayedEmpty());
|
&& (!_migrated || _migrated->isDisplayedEmpty());
|
||||||
|
const auto translatedTo = _history->translatedTo();
|
||||||
if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
|
if (_botAbout && !_botAbout->info->text.isEmpty() && _botAbout->height > 0) {
|
||||||
const auto st = context.st;
|
const auto st = context.st;
|
||||||
const auto stm = &st->messageStyle(false, false);
|
const auto stm = &st->messageStyle(false, false);
|
||||||
|
@ -958,9 +963,11 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_translateTracker->startBunch();
|
||||||
auto readTill = (HistoryItem*)nullptr;
|
auto readTill = (HistoryItem*)nullptr;
|
||||||
auto readContents = base::flat_set<not_null<HistoryItem*>>();
|
auto readContents = base::flat_set<not_null<HistoryItem*>>();
|
||||||
const auto guard = gsl::finally([&] {
|
const auto guard = gsl::finally([&] {
|
||||||
|
_translateTracker->finishBunch();
|
||||||
if (readTill && _widget->markingMessagesRead()) {
|
if (readTill && _widget->markingMessagesRead()) {
|
||||||
session().data().histories().readInboxTill(readTill);
|
session().data().histories().readInboxTill(readTill);
|
||||||
}
|
}
|
||||||
|
@ -974,6 +981,7 @@ void HistoryInner::paintEvent(QPaintEvent *e) {
|
||||||
not_null<Element*> view,
|
not_null<Element*> view,
|
||||||
int top,
|
int top,
|
||||||
int height) {
|
int height) {
|
||||||
|
_translateTracker->add(view, translatedTo);
|
||||||
const auto item = view->data();
|
const auto item = view->data();
|
||||||
const auto isSponsored = item->isSponsored();
|
const auto isSponsored = item->isSponsored();
|
||||||
const auto isUnread = !item->out()
|
const auto isUnread = !item->out()
|
||||||
|
@ -2414,7 +2422,8 @@ void HistoryInner::showContextMenu(QContextMenuEvent *e, bool showFromTouch) {
|
||||||
[=] { copyContextText(itemId); },
|
[=] { copyContextText(itemId); },
|
||||||
&st::menuIconCopy);
|
&st::menuIconCopy);
|
||||||
}
|
}
|
||||||
if (view->hasVisibleText() || mediaHasTextForCopy) {
|
if ((!item->translation() || !_history->translatedTo())
|
||||||
|
&& (view->hasVisibleText() || mediaHasTextForCopy)) {
|
||||||
const auto translate = mediaHasTextForCopy
|
const auto translate = mediaHasTextForCopy
|
||||||
? (HistoryView::TransribedText(item)
|
? (HistoryView::TransribedText(item)
|
||||||
.append('\n')
|
.append('\n')
|
||||||
|
@ -3925,6 +3934,7 @@ void HistoryInner::notifyIsBotChanged() {
|
||||||
|
|
||||||
void HistoryInner::notifyMigrateUpdated() {
|
void HistoryInner::notifyMigrateUpdated() {
|
||||||
_migrated = _history->migrateFrom();
|
_migrated = _history->migrateFrom();
|
||||||
|
_migrated->translateTo(_history->translatedTo());
|
||||||
}
|
}
|
||||||
|
|
||||||
void HistoryInner::applyDragSelection() {
|
void HistoryInner::applyDragSelection() {
|
||||||
|
|
|
@ -31,6 +31,7 @@ enum class CursorState : char;
|
||||||
enum class PointState : char;
|
enum class PointState : char;
|
||||||
class EmptyPainter;
|
class EmptyPainter;
|
||||||
class Element;
|
class Element;
|
||||||
|
class TranslateTracker;
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
||||||
namespace HistoryView::Reactions {
|
namespace HistoryView::Reactions {
|
||||||
|
@ -445,6 +446,7 @@ private:
|
||||||
|
|
||||||
std::unique_ptr<BotAbout> _botAbout;
|
std::unique_ptr<BotAbout> _botAbout;
|
||||||
std::unique_ptr<HistoryView::EmptyPainter> _emptyPainter;
|
std::unique_ptr<HistoryView::EmptyPainter> _emptyPainter;
|
||||||
|
std::unique_ptr<HistoryView::TranslateTracker> _translateTracker;
|
||||||
|
|
||||||
mutable History *_curHistory = nullptr;
|
mutable History *_curHistory = nullptr;
|
||||||
mutable int _curBlock = 0;
|
mutable int _curBlock = 0;
|
||||||
|
|
|
@ -78,6 +78,25 @@ constexpr auto kPinnedMessageTextLimit = 16;
|
||||||
|
|
||||||
using ItemPreview = HistoryView::ItemPreview;
|
using ItemPreview = HistoryView::ItemPreview;
|
||||||
|
|
||||||
|
[[nodiscard]] bool IsOnlyEmojiAndSpaces(const QString &text) {
|
||||||
|
if (text.isEmpty()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
auto emoji = 0;
|
||||||
|
auto start = text.data();
|
||||||
|
const auto end = start + text.size();
|
||||||
|
while (start < end) {
|
||||||
|
if (start->isSpace()) {
|
||||||
|
++start;
|
||||||
|
} else if (Ui::Emoji::Find(start, end, &emoji)) {
|
||||||
|
start += emoji;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) {
|
void HistoryItem::HistoryItem::Destroyer::operator()(HistoryItem *value) {
|
||||||
|
@ -2006,6 +2025,94 @@ std::optional<QString> HistoryItem::errorTextForForward(
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const HistoryMessageTranslation *HistoryItem::translation() const {
|
||||||
|
return Get<HistoryMessageTranslation>();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HistoryItem::translationShowRequiresCheck(LanguageId to) const {
|
||||||
|
// Check if a call to translationShowRequiresRequest(to) is not a no-op.
|
||||||
|
if (!to) {
|
||||||
|
if (const auto translation = Get<HistoryMessageTranslation>()) {
|
||||||
|
return (!translation->failed && translation->text.empty())
|
||||||
|
|| translation->used;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else if (const auto translation = Get<HistoryMessageTranslation>()) {
|
||||||
|
if (translation->to == to) {
|
||||||
|
return !translation->used && !translation->text.empty();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HistoryItem::translationShowRequiresRequest(LanguageId to) {
|
||||||
|
// When changing be sure to reflect in translationShowRequiresCheck(to).
|
||||||
|
if (!to) {
|
||||||
|
if (const auto translation = Get<HistoryMessageTranslation>()) {
|
||||||
|
if (!translation->failed && translation->text.empty()) {
|
||||||
|
Assert(!translation->used);
|
||||||
|
RemoveComponents(HistoryMessageTranslation::Bit());
|
||||||
|
} else {
|
||||||
|
translationToggle(translation, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} else if (const auto translation = Get<HistoryMessageTranslation>()) {
|
||||||
|
if (translation->to == to) {
|
||||||
|
translationToggle(translation, true);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
translationToggle(translation, false);
|
||||||
|
translation->to = to;
|
||||||
|
translation->requested = true;
|
||||||
|
translation->failed = false;
|
||||||
|
translation->text = {};
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
AddComponents(HistoryMessageTranslation::Bit());
|
||||||
|
const auto added = Get<HistoryMessageTranslation>();
|
||||||
|
added->to = to;
|
||||||
|
added->requested = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryItem::translationToggle(
|
||||||
|
not_null<HistoryMessageTranslation*> translation,
|
||||||
|
bool used) {
|
||||||
|
if (translation->used != used && !translation->text.empty()) {
|
||||||
|
translation->used = used;
|
||||||
|
_history->owner().requestItemTextRefresh(this);
|
||||||
|
_history->owner().updateDependentMessages(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryItem::translationDone(LanguageId to, TextWithEntities result) {
|
||||||
|
const auto set = [&](not_null<HistoryMessageTranslation*> translation) {
|
||||||
|
if (result.empty()) {
|
||||||
|
translation->failed = true;
|
||||||
|
} else {
|
||||||
|
translation->text = std::move(result);
|
||||||
|
if (_history->translatedTo() == to) {
|
||||||
|
translationToggle(translation, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (const auto translation = Get<HistoryMessageTranslation>()) {
|
||||||
|
if (translation->to == to && translation->text.empty()) {
|
||||||
|
translation->requested = false;
|
||||||
|
set(translation);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
AddComponents(HistoryMessageTranslation::Bit());
|
||||||
|
const auto added = Get<HistoryMessageTranslation>();
|
||||||
|
added->to = to;
|
||||||
|
set(added);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool HistoryItem::canReact() const {
|
bool HistoryItem::canReact() const {
|
||||||
if (!isRegular() || isService()) {
|
if (!isRegular() || isService()) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -2180,14 +2287,31 @@ MsgId HistoryItem::idOriginal() const {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities HistoryItem::originalText() const {
|
const TextWithEntities &HistoryItem::originalText() const {
|
||||||
return isService() ? TextWithEntities() : _text;
|
static const auto kEmpty = TextWithEntities();
|
||||||
|
return isService() ? kEmpty : _text;
|
||||||
}
|
}
|
||||||
|
|
||||||
TextWithEntities HistoryItem::originalTextWithLocalEntities() const {
|
const TextWithEntities &HistoryItem::translatedText() const {
|
||||||
return isService()
|
if (isService()) {
|
||||||
? TextWithEntities()
|
static const auto kEmpty = TextWithEntities();
|
||||||
: withLocalEntities(originalText());
|
return kEmpty;
|
||||||
|
} else if (const auto translation = this->translation()
|
||||||
|
; translation
|
||||||
|
&& translation->used
|
||||||
|
&& (translation->to == history()->translatedTo())) {
|
||||||
|
return translation->text;
|
||||||
|
} else {
|
||||||
|
return originalText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextWithEntities HistoryItem::translatedTextWithLocalEntities() const {
|
||||||
|
if (isService()) {
|
||||||
|
return {};
|
||||||
|
} else {
|
||||||
|
return withLocalEntities(translatedText());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TextForMimeData HistoryItem::clipboardText() const {
|
TextForMimeData HistoryItem::clipboardText() const {
|
||||||
|
@ -2595,6 +2719,7 @@ void HistoryItem::setText(const TextWithEntities &textWithEntities) {
|
||||||
void HistoryItem::setTextValue(TextWithEntities text) {
|
void HistoryItem::setTextValue(TextWithEntities text) {
|
||||||
const auto had = !_text.empty();
|
const auto had = !_text.empty();
|
||||||
_text = std::move(text);
|
_text = std::move(text);
|
||||||
|
RemoveComponents(HistoryMessageTranslation::Bit());
|
||||||
if (had) {
|
if (had) {
|
||||||
history()->owner().requestItemTextRefresh(this);
|
history()->owner().requestItemTextRefresh(this);
|
||||||
}
|
}
|
||||||
|
@ -2658,7 +2783,7 @@ ItemPreview HistoryItem::toPreview(ToPreviewOptions options) const {
|
||||||
if (_media) {
|
if (_media) {
|
||||||
return _media->toPreview(options);
|
return _media->toPreview(options);
|
||||||
} else if (!emptyText()) {
|
} else if (!emptyText()) {
|
||||||
return { .text = _text };
|
return { .text = options.translated ? translatedText() : _text };
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}();
|
}();
|
||||||
|
@ -2705,6 +2830,7 @@ TextWithEntities HistoryItem::inReplyText() const {
|
||||||
return toPreview({
|
return toPreview({
|
||||||
.hideSender = true,
|
.hideSender = true,
|
||||||
.generateImages = false,
|
.generateImages = false,
|
||||||
|
.translated = true,
|
||||||
}).text;
|
}).text;
|
||||||
}
|
}
|
||||||
auto result = notificationText();
|
auto result = notificationText();
|
||||||
|
@ -4256,7 +4382,7 @@ PreparedServiceText HistoryItem::preparePinnedText() {
|
||||||
result.links.push_back(fromLink());
|
result.links.push_back(fromLink());
|
||||||
result.links.push_back(pinned->lnk);
|
result.links.push_back(pinned->lnk);
|
||||||
if (mediaText.isEmpty()) {
|
if (mediaText.isEmpty()) {
|
||||||
auto original = pinned->msg->originalText();
|
auto original = pinned->msg->translatedText();
|
||||||
auto cutAt = 0;
|
auto cutAt = 0;
|
||||||
auto limit = kPinnedMessageTextLimit;
|
auto limit = kPinnedMessageTextLimit;
|
||||||
auto size = original.text.size();
|
auto size = original.text.size();
|
||||||
|
@ -4544,6 +4670,23 @@ crl::time HistoryItem::getSelfDestructIn(crl::time now) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HistoryItem::cacheOnlyEmojiAndSpaces(bool only) {
|
||||||
|
_flags |= MessageFlag::OnlyEmojiAndSpacesSet;
|
||||||
|
if (only) {
|
||||||
|
_flags |= MessageFlag::OnlyEmojiAndSpaces;
|
||||||
|
} else {
|
||||||
|
_flags &= ~MessageFlag::OnlyEmojiAndSpaces;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool HistoryItem::isOnlyEmojiAndSpaces() const {
|
||||||
|
if (!(_flags & MessageFlag::OnlyEmojiAndSpacesSet)) {
|
||||||
|
const_cast<HistoryItem*>(this)->cacheOnlyEmojiAndSpaces(
|
||||||
|
IsOnlyEmojiAndSpaces(_text.text));
|
||||||
|
}
|
||||||
|
return (_flags & MessageFlag::OnlyEmojiAndSpaces);
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryItem::setupChatThemeChange() {
|
void HistoryItem::setupChatThemeChange() {
|
||||||
if (const auto user = history()->peer->asUser()) {
|
if (const auto user = history()->peer->asUser()) {
|
||||||
auto link = std::make_shared<LambdaClickHandler>([=](
|
auto link = std::make_shared<LambdaClickHandler>([=](
|
||||||
|
|
|
@ -21,10 +21,12 @@ struct HistoryMessageReply;
|
||||||
struct HistoryMessageViews;
|
struct HistoryMessageViews;
|
||||||
struct HistoryMessageMarkupData;
|
struct HistoryMessageMarkupData;
|
||||||
struct HistoryMessageReplyMarkup;
|
struct HistoryMessageReplyMarkup;
|
||||||
|
struct HistoryMessageTranslation;
|
||||||
struct HistoryServiceDependentData;
|
struct HistoryServiceDependentData;
|
||||||
enum class HistorySelfDestructType;
|
enum class HistorySelfDestructType;
|
||||||
struct PreparedServiceText;
|
struct PreparedServiceText;
|
||||||
class ReplyKeyboard;
|
class ReplyKeyboard;
|
||||||
|
struct LanguageId;
|
||||||
|
|
||||||
namespace base {
|
namespace base {
|
||||||
template <typename Enum>
|
template <typename Enum>
|
||||||
|
@ -259,6 +261,8 @@ public:
|
||||||
[[nodiscard]] bool definesReplyKeyboard() const;
|
[[nodiscard]] bool definesReplyKeyboard() const;
|
||||||
[[nodiscard]] ReplyMarkupFlags replyKeyboardFlags() const;
|
[[nodiscard]] ReplyMarkupFlags replyKeyboardFlags() const;
|
||||||
|
|
||||||
|
void cacheOnlyEmojiAndSpaces(bool only);
|
||||||
|
[[nodiscard]] bool isOnlyEmojiAndSpaces() const;
|
||||||
[[nodiscard]] bool hasSwitchInlineButton() const {
|
[[nodiscard]] bool hasSwitchInlineButton() const {
|
||||||
return _flags & MessageFlag::HasSwitchInlineButton;
|
return _flags & MessageFlag::HasSwitchInlineButton;
|
||||||
}
|
}
|
||||||
|
@ -358,8 +362,9 @@ public:
|
||||||
// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
|
// Example: "[link1-start]You:[link1-end] [link1-start]Photo,[link1-end] caption text"
|
||||||
[[nodiscard]] ItemPreview toPreview(ToPreviewOptions options) const;
|
[[nodiscard]] ItemPreview toPreview(ToPreviewOptions options) const;
|
||||||
[[nodiscard]] TextWithEntities inReplyText() const;
|
[[nodiscard]] TextWithEntities inReplyText() const;
|
||||||
[[nodiscard]] TextWithEntities originalText() const;
|
[[nodiscard]] const TextWithEntities &originalText() const;
|
||||||
[[nodiscard]] TextWithEntities originalTextWithLocalEntities() const;
|
[[nodiscard]] const TextWithEntities &translatedText() const;
|
||||||
|
[[nodiscard]] TextWithEntities translatedTextWithLocalEntities() const;
|
||||||
[[nodiscard]] const std::vector<ClickHandlerPtr> &customTextLinks() const;
|
[[nodiscard]] const std::vector<ClickHandlerPtr> &customTextLinks() const;
|
||||||
[[nodiscard]] TextForMimeData clipboardText() const;
|
[[nodiscard]] TextForMimeData clipboardText() const;
|
||||||
|
|
||||||
|
@ -397,6 +402,10 @@ public:
|
||||||
[[nodiscard]] bool requiresSendInlineRight() const;
|
[[nodiscard]] bool requiresSendInlineRight() const;
|
||||||
[[nodiscard]] std::optional<QString> errorTextForForward(
|
[[nodiscard]] std::optional<QString> errorTextForForward(
|
||||||
not_null<Data::Thread*> to) const;
|
not_null<Data::Thread*> to) const;
|
||||||
|
[[nodiscard]] const HistoryMessageTranslation *translation() const;
|
||||||
|
[[nodiscard]] bool translationShowRequiresCheck(LanguageId to) const;
|
||||||
|
bool translationShowRequiresRequest(LanguageId to);
|
||||||
|
void translationDone(LanguageId to, TextWithEntities result);
|
||||||
|
|
||||||
[[nodiscard]] bool canReact() const;
|
[[nodiscard]] bool canReact() const;
|
||||||
enum class ReactionSource {
|
enum class ReactionSource {
|
||||||
|
@ -542,6 +551,9 @@ private:
|
||||||
void setupChatThemeChange();
|
void setupChatThemeChange();
|
||||||
void setupTTLChange();
|
void setupTTLChange();
|
||||||
|
|
||||||
|
void translationToggle(
|
||||||
|
not_null<HistoryMessageTranslation*> translation,
|
||||||
|
bool used);
|
||||||
void setSelfDestruct(HistorySelfDestructType type, int ttlSeconds);
|
void setSelfDestruct(HistorySelfDestructType type, int ttlSeconds);
|
||||||
|
|
||||||
TextWithEntities fromLinkText() const;
|
TextWithEntities fromLinkText() const;
|
||||||
|
|
|
@ -9,6 +9,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
|
||||||
#include "data/data_cloud_file.h"
|
#include "data/data_cloud_file.h"
|
||||||
#include "history/history_item.h"
|
#include "history/history_item.h"
|
||||||
|
#include "spellcheck/spellcheck_types.h" // LanguageId.
|
||||||
#include "ui/empty_userpic.h"
|
#include "ui/empty_userpic.h"
|
||||||
#include "ui/effects/animations.h"
|
#include "ui/effects/animations.h"
|
||||||
#include "ui/chat/message_bubble.h"
|
#include "ui/chat/message_bubble.h"
|
||||||
|
@ -258,6 +259,15 @@ struct HistoryMessageReply
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct HistoryMessageTranslation
|
||||||
|
: public RuntimeComponent<HistoryMessageTranslation, HistoryItem> {
|
||||||
|
TextWithEntities text;
|
||||||
|
LanguageId to;
|
||||||
|
bool requested = false;
|
||||||
|
bool failed = false;
|
||||||
|
bool used = false;
|
||||||
|
};
|
||||||
|
|
||||||
struct HistoryMessageReplyMarkup
|
struct HistoryMessageReplyMarkup
|
||||||
: public RuntimeComponent<HistoryMessageReplyMarkup, HistoryItem> {
|
: public RuntimeComponent<HistoryMessageReplyMarkup, HistoryItem> {
|
||||||
using Button = HistoryMessageMarkupButton;
|
using Button = HistoryMessageMarkupButton;
|
||||||
|
|
51
Telegram/SourceFiles/history/history_translation.cpp
Normal file
51
Telegram/SourceFiles/history/history_translation.cpp
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "history/history_translation.h"
|
||||||
|
|
||||||
|
#include "data/data_changes.h"
|
||||||
|
#include "history/history.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
using UpdateFlag = Data::HistoryUpdate::Flag;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
HistoryTranslation::HistoryTranslation(
|
||||||
|
not_null<History*> history,
|
||||||
|
const LanguageId &offerFrom)
|
||||||
|
: _history(history) {
|
||||||
|
this->offerFrom(offerFrom);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryTranslation::offerFrom(LanguageId id) {
|
||||||
|
if (_offerFrom == id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_offerFrom = id;
|
||||||
|
auto &changes = _history->session().changes();
|
||||||
|
changes.historyUpdated(_history, UpdateFlag::TranslateFrom);
|
||||||
|
}
|
||||||
|
|
||||||
|
LanguageId HistoryTranslation::offeredFrom() const {
|
||||||
|
return _offerFrom;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HistoryTranslation::translateTo(LanguageId id) {
|
||||||
|
if (_translatedTo == id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_translatedTo = id;
|
||||||
|
auto &changes = _history->session().changes();
|
||||||
|
changes.historyUpdated(_history, UpdateFlag::TranslatedTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
LanguageId HistoryTranslation::translatedTo() const {
|
||||||
|
return _translatedTo;
|
||||||
|
}
|
32
Telegram/SourceFiles/history/history_translation.h
Normal file
32
Telegram/SourceFiles/history/history_translation.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "spellcheck/spellcheck_types.h"
|
||||||
|
|
||||||
|
class History;
|
||||||
|
|
||||||
|
class HistoryTranslation final {
|
||||||
|
public:
|
||||||
|
HistoryTranslation(
|
||||||
|
not_null<History*> history,
|
||||||
|
const LanguageId &offerFrom);
|
||||||
|
|
||||||
|
void offerFrom(LanguageId id);
|
||||||
|
[[nodiscard]] LanguageId offeredFrom() const;
|
||||||
|
|
||||||
|
void translateTo(LanguageId id);
|
||||||
|
[[nodiscard]] LanguageId translatedTo() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const not_null<History*> _history;
|
||||||
|
|
||||||
|
LanguageId _offerFrom;
|
||||||
|
LanguageId _translatedTo;
|
||||||
|
|
||||||
|
};
|
|
@ -102,6 +102,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "history/view/history_view_item_preview.h"
|
#include "history/view/history_view_item_preview.h"
|
||||||
#include "history/view/history_view_requests_bar.h"
|
#include "history/view/history_view_requests_bar.h"
|
||||||
#include "history/view/history_view_sticker_toast.h"
|
#include "history/view/history_view_sticker_toast.h"
|
||||||
|
#include "history/view/history_view_translate_bar.h"
|
||||||
#include "history/view/media/history_view_media.h"
|
#include "history/view/media/history_view_media.h"
|
||||||
#include "profile/profile_block_group_members.h"
|
#include "profile/profile_block_group_members.h"
|
||||||
#include "info/info_memento.h"
|
#include "info/info_memento.h"
|
||||||
|
@ -1488,6 +1489,9 @@ void HistoryWidget::orderWidgets() {
|
||||||
if (_pinnedBar) {
|
if (_pinnedBar) {
|
||||||
_pinnedBar->raise();
|
_pinnedBar->raise();
|
||||||
}
|
}
|
||||||
|
if (_translateBar) {
|
||||||
|
_translateBar->raise();
|
||||||
|
}
|
||||||
if (_requestsBar) {
|
if (_requestsBar) {
|
||||||
_requestsBar->raise();
|
_requestsBar->raise();
|
||||||
}
|
}
|
||||||
|
@ -2093,6 +2097,7 @@ void HistoryWidget::showHistory(
|
||||||
_history->showAtMsgId = _showAtMsgId;
|
_history->showAtMsgId = _showAtMsgId;
|
||||||
|
|
||||||
destroyUnreadBarOnClose();
|
destroyUnreadBarOnClose();
|
||||||
|
_translateBar = nullptr;
|
||||||
_pinnedBar = nullptr;
|
_pinnedBar = nullptr;
|
||||||
_pinnedTracker = nullptr;
|
_pinnedTracker = nullptr;
|
||||||
_groupCallBar = nullptr;
|
_groupCallBar = nullptr;
|
||||||
|
@ -2238,6 +2243,7 @@ void HistoryWidget::showHistory(
|
||||||
|
|
||||||
_updateHistoryItems.cancel();
|
_updateHistoryItems.cancel();
|
||||||
|
|
||||||
|
setupTranslateBar();
|
||||||
setupPinnedTracker();
|
setupPinnedTracker();
|
||||||
setupGroupCallBar();
|
setupGroupCallBar();
|
||||||
setupRequestsBar();
|
setupRequestsBar();
|
||||||
|
@ -3966,6 +3972,9 @@ void HistoryWidget::showAnimated(
|
||||||
show();
|
show();
|
||||||
_topBar->finishAnimating();
|
_topBar->finishAnimating();
|
||||||
_cornerButtons.finishAnimations();
|
_cornerButtons.finishAnimations();
|
||||||
|
if (_translateBar) {
|
||||||
|
_translateBar->finishAnimating();
|
||||||
|
}
|
||||||
if (_pinnedBar) {
|
if (_pinnedBar) {
|
||||||
_pinnedBar->finishAnimating();
|
_pinnedBar->finishAnimating();
|
||||||
}
|
}
|
||||||
|
@ -4029,6 +4038,9 @@ void HistoryWidget::doneShow() {
|
||||||
_preserveScrollTop = true;
|
_preserveScrollTop = true;
|
||||||
preloadHistoryIfNeeded();
|
preloadHistoryIfNeeded();
|
||||||
updatePinnedViewer();
|
updatePinnedViewer();
|
||||||
|
if (_translateBar) {
|
||||||
|
_translateBar->finishAnimating();
|
||||||
|
}
|
||||||
if (_pinnedBar) {
|
if (_pinnedBar) {
|
||||||
_pinnedBar->finishAnimating();
|
_pinnedBar->finishAnimating();
|
||||||
}
|
}
|
||||||
|
@ -5320,7 +5332,12 @@ void HistoryWidget::updateControlsGeometry() {
|
||||||
_requestsBar->move(0, requestsTop);
|
_requestsBar->move(0, requestsTop);
|
||||||
_requestsBar->resizeToWidth(width());
|
_requestsBar->resizeToWidth(width());
|
||||||
}
|
}
|
||||||
const auto pinnedBarTop = requestsTop + (_requestsBar ? _requestsBar->height() : 0);
|
const auto translateTop = requestsTop + (_requestsBar ? _requestsBar->height() : 0);
|
||||||
|
if (_translateBar) {
|
||||||
|
_translateBar->move(0, translateTop);
|
||||||
|
_translateBar->resizeToWidth(width());
|
||||||
|
}
|
||||||
|
const auto pinnedBarTop = translateTop + (_translateBar ? _translateBar->height() : 0);
|
||||||
if (_pinnedBar) {
|
if (_pinnedBar) {
|
||||||
_pinnedBar->move(0, pinnedBarTop);
|
_pinnedBar->move(0, pinnedBarTop);
|
||||||
_pinnedBar->resizeToWidth(width());
|
_pinnedBar->resizeToWidth(width());
|
||||||
|
@ -5499,6 +5516,9 @@ void HistoryWidget::updateHistoryGeometry(
|
||||||
}
|
}
|
||||||
|
|
||||||
auto newScrollHeight = height() - _topBar->height();
|
auto newScrollHeight = height() - _topBar->height();
|
||||||
|
if (_translateBar) {
|
||||||
|
newScrollHeight -= _translateBar->height();
|
||||||
|
}
|
||||||
if (_pinnedBar) {
|
if (_pinnedBar) {
|
||||||
newScrollHeight -= _pinnedBar->height();
|
newScrollHeight -= _pinnedBar->height();
|
||||||
}
|
}
|
||||||
|
@ -6229,6 +6249,40 @@ void HistoryWidget::checkLastPinnedClickedIdReset(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void HistoryWidget::setupTranslateBar() {
|
||||||
|
Expects(_history != nullptr);
|
||||||
|
|
||||||
|
_translateBar = std::make_unique<HistoryView::TranslateBar>(
|
||||||
|
this,
|
||||||
|
_history);
|
||||||
|
|
||||||
|
controller()->adaptive().oneColumnValue(
|
||||||
|
) | rpl::start_with_next([=, raw = _translateBar.get()](bool one) {
|
||||||
|
raw->setShadowGeometryPostprocess([=](QRect geometry) {
|
||||||
|
if (!one) {
|
||||||
|
geometry.setLeft(geometry.left() + st::lineWidth);
|
||||||
|
}
|
||||||
|
return geometry;
|
||||||
|
});
|
||||||
|
}, _translateBar->lifetime());
|
||||||
|
|
||||||
|
_translateBarHeight = 0;
|
||||||
|
_translateBar->heightValue(
|
||||||
|
) | rpl::start_with_next([=](int height) {
|
||||||
|
_topDelta = _preserveScrollTop ? 0 : (height - _translateBarHeight);
|
||||||
|
_translateBarHeight = height;
|
||||||
|
updateHistoryGeometry();
|
||||||
|
updateControlsGeometry();
|
||||||
|
_topDelta = 0;
|
||||||
|
}, _translateBar->lifetime());
|
||||||
|
|
||||||
|
orderWidgets();
|
||||||
|
|
||||||
|
if (_showAnimation) {
|
||||||
|
_translateBar->hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void HistoryWidget::setupPinnedTracker() {
|
void HistoryWidget::setupPinnedTracker() {
|
||||||
Expects(_history != nullptr);
|
Expects(_history != nullptr);
|
||||||
|
|
||||||
|
|
|
@ -91,6 +91,7 @@ class TopBarWidget;
|
||||||
class ContactStatus;
|
class ContactStatus;
|
||||||
class Element;
|
class Element;
|
||||||
class PinnedTracker;
|
class PinnedTracker;
|
||||||
|
class TranslateBar;
|
||||||
class ComposeSearch;
|
class ComposeSearch;
|
||||||
namespace Controls {
|
namespace Controls {
|
||||||
class RecordLock;
|
class RecordLock;
|
||||||
|
@ -494,6 +495,7 @@ private:
|
||||||
void updateReplyEditText(not_null<HistoryItem*> item);
|
void updateReplyEditText(not_null<HistoryItem*> item);
|
||||||
|
|
||||||
void updatePinnedViewer();
|
void updatePinnedViewer();
|
||||||
|
void setupTranslateBar();
|
||||||
void setupPinnedTracker();
|
void setupPinnedTracker();
|
||||||
void checkPinnedBarState();
|
void checkPinnedBarState();
|
||||||
void clearHidingPinnedBar();
|
void clearHidingPinnedBar();
|
||||||
|
@ -638,6 +640,9 @@ private:
|
||||||
|
|
||||||
object_ptr<Ui::IconButton> _fieldBarCancel;
|
object_ptr<Ui::IconButton> _fieldBarCancel;
|
||||||
|
|
||||||
|
std::unique_ptr<HistoryView::TranslateBar> _translateBar;
|
||||||
|
int _translateBarHeight = 0;
|
||||||
|
|
||||||
std::unique_ptr<HistoryView::PinnedTracker> _pinnedTracker;
|
std::unique_ptr<HistoryView::PinnedTracker> _pinnedTracker;
|
||||||
std::unique_ptr<Ui::PinnedBar> _pinnedBar;
|
std::unique_ptr<Ui::PinnedBar> _pinnedBar;
|
||||||
std::unique_ptr<Ui::PinnedBar> _hidingPinnedBar;
|
std::unique_ptr<Ui::PinnedBar> _hidingPinnedBar;
|
||||||
|
|
|
@ -66,6 +66,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
#include "core/application.h"
|
#include "core/application.h"
|
||||||
#include "main/main_session.h"
|
#include "main/main_session.h"
|
||||||
#include "main/main_session_settings.h"
|
#include "main/main_session_settings.h"
|
||||||
|
#include "spellcheck/spellcheck_types.h"
|
||||||
#include "apiwrap.h"
|
#include "apiwrap.h"
|
||||||
#include "styles/style_chat.h"
|
#include "styles/style_chat.h"
|
||||||
#include "styles/style_menu_icons.h"
|
#include "styles/style_menu_icons.h"
|
||||||
|
@ -1058,7 +1059,8 @@ base::unique_qptr<Ui::PopupMenu> FillContextMenu(
|
||||||
.append('\n')
|
.append('\n')
|
||||||
.append(item->originalText()))
|
.append(item->originalText()))
|
||||||
: item->originalText();
|
: item->originalText();
|
||||||
if (!translate.text.isEmpty()
|
if ((!item->translation() || !item->history()->translatedTo())
|
||||||
|
&& !translate.text.isEmpty()
|
||||||
&& !Ui::SkipTranslate(translate)) {
|
&& !Ui::SkipTranslate(translate)) {
|
||||||
result->addAction(tr::lng_context_translate(tr::now), [=] {
|
result->addAction(tr::lng_context_translate(tr::now), [=] {
|
||||||
if (const auto item = owner->message(itemId)) {
|
if (const auto item = owner->message(itemId)) {
|
||||||
|
|
|
@ -652,6 +652,18 @@ const Ui::Text::String &Element::text() const {
|
||||||
return _text;
|
return _text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OnlyEmojiAndSpaces Element::isOnlyEmojiAndSpaces() const {
|
||||||
|
if (data()->Has<HistoryMessageTranslation>()) {
|
||||||
|
return OnlyEmojiAndSpaces::No;
|
||||||
|
} else if (!_text.isEmpty() || data()->originalText().empty()) {
|
||||||
|
return _text.isOnlyEmojiAndSpaces()
|
||||||
|
? OnlyEmojiAndSpaces::Yes
|
||||||
|
: OnlyEmojiAndSpaces::No;
|
||||||
|
} else {
|
||||||
|
return OnlyEmojiAndSpaces::Unknown;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int Element::textHeightFor(int textWidth) {
|
int Element::textHeightFor(int textWidth) {
|
||||||
validateText();
|
validateText();
|
||||||
if (_textWidth != textWidth) {
|
if (_textWidth != textWidth) {
|
||||||
|
@ -837,7 +849,7 @@ void Element::validateText() {
|
||||||
};
|
};
|
||||||
_text.setMarkedText(
|
_text.setMarkedText(
|
||||||
st::messageTextStyle,
|
st::messageTextStyle,
|
||||||
item->originalTextWithLocalEntities(),
|
item->translatedTextWithLocalEntities(),
|
||||||
Ui::ItemTextOptions(item),
|
Ui::ItemTextOptions(item),
|
||||||
context);
|
context);
|
||||||
if (!text.empty() && _text.isEmpty()) {
|
if (!text.empty() && _text.isEmpty()) {
|
||||||
|
|
|
@ -58,6 +58,12 @@ enum class Context : char {
|
||||||
ContactPreview
|
ContactPreview
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum class OnlyEmojiAndSpaces : char {
|
||||||
|
Unknown,
|
||||||
|
Yes,
|
||||||
|
No,
|
||||||
|
};
|
||||||
|
|
||||||
class Element;
|
class Element;
|
||||||
class ElementDelegate {
|
class ElementDelegate {
|
||||||
public:
|
public:
|
||||||
|
@ -309,6 +315,8 @@ public:
|
||||||
[[nodiscard]] Ui::Text::IsolatedEmoji isolatedEmoji() const;
|
[[nodiscard]] Ui::Text::IsolatedEmoji isolatedEmoji() const;
|
||||||
[[nodiscard]] Ui::Text::OnlyCustomEmoji onlyCustomEmoji() const;
|
[[nodiscard]] Ui::Text::OnlyCustomEmoji onlyCustomEmoji() const;
|
||||||
|
|
||||||
|
[[nodiscard]] OnlyEmojiAndSpaces isOnlyEmojiAndSpaces() const;
|
||||||
|
|
||||||
// For blocks context this should be called only from recountAttachToPreviousInBlocks().
|
// For blocks context this should be called only from recountAttachToPreviousInBlocks().
|
||||||
void setAttachToPrevious(bool attachToNext, Element *previous = nullptr);
|
void setAttachToPrevious(bool attachToNext, Element *previous = nullptr);
|
||||||
|
|
||||||
|
|
|
@ -37,6 +37,7 @@ struct ToPreviewOptions {
|
||||||
bool generateImages = true;
|
bool generateImages = true;
|
||||||
bool ignoreGroup = false;
|
bool ignoreGroup = false;
|
||||||
bool ignoreTopic = true;
|
bool ignoreTopic = true;
|
||||||
|
bool translated = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace HistoryView
|
} // namespace HistoryView
|
||||||
|
|
|
@ -295,17 +295,6 @@ RepliesWidget::RepliesWidget(
|
||||||
searchInTopic();
|
searchInTopic();
|
||||||
}, _topBar->lifetime());
|
}, _topBar->lifetime());
|
||||||
|
|
||||||
if (_rootView) {
|
|
||||||
_rootView->raise();
|
|
||||||
}
|
|
||||||
if (_pinnedBar) {
|
|
||||||
_pinnedBar->raise();
|
|
||||||
}
|
|
||||||
if (_topicReopenBar) {
|
|
||||||
_topicReopenBar->bar().raise();
|
|
||||||
}
|
|
||||||
_topBarShadow->raise();
|
|
||||||
|
|
||||||
controller->adaptive().value(
|
controller->adaptive().value(
|
||||||
) | rpl::start_with_next([=] {
|
) | rpl::start_with_next([=] {
|
||||||
updateAdaptiveLayout();
|
updateAdaptiveLayout();
|
||||||
|
@ -426,6 +415,9 @@ void RepliesWidget::orderWidgets() {
|
||||||
if (_pinnedBar) {
|
if (_pinnedBar) {
|
||||||
_pinnedBar->raise();
|
_pinnedBar->raise();
|
||||||
}
|
}
|
||||||
|
if (_topicReopenBar) {
|
||||||
|
_topicReopenBar->bar().raise();
|
||||||
|
}
|
||||||
_topBarShadow->raise();
|
_topBarShadow->raise();
|
||||||
_composeControls->raisePanels();
|
_composeControls->raisePanels();
|
||||||
}
|
}
|
||||||
|
|
197
Telegram/SourceFiles/history/view/history_view_translate_bar.cpp
Normal file
197
Telegram/SourceFiles/history/view/history_view_translate_bar.cpp
Normal file
|
@ -0,0 +1,197 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "history/view/history_view_translate_bar.h"
|
||||||
|
|
||||||
|
#include "boxes/translate_box.h" // Ui::LanguageName.
|
||||||
|
#include "core/application.h"
|
||||||
|
#include "core/core_settings.h"
|
||||||
|
#include "data/data_changes.h"
|
||||||
|
#include "history/history.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "spellcheck/spellcheck_types.h"
|
||||||
|
#include "ui/widgets/buttons.h"
|
||||||
|
#include "ui/widgets/labels.h"
|
||||||
|
#include "ui/widgets/shadow.h"
|
||||||
|
#include "styles/style_chat.h"
|
||||||
|
|
||||||
|
#include <QtGui/QtEvents>
|
||||||
|
|
||||||
|
namespace HistoryView {
|
||||||
|
|
||||||
|
TranslateBar::TranslateBar(
|
||||||
|
not_null<QWidget*> parent,
|
||||||
|
not_null<History*> history)
|
||||||
|
: _wrap(parent, object_ptr<Ui::FlatButton>(
|
||||||
|
parent,
|
||||||
|
QString(),
|
||||||
|
st::historyComposeButton))
|
||||||
|
, _shadow(std::make_unique<Ui::PlainShadow>(_wrap.parentWidget())) {
|
||||||
|
_wrap.hide(anim::type::instant);
|
||||||
|
_shadow->hide();
|
||||||
|
|
||||||
|
setup(history);
|
||||||
|
}
|
||||||
|
|
||||||
|
TranslateBar::~TranslateBar() = default;
|
||||||
|
|
||||||
|
void TranslateBar::updateControlsGeometry(QRect wrapGeometry) {
|
||||||
|
const auto hidden = _wrap.isHidden() || !wrapGeometry.height();
|
||||||
|
if (_shadow->isHidden() != hidden) {
|
||||||
|
_shadow->setVisible(!hidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateBar::setShadowGeometryPostprocess(
|
||||||
|
Fn<QRect(QRect)> postprocess) {
|
||||||
|
_shadowGeometryPostprocess = std::move(postprocess);
|
||||||
|
updateShadowGeometry(_wrap.geometry());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateBar::updateShadowGeometry(QRect wrapGeometry) {
|
||||||
|
const auto regular = QRect(
|
||||||
|
wrapGeometry.x(),
|
||||||
|
wrapGeometry.y() + wrapGeometry.height(),
|
||||||
|
wrapGeometry.width(),
|
||||||
|
st::lineWidth);
|
||||||
|
_shadow->setGeometry(_shadowGeometryPostprocess
|
||||||
|
? _shadowGeometryPostprocess(regular)
|
||||||
|
: regular);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateBar::setup(not_null<History*> history) {
|
||||||
|
_wrap.geometryValue(
|
||||||
|
) | rpl::start_with_next([=](QRect rect) {
|
||||||
|
updateShadowGeometry(rect);
|
||||||
|
updateControlsGeometry(rect);
|
||||||
|
}, _wrap.lifetime());
|
||||||
|
|
||||||
|
const auto button = static_cast<Ui::FlatButton*>(_wrap.entity());
|
||||||
|
button->setClickedCallback([=] {
|
||||||
|
const auto to = history->translatedTo()
|
||||||
|
? LanguageId()
|
||||||
|
: Core::App().settings().translateTo();
|
||||||
|
history->translateTo(to);
|
||||||
|
if (const auto migrated = history->migrateFrom()) {
|
||||||
|
migrated->translateTo(to);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const auto label = Ui::CreateChild<Ui::FlatLabel>(
|
||||||
|
button,
|
||||||
|
st::historyTranslateLabel);
|
||||||
|
const auto icon = Ui::CreateChild<Ui::RpWidget>(button);
|
||||||
|
label->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
icon->setAttribute(Qt::WA_TransparentForMouseEvents);
|
||||||
|
icon->resize(st::historyTranslateIcon.size());
|
||||||
|
icon->paintRequest() | rpl::start_with_next([=] {
|
||||||
|
auto p = QPainter(icon);
|
||||||
|
st::historyTranslateIcon.paint(p, 0, 0, icon->width());
|
||||||
|
}, icon->lifetime());
|
||||||
|
const auto settings = Ui::CreateChild<Ui::IconButton>(
|
||||||
|
button,
|
||||||
|
st::historyTranslateSettings);
|
||||||
|
const auto updateLabelGeometry = [=] {
|
||||||
|
const auto full = _wrap.width() - icon->width();
|
||||||
|
const auto skip = st::semiboldFont->spacew * 2;
|
||||||
|
const auto natural = label->naturalWidth();
|
||||||
|
const auto top = [&] {
|
||||||
|
return (_wrap.height() - label->height()) / 2;
|
||||||
|
};
|
||||||
|
if (natural <= full - 2 * (settings->width() + skip)) {
|
||||||
|
label->resizeToWidth(natural);
|
||||||
|
label->moveToRight((full - label->width()) / 2, top());
|
||||||
|
} else {
|
||||||
|
const auto available = full - settings->width() - 2 * skip;
|
||||||
|
label->resizeToWidth(std::min(natural, available));
|
||||||
|
label->moveToRight(settings->width() + skip, top());
|
||||||
|
}
|
||||||
|
icon->move(
|
||||||
|
label->x() - icon->width(),
|
||||||
|
(_wrap.height() - icon->height()) / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
_wrap.sizeValue() | rpl::start_with_next([=](QSize size) {
|
||||||
|
settings->moveToRight(0, 0, size.width());
|
||||||
|
updateLabelGeometry();
|
||||||
|
}, lifetime());
|
||||||
|
|
||||||
|
rpl::combine(
|
||||||
|
Core::App().settings().translateToValue(),
|
||||||
|
history->session().changes().historyFlagsValue(
|
||||||
|
history,
|
||||||
|
(Data::HistoryUpdate::Flag::TranslatedTo
|
||||||
|
| Data::HistoryUpdate::Flag::TranslateFrom))
|
||||||
|
) | rpl::map([=](LanguageId to, const auto&) {
|
||||||
|
return history->translatedTo()
|
||||||
|
? u"Show Original"_q
|
||||||
|
: history->translateOfferedFrom()
|
||||||
|
? u"Translate to "_q + Ui::LanguageName(to.locale())
|
||||||
|
: QString();
|
||||||
|
}) | rpl::distinct_until_changed(
|
||||||
|
) | rpl::start_with_next([=](QString phrase) {
|
||||||
|
_shouldBeShown = !phrase.isEmpty();
|
||||||
|
if (_shouldBeShown) {
|
||||||
|
label->setText(phrase);
|
||||||
|
updateLabelGeometry();
|
||||||
|
}
|
||||||
|
if (!_forceHidden) {
|
||||||
|
_wrap.toggle(_shouldBeShown, anim::type::normal);
|
||||||
|
}
|
||||||
|
}, lifetime());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateBar::show() {
|
||||||
|
if (!_forceHidden) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_forceHidden = false;
|
||||||
|
if (_shouldBeShown) {
|
||||||
|
_wrap.show(anim::type::instant);
|
||||||
|
_shadow->show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateBar::hide() {
|
||||||
|
if (_forceHidden) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_forceHidden = true;
|
||||||
|
_wrap.hide(anim::type::instant);
|
||||||
|
_shadow->hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateBar::raise() {
|
||||||
|
_wrap.raise();
|
||||||
|
_shadow->raise();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateBar::finishAnimating() {
|
||||||
|
_wrap.finishAnimating();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateBar::move(int x, int y) {
|
||||||
|
_wrap.move(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateBar::resizeToWidth(int width) {
|
||||||
|
_wrap.entity()->resizeToWidth(width);
|
||||||
|
}
|
||||||
|
|
||||||
|
int TranslateBar::height() const {
|
||||||
|
return !_forceHidden
|
||||||
|
? _wrap.height()
|
||||||
|
: _shouldBeShown
|
||||||
|
? st::historyReplyHeight
|
||||||
|
: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<int> TranslateBar::heightValue() const {
|
||||||
|
return _wrap.heightValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Ui
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "ui/wrap/slide_wrap.h"
|
||||||
|
|
||||||
|
class History;
|
||||||
|
struct LanguageId;
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class PlainShadow;
|
||||||
|
} // namespace Ui
|
||||||
|
|
||||||
|
namespace HistoryView {
|
||||||
|
|
||||||
|
class TranslateBar final {
|
||||||
|
public:
|
||||||
|
TranslateBar(not_null<QWidget*> parent, not_null<History*> history);
|
||||||
|
~TranslateBar();
|
||||||
|
|
||||||
|
void show();
|
||||||
|
void hide();
|
||||||
|
void raise();
|
||||||
|
void finishAnimating();
|
||||||
|
|
||||||
|
void setShadowGeometryPostprocess(Fn<QRect(QRect)> postprocess);
|
||||||
|
|
||||||
|
void move(int x, int y);
|
||||||
|
void resizeToWidth(int width);
|
||||||
|
[[nodiscard]] int height() const;
|
||||||
|
[[nodiscard]] rpl::producer<int> heightValue() const;
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::lifetime &lifetime() {
|
||||||
|
return _wrap.lifetime();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setup(not_null<History*> history);
|
||||||
|
void updateShadowGeometry(QRect wrapGeometry);
|
||||||
|
void updateControlsGeometry(QRect wrapGeometry);
|
||||||
|
|
||||||
|
Ui::SlideWrap<> _wrap;
|
||||||
|
std::unique_ptr<Ui::PlainShadow> _shadow;
|
||||||
|
Fn<QRect(QRect)> _shadowGeometryPostprocess;
|
||||||
|
bool _shouldBeShown = false;
|
||||||
|
bool _forceHidden = false;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace HistoryView
|
|
@ -0,0 +1,351 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#include "history/view/history_view_translate_tracker.h"
|
||||||
|
|
||||||
|
#include "apiwrap.h"
|
||||||
|
#include "api/api_text_entities.h"
|
||||||
|
#include "core/application.h"
|
||||||
|
#include "core/core_settings.h"
|
||||||
|
#include "data/data_changes.h"
|
||||||
|
#include "data/data_peer_values.h" // Data::AmPremiumValue.
|
||||||
|
#include "data/data_session.h"
|
||||||
|
#include "history/history.h"
|
||||||
|
#include "history/history_item.h"
|
||||||
|
#include "history/history_item_components.h"
|
||||||
|
#include "history/view/history_view_element.h"
|
||||||
|
#include "main/main_session.h"
|
||||||
|
#include "spellcheck/spellcheck_types.h"
|
||||||
|
#include "spellcheck/platform/platform_language.h"
|
||||||
|
|
||||||
|
namespace HistoryView {
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr auto kEnoughForRecognition = 10;
|
||||||
|
constexpr auto kEnoughForTranslation = 6;
|
||||||
|
constexpr auto kRequestLengthLimit = 24 * 1024;
|
||||||
|
constexpr auto kRequestCountLimit = 20;
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
struct TranslateTracker::ItemForRecognize {
|
||||||
|
uint64 generation = 0;
|
||||||
|
MaybeLanguageId id;
|
||||||
|
};
|
||||||
|
|
||||||
|
TranslateTracker::TranslateTracker(not_null<History*> history)
|
||||||
|
: _history(history)
|
||||||
|
, _limit(kEnoughForRecognition) {
|
||||||
|
setup();
|
||||||
|
}
|
||||||
|
|
||||||
|
TranslateTracker::~TranslateTracker() {
|
||||||
|
cancelToRequest();
|
||||||
|
cancelSentRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
rpl::producer<bool> TranslateTracker::trackingLanguage() const {
|
||||||
|
return _trackingLanguage.value();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateTracker::setup() {
|
||||||
|
const auto peer = _history->peer;
|
||||||
|
const auto session = &_history->session();
|
||||||
|
peer->updateFull();
|
||||||
|
|
||||||
|
auto translationEnabled = session->changes().peerFlagsValue(
|
||||||
|
peer,
|
||||||
|
Data::PeerUpdate::Flag::TranslationDisabled
|
||||||
|
) | rpl::map([=] {
|
||||||
|
return peer->translationFlag() == PeerData::TranslationFlag::Enabled;
|
||||||
|
}) | rpl::distinct_until_changed();
|
||||||
|
|
||||||
|
_trackingLanguage = Data::AmPremiumValue(&_history->session());
|
||||||
|
|
||||||
|
_trackingLanguage.value(
|
||||||
|
) | rpl::start_with_next([=](bool tracking) {
|
||||||
|
_trackingLifetime.destroy();
|
||||||
|
if (tracking) {
|
||||||
|
recognizeCollected();
|
||||||
|
trackSkipLanguages();
|
||||||
|
} else {
|
||||||
|
checkRecognized({});
|
||||||
|
_history->translateTo({});
|
||||||
|
if (const auto migrated = _history->migrateFrom()) {
|
||||||
|
migrated->translateTo({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, _lifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TranslateTracker::enoughForRecognition() const {
|
||||||
|
return _itemsForRecognize.size() >= kEnoughForRecognition;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateTracker::startBunch() {
|
||||||
|
_addedInBunch = 0;
|
||||||
|
++_generation;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateTracker::add(
|
||||||
|
not_null<Element*> view,
|
||||||
|
LanguageId translatedTo) {
|
||||||
|
const auto item = view->data();
|
||||||
|
const auto only = view->isOnlyEmojiAndSpaces();
|
||||||
|
if (only != OnlyEmojiAndSpaces::Unknown) {
|
||||||
|
item->cacheOnlyEmojiAndSpaces(only == OnlyEmojiAndSpaces::Yes);
|
||||||
|
}
|
||||||
|
add(item, translatedTo, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateTracker::add(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
LanguageId translatedTo) {
|
||||||
|
add(item, translatedTo, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateTracker::add(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
LanguageId translatedTo,
|
||||||
|
bool skipDependencies) {
|
||||||
|
Expects(_addedInBunch >= 0);
|
||||||
|
|
||||||
|
if (item->out()
|
||||||
|
|| item->isService()
|
||||||
|
|| !item->isRegular()
|
||||||
|
|| item->isOnlyEmojiAndSpaces()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (item->translationShowRequiresCheck(translatedTo)) {
|
||||||
|
_switchTranslations[item] = translatedTo;
|
||||||
|
}
|
||||||
|
if (!skipDependencies) {
|
||||||
|
if (const auto reply = item->Get<HistoryMessageReply>()) {
|
||||||
|
if (const auto to = reply->replyToMsg.get()) {
|
||||||
|
add(to, translatedTo, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const auto id = item->fullId();
|
||||||
|
const auto i = _itemsForRecognize.find(id);
|
||||||
|
if (i != end(_itemsForRecognize)) {
|
||||||
|
i->second.generation = _generation;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto &text = item->originalText().text;
|
||||||
|
_itemsForRecognize.emplace(id, ItemForRecognize{
|
||||||
|
.generation = _generation,
|
||||||
|
.id = (_trackingLanguage.current()
|
||||||
|
? Platform::Language::Recognize(text)
|
||||||
|
: MaybeLanguageId{ text }),
|
||||||
|
});
|
||||||
|
++_addedInBunch;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateTracker::switchTranslation(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
LanguageId id) {
|
||||||
|
if (item->translationShowRequiresRequest(id)) {
|
||||||
|
_itemsToRequest.emplace(
|
||||||
|
item->fullId(),
|
||||||
|
ItemToRequest{ item->originalText().text.size() });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateTracker::finishBunch() {
|
||||||
|
if (_addedInBunch > 0) {
|
||||||
|
accumulate_max(_limit, _addedInBunch + kEnoughForRecognition);
|
||||||
|
_addedInBunch = -1;
|
||||||
|
applyLimit();
|
||||||
|
if (_trackingLanguage.current()) {
|
||||||
|
checkRecognized();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
requestSome();
|
||||||
|
if (!_switchTranslations.empty()) {
|
||||||
|
auto switching = base::take(_switchTranslations);
|
||||||
|
for (const auto &[item, id] : switching) {
|
||||||
|
switchTranslation(item, id);
|
||||||
|
}
|
||||||
|
_switchTranslations = std::move(switching);
|
||||||
|
_switchTranslations.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateTracker::cancelToRequest() {
|
||||||
|
if (!_itemsToRequest.empty()) {
|
||||||
|
const auto owner = &_history->owner();
|
||||||
|
for (const auto &[id, entry] : base::take(_itemsToRequest)) {
|
||||||
|
if (const auto item = owner->message(id)) {
|
||||||
|
item->translationShowRequiresRequest({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateTracker::cancelSentRequest() {
|
||||||
|
if (_requestId) {
|
||||||
|
const auto owner = &_history->owner();
|
||||||
|
for (const auto &id : base::take(_requested)) {
|
||||||
|
if (const auto item = owner->message(id)) {
|
||||||
|
item->translationShowRequiresRequest({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_history->session().api().request(base::take(_requestId)).cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateTracker::requestSome() {
|
||||||
|
if (_requestId || _itemsToRequest.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto to = _history->translatedTo();
|
||||||
|
if (!to) {
|
||||||
|
cancelToRequest();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_requested.clear();
|
||||||
|
_requested.reserve(_itemsToRequest.size());
|
||||||
|
const auto session = &_history->session();
|
||||||
|
const auto peerId = _itemsToRequest.back().first.peer;
|
||||||
|
auto peer = (peerId == _history->peer->id)
|
||||||
|
? _history->peer
|
||||||
|
: session->data().peer(peerId);
|
||||||
|
auto length = 0;
|
||||||
|
auto list = QVector<MTPint>();
|
||||||
|
list.reserve(_itemsToRequest.size());
|
||||||
|
for (auto i = _itemsToRequest.end(); i != _itemsToRequest.begin();) {
|
||||||
|
if ((--i)->first.peer != peerId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
length += i->second.length;
|
||||||
|
_requested.push_back(i->first);
|
||||||
|
list.push_back(MTP_int(i->first.msg));
|
||||||
|
i = _itemsToRequest.erase(i);
|
||||||
|
if (list.size() >= kRequestCountLimit
|
||||||
|
|| length >= kRequestLengthLimit) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using Flag = MTPmessages_TranslateText::Flag;
|
||||||
|
_requestId = session->api().request(MTPmessages_TranslateText(
|
||||||
|
MTP_flags(Flag::f_peer | Flag::f_id),
|
||||||
|
peer->input,
|
||||||
|
MTP_vector<MTPint>(list),
|
||||||
|
MTPVector<MTPTextWithEntities>(),
|
||||||
|
MTP_string(to.locale().name().mid(0, 2))
|
||||||
|
)).done([=](const MTPmessages_TranslatedText &result) {
|
||||||
|
requestDone(to, result.data().vresult().v);
|
||||||
|
}).fail([=] {
|
||||||
|
requestDone(to, {});
|
||||||
|
}).send();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateTracker::requestDone(
|
||||||
|
LanguageId to,
|
||||||
|
const QVector<MTPTextWithEntities> &list) {
|
||||||
|
auto index = 0;
|
||||||
|
const auto session = &_history->session();
|
||||||
|
const auto owner = &session->data();
|
||||||
|
for (const auto &id : base::take(_requested)) {
|
||||||
|
if (const auto item = owner->message(id)) {
|
||||||
|
const auto data = (index >= list.size())
|
||||||
|
? nullptr
|
||||||
|
: &list[index].data();
|
||||||
|
auto text = data ? TextWithEntities{
|
||||||
|
qs(data->vtext()),
|
||||||
|
Api::EntitiesFromMTP(session, data->ventities().v)
|
||||||
|
} : TextWithEntities();
|
||||||
|
item->translationDone(to, std::move(text));
|
||||||
|
}
|
||||||
|
++index;
|
||||||
|
}
|
||||||
|
_requestId = 0;
|
||||||
|
requestSome();
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateTracker::applyLimit() {
|
||||||
|
const auto generationProjection = [](const auto &pair) {
|
||||||
|
return pair.second.generation;
|
||||||
|
};
|
||||||
|
const auto owner = &_history->owner();
|
||||||
|
|
||||||
|
// Erase starting with oldest generation till items count is not too big.
|
||||||
|
while (_itemsForRecognize.size() > _limit) {
|
||||||
|
const auto oldest = ranges::min_element(
|
||||||
|
_itemsForRecognize,
|
||||||
|
ranges::less(),
|
||||||
|
generationProjection
|
||||||
|
)->second.generation;
|
||||||
|
for (auto i = begin(_itemsForRecognize)
|
||||||
|
; i != end(_itemsForRecognize);) {
|
||||||
|
if (i->second.generation == oldest) {
|
||||||
|
if (const auto j = _itemsToRequest.find(i->first)
|
||||||
|
; j != end(_itemsToRequest)) {
|
||||||
|
if (const auto item = owner->message(i->first)) {
|
||||||
|
item->translationShowRequiresRequest({});
|
||||||
|
}
|
||||||
|
_itemsToRequest.erase(j);
|
||||||
|
}
|
||||||
|
i = _itemsForRecognize.erase(i);
|
||||||
|
} else {
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateTracker::recognizeCollected() {
|
||||||
|
const auto owner = &_history->owner();
|
||||||
|
for (auto &[id, entry] : _itemsForRecognize) {
|
||||||
|
if (const auto text = std::get_if<QString>(&entry.id)) {
|
||||||
|
entry.id = Platform::Language::Recognize(*text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateTracker::trackSkipLanguages() {
|
||||||
|
Core::App().settings().skipTranslationLanguagesValue(
|
||||||
|
) | rpl::start_with_next([=](const std::vector<LanguageId> &skip) {
|
||||||
|
checkRecognized(skip);
|
||||||
|
}, _trackingLifetime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateTracker::checkRecognized() {
|
||||||
|
checkRecognized(Core::App().settings().skipTranslationLanguages());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TranslateTracker::checkRecognized(const std::vector<LanguageId> &skip) {
|
||||||
|
if (!_trackingLanguage.current()) {
|
||||||
|
_history->translateOfferFrom({});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto languages = base::flat_map<LanguageId, int>();
|
||||||
|
for (const auto &[id, entry] : _itemsForRecognize) {
|
||||||
|
if (const auto id = std::get_if<LanguageId>(&entry.id)) {
|
||||||
|
if (*id && !ranges::contains(skip, *id)) {
|
||||||
|
++languages[*id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
using namespace base;
|
||||||
|
const auto count = int(_itemsForRecognize.size());
|
||||||
|
constexpr auto p = &flat_multi_map_pair_type<LanguageId, int>::second;
|
||||||
|
const auto threshold = (count > kEnoughForRecognition)
|
||||||
|
? (count * kEnoughForTranslation / kEnoughForRecognition)
|
||||||
|
: _allLoaded
|
||||||
|
? std::min(count, kEnoughForTranslation)
|
||||||
|
: kEnoughForTranslation;
|
||||||
|
if (ranges::accumulate(languages, 0, ranges::plus(), p) >= threshold) {
|
||||||
|
_history->translateOfferFrom(
|
||||||
|
ranges::max_element(languages, ranges::less(), p)->first);
|
||||||
|
} else {
|
||||||
|
_history->translateOfferFrom({});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace HistoryView
|
|
@ -0,0 +1,76 @@
|
||||||
|
/*
|
||||||
|
This file is part of Telegram Desktop,
|
||||||
|
the official desktop application for the Telegram messaging service.
|
||||||
|
|
||||||
|
For license and copyright information please follow this link:
|
||||||
|
https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
class History;
|
||||||
|
class HistoryItem;
|
||||||
|
struct LanguageId;
|
||||||
|
|
||||||
|
namespace HistoryView {
|
||||||
|
|
||||||
|
class Element;
|
||||||
|
|
||||||
|
class TranslateTracker final {
|
||||||
|
public:
|
||||||
|
explicit TranslateTracker(not_null<History*> history);
|
||||||
|
~TranslateTracker();
|
||||||
|
|
||||||
|
[[nodiscard]] bool enoughForRecognition() const;
|
||||||
|
void startBunch();
|
||||||
|
void add(not_null<Element*> view, LanguageId translatedTo);
|
||||||
|
void add(not_null<HistoryItem*> item, LanguageId translatedTo);
|
||||||
|
void finishBunch();
|
||||||
|
|
||||||
|
[[nodiscard]] rpl::producer<bool> trackingLanguage() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
using MaybeLanguageId = std::variant<QString, LanguageId>;
|
||||||
|
struct ItemForRecognize;
|
||||||
|
struct ItemToRequest {
|
||||||
|
int length = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
void setup();
|
||||||
|
void add(
|
||||||
|
not_null<HistoryItem*> item,
|
||||||
|
LanguageId translatedTo,
|
||||||
|
bool skipDependencies);
|
||||||
|
void recognizeCollected();
|
||||||
|
void trackSkipLanguages();
|
||||||
|
void checkRecognized();
|
||||||
|
void checkRecognized(const std::vector<LanguageId> &skip);
|
||||||
|
void applyLimit();
|
||||||
|
void requestSome();
|
||||||
|
void cancelToRequest();
|
||||||
|
void cancelSentRequest();
|
||||||
|
void switchTranslation(not_null<HistoryItem*> item, LanguageId id);
|
||||||
|
|
||||||
|
void requestDone(
|
||||||
|
LanguageId to,
|
||||||
|
const QVector<MTPTextWithEntities> &list);
|
||||||
|
|
||||||
|
const not_null<History*> _history;
|
||||||
|
rpl::variable<bool> _trackingLanguage = false;
|
||||||
|
base::flat_map<FullMsgId, ItemForRecognize> _itemsForRecognize;
|
||||||
|
uint64 _generation = 0;
|
||||||
|
int _limit = 0;
|
||||||
|
int _addedInBunch = -1;
|
||||||
|
bool _allLoaded = false;
|
||||||
|
|
||||||
|
base::flat_map<not_null<HistoryItem*>, LanguageId> _switchTranslations;
|
||||||
|
base::flat_map<FullMsgId, ItemToRequest> _itemsToRequest;
|
||||||
|
std::vector<FullMsgId> _requested;
|
||||||
|
mtpRequestId _requestId = 0;
|
||||||
|
|
||||||
|
rpl::lifetime _trackingLifetime;
|
||||||
|
rpl::lifetime _lifetime;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace HistoryView
|
||||||
|
|
|
@ -305,7 +305,7 @@ Ui::Text::String Media::createCaption(not_null<HistoryItem*> item) const {
|
||||||
};
|
};
|
||||||
result.setMarkedText(
|
result.setMarkedText(
|
||||||
st::messageTextStyle,
|
st::messageTextStyle,
|
||||||
item->originalTextWithLocalEntities(),
|
item->translatedTextWithLocalEntities(),
|
||||||
Ui::ItemTextOptions(item),
|
Ui::ItemTextOptions(item),
|
||||||
context);
|
context);
|
||||||
FillTextWithAnimatedSpoilers(_parent, result);
|
FillTextWithAnimatedSpoilers(_parent, result);
|
||||||
|
|
|
@ -2300,7 +2300,7 @@ void OverlayWidget::refreshCaption() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const auto caption = _message->originalText();
|
const auto caption = _message->translatedText();
|
||||||
if (caption.text.isEmpty()) {
|
if (caption.text.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1257,3 +1257,19 @@ historyHasCustomEmoji: FlatLabel(defaultFlatLabel) {
|
||||||
minWidth: 80px;
|
minWidth: 80px;
|
||||||
}
|
}
|
||||||
historyHasCustomEmojiPosition: point(12px, 4px);
|
historyHasCustomEmojiPosition: point(12px, 4px);
|
||||||
|
|
||||||
|
historyTranslateLabel: FlatLabel(defaultFlatLabel) {
|
||||||
|
style: semiboldTextStyle;
|
||||||
|
textFg: windowActiveTextFg;
|
||||||
|
minWidth: 80px;
|
||||||
|
}
|
||||||
|
historyTranslateIcon: icon{{ "menu/translate", windowActiveTextFg }};
|
||||||
|
historyTranslateSettings: IconButton(defaultIconButton) {
|
||||||
|
width: 46px;
|
||||||
|
height: 46px;
|
||||||
|
icon: icon{{ "menu/customize", windowActiveTextFg }};
|
||||||
|
iconOver: icon{{ "menu/customize", windowActiveTextFg }};
|
||||||
|
rippleAreaPosition: point(4px, 4px);
|
||||||
|
rippleAreaSize: 38px;
|
||||||
|
ripple: defaultRippleAnimation;
|
||||||
|
}
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit cf59ca87b761ab5bbd80be02a61cd38a70142898
|
Subproject commit e5ac664fe397d5874a244bbbc8a7b266223cb88b
|
|
@ -1 +1 @@
|
||||||
Subproject commit 43e9128014c5239a6732ae34bdfe007efb9692c8
|
Subproject commit f2e698f2209a86c133261196275ca98273c7a4dc
|
Loading…
Add table
Reference in a new issue