commit abb138b6c1004db8c91560c75e9ffb92aa3a9f3a
Author: Koji Yokota <[email protected]>
Date: Fri Oct 23 17:34:58 2026 +0900
Fix #13284 (Emacs-like two-stroke shortcuts doesn't work)
macOS automatically registers the text after '\t' in a menu item
as a shortcut and it overrides the shortcut set in QAction.
The patch basically diables menu actions in the prefix-key stage.
Also, appearance of two-stroke key shortcut in menu is adjusted to
match with that of one-stroke key shortcut.
Assisted-by: Claude
---
configure.ac | 4 ++++
src/frontends/qt/CMakeLists.txt | 12 ++++++++--
src/frontends/qt/GuiView.cpp | 36 ++++++++++++++++++++++++++++++
src/frontends/qt/Makefile.am | 5 +++++
src/frontends/qt/Menus.cpp | 49 ++++++++++++++++++++++++++++++-----------
5 files changed, 91 insertions(+), 15 deletions(-)
diff --git a/configure.ac b/configure.ac
index 1dae44109a..4e49e9c8fd 100644
--- a/configure.ac
+++ b/configure.ac
@@ -67,6 +67,10 @@ AC_LANG(C++)
AC_PROG_OBJC
_AM_DEPENDENCIES([OBJC])
+### Objective-C++ compiler
+AC_PROG_OBJCXX
+_AM_DEPENDENCIES([OBJCXX])
+
### Add extra directories to check for libraries.
LYX_WITH_DIR([extra-lib],[extra library directory],extra_lib, NONE)
LYX_LOOP_DIR($lyx_cv_extra_lib,LYX_ADD_LIB_DIR(lyx_ldflags,$dir))
diff --git a/src/frontends/qt/CMakeLists.txt b/src/frontends/qt/CMakeLists.txt
index 05d4c4abf4..ab2ecfb8c2 100644
--- a/src/frontends/qt/CMakeLists.txt
+++ b/src/frontends/qt/CMakeLists.txt
@@ -27,6 +27,12 @@ file(GLOB moc_files
${TOP_SRC_DIR}/src/frontends/qt/${LYX_MOC_FILES})
list(REMOVE_ITEM frontends_qt_sources ${moc_files} .)
+if(APPLE)
+ set(frontends_qt_objcxx_sources
${TOP_SRC_DIR}/src/frontends/qt/MenusMacOS.mm)
+else()
+ set(frontends_qt_objcxx_sources "")
+endif()
+
file(GLOB frontends_qt_headers
${TOP_SRC_DIR}/src/frontends/qt/${LYX_HPP_FILES})
file(GLOB frontend_qt_UI ${TOP_SRC_DIR}/src/frontends/qt/ui/*.ui)
@@ -57,9 +63,9 @@ if(LYX_MERGE_FILES)
set_source_files_properties(_allinone_const.C PROPERTIES OBJECT_DEPENDS
"${depends_moc_uic}")
set_source_files_properties(_allinone_touched.C PROPERTIES
OBJECT_DEPENDS "${depends_moc_uic}")
add_library(frontend_qt ${library_type}
- ${_allinone_files} ${frontends_qt_headers} ${ui_files}
${resource_files})
+ ${_allinone_files} ${frontends_qt_objcxx_sources}
${frontends_qt_headers} ${ui_files} ${resource_files})
else()
- add_library(frontend_qt ${library_type} ${frontends_qt_sources}
${frontends_qt_headers} ${ui_files} ${resource_files})
+ add_library(frontend_qt ${library_type} ${frontends_qt_sources}
${frontends_qt_objcxx_sources} ${frontends_qt_headers} ${ui_files}
${resource_files})
endif()
set_target_properties(frontend_qt PROPERTIES FOLDER "applications/LyX"
QT_NO_UNICODE_DEFINES TRUE)
@@ -83,6 +89,8 @@ lyx_add_gcc_pch(frontend_qt)
if(WIN32)
target_link_libraries(frontend_qt gdi32)
+elseif(APPLE)
+ target_link_libraries(frontend_qt "-framework AppKit")
endif()
project_source_group("${GROUP_CODE}" frontends_qt_sources frontends_qt_headers)
diff --git a/src/frontends/qt/GuiView.cpp b/src/frontends/qt/GuiView.cpp
index b81fa54728..36df8c7b16 100644
--- a/src/frontends/qt/GuiView.cpp
+++ b/src/frontends/qt/GuiView.cpp
@@ -109,6 +109,7 @@
#include <QPainter>
#include <QPixmap>
#include <QPoint>
+#include <QPointer>
#include <QSettings>
#include <QShowEvent>
#include <QSlider>
@@ -568,6 +569,35 @@ public:
/// equivalent time_to_update = 0; but better to see it the code
bool stats_update_trigger_ = false;
+ /// Menu actions disabled while a multi-key binding is pending
+ QList<QPointer<QAction>> key_equiv_blocked_;
+
+ /// On macOS, Qt turns the binding shown after '\t' in a menu label into
+ /// a native key equivalent, which then steals the second stroke of a
+ /// multi-key binding (e.g. C-s in C-x C-s). Disable those menu items
+ /// while a prefix is pending (bug #13284).
+ void blockMenuKeyEquivalents(bool block)
+ {
+ if (!block) {
+ for (QPointer<QAction> const & a : key_equiv_blocked_)
+ if (a)
+ a->setEnabled(true);
+ key_equiv_blocked_.clear();
+ return;
+ }
+ if (!key_equiv_blocked_.isEmpty())
+ return;
+ QList<QAction *> todo = gv_->menuBar()->actions();
+ while (!todo.isEmpty()) {
+ QAction * a = todo.takeLast();
+ if (QMenu * m = a->menu())
+ todo += m->actions();
+ else if (a->isEnabled() && a->text().contains('\t')) {
+ a->setEnabled(false);
+ key_equiv_blocked_.append(a);
+ }
+ }
+ }
};
QSet<Buffer const *> GuiView::GuiViewPrivate::busyBuffers;
@@ -595,6 +625,12 @@ GuiView::GuiView(int id)
// Fill up the menu bar.
guiApp->menus().fillMenuBar(menuBar(), this, true);
+#if defined(Q_OS_MAC)
+ connect(guiApp, &GuiApplication::keyCommandStateSet, this, [this]() {
+ d.blockMenuKeyEquivalents(guiApp->isInCommandMode());
+ });
+#endif
+
setCentralWidget(d.stack_widget_);
// Start autosave timer
diff --git a/src/frontends/qt/Makefile.am b/src/frontends/qt/Makefile.am
index e87478abe8..4705295799 100644
--- a/src/frontends/qt/Makefile.am
+++ b/src/frontends/qt/Makefile.am
@@ -365,4 +365,9 @@ liblyxqt_a_SOURCES = \
$(MOCHEADER) \
$(NOMOCHEADER)
+if INSTALL_MACOSX
+liblyxqt_a_SOURCES += MenusMacOS.mm
+AM_OBJCXXFLAGS = $(AM_CXXFLAGS)
+endif
+
EXTRA_DIST = CMakeLists.txt $(UIFILES:%=ui/%)
diff --git a/src/frontends/qt/Menus.cpp b/src/frontends/qt/Menus.cpp
index e8f4cc0902..f676d8e2ce 100644
--- a/src/frontends/qt/Menus.cpp
+++ b/src/frontends/qt/Menus.cpp
@@ -89,6 +89,12 @@ using namespace lyx::support;
namespace lyx {
namespace frontend {
+#if defined(Q_OS_MAC)
+// Defined in MenusMacOS.mm. Shows the key bindings appended to the labels of
+// the native menu items of \p qmenu (and its submenus) in a dimmed color.
+void greyOutBindings(QMenu * qmenu);
+#endif
+
namespace {
// macOS specific stuff is at the end.
@@ -2332,7 +2338,8 @@ struct Menu::Impl
/// Get a MenuDefinition item label from the menu backend
-static QString label(MenuItem const & mi, MenuDefinition const & menu)
+static QString label(MenuItem const & mi, MenuDefinition const & menu,
+ QString const & binding)
{
QString label = mi.label();
label.replace("&", "&&");
@@ -2345,17 +2352,22 @@ static QString label(MenuItem const & mi,
MenuDefinition const & menu)
label.replace(pos, 0, "&");
}
- QString const binding = mi.binding();
+ if (!binding.isEmpty()) {
#if defined(Q_OS_MAC)
- // MacOS cannot display complex bindings, so hide those
- // https://doc.qt.io/qt-6/macos-issues.html#menu-actions
- // #12693
- if (!binding.isEmpty() && !binding.contains(" "))
- label += '\t' + binding;
+ // Strings after '\t' are automatically registered as a shortcut
+ // in macOS which causes a problem in two-stroke shortcuts
+ // https://www.lyx.org/trac/ticket/13284
+ //
+ // Also note:
+ // MacOS cannot display complex bindings, so hide those
+ // https://doc.qt.io/qt-6/macos-issues.html#menu-actions
+ // #12693
+ QString mod = binding;
+ label += '\t' + mod.replace(" ", ",");
#else
- if (!binding.isEmpty())
label += '\t' + binding;
#endif
+ }
return label;
}
@@ -2374,20 +2386,28 @@ void Menu::Impl::populate(QMenu * qMenu, MenuDefinition
const & menu)
qMenu->addSeparator();
break;
case MenuItem::Submenu: {
- QMenu * subMenu = qMenu->addMenu(label(m, menu));
+ QMenu * subMenu = qMenu->addMenu(label(m, menu,
QString()));
populate(subMenu, m.submenu());
subMenu->setEnabled(!subMenu->isEmpty());
break;
}
case MenuItem::Command:
- default:
+ default: {
// FIXME: A previous comment assured that
MenuItem::Command was the
// only possible case in practice, but this is wrong.
It would be
// good to document which cases are actually treated
here.
- qMenu->addAction(new Action(m.func(), QIcon(), label(m,
menu),
- m.tooltip(), qMenu));
+ QString const binding = m.binding();
+ QString const lbl = label(m, menu, binding);
+ Action * act = new Action(m.func(), QIcon(), lbl,
m.tooltip(), qMenu);
+#if defined(Q_OS_MAC)
+ // used by greyOutBindings() to locate the binding in
the title
+ if (!binding.isEmpty())
+ act->setProperty("lyx_binding",
lbl.section('\t', 1));
+#endif
+ qMenu->addAction(act);
break;
}
+ }
}
#if (defined(Q_OS_WIN) || defined(Q_CYGWIN_WIN))
@@ -2435,6 +2455,9 @@ Menu::~Menu()
void Menu::updateView()
{
guiApp->menus().updateMenu(this);
+#if defined(Q_OS_MAC)
+ greyOutBindings(this);
+#endif
}
@@ -2934,7 +2957,7 @@ void Menus::fillMenuBar(QMenuBar * qmb, GuiView * view,
bool initial)
}
Menu * menuptr = new Menu(view, m.submenuname(), true);
- menuptr->setTitle(label(m, menu));
+ menuptr->setTitle(label(m, menu, QString()));
#if defined(Q_OS_MAC)
// On Mac OS with Qt/Cocoa, the menu is not displayed if there
is no action
--
lyx-cvs mailing list
[email protected]
https://lists.lyx.org/mailman/listinfo/lyx-cvs