2022-11-12 11:13:23 +00:00
|
|
|
/*
|
|
|
|
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 "boxes/translate_box.h"
|
|
|
|
|
2023-02-07 16:06:16 +00:00
|
|
|
#include "api/api_text_entities.h" // Api::EntitiesToMTP / EntitiesFromMTP.
|
2022-11-18 10:42:12 +00:00
|
|
|
#include "core/application.h"
|
|
|
|
#include "core/core_settings.h"
|
2022-12-08 14:59:53 +00:00
|
|
|
#include "core/ui_integration.h"
|
2022-11-12 11:13:23 +00:00
|
|
|
#include "data/data_peer.h"
|
2023-02-07 12:29:34 +00:00
|
|
|
#include "data/data_session.h"
|
|
|
|
#include "history/history.h"
|
2022-11-12 11:13:23 +00:00
|
|
|
#include "lang/lang_instance.h"
|
|
|
|
#include "lang/lang_keys.h"
|
|
|
|
#include "main/main_session.h"
|
|
|
|
#include "mtproto/sender.h"
|
|
|
|
#include "settings/settings_common.h"
|
2022-11-18 10:42:12 +00:00
|
|
|
#include "spellcheck/platform/platform_language.h"
|
2023-01-30 10:10:42 +00:00
|
|
|
#include "ui/boxes/choose_language_box.h"
|
2022-11-12 16:34:41 +00:00
|
|
|
#include "ui/effects/loading_element.h"
|
2022-11-12 11:13:23 +00:00
|
|
|
#include "ui/layers/generic_box.h"
|
2023-02-07 16:06:16 +00:00
|
|
|
#include "ui/text/text_utilities.h"
|
2022-12-12 16:58:43 +00:00
|
|
|
#include "ui/painter.h"
|
2022-11-12 11:13:23 +00:00
|
|
|
#include "ui/widgets/buttons.h"
|
|
|
|
#include "ui/widgets/labels.h"
|
2022-12-12 16:58:43 +00:00
|
|
|
#include "ui/widgets/multi_select.h"
|
2022-11-12 11:13:23 +00:00
|
|
|
#include "ui/wrap/fade_wrap.h"
|
|
|
|
#include "ui/wrap/slide_wrap.h"
|
|
|
|
#include "styles/style_boxes.h"
|
|
|
|
#include "styles/style_chat_helpers.h"
|
2022-12-12 16:58:43 +00:00
|
|
|
#include "styles/style_info.h" // inviteLinkListItem.
|
2022-11-12 11:13:23 +00:00
|
|
|
#include "styles/style_layers.h"
|
2022-11-12 18:13:15 +00:00
|
|
|
#include "styles/style_settings.h" // settingsSubsectionTitlePadding.
|
|
|
|
|
|
|
|
#include <QLocale>
|
2022-11-12 11:13:23 +00:00
|
|
|
|
|
|
|
namespace Ui {
|
|
|
|
namespace {
|
|
|
|
|
2023-02-07 14:38:10 +00:00
|
|
|
constexpr auto kSkipAtLeastOneDuration = 3 * crl::time(1000);
|
|
|
|
|
2022-12-12 16:58:43 +00:00
|
|
|
class ShowButton final : public RpWidget {
|
2022-11-12 11:13:23 +00:00
|
|
|
public:
|
|
|
|
ShowButton(not_null<Ui::RpWidget*> parent);
|
|
|
|
|
|
|
|
[[nodiscard]] rpl::producer<Qt::MouseButton> clicks() const;
|
|
|
|
|
|
|
|
protected:
|
|
|
|
void paintEvent(QPaintEvent *e) override;
|
|
|
|
|
|
|
|
private:
|
|
|
|
LinkButton _button;
|
|
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
ShowButton::ShowButton(not_null<Ui::RpWidget*> parent)
|
|
|
|
: RpWidget(parent)
|
|
|
|
, _button(this, tr::lng_usernames_activate_confirm(tr::now)) {
|
|
|
|
_button.sizeValue(
|
|
|
|
) | rpl::start_with_next([=](const QSize &s) {
|
|
|
|
resize(
|
2023-05-17 11:51:04 +00:00
|
|
|
s.width() + st::defaultEmojiSuggestions.fadeRight.width(),
|
2022-11-12 11:13:23 +00:00
|
|
|
s.height());
|
|
|
|
_button.moveToRight(0, 0);
|
|
|
|
}, lifetime());
|
|
|
|
_button.show();
|
|
|
|
}
|
|
|
|
|
|
|
|
void ShowButton::paintEvent(QPaintEvent *e) {
|
|
|
|
auto p = QPainter(this);
|
|
|
|
const auto clip = e->rect();
|
|
|
|
|
2023-05-17 11:51:04 +00:00
|
|
|
const auto &icon = st::defaultEmojiSuggestions.fadeRight;
|
2022-11-12 11:13:23 +00:00
|
|
|
const auto fade = QRect(0, 0, icon.width(), height());
|
|
|
|
if (fade.intersects(clip)) {
|
|
|
|
icon.fill(p, fade);
|
|
|
|
}
|
|
|
|
const auto fill = clip.intersected(
|
|
|
|
{ icon.width(), 0, width() - icon.width(), height() });
|
|
|
|
if (!fill.isEmpty()) {
|
|
|
|
p.fillRect(fill, st::boxBg);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
rpl::producer<Qt::MouseButton> ShowButton::clicks() const {
|
|
|
|
return _button.clicks();
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
void TranslateBox(
|
|
|
|
not_null<Ui::GenericBox*> box,
|
|
|
|
not_null<PeerData*> peer,
|
|
|
|
MsgId msgId,
|
2022-11-27 13:12:30 +00:00
|
|
|
TextWithEntities text,
|
|
|
|
bool hasCopyRestriction) {
|
2022-11-12 11:13:23 +00:00
|
|
|
box->setWidth(st::boxWideWidth);
|
|
|
|
box->addButton(tr::lng_box_ok(), [=] { box->closeBox(); });
|
|
|
|
const auto container = box->verticalLayout();
|
|
|
|
|
2023-02-07 12:29:34 +00:00
|
|
|
struct State {
|
|
|
|
State(not_null<Main::Session*> session) : api(&session->mtp()) {
|
|
|
|
}
|
|
|
|
|
|
|
|
MTP::Sender api;
|
|
|
|
rpl::variable<LanguageId> to;
|
|
|
|
};
|
|
|
|
const auto state = box->lifetime().make_state<State>(&peer->session());
|
|
|
|
state->to = ChooseTranslateTo(peer->owner().history(peer));
|
2022-11-12 11:13:23 +00:00
|
|
|
|
2022-11-23 11:35:10 +00:00
|
|
|
if (!IsServerMsgId(msgId)) {
|
|
|
|
msgId = 0;
|
|
|
|
}
|
|
|
|
|
2023-01-26 15:36:43 +00:00
|
|
|
using Flag = MTPmessages_TranslateText::Flag;
|
2022-11-12 11:13:23 +00:00
|
|
|
const auto flags = msgId
|
2023-01-18 05:27:47 +00:00
|
|
|
? (Flag::f_peer | Flag::f_id)
|
2022-11-12 11:13:23 +00:00
|
|
|
: !text.text.isEmpty()
|
|
|
|
? Flag::f_text
|
|
|
|
: Flag(0);
|
|
|
|
|
|
|
|
const auto &stLabel = st::aboutLabel;
|
|
|
|
const auto lineHeight = stLabel.style.lineHeight;
|
|
|
|
|
|
|
|
Settings::AddSkip(container);
|
2022-11-12 18:13:15 +00:00
|
|
|
// Settings::AddSubsectionTitle(
|
|
|
|
// container,
|
|
|
|
// tr::lng_translate_box_original());
|
2022-11-12 11:13:23 +00:00
|
|
|
|
|
|
|
const auto original = box->addRow(object_ptr<SlideWrap<FlatLabel>>(
|
|
|
|
box,
|
|
|
|
object_ptr<FlatLabel>(box, stLabel)));
|
|
|
|
{
|
2022-11-27 13:12:30 +00:00
|
|
|
if (hasCopyRestriction) {
|
|
|
|
original->entity()->setContextMenuHook([](auto&&) {
|
|
|
|
});
|
|
|
|
}
|
2022-12-08 14:59:53 +00:00
|
|
|
original->entity()->setMarkedText(
|
|
|
|
text,
|
|
|
|
Core::MarkedTextContext{
|
|
|
|
.session = &peer->session(),
|
|
|
|
.customEmojiRepaint = [=] { original->entity()->update(); },
|
|
|
|
});
|
2022-11-12 11:13:23 +00:00
|
|
|
original->setMinimalHeight(lineHeight);
|
|
|
|
original->hide(anim::type::instant);
|
|
|
|
|
|
|
|
const auto show = Ui::CreateChild<FadeWrap<ShowButton>>(
|
|
|
|
container.get(),
|
|
|
|
object_ptr<ShowButton>(container));
|
|
|
|
show->hide(anim::type::instant);
|
2022-11-16 09:42:47 +00:00
|
|
|
rpl::combine(
|
|
|
|
container->widthValue(),
|
|
|
|
original->geometryValue()
|
|
|
|
) | rpl::start_with_next([=](int width, const QRect &rect) {
|
2022-11-12 11:13:23 +00:00
|
|
|
show->moveToLeft(
|
2022-11-16 09:42:47 +00:00
|
|
|
width - show->width() - st::boxRowPadding.right(),
|
2022-11-12 11:13:23 +00:00
|
|
|
rect.y() + std::abs(lineHeight - show->height()) / 2);
|
|
|
|
}, show->lifetime());
|
|
|
|
original->entity()->heightValue(
|
|
|
|
) | rpl::filter([](int height) {
|
|
|
|
return height > 0;
|
|
|
|
}) | rpl::take(1) | rpl::start_with_next([=](int height) {
|
|
|
|
if (height > lineHeight) {
|
|
|
|
show->show(anim::type::instant);
|
|
|
|
}
|
|
|
|
}, show->lifetime());
|
|
|
|
show->toggleOn(show->entity()->clicks() | rpl::map_to(false));
|
|
|
|
original->toggleOn(show->entity()->clicks() | rpl::map_to(true));
|
|
|
|
}
|
|
|
|
Settings::AddSkip(container);
|
|
|
|
Settings::AddSkip(container);
|
|
|
|
Settings::AddDivider(container);
|
|
|
|
Settings::AddSkip(container);
|
|
|
|
|
2022-11-12 18:13:15 +00:00
|
|
|
{
|
|
|
|
const auto padding = st::settingsSubsectionTitlePadding;
|
|
|
|
const auto subtitle = Settings::AddSubsectionTitle(
|
|
|
|
container,
|
2023-02-07 12:29:34 +00:00
|
|
|
state->to.value() | rpl::map(LanguageName));
|
2022-11-12 18:13:15 +00:00
|
|
|
|
|
|
|
// Workaround.
|
2023-02-07 12:29:34 +00:00
|
|
|
state->to.value() | rpl::start_with_next([=] {
|
2022-11-12 18:13:15 +00:00
|
|
|
subtitle->resizeToWidth(container->width()
|
|
|
|
- padding.left()
|
|
|
|
- padding.right());
|
|
|
|
}, subtitle->lifetime());
|
|
|
|
}
|
2022-11-12 11:13:23 +00:00
|
|
|
|
|
|
|
const auto translated = box->addRow(object_ptr<SlideWrap<FlatLabel>>(
|
|
|
|
box,
|
|
|
|
object_ptr<FlatLabel>(box, stLabel)));
|
2022-11-27 13:12:30 +00:00
|
|
|
translated->entity()->setSelectable(!hasCopyRestriction);
|
2022-11-12 11:13:23 +00:00
|
|
|
|
2022-11-12 16:34:41 +00:00
|
|
|
constexpr auto kMaxLines = 3;
|
|
|
|
container->resizeToWidth(box->width());
|
|
|
|
const auto loading = box->addRow(object_ptr<SlideWrap<RpWidget>>(
|
|
|
|
box,
|
|
|
|
CreateLoadingTextWidget(
|
|
|
|
box,
|
|
|
|
st::aboutLabel,
|
|
|
|
std::min(original->entity()->height() / lineHeight, kMaxLines),
|
2023-02-07 12:29:34 +00:00
|
|
|
state->to.value() | rpl::map([=](LanguageId id) {
|
2023-01-30 10:10:42 +00:00
|
|
|
return id.locale().textDirection() == Qt::RightToLeft;
|
2022-11-12 18:13:15 +00:00
|
|
|
}))));
|
2022-11-12 16:34:41 +00:00
|
|
|
|
2023-02-07 16:06:16 +00:00
|
|
|
const auto showText = [=](TextWithEntities text) {
|
|
|
|
const auto label = translated->entity();
|
|
|
|
label->setMarkedText(
|
|
|
|
text,
|
|
|
|
Core::MarkedTextContext{
|
|
|
|
.session = &peer->session(),
|
|
|
|
.customEmojiRepaint = [=] { label->update(); },
|
|
|
|
});
|
2022-11-12 16:34:41 +00:00
|
|
|
translated->show(anim::type::instant);
|
|
|
|
loading->hide(anim::type::instant);
|
2022-11-12 11:13:23 +00:00
|
|
|
};
|
|
|
|
|
2023-01-30 10:10:42 +00:00
|
|
|
const auto send = [=](LanguageId to) {
|
|
|
|
loading->show(anim::type::instant);
|
|
|
|
translated->hide(anim::type::instant);
|
2023-02-07 12:29:34 +00:00
|
|
|
state->api.request(MTPmessages_TranslateText(
|
2022-11-12 18:13:15 +00:00
|
|
|
MTP_flags(flags),
|
|
|
|
msgId ? peer->input : MTP_inputPeerEmpty(),
|
2023-01-18 05:27:47 +00:00
|
|
|
(msgId
|
|
|
|
? MTP_vector<MTPint>(1, MTP_int(msgId))
|
|
|
|
: MTPVector<MTPint>()),
|
|
|
|
(msgId
|
|
|
|
? MTPVector<MTPTextWithEntities>()
|
|
|
|
: MTP_vector<MTPTextWithEntities>(1, MTP_textWithEntities(
|
|
|
|
MTP_string(text.text),
|
2023-02-07 16:06:16 +00:00
|
|
|
Api::EntitiesToMTP(
|
|
|
|
&peer->session(),
|
|
|
|
text.entities,
|
|
|
|
Api::ConvertOption::SkipLocal)))),
|
2023-02-07 12:29:34 +00:00
|
|
|
MTP_string(to.twoLetterCode())
|
2022-11-12 18:13:15 +00:00
|
|
|
)).done([=](const MTPmessages_TranslatedText &result) {
|
2023-01-18 05:27:47 +00:00
|
|
|
const auto &data = result.data();
|
|
|
|
const auto &list = data.vresult().v;
|
2023-02-07 16:06:16 +00:00
|
|
|
if (list.isEmpty()) {
|
|
|
|
showText(
|
|
|
|
Ui::Text::Italic(tr::lng_translate_box_error(tr::now)));
|
|
|
|
} else {
|
|
|
|
showText(TextWithEntities{
|
|
|
|
.text = qs(list.front().data().vtext()),
|
|
|
|
.entities = Api::EntitiesFromMTP(
|
|
|
|
&peer->session(),
|
|
|
|
list.front().data().ventities().v),
|
|
|
|
});
|
|
|
|
}
|
2022-11-12 18:13:15 +00:00
|
|
|
}).fail([=](const MTP::Error &error) {
|
2023-02-07 16:06:16 +00:00
|
|
|
showText(
|
|
|
|
Ui::Text::Italic(tr::lng_translate_box_error(tr::now)));
|
2022-11-12 18:13:15 +00:00
|
|
|
}).send();
|
|
|
|
};
|
2023-02-07 12:29:34 +00:00
|
|
|
state->to.value() | rpl::start_with_next(send, box->lifetime());
|
2022-11-12 18:13:15 +00:00
|
|
|
|
|
|
|
box->addLeftButton(tr::lng_settings_language(), [=] {
|
|
|
|
if (loading->toggled()) {
|
|
|
|
return;
|
|
|
|
}
|
2023-05-02 09:33:19 +00:00
|
|
|
box->uiShow()->showBox(ChooseTranslateToBox(
|
2023-02-07 12:29:34 +00:00
|
|
|
state->to.current(),
|
|
|
|
crl::guard(box, [=](LanguageId id) { state->to = id; })));
|
2022-11-12 18:13:15 +00:00
|
|
|
});
|
2022-11-12 11:13:23 +00:00
|
|
|
}
|
|
|
|
|
2022-11-18 14:00:19 +00:00
|
|
|
bool SkipTranslate(TextWithEntities textWithEntities) {
|
|
|
|
const auto &text = textWithEntities.text;
|
|
|
|
if (text.isEmpty()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (!Core::App().settings().translateButtonEnabled()) {
|
|
|
|
return true;
|
|
|
|
}
|
2022-11-23 15:50:50 +00:00
|
|
|
constexpr auto kFirstChunk = size_t(100);
|
|
|
|
auto hasLetters = (text.size() >= kFirstChunk);
|
2022-11-18 14:00:19 +00:00
|
|
|
for (auto i = 0; i < kFirstChunk; i++) {
|
|
|
|
if (i >= text.size()) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (text.at(i).isLetter()) {
|
|
|
|
hasLetters = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!hasLetters) {
|
|
|
|
return true;
|
|
|
|
}
|
2022-12-06 22:45:14 +00:00
|
|
|
#ifndef TDESKTOP_DISABLE_SPELLCHECK
|
2022-11-18 14:00:19 +00:00
|
|
|
const auto result = Platform::Language::Recognize(text);
|
2023-01-26 15:36:43 +00:00
|
|
|
const auto skip = Core::App().settings().skipTranslationLanguages();
|
|
|
|
return result.known() && ranges::contains(skip, result);
|
2022-12-06 22:45:14 +00:00
|
|
|
#else
|
|
|
|
return false;
|
|
|
|
#endif
|
2022-11-18 14:00:19 +00:00
|
|
|
}
|
|
|
|
|
2023-01-30 10:10:42 +00:00
|
|
|
object_ptr<BoxContent> EditSkipTranslationLanguages() {
|
|
|
|
auto title = tr::lng_translate_settings_choose();
|
2023-02-07 14:38:10 +00:00
|
|
|
const auto selected = std::make_shared<std::vector<LanguageId>>(
|
|
|
|
Core::App().settings().skipTranslationLanguages());
|
|
|
|
const auto weak = std::make_shared<QPointer<BoxContent>>();
|
|
|
|
const auto check = [=](LanguageId id) {
|
|
|
|
const auto already = ranges::contains(*selected, id);
|
|
|
|
if (already) {
|
|
|
|
selected->erase(ranges::remove(*selected, id), selected->end());
|
|
|
|
} else {
|
|
|
|
selected->push_back(id);
|
|
|
|
}
|
|
|
|
if (already && selected->empty()) {
|
|
|
|
if (const auto strong = weak->data()) {
|
2023-05-02 09:33:19 +00:00
|
|
|
strong->showToast(
|
|
|
|
tr::lng_translate_settings_one(tr::now),
|
|
|
|
kSkipAtLeastOneDuration);
|
2023-02-07 14:38:10 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
auto result = Box(ChooseLanguageBox, std::move(title), [=](
|
2023-01-30 10:10:42 +00:00
|
|
|
std::vector<LanguageId> &&list) {
|
|
|
|
Core::App().settings().setSkipTranslationLanguages(
|
|
|
|
std::move(list));
|
|
|
|
Core::App().saveSettingsDelayed();
|
2023-02-07 14:38:10 +00:00
|
|
|
}, *selected, true, check);
|
|
|
|
*weak = result.data();
|
|
|
|
return result;
|
2023-01-30 10:10:42 +00:00
|
|
|
}
|
|
|
|
|
2023-02-07 12:29:34 +00:00
|
|
|
object_ptr<BoxContent> ChooseTranslateToBox(
|
|
|
|
LanguageId bringUp,
|
|
|
|
Fn<void(LanguageId)> callback) {
|
2023-02-07 15:45:18 +00:00
|
|
|
auto &settings = Core::App().settings();
|
2023-02-07 12:29:34 +00:00
|
|
|
auto selected = std::vector<LanguageId>{
|
2023-02-07 15:45:18 +00:00
|
|
|
settings.translateTo(),
|
2023-02-03 07:20:09 +00:00
|
|
|
};
|
2023-02-07 15:45:18 +00:00
|
|
|
for (const auto &id : settings.skipTranslationLanguages()) {
|
|
|
|
if (id != selected.front()) {
|
|
|
|
selected.push_back(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (bringUp && ranges::contains(selected, bringUp)) {
|
2023-02-07 12:29:34 +00:00
|
|
|
selected.push_back(bringUp);
|
|
|
|
}
|
2023-01-30 10:10:42 +00:00
|
|
|
return Box(ChooseLanguageBox, tr::lng_languages(), [=](
|
|
|
|
const std::vector<LanguageId> &ids) {
|
|
|
|
Expects(!ids.empty());
|
|
|
|
|
2023-02-07 12:29:34 +00:00
|
|
|
const auto id = ids.front();
|
|
|
|
Core::App().settings().setTranslateTo(id);
|
2023-01-30 10:10:42 +00:00
|
|
|
Core::App().saveSettingsDelayed();
|
2023-02-07 12:29:34 +00:00
|
|
|
callback(id);
|
2023-02-07 14:38:10 +00:00
|
|
|
}, selected, false, nullptr);
|
2023-01-30 10:10:42 +00:00
|
|
|
}
|
|
|
|
|
2023-02-07 12:29:34 +00:00
|
|
|
LanguageId ChooseTranslateTo(not_null<History*> history) {
|
|
|
|
return ChooseTranslateTo(history->translateOfferedFrom());
|
|
|
|
}
|
|
|
|
|
|
|
|
LanguageId ChooseTranslateTo(LanguageId offeredFrom) {
|
|
|
|
auto &settings = Core::App().settings();
|
|
|
|
return ChooseTranslateTo(
|
|
|
|
offeredFrom,
|
|
|
|
settings.translateTo(),
|
|
|
|
settings.skipTranslationLanguages());
|
|
|
|
}
|
|
|
|
|
|
|
|
LanguageId ChooseTranslateTo(
|
|
|
|
not_null<History*> history,
|
|
|
|
LanguageId savedTo,
|
|
|
|
const std::vector<LanguageId> &skip) {
|
|
|
|
return ChooseTranslateTo(history->translateOfferedFrom(), savedTo, skip);
|
|
|
|
}
|
|
|
|
|
|
|
|
LanguageId ChooseTranslateTo(
|
|
|
|
LanguageId offeredFrom,
|
|
|
|
LanguageId savedTo,
|
|
|
|
const std::vector<LanguageId> &skip) {
|
|
|
|
return (offeredFrom != savedTo) ? savedTo : skip.front();
|
|
|
|
}
|
|
|
|
|
2022-11-12 11:13:23 +00:00
|
|
|
} // namespace Ui
|