Use single global menu on macOS.

This commit is contained in:
John Preston
2026-04-24 12:14:14 +07:00
parent d9649bc8d9
commit 0e1b53dfa7
8 changed files with 680 additions and 386 deletions
+2
View File
@@ -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
+12 -1
View File
@@ -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 <QtCore/QLockFile>
#include <QtGui/QSessionManager>
#include <QtGui/QScreen>
@@ -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) {
@@ -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<Ui::MarkdownEnabledState> GlobalMenuMarkdownState();
} // namespace Platform
@@ -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 <QtWidgets/QMenuBar>
#include <QtWidgets/QMenu>
#include <QtWidgets/QLineEdit>
#include <QtWidgets/QTextEdit>
#include <QtGui/QAction>
#include <QtGui/QGuiApplication>
#include <QtGui/QClipboard>
#include <Cocoa/Cocoa.h>
namespace Platform {
namespace {
class Manager final {
public:
void create();
void destroy();
void requestUpdate();
[[nodiscard]] auto markdownStateChanges() const
-> rpl::producer<Ui::MarkdownEnabledState>;
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 <typename Callback>
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<QMenuBar> _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<Ui::MarkdownEnabledState> _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<QLineEdit*>(focused)
|| qobject_cast<QTextEdit*>(focused)
|| dynamic_cast<HistoryInner*>(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<int>([_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<QLineEdit*>(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<QTextEdit*>(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<Ui::InputField*>(
focused->parentWidget())) {
markdownState = inputField->markdownEnabledState();
}
}
} else if (const auto list = dynamic_cast<HistoryInner*>(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::Controller*> 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::Controller*> 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::Controller*> 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<Window::Controller*> w) {
const auto view = reinterpret_cast<NSView*>(
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<Window::Controller*> w) {
const auto sc = w->sessionController();
if (!sc || w->locked()) {
return;
}
sc->show(PrepareContactsBox(sc));
});
});
{
auto callback = [this] {
withActiveWindow([](not_null<Window::Controller*> 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<Window::Controller*> 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<Window::Controller*> 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<QMenuBar>();
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<Ui::MarkdownEnabledState> {
return _markdownChanges.events();
}
Manager GlobalMenu;
} // namespace
void CreateGlobalMenu() {
GlobalMenu.create();
}
void DestroyGlobalMenu() {
GlobalMenu.destroy();
}
void RequestUpdateGlobalMenu() {
GlobalMenu.requestUpdate();
}
rpl::producer<Ui::MarkdownEnabledState> GlobalMenuMarkdownState() {
return GlobalMenu.markdownStateChanges();
}
} // namespace Platform
@@ -11,7 +11,6 @@ https://github.com/telegramdesktop/tdesktop/blob/master/LEGAL
#include "platform/mac/specific_mac_p.h"
#include "base/timer.h"
#include <QtWidgets/QMenuBar>
#include <QtCore/QTimer>
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<QPoint> _forceClicks;
int _customTitleHeight = 0;
int _lastPressureStage = 0;
@@ -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<Window::Controller*> 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<QLineEdit*>(focused)
|| qobject_cast<QTextEdit*>(focused)
|| dynamic_cast<HistoryInner*>(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<MainWindow*> 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<int>([_generalPasteboard changeCount]);
if (_generalPasteboardChangeCount != currentChangeCount) {
_generalPasteboardChangeCount = currentChangeCount;
_generalPasteboardHasText = !QGuiApplication::clipboard()->text().isEmpty();
}
return _generalPasteboardHasText;
}
MainWindow::Private::~Private() {
[_observer release];
}
MainWindow::MainWindow(not_null<Window::Controller*> controller)
: Window::MainWindow(controller)
, _private(std::make_unique<Private>(this))
, psMainMenu(this) {
, _private(std::make_unique<Private>(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<NSView*>(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<QLineEdit*>(focused)) {
canCut = canCopy = canDelete = edit->hasSelectedText();
canSelectAll = !edit->text().isEmpty();
canUndo = edit->isUndoAvailable();
canRedo = edit->isRedoAvailable();
canPaste = clipboardHasText;
} else if (auto edit = qobject_cast<QTextEdit*>(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<Ui::InputField*>(
focused->parentWidget())) {
markdownState = inputField->markdownEnabledState();
}
}
} else if (auto list = dynamic_cast<HistoryInner*>(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) {
@@ -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 <QtCore/QMimeData>
#include <QtGui/QWindow>
#include <QtGui/QScreen>
@@ -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) {
+1 -3
View File
@@ -138,9 +138,7 @@ public:
void firstShow();
bool minimizeToTray();
void updateGlobalMenu() {
updateGlobalMenuHook();
}
void updateGlobalMenu();
[[nodiscard]] virtual rpl::producer<QPoint> globalForceClicks() {
return rpl::never<QPoint>();