main_window.cpp 31 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150
  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/main_window.h"
  8. #include "api/api_updates.h"
  9. #include "storage/localstorage.h"
  10. #include "platform/platform_specific.h"
  11. #include "ui/platform/ui_platform_window.h"
  12. #include "platform/platform_window_title.h"
  13. #include "history/history.h"
  14. #include "window/window_separate_id.h"
  15. #include "window/window_session_controller.h"
  16. #include "window/window_lock_widgets.h"
  17. #include "window/window_controller.h"
  18. #include "main/main_account.h" // Account::sessionValue.
  19. #include "main/main_domain.h"
  20. #include "core/application.h"
  21. #include "core/sandbox.h"
  22. #include "core/shortcuts.h"
  23. #include "lang/lang_keys.h"
  24. #include "data/data_session.h"
  25. #include "data/data_forum_topic.h"
  26. #include "data/data_user.h"
  27. #include "main/main_session.h"
  28. #include "main/main_session_settings.h"
  29. #include "base/options.h"
  30. #include "base/crc32hash.h"
  31. #include "ui/toast/toast.h"
  32. #include "ui/widgets/shadow.h"
  33. #include "ui/controls/window_outdated_bar.h"
  34. #include "ui/painter.h"
  35. #include "ui/ui_utility.h"
  36. #include "apiwrap.h"
  37. #include "mainwidget.h" // session->content()->windowShown().
  38. #include "tray.h"
  39. #include "styles/style_window.h"
  40. #include "styles/style_dialogs.h" // ChildSkip().x() for new child windows.
  41. #include <QtCore/QMimeData>
  42. #include <QtGui/QWindow>
  43. #include <QtGui/QScreen>
  44. #include <QtGui/QDrag>
  45. #include <kurlmimedata.h>
  46. namespace Window {
  47. namespace {
  48. constexpr auto kSaveWindowPositionTimeout = crl::time(1000);
  49. using Core::WindowPosition;
  50. [[nodiscard]] QPoint ChildSkip() {
  51. const auto skipx = st::defaultDialogRow.padding.left()
  52. + st::defaultDialogRow.photoSize
  53. + st::defaultDialogRow.padding.left();
  54. const auto skipy = st::windowTitleHeight;
  55. return { skipx, skipy };
  56. }
  57. [[nodiscard]] QImage &OverridenIcon() {
  58. static auto result = QImage();
  59. return result;
  60. }
  61. base::options::toggle OptionNewWindowsSizeAsFirst({
  62. .id = kOptionNewWindowsSizeAsFirst,
  63. .name = "Adjust size of new chat windows.",
  64. .description = "Open new windows with a size of the main window.",
  65. });
  66. base::options::toggle OptionDisableTouchbar({
  67. .id = kOptionDisableTouchbar,
  68. .name = "Disable Touch Bar (macOS only).",
  69. .scope = [] {
  70. #ifdef Q_OS_MAC
  71. return true;
  72. #else // !Q_OS_MAC
  73. return false;
  74. #endif // !Q_OS_MAC
  75. },
  76. .restartRequired = true,
  77. });
  78. [[nodiscard]] QString TitleFromSeparateId(
  79. const Core::WindowTitleContent &settings,
  80. const SeparateId &id) {
  81. if (id.sharedMedia == SeparateSharedMediaType::None
  82. || !id.sharedMediaPeer()) {
  83. return QString();
  84. }
  85. const auto result = (id.sharedMedia == SeparateSharedMediaType::Photos)
  86. ? tr::lng_media_type_photos(tr::now)
  87. : (id.sharedMedia == SeparateSharedMediaType::Videos)
  88. ? tr::lng_media_type_videos(tr::now)
  89. : (id.sharedMedia == SeparateSharedMediaType::Files)
  90. ? tr::lng_media_type_files(tr::now)
  91. : (id.sharedMedia == SeparateSharedMediaType::Audio)
  92. ? tr::lng_media_type_songs(tr::now)
  93. : (id.sharedMedia == SeparateSharedMediaType::Links)
  94. ? tr::lng_media_type_links(tr::now)
  95. : (id.sharedMedia == SeparateSharedMediaType::GIF)
  96. ? tr::lng_media_type_gifs(tr::now)
  97. : (id.sharedMedia == SeparateSharedMediaType::Voices)
  98. ? tr::lng_media_type_audios(tr::now)
  99. : QString();
  100. if (settings.hideChatName) {
  101. return result;
  102. }
  103. const auto peer = id.sharedMediaPeer();
  104. const auto topicRootId = id.sharedMediaTopicRootId();
  105. const auto topic = topicRootId
  106. ? peer->forumTopicFor(topicRootId)
  107. : nullptr;
  108. const auto name = topic
  109. ? topic->title()
  110. : peer->isSelf()
  111. ? tr::lng_saved_messages(tr::now)
  112. : peer->name();
  113. const auto wrapped = st::wrap_rtl(name);
  114. return name + u" @ "_q + result;
  115. }
  116. } // namespace
  117. const char kOptionNewWindowsSizeAsFirst[] = "new-windows-size-as-first";
  118. const char kOptionDisableTouchbar[] = "touchbar-disabled";
  119. const QImage &Logo() {
  120. static const auto result = QImage(u":/gui/art/logo_256.png"_q);
  121. return result;
  122. }
  123. const QImage &LogoNoMargin() {
  124. static const auto result = QImage(u":/gui/art/logo_256_no_margin.png"_q);
  125. return result;
  126. }
  127. void ConvertIconToBlack(QImage &image) {
  128. if (image.format() != QImage::Format_ARGB32_Premultiplied) {
  129. image = std::move(image).convertToFormat(
  130. QImage::Format_ARGB32_Premultiplied);
  131. }
  132. //const auto gray = red * 0.299 + green * 0.587 + blue * 0.114;
  133. //const auto result = (gray - 100 < 0) ? 0 : (gray - 100) * 255 / 155;
  134. constexpr auto scale = 255 / 155.;
  135. constexpr auto red = 0.299;
  136. constexpr auto green = 0.587;
  137. constexpr auto blue = 0.114;
  138. static constexpr auto shift = (1 << 24);
  139. auto shifter = [](double value) {
  140. return uint32(value * shift);
  141. };
  142. constexpr auto iscale = shifter(scale);
  143. constexpr auto ired = shifter(red);
  144. constexpr auto igreen = shifter(green);
  145. constexpr auto iblue = shifter(blue);
  146. constexpr auto threshold = 100;
  147. const auto width = image.width();
  148. const auto height = image.height();
  149. const auto data = reinterpret_cast<uint32*>(image.bits());
  150. const auto intsPerLine = image.bytesPerLine() / 4;
  151. const auto intsPerLineAdded = intsPerLine - width;
  152. auto pixel = data;
  153. for (auto j = 0; j != height; ++j) {
  154. for (auto i = 0; i != width; ++i) {
  155. const auto value = *pixel;
  156. const auto gray = (((value >> 16) & 0xFF) * ired
  157. + ((value >> 8) & 0xFF) * igreen
  158. + (value & 0xFF) * iblue) >> 24;
  159. const auto small = gray - threshold;
  160. const auto test = ~small;
  161. const auto result = (test >> 31) * small * iscale;
  162. const auto component = (result >> 24) & 0xFF;
  163. *pixel++ = (value & 0xFF000000U)
  164. | (component << 16)
  165. | (component << 8)
  166. | component;
  167. }
  168. pixel += intsPerLineAdded;
  169. }
  170. }
  171. void OverrideApplicationIcon(QImage image) {
  172. OverridenIcon() = std::move(image);
  173. }
  174. QIcon CreateOfficialIcon(Main::Session *session) {
  175. const auto support = (session && session->supportMode());
  176. if (!support) {
  177. return QIcon();
  178. }
  179. auto overriden = OverridenIcon();
  180. auto image = overriden.isNull()
  181. ? Platform::DefaultApplicationIcon()
  182. : overriden;
  183. ConvertIconToBlack(image);
  184. return QIcon(Ui::PixmapFromImage(std::move(image)));
  185. }
  186. QIcon CreateIcon(Main::Session *session, bool returnNullIfDefault) {
  187. const auto officialIcon = CreateOfficialIcon(session);
  188. if (!officialIcon.isNull() || returnNullIfDefault) {
  189. return officialIcon;
  190. }
  191. auto result = QIcon(Ui::PixmapFromImage(base::duplicate(Logo())));
  192. if constexpr (!Platform::IsLinux()) {
  193. return result;
  194. }
  195. const auto iconFromTheme = QIcon::fromTheme(
  196. Platform::ApplicationIconName(),
  197. result);
  198. result = QIcon();
  199. static const auto iconSizes = {
  200. 16,
  201. 22,
  202. 32,
  203. 48,
  204. 64,
  205. 128,
  206. 256,
  207. };
  208. // Qt's standard QIconLoaderEngine sets availableSizes
  209. // to XDG directories sizes, since svg icons are scalable,
  210. // they could be only in one XDG folder (like 48x48)
  211. // and Qt will set only a 48px icon to the window
  212. // even though the icon could be scaled to other sizes.
  213. // Thus, scale it manually to the most widespread sizes.
  214. for (const auto iconSize : iconSizes) {
  215. // We can't use QIcon::actualSize here
  216. // since it works incorrectly with svg icon themes
  217. const auto iconPixmap = iconFromTheme.pixmap(iconSize);
  218. const auto iconPixmapSize = iconPixmap.size()
  219. / iconPixmap.devicePixelRatio();
  220. // Not a svg icon, don't scale it
  221. if (iconPixmapSize.width() != iconSize) {
  222. return iconFromTheme;
  223. }
  224. result.addPixmap(iconPixmap);
  225. }
  226. return result;
  227. }
  228. QImage GenerateCounterLayer(CounterLayerArgs &&args) {
  229. const auto count = args.count.value();
  230. const auto text = (count < 1000)
  231. ? QString::number(count)
  232. : u"..%1"_q.arg(count % 100, 2, 10, QChar('0'));
  233. const auto textSize = text.size();
  234. struct Dimensions {
  235. int size = 0;
  236. int font = 0;
  237. int delta = 0;
  238. int radius = 0;
  239. };
  240. const auto d = [&]() -> Dimensions {
  241. switch (args.size.value()) {
  242. case 16:
  243. return {
  244. .size = 16,
  245. .font = ((textSize < 2) ? 11 : (textSize < 3) ? 11 : 8),
  246. .delta = ((textSize < 2) ? 5 : (textSize < 3) ? 2 : 1),
  247. .radius = ((textSize < 2) ? 8 : (textSize < 3) ? 7 : 3),
  248. };
  249. case 20:
  250. return {
  251. .size = 20,
  252. .font = ((textSize < 2) ? 14 : (textSize < 3) ? 13 : 10),
  253. .delta = ((textSize < 2) ? 6 : (textSize < 3) ? 2 : 1),
  254. .radius = ((textSize < 2) ? 10 : (textSize < 3) ? 9 : 5),
  255. };
  256. case 24:
  257. return {
  258. .size = 24,
  259. .font = ((textSize < 2) ? 17 : (textSize < 3) ? 16 : 12),
  260. .delta = ((textSize < 2) ? 7 : (textSize < 3) ? 3 : 1),
  261. .radius = ((textSize < 2) ? 12 : (textSize < 3) ? 11 : 6),
  262. };
  263. default:
  264. return {
  265. .size = 32,
  266. .font = ((textSize < 2) ? 22 : (textSize < 3) ? 20 : 16),
  267. .delta = ((textSize < 2) ? 9 : (textSize < 3) ? 4 : 2),
  268. .radius = ((textSize < 2) ? 16 : (textSize < 3) ? 14 : 8),
  269. };
  270. }
  271. }();
  272. auto result = QImage(
  273. QSize(d.size, d.size) * args.devicePixelRatio,
  274. QImage::Format_ARGB32);
  275. result.setDevicePixelRatio(args.devicePixelRatio);
  276. result.fill(Qt::transparent);
  277. auto p = QPainter(&result);
  278. auto hq = PainterHighQualityEnabler(p);
  279. const auto f = style::font{ d.font, 0, 0 };
  280. const auto w = f->width(text);
  281. p.setBrush(args.bg.value());
  282. p.setPen(Qt::NoPen);
  283. p.drawRoundedRect(
  284. QRect(
  285. d.size - w - d.delta * 2,
  286. d.size - f->height,
  287. w + d.delta * 2,
  288. f->height),
  289. d.radius,
  290. d.radius);
  291. p.setFont(f);
  292. p.setPen(args.fg.value());
  293. p.drawText(d.size - w - d.delta, d.size - f->height + f->ascent, text);
  294. p.end();
  295. return result;
  296. }
  297. QImage WithSmallCounter(QImage image, CounterLayerArgs &&args) {
  298. // platform/linux/tray_linux depends on count used the same
  299. // way for all the same (count % 100) values.
  300. const auto count = args.count.value();
  301. const auto text = (count < 100)
  302. ? QString::number(count)
  303. : QString("..%1").arg(count % 10, 1, 10, QChar('0'));
  304. const auto textSize = text.size();
  305. struct Dimensions {
  306. int size = 0;
  307. int font = 0;
  308. int delta = 0;
  309. int radius = 0;
  310. };
  311. const auto d = Dimensions{
  312. .size = args.size.value(),
  313. .font = args.size.value() / 2,
  314. .delta = args.size.value() / ((textSize < 2) ? 8 : 16),
  315. .radius = args.size.value() / ((textSize < 2) ? 4 : 5),
  316. };
  317. auto p = QPainter(&image);
  318. auto hq = PainterHighQualityEnabler(p);
  319. const auto f = style::font{ d.font, 0, 0 };
  320. const auto w = f->width(text);
  321. p.setBrush(args.bg.value());
  322. p.setPen(Qt::NoPen);
  323. p.drawRoundedRect(
  324. QRect(
  325. d.size - w - d.delta * 2,
  326. d.size - f->height,
  327. w + d.delta * 2,
  328. f->height),
  329. d.radius,
  330. d.radius);
  331. p.setFont(f);
  332. p.setPen(args.fg.value());
  333. p.drawText(d.size - w - d.delta, d.size - f->height + f->ascent, text);
  334. p.end();
  335. return image;
  336. }
  337. MainWindow::MainWindow(not_null<Controller*> controller)
  338. : _controller(controller)
  339. , _positionUpdatedTimer([=] { savePosition(); })
  340. , _outdated(Ui::CreateOutdatedBar(body(), cWorkingDir()))
  341. , _body(body()) {
  342. style::PaletteChanged(
  343. ) | rpl::start_with_next([=] {
  344. updatePalette();
  345. }, lifetime());
  346. Core::App().unreadBadgeChanges(
  347. ) | rpl::start_with_next([=] {
  348. updateTitle();
  349. unreadCounterChangedHook();
  350. Core::App().tray().updateIconCounters();
  351. }, lifetime());
  352. Core::App().settings().workModeChanges(
  353. ) | rpl::start_with_next([=](Core::Settings::WorkMode mode) {
  354. workmodeUpdated(mode);
  355. }, lifetime());
  356. if (isPrimary()) {
  357. Ui::Toast::SetDefaultParent(_body.data());
  358. }
  359. windowActiveValue(
  360. ) | rpl::skip(1) | rpl::start_with_next([=](bool active) {
  361. InvokeQueued(this, [=] {
  362. handleActiveChanged(active);
  363. });
  364. }, lifetime());
  365. shownValue(
  366. ) | rpl::skip(1) | rpl::start_with_next([=](bool visible) {
  367. InvokeQueued(this, [=] {
  368. handleVisibleChanged(visible);
  369. });
  370. }, lifetime());
  371. body()->sizeValue(
  372. ) | rpl::start_with_next([=](QSize size) {
  373. updateControlsGeometry();
  374. }, lifetime());
  375. if (_outdated) {
  376. _outdated->heightValue(
  377. ) | rpl::start_with_next([=](int height) {
  378. if (!height) {
  379. crl::on_main(this, [=] { _outdated.destroy(); });
  380. }
  381. updateControlsGeometry();
  382. }, _outdated->lifetime());
  383. }
  384. Shortcuts::Listen(this);
  385. }
  386. Main::Account &MainWindow::account() const {
  387. return _controller->account();
  388. }
  389. Window::SeparateId MainWindow::id() const {
  390. return _controller->id();
  391. }
  392. bool MainWindow::isPrimary() const {
  393. return _controller->isPrimary();
  394. }
  395. Window::SessionController *MainWindow::sessionController() const {
  396. return _controller->sessionController();
  397. }
  398. bool MainWindow::hideNoQuit() {
  399. if (Core::Quitting()) {
  400. return false;
  401. }
  402. const auto workMode = Core::App().settings().workMode();
  403. using Mode = Core::Settings::WorkMode;
  404. if (workMode == Mode::TrayOnly || workMode == Mode::WindowAndTray) {
  405. if (minimizeToTray()) {
  406. if (const auto controller = sessionController()) {
  407. controller->clearSectionStack();
  408. }
  409. return true;
  410. }
  411. }
  412. using Behavior = Core::Settings::CloseBehavior;
  413. const auto behavior = Platform::IsMac()
  414. ? Behavior::RunInBackground
  415. : Core::App().settings().closeBehavior();
  416. if (behavior == Behavior::RunInBackground) {
  417. closeWithoutDestroy();
  418. } else if (behavior == Behavior::CloseToTaskbar) {
  419. setWindowState(window()->windowState() | Qt::WindowMinimized);
  420. } else {
  421. return false;
  422. }
  423. controller().updateIsActiveBlur();
  424. updateGlobalMenu();
  425. if (const auto controller = sessionController()) {
  426. controller->clearSectionStack();
  427. }
  428. return true;
  429. }
  430. void MainWindow::clearWidgets() {
  431. clearWidgetsHook();
  432. updateGlobalMenu();
  433. }
  434. void MainWindow::updateIsActive() {
  435. const auto isActive = computeIsActive();
  436. if (_isActive != isActive) {
  437. _isActive = isActive;
  438. }
  439. }
  440. bool MainWindow::computeIsActive() const {
  441. return isActiveWindow() && isVisible() && !(windowState() & Qt::WindowMinimized);
  442. }
  443. QRect MainWindow::desktopRect() const {
  444. const auto now = crl::now();
  445. if (!_monitorLastGot || now >= _monitorLastGot + crl::time(1000)) {
  446. _monitorLastGot = now;
  447. _monitorRect = computeDesktopRect();
  448. }
  449. return _monitorRect;
  450. }
  451. void MainWindow::init() {
  452. initHook();
  453. updatePalette();
  454. if (Ui::Platform::NativeWindowFrameSupported()) {
  455. Core::App().settings().nativeWindowFrameChanges(
  456. ) | rpl::start_with_next([=](bool native) {
  457. refreshTitleWidget();
  458. recountGeometryConstraints();
  459. }, lifetime());
  460. }
  461. refreshTitleWidget();
  462. updateTitle();
  463. updateWindowIcon();
  464. }
  465. void MainWindow::handleStateChanged(Qt::WindowState state) {
  466. stateChangedHook(state);
  467. updateControlsGeometry();
  468. if (state == Qt::WindowMinimized) {
  469. controller().updateIsActiveBlur();
  470. } else {
  471. controller().updateIsActiveFocus();
  472. }
  473. Core::App().updateNonIdle();
  474. using WorkMode = Core::Settings::WorkMode;
  475. if (state == Qt::WindowMinimized
  476. && (Core::App().settings().workMode() == WorkMode::TrayOnly)) {
  477. minimizeToTray();
  478. }
  479. savePosition(state);
  480. }
  481. void MainWindow::handleActiveChanged(bool active) {
  482. checkActivation();
  483. if (active) {
  484. Core::App().windowActivated(&controller());
  485. }
  486. if (const auto controller = sessionController()) {
  487. controller->session().updates().updateOnline();
  488. }
  489. }
  490. void MainWindow::handleVisibleChanged(bool visible) {
  491. if (visible) {
  492. if (_maximizedBeforeHide) {
  493. DEBUG_LOG(("Window Pos: Window was maximized before hidding, setting maximized."));
  494. setWindowState(Qt::WindowMaximized);
  495. }
  496. } else {
  497. _maximizedBeforeHide = Core::App().settings().windowPosition().maximized;
  498. }
  499. handleVisibleChangedHook(visible);
  500. }
  501. void MainWindow::showFromTray() {
  502. InvokeQueued(this, [=] {
  503. updateGlobalMenu();
  504. });
  505. activate();
  506. unreadCounterChangedHook();
  507. Core::App().tray().updateIconCounters();
  508. }
  509. void MainWindow::quitFromTray() {
  510. Core::Quit();
  511. }
  512. void MainWindow::activate() {
  513. bool wasHidden = !isVisible();
  514. setWindowState(windowState() & ~Qt::WindowMinimized);
  515. setVisible(true);
  516. Platform::ActivateThisProcess();
  517. raise();
  518. activateWindow();
  519. controller().updateIsActiveFocus();
  520. if (wasHidden) {
  521. if (const auto session = sessionController()) {
  522. session->content()->windowShown();
  523. }
  524. }
  525. }
  526. void MainWindow::updatePalette() {
  527. Ui::ForceFullRepaint(this);
  528. auto p = palette();
  529. p.setColor(QPalette::Window, st::windowBg->c);
  530. setPalette(p);
  531. }
  532. int MainWindow::computeMinWidth() const {
  533. auto result = st::windowMinWidth;
  534. if (_rightColumn) {
  535. result += _rightColumn->width();
  536. }
  537. return result;
  538. }
  539. int MainWindow::computeMinHeight() const {
  540. const auto outdated = [&] {
  541. if (!_outdated) {
  542. return 0;
  543. }
  544. _outdated->resizeToWidth(st::windowMinWidth);
  545. return _outdated->height();
  546. }();
  547. return outdated + st::windowMinHeight;
  548. }
  549. void MainWindow::refreshTitleWidget() {
  550. if (Ui::Platform::NativeWindowFrameSupported()
  551. && Core::App().settings().nativeWindowFrame()) {
  552. setNativeFrame(true);
  553. if (Platform::NativeTitleRequiresShadow()) {
  554. _titleShadow.create(this);
  555. _titleShadow->show();
  556. }
  557. } else {
  558. setNativeFrame(false);
  559. _titleShadow.destroy();
  560. }
  561. }
  562. void MainWindow::updateMinimumSize() {
  563. setMinimumSize(QSize(computeMinWidth(), computeMinHeight()));
  564. }
  565. void MainWindow::recountGeometryConstraints() {
  566. updateMinimumSize();
  567. updateControlsGeometry();
  568. fixOrder();
  569. }
  570. WindowPosition MainWindow::initialPosition() const {
  571. const auto active = Core::App().activeWindow();
  572. return (!active || active == &controller())
  573. ? Core::AdjustToScale(
  574. Core::App().settings().windowPosition(),
  575. u"Window"_q)
  576. : active->widget()->nextInitialChildPosition(id());
  577. }
  578. WindowPosition MainWindow::nextInitialChildPosition(SeparateId childId) {
  579. const auto rect = geometry().marginsRemoved(frameMargins());
  580. const auto position = rect.topLeft();
  581. const auto adjust = [&](int value) {
  582. return (value * 3 / 4);
  583. };
  584. const auto width = OptionNewWindowsSizeAsFirst.value()
  585. ? Core::App().settings().windowPosition().w
  586. : childId.primary()
  587. ? st::windowDefaultWidth
  588. : childId.hasChatsList()
  589. ? (st::columnMinimalWidthLeft + adjust(st::windowDefaultWidth))
  590. : adjust(st::windowDefaultWidth);
  591. const auto height = OptionNewWindowsSizeAsFirst.value()
  592. ? Core::App().settings().windowPosition().h
  593. : childId.primary()
  594. ? st::windowDefaultHeight
  595. : adjust(st::windowDefaultHeight);
  596. const auto skip = ChildSkip();
  597. const auto delta = _lastChildIndex
  598. ? (_lastMyChildCreatePosition - position)
  599. : skip;
  600. if (qAbs(delta.x()) >= skip.x() || qAbs(delta.y()) >= skip.y()) {
  601. _lastChildIndex = 1;
  602. } else {
  603. ++_lastChildIndex;
  604. }
  605. _lastMyChildCreatePosition = position;
  606. const auto use = position + (skip * _lastChildIndex);
  607. return withScreenInPosition({
  608. .scale = cScale(),
  609. .x = use.x(),
  610. .y = use.y(),
  611. .w = width,
  612. .h = height,
  613. });
  614. }
  615. QRect MainWindow::countInitialGeometry(WindowPosition position) {
  616. const auto primaryScreen = QGuiApplication::primaryScreen();
  617. const auto primaryAvailable = primaryScreen
  618. ? primaryScreen->availableGeometry()
  619. : QRect(0, 0, st::windowDefaultWidth, st::windowDefaultHeight);
  620. const auto initialWidth = Core::Settings::ThirdColumnByDefault()
  621. ? st::windowBigDefaultWidth
  622. : st::windowDefaultWidth;
  623. const auto initialHeight = Core::Settings::ThirdColumnByDefault()
  624. ? st::windowBigDefaultHeight
  625. : st::windowDefaultHeight;
  626. const auto initial = WindowPosition{
  627. .x = (primaryAvailable.x()
  628. + std::max((primaryAvailable.width() - initialWidth) / 2, 0)),
  629. .y = (primaryAvailable.y()
  630. + std::max((primaryAvailable.height() - initialHeight) / 2, 0)),
  631. .w = initialWidth,
  632. .h = initialHeight,
  633. };
  634. return countInitialGeometry(
  635. position,
  636. initial,
  637. { st::windowMinWidth, st::windowMinHeight });
  638. }
  639. QRect MainWindow::countInitialGeometry(
  640. WindowPosition position,
  641. WindowPosition initial,
  642. QSize minSize) const {
  643. if (!position.w || !position.h) {
  644. return initial.rect();
  645. }
  646. const auto screen = [&]() -> QScreen* {
  647. for (const auto screen : QGuiApplication::screens()) {
  648. const auto sum = Platform::ScreenNameChecksum(screen->name());
  649. if (position.moncrc == sum) {
  650. return screen;
  651. }
  652. }
  653. return nullptr;
  654. }();
  655. if (!screen) {
  656. return initial.rect();
  657. }
  658. const auto frame = frameMargins();
  659. const auto screenGeometry = screen->geometry();
  660. const auto availableGeometry = screen->availableGeometry();
  661. const auto spaceForInner = availableGeometry.marginsRemoved(frame);
  662. DEBUG_LOG(("Window Pos: "
  663. "Screen found, screen geometry: %1, %2, %3, %4, "
  664. "available: %5, %6, %7, %8"
  665. ).arg(screenGeometry.x()
  666. ).arg(screenGeometry.y()
  667. ).arg(screenGeometry.width()
  668. ).arg(screenGeometry.height()
  669. ).arg(availableGeometry.x()
  670. ).arg(availableGeometry.y()
  671. ).arg(availableGeometry.width()
  672. ).arg(availableGeometry.height()));
  673. DEBUG_LOG(("Window Pos: "
  674. "Window frame margins: %1, %2, %3, %4, "
  675. "available space for inner geometry: %5, %6, %7, %8"
  676. ).arg(frame.left()
  677. ).arg(frame.top()
  678. ).arg(frame.right()
  679. ).arg(frame.bottom()
  680. ).arg(spaceForInner.x()
  681. ).arg(spaceForInner.y()
  682. ).arg(spaceForInner.width()
  683. ).arg(spaceForInner.height()));
  684. const auto x = spaceForInner.x() - screenGeometry.x();
  685. const auto y = spaceForInner.y() - screenGeometry.y();
  686. const auto w = spaceForInner.width();
  687. const auto h = spaceForInner.height();
  688. if (w < st::windowMinWidth || h < st::windowMinHeight) {
  689. return initial.rect();
  690. }
  691. if (position.x < x) position.x = x;
  692. if (position.y < y) position.y = y;
  693. if (position.w > w) position.w = w;
  694. if (position.h > h) position.h = h;
  695. const auto rightPoint = position.x + position.w;
  696. const auto screenRightPoint = x + w;
  697. if (rightPoint > screenRightPoint) {
  698. const auto distance = rightPoint - screenRightPoint;
  699. const auto newXPos = position.x - distance;
  700. if (newXPos >= x) {
  701. position.x = newXPos;
  702. } else {
  703. position.x = x;
  704. const auto newRightPoint = position.x + position.w;
  705. const auto newDistance = newRightPoint - screenRightPoint;
  706. position.w -= newDistance;
  707. }
  708. }
  709. const auto bottomPoint = position.y + position.h;
  710. const auto screenBottomPoint = y + h;
  711. if (bottomPoint > screenBottomPoint) {
  712. const auto distance = bottomPoint - screenBottomPoint;
  713. const auto newYPos = position.y - distance;
  714. if (newYPos >= y) {
  715. position.y = newYPos;
  716. } else {
  717. position.y = y;
  718. const auto newBottomPoint = position.y + position.h;
  719. const auto newDistance = newBottomPoint - screenBottomPoint;
  720. position.h -= newDistance;
  721. }
  722. }
  723. position.x += screenGeometry.x();
  724. position.y += screenGeometry.y();
  725. if ((position.x + st::windowMinWidth
  726. > screenGeometry.x() + screenGeometry.width())
  727. || (position.y + st::windowMinHeight
  728. > screenGeometry.y() + screenGeometry.height())) {
  729. return initial.rect();
  730. }
  731. DEBUG_LOG(("Window Pos: Resulting geometry is %1, %2, %3, %4"
  732. ).arg(position.x
  733. ).arg(position.y
  734. ).arg(position.w
  735. ).arg(position.h));
  736. return position.rect();
  737. }
  738. void MainWindow::firstShow() {
  739. updateMinimumSize();
  740. if (initGeometryFromSystem()) {
  741. show();
  742. return;
  743. }
  744. const auto geometry = countInitialGeometry(initialPosition());
  745. DEBUG_LOG(("Window Pos: Setting first %1, %2, %3, %4"
  746. ).arg(geometry.x()
  747. ).arg(geometry.y()
  748. ).arg(geometry.width()
  749. ).arg(geometry.height()));
  750. setGeometry(geometry);
  751. show();
  752. }
  753. void MainWindow::positionUpdated() {
  754. _positionUpdatedTimer.callOnce(kSaveWindowPositionTimeout);
  755. }
  756. void MainWindow::setPositionInited() {
  757. _positionInited = true;
  758. }
  759. void MainWindow::imeCompositionStartReceived() {
  760. _imeCompositionStartReceived.fire({});
  761. }
  762. rpl::producer<> MainWindow::leaveEvents() const {
  763. return _leaveEvents.events();
  764. }
  765. rpl::producer<> MainWindow::imeCompositionStarts() const {
  766. return _imeCompositionStartReceived.events();
  767. }
  768. void MainWindow::leaveEventHook(QEvent *e) {
  769. _leaveEvents.fire({});
  770. }
  771. void MainWindow::updateControlsGeometry() {
  772. const auto inner = body()->rect();
  773. auto bodyLeft = inner.x();
  774. auto bodyTop = inner.y();
  775. auto bodyWidth = inner.width();
  776. if (_titleShadow) {
  777. _titleShadow->setGeometry(inner.x(), bodyTop, inner.width(), st::lineWidth);
  778. }
  779. if (_outdated) {
  780. Ui::SendPendingMoveResizeEvents(_outdated.data());
  781. _outdated->resizeToWidth(inner.width());
  782. _outdated->moveToLeft(inner.x(), bodyTop);
  783. bodyTop += _outdated->height();
  784. }
  785. if (_rightColumn) {
  786. bodyWidth -= _rightColumn->width();
  787. _rightColumn->setGeometry(bodyWidth, bodyTop, inner.width() - bodyWidth, inner.height() - (bodyTop - inner.y()));
  788. }
  789. _body->setGeometry(bodyLeft, bodyTop, bodyWidth, inner.height() - (bodyTop - inner.y()));
  790. }
  791. void MainWindow::updateTitle() {
  792. if (Core::Quitting()) {
  793. return;
  794. }
  795. const auto settings = Core::App().settings().windowTitleContent();
  796. const auto locked = Core::App().passcodeLocked();
  797. const auto counter = settings.hideTotalUnread
  798. ? 0
  799. : Core::App().unreadBadge();
  800. const auto added = (counter > 0) ? u" (%1)"_q.arg(counter) : QString();
  801. const auto session = locked ? nullptr : _controller->sessionController();
  802. const auto user = (session
  803. && !settings.hideAccountName
  804. && Core::App().domain().accountsAuthedCount() > 1)
  805. ? st::wrap_rtl(session->authedName())
  806. : QString();
  807. const auto separateIdTitle = session
  808. ? TitleFromSeparateId(settings, session->windowId())
  809. : QString();
  810. if (!separateIdTitle.isEmpty()) {
  811. setTitle(separateIdTitle);
  812. return;
  813. }
  814. const auto key = (session && !settings.hideChatName)
  815. ? session->activeChatCurrent()
  816. : Dialogs::Key();
  817. const auto thread = key ? key.thread() : nullptr;
  818. if (!thread) {
  819. setTitle((user.isEmpty() ? u"Telegram"_q : user) + added);
  820. return;
  821. }
  822. const auto history = thread->owningHistory();
  823. const auto topic = thread->asTopic();
  824. const auto name = topic
  825. ? topic->title()
  826. : history->peer->isSelf()
  827. ? tr::lng_saved_messages(tr::now)
  828. : history->peer->name();
  829. const auto wrapped = st::wrap_rtl(name);
  830. const auto threadCounter = thread->chatListBadgesState().unreadCounter;
  831. const auto primary = (threadCounter > 0)
  832. ? u"(%1) %2"_q.arg(threadCounter).arg(wrapped)
  833. : wrapped;
  834. const auto middle = !user.isEmpty()
  835. ? (u" @ "_q + user)
  836. : !added.isEmpty()
  837. ? u" \u2013"_q
  838. : QString();
  839. setTitle(primary + middle + added);
  840. }
  841. QRect MainWindow::computeDesktopRect() const {
  842. return screen()->availableGeometry();
  843. }
  844. void MainWindow::savePosition(Qt::WindowState state) {
  845. if (state == Qt::WindowActive) {
  846. state = windowHandle()->windowState();
  847. }
  848. if (state == Qt::WindowMinimized
  849. || !isVisible()
  850. || !Core::App().savingPositionFor(&controller())
  851. || !positionInited()) {
  852. return;
  853. }
  854. const auto &savedPosition = Core::App().settings().windowPosition();
  855. auto realPosition = savedPosition;
  856. if (state == Qt::WindowMaximized) {
  857. realPosition.maximized = 1;
  858. DEBUG_LOG(("Window Pos: Saving maximized position."));
  859. } else {
  860. auto r = body()->mapToGlobal(body()->rect());
  861. realPosition.x = r.x();
  862. realPosition.y = r.y();
  863. realPosition.w = r.width() - (_rightColumn ? _rightColumn->width() : 0);
  864. realPosition.h = r.height();
  865. realPosition.scale = cScale();
  866. realPosition.maximized = 0;
  867. realPosition.moncrc = 0;
  868. DEBUG_LOG(("Window Pos: Saving non-maximized position: %1, %2, %3, %4").arg(realPosition.x).arg(realPosition.y).arg(realPosition.w).arg(realPosition.h));
  869. realPosition = withScreenInPosition(realPosition);
  870. }
  871. if (realPosition.w >= st::windowMinWidth && realPosition.h >= st::windowMinHeight) {
  872. if (realPosition.x != savedPosition.x
  873. || realPosition.y != savedPosition.y
  874. || realPosition.w != savedPosition.w
  875. || realPosition.h != savedPosition.h
  876. || realPosition.scale != savedPosition.scale
  877. || realPosition.moncrc != savedPosition.moncrc
  878. || realPosition.maximized != savedPosition.maximized) {
  879. DEBUG_LOG(("Window Pos: Writing: %1, %2, %3, %4 (scale %5%, maximized %6)")
  880. .arg(realPosition.x)
  881. .arg(realPosition.y)
  882. .arg(realPosition.w)
  883. .arg(realPosition.h)
  884. .arg(realPosition.scale)
  885. .arg(Logs::b(realPosition.maximized)));
  886. Core::App().settings().setWindowPosition(realPosition);
  887. Core::App().saveSettingsDelayed();
  888. }
  889. }
  890. }
  891. WindowPosition MainWindow::withScreenInPosition(
  892. WindowPosition position) const {
  893. return PositionWithScreen(
  894. position,
  895. this,
  896. { st::windowMinWidth, st::windowMinHeight });
  897. }
  898. bool MainWindow::minimizeToTray() {
  899. if (Core::Quitting() || !Core::App().tray().has()) {
  900. return false;
  901. }
  902. closeWithoutDestroy();
  903. controller().updateIsActiveBlur();
  904. updateGlobalMenu();
  905. return true;
  906. }
  907. void MainWindow::showRightColumn(object_ptr<TWidget> widget) {
  908. const auto wasWidth = width();
  909. const auto wasRightWidth = _rightColumn ? _rightColumn->width() : 0;
  910. _rightColumn = std::move(widget);
  911. if (_rightColumn) {
  912. _rightColumn->setParent(body());
  913. _rightColumn->show();
  914. _rightColumn->setFocus();
  915. } else {
  916. setInnerFocus();
  917. }
  918. const auto nowRightWidth = _rightColumn ? _rightColumn->width() : 0;
  919. const auto wasMinimumWidth = minimumWidth();
  920. const auto nowMinimumWidth = computeMinWidth();
  921. const auto firstResize = (nowMinimumWidth < wasMinimumWidth);
  922. if (firstResize) {
  923. updateMinimumSize();
  924. }
  925. if (!isMaximized()) {
  926. tryToExtendWidthBy(wasWidth + nowRightWidth - wasRightWidth - width());
  927. } else {
  928. updateControlsGeometry();
  929. }
  930. if (!firstResize) {
  931. updateMinimumSize();
  932. }
  933. }
  934. int MainWindow::maximalExtendBy() const {
  935. auto desktop = screen()->availableGeometry();
  936. return std::max(desktop.width() - body()->width(), 0);
  937. }
  938. bool MainWindow::canExtendNoMove(int extendBy) const {
  939. auto desktop = screen()->availableGeometry();
  940. auto inner = body()->mapToGlobal(body()->rect());
  941. auto innerRight = (inner.x() + inner.width() + extendBy);
  942. auto desktopRight = (desktop.x() + desktop.width());
  943. return innerRight <= desktopRight;
  944. }
  945. int MainWindow::tryToExtendWidthBy(int addToWidth) {
  946. auto desktop = screen()->availableGeometry();
  947. auto inner = body()->mapToGlobal(body()->rect());
  948. accumulate_min(
  949. addToWidth,
  950. std::max(desktop.width() - inner.width(), 0));
  951. auto newWidth = inner.width() + addToWidth;
  952. auto newLeft = std::min(
  953. inner.x(),
  954. desktop.x() + desktop.width() - newWidth);
  955. if (inner.x() != newLeft || inner.width() != newWidth) {
  956. setGeometry(QRect(newLeft, inner.y(), newWidth, inner.height()));
  957. } else {
  958. updateControlsGeometry();
  959. }
  960. return addToWidth;
  961. }
  962. void MainWindow::launchDrag(
  963. std::unique_ptr<QMimeData> data,
  964. Fn<void()> &&callback) {
  965. // Qt destroys this QDrag automatically after the drag is finished
  966. // We must not delete this at the end of this function, as this breaks DnD on Linux
  967. auto drag = new QDrag(this);
  968. KUrlMimeData::exportUrlsToPortal(data.get());
  969. drag->setMimeData(data.release());
  970. drag->exec(Qt::CopyAction);
  971. // We don't receive mouseReleaseEvent when drag is finished.
  972. ClickHandler::unpressed();
  973. callback();
  974. }
  975. MainWindow::~MainWindow() {
  976. // Otherwise:
  977. // ~QWidget
  978. // QWidgetPrivate::close_helper
  979. // QWidgetPrivate::setVisible
  980. // QWidgetPrivate::hide_helper
  981. // QWidgetPrivate::hide_sys
  982. // QWindowPrivate::setVisible
  983. // QMetaObject::activate
  984. // Window::MainWindow::handleVisibleChanged on a destroyed MainWindow.
  985. hide();
  986. }
  987. int32 DefaultScreenNameChecksum(const QString &name) {
  988. const auto bytes = name.toUtf8();
  989. return base::crc32(bytes.constData(), bytes.size());
  990. }
  991. WindowPosition PositionWithScreen(
  992. WindowPosition position,
  993. const QScreen *chosen,
  994. QSize minimal) {
  995. if (!chosen) {
  996. return position;
  997. }
  998. const auto available = chosen->availableGeometry();
  999. if (available.width() < minimal.width()
  1000. || available.height() < minimal.height()) {
  1001. return position;
  1002. }
  1003. accumulate_min(position.w, available.width());
  1004. accumulate_min(position.h, available.height());
  1005. if (position.x + position.w > available.x() + available.width()) {
  1006. position.x = available.x() + available.width() - position.w;
  1007. }
  1008. if (position.y + position.h > available.y() + available.height()) {
  1009. position.y = available.y() + available.height() - position.h;
  1010. }
  1011. const auto geometry = chosen->geometry();
  1012. DEBUG_LOG(("Window Pos: Screen found, geometry: %1, %2, %3, %4"
  1013. ).arg(geometry.x()
  1014. ).arg(geometry.y()
  1015. ).arg(geometry.width()
  1016. ).arg(geometry.height()));
  1017. position.x -= geometry.x();
  1018. position.y -= geometry.y();
  1019. position.moncrc = Platform::ScreenNameChecksum(chosen->name());
  1020. return position;
  1021. }
  1022. WindowPosition PositionWithScreen(
  1023. WindowPosition position,
  1024. not_null<const QWidget*> widget,
  1025. QSize minimal) {
  1026. const auto screen = widget->screen();
  1027. return PositionWithScreen(
  1028. position,
  1029. screen ? screen : QGuiApplication::primaryScreen(),
  1030. minimal);
  1031. }
  1032. } // namespace Window