OSX: add several menuitem roles to support menu shortcuts in dialogs
Now menu items and key shortcuts for Cut, Copy, Paste and Select All work in the standard ways in dialogs such as the file dialog, provided that the corresponding QActions have been created and added to the menu. This depends on new roles to identify each menu item which is so broadly applicable that it should work even when a native widget has focus; but the role will be auto-detected, just as we were already doing for application menu items such as Quit, About and Preferences. When the QFileDialog is opened, it will call redirectKnownMenuItemsToFirstResponder() which will make only those "special" menu items have the standard actions and nil targets. When the dialog is dismissed, those NSMenuItems must be reverted by calling resetKnownMenuItemsToQt(), because to invoke a QAction, the NSMenuItem's action should be itemFired and the target should be the QCocoaMenuDelegate. Task-number: QTBUG-17291 Change-Id: I501375ca6fa13fac75d4b4fdcede993ec2329cc7 Reviewed-by: Morten Johan Sørvig <morten.sorvig@digia.com>
This commit is contained in:
parent
a7f98a7ac0
commit
b5eb850e0d
@ -66,7 +66,11 @@ Q_OBJECT
|
||||
public:
|
||||
// copied from, and must stay in sync with, QAction menu roles.
|
||||
enum MenuRole { NoRole = 0, TextHeuristicRole, ApplicationSpecificRole, AboutQtRole,
|
||||
AboutRole, PreferencesRole, QuitRole };
|
||||
AboutRole, PreferencesRole, QuitRole,
|
||||
// However these roles are private, perhaps temporarily.
|
||||
// They could be added as public QAction roles if necessary.
|
||||
CutRole, CopyRole, PasteRole, SelectAllRole,
|
||||
RoleCount };
|
||||
|
||||
virtual void setTag(quintptr tag) = 0;
|
||||
virtual quintptr tag()const = 0;
|
||||
|
@ -90,6 +90,14 @@ QPlatformMenuItem::MenuRole detectMenuRole(const QString &caption)
|
||||
|| caption.startsWith(QCoreApplication::translate("QCocoaMenuItem", "Exit"), Qt::CaseInsensitive)) {
|
||||
return QPlatformMenuItem::QuitRole;
|
||||
}
|
||||
if (!caption.compare(QCoreApplication::translate("QCocoaMenuItem", "Cut"), Qt::CaseInsensitive))
|
||||
return QPlatformMenuItem::CutRole;
|
||||
if (!caption.compare(QCoreApplication::translate("QCocoaMenuItem", "Copy"), Qt::CaseInsensitive))
|
||||
return QPlatformMenuItem::CopyRole;
|
||||
if (!caption.compare(QCoreApplication::translate("QCocoaMenuItem", "Paste"), Qt::CaseInsensitive))
|
||||
return QPlatformMenuItem::PasteRole;
|
||||
if (!caption.compare(QCoreApplication::translate("QCocoaMenuItem", "Select All"), Qt::CaseInsensitive))
|
||||
return QPlatformMenuItem::SelectAllRole;
|
||||
return QPlatformMenuItem::NoRole;
|
||||
}
|
||||
|
||||
|
@ -52,6 +52,7 @@
|
||||
#include <private/qguiapplication_p.h>
|
||||
#include "qt_mac_p.h"
|
||||
#include "qcocoahelpers.h"
|
||||
#include "qcocoamenubar.h"
|
||||
#include <qregexp.h>
|
||||
#include <qbuffer.h>
|
||||
#include <qdebug.h>
|
||||
@ -225,6 +226,7 @@ static QString strippedText(QString s)
|
||||
|| [self panel:nil shouldShowFilename:filepath];
|
||||
|
||||
[self updateProperties];
|
||||
QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder();
|
||||
[mOpenPanel setAllowedFileTypes:nil];
|
||||
[mSavePanel setNameFieldStringValue:selectable ? QT_PREPEND_NAMESPACE(QCFString::toNSString)(info.fileName()) : @""];
|
||||
|
||||
@ -250,7 +252,9 @@ static QString strippedText(QString s)
|
||||
// cleanup of modal sessions. Do this before showing the native dialog, otherwise it will
|
||||
// close down during the cleanup.
|
||||
qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
|
||||
QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder();
|
||||
mReturnCode = [mSavePanel runModal];
|
||||
QCocoaMenuBar::resetKnownMenuItemsToQt();
|
||||
|
||||
QAbstractEventDispatcher::instance()->interrupt();
|
||||
return (mReturnCode == NSOKButton);
|
||||
@ -269,6 +273,7 @@ static QString strippedText(QString s)
|
||||
|| [self panel:nil shouldShowFilename:filepath];
|
||||
|
||||
[self updateProperties];
|
||||
QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder();
|
||||
[mSavePanel setDirectoryURL: [NSURL fileURLWithPath:mCurrentDir]];
|
||||
|
||||
[mSavePanel setNameFieldStringValue:selectable ? QCFString::toNSString(info.fileName()) : @""];
|
||||
@ -583,6 +588,7 @@ void QCocoaFileDialogHelper::QNSOpenSavePanelDelegate_selectionChanged(const QSt
|
||||
|
||||
void QCocoaFileDialogHelper::QNSOpenSavePanelDelegate_panelClosed(bool accepted)
|
||||
{
|
||||
QCocoaMenuBar::resetKnownMenuItemsToQt();
|
||||
if (accepted) {
|
||||
emit accept();
|
||||
} else {
|
||||
|
@ -67,9 +67,13 @@ public:
|
||||
inline NSMenu *nsMenu() const
|
||||
{ return m_nativeMenu; }
|
||||
|
||||
static void redirectKnownMenuItemsToFirstResponder();
|
||||
static void resetKnownMenuItemsToQt();
|
||||
static void updateMenuBarImmediately();
|
||||
|
||||
QList<QCocoaMenuItem*> merged() const;
|
||||
NSMenuItem *itemForRole(QPlatformMenuItem::MenuRole r);
|
||||
|
||||
private:
|
||||
static QCocoaWindow *findWindowForMenubar();
|
||||
static QCocoaMenuBar *findGlobalMenubar();
|
||||
|
@ -205,6 +205,59 @@ QCocoaMenuBar *QCocoaMenuBar::findGlobalMenubar()
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder()
|
||||
{
|
||||
// QTBUG-17291: http://forums.macrumors.com/showthread.php?t=1249452
|
||||
// When a dialog is opened, shortcuts for actions inside the dialog (cut, paste, ...)
|
||||
// continue to go through the same menu items which claimed those shortcuts.
|
||||
// They are not keystrokes which we can intercept in any other way; the OS intercepts them.
|
||||
// The menu items had to be created by the application. That's why we need roles
|
||||
// to identify those "special" menu items which can be useful even when non-Qt
|
||||
// native widgets are in focus. When the native widget is focused it will be the
|
||||
// first responder, so the menu item needs to have its target be the first responder;
|
||||
// this is done by setting it to nil.
|
||||
|
||||
// This function will find all menu items on all menus which have
|
||||
// "special" roles, set the target and also set the standard actions which
|
||||
// apply to those roles. But afterwards it is necessary to call
|
||||
// resetKnownMenuItemsToQt() to put back the target and action so that
|
||||
// those menu items will go back to invoking their associated QActions.
|
||||
foreach (QCocoaMenuBar *mb, static_menubars)
|
||||
foreach (QCocoaMenu *m, mb->m_menus)
|
||||
foreach (QCocoaMenuItem *i, m->items()) {
|
||||
bool known = true;
|
||||
switch (i->effectiveRole()) {
|
||||
case QPlatformMenuItem::CutRole:
|
||||
[i->nsItem() setAction:@selector(cut:)];
|
||||
break;
|
||||
case QPlatformMenuItem::CopyRole:
|
||||
[i->nsItem() setAction:@selector(copy:)];
|
||||
break;
|
||||
case QPlatformMenuItem::PasteRole:
|
||||
[i->nsItem() setAction:@selector(paste:)];
|
||||
break;
|
||||
case QPlatformMenuItem::SelectAllRole:
|
||||
[i->nsItem() setAction:@selector(selectAll:)];
|
||||
break;
|
||||
// We may discover later that there are other roles/actions which
|
||||
// are meaningful to standard native widgets; they can be added.
|
||||
default:
|
||||
known = false;
|
||||
break;
|
||||
}
|
||||
if (known)
|
||||
[i->nsItem() setTarget:nil];
|
||||
}
|
||||
}
|
||||
|
||||
void QCocoaMenuBar::resetKnownMenuItemsToQt()
|
||||
{
|
||||
// Undo the effect of redirectKnownMenuItemsToFirstResponder():
|
||||
// set the menu items' actions to itemFired and their targets to
|
||||
// the QCocoaMenuDelegate.
|
||||
updateMenuBarImmediately();
|
||||
}
|
||||
|
||||
void QCocoaMenuBar::updateMenuBarImmediately()
|
||||
{
|
||||
QCocoaAutoReleasePool pool;
|
||||
@ -321,3 +374,13 @@ QPlatformMenu *QCocoaMenuBar::menuForTag(quintptr tag) const
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
NSMenuItem *QCocoaMenuBar::itemForRole(QPlatformMenuItem::MenuRole r)
|
||||
{
|
||||
foreach (QCocoaMenu *m, m_menus)
|
||||
foreach (QCocoaMenuItem *i, m->items())
|
||||
if (i->effectiveRole() == r)
|
||||
return i->nsItem();
|
||||
return Q_NULLPTR;
|
||||
}
|
||||
|
||||
|
@ -98,6 +98,8 @@ public:
|
||||
inline bool isSeparator() const { return m_isSeparator; }
|
||||
|
||||
QCocoaMenu *menu() const { return m_menu; }
|
||||
MenuRole effectiveRole() const;
|
||||
|
||||
private:
|
||||
QString mergeText();
|
||||
QKeySequence mergeAccel();
|
||||
@ -112,6 +114,7 @@ private:
|
||||
bool m_isSeparator;
|
||||
QFont m_font;
|
||||
MenuRole m_role;
|
||||
MenuRole m_detectedRole;
|
||||
QKeySequence m_shortcut;
|
||||
bool m_checked;
|
||||
bool m_merged;
|
||||
|
@ -227,7 +227,8 @@ NSMenuItem *QCocoaMenuItem::sync()
|
||||
if (depth == 3 || !menubar)
|
||||
break; // Menu item too deep in the hierarchy, or not connected to any menubar
|
||||
|
||||
switch (detectMenuRole(m_text)) {
|
||||
m_detectedRole = detectMenuRole(m_text);
|
||||
switch (m_detectedRole) {
|
||||
case QPlatformMenuItem::AboutRole:
|
||||
if (m_text.indexOf(QRegExp(QString::fromLatin1("qt$"), Qt::CaseInsensitive)) == -1)
|
||||
mergeItem = [loader aboutMenuItem];
|
||||
@ -241,6 +242,8 @@ NSMenuItem *QCocoaMenuItem::sync()
|
||||
mergeItem = [loader quitMenuItem];
|
||||
break;
|
||||
default:
|
||||
if (m_detectedRole >= CutRole && m_detectedRole < RoleCount && menubar)
|
||||
mergeItem = menubar->itemForRole(m_detectedRole);
|
||||
if (!m_text.isEmpty())
|
||||
m_textSynced = true;
|
||||
break;
|
||||
@ -249,7 +252,7 @@ NSMenuItem *QCocoaMenuItem::sync()
|
||||
}
|
||||
|
||||
default:
|
||||
qWarning() << Q_FUNC_INFO << "unsupported role" << (int) m_role;
|
||||
qWarning() << Q_FUNC_INFO << "menu item" << m_text << "has unsupported role" << (int)m_role;
|
||||
}
|
||||
|
||||
if (mergeItem) {
|
||||
@ -374,3 +377,11 @@ void QCocoaMenuItem::syncModalState(bool modal)
|
||||
else
|
||||
[m_native setEnabled:m_enabled];
|
||||
}
|
||||
|
||||
QPlatformMenuItem::MenuRole QCocoaMenuItem::effectiveRole() const
|
||||
{
|
||||
if (m_role > TextHeuristicRole)
|
||||
return m_role;
|
||||
else
|
||||
return m_detectedRole;
|
||||
}
|
||||
|
@ -70,6 +70,15 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent)
|
||||
QAction *quitAction = fileMenu->addAction(tr("Quit"));
|
||||
quitAction->setShortcut(QKeySequence(QKeySequence::Quit));
|
||||
connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit()));
|
||||
QMenu *editMenu = menuBar()->addMenu(tr("&Edit"));
|
||||
QAction *action = editMenu->addAction(tr("Cut"));
|
||||
action->setShortcut(QKeySequence(QKeySequence::Quit));
|
||||
action = editMenu->addAction(tr("Copy"));
|
||||
action->setShortcut(QKeySequence(QKeySequence::Copy));
|
||||
action = editMenu->addAction(tr("Paste"));
|
||||
action->setShortcut(QKeySequence(QKeySequence::Paste));
|
||||
action = editMenu->addAction(tr("Select All"));
|
||||
action->setShortcut(QKeySequence(QKeySequence::SelectAll));
|
||||
QTabWidget *tabWidget = new QTabWidget;
|
||||
tabWidget->addTab(new FileDialogPanel, tr("QFileDialog"));
|
||||
tabWidget->addTab(new ColorDialogPanel, tr("QColorDialog"));
|
||||
|
Loading…
Reference in New Issue
Block a user