window_controller.cpp 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  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 "window/window_controller.h"
  8. #include "api/api_updates.h"
  9. #include "core/application.h"
  10. #include "core/click_handler_types.h"
  11. #include "export/export_manager.h"
  12. #include "ui/platform/ui_platform_window.h"
  13. #include "platform/platform_window_title.h"
  14. #include "main/main_account.h"
  15. #include "main/main_domain.h"
  16. #include "main/main_session.h"
  17. #include "main/main_session_settings.h"
  18. #include "main/main_app_config.h"
  19. #include "media/view/media_view_open_common.h"
  20. #include "lang/lang_keys.h"
  21. #include "intro/intro_widget.h"
  22. #include "mtproto/mtproto_config.h"
  23. #include "ui/toast/toast.h"
  24. #include "ui/emoji_config.h"
  25. #include "chat_helpers/emoji_sets_manager.h"
  26. #include "window/window_session_controller.h"
  27. #include "window/themes/window_theme_editor.h"
  28. #include "ui/boxes/confirm_box.h"
  29. #include "data/data_thread.h"
  30. #include "apiwrap.h" // ApiWrap::acceptTerms.
  31. #include "styles/style_layers.h"
  32. #include <QtGui/QWindow>
  33. #include <QtGui/QScreen>
  34. namespace Window {
  35. namespace {
  36. class Show final : public Ui::Show {
  37. public:
  38. explicit Show(not_null<Controller*> window);
  39. void showOrHideBoxOrLayer(
  40. std::variant<
  41. v::null_t,
  42. object_ptr<Ui::BoxContent>,
  43. std::unique_ptr<Ui::LayerWidget>> &&layer,
  44. Ui::LayerOptions options,
  45. anim::type animated) const override;
  46. [[nodiscard]] not_null<QWidget*> toastParent() const override;
  47. [[nodiscard]] bool valid() const override;
  48. operator bool() const override;
  49. private:
  50. const base::weak_ptr<Controller> _window;
  51. };
  52. Show::Show(not_null<Controller*> window)
  53. : _window(base::make_weak(window)) {
  54. }
  55. void Show::showOrHideBoxOrLayer(
  56. std::variant<
  57. v::null_t,
  58. object_ptr<Ui::BoxContent>,
  59. std::unique_ptr<Ui::LayerWidget>> &&layer,
  60. Ui::LayerOptions options,
  61. anim::type animated) const {
  62. if (const auto window = _window.get()) {
  63. window->widget()->showOrHideBoxOrLayer(
  64. std::move(layer),
  65. options,
  66. animated);
  67. }
  68. }
  69. not_null<QWidget*> Show::toastParent() const {
  70. const auto window = _window.get();
  71. Assert(window != nullptr);
  72. return window->widget()->bodyWidget();
  73. }
  74. bool Show::valid() const {
  75. return !_window.empty();
  76. }
  77. Show::operator bool() const {
  78. return valid();
  79. }
  80. } // namespace
  81. Controller::Controller() : Controller(CreateArgs{ nullptr }) {
  82. }
  83. Controller::Controller(SeparateId id, MsgId showAtMsgId)
  84. : Controller(CreateArgs{ id }) {
  85. if (id) {
  86. showAccount(id.account, showAtMsgId);
  87. }
  88. }
  89. Controller::Controller(CreateArgs &&args)
  90. : _id(args.id)
  91. , _isActiveTimer([=] { updateIsActive(); })
  92. , _widget(this)
  93. , _adaptive(std::make_unique<Adaptive>()) {
  94. _widget.init();
  95. }
  96. Controller::~Controller() {
  97. // We want to delete all widgets before the _sessionController.
  98. _widget.ui_hideSettingsAndLayer(anim::type::instant);
  99. _widget.clearWidgets();
  100. _accountLifetime.destroy();
  101. _sessionControllerValue = nullptr;
  102. _sessionController = nullptr;
  103. }
  104. SeparateId Controller::id() const {
  105. return _id;
  106. }
  107. bool Controller::isPrimary() const {
  108. return _id.primary();
  109. }
  110. Main::Account &Controller::account() const {
  111. Expects(_id.account != nullptr);
  112. return *_id.account;
  113. }
  114. void Controller::showAccount(not_null<Main::Account*> account) {
  115. showAccount(account, ShowAtUnreadMsgId);
  116. }
  117. void Controller::showAccount(
  118. not_null<Main::Account*> account,
  119. MsgId singlePeerShowAtMsgId) {
  120. Expects(isPrimary() || _id.account == account);
  121. const auto prevSession = maybeSession();
  122. const auto prevSessionUniqueId = prevSession
  123. ? prevSession->uniqueId()
  124. : 0;
  125. _accountLifetime.destroy();
  126. _id.account = account;
  127. Core::App().checkWindowId(this);
  128. const auto updateOnlineOfPrevSesssion = crl::guard(account, [=] {
  129. if (!prevSessionUniqueId) {
  130. return;
  131. }
  132. for (auto &[index, account] : _id.account->domain().accounts()) {
  133. if (const auto anotherSession = account->maybeSession()) {
  134. if (anotherSession->uniqueId() == prevSessionUniqueId) {
  135. anotherSession->updates().updateOnline(crl::now());
  136. return;
  137. }
  138. }
  139. }
  140. });
  141. if (!isPrimary()) {
  142. _id.account->sessionChanges(
  143. ) | rpl::start_with_next([=](Main::Session *session) {
  144. Core::App().closeWindow(this);
  145. }, _accountLifetime);
  146. }
  147. _id.account->sessionValue(
  148. ) | rpl::start_with_next([=](Main::Session *session) {
  149. const auto was = base::take(_sessionController);
  150. _sessionController = session
  151. ? std::make_unique<SessionController>(session, this)
  152. : nullptr;
  153. _sessionControllerValue = _sessionController.get();
  154. auto oldContentCache = _widget.grabForSlideAnimation();
  155. _widget.updateWindowIcon();
  156. if (session) {
  157. setupSideBar();
  158. setupMain(singlePeerShowAtMsgId, std::move(oldContentCache));
  159. session->updates().isIdleValue(
  160. ) | rpl::filter([=](bool idle) {
  161. return !idle;
  162. }) | rpl::start_with_next([=] {
  163. widget()->checkActivation();
  164. }, _sessionController->lifetime());
  165. session->termsLockValue(
  166. ) | rpl::start_with_next([=] {
  167. checkLockByTerms();
  168. _widget.updateGlobalMenu();
  169. }, _sessionController->lifetime());
  170. widget()->setInnerFocus();
  171. _sessionController->activeChatChanges(
  172. ) | rpl::start_with_next([=] {
  173. _widget.updateTitle();
  174. }, _sessionController->lifetime());
  175. _widget.updateTitle();
  176. session->updates().updateOnline(crl::now());
  177. } else {
  178. sideBarChanged();
  179. setupIntro(std::move(oldContentCache));
  180. _widget.updateGlobalMenu();
  181. }
  182. crl::on_main(updateOnlineOfPrevSesssion);
  183. }, _accountLifetime);
  184. }
  185. void Controller::setupSideBar() {
  186. Expects(_sessionController != nullptr);
  187. if (!isPrimary()) {
  188. return;
  189. }
  190. _sessionController->filtersMenuChanged(
  191. ) | rpl::start_with_next([=] {
  192. sideBarChanged();
  193. }, _sessionController->lifetime());
  194. if (_sessionController->session().settings().dialogsFiltersEnabled()
  195. && _sessionController->enoughSpaceForFilters()
  196. && !Core::App().settings().chatFiltersHorizontal()) {
  197. _sessionController->toggleFiltersMenu(true);
  198. } else {
  199. sideBarChanged();
  200. }
  201. }
  202. void Controller::checkLockByTerms() {
  203. const auto data = account().sessionExists()
  204. ? account().session().termsLocked()
  205. : std::nullopt;
  206. if (!data) {
  207. if (_termsBox) {
  208. _termsBox->closeBox();
  209. }
  210. return;
  211. }
  212. hideSettingsAndLayer(anim::type::instant);
  213. const auto box = show(Box<TermsBox>(
  214. *data,
  215. tr::lng_terms_agree(),
  216. tr::lng_terms_decline()));
  217. box->setCloseByEscape(false);
  218. box->setCloseByOutsideClick(false);
  219. const auto id = data->id;
  220. box->agreeClicks(
  221. ) | rpl::start_with_next([=] {
  222. const auto mention = box ? box->lastClickedMention() : QString();
  223. box->closeBox();
  224. if (const auto session = account().maybeSession()) {
  225. session->api().acceptTerms(id);
  226. session->unlockTerms();
  227. if (!mention.isEmpty()) {
  228. MentionClickHandler(mention).onClick({});
  229. }
  230. }
  231. }, box->lifetime());
  232. box->cancelClicks(
  233. ) | rpl::start_with_next([=] {
  234. showTermsDecline();
  235. }, box->lifetime());
  236. QObject::connect(box, &QObject::destroyed, [=] {
  237. crl::on_main(widget(), [=] { checkLockByTerms(); });
  238. });
  239. _termsBox = box;
  240. }
  241. void Controller::showTermsDecline() {
  242. const auto box = show(Box<Window::TermsBox>(
  243. TextWithEntities{ tr::lng_terms_update_sorry(tr::now) },
  244. tr::lng_terms_decline_and_delete(),
  245. tr::lng_terms_back(),
  246. true));
  247. box->agreeClicks(
  248. ) | rpl::start_with_next([=] {
  249. if (box) {
  250. box->closeBox();
  251. }
  252. showTermsDelete();
  253. }, box->lifetime());
  254. box->cancelClicks(
  255. ) | rpl::start_with_next([=] {
  256. if (box) {
  257. box->closeBox();
  258. }
  259. }, box->lifetime());
  260. }
  261. void Controller::showTermsDelete() {
  262. const auto deleteByTerms = [=] {
  263. if (const auto session = account().maybeSession()) {
  264. session->termsDeleteNow();
  265. } else {
  266. hideLayer();
  267. }
  268. };
  269. show(Ui::MakeConfirmBox({
  270. .text = tr::lng_terms_delete_warning(),
  271. .confirmed = deleteByTerms,
  272. .confirmText = tr::lng_terms_delete_now(),
  273. .confirmStyle = &st::attentionBoxButton,
  274. }));
  275. }
  276. void Controller::firstShow() {
  277. _widget.firstShow();
  278. }
  279. void Controller::finishFirstShow() {
  280. _widget.finishFirstShow();
  281. checkThemeEditor();
  282. }
  283. Main::Session *Controller::maybeSession() const {
  284. return _id.account ? _id.account->maybeSession() : nullptr;
  285. }
  286. auto Controller::sessionControllerValue() const
  287. -> rpl::producer<SessionController*> {
  288. return _sessionControllerValue.value();
  289. }
  290. auto Controller::sessionControllerChanges() const
  291. -> rpl::producer<SessionController*> {
  292. return _sessionControllerValue.changes();
  293. }
  294. bool Controller::locked() const {
  295. if (Core::App().passcodeLocked()) {
  296. return true;
  297. } else if (const auto controller = sessionController()) {
  298. return controller->session().termsLocked().has_value();
  299. }
  300. return false;
  301. }
  302. void Controller::checkThemeEditor() {
  303. using namespace Window::Theme;
  304. if (const auto editing = Background()->editingTheme()) {
  305. showRightColumn(Box<Editor>(this, *editing));
  306. }
  307. }
  308. void Controller::setupPasscodeLock() {
  309. _widget.setupPasscodeLock();
  310. }
  311. void Controller::clearPasscodeLock() {
  312. if (!_id) {
  313. showAccount(&Core::App().activeAccount());
  314. } else {
  315. _widget.clearPasscodeLock();
  316. }
  317. }
  318. void Controller::setupIntro(QPixmap oldContentCache) {
  319. const auto point = Core::App().domain().maybeLastOrSomeAuthedAccount()
  320. ? Intro::EnterPoint::Qr
  321. : Intro::EnterPoint::Start;
  322. _widget.setupIntro(point, std::move(oldContentCache));
  323. }
  324. void Controller::setupMain(
  325. MsgId singlePeerShowAtMsgId,
  326. QPixmap oldContentCache) {
  327. Expects(_sessionController != nullptr);
  328. _widget.setupMain(singlePeerShowAtMsgId, std::move(oldContentCache));
  329. if (const auto id = Ui::Emoji::NeedToSwitchBackToId()) {
  330. Ui::Emoji::LoadAndSwitchTo(&_sessionController->session(), id);
  331. }
  332. }
  333. void Controller::showSettings() {
  334. _widget.showSettings();
  335. }
  336. int Controller::verticalShadowTop() const {
  337. return (Platform::NativeTitleRequiresShadow()
  338. && Ui::Platform::NativeWindowFrameSupported()
  339. && Core::App().settings().nativeWindowFrame())
  340. ? st::lineWidth
  341. : 0;
  342. }
  343. void Controller::showToast(Ui::Toast::Config &&config) {
  344. Show(this).showToast(std::move(config));
  345. }
  346. void Controller::showToast(TextWithEntities &&text, crl::time duration) {
  347. Show(this).showToast(std::move(text), duration);
  348. }
  349. void Controller::showToast(const QString &text, crl::time duration) {
  350. Show(this).showToast(text, duration);
  351. }
  352. void Controller::showLayer(
  353. std::unique_ptr<Ui::LayerWidget> &&layer,
  354. Ui::LayerOptions options,
  355. anim::type animated) {
  356. _widget.showOrHideBoxOrLayer(std::move(layer), options, animated);
  357. }
  358. void Controller::showBox(
  359. object_ptr<Ui::BoxContent> content,
  360. Ui::LayerOptions options,
  361. anim::type animated) {
  362. _widget.showOrHideBoxOrLayer(std::move(content), options, animated);
  363. }
  364. void Controller::showRightColumn(object_ptr<TWidget> widget) {
  365. _widget.showRightColumn(std::move(widget));
  366. }
  367. void Controller::hideLayer(anim::type animated) {
  368. _widget.showOrHideBoxOrLayer(v::null, Ui::LayerOption::CloseOther, animated);
  369. }
  370. void Controller::hideSettingsAndLayer(anim::type animated) {
  371. _widget.ui_hideSettingsAndLayer(animated);
  372. }
  373. bool Controller::isLayerShown() const {
  374. return _widget.ui_isLayerShown();
  375. }
  376. void Controller::sideBarChanged() {
  377. _widget.recountGeometryConstraints();
  378. }
  379. void Controller::activate() {
  380. _widget.activate();
  381. }
  382. void Controller::updateIsActiveFocus() {
  383. _isActiveTimer.callOnce(sessionController()
  384. ? sessionController()->session().serverConfig().onlineFocusTimeout
  385. : crl::time(1000));
  386. }
  387. void Controller::updateIsActiveBlur() {
  388. _isActiveTimer.callOnce(sessionController()
  389. ? sessionController()->session().serverConfig().offlineBlurTimeout
  390. : crl::time(1000));
  391. }
  392. void Controller::updateIsActive() {
  393. _widget.updateIsActive();
  394. }
  395. void Controller::minimize() {
  396. if (Core::App().settings().workMode()
  397. == Core::Settings::WorkMode::TrayOnly) {
  398. _widget.minimizeToTray();
  399. } else {
  400. _widget.setWindowState(_widget.windowState() | Qt::WindowMinimized);
  401. }
  402. }
  403. void Controller::close() {
  404. _widget.close();
  405. }
  406. void Controller::preventOrInvoke(Fn<void()> &&callback) {
  407. _widget.preventOrInvoke(std::move(callback));
  408. }
  409. void Controller::invokeForSessionController(
  410. not_null<Main::Account*> account,
  411. PeerData *singlePeer,
  412. Fn<void(not_null<SessionController*>)> &&callback) {
  413. const auto separateWindow = singlePeer
  414. ? Core::App().separateWindowFor(not_null(singlePeer))
  415. : nullptr;
  416. const auto separateSession = separateWindow
  417. ? separateWindow->sessionController()
  418. : nullptr;
  419. if (separateSession) {
  420. return callback(separateSession);
  421. }
  422. const auto accountWindow = account
  423. ? Core::App().separateWindowFor(not_null(account))
  424. : nullptr;
  425. const auto accountSession = accountWindow
  426. ? accountWindow->sessionController()
  427. : nullptr;
  428. if (accountSession) {
  429. return callback(accountSession);
  430. }
  431. _id.account->domain().activate(std::move(account));
  432. if (_sessionController) {
  433. callback(_sessionController.get());
  434. }
  435. }
  436. QPoint Controller::getPointForCallPanelCenter() const {
  437. return _widget.isActive()
  438. ? _widget.geometry().center()
  439. : _widget.screen()->geometry().center();
  440. }
  441. void Controller::showLogoutConfirmation() {
  442. const auto account = Core::App().passcodeLocked()
  443. ? nullptr
  444. : sessionController()
  445. ? &sessionController()->session().account()
  446. : nullptr;
  447. const auto weak = base::make_weak(account);
  448. const auto callback = [=](Fn<void()> close) {
  449. if (!account || weak) {
  450. Core::App().logoutWithChecks(account);
  451. }
  452. if (close) {
  453. close();
  454. }
  455. };
  456. show(Ui::MakeConfirmBox({
  457. .text = tr::lng_sure_logout(),
  458. .confirmed = callback,
  459. .confirmText = tr::lng_settings_logout(),
  460. .confirmStyle = &st::attentionBoxButton,
  461. }));
  462. }
  463. Window::Adaptive &Controller::adaptive() const {
  464. return *_adaptive;
  465. }
  466. void Controller::openInMediaView(Media::View::OpenRequest &&request) {
  467. _openInMediaViewRequests.fire(std::move(request));
  468. }
  469. auto Controller::openInMediaViewRequests() const
  470. -> rpl::producer<Media::View::OpenRequest> {
  471. return _openInMediaViewRequests.events();
  472. }
  473. void Controller::setDefaultFloatPlayerDelegate(
  474. not_null<Media::Player::FloatDelegate*> delegate) {
  475. _defaultFloatPlayerDelegate = delegate;
  476. _replacementFloatPlayerDelegate = nullptr;
  477. _floatPlayerDelegate = delegate;
  478. }
  479. void Controller::replaceFloatPlayerDelegate(
  480. not_null<Media::Player::FloatDelegate*> replacement) {
  481. Expects(_defaultFloatPlayerDelegate != nullptr);
  482. _replacementFloatPlayerDelegate = replacement;
  483. _floatPlayerDelegate = replacement;
  484. }
  485. void Controller::restoreFloatPlayerDelegate(
  486. not_null<Media::Player::FloatDelegate*> replacement) {
  487. Expects(_defaultFloatPlayerDelegate != nullptr);
  488. if (_replacementFloatPlayerDelegate == replacement) {
  489. _replacementFloatPlayerDelegate = nullptr;
  490. _floatPlayerDelegate = _defaultFloatPlayerDelegate;
  491. }
  492. }
  493. auto Controller::floatPlayerDelegate() const -> FloatDelegate* {
  494. return _floatPlayerDelegate.current();
  495. }
  496. auto Controller::floatPlayerDelegateValue() const
  497. -> rpl::producer<FloatDelegate*> {
  498. return _floatPlayerDelegate.value();
  499. }
  500. std::shared_ptr<Ui::Show> Controller::uiShow() {
  501. return std::make_shared<Show>(this);
  502. }
  503. rpl::lifetime &Controller::lifetime() {
  504. return _lifetime;
  505. }
  506. } // namespace Window