main_window_mac.mm 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666
  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 "platform/mac/main_window_mac.h"
  8. #include "data/data_session.h"
  9. #include "core/application.h"
  10. #include "core/sandbox.h"
  11. #include "main/main_session.h"
  12. #include "history/history_widget.h"
  13. #include "history/history_inner_widget.h"
  14. #include "main/main_account.h"
  15. #include "main/main_domain.h" // Domain::activeSessionValue
  16. #include "media/player/media_player_instance.h"
  17. #include "media/audio/media_audio.h"
  18. #include "storage/localstorage.h"
  19. #include "ui/text/text_utilities.h"
  20. #include "window/window_controller.h"
  21. #include "window/window_session_controller.h"
  22. #include "platform/mac/touchbar/mac_touchbar_manager.h"
  23. #include "platform/platform_specific.h"
  24. #include "platform/platform_notifications_manager.h"
  25. #include "base/platform/base_platform_info.h"
  26. #include "base/options.h"
  27. #include "boxes/peer_list_controllers.h"
  28. #include "boxes/about_box.h"
  29. #include "lang/lang_keys.h"
  30. #include "base/platform/mac/base_utilities_mac.h"
  31. #include <QtWidgets/QLineEdit>
  32. #include <QtWidgets/QTextEdit>
  33. #if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
  34. #include <qpa/qwindowsysteminterface.h>
  35. #endif // Qt < 6.6.0
  36. #include <Cocoa/Cocoa.h>
  37. #include <CoreFoundation/CFURL.h>
  38. #include <IOKit/IOKitLib.h>
  39. #include <IOKit/hidsystem/ev_keymap.h>
  40. @interface MainWindowObserver : NSObject {
  41. }
  42. - (id) init:(MainWindow::Private*)window;
  43. - (void) activeSpaceDidChange:(NSNotification *)aNotification;
  44. #if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
  45. - (void) darkModeChanged:(NSNotification *)aNotification;
  46. #endif // Qt < 6.6.0
  47. - (void) screenIsLocked:(NSNotification *)aNotification;
  48. - (void) screenIsUnlocked:(NSNotification *)aNotification;
  49. @end // @interface MainWindowObserver
  50. namespace Platform {
  51. namespace {
  52. // When we close a window that is fullscreen we first leave the fullscreen
  53. // mode and after that hide the window. This is a timeout for elaving the
  54. // fullscreen mode, after that we'll hide the window no matter what.
  55. constexpr auto kHideAfterFullscreenTimeoutMs = 3000;
  56. [[nodiscard]] bool PossiblyTextTypingEvent(NSEvent *e) {
  57. if ([e type] != NSEventTypeKeyDown) {
  58. return false;
  59. }
  60. NSEventModifierFlags flags = [e modifierFlags]
  61. & NSEventModifierFlagDeviceIndependentFlagsMask;
  62. if ((flags & ~NSEventModifierFlagShift) != 0) {
  63. return false;
  64. }
  65. NSString *text = [e characters];
  66. const auto length = int([text length]);
  67. for (auto i = 0; i != length; ++i) {
  68. const auto utf16 = [text characterAtIndex:i];
  69. if (utf16 >= 32) {
  70. return true;
  71. }
  72. }
  73. return false;
  74. }
  75. } // namespace
  76. class MainWindow::Private {
  77. public:
  78. explicit Private(not_null<MainWindow*> window);
  79. void setNativeWindow(NSWindow *window, NSView *view);
  80. void initTouchBar(
  81. NSWindow *window,
  82. not_null<Window::Controller*> controller);
  83. void setWindowBadge(const QString &str);
  84. void setMarkdownEnabledState(Ui::MarkdownEnabledState state);
  85. bool clipboardHasText();
  86. ~Private();
  87. private:
  88. not_null<MainWindow*> _public;
  89. friend class MainWindow;
  90. rpl::variable<Ui::MarkdownEnabledState> _markdownState;
  91. NSWindow * __weak _nativeWindow = nil;
  92. NSView * __weak _nativeView = nil;
  93. MainWindowObserver *_observer = nullptr;
  94. NSPasteboard *_generalPasteboard = nullptr;
  95. int _generalPasteboardChangeCount = -1;
  96. bool _generalPasteboardHasText = false;
  97. };
  98. } // namespace Platform
  99. @implementation MainWindowObserver {
  100. MainWindow::Private *_private;
  101. }
  102. - (id) init:(MainWindow::Private*)window {
  103. if (self = [super init]) {
  104. _private = window;
  105. }
  106. return self;
  107. }
  108. - (void) activeSpaceDidChange:(NSNotification *)aNotification {
  109. }
  110. #if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
  111. - (void) darkModeChanged:(NSNotification *)aNotification {
  112. Core::Sandbox::Instance().customEnterFromEventLoop([&] {
  113. #if QT_VERSION >= QT_VERSION_CHECK(6, 5, 0)
  114. QWindowSystemInterface::handleThemeChange();
  115. #else // Qt >= 6.5.0
  116. Core::App().settings().setSystemDarkMode(Platform::IsDarkMode());
  117. #endif // Qt < 6.5.0
  118. });
  119. }
  120. #endif // Qt < 6.6.0
  121. - (void) screenIsLocked:(NSNotification *)aNotification {
  122. Core::App().setScreenIsLocked(true);
  123. }
  124. - (void) screenIsUnlocked:(NSNotification *)aNotification {
  125. Core::App().setScreenIsLocked(false);
  126. }
  127. @end // @implementation MainWindowObserver
  128. namespace Platform {
  129. namespace {
  130. void SendKeySequence(
  131. Qt::Key key,
  132. Qt::KeyboardModifiers modifiers = Qt::NoModifier) {
  133. const auto focused = QApplication::focusWidget();
  134. if (qobject_cast<QLineEdit*>(focused)
  135. || qobject_cast<QTextEdit*>(focused)
  136. || dynamic_cast<HistoryInner*>(focused)) {
  137. QApplication::postEvent(
  138. focused,
  139. new QKeyEvent(QEvent::KeyPress, key, modifiers));
  140. QApplication::postEvent(
  141. focused,
  142. new QKeyEvent(QEvent::KeyRelease, key, modifiers));
  143. }
  144. }
  145. void ForceDisabled(QAction *action, bool disabled) {
  146. if (action->isEnabled()) {
  147. if (disabled) action->setDisabled(true);
  148. } else if (!disabled) {
  149. action->setDisabled(false);
  150. }
  151. }
  152. #if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
  153. QString strNotificationAboutThemeChange() {
  154. const uint32 letters[] = { 0x75E86256, 0xD03E11B1, 0x4D92201D, 0xA2144987, 0x99D5B34F, 0x037589C3, 0x38ED2A7C, 0xD2371ABC, 0xDC98BB02, 0x27964E1B, 0x01748AED, 0xE06679F8, 0x761C9580, 0x4F2595BF, 0x6B5FCBF4, 0xE4D9C24E, 0xBA2F6AB5, 0xE6E3FA71, 0xF2CFC255, 0x56A50C19, 0x43AE1239, 0x77CA4254, 0x7D189A89, 0xEA7663EE, 0x84CEB554, 0xA0ADF236, 0x886512D4, 0x7D3FBDAF, 0x85C4BE4F, 0x12C8255E, 0x9AD8BD41, 0xAC154683, 0xB117598B, 0xDFD9F947, 0x63F06C7B, 0x6340DCD6, 0x3AAE6B3E, 0x26CB125A };
  155. return Platform::MakeFromLetters(letters);
  156. }
  157. #endif // Qt < 6.6.0
  158. QString strNotificationAboutScreenLocked() {
  159. const uint32 letters[] = { 0x34B47F28, 0x47E95179, 0x73D05C42, 0xB4E2A933, 0x924F22D1, 0x4265D8EA, 0x9E4D2CC2, 0x02E8157B, 0x35BF7525, 0x75901A41, 0xB0400FCC, 0xE801169D, 0x4E04B589, 0xC1CEF054, 0xAB2A7EB0, 0x5C67C4F6, 0xA4E2B954, 0xB35E12D2, 0xD598B22B, 0x4E3B8AAB, 0xBEA5E439, 0xFDA8AA3C, 0x1632DBA8, 0x88FE8965 };
  160. return Platform::MakeFromLetters(letters);
  161. }
  162. QString strNotificationAboutScreenUnlocked() {
  163. const uint32 letters[] = { 0xF897900B, 0x19A04630, 0x144DA6DF, 0x643CA7ED, 0x81DDA343, 0x88C6B149, 0x5F9A3A15, 0x31804E13, 0xDF2202B8, 0x9BD1B500, 0x61B92735, 0x7DDF5D43, 0xB74E06C3, 0x16FF1665, 0x9098F702, 0x4461DAF0, 0xA3134FA5, 0x52B01D3C, 0x6BC35769, 0xA7CC945D, 0x8B5327C0, 0x7630B9A0, 0x4E52E3CE, 0xED7765E3, 0xCEB7862D, 0xA06B34F0 };
  164. return Platform::MakeFromLetters(letters);
  165. }
  166. } // namespace
  167. MainWindow::Private::Private(not_null<MainWindow*> window)
  168. : _public(window)
  169. , _observer([[MainWindowObserver alloc] init:this]) {
  170. _generalPasteboard = [NSPasteboard generalPasteboard];
  171. @autoreleasepool {
  172. [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:_observer selector:@selector(activeSpaceDidChange:) name:NSWorkspaceActiveSpaceDidChangeNotification object:nil];
  173. #if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
  174. [[NSDistributedNotificationCenter defaultCenter] addObserver:_observer selector:@selector(darkModeChanged:) name:Q2NSString(strNotificationAboutThemeChange()) object:nil];
  175. #endif // Qt < 6.6.0
  176. [[NSDistributedNotificationCenter defaultCenter] addObserver:_observer selector:@selector(screenIsLocked:) name:Q2NSString(strNotificationAboutScreenLocked()) object:nil];
  177. [[NSDistributedNotificationCenter defaultCenter] addObserver:_observer selector:@selector(screenIsUnlocked:) name:Q2NSString(strNotificationAboutScreenUnlocked()) object:nil];
  178. }
  179. }
  180. void MainWindow::Private::setWindowBadge(const QString &str) {
  181. @autoreleasepool {
  182. [[NSApp dockTile] setBadgeLabel:Q2NSString(str)];
  183. }
  184. }
  185. void MainWindow::Private::setNativeWindow(NSWindow *window, NSView *view) {
  186. _nativeWindow = window;
  187. _nativeView = view;
  188. auto inner = [_nativeWindow contentLayoutRect];
  189. auto full = [_nativeView frame];
  190. _public->_customTitleHeight = qMax(qRound(full.size.height - inner.size.height), 0);
  191. }
  192. void MainWindow::Private::initTouchBar(
  193. NSWindow *window,
  194. not_null<Window::Controller*> controller) {
  195. if (!IsMac10_13OrGreater()) {
  196. return;
  197. }
  198. [NSApplication sharedApplication]
  199. .automaticCustomizeTouchBarMenuItemEnabled = true;
  200. [window
  201. performSelectorOnMainThread:@selector(setTouchBar:)
  202. withObject:[[[RootTouchBar alloc]
  203. init:_markdownState.value()
  204. controller:controller
  205. domain:(&Core::App().domain())] autorelease]
  206. waitUntilDone:true];
  207. }
  208. void MainWindow::Private::setMarkdownEnabledState(
  209. Ui::MarkdownEnabledState state) {
  210. _markdownState = state;
  211. }
  212. bool MainWindow::Private::clipboardHasText() {
  213. auto currentChangeCount = static_cast<int>([_generalPasteboard changeCount]);
  214. if (_generalPasteboardChangeCount != currentChangeCount) {
  215. _generalPasteboardChangeCount = currentChangeCount;
  216. _generalPasteboardHasText = !QGuiApplication::clipboard()->text().isEmpty();
  217. }
  218. return _generalPasteboardHasText;
  219. }
  220. MainWindow::Private::~Private() {
  221. [_observer release];
  222. }
  223. MainWindow::MainWindow(not_null<Window::Controller*> controller)
  224. : Window::MainWindow(controller)
  225. , _private(std::make_unique<Private>(this))
  226. , psMainMenu(this) {
  227. _hideAfterFullScreenTimer.setCallback([this] { hideAndDeactivate(); });
  228. }
  229. void MainWindow::closeWithoutDestroy() {
  230. NSWindow *nsWindow = [reinterpret_cast<NSView*>(winId()) window];
  231. auto isFullScreen = (([nsWindow styleMask] & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen);
  232. if (isFullScreen) {
  233. _hideAfterFullScreenTimer.callOnce(kHideAfterFullscreenTimeoutMs);
  234. [nsWindow toggleFullScreen:nsWindow];
  235. } else {
  236. hideAndDeactivate();
  237. }
  238. }
  239. void MainWindow::stateChangedHook(Qt::WindowState state) {
  240. if (_hideAfterFullScreenTimer.isActive()) {
  241. _hideAfterFullScreenTimer.callOnce(0);
  242. }
  243. }
  244. void MainWindow::initHook() {
  245. _customTitleHeight = 0;
  246. if (auto view = reinterpret_cast<NSView*>(winId())) {
  247. if (auto window = [view window]) {
  248. _private->setNativeWindow(window, view);
  249. if (!base::options::lookup<bool>(
  250. Window::kOptionDisableTouchbar).value()) {
  251. _private->initTouchBar(window, &controller());
  252. } else {
  253. LOG(("Touch Bar was disabled from Experimental Settings."));
  254. }
  255. }
  256. }
  257. }
  258. void MainWindow::updateWindowIcon() {
  259. }
  260. bool MainWindow::nativeEvent(
  261. const QByteArray &eventType,
  262. void *message,
  263. qintptr *result) {
  264. if (message && eventType == "NSEvent") {
  265. const auto event = static_cast<NSEvent*>(message);
  266. if (PossiblyTextTypingEvent(event)) {
  267. Core::Sandbox::Instance().customEnterFromEventLoop([&] {
  268. imeCompositionStartReceived();
  269. });
  270. } else if ([event type] == NSEventTypePressure) {
  271. const auto stage = [event stage];
  272. if (_lastPressureStage != stage) {
  273. _lastPressureStage = stage;
  274. if (stage == 2) {
  275. Core::Sandbox::Instance().customEnterFromEventLoop([&] {
  276. _forceClicks.fire(QCursor::pos());
  277. });
  278. }
  279. }
  280. }
  281. }
  282. return false;
  283. }
  284. void MainWindow::hideAndDeactivate() {
  285. hide();
  286. }
  287. void MainWindow::unreadCounterChangedHook() {
  288. updateDockCounter();
  289. }
  290. void MainWindow::updateDockCounter() {
  291. const auto counter = Core::App().unreadBadge();
  292. const auto string = !counter
  293. ? QString()
  294. : (counter < 1000)
  295. ? QString("%1").arg(counter)
  296. : QString("..%1").arg(counter % 100, 2, 10, QChar('0'));
  297. _private->setWindowBadge(string);
  298. }
  299. void MainWindow::createGlobalMenu() {
  300. const auto ensureWindowShown = [=] {
  301. if (isHidden()) {
  302. showFromTray();
  303. }
  304. };
  305. auto main = psMainMenu.addMenu(u"Telegram"_q);
  306. {
  307. auto callback = [=] {
  308. ensureWindowShown();
  309. controller().show(Box<AboutBox>());
  310. };
  311. main->addAction(
  312. tr::lng_mac_menu_about_telegram(
  313. tr::now,
  314. lt_telegram,
  315. u"Telegram"_q),
  316. std::move(callback))
  317. ->setMenuRole(QAction::AboutQtRole);
  318. }
  319. main->addSeparator();
  320. {
  321. auto callback = [=] {
  322. ensureWindowShown();
  323. controller().showSettings();
  324. };
  325. auto prefs = main->addAction(
  326. tr::lng_mac_menu_preferences(tr::now),
  327. this,
  328. std::move(callback),
  329. QKeySequence(Qt::ControlModifier | Qt::Key_Comma));
  330. prefs->setMenuRole(QAction::PreferencesRole);
  331. prefs->setShortcutContext(Qt::WidgetShortcut);
  332. }
  333. QMenu *file = psMainMenu.addMenu(tr::lng_mac_menu_file(tr::now));
  334. {
  335. auto callback = [=] {
  336. ensureWindowShown();
  337. controller().showLogoutConfirmation();
  338. };
  339. psLogout = file->addAction(
  340. tr::lng_mac_menu_logout(tr::now),
  341. this,
  342. std::move(callback));
  343. }
  344. QMenu *edit = psMainMenu.addMenu(tr::lng_mac_menu_edit(tr::now));
  345. psUndo = edit->addAction(
  346. tr::lng_mac_menu_undo(tr::now),
  347. this,
  348. [] { SendKeySequence(Qt::Key_Z, Qt::ControlModifier); },
  349. QKeySequence::Undo);
  350. psUndo->setShortcutContext(Qt::WidgetShortcut);
  351. psRedo = edit->addAction(
  352. tr::lng_mac_menu_redo(tr::now),
  353. this,
  354. [] {
  355. SendKeySequence(
  356. Qt::Key_Z,
  357. Qt::ControlModifier | Qt::ShiftModifier);
  358. },
  359. QKeySequence::Redo);
  360. psRedo->setShortcutContext(Qt::WidgetShortcut);
  361. edit->addSeparator();
  362. psCut = edit->addAction(
  363. tr::lng_mac_menu_cut(tr::now),
  364. this,
  365. [] { SendKeySequence(Qt::Key_X, Qt::ControlModifier); },
  366. QKeySequence::Cut);
  367. psCut->setShortcutContext(Qt::WidgetShortcut);
  368. psCopy = edit->addAction(
  369. tr::lng_mac_menu_copy(tr::now),
  370. this,
  371. [] { SendKeySequence(Qt::Key_C, Qt::ControlModifier); },
  372. QKeySequence::Copy);
  373. psCopy->setShortcutContext(Qt::WidgetShortcut);
  374. psPaste = edit->addAction(
  375. tr::lng_mac_menu_paste(tr::now),
  376. this,
  377. [] { SendKeySequence(Qt::Key_V, Qt::ControlModifier); },
  378. QKeySequence::Paste);
  379. psPaste->setShortcutContext(Qt::WidgetShortcut);
  380. psDelete = edit->addAction(
  381. tr::lng_mac_menu_delete(tr::now),
  382. this,
  383. [] { SendKeySequence(Qt::Key_Delete); },
  384. QKeySequence(Qt::ControlModifier | Qt::Key_Backspace));
  385. psDelete->setShortcutContext(Qt::WidgetShortcut);
  386. edit->addSeparator();
  387. psBold = edit->addAction(
  388. tr::lng_menu_formatting_bold(tr::now),
  389. this,
  390. [] { SendKeySequence(Qt::Key_B, Qt::ControlModifier); },
  391. QKeySequence::Bold);
  392. psBold->setShortcutContext(Qt::WidgetShortcut);
  393. psItalic = edit->addAction(
  394. tr::lng_menu_formatting_italic(tr::now),
  395. this,
  396. [] { SendKeySequence(Qt::Key_I, Qt::ControlModifier); },
  397. QKeySequence::Italic);
  398. psItalic->setShortcutContext(Qt::WidgetShortcut);
  399. psUnderline = edit->addAction(
  400. tr::lng_menu_formatting_underline(tr::now),
  401. this,
  402. [] { SendKeySequence(Qt::Key_U, Qt::ControlModifier); },
  403. QKeySequence::Underline);
  404. psUnderline->setShortcutContext(Qt::WidgetShortcut);
  405. psStrikeOut = edit->addAction(
  406. tr::lng_menu_formatting_strike_out(tr::now),
  407. this,
  408. [] {
  409. SendKeySequence(
  410. Qt::Key_X,
  411. Qt::ControlModifier | Qt::ShiftModifier);
  412. },
  413. Ui::kStrikeOutSequence);
  414. psStrikeOut->setShortcutContext(Qt::WidgetShortcut);
  415. psBlockquote = edit->addAction(
  416. tr::lng_menu_formatting_blockquote(tr::now),
  417. this,
  418. [] {
  419. SendKeySequence(
  420. Qt::Key_Period,
  421. Qt::ControlModifier | Qt::ShiftModifier);
  422. },
  423. Ui::kBlockquoteSequence);
  424. psBlockquote->setShortcutContext(Qt::WidgetShortcut);
  425. psMonospace = edit->addAction(
  426. tr::lng_menu_formatting_monospace(tr::now),
  427. this,
  428. [] {
  429. SendKeySequence(
  430. Qt::Key_M,
  431. Qt::ControlModifier | Qt::ShiftModifier);
  432. },
  433. Ui::kMonospaceSequence);
  434. psMonospace->setShortcutContext(Qt::WidgetShortcut);
  435. psClearFormat = edit->addAction(
  436. tr::lng_menu_formatting_clear(tr::now),
  437. this,
  438. [] {
  439. SendKeySequence(
  440. Qt::Key_N,
  441. Qt::ControlModifier | Qt::ShiftModifier);
  442. },
  443. Ui::kClearFormatSequence);
  444. psClearFormat->setShortcutContext(Qt::WidgetShortcut);
  445. edit->addSeparator();
  446. psSelectAll = edit->addAction(
  447. tr::lng_mac_menu_select_all(tr::now),
  448. this,
  449. [] { SendKeySequence(Qt::Key_A, Qt::ControlModifier); },
  450. QKeySequence::SelectAll);
  451. psSelectAll->setShortcutContext(Qt::WidgetShortcut);
  452. edit->addSeparator();
  453. edit->addAction(
  454. tr::lng_mac_menu_emoji_and_symbols(
  455. tr::now,
  456. Ui::Text::FixAmpersandInAction),
  457. this,
  458. [] { [NSApp orderFrontCharacterPalette:nil]; },
  459. QKeySequence(Qt::MetaModifier | Qt::ControlModifier | Qt::Key_Space)
  460. )->setShortcutContext(Qt::WidgetShortcut);
  461. QMenu *window = psMainMenu.addMenu(tr::lng_mac_menu_window(tr::now));
  462. psContacts = window->addAction(tr::lng_mac_menu_contacts(tr::now));
  463. connect(psContacts, &QAction::triggered, psContacts, crl::guard(this, [=] {
  464. Expects(sessionController() != nullptr && !controller().locked());
  465. ensureWindowShown();
  466. sessionController()->show(PrepareContactsBox(sessionController()));
  467. }));
  468. {
  469. auto callback = [=] {
  470. Expects(sessionController() != nullptr && !controller().locked());
  471. ensureWindowShown();
  472. sessionController()->showAddContact();
  473. };
  474. psAddContact = window->addAction(
  475. tr::lng_mac_menu_add_contact(tr::now),
  476. this,
  477. std::move(callback));
  478. }
  479. window->addSeparator();
  480. {
  481. auto callback = [=] {
  482. Expects(sessionController() != nullptr && !controller().locked());
  483. ensureWindowShown();
  484. sessionController()->showNewGroup();
  485. };
  486. psNewGroup = window->addAction(
  487. tr::lng_mac_menu_new_group(tr::now),
  488. this,
  489. std::move(callback));
  490. }
  491. {
  492. auto callback = [=] {
  493. Expects(sessionController() != nullptr && !controller().locked());
  494. ensureWindowShown();
  495. sessionController()->showNewChannel();
  496. };
  497. psNewChannel = window->addAction(
  498. tr::lng_mac_menu_new_channel(tr::now),
  499. this,
  500. std::move(callback));
  501. }
  502. window->addSeparator();
  503. psShowTelegram = window->addAction(
  504. tr::lng_mac_menu_show(tr::now),
  505. this,
  506. [=] { showFromTray(); });
  507. updateGlobalMenu();
  508. }
  509. void MainWindow::updateGlobalMenuHook() {
  510. if (!positionInited()) {
  511. return;
  512. }
  513. auto focused = QApplication::focusWidget();
  514. bool canUndo = false, canRedo = false, canCut = false, canCopy = false, canPaste = false, canDelete = false, canSelectAll = false;
  515. auto clipboardHasText = _private->clipboardHasText();
  516. auto markdownState = Ui::MarkdownEnabledState();
  517. if (auto edit = qobject_cast<QLineEdit*>(focused)) {
  518. canCut = canCopy = canDelete = edit->hasSelectedText();
  519. canSelectAll = !edit->text().isEmpty();
  520. canUndo = edit->isUndoAvailable();
  521. canRedo = edit->isRedoAvailable();
  522. canPaste = clipboardHasText;
  523. } else if (auto edit = qobject_cast<QTextEdit*>(focused)) {
  524. canCut = canCopy = canDelete = edit->textCursor().hasSelection();
  525. canSelectAll = !edit->document()->isEmpty();
  526. canUndo = edit->document()->isUndoAvailable();
  527. canRedo = edit->document()->isRedoAvailable();
  528. canPaste = clipboardHasText;
  529. if (canCopy) {
  530. if (const auto inputField = dynamic_cast<Ui::InputField*>(
  531. focused->parentWidget())) {
  532. markdownState = inputField->markdownEnabledState();
  533. }
  534. }
  535. } else if (auto list = dynamic_cast<HistoryInner*>(focused)) {
  536. canCopy = list->canCopySelected();
  537. canDelete = list->canDeleteSelected();
  538. }
  539. _private->setMarkdownEnabledState(markdownState);
  540. updateIsActive();
  541. const auto logged = (sessionController() != nullptr);
  542. const auto inactive = !logged || controller().locked();
  543. const auto support = logged
  544. && sessionController()->session().supportMode();
  545. ForceDisabled(psLogout, !logged && !Core::App().passcodeLocked());
  546. ForceDisabled(psUndo, !canUndo);
  547. ForceDisabled(psRedo, !canRedo);
  548. ForceDisabled(psCut, !canCut);
  549. ForceDisabled(psCopy, !canCopy);
  550. ForceDisabled(psPaste, !canPaste);
  551. ForceDisabled(psDelete, !canDelete);
  552. ForceDisabled(psSelectAll, !canSelectAll);
  553. ForceDisabled(psContacts, inactive || support);
  554. ForceDisabled(psAddContact, inactive);
  555. ForceDisabled(psNewGroup, inactive || support);
  556. ForceDisabled(psNewChannel, inactive || support);
  557. ForceDisabled(psShowTelegram, isActive());
  558. const auto diabled = [=](const QString &tag) {
  559. return !markdownState.enabledForTag(tag);
  560. };
  561. using Field = Ui::InputField;
  562. ForceDisabled(psBold, diabled(Field::kTagBold));
  563. ForceDisabled(psItalic, diabled(Field::kTagItalic));
  564. ForceDisabled(psUnderline, diabled(Field::kTagUnderline));
  565. ForceDisabled(psStrikeOut, diabled(Field::kTagStrikeOut));
  566. ForceDisabled(psBlockquote, diabled(Field::kTagBlockquote));
  567. ForceDisabled(
  568. psMonospace,
  569. diabled(Field::kTagPre) || diabled(Field::kTagCode));
  570. ForceDisabled(psClearFormat, markdownState.disabled());
  571. }
  572. bool MainWindow::eventFilter(QObject *obj, QEvent *evt) {
  573. QEvent::Type t = evt->type();
  574. if (t == QEvent::FocusIn || t == QEvent::FocusOut) {
  575. if (qobject_cast<QLineEdit*>(obj) || qobject_cast<QTextEdit*>(obj) || dynamic_cast<HistoryInner*>(obj)) {
  576. updateGlobalMenu();
  577. }
  578. }
  579. return Window::MainWindow::eventFilter(obj, evt);
  580. }
  581. MainWindow::~MainWindow() {
  582. }
  583. } // namespace