choose_language_box.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382
  1. /*
  2. This file is part of Telegram Desktop,
  3. the official desktop application for the Telegram messaging service.
  4. For license and copyright information please follow this link:
  5. https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
  6. */
  7. #include "ui/boxes/choose_language_box.h"
  8. #include "lang/lang_keys.h"
  9. #include "spellcheck/spellcheck_types.h"
  10. #include "ui/layers/generic_box.h"
  11. #include "ui/widgets/buttons.h"
  12. #include "ui/widgets/multi_select.h"
  13. #include "ui/wrap/slide_wrap.h"
  14. #include "ui/painter.h"
  15. #include "base/debug_log.h"
  16. #include "styles/style_info.h"
  17. #include "styles/style_layers.h"
  18. namespace Ui {
  19. namespace {
  20. const auto kLanguageNamePrefix = "cloud_lng_language_";
  21. const auto kTranslateToPrefix = "cloud_lng_translate_to_";
  22. [[nodiscard]] std::vector<LanguageId> TranslationLanguagesList() {
  23. // If adding some languages here you need to check that it is
  24. // supported on the server. Right now server supports those:
  25. //
  26. // 'af', 'sq', 'am', 'ar', 'hy', 'az', 'eu', 'be', 'bn', 'bs', 'bg',
  27. // 'ca', 'ceb', 'zh-CN', 'zh', 'zh-TW', 'co', 'hr', 'cs', 'da', 'nl',
  28. // 'en', 'eo', 'et', 'fi', 'fr', 'fy', 'gl', 'ka', 'de', 'el', 'gu',
  29. // 'ht', 'ha', 'haw', 'he', 'iw', 'hi', 'hmn', 'hu', 'is', 'ig', 'id',
  30. // 'ga', 'it', 'ja', 'jv', 'kn', 'kk', 'km', 'rw', 'ko', 'ku', 'ky',
  31. // 'lo', 'la', 'lv', 'lt', 'lb', 'mk', 'mg', 'ms', 'ml', 'mt', 'mi',
  32. // 'mr', 'mn', 'my', 'ne', 'no', 'ny', 'or', 'ps', 'fa', 'pl', 'pt',
  33. // 'pa', 'ro', 'ru', 'sm', 'gd', 'sr', 'st', 'sn', 'sd', 'si', 'sk',
  34. // 'sl', 'so', 'es', 'su', 'sw', 'sv', 'tl', 'tg', 'ta', 'tt', 'te',
  35. // 'th', 'tr', 'tk', 'uk', 'ur', 'ug', 'uz', 'vi', 'cy', 'xh', 'yi',
  36. // 'yo', 'zu',
  37. return {
  38. { QLocale::English },
  39. { QLocale::Arabic },
  40. { QLocale::Belarusian },
  41. { QLocale::Catalan },
  42. { QLocale::Chinese },
  43. { QLocale::Dutch },
  44. { QLocale::French },
  45. { QLocale::German },
  46. { QLocale::Indonesian },
  47. { QLocale::Italian },
  48. { QLocale::Japanese },
  49. { QLocale::Korean },
  50. { QLocale::Polish },
  51. { QLocale::Portuguese },
  52. { QLocale::Russian },
  53. { QLocale::Spanish },
  54. { QLocale::Ukrainian },
  55. { QLocale::Afrikaans },
  56. { QLocale::Albanian },
  57. { QLocale::Amharic },
  58. { QLocale::Armenian },
  59. { QLocale::Azerbaijani },
  60. { QLocale::Basque },
  61. { QLocale::Bosnian },
  62. { QLocale::Bulgarian },
  63. { QLocale::Burmese },
  64. { QLocale::Croatian },
  65. { QLocale::Czech },
  66. { QLocale::Danish },
  67. { QLocale::Esperanto },
  68. { QLocale::Estonian },
  69. { QLocale::Finnish },
  70. { QLocale::Gaelic },
  71. { QLocale::Galician },
  72. { QLocale::Georgian },
  73. { QLocale::Greek },
  74. { QLocale::Gusii },
  75. { QLocale::Hausa },
  76. { QLocale::Hebrew },
  77. { QLocale::Hungarian },
  78. { QLocale::Icelandic },
  79. { QLocale::Igbo },
  80. { QLocale::Irish },
  81. { QLocale::Kazakh },
  82. { QLocale::Kinyarwanda },
  83. { QLocale::Kurdish },
  84. { QLocale::Lao },
  85. { QLocale::Latvian },
  86. { QLocale::Lithuanian },
  87. { QLocale::Luxembourgish },
  88. { QLocale::Macedonian },
  89. { QLocale::Malagasy },
  90. { QLocale::Malay },
  91. { QLocale::Maltese },
  92. { QLocale::Maori },
  93. { QLocale::Mongolian },
  94. { QLocale::Nepali },
  95. { QLocale::Pashto },
  96. { QLocale::Persian },
  97. { QLocale::Romanian },
  98. { QLocale::Serbian },
  99. { QLocale::Shona },
  100. { QLocale::Sindhi },
  101. { QLocale::Sinhala },
  102. { QLocale::Slovak },
  103. { QLocale::Slovenian },
  104. { QLocale::Somali },
  105. { QLocale::Sundanese },
  106. { QLocale::Swahili },
  107. { QLocale::Swedish },
  108. { QLocale::Tajik },
  109. { QLocale::Tatar },
  110. { QLocale::Teso },
  111. { QLocale::Thai },
  112. { QLocale::Turkish },
  113. { QLocale::Turkmen },
  114. { QLocale::Urdu },
  115. { QLocale::Uzbek },
  116. { QLocale::Vietnamese },
  117. { QLocale::Welsh },
  118. { QLocale::WesternFrisian },
  119. { QLocale::Xhosa },
  120. { QLocale::Yiddish },
  121. };
  122. }
  123. class Row final : public SettingsButton {
  124. public:
  125. Row(not_null<RpWidget*> parent, LanguageId id);
  126. [[nodiscard]] bool filtered(const QString &query) const;
  127. [[nodiscard]] LanguageId id() const;
  128. int resizeGetHeight(int newWidth) override;
  129. protected:
  130. void paintEvent(QPaintEvent *e) override;
  131. private:
  132. const style::PeerListItem &_st;
  133. const LanguageId _id;
  134. const QString _status;
  135. const QString _titleText;
  136. Text::String _title;
  137. };
  138. Row::Row(not_null<RpWidget*> parent, LanguageId id)
  139. : SettingsButton(parent, rpl::never<QString>())
  140. , _st(st::inviteLinkListItem)
  141. , _id(id)
  142. , _status(LanguageName(id))
  143. , _titleText(LanguageNameNative(id))
  144. , _title(_st.nameStyle, _titleText) {
  145. }
  146. LanguageId Row::id() const {
  147. return _id;
  148. }
  149. bool Row::filtered(const QString &query) const {
  150. return _status.startsWith(query, Qt::CaseInsensitive)
  151. || _titleText.startsWith(query, Qt::CaseInsensitive);
  152. }
  153. int Row::resizeGetHeight(int newWidth) {
  154. return _st.height;
  155. }
  156. void Row::paintEvent(QPaintEvent *e) {
  157. auto p = Painter(this);
  158. const auto paintOver = (isOver() || isDown()) && !isDisabled();
  159. SettingsButton::paintBg(p, e->rect(), paintOver);
  160. SettingsButton::paintRipple(p, 0, 0);
  161. SettingsButton::paintToggle(p, width());
  162. const auto &color = st::windowSubTextFg;
  163. p.setPen(Qt::NoPen);
  164. p.setBrush(color);
  165. const auto left = st::defaultSubsectionTitlePadding.left();
  166. const auto toggleRect = SettingsButton::maybeToggleRect();
  167. const auto right = left
  168. + (toggleRect.isEmpty() ? 0 : (width() - toggleRect.x()));
  169. const auto availableWidth = std::min(
  170. _title.maxWidth(),
  171. width() - left - right);
  172. p.setPen(_st.nameFg);
  173. _title.drawLeft(
  174. p,
  175. left,
  176. _st.namePosition.y(),
  177. availableWidth,
  178. width() - left - right);
  179. p.setPen(paintOver ? _st.statusFgOver : _st.statusFg);
  180. p.setFont(st::contactsStatusFont);
  181. p.drawTextLeft(
  182. left,
  183. _st.statusPosition.y(),
  184. width() - left - right,
  185. _status);
  186. }
  187. } // namespace
  188. QString LanguageNameTranslated(const QString &twoLetterCode) {
  189. return Lang::GetNonDefaultValue(
  190. kLanguageNamePrefix + twoLetterCode.toUtf8());
  191. }
  192. QString LanguageNameLocal(LanguageId id) {
  193. return QLocale::languageToString(id.language());
  194. }
  195. QString LanguageName(LanguageId id) {
  196. const auto translated = LanguageNameTranslated(id.twoLetterCode());
  197. return translated.isEmpty() ? LanguageNameLocal(id) : translated;
  198. }
  199. QString LanguageNameNative(LanguageId id) {
  200. const auto locale = id.locale();
  201. if (locale.language() == QLocale::English
  202. && (locale.country() == QLocale::UnitedStates
  203. || locale.country() == QLocale::AnyCountry)) {
  204. return u"English"_q;
  205. } else if (locale.language() == QLocale::Spanish) {
  206. return QString::fromUtf8("\x45\x73\x70\x61\xc3\xb1\x6f\x6c");
  207. } else {
  208. const auto name = locale.nativeLanguageName();
  209. return name.left(1).toUpper() + name.mid(1);
  210. }
  211. }
  212. rpl::producer<QString> TranslateBarTo(LanguageId id) {
  213. const auto translated = Lang::GetNonDefaultValue(
  214. kTranslateToPrefix + id.twoLetterCode().toUtf8());
  215. return (translated.isEmpty()
  216. ? tr::lng_translate_bar_to_other
  217. : tr::lng_translate_bar_to)(
  218. lt_name,
  219. rpl::single(translated.isEmpty()
  220. ? LanguageNameLocal(id)
  221. : translated));
  222. }
  223. QString TranslateMenuDont(tr::now_t, LanguageId id) {
  224. const auto translated = Lang::GetNonDefaultValue(
  225. kTranslateToPrefix + id.twoLetterCode().toUtf8());
  226. return (translated.isEmpty()
  227. ? tr::lng_translate_menu_dont_other
  228. : tr::lng_translate_menu_dont)(
  229. tr::now,
  230. lt_name,
  231. translated.isEmpty() ? LanguageNameLocal(id) : translated);
  232. }
  233. void ChooseLanguageBox(
  234. not_null<GenericBox*> box,
  235. rpl::producer<QString> title,
  236. Fn<void(std::vector<LanguageId>)> callback,
  237. std::vector<LanguageId> selected,
  238. bool multiselect,
  239. Fn<bool(LanguageId)> toggleCheck) {
  240. box->setMinHeight(st::boxWidth);
  241. box->setMaxHeight(st::boxWidth);
  242. box->setTitle(std::move(title));
  243. const auto multiSelect = box->setPinnedToTopContent(
  244. object_ptr<MultiSelect>(
  245. box,
  246. st::defaultMultiSelect,
  247. tr::lng_participant_filter()));
  248. box->setFocusCallback([=] { multiSelect->setInnerFocus(); });
  249. const auto container = box->verticalLayout();
  250. const auto langs = [&] {
  251. auto list = TranslationLanguagesList();
  252. for (const auto id : list) {
  253. LOG(("cloud_lng_language_%1").arg(id.twoLetterCode()));
  254. }
  255. const auto current = LanguageId{ QLocale(
  256. Lang::LanguageIdOrDefault(Lang::Id())).language() };
  257. if (const auto i = ranges::find(list, current); i != end(list)) {
  258. base::reorder(list, std::distance(begin(list), i), 0);
  259. }
  260. ranges::stable_partition(list, [&](LanguageId id) {
  261. return ranges::contains(selected, id);
  262. });
  263. return list;
  264. }();
  265. struct ToggleOne {
  266. LanguageId id;
  267. bool selected = false;
  268. };
  269. struct State {
  270. rpl::event_stream<ToggleOne> toggles;
  271. };
  272. const auto state = box->lifetime().make_state<State>();
  273. auto rows = std::vector<not_null<SlideWrap<Row>*>>();
  274. rows.reserve(langs.size());
  275. for (const auto &id : langs) {
  276. const auto button = container->add(
  277. object_ptr<SlideWrap<Row>>(
  278. container,
  279. object_ptr<Row>(container, id)));
  280. if (multiselect) {
  281. button->entity()->toggleOn(rpl::single(
  282. ranges::contains(selected, id)
  283. ) | rpl::then(state->toggles.events(
  284. ) | rpl::filter([=](ToggleOne one) {
  285. return one.id == id;
  286. }) | rpl::map([=](ToggleOne one) {
  287. return one.selected;
  288. })));
  289. button->entity()->toggledChanges(
  290. ) | rpl::start_with_next([=](bool value) {
  291. if (toggleCheck && !toggleCheck(id)) {
  292. state->toggles.fire({ .id = id, .selected = !value });
  293. }
  294. }, button->lifetime());
  295. } else {
  296. button->entity()->setClickedCallback([=] {
  297. callback({ id });
  298. box->closeBox();
  299. });
  300. }
  301. rows.push_back(button);
  302. }
  303. multiSelect->setQueryChangedCallback([=](const QString &query) {
  304. for (const auto &row : rows) {
  305. const auto toggled = row->entity()->filtered(query);
  306. if (toggled != row->toggled()) {
  307. row->toggle(toggled, anim::type::instant);
  308. }
  309. }
  310. });
  311. {
  312. const auto label = CreateChild<FlatLabel>(
  313. box.get(),
  314. tr::lng_languages_none(),
  315. st::membersAbout);
  316. box->verticalLayout()->geometryValue(
  317. ) | rpl::start_with_next([=](const QRect &geometry) {
  318. const auto shown = (geometry.height() <= 0);
  319. label->setVisible(shown);
  320. if (shown) {
  321. label->moveToLeft(
  322. (geometry.width() - label->width()) / 2,
  323. geometry.y() + st::membersAbout.style.font->height * 4);
  324. label->stackUnder(box->verticalLayout());
  325. }
  326. }, label->lifetime());
  327. }
  328. if (multiselect) {
  329. box->addButton(tr::lng_settings_save(), [=] {
  330. auto result = ranges::views::all(
  331. rows
  332. ) | ranges::views::filter([](const auto &row) {
  333. return row->entity()->toggled();
  334. }) | ranges::views::transform([](const auto &row) {
  335. return row->entity()->id();
  336. }) | ranges::to_vector;
  337. if (!result.empty()) {
  338. callback(std::move(result));
  339. }
  340. box->closeBox();
  341. });
  342. }
  343. box->addButton(tr::lng_cancel(), [=] { box->closeBox(); });
  344. }
  345. } // namespace Ui