language_box.cpp 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318
  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 "boxes/language_box.h"
  8. #include "data/data_peer_values.h"
  9. #include "lang/lang_keys.h"
  10. #include "ui/boxes/choose_language_box.h"
  11. #include "ui/widgets/checkbox.h"
  12. #include "ui/widgets/buttons.h"
  13. #include "ui/widgets/labels.h"
  14. #include "ui/widgets/multi_select.h"
  15. #include "ui/widgets/scroll_area.h"
  16. #include "ui/widgets/dropdown_menu.h"
  17. #include "ui/widgets/box_content_divider.h"
  18. #include "ui/text/text_entity.h"
  19. #include "ui/wrap/vertical_layout.h"
  20. #include "ui/wrap/slide_wrap.h"
  21. #include "ui/effects/ripple_animation.h"
  22. #include "ui/toast/toast.h"
  23. #include "ui/text/text_options.h"
  24. #include "ui/painter.h"
  25. #include "ui/vertical_list.h"
  26. #include "ui/ui_utility.h"
  27. #include "storage/localstorage.h"
  28. #include "boxes/abstract_box.h"
  29. #include "boxes/premium_preview_box.h"
  30. #include "boxes/translate_box.h"
  31. #include "ui/boxes/confirm_box.h"
  32. #include "main/main_session.h"
  33. #include "mainwidget.h"
  34. #include "mainwindow.h"
  35. #include "core/application.h"
  36. #include "lang/lang_instance.h"
  37. #include "lang/lang_cloud_manager.h"
  38. #include "settings/settings_common.h"
  39. #include "spellcheck/spellcheck_types.h"
  40. #include "window/window_session_controller.h"
  41. #include "styles/style_layers.h"
  42. #include "styles/style_boxes.h"
  43. #include "styles/style_info.h"
  44. #include "styles/style_passport.h"
  45. #include "styles/style_chat_helpers.h"
  46. #include "styles/style_menu_icons.h"
  47. #include "styles/style_settings.h"
  48. #include <QtGui/QGuiApplication>
  49. #include <QtGui/QClipboard>
  50. namespace {
  51. using Language = Lang::Language;
  52. using Languages = Lang::CloudManager::Languages;
  53. class Rows : public Ui::RpWidget {
  54. public:
  55. Rows(
  56. QWidget *parent,
  57. const Languages &data,
  58. const QString &chosen,
  59. bool areOfficial);
  60. void filter(const QString &query);
  61. int count() const;
  62. int selected() const;
  63. void setSelected(int selected);
  64. rpl::producer<bool> hasSelection() const;
  65. rpl::producer<bool> isEmpty() const;
  66. void activateSelected();
  67. rpl::producer<Language> activations() const;
  68. void changeChosen(const QString &chosen);
  69. Ui::ScrollToRequest rowScrollRequest(int index) const;
  70. static int DefaultRowHeight();
  71. protected:
  72. int resizeGetHeight(int newWidth) override;
  73. void paintEvent(QPaintEvent *e) override;
  74. void mouseMoveEvent(QMouseEvent *e) override;
  75. void mousePressEvent(QMouseEvent *e) override;
  76. void mouseReleaseEvent(QMouseEvent *e) override;
  77. void leaveEventHook(QEvent *e) override;
  78. private:
  79. struct Row {
  80. Language data;
  81. Ui::Text::String title = { st::boxWideWidth / 2 };
  82. Ui::Text::String description = { st::boxWideWidth / 2 };
  83. int top = 0;
  84. int height = 0;
  85. mutable std::unique_ptr<Ui::RippleAnimation> ripple;
  86. mutable std::unique_ptr<Ui::RippleAnimation> menuToggleRipple;
  87. bool menuToggleForceRippled = false;
  88. int titleHeight = 0;
  89. int descriptionHeight = 0;
  90. QStringList keywords;
  91. std::unique_ptr<Ui::RadioView> check;
  92. bool removed = false;
  93. };
  94. struct RowSelection {
  95. int index = 0;
  96. inline bool operator==(const RowSelection &other) const {
  97. return (index == other.index);
  98. }
  99. };
  100. struct MenuSelection {
  101. int index = 0;
  102. inline bool operator==(const MenuSelection &other) const {
  103. return (index == other.index);
  104. }
  105. };
  106. using Selection = std::variant<v::null_t, RowSelection, MenuSelection>;
  107. void updateSelected(Selection selected);
  108. void updatePressed(Selection pressed);
  109. Rows::Row &rowByIndex(int index);
  110. const Rows::Row &rowByIndex(int index) const;
  111. Rows::Row &rowBySelection(Selection selected);
  112. const Rows::Row &rowBySelection(Selection selected) const;
  113. std::unique_ptr<Ui::RippleAnimation> &rippleBySelection(
  114. Selection selected);
  115. [[maybe_unused]] const std::unique_ptr<Ui::RippleAnimation> &rippleBySelection(
  116. Selection selected) const;
  117. std::unique_ptr<Ui::RippleAnimation> &rippleBySelection(
  118. not_null<Row*> row,
  119. Selection selected);
  120. [[maybe_unused]] const std::unique_ptr<Ui::RippleAnimation> &rippleBySelection(
  121. not_null<const Row*> row,
  122. Selection selected) const;
  123. void addRipple(Selection selected, QPoint position);
  124. void ensureRippleBySelection(Selection selected);
  125. void ensureRippleBySelection(not_null<Row*> row, Selection selected);
  126. int indexFromSelection(Selection selected) const;
  127. int countAvailableWidth() const;
  128. int countAvailableWidth(int newWidth) const;
  129. QRect menuToggleArea() const;
  130. QRect menuToggleArea(not_null<const Row*> row) const;
  131. void repaint(Selection selected);
  132. void repaint(int index);
  133. void repaint(const Row &row);
  134. void repaintChecked(not_null<const Row*> row);
  135. void activateByIndex(int index);
  136. void showMenu(int index);
  137. void setForceRippled(not_null<Row*> row, bool rippled);
  138. bool canShare(not_null<const Row*> row) const;
  139. bool canRemove(not_null<const Row*> row) const;
  140. bool hasMenu(not_null<const Row*> row) const;
  141. void share(not_null<const Row*> row) const;
  142. void remove(not_null<Row*> row);
  143. void restore(not_null<Row*> row);
  144. std::vector<Row> _rows;
  145. std::vector<not_null<Row*>> _filtered;
  146. Selection _selected;
  147. Selection _pressed;
  148. QString _chosen;
  149. QStringList _query;
  150. bool _areOfficial = false;
  151. bool _mouseSelection = false;
  152. QPoint _globalMousePosition;
  153. base::unique_qptr<Ui::DropdownMenu> _menu;
  154. int _menuShownIndex = -1;
  155. bool _menuOtherEntered = false;
  156. rpl::event_stream<bool> _hasSelection;
  157. rpl::event_stream<Language> _activations;
  158. rpl::event_stream<bool> _isEmpty;
  159. };
  160. class Content : public Ui::RpWidget {
  161. public:
  162. Content(
  163. QWidget *parent,
  164. const Languages &recent,
  165. const Languages &official);
  166. Ui::ScrollToRequest jump(int rows);
  167. void filter(const QString &query);
  168. rpl::producer<Language> activations() const;
  169. void changeChosen(const QString &chosen);
  170. void activateBySubmit();
  171. private:
  172. void setupContent(
  173. const Languages &recent,
  174. const Languages &official);
  175. Fn<Ui::ScrollToRequest(int rows)> _jump;
  176. Fn<void(const QString &query)> _filter;
  177. Fn<rpl::producer<Language>()> _activations;
  178. Fn<void(const QString &chosen)> _changeChosen;
  179. Fn<void()> _activateBySubmit;
  180. };
  181. std::pair<Languages, Languages> PrepareLists() {
  182. const auto projId = [](const Language &language) {
  183. return language.id;
  184. };
  185. const auto current = Lang::LanguageIdOrDefault(Lang::Id());
  186. auto official = Lang::CurrentCloudManager().languageList();
  187. auto recent = Local::readRecentLanguages();
  188. ranges::stable_partition(recent, [&](const Language &language) {
  189. return (language.id == current);
  190. });
  191. if (recent.empty() || recent.front().id != current) {
  192. if (ranges::find(official, current, projId) == end(official)) {
  193. const auto generate = [&] {
  194. const auto name = (current == "#custom")
  195. ? "Custom lang pack"
  196. : Lang::GetInstance().name();
  197. return Language{
  198. current,
  199. QString(),
  200. QString(),
  201. name,
  202. Lang::GetInstance().nativeName()
  203. };
  204. };
  205. recent.insert(begin(recent), generate());
  206. }
  207. }
  208. auto i = begin(official), e = end(official);
  209. const auto remover = [&](const Language &language) {
  210. auto k = ranges::find(i, e, language.id, projId);
  211. if (k == e) {
  212. return false;
  213. }
  214. for (; k != i; --k) {
  215. std::swap(*k, *(k - 1));
  216. }
  217. ++i;
  218. return true;
  219. };
  220. recent.erase(ranges::remove_if(recent, remover), end(recent));
  221. return { std::move(recent), std::move(official) };
  222. }
  223. Rows::Rows(
  224. QWidget *parent,
  225. const Languages &data,
  226. const QString &chosen,
  227. bool areOfficial)
  228. : RpWidget(parent)
  229. , _chosen(chosen)
  230. , _areOfficial(areOfficial) {
  231. const auto descriptionOptions = TextParseOptions{
  232. TextParseMultiline,
  233. 0,
  234. 0,
  235. Qt::LayoutDirectionAuto
  236. };
  237. _rows.reserve(data.size());
  238. for (const auto &item : data) {
  239. _rows.push_back(Row{ item });
  240. auto &row = _rows.back();
  241. row.check = std::make_unique<Ui::RadioView>(
  242. st::langsRadio,
  243. (row.data.id == _chosen),
  244. [=, row = &row] { repaint(*row); });
  245. row.title.setText(
  246. st::semiboldTextStyle,
  247. item.nativeName,
  248. Ui::NameTextOptions());
  249. row.description.setText(
  250. st::defaultTextStyle,
  251. item.name,
  252. descriptionOptions);
  253. row.keywords = TextUtilities::PrepareSearchWords(
  254. item.name + ' ' + item.nativeName);
  255. }
  256. resizeToWidth(width());
  257. setAttribute(Qt::WA_MouseTracking);
  258. update();
  259. }
  260. void Rows::mouseMoveEvent(QMouseEvent *e) {
  261. const auto position = e->globalPos();
  262. if (_menu) {
  263. const auto rect = (_menuShownIndex >= 0)
  264. ? menuToggleArea(&rowByIndex(_menuShownIndex))
  265. : QRect();
  266. if (rect.contains(e->pos())) {
  267. if (!_menuOtherEntered) {
  268. _menuOtherEntered = true;
  269. _menu->otherEnter();
  270. }
  271. } else {
  272. if (_menuOtherEntered) {
  273. _menuOtherEntered = false;
  274. _menu->otherLeave();
  275. }
  276. }
  277. }
  278. if (!_mouseSelection && position == _globalMousePosition) {
  279. return;
  280. }
  281. _mouseSelection = true;
  282. _globalMousePosition = position;
  283. const auto index = [&] {
  284. const auto y = e->pos().y();
  285. if (y < 0) {
  286. return -1;
  287. }
  288. for (auto i = 0, till = count(); i != till; ++i) {
  289. const auto &row = rowByIndex(i);
  290. if (row.top + row.height > y) {
  291. return i;
  292. }
  293. }
  294. return -1;
  295. }();
  296. const auto row = (index >= 0) ? &rowByIndex(index) : nullptr;
  297. const auto inMenuToggle = (index >= 0 && hasMenu(row))
  298. ? menuToggleArea(row).contains(e->pos())
  299. : false;
  300. if (index < 0) {
  301. updateSelected({});
  302. } else if (inMenuToggle) {
  303. updateSelected(MenuSelection{ index });
  304. } else if (!row->removed) {
  305. updateSelected(RowSelection{ index });
  306. } else {
  307. updateSelected({});
  308. }
  309. }
  310. void Rows::mousePressEvent(QMouseEvent *e) {
  311. updatePressed(_selected);
  312. if (!v::is_null(_pressed)
  313. && !rowBySelection(_pressed).menuToggleForceRippled) {
  314. addRipple(_pressed, e->pos());
  315. }
  316. }
  317. QRect Rows::menuToggleArea() const {
  318. const auto size = st::topBarSearch.width;
  319. const auto top = (DefaultRowHeight() - size) / 2;
  320. const auto skip = st::boxScroll.width
  321. - st::boxScroll.deltax
  322. + top;
  323. const auto left = width() - skip - size;
  324. return QRect(left, top, size, size);
  325. }
  326. QRect Rows::menuToggleArea(not_null<const Row*> row) const {
  327. return menuToggleArea().translated(0, row->top);
  328. }
  329. void Rows::addRipple(Selection selected, QPoint position) {
  330. Expects(!v::is_null(selected));
  331. ensureRippleBySelection(selected);
  332. const auto menu = v::is<MenuSelection>(selected);
  333. const auto &row = rowBySelection(selected);
  334. const auto menuArea = menuToggleArea(&row);
  335. auto &ripple = rippleBySelection(&row, selected);
  336. const auto topleft = menu ? menuArea.topLeft() : QPoint(0, row.top);
  337. ripple->add(position - topleft);
  338. }
  339. void Rows::ensureRippleBySelection(Selection selected) {
  340. ensureRippleBySelection(&rowBySelection(selected), selected);
  341. }
  342. void Rows::ensureRippleBySelection(not_null<Row*> row, Selection selected) {
  343. auto &ripple = rippleBySelection(row, selected);
  344. if (ripple) {
  345. return;
  346. }
  347. const auto menu = v::is<MenuSelection>(selected);
  348. const auto menuArea = menuToggleArea(row);
  349. auto mask = menu
  350. ? Ui::RippleAnimation::EllipseMask(menuArea.size())
  351. : Ui::RippleAnimation::RectMask({ width(), row->height });
  352. ripple = std::make_unique<Ui::RippleAnimation>(
  353. st::defaultRippleAnimation,
  354. std::move(mask),
  355. [=] { repaintChecked(row); });
  356. }
  357. void Rows::mouseReleaseEvent(QMouseEvent *e) {
  358. if (_menu && e->button() == Qt::LeftButton) {
  359. if (_menu->isHiding()) {
  360. _menu->otherEnter();
  361. } else {
  362. _menu->otherLeave();
  363. }
  364. }
  365. const auto pressed = _pressed;
  366. updatePressed({});
  367. if (pressed == _selected) {
  368. v::match(pressed, [&](RowSelection data) {
  369. activateByIndex(data.index);
  370. }, [&](MenuSelection data) {
  371. showMenu(data.index);
  372. }, [](v::null_t) {});
  373. }
  374. }
  375. bool Rows::canShare(not_null<const Row*> row) const {
  376. return !_areOfficial && !row->data.id.startsWith('#');
  377. }
  378. bool Rows::canRemove(not_null<const Row*> row) const {
  379. return !_areOfficial && !row->check->checked();
  380. }
  381. bool Rows::hasMenu(not_null<const Row*> row) const {
  382. return canShare(row) || canRemove(row);
  383. }
  384. void Rows::share(not_null<const Row*> row) const {
  385. const auto link = u"https://t.me/setlanguage/"_q + row->data.id;
  386. QGuiApplication::clipboard()->setText(link);
  387. Ui::Toast::Show(tr::lng_username_copied(tr::now));
  388. }
  389. void Rows::remove(not_null<Row*> row) {
  390. row->removed = true;
  391. Local::removeRecentLanguage(row->data.id);
  392. }
  393. void Rows::restore(not_null<Row*> row) {
  394. row->removed = false;
  395. Local::saveRecentLanguages(ranges::views::all(
  396. _rows
  397. ) | ranges::views::filter([](const Row &row) {
  398. return !row.removed;
  399. }) | ranges::views::transform([](const Row &row) {
  400. return row.data;
  401. }) | ranges::to_vector);
  402. }
  403. void Rows::showMenu(int index) {
  404. const auto row = &rowByIndex(index);
  405. if (_menu || !hasMenu(row)) {
  406. return;
  407. }
  408. _menu = base::make_unique_q<Ui::DropdownMenu>(
  409. window(),
  410. st::dropdownMenuWithIcons);
  411. const auto weak = _menu.get();
  412. _menu->setHiddenCallback([=] {
  413. weak->deleteLater();
  414. if (_menu == weak) {
  415. setForceRippled(row, false);
  416. _menuShownIndex = -1;
  417. }
  418. });
  419. _menu->setShowStartCallback([=] {
  420. if (_menu == weak) {
  421. setForceRippled(row, true);
  422. _menuShownIndex = index;
  423. }
  424. });
  425. _menu->setHideStartCallback([=] {
  426. if (_menu == weak) {
  427. setForceRippled(row, false);
  428. _menuShownIndex = -1;
  429. }
  430. });
  431. const auto addAction = [&](
  432. const QString &text,
  433. Fn<void()> callback,
  434. const style::icon *icon) {
  435. return _menu->addAction(text, std::move(callback), icon);
  436. };
  437. if (canShare(row)) {
  438. addAction(
  439. tr::lng_proxy_edit_share(tr::now),
  440. [=] { share(row); },
  441. &st::menuIconShare);
  442. }
  443. if (canRemove(row)) {
  444. if (row->removed) {
  445. addAction(tr::lng_proxy_menu_restore(tr::now), [=] {
  446. restore(row);
  447. }, &st::menuIconRestore);
  448. } else {
  449. addAction(tr::lng_proxy_menu_delete(tr::now), [=] {
  450. remove(row);
  451. }, &st::menuIconDelete);
  452. }
  453. }
  454. const auto toggle = menuToggleArea(row);
  455. const auto parentTopLeft = window()->mapToGlobal(QPoint());
  456. const auto buttonTopLeft = mapToGlobal(toggle.topLeft());
  457. const auto parent = QRect(parentTopLeft, window()->size());
  458. const auto button = QRect(buttonTopLeft, toggle.size());
  459. const auto bottom = button.y()
  460. + st::proxyDropdownDownPosition.y()
  461. + _menu->height()
  462. - parent.y();
  463. const auto top = button.y()
  464. + st::proxyDropdownUpPosition.y()
  465. - _menu->height()
  466. - parent.y();
  467. _menuShownIndex = index;
  468. _menuOtherEntered = true;
  469. if (bottom > parent.height() && top >= 0) {
  470. const auto left = button.x()
  471. + button.width()
  472. + st::proxyDropdownUpPosition.x()
  473. - _menu->width()
  474. - parent.x();
  475. _menu->move(left, top);
  476. _menu->showAnimated(Ui::PanelAnimation::Origin::BottomRight);
  477. } else {
  478. const auto left = button.x()
  479. + button.width()
  480. + st::proxyDropdownDownPosition.x()
  481. - _menu->width()
  482. - parent.x();
  483. _menu->move(left, bottom - _menu->height());
  484. _menu->showAnimated(Ui::PanelAnimation::Origin::TopRight);
  485. }
  486. }
  487. void Rows::setForceRippled(not_null<Row*> row, bool rippled) {
  488. if (row->menuToggleForceRippled != rippled) {
  489. row->menuToggleForceRippled = rippled;
  490. auto &ripple = rippleBySelection(row, MenuSelection{});
  491. if (row->menuToggleForceRippled) {
  492. ensureRippleBySelection(row, MenuSelection{});
  493. if (ripple->empty()) {
  494. ripple->addFading();
  495. } else {
  496. ripple->lastUnstop();
  497. }
  498. } else {
  499. if (ripple) {
  500. ripple->lastStop();
  501. }
  502. }
  503. }
  504. repaint(*row);
  505. }
  506. void Rows::activateByIndex(int index) {
  507. _activations.fire_copy(rowByIndex(index).data);
  508. }
  509. void Rows::leaveEventHook(QEvent *e) {
  510. updateSelected({});
  511. if (_menu && _menuOtherEntered) {
  512. _menuOtherEntered = false;
  513. _menu->otherLeave();
  514. }
  515. }
  516. void Rows::filter(const QString &query) {
  517. updateSelected({});
  518. updatePressed({});
  519. _menu = nullptr;
  520. _menuShownIndex = -1;
  521. _query = TextUtilities::PrepareSearchWords(query);
  522. const auto skip = [](
  523. const QStringList &haystack,
  524. const QStringList &needles) {
  525. const auto find = [](
  526. const QStringList &haystack,
  527. const QString &needle) {
  528. for (const auto &item : haystack) {
  529. if (item.startsWith(needle)) {
  530. return true;
  531. }
  532. }
  533. return false;
  534. };
  535. for (const auto &needle : needles) {
  536. if (!find(haystack, needle)) {
  537. return true;
  538. }
  539. }
  540. return false;
  541. };
  542. if (!_query.isEmpty()) {
  543. _filtered.clear();
  544. _filtered.reserve(_rows.size());
  545. for (auto &row : _rows) {
  546. if (!skip(row.keywords, _query)) {
  547. _filtered.push_back(&row);
  548. } else {
  549. row.ripple = nullptr;
  550. }
  551. }
  552. }
  553. resizeToWidth(width());
  554. Ui::SendPendingMoveResizeEvents(this);
  555. _isEmpty.fire(count() == 0);
  556. }
  557. int Rows::count() const {
  558. return _query.isEmpty() ? _rows.size() : _filtered.size();
  559. }
  560. int Rows::indexFromSelection(Selection selected) const {
  561. return v::match(selected, [&](RowSelection data) {
  562. return data.index;
  563. }, [&](MenuSelection data) {
  564. return data.index;
  565. }, [](v::null_t) {
  566. return -1;
  567. });
  568. }
  569. int Rows::selected() const {
  570. return indexFromSelection(_selected);
  571. }
  572. void Rows::activateSelected() {
  573. const auto index = selected();
  574. if (index >= 0) {
  575. activateByIndex(index);
  576. }
  577. }
  578. rpl::producer<Language> Rows::activations() const {
  579. return _activations.events();
  580. }
  581. void Rows::changeChosen(const QString &chosen) {
  582. for (const auto &row : _rows) {
  583. row.check->setChecked(row.data.id == chosen, anim::type::normal);
  584. }
  585. }
  586. void Rows::setSelected(int selected) {
  587. _mouseSelection = false;
  588. const auto limit = count();
  589. if (selected >= 0 && selected < limit) {
  590. updateSelected(RowSelection{ selected });
  591. } else {
  592. updateSelected({});
  593. }
  594. }
  595. rpl::producer<bool> Rows::hasSelection() const {
  596. return _hasSelection.events();
  597. }
  598. rpl::producer<bool> Rows::isEmpty() const {
  599. return _isEmpty.events_starting_with(
  600. count() == 0
  601. ) | rpl::distinct_until_changed();
  602. }
  603. void Rows::repaint(Selection selected) {
  604. v::match(selected, [](v::null_t) {
  605. }, [&](const auto &data) {
  606. repaint(data.index);
  607. });
  608. }
  609. void Rows::repaint(int index) {
  610. if (index >= 0) {
  611. repaint(rowByIndex(index));
  612. }
  613. }
  614. void Rows::repaint(const Row &row) {
  615. update(0, row.top, width(), row.height);
  616. }
  617. void Rows::repaintChecked(not_null<const Row*> row) {
  618. const auto found = (ranges::find(_filtered, row) != end(_filtered));
  619. if (_query.isEmpty() || found) {
  620. repaint(*row);
  621. }
  622. }
  623. void Rows::updateSelected(Selection selected) {
  624. const auto changed = (v::is_null(_selected) != v::is_null(selected));
  625. repaint(_selected);
  626. _selected = selected;
  627. repaint(_selected);
  628. if (changed) {
  629. _hasSelection.fire(!v::is_null(_selected));
  630. }
  631. }
  632. void Rows::updatePressed(Selection pressed) {
  633. if (!v::is_null(_pressed)) {
  634. if (!rowBySelection(_pressed).menuToggleForceRippled) {
  635. if (const auto ripple = rippleBySelection(_pressed).get()) {
  636. ripple->lastStop();
  637. }
  638. }
  639. }
  640. _pressed = pressed;
  641. }
  642. Rows::Row &Rows::rowByIndex(int index) {
  643. Expects(index >= 0 && index < count());
  644. return _query.isEmpty() ? _rows[index] : *_filtered[index];
  645. }
  646. const Rows::Row &Rows::rowByIndex(int index) const {
  647. Expects(index >= 0 && index < count());
  648. return _query.isEmpty() ? _rows[index] : *_filtered[index];
  649. }
  650. Rows::Row &Rows::rowBySelection(Selection selected) {
  651. return rowByIndex(indexFromSelection(selected));
  652. }
  653. const Rows::Row &Rows::rowBySelection(Selection selected) const {
  654. return rowByIndex(indexFromSelection(selected));
  655. }
  656. std::unique_ptr<Ui::RippleAnimation> &Rows::rippleBySelection(
  657. Selection selected) {
  658. return rippleBySelection(&rowBySelection(selected), selected);
  659. }
  660. const std::unique_ptr<Ui::RippleAnimation> &Rows::rippleBySelection(
  661. Selection selected) const {
  662. return rippleBySelection(&rowBySelection(selected), selected);
  663. }
  664. std::unique_ptr<Ui::RippleAnimation> &Rows::rippleBySelection(
  665. not_null<Row*> row,
  666. Selection selected) {
  667. return v::is<MenuSelection>(selected)
  668. ? row->menuToggleRipple
  669. : row->ripple;
  670. }
  671. const std::unique_ptr<Ui::RippleAnimation> &Rows::rippleBySelection(
  672. not_null<const Row*> row,
  673. Selection selected) const {
  674. return const_cast<Rows*>(this)->rippleBySelection(
  675. const_cast<Row*>(row.get()),
  676. selected);
  677. }
  678. Ui::ScrollToRequest Rows::rowScrollRequest(int index) const {
  679. const auto &row = rowByIndex(index);
  680. return Ui::ScrollToRequest(row.top, row.top + row.height);
  681. }
  682. int Rows::DefaultRowHeight() {
  683. return st::passportRowPadding.top()
  684. + st::semiboldFont->height
  685. + st::passportRowSkip
  686. + st::normalFont->height
  687. + st::passportRowPadding.bottom();
  688. }
  689. int Rows::resizeGetHeight(int newWidth) {
  690. const auto availableWidth = countAvailableWidth(newWidth);
  691. auto result = 0;
  692. for (auto i = 0, till = count(); i != till; ++i) {
  693. auto &row = rowByIndex(i);
  694. row.top = result;
  695. row.titleHeight = row.title.countHeight(availableWidth);
  696. row.descriptionHeight = row.description.countHeight(availableWidth);
  697. row.height = st::passportRowPadding.top()
  698. + row.titleHeight
  699. + st::passportRowSkip
  700. + row.descriptionHeight
  701. + st::passportRowPadding.bottom();
  702. result += row.height;
  703. }
  704. return result;
  705. }
  706. int Rows::countAvailableWidth(int newWidth) const {
  707. const auto right = width() - menuToggleArea().x();
  708. return newWidth
  709. - st::passportRowPadding.left()
  710. - st::langsRadio.diameter
  711. - st::passportRowPadding.left()
  712. - right
  713. - st::passportRowIconSkip;
  714. }
  715. int Rows::countAvailableWidth() const {
  716. return countAvailableWidth(width());
  717. }
  718. void Rows::paintEvent(QPaintEvent *e) {
  719. Painter p(this);
  720. const auto clip = e->rect();
  721. const auto checkLeft = st::passportRowPadding.left();
  722. const auto left = checkLeft
  723. + st::langsRadio.diameter
  724. + st::passportRowPadding.left();
  725. const auto availableWidth = countAvailableWidth();
  726. const auto menu = menuToggleArea();
  727. const auto selectedIndex = (_menuShownIndex >= 0)
  728. ? _menuShownIndex
  729. : indexFromSelection(!v::is_null(_pressed) ? _pressed : _selected);
  730. for (auto i = 0, till = count(); i != till; ++i) {
  731. const auto &row = rowByIndex(i);
  732. if (row.top + row.height <= clip.y()) {
  733. continue;
  734. } else if (row.top >= clip.y() + clip.height()) {
  735. break;
  736. }
  737. p.setOpacity(row.removed ? st::stickersRowDisabledOpacity : 1.);
  738. p.translate(0, row.top);
  739. const auto guard = gsl::finally([&] { p.translate(0, -row.top); });
  740. const auto selected = (selectedIndex == i);
  741. if (selected && !row.removed) {
  742. p.fillRect(0, 0, width(), row.height, st::windowBgOver);
  743. }
  744. if (row.ripple) {
  745. row.ripple->paint(p, 0, 0, width());
  746. if (row.ripple->empty()) {
  747. row.ripple.reset();
  748. }
  749. }
  750. const auto checkTop = (row.height - st::defaultRadio.diameter) / 2;
  751. row.check->paint(p, checkLeft, checkTop, width());
  752. auto top = st::passportRowPadding.top();
  753. p.setPen(st::passportRowTitleFg);
  754. row.title.drawLeft(p, left, top, availableWidth, width());
  755. top += row.titleHeight + st::passportRowSkip;
  756. p.setPen(selected ? st::windowSubTextFgOver : st::windowSubTextFg);
  757. row.description.drawLeft(p, left, top, availableWidth, width());
  758. top += row.descriptionHeight + st::passportRowPadding.bottom();
  759. if (hasMenu(&row)) {
  760. p.setOpacity(1.);
  761. if (selected && row.removed) {
  762. PainterHighQualityEnabler hq(p);
  763. p.setPen(Qt::NoPen);
  764. p.setBrush(st::windowBgOver);
  765. p.drawEllipse(menu);
  766. }
  767. if (row.menuToggleRipple) {
  768. row.menuToggleRipple->paint(p, menu.x(), menu.y(), width());
  769. if (row.menuToggleRipple->empty()) {
  770. row.menuToggleRipple.reset();
  771. }
  772. }
  773. (selected
  774. ? st::topBarMenuToggle.iconOver
  775. : st::topBarMenuToggle.icon).paintInCenter(p, menu);
  776. }
  777. }
  778. }
  779. Content::Content(
  780. QWidget *parent,
  781. const Languages &recent,
  782. const Languages &official)
  783. : RpWidget(parent) {
  784. setupContent(recent, official);
  785. }
  786. void Content::setupContent(
  787. const Languages &recent,
  788. const Languages &official) {
  789. using namespace rpl::mappers;
  790. const auto current = Lang::LanguageIdOrDefault(Lang::Id());
  791. const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
  792. const auto add = [&](const Languages &list, bool areOfficial) {
  793. if (list.empty()) {
  794. return (Rows*)nullptr;
  795. }
  796. const auto wrap = content->add(
  797. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  798. content,
  799. object_ptr<Ui::VerticalLayout>(content)));
  800. const auto inner = wrap->entity();
  801. inner->add(object_ptr<Ui::FixedHeightWidget>(
  802. inner,
  803. st::defaultBox.margin.top()));
  804. const auto rows = inner->add(object_ptr<Rows>(
  805. inner,
  806. list,
  807. current,
  808. areOfficial));
  809. inner->add(object_ptr<Ui::FixedHeightWidget>(
  810. inner,
  811. st::defaultBox.margin.top()));
  812. rows->isEmpty() | rpl::start_with_next([=](bool empty) {
  813. wrap->toggle(!empty, anim::type::instant);
  814. }, rows->lifetime());
  815. return rows;
  816. };
  817. const auto main = add(recent, false);
  818. const auto divider = content->add(
  819. object_ptr<Ui::SlideWrap<Ui::BoxContentDivider>>(
  820. content,
  821. object_ptr<Ui::BoxContentDivider>(content)));
  822. const auto other = add(official, true);
  823. const auto empty = content->add(
  824. object_ptr<Ui::SlideWrap<Ui::FixedHeightWidget>>(
  825. content,
  826. object_ptr<Ui::FixedHeightWidget>(
  827. content,
  828. st::membersAbout.style.font->height * 9)));
  829. const auto label = Ui::CreateChild<Ui::FlatLabel>(
  830. empty->entity(),
  831. tr::lng_languages_none(),
  832. st::membersAbout);
  833. empty->entity()->sizeValue(
  834. ) | rpl::start_with_next([=](QSize size) {
  835. label->move(
  836. (size.width() - label->width()) / 2,
  837. (size.height() - label->height()) / 2);
  838. }, label->lifetime());
  839. empty->toggleOn(
  840. rpl::combine(
  841. main ? main->isEmpty() : rpl::single(true),
  842. other ? other->isEmpty() : rpl::single(true),
  843. _1 && _2),
  844. anim::type::instant);
  845. Ui::ResizeFitChild(this, content);
  846. if (main && other) {
  847. rpl::combine(
  848. main->isEmpty(),
  849. other->isEmpty(),
  850. _1 || _2
  851. ) | rpl::start_with_next([=](bool empty) {
  852. divider->toggle(!empty, anim::type::instant);
  853. }, divider->lifetime());
  854. const auto excludeSelections = [](Rows *a, Rows *b) {
  855. a->hasSelection(
  856. ) | rpl::filter(
  857. _1
  858. ) | rpl::start_with_next([=] {
  859. b->setSelected(-1);
  860. }, a->lifetime());
  861. };
  862. excludeSelections(main, other);
  863. excludeSelections(other, main);
  864. } else {
  865. divider->hide(anim::type::instant);
  866. }
  867. const auto count = [](Rows *widget) {
  868. return widget ? widget->count() : 0;
  869. };
  870. const auto selected = [](Rows *widget) {
  871. return widget ? widget->selected() : -1;
  872. };
  873. const auto rowsCount = [=] {
  874. return count(main) + count(other);
  875. };
  876. const auto selectedIndex = [=] {
  877. if (const auto index = selected(main); index >= 0) {
  878. return index;
  879. } else if (const auto index = selected(other); index >= 0) {
  880. return count(main) + index;
  881. }
  882. return -1;
  883. };
  884. const auto setSelectedIndex = [=](int index) {
  885. const auto first = count(main);
  886. if (index >= first) {
  887. if (main) {
  888. main->setSelected(-1);
  889. }
  890. if (other) {
  891. other->setSelected(index - first);
  892. }
  893. } else {
  894. if (main) {
  895. main->setSelected(index);
  896. }
  897. if (other) {
  898. other->setSelected(-1);
  899. }
  900. }
  901. };
  902. const auto selectedCoords = [=] {
  903. const auto coords = [=](Rows *rows, int index) {
  904. const auto result = rows->rowScrollRequest(index);
  905. const auto shift = rows->mapToGlobal({ 0, 0 }).y()
  906. - mapToGlobal({ 0, 0 }).y();
  907. return Ui::ScrollToRequest(
  908. result.ymin + shift,
  909. result.ymax + shift);
  910. };
  911. if (const auto index = selected(main); index >= 0) {
  912. return coords(main, index);
  913. } else if (const auto index = selected(other); index >= 0) {
  914. return coords(other, index);
  915. }
  916. return Ui::ScrollToRequest(-1, -1);
  917. };
  918. _jump = [=](int rows) {
  919. const auto count = rowsCount();
  920. const auto now = selectedIndex();
  921. if (now >= 0) {
  922. const auto changed = now + rows;
  923. if (changed < 0) {
  924. setSelectedIndex((now > 0) ? 0 : -1);
  925. } else if (changed >= count) {
  926. setSelectedIndex(count - 1);
  927. } else {
  928. setSelectedIndex(changed);
  929. }
  930. } else if (rows > 0) {
  931. setSelectedIndex(0);
  932. }
  933. return selectedCoords();
  934. };
  935. const auto filter = [](Rows *widget, const QString &query) {
  936. if (widget) {
  937. widget->filter(query);
  938. }
  939. };
  940. _filter = [=](const QString &query) {
  941. filter(main, query);
  942. filter(other, query);
  943. };
  944. _activations = [=] {
  945. if (!main && !other) {
  946. return rpl::never<Language>() | rpl::type_erased();
  947. } else if (!main) {
  948. return other->activations();
  949. } else if (!other) {
  950. return main->activations();
  951. }
  952. return rpl::merge(
  953. main->activations(),
  954. other->activations()
  955. ) | rpl::type_erased();
  956. };
  957. _changeChosen = [=](const QString &chosen) {
  958. if (main) {
  959. main->changeChosen(chosen);
  960. }
  961. if (other) {
  962. other->changeChosen(chosen);
  963. }
  964. };
  965. _activateBySubmit = [=] {
  966. if (selectedIndex() < 0) {
  967. _jump(1);
  968. }
  969. if (main) {
  970. main->activateSelected();
  971. }
  972. if (other) {
  973. other->activateSelected();
  974. }
  975. };
  976. }
  977. void Content::filter(const QString &query) {
  978. _filter(query);
  979. }
  980. rpl::producer<Language> Content::activations() const {
  981. return _activations();
  982. }
  983. void Content::changeChosen(const QString &chosen) {
  984. _changeChosen(chosen);
  985. }
  986. void Content::activateBySubmit() {
  987. _activateBySubmit();
  988. }
  989. Ui::ScrollToRequest Content::jump(int rows) {
  990. return _jump(rows);
  991. }
  992. } // namespace
  993. LanguageBox::LanguageBox(QWidget*, Window::SessionController *controller)
  994. : _controller(controller) {
  995. }
  996. void LanguageBox::prepare() {
  997. addButton(tr::lng_box_ok(), [=] { closeBox(); });
  998. setTitle(tr::lng_languages());
  999. const auto topContainer = Ui::CreateChild<Ui::VerticalLayout>(this);
  1000. setupTop(topContainer);
  1001. const auto select = topContainer->add(
  1002. object_ptr<Ui::MultiSelect>(
  1003. topContainer,
  1004. st::defaultMultiSelect,
  1005. tr::lng_participant_filter()));
  1006. topContainer->resizeToWidth(st::boxWidth);
  1007. using namespace rpl::mappers;
  1008. const auto &[recent, official] = PrepareLists();
  1009. const auto inner = setInnerWidget(
  1010. object_ptr<Content>(this, recent, official),
  1011. st::boxScroll,
  1012. topContainer->height());
  1013. inner->resizeToWidth(st::boxWidth);
  1014. const auto max = lifetime().make_state<int>(0);
  1015. rpl::combine(
  1016. inner->heightValue(),
  1017. topContainer->heightValue(),
  1018. _1 + _2
  1019. ) | rpl::start_with_next([=](int height) {
  1020. accumulate_max(*max, height);
  1021. setDimensions(st::boxWidth, qMin(*max, st::boxMaxListHeight));
  1022. }, inner->lifetime());
  1023. topContainer->heightValue(
  1024. ) | rpl::start_with_next([=](int height) {
  1025. setInnerTopSkip(height);
  1026. }, inner->lifetime());
  1027. select->setSubmittedCallback([=](Qt::KeyboardModifiers) {
  1028. inner->activateBySubmit();
  1029. });
  1030. select->setQueryChangedCallback([=](const QString &query) {
  1031. inner->filter(query);
  1032. });
  1033. select->setCancelledCallback([=] {
  1034. select->clearQuery();
  1035. });
  1036. inner->activations(
  1037. ) | rpl::start_with_next([=](const Language &language) {
  1038. // "#custom" is applied each time it's passed to switchToLanguage().
  1039. // So we check that the language really has changed.
  1040. const auto currentId = [] {
  1041. return Lang::LanguageIdOrDefault(Lang::Id());
  1042. };
  1043. if (language.id != currentId()) {
  1044. Lang::CurrentCloudManager().switchToLanguage(language);
  1045. if (inner) {
  1046. inner->changeChosen(currentId());
  1047. }
  1048. }
  1049. }, inner->lifetime());
  1050. _setInnerFocus = [=] {
  1051. select->setInnerFocus();
  1052. };
  1053. _jump = [=](int rows) {
  1054. return inner->jump(rows);
  1055. };
  1056. }
  1057. void LanguageBox::setupTop(not_null<Ui::VerticalLayout*> container) {
  1058. if (!_controller) {
  1059. return;
  1060. }
  1061. const auto translateEnabled = container->add(
  1062. object_ptr<Ui::SettingsButton>(
  1063. container,
  1064. tr::lng_translate_settings_show(),
  1065. st::settingsButtonNoIcon))->toggleOn(
  1066. rpl::single(Core::App().settings().translateButtonEnabled()));
  1067. translateEnabled->toggledValue(
  1068. ) | rpl::filter([](bool checked) {
  1069. return (checked != Core::App().settings().translateButtonEnabled());
  1070. }) | rpl::start_with_next([=](bool checked) {
  1071. Core::App().settings().setTranslateButtonEnabled(checked);
  1072. Core::App().saveSettingsDelayed();
  1073. }, translateEnabled->lifetime());
  1074. using namespace rpl::mappers;
  1075. auto premium = Data::AmPremiumValue(&_controller->session());
  1076. const auto translateChat = container->add(object_ptr<Ui::SettingsButton>(
  1077. container,
  1078. tr::lng_translate_settings_chat(),
  1079. st::settingsButtonNoIconLocked
  1080. ))->toggleOn(rpl::merge(
  1081. rpl::combine(
  1082. Core::App().settings().translateChatEnabledValue(),
  1083. rpl::duplicate(premium),
  1084. _1 && _2),
  1085. _translateChatTurnOff.events()));
  1086. std::move(premium) | rpl::start_with_next([=](bool value) {
  1087. translateChat->setToggleLocked(!value);
  1088. }, translateChat->lifetime());
  1089. translateChat->toggledValue(
  1090. ) | rpl::filter([=](bool checked) {
  1091. const auto premium = _controller->session().premium();
  1092. if (checked && !premium) {
  1093. ShowPremiumPreviewToBuy(
  1094. _controller,
  1095. PremiumFeature::RealTimeTranslation);
  1096. _translateChatTurnOff.fire(false);
  1097. }
  1098. return premium
  1099. && (checked != Core::App().settings().translateChatEnabled());
  1100. }) | rpl::start_with_next([=](bool checked) {
  1101. Core::App().settings().setTranslateChatEnabled(checked);
  1102. Core::App().saveSettingsDelayed();
  1103. }, translateChat->lifetime());
  1104. using Languages = std::vector<LanguageId>;
  1105. const auto translateSkipWrap = container->add(
  1106. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  1107. container,
  1108. object_ptr<Ui::VerticalLayout>(container)));
  1109. translateSkipWrap->toggle(
  1110. translateEnabled->toggled(),
  1111. anim::type::normal);
  1112. translateSkipWrap->toggleOn(rpl::combine(
  1113. translateEnabled->toggledValue(),
  1114. translateChat->toggledValue(),
  1115. rpl::mappers::_1 || rpl::mappers::_2));
  1116. const auto translateSkip = Settings::AddButtonWithLabel(
  1117. translateSkipWrap->entity(),
  1118. tr::lng_translate_settings_choose(),
  1119. Core::App().settings().skipTranslationLanguagesValue(
  1120. ) | rpl::map([](const Languages &list) {
  1121. return (list.size() > 1)
  1122. ? tr::lng_languages_count(tr::now, lt_count, list.size())
  1123. : Ui::LanguageName(list.front());
  1124. }),
  1125. st::settingsButtonNoIcon);
  1126. translateSkip->setClickedCallback([=] {
  1127. uiShow()->showBox(Ui::EditSkipTranslationLanguages());
  1128. });
  1129. Ui::AddSkip(container);
  1130. Ui::AddDividerText(container, tr::lng_translate_settings_about());
  1131. }
  1132. void LanguageBox::keyPressEvent(QKeyEvent *e) {
  1133. const auto key = e->key();
  1134. if (key == Qt::Key_Escape) {
  1135. closeBox();
  1136. return;
  1137. }
  1138. const auto selected = [&] {
  1139. if (key == Qt::Key_Up) {
  1140. return _jump(-1);
  1141. } else if (key == Qt::Key_Down) {
  1142. return _jump(1);
  1143. } else if (key == Qt::Key_PageUp) {
  1144. return _jump(-rowsInPage());
  1145. } else if (key == Qt::Key_PageDown) {
  1146. return _jump(rowsInPage());
  1147. }
  1148. return Ui::ScrollToRequest(-1, -1);
  1149. }();
  1150. if (selected.ymin >= 0 && selected.ymax >= 0) {
  1151. scrollToY(selected.ymin, selected.ymax);
  1152. }
  1153. }
  1154. int LanguageBox::rowsInPage() const {
  1155. return std::max(height() / Rows::DefaultRowHeight(), 1);
  1156. }
  1157. void LanguageBox::setInnerFocus() {
  1158. _setInnerFocus();
  1159. }
  1160. base::binary_guard LanguageBox::Show(Window::SessionController *controller) {
  1161. auto result = base::binary_guard();
  1162. auto &manager = Lang::CurrentCloudManager();
  1163. if (manager.languageList().empty()) {
  1164. const auto weak = base::make_weak(controller);
  1165. auto guard = std::make_shared<base::binary_guard>(
  1166. result.make_guard());
  1167. auto lifetime = std::make_shared<rpl::lifetime>();
  1168. manager.languageListChanged(
  1169. ) | rpl::take(
  1170. 1
  1171. ) | rpl::start_with_next([=]() mutable {
  1172. const auto show = guard->alive();
  1173. if (lifetime) {
  1174. base::take(lifetime)->destroy();
  1175. }
  1176. if (show) {
  1177. Ui::show(Box<LanguageBox>(weak.get()));
  1178. }
  1179. }, *lifetime);
  1180. } else {
  1181. Ui::show(Box<LanguageBox>(controller));
  1182. }
  1183. manager.requestLanguageList();
  1184. return result;
  1185. }