settings_advanced.cpp 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101
  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 "settings/settings_advanced.h"
  8. #include "api/api_global_privacy.h"
  9. #include "apiwrap.h"
  10. #include "settings/settings_chat.h"
  11. #include "settings/settings_power_saving.h"
  12. #include "settings/settings_privacy_security.h"
  13. #include "ui/wrap/vertical_layout.h"
  14. #include "ui/wrap/slide_wrap.h"
  15. #include "ui/widgets/labels.h"
  16. #include "ui/widgets/checkbox.h"
  17. #include "ui/widgets/buttons.h"
  18. #include "ui/gl/gl_detection.h"
  19. #include "ui/layers/generic_box.h"
  20. #include "ui/text/format_values.h"
  21. #include "ui/boxes/single_choice_box.h"
  22. #include "ui/painter.h"
  23. #include "ui/vertical_list.h"
  24. #include "ui/ui_utility.h"
  25. #include "boxes/connection_box.h"
  26. #include "boxes/about_box.h"
  27. #include "ui/boxes/confirm_box.h"
  28. #include "platform/platform_specific.h"
  29. #include "ui/platform/ui_platform_window.h"
  30. #include "base/platform/base_platform_custom_app_icon.h"
  31. #include "base/platform/base_platform_info.h"
  32. #include "window/window_controller.h"
  33. #include "window/window_session_controller.h"
  34. #include "lang/lang_keys.h"
  35. #include "core/update_checker.h"
  36. #include "core/launcher.h"
  37. #include "core/application.h"
  38. #include "tray.h"
  39. #include "storage/localstorage.h"
  40. #include "storage/storage_domain.h"
  41. #include "data/data_session.h"
  42. #include "main/main_account.h"
  43. #include "main/main_domain.h"
  44. #include "main/main_session.h"
  45. #include "mtproto/facade.h"
  46. #include "styles/style_layers.h"
  47. #include "styles/style_menu_icons.h"
  48. #include "styles/style_settings.h"
  49. #ifdef Q_OS_MAC
  50. #include "base/platform/mac/base_confirm_quit.h"
  51. #endif // Q_OS_MAC
  52. #ifndef TDESKTOP_DISABLE_SPELLCHECK
  53. #include "boxes/dictionaries_manager.h"
  54. #include "chat_helpers/spellchecker_common.h"
  55. #include "spellcheck/platform/platform_spellcheck.h"
  56. #endif // !TDESKTOP_DISABLE_SPELLCHECK
  57. namespace Settings {
  58. namespace {
  59. #if defined Q_OS_MAC && !defined OS_MAC_STORE
  60. [[nodiscard]] const QImage &IconMacRound() {
  61. static const auto result = QImage(u":/gui/art/icon_round512@2x.png"_q);
  62. return result;
  63. }
  64. #endif // Q_OS_MAC && !OS_MAC_STORE
  65. } // namespace
  66. void SetupConnectionType(
  67. not_null<Window::Controller*> controller,
  68. not_null<Main::Account*> account,
  69. not_null<Ui::VerticalLayout*> container) {
  70. const auto connectionType = [=] {
  71. const auto transport = account->mtp().dctransport();
  72. if (!Core::App().settings().proxy().isEnabled()) {
  73. return transport.isEmpty()
  74. ? tr::lng_connection_auto_connecting(tr::now)
  75. : tr::lng_connection_auto(tr::now, lt_transport, transport);
  76. } else {
  77. return transport.isEmpty()
  78. ? tr::lng_connection_proxy_connecting(tr::now)
  79. : tr::lng_connection_proxy(tr::now, lt_transport, transport);
  80. }
  81. };
  82. const auto button = AddButtonWithLabel(
  83. container,
  84. tr::lng_settings_connection_type(),
  85. rpl::merge(
  86. Core::App().settings().proxy().connectionTypeChanges(),
  87. // Handle language switch.
  88. tr::lng_connection_auto_connecting() | rpl::to_empty
  89. ) | rpl::map(connectionType),
  90. st::settingsButton,
  91. { &st::menuIconNetwork });
  92. button->addClickHandler([=] {
  93. controller->show(ProxiesBoxController::CreateOwningBox(account));
  94. });
  95. }
  96. bool HasUpdate() {
  97. return !Core::UpdaterDisabled();
  98. }
  99. void SetupUpdate(not_null<Ui::VerticalLayout*> container) {
  100. if (!HasUpdate()) {
  101. return;
  102. }
  103. const auto texts = Ui::CreateChild<rpl::event_stream<QString>>(
  104. container.get());
  105. const auto downloading = Ui::CreateChild<rpl::event_stream<bool>>(
  106. container.get());
  107. const auto version = tr::lng_settings_current_version(
  108. tr::now,
  109. lt_version,
  110. currentVersionText());
  111. const auto toggle = container->add(object_ptr<Button>(
  112. container,
  113. tr::lng_settings_update_automatically(),
  114. st::settingsUpdateToggle));
  115. const auto label = Ui::CreateChild<Ui::FlatLabel>(
  116. toggle,
  117. texts->events(),
  118. st::settingsUpdateState);
  119. const auto options = container->add(
  120. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  121. container,
  122. object_ptr<Ui::VerticalLayout>(container)));
  123. const auto inner = options->entity();
  124. const auto install = cAlphaVersion()
  125. ? nullptr
  126. : inner->add(object_ptr<Button>(
  127. inner,
  128. tr::lng_settings_install_beta(),
  129. st::settingsButtonNoIcon));
  130. const auto check = inner->add(object_ptr<Button>(
  131. inner,
  132. tr::lng_settings_check_now(),
  133. st::settingsButtonNoIcon));
  134. const auto update = Ui::CreateChild<Button>(
  135. check,
  136. tr::lng_update_telegram(),
  137. st::settingsUpdate);
  138. update->hide();
  139. check->widthValue() | rpl::start_with_next([=](int width) {
  140. update->resizeToWidth(width);
  141. update->moveToLeft(0, 0);
  142. }, update->lifetime());
  143. rpl::combine(
  144. toggle->widthValue(),
  145. label->widthValue()
  146. ) | rpl::start_with_next([=] {
  147. label->moveToLeft(
  148. st::settingsUpdateStatePosition.x(),
  149. st::settingsUpdateStatePosition.y());
  150. }, label->lifetime());
  151. label->setAttribute(Qt::WA_TransparentForMouseEvents);
  152. const auto showDownloadProgress = [=](int64 ready, int64 total) {
  153. texts->fire(tr::lng_settings_downloading_update(
  154. tr::now,
  155. lt_progress,
  156. Ui::FormatDownloadText(ready, total)));
  157. downloading->fire(true);
  158. };
  159. const auto setDefaultStatus = [=](const Core::UpdateChecker &checker) {
  160. using State = Core::UpdateChecker::State;
  161. const auto state = checker.state();
  162. switch (state) {
  163. case State::Download:
  164. showDownloadProgress(checker.already(), checker.size());
  165. break;
  166. case State::Ready:
  167. texts->fire(tr::lng_settings_update_ready(tr::now));
  168. update->show();
  169. break;
  170. default:
  171. texts->fire_copy(version);
  172. break;
  173. }
  174. };
  175. toggle->toggleOn(rpl::single(cAutoUpdate()));
  176. toggle->toggledValue(
  177. ) | rpl::filter([](bool toggled) {
  178. return (toggled != cAutoUpdate());
  179. }) | rpl::start_with_next([=](bool toggled) {
  180. cSetAutoUpdate(toggled);
  181. Local::writeSettings();
  182. Core::UpdateChecker checker;
  183. if (cAutoUpdate()) {
  184. checker.start();
  185. } else {
  186. checker.stop();
  187. setDefaultStatus(checker);
  188. }
  189. }, toggle->lifetime());
  190. if (install) {
  191. install->toggleOn(rpl::single(cInstallBetaVersion()));
  192. install->toggledValue(
  193. ) | rpl::filter([](bool toggled) {
  194. return (toggled != cInstallBetaVersion());
  195. }) | rpl::start_with_next([=](bool toggled) {
  196. cSetInstallBetaVersion(toggled);
  197. Core::Launcher::Instance().writeInstallBetaVersionsSetting();
  198. Core::UpdateChecker checker;
  199. checker.stop();
  200. if (toggled) {
  201. cSetLastUpdateCheck(0);
  202. }
  203. checker.start();
  204. }, toggle->lifetime());
  205. }
  206. Core::UpdateChecker checker;
  207. options->toggleOn(rpl::combine(
  208. toggle->toggledValue(),
  209. downloading->events_starting_with(
  210. checker.state() == Core::UpdateChecker::State::Download)
  211. ) | rpl::map([](bool check, bool downloading) {
  212. return check && !downloading;
  213. }));
  214. checker.checking() | rpl::start_with_next([=] {
  215. options->setAttribute(Qt::WA_TransparentForMouseEvents);
  216. texts->fire(tr::lng_settings_update_checking(tr::now));
  217. downloading->fire(false);
  218. }, options->lifetime());
  219. checker.isLatest() | rpl::start_with_next([=] {
  220. options->setAttribute(Qt::WA_TransparentForMouseEvents, false);
  221. texts->fire(tr::lng_settings_latest_installed(tr::now));
  222. downloading->fire(false);
  223. }, options->lifetime());
  224. checker.progress(
  225. ) | rpl::start_with_next([=](Core::UpdateChecker::Progress progress) {
  226. showDownloadProgress(progress.already, progress.size);
  227. }, options->lifetime());
  228. checker.failed() | rpl::start_with_next([=] {
  229. options->setAttribute(Qt::WA_TransparentForMouseEvents, false);
  230. texts->fire(tr::lng_settings_update_fail(tr::now));
  231. downloading->fire(false);
  232. }, options->lifetime());
  233. checker.ready() | rpl::start_with_next([=] {
  234. options->setAttribute(Qt::WA_TransparentForMouseEvents, false);
  235. texts->fire(tr::lng_settings_update_ready(tr::now));
  236. update->show();
  237. downloading->fire(false);
  238. }, options->lifetime());
  239. setDefaultStatus(checker);
  240. check->addClickHandler([] {
  241. Core::UpdateChecker checker;
  242. cSetLastUpdateCheck(0);
  243. checker.start();
  244. });
  245. update->addClickHandler([] {
  246. if (!Core::UpdaterDisabled()) {
  247. Core::checkReadyUpdate();
  248. }
  249. Core::Restart();
  250. });
  251. }
  252. bool HasSystemSpellchecker() {
  253. #ifdef TDESKTOP_DISABLE_SPELLCHECK
  254. return false;
  255. #endif // TDESKTOP_DISABLE_SPELLCHECK
  256. return true;
  257. }
  258. void SetupSpellchecker(
  259. not_null<Window::SessionController*> controller,
  260. not_null<Ui::VerticalLayout*> container) {
  261. #ifndef TDESKTOP_DISABLE_SPELLCHECK
  262. const auto session = &controller->session();
  263. const auto settings = &Core::App().settings();
  264. const auto isSystem = Platform::Spellchecker::IsSystemSpellchecker();
  265. const auto button = container->add(object_ptr<Button>(
  266. container,
  267. isSystem
  268. ? tr::lng_settings_system_spellchecker()
  269. : tr::lng_settings_custom_spellchecker(),
  270. st::settingsButtonNoIcon
  271. ))->toggleOn(
  272. rpl::single(settings->spellcheckerEnabled())
  273. );
  274. button->toggledValue(
  275. ) | rpl::filter([=](bool enabled) {
  276. return (enabled != settings->spellcheckerEnabled());
  277. }) | rpl::start_with_next([=](bool enabled) {
  278. settings->setSpellcheckerEnabled(enabled);
  279. Core::App().saveSettingsDelayed();
  280. }, container->lifetime());
  281. if (isSystem) {
  282. return;
  283. }
  284. const auto sliding = container->add(
  285. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  286. container,
  287. object_ptr<Ui::VerticalLayout>(container)));
  288. sliding->entity()->add(object_ptr<Button>(
  289. sliding->entity(),
  290. tr::lng_settings_auto_download_dictionaries(),
  291. st::settingsButtonNoIcon
  292. ))->toggleOn(
  293. rpl::single(settings->autoDownloadDictionaries())
  294. )->toggledValue(
  295. ) | rpl::filter([=](bool enabled) {
  296. return (enabled != settings->autoDownloadDictionaries());
  297. }) | rpl::start_with_next([=](bool enabled) {
  298. settings->setAutoDownloadDictionaries(enabled);
  299. Core::App().saveSettingsDelayed();
  300. }, sliding->entity()->lifetime());
  301. AddButtonWithLabel(
  302. sliding->entity(),
  303. tr::lng_settings_manage_dictionaries(),
  304. Spellchecker::ButtonManageDictsState(session),
  305. st::settingsButtonNoIcon
  306. )->addClickHandler([=] {
  307. controller->show(
  308. Box<Ui::ManageDictionariesBox>(&controller->session()));
  309. });
  310. button->toggledValue(
  311. ) | rpl::start_with_next([=](bool enabled) {
  312. sliding->toggle(enabled, anim::type::normal);
  313. }, container->lifetime());
  314. #endif // !TDESKTOP_DISABLE_SPELLCHECK
  315. }
  316. void SetupWindowTitleContent(
  317. Window::SessionController *controller,
  318. not_null<Ui::VerticalLayout*> container) {
  319. const auto checkbox = [&](rpl::producer<QString> &&label, bool checked) {
  320. return object_ptr<Ui::Checkbox>(
  321. container,
  322. std::move(label),
  323. checked,
  324. st::settingsCheckbox);
  325. };
  326. const auto addCheckbox = [&](
  327. rpl::producer<QString> &&label,
  328. bool checked) {
  329. return container->add(
  330. checkbox(std::move(label), checked),
  331. st::settingsCheckboxPadding);
  332. };
  333. const auto settings = &Core::App().settings();
  334. if (controller) {
  335. const auto content = [=] {
  336. return settings->windowTitleContent();
  337. };
  338. const auto showChatName = addCheckbox(
  339. tr::lng_settings_title_chat_name(),
  340. !content().hideChatName);
  341. showChatName->checkedChanges(
  342. ) | rpl::filter([=](bool checked) {
  343. return (checked == content().hideChatName);
  344. }) | rpl::start_with_next([=](bool checked) {
  345. auto updated = content();
  346. updated.hideChatName = !checked;
  347. settings->setWindowTitleContent(updated);
  348. Core::App().saveSettingsDelayed();
  349. }, showChatName->lifetime());
  350. if (Core::App().domain().accountsAuthedCount() > 1) {
  351. const auto showAccountName = addCheckbox(
  352. tr::lng_settings_title_account_name(),
  353. !content().hideAccountName);
  354. showAccountName->checkedChanges(
  355. ) | rpl::filter([=](bool checked) {
  356. return (checked == content().hideAccountName);
  357. }) | rpl::start_with_next([=](bool checked) {
  358. auto updated = content();
  359. updated.hideAccountName = !checked;
  360. settings->setWindowTitleContent(updated);
  361. Core::App().saveSettingsDelayed();
  362. }, showAccountName->lifetime());
  363. }
  364. const auto showTotalUnread = addCheckbox(
  365. tr::lng_settings_title_total_count(),
  366. !content().hideTotalUnread);
  367. showTotalUnread->checkedChanges(
  368. ) | rpl::filter([=](bool checked) {
  369. return (checked == content().hideTotalUnread);
  370. }) | rpl::start_with_next([=](bool checked) {
  371. auto updated = content();
  372. updated.hideTotalUnread = !checked;
  373. settings->setWindowTitleContent(updated);
  374. Core::App().saveSettingsDelayed();
  375. }, showTotalUnread->lifetime());
  376. }
  377. if (Ui::Platform::NativeWindowFrameSupported()) {
  378. const auto nativeFrame = addCheckbox(
  379. Platform::IsWayland()
  380. ? tr::lng_settings_qt_frame()
  381. : tr::lng_settings_native_frame(),
  382. Core::App().settings().nativeWindowFrame());
  383. nativeFrame->checkedChanges(
  384. ) | rpl::filter([](bool checked) {
  385. return (checked != Core::App().settings().nativeWindowFrame());
  386. }) | rpl::start_with_next([=](bool checked) {
  387. Core::App().settings().setNativeWindowFrame(checked);
  388. Core::App().saveSettingsDelayed();
  389. }, nativeFrame->lifetime());
  390. }
  391. }
  392. void SetupSystemIntegrationContent(
  393. Window::SessionController *controller,
  394. not_null<Ui::VerticalLayout*> container) {
  395. using WorkMode = Core::Settings::WorkMode;
  396. const auto checkbox = [&](rpl::producer<QString> &&label, bool checked) {
  397. return object_ptr<Ui::Checkbox>(
  398. container,
  399. std::move(label),
  400. checked,
  401. st::settingsCheckbox);
  402. };
  403. const auto addCheckbox = [&](
  404. rpl::producer<QString> &&label,
  405. bool checked) {
  406. return container->add(
  407. checkbox(std::move(label), checked),
  408. st::settingsCheckboxPadding);
  409. };
  410. const auto addSlidingCheckbox = [&](
  411. rpl::producer<QString> &&label,
  412. bool checked) {
  413. return container->add(
  414. object_ptr<Ui::SlideWrap<Ui::Checkbox>>(
  415. container,
  416. checkbox(std::move(label), checked),
  417. st::settingsCheckboxPadding));
  418. };
  419. const auto settings = &Core::App().settings();
  420. if (Platform::TrayIconSupported()) {
  421. const auto trayEnabled = [=] {
  422. const auto workMode = settings->workMode();
  423. return (workMode == WorkMode::TrayOnly)
  424. || (workMode == WorkMode::WindowAndTray);
  425. };
  426. const auto tray = addCheckbox(
  427. tr::lng_settings_workmode_tray(),
  428. trayEnabled());
  429. const auto monochrome = Platform::HasMonochromeSetting()
  430. ? addSlidingCheckbox(
  431. tr::lng_settings_monochrome_icon(),
  432. settings->trayIconMonochrome())
  433. : nullptr;
  434. if (monochrome) {
  435. monochrome->toggle(tray->checked(), anim::type::instant);
  436. monochrome->entity()->checkedChanges(
  437. ) | rpl::filter([=](bool value) {
  438. return (value != settings->trayIconMonochrome());
  439. }) | rpl::start_with_next([=](bool value) {
  440. settings->setTrayIconMonochrome(value);
  441. Core::App().saveSettingsDelayed();
  442. }, monochrome->lifetime());
  443. }
  444. const auto taskbarEnabled = [=] {
  445. const auto workMode = settings->workMode();
  446. return (workMode == WorkMode::WindowOnly)
  447. || (workMode == WorkMode::WindowAndTray);
  448. };
  449. const auto taskbar = Platform::SkipTaskbarSupported()
  450. ? addCheckbox(
  451. tr::lng_settings_workmode_window(),
  452. taskbarEnabled())
  453. : nullptr;
  454. const auto updateWorkmode = [=] {
  455. const auto newMode = tray->checked()
  456. ? ((!taskbar || taskbar->checked())
  457. ? WorkMode::WindowAndTray
  458. : WorkMode::TrayOnly)
  459. : WorkMode::WindowOnly;
  460. if ((newMode == WorkMode::WindowAndTray
  461. || newMode == WorkMode::TrayOnly)
  462. && settings->workMode() != newMode) {
  463. cSetSeenTrayTooltip(false);
  464. }
  465. settings->setWorkMode(newMode);
  466. Core::App().saveSettingsDelayed();
  467. };
  468. tray->checkedChanges(
  469. ) | rpl::filter([=](bool checked) {
  470. return (checked != trayEnabled());
  471. }) | rpl::start_with_next([=](bool checked) {
  472. if (!checked && taskbar && !taskbar->checked()) {
  473. taskbar->setChecked(true);
  474. } else {
  475. updateWorkmode();
  476. }
  477. if (monochrome) {
  478. monochrome->toggle(checked, anim::type::normal);
  479. }
  480. }, tray->lifetime());
  481. if (taskbar) {
  482. taskbar->checkedChanges(
  483. ) | rpl::filter([=](bool checked) {
  484. return (checked != taskbarEnabled());
  485. }) | rpl::start_with_next([=](bool checked) {
  486. if (!checked && !tray->checked()) {
  487. tray->setChecked(true);
  488. } else {
  489. updateWorkmode();
  490. }
  491. }, taskbar->lifetime());
  492. }
  493. }
  494. #ifdef Q_OS_MAC
  495. const auto warnBeforeQuit = addCheckbox(
  496. tr::lng_settings_mac_warn_before_quit(
  497. lt_text,
  498. rpl::single(Platform::ConfirmQuit::QuitKeysString())),
  499. settings->macWarnBeforeQuit());
  500. warnBeforeQuit->checkedChanges(
  501. ) | rpl::filter([=](bool checked) {
  502. return (checked != settings->macWarnBeforeQuit());
  503. }) | rpl::start_with_next([=](bool checked) {
  504. settings->setMacWarnBeforeQuit(checked);
  505. Core::App().saveSettingsDelayed();
  506. }, warnBeforeQuit->lifetime());
  507. #ifndef OS_MAC_STORE
  508. const auto enabled = [=] {
  509. const auto digest = base::Platform::CurrentCustomAppIconDigest();
  510. return digest && (settings->macRoundIconDigest() == digest);
  511. };
  512. const auto roundIcon = addCheckbox(
  513. tr::lng_settings_mac_round_icon(),
  514. enabled());
  515. roundIcon->checkedChanges(
  516. ) | rpl::filter([=](bool checked) {
  517. return (checked != enabled());
  518. }) | rpl::start_with_next([=](bool checked) {
  519. const auto digest = checked
  520. ? base::Platform::SetCustomAppIcon(IconMacRound())
  521. : std::optional<uint64>();
  522. if (!checked) {
  523. base::Platform::ClearCustomAppIcon();
  524. }
  525. Window::OverrideApplicationIcon(checked ? IconMacRound() : QImage());
  526. Core::App().refreshApplicationIcon();
  527. settings->setMacRoundIconDigest(digest);
  528. Core::App().saveSettings();
  529. }, roundIcon->lifetime());
  530. #endif // OS_MAC_STORE
  531. #elif defined Q_OS_WIN // Q_OS_MAC
  532. using Behavior = Core::Settings::CloseBehavior;
  533. const auto closeToTaskbar = addSlidingCheckbox(
  534. tr::lng_settings_close_to_taskbar(),
  535. settings->closeBehavior() == Behavior::CloseToTaskbar);
  536. const auto closeToTaskbarShown = std::make_shared<
  537. rpl::variable<bool>
  538. >(false);
  539. settings->workModeValue(
  540. ) | rpl::start_with_next([=](WorkMode workMode) {
  541. *closeToTaskbarShown = !Core::App().tray().has();
  542. }, closeToTaskbar->lifetime());
  543. closeToTaskbar->toggleOn(closeToTaskbarShown->value());
  544. closeToTaskbar->entity()->checkedChanges(
  545. ) | rpl::map([=](bool checked) {
  546. return checked ? Behavior::CloseToTaskbar : Behavior::Quit;
  547. }) | rpl::filter([=](Behavior value) {
  548. return (settings->closeBehavior() != value);
  549. }) | rpl::start_with_next([=](Behavior value) {
  550. settings->setCloseBehavior(value);
  551. Local::writeSettings();
  552. }, closeToTaskbar->lifetime());
  553. #endif // Q_OS_MAC || Q_OS_WIN
  554. if (Platform::AutostartSupported() && controller) {
  555. const auto minimizedToggled = [=] {
  556. return cStartMinimized()
  557. && !controller->session().domain().local().hasLocalPasscode();
  558. };
  559. const auto autostart = addCheckbox(
  560. tr::lng_settings_auto_start(),
  561. cAutoStart());
  562. const auto minimized = addSlidingCheckbox(
  563. tr::lng_settings_start_min(),
  564. minimizedToggled());
  565. autostart->checkedChanges(
  566. ) | rpl::filter([](bool checked) {
  567. return (checked != cAutoStart());
  568. }) | rpl::start_with_next([=](bool checked) {
  569. const auto weak = base::make_weak(controller);
  570. cSetAutoStart(checked);
  571. Platform::AutostartToggle(checked, crl::guard(autostart, [=](
  572. bool enabled) {
  573. if (checked && !enabled && weak) {
  574. weak->window().showToast(
  575. Lang::Hard::AutostartEnableError());
  576. }
  577. Ui::PostponeCall(autostart, [=] {
  578. autostart->setChecked(enabled);
  579. });
  580. if (enabled || !minimized->entity()->checked()) {
  581. Local::writeSettings();
  582. } else {
  583. minimized->entity()->setChecked(false);
  584. }
  585. }));
  586. }, autostart->lifetime());
  587. Platform::AutostartRequestStateFromSystem(crl::guard(
  588. controller,
  589. [=](bool enabled) { autostart->setChecked(enabled); }));
  590. minimized->toggleOn(autostart->checkedValue());
  591. minimized->entity()->checkedChanges(
  592. ) | rpl::filter([=](bool checked) {
  593. return (checked != minimizedToggled());
  594. }) | rpl::start_with_next([=](bool checked) {
  595. if (controller->session().domain().local().hasLocalPasscode()) {
  596. minimized->entity()->setChecked(false);
  597. controller->show(Ui::MakeInformBox(
  598. tr::lng_error_start_minimized_passcoded()));
  599. } else {
  600. cSetStartMinimized(checked);
  601. Local::writeSettings();
  602. }
  603. }, minimized->lifetime());
  604. controller->session().domain().local().localPasscodeChanged(
  605. ) | rpl::start_with_next([=] {
  606. minimized->entity()->setChecked(minimizedToggled());
  607. }, minimized->lifetime());
  608. }
  609. if (Platform::IsWindows() && !Platform::IsWindowsStoreBuild()) {
  610. const auto sendto = addCheckbox(
  611. tr::lng_settings_add_sendto(),
  612. cSendToMenu());
  613. sendto->checkedChanges(
  614. ) | rpl::filter([](bool checked) {
  615. return (checked != cSendToMenu());
  616. }) | rpl::start_with_next([](bool checked) {
  617. cSetSendToMenu(checked);
  618. psSendToMenu(checked);
  619. Local::writeSettings();
  620. }, sendto->lifetime());
  621. }
  622. }
  623. template <typename Fill>
  624. void CheckNonEmptyOptions(
  625. not_null<Window::SessionController*> controller,
  626. not_null<Ui::VerticalLayout*> container,
  627. Fill fill) {
  628. auto wrap = object_ptr<Ui::VerticalLayout>(container);
  629. fill(controller, wrap.data());
  630. if (wrap->count() > 0) {
  631. container->add(object_ptr<Ui::OverrideMargins>(
  632. container,
  633. std::move(wrap)));
  634. Ui::AddSkip(container, st::settingsCheckboxesSkip);
  635. }
  636. }
  637. void SetupSystemIntegrationOptions(
  638. not_null<Window::SessionController*> controller,
  639. not_null<Ui::VerticalLayout*> container) {
  640. CheckNonEmptyOptions(
  641. controller,
  642. container,
  643. SetupSystemIntegrationContent);
  644. }
  645. void SetupWindowTitleOptions(
  646. not_null<Window::SessionController*> controller,
  647. not_null<Ui::VerticalLayout*> container) {
  648. CheckNonEmptyOptions(
  649. controller,
  650. container,
  651. SetupWindowTitleContent);
  652. }
  653. void SetupAnimations(
  654. not_null<Window::Controller*> window,
  655. not_null<Ui::VerticalLayout*> container) {
  656. container->add(object_ptr<Button>(
  657. container,
  658. tr::lng_settings_power_menu(),
  659. st::settingsButtonNoIcon
  660. ))->setClickedCallback([=] { window->show(Box(PowerSavingBox)); });
  661. }
  662. void ArchiveSettingsBox(
  663. not_null<Ui::GenericBox*> box,
  664. not_null<Window::SessionController*> controller) {
  665. box->setTitle(tr::lng_settings_archive_title());
  666. box->setWidth(st::boxWideWidth);
  667. box->addButton(tr::lng_about_done(), [=] { box->closeBox(); });
  668. PreloadArchiveSettings(&controller->session());
  669. struct State {
  670. Ui::SlideWrap<Ui::VerticalLayout> *foldersWrap = nullptr;
  671. Ui::SettingsButton *folders = nullptr;
  672. };
  673. const auto state = box->lifetime().make_state<State>();
  674. const auto privacy = &controller->session().api().globalPrivacy();
  675. const auto container = box->verticalLayout();
  676. AddSkip(container);
  677. AddSubsectionTitle(container, tr::lng_settings_unmuted_chats());
  678. using Unarchive = Api::UnarchiveOnNewMessage;
  679. container->add(object_ptr<Button>(
  680. container,
  681. tr::lng_settings_always_in_archive(),
  682. st::settingsButtonNoIcon
  683. ))->toggleOn(privacy->unarchiveOnNewMessage(
  684. ) | rpl::map(
  685. rpl::mappers::_1 == Unarchive::None
  686. ))->toggledChanges(
  687. ) | rpl::filter([=](bool toggled) {
  688. const auto current = privacy->unarchiveOnNewMessageCurrent();
  689. state->foldersWrap->toggle(!toggled, anim::type::normal);
  690. return toggled != (current == Unarchive::None);
  691. }) | rpl::start_with_next([=](bool toggled) {
  692. privacy->updateUnarchiveOnNewMessage(toggled
  693. ? Unarchive::None
  694. : state->folders->toggled()
  695. ? Unarchive::NotInFoldersUnmuted
  696. : Unarchive::AnyUnmuted);
  697. }, container->lifetime());
  698. AddSkip(container);
  699. AddDividerText(container, tr::lng_settings_unmuted_chats_about());
  700. state->foldersWrap = container->add(
  701. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  702. container,
  703. object_ptr<Ui::VerticalLayout>(container)));
  704. const auto inner = state->foldersWrap->entity();
  705. AddSkip(inner);
  706. AddSubsectionTitle(inner, tr::lng_settings_chats_from_folders());
  707. state->folders = inner->add(object_ptr<Button>(
  708. inner,
  709. tr::lng_settings_always_in_archive(),
  710. st::settingsButtonNoIcon
  711. ))->toggleOn(privacy->unarchiveOnNewMessage(
  712. ) | rpl::map(
  713. rpl::mappers::_1 != Unarchive::AnyUnmuted
  714. ));
  715. state->folders->toggledChanges(
  716. ) | rpl::filter([=](bool toggled) {
  717. const auto current = privacy->unarchiveOnNewMessageCurrent();
  718. return toggled != (current != Unarchive::AnyUnmuted);
  719. }) | rpl::start_with_next([=](bool toggled) {
  720. const auto current = privacy->unarchiveOnNewMessageCurrent();
  721. privacy->updateUnarchiveOnNewMessage(!toggled
  722. ? Unarchive::AnyUnmuted
  723. : (current == Unarchive::AnyUnmuted)
  724. ? Unarchive::NotInFoldersUnmuted
  725. : current);
  726. }, inner->lifetime());
  727. AddSkip(inner);
  728. AddDividerText(inner, tr::lng_settings_chats_from_folders_about());
  729. state->foldersWrap->toggle(
  730. privacy->unarchiveOnNewMessageCurrent() != Unarchive::None,
  731. anim::type::instant);
  732. SetupArchiveAndMute(controller, box->verticalLayout());
  733. }
  734. void PreloadArchiveSettings(not_null<::Main::Session*> session) {
  735. session->api().globalPrivacy().reload();
  736. }
  737. void SetupHardwareAcceleration(not_null<Ui::VerticalLayout*> container) {
  738. const auto settings = &Core::App().settings();
  739. container->add(object_ptr<Button>(
  740. container,
  741. tr::lng_settings_enable_hwaccel(),
  742. st::settingsButtonNoIcon
  743. ))->toggleOn(
  744. rpl::single(settings->hardwareAcceleratedVideo())
  745. )->toggledValue(
  746. ) | rpl::filter([=](bool enabled) {
  747. return (enabled != settings->hardwareAcceleratedVideo());
  748. }) | rpl::start_with_next([=](bool enabled) {
  749. settings->setHardwareAcceleratedVideo(enabled);
  750. Core::App().saveSettingsDelayed();
  751. }, container->lifetime());
  752. }
  753. #ifdef DESKTOP_APP_USE_ANGLE
  754. void SetupANGLE(
  755. not_null<Window::SessionController*> controller,
  756. not_null<Ui::VerticalLayout*> container) {
  757. using ANGLE = Ui::GL::ANGLE;
  758. const auto options = std::vector{
  759. tr::lng_settings_angle_backend_auto(tr::now),
  760. tr::lng_settings_angle_backend_d3d11(tr::now),
  761. tr::lng_settings_angle_backend_d3d9(tr::now),
  762. tr::lng_settings_angle_backend_d3d11on12(tr::now),
  763. //tr::lng_settings_angle_backend_opengl(tr::now),
  764. tr::lng_settings_angle_backend_disabled(tr::now),
  765. };
  766. const auto disabled = int(options.size()) - 1;
  767. const auto backendIndex = [=] {
  768. if (Core::App().settings().disableOpenGL()) {
  769. return disabled;
  770. } else switch (Ui::GL::CurrentANGLE()) {
  771. case ANGLE::Auto: return 0;
  772. case ANGLE::D3D11: return 1;
  773. case ANGLE::D3D9: return 2;
  774. case ANGLE::D3D11on12: return 3;
  775. //case ANGLE::OpenGL: return 4;
  776. }
  777. Unexpected("Ui::GL::CurrentANGLE value in SetupANGLE.");
  778. }();
  779. const auto button = AddButtonWithLabel(
  780. container,
  781. tr::lng_settings_angle_backend(),
  782. rpl::single(options[backendIndex]),
  783. st::settingsButtonNoIcon);
  784. button->addClickHandler([=] {
  785. controller->show(Box([=](not_null<Ui::GenericBox*> box) {
  786. const auto save = [=](int index) {
  787. if (index == backendIndex) {
  788. return;
  789. }
  790. const auto confirmed = crl::guard(button, [=] {
  791. const auto nowDisabled = (index == disabled);
  792. if (!nowDisabled) {
  793. Ui::GL::ChangeANGLE([&] {
  794. switch (index) {
  795. case 0: return ANGLE::Auto;
  796. case 1: return ANGLE::D3D11;
  797. case 2: return ANGLE::D3D9;
  798. case 3: return ANGLE::D3D11on12;
  799. //case 4: return ANGLE::OpenGL;
  800. }
  801. Unexpected("Index in SetupANGLE.");
  802. }());
  803. }
  804. const auto wasDisabled = (backendIndex == disabled);
  805. if (nowDisabled != wasDisabled) {
  806. Core::App().settings().setDisableOpenGL(nowDisabled);
  807. Local::writeSettings();
  808. }
  809. Core::Restart();
  810. });
  811. controller->show(Ui::MakeConfirmBox({
  812. .text = tr::lng_settings_need_restart(),
  813. .confirmed = confirmed,
  814. .confirmText = tr::lng_settings_restart_now(),
  815. }));
  816. };
  817. SingleChoiceBox(box, {
  818. .title = tr::lng_settings_angle_backend(),
  819. .options = options,
  820. .initialSelection = backendIndex,
  821. .callback = save,
  822. });
  823. }));
  824. });
  825. }
  826. #endif // DESKTOP_APP_USE_ANGLE
  827. void SetupOpenGL(
  828. not_null<Window::SessionController*> controller,
  829. not_null<Ui::VerticalLayout*> container) {
  830. const auto toggles = container->lifetime().make_state<
  831. rpl::event_stream<bool>
  832. >();
  833. const auto button = container->add(object_ptr<Button>(
  834. container,
  835. tr::lng_settings_enable_opengl(),
  836. st::settingsButtonNoIcon
  837. ))->toggleOn(
  838. toggles->events_starting_with_copy(
  839. !Core::App().settings().disableOpenGL())
  840. );
  841. button->toggledValue(
  842. ) | rpl::filter([](bool enabled) {
  843. return (enabled == Core::App().settings().disableOpenGL());
  844. }) | rpl::start_with_next([=](bool enabled) {
  845. const auto confirmed = crl::guard(button, [=] {
  846. Core::App().settings().setDisableOpenGL(!enabled);
  847. Local::writeSettings();
  848. Core::Restart();
  849. });
  850. const auto cancelled = crl::guard(button, [=](Fn<void()> close) {
  851. toggles->fire(!enabled);
  852. close();
  853. });
  854. controller->show(Ui::MakeConfirmBox({
  855. .text = tr::lng_settings_need_restart(),
  856. .confirmed = confirmed,
  857. .cancelled = cancelled,
  858. .confirmText = tr::lng_settings_restart_now(),
  859. }));
  860. }, container->lifetime());
  861. }
  862. void SetupPerformance(
  863. not_null<Window::SessionController*> controller,
  864. not_null<Ui::VerticalLayout*> container) {
  865. SetupAnimations(&controller->window(), container);
  866. SetupHardwareAcceleration(container);
  867. #ifdef DESKTOP_APP_USE_ANGLE
  868. SetupANGLE(controller, container);
  869. #else // DESKTOP_APP_USE_ANGLE
  870. if constexpr (!Platform::IsMac()) {
  871. SetupOpenGL(controller, container);
  872. }
  873. #endif // DESKTOP_APP_USE_ANGLE
  874. }
  875. void SetupWindowTitle(
  876. not_null<Window::SessionController*> controller,
  877. not_null<Ui::VerticalLayout*> container) {
  878. AddDivider(container);
  879. AddSkip(container);
  880. AddSubsectionTitle(container, tr::lng_settings_window_system());
  881. SetupWindowTitleOptions(controller, container);
  882. AddSkip(container);
  883. }
  884. void SetupWindowCloseBehavior(
  885. not_null<Window::SessionController*> controller,
  886. not_null<Ui::VerticalLayout*> container) {
  887. #if !defined Q_OS_WIN && !defined Q_OS_MAC
  888. const auto wrap = container->add(
  889. object_ptr<Ui::SlideWrap<Ui::VerticalLayout>>(
  890. container,
  891. object_ptr<Ui::VerticalLayout>(container)));
  892. const auto inner = wrap->entity();
  893. AddDivider(inner);
  894. AddSkip(inner);
  895. AddSubsectionTitle(inner, tr::lng_settings_window_close());
  896. const auto settings = &Core::App().settings();
  897. using Behavior = Core::Settings::CloseBehavior;
  898. const auto group = std::make_shared<Ui::RadioenumGroup<Behavior>>(
  899. settings->closeBehavior());
  900. const auto add = [&](Behavior value, const QString &label) {
  901. inner->add(
  902. object_ptr<Ui::Radioenum<Behavior>>(
  903. inner,
  904. group,
  905. value,
  906. label,
  907. st::settingsSendType),
  908. st::settingsSendTypePadding);
  909. };
  910. add(
  911. Behavior::RunInBackground,
  912. tr::lng_settings_run_in_background(tr::now));
  913. add(
  914. Behavior::CloseToTaskbar,
  915. tr::lng_settings_close_to_taskbar(tr::now));
  916. add(
  917. Behavior::Quit,
  918. tr::lng_settings_quit_on_close(tr::now));
  919. group->value() | rpl::filter([=](Behavior value) {
  920. return (value != settings->closeBehavior());
  921. }) | rpl::start_with_next([=](Behavior value) {
  922. settings->setCloseBehavior(value);
  923. Local::writeSettings();
  924. }, inner->lifetime());
  925. AddSkip(inner);
  926. if (!Platform::TrayIconSupported()) {
  927. wrap->toggle(true, anim::type::instant);
  928. } else {
  929. wrap->toggleOn(Core::App().settings().workModeValue(
  930. ) | rpl::map([=](Core::Settings::WorkMode mode) {
  931. return (mode == Core::Settings::WorkMode::WindowOnly);
  932. }) | rpl::distinct_until_changed(), anim::type::normal);
  933. wrap->finishAnimating();
  934. }
  935. #endif
  936. }
  937. void SetupSystemIntegration(
  938. not_null<Window::SessionController*> controller,
  939. not_null<Ui::VerticalLayout*> container) {
  940. AddDivider(container);
  941. AddSkip(container);
  942. AddSubsectionTitle(container, tr::lng_settings_system_integration());
  943. SetupSystemIntegrationOptions(controller, container);
  944. AddSkip(container);
  945. }
  946. Advanced::Advanced(
  947. QWidget *parent,
  948. not_null<Window::SessionController*> controller)
  949. : Section(parent) {
  950. setupContent(controller);
  951. }
  952. rpl::producer<QString> Advanced::title() {
  953. return tr::lng_settings_advanced();
  954. }
  955. void Advanced::setupContent(not_null<Window::SessionController*> controller) {
  956. const auto content = Ui::CreateChild<Ui::VerticalLayout>(this);
  957. auto empty = true;
  958. const auto addDivider = [&] {
  959. if (empty) {
  960. empty = false;
  961. } else {
  962. AddDivider(content);
  963. }
  964. };
  965. const auto addUpdate = [&] {
  966. if (HasUpdate()) {
  967. addDivider();
  968. AddSkip(content);
  969. AddSubsectionTitle(content, tr::lng_settings_version_info());
  970. SetupUpdate(content);
  971. AddSkip(content);
  972. }
  973. };
  974. if (!cAutoUpdate()) {
  975. addUpdate();
  976. }
  977. addDivider();
  978. SetupDataStorage(controller, content);
  979. SetupAutoDownload(controller, content);
  980. SetupWindowTitle(controller, content);
  981. SetupWindowCloseBehavior(controller, content);
  982. SetupSystemIntegration(controller, content);
  983. empty = false;
  984. AddDivider(content);
  985. AddSkip(content);
  986. AddSubsectionTitle(content, tr::lng_settings_performance());
  987. SetupPerformance(controller, content);
  988. AddSkip(content);
  989. if (HasSystemSpellchecker()) {
  990. AddDivider(content);
  991. AddSkip(content);
  992. AddSubsectionTitle(content, tr::lng_settings_spellchecker());
  993. SetupSpellchecker(controller, content);
  994. AddSkip(content);
  995. }
  996. if (cAutoUpdate()) {
  997. addUpdate();
  998. }
  999. AddSkip(content);
  1000. AddDivider(content);
  1001. AddSkip(content);
  1002. SetupExport(controller, content, showOtherMethod());
  1003. Ui::ResizeFitChild(this, content);
  1004. }
  1005. } // namespace Settings