diff --git a/Telegram/CMakeLists.txt b/Telegram/CMakeLists.txt index 9ac95495be..1c7484a603 100644 --- a/Telegram/CMakeLists.txt +++ b/Telegram/CMakeLists.txt @@ -1511,6 +1511,8 @@ PRIVATE platform/linux/webauthn_linux.cpp platform/mac/file_utilities_mac.mm platform/mac/file_utilities_mac.h + platform/mac/global_menu_mac.h + platform/mac/global_menu_mac.mm platform/mac/launcher_mac.mm platform/mac/launcher_mac.h platform/mac/integration_mac.mm diff --git a/Telegram/SourceFiles/core/sandbox.cpp b/Telegram/SourceFiles/core/sandbox.cpp index 81ea4e4906..40bba594f8 100644 --- a/Telegram/SourceFiles/core/sandbox.cpp +++ b/Telegram/SourceFiles/core/sandbox.cpp @@ -30,6 +30,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/ui_utility.h" #include "ui/effects/animations.h" +#ifdef Q_OS_MAC +#include "platform/mac/global_menu_mac.h" +#endif // Q_OS_MAC + #include #include #include @@ -54,6 +58,9 @@ bool Sandbox::QuitOnStartRequested = false; Sandbox::Sandbox(int &argc, char **argv) : QApplication(argc, argv) , _mainThreadId(QThread::currentThreadId()) { +#ifdef Q_OS_MAC + Platform::CreateGlobalMenu(); +#endif // Q_OS_MAC } int Sandbox::start() { @@ -222,7 +229,11 @@ void Sandbox::setupScreenScale() { LOG(("ScreenScale: %1").arg(cScreenScale())); } -Sandbox::~Sandbox() = default; +Sandbox::~Sandbox() { +#ifdef Q_OS_MAC + Platform::DestroyGlobalMenu(); +#endif // Q_OS_MAC +} bool Sandbox::event(QEvent *e) { if (e->type() == QEvent::Quit) { diff --git a/Telegram/SourceFiles/platform/mac/global_menu_mac.h b/Telegram/SourceFiles/platform/mac/global_menu_mac.h new file mode 100644 index 0000000000..4e946908a9 --- /dev/null +++ b/Telegram/SourceFiles/platform/mac/global_menu_mac.h @@ -0,0 +1,22 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#pragma once + +namespace Ui { +struct MarkdownEnabledState; +} // namespace Ui + +namespace Platform { + +void CreateGlobalMenu(); +void DestroyGlobalMenu(); +void RequestUpdateGlobalMenu(); + +[[nodiscard]] rpl::producer GlobalMenuMarkdownState(); + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/mac/global_menu_mac.mm b/Telegram/SourceFiles/platform/mac/global_menu_mac.mm new file mode 100644 index 0000000000..19390fa11b --- /dev/null +++ b/Telegram/SourceFiles/platform/mac/global_menu_mac.mm @@ -0,0 +1,621 @@ +/* +This file is part of Telegram Desktop, +the official desktop application for the Telegram messaging service. + +For license and copyright information please follow this link: +https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL +*/ +#include "platform/mac/global_menu_mac.h" + +#include "core/application.h" +#include "core/sandbox.h" +#include "window/window_controller.h" +#include "window/window_session_controller.h" +#include "window/main_window.h" +#include "main/main_session.h" +#include "history/history_inner_widget.h" +#include "boxes/peer_list_controllers.h" +#include "boxes/about_box.h" +#include "lang/lang_keys.h" +#include "base/platform/base_platform_info.h" +#include "ui/text/text_utilities.h" +#include "ui/widgets/fields/input_field.h" + +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace Platform { +namespace { + +class Manager final { +public: + void create(); + void destroy(); + void requestUpdate(); + [[nodiscard]] auto markdownStateChanges() const + -> rpl::producer; + +private: + void buildMenu(); + void buildAppleMenu(QMenu *main); + void buildFileMenu(QMenu *file); + void buildEditMenu(QMenu *edit); + void buildWindowMenu(QMenu *window); + void retranslate(); + void ensureLanguageBound(); + void recomputeState(); + [[nodiscard]] bool clipboardHasText(); + [[nodiscard]] Window::Controller *resolveActiveWindow() const; + + template + void withActiveWindow(Callback callback) { + const auto window = resolveActiveWindow(); + if (!window) { + return; + } + if (window->widget()->isHidden()) { + window->widget()->showFromTray(); + } else { + window->activate(); + } + callback(window); + } + + std::unique_ptr _menuBar; + QAction *_logout = nullptr; + QAction *_undo = nullptr; + QAction *_redo = nullptr; + QAction *_cut = nullptr; + QAction *_copy = nullptr; + QAction *_paste = nullptr; + QAction *_delete = nullptr; + QAction *_selectAll = nullptr; + QAction *_contacts = nullptr; + QAction *_addContact = nullptr; + QAction *_newGroup = nullptr; + QAction *_newChannel = nullptr; + QAction *_showTelegram = nullptr; + QAction *_fullScreen = nullptr; + QAction *_emoji = nullptr; + QAction *_bold = nullptr; + QAction *_italic = nullptr; + QAction *_underline = nullptr; + QAction *_strikeOut = nullptr; + QAction *_blockquote = nullptr; + QAction *_monospace = nullptr; + QAction *_clearFormat = nullptr; + + NSPasteboard *_pasteboard = nullptr; + int _pasteboardChangeCount = -1; + bool _pasteboardHasText = false; + + rpl::event_stream<> _updateRequests; + rpl::event_stream _markdownChanges; + rpl::lifetime _lifetime; + bool _languageBound = false; + +}; + +void SendKeySequence( + Qt::Key key, + Qt::KeyboardModifiers modifiers = Qt::NoModifier) { + const auto focused = QApplication::focusWidget(); + if (qobject_cast(focused) + || qobject_cast(focused) + || dynamic_cast(focused)) { + QApplication::postEvent( + focused, + new QKeyEvent(QEvent::KeyPress, key, modifiers)); + QApplication::postEvent( + focused, + new QKeyEvent(QEvent::KeyRelease, key, modifiers)); + } +} + +void ForceDisabled(QAction *action, bool disabled) { + if (action->isEnabled()) { + if (disabled) action->setDisabled(true); + } else if (!disabled) { + action->setDisabled(false); + } +} + +Window::Controller *Manager::resolveActiveWindow() const { + if (!Core::IsAppLaunched()) { + return nullptr; + } + const auto active = Core::App().activeWindow(); + return active ? active : Core::App().activePrimaryWindow(); +} + +bool Manager::clipboardHasText() { + const auto current = static_cast([_pasteboard changeCount]); + if (_pasteboardChangeCount != current) { + _pasteboardChangeCount = current; + _pasteboardHasText = !QGuiApplication::clipboard()->text().isEmpty(); + } + return _pasteboardHasText; +} + +void Manager::retranslate() { + if (_logout) { + _logout->setText(tr::lng_mac_menu_logout(tr::now)); + } + if (_delete) { + _delete->setText(tr::lng_mac_menu_delete(tr::now)); + } + if (_contacts) { + _contacts->setText(tr::lng_mac_menu_contacts(tr::now)); + } + if (_addContact) { + _addContact->setText(tr::lng_mac_menu_add_contact(tr::now)); + } + if (_newGroup) { + _newGroup->setText(tr::lng_mac_menu_new_group(tr::now)); + } + if (_newChannel) { + _newChannel->setText(tr::lng_mac_menu_new_channel(tr::now)); + } + if (_showTelegram) { + _showTelegram->setText(tr::lng_mac_menu_show(tr::now)); + } + if (_fullScreen) { + _fullScreen->setText(tr::lng_mac_menu_fullscreen(tr::now)); + } + if (_emoji) { + _emoji->setText(tr::lng_mac_menu_emoji_and_symbols( + tr::now, + Ui::Text::FixAmpersandInAction)); + } + if (_bold) { + _bold->setText(tr::lng_menu_formatting_bold(tr::now)); + } + if (_italic) { + _italic->setText(tr::lng_menu_formatting_italic(tr::now)); + } + if (_underline) { + _underline->setText(tr::lng_menu_formatting_underline(tr::now)); + } + if (_strikeOut) { + _strikeOut->setText(tr::lng_menu_formatting_strike_out(tr::now)); + } + if (_blockquote) { + _blockquote->setText(tr::lng_menu_formatting_blockquote(tr::now)); + } + if (_monospace) { + _monospace->setText(tr::lng_menu_formatting_monospace(tr::now)); + } + if (_clearFormat) { + _clearFormat->setText(tr::lng_menu_formatting_clear(tr::now)); + } +} + +void Manager::ensureLanguageBound() { + if (_languageBound || !Core::IsAppLaunched()) { + return; + } + _languageBound = true; + retranslate(); + Lang::Updated() | rpl::on_next([this] { + retranslate(); + }, _lifetime); +} + +void Manager::recomputeState() { + const auto window = resolveActiveWindow(); + if (!window) { + return; + } + const auto widget = window->widget(); + if (!widget->positionInited()) { + return; + } + + const auto focused = QApplication::focusWidget(); + auto canUndo = false; + auto canRedo = false; + auto canCut = false; + auto canCopy = false; + auto canPaste = false; + auto canDelete = false; + auto canSelectAll = false; + const auto clipboardHasText = this->clipboardHasText(); + auto markdownState = Ui::MarkdownEnabledState(); + if (const auto edit = qobject_cast(focused)) { + canCut = canCopy = canDelete = edit->hasSelectedText(); + canSelectAll = !edit->text().isEmpty(); + canUndo = edit->isUndoAvailable(); + canRedo = edit->isRedoAvailable(); + canPaste = clipboardHasText; + } else if (const auto edit = qobject_cast(focused)) { + canCut = canCopy = canDelete = edit->textCursor().hasSelection(); + canSelectAll = !edit->document()->isEmpty(); + canUndo = edit->document()->isUndoAvailable(); + canRedo = edit->document()->isRedoAvailable(); + canPaste = clipboardHasText; + if (canCopy) { + if (const auto inputField = dynamic_cast( + focused->parentWidget())) { + markdownState = inputField->markdownEnabledState(); + } + } + } else if (const auto list = dynamic_cast(focused)) { + canCopy = list->canCopySelected(); + canDelete = list->canDeleteSelected(); + } + + _markdownChanges.fire_copy(markdownState); + + widget->updateIsActive(); + const auto controller = window->sessionController(); + const auto logged = (controller != nullptr); + const auto inactive = !logged || window->locked(); + const auto support = logged && controller->session().supportMode(); + ForceDisabled(_logout, !logged && !Core::App().passcodeLocked()); + ForceDisabled(_undo, !canUndo); + ForceDisabled(_redo, !canRedo); + ForceDisabled(_cut, !canCut); + ForceDisabled(_copy, !canCopy); + ForceDisabled(_paste, !canPaste); + ForceDisabled(_delete, !canDelete); + ForceDisabled(_selectAll, !canSelectAll); + ForceDisabled(_contacts, inactive || support); + ForceDisabled(_addContact, inactive); + ForceDisabled(_newGroup, inactive || support); + ForceDisabled(_newChannel, inactive || support); + ForceDisabled(_showTelegram, widget->isActive()); + + const auto disabled = [&](const QString &tag) { + return !markdownState.enabledForTag(tag); + }; + using Field = Ui::InputField; + ForceDisabled(_bold, disabled(Field::kTagBold)); + ForceDisabled(_italic, disabled(Field::kTagItalic)); + ForceDisabled(_underline, disabled(Field::kTagUnderline)); + ForceDisabled(_strikeOut, disabled(Field::kTagStrikeOut)); + ForceDisabled(_blockquote, disabled(Field::kTagBlockquote)); + ForceDisabled( + _monospace, + disabled(Field::kTagPre) || disabled(Field::kTagCode)); + ForceDisabled(_clearFormat, markdownState.disabled()); +} + +void Manager::buildAppleMenu(QMenu *main) { + { + auto callback = [this] { + withActiveWindow([](not_null window) { + window->show(Box(AboutBox)); + }); + }; + const auto about = main->addAction( + u"About Telegram"_q, + std::move(callback)); + about->setMenuRole(QAction::AboutQtRole); + } + + main->addSeparator(); + { + auto callback = [this] { + withActiveWindow([](not_null window) { + window->showSettings(); + }); + }; + const auto preferences = main->addAction( + u"Preferences..."_q, + _menuBar.get(), + std::move(callback), + QKeySequence(Qt::ControlModifier | Qt::Key_Comma)); + preferences->setMenuRole(QAction::PreferencesRole); + preferences->setShortcutContext(Qt::WidgetShortcut); + } +} + +void Manager::buildFileMenu(QMenu *file) { + auto callback = [this] { + withActiveWindow([](not_null window) { + window->showLogoutConfirmation(); + }); + }; + _logout = file->addAction( + u"Log Out"_q, + _menuBar.get(), + std::move(callback)); +} + +void Manager::buildEditMenu(QMenu *edit) { + const auto receiver = _menuBar.get(); + _undo = edit->addAction( + u"Undo"_q, + receiver, + [] { SendKeySequence(Qt::Key_Z, Qt::ControlModifier); }, + QKeySequence::Undo); + _undo->setShortcutContext(Qt::WidgetShortcut); + _redo = edit->addAction( + u"Redo"_q, + receiver, + [] { + SendKeySequence( + Qt::Key_Z, + Qt::ControlModifier | Qt::ShiftModifier); + }, + QKeySequence::Redo); + _redo->setShortcutContext(Qt::WidgetShortcut); + edit->addSeparator(); + _cut = edit->addAction( + u"Cut"_q, + receiver, + [] { SendKeySequence(Qt::Key_X, Qt::ControlModifier); }, + QKeySequence::Cut); + _cut->setShortcutContext(Qt::WidgetShortcut); +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + _cut->setMenuRole(QAction::CutRole); +#endif // Qt >= 6.8.0 + _copy = edit->addAction( + u"Copy"_q, + receiver, + [] { SendKeySequence(Qt::Key_C, Qt::ControlModifier); }, + QKeySequence::Copy); + _copy->setShortcutContext(Qt::WidgetShortcut); +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + _copy->setMenuRole(QAction::CopyRole); +#endif // Qt >= 6.8.0 + _paste = edit->addAction( + u"Paste"_q, + receiver, + [] { SendKeySequence(Qt::Key_V, Qt::ControlModifier); }, + QKeySequence::Paste); + _paste->setShortcutContext(Qt::WidgetShortcut); +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + _paste->setMenuRole(QAction::PasteRole); +#endif // Qt >= 6.8.0 + _delete = edit->addAction( + u"Delete"_q, + receiver, + [] { SendKeySequence(Qt::Key_Delete); }, + QKeySequence(Qt::ControlModifier | Qt::Key_Backspace)); + _delete->setShortcutContext(Qt::WidgetShortcut); + + edit->addSeparator(); + _bold = edit->addAction( + u"Bold"_q, + receiver, + [] { SendKeySequence(Qt::Key_B, Qt::ControlModifier); }, + QKeySequence::Bold); + _bold->setShortcutContext(Qt::WidgetShortcut); + _italic = edit->addAction( + u"Italic"_q, + receiver, + [] { SendKeySequence(Qt::Key_I, Qt::ControlModifier); }, + QKeySequence::Italic); + _italic->setShortcutContext(Qt::WidgetShortcut); + _underline = edit->addAction( + u"Underline"_q, + receiver, + [] { SendKeySequence(Qt::Key_U, Qt::ControlModifier); }, + QKeySequence::Underline); + _underline->setShortcutContext(Qt::WidgetShortcut); + _strikeOut = edit->addAction( + u"Strikethrough"_q, + receiver, + [] { + SendKeySequence( + Qt::Key_X, + Qt::ControlModifier | Qt::ShiftModifier); + }, + Ui::kStrikeOutSequence); + _strikeOut->setShortcutContext(Qt::WidgetShortcut); + _blockquote = edit->addAction( + u"Quote"_q, + receiver, + [] { + SendKeySequence( + Qt::Key_Period, + Qt::ControlModifier | Qt::ShiftModifier); + }, + Ui::kBlockquoteSequence); + _blockquote->setShortcutContext(Qt::WidgetShortcut); + _monospace = edit->addAction( + u"Monospace"_q, + receiver, + [] { + SendKeySequence( + Qt::Key_M, + Qt::ControlModifier | Qt::ShiftModifier); + }, + Ui::kMonospaceSequence); + _monospace->setShortcutContext(Qt::WidgetShortcut); + _clearFormat = edit->addAction( + u"Clear formatting"_q, + receiver, + [] { + SendKeySequence( + Qt::Key_N, + Qt::ControlModifier | Qt::ShiftModifier); + }, + Ui::kClearFormatSequence); + _clearFormat->setShortcutContext(Qt::WidgetShortcut); + + edit->addSeparator(); + _selectAll = edit->addAction( + u"Select All"_q, + receiver, + [] { SendKeySequence(Qt::Key_A, Qt::ControlModifier); }, + QKeySequence::SelectAll); + _selectAll->setShortcutContext(Qt::WidgetShortcut); +#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0) + _selectAll->setMenuRole(QAction::SelectAllRole); +#endif // Qt >= 6.8.0 + + if (!Platform::IsMac26_0OrGreater()) { + edit->addSeparator(); + _emoji = edit->addAction( + u"Emoji & Symbols"_q, + receiver, + [] { [NSApp orderFrontCharacterPalette:nil]; }, + QKeySequence(Qt::MetaModifier + | Qt::ControlModifier + | Qt::Key_Space)); + _emoji->setShortcutContext(Qt::WidgetShortcut); + } +} + +void Manager::buildWindowMenu(QMenu *window) { + const auto receiver = _menuBar.get(); + _fullScreen = window->addAction( + u"Toggle Full Screen"_q, + receiver, + [this] { + withActiveWindow([](not_null w) { + const auto view = reinterpret_cast( + w->widget()->winId()); + NSWindow *nsWindow = [view window]; + [nsWindow toggleFullScreen:nsWindow]; + }); + }, + QKeySequence(Qt::MetaModifier | Qt::ControlModifier | Qt::Key_F)); + _fullScreen->setShortcutContext(Qt::WidgetShortcut); + window->addSeparator(); + + _contacts = window->addAction(u"Contacts"_q); + QObject::connect(_contacts, &QAction::triggered, _contacts, [this] { + withActiveWindow([](not_null w) { + const auto sc = w->sessionController(); + if (!sc || w->locked()) { + return; + } + sc->show(PrepareContactsBox(sc)); + }); + }); + { + auto callback = [this] { + withActiveWindow([](not_null w) { + const auto sc = w->sessionController(); + if (!sc || w->locked()) { + return; + } + sc->showAddContact(); + }); + }; + _addContact = window->addAction( + u"Add Contact"_q, + receiver, + std::move(callback)); + } + window->addSeparator(); + { + auto callback = [this] { + withActiveWindow([](not_null w) { + const auto sc = w->sessionController(); + if (!sc || w->locked()) { + return; + } + sc->showNewGroup(); + }); + }; + _newGroup = window->addAction( + u"New Group"_q, + receiver, + std::move(callback)); + } + { + auto callback = [this] { + withActiveWindow([](not_null w) { + const auto sc = w->sessionController(); + if (!sc || w->locked()) { + return; + } + sc->showNewChannel(); + }); + }; + _newChannel = window->addAction( + u"New Channel"_q, + receiver, + std::move(callback)); + } + window->addSeparator(); + _showTelegram = window->addAction( + u"Show Telegram"_q, + receiver, + [this] { + if (const auto w = resolveActiveWindow()) { + w->widget()->showFromTray(); + } + }); +} + +void Manager::buildMenu() { + buildAppleMenu(_menuBar->addMenu(u"Telegram"_q)); + buildFileMenu(_menuBar->addMenu(u"File"_q)); + buildEditMenu(_menuBar->addMenu(u"Edit"_q)); + buildWindowMenu(_menuBar->addMenu(u"Window"_q)); +} + +void Manager::create() { + Expects(_menuBar == nullptr); + + _pasteboard = [NSPasteboard generalPasteboard]; + _menuBar = std::make_unique(); + + buildMenu(); + + _updateRequests.events() | rpl::on_next([this] { + ensureLanguageBound(); + recomputeState(); + }, _lifetime); +} + +void Manager::destroy() { + _lifetime.destroy(); + _menuBar.reset(); + _languageBound = false; + _logout = _undo = _redo = _cut = _copy = _paste = _delete + = _selectAll = _contacts = _addContact = _newGroup + = _newChannel = _showTelegram = _fullScreen = _emoji + = _bold = _italic = _underline + = _strikeOut = _blockquote = _monospace = _clearFormat + = nullptr; + _pasteboard = nullptr; + _pasteboardChangeCount = -1; + _pasteboardHasText = false; +} + +void Manager::requestUpdate() { + if (!_menuBar) { + return; + } + _updateRequests.fire({}); +} + +auto Manager::markdownStateChanges() const +-> rpl::producer { + return _markdownChanges.events(); +} + +Manager GlobalMenu; + +} // namespace + +void CreateGlobalMenu() { + GlobalMenu.create(); +} + +void DestroyGlobalMenu() { + GlobalMenu.destroy(); +} + +void RequestUpdateGlobalMenu() { + GlobalMenu.requestUpdate(); +} + +rpl::producer GlobalMenuMarkdownState() { + return GlobalMenu.markdownStateChanges(); +} + +} // namespace Platform diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.h b/Telegram/SourceFiles/platform/mac/main_window_mac.h index ab212a6c76..dcc064c34f 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.h +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.h @@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "platform/mac/specific_mac_p.h" #include "base/timer.h" -#include #include namespace Platform { @@ -41,10 +40,7 @@ protected: void initHook() override; void unreadCounterChangedHook() override; - void updateGlobalMenuHook() override; - void closeWithoutDestroy() override; - void createGlobalMenu() override; private: friend class Private; @@ -63,29 +59,6 @@ private: base::Timer _hideAfterFullScreenTimer; - QMenuBar psMainMenu; - QAction *psLogout = nullptr; - QAction *psUndo = nullptr; - QAction *psRedo = nullptr; - QAction *psCut = nullptr; - QAction *psCopy = nullptr; - QAction *psPaste = nullptr; - QAction *psDelete = nullptr; - QAction *psSelectAll = nullptr; - QAction *psContacts = nullptr; - QAction *psAddContact = nullptr; - QAction *psNewGroup = nullptr; - QAction *psNewChannel = nullptr; - QAction *psShowTelegram = nullptr; - - QAction *psBold = nullptr; - QAction *psItalic = nullptr; - QAction *psUnderline = nullptr; - QAction *psStrikeOut = nullptr; - QAction *psBlockquote = nullptr; - QAction *psMonospace = nullptr; - QAction *psClearFormat = nullptr; - rpl::event_stream _forceClicks; int _customTitleHeight = 0; int _lastPressureStage = 0; diff --git a/Telegram/SourceFiles/platform/mac/main_window_mac.mm b/Telegram/SourceFiles/platform/mac/main_window_mac.mm index dbd46e4fe1..0232f25d08 100644 --- a/Telegram/SourceFiles/platform/mac/main_window_mac.mm +++ b/Telegram/SourceFiles/platform/mac/main_window_mac.mm @@ -21,6 +21,7 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "ui/text/text_utilities.h" #include "window/window_controller.h" #include "window/window_session_controller.h" +#include "platform/mac/global_menu_mac.h" #include "platform/mac/touchbar/mac_touchbar_manager.h" #include "platform/platform_specific.h" #include "platform/platform_notifications_manager.h" @@ -96,9 +97,10 @@ public: not_null controller); void setWindowBadge(const QString &str); - void setMarkdownEnabledState(Ui::MarkdownEnabledState state); + void updateMarkdownState(Ui::MarkdownEnabledState state) { + _markdownState = state; + } - bool clipboardHasText(); ~Private(); private: @@ -111,9 +113,6 @@ private: NSView * __weak _nativeView = nil; MainWindowObserver *_observer = nullptr; - NSPasteboard *_generalPasteboard = nullptr; - int _generalPasteboardChangeCount = -1; - bool _generalPasteboardHasText = false; }; @@ -159,30 +158,6 @@ private: namespace Platform { namespace { -void SendKeySequence( - Qt::Key key, - Qt::KeyboardModifiers modifiers = Qt::NoModifier) { - const auto focused = QApplication::focusWidget(); - if (qobject_cast(focused) - || qobject_cast(focused) - || dynamic_cast(focused)) { - QApplication::postEvent( - focused, - new QKeyEvent(QEvent::KeyPress, key, modifiers)); - QApplication::postEvent( - focused, - new QKeyEvent(QEvent::KeyRelease, key, modifiers)); - } -} - -void ForceDisabled(QAction *action, bool disabled) { - if (action->isEnabled()) { - if (disabled) action->setDisabled(true); - } else if (!disabled) { - action->setDisabled(false); - } -} - #if QT_VERSION < QT_VERSION_CHECK(6, 6, 0) QString strNotificationAboutThemeChange() { 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 }; @@ -205,8 +180,6 @@ QString strNotificationAboutScreenUnlocked() { MainWindow::Private::Private(not_null window) : _public(window) , _observer([[MainWindowObserver alloc] init:this]) { - _generalPasteboard = [NSPasteboard generalPasteboard]; - @autoreleasepool { [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:_observer selector:@selector(activeSpaceDidChange:) name:NSWorkspaceActiveSpaceDidChangeNotification object:nil]; @@ -253,28 +226,13 @@ void MainWindow::Private::initTouchBar( waitUntilDone:true]; } -void MainWindow::Private::setMarkdownEnabledState( - Ui::MarkdownEnabledState state) { - _markdownState = state; -} - -bool MainWindow::Private::clipboardHasText() { - auto currentChangeCount = static_cast([_generalPasteboard changeCount]); - if (_generalPasteboardChangeCount != currentChangeCount) { - _generalPasteboardChangeCount = currentChangeCount; - _generalPasteboardHasText = !QGuiApplication::clipboard()->text().isEmpty(); - } - return _generalPasteboardHasText; -} - MainWindow::Private::~Private() { [_observer release]; } MainWindow::MainWindow(not_null controller) : Window::MainWindow(controller) -, _private(std::make_unique(this)) -, psMainMenu(this) { +, _private(std::make_unique(this)) { _hideAfterFullScreenTimer.setCallback([this] { hideAndDeactivate(); }); } @@ -309,6 +267,11 @@ void MainWindow::initHook() { } } } + + Platform::GlobalMenuMarkdownState( + ) | rpl::on_next([this](Ui::MarkdownEnabledState state) { + _private->updateMarkdownState(state); + }, lifetime()); } void MainWindow::updateWindowIcon() { @@ -358,314 +321,6 @@ void MainWindow::updateDockCounter() { _private->setWindowBadge(string); } -void MainWindow::createGlobalMenu() { - const auto ensureWindowShown = [=] { - if (isHidden()) { - showFromTray(); - } - }; - - auto main = psMainMenu.addMenu(u"Telegram"_q); - { - auto callback = [=] { - ensureWindowShown(); - controller().show(Box(AboutBox)); - }; - main->addAction( - tr::lng_mac_menu_about_telegram( - tr::now, - lt_telegram, - u"Telegram"_q), - std::move(callback)) - ->setMenuRole(QAction::AboutQtRole); - } - - main->addSeparator(); - { - auto callback = [=] { - ensureWindowShown(); - controller().showSettings(); - }; - auto prefs = main->addAction( - tr::lng_mac_menu_preferences(tr::now), - this, - std::move(callback), - QKeySequence(Qt::ControlModifier | Qt::Key_Comma)); - prefs->setMenuRole(QAction::PreferencesRole); - prefs->setShortcutContext(Qt::WidgetShortcut); - } - - QMenu *file = psMainMenu.addMenu(tr::lng_mac_menu_file(tr::now)); - { - auto callback = [=] { - ensureWindowShown(); - controller().showLogoutConfirmation(); - }; - psLogout = file->addAction( - tr::lng_mac_menu_logout(tr::now), - this, - std::move(callback)); - } - - QMenu *edit = psMainMenu.addMenu(tr::lng_mac_menu_edit(tr::now)); - psUndo = edit->addAction( - tr::lng_mac_menu_undo(tr::now), - this, - [] { SendKeySequence(Qt::Key_Z, Qt::ControlModifier); }, - QKeySequence::Undo); - psUndo->setShortcutContext(Qt::WidgetShortcut); - psRedo = edit->addAction( - tr::lng_mac_menu_redo(tr::now), - this, - [] { - SendKeySequence( - Qt::Key_Z, - Qt::ControlModifier | Qt::ShiftModifier); - }, - QKeySequence::Redo); - psRedo->setShortcutContext(Qt::WidgetShortcut); - edit->addSeparator(); - psCut = edit->addAction( - tr::lng_mac_menu_cut(tr::now), - this, - [] { SendKeySequence(Qt::Key_X, Qt::ControlModifier); }, - QKeySequence::Cut); - psCut->setShortcutContext(Qt::WidgetShortcut); - psCopy = edit->addAction( - tr::lng_mac_menu_copy(tr::now), - this, - [] { SendKeySequence(Qt::Key_C, Qt::ControlModifier); }, - QKeySequence::Copy); - psCopy->setShortcutContext(Qt::WidgetShortcut); - psPaste = edit->addAction( - tr::lng_mac_menu_paste(tr::now), - this, - [] { SendKeySequence(Qt::Key_V, Qt::ControlModifier); }, - QKeySequence::Paste); - psPaste->setShortcutContext(Qt::WidgetShortcut); - psDelete = edit->addAction( - tr::lng_mac_menu_delete(tr::now), - this, - [] { SendKeySequence(Qt::Key_Delete); }, - QKeySequence(Qt::ControlModifier | Qt::Key_Backspace)); - psDelete->setShortcutContext(Qt::WidgetShortcut); - - edit->addSeparator(); - psBold = edit->addAction( - tr::lng_menu_formatting_bold(tr::now), - this, - [] { SendKeySequence(Qt::Key_B, Qt::ControlModifier); }, - QKeySequence::Bold); - psBold->setShortcutContext(Qt::WidgetShortcut); - psItalic = edit->addAction( - tr::lng_menu_formatting_italic(tr::now), - this, - [] { SendKeySequence(Qt::Key_I, Qt::ControlModifier); }, - QKeySequence::Italic); - psItalic->setShortcutContext(Qt::WidgetShortcut); - psUnderline = edit->addAction( - tr::lng_menu_formatting_underline(tr::now), - this, - [] { SendKeySequence(Qt::Key_U, Qt::ControlModifier); }, - QKeySequence::Underline); - psUnderline->setShortcutContext(Qt::WidgetShortcut); - psStrikeOut = edit->addAction( - tr::lng_menu_formatting_strike_out(tr::now), - this, - [] { - SendKeySequence( - Qt::Key_X, - Qt::ControlModifier | Qt::ShiftModifier); - }, - Ui::kStrikeOutSequence); - psStrikeOut->setShortcutContext(Qt::WidgetShortcut); - psBlockquote = edit->addAction( - tr::lng_menu_formatting_blockquote(tr::now), - this, - [] { - SendKeySequence( - Qt::Key_Period, - Qt::ControlModifier | Qt::ShiftModifier); - }, - Ui::kBlockquoteSequence); - psBlockquote->setShortcutContext(Qt::WidgetShortcut); - psMonospace = edit->addAction( - tr::lng_menu_formatting_monospace(tr::now), - this, - [] { - SendKeySequence( - Qt::Key_M, - Qt::ControlModifier | Qt::ShiftModifier); - }, - Ui::kMonospaceSequence); - psMonospace->setShortcutContext(Qt::WidgetShortcut); - psClearFormat = edit->addAction( - tr::lng_menu_formatting_clear(tr::now), - this, - [] { - SendKeySequence( - Qt::Key_N, - Qt::ControlModifier | Qt::ShiftModifier); - }, - Ui::kClearFormatSequence); - psClearFormat->setShortcutContext(Qt::WidgetShortcut); - - edit->addSeparator(); - psSelectAll = edit->addAction( - tr::lng_mac_menu_select_all(tr::now), - this, - [] { SendKeySequence(Qt::Key_A, Qt::ControlModifier); }, - QKeySequence::SelectAll); - psSelectAll->setShortcutContext(Qt::WidgetShortcut); - - if (!Platform::IsMac26_0OrGreater()) { - edit->addSeparator(); - edit->addAction( - tr::lng_mac_menu_emoji_and_symbols( - tr::now, - Ui::Text::FixAmpersandInAction), - this, - [] { [NSApp orderFrontCharacterPalette:nil]; }, - QKeySequence(Qt::MetaModifier - | Qt::ControlModifier - | Qt::Key_Space) - )->setShortcutContext(Qt::WidgetShortcut); - } - - QMenu *window = psMainMenu.addMenu(tr::lng_mac_menu_window(tr::now)); - - window->addAction( - tr::lng_mac_menu_fullscreen(tr::now), - this, - [=] { - NSWindow *nsWindow = [reinterpret_cast(winId()) window]; - [nsWindow toggleFullScreen:nsWindow]; - }, - QKeySequence(Qt::MetaModifier | Qt::ControlModifier | Qt::Key_F) - )->setShortcutContext(Qt::WidgetShortcut); - window->addSeparator(); - - psContacts = window->addAction(tr::lng_mac_menu_contacts(tr::now)); - connect(psContacts, &QAction::triggered, psContacts, crl::guard(this, [=] { - Expects(sessionController() != nullptr && !controller().locked()); - - ensureWindowShown(); - sessionController()->show(PrepareContactsBox(sessionController())); - })); - { - auto callback = [=] { - Expects(sessionController() != nullptr && !controller().locked()); - - ensureWindowShown(); - sessionController()->showAddContact(); - }; - psAddContact = window->addAction( - tr::lng_mac_menu_add_contact(tr::now), - this, - std::move(callback)); - } - window->addSeparator(); - { - auto callback = [=] { - Expects(sessionController() != nullptr && !controller().locked()); - - ensureWindowShown(); - sessionController()->showNewGroup(); - }; - psNewGroup = window->addAction( - tr::lng_mac_menu_new_group(tr::now), - this, - std::move(callback)); - } - { - auto callback = [=] { - Expects(sessionController() != nullptr && !controller().locked()); - - ensureWindowShown(); - sessionController()->showNewChannel(); - }; - psNewChannel = window->addAction( - tr::lng_mac_menu_new_channel(tr::now), - this, - std::move(callback)); - } - window->addSeparator(); - psShowTelegram = window->addAction( - tr::lng_mac_menu_show(tr::now), - this, - [=] { showFromTray(); }); - - updateGlobalMenu(); -} - -void MainWindow::updateGlobalMenuHook() { - if (!positionInited()) { - return; - } - - auto focused = QApplication::focusWidget(); - bool canUndo = false, canRedo = false, canCut = false, canCopy = false, canPaste = false, canDelete = false, canSelectAll = false; - auto clipboardHasText = _private->clipboardHasText(); - auto markdownState = Ui::MarkdownEnabledState(); - if (auto edit = qobject_cast(focused)) { - canCut = canCopy = canDelete = edit->hasSelectedText(); - canSelectAll = !edit->text().isEmpty(); - canUndo = edit->isUndoAvailable(); - canRedo = edit->isRedoAvailable(); - canPaste = clipboardHasText; - } else if (auto edit = qobject_cast(focused)) { - canCut = canCopy = canDelete = edit->textCursor().hasSelection(); - canSelectAll = !edit->document()->isEmpty(); - canUndo = edit->document()->isUndoAvailable(); - canRedo = edit->document()->isRedoAvailable(); - canPaste = clipboardHasText; - if (canCopy) { - if (const auto inputField = dynamic_cast( - focused->parentWidget())) { - markdownState = inputField->markdownEnabledState(); - } - } - } else if (auto list = dynamic_cast(focused)) { - canCopy = list->canCopySelected(); - canDelete = list->canDeleteSelected(); - } - - _private->setMarkdownEnabledState(markdownState); - - updateIsActive(); - const auto logged = (sessionController() != nullptr); - const auto inactive = !logged || controller().locked(); - const auto support = logged - && sessionController()->session().supportMode(); - ForceDisabled(psLogout, !logged && !Core::App().passcodeLocked()); - ForceDisabled(psUndo, !canUndo); - ForceDisabled(psRedo, !canRedo); - ForceDisabled(psCut, !canCut); - ForceDisabled(psCopy, !canCopy); - ForceDisabled(psPaste, !canPaste); - ForceDisabled(psDelete, !canDelete); - ForceDisabled(psSelectAll, !canSelectAll); - ForceDisabled(psContacts, inactive || support); - ForceDisabled(psAddContact, inactive); - ForceDisabled(psNewGroup, inactive || support); - ForceDisabled(psNewChannel, inactive || support); - ForceDisabled(psShowTelegram, isActive()); - - const auto diabled = [=](const QString &tag) { - return !markdownState.enabledForTag(tag); - }; - using Field = Ui::InputField; - ForceDisabled(psBold, diabled(Field::kTagBold)); - ForceDisabled(psItalic, diabled(Field::kTagItalic)); - ForceDisabled(psUnderline, diabled(Field::kTagUnderline)); - ForceDisabled(psStrikeOut, diabled(Field::kTagStrikeOut)); - ForceDisabled(psBlockquote, diabled(Field::kTagBlockquote)); - ForceDisabled( - psMonospace, - diabled(Field::kTagPre) || diabled(Field::kTagCode)); - ForceDisabled(psClearFormat, markdownState.disabled()); -} - bool MainWindow::eventFilter(QObject *obj, QEvent *evt) { QEvent::Type t = evt->type(); if (t == QEvent::FocusIn || t == QEvent::FocusOut) { diff --git a/Telegram/SourceFiles/window/main_window.cpp b/Telegram/SourceFiles/window/main_window.cpp index 51da6eb006..5b11b2eaf4 100644 --- a/Telegram/SourceFiles/window/main_window.cpp +++ b/Telegram/SourceFiles/window/main_window.cpp @@ -45,6 +45,10 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL #include "styles/style_window.h" #include "styles/style_dialogs.h" // ChildSkip().x() for new child windows. +#ifdef Q_OS_MAC +#include "platform/mac/global_menu_mac.h" +#endif // Q_OS_MAC + #include #include #include @@ -500,6 +504,14 @@ void MainWindow::clearWidgets() { updateGlobalMenu(); } +void MainWindow::updateGlobalMenu() { +#ifdef Q_OS_MAC + Platform::RequestUpdateGlobalMenu(); +#else // Q_OS_MAC + updateGlobalMenuHook(); +#endif // Q_OS_MAC +} + void MainWindow::updateIsActive() { const auto isActive = computeIsActive(); if (_isActive != isActive) { diff --git a/Telegram/SourceFiles/window/main_window.h b/Telegram/SourceFiles/window/main_window.h index 2dcf1a77f4..f3005d2171 100644 --- a/Telegram/SourceFiles/window/main_window.h +++ b/Telegram/SourceFiles/window/main_window.h @@ -138,9 +138,7 @@ public: void firstShow(); bool minimizeToTray(); - void updateGlobalMenu() { - updateGlobalMenuHook(); - } + void updateGlobalMenu(); [[nodiscard]] virtual rpl::producer globalForceClicks() { return rpl::never();