-
Notifications
You must be signed in to change notification settings - Fork 74
[202_106]: fix Cmd+V paste not working in macOS save file dialog #2901
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| # [202_106] Cmd+V paste not working in macOS save file dialog | ||
|
|
||
| ### How to test | ||
| 1. Open Mogan on macOS | ||
| 2. Copy some text to the clipboard | ||
| 3. Open **Save As** dialog (File → Save as) | ||
| 4. In the filename text field, press **Cmd+V** to paste | ||
| 5. Verify the clipboard text is pasted into the filename field | ||
| 6. Also verify **Cmd+C**, **Cmd+X**, **Cmd+A** work in the dialog | ||
| 7. After closing the dialog, verify editor shortcuts still work normally | ||
|
|
||
| ## 2026/03/01 | ||
| ### What | ||
| Temporarily disable QAction keyboard shortcuts while a native file dialog is open on macOS, then restore them after the dialog closes. | ||
|
|
||
| ### Why | ||
| On macOS, QAction shortcuts registered in the menu bar are converted to NSMenuItem key equivalents. When a native file dialog (NSSavePanel/NSOpenPanel) is shown via `QFileDialog::exec()`, pressing Cmd+V triggers the QAction for "Paste" in Mogan's menu bar instead of the standard Cocoa `paste:` action in the dialog's text field. This prevents users from pasting filenames in the Save As dialog. | ||
|
|
||
| The `ShortcutOverride` mechanism in `QTMWidget::event()` does not help here because the QTMWidget does not have focus while a modal dialog is open. | ||
|
|
||
| Fixes: https://github.com/XmacsLabs/mogan/issues/2894 | ||
|
|
||
| ### How | ||
| In `qt_chooser_widget_rep::perform_dialog()` in `src/Plugins/Qt/qt_chooser_widget.cpp`: | ||
|
|
||
| - Before `dialog->exec()`: on macOS (`Q_OS_MACOS`), iterate all `QAction`s in the active window, save their shortcuts, then clear them via `action->setShortcut(QKeySequence())` | ||
| - After `dialog->exec()` returns: restore all saved shortcuts | ||
|
|
||
| This removes the NSMenuItem key equivalents while the dialog is open, allowing the native dialog to process standard editing shortcuts (Cmd+V/C/X/A) through the Cocoa responder chain. |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -24,9 +24,11 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include "mupdf_picture.hpp" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #endif | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <QApplication> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <QByteArray> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <QDebug> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <QFileDialog> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <QKeySequence> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <QString> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #include <QStringList> | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -344,6 +346,24 @@ qt_chooser_widget_rep::perform_dialog () { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| r.moveCenter (pos); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| dialog->setGeometry (r); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| #ifdef Q_OS_MACOS | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // On macOS, QAction shortcuts registered in the menu bar become NSMenuItem | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // key equivalents. When a native file dialog is open, these key equivalents | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // intercept standard editing shortcuts (Cmd+V, Cmd+C, Cmd+X, Cmd+A) before | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // the dialog's text field can handle them. Temporarily clearing all QAction | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // shortcuts allows the native dialog to process these keys normally. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| QList<QPair<QAction*, QKeySequence>> savedShortcuts; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| QWidget* mainWin= QApplication::activeWindow (); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (mainWin) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for (QAction* action : mainWin->findChildren<QAction*> ()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!action->shortcut ().isEmpty ()) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| savedShortcuts.append (qMakePair (action, action->shortcut ())); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+352
to
+360
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // intercept standard editing shortcuts (Cmd+V, Cmd+C, Cmd+X, Cmd+A) before | |
| // the dialog's text field can handle them. Temporarily clearing all QAction | |
| // shortcuts allows the native dialog to process these keys normally. | |
| QList<QPair<QAction*, QKeySequence>> savedShortcuts; | |
| QWidget* mainWin= QApplication::activeWindow (); | |
| if (mainWin) { | |
| for (QAction* action : mainWin->findChildren<QAction*> ()) { | |
| if (!action->shortcut ().isEmpty ()) { | |
| savedShortcuts.append (qMakePair (action, action->shortcut ())); | |
| // intercept standard editing shortcuts (Cmd+V, Cmd+C, Cmd+X, Cmd+A, etc.) | |
| // before the dialog's text field can handle them. Temporarily clearing | |
| // shortcuts only for these standard editing sequences allows the native | |
| // dialog to process them normally while preserving other application | |
| // shortcuts. | |
| QList<QPair<QAction*, QKeySequence>> savedShortcuts; | |
| QWidget* mainWin= QApplication::activeWindow (); | |
| if (mainWin) { | |
| const QList<QKeySequence> conflictingSequences = { | |
| QKeySequence::Copy, | |
| QKeySequence::Paste, | |
| QKeySequence::Cut, | |
| QKeySequence::SelectAll, | |
| QKeySequence::Undo, | |
| QKeySequence::Redo | |
| }; | |
| for (QAction* action : mainWin->findChildren<QAction*> ()) { | |
| const QKeySequence shortcut = action->shortcut (); | |
| if (!shortcut.isEmpty () && conflictingSequences.contains (shortcut)) { | |
| savedShortcuts.append (qMakePair (action, shortcut)); |
Copilot
AI
Mar 1, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The restore loop assumes every QAction* in savedShortcuts is still alive. While a native modal dialog is open on macOS, it’s possible for actions/windows to be destroyed (e.g., via menu interactions), which would leave dangling pointers here and can crash. Consider storing QPointer<QAction> (or tracking destroyed) and skipping entries whose action has been deleted before restoring.
| pair.first->setShortcut (pair.second); | |
| QAction* action = pair.first; | |
| if (!action) | |
| continue; | |
| action->setShortcut (pair.second); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function already uses
OS_MACOSearlier, but the new shortcut-disabling logic is guarded withQ_OS_MACOS. Mixing platform macros in the same file makes it harder to reason about which builds include the code and can lead to accidental omissions in some configurations. Consider using the same macOS guard consistently (whichever is standard for this module).