/* 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 "ui/boxes/choose_language_box.h" #include "lang/lang_keys.h" #include "spellcheck/spellcheck_types.h" #include "ui/layers/generic_box.h" #include "ui/widgets/buttons.h" #include "ui/widgets/multi_select.h" #include "ui/wrap/slide_wrap.h" #include "ui/painter.h" #include "base/debug_log.h" #include "styles/style_info.h" #include "styles/style_layers.h" #include "styles/style_settings.h" namespace Ui { namespace { const auto kLanguageNamePrefix = "cloud_lng_language_"; const auto kTranslateToPrefix = "cloud_lng_translate_to_"; [[nodiscard]] std::vector TranslationLanguagesList() { // If adding some languages here you need to check that it is // supported on the server. Right now server supports those: // // 'af', 'sq', 'am', 'ar', 'hy', 'az', 'eu', 'be', 'bn', 'bs', 'bg', // 'ca', 'ceb', 'zh-CN', 'zh', 'zh-TW', 'co', 'hr', 'cs', 'da', 'nl', // 'en', 'eo', 'et', 'fi', 'fr', 'fy', 'gl', 'ka', 'de', 'el', 'gu', // 'ht', 'ha', 'haw', 'he', 'iw', 'hi', 'hmn', 'hu', 'is', 'ig', 'id', // 'ga', 'it', 'ja', 'jv', 'kn', 'kk', 'km', 'rw', 'ko', 'ku', 'ky', // 'lo', 'la', 'lv', 'lt', 'lb', 'mk', 'mg', 'ms', 'ml', 'mt', 'mi', // 'mr', 'mn', 'my', 'ne', 'no', 'ny', 'or', 'ps', 'fa', 'pl', 'pt', // 'pa', 'ro', 'ru', 'sm', 'gd', 'sr', 'st', 'sn', 'sd', 'si', 'sk', // 'sl', 'so', 'es', 'su', 'sw', 'sv', 'tl', 'tg', 'ta', 'tt', 'te', // 'th', 'tr', 'tk', 'uk', 'ur', 'ug', 'uz', 'vi', 'cy', 'xh', 'yi', // 'yo', 'zu', return { { QLocale::English }, { QLocale::Arabic }, { QLocale::Belarusian }, { QLocale::Catalan }, { QLocale::Chinese }, { QLocale::Dutch }, { QLocale::French }, { QLocale::German }, { QLocale::Indonesian }, { QLocale::Italian }, { QLocale::Japanese }, { QLocale::Korean }, { QLocale::Polish }, { QLocale::Portuguese }, { QLocale::Russian }, { QLocale::Spanish }, { QLocale::Ukrainian }, { QLocale::Afrikaans }, { QLocale::Albanian }, { QLocale::Amharic }, { QLocale::Armenian }, { QLocale::Azerbaijani }, { QLocale::Basque }, { QLocale::Bosnian }, { QLocale::Bulgarian }, { QLocale::Burmese }, { QLocale::Croatian }, { QLocale::Czech }, { QLocale::Danish }, { QLocale::Esperanto }, { QLocale::Estonian }, { QLocale::Finnish }, { QLocale::Gaelic }, { QLocale::Galician }, { QLocale::Georgian }, { QLocale::Greek }, { QLocale::Gusii }, { QLocale::Hausa }, { QLocale::Hebrew }, { QLocale::Hungarian }, { QLocale::Icelandic }, { QLocale::Igbo }, { QLocale::Irish }, { QLocale::Kazakh }, { QLocale::Kinyarwanda }, { QLocale::Kurdish }, { QLocale::Lao }, { QLocale::Latvian }, { QLocale::Lithuanian }, { QLocale::Luxembourgish }, { QLocale::Macedonian }, { QLocale::Malagasy }, { QLocale::Malay }, { QLocale::Maltese }, { QLocale::Maori }, { QLocale::Mongolian }, { QLocale::Nepali }, { QLocale::Pashto }, { QLocale::Persian }, { QLocale::Romanian }, { QLocale::Serbian }, { QLocale::Shona }, { QLocale::Sindhi }, { QLocale::Sinhala }, { QLocale::Slovak }, { QLocale::Slovenian }, { QLocale::Somali }, { QLocale::Sundanese }, { QLocale::Swahili }, { QLocale::Swedish }, { QLocale::Tajik }, { QLocale::Tatar }, { QLocale::Teso }, { QLocale::Thai }, { QLocale::Turkish }, { QLocale::Turkmen }, { QLocale::Urdu }, { QLocale::Uzbek }, { QLocale::Vietnamese }, { QLocale::Welsh }, { QLocale::WesternFrisian }, { QLocale::Xhosa }, { QLocale::Yiddish }, }; } class Row final : public SettingsButton { public: Row(not_null parent, LanguageId id); [[nodiscard]] bool filtered(const QString &query) const; [[nodiscard]] LanguageId id() const; int resizeGetHeight(int newWidth) override; protected: void paintEvent(QPaintEvent *e) override; private: const style::PeerListItem &_st; const LanguageId _id; const QString _status; const QString _titleText; Text::String _title; }; Row::Row(not_null parent, LanguageId id) : SettingsButton(parent, rpl::never()) , _st(st::inviteLinkListItem) , _id(id) , _status(LanguageName(id)) , _titleText(LanguageNameNative(id)) , _title(_st.nameStyle, _titleText) { } LanguageId Row::id() const { return _id; } bool Row::filtered(const QString &query) const { return _status.startsWith(query, Qt::CaseInsensitive) || _titleText.startsWith(query, Qt::CaseInsensitive); } int Row::resizeGetHeight(int newWidth) { return _st.height; } void Row::paintEvent(QPaintEvent *e) { auto p = Painter(this); const auto paintOver = (isOver() || isDown()) && !isDisabled(); SettingsButton::paintBg(p, e->rect(), paintOver); SettingsButton::paintRipple(p, 0, 0); SettingsButton::paintToggle(p, width()); const auto &color = st::windowSubTextFg; p.setPen(Qt::NoPen); p.setBrush(color); const auto left = st::settingsSubsectionTitlePadding.left(); const auto toggleRect = SettingsButton::maybeToggleRect(); const auto right = left + (toggleRect.isEmpty() ? 0 : (width() - toggleRect.x())); const auto availableWidth = std::min( _title.maxWidth(), width() - left - right); p.setPen(_st.nameFg); _title.drawLeft( p, left, _st.namePosition.y(), availableWidth, width() - left - right); p.setPen(paintOver ? _st.statusFgOver : _st.statusFg); p.setFont(st::contactsStatusFont); p.drawTextLeft( left, _st.statusPosition.y(), width() - left - right, _status); } } // namespace QString LanguageNameTranslated(const QString &twoLetterCode) { return Lang::GetNonDefaultValue( kLanguageNamePrefix + twoLetterCode.toUtf8()); } QString LanguageNameLocal(LanguageId id) { return QLocale::languageToString(id.language()); } QString LanguageName(LanguageId id) { const auto translated = LanguageNameTranslated(id.twoLetterCode()); return translated.isEmpty() ? LanguageNameLocal(id) : translated; } QString LanguageNameNative(LanguageId id) { const auto locale = id.locale(); if (locale.language() == QLocale::English && (locale.country() == QLocale::UnitedStates || locale.country() == QLocale::AnyCountry)) { return u"English"_q; } else if (locale.language() == QLocale::Spanish) { return QString::fromUtf8("\x45\x73\x70\x61\xc3\xb1\x6f\x6c"); } else { const auto name = locale.nativeLanguageName(); return name.left(1).toUpper() + name.mid(1); } } rpl::producer TranslateBarTo(LanguageId id) { const auto translated = Lang::GetNonDefaultValue( kTranslateToPrefix + id.twoLetterCode().toUtf8()); return (translated.isEmpty() ? tr::lng_translate_bar_to_other : tr::lng_translate_bar_to)( lt_name, rpl::single(translated.isEmpty() ? LanguageNameLocal(id) : translated)); } QString TranslateMenuDont(tr::now_t, LanguageId id) { const auto translated = Lang::GetNonDefaultValue( kTranslateToPrefix + id.twoLetterCode().toUtf8()); return (translated.isEmpty() ? tr::lng_translate_menu_dont_other : tr::lng_translate_menu_dont)( tr::now, lt_name, translated.isEmpty() ? LanguageNameLocal(id) : translated); } void ChooseLanguageBox( not_null box, rpl::producer title, Fn)> callback, std::vector selected, bool multiselect, Fn toggleCheck) { box->setMinHeight(st::boxWidth); box->setMaxHeight(st::boxWidth); box->setTitle(std::move(title)); const auto multiSelect = box->setPinnedToTopContent( object_ptr( box, st::defaultMultiSelect, tr::lng_participant_filter())); box->setFocusCallback([=] { multiSelect->setInnerFocus(); }); const auto container = box->verticalLayout(); const auto langs = [&] { auto list = TranslationLanguagesList(); for (const auto id : list) { LOG(("cloud_lng_language_%1").arg(id.twoLetterCode())); } const auto current = LanguageId{ QLocale( Lang::LanguageIdOrDefault(Lang::Id())).language() }; if (const auto i = ranges::find(list, current); i != end(list)) { base::reorder(list, std::distance(begin(list), i), 0); } ranges::stable_partition(list, [&](LanguageId id) { return ranges::contains(selected, id); }); return list; }(); struct ToggleOne { LanguageId id; bool selected = false; }; struct State { rpl::event_stream toggles; }; const auto state = box->lifetime().make_state(); auto rows = std::vector*>>(); rows.reserve(langs.size()); for (const auto &id : langs) { const auto button = container->add( object_ptr>( container, object_ptr(container, id))); if (multiselect) { button->entity()->toggleOn(rpl::single( ranges::contains(selected, id) ) | rpl::then(state->toggles.events( ) | rpl::filter([=](ToggleOne one) { return one.id == id; }) | rpl::map([=](ToggleOne one) { return one.selected; }))); button->entity()->toggledChanges( ) | rpl::start_with_next([=](bool value) { if (toggleCheck && !toggleCheck(id)) { state->toggles.fire({ .id = id, .selected = !value }); } }, button->lifetime()); } else { button->entity()->setClickedCallback([=] { callback({ id }); box->closeBox(); }); } rows.push_back(button); } multiSelect->setQueryChangedCallback([=](const QString &query) { for (const auto &row : rows) { const auto toggled = row->entity()->filtered(query); if (toggled != row->toggled()) { row->toggle(toggled, anim::type::instant); } } }); { const auto label = CreateChild( box.get(), tr::lng_languages_none(), st::membersAbout); box->verticalLayout()->geometryValue( ) | rpl::start_with_next([=](const QRect &geometry) { const auto shown = (geometry.height() <= 0); label->setVisible(shown); if (shown) { label->moveToLeft( (geometry.width() - label->width()) / 2, geometry.y() + st::membersAbout.style.font->height * 4); label->stackUnder(box->verticalLayout()); } }, label->lifetime()); } if (multiselect) { box->addButton(tr::lng_settings_save(), [=] { auto result = ranges::views::all( rows ) | ranges::views::filter([](const auto &row) { return row->entity()->toggled(); }) | ranges::views::transform([](const auto &row) { return row->entity()->id(); }) | ranges::to_vector; if (!result.empty()) { callback(std::move(result)); } box->closeBox(); }); } box->addButton(tr::lng_cancel(), [=] { box->closeBox(); }); } } // namespace Ui