diff --git a/src/qcocoa-qpa/README.ALT b/src/qcocoa-qpa/README.ALT index 501032e..8cdaea7 100644 --- a/src/qcocoa-qpa/README.ALT +++ b/src/qcocoa-qpa/README.ALT @@ -1,423 +1,434 @@ +20180207 : upstream head (5.9): fef6b31b994befac0ee14391572ebc2b9b33e104 (v5.9.4-86-gfef6b31b99) + src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm | 12 ++- + src/plugins/platforms/cocoa/qcocoamenu.mm | 6 +- +patch: patches/32-sync-upstream-180207.diff +Changes: git log 5a05348fb6c3940449a9c2950bb65bdea2112a15..fef6b31b994befac0ee14391572ebc2b9b33e104 src/plugins/platforms/cocoa +commit 1ffc6ba402114a3443df5309dec186fd337e5b66 + macOS: fix menu positioning on high-DPI +commit 9b2913377807b3dae107befeec0bc488f4a2cbad + Fix QFileDialog::defaultSuffix on macOS + + 20180128 : upstream head (5.9): 5a05348fb6c3940449a9c2950bb65bdea2112a15 (v5.9.4-70-g5a05348fb6) src/plugins/platforms/cocoa/qcocoaapplication.h | 12 - src/plugins/platforms/cocoa/qcocoaapplication.mm | 30 +- src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm | 18 + src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm | 2 +- src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm | 2 +- src/plugins/platforms/cocoa/qcocoahelpers.h | 7 +- src/plugins/platforms/cocoa/qcocoahelpers.mm | 2 +- src/plugins/platforms/cocoa/qcocoanativeinterface.mm | 4 +- src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm | 5 - src/plugins/platforms/cocoa/qcocoawindow.h | 3 + src/plugins/platforms/cocoa/qcocoawindow.mm | 5 + patch: patches/31-sync-upstream-180128.diff Changes: git log 1df2906c75f379a62bce487d3d2db3ade3095b16..5a05348fb6c3940449a9c2950bb65bdea2112a15 src/plugins/platforms/cocoa commit 089969540f4877c778172ee0ccbd69960a179b43 Remove traces of Growl in QSystemTrayIcon code and documentation commit 965bcca6d4e22788721a9af906adfbd97af9af29 Cocoa: Explicitly hide popup windows when the application is hidden commit 0c5953fd4edbb8e6495aaf288186dbd6737fb8c0 macOS: Namespace FullScreenProperty category on NSWindow commit 841542225bf5d3f1c4b3fd4c24adf4201f3a131f macOS: Simplify mangling of QNSPanelDelegate protocol commit 3f6bc9a9838e1bf27c10a01cfb2237afb60afe70 macOS: Namespace QNSWindowProtocol when building with -qtnamespace commit 36ddfb6cc1bb4ece153e19d86e0175cbb7052310 macOS: Replace category methods with functions 20180110 : upstream head (5.9): 1df2906c75f379a62bce487d3d2db3ade3095b16 src/plugins/platforms/cocoa/qnsview.mm | 7 +- patch: 30-sync-upstream-180110.diff Changes: git log b0938cb6c1fa29ec2d2a4fb9273e515cd0d6c08e..1df2906c75f379a62bce487d3d2db3ade3095b16 src/plugins/platforms/cocoa commit 4f76c2dbadb09a27cecaba5a36512f68fac3d319 Cocoa: make "Send correct mouse buttons for tablets" optional 20171231 Improved control over selecting the FreeType fontengine and equivalent options to select the use of a FontConfig fontdatabase with FreeType fontengine. 20171219 : upstream head (5.9): b0938cb6c1fa29ec2d2a4fb9273e515cd0d6c08e src/plugins/platforms/cocoa/qcocoamenuloader.mm | 8 +- patch: patches/26-sync-upstream-171219.diff Changes: git log 6a9d076e87f0c8aa4fb49bbcc2f56eefd85af2e3..b0938cb6c1fa29ec2d2a4fb9273e515cd0d6c08e src/plugins/platforms/cocoa commit 29104c85db53e7c0c0aaf3fe78f84b737fce4886 Cocoa: Disable “Hide” menu item on open popups 20171126 : upstream head (5.9): 6a9d076e87f0c8aa4fb49bbcc2f56eefd85af2e3 / v5.9.3-43-g6a9d076 src/plugins/platforms/cocoa/qcocoamenu.h | 2 + src/plugins/platforms/cocoa/qcocoamenu.mm | 16 ++- src/plugins/platforms/cocoa/qcocoamenubar.h | 2 + src/plugins/platforms/cocoa/qcocoamenubar.mm | 11 +- src/plugins/platforms/cocoa/qcocoamenuitem.mm | 4 - src/plugins/platforms/cocoa/qcocoawindow.mm | 15 +++ src/plugins/platforms/cocoa/qnsview.mm | 1 + patch: patches/25-sync-upstream-171126.diff Changes: git log 01c7b474f5ad2c9fcf4b90c71048624070811618..6a9d076e87f0c8aa4fb49bbcc2f56eefd85af2e3 src/plugins/platforms/cocoa commit d0736e9d17d0b3ec2f7aa3f323a1edf70aa83e27 Cocoa: Make High DPI drag cursor work commit 3f519ffa150ce5a2d9e3ad3f147745b312d29afb QCocoaMenuItem: Don't clear NSMenuItem.action when setting submenu commit 5194817941985c766bbc7f80039a58e0cf504b55 Cocoa: optimize backingstore flush on 10-bit displays commit 385589ef458715fcaa533bbd01ca421dc1040eba QCocoaMenu: Attach menu items when updating the menubar 20171030 : upstream head (5.9): 01c7b474f5ad2c9fcf4b90c71048624070811618 src/plugins/platforms/cocoa/cocoa.pro | 11 +- src/plugins/platforms/cocoa/messages.cpp | 2 +- src/plugins/platforms/cocoa/messages.h | 11 + src/plugins/platforms/cocoa/qcocoamenuitem.mm | 10 +- src/plugins/platforms/cocoa/qcocoamenuloader.mm | 20 +- src/plugins/platforms/cocoa/qcocoasystemtrayicon.mm | 31 --- src/plugins/platforms/cocoa/qcocoawindow.mm | 2 + patches: patches/24-sync-upstream-171030.diff patches/24b-patch-build-on-OSX109.diff Changes: git log e03b64c5b1eeebfbbb94d67eb9a9c1d35eaba0bb..01c7b474f5ad2c9fcf4b90c71048624070811618 commit 37a1478787d64b34a0716421c8a47f3246e41bfd Cocoa QPA: Code clean up, make some bits more readable commit c99d8532c892f72f897c9e686be75d1ebba67618 QCocoaSystemTrayIcon: Remove unused classes Both QNSMenu and QSystemTrayIconQMenu aren't referenced anywhere else, including within qcocoasystemtrayicon.mm, since the QPA backend was added. commit f13e75345d035ec906846aaa3540454787edbd3f Cocoa QPA: Remove usage of OBJECTIVE_SOURCES commit 8ac9addd946637401e4685c6e91d1a3cd5b2d768 QCocoaWindow: Toggle titlebar transparency to support unified toolbar This is need from macOS 10.13 onwards. See NSWindow related notes on https://developer.apple.com/library/content/releasenotes/AppKit/RN-AppKit/ NB: that NSWindow change required runtime checking for 10.9 support. 20171011 : upstream head (5.9): e03b64c5b1eeebfbbb94d67eb9a9c1d35eaba0bb src/plugins/platforms/cocoa/qnsview.mm | 4 + patch: patches/23-sync-upstream-171011.diff Changes: git log e0c2d328b11ab893538393fad66ad61d22d823c6..e03b64c5b1eeebfbbb94d67eb9a9c1d35eaba0bb src/plugins/platforms/cocoa/qnsview.mm | cat commit cbbf843e96de3067e4cb7c0a7b4e59a6c27b10f7 macOS: Bail out early when handling shortcut event results in closing window NB: this protection has been added to the contextmenu handling block just above, too. 20170920 : upstream head (5.9): e0c2d328b11ab893538393fad66ad61d22d823c6 src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm | 12 +- src/plugins/platforms/cocoa/qcocoainputcontext.mm | 17 +- src/plugins/platforms/cocoa/qcocoamenubar.mm | 10 +- src/plugins/platforms/cocoa/qnsview.h | 2 + src/plugins/platforms/cocoa/qnsview.mm | 41 ++- patch: patches/22-sync-upstream-170920.diff Changes: git log e81f430e30635f975dd4635ffb64d66fc1bce355..HEAD src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm src/plugins/platforms/cocoa/qcocoainputcontext.mm src/plugins/platforms/cocoa/qcocoamenubar.mm src/plugins/platforms/cocoa/qnsview.h src/plugins/platforms/cocoa/qnsview.mm commit 689606de91faecf91f1f92e8d355789d9be62d2f Cocoa: Update the known menu items when the QCocoaMenuBar is deleted Task-number: QTBUG-62193 commit 202d3ba3e6c9982608f41f5e7d836825c8664c93 Cocoa: Check if charactersIgnoringModifiers is not empty too Task-number: QTBUG-57933 commit 0adc14d8dbdd9e28ccb72c49d865009dd8df1b1c macOS: Don‚Äôt color convert the backing store Task-number: QTBUG-61384 commit 52bda430af2749da1a0467b71d9cca5208f22402 macOS: Reset composition when focus object changes inside window Task-number: QTBUG-59222 commit 0270651dda8e247164a8dccd71fb65712c7d1480 Cocoa integration: do not use released session Task-number: QTBUG-62589 20170829 : upstream head (5.9): e81f430e30635f975dd4635ffb64d66fc1bce355 touched: src/plugins/platforms/cocoa/qcocoamenu.h | 4 + src/plugins/platforms/cocoa/qcocoamenu.mm | 26 +- src/plugins/platforms/cocoa/qcocoamenubar.mm | 2 +- src/plugins/platforms/cocoa/qcocoaprintersupport.h | 2 +- src/plugins/platforms/cocoa/qcocoaprintersupport.mm | 4 +- src/plugins/platforms/cocoa/qcocoawindow.mm | 14 +- src/plugins/platforms/cocoa/qnsview.h | 2 + src/plugins/platforms/cocoa/qnsview.mm | 4 + src/plugins/platforms/cocoa/qprintengine_mac.mm | 8 +- src/plugins/platforms/cocoa/qprintengine_mac_p.h | 2 +- patch: patches/21-sync-upstream-170828.diff Changes: git log 0a5f71c6062d575602ff041fb1b88ec2d8ad92bc..HEAD src/plugins/platforms/cocoa commit 48784486a36f60dea882baabc6923f4b59d2bfe6 QCocoaMenu: Stop update timer commit dbaa4de28e5cdfa1787af77d236586833786ee61 Cocoa: Fix compile when using QT_NO_TABLETEVENT commit 8ebfe00f4ab79516a8276929a682c24f4c675b5f Initialize the print engine with the given printer name QAltCocoa: only on Qt >= 5.9.0 Task-number: QTBUG-62221 commit 306071e50eac8290d234caab90985ddf705a5fc6 QCocoaMenu: Sync menubar menu when adding items Task-number: QTBUG-62260 commit f27d1ccbb24ec2fd4098f2976503478831006cc8 QCocoaMenu: De-pessimize the number of calls to validateMenuItem: Task-number: QTBUG-62396 >>> change enabled for Qt >= 5.9.2 commit c35fc435950437d3d046b17d06593873d7b82011 macOS: Make alpha-based click-trough work again Restore 5.6 behavior by not modifying ignoresMouseEvent if we can. Toggling WindowTransparentForInput on and off again is still broken. Task-number: QTBUG-54830 20180801: upstream head (5.9): 0a5f71c6062d575602ff041fb1b88ec2d8ad92bc touched: src/plugins/platforms/cocoa/cocoa.pro | 7 +- src/plugins/platforms/cocoa/qcocoafontdialoghelper.h | 3 + src/plugins/platforms/cocoa/qcocoafontdialoghelper.mm | 4 - src/plugins/platforms/cocoa/qcocoahelpers.h | 7 +- src/plugins/platforms/cocoa/qcocoahelpers.mm | 5 ++ src/plugins/platforms/cocoa/qcocoatheme.mm | 13 +-- src/plugins/platforms/cocoa/qnswindowdelegate.mm | 7 +- > patch: patches/20-sync-upstream-170801.diff Changes: Fix 32-bit build on macOS macOS: Don't assume the proposed fullscreen size matches the screen size Sometimes AppKit will pass in a proposed size that's smaller than the geometry of the screen. We don't know why, but shouldn't assert. Add missing #include for -no-widgets macOS: Fix unused variable in window:willUseFullScreenContentSize: Convert features.fontdialog to QT_[REQUIRE_]CONFIG Fix macOS build for -no-widgets, take 2 20170707: upstream head (5.9): 9d3cd2268ce3beafcf6fa886bb70d8463260d602 touched: src/plugins/platforms/cocoa/qcocoatheme.mm src/plugins/platforms/cocoa/qnswindowdelegate.mm > patch: patches/19.2-sync-upstream-170707.diff Changes: from git log 03b4838cb51513bd5d2edf76dccc4bc4a1181681..HEAD src/plugins/platforms/cocoa/{qcocoatheme.mm,qnswindowdelegate.mm} commit 9d3cd2268ce3beafcf6fa886bb70d8463260d602 Author: Tor Arne Vestb#c3#b8 Date: Thu Jul 6 17:03:41 2017 +0200 macOS: Account for fullscreen geometry bug in AppKit on OS X 10.10 commit 6a1046e17691c6e35c7384590ba241edb4082707 Author: Stephan Binner qq Date: Tue Jul 4 19:08:55 2017 +0200 Fix macOS build for -no-widgets 20170705: upstream head (5.9 branch): 03b4838cb51513bd5d2edf76dccc4bc4a1181681 touched: src/plugins/platforms/cocoa/cocoa.pro | 16 +++-- src/plugins/platforms/cocoa/qcocoacolordialoghelper.h | 4 ++ src/plugins/platforms/cocoa/qcocoacolordialoghelper.mm | 4 -- src/plugins/platforms/cocoa/qcocoafiledialoghelper.h | 3 + src/plugins/platforms/cocoa/qcocoafiledialoghelper.mm | 4 -- src/plugins/platforms/cocoa/qcocoaintegration.mm | 8 ++- src/plugins/platforms/cocoa/qcocoamenubar.mm | 18 ++++- src/plugins/platforms/cocoa/qcocoatheme.mm | 14 ++-- src/plugins/platforms/cocoa/qnsview.mm | 33 +++++---- > patch: patches/19-sync-upstream-170705.diff Main changes from `cd src/plugins/platforms/cocoa && git log b2cb83ecbb1eec29268852d1b230f37e4c8592e9..HEAD cocoa.pro qcocoacolordialoghelper.h qcocoacolordialoghelper.mm qcocoafiledialoghelper.h qcocoafiledialoghelper.mm qcocoaintegration.mm qcocoamenubar.mm qcocoatheme.mm qnsview.mm ` commit 3e8ebea95b634c7ded9ee0b884768155e9e7f686 Author: Andy Shaw Date: Fri Jun 23 09:08:19 2017 +0200 Cocoa: Reset the target + action for a menuitem after showing a dialog To make it more reliable and efficient we now do the reverse of what we are doing when redirecting the items. This will ensure that the actions are correctly reset to the original target and action. The original approach of updateMenubarImmediately() was not always doing this and it also does other unnecessary things as a result when all we need is to just reset the things we changed. commit 198b67d14bf3ca76d6bdf8901348da0838cf3b8d Author: Tor Arne Vestb#c3#b8 Date: Mon Jun 12 17:58:41 2017 +0200 macOS: Send text input and key events to focus object of window, not app The key events and input method callbacks coming from Cocoa are targeted at our specific NSView, so we should deliver them to the focus object of the corresponding QWindow, not the global application focus object. This means that we'll deliver key events to windows also when they are not key (active), but this is intentional, as we would otherwise fail to deliver input method events coming from e.g. the emoji/symbol picker, which steals the key window when active. Task-number: QTBUG-61359 commit 3851a8ff20c6aed0807bfdc4588ae108a2b108ec Author: Tor Arne Vestb#c3#b8 Date: Fri Jun 23 13:32:25 2017 +0200 macOS: Work around buildup of NSDisplayCycle objects during rapid painting Task-number: QTBUG-60012 commit e6ccc8e56fdaf402602130176064ffb1a0ce29bf Author: R.J.V. Bertin Date: Wed Jun 21 18:16:04 2017 +0200 sync with upstream Cocoa QPA changes in the 5.9 branch (qtbase v5.9.0-208-gb2cb83e) commit 12e9ff4bed9513fe8ed390a1f421d8f73c45960a Author: R.J.V. Bertin Date: Mon Jun 5 17:40:51 2017 +0200 sync with upstream Cocoa QPA changes in the 5.9 branch (qtbase v5.9.0-119-g934235e) commit 0ecceaf6d7871b fc42141909af210f6bb408ac61 Author: R.J.V. Bertin Date: Thu Apr 20 20:50:58 2017 +0200 15-patch-restore-legacy-fullscreen-mode.diff alternative fullscreen mode: simplified and restore window icons commit d4ce302e7c08dcf38a0f0f40b7e9183cab2fc2b1 Author: R.J.V. Bertin Date: Wed Apr 19 23:58:24 2017 +0200 AltCocoa QPA: add a legacy fullscreen mode When windows don't have the fullscreen titlebar button or the env. var. QT_LEGACY_FULLSCREEN is set, toggleFullScreen() adopts a legacy mode. Windows are then made fullscreen by converting them to frame-less and resizing them to the current screen size. This is faster (instantaneous) and preserves display content on other screens in multi-head set-ups. commit 85bb84969c6922667ff83e1483a7ef71628c046e Author: R.J.V. Bertin Date: Tue Apr 11 20:16:12 2017 +0200 qmacstyle : really build against Qt 5.8.0 commit 934afde2c352b401cfffed83cb0877462dc27787 Author: R.J.V. Bertin Date: Mon Apr 10 23:56:13 2017 +0200 modified (Alt) 5.9.0 commit c8dd86fd23c6787f949c2849ccb20eaf22e118ae Author: R.J.V. Bertin Date: Mon Apr 10 23:45:31 2017 +0200 13-themehint-add-aqua.diff commit cbf229875c49c2a57d7b62b641d866134e569a1a Author: R.J.V. Bertin Date: Thu Apr 6 21:57:00 2017 +0200 12-deactivate-menurole-heuristics.patch this patch deactivates the worst of the TextHeuristic menu role guessmatics that can lead to putting the wrong QAction under the Mac's About or Preferences menu items in the Application menu. Many KDE applications have more than 1 About and Configure menu actions and are thus especially at risk. KDE applications that use KStandardActions to create the standard menu items will see the appropriate actions assigned to the About and Preferences menu items because their roles are set explicitly. commit af7c4544d1883e9978609742b48dbb152e880c34 Author: R.J.V. Bertin Date: Thu Apr 6 21:50:46 2017 +0200 11-patch-keyboard-support-menukey.diff this patch introduces support for a Menu key. Macs don't normally have one but the SDKs do provide a special keysym (NSMenuFunctionKey) which might be used (generated) by 3rd party keyboard drivers or even a native event filter. The patch completes the pre-existing support and generates a ContextMenu event at an appropriate location when the keysym is received. commit c45430fc3c1827b44dd9002932b7b582da19729e Author: R.J.V. Bertin Date: Thu Apr 6 19:22:14 2017 +0200 10-patch-keyboard-mapping.diff From 3ee093b47109e2b24ab77ccdecde84437c5aced9 Mon Sep 17 00:00:00 2001 From: Timur Pocheptsov Date: Wed, 2 Nov 2016 14:19:26 +0100 Subject: [PATCH] WIP - Cocoa integration: fix incorrect keyboard mapping When switching between different input sources, we have to update layouts. Task-number: QTBUG-50865 Change-Id: I19ee45feabf014e61dfef7454b5468e596ce2786 Committed from host : Portia.local commit 65de5666d5574a08a2fe49b82f29fd0b8a6b3cbf Author: R.J.V. Bertin Date: Thu Apr 6 19:21:36 2017 +0200 09-patch-silence-setscreen-warning.diff Prevent an unnecessary warning commit 07527d713953a417b93393558cfb44475bc42d23 Author: R.J.V. Bertin Date: Thu Apr 6 19:20:56 2017 +0200 08-patch-freetype-gamma-cocoa.diff Adapt the font smoothing gamma when using the FreeType rendering engine. commit a7b0481d07c592c1a1810160e317d6792eeb7a5d Author: R.J.V. Bertin Date: Thu Apr 6 19:19:32 2017 +0200 06-patch-respect-DontSwapCtrlMeta.diff From ce953f011356257c1e1862d0353f785453559028 Mon Sep 17 00:00:00 2001 From: Dyami Caliri Date: Tue, 16 Sep 2014 17:32:22 -0700 Subject: [PATCH] OS X: Handle Qt::AA_MacDontSwapCtrlAndMeta for regular key events The flag Qt::AA_MacDontSwapCtrlAndMeta was only being used with shortcut detection. Now trying to incorporate flag in normal key events. Task-number: QTBUG-22775 Change-Id: I9edf97d0de93aa225435c9802372e33641cdac73 Committed from host : Portia.local commit 5ffc2fff1bd729ec9e54120e424fdfd51197f438 Author: R.J.V. Bertin Date: Thu Apr 6 19:18:46 2017 +0200 05-patch-improve-fontweight-support9.diff part of a more comprehensive patch improving support for less-common font weights. I'm not sure how much effect it has on its own but it should not have adverse effects when used without the companion modifications. commit 658f1d0e2fe5a9a678bdae712cc5a47878bc59de Author: R.J.V. Bertin Date: Thu Apr 6 19:16:29 2017 +0200 04-patch-qmenuAddSection.diff add support for named menu sections ("texted separators") in menus that are part of the native menubar. This requires the companion patch to the "native" Macintosh theme otherwise named sections won't appear when they are the 1st item in a menu. commit 42f58e14282bf4beae408f5440397dd5a6b30471 Author: R.J.V. Bertin Date: Thu Apr 6 19:14:00 2017 +0200 03-patch-better-menuitem-insert-warning.diff Provide a more helpful warning when adding an item to an additional menu or removing it from the wrong menu. commit f5bbcfc20e52dfcfa27eedb4a1240e2fccf5fb93 Author: R.J.V. Bertin Date: Thu Apr 6 19:12:01 2017 +0200 02-patch-enable-kde-theme.diff advertise the existence of support for the KDE platform theme plugin when KDE_SESSION_VERSION is set to at least 4 in the environment. commit f3707f1516f1393235c178d5829cf3f88d5844e1 Author: R.J.V. Bertin Date: Thu Apr 6 19:03:23 2017 +0200 01-patch-missing-autoreleasepools.diff Add autorelease pools to functions that (used to) need them. This is safe and comes at almost no cost. diff --git a/src/qcocoa-qpa/patches/32-sync-upstream-180207.diff b/src/qcocoa-qpa/patches/32-sync-upstream-180207.diff new file mode 100644 index 0000000..5674139 --- /dev/null +++ b/src/qcocoa-qpa/patches/32-sync-upstream-180207.diff @@ -0,0 +1,54 @@ +diff --git a/cocoa/qcocoafiledialoghelper.mm b/cocoa/qcocoafiledialoghelper.mm +index bb1a9c3..5a4788c 100644 +--- a/cocoa/qcocoafiledialoghelper.mm ++++ b/cocoa/qcocoafiledialoghelper.mm +@@ -422,6 +422,13 @@ static QString strippedText(QString s) + } else { + QList result; + QString filename = QString::fromNSString([[mSavePanel URL] path]).normalized(QString::NormalizationForm_C); ++ const QString defaultSuffix = mOptions->defaultSuffix(); ++ const QFileInfo fileInfo(filename); ++ // If neither the user or the NSSavePanel have provided a suffix, use ++ // the default suffix (if it exists). ++ if (fileInfo.suffix().isEmpty() && !defaultSuffix.isEmpty()) { ++ filename.append('.').append(defaultSuffix); ++ } + result << QUrl::fromLocalFile(filename.remove(QLatin1String("___qt_very_unlikely_prefix_"))); + return result; + } +@@ -449,10 +456,7 @@ static QString strippedText(QString s) + [mPopUpButton setHidden:chooseDirsOnly]; // TODO hide the whole sunken pane instead? + + if (mOptions->acceptMode() == QFileDialogOptions::AcceptSave) { +- QStringList ext = [self acceptableExtensionsForSave]; +- const QString defaultSuffix = mOptions->defaultSuffix(); +- if (!ext.isEmpty() && !defaultSuffix.isEmpty()) +- ext.prepend(defaultSuffix); ++ const QStringList ext = [self acceptableExtensionsForSave]; + [mSavePanel setAllowedFileTypes:ext.isEmpty() ? nil : qt_mac_QStringListToNSMutableArray(ext)]; + } else { + [mOpenPanel setAllowedFileTypes:nil]; // delegate panel:shouldEnableURL: does the file filtering for NSOpenPanel +diff --git a/cocoa/qcocoamenu.mm b/cocoa/qcocoamenu.mm +index 77eca5a..14ce632 100644 +--- a/cocoa/qcocoamenu.mm ++++ b/cocoa/qcocoamenu.mm +@@ -46,6 +46,7 @@ + #include + #include + #include "qcocoaapplication.h" ++#include "qcocoaintegration.h" + #include "qcocoamenuloader.h" + #include "qcocoamenubar.h" + #include "qcocoawindow.h" +@@ -596,8 +597,9 @@ void QCocoaMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect, + [popupCell setMenu:m_nativeMenu]; + [popupCell selectItem:nsItem]; + +- int availableHeight = screen->availableSize().height(); +- const QPoint &globalPos = parentWindow->mapToGlobal(pos); ++ QCocoaScreen *cocoaScreen = static_cast(screen->handle()); ++ int availableHeight = cocoaScreen->availableGeometry().height(); ++ const QPoint &globalPos = cocoaWindow->mapToGlobal(pos); + int menuHeight = m_nativeMenu.size.height; + if (globalPos.y() + menuHeight > availableHeight) { + // Maybe we need to fix the vertical popup position but we don't know the diff --git a/src/qcocoa-qpa/qcocoafiledialoghelper.mm b/src/qcocoa-qpa/qcocoafiledialoghelper.mm index bb1a9c3..5a4788c 100644 --- a/src/qcocoa-qpa/qcocoafiledialoghelper.mm +++ b/src/qcocoa-qpa/qcocoafiledialoghelper.mm @@ -1,779 +1,783 @@ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtGui module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include "qcocoafiledialoghelper.h" /***************************************************************************** QFileDialog debug facilities *****************************************************************************/ //#define DEBUG_FILEDIALOG_FILTERS #include #include #include "qt_mac_p.h" #include "qcocoahelpers.h" #include "qcocoamenubar.h" #include "qcocoaeventdispatcher.h" #include #include #include #include #include #include #include #include #include #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) #include #else #include #endif #include #include #include #import #import QT_FORWARD_DECLARE_CLASS(QString) QT_FORWARD_DECLARE_CLASS(QStringList) QT_FORWARD_DECLARE_CLASS(QFileInfo) QT_FORWARD_DECLARE_CLASS(QWindow) QT_USE_NAMESPACE typedef QSharedPointer SharedPointerFileDialogOptions; @interface QT_MANGLE_NAMESPACE(QNSOpenSavePanelDelegate) : NSObject { @public NSOpenPanel *mOpenPanel; NSSavePanel *mSavePanel; NSView *mAccessoryView; NSPopUpButton *mPopUpButton; NSTextField *mTextField; QCocoaFileDialogHelper *mHelper; NSString *mCurrentDir; int mReturnCode; SharedPointerFileDialogOptions mOptions; QString *mCurrentSelection; QStringList *mNameFilterDropDownList; QStringList *mSelectedNameFilter; } - (NSString *)strip:(const QString &)label; - (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url; - (void)filterChanged:(id)sender; - (void)showModelessPanel; - (BOOL)runApplicationModalPanel; - (void)showWindowModalSheet:(QWindow *)docWidget; - (void)updateProperties; - (QStringList)acceptableExtensionsForSave; - (QString)removeExtensions:(const QString &)filter; - (void)createTextField; - (void)createPopUpButton:(const QString &)selectedFilter hideDetails:(BOOL)hideDetails; - (QStringList)findStrippedFilterWithVisualFilterName:(QString)name; - (void)createAccessory; @end QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSOpenSavePanelDelegate); @implementation QNSOpenSavePanelDelegate - (id)initWithAcceptMode: (const QString &)selectFile options:(SharedPointerFileDialogOptions)options helper:(QCocoaFileDialogHelper *)helper { self = [super init]; mOptions = options; if (mOptions->acceptMode() == QFileDialogOptions::AcceptOpen){ mOpenPanel = [NSOpenPanel openPanel]; mSavePanel = mOpenPanel; } else { mSavePanel = [NSSavePanel savePanel]; [mSavePanel setCanSelectHiddenExtension:YES]; mOpenPanel = 0; } if ([mSavePanel respondsToSelector:@selector(setLevel:)]) [mSavePanel setLevel:NSModalPanelWindowLevel]; mReturnCode = -1; mHelper = helper; mNameFilterDropDownList = new QStringList(mOptions->nameFilters()); QString selectedVisualNameFilter = mOptions->initiallySelectedNameFilter(); mSelectedNameFilter = new QStringList([self findStrippedFilterWithVisualFilterName:selectedVisualNameFilter]); QFileInfo sel(selectFile); if (sel.isDir() && !sel.isBundle()){ mCurrentDir = [sel.absoluteFilePath().toNSString() retain]; mCurrentSelection = new QString; } else { mCurrentDir = [sel.absolutePath().toNSString() retain]; mCurrentSelection = new QString(sel.absoluteFilePath()); } [mSavePanel setTitle:options->windowTitle().toNSString()]; [self createPopUpButton:selectedVisualNameFilter hideDetails:options->testOption(QFileDialogOptions::HideNameFilterDetails)]; [self createTextField]; [self createAccessory]; [mSavePanel setAccessoryView:mNameFilterDropDownList->size() > 1 ? mAccessoryView : nil]; // -setAccessoryView: can result in -panel:directoryDidChange: // resetting our mCurrentDir, set the delegate // here to make sure it gets the correct value. [mSavePanel setDelegate:self]; #if QT_OSX_PLATFORM_SDK_EQUAL_OR_ABOVE(__MAC_10_11) if (QOperatingSystemVersion::current() >= QOperatingSystemVersion::OSXElCapitan) mOpenPanel.accessoryViewDisclosed = YES; #endif if (mOptions->isLabelExplicitlySet(QFileDialogOptions::Accept)) [mSavePanel setPrompt:[self strip:options->labelText(QFileDialogOptions::Accept)]]; if (mOptions->isLabelExplicitlySet(QFileDialogOptions::FileName)) [mSavePanel setNameFieldLabel:[self strip:options->labelText(QFileDialogOptions::FileName)]]; [self updateProperties]; [mSavePanel retain]; return self; } - (void)dealloc { delete mNameFilterDropDownList; delete mSelectedNameFilter; delete mCurrentSelection; if ([mSavePanel respondsToSelector:@selector(orderOut:)]) [mSavePanel orderOut:mSavePanel]; [mSavePanel setAccessoryView:nil]; [mPopUpButton release]; [mTextField release]; [mAccessoryView release]; [mSavePanel setDelegate:nil]; [mSavePanel release]; [mCurrentDir release]; [super dealloc]; } static QString strippedText(QString s) { s.remove( QString::fromLatin1("...") ); return QPlatformTheme::removeMnemonics(s).trimmed(); } - (NSString *)strip:(const QString &)label { return strippedText(label).toNSString(); } - (void)closePanel { *mCurrentSelection = QString::fromNSString([[mSavePanel URL] path]).normalized(QString::NormalizationForm_C); if ([mSavePanel respondsToSelector:@selector(close)]) [mSavePanel close]; if ([mSavePanel isSheet]) [NSApp endSheet: mSavePanel]; } - (void)showModelessPanel { if (mOpenPanel){ QFileInfo info(*mCurrentSelection); NSString *filepath = info.filePath().toNSString(); NSURL *url = [NSURL fileURLWithPath:filepath isDirectory:info.isDir()]; bool selectable = (mOptions->acceptMode() == QFileDialogOptions::AcceptSave) || [self panel:nil shouldEnableURL:url]; [self updateProperties]; QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder(); [mSavePanel setNameFieldStringValue:selectable ? info.fileName().toNSString() : @""]; [mOpenPanel beginWithCompletionHandler:^(NSInteger result){ mReturnCode = result; if (mHelper) mHelper->QNSOpenSavePanelDelegate_panelClosed(result == NSOKButton); }]; } } - (BOOL)runApplicationModalPanel { QFileInfo info(*mCurrentSelection); NSString *filepath = info.filePath().toNSString(); NSURL *url = [NSURL fileURLWithPath:filepath isDirectory:info.isDir()]; bool selectable = (mOptions->acceptMode() == QFileDialogOptions::AcceptSave) || [self panel:nil shouldEnableURL:url]; [mSavePanel setDirectoryURL: [NSURL fileURLWithPath:mCurrentDir]]; [mSavePanel setNameFieldStringValue:selectable ? info.fileName().toNSString() : @""]; // Call processEvents in case the event dispatcher has been interrupted, and needs to do // 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); // Make sure we don't interrupt the runModal call below. QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag(); QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder(); mReturnCode = [mSavePanel runModal]; QCocoaMenuBar::resetKnownMenuItemsToQt(); QAbstractEventDispatcher::instance()->interrupt(); return (mReturnCode == NSOKButton); } - (QPlatformDialogHelper::DialogCode)dialogResultCode { return (mReturnCode == NSOKButton) ? QPlatformDialogHelper::Accepted : QPlatformDialogHelper::Rejected; } - (void)showWindowModalSheet:(QWindow *)parent { QFileInfo info(*mCurrentSelection); NSString *filepath = info.filePath().toNSString(); NSURL *url = [NSURL fileURLWithPath:filepath isDirectory:info.isDir()]; bool selectable = (mOptions->acceptMode() == QFileDialogOptions::AcceptSave) || [self panel:nil shouldEnableURL:url]; [self updateProperties]; QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder(); [mSavePanel setDirectoryURL: [NSURL fileURLWithPath:mCurrentDir]]; [mSavePanel setNameFieldStringValue:selectable ? info.fileName().toNSString() : @""]; NSWindow *nsparent = static_cast(qGuiApp->platformNativeInterface()->nativeResourceForWindow("nswindow", parent)); [mSavePanel beginSheetModalForWindow:nsparent completionHandler:^(NSInteger result){ mReturnCode = result; if (mHelper) mHelper->QNSOpenSavePanelDelegate_panelClosed(result == NSOKButton); }]; } - (BOOL)isHiddenFileAtURL:(NSURL *)url { BOOL hidden = NO; if (url) { CFBooleanRef isHiddenProperty; if (CFURLCopyResourcePropertyForKey((__bridge CFURLRef)url, kCFURLIsHiddenKey, &isHiddenProperty, NULL)) { hidden = CFBooleanGetValue(isHiddenProperty); CFRelease(isHiddenProperty); } } return hidden; } - (BOOL)panel:(id)sender shouldEnableURL:(NSURL *)url { Q_UNUSED(sender); NSString *filename = [url path]; if ([filename length] == 0) return NO; // Always accept directories regardless of their names (unless it is a bundle): NSFileManager *fm = [NSFileManager defaultManager]; NSDictionary *fileAttrs = [fm attributesOfItemAtPath:filename error:nil]; if (!fileAttrs) return NO; // Error accessing the file means 'no'. NSString *fileType = [fileAttrs fileType]; bool isDir = [fileType isEqualToString:NSFileTypeDirectory]; if (isDir) { if ([mSavePanel treatsFilePackagesAsDirectories] == NO) { if ([[NSWorkspace sharedWorkspace] isFilePackageAtPath:filename] == NO) return YES; } } QString qtFileName = QFileInfo(QString::fromNSString(filename)).fileName(); // No filter means accept everything bool nameMatches = mSelectedNameFilter->isEmpty(); // Check if the current file name filter accepts the file: for (int i = 0; !nameMatches && i < mSelectedNameFilter->size(); ++i) { if (QDir::match(mSelectedNameFilter->at(i), qtFileName)) nameMatches = true; } if (!nameMatches) return NO; QDir::Filters filter = mOptions->filter(); if ((!(filter & (QDir::Dirs | QDir::AllDirs)) && isDir) || (!(filter & QDir::Files) && [fileType isEqualToString:NSFileTypeRegular]) || ((filter & QDir::NoSymLinks) && [fileType isEqualToString:NSFileTypeSymbolicLink])) return NO; bool filterPermissions = ((filter & QDir::PermissionMask) && (filter & QDir::PermissionMask) != QDir::PermissionMask); if (filterPermissions) { if ((!(filter & QDir::Readable) && [fm isReadableFileAtPath:filename]) || (!(filter & QDir::Writable) && [fm isWritableFileAtPath:filename]) || (!(filter & QDir::Executable) && [fm isExecutableFileAtPath:filename])) return NO; } if (!(filter & QDir::Hidden) && (qtFileName.startsWith(QLatin1Char('.')) || [self isHiddenFileAtURL:url])) return NO; return YES; } - (NSString *)panel:(id)sender userEnteredFilename:(NSString *)filename confirmed:(BOOL)okFlag { Q_UNUSED(sender); if (!okFlag) return filename; if (!mOptions->testOption(QFileDialogOptions::DontConfirmOverwrite)) return filename; // User has clicked save, and no overwrite confirmation should occur. // To get the latter, we need to change the name we return (hence the prefix): return [@"___qt_very_unlikely_prefix_" stringByAppendingString:filename]; } - (void)setNameFilters:(const QStringList &)filters hideDetails:(BOOL)hideDetails { [mPopUpButton removeAllItems]; *mNameFilterDropDownList = filters; if (filters.size() > 0){ for (int i=0; ivalue([mPopUpButton indexOfSelectedItem]); *mSelectedNameFilter = [self findStrippedFilterWithVisualFilterName:selection]; if ([mSavePanel respondsToSelector:@selector(validateVisibleColumns:)]) [mSavePanel validateVisibleColumns]; [self updateProperties]; if (mHelper) mHelper->QNSOpenSavePanelDelegate_filterSelected([mPopUpButton indexOfSelectedItem]); } - (QString)currentNameFilter { return mNameFilterDropDownList->value([mPopUpButton indexOfSelectedItem]); } - (QList)selectedFiles { if (mOpenPanel) { QList result; NSArray* array = [mOpenPanel URLs]; for (NSUInteger i=0; i<[array count]; ++i) { QString path = QString::fromNSString([[array objectAtIndex:i] path]).normalized(QString::NormalizationForm_C); result << QUrl::fromLocalFile(path); } return result; } else { QList result; QString filename = QString::fromNSString([[mSavePanel URL] path]).normalized(QString::NormalizationForm_C); + const QString defaultSuffix = mOptions->defaultSuffix(); + const QFileInfo fileInfo(filename); + // If neither the user or the NSSavePanel have provided a suffix, use + // the default suffix (if it exists). + if (fileInfo.suffix().isEmpty() && !defaultSuffix.isEmpty()) { + filename.append('.').append(defaultSuffix); + } result << QUrl::fromLocalFile(filename.remove(QLatin1String("___qt_very_unlikely_prefix_"))); return result; } } - (void)updateProperties { // Call this functions if mFileMode, mFileOptions, // mNameFilterDropDownList or mQDirFilter changes. // The savepanel does not contain the necessary functions for this. const QFileDialogOptions::FileMode fileMode = mOptions->fileMode(); bool chooseFilesOnly = fileMode == QFileDialogOptions::ExistingFile || fileMode == QFileDialogOptions::ExistingFiles; bool chooseDirsOnly = fileMode == QFileDialogOptions::Directory || fileMode == QFileDialogOptions::DirectoryOnly || mOptions->testOption(QFileDialogOptions::ShowDirsOnly); [mOpenPanel setCanChooseFiles:!chooseDirsOnly]; [mOpenPanel setCanChooseDirectories:!chooseFilesOnly]; [mSavePanel setCanCreateDirectories:!(mOptions->testOption(QFileDialogOptions::ReadOnly))]; [mOpenPanel setAllowsMultipleSelection:(fileMode == QFileDialogOptions::ExistingFiles)]; [mOpenPanel setResolvesAliases:!(mOptions->testOption(QFileDialogOptions::DontResolveSymlinks))]; [mOpenPanel setTitle:mOptions->windowTitle().toNSString()]; [mSavePanel setTitle:mOptions->windowTitle().toNSString()]; [mPopUpButton setHidden:chooseDirsOnly]; // TODO hide the whole sunken pane instead? if (mOptions->acceptMode() == QFileDialogOptions::AcceptSave) { - QStringList ext = [self acceptableExtensionsForSave]; - const QString defaultSuffix = mOptions->defaultSuffix(); - if (!ext.isEmpty() && !defaultSuffix.isEmpty()) - ext.prepend(defaultSuffix); + const QStringList ext = [self acceptableExtensionsForSave]; [mSavePanel setAllowedFileTypes:ext.isEmpty() ? nil : qt_mac_QStringListToNSMutableArray(ext)]; } else { [mOpenPanel setAllowedFileTypes:nil]; // delegate panel:shouldEnableURL: does the file filtering for NSOpenPanel } if ([mSavePanel respondsToSelector:@selector(isVisible)] && [mSavePanel isVisible]) { if ([mSavePanel respondsToSelector:@selector(validateVisibleColumns)]) [mSavePanel validateVisibleColumns]; } } - (void)panelSelectionDidChange:(id)sender { Q_UNUSED(sender); if (mHelper && [mSavePanel isVisible]) { QString selection = QString::fromNSString([[mSavePanel URL] path]); if (selection != mCurrentSelection) { *mCurrentSelection = selection; mHelper->QNSOpenSavePanelDelegate_selectionChanged(selection); } } } - (void)panel:(id)sender directoryDidChange:(NSString *)path { Q_UNUSED(sender); if (!mHelper) return; if (!(path && path.length) || [path isEqualToString:mCurrentDir]) return; [mCurrentDir release]; mCurrentDir = [path retain]; mHelper->QNSOpenSavePanelDelegate_directoryEntered(QString::fromNSString(mCurrentDir)); } /* Returns a list of extensions (e.g. "png", "jpg", "gif") for the current name filter. If a filter do not conform to the format *.xyz or * or *.*, an empty list is returned meaning accept everything. */ - (QStringList)acceptableExtensionsForSave { QStringList result; for (int i=0; icount(); ++i) { const QString &filter = mSelectedNameFilter->at(i); if (filter.startsWith(QLatin1String("*.")) && !filter.contains(QLatin1Char('?')) && filter.count(QLatin1Char('*')) == 1) { result += filter.mid(2); } else { return QStringList(); // Accept everything } } return result; } - (QString)removeExtensions:(const QString &)filter { QRegExp regExp(QString::fromLatin1(QPlatformFileDialogHelper::filterRegExp)); if (regExp.indexIn(filter) != -1) return regExp.cap(1).trimmed(); return filter; } - (void)createTextField { NSRect textRect = { { 0.0, 3.0 }, { 100.0, 25.0 } }; mTextField = [[NSTextField alloc] initWithFrame:textRect]; [[mTextField cell] setFont:[NSFont systemFontOfSize: [NSFont systemFontSizeForControlSize:NSRegularControlSize]]]; [mTextField setAlignment:NSRightTextAlignment]; [mTextField setEditable:false]; [mTextField setSelectable:false]; [mTextField setBordered:false]; [mTextField setDrawsBackground:false]; if (mOptions->isLabelExplicitlySet(QFileDialogOptions::FileType)) [mTextField setStringValue:[self strip:mOptions->labelText(QFileDialogOptions::FileType)]]; } - (void)createPopUpButton:(const QString &)selectedFilter hideDetails:(BOOL)hideDetails { NSRect popUpRect = { { 100.0, 5.0 }, { 250.0, 25.0 } }; mPopUpButton = [[NSPopUpButton alloc] initWithFrame:popUpRect pullsDown:NO]; [mPopUpButton setTarget:self]; [mPopUpButton setAction:@selector(filterChanged:)]; if (mNameFilterDropDownList->size() > 0) { int filterToUse = -1; for (int i=0; isize(); ++i) { QString currentFilter = mNameFilterDropDownList->at(i); if (selectedFilter == currentFilter || (filterToUse == -1 && currentFilter.startsWith(selectedFilter))) filterToUse = i; QString filter = hideDetails ? [self removeExtensions:currentFilter] : currentFilter; [mPopUpButton addItemWithTitle:filter.toNSString()]; } if (filterToUse != -1) [mPopUpButton selectItemAtIndex:filterToUse]; } } - (QStringList) findStrippedFilterWithVisualFilterName:(QString)name { for (int i=0; isize(); ++i) { if (mNameFilterDropDownList->at(i).startsWith(name)) return QPlatformFileDialogHelper::cleanFilterList(mNameFilterDropDownList->at(i)); } return QStringList(); } - (void)createAccessory { NSRect accessoryRect = { { 0.0, 0.0 }, { 450.0, 33.0 } }; mAccessoryView = [[NSView alloc] initWithFrame:accessoryRect]; [mAccessoryView addSubview:mTextField]; [mAccessoryView addSubview:mPopUpButton]; } @end QT_BEGIN_NAMESPACE QCocoaFileDialogHelper::QCocoaFileDialogHelper() :mDelegate(0) { } QCocoaFileDialogHelper::~QCocoaFileDialogHelper() { if (!mDelegate) return; QMacAutoReleasePool pool; [mDelegate release]; mDelegate = 0; } void QCocoaFileDialogHelper::QNSOpenSavePanelDelegate_selectionChanged(const QString &newPath) { emit currentChanged(QUrl::fromLocalFile(newPath)); } void QCocoaFileDialogHelper::QNSOpenSavePanelDelegate_panelClosed(bool accepted) { QCocoaMenuBar::resetKnownMenuItemsToQt(); if (accepted) { emit accept(); } else { emit reject(); } } void QCocoaFileDialogHelper::QNSOpenSavePanelDelegate_directoryEntered(const QString &newDir) { // ### fixme: priv->setLastVisitedDirectory(newDir); emit directoryEntered(QUrl::fromLocalFile(newDir)); } void QCocoaFileDialogHelper::QNSOpenSavePanelDelegate_filterSelected(int menuIndex) { const QStringList filters = options()->nameFilters(); emit filterSelected(menuIndex >= 0 && menuIndex < filters.size() ? filters.at(menuIndex) : QString()); } void QCocoaFileDialogHelper::setDirectory(const QUrl &directory) { if (mDelegate) [mDelegate->mSavePanel setDirectoryURL:[NSURL fileURLWithPath:directory.toLocalFile().toNSString()]]; else mDir = directory; } QUrl QCocoaFileDialogHelper::directory() const { if (mDelegate) { QString path = QString::fromNSString([[mDelegate->mSavePanel directoryURL] path]).normalized(QString::NormalizationForm_C); return QUrl::fromLocalFile(path); } return mDir; } void QCocoaFileDialogHelper::selectFile(const QUrl &filename) { QString filePath = filename.toLocalFile(); if (QDir::isRelativePath(filePath)) filePath = QFileInfo(directory().toLocalFile(), filePath).filePath(); // There seems to no way to select a file once the dialog is running. // So do the next best thing, set the file's directory: setDirectory(QFileInfo(filePath).absolutePath()); } QList QCocoaFileDialogHelper::selectedFiles() const { if (mDelegate) return [mDelegate selectedFiles]; return QList(); } void QCocoaFileDialogHelper::setFilter() { if (!mDelegate) return; const SharedPointerFileDialogOptions &opts = options(); [mDelegate->mSavePanel setTitle:opts->windowTitle().toNSString()]; if (opts->isLabelExplicitlySet(QFileDialogOptions::Accept)) [mDelegate->mSavePanel setPrompt:[mDelegate strip:opts->labelText(QFileDialogOptions::Accept)]]; if (opts->isLabelExplicitlySet(QFileDialogOptions::FileName)) [mDelegate->mSavePanel setNameFieldLabel:[mDelegate strip:opts->labelText(QFileDialogOptions::FileName)]]; [mDelegate updateProperties]; } void QCocoaFileDialogHelper::selectNameFilter(const QString &filter) { if (!options()) return; const int index = options()->nameFilters().indexOf(filter); if (index != -1) { if (!mDelegate) { options()->setInitiallySelectedNameFilter(filter); return; } [mDelegate->mPopUpButton selectItemAtIndex:index]; [mDelegate filterChanged:nil]; } } QString QCocoaFileDialogHelper::selectedNameFilter() const { if (!mDelegate) return options()->initiallySelectedNameFilter(); int index = [mDelegate->mPopUpButton indexOfSelectedItem]; if (index >= options()->nameFilters().count()) return QString(); return index != -1 ? options()->nameFilters().at(index) : QString(); } void QCocoaFileDialogHelper::hide() { hideCocoaFilePanel(); } bool QCocoaFileDialogHelper::show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) { // Q_Q(QFileDialog); if (windowFlags & Qt::WindowStaysOnTopHint) { // The native file dialog tries all it can to stay // on the NSModalPanel level. And it might also show // its own "create directory" dialog that we cannot control. // So we need to use the non-native version in this case... return false; } return showCocoaFilePanel(windowModality, parent); } void QCocoaFileDialogHelper::createNSOpenSavePanelDelegate() { QMacAutoReleasePool pool; const SharedPointerFileDialogOptions &opts = options(); const QList selectedFiles = opts->initiallySelectedFiles(); const QUrl directory = mDir.isEmpty() ? opts->initialDirectory() : mDir; const bool selectDir = selectedFiles.isEmpty(); QString selection(selectDir ? directory.toLocalFile() : selectedFiles.front().toLocalFile()); QNSOpenSavePanelDelegate *delegate = [[QNSOpenSavePanelDelegate alloc] initWithAcceptMode: selection options:opts helper:this]; [static_cast(mDelegate) release]; mDelegate = delegate; } bool QCocoaFileDialogHelper::showCocoaFilePanel(Qt::WindowModality windowModality, QWindow *parent) { createNSOpenSavePanelDelegate(); if (!mDelegate) return false; if (windowModality == Qt::NonModal) [mDelegate showModelessPanel]; else if (windowModality == Qt::WindowModal && parent) [mDelegate showWindowModalSheet:parent]; // no need to show a Qt::ApplicationModal dialog here, since it will be done in _q_platformRunNativeAppModalPanel() return true; } bool QCocoaFileDialogHelper::hideCocoaFilePanel() { if (!mDelegate){ // Nothing to do. We return false to leave the question // open regarding whether or not to go native: return false; } else { [mDelegate closePanel]; // Even when we hide it, we are still using a // native dialog, so return true: return true; } } void QCocoaFileDialogHelper::exec() { // Note: If NSApp is not running (which is the case if e.g a top-most // QEventLoop has been interrupted, and the second-most event loop has not // yet been reactivated (regardless if [NSApp run] is still on the stack)), // showing a native modal dialog will fail. QMacAutoReleasePool pool; if ([mDelegate runApplicationModalPanel]) emit accept(); else emit reject(); } bool QCocoaFileDialogHelper::defaultNameFilterDisables() const { return true; } QT_END_NAMESPACE diff --git a/src/qcocoa-qpa/qcocoamenu.mm b/src/qcocoa-qpa/qcocoamenu.mm index 77eca5a..14ce632 100644 --- a/src/qcocoa-qpa/qcocoamenu.mm +++ b/src/qcocoa-qpa/qcocoamenu.mm @@ -1,728 +1,730 @@ /**************************************************************************** ** ** Copyright (C) 2012 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author James Turner ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qcocoamenu.h" #include "qcocoahelpers.h" #include #include #include #include #include "qcocoaapplication.h" +#include "qcocoaintegration.h" #include "qcocoamenuloader.h" #include "qcocoamenubar.h" #include "qcocoawindow.h" #import "qnsview.h" QT_BEGIN_NAMESPACE NSString *qt_mac_removePrivateUnicode(NSString* string) { int len = [string length]; if (len) { QVarLengthArray characters(len); bool changed = false; for (int i = 0; i { QCocoaMenu *m_menu; } - (id) initWithMenu:(QCocoaMenu*) m; - (NSMenuItem *)findItem:(NSMenu *)menu forKey:(NSString *)key forModifiers:(NSUInteger)modifier; @end QT_NAMESPACE_ALIAS_OBJC_CLASS(QCocoaMenuDelegate); @implementation QCocoaMenuDelegate - (id) initWithMenu:(QCocoaMenu*) m { if ((self = [super init])) m_menu = m; return self; } - (NSInteger)numberOfItemsInMenu:(NSMenu *)menu { Q_ASSERT(m_menu->nsMenu() == menu); return menu.numberOfItems; } - (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel { Q_UNUSED(index); Q_ASSERT(m_menu->nsMenu() == menu); if (shouldCancel) { // TODO detach all submenus return NO; } QCocoaMenuItem *menuItem = reinterpret_cast(item.tag); if (m_menu->items().contains(menuItem)) { if (QCocoaMenu *itemSubmenu = menuItem->menu()) itemSubmenu->setAttachedItem(item); } return YES; } - (void)menu:(NSMenu*)menu willHighlightItem:(NSMenuItem*)item { Q_UNUSED(menu); if (item && [item tag]) { QCocoaMenuItem *cocoaItem = reinterpret_cast([item tag]); cocoaItem->hovered(); } } - (void) menuWillOpen:(NSMenu*)m { Q_UNUSED(m); m_menu->setIsOpen(true); emit m_menu->aboutToShow(); } - (void) menuDidClose:(NSMenu*)m { Q_UNUSED(m); m_menu->setIsOpen(false); // wrong, but it's the best we can do emit m_menu->aboutToHide(); } - (void) itemFired:(NSMenuItem*) item { QCocoaMenuItem *cocoaItem = reinterpret_cast([item tag]); // Menu-holding items also get a target to play nicely // with NSMenuValidation but should not trigger. if (cocoaItem->menu()) return; QScopedScopeLevelCounter scopeLevelCounter(QGuiApplicationPrivate::instance()->threadData); QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers:[NSEvent modifierFlags]]; static QMetaMethod activatedSignal = QMetaMethod::fromSignal(&QCocoaMenuItem::activated); activatedSignal.invoke(cocoaItem, Qt::QueuedConnection); } - (BOOL)validateMenuItem:(NSMenuItem*)menuItem { QCocoaMenuItem *cocoaItem = reinterpret_cast(menuItem.tag); // Menu-holding items are always enabled, as it's conventional in Cocoa if (!cocoaItem || cocoaItem->menu()) return YES; return cocoaItem->isEnabled(); } - (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action { /* Check if the menu actually has a keysequence defined for this key event. If it does, then we will first send the key sequence to the QWidget that has focus since (in Qt's eyes) it needs to a chance at the key event first (QEvent::ShortcutOverride). If the widget accepts the key event, we then return YES, but set the target and action to be nil, which means that the action should not be triggered, and instead dispatch the event ourselves. In every other case we return NO, which means that Cocoa can do as it pleases (i.e., fire the menu action). */ // Change the private unicode keys to the ones used in setting the "Key Equivalents" NSString *characters = qt_mac_removePrivateUnicode([event characters]); // Interested only in Shift, Cmd, Ctrl & Alt Keys, so ignoring masks like, Caps lock, Num Lock ... const NSUInteger mask = NSShiftKeyMask | NSControlKeyMask | NSCommandKeyMask | NSAlternateKeyMask; if (NSMenuItem *menuItem = [self findItem:menu forKey:characters forModifiers:([event modifierFlags] & mask)]) { if (!menuItem.target) { // This item was modified by QCocoaMenuBar::redirectKnownMenuItemsToFirstResponder // and it looks like we're running a modal session for NSOpenPanel/NSSavePanel. // QCocoaFileDialogHelper is actually the only place we use this and we run NSOpenPanel modal // (modal sheet, window modal, application modal). // Whatever the current first responder is, let's give it a chance // and do not touch the Qt's focusObject (which is different from some native view // having a focus inside NSSave/OpenPanel. *target = nil; *action = menuItem.action; return YES; } QObject *object = qApp->focusObject(); if (object) { QChar ch; int keyCode; ulong nativeModifiers = [event modifierFlags]; Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers: nativeModifiers]; NSString *charactersIgnoringModifiers = [event charactersIgnoringModifiers]; NSString *characters = [event characters]; if ([charactersIgnoringModifiers length] > 0) { // convert the first character into a key code if ((modifiers & Qt::ControlModifier) && ([characters length] != 0)) { ch = QChar([characters characterAtIndex:0]); } else { ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); } keyCode = qt_mac_cocoaKey2QtKey(ch); } else { // might be a dead key ch = QChar::ReplacementCharacter; keyCode = Qt::Key_unknown; } QKeyEvent accel_ev(QEvent::ShortcutOverride, (keyCode & (~Qt::KeyboardModifierMask)), Qt::KeyboardModifiers(modifiers & Qt::KeyboardModifierMask)); accel_ev.ignore(); QCoreApplication::sendEvent(object, &accel_ev); if (accel_ev.isAccepted()) { [[NSApp keyWindow] sendEvent: event]; *target = nil; *action = nil; return YES; } } } return NO; } - (NSMenuItem *)findItem:(NSMenu *)menu forKey:(NSString *)key forModifiers:(NSUInteger)modifier { for (NSMenuItem *item in [menu itemArray]) { if (![item isEnabled] || [item isHidden] || [item isSeparatorItem]) continue; if ([item hasSubmenu]) { if (NSMenuItem *nested = [self findItem:[item submenu] forKey:key forModifiers:modifier]) return nested; } NSString *menuKey = [item keyEquivalent]; if (menuKey && NSOrderedSame == [menuKey compare:key] && modifier == [item keyEquivalentModifierMask]) return item; } return nil; } @end QT_BEGIN_NAMESPACE QCocoaMenu::QCocoaMenu() : m_attachedItem(0), m_tag(0), m_updateTimer(0), m_enabled(true), m_parentEnabled(true), m_visible(true), m_isOpen(false) { QMacAutoReleasePool pool; m_nativeMenu = [[NSMenu alloc] initWithTitle:@"Untitled"]; [m_nativeMenu setAutoenablesItems:YES]; m_nativeMenu.delegate = [[QCocoaMenuDelegate alloc] initWithMenu:this]; } QCocoaMenu::~QCocoaMenu() { foreach (QCocoaMenuItem *item, m_menuItems) { if (item->menuParent() == this) item->setMenuParent(0); } QMacAutoReleasePool pool; NSObject *delegate = m_nativeMenu.delegate; m_nativeMenu.delegate = nil; [delegate release]; [m_nativeMenu release]; } void QCocoaMenu::setText(const QString &text) { QMacAutoReleasePool pool; QString stripped = qt_mac_removeAmpersandEscapes(text); [m_nativeMenu setTitle:stripped.toNSString()]; } void QCocoaMenu::setMinimumWidth(int width) { m_nativeMenu.minimumWidth = width; } void QCocoaMenu::setFont(const QFont &font) { if (font.resolve()) { NSFont *customMenuFont = [NSFont fontWithName:font.family().toNSString() size:font.pointSize()]; m_nativeMenu.font = customMenuFont; } } void QCocoaMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) { QMacAutoReleasePool pool; QCocoaMenuItem *cocoaItem = static_cast(menuItem); QCocoaMenuItem *beforeItem = static_cast(before); cocoaItem->sync(); if (beforeItem) { int index = m_menuItems.indexOf(beforeItem); // if a before item is supplied, it should be in the menu if (index < 0) { qWarning("Before menu item not found"); return; } m_menuItems.insert(index, cocoaItem); } else { m_menuItems.append(cocoaItem); } insertNative(cocoaItem, beforeItem); // Empty menus on a menubar are hidden by default. If the menu gets // added to the menubar before it contains any item, we need to sync. if (isVisible() && [m_attachedItem isHidden]) { if (auto *mb = qobject_cast(menuParent())) mb->syncMenu(this); } } void QCocoaMenu::insertNative(QCocoaMenuItem *item, QCocoaMenuItem *beforeItem) { NSMenuItem *nativeItem = item->nsItem(); nativeItem.target = m_nativeMenu.delegate; nativeItem.action = @selector(itemFired:); // Someone's adding new items after aboutToShow() was emitted if (isOpen() && nativeItem && item->menu()) item->menu()->setAttachedItem(nativeItem); item->setParentEnabled(isEnabled()); if (item->isMerged()) return; NSMenu *m = [item->nsItem() menu]; if (m) { QString mTitle = QString::fromNSString([m title]); if (beforeItem) { qWarning() << Q_FUNC_INFO << "Menu item" << item->text() << "is already in menu" << mTitle << "after item" << beforeItem->text() << ", remove it from the other menu first before inserting into" << QString::fromNSString([nsMenu() title]); } else { qWarning() << Q_FUNC_INFO << "Menu item" << item->text() << "is already in menu" << mTitle << ", remove it from the other menu first before inserting into" << QString::fromNSString([nsMenu() title]); } return; } // if the item we're inserting before is merged, skip along until // we find a non-merged real item to insert ahead of. while (beforeItem && beforeItem->isMerged()) { beforeItem = itemOrNull(m_menuItems.indexOf(beforeItem) + 1); } if (nativeItem.menu) { qWarning() << "Menu item" << item->text() << "already in menu" << QString::fromNSString(nativeItem.menu.title); return; } if (beforeItem) { if (beforeItem->isMerged()) { qWarning("No non-merged before menu item found"); return; } if (item->isSeparator() && !item->text().isEmpty()) { // menu section: add a separator before the section title text const NSInteger idx = [m_nativeMenu indexOfItem:beforeItem->nsItem()]; [m_nativeMenu insertItem:[NSMenuItem separatorItem] atIndex:idx]; } const NSInteger nativeIndex = [m_nativeMenu indexOfItem:beforeItem->nsItem()]; [m_nativeMenu insertItem:nativeItem atIndex:nativeIndex]; } else { if (item->isSeparator() && !item->text().isEmpty()) { // menu section: add a separator before the section title text [m_nativeMenu addItem:[NSMenuItem separatorItem]]; } [m_nativeMenu addItem:nativeItem]; } item->setMenuParent(this); } bool QCocoaMenu::isOpen() const { return m_isOpen; } void QCocoaMenu::setIsOpen(bool isOpen) { m_isOpen = isOpen; } void QCocoaMenu::removeMenuItem(QPlatformMenuItem *menuItem) { QMacAutoReleasePool pool; QCocoaMenuItem *cocoaItem = static_cast(menuItem); if (!m_menuItems.contains(cocoaItem)) { qWarning("Menu does not contain the item to be removed"); return; } if (cocoaItem->menuParent() == this) cocoaItem->setMenuParent(0); // Ignore any parent enabled state cocoaItem->setParentEnabled(true); m_menuItems.removeOne(cocoaItem); if (!cocoaItem->isMerged()) { if (m_nativeMenu != [cocoaItem->nsItem() menu]) { qWarning() << Q_FUNC_INFO << "Item" << cocoaItem->text() << "to remove does not belong to this menu" << QString::fromNSString([nsMenu() title]); return; } [m_nativeMenu removeItem: cocoaItem->nsItem()]; } } QCocoaMenuItem *QCocoaMenu::itemOrNull(int index) const { if ((index < 0) || (index >= m_menuItems.size())) return 0; return m_menuItems.at(index); } void QCocoaMenu::scheduleUpdate() { if (!m_updateTimer) m_updateTimer = startTimer(0); } void QCocoaMenu::timerEvent(QTimerEvent *e) { if (e->timerId() == m_updateTimer) { killTimer(m_updateTimer); m_updateTimer = 0; [m_nativeMenu update]; } } void QCocoaMenu::syncMenuItem(QPlatformMenuItem *menuItem) { syncMenuItem_helper(menuItem, false /*menubarUpdate*/); } void QCocoaMenu::syncMenuItem_helper(QPlatformMenuItem *menuItem, bool menubarUpdate) { QMacAutoReleasePool pool; QCocoaMenuItem *cocoaItem = static_cast(menuItem); if (!m_menuItems.contains(cocoaItem)) { qWarning("Item does not belong to this menu"); return; } const bool wasMerged = cocoaItem->isMerged(); NSMenuItem *oldItem = cocoaItem->nsItem(); NSMenuItem *syncedItem = cocoaItem->sync(); if (syncedItem != oldItem) { // native item was changed for some reason if (oldItem) { if (wasMerged) { [oldItem setEnabled:NO]; [oldItem setHidden:YES]; } else { [m_nativeMenu removeItem:oldItem]; } } QCocoaMenuItem* beforeItem = itemOrNull(m_menuItems.indexOf(cocoaItem) + 1); insertNative(cocoaItem, beforeItem); } else { // Schedule NSMenuValidation to kick in. This is needed e.g. // when an item's enabled state changes after menuWillOpen: scheduleUpdate(); } // This may be a good moment to attach this item's eventual submenu to the // synced item, but only on the condition we're all currently hooked to the // menunbar. A good indicator of this being the right moment is knowing that // we got called from QCocoaMenuBar::updateMenuBarImmediately(). if (menubarUpdate) if (QCocoaMenu *submenu = cocoaItem->menu()) submenu->setAttachedItem(syncedItem); } void QCocoaMenu::syncSeparatorsCollapsible(bool enable) { QMacAutoReleasePool pool; if (enable) { bool previousIsSeparator = true; // setting to true kills all the separators placed at the top. NSMenuItem *previousItem = nil; NSArray *itemArray = [m_nativeMenu itemArray]; for (unsigned int i = 0; i < [itemArray count]; ++i) { NSMenuItem *item = reinterpret_cast([itemArray objectAtIndex:i]); if ([item isSeparatorItem]) { QCocoaMenuItem *cocoaItem = reinterpret_cast([item tag]); if (cocoaItem) cocoaItem->setVisible(!previousIsSeparator); [item setHidden:previousIsSeparator]; } if (![item isHidden]) { previousItem = item; previousIsSeparator = ([previousItem isSeparatorItem]); } } // We now need to check the final item since we don't want any separators at the end of the list. if (previousItem && previousIsSeparator) { QCocoaMenuItem *cocoaItem = reinterpret_cast([previousItem tag]); if (cocoaItem) cocoaItem->setVisible(false); [previousItem setHidden:YES]; } } else { foreach (QCocoaMenuItem *item, m_menuItems) { if (!item->isSeparator() || !item->text().isEmpty()) continue; // sync the visiblity directly item->sync(); } } } void QCocoaMenu::setEnabled(bool enabled) { if (m_enabled == enabled) return; m_enabled = enabled; const bool wasParentEnabled = m_parentEnabled; propagateEnabledState(m_enabled); m_parentEnabled = wasParentEnabled; // Reset to the parent value } bool QCocoaMenu::isEnabled() const { return m_attachedItem ? [m_attachedItem isEnabled] : m_enabled && m_parentEnabled; } void QCocoaMenu::setVisible(bool visible) { m_visible = visible; } void QCocoaMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect, const QPlatformMenuItem *item) { QMacAutoReleasePool pool; QPoint pos = QPoint(targetRect.left(), targetRect.top() + targetRect.height()); QCocoaWindow *cocoaWindow = parentWindow ? static_cast(parentWindow->handle()) : 0; NSView *view = cocoaWindow ? cocoaWindow->view() : nil; NSMenuItem *nsItem = item ? ((QCocoaMenuItem *)item)->nsItem() : nil; QScreen *screen = 0; if (parentWindow) screen = parentWindow->screen(); if (!screen && !QGuiApplication::screens().isEmpty()) screen = QGuiApplication::screens().at(0); Q_ASSERT(screen); // Ideally, we would call -popUpMenuPositioningItem:atLocation:inView:. // However, this showed not to work with modal windows where the menu items // would appear disabled. So, we resort to a more artisanal solution. Note // that this implies several things. if (nsItem) { // If we want to position the menu popup so that a specific item lies under // the mouse cursor, we resort to NSPopUpButtonCell to do that. This is the // typical use-case for a choice list, or non-editable combobox. We can't // re-use the popUpContextMenu:withEvent:forView: logic below since it won't // respect the menu's minimum width. NSPopUpButtonCell *popupCell = [[[NSPopUpButtonCell alloc] initTextCell:@"" pullsDown:NO] autorelease]; [popupCell setAltersStateOfSelectedItem:NO]; [popupCell setTransparent:YES]; [popupCell setMenu:m_nativeMenu]; [popupCell selectItem:nsItem]; - int availableHeight = screen->availableSize().height(); - const QPoint &globalPos = parentWindow->mapToGlobal(pos); + QCocoaScreen *cocoaScreen = static_cast(screen->handle()); + int availableHeight = cocoaScreen->availableGeometry().height(); + const QPoint &globalPos = cocoaWindow->mapToGlobal(pos); int menuHeight = m_nativeMenu.size.height; if (globalPos.y() + menuHeight > availableHeight) { // Maybe we need to fix the vertical popup position but we don't know the // exact popup height at the moment (and Cocoa is just guessing) nor its // position. So, instead of translating by the popup's full height, we need // to estimate where the menu will show up and translate by the remaining height. float idx = ([m_nativeMenu indexOfItem:nsItem] + 1.0f) / m_nativeMenu.numberOfItems; float heightBelowPos = (1.0 - idx) * menuHeight; if (globalPos.y() + heightBelowPos > availableHeight) pos.setY(pos.y() - globalPos.y() + availableHeight - heightBelowPos); } NSRect cellFrame = NSMakeRect(pos.x(), pos.y(), m_nativeMenu.minimumWidth, 10); [popupCell performClickWithFrame:cellFrame inView:view]; } else { // Else, we need to transform 'pos' to window or screen coordinates. NSPoint nsPos = NSMakePoint(pos.x() - 1, pos.y()); if (view) { // convert coordinates from view to the view's window nsPos = [view convertPoint:nsPos toView:nil]; } else { nsPos.y = screen->availableVirtualSize().height() - nsPos.y; } if (view) { // Finally, we need to synthesize an event. NSEvent *menuEvent = [NSEvent mouseEventWithType:NSRightMouseDown location:nsPos modifierFlags:0 timestamp:0 windowNumber:view ? view.window.windowNumber : 0 context:nil eventNumber:0 clickCount:1 pressure:1.0]; [NSMenu popUpContextMenu:m_nativeMenu withEvent:menuEvent forView:view]; } else { [m_nativeMenu popUpMenuPositioningItem:nsItem atLocation:nsPos inView:0]; } } // The calls above block, and also swallow any mouse release event, // so we need to clear any mouse button that triggered the menu popup. if (!cocoaWindow->isForeignWindow()) [qnsview_cast(view) resetMouseButtons]; } void QCocoaMenu::dismiss() { [m_nativeMenu cancelTracking]; } QPlatformMenuItem *QCocoaMenu::menuItemAt(int position) const { if (0 <= position && position < m_menuItems.count()) return m_menuItems.at(position); return 0; } QPlatformMenuItem *QCocoaMenu::menuItemForTag(quintptr tag) const { foreach (QCocoaMenuItem *item, m_menuItems) { if (item->tag() == tag) return item; } return 0; } QList QCocoaMenu::items() const { return m_menuItems; } QList QCocoaMenu::merged() const { QList result; foreach (QCocoaMenuItem *item, m_menuItems) { if (item->menu()) { // recurse into submenus result.append(item->menu()->merged()); continue; } if (item->isMerged()) result.append(item); } return result; } void QCocoaMenu::propagateEnabledState(bool enabled) { QMacAutoReleasePool pool; // FIXME Is this still needed for Creator? See 6a0bb4206a2928b83648 m_parentEnabled = enabled; if (!m_enabled && enabled) // Some ancestor was enabled, but this menu is not return; foreach (QCocoaMenuItem *item, m_menuItems) { if (QCocoaMenu *menu = item->menu()) menu->propagateEnabledState(enabled); else item->setParentEnabled(enabled); } } void QCocoaMenu::setAttachedItem(NSMenuItem *item) { if (item == m_attachedItem) return; if (m_attachedItem) m_attachedItem.submenu = nil; m_attachedItem = item; if (m_attachedItem) m_attachedItem.submenu = m_nativeMenu; } NSMenuItem *QCocoaMenu::attachedItem() const { return m_attachedItem; } QT_END_NAMESPACE