diff options
Diffstat (limited to 'src/plugins/platforms/cocoa')
41 files changed, 5776 insertions, 264 deletions
diff --git a/src/plugins/platforms/cocoa/cocoa.pro b/src/plugins/platforms/cocoa/cocoa.pro index bfa147f948..d6801e0bed 100644 --- a/src/plugins/platforms/cocoa/cocoa.pro +++ b/src/plugins/platforms/cocoa/cocoa.pro @@ -2,28 +2,46 @@ TARGET = qcocoa load(qt_plugin) DESTDIR = $$QT.gui.plugins/platforms -OBJECTIVE_SOURCES = main.mm \ +OBJECTIVE_SOURCES += main.mm \ qcocoaintegration.mm \ - qcocoawindowsurface.mm \ + qcocoabackingstore.mm \ qcocoawindow.mm \ qnsview.mm \ - qcocoaeventloopintegration.mm \ qcocoaautoreleasepool.mm \ - qnswindowdelegate.mm + qnswindowdelegate.mm \ + qcocoaglcontext.mm \ + qcocoanativeinterface.mm \ + qcocoaeventdispatcher.mm \ + qcocoamenuloader.mm \ + qcocoaapplicationdelegate.mm \ + qcocoaapplication.mm \ + qcocoamenu.mm \ + qmenu_mac.mm \ + qcocoahelpers.mm \ -OBJECTIVE_HEADERS = qcocoaintegration.h \ - qcocoawindowsurface.h \ +HEADERS += qcocoaintegration.h \ + qcocoabackingstore.h \ qcocoawindow.h \ qnsview.h \ - qcocoaeventloopintegration.h \ qcocoaautoreleasepool.h \ - qnswindowdelegate.h + qnswindowdelegate.h \ + qcocoaglcontext.h \ + qcocoanativeinterface.h \ + qcocoaeventdispatcher.h \ + qcocoamenuloader.h \ + qcocoaapplicationdelegate.h \ + qcocoaapplication.h \ + qcocoamenu.h \ + qmenu_mac.h \ + qcocoahelpers.h \ + +RESOURCES += qcocoaresources.qrc #add libz for freetype. -LIBS += -lz -LIBS += -framework cocoa +LIBS += -lz -framework Cocoa + +QT += core-private gui-private widgets-private platformsupport-private -include(../fontdatabases/coretext/coretext.pri) +CONFIG += qpa/basicunixfontdatabase target.path += $$[QT_INSTALL_PLUGINS]/platforms INSTALLS += target - diff --git a/src/plugins/platforms/cocoa/images/copyarrowcursor.png b/src/plugins/platforms/cocoa/images/copyarrowcursor.png Binary files differnew file mode 100644 index 0000000000..13dfca95bc --- /dev/null +++ b/src/plugins/platforms/cocoa/images/copyarrowcursor.png diff --git a/src/plugins/platforms/cocoa/images/forbiddencursor.png b/src/plugins/platforms/cocoa/images/forbiddencursor.png Binary files differnew file mode 100644 index 0000000000..a9f21b4a5e --- /dev/null +++ b/src/plugins/platforms/cocoa/images/forbiddencursor.png diff --git a/src/plugins/platforms/cocoa/images/leopard-unified-toolbar-on.png b/src/plugins/platforms/cocoa/images/leopard-unified-toolbar-on.png Binary files differnew file mode 100644 index 0000000000..6716597046 --- /dev/null +++ b/src/plugins/platforms/cocoa/images/leopard-unified-toolbar-on.png diff --git a/src/plugins/platforms/cocoa/images/pluscursor.png b/src/plugins/platforms/cocoa/images/pluscursor.png Binary files differnew file mode 100644 index 0000000000..c583c088c9 --- /dev/null +++ b/src/plugins/platforms/cocoa/images/pluscursor.png diff --git a/src/plugins/platforms/cocoa/images/spincursor.png b/src/plugins/platforms/cocoa/images/spincursor.png Binary files differnew file mode 100644 index 0000000000..ca44ab50fd --- /dev/null +++ b/src/plugins/platforms/cocoa/images/spincursor.png diff --git a/src/plugins/platforms/cocoa/images/waitcursor.png b/src/plugins/platforms/cocoa/images/waitcursor.png Binary files differnew file mode 100644 index 0000000000..a9abe61320 --- /dev/null +++ b/src/plugins/platforms/cocoa/images/waitcursor.png diff --git a/src/plugins/platforms/cocoa/qcocoaapplication.h b/src/plugins/platforms/cocoa/qcocoaapplication.h new file mode 100644 index 0000000000..5b6b2f48f2 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoaapplication.h @@ -0,0 +1,116 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qapplication_*.cpp, qwidget*.cpp, qcolor_x11.cpp, qfiledialog.cpp +// and many other. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + +/* + Cocoa Application Categories +*/ +#include "qglobal.h" + +#import <AppKit/AppKit.h> +QT_FORWARD_DECLARE_CLASS(QApplicationPrivate) +@class QT_MANGLE_NAMESPACE(QCocoaMenuLoader); + +@interface NSApplication (QT_MANGLE_NAMESPACE(QApplicationIntegration)) +- (void)QT_MANGLE_NAMESPACE(qt_setDockMenu):(NSMenu *)newMenu; +- (QApplicationPrivate *)QT_MANGLE_NAMESPACE(qt_qappPrivate); +- (QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *)QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader); +- (int)QT_MANGLE_NAMESPACE(qt_validModesForFontPanel):(NSFontPanel *)fontPanel; + +- (void)qt_sendPostedMessage:(NSEvent *)event; +- (BOOL)qt_filterEvent:(NSEvent *)event; +@end + +@interface QNSApplication : NSApplication { +} +@end + +QT_BEGIN_NAMESPACE + +void qt_redirectNSApplicationSendEvent(); + +QT_END_NAMESPACE + diff --git a/src/plugins/platforms/cocoa/qcocoaapplication.mm b/src/plugins/platforms/cocoa/qcocoaapplication.mm new file mode 100644 index 0000000000..2adf6a57f0 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoaapplication.mm @@ -0,0 +1,227 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +#include <qcocoaapplication.h> + +#include <qcocoaapplicationdelegate.h> +#include <qcocoahelpers.h> +#include <qguiapplication.h> +#include <qdebug.h> + +QT_USE_NAMESPACE + +@implementation NSApplication (QT_MANGLE_NAMESPACE(QApplicationIntegration)) + +- (void)QT_MANGLE_NAMESPACE(qt_setDockMenu):(NSMenu *)newMenu +{ + [[QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) sharedDelegate] setDockMenu:newMenu]; +} + +- (QApplicationPrivate *)QT_MANGLE_NAMESPACE(qt_qappPrivate) +{ + return [[QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) sharedDelegate] qAppPrivate]; +} + +- (QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *)QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader) +{ + return [[QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) sharedDelegate] menuLoader]; +} + +- (int)QT_MANGLE_NAMESPACE(qt_validModesForFontPanel):(NSFontPanel *)fontPanel +{ + Q_UNUSED(fontPanel); + // only display those things that QFont can handle + return NSFontPanelFaceModeMask + | NSFontPanelSizeModeMask + | NSFontPanelCollectionModeMask + | NSFontPanelUnderlineEffectModeMask + | NSFontPanelStrikethroughEffectModeMask; +} + +- (void)qt_sendPostedMessage:(NSEvent *)event +{ +/* + // WARNING: data1 and data2 is truncated to from 64-bit to 32-bit on OS 10.5! + // That is why we need to split the address in two parts: + quint64 lower = [event data1]; + quint64 upper = [event data2]; + QCocoaPostMessageArgs *args = reinterpret_cast<QCocoaPostMessageArgs *>(lower | (upper << 32)); + // Special case for convenience: if the argument is an NSNumber, we unbox it directly. + // Use NSValue instead if this behaviour is unwanted. + id a1 = ([args->arg1 isKindOfClass:[NSNumber class]]) ? (id)[args->arg1 intValue] : args->arg1; + id a2 = ([args->arg2 isKindOfClass:[NSNumber class]]) ? (id)[args->arg2 intValue] : args->arg2; + switch (args->argCount) { + case 0: + [args->target performSelector:args->selector]; + break; + case 1: + [args->target performSelector:args->selector withObject:a1]; + break; + case 3: + [args->target performSelector:args->selector withObject:a1 withObject:a2]; + break; + } + + delete args; +*/ +} + +- (BOOL)qt_filterEvent:(NSEvent *)event +{ +/* + if (qApp->macEventFilter(0, reinterpret_cast<EventRef>(event))) + return true; + + if ([event type] == NSApplicationDefined) { + switch ([event subtype]) { + case QtCocoaEventSubTypePostMessage: + [NSApp qt_sendPostedMessage:event]; + return true; + default: + break; + } + } +*/ + return false; +} + +@end + +@implementation QNSApplication + +- (void)qt_sendEvent_original:(NSEvent *)event +{ + Q_UNUSED(event); + // This method will only be used as a signature + // template for the method we add into NSApplication + // containing the original [NSApplication sendEvent:] implementation +} + +- (void)qt_sendEvent_replacement:(NSEvent *)event +{ + // This method (or its implementation to be precise) will + // be called instead of sendEvent if redirection occurs. + // 'self' will then be an instance of NSApplication + // (and not QNSApplication) + if (![NSApp qt_filterEvent:event]) + [self qt_sendEvent_original:event]; +} + +- (void)sendEvent:(NSEvent *)event +{ + // This method will be called if + // no redirection occurs + if (![NSApp qt_filterEvent:event]) + [super sendEvent:event]; +} + +- (void)qtDispatcherToQAction:(id)sender +{ + // Forward actions sendt from the menu bar (e.g. quit) to the menu loader. + // Having this method here means that we are the last stop in the responder + // chain, and that we are able to handle menu actions even when no window is + // visible on screen. Note: If Qt is used as a plugin, Qt will not use a + // native menu bar. Hence, we will also not need to do any redirection etc. as + // we do with sendEvent. + [[NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)] qtDispatcherToQAction:sender]; +} + +@end + +QT_BEGIN_NAMESPACE + +void qt_redirectNSApplicationSendEvent() +{ +/* + if ([NSApp isMemberOfClass:[QNSApplication class]]) { + // No need to change implementation since Qt + // already controls a subclass of NSApplication + return; + } + + // Change the implementation of [NSApplication sendEvent] to the + // implementation of qt_sendEvent_replacement found in QNSApplication. + // And keep the old implementation that gets overwritten inside a new + // method 'qt_sendEvent_original' that we add to NSApplication + qt_cocoa_change_implementation( + [NSApplication class], + @selector(sendEvent:), + [QNSApplication class], + @selector(qt_sendEvent_replacement:), + @selector(qt_sendEvent_original:)); + */ + } + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.h b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.h new file mode 100644 index 0000000000..7f8d1dfacd --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.h @@ -0,0 +1,127 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +/**************************************************************************** + ** + ** Copyright (c) 2007-2008, Apple, Inc. + ** + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions are met: + ** + ** * Redistributions of source code must retain the above copyright notice, + ** this list of conditions and the following disclaimer. + ** + ** * Redistributions in binary form must reproduce the above copyright notice, + ** this list of conditions and the following disclaimer in the documentation + ** and/or other materials provided with the distribution. + ** + ** * Neither the name of Apple, Inc. nor the names of its contributors + ** may be used to endorse or promote products derived from this software + ** without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + ** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + ** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + ** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qapplication_*.cpp, qwidget*.cpp, qcolor_x11.cpp, qfiledialog.cpp +// and many other. This header file may change from version to version +// without notice, or even be removed. +// +// We mean it. +// + + +#import <Cocoa/Cocoa.h> + +#include <qglobal.h> + +QT_FORWARD_DECLARE_CLASS(QApplicationPrivate); + +@class QT_MANGLE_NAMESPACE(QCocoaMenuLoader); + +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 + +@protocol NSApplicationDelegate <NSObject> +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender; +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification; +- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames; +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender; +- (void)applicationDidBecomeActive:(NSNotification *)notification; +- (void)applicationDidResignActive:(NSNotification *)notification; +@end + +#endif + +@interface QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) : NSObject <NSApplicationDelegate> { + bool startedQuit; + QApplicationPrivate *qtPrivate; + NSMenu *dockMenu; + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *qtMenuLoader; + NSObject <NSApplicationDelegate> *reflectionDelegate; + bool inLaunch; +} ++ (QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate)*)sharedDelegate; +- (void)setDockMenu:(NSMenu *)newMenu; +- (void)setQtPrivate:(QApplicationPrivate *)value; +- (QApplicationPrivate *)qAppPrivate; +- (void)setMenuLoader:(QT_MANGLE_NAMESPACE(QCocoaMenuLoader)*)menuLoader; +- (QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *)menuLoader; +- (void)setReflectionDelegate:(NSObject <NSApplicationDelegate> *)oldDelegate; +- (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent; +@end diff --git a/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm new file mode 100644 index 0000000000..6a2508359b --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoaapplicationdelegate.mm @@ -0,0 +1,354 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** + ** + ** Copyright (c) 2007-2008, Apple, Inc. + ** + ** All rights reserved. + ** + ** Redistribution and use in source and binary forms, with or without + ** modification, are permitted provided that the following conditions are met: + ** + ** * Redistributions of source code must retain the above copyright notice, + ** this list of conditions and the following disclaimer. + ** + ** * Redistributions in binary form must reproduce the above copyright notice, + ** this list of conditions and the following disclaimer in the documentation + ** and/or other materials provided with the distribution. + ** + ** * Neither the name of Apple, Inc. nor the names of its contributors + ** may be used to endorse or promote products derived from this software + ** without specific prior written permission. + ** + ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + ** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + ** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + ** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + ** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + ** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + ** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ** + ****************************************************************************/ + + +#import "qcocoaapplicationdelegate.h" +#import "qnswindowdelegate.h" +#include <qevent.h> +#include <qurl.h> +#include <qdebug.h> +#include <qguiapplication.h> + +QT_USE_NAMESPACE + +static QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) *sharedCocoaApplicationDelegate = nil; + +static void cleanupCocoaApplicationDelegate() +{ + [sharedCocoaApplicationDelegate release]; +} + +@implementation QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) + +- (id)init +{ + self = [super init]; + if (self) + inLaunch = true; + return self; +} + +- (void)dealloc +{ + sharedCocoaApplicationDelegate = nil; + [dockMenu release]; + [qtMenuLoader release]; + if (reflectionDelegate) { + [NSApp setDelegate:reflectionDelegate]; + [reflectionDelegate release]; + } + [super dealloc]; +} + ++ (id)allocWithZone:(NSZone *)zone +{ + @synchronized(self) { + if (sharedCocoaApplicationDelegate == nil) { + sharedCocoaApplicationDelegate = [super allocWithZone:zone]; + return sharedCocoaApplicationDelegate; + qAddPostRoutine(cleanupCocoaApplicationDelegate); + } + } + return nil; +} + ++ (QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate)*)sharedDelegate +{ + @synchronized(self) { + if (sharedCocoaApplicationDelegate == nil) + [[self alloc] init]; + } + return [[sharedCocoaApplicationDelegate retain] autorelease]; +} + +- (void)setDockMenu:(NSMenu*)newMenu +{ + [newMenu retain]; + [dockMenu release]; + dockMenu = newMenu; +} + +- (NSMenu *)applicationDockMenu +{ + return [[dockMenu retain] autorelease]; +} + +- (QApplicationPrivate *)qAppPrivate +{ + return qtPrivate; +} + +- (void)setQtPrivate:(QApplicationPrivate *)value +{ + qtPrivate = value; +} + +- (void)setMenuLoader:(QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *)menuLoader +{ + [menuLoader retain]; + [qtMenuLoader release]; + qtMenuLoader = menuLoader; +} + +- (QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *)menuLoader +{ + return [[qtMenuLoader retain] autorelease]; +} + +// This function will only be called when NSApp is actually running. Before +// that, the kAEQuitApplication Apple event will be sent to +// QApplicationPrivate::globalAppleEventProcessor in qapplication_mac.mm +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)sender +{ +/* + Q_UNUSED(sender); + // The reflection delegate gets precedence + if (reflectionDelegate + && [reflectionDelegate respondsToSelector:@selector(applicationShouldTerminate:)]) { + return [reflectionDelegate applicationShouldTerminate:sender]; + } + + if (qtPrivate->canQuit()) { + if (!startedQuit) { + startedQuit = true; + qAppInstance()->quit(); + startedQuit = false; + } + } + + if (qtPrivate->threadData->eventLoops.size() == 0) { + // INVARIANT: No event loop is executing. This probably + // means that Qt is used as a plugin, or as a part of a native + // Cocoa application. In any case it should be fine to + // terminate now: + return NSTerminateNow; + } + + return NSTerminateCancel; +*/ + return NSTerminateNow; +} + +- (void)applicationDidFinishLaunching:(NSNotification *)aNotification +{ + Q_UNUSED(aNotification); + inLaunch = false; + // qt_release_apple_event_handler(); + + + // Insert code here to initialize your application +} + + + +- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames +{ +/* + for (NSString *fileName in filenames) { + QString qtFileName = qt_mac_NSStringToQString(fileName); + if (inLaunch) { + // We need to be careful because Cocoa will be nice enough to take + // command line arguments and send them to us as events. Given the history + // of Qt Applications, this will result in behavior people don't want, as + // they might be doing the opening themselves with the command line parsing. + if (qApp->arguments().contains(qtFileName)) + continue; + } + QFileOpenEvent foe(qtFileName); + qt_sendSpontaneousEvent(qAppInstance(), &foe); + } + + if (reflectionDelegate && + [reflectionDelegate respondsToSelector:@selector(application:openFiles:)]) + [reflectionDelegate application:sender openFiles:filenames]; +*/ +} + +- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender +{ + // If we have a reflection delegate, that will get to call the shots. + if (reflectionDelegate + && [reflectionDelegate respondsToSelector: + @selector(applicationShouldTerminateAfterLastWindowClosed:)]) + return [reflectionDelegate applicationShouldTerminateAfterLastWindowClosed:sender]; + return NO; // Someday qApp->quitOnLastWindowClosed(); when QApp and NSApp work closer together. +} + + +- (void)applicationDidBecomeActive:(NSNotification *)notification +{ +/* + if (reflectionDelegate + && [reflectionDelegate respondsToSelector:@selector(applicationDidBecomeActive:)]) + [reflectionDelegate applicationDidBecomeActive:notification]; + + onApplicationChangedActivation(true); + + if (!QWidget::mouseGrabber()){ + // Update enter/leave immidiatly, don't wait for a move event. But only + // if no grab exists (even if the grab points to this widget, it seems, ref X11) + QPoint qlocal, qglobal; + QWidget *widgetUnderMouse = 0; + qt_mac_getTargetForMouseEvent(0, QEvent::Enter, qlocal, qglobal, 0, &widgetUnderMouse); + QApplicationPrivate::dispatchEnterLeave(widgetUnderMouse, 0); + qt_last_mouse_receiver = widgetUnderMouse; + qt_last_native_mouse_receiver = widgetUnderMouse ? + (widgetUnderMouse->internalWinId() ? widgetUnderMouse : widgetUnderMouse->nativeParentWidget()) : 0; + } +*/ +} + +- (void)applicationDidResignActive:(NSNotification *)notification +{ +/* + if (reflectionDelegate + && [reflectionDelegate respondsToSelector:@selector(applicationDidResignActive:)]) + [reflectionDelegate applicationDidResignActive:notification]; + + onApplicationChangedActivation(false); + + if (!QWidget::mouseGrabber()) + QApplicationPrivate::dispatchEnterLeave(0, qt_last_mouse_receiver); + qt_last_mouse_receiver = 0; + qt_last_native_mouse_receiver = 0; + qt_button_down = 0; +*/ +} + +- (void)applicationDidChangeScreenParameters:(NSNotification *)notification +{ + Q_UNUSED(notification); + //QDesktopWidgetImplementation::instance()->onResize(); +} + +- (void)setReflectionDelegate:(NSObject <NSApplicationDelegate> *)oldDelegate +{ + [oldDelegate retain]; + [reflectionDelegate release]; + reflectionDelegate = oldDelegate; +} + +- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector +{ + NSMethodSignature *result = [super methodSignatureForSelector:aSelector]; + if (!result && reflectionDelegate) { + result = [reflectionDelegate methodSignatureForSelector:aSelector]; + } + return result; +} + +- (BOOL)respondsToSelector:(SEL)aSelector +{ + BOOL result = [super respondsToSelector:aSelector]; + if (!result && reflectionDelegate) + result = [reflectionDelegate respondsToSelector:aSelector]; + return result; +} + +- (void)forwardInvocation:(NSInvocation *)invocation +{ + SEL invocationSelector = [invocation selector]; + if (reflectionDelegate && [reflectionDelegate respondsToSelector:invocationSelector]) + [invocation invokeWithTarget:reflectionDelegate]; + else + [self doesNotRecognizeSelector:invocationSelector]; +} + +- (void)getUrl:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent +{ + Q_UNUSED(replyEvent); +/* + NSString *urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; + QUrl url(qt_mac_NSStringToQString(urlString)); + QFileOpenEvent qtEvent(url); + qt_sendSpontaneousEvent(qAppInstance(), &qtEvent); +*/ +} + +- (void)appleEventQuit:(NSAppleEventDescriptor *)event withReplyEvent:(NSAppleEventDescriptor *)replyEvent +{ + Q_UNUSED(event); + Q_UNUSED(replyEvent); + qDebug() << "appleEventQuit"; + + [NSApp terminate:self]; +} + +- (void)qtDispatcherToQAction:(id)sender +{ + //[[NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)] qtDispatcherToQAction:sender]; +} + +@end diff --git a/src/plugins/platforms/cocoa/qcocoaautoreleasepool.h b/src/plugins/platforms/cocoa/qcocoaautoreleasepool.h index 862bc27f9d..359b5d34d0 100644 --- a/src/plugins/platforms/cocoa/qcocoaautoreleasepool.h +++ b/src/plugins/platforms/cocoa/qcocoaautoreleasepool.h @@ -42,6 +42,8 @@ #ifndef QCOCOAAUTORELEASEPOOL_H #define QCOCOAAUTORELEASEPOOL_H +#undef slots + #include <Cocoa/Cocoa.h> class QCocoaAutoReleasePool diff --git a/src/plugins/platforms/cocoa/qcocoawindowsurface.h b/src/plugins/platforms/cocoa/qcocoabackingstore.h index 95eea2b7ea..938e27347c 100644 --- a/src/plugins/platforms/cocoa/qcocoawindowsurface.h +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.h @@ -39,33 +39,31 @@ ** ****************************************************************************/ -#ifndef QWINDOWSURFACE_COCOA_H -#define QWINDOWSURFACE_COCOA_H +#ifndef QBACKINGSTORE_COCOA_H +#define QBACKINGSTORE_COCOA_H #include <Cocoa/Cocoa.h> #include "qcocoawindow.h" #include "qnsview.h" -#include <QtGui/private/qwindowsurface_p.h> +#include <QPlatformBackingStore> QT_BEGIN_NAMESPACE -class QCocoaWindowSurface : public QWindowSurface +class QCocoaBackingStore : public QPlatformBackingStore { public: - QCocoaWindowSurface(QWidget *window, WId wid); - ~QCocoaWindowSurface(); + QCocoaBackingStore(QWindow *window); + ~QCocoaBackingStore(); QPaintDevice *paintDevice(); - void flush(QWidget *widget, const QRegion ®ion, const QPoint &offset); - void resize (const QSize &size); + void flush(QWindow *widget, const QRegion ®ion, const QPoint &offset); + void resize (const QSize &size, const QRegion &); private: - QCocoaWindow *m_cocoaWindow; QImage *m_image; - QNSView *m_contentView; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoawindowsurface.mm b/src/plugins/platforms/cocoa/qcocoabackingstore.mm index 16bb327196..5a59fb5c49 100644 --- a/src/plugins/platforms/cocoa/qcocoawindowsurface.mm +++ b/src/plugins/platforms/cocoa/qcocoabackingstore.mm @@ -39,10 +39,9 @@ ** ****************************************************************************/ -#include "qcocoawindowsurface.h" +#include "qcocoabackingstore.h" #include <QtCore/qdebug.h> - #include <QtGui/QPainter> QT_BEGIN_NAMESPACE @@ -56,30 +55,28 @@ QRect flipedRect(const QRect &sourceRect,int height) return flippedRect; } -QCocoaWindowSurface::QCocoaWindowSurface(QWidget *window, WId wId) - : QWindowSurface(window) +QCocoaBackingStore::QCocoaBackingStore(QWindow *window) + : QPlatformBackingStore(window) { - m_cocoaWindow = static_cast<QCocoaWindow *>(window->platformWindow()); + m_cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); const QRect geo = window->geometry(); NSRect rect = NSMakeRect(geo.x(),geo.y(),geo.width(),geo.height()); - m_contentView = [[QNSView alloc] initWithWidget:window]; - m_cocoaWindow->setContentView(m_contentView); - m_image = new QImage(window->size(),QImage::Format_ARGB32); + m_image = new QImage(window->geometry().size(),QImage::Format_ARGB32); } -QCocoaWindowSurface::~QCocoaWindowSurface() +QCocoaBackingStore::~QCocoaBackingStore() { delete m_image; } -QPaintDevice *QCocoaWindowSurface::paintDevice() +QPaintDevice *QCocoaBackingStore::paintDevice() { return m_image; } -void QCocoaWindowSurface::flush(QWidget *widget, const QRegion ®ion, const QPoint &offset) +void QCocoaBackingStore::flush(QWindow *widget, const QRegion ®ion, const QPoint &offset) { Q_UNUSED(widget); Q_UNUSED(offset); @@ -87,17 +84,15 @@ void QCocoaWindowSurface::flush(QWidget *widget, const QRegion ®ion, const QP QRect geo = region.boundingRect(); NSRect rect = NSMakeRect(geo.x(), geo.y(), geo.width(), geo.height()); - [m_contentView displayRect:rect]; + [m_cocoaWindow->m_contentView displayRect:rect]; } -void QCocoaWindowSurface::resize(const QSize &size) +void QCocoaBackingStore::resize(const QSize &size, const QRegion &) { - QWindowSurface::resize(size); delete m_image; m_image = new QImage(size,QImage::Format_ARGB32_Premultiplied); NSSize newSize = NSMakeSize(size.width(),size.height()); - [m_contentView setImage:m_image]; - + [static_cast<QNSView *>(m_cocoaWindow->m_contentView) setImage:m_image]; } QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h new file mode 100644 index 0000000000..7184db84fa --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.h @@ -0,0 +1,220 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +#ifndef QEVENTDISPATCHER_MAC_P_H +#define QEVENTDISPATCHER_MAC_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include <QtCore/qhash.h> +#include <QtCore/qstack.h> +#include <QtGui/qwindowdefs.h> +#include <QtCore/private/qeventdispatcher_unix_p.h> + +#include <CoreFoundation/CoreFoundation.h> + +QT_BEGIN_NAMESPACE + +typedef struct _NSModalSession *NSModalSession; +typedef struct _QCocoaModalSessionInfo { + QPointer<QWindow> window; + NSModalSession session; + void *nswindow; +} QCocoaModalSessionInfo; + +class QCocoaEventDispatcherPrivate; +class QCocoaEventDispatcher : public QEventDispatcherUNIX +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QCocoaEventDispatcher) + +public: + QCocoaEventDispatcher(QAbstractEventDispatcherPrivate &priv, QObject *parent = 0); + explicit QCocoaEventDispatcher(QObject *parent = 0); + ~QCocoaEventDispatcher(); + + + bool processEvents(QEventLoop::ProcessEventsFlags flags); + bool hasPendingEvents(); + + void registerSocketNotifier(QSocketNotifier *notifier); + void unregisterSocketNotifier(QSocketNotifier *notifier); + + void registerTimer(int timerId, int interval, QObject *object); + bool unregisterTimer(int timerId); + bool unregisterTimers(QObject *object); + QList<TimerInfo> registeredTimers(QObject *object) const; + + void wakeUp(); + void interrupt(); + +private: + //friend void qt_mac_select_timer_callbk(__EventLoopTimer*, void*); + friend class QApplicationPrivate; +}; + +struct MacTimerInfo { + int id; + int interval; + QObject *obj; + bool pending; + CFRunLoopTimerRef runLoopTimer; + bool operator==(const MacTimerInfo &other) + { + return (id == other.id); + } +}; +typedef QHash<int, MacTimerInfo *> MacTimerHash; + +struct MacSocketInfo { + MacSocketInfo() : socket(0), runloop(0), readNotifier(0), writeNotifier(0) {} + CFSocketRef socket; + CFRunLoopSourceRef runloop; + QObject *readNotifier; + QObject *writeNotifier; +}; +typedef QHash<int, MacSocketInfo *> MacSocketHash; + +class QCocoaEventDispatcherPrivate : public QEventDispatcherUNIXPrivate +{ + Q_DECLARE_PUBLIC(QCocoaEventDispatcher) + +public: + QCocoaEventDispatcherPrivate(); + + static MacTimerHash macTimerHash; + // Set 'blockSendPostedEvents' to true if you _really_ need + // to make sure that qt events are not posted while calling + // low-level cocoa functions (like beginModalForWindow). And + // use a QBoolBlocker to be safe: + static bool blockSendPostedEvents; + // The following variables help organizing modal sessions: + static QStack<QCocoaModalSessionInfo> cocoaModalSessionStack; + static bool currentExecIsNSAppRun; + static bool nsAppRunCalledByQt; + static bool cleanupModalSessionsNeeded; + static NSModalSession currentModalSessionCached; + static NSModalSession currentModalSession(); + static void updateChildrenWorksWhenModal(); + static void temporarilyStopAllModalSessions(); + static void beginModalSession(QWindow *widget); + static void endModalSession(QWindow *widget); + static void cancelWaitForMoreEvents(); + static void cleanupModalSessions(); + static void ensureNSAppInitialized(); + + MacSocketHash macSockets; + QList<void *> queuedUserInputEvents; // NSEvent * + CFRunLoopSourceRef postedEventsSource; + CFRunLoopObserverRef waitingObserver; + CFRunLoopObserverRef firstTimeObserver; + QAtomicInt serialNumber; + int lastSerial; + static bool interrupt; +private: + static Boolean postedEventSourceEqualCallback(const void *info1, const void *info2); + static void postedEventsSourcePerformCallback(void *info); + static void activateTimer(CFRunLoopTimerRef, void *info); + static void waitingObserverCallback(CFRunLoopObserverRef observer, + CFRunLoopActivity activity, void *info); + static void firstLoopEntry(CFRunLoopObserverRef ref, CFRunLoopActivity activity, void *info); + friend void processPostedEvents(QCocoaEventDispatcherPrivate *const d, const bool blockSendPostedEvents); +}; + +class QtCocoaInterruptDispatcher : public QObject +{ + static QtCocoaInterruptDispatcher *instance; + bool cancelled; + + QtCocoaInterruptDispatcher(); + ~QtCocoaInterruptDispatcher(); + + public: + static void interruptLater(); + static void cancelInterruptLater(); +}; + +QT_END_NAMESPACE + +#endif // QEVENTDISPATCHER_MAC_P_H diff --git a/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm new file mode 100644 index 0000000000..9525b47c65 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoaeventdispatcher.mm @@ -0,0 +1,1123 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/**************************************************************************** +** +** Copyright (c) 2007-2008, Apple, Inc. +** +** All rights reserved. +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are met: +** +** * Redistributions of source code must retain the above copyright notice, +** this list of conditions and the following disclaimer. +** +** * Redistributions in binary form must reproduce the above copyright notice, +** this list of conditions and the following disclaimer in the documentation +** and/or other materials provided with the distribution. +** +** * Neither the name of Apple, Inc. nor the names of its contributors +** may be used to endorse or promote products derived from this software +** without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +** CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +** EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +** PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +** PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +** NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +** SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +** +****************************************************************************/ + +#include "qcocoaeventdispatcher.h" +#include "qcocoaautoreleasepool.h" + +#include "qguiapplication.h" +#include "qevent.h" +#include "qhash.h" +#include "qmutex.h" +#include "qsocketnotifier.h" +#include <qplatformwindow_qpa.h> +#include "private/qthread_p.h" +#include "private/qguiapplication_p.h" +#include <qdebug.h> + +#undef slots +#include <Cocoa/Cocoa.h> +#include <Carbon/Carbon.h> + +QT_BEGIN_NAMESPACE + +QT_USE_NAMESPACE + +enum { + QtCocoaEventSubTypeWakeup = SHRT_MAX, + QtCocoaEventSubTypePostMessage = SHRT_MAX-1 +}; + +static inline CFRunLoopRef mainRunLoop() +{ + return CFRunLoopGetMain(); +} + +/***************************************************************************** + Timers stuff + *****************************************************************************/ + +/* timer call back */ +void QCocoaEventDispatcherPrivate::activateTimer(CFRunLoopTimerRef, void *info) +{ + int timerID = +#ifdef Q_OS_MAC64 + qint64(info); +#else + int(info); +#endif + + MacTimerInfo *tmr; + tmr = macTimerHash.value(timerID); + if (tmr == 0 || tmr->pending == true) + return; // Can't send another timer event if it's pending. + + + if (blockSendPostedEvents) { + QCoreApplication::postEvent(tmr->obj, new QTimerEvent(tmr->id)); + } else { + tmr->pending = true; + QTimerEvent e(tmr->id); + + QCoreApplication::sendSpontaneousEvent(tmr->obj, &e); + // Get the value again in case the timer gets unregistered during the sendEvent. + tmr = macTimerHash.value(timerID); + if (tmr != 0) + tmr->pending = false; + } + +} + +void QCocoaEventDispatcher::registerTimer(int timerId, int interval, QObject *obj) +{ +#ifndef QT_NO_DEBUG + if (timerId < 1 || interval < 0 || !obj) { + qWarning("QEventDispatcherMac::registerTimer: invalid arguments"); + return; + } else if (obj->thread() != thread() || thread() != QThread::currentThread()) { + qWarning("QObject::startTimer: timers cannot be started from another thread"); + return; + } +#endif + + MacTimerInfo *t = new MacTimerInfo(); + t->id = timerId; + t->interval = interval; + t->obj = obj; + t->runLoopTimer = 0; + t->pending = false; + + CFAbsoluteTime fireDate = CFAbsoluteTimeGetCurrent(); + CFTimeInterval cfinterval = qMax(CFTimeInterval(interval) / 1000, 0.0000001); + fireDate += cfinterval; + QCocoaEventDispatcherPrivate::macTimerHash.insert(timerId, t); + CFRunLoopTimerContext info = { 0, (void *)timerId, 0, 0, 0 }; + t->runLoopTimer = CFRunLoopTimerCreate(0, fireDate, cfinterval, 0, 0, + QCocoaEventDispatcherPrivate::activateTimer, &info); + if (t->runLoopTimer == 0) { + qFatal("QEventDispatcherMac::registerTimer: Cannot create timer"); + } + CFRunLoopAddTimer(mainRunLoop(), t->runLoopTimer, kCFRunLoopCommonModes); +} + +bool QCocoaEventDispatcher::unregisterTimer(int identifier) +{ +#ifndef QT_NO_DEBUG + if (identifier < 1) { + qWarning("QEventDispatcherMac::unregisterTimer: invalid argument"); + return false; + } else if (thread() != QThread::currentThread()) { + qWarning("QObject::killTimer: timers cannot be stopped from another thread"); + return false; + } +#endif + if (identifier <= 0) + return false; // not init'd or invalid timer + + MacTimerInfo *timerInfo = QCocoaEventDispatcherPrivate::macTimerHash.take(identifier); + if (timerInfo == 0) + return false; + + if (!QObjectPrivate::get(timerInfo->obj)->inThreadChangeEvent) + QAbstractEventDispatcherPrivate::releaseTimerId(identifier); + CFRunLoopTimerInvalidate(timerInfo->runLoopTimer); + CFRelease(timerInfo->runLoopTimer); + delete timerInfo; + + return true; +} + +bool QCocoaEventDispatcher::unregisterTimers(QObject *obj) +{ +#ifndef QT_NO_DEBUG + if (!obj) { + qWarning("QEventDispatcherMac::unregisterTimers: invalid argument"); + return false; + } else if (obj->thread() != thread() || thread() != QThread::currentThread()) { + qWarning("QObject::killTimers: timers cannot be stopped from another thread"); + return false; + } +#endif + + MacTimerHash::iterator it = QCocoaEventDispatcherPrivate::macTimerHash.begin(); + while (it != QCocoaEventDispatcherPrivate::macTimerHash.end()) { + MacTimerInfo *timerInfo = it.value(); + if (timerInfo->obj != obj) { + ++it; + } else { + if (!QObjectPrivate::get(timerInfo->obj)->inThreadChangeEvent) + QAbstractEventDispatcherPrivate::releaseTimerId(timerInfo->id); + CFRunLoopTimerInvalidate(timerInfo->runLoopTimer); + CFRelease(timerInfo->runLoopTimer); + delete timerInfo; + it = QCocoaEventDispatcherPrivate::macTimerHash.erase(it); + } + } + return true; +} + +QList<QCocoaEventDispatcher::TimerInfo> +QCocoaEventDispatcher::registeredTimers(QObject *object) const +{ + if (!object) { + qWarning("QEventDispatcherMac:registeredTimers: invalid argument"); + return QList<TimerInfo>(); + } + + QList<TimerInfo> list; + + MacTimerHash::const_iterator it = QCocoaEventDispatcherPrivate::macTimerHash.constBegin(); + while (it != QCocoaEventDispatcherPrivate::macTimerHash.constEnd()) { + MacTimerInfo *t = it.value(); + if (t->obj == object) + list << TimerInfo(t->id, t->interval); + ++it; + } + return list; +} + +/************************************************************************** + Socket Notifiers + *************************************************************************/ +void qt_mac_socket_callback(CFSocketRef s, CFSocketCallBackType callbackType, CFDataRef, + const void *, void *info) { + QCocoaEventDispatcherPrivate *const eventDispatcher + = static_cast<QCocoaEventDispatcherPrivate *>(info); + int nativeSocket = CFSocketGetNative(s); + MacSocketInfo *socketInfo = eventDispatcher->macSockets.value(nativeSocket); + QEvent notifierEvent(QEvent::SockAct); + + // There is a race condition that happen where we disable the notifier and + // the kernel still has a notification to pass on. We then get this + // notification after we've successfully disabled the CFSocket, but our Qt + // notifier is now gone. The upshot is we have to check the notifier + // everytime. + if (callbackType == kCFSocketReadCallBack) { + if (socketInfo->readNotifier) + QGuiApplication::sendEvent(socketInfo->readNotifier, ¬ifierEvent); + } else if (callbackType == kCFSocketWriteCallBack) { + if (socketInfo->writeNotifier) + QGuiApplication::sendEvent(socketInfo->writeNotifier, ¬ifierEvent); + } +} + +/* + Adds a loop source for the given socket to the current run loop. +*/ +CFRunLoopSourceRef qt_mac_add_socket_to_runloop(const CFSocketRef socket) +{ + CFRunLoopSourceRef loopSource = CFSocketCreateRunLoopSource(kCFAllocatorDefault, socket, 0); + if (!loopSource) + return 0; + + CFRunLoopAddSource(mainRunLoop(), loopSource, kCFRunLoopCommonModes); + return loopSource; +} + +/* + Removes the loop source for the given socket from the current run loop. +*/ +void qt_mac_remove_socket_from_runloop(const CFSocketRef socket, CFRunLoopSourceRef runloop) +{ + Q_ASSERT(runloop); + CFRunLoopRemoveSource(mainRunLoop(), runloop, kCFRunLoopCommonModes); + CFSocketDisableCallBacks(socket, kCFSocketReadCallBack); + CFSocketDisableCallBacks(socket, kCFSocketWriteCallBack); + CFRunLoopSourceInvalidate(runloop); +} + +/* + Register a QSocketNotifier with the mac event system by creating a CFSocket with + with a read/write callback. + + Qt has separate socket notifiers for reading and writing, but on the mac there is + a limitation of one CFSocket object for each native socket. +*/ +void QCocoaEventDispatcher::registerSocketNotifier(QSocketNotifier *notifier) +{ + Q_ASSERT(notifier); + int nativeSocket = notifier->socket(); + int type = notifier->type(); +#ifndef QT_NO_DEBUG + if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) { + qWarning("QSocketNotifier: Internal error"); + return; + } else if (notifier->thread() != thread() + || thread() != QThread::currentThread()) { + qWarning("QSocketNotifier: socket notifiers cannot be enabled from another thread"); + return; + } +#endif + + Q_D(QCocoaEventDispatcher); + + if (type == QSocketNotifier::Exception) { + qWarning("QSocketNotifier::Exception is not supported on Mac OS X"); + return; + } + + // Check if we have a CFSocket for the native socket, create one if not. + MacSocketInfo *socketInfo = d->macSockets.value(nativeSocket); + if (!socketInfo) { + socketInfo = new MacSocketInfo(); + + // Create CFSocket, specify that we want both read and write callbacks (the callbacks + // are enabled/disabled later on). + const int callbackTypes = kCFSocketReadCallBack | kCFSocketWriteCallBack; + CFSocketContext context = {0, d, 0, 0, 0}; + socketInfo->socket = CFSocketCreateWithNative(kCFAllocatorDefault, nativeSocket, callbackTypes, qt_mac_socket_callback, &context); + if (CFSocketIsValid(socketInfo->socket) == false) { + qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to create CFSocket"); + return; + } + + CFOptionFlags flags = CFSocketGetSocketFlags(socketInfo->socket); + flags |= kCFSocketAutomaticallyReenableWriteCallBack; //QSocketNotifier stays enabled after a write + flags &= ~kCFSocketCloseOnInvalidate; //QSocketNotifier doesn't close the socket upon destruction/invalidation + CFSocketSetSocketFlags(socketInfo->socket, flags); + + // Add CFSocket to runloop. + if(!(socketInfo->runloop = qt_mac_add_socket_to_runloop(socketInfo->socket))) { + qWarning("QEventDispatcherMac::registerSocketNotifier: Failed to add CFSocket to runloop"); + CFSocketInvalidate(socketInfo->socket); + CFRelease(socketInfo->socket); + return; + } + + // Disable both callback types by default. This must be done after + // we add the CFSocket to the runloop, or else these calls will have + // no effect. + CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack); + CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack); + + d->macSockets.insert(nativeSocket, socketInfo); + } + + // Increment read/write counters and select enable callbacks if necessary. + if (type == QSocketNotifier::Read) { + Q_ASSERT(socketInfo->readNotifier == 0); + socketInfo->readNotifier = notifier; + CFSocketEnableCallBacks(socketInfo->socket, kCFSocketReadCallBack); + } else if (type == QSocketNotifier::Write) { + Q_ASSERT(socketInfo->writeNotifier == 0); + socketInfo->writeNotifier = notifier; + CFSocketEnableCallBacks(socketInfo->socket, kCFSocketWriteCallBack); + } +} + +/* + Unregister QSocketNotifer. The CFSocket correspoding to this notifier is + removed from the runloop of this is the last notifier that users + that CFSocket. +*/ +void QCocoaEventDispatcher::unregisterSocketNotifier(QSocketNotifier *notifier) +{ + Q_ASSERT(notifier); + int nativeSocket = notifier->socket(); + int type = notifier->type(); +#ifndef QT_NO_DEBUG + if (nativeSocket < 0 || nativeSocket > FD_SETSIZE) { + qWarning("QSocketNotifier: Internal error"); + return; + } else if (notifier->thread() != thread() || thread() != QThread::currentThread()) { + qWarning("QSocketNotifier: socket notifiers cannot be disabled from another thread"); + return; + } +#endif + + Q_D(QCocoaEventDispatcher); + + if (type == QSocketNotifier::Exception) { + qWarning("QSocketNotifier::Exception is not supported on Mac OS X"); + return; + } + MacSocketInfo *socketInfo = d->macSockets.value(nativeSocket); + if (!socketInfo) { + qWarning("QEventDispatcherMac::unregisterSocketNotifier: Tried to unregister a not registered notifier"); + return; + } + + // Decrement read/write counters and disable callbacks if necessary. + if (type == QSocketNotifier::Read) { + Q_ASSERT(notifier == socketInfo->readNotifier); + socketInfo->readNotifier = 0; + CFSocketDisableCallBacks(socketInfo->socket, kCFSocketReadCallBack); + } else if (type == QSocketNotifier::Write) { + Q_ASSERT(notifier == socketInfo->writeNotifier); + socketInfo->writeNotifier = 0; + CFSocketDisableCallBacks(socketInfo->socket, kCFSocketWriteCallBack); + } + + // Remove CFSocket from runloop if this was the last QSocketNotifier. + if (socketInfo->readNotifier == 0 && socketInfo->writeNotifier == 0) { + if (CFSocketIsValid(socketInfo->socket)) + qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop); + CFRunLoopSourceInvalidate(socketInfo->runloop); + CFRelease(socketInfo->runloop); + CFSocketInvalidate(socketInfo->socket); + CFRelease(socketInfo->socket); + delete socketInfo; + d->macSockets.remove(nativeSocket); + } +} + +bool QCocoaEventDispatcher::hasPendingEvents() +{ + extern uint qGlobalPostedEventsCount(); + extern bool qt_is_gui_used; //qapplication.cpp + return qGlobalPostedEventsCount() || (qt_is_gui_used && GetNumEventsInQueue(GetMainEventQueue())); +} + +static bool IsMouseOrKeyEvent( NSEvent* event ) +{ + bool result = false; + + switch( [event type] ) + { + case NSLeftMouseDown: + case NSLeftMouseUp: + case NSRightMouseDown: + case NSRightMouseUp: + case NSMouseMoved: // ?? + case NSLeftMouseDragged: + case NSRightMouseDragged: + case NSMouseEntered: + case NSMouseExited: + case NSKeyDown: + case NSKeyUp: + case NSFlagsChanged: // key modifiers changed? + case NSCursorUpdate: // ?? + case NSScrollWheel: + case NSTabletPoint: + case NSTabletProximity: + case NSOtherMouseDown: + case NSOtherMouseUp: + case NSOtherMouseDragged: +#ifndef QT_NO_GESTURES +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_6 + case NSEventTypeGesture: // touch events + case NSEventTypeMagnify: + case NSEventTypeSwipe: + case NSEventTypeRotate: + case NSEventTypeBeginGesture: + case NSEventTypeEndGesture: +#endif +#endif // QT_NO_GESTURES + result = true; + break; + + default: + break; + } + return result; +} + +static inline void qt_mac_waitForMoreEvents() +{ + // If no event exist in the cocoa event que, wait + // (and free up cpu time) until at least one event occur. + // This implementation is a bit on the edge, but seems to + // work fine: + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantFuture] + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event) + [NSApp postEvent:event atStart:YES]; +} + +static inline void qt_mac_waitForMoreModalSessionEvents() +{ + // If no event exist in the cocoa event que, wait + // (and free up cpu time) until at least one event occur. + // This implementation is a bit on the edge, but seems to + // work fine: + NSEvent* event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:[NSDate distantFuture] + inMode:NSModalPanelRunLoopMode + dequeue:YES]; + if (event) + [NSApp postEvent:event atStart:YES]; +} + +bool QCocoaEventDispatcher::processEvents(QEventLoop::ProcessEventsFlags flags) +{ + Q_D(QCocoaEventDispatcher); + d->interrupt = false; + + bool interruptLater = false; + QtCocoaInterruptDispatcher::cancelInterruptLater(); + + // In case we end up recursing while we now process events, make sure + // that we send remaining posted Qt events before this call returns: + wakeUp(); + emit awake(); + + bool excludeUserEvents = flags & QEventLoop::ExcludeUserInputEvents; + bool retVal = false; + forever { + if (d->interrupt) + break; + + QCocoaAutoReleasePool pool; + NSEvent* event = 0; + + // First, send all previously excluded input events, if any: + if (!excludeUserEvents) { + while (!d->queuedUserInputEvents.isEmpty()) { + event = static_cast<NSEvent *>(d->queuedUserInputEvents.takeFirst()); + if (!filterEvent(event)) { + [NSApp sendEvent:event]; + retVal = true; + } + [event release]; + } + } + + // If Qt is used as a plugin, or as an extension in a native cocoa + // application, we should not run or stop NSApplication; This will be + // done from the application itself. And if processEvents is called + // manually (rather than from a QEventLoop), we cannot enter a tight + // loop and block this call, but instead we need to return after one flush. + // Finally, if we are to exclude user input events, we cannot call [NSApp run] + // as we then loose control over which events gets dispatched: + const bool canExec_3rdParty = d->nsAppRunCalledByQt || ![NSApp isRunning]; + const bool canExec_Qt = !excludeUserEvents && + (flags & QEventLoop::DialogExec || flags & QEventLoop::EventLoopExec) ; + + if (canExec_Qt && canExec_3rdParty) { + // We can use exec-mode, meaning that we can stay in a tight loop until + // interrupted. This is mostly an optimization, but it allow us to use + // [NSApp run], which is the normal code path for cocoa applications. + if (NSModalSession session = d->currentModalSession()) { + QBoolBlocker execGuard(d->currentExecIsNSAppRun, false); + while ([NSApp runModalSession:session] == NSRunContinuesResponse && !d->interrupt) + qt_mac_waitForMoreModalSessionEvents(); + + if (!d->interrupt && session == d->currentModalSessionCached) { + // Someone called [NSApp stopModal:] from outside the event + // dispatcher (e.g to stop a native dialog). But that call wrongly stopped + // 'session' as well. As a result, we need to restart all internal sessions: + d->temporarilyStopAllModalSessions(); + } + } else { + d->nsAppRunCalledByQt = true; + QBoolBlocker execGuard(d->currentExecIsNSAppRun, true); + [NSApp run]; + } + retVal = true; + } else { + // We cannot block the thread (and run in a tight loop). + // Instead we will process all current pending events and return. + d->ensureNSAppInitialized(); + if (NSModalSession session = d->currentModalSession()) { + // INVARIANT: a modal window is executing. + if (!excludeUserEvents) { + // Since we can dispatch all kinds of events, we choose + // to use cocoa's native way of running modal sessions: + if (flags & QEventLoop::WaitForMoreEvents) + qt_mac_waitForMoreModalSessionEvents(); + NSInteger status = [NSApp runModalSession:session]; + if (status != NSRunContinuesResponse && session == d->currentModalSessionCached) { + // INVARIANT: Someone called [NSApp stopModal:] from outside the event + // dispatcher (e.g to stop a native dialog). But that call wrongly stopped + // 'session' as well. As a result, we need to restart all internal sessions: + d->temporarilyStopAllModalSessions(); + } + retVal = true; + } else do { + // Dispatch all non-user events (but que non-user events up for later). In + // this case, we need more control over which events gets dispatched, and + // cannot use [NSApp runModalSession:session]: + event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:nil + inMode:NSModalPanelRunLoopMode + dequeue: YES]; + + if (event) { + if (IsMouseOrKeyEvent(event)) { + [event retain]; + d->queuedUserInputEvents.append(event); + continue; + } + if (!filterEvent(event)) { + [NSApp sendEvent:event]; + retVal = true; + } + } + } while (!d->interrupt && event != nil); + } else do { + // INVARIANT: No modal window is executing. + event = [NSApp nextEventMatchingMask:NSAnyEventMask + untilDate:nil + inMode:NSDefaultRunLoopMode + dequeue: YES]; + + if (event) { + if (flags & QEventLoop::ExcludeUserInputEvents) { + if (IsMouseOrKeyEvent(event)) { + [event retain]; + d->queuedUserInputEvents.append(event); + continue; + } + } + if (!filterEvent(event)) { + [NSApp sendEvent:event]; + retVal = true; + } + } + } while (!d->interrupt && event != nil); + + // Be sure to flush the Qt posted events when not using exec mode + // (exec mode will always do this call from the event loop source): + if (!d->interrupt) + QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData); + + // Since the window that holds modality might have changed while processing + // events, we we need to interrupt when we return back the previous process + // event recursion to ensure that we spin the correct modal session. + // We do the interruptLater at the end of the function to ensure that we don't + // disturb the 'wait for more events' below (as deleteLater will post an event): + interruptLater = true; + } + bool canWait = (d->threadData->canWait + && !retVal + && !d->interrupt + && (flags & QEventLoop::WaitForMoreEvents)); + if (canWait) { + // INVARIANT: We haven't processed any events yet. And we're told + // to stay inside this function until at least one event is processed. + qt_mac_waitForMoreEvents(); + flags &= ~QEventLoop::WaitForMoreEvents; + } else { + // Done with event processing for now. + // Leave the function: + break; + } + } + + // If we're interrupted, we need to interrupt the _current_ + // recursion as well to check if it is still supposed to be + // executing. This way we wind down the stack until we land + // on a recursion that again calls processEvents (typically + // from QEventLoop), and set interrupt to false: + if (d->interrupt) + interrupt(); + + if (interruptLater) + QtCocoaInterruptDispatcher::interruptLater(); + + return retVal; +} + +void QCocoaEventDispatcher::wakeUp() +{ + Q_D(QCocoaEventDispatcher); + d->serialNumber.ref(); + CFRunLoopSourceSignal(d->postedEventsSource); + CFRunLoopWakeUp(mainRunLoop()); +} + +/***************************************************************************** + QEventDispatcherMac Implementation + *****************************************************************************/ +MacTimerHash QCocoaEventDispatcherPrivate::macTimerHash; +bool QCocoaEventDispatcherPrivate::blockSendPostedEvents = false; +bool QCocoaEventDispatcherPrivate::interrupt = false; + + +QStack<QCocoaModalSessionInfo> QCocoaEventDispatcherPrivate::cocoaModalSessionStack; +bool QCocoaEventDispatcherPrivate::currentExecIsNSAppRun = false; +bool QCocoaEventDispatcherPrivate::nsAppRunCalledByQt = false; +bool QCocoaEventDispatcherPrivate::cleanupModalSessionsNeeded = false; +NSModalSession QCocoaEventDispatcherPrivate::currentModalSessionCached = 0; + +void QCocoaEventDispatcherPrivate::ensureNSAppInitialized() +{ + // Some elements in Cocoa require NSApplication to be running before + // they get fully initialized, in particular the menu bar. This + // function is intended for cases where a dialog is told to execute before + // QGuiApplication::exec is called, or the application spins the events loop + // manually rather than calling QGuiApplication:exec. + // The function makes sure that NSApplication starts running, but stops + // it again as soon as the send posted events callback is called. That way + // we let Cocoa finish the initialization it seems to need. We'll only + // apply this trick at most once for any application, and we avoid doing it + // for the common case where main just starts QGuiApplication::exec. + if (nsAppRunCalledByQt || [NSApp isRunning]) + return; + nsAppRunCalledByQt = true; + QBoolBlocker block1(interrupt, true); + QBoolBlocker block2(currentExecIsNSAppRun, true); + [NSApp run]; +} + +void QCocoaEventDispatcherPrivate::temporarilyStopAllModalSessions() +{ + // Flush, and Stop, all created modal session, and as + // such, make them pending again. The next call to + // currentModalSession will recreate them again. The + // reason to stop all session like this is that otherwise + // a call [NSApp stop] would not stop NSApp, but rather + // the current modal session. So if we need to stop NSApp + // we need to stop all the modal session first. To avoid changing + // the stacking order of the windows while doing so, we put + // up a block that is used in QCocoaWindow and QCocoaPanel: + int stackSize = cocoaModalSessionStack.size(); + for (int i=0; i<stackSize; ++i) { + QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; + if (info.session) { + [NSApp endModalSession:info.session]; + info.session = 0; + } + } + currentModalSessionCached = 0; +} + +NSModalSession QCocoaEventDispatcherPrivate::currentModalSession() +{ + // If we have one or more modal windows, this function will create + // a session for each of those, and return the one for the top. + if (currentModalSessionCached) + return currentModalSessionCached; + + if (cocoaModalSessionStack.isEmpty()) + return 0; + + int sessionCount = cocoaModalSessionStack.size(); + for (int i=0; i<sessionCount; ++i) { + QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; + if (!info.window) + continue; +// ### port +// if (info.window->testAttribute(Qt::WA_DontShowOnScreen)) +// continue; + + if (!info.session) { + QCocoaAutoReleasePool pool; + NSWindow *window = reinterpret_cast<NSWindow *>(info.window->handle()->winId()); + if (!window) + continue; + + ensureNSAppInitialized(); + QBoolBlocker block1(blockSendPostedEvents, true); + info.nswindow = window; + [(NSWindow*) info.nswindow retain]; + int levelBeforeEnterModal = [window level]; + info.session = [NSApp beginModalSessionForWindow:window]; + // Make sure we don't stack the window lower that it was before + // entering modal, in case it e.g. had the stays-on-top flag set: + if (levelBeforeEnterModal > [window level]) + [window setLevel:levelBeforeEnterModal]; + } + currentModalSessionCached = info.session; + cleanupModalSessionsNeeded = false; + } + return currentModalSessionCached; +} + +static void setChildrenWorksWhenModal(QWindow *window, bool worksWhenModal) +{ + // For NSPanels (but not NSWindows, sadly), we can set the flag + // worksWhenModal, so that they are active even when they are not modal. +/* + ### not ported + QList<QDialog *> dialogs = window->findChildren<QDialog *>(); + for (int i=0; i<dialogs.size(); ++i){ + NSWindow *window = qt_mac_window_for(dialogs[i]); + if (window && [window isKindOfClass:[NSPanel class]]) { + [static_cast<NSPanel *>(window) setWorksWhenModal:worksWhenModal]; + if (worksWhenModal && [window isVisible]){ + [window orderFront:window]; + } + } + } +*/ +} + +void QCocoaEventDispatcherPrivate::updateChildrenWorksWhenModal() +{ + // Make the dialog children of the window + // active. And make the dialog children of + // the previous modal dialog unactive again: + QCocoaAutoReleasePool pool; + int size = cocoaModalSessionStack.size(); + if (size > 0){ + if (QWindow *prevModal = cocoaModalSessionStack[size-1].window) + setChildrenWorksWhenModal(prevModal, true); + if (size > 1){ + if (QWindow *prevModal = cocoaModalSessionStack[size-2].window) + setChildrenWorksWhenModal(prevModal, false); + } + } +} + +void QCocoaEventDispatcherPrivate::cleanupModalSessions() +{ + // Go through the list of modal sessions, and end those + // that no longer has a window assosiated; no window means + // the the session has logically ended. The reason we wait like + // this to actually end the sessions for real (rather than at the + // point they were marked as stopped), is that ending a session + // when no other session runs below it on the stack will make cocoa + // drop some events on the floor. + QCocoaAutoReleasePool pool; + int stackSize = cocoaModalSessionStack.size(); + + for (int i=stackSize-1; i>=0; --i) { + QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; + if (info.window) { + // This session has a window, and is therefore not marked + // as stopped. So just make it current. There might still be other + // stopped sessions on the stack, but those will be stopped on + // a later "cleanup" call. + currentModalSessionCached = info.session; + break; + } + cocoaModalSessionStack.remove(i); + currentModalSessionCached = 0; + if (info.session) { + [NSApp endModalSession:info.session]; + [(NSWindow *)info.nswindow release]; + } + } + + updateChildrenWorksWhenModal(); + cleanupModalSessionsNeeded = false; +} + +void QCocoaEventDispatcherPrivate::beginModalSession(QWindow *window) +{ + // Add a new, empty (null), NSModalSession to the stack. + // It will become active the next time QEventDispatcher::processEvents is called. + // A QCocoaModalSessionInfo is considered pending to become active if the window pointer + // is non-zero, and the session pointer is zero (it will become active upon a call to + // currentModalSession). A QCocoaModalSessionInfo is considered pending to be stopped if + // the window pointer is zero, and the session pointer is non-zero (it will be fully + // stopped in cleanupModalSessions()). + QCocoaModalSessionInfo info = {window, 0, 0}; + cocoaModalSessionStack.push(info); + updateChildrenWorksWhenModal(); + currentModalSessionCached = 0; +} + +void QCocoaEventDispatcherPrivate::endModalSession(QWindow *window) +{ + // Mark all sessions attached to window as pending to be stopped. We do this + // by setting the window pointer to zero, but leave the session pointer. + // We don't tell cocoa to stop any sessions just yet, because cocoa only understands + // when we stop the _current_ modal session (which is the session on top of + // the stack, and might not belong to 'window'). + int stackSize = cocoaModalSessionStack.size(); + for (int i=stackSize-1; i>=0; --i) { + QCocoaModalSessionInfo &info = cocoaModalSessionStack[i]; + if (info.window == window) { + info.window = 0; + if (i == stackSize-1) { + // The top sessions ended. Interrupt the event dispatcher + // to start spinning the correct session immidiatly: + currentModalSessionCached = 0; + cleanupModalSessionsNeeded = true; + QCocoaEventDispatcher::instance()->interrupt(); + } + } + } +} + +QCocoaEventDispatcherPrivate::QCocoaEventDispatcherPrivate() +{ +} + +QCocoaEventDispatcher::QCocoaEventDispatcher(QObject *parent) + : QEventDispatcherUNIX(*new QCocoaEventDispatcherPrivate, parent) +{ + Q_D(QCocoaEventDispatcher); + CFRunLoopSourceContext context; + bzero(&context, sizeof(CFRunLoopSourceContext)); + context.info = d; + context.equal = QCocoaEventDispatcherPrivate::postedEventSourceEqualCallback; + context.perform = QCocoaEventDispatcherPrivate::postedEventsSourcePerformCallback; + d->postedEventsSource = CFRunLoopSourceCreate(0, 0, &context); + Q_ASSERT(d->postedEventsSource); + CFRunLoopAddSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes); + + CFRunLoopObserverContext observerContext; + bzero(&observerContext, sizeof(CFRunLoopObserverContext)); + observerContext.info = this; + d->waitingObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, + kCFRunLoopBeforeWaiting | kCFRunLoopAfterWaiting, + true, 0, + QCocoaEventDispatcherPrivate::waitingObserverCallback, + &observerContext); + CFRunLoopAddObserver(mainRunLoop(), d->waitingObserver, kCFRunLoopCommonModes); + + /* The first cycle in the loop adds the source and the events of the source + are not processed. + We use an observer to process the posted events for the first + execution of the loop. */ + CFRunLoopObserverContext firstTimeObserverContext; + bzero(&firstTimeObserverContext, sizeof(CFRunLoopObserverContext)); + firstTimeObserverContext.info = d; + d->firstTimeObserver = CFRunLoopObserverCreate(kCFAllocatorDefault, + kCFRunLoopEntry, + /* repeats = */ false, + 0, + QCocoaEventDispatcherPrivate::firstLoopEntry, + &firstTimeObserverContext); + CFRunLoopAddObserver(mainRunLoop(), d->firstTimeObserver, kCFRunLoopCommonModes); +} + +void QCocoaEventDispatcherPrivate::waitingObserverCallback(CFRunLoopObserverRef, + CFRunLoopActivity activity, void *info) +{ + if (activity == kCFRunLoopBeforeWaiting) + emit static_cast<QCocoaEventDispatcher*>(info)->aboutToBlock(); + else + emit static_cast<QCocoaEventDispatcher*>(info)->awake(); +} + +Boolean QCocoaEventDispatcherPrivate::postedEventSourceEqualCallback(const void *info1, const void *info2) +{ + return info1 == info2; +} + +void processPostedEvents(QCocoaEventDispatcherPrivate *const d, const bool blockSendPostedEvents) +{ + if (blockSendPostedEvents) { + // We're told to not send posted events (because the event dispatcher + // is currently working on setting up the correct session to run). But + // we still need to make sure that we don't fall asleep until pending events + // are sendt, so we just signal this need, and return: + CFRunLoopSourceSignal(d->postedEventsSource); + return; + } + + if (d->cleanupModalSessionsNeeded) + d->cleanupModalSessions(); + + if (d->interrupt) { + if (d->currentExecIsNSAppRun) { + // The event dispatcher has been interrupted. But since + // [NSApplication run] is running the event loop, we + // delayed stopping it until now (to let cocoa process + // pending cocoa events first). + if (d->currentModalSessionCached) + d->temporarilyStopAllModalSessions(); + [NSApp stop:NSApp]; + d->cancelWaitForMoreEvents(); + } + return; + } + + if (!d->threadData->canWait || (d->serialNumber != d->lastSerial)) { + d->lastSerial = d->serialNumber; + QWindowSystemInterface::sendWindowSystemEvents(d->q_func(), QEventLoop::AllEvents); + } +} + +void QCocoaEventDispatcherPrivate::firstLoopEntry(CFRunLoopObserverRef ref, + CFRunLoopActivity activity, + void *info) +{ + Q_UNUSED(ref); + Q_UNUSED(activity); +/* + // This function is called when NSApplication has finished initialization, + // which appears to be just after [NSApplication run] has started to execute. + // By setting up our apple events handlers this late, we override the ones + // set up by NSApplication. + + // If Qt is used as a plugin, we let the 3rd party application handle events + // like quit and open file events. Otherwise, if we install our own handlers, we + // easily end up breaking functionallity the 3rd party application depend on: + if (QGuiApplication::testAttribute(Qt::AA_MacPluginApplication)) + return; + + QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) *newDelegate = [QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) sharedDelegate]; + NSAppleEventManager *eventManager = [NSAppleEventManager sharedAppleEventManager]; + [eventManager setEventHandler:newDelegate andSelector:@selector(appleEventQuit:withReplyEvent:) + forEventClass:kCoreEventClass andEventID:kAEQuitApplication]; + [eventManager setEventHandler:newDelegate andSelector:@selector(getUrl:withReplyEvent:) + forEventClass:kInternetEventClass andEventID:kAEGetURL]; +*/ + + processPostedEvents(static_cast<QCocoaEventDispatcherPrivate *>(info), blockSendPostedEvents); +} + +void QCocoaEventDispatcherPrivate::postedEventsSourcePerformCallback(void *info) +{ + processPostedEvents(static_cast<QCocoaEventDispatcherPrivate *>(info), blockSendPostedEvents); +} + +void QCocoaEventDispatcherPrivate::cancelWaitForMoreEvents() +{ + // In case the event dispatcher is waiting for more + // events somewhere, we post a dummy event to wake it up: + QCocoaAutoReleasePool pool; + [NSApp postEvent:[NSEvent otherEventWithType:NSApplicationDefined location:NSZeroPoint + modifierFlags:0 timestamp:0. windowNumber:0 context:0 + subtype:QtCocoaEventSubTypeWakeup data1:0 data2:0] atStart:NO]; +} + +void QCocoaEventDispatcher::interrupt() +{ + Q_D(QCocoaEventDispatcher); + d->interrupt = true; + wakeUp(); + + // We do nothing more here than setting d->interrupt = true, and + // poke the event loop if it is sleeping. Actually stopping + // NSApp, or the current modal session, is done inside the send + // posted events callback. We do this to ensure that all current pending + // cocoa events gets delivered before we stop. Otherwise, if we now stop + // the last event loop recursion, cocoa will just drop pending posted + // events on the floor before we get a chance to reestablish a new session. + d->cancelWaitForMoreEvents(); +} + +QCocoaEventDispatcher::~QCocoaEventDispatcher() +{ + Q_D(QCocoaEventDispatcher); + //timer cleanup + MacTimerHash::iterator it = QCocoaEventDispatcherPrivate::macTimerHash.begin(); + while (it != QCocoaEventDispatcherPrivate::macTimerHash.end()) { + MacTimerInfo *t = it.value(); + if (t->runLoopTimer) { + CFRunLoopTimerInvalidate(t->runLoopTimer); + CFRelease(t->runLoopTimer); + } + delete t; + ++it; + } + QCocoaEventDispatcherPrivate::macTimerHash.clear(); + + // Remove CFSockets from the runloop. + for (MacSocketHash::ConstIterator it = d->macSockets.constBegin(); it != d->macSockets.constEnd(); ++it) { + MacSocketInfo *socketInfo = (*it); + if (CFSocketIsValid(socketInfo->socket)) { + qt_mac_remove_socket_from_runloop(socketInfo->socket, socketInfo->runloop); + CFRunLoopSourceInvalidate(socketInfo->runloop); + CFRelease(socketInfo->runloop); + CFSocketInvalidate(socketInfo->socket); + CFRelease(socketInfo->socket); + } + } + CFRunLoopRemoveSource(mainRunLoop(), d->postedEventsSource, kCFRunLoopCommonModes); + CFRelease(d->postedEventsSource); + + CFRunLoopObserverInvalidate(d->waitingObserver); + CFRelease(d->waitingObserver); + + CFRunLoopObserverInvalidate(d->firstTimeObserver); + CFRelease(d->firstTimeObserver); +} + +QtCocoaInterruptDispatcher* QtCocoaInterruptDispatcher::instance = 0; + +QtCocoaInterruptDispatcher::QtCocoaInterruptDispatcher() : cancelled(false) +{ + // The whole point of this class is that we enable a way to interrupt + // the event dispatcher when returning back to a lower recursion level + // than where interruptLater was called. This is needed to detect if + // [NSApp run] should still be running at the recursion level it is at. + // Since the interrupt is canceled if processEvents is called before + // this object gets deleted, we also avoid interrupting unnecessary. + deleteLater(); +} + +QtCocoaInterruptDispatcher::~QtCocoaInterruptDispatcher() +{ + if (cancelled) + return; + instance = 0; + QCocoaEventDispatcher::instance()->interrupt(); +} + +void QtCocoaInterruptDispatcher::cancelInterruptLater() +{ + if (!instance) + return; + instance->cancelled = true; + delete instance; + instance = 0; +} + +void QtCocoaInterruptDispatcher::interruptLater() +{ + cancelInterruptLater(); + instance = new QtCocoaInterruptDispatcher; +} + +QT_END_NAMESPACE + diff --git a/src/plugins/platforms/cocoa/qcocoaeventloopintegration.mm b/src/plugins/platforms/cocoa/qcocoaeventloopintegration.mm deleted file mode 100644 index ac0b75e9ea..0000000000 --- a/src/plugins/platforms/cocoa/qcocoaeventloopintegration.mm +++ /dev/null @@ -1,112 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). -** All rights reserved. -** Contact: Nokia Corporation (qt-info@nokia.com) -** -** This file is part of the plugins of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** GNU Lesser General Public License Usage -** This file may be used under the terms of the GNU Lesser General Public -** License version 2.1 as published by the Free Software Foundation and -** appearing in the file LICENSE.LGPL included in the packaging of this -** file. Please review the following information to ensure the GNU Lesser -** General Public License version 2.1 requirements will be met: -** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Nokia gives you certain additional -** rights. These rights are described in the Nokia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU General -** Public License version 3.0 as published by the Free Software Foundation -** and appearing in the file LICENSE.GPL included in the packaging of this -** file. Please review the following information to ensure the GNU General -** Public License version 3.0 requirements will be met: -** http://www.gnu.org/copyleft/gpl.html. -** -** Other Usage -** Alternatively, this file may be used in accordance with the terms and -** conditions contained in a signed written agreement between you and Nokia. -** -** -** -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "qcocoaeventloopintegration.h" - -#import <Cocoa/Cocoa.h> - -#include "qcocoaautoreleasepool.h" - -#include <QtCore/QElapsedTimer> - -#include <QDebug> -#include <QApplication> - -void wakeupCallback ( void * ) { - QPlatformEventLoopIntegration::processEvents(); -} - -void timerCallback( CFRunLoopTimerRef timer, void *info) -{ - QPlatformEventLoopIntegration::processEvents(); - QCocoaEventLoopIntegration *eventLoopIntegration = - static_cast<QCocoaEventLoopIntegration *>(info); - qint64 nextTime = eventLoopIntegration->nextTimerEvent(); - CFAbsoluteTime nexttime = CFAbsoluteTimeGetCurrent(); - nexttime = nexttime + (double(nextTime)/1000); - CFRunLoopTimerSetNextFireDate(timer,nexttime); -} - -QCocoaEventLoopIntegration::QCocoaEventLoopIntegration() : - QPlatformEventLoopIntegration() -{ - [NSApplication sharedApplication]; - m_sourceContext.version = 0; - m_sourceContext.info = this; - m_sourceContext.retain = 0; - m_sourceContext.release = 0; - m_sourceContext.copyDescription = 0; - m_sourceContext.equal = 0; - m_sourceContext.hash = 0; - m_sourceContext.schedule = 0; - m_sourceContext.cancel = 0; - m_sourceContext.perform = wakeupCallback; - - m_source = CFRunLoopSourceCreate(0,0,&m_sourceContext); - CFRunLoopAddSource(CFRunLoopGetMain(),m_source,kCFRunLoopCommonModes); - - m_timerContext.version = 0; - m_timerContext.info = this; - m_timerContext.retain = 0; - m_timerContext.release = 0; - m_timerContext.copyDescription = 0; - CFAbsoluteTime fireDate = CFAbsoluteTimeGetCurrent (); - CFTimeInterval interval = 30; - - CFRunLoopTimerRef m_timerSource = CFRunLoopTimerCreate(0,fireDate,interval,0,0,timerCallback,&m_timerContext); - CFRunLoopAddTimer(CFRunLoopGetMain(),m_timerSource,kCFRunLoopCommonModes); -} - -void QCocoaEventLoopIntegration::startEventLoop() -{ - [[NSApplication sharedApplication] run]; -} - -void QCocoaEventLoopIntegration::quitEventLoop() -{ - [[NSApplication sharedApplication] terminate:nil]; -} - -void QCocoaEventLoopIntegration::qtNeedsToProcessEvents() -{ - CFRunLoopSourceSignal(m_source); -} - diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.h b/src/plugins/platforms/cocoa/qcocoaglcontext.h new file mode 100644 index 0000000000..1b84e7b305 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.h @@ -0,0 +1,43 @@ +#ifndef QCOCOAGLCONTEXT_H +#define QCOCOAGLCONTEXT_H + +#include <QtCore/QWeakPointer> +#include <QtGui/QPlatformOpenGLContext> +#include <QtGui/QOpenGLContext> +#include <QtGui/QWindow> + +#undef slots +#include <Cocoa/Cocoa.h> + +QT_BEGIN_NAMESPACE + +class QCocoaGLContext : public QPlatformOpenGLContext +{ +public: + QCocoaGLContext(const QSurfaceFormat &format, QPlatformOpenGLContext *share); + + QSurfaceFormat format() const; + + void swapBuffers(QPlatformSurface *surface); + + bool makeCurrent(QPlatformSurface *surface); + void doneCurrent(); + + void (*getProcAddress(const QByteArray &procName)) (); + + void update(); + + static NSOpenGLPixelFormat *createNSOpenGLPixelFormat(); + NSOpenGLContext *nsOpenGLContext() const; + +private: + void setActiveWindow(QWindow *window); + + NSOpenGLContext *m_context; + QSurfaceFormat m_format; + QWeakPointer<QWindow> m_currentWindow; +}; + +QT_END_NAMESPACE + +#endif // QCOCOAGLCONTEXT_H diff --git a/src/plugins/platforms/cocoa/qcocoaglcontext.mm b/src/plugins/platforms/cocoa/qcocoaglcontext.mm new file mode 100644 index 0000000000..8b07315378 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoaglcontext.mm @@ -0,0 +1,99 @@ +#include "qcocoaglcontext.h" +#include "qcocoawindow.h" +#include "qcocoaautoreleasepool.h" +#include <qdebug.h> +#include <QtCore/private/qcore_mac_p.h> +#include <QtPlatformSupport/private/cglconvenience_p.h> + +#import <Cocoa/Cocoa.h> + +QCocoaGLContext::QCocoaGLContext(const QSurfaceFormat &format, QPlatformOpenGLContext *share) + : m_format(format) +{ + QCocoaAutoReleasePool pool; // For the SG Canvas render thread. + + NSOpenGLPixelFormat *pixelFormat = static_cast <NSOpenGLPixelFormat *>(qcgl_createNSOpenGLPixelFormat()); + NSOpenGLContext *actualShare = share ? static_cast<QCocoaGLContext *>(share)->m_context : 0; + + m_context = [NSOpenGLContext alloc]; + [m_context initWithFormat:pixelFormat shareContext:actualShare]; + + const GLint interval = 1; + [m_context setValues:&interval forParameter:NSOpenGLCPSwapInterval]; + +} + +// Match up with createNSOpenGLPixelFormat! +QSurfaceFormat QCocoaGLContext::format() const +{ + return m_format; +} + +void QCocoaGLContext::swapBuffers(QPlatformSurface *surface) +{ + QWindow *window = static_cast<QCocoaWindow *>(surface)->window(); + setActiveWindow(window); + + [m_context flushBuffer]; +} + +bool QCocoaGLContext::makeCurrent(QPlatformSurface *surface) +{ + QCocoaAutoReleasePool pool; + + QWindow *window = static_cast<QCocoaWindow *>(surface)->window(); + setActiveWindow(window); + + [m_context makeCurrentContext]; + return true; +} + +void QCocoaGLContext::setActiveWindow(QWindow *window) +{ + if (window == m_currentWindow.data()) + return; + + if (m_currentWindow) + static_cast<QCocoaWindow *>(m_currentWindow.data()->handle())->setCurrentContext(0); + + Q_ASSERT(window->handle()); + + m_currentWindow = window; + + QCocoaWindow *cocoaWindow = static_cast<QCocoaWindow *>(window->handle()); + cocoaWindow->setCurrentContext(this); + + NSView *view = cocoaWindow->contentView(); + [m_context setView:view]; +} + +void QCocoaGLContext::doneCurrent() +{ + if (m_currentWindow) + static_cast<QCocoaWindow *>(m_currentWindow.data()->handle())->setCurrentContext(0); + + m_currentWindow.clear(); + + [NSOpenGLContext clearCurrentContext]; +} + +void (*QCocoaGLContext::getProcAddress(const QByteArray &procName))() +{ + return qcgl_getProcAddress(procName); +} + +void QCocoaGLContext::update() +{ + [m_context update]; +} + +NSOpenGLPixelFormat *QCocoaGLContext::createNSOpenGLPixelFormat() +{ + return static_cast<NSOpenGLPixelFormat *>(qcgl_createNSOpenGLPixelFormat()); +} + +NSOpenGLContext *QCocoaGLContext::nsOpenGLContext() const +{ + return m_context; +} + diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.h b/src/plugins/platforms/cocoa/qcocoahelpers.h new file mode 100644 index 0000000000..8e807cc288 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoahelpers.h @@ -0,0 +1,90 @@ +/**************************************************************************** + ** + ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). + ** All rights reserved. + ** Contact: Nokia Corporation (qt-info@nokia.com) + ** + ** This file is part of the plugins of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL$ + ** GNU Lesser General Public License Usage + ** This file may be used under the terms of the GNU Lesser General Public + ** License version 2.1 as published by the Free Software Foundation and + ** appearing in the file LICENSE.LGPL included in the packaging of this + ** file. Please review the following information to ensure the GNU Lesser + ** General Public License version 2.1 requirements will be met: + ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. + ** + ** In addition, as a special exception, Nokia gives you certain additional + ** rights. These rights are described in the Nokia Qt LGPL Exception + ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. + ** + ** GNU General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU General + ** Public License version 3.0 as published by the Free Software Foundation + ** and appearing in the file LICENSE.GPL included in the packaging of this + ** file. Please review the following information to ensure the GNU General + ** Public License version 3.0 requirements will be met: + ** http://www.gnu.org/copyleft/gpl.html. + ** + ** Other Usage + ** Alternatively, this file may be used in accordance with the terms and + ** conditions contained in a signed written agreement between you and Nokia. + ** + ** + ** + ** + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +#ifndef QCOCOAHELPERS_H +#define QCOCOAHELPERS_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It provides helper functions +// for the Cocoa lighthouse plugin. This header file may +// change from version to version without notice, or even be removed. +// +// We mean it. +// + +#include <private/qt_mac_p.h> + +class QPixmap; +class QString; + +// Conversion functions +QStringList qt_mac_NSArrayToQStringList(void *nsarray); +void *qt_mac_QStringListToNSMutableArrayVoid(const QStringList &list); + +inline NSMutableArray *qt_mac_QStringListToNSMutableArray(const QStringList &qstrlist) +{ return reinterpret_cast<NSMutableArray *>(qt_mac_QStringListToNSMutableArrayVoid(qstrlist)); } + +inline QString qt_mac_NSStringToQString(const NSString *nsstr) +{ return QCFString::toQString(reinterpret_cast<const CFStringRef>(nsstr)); } + +inline NSString *qt_mac_QStringToNSString(const QString &qstr) +{ return [const_cast<NSString *>(reinterpret_cast<const NSString *>(QCFString::toCFStringRef(qstr))) autorelease]; } + +CGImageRef qt_mac_image_to_cgimage(const QImage &image); +NSImage *qt_mac_cgimage_to_nsimage(CGImageRef iamge); +NSImage *qt_mac_create_nsimage(const QPixmap &pm); + +QChar qt_mac_qtKey2CocoaKey(Qt::Key key); +Qt::Key qt_mac_cocoaKey2QtKey(QChar keyCode); + +// Misc +void qt_mac_transformProccessToForegroundApplication(); +QString qt_mac_removeMnemonics(const QString &original); +CGColorSpaceRef qt_mac_genericColorSpace(); +CGColorSpaceRef qt_mac_displayColorSpace(const QWidget *widget); +QString qt_mac_applicationName(); + + +#endif //QCOCOAHELPERS_H + diff --git a/src/plugins/platforms/cocoa/qcocoahelpers.mm b/src/plugins/platforms/cocoa/qcocoahelpers.mm new file mode 100644 index 0000000000..03e83f1130 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoahelpers.mm @@ -0,0 +1,448 @@ +/**************************************************************************** + ** + ** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). + ** All rights reserved. + ** Contact: Nokia Corporation (qt-info@nokia.com) + ** + ** This file is part of the plugins of the Qt Toolkit. + ** + ** $QT_BEGIN_LICENSE:LGPL$ + ** GNU Lesser General Public License Usage + ** This file may be used under the terms of the GNU Lesser General Public + ** License version 2.1 as published by the Free Software Foundation and + ** appearing in the file LICENSE.LGPL included in the packaging of this + ** file. Please review the following information to ensure the GNU Lesser + ** General Public License version 2.1 requirements will be met: + ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. + ** + ** In addition, as a special exception, Nokia gives you certain additional + ** rights. These rights are described in the Nokia Qt LGPL Exception + ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. + ** + ** GNU General Public License Usage + ** Alternatively, this file may be used under the terms of the GNU General + ** Public License version 3.0 as published by the Free Software Foundation + ** and appearing in the file LICENSE.GPL included in the packaging of this + ** file. Please review the following information to ensure the GNU General + ** Public License version 3.0 requirements will be met: + ** http://www.gnu.org/copyleft/gpl.html. + ** + ** Other Usage + ** Alternatively, this file may be used in accordance with the terms and + ** conditions contained in a signed written agreement between you and Nokia. + ** + ** + ** + ** + ** + ** $QT_END_LICENSE$ + ** + ****************************************************************************/ + +#include "qcocoahelpers.h" + +#include "qcocoaautoreleasepool.h" + +#include <QtCore> +#include <QtGui> + +// +// Conversion Functions +// + +QStringList qt_mac_NSArrayToQStringList(void *nsarray) +{ + QStringList result; + NSArray *array = static_cast<NSArray *>(nsarray); + for (NSUInteger i=0; i<[array count]; ++i) + result << qt_mac_NSStringToQString([array objectAtIndex:i]); + return result; +} + +void *qt_mac_QStringListToNSMutableArrayVoid(const QStringList &list) +{ + NSMutableArray *result = [NSMutableArray arrayWithCapacity:list.size()]; + for (int i=0; i<list.size(); ++i){ + [result addObject:reinterpret_cast<const NSString *>(QCFString::toCFStringRef(list[i]))]; + } + return result; +} + +static void drawImageReleaseData (void *info, const void *, size_t) +{ + delete static_cast<QImage *>(info); +} + +CGImageRef qt_mac_image_to_cgimage(const QImage &img) +{ + QImage *image; + if (img.depth() != 32) + image = new QImage(img.convertToFormat(QImage::Format_ARGB32_Premultiplied)); + else + image = new QImage(img); + + uint cgflags = kCGImageAlphaNone; + switch (image->format()) { + case QImage::Format_ARGB32_Premultiplied: + cgflags = kCGImageAlphaPremultipliedFirst; + break; + case QImage::Format_ARGB32: + cgflags = kCGImageAlphaFirst; + break; + case QImage::Format_RGB32: + cgflags = kCGImageAlphaNoneSkipFirst; + default: + break; + } + cgflags |= kCGBitmapByteOrder32Host; + QCFType<CGDataProviderRef> dataProvider = CGDataProviderCreateWithData(image, + static_cast<const QImage *>(image)->bits(), + image->byteCount(), + drawImageReleaseData); + + return CGImageCreate(image->width(), image->height(), 8, 32, + image->bytesPerLine(), + qt_mac_genericColorSpace(), + cgflags, dataProvider, 0, false, kCGRenderingIntentDefault); + +} + +NSImage *qt_mac_cgimage_to_nsimage(CGImageRef image) +{ + QCocoaAutoReleasePool pool; + NSImage *newImage = 0; + NSRect imageRect = NSMakeRect(0.0, 0.0, CGImageGetWidth(image), CGImageGetHeight(image)); + newImage = [[NSImage alloc] initWithSize:imageRect.size]; + [newImage lockFocus]; + { + CGContextRef imageContext = (CGContextRef)[[NSGraphicsContext currentContext] graphicsPort]; + CGContextDrawImage(imageContext, *(CGRect*)&imageRect, image); + } + [newImage unlockFocus]; + return newImage; +} + +NSImage *qt_mac_create_nsimage(const QPixmap &pm) +{ + QImage image = pm.toImage(); + return qt_mac_cgimage_to_nsimage(qt_mac_image_to_cgimage(image)); +} + + +// Use this method to keep all the information in the TextSegment. As long as it is ordered +// we are in OK shape, and we can influence that ourselves. +struct KeyPair +{ + QChar cocoaKey; + Qt::Key qtKey; +}; + +bool operator==(const KeyPair &entry, QChar qchar) +{ + return entry.cocoaKey == qchar; +} + +bool operator<(const KeyPair &entry, QChar qchar) +{ + return entry.cocoaKey < qchar; +} + +bool operator<(QChar qchar, const KeyPair &entry) +{ + return qchar < entry.cocoaKey; +} + +bool operator<(const Qt::Key &key, const KeyPair &entry) +{ + return key < entry.qtKey; +} + +bool operator<(const KeyPair &entry, const Qt::Key &key) +{ + return entry.qtKey < key; +} + +static bool qtKey2CocoaKeySortLessThan(const KeyPair &entry1, const KeyPair &entry2) +{ + return entry1.qtKey < entry2.qtKey; +} + +static const int NumEntries = 59; +static const KeyPair entries[NumEntries] = { + { NSEnterCharacter, Qt::Key_Enter }, + { NSBackspaceCharacter, Qt::Key_Backspace }, + { NSTabCharacter, Qt::Key_Tab }, + { NSNewlineCharacter, Qt::Key_Return }, + { NSCarriageReturnCharacter, Qt::Key_Return }, + { NSBackTabCharacter, Qt::Key_Backtab }, + { kEscapeCharCode, Qt::Key_Escape }, + // Cocoa sends us delete when pressing backspace! + // (NB when we reverse this list in qtKey2CocoaKey, there + // will be two indices of Qt::Key_Backspace. But is seems to work + // ok for menu shortcuts (which uses that function): + { NSDeleteCharacter, Qt::Key_Backspace }, + { NSUpArrowFunctionKey, Qt::Key_Up }, + { NSDownArrowFunctionKey, Qt::Key_Down }, + { NSLeftArrowFunctionKey, Qt::Key_Left }, + { NSRightArrowFunctionKey, Qt::Key_Right }, + { NSF1FunctionKey, Qt::Key_F1 }, + { NSF2FunctionKey, Qt::Key_F2 }, + { NSF3FunctionKey, Qt::Key_F3 }, + { NSF4FunctionKey, Qt::Key_F4 }, + { NSF5FunctionKey, Qt::Key_F5 }, + { NSF6FunctionKey, Qt::Key_F6 }, + { NSF7FunctionKey, Qt::Key_F7 }, + { NSF8FunctionKey, Qt::Key_F8 }, + { NSF9FunctionKey, Qt::Key_F8 }, + { NSF10FunctionKey, Qt::Key_F10 }, + { NSF11FunctionKey, Qt::Key_F11 }, + { NSF12FunctionKey, Qt::Key_F12 }, + { NSF13FunctionKey, Qt::Key_F13 }, + { NSF14FunctionKey, Qt::Key_F14 }, + { NSF15FunctionKey, Qt::Key_F15 }, + { NSF16FunctionKey, Qt::Key_F16 }, + { NSF17FunctionKey, Qt::Key_F17 }, + { NSF18FunctionKey, Qt::Key_F18 }, + { NSF19FunctionKey, Qt::Key_F19 }, + { NSF20FunctionKey, Qt::Key_F20 }, + { NSF21FunctionKey, Qt::Key_F21 }, + { NSF22FunctionKey, Qt::Key_F22 }, + { NSF23FunctionKey, Qt::Key_F23 }, + { NSF24FunctionKey, Qt::Key_F24 }, + { NSF25FunctionKey, Qt::Key_F25 }, + { NSF26FunctionKey, Qt::Key_F26 }, + { NSF27FunctionKey, Qt::Key_F27 }, + { NSF28FunctionKey, Qt::Key_F28 }, + { NSF29FunctionKey, Qt::Key_F29 }, + { NSF30FunctionKey, Qt::Key_F30 }, + { NSF31FunctionKey, Qt::Key_F31 }, + { NSF32FunctionKey, Qt::Key_F32 }, + { NSF33FunctionKey, Qt::Key_F33 }, + { NSF34FunctionKey, Qt::Key_F34 }, + { NSF35FunctionKey, Qt::Key_F35 }, + { NSInsertFunctionKey, Qt::Key_Insert }, + { NSDeleteFunctionKey, Qt::Key_Delete }, + { NSHomeFunctionKey, Qt::Key_Home }, + { NSEndFunctionKey, Qt::Key_End }, + { NSPageUpFunctionKey, Qt::Key_PageUp }, + { NSPageDownFunctionKey, Qt::Key_PageDown }, + { NSPrintScreenFunctionKey, Qt::Key_Print }, + { NSScrollLockFunctionKey, Qt::Key_ScrollLock }, + { NSPauseFunctionKey, Qt::Key_Pause }, + { NSSysReqFunctionKey, Qt::Key_SysReq }, + { NSMenuFunctionKey, Qt::Key_Menu }, + { NSHelpFunctionKey, Qt::Key_Help }, +}; +static const KeyPair * const end = entries + NumEntries; + +QChar qt_mac_qtKey2CocoaKey(Qt::Key key) +{ + // The first time this function is called, create a reverse + // looup table sorted on Qt Key rather than Cocoa key: + static QVector<KeyPair> rev_entries(NumEntries); + static bool mustInit = true; + if (mustInit){ + mustInit = false; + for (int i=0; i<NumEntries; ++i) + rev_entries[i] = entries[i]; + qSort(rev_entries.begin(), rev_entries.end(), qtKey2CocoaKeySortLessThan); + } + const QVector<KeyPair>::iterator i + = qBinaryFind(rev_entries.begin(), rev_entries.end(), key); + if (i == rev_entries.end()) + return QChar(); + return i->cocoaKey; +} + +Qt::Key qt_mac_cocoaKey2QtKey(QChar keyCode) +{ + const KeyPair *i = qBinaryFind(entries, end, keyCode); + if (i == end) + return Qt::Key(keyCode.unicode()); + return i->qtKey; +} + +// +// Misc +// + +// Changes the process type for this process to kProcessTransformToForegroundApplication, +// unless either LSUIElement or LSBackgroundOnly is set in the Info.plist. +void qt_mac_transformProccessToForegroundApplication() +{ + ProcessSerialNumber psn; + if (GetCurrentProcess(&psn) == noErr) { + bool forceTransform = true; + CFTypeRef value = CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(), + CFSTR("LSUIElement")); + if (value) { + CFTypeID valueType = CFGetTypeID(value); + // Officially it's supposed to be a string, a boolean makes sense, so we'll check. + // A number less so, but OK. + if (valueType == CFStringGetTypeID()) + forceTransform = !(QCFString::toQString(static_cast<CFStringRef>(value)).toInt()); + else if (valueType == CFBooleanGetTypeID()) + forceTransform = !CFBooleanGetValue(static_cast<CFBooleanRef>(value)); + else if (valueType == CFNumberGetTypeID()) { + int valueAsInt; + CFNumberGetValue(static_cast<CFNumberRef>(value), kCFNumberIntType, &valueAsInt); + forceTransform = !valueAsInt; + } + } + + if (forceTransform) { + value = CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(), + CFSTR("LSBackgroundOnly")); + if (value) { + CFTypeID valueType = CFGetTypeID(value); + if (valueType == CFBooleanGetTypeID()) + forceTransform = !CFBooleanGetValue(static_cast<CFBooleanRef>(value)); + else if (valueType == CFStringGetTypeID()) + forceTransform = !(QCFString::toQString(static_cast<CFStringRef>(value)).toInt()); + else if (valueType == CFNumberGetTypeID()) { + int valueAsInt; + CFNumberGetValue(static_cast<CFNumberRef>(value), kCFNumberIntType, &valueAsInt); + forceTransform = !valueAsInt; + } + } + } + + if (forceTransform) { + TransformProcessType(&psn, kProcessTransformToForegroundApplication); + } + } +} + +QString qt_mac_removeMnemonics(const QString &original) +{ + QString returnText(original.size(), 0); + int finalDest = 0; + int currPos = 0; + int l = original.length(); + while (l) { + if (original.at(currPos) == QLatin1Char('&') + && (l == 1 || original.at(currPos + 1) != QLatin1Char('&'))) { + ++currPos; + --l; + if (l == 0) + break; + } + returnText[finalDest] = original.at(currPos); + ++currPos; + ++finalDest; + --l; + } + returnText.truncate(finalDest); + return returnText; +} + + +CGColorSpaceRef m_genericColorSpace = 0; +QHash<CGDirectDisplayID, CGColorSpaceRef> m_displayColorSpaceHash; +bool m_postRoutineRegistered = false; + +CGColorSpaceRef qt_mac_genericColorSpace() +{ +#if 0 + if (!m_genericColorSpace) { +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_4 + if (QSysInfo::MacintoshVersion >= QSysInfo::MV_10_4) { + m_genericColorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericRGB); + } else +#endif + { + m_genericColorSpace = CGColorSpaceCreateDeviceRGB(); + } + if (!m_postRoutineRegistered) { + m_postRoutineRegistered = true; + qAddPostRoutine(QCoreGraphicsPaintEngine::cleanUpMacColorSpaces); + } + } + return m_genericColorSpace; +#else + // Just return the main display colorspace for the moment. + return qt_mac_displayColorSpace(0); +#endif +} + +/* + Ideally, we should pass the widget in here, and use CGGetDisplaysWithRect() etc. + to support multiple displays correctly. +*/ +CGColorSpaceRef qt_mac_displayColorSpace(const QWidget *widget) +{ + CGColorSpaceRef colorSpace; + + CGDirectDisplayID displayID; + CMProfileRef displayProfile = 0; + if (widget == 0) { + displayID = CGMainDisplayID(); + } else { + displayID = CGMainDisplayID(); + /* + ### get correct display + const QRect &qrect = widget->window()->geometry(); + CGRect rect = CGRectMake(qrect.x(), qrect.y(), qrect.width(), qrect.height()); + CGDisplayCount throwAway; + CGDisplayErr dErr = CGGetDisplaysWithRect(rect, 1, &displayID, &throwAway); + if (dErr != kCGErrorSuccess) + return macDisplayColorSpace(0); // fall back on main display + */ + } + if ((colorSpace = m_displayColorSpaceHash.value(displayID))) + return colorSpace; + + CMError err = CMGetProfileByAVID((CMDisplayIDType)displayID, &displayProfile); + if (err == noErr) { + colorSpace = CGColorSpaceCreateWithPlatformColorSpace(displayProfile); + } else if (widget) { + return qt_mac_displayColorSpace(0); // fall back on main display + } + + if (colorSpace == 0) + colorSpace = CGColorSpaceCreateDeviceRGB(); + + m_displayColorSpaceHash.insert(displayID, colorSpace); + CMCloseProfile(displayProfile); + if (!m_postRoutineRegistered) { + m_postRoutineRegistered = true; + void qt_mac_cleanUpMacColorSpaces(); + qAddPostRoutine(qt_mac_cleanUpMacColorSpaces); + } + return colorSpace; +} + +void qt_mac_cleanUpMacColorSpaces() +{ + if (m_genericColorSpace) { + CFRelease(m_genericColorSpace); + m_genericColorSpace = 0; + } + QHash<CGDirectDisplayID, CGColorSpaceRef>::const_iterator it = m_displayColorSpaceHash.constBegin(); + while (it != m_displayColorSpaceHash.constEnd()) { + if (it.value()) + CFRelease(it.value()); + ++it; + } + m_displayColorSpaceHash.clear(); +} + +QString qt_mac_applicationName() +{ + QString appName; + CFTypeRef string = CFBundleGetValueForInfoDictionaryKey(CFBundleGetMainBundle(), CFSTR("CFBundleName")); + if (string) + appName = QCFString::toQString(static_cast<CFStringRef>(string)); + + if (appName.isEmpty()) { + QString arg0 = qApp->arguments().at(0); + if (arg0.contains("/")) { + QStringList parts = arg0.split("/"); + appName = parts.at(parts.count() - 1); + } else { + appName = arg0; + } + } + return appName; +} + diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.h b/src/plugins/platforms/cocoa/qcocoaintegration.h index 120bee46b7..a253a6bea3 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.h +++ b/src/plugins/platforms/cocoa/qcocoaintegration.h @@ -76,19 +76,19 @@ public: ~QCocoaIntegration(); bool hasCapability(QPlatformIntegration::Capability cap) const; - QPixmapData *createPixmapData(QPixmapData::PixelType type) const; - QPlatformWindow *createPlatformWindow(QWidget *widget, WId winId = 0) const; - QWindowSurface *createWindowSurface(QWidget *widget, WId winId) const; - - QList<QPlatformScreen *> screens() const { return mScreens; } + QPlatformWindow *createPlatformWindow(QWindow *window) const; + QPlatformOpenGLContext *createPlatformOpenGLContext(QOpenGLContext *context) const; + QPlatformBackingStore *createPlatformBackingStore(QWindow *widget) const; + QAbstractEventDispatcher *guiThreadEventDispatcher() const; QPlatformFontDatabase *fontDatabase() const; + QPlatformMenu *createPlatformMenu(QMenu *menu = 0) const; + QPlatformMenuBar *createPlatformMenuBar(QMenuBar *menuBar = 0) const; - QPlatformEventLoopIntegration *createEventLoopIntegration() const; - + QPlatformNativeInterface *nativeInterface() const; private: - QList<QPlatformScreen *> mScreens; QPlatformFontDatabase *mFontDb; + QAbstractEventDispatcher *mEventDispatcher; QCocoaAutoReleasePool *mPool; }; diff --git a/src/plugins/platforms/cocoa/qcocoaintegration.mm b/src/plugins/platforms/cocoa/qcocoaintegration.mm index 086f7b62e9..e3e204226f 100644 --- a/src/plugins/platforms/cocoa/qcocoaintegration.mm +++ b/src/plugins/platforms/cocoa/qcocoaintegration.mm @@ -42,14 +42,18 @@ #include "qcocoaintegration.h" #include "qcocoawindow.h" -#include "qcocoawindowsurface.h" -#include "qcocoaeventloopintegration.h" +#include "qcocoabackingstore.h" +#include "qcocoanativeinterface.h" +#include "qcocoamenuloader.h" +#include "qcocoaeventdispatcher.h" +#include "qcocoahelpers.h" +#include "qcocoaapplication.h" +#include "qcocoaapplicationdelegate.h" +#include "qmenu_mac.h" -#include "qcoretextfontdatabase.h" +#include <QtCore/qcoreapplication.h> -#include <QtGui/QApplication> - -#include <private/qpixmap_raster_p.h> +#include <QtPlatformSupport/private/qbasicunixfontdatabase_p.h> QT_BEGIN_NAMESPACE @@ -74,18 +78,47 @@ QCocoaScreen::~QCocoaScreen() } QCocoaIntegration::QCocoaIntegration() - : mFontDb(new QCoreTextFontDatabase()) + : mFontDb(new QBasicUnixFontDatabase()) + , mEventDispatcher(new QCocoaEventDispatcher()) { mPool = new QCocoaAutoReleasePool; - //Make sure we have a nsapplication :) - [NSApplication sharedApplication]; -// [[OurApplication alloc] init]; + QNSApplication *cocoaApplication = [QNSApplication sharedApplication]; + + // Applications launched from plain executables (without an app + // bundle) are "background" applications that does not take keybaord + // focus or have a dock icon or task switcher entry. Qt Gui apps generally + // wants to be foreground applications so change the process type. (But + // see the function implementation for exceptions.) + qt_mac_transformProccessToForegroundApplication(); + + // Move the application window to front to avoid launching behind the terminal. + // Ignoring other apps is neccessary (we must ignore the terminal), but makes + // Qt apps play slightly less nice with other apps when lanching from Finder + // (See the activateIgnoringOtherApps docs.) + [cocoaApplication activateIgnoringOtherApps : YES]; + + // ### For AA_MacPluginApplication we don't want to load the menu nib. + // Qt 4 also does not set the application delegate, so that behavior + // is matched here. + if (!QCoreApplication::testAttribute(Qt::AA_MacPluginApplication)) { + + // Set app delegate, link to the current delegate (if any) + QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) *newDelegate = [QT_MANGLE_NAMESPACE(QCocoaApplicationDelegate) sharedDelegate]; + [newDelegate setReflectionDelegate:[cocoaApplication delegate]]; + [cocoaApplication setDelegate:newDelegate]; + + // Load the application menu. This menu contains Preferences, Hide, Quit. + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *qtMenuLoader = [[QT_MANGLE_NAMESPACE(QCocoaMenuLoader) alloc] init]; + qt_mac_loadMenuNib(qtMenuLoader); + [cocoaApplication setMenu:[qtMenuLoader menu]]; + [newDelegate setMenuLoader:qtMenuLoader]; + } NSArray *screens = [NSScreen screens]; for (uint i = 0; i < [screens count]; i++) { QCocoaScreen *screen = new QCocoaScreen(i); - mScreens.append(screen); + screenAdded(screen); } } @@ -98,26 +131,32 @@ bool QCocoaIntegration::hasCapability(QPlatformIntegration::Capability cap) cons { switch (cap) { case ThreadedPixmaps: return true; + case OpenGL : return true; + case ThreadedOpenGL : return true; default: return QPlatformIntegration::hasCapability(cap); } } -QPixmapData *QCocoaIntegration::createPixmapData(QPixmapData::PixelType type) const +QPlatformWindow *QCocoaIntegration::createPlatformWindow(QWindow *window) const +{ + return new QCocoaWindow(window); +} + +QPlatformOpenGLContext *QCocoaIntegration::createPlatformOpenGLContext(QOpenGLContext *context) const { - return new QRasterPixmapData(type); + return new QCocoaGLContext(context->format(), context->shareHandle()); } -QPlatformWindow *QCocoaIntegration::createPlatformWindow(QWidget *widget, WId winId) const +QPlatformBackingStore *QCocoaIntegration::createPlatformBackingStore(QWindow *window) const { - Q_UNUSED(winId); - return new QCocoaWindow(widget); + return new QCocoaBackingStore(window); } -QWindowSurface *QCocoaIntegration::createWindowSurface(QWidget *widget, WId winId) const +QAbstractEventDispatcher *QCocoaIntegration::guiThreadEventDispatcher() const { - return new QCocoaWindowSurface(widget,winId); + return mEventDispatcher; } QPlatformFontDatabase *QCocoaIntegration::fontDatabase() const @@ -125,8 +164,21 @@ QPlatformFontDatabase *QCocoaIntegration::fontDatabase() const return mFontDb; } -QPlatformEventLoopIntegration *QCocoaIntegration::createEventLoopIntegration() const +QPlatformMenu *QCocoaIntegration::createPlatformMenu(QMenu *menu) const +{ + // return new QCocoaMenu(menu); + return 0; +} + +QPlatformMenuBar *QCocoaIntegration::createPlatformMenuBar(QMenuBar *menuBar) const +{ + //return new QCocoaMenuBar(menuBar); + return 0; +} + +QPlatformNativeInterface *QCocoaIntegration::nativeInterface() const { - return new QCocoaEventLoopIntegration(); + return new QCocoaNativeInterface(); } + QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoamenu.h b/src/plugins/platforms/cocoa/qcocoamenu.h new file mode 100644 index 0000000000..4e8ce20580 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoamenu.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include "qmacdefines_mac.h" +#import <Cocoa/Cocoa.h> + +QT_FORWARD_DECLARE_CLASS(QMenu) +QT_FORWARD_DECLARE_CLASS(QAction) + +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 + +@protocol NSMenuDelegate <NSObject> +- (void)menu:(NSMenu*)menu willHighlightItem:(NSMenuItem*)item; +- (void)menuWillOpen:(NSMenu*)menu; +- (void)menuDidClose:(NSMenu*)menu; +- (BOOL)hasShortcut:(NSMenu *)menu forKey:(NSString *)key forModifiers:(NSUInteger)modifier + whichItem:(NSMenuItem**)outItem; +@end + +#endif + +@interface QT_MANGLE_NAMESPACE(QNativeCocoaMenu) : NSMenu <NSMenuDelegate> +{ + QMenu *qmenu; + QAction *previousAction; +} +- (id)initWithQMenu:(QMenu*)menu; +- (BOOL)menuHasKeyEquivalent:(NSMenu *)menu forEvent:(NSEvent *)event target:(id *)target action:(SEL *)action; +- (NSInteger)indexOfItemWithTarget:(id)anObject andAction:(SEL)actionSelector; +@end + diff --git a/src/plugins/platforms/cocoa/qcocoamenu.mm b/src/plugins/platforms/cocoa/qcocoamenu.mm new file mode 100644 index 0000000000..1bb5f45a94 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoamenu.mm @@ -0,0 +1,267 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qapplication.h" +#include "qvarlengtharray.h" +#import "qcocoamenu.h" +#import "qcocoamenuloader.h" +#import "qcocoaapplication.h" +#include "qcocoahelpers.h" +#include <private/qapplication_p.h> +#include <private/qaction_p.h> + +QT_FORWARD_DECLARE_CLASS(QAction) +QT_FORWARD_DECLARE_CLASS(QWidget) +QT_FORWARD_DECLARE_CLASS(QApplication) +QT_FORWARD_DECLARE_CLASS(QCoreApplication) +QT_FORWARD_DECLARE_CLASS(QApplicationPrivate) +QT_FORWARD_DECLARE_CLASS(QKeyEvent) +QT_FORWARD_DECLARE_CLASS(QEvent) + +QT_BEGIN_NAMESPACE +extern void qt_mac_menu_collapseSeparators(NSMenu *menu, bool collapse); +void qt_mac_clear_status_text(QAction *action); +extern void qt_mac_emit_menuSignals(QMenu *menu, bool show); +extern void qt_mac_menu_emit_hovered(QMenu *menu, QAction *action); +QT_END_NAMESPACE + +QT_USE_NAMESPACE + +@implementation QT_MANGLE_NAMESPACE(QNativeCocoaMenu) + +- (id)initWithQMenu:(QMenu*)menu +{ + self = [super init]; + if (self) { + qmenu = menu; + previousAction = 0; + [self setAutoenablesItems:NO]; + [self setDelegate:self]; + } + return self; +} + +- (void)menu:(NSMenu*)menu willHighlightItem:(NSMenuItem*)item +{ + Q_UNUSED(menu); + + if (!item) { + if (previousAction) { + qt_mac_clear_status_text(previousAction); + previousAction = 0; + } + return; + } + + if (QAction *action = reinterpret_cast<QAction *>([item tag])) { + QMenu *qtmenu = static_cast<QT_MANGLE_NAMESPACE(QNativeCocoaMenu) *>(menu)->qmenu; + previousAction = action; + action->activate(QAction::Hover); + qt_mac_menu_emit_hovered(qtmenu, action); + action->showStatusText(0); // 0 widget -> action's parent + } +} + +- (void)menuWillOpen:(NSMenu*)menu +{ + while (QWidget *popup + = QApplication::activePopupWidget()) + popup->close(); + QMenu *qtmenu = static_cast<QT_MANGLE_NAMESPACE(QNativeCocoaMenu) *>(menu)->qmenu; + qt_mac_emit_menuSignals(qtmenu, true); + qt_mac_menu_collapseSeparators(menu, qtmenu->separatorsCollapsible()); +} + +- (void)menuDidClose:(NSMenu*)menu +{ + qt_mac_emit_menuSignals(((QT_MANGLE_NAMESPACE(QNativeCocoaMenu) *)menu)->qmenu, false); + if (previousAction) { + qt_mac_clear_status_text(previousAction); + previousAction = 0; + } +} + +- (BOOL)hasShortcut:(NSMenu *)menu forKey:(NSString *)key forModifiers:(NSUInteger)modifier + whichItem:(NSMenuItem**)outItem +{ + for (NSMenuItem *item in [menu itemArray]) { + if (![item isEnabled] || [item isHidden] || [item isSeparatorItem]) + continue; + if ([item hasSubmenu]) { + if ([self hasShortcut:[item submenu] + forKey:key + forModifiers:modifier whichItem:outItem]) { + if (outItem) + *outItem = item; + return YES; + } + } + NSString *menuKey = [item keyEquivalent]; + if (menuKey && NSOrderedSame == [menuKey compare:key] + && (modifier == [item keyEquivalentModifierMask])) { + if (outItem) + *outItem = item; + return YES; + } + } + if (outItem) + *outItem = 0; + return NO; +} + +NSString *qt_mac_removePrivateUnicode(NSString* string) +{ + int len = [string length]; + if (len) { + QVarLengthArray <unichar, 10> characters(len); + bool changed = false; + for (int i = 0; i<len; i++) { + characters[i] = [string characterAtIndex:i]; + // check if they belong to key codes in private unicode range + // currently we need to handle only the NSDeleteFunctionKey + if (characters[i] == NSDeleteFunctionKey) { + characters[i] = NSDeleteCharacter; + changed = true; + } + } + if (changed) + return [NSString stringWithCharacters:characters.data() length:len]; + } + return string; +} + +- (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. 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). + NSMenuItem *whichItem; + // Change the private unicode keys to the ones used in setting the "Key Equivalents" + NSString *characters = qt_mac_removePrivateUnicode([event characters]); + if ([self hasShortcut:menu + forKey:characters + // Interested only in Shift, Cmd, Ctrl & Alt Keys, so ignoring masks like, Caps lock, Num Lock ... + forModifiers:([event modifierFlags] & (NSShiftKeyMask | NSControlKeyMask | NSCommandKeyMask | NSAlternateKeyMask)) + whichItem:&whichItem]) { + QWidget *widget = 0; + QAction *qaction = 0; + if (whichItem && [whichItem tag]) { + qaction = reinterpret_cast<QAction *>([whichItem tag]); + } + if (qApp->activePopupWidget()) + widget = (qApp->activePopupWidget()->focusWidget() ? + qApp->activePopupWidget()->focusWidget() : qApp->activePopupWidget()); + else if (QApplicationPrivate::focus_widget) + widget = QApplicationPrivate::focus_widget; + // If we could not find any receivers, pass it to the active window + if (!widget) + widget = qApp->activeWindow(); + if (qaction && widget) { + int key = qaction->shortcut(); + QKeyEvent accel_ev(QEvent::ShortcutOverride, (key & (~Qt::KeyboardModifierMask)), + Qt::KeyboardModifiers(key & Qt::KeyboardModifierMask)); + accel_ev.ignore(); + +// ### qt_sendSpontaneousEvent(widget, &accel_ev); + + if (accel_ev.isAccepted()) { + qWarning("Unimplemented: qt_dispatchKeyEvent"); +#if 0 + qt_dispatchKeyEvent(event, widget); +#endif + *target = nil; + *action = nil; + return YES; + } + } + } + return NO; +} + +- (NSInteger)indexOfItemWithTarget:(id)anObject andAction:(SEL)actionSelector +{ + NSInteger index = [super indexOfItemWithTarget:anObject andAction:actionSelector]; + static SEL selForOFCP = NSSelectorFromString(@"orderFrontCharacterPalette:"); + if (index == -1 && selForOFCP == actionSelector) { + // Check if the 'orderFrontCharacterPalette' SEL exists for QNativeCocoaMenuLoader object + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = [NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)]; + return [super indexOfItemWithTarget:loader andAction:actionSelector]; + } + return index; +} + +@end + +QT_BEGIN_NAMESPACE +extern int qt_mac_menus_open_count; // qmenu_mac.mm + +void qt_mac_emit_menuSignals(QMenu *menu, bool show) +{ + if (!menu) + return; + int delta; + if (show) { + emit menu->aboutToShow(); + delta = 1; + } else { + emit menu->aboutToHide(); + delta = -1; + } + qt_mac_menus_open_count += delta; +} + +void qt_mac_clear_status_text(QAction *action) +{ + action->d_func()->showStatusText(0, QString()); +} + +void qt_mac_menu_emit_hovered(QMenu *menu, QAction *action) +{ + emit menu->hovered(action); +} + + +QT_END_NAMESPACE + diff --git a/src/plugins/platforms/cocoa/qcocoamenuloader.h b/src/plugins/platforms/cocoa/qcocoamenuloader.h new file mode 100644 index 0000000000..2fcda512f0 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoamenuloader.h @@ -0,0 +1,95 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QCOCOAMENULOADER_P_H +#define QCOCOAMENULOADER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of qapplication_*.cpp, qwidget*.cpp and qfiledialog.cpp. This header +// file may change from version to version without notice, or even be removed. +// +// We mean it. +// + +#import <Cocoa/Cocoa.h> +#include <QtCore/private/qcore_mac_p.h> + +@interface QT_MANGLE_NAMESPACE(QCocoaMenuLoader) : NSResponder +{ + IBOutlet NSMenu *theMenu; + IBOutlet NSMenu *appMenu; + IBOutlet NSMenuItem *quitItem; + IBOutlet NSMenuItem *preferencesItem; + IBOutlet NSMenuItem *aboutItem; + IBOutlet NSMenuItem *aboutQtItem; + IBOutlet NSMenuItem *hideItem; + NSMenuItem *lastAppSpecificItem; + NSMenuItem *servicesItem; + NSMenuItem *hideAllOthersItem; + NSMenuItem *showAllItem; +} +- (void)ensureAppMenuInMenu:(NSMenu *)menu; +- (void)removeActionsFromAppMenu; +- (NSMenu *)applicationMenu; +- (NSMenu *)menu; +- (NSMenuItem *)quitMenuItem; +- (NSMenuItem *)preferencesMenuItem; +- (NSMenuItem *)aboutMenuItem; +- (NSMenuItem *)aboutQtMenuItem; +- (NSMenuItem *)hideMenuItem; +- (NSMenuItem *)appSpecificMenuItem; +- (IBAction)terminate:(id)sender; +- (IBAction)orderFrontStandardAboutPanel:(id)sender; +- (IBAction)hideOtherApplications:(id)sender; +- (IBAction)unhideAllApplications:(id)sender; +- (IBAction)hide:(id)sender; +- (IBAction)qtDispatcherToQAction:(id)sender; +- (void)qtUpdateMenubar; +- (void)orderFrontCharacterPalette:(id)sender; +@end + +void qt_mac_loadMenuNib(QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *qtMenuLoader); + +#endif // QCOCOAMENULOADER_P_H diff --git a/src/plugins/platforms/cocoa/qcocoamenuloader.mm b/src/plugins/platforms/cocoa/qcocoamenuloader.mm new file mode 100644 index 0000000000..353808655f --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoamenuloader.mm @@ -0,0 +1,314 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcocoamenuloader.h" + +#include "qmenu_mac.h" +#include "qcocoahelpers.h" + +#include <QtCore/private/qcore_mac_p.h> +#include <QtCore/qcoreapplication.h> +#include <QtCore/qdir.h> +#include <QtCore/qstring.h> +#include <QtCore/qdebug.h> + +QT_FORWARD_DECLARE_CLASS(QCFString) +QT_FORWARD_DECLARE_CLASS(QString) + +#ifndef QT_NO_TRANSLATION + QT_BEGIN_NAMESPACE + extern QString qt_mac_applicationmenu_string(int type); + QT_END_NAMESPACE +#endif + +QT_USE_NAMESPACE + +/* + Loads and instantiates the main app menu from the menu nib file(s). + + The main app menu contains the Quit, Hide About, Preferences entries, and + The reason for having the nib file is that those can not be created + programmatically. To ease deployment the nib files are stored in Qt resources + and written to QDir::temp() before loading. (Earlier Qt versions used + to require having the nib file in the QtGui framework.) +*/ +void qt_mac_loadMenuNib(QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *qtMenuLoader) +{ + // Create qt_menu.nib dir in temp. + QDir temp = QDir::temp(); + temp.mkdir("qt_menu.nib"); + QString nibDir = temp.canonicalPath() + QLatin1String("/") + QLatin1String("qt_menu.nib/"); + if (!QDir(nibDir).exists()) { + qWarning("qt_mac_loadMenuNib: could not create nib directory in temp"); + return; + } + + // Copy nib files from resources to temp. + QDir nibResource(":/trolltech/mac/qt_menu.nib/"); + if (!nibResource.exists()) { + qWarning("qt_mac_loadMenuNib: could not load nib from resources"); + return; + } + foreach (const QFileInfo &file, nibResource.entryInfoList()) { + QFile::copy(file.absoluteFilePath(), nibDir + QLatin1String("/") + file.fileName()); + } + + // Load and instantiate nib file from temp + NSURL *nibUrl = [NSURL fileURLWithPath : const_cast<NSString *>(reinterpret_cast<const NSString *>(QCFString::toCFStringRef(nibDir)))]; + [nibUrl autorelease]; + NSNib *nib = [[NSNib alloc] initWithContentsOfURL : nibUrl]; + [nib autorelease]; + if(!nib) { + qWarning("qt_mac_loadMenuNib: could not load nib from temp"); + return; + } + bool ok = [nib instantiateNibWithOwner : qtMenuLoader topLevelObjects : nil]; + if (!ok) { + qWarning("qt_mac_loadMenuNib: could not instantiate nib"); + } +} + + + +@implementation QT_MANGLE_NAMESPACE(QCocoaMenuLoader) + +- (void)awakeFromNib +{ + servicesItem = [[appMenu itemWithTitle:@"Services"] retain]; + hideAllOthersItem = [[appMenu itemWithTitle:@"Hide Others"] retain]; + showAllItem = [[appMenu itemWithTitle:@"Show All"] retain]; + + // Get the names in the nib to match the app name set by Qt. + const NSString *appName = reinterpret_cast<const NSString*>(QCFString::toCFStringRef(qt_mac_applicationName())); + [quitItem setTitle:[[quitItem title] stringByReplacingOccurrencesOfString:@"NewApplication" + withString:const_cast<NSString *>(appName)]]; + [hideItem setTitle:[[hideItem title] stringByReplacingOccurrencesOfString:@"NewApplication" + withString:const_cast<NSString *>(appName)]]; + [aboutItem setTitle:[[aboutItem title] stringByReplacingOccurrencesOfString:@"NewApplication" + withString:const_cast<NSString *>(appName)]]; + [appName release]; + // Disable the items that don't do anything. If someone associates a QAction with them + // They should get synced back in. + [preferencesItem setEnabled:NO]; + [preferencesItem setHidden:YES]; + [aboutItem setEnabled:NO]; + [aboutItem setHidden:YES]; +} + +- (void)ensureAppMenuInMenu:(NSMenu *)menu +{ + // The application menu is the menu in the menu bar that contains the + // 'Quit' item. When changing menu bar (e.g when switching between + // windows with different menu bars), we never recreate this menu, but + // instead pull it out the current menu bar and place into the new one: + NSMenu *mainMenu = [NSApp mainMenu]; + if ([NSApp mainMenu] == menu) + return; // nothing to do (menu is the current menu bar)! + +#ifndef QT_NAMESPACE + Q_ASSERT(mainMenu); +#endif + // Grab the app menu out of the current menu. + int numItems = [mainMenu numberOfItems]; + NSMenuItem *oldAppMenuItem = 0; + for (int i = 0; i < numItems; ++i) { + NSMenuItem *item = [mainMenu itemAtIndex:i]; + if ([item submenu] == appMenu) { + oldAppMenuItem = item; + [oldAppMenuItem retain]; + [mainMenu removeItemAtIndex:i]; + break; + } + } + + if (oldAppMenuItem) { + [oldAppMenuItem setSubmenu:nil]; + [oldAppMenuItem release]; + NSMenuItem *appMenuItem = [[NSMenuItem alloc] initWithTitle:@"Apple" + action:nil keyEquivalent:@""]; + [appMenuItem setSubmenu:appMenu]; + [menu insertItem:appMenuItem atIndex:0]; + } +} + +- (void)removeActionsFromAppMenu +{ + for (NSMenuItem *item in [appMenu itemArray]) + [item setTag:nil]; +} + +- (void)dealloc +{ + [servicesItem release]; + [hideAllOthersItem release]; + [showAllItem release]; + + [lastAppSpecificItem release]; + [theMenu release]; + [appMenu release]; + [super dealloc]; +} + +- (NSMenu *)menu +{ + return [[theMenu retain] autorelease]; +} + +- (NSMenu *)applicationMenu +{ + return [[appMenu retain] autorelease]; +} + +- (NSMenuItem *)quitMenuItem +{ + return [[quitItem retain] autorelease]; +} + +- (NSMenuItem *)preferencesMenuItem +{ + return [[preferencesItem retain] autorelease]; +} + +- (NSMenuItem *)aboutMenuItem +{ + return [[aboutItem retain] autorelease]; +} + +- (NSMenuItem *)aboutQtMenuItem +{ + return [[aboutQtItem retain] autorelease]; +} + +- (NSMenuItem *)hideMenuItem +{ + return [[hideItem retain] autorelease]; +} + +- (NSMenuItem *)appSpecificMenuItem +{ + // Create an App-Specific menu item, insert it into the menu and return + // it as an autorelease item. + NSMenuItem *item = [[NSMenuItem alloc] init]; + + NSInteger location; + if (lastAppSpecificItem == nil) { + location = [appMenu indexOfItem:aboutQtItem]; + } else { + location = [appMenu indexOfItem:lastAppSpecificItem]; + [lastAppSpecificItem release]; + } + lastAppSpecificItem = item; // Keep track of this for later (i.e., don't release it) + [appMenu insertItem:item atIndex:location + 1]; + + return [[item retain] autorelease]; +} + +- (BOOL) acceptsFirstResponder +{ + return YES; +} + +- (void)terminate:(id)sender +{ + [NSApp terminate:sender]; +} + +- (void)orderFrontStandardAboutPanel:(id)sender +{ + [NSApp orderFrontStandardAboutPanel:sender]; +} + +- (void)hideOtherApplications:(id)sender +{ + [NSApp hideOtherApplications:sender]; +} + +- (void)unhideAllApplications:(id)sender +{ + [NSApp unhideAllApplications:sender]; +} + +- (void)hide:(id)sender +{ + [NSApp hide:sender]; +} + +- (void)qtUpdateMenubar +{ + QCocoaMenuBar::macUpdateMenuBarImmediatly(); +} + +- (void)qtTranslateApplicationMenu +{ + + qDebug() << "qtTranslateApplicationMenu"; + +#ifndef QT_NO_TRANSLATION + [servicesItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(0))]; + [hideItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(1).arg(qt_mac_applicationName()))]; + [hideAllOthersItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(2))]; + [showAllItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(3))]; + [preferencesItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(4))]; + [quitItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(5).arg(qt_mac_applicationName()))]; + [aboutItem setTitle: qt_mac_QStringToNSString(qt_mac_applicationmenu_string(6).arg(qt_mac_applicationName()))]; +#endif +} + +- (IBAction)qtDispatcherToQAction:(id)sender +{ + // + //QScopedLoopLevelCounter loopLevelCounter(QApplicationPrivate::instance()->threadData); + NSMenuItem *item = static_cast<NSMenuItem *>(sender); + if (QAction *action = reinterpret_cast<QAction *>([item tag])) { + action->trigger(); + } else if (item == quitItem) { + // We got here because someone was once the quitItem, but it has been + // abandoned (e.g., the menubar was deleted). In the meantime, just do + // normal QApplication::quit(). + qApp->quit(); + } +} + + - (void)orderFrontCharacterPalette:(id)sender + { + [NSApp orderFrontCharacterPalette:sender]; + } +@end diff --git a/src/plugins/platforms/cocoa/qcocoaeventloopintegration.h b/src/plugins/platforms/cocoa/qcocoanativeinterface.h index 5765483fc7..f8216d8e61 100644 --- a/src/plugins/platforms/cocoa/qcocoaeventloopintegration.h +++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.h @@ -39,27 +39,17 @@ ** ****************************************************************************/ -#ifndef QCOCAEVENTLOOPINTEGRATION_H -#define QCOCAEVENTLOOPINTEGRATION_H +#ifndef QCOCOANATIVEINTERFACE_H +#define QCOCOANATIVEINTERFACE_H -#include <Cocoa/Cocoa.h> +#include <QtGui/QPlatformNativeInterface> -#include <QPlatformEventLoopIntegration> +class QWidget; - -class QCocoaEventLoopIntegration : public QPlatformEventLoopIntegration +class QCocoaNativeInterface : public QPlatformNativeInterface { public: - QCocoaEventLoopIntegration(); - void startEventLoop(); - void quitEventLoop(); - void qtNeedsToProcessEvents(); - -private: - CFRunLoopSourceContext m_sourceContext; - CFRunLoopTimerContext m_timerContext; - CFRunLoopSourceRef m_source; + void *nativeResourceForWindow(const QByteArray &resourceString, QWindow *window); }; -#endif // QCOCAEVENTLOOPINTEGRATION_H - +#endif // QCOCOANATIVEINTERFACE_H diff --git a/src/plugins/platforms/cocoa/qcocoanativeinterface.mm b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm new file mode 100644 index 0000000000..c6aa0d39e6 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoanativeinterface.mm @@ -0,0 +1,59 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qcocoanativeinterface.h" +#include "qcocoaglcontext.h" +#include "qcocoawindow.h" +#include <qbytearray.h> +#include <qwindow.h> +#include "qplatformwindow_qpa.h" +#include "qsurfaceformat.h" +#include "qplatformopenglcontext_qpa.h" +#include "qopenglcontext.h" +#include <qdebug.h> + +void *QCocoaNativeInterface::nativeResourceForWindow(const QByteArray &resourceString, QWindow *window) +{ + if (resourceString == "nsopenglcontext") { + return static_cast<QCocoaWindow *>(window->handle())->currentContext()->nsOpenGLContext(); + } + return 0; +} diff --git a/src/plugins/platforms/cocoa/qcocoaresources.qrc b/src/plugins/platforms/cocoa/qcocoaresources.qrc new file mode 100644 index 0000000000..de50d397c6 --- /dev/null +++ b/src/plugins/platforms/cocoa/qcocoaresources.qrc @@ -0,0 +1,17 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource prefix="/trolltech/mac/cursors"> +<file>images/copyarrowcursor.png</file> +<file>images/forbiddencursor.png</file> +<file>images/spincursor.png</file> +<file>images/waitcursor.png</file> +<file>images/pluscursor.png</file> +</qresource> +<qresource prefix="/trolltech/mac/style"> +<file>images/leopard-unified-toolbar-on.png</file> +</qresource> +<qresource prefix="/trolltech/mac/"> +<file>qt_menu.nib/classes.nib</file> +<file>qt_menu.nib/info.nib</file> +<file>qt_menu.nib/keyedobjects.nib</file> +</qresource> +</RCC> diff --git a/src/plugins/platforms/cocoa/qcocoawindow.h b/src/plugins/platforms/cocoa/qcocoawindow.h index 9e7e68b7d2..ce79d3967f 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.h +++ b/src/plugins/platforms/cocoa/qcocoawindow.h @@ -45,28 +45,55 @@ #include <Cocoa/Cocoa.h> #include <QPlatformWindow> +#include <QRect> + +#include "qcocoaglcontext.h" +#include "qnsview.h" QT_BEGIN_NAMESPACE +@interface QNSWindow : NSWindow { + +} + +@end + class QCocoaWindow : public QPlatformWindow { public: - QCocoaWindow(QWidget *tlw); + QCocoaWindow(QWindow *tlw); ~QCocoaWindow(); void setGeometry(const QRect &rect); - void setVisible(bool visible); + void setWindowTitle(const QString &title); + void raise(); + void lower(); WId winId() const; - NSView *contentView() const; - void setContentView(NSView *contentView); + void windowDidMove(); void windowDidResize(); + void windowWillClose(); + + void setCurrentContext(QCocoaGLContext *context); + QCocoaGLContext *currentContext() const; + +protected: + void determineWindowClass(); + QNSWindow *createWindow(); + NSRect globalGeometry(const QRect localWindowGeometry) const; + QRect windowGeometry() const; + QCocoaWindow *parentCocoaWindow() const; private: - NSWindow *m_nsWindow; + friend class QCocoaBackingStore; + QNSWindow *m_nsWindow; + QNSView *m_contentView; + quint32 m_windowAttributes; + quint32 m_windowClass; + QCocoaGLContext *m_glContext; }; QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qcocoawindow.mm b/src/plugins/platforms/cocoa/qcocoawindow.mm index a2fdce3520..7e4d4217ef 100644 --- a/src/plugins/platforms/cocoa/qcocoawindow.mm +++ b/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -41,54 +41,116 @@ #include "qcocoawindow.h" #include "qnswindowdelegate.h" #include "qcocoaautoreleasepool.h" +#include "qcocoaglcontext.h" +#include "qnsview.h" +#include <QtCore/private/qcore_mac_p.h> +#include <qwindow.h> +#include <QWindowSystemInterface> +#include <QPlatformScreen> -#include <QWidget> +#include <Cocoa/Cocoa.h> +#include <Carbon/Carbon.h> -#include <QtGui/QApplication> +#include <QDebug> -#include <QWindowSystemInterface> +@implementation QNSWindow -#include <QDebug> +- (BOOL)canBecomeKeyWindow +{ + return YES; +} + +- (BOOL)canBecomeMainWindow +{ + return YES; +} + +@end -QCocoaWindow::QCocoaWindow(QWidget *tlw) +QCocoaWindow::QCocoaWindow(QWindow *tlw) : QPlatformWindow(tlw) + , m_windowAttributes(0) + , m_windowClass(0) + , m_glContext(0) { QCocoaAutoReleasePool pool; - const QRect geo = tlw->geometry(); - NSRect frame = NSMakeRect(geo.x(), geo.y(), geo.width(), geo.height()); - m_nsWindow = [[NSWindow alloc] initWithContentRect:frame - styleMask:NSTitledWindowMask|NSClosableWindowMask|NSMiniaturizableWindowMask|NSResizableWindowMask - backing:NSBackingStoreBuffered - defer:YES]; + determineWindowClass(); + m_nsWindow = createWindow(); QNSWindowDelegate *delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:this]; [m_nsWindow setDelegate:delegate]; - - [m_nsWindow makeKeyAndOrderFront:nil]; [m_nsWindow setAcceptsMouseMovedEvents:YES]; + + // Prevent Cocoa from releasing the window on close. Qt + // handles the close event asynchronously and we want to + // make sure that m_nsWindow stays valid until the + // QCocoaWindow is deleted by Qt. + [m_nsWindow setReleasedWhenClosed : NO]; + + m_contentView = [[QNSView alloc] initWithQWindow:tlw]; + + setGeometry(tlw->geometry()); + + [m_nsWindow setContentView:m_contentView]; } QCocoaWindow::~QCocoaWindow() { + [m_nsWindow release]; } void QCocoaWindow::setGeometry(const QRect &rect) { QPlatformWindow::setGeometry(rect); - NSRect bounds = NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height()); + NSRect bounds = globalGeometry(rect); [[m_nsWindow contentView]setFrameSize:bounds.size]; + [m_nsWindow setContentSize : bounds.size]; + [m_nsWindow setFrameOrigin : bounds.origin]; + + if (m_glContext) + m_glContext->update(); } void QCocoaWindow::setVisible(bool visible) { - Q_UNUSED(visible); + if (visible) { + // The parent window might have moved while this window was hidden, + // update the window geometry if there is a parent. + if (window()->transientParent()) + setGeometry(window()->geometry()); + + // Make sure the QWindow has a frame ready before we show the NSWindow. + QWindowSystemInterface::handleSynchronousExposeEvent(window(), QRect(QPoint(), geometry().size())); + + [m_nsWindow makeKeyAndOrderFront:nil]; + } else { + [m_nsWindow orderOut:nil]; + } +} + +void QCocoaWindow::setWindowTitle(const QString &title) +{ + CFStringRef windowTitle = QCFString::toCFStringRef(title); + [m_nsWindow setTitle: const_cast<NSString *>(reinterpret_cast<const NSString *>(windowTitle))]; + CFRelease(windowTitle); +} + +void QCocoaWindow::raise() +{ + // ### handle spaces (see Qt 4 raise_sys in qwidget_mac.mm) + [m_nsWindow orderFront: m_nsWindow]; +} + +void QCocoaWindow::lower() +{ + [m_nsWindow orderFront: m_nsWindow]; } WId QCocoaWindow::winId() const { - return WId([m_nsWindow windowNumber]); + return WId(m_nsWindow); } NSView *QCocoaWindow::contentView() const @@ -96,15 +158,231 @@ NSView *QCocoaWindow::contentView() const return [m_nsWindow contentView]; } -void QCocoaWindow::setContentView(NSView *contentView) +void QCocoaWindow::windowDidMove() { - [m_nsWindow setContentView:contentView]; + if (m_glContext) + m_glContext->update(); } void QCocoaWindow::windowDidResize() { - //jlind: XXX This isn't ideal. Eventdispatcher does not run when resizing... + if (m_glContext) + m_glContext->update(); + NSRect rect = [[m_nsWindow contentView]frame]; QRect geo(rect.origin.x,rect.origin.y,rect.size.width,rect.size.height); - QWindowSystemInterface::handleGeometryChange(widget(),geo); + QWindowSystemInterface::handleSynchronousGeometryChange(window(), geo); +} + + +void QCocoaWindow::windowWillClose() +{ + QWindowSystemInterface::handleCloseEvent(window()); +} + +void QCocoaWindow::setCurrentContext(QCocoaGLContext *context) +{ + m_glContext = context; +} + +QCocoaGLContext *QCocoaWindow::currentContext() const +{ + return m_glContext; } + +/* + Determine the window class based on the window type and + window flags, and widget attr Sets m_windowAttributes + and m_windowClass. +*/ +void QCocoaWindow::determineWindowClass() +{ + Qt::WindowType type = window()->windowType(); + Qt::WindowFlags flags = window()->windowFlags(); + + const bool popup = (type == Qt::Popup); + + if (type == Qt::ToolTip || type == Qt::SplashScreen || popup) + flags |= Qt::FramelessWindowHint; + + m_windowClass = kSheetWindowClass; + + if (popup || type == Qt::SplashScreen) + m_windowClass = kModalWindowClass; + else if (type == Qt::ToolTip) + m_windowClass = kHelpWindowClass; + else if (type == Qt::Tool) + m_windowClass = kFloatingWindowClass; + else + m_windowClass = kDocumentWindowClass; + + m_windowAttributes = (kWindowCompositingAttribute | kWindowStandardHandlerAttribute); + +// if(qt_mac_is_macsheet(window())) { +// m_windowClass = kSheetWindowClass; +// } else + + { + // Shift things around a bit to get the correct window class based on the presence + // (or lack) of the border. + + bool customize = flags & Qt::CustomizeWindowHint; + bool framelessWindow = (flags & Qt::FramelessWindowHint || (customize && !(flags & Qt::WindowTitleHint))); + if (framelessWindow) { + if (m_windowClass == kDocumentWindowClass) { + m_windowAttributes |= kWindowNoTitleBarAttribute; + } else if (m_windowClass == kFloatingWindowClass) { + m_windowAttributes |= kWindowNoTitleBarAttribute; + } else if (m_windowClass == kMovableModalWindowClass) { + m_windowClass = kModalWindowClass; + } + } else { + m_windowAttributes |= NSTitledWindowMask; + if (m_windowClass != kModalWindowClass) + m_windowAttributes |= NSResizableWindowMask; + } + + // Only add extra decorations (well, buttons) for widgets that can have them + // and have an actual border we can put them on. + + if(m_windowClass != kModalWindowClass && m_windowClass != kMovableModalWindowClass + && m_windowClass != kSheetWindowClass && m_windowClass != kPlainWindowClass + && !framelessWindow && m_windowClass != kDrawerWindowClass + && m_windowClass != kHelpWindowClass) { + if (flags & Qt::WindowMinimizeButtonHint) + m_windowAttributes |= NSMiniaturizableWindowMask; + if (flags & Qt::WindowSystemMenuHint || flags & Qt::WindowCloseButtonHint) + m_windowAttributes |= NSClosableWindowMask; + } else { + // Clear these hints so that we aren't call them on invalid windows + flags &= ~(Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint + | Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint); + } + + } + + if((popup || type == Qt::Tool) && !window()->isModal()) + m_windowAttributes |= kWindowHideOnSuspendAttribute; + m_windowAttributes |= kWindowLiveResizeAttribute; +} + +/* + +*/ +QNSWindow * QCocoaWindow::createWindow() +{ + // Determine if we need to add in our "custom window" attribute. Cocoa is rather clever + // in deciding if we need the maximize button or not (i.e., it's resizeable, so you + // must need a maximize button). So, the only buttons we have control over are the + // close and minimize buttons. If someone wants to customize and NOT have the maximize + // button, then we have to do our hack. We only do it for these cases because otherwise + // the window looks different when activated. This "QtMacCustomizeWindow" attribute is + // intruding on a public space and WILL BREAK in the future. + // One can hope that there is a more public API available by that time. +/* + Qt::WindowFlags flags = widget ? widget->windowFlags() : Qt::WindowFlags(0); + if ((flags & Qt::CustomizeWindowHint)) { + if ((flags & (Qt::WindowCloseButtonHint | Qt::WindowSystemMenuHint + | Qt::WindowMinimizeButtonHint | Qt::WindowTitleHint)) + && !(flags & Qt::WindowMaximizeButtonHint)) + wattr |= QtMacCustomizeWindow; + } +*/ + NSRect frame = globalGeometry(window()->geometry()); + QCocoaAutoReleasePool pool; + QNSWindow *window; + + switch (m_windowClass) { + case kMovableModalWindowClass: + case kModalWindowClass: + case kSheetWindowClass: + case kFloatingWindowClass: + case kOverlayWindowClass: + case kHelpWindowClass: { + NSPanel *panel; + + BOOL needFloating = NO; + BOOL worksWhenModal = (this->window()->windowType() == Qt::Popup); + + // Add in the extra flags if necessary. + switch (m_windowClass) { + case kSheetWindowClass: + m_windowAttributes |= NSDocModalWindowMask; + break; + case kFloatingWindowClass: + case kHelpWindowClass: + needFloating = YES; + m_windowAttributes |= NSUtilityWindowMask; + break; + default: + break; + } + + panel = [[NSPanel alloc] initWithContentRect:frame + styleMask:m_windowAttributes + backing:NSBackingStoreBuffered + defer:NO]; // see window case below +// ### crashes +// [panel setFloatingPanel:needFloating]; +// [panel setWorksWhenModal:worksWhenModal]; + window = static_cast<NSWindow *>(panel); + break; + } + default: + window = [[QNSWindow alloc] initWithContentRect:frame + styleMask:(NSResizableWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSTitledWindowMask) + backing:NSBackingStoreBuffered + defer:NO]; // Deferring window creation breaks OpenGL (the GL context is set up + // before the window is shown and needs a proper window.). + break; + } + + //qt_syncCocoaTitleBarButtons(window, widget); + return window; +} + +// Calculate the global screen geometry for the given local geometry, which +// might be in the parent window coordinate system. +NSRect QCocoaWindow::globalGeometry(const QRect localGeometry) const +{ + QRect finalGeometry = localGeometry; + + if (QCocoaWindow *parent = parentCocoaWindow()) { + QRect parentGeometry = parent->windowGeometry(); + finalGeometry.adjust(parentGeometry.x(), parentGeometry.y(), parentGeometry.x(), parentGeometry.y()); + + // Qt child window geometry assumes that the origin is at the + // top-left of the content area of the parent window. The title + // bar is not a part of this contet area, but is still included + // in the NSWindow height. Move the child window down to acccount + // for this if the parent window has a title bar. + const int titlebarHeight = 22; + if (!(window()->windowFlags() & Qt::FramelessWindowHint)) + finalGeometry.adjust(0, titlebarHeight, 0, titlebarHeight); + } + + // The final "y invert" to get OS X global geometry: + QPlatformScreen *onScreen = QPlatformScreen::platformScreenForWindow(window()); + int flippedY = onScreen->geometry().height() - finalGeometry.y() - finalGeometry.height(); + return NSMakeRect(finalGeometry.x(), flippedY, finalGeometry.width(), finalGeometry.height()); +} + +// Returns the current global screen geometry for the nswindow accociated with this window. +QRect QCocoaWindow::windowGeometry() const +{ + NSRect rect = [m_nsWindow frame]; + QPlatformScreen *onScreen = QPlatformScreen::platformScreenForWindow(window()); + int flippedY = onScreen->geometry().height() - rect.origin.y - rect.size.height; // account for nswindow inverted y. + QRect qRect = QRect(rect.origin.x, flippedY, rect.size.width, rect.size.height); + return qRect; +} + +// Returns a pointer to the parent QCocoaWindow for this window, or 0 if there is none. +QCocoaWindow *QCocoaWindow::parentCocoaWindow() const +{ + if (window() && window()->transientParent()) { + return static_cast<QCocoaWindow*>(window()->transientParent()->handle()); + } + return 0; +} + diff --git a/src/plugins/platforms/cocoa/qmenu_mac.h b/src/plugins/platforms/cocoa/qmenu_mac.h new file mode 100644 index 0000000000..f20f82c761 --- /dev/null +++ b/src/plugins/platforms/cocoa/qmenu_mac.h @@ -0,0 +1,85 @@ + +#include <private/qt_mac_p.h> +#include <QtCore/qpointer.h> +#include <QtWidgets/qmenu.h> +#include <QtWidgets/qmenubar.h> +#include <QtWidgets/qplatformmenu_qpa.h> + +@class NSMenuItem; +class QCocoaMenuAction : public QPlatformMenuAction +{ +public: + QCocoaMenuAction(); + ~QCocoaMenuAction(); + + NSMenuItem *menuItem; + uchar ignore_accel : 1; + uchar merged : 1; + OSMenuRef menu; + QPointer<QMenu> qtMenu; +}; + +struct QMenuMergeItem +{ + inline QMenuMergeItem(NSMenuItem *c, QCocoaMenuAction *a) : menuItem(c), action(a) { } + NSMenuItem *menuItem; + QCocoaMenuAction *action; +}; +typedef QList<QMenuMergeItem> QMenuMergeList; + +class QCocoaMenu : public QPlatformMenu +{ +public: + QCocoaMenu(QMenu *qtMenu); + ~QCocoaMenu(); + + OSMenuRef macMenu(OSMenuRef merge = 0); + void syncSeparatorsCollapsible(bool collapse); + void setMenuEnabled(bool enable); + + void addAction(QAction *action, QAction *before); + void syncAction(QAction *action); + void removeAction(QAction *action); + + void addAction(QCocoaMenuAction *action, QCocoaMenuAction *before); + void syncAction(QCocoaMenuAction *action); + void removeAction(QCocoaMenuAction *action); + bool merged(const QAction *action) const; + QCocoaMenuAction *findAction(QAction *action) const; + + OSMenuRef menu; + static QHash<OSMenuRef, OSMenuRef> mergeMenuHash; + static QHash<OSMenuRef, QMenuMergeList*> mergeMenuItemsHash; + QList<QCocoaMenuAction*> actionItems; + QMenu *qtMenu; +}; + +class QCocoaMenuBar : public QPlatformMenuBar +{ +public: + QCocoaMenuBar(QMenuBar *qtMenuBar); + ~QCocoaMenuBar(); + + void handleReparent(QWidget *newParent); + + void addAction(QAction *action, QAction *before); + void syncAction(QAction *action); + void removeAction(QAction *action); + + void addAction(QCocoaMenuAction *action, QCocoaMenuAction *before); + void syncAction(QCocoaMenuAction *action); + void removeAction(QCocoaMenuAction *action); + + bool macWidgetHasNativeMenubar(QWidget *widget); + void macCreateMenuBar(QWidget *parent); + void macDestroyMenuBar(); + OSMenuRef macMenu(); + static bool macUpdateMenuBarImmediatly(); + static void macUpdateMenuBar(); + QCocoaMenuAction *findAction(QAction *action) const; + + OSMenuRef menu; + OSMenuRef apple_menu; + QList<QCocoaMenuAction*> actionItems; + QMenuBar *qtMenuBar; +}; diff --git a/src/plugins/platforms/cocoa/qmenu_mac.mm b/src/plugins/platforms/cocoa/qmenu_mac.mm new file mode 100644 index 0000000000..7ca546dd79 --- /dev/null +++ b/src/plugins/platforms/cocoa/qmenu_mac.mm @@ -0,0 +1,1274 @@ +/**************************************************************************** +** +** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qmenu_mac.h" + +#include <Cocoa/Cocoa.h> + +#include "qmenu.h" +#include "qhash.h" +#include <qdebug.h> +#include "qapplication.h" +#include "qregexp.h" +#include "qtoolbar.h" +#include "qevent.h" +#include "qstyle.h" +#include "qwidgetaction.h" + +#include <private/qmenu_p.h> +#include <private/qmenubar_p.h> +#include <private/qguiapplication_p.h> + +#include "qcocoahelpers.h" +#include "qcocoaapplication.h" +#include "qcocoamenuloader.h" +#include "qcocoamenu.h" +#include "qcocoahelpers.h" +#include "qcocoaautoreleasepool.h" + +QT_BEGIN_NAMESPACE + +/***************************************************************************** + QMenu debug facilities + *****************************************************************************/ + +/***************************************************************************** + QMenu globals + *****************************************************************************/ +bool qt_mac_no_menubar_merge = false; +bool qt_mac_quit_menu_item_enabled = true; +int qt_mac_menus_open_count = 0; + +static OSMenuRef qt_mac_create_menu(QWidget *w); + +static struct { + QPointer<QMenuBar> qmenubar; + bool modal; +} qt_mac_current_menubar = { 0, false }; + + + + +/***************************************************************************** + Externals + *****************************************************************************/ +extern OSViewRef qt_mac_hiview_for(const QWidget *w); //qwidget_mac.cpp +extern IconRef qt_mac_create_iconref(const QPixmap &px); //qpixmap_mac.cpp +extern QWidget * mac_keyboard_grabber; //qwidget_mac.cpp +extern bool qt_sendSpontaneousEvent(QObject*, QEvent*); //qapplication_xxx.cpp +RgnHandle qt_mac_get_rgn(); //qregion_mac.cpp +void qt_mac_dispose_rgn(RgnHandle r); //qregion_mac.cpp + +/***************************************************************************** + QMenu utility functions + *****************************************************************************/ +bool qt_mac_watchingAboutToShow(QMenu *menu) +{ + return menu; /* && menu->receivers(SIGNAL(aboutToShow()));*/ +} + +static int qt_mac_CountMenuItems(OSMenuRef menu) +{ + if (menu) { + return [menu numberOfItems]; + } + return 0; +} + +void qt_mac_menu_collapseSeparators(NSMenu * theMenu, bool collapse) +{ + QCocoaAutoReleasePool pool; + OSMenuRef menu = static_cast<OSMenuRef>(theMenu); + if (collapse) { + bool previousIsSeparator = true; // setting to true kills all the separators placed at the top. + NSMenuItem *previousItem = nil; + + NSArray *itemArray = [menu itemArray]; + for (unsigned int i = 0; i < [itemArray count]; ++i) { + NSMenuItem *item = reinterpret_cast<NSMenuItem *>([itemArray objectAtIndex:i]); + if ([item isSeparatorItem]) { + [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) + [previousItem setHidden:YES]; + } else { + NSArray *itemArray = [menu itemArray]; + for (unsigned int i = 0; i < [itemArray count]; ++i) { + NSMenuItem *item = reinterpret_cast<NSMenuItem *>([itemArray objectAtIndex:i]); + if (QAction *action = reinterpret_cast<QAction *>([item tag])) + [item setHidden:!action->isVisible()]; + } + } +} + +#ifndef QT_NO_TRANSLATION +static const char *application_menu_strings[] = { + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Services"), + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Hide %1"), + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Hide Others"), + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Show All"), + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Preferences..."), + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","Quit %1"), + QT_TRANSLATE_NOOP("MAC_APPLICATION_MENU","About %1") + }; + +QString qt_mac_applicationmenu_string(int type) +{ + QString menuString = QString::fromLatin1(application_menu_strings[type]); + QString translated = qApp->translate("QMenuBar", application_menu_strings[type]); + if (translated != menuString) + return translated; + else + return qApp->translate("MAC_APPLICATION_MENU", + application_menu_strings[type]); +} +#endif + + +static quint32 constructModifierMask(quint32 accel_key) +{ + quint32 ret = 0; + const bool dontSwap = qApp->testAttribute(Qt::AA_MacDontSwapCtrlAndMeta); + if ((accel_key & Qt::CTRL) == Qt::CTRL) + ret |= (dontSwap ? NSControlKeyMask : NSCommandKeyMask); + if ((accel_key & Qt::META) == Qt::META) + ret |= (dontSwap ? NSCommandKeyMask : NSControlKeyMask); + if ((accel_key & Qt::ALT) == Qt::ALT) + ret |= NSAlternateKeyMask; + if ((accel_key & Qt::SHIFT) == Qt::SHIFT) + ret |= NSShiftKeyMask; + return ret; +} + +static void cancelAllMenuTracking() +{ + QCocoaAutoReleasePool pool; + NSMenu *mainMenu = [NSApp mainMenu]; + [mainMenu cancelTracking]; + for (NSMenuItem *item in [mainMenu itemArray]) { + if ([item submenu]) { + [[item submenu] cancelTracking]; + } + } +} + +static bool actualMenuItemVisibility(const QCocoaMenuBar *mbp, + const QCocoaMenuAction *action) +{ + bool visible = action->action->isVisible(); + if (visible && action->action->text() == QString(QChar(0x14))) + return false; + + if (visible && action->action->menu() && !action->action->menu()->actions().isEmpty() && +/* ### !qt_mac_CountMenuItems(cocoaMenu->macMenu(mbp->apple_menu)) &&*/ + !qt_mac_watchingAboutToShow(action->action->menu())) { + return false; + } + return visible; +} + +static inline void syncNSMenuItemVisiblity(NSMenuItem *menuItem, bool actionVisibility) +{ + [menuItem setHidden:NO]; + [menuItem setHidden:YES]; + [menuItem setHidden:!actionVisibility]; +} + +static inline void syncNSMenuItemEnabled(NSMenuItem *menuItem, bool enabled) +{ + [menuItem setEnabled:NO]; + [menuItem setEnabled:YES]; + [menuItem setEnabled:enabled]; +} + +static inline void syncMenuBarItemsVisiblity(const QCocoaMenuBar *mac_menubar) +{ + const QList<QCocoaMenuAction *> &menubarActions = mac_menubar->actionItems; + for (int i = 0; i < menubarActions.size(); ++i) { + const QCocoaMenuAction *action = menubarActions.at(i); + syncNSMenuItemVisiblity(action->menuItem, actualMenuItemVisibility(mac_menubar, action)); + } +} + +static inline QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *getMenuLoader() +{ + return [NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)]; +} + +static NSMenuItem *createNSMenuItem(const QString &title) +{ + NSMenuItem *item = [[NSMenuItem alloc] + initWithTitle:qt_mac_QStringToNSString(title) + action:@selector(qtDispatcherToQAction:) keyEquivalent:@""]; + [item setTarget:nil]; + return item; +} + +// helper that recurses into a menu structure and en/dis-ables them +void qt_mac_set_modal_state_helper_recursive(OSMenuRef menu, OSMenuRef merge, bool on) +{ + bool modalWindowOnScreen = qApp->activeModalWidget() != 0; + for (NSMenuItem *item in [menu itemArray]) { + OSMenuRef submenu = [item submenu]; + if (submenu != merge) { + if (submenu) + qt_mac_set_modal_state_helper_recursive(submenu, merge, on); + if (!on) { + // The item should follow what the QAction has. + if ([item tag]) { + QAction *action = reinterpret_cast<QAction *>([item tag]); + syncNSMenuItemEnabled(item, action->isEnabled()); + } else { + syncNSMenuItemEnabled(item, YES); + } + // We sneak in some extra code here to handle a menu problem: + // If there is no window on screen, we cannot set 'nil' as + // menu item target, because then cocoa will disable the item + // (guess it assumes that there will be no first responder to + // catch the trigger anyway?) OTOH, If we have a modal window, + // then setting the menu loader as target will make cocoa not + // deliver the trigger because the loader is then seen as modally + // shaddowed). So either way there are shortcomings. Instead, we + // decide the target as late as possible: + [item setTarget:modalWindowOnScreen ? nil : getMenuLoader()]; + } else { + syncNSMenuItemEnabled(item, NO); + } + } + } +} + +//toggling of modal state +static void qt_mac_set_modal_state(OSMenuRef menu, bool on) +{ + OSMenuRef merge = QCocoaMenu::mergeMenuHash.value(menu); + qt_mac_set_modal_state_helper_recursive(menu, merge, on); + // I'm ignoring the special items now, since they should get handled via a syncAction() +} + +bool qt_mac_menubar_is_open() +{ + return qt_mac_menus_open_count > 0; +} + +QCocoaMenuAction::~QCocoaMenuAction() +{ + [menu release]; + // Update the menu item if this action still owns it. For some items + // (like 'Quit') ownership will be transferred between all menu bars... + if (action && action.data() == reinterpret_cast<QAction *>([menuItem tag])) { + QAction::MenuRole role = action->menuRole(); + // Check if the item is owned by Qt, and should be hidden to keep it from causing + // problems. Do it for everything but the quit menu item since that should always + // be visible. + if (role > QAction::ApplicationSpecificRole && role < QAction::QuitRole) { + [menuItem setHidden:YES]; + } else if (role == QAction::TextHeuristicRole + && menuItem != [getMenuLoader() quitMenuItem]) { + [menuItem setHidden:YES]; + } + [menuItem setTag:nil]; + } + [menuItem release]; +} + +static NSMenuItem *qt_mac_menu_merge_action(OSMenuRef merge, QCocoaMenuAction *action) +{ + if (qt_mac_no_menubar_merge || action->action->menu() || action->action->isSeparator() + || action->action->menuRole() == QAction::NoRole) + return 0; + + QString t = qt_mac_removeMnemonics(action->action->text().toLower()); + int st = t.lastIndexOf(QLatin1Char('\t')); + if (st != -1) + t.remove(st, t.length()-st); + t.replace(QRegExp(QString::fromLatin1("\\.*$")), QLatin1String("")); //no ellipses + //now the fun part + NSMenuItem *ret = 0; + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); + + switch (action->action->menuRole()) { + case QAction::NoRole: + ret = 0; + break; + case QAction::ApplicationSpecificRole: + ret = [loader appSpecificMenuItem]; + break; + case QAction::AboutRole: + ret = [loader aboutMenuItem]; + break; + case QAction::AboutQtRole: + ret = [loader aboutQtMenuItem]; + break; + case QAction::QuitRole: + ret = [loader quitMenuItem]; + break; + case QAction::PreferencesRole: + ret = [loader preferencesMenuItem]; + break; + case QAction::TextHeuristicRole: { + QString aboutString = QMenuBar::tr("About").toLower(); + if (t.startsWith(aboutString) || t.endsWith(aboutString)) { + if (t.indexOf(QRegExp(QString::fromLatin1("qt$"), Qt::CaseInsensitive)) == -1) { + ret = [loader aboutMenuItem]; + } else { + ret = [loader aboutQtMenuItem]; + } + } else if (t.startsWith(QMenuBar::tr("Config").toLower()) + || t.startsWith(QMenuBar::tr("Preference").toLower()) + || t.startsWith(QMenuBar::tr("Options").toLower()) + || t.startsWith(QMenuBar::tr("Setting").toLower()) + || t.startsWith(QMenuBar::tr("Setup").toLower())) { + ret = [loader preferencesMenuItem]; + } else if (t.startsWith(QMenuBar::tr("Quit").toLower()) + || t.startsWith(QMenuBar::tr("Exit").toLower())) { + ret = [loader quitMenuItem]; + } + } + break; + } + + if (QMenuMergeList *list = QCocoaMenu::mergeMenuItemsHash.value(merge)) { + for(int i = 0; i < list->size(); ++i) { + const QMenuMergeItem &item = list->at(i); + if (item.menuItem == ret && item.action) + return 0; + } + } + + return ret; +} + +static QString qt_mac_menu_merge_text(QCocoaMenuAction *action) +{ + QString ret; + extern QString qt_mac_applicationmenu_string(int type); + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); + if (action->action->menuRole() == QAction::ApplicationSpecificRole) + ret = action->action->text(); + else if (action->menuItem == [loader aboutMenuItem]) { + ret = qt_mac_applicationmenu_string(6).arg(qt_mac_applicationName()); + } else if (action->menuItem == [loader aboutQtMenuItem]) { + if (action->action->text() == QString("About Qt")) + ret = QMenuBar::tr("About Qt"); + else + ret = action->action->text(); + } else if (action->menuItem == [loader preferencesMenuItem]) { + ret = qt_mac_applicationmenu_string(4); + } else if (action->menuItem == [loader quitMenuItem]) { + ret = qt_mac_applicationmenu_string(5).arg(qt_mac_applicationName()); + } + return ret; +} + +static QKeySequence qt_mac_menu_merge_accel(QCocoaMenuAction *action) +{ + QKeySequence ret; + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); + if (action->action->menuRole() == QAction::ApplicationSpecificRole) + ret = action->action->shortcut(); + else if (action->menuItem == [loader preferencesMenuItem]) + ret = QKeySequence(QKeySequence::Preferences); + else if (action->menuItem == [loader quitMenuItem]) + ret = QKeySequence(QKeySequence::Quit); + return ret; +} + +void Q_WIDGETS_EXPORT qt_mac_set_menubar_icons(bool b) +{ QApplication::instance()->setAttribute(Qt::AA_DontShowIconsInMenus, !b); } +void Q_WIDGETS_EXPORT qt_mac_set_native_menubar(bool b) +{ QApplication::instance()->setAttribute(Qt::AA_DontUseNativeMenuBar, !b); } +void Q_WIDGETS_EXPORT qt_mac_set_menubar_merge(bool b) { qt_mac_no_menubar_merge = !b; } + +/***************************************************************************** + QMenu bindings + *****************************************************************************/ + +QCocoaMenuAction::QCocoaMenuAction() + : menuItem(0) + , ignore_accel(0), merged(0), menu(0) +{ + +} + +QCocoaMenu::QCocoaMenu(QMenu *a_qtMenu) : menu(0), qtMenu(a_qtMenu) +{ +} + +QCocoaMenu::~QCocoaMenu() +{ + QCocoaAutoReleasePool pool; + while (actionItems.size()) { + QCocoaMenuAction *action = static_cast<QCocoaMenuAction *>(actionItems.takeFirst()); + if (QMenuMergeList *list = mergeMenuItemsHash.value(action->menu)) { + int i = 0; + while (i < list->size()) { + const QMenuMergeItem &item = list->at(i); + if (item.action == action) + list->removeAt(i); + else + ++i; + } + } + delete action; + } + mergeMenuHash.remove(menu); + mergeMenuItemsHash.remove(menu); + [menu release]; +} + + +void QCocoaMenu::addAction(QAction *a, QAction *before) +{ + QCocoaMenuAction *action = new QCocoaMenuAction; + action->action = a; + action->ignore_accel = 0; + action->merged = 0; + action->menu = 0; + + QCocoaMenuAction *cocoaBefore = findAction(before); + addAction(action, cocoaBefore); +} + + +void QCocoaMenu::addAction(QCocoaMenuAction *action, QCocoaMenuAction *before) +{ + QCocoaAutoReleasePool pool; + if (!action) + return; + int before_index = actionItems.indexOf(before); + if (before_index < 0) { + before = 0; + before_index = actionItems.size(); + } + actionItems.insert(before_index, action); + + [menu retain]; + [action->menu release]; + action->menu = menu; + + /* When the action is considered a mergable action it + will stay that way, until removed.. */ + if (!qt_mac_no_menubar_merge) { + OSMenuRef merge = QCocoaMenu::mergeMenuHash.value(menu); + if (merge) { + if (NSMenuItem *cmd = qt_mac_menu_merge_action(merge, action)) { + action->merged = 1; + [merge retain]; + [action->menu release]; + action->menu = merge; + [cmd retain]; + [cmd setAction:@selector(qtDispatcherToQAction:)]; + [cmd setTarget:nil]; + [action->menuItem release]; + action->menuItem = cmd; + QMenuMergeList *list = QCocoaMenu::mergeMenuItemsHash.value(merge); + if (!list) { + list = new QMenuMergeList; + QCocoaMenu::mergeMenuItemsHash.insert(merge, list); + } + list->append(QMenuMergeItem(cmd, action)); + } + } + } + + NSMenuItem *newItem = action->menuItem; + if (newItem == 0) { + newItem = createNSMenuItem(action->action->text()); + action->menuItem = newItem; + if (before) { + [menu insertItem:newItem atIndex:qMax(before_index, 0)]; + } else { + [menu addItem:newItem]; + } + } else { + [newItem setEnabled:YES]; + // ### + //[newItem setEnabled:!QApplicationPrivate::modalState()]; + + } + [newItem setTag:long(static_cast<QAction *>(action->action))]; + syncAction(action); +} + +void QCocoaMenu::syncAction(QAction *a) +{ + syncAction(findAction(a)); +} + +void QCocoaMenu::removeAction(QAction *a) +{ + removeAction(findAction(a)); +} + +QCocoaMenuAction *QCocoaMenu::findAction(QAction *action) const +{ + for (int i = 0; i < actionItems.size(); i++) { + QCocoaMenuAction *act = actionItems[i]; + if (action == act->action) + return act; + } + return 0; +} + + +// return an autoreleased string given a QKeySequence (currently only looks at the first one). +NSString *keySequenceToKeyEqivalent(const QKeySequence &accel) +{ + quint32 accel_key = (accel[0] & ~(Qt::MODIFIER_MASK | Qt::UNICODE_ACCEL)); + QChar cocoa_key = qt_mac_qtKey2CocoaKey(Qt::Key(accel_key)); + if (cocoa_key.isNull()) + cocoa_key = QChar(accel_key).toLower().unicode(); + return [NSString stringWithCharacters:&cocoa_key.unicode() length:1]; +} + +// return the cocoa modifier mask for the QKeySequence (currently only looks at the first one). +NSUInteger keySequenceModifierMask(const QKeySequence &accel) +{ + return constructModifierMask(accel[0]); +} + +void QCocoaMenu::syncAction(QCocoaMenuAction *action) +{ + if (!action) + return; + + NSMenuItem *item = action->menuItem; + if (!item) + return; + + QCocoaAutoReleasePool pool; + NSMenu *menu = [item menu]; + bool actionVisible = action->action->isVisible(); + [item setHidden:!actionVisible]; + if (!actionVisible) + return; + + int itemIndex = [menu indexOfItem:item]; + Q_ASSERT(itemIndex != -1); + if (action->action->isSeparator()) { + action->menuItem = [NSMenuItem separatorItem]; + [action->menuItem retain]; + [menu insertItem: action->menuItem atIndex:itemIndex]; + [menu removeItem:item]; + [item release]; + item = action->menuItem; + return; + } else if ([item isSeparatorItem]) { + // I'm no longer a separator... + action->menuItem = createNSMenuItem(action->action->text()); + [menu insertItem:action->menuItem atIndex:itemIndex]; + [menu removeItem:item]; + [item release]; + item = action->menuItem; + } + + //find text (and accel) + action->ignore_accel = 0; + QString text = action->action->text(); + QKeySequence accel = action->action->shortcut(); + { + int st = text.lastIndexOf(QLatin1Char('\t')); + if (st != -1) { + action->ignore_accel = 1; + accel = QKeySequence(text.right(text.length()-(st+1))); + text.remove(st, text.length()-st); + } + } + { + QString cmd_text = qt_mac_menu_merge_text(action); + if (!cmd_text.isEmpty()) { + text = cmd_text; + accel = qt_mac_menu_merge_accel(action); + } + } + // Show multiple key sequences as part of the menu text. + if (accel.count() > 1) + text += QLatin1String(" (") + accel.toString(QKeySequence::NativeText) + QLatin1String(")"); + +#if 0 + QString finalString = qt_mac_removeMnemonics(text); +#else + QString finalString = qt_mac_removeMnemonics(text); +#endif + // Cocoa Font and title + if (action->action->font().resolve()) { + const QFont &actionFont = action->action->font(); + NSFont *customMenuFont = [NSFont fontWithName:qt_mac_QStringToNSString(actionFont.family()) + size:actionFont.pointSize()]; + NSArray *keys = [NSArray arrayWithObjects:NSFontAttributeName, nil]; + NSArray *objects = [NSArray arrayWithObjects:customMenuFont, nil]; + NSDictionary *attributes = [NSDictionary dictionaryWithObjects:objects forKeys:keys]; + NSAttributedString *str = [[[NSAttributedString alloc] initWithString:qt_mac_QStringToNSString(finalString) + attributes:attributes] autorelease]; + [item setAttributedTitle: str]; + } else { + [item setTitle: qt_mac_QStringToNSString(finalString)]; + } + + if (action->action->menuRole() == QAction::AboutRole || action->action->menuRole() == QAction::QuitRole) + [item setTitle:qt_mac_QStringToNSString(text)]; + else + [item setTitle:qt_mac_QStringToNSString(qt_mac_removeMnemonics(text))]; + + // Cocoa Enabled + [item setEnabled: action->action->isEnabled()]; + + // Cocoa icon + NSImage *nsimage = 0; + if (!action->action->icon().isNull() && action->action->isIconVisibleInMenu()) { + nsimage = static_cast<NSImage *>(qt_mac_create_nsimage(action->action->icon().pixmap(16, QIcon::Normal))); + } + [item setImage:nsimage]; + [nsimage release]; + + if (action->action->menu()) { //submenu + QCocoaMenu *cocoaMenu = static_cast<QCocoaMenu *>(action->action->menu()->platformMenu()); + NSMenu *subMenu = cocoaMenu->macMenu(); + if ([subMenu supermenu] && [subMenu supermenu] != [item menu]) { + // The menu is already a sub-menu of another one. Cocoa will throw an exception, + // in such cases. For the time being, a new QMenu with same set of actions is the + // only workaround. + action->action->setEnabled(false); + } else { + [item setSubmenu:subMenu]; + } + } else { //respect some other items + [item setSubmenu:0]; + // No key equivalent set for multiple key QKeySequence. + if (accel.count() == 1) { + [item setKeyEquivalent:keySequenceToKeyEqivalent(accel)]; + [item setKeyEquivalentModifierMask:keySequenceModifierMask(accel)]; + } else { + [item setKeyEquivalent:@""]; + [item setKeyEquivalentModifierMask:NSCommandKeyMask]; + } + } + //mark glyph + [item setState:action->action->isChecked() ? NSOnState : NSOffState]; +} + +void QCocoaMenu::removeAction(QCocoaMenuAction *action) +{ + if (!action) + return; + QCocoaAutoReleasePool pool; + if (action->merged) { + if (reinterpret_cast<QAction *>([action->menuItem tag]) == action->action) { + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); + [action->menuItem setEnabled:false]; + if (action->menuItem != [loader quitMenuItem] + && action->menuItem != [loader preferencesMenuItem]) { + [[action->menuItem menu] removeItem:action->menuItem]; + } + } + } else { + [[action->menuItem menu] removeItem:action->menuItem]; + } + actionItems.removeAll(action); +} + +OSMenuRef QCocoaMenu::macMenu(OSMenuRef merge) +{ + if (menu) + return menu; + menu = qt_mac_create_menu(qtMenu); + if (merge) { + mergeMenuHash.insert(menu, merge); + } + QList<QAction*> items = qtMenu->actions(); + for(int i = 0; i < items.count(); i++) + addAction(items[i], 0); + syncSeparatorsCollapsible(qtMenu->separatorsCollapsible()); + return menu; +} + +/*! + \internal +*/ +void +QCocoaMenu::syncSeparatorsCollapsible(bool collapse) +{ + qt_mac_menu_collapseSeparators(menu, collapse); +} + +/*! + \internal +*/ +void QCocoaMenu::setMenuEnabled(bool enable) +{ + QCocoaAutoReleasePool pool; + if (enable) { + for (int i = 0; i < actionItems.count(); ++i) { + QCocoaMenuAction *menuItem = static_cast<QCocoaMenuAction *>(actionItems.at(i)); + if (menuItem && menuItem->action && menuItem->action->isEnabled()) { + [menuItem->menuItem setEnabled:true]; + } + } + } else { + NSMenu *menu = menu; + for (NSMenuItem *item in [menu itemArray]) { + [item setEnabled:false]; + } + } +} + +/*! + \internal + + This function will return the OSMenuRef used to create the native menu bar + bindings. + + If Qt is built against Carbon, the OSMenuRef is a MenuRef that can be used + with Carbon's Menu Manager API. + + If Qt is built against Cocoa, the OSMenuRef is a NSMenu pointer. + + \warning This function is not portable. + + \sa QMenuBar::macMenu() +*/ +/// OSMenuRef QMenu::macMenu(OSMenuRef merge) { return d_func()->macMenu(merge); } + +/***************************************************************************** + QMenuBar bindings + *****************************************************************************/ +typedef QHash<QWidget *, QMenuBar *> MenuBarHash; +Q_GLOBAL_STATIC(MenuBarHash, menubars) +static QMenuBar *fallback = 0; + +QCocoaMenuBar::QCocoaMenuBar(QMenuBar *a_qtMenuBar) : menu(0), apple_menu(0), qtMenuBar(a_qtMenuBar) +{ + macCreateMenuBar(qtMenuBar->parentWidget()); +} + +QCocoaMenuBar::~QCocoaMenuBar() +{ + for(QList<QCocoaMenuAction*>::Iterator it = actionItems.begin(); it != actionItems.end(); ++it) + delete (*it); + [apple_menu release]; + [menu release]; +} +void QCocoaMenuBar::handleReparent(QWidget *newParent) +{ + if (macWidgetHasNativeMenubar(newParent)) { + // If the new parent got a native menubar from before, keep that + // menubar rather than replace it with this one (because a parents + // menubar has precedence over children menubars). + macDestroyMenuBar(); + macCreateMenuBar(newParent); + } + +} + +void QCocoaMenuBar::addAction(QAction *action, QAction *beforeAction) +{ + if (action->isSeparator() || !menu) + return; + QCocoaMenuAction *cocoaAction = new QCocoaMenuAction; + cocoaAction->action = action; + cocoaAction->ignore_accel = 1; + QCocoaMenuAction *cocoaBeforeAction = findAction(beforeAction); + addAction(cocoaAction, cocoaBeforeAction); +} + +void QCocoaMenuBar::addAction(QCocoaMenuAction *action, QCocoaMenuAction *before) +{ + if (!action || !menu) + return; + + int before_index = actionItems.indexOf(before); + if (before_index < 0) { + before = 0; + before_index = actionItems.size(); + } + actionItems.insert(before_index, action); + + MenuItemIndex index = actionItems.size()-1; + + action->menu = menu; + QCocoaAutoReleasePool pool; + [action->menu retain]; + NSMenuItem *newItem = createNSMenuItem(action->action->text()); + action->menuItem = newItem; + + if (before) { + [menu insertItem:newItem atIndex:qMax(1, before_index + 1)]; + index = before_index; + } else { + [menu addItem:newItem]; + } + [newItem setTag:long(static_cast<QAction *>(action->action))]; + syncAction(action); +} + + +void QCocoaMenuBar::syncAction(QCocoaMenuAction *action) +{ + if (!action || !menu) + return; + + QCocoaAutoReleasePool pool; + NSMenuItem *item = action->menuItem; + + OSMenuRef submenu = 0; + bool release_submenu = false; + if (action->action->menu()) { + QCocoaMenu *cocoaMenu = static_cast<QCocoaMenu *>(action->action->menu()->platformMenu()); + if (!cocoaMenu) { + + } + + if ((submenu = cocoaMenu->macMenu(apple_menu))) { + if ([submenu supermenu] && [submenu supermenu] != [item menu]) + return; + else + [item setSubmenu:submenu]; + } + } + + if (submenu) { + bool visible = actualMenuItemVisibility(this, action); + [item setSubmenu: submenu]; + [submenu setTitle:qt_mac_QStringToNSString(qt_mac_removeMnemonics(action->action->text()))]; + syncNSMenuItemVisiblity(item, visible); + if (release_submenu) { //no pointers to it + [submenu release]; + } + } else { + qWarning("QMenu: No OSMenuRef created for popup menu"); + } +} + + +void QCocoaMenuBar::removeAction(QCocoaMenuAction *action) +{ + if (!action || !menu) + return; + QCocoaAutoReleasePool pool; + [action->menu removeItem:action->menuItem]; + actionItems.removeAll(action); +} + +void QCocoaMenuBar::syncAction(QAction *a) +{ + syncAction(findAction(a)); +} + +void QCocoaMenuBar::removeAction(QAction *a) +{ + removeAction(findAction(a)); +} + +QCocoaMenuAction *QCocoaMenuBar::findAction(QAction *action) const +{ + for (int i = 0; i < actionItems.size(); i++) { + QCocoaMenuAction *act = actionItems[i]; + if (action == act->action) + return act; + } + return 0; +} + +bool QCocoaMenuBar::macWidgetHasNativeMenubar(QWidget *widget) +{ + // This function is different from q->isNativeMenuBar(), as + // it returns true only if a native menu bar is actually + // _created_. + if (!widget) + return false; + return menubars()->contains(widget->window()); +} + +void QCocoaMenuBar::macCreateMenuBar(QWidget *parent) +{ + static int dontUseNativeMenuBar = -1; + // We call the isNativeMenuBar function here + // because that will make sure that local overrides + // are dealt with correctly. q->isNativeMenuBar() will, if not + // overridden, depend on the attribute Qt::AA_DontUseNativeMenuBar: + bool qt_mac_no_native_menubar = !qtMenuBar->isNativeMenuBar(); + if (qt_mac_no_native_menubar == false && dontUseNativeMenuBar < 0) { + // The menubar is set to be native. Let's check (one time only + // for all menubars) if this is OK with the rest of the environment. + // As a result, Qt::AA_DontUseNativeMenuBar is set. NB: the application + // might still choose to not respect, or change, this flag. + bool isPlugin = QApplication::testAttribute(Qt::AA_MacPluginApplication); + bool environmentSaysNo = !qgetenv("QT_MAC_NO_NATIVE_MENUBAR").isEmpty(); + dontUseNativeMenuBar = isPlugin || environmentSaysNo; + QApplication::instance()->setAttribute(Qt::AA_DontUseNativeMenuBar, dontUseNativeMenuBar); + qt_mac_no_native_menubar = !qtMenuBar->isNativeMenuBar(); + } + if (qt_mac_no_native_menubar == false) { + // INVARIANT: Use native menubar. + macUpdateMenuBar(); + if (!parent && !fallback) { + fallback = qtMenuBar; + } else if (parent && parent->isWindow()) { + menubars()->insert(qtMenuBar->window(), qtMenuBar); + } + } +} + +void QCocoaMenuBar::macDestroyMenuBar() +{ + QCocoaAutoReleasePool pool; + if (fallback == qtMenuBar) + fallback = 0; + QWidget *tlw = qtMenuBar->window(); + menubars()->remove(tlw); + + if (!qt_mac_current_menubar.qmenubar || qt_mac_current_menubar.qmenubar == qtMenuBar) { + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); + [loader removeActionsFromAppMenu]; + QCocoaMenuBar::macUpdateMenuBar(); + } +} + +OSMenuRef QCocoaMenuBar::macMenu() +{ + if (!qtMenuBar->isNativeMenuBar()) { + return 0; + } else if (!menu) { + menu = qt_mac_create_menu(qtMenuBar); + ProcessSerialNumber mine, front; + if (GetCurrentProcess(&mine) == noErr && GetFrontProcess(&front) == noErr) { + if (!qt_mac_no_menubar_merge && !apple_menu) { + apple_menu = qt_mac_create_menu(qtMenuBar); + [apple_menu setTitle:qt_mac_QStringToNSString(QString(QChar(0x14)))]; + NSMenuItem *apple_menuItem = [[NSMenuItem alloc] init]; + [apple_menuItem setSubmenu:menu]; + [apple_menu addItem:apple_menuItem]; + [apple_menuItem release]; + } + if (apple_menu) { + QCocoaMenu::mergeMenuHash.insert(menu, apple_menu); + } + QList<QAction*> items = qtMenuBar->actions(); + for(int i = 0; i < items.count(); i++) + addAction(items[i], 0); + } + } + return menu; +} + +/*! + \internal + + This function will return the OSMenuRef used to create the native menu bar + bindings. This OSMenuRef is then set as the root menu for the Menu + Manager. + + \warning This function is not portable. + + \sa QMenu::macMenu() +*/ +//OSMenuRef QMenuBar::macMenu() { return d_func()->macMenu(); } + +/* ! + \internal + Ancestor function that crosses windows (QWidget::isAncestorOf + only considers widgets within the same window). +*/ +static bool qt_mac_is_ancestor(QWidget* possibleAncestor, QWidget *child) +{ + if (!possibleAncestor) + return false; + + QWidget * current = child->parentWidget(); + while (current != 0) { + if (current == possibleAncestor) + return true; + current = current->parentWidget(); + } + return false; +} + +/* ! + \internal + Returns true if the entries of menuBar should be disabled, + based on the modality type of modalWidget. +*/ +static bool qt_mac_should_disable_menu(QMenuBar *menuBar) +{ + QWidget *modalWidget = qApp->activeModalWidget(); + if (!modalWidget) + return false; + + if (menuBar && menuBar == menubars()->value(modalWidget)) + // The menu bar is owned by the modal widget. + // In that case we should enable it: + return false; + + // When there is an application modal window on screen, the entries of + // the menubar should be disabled. The exception in Qt is that if the + // modal window is the only window on screen, then we enable the menu bar. + QWidget *w = modalWidget; + QWidgetList topLevelWidgets = QApplication::topLevelWidgets(); + while (w) { + if (w->isVisible() && w->windowModality() == Qt::ApplicationModal) { + for (int i=0; i<topLevelWidgets.size(); ++i) { + QWidget *top = topLevelWidgets.at(i); + if (w != top && top->isVisible()) { + // INVARIANT: we found another visible window + // on screen other than our modalWidget. We therefore + // disable the menu bar to follow normal modality logic: + return true; + } + } + // INVARIANT: We have only one window on screen that happends + // to be application modal. We choose to enable the menu bar + // in that case to e.g. enable the quit menu item. + return false; + } + w = w->parentWidget(); + } + + // INVARIANT: modalWidget is window modal. Disable menu entries + // if the menu bar belongs to an ancestor of modalWidget. If menuBar + // is nil, we understand it as the default menu bar set by the nib: + return menuBar ? qt_mac_is_ancestor(menuBar->parentWidget(), modalWidget) : false; +} + +static QWidget *findWindowThatShouldDisplayMenubar() +{ + QWidget *w = qApp->activeWindow(); + + if (!w) { + // We have no active window on screen. Try to + // find a window from the list of top levels: + QWidgetList tlws = QApplication::topLevelWidgets(); + for(int i = 0; i < tlws.size(); ++i) { + QWidget *tlw = tlws.at(i); + if ((tlw->isVisible() && tlw->windowType() != Qt::Tool && + tlw->windowType() != Qt::Popup)) { + w = tlw; + break; + } + } + } + + return w; +} + +static QMenuBar *findMenubarForWindow(QWidget *w) +{ + QMenuBar *mb = 0; + if (w) { + mb = menubars()->value(w); + +#if 0 +// ### +//#ifndef QT_NO_MAINWINDOW + QDockWidget *dw = qobject_cast<QDockWidget *>(w); + if (!mb && dw) { + QMainWindow *mw = qobject_cast<QMainWindow *>(dw->parentWidget()); + if (mw && (mb = menubars()->value(mw))) + w = mw; + } +#endif + while(w && !mb) + mb = menubars()->value((w = w->parentWidget())); + } + + if (!mb) { + // We could not find a menu bar for the window. Lets + // check if we have a global (parentless) menu bar instead: + mb = fallback; + } + + return mb; +} + +void qt_mac_clear_menubar() +{ + if (QApplication::testAttribute(Qt::AA_MacPluginApplication)) + return; + + QCocoaAutoReleasePool pool; + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); + NSMenu *menu = [loader menu]; + [loader ensureAppMenuInMenu:menu]; + [NSApp setMainMenu:menu]; + const bool modal = qt_mac_should_disable_menu(0); + if (qt_mac_current_menubar.qmenubar || modal != qt_mac_current_menubar.modal) + qt_mac_set_modal_state(menu, modal); + qt_mac_current_menubar.qmenubar = 0; + qt_mac_current_menubar.modal = modal; +} + +/*! + \internal + + This function will update the current menu bar and set it as the + active menu bar in the Menu Manager. + + \warning This function is not portable. +*/ +void QCocoaMenuBar::macUpdateMenuBar() +{ + [getMenuLoader() performSelectorOnMainThread: @selector(qtUpdateMenubar) withObject: nil waitUntilDone: NO]; +} + +bool QCocoaMenuBar::macUpdateMenuBarImmediatly() +{ + bool ret = false; + cancelAllMenuTracking(); + QWidget *w = findWindowThatShouldDisplayMenubar(); + QMenuBar *mb = findMenubarForWindow(w); + + // ### extern bool qt_mac_app_fullscreen; //qapplication_mac.mm + bool qt_mac_app_fullscreen = false; + // We need to see if we are in full screen mode, if so we need to + // switch the full screen mode to be able to show or hide the menubar. + if(w && mb) { + // This case means we are creating a menubar, check if full screen + if(w->isFullScreen()) { + // Ok, switch to showing the menubar when hovering over it. + SetSystemUIMode(kUIModeAllHidden, kUIOptionAutoShowMenuBar); + qt_mac_app_fullscreen = true; + } + } else if(w) { + // Removing a menubar + if(w->isFullScreen()) { + // Ok, switch to not showing the menubar when hovering on it + SetSystemUIMode(kUIModeAllHidden, 0); + qt_mac_app_fullscreen = true; + } + } + + if (mb && mb->isNativeMenuBar()) { + + // ### + bool modal = false; + //bool modal = QGuiApplicationPrivate::modalState(); + QCocoaAutoReleasePool pool; + if (OSMenuRef menu = reinterpret_cast<QCocoaMenuBar *>(mb->platformMenuBar())->macMenu()) { + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); + [loader ensureAppMenuInMenu:menu]; + [NSApp setMainMenu:menu]; + syncMenuBarItemsVisiblity(reinterpret_cast<QCocoaMenuBar *>(mb->platformMenuBar())); + + if (OSMenuRef tmpMerge = QCocoaMenu::mergeMenuHash.value(menu)) { + if (QMenuMergeList *mergeList + = QCocoaMenu::mergeMenuItemsHash.value(tmpMerge)) { + const int mergeListSize = mergeList->size(); + + for (int i = 0; i < mergeListSize; ++i) { + const QMenuMergeItem &mergeItem = mergeList->at(i); + // Ideally we would call QCocoaMenu::syncAction, but that requires finding + // the original QMen and likely doing more work than we need. + // For example, enabled is handled below. + [mergeItem.menuItem setTag:reinterpret_cast<long>( + static_cast<QAction *>(mergeItem.action->action))]; + [mergeItem.menuItem setHidden:!(mergeItem.action->action->isVisible())]; + } + } + } + // Check if menu is modally shaddowed and should be disabled: + modal = qt_mac_should_disable_menu(mb); + if (mb != qt_mac_current_menubar.qmenubar || modal != qt_mac_current_menubar.modal) + qt_mac_set_modal_state(menu, modal); + } + qt_mac_current_menubar.qmenubar = mb; + qt_mac_current_menubar.modal = modal; + ret = true; + } else if (qt_mac_current_menubar.qmenubar && qt_mac_current_menubar.qmenubar->isNativeMenuBar()) { + // INVARIANT: The currently active menu bar (if any) is not native. But we do have a + // native menu bar from before. So we need to decide whether or not is should be enabled: + const bool modal = qt_mac_should_disable_menu(qt_mac_current_menubar.qmenubar); + if (modal != qt_mac_current_menubar.modal) { + ret = true; + if (OSMenuRef menu = reinterpret_cast<QCocoaMenuBar *>(qt_mac_current_menubar.qmenubar->platformMenuBar())->macMenu()) { + QT_MANGLE_NAMESPACE(QCocoaMenuLoader) *loader = getMenuLoader(); + [loader ensureAppMenuInMenu:menu]; + [NSApp setMainMenu:menu]; + syncMenuBarItemsVisiblity(reinterpret_cast<QCocoaMenuBar *>(qt_mac_current_menubar.qmenubar->platformMenuBar())); + qt_mac_set_modal_state(menu, modal); + } + qt_mac_current_menubar.modal = modal; + } + } + + if (!ret) { + qt_mac_clear_menubar(); + } + return ret; +} + +QHash<OSMenuRef, OSMenuRef> QCocoaMenu::mergeMenuHash; +QHash<OSMenuRef, QMenuMergeList*> QCocoaMenu::mergeMenuItemsHash; + +bool QCocoaMenu::merged(const QAction *action) const +{ + if (OSMenuRef merge = mergeMenuHash.value(menu)) { + if (QMenuMergeList *list = mergeMenuItemsHash.value(merge)) { + for(int i = 0; i < list->size(); ++i) { + const QMenuMergeItem &item = list->at(i); + if (item.action->action == action) + return true; + } + } + } + return false; +} + +//creation of the OSMenuRef +static OSMenuRef qt_mac_create_menu(QWidget *w) +{ + OSMenuRef ret; + if (QMenu *qmenu = qobject_cast<QMenu *>(w)){ + ret = [[QT_MANGLE_NAMESPACE(QNativeCocoaMenu) alloc] initWithQMenu:qmenu]; + } else { + ret = [[NSMenu alloc] init]; + } + return ret; +} + +QT_END_NAMESPACE diff --git a/src/plugins/platforms/cocoa/qnsview.h b/src/plugins/platforms/cocoa/qnsview.h index 69a11134bd..0b96928d5b 100644 --- a/src/plugins/platforms/cocoa/qnsview.h +++ b/src/plugins/platforms/cocoa/qnsview.h @@ -48,17 +48,18 @@ @interface QNSView : NSView { CGImageRef m_cgImage; - QWidget *m_widget; + QWindow *m_window; Qt::MouseButtons m_buttons; } - (id)init; -- (id)initWithWidget:(QWidget *)widget; +- (id)initWithQWindow:(QWindow *)window; - (void)setImage:(QImage *)image; - (void)drawRect:(NSRect)dirtyRect; - (BOOL)isFlipped; +- (BOOL)acceptsFirstResponder; - (void)handleMouseEvent:(NSEvent *)theEvent; - (void)mouseDown:(NSEvent *)theEvent; @@ -74,6 +75,12 @@ - (void)otherMouseDragged:(NSEvent *)theEvent; - (void)otherMouseUp:(NSEvent *)theEvent; +- (int) convertKeyCode : (QChar)keyCode; +- (Qt::KeyboardModifiers) convertKeyModifiers : (ulong)modifierFlags; +- (void)handleKeyEvent:(NSEvent *)theEvent eventType:(int)eventType; +- (void)keyDown:(NSEvent *)theEvent; +- (void)keyUp:(NSEvent *)theEvent; + @end #endif //QNSVIEW_H diff --git a/src/plugins/platforms/cocoa/qnsview.mm b/src/plugins/platforms/cocoa/qnsview.mm index 037cbdb5d6..f3c71d9eed 100644 --- a/src/plugins/platforms/cocoa/qnsview.mm +++ b/src/plugins/platforms/cocoa/qnsview.mm @@ -39,12 +39,20 @@ ** ****************************************************************************/ +#include <Carbon/Carbon.h> + #include "qnsview.h" +#include "qcocoahelpers.h" #include <QtGui/QWindowSystemInterface> - #include <QtCore/QDebug> +@interface NSEvent (Qt_Compile_Leopard_DeviceDelta) + - (CGFloat)deviceDeltaX; + - (CGFloat)deviceDeltaY; + - (CGFloat)deviceDeltaZ; +@end + @implementation QNSView - (id) init @@ -52,16 +60,16 @@ self = [super init]; if (self) { m_cgImage = 0; - m_widget = 0; + m_window = 0; m_buttons = Qt::NoButton; } return self; } -- (id)initWithWidget:(QWidget *)widget { +- (id)initWithQWindow:(QWindow *)widget { self = [self init]; if (self) { - m_widget = widget; + m_window = widget; } return self; } @@ -91,7 +99,7 @@ bitDepth, bytesPrLine, cgColourSpaceRef, - kCGImageAlphaNone, + kCGBitmapByteOrder32Little | kCGImageAlphaNoneSkipFirst, cgDataProviderRef, NULL, false, @@ -130,82 +138,185 @@ return YES; } +- (BOOL)acceptsFirstResponder +{ + return YES; +} + - (void)handleMouseEvent:(NSEvent *)theEvent; { - NSPoint point = [self convertPoint: [theEvent locationInWindow] fromView: nil]; - QPoint qt_localPoint(point.x,point.y); + NSPoint windowPoint = [self convertPoint: [theEvent locationInWindow] fromView: nil]; + QPoint qt_windowPoint(windowPoint.x, windowPoint.y); NSTimeInterval timestamp = [theEvent timestamp]; ulong qt_timestamp = timestamp * 1000; - QWindowSystemInterface::handleMouseEvent(m_widget,qt_timestamp,qt_localPoint,QPoint(),m_buttons); + // ### Should the points be windowPoint and screenPoint? + QWindowSystemInterface::handleMouseEvent(m_window, qt_timestamp, qt_windowPoint, qt_windowPoint, m_buttons); +} +- (void)mouseDown:(NSEvent *)theEvent +{ + m_buttons |= Qt::LeftButton; + [self handleMouseEvent:theEvent]; +} + +- (void)mouseDragged:(NSEvent *)theEvent +{ + if (!(m_buttons & Qt::LeftButton)) + qWarning("Internal Mousebutton tracking invalid(missing Qt::LeftButton"); + [self handleMouseEvent:theEvent]; +} + +- (void)mouseUp:(NSEvent *)theEvent +{ + m_buttons &= QFlag(~int(Qt::LeftButton)); + [self handleMouseEvent:theEvent]; } - - (void)mouseDown:(NSEvent *)theEvent - { - m_buttons |= Qt::LeftButton; - [self handleMouseEvent:theEvent]; - } - - (void)mouseDragged:(NSEvent *)theEvent - { - if (!(m_buttons & Qt::LeftButton)) - qWarning("Internal Mousebutton tracking invalid(missing Qt::LeftButton"); - [self handleMouseEvent:theEvent]; - } - - (void)mouseUp:(NSEvent *)theEvent - { - m_buttons &= QFlag(~int(Qt::LeftButton)); - [self handleMouseEvent:theEvent]; - } - (void)mouseMoved:(NSEvent *)theEvent { - qDebug() << "mouseMove"; [self handleMouseEvent:theEvent]; } + - (void)mouseEntered:(NSEvent *)theEvent { - Q_UNUSED(theEvent); - QWindowSystemInterface::handleEnterEvent(m_widget); + Q_UNUSED(theEvent); + QWindowSystemInterface::handleEnterEvent(m_window); } + - (void)mouseExited:(NSEvent *)theEvent { - Q_UNUSED(theEvent); - QWindowSystemInterface::handleLeaveEvent(m_widget); + Q_UNUSED(theEvent); + QWindowSystemInterface::handleLeaveEvent(m_window); } + - (void)rightMouseDown:(NSEvent *)theEvent { - m_buttons |= Qt::RightButton; + m_buttons |= Qt::RightButton; [self handleMouseEvent:theEvent]; } + - (void)rightMouseDragged:(NSEvent *)theEvent { - if (!(m_buttons & Qt::LeftButton)) - qWarning("Internal Mousebutton tracking invalid(missing Qt::LeftButton"); - [self handleMouseEvent:theEvent]; + if (!(m_buttons & Qt::LeftButton)) + qWarning("Internal Mousebutton tracking invalid(missing Qt::LeftButton"); + [self handleMouseEvent:theEvent]; } + - (void)rightMouseUp:(NSEvent *)theEvent { - m_buttons &= QFlag(~int(Qt::RightButton)); - [self handleMouseEvent:theEvent]; + m_buttons &= QFlag(~int(Qt::RightButton)); + [self handleMouseEvent:theEvent]; } + - (void)otherMouseDown:(NSEvent *)theEvent { - m_buttons |= Qt::RightButton; + m_buttons |= Qt::RightButton; [self handleMouseEvent:theEvent]; } + - (void)otherMouseDragged:(NSEvent *)theEvent { - if (!(m_buttons & Qt::LeftButton)) - qWarning("Internal Mousebutton tracking invalid(missing Qt::LeftButton"); - [self handleMouseEvent:theEvent]; + if (!(m_buttons & Qt::LeftButton)) + qWarning("Internal Mousebutton tracking invalid(missing Qt::LeftButton"); + [self handleMouseEvent:theEvent]; } + - (void)otherMouseUp:(NSEvent *)theEvent { - m_buttons &= QFlag(~int(Qt::MiddleButton)); - [self handleMouseEvent:theEvent]; + m_buttons &= QFlag(~int(Qt::MiddleButton)); + [self handleMouseEvent:theEvent]; } +#ifndef QT_NO_WHEELEVENT +- (void)scrollWheel:(NSEvent *)theEvent +{ + int deltaX = 0; + int deltaY = 0; + int deltaZ = 0; + + const EventRef carbonEvent = (EventRef)[theEvent eventRef]; + const UInt32 carbonEventKind = carbonEvent ? ::GetEventKind(carbonEvent) : 0; + const bool scrollEvent = carbonEventKind == kEventMouseScroll; + + if (scrollEvent) { + // The mouse device containts pixel scroll wheel support (Mighty Mouse, Trackpad). + // Since deviceDelta is delivered as pixels rather than degrees, we need to + // convert from pixels to degrees in a sensible manner. + // It looks like 1/4 degrees per pixel behaves most native. + // (NB: Qt expects the unit for delta to be 8 per degree): + const int pixelsToDegrees = 2; // 8 * 1/4 + deltaX = [theEvent deviceDeltaX] * pixelsToDegrees; + deltaY = [theEvent deviceDeltaY] * pixelsToDegrees; + deltaZ = [theEvent deviceDeltaZ] * pixelsToDegrees; + } else { + // carbonEventKind == kEventMouseWheelMoved + // Remove acceleration, and use either -120 or 120 as delta: + deltaX = qBound(-120, int([theEvent deltaX] * 10000), 120); + deltaY = qBound(-120, int([theEvent deltaY] * 10000), 120); + deltaZ = qBound(-120, int([theEvent deltaZ] * 10000), 120); + } + + NSPoint windowPoint = [self convertPoint: [theEvent locationInWindow] fromView: nil]; + QPoint qt_windowPoint(windowPoint.x, windowPoint.y); + NSTimeInterval timestamp = [theEvent timestamp]; + ulong qt_timestamp = timestamp * 1000; + + if (deltaX != 0) + QWindowSystemInterface::handleWheelEvent(m_window, qt_timestamp, qt_windowPoint, qt_windowPoint, deltaX, Qt::Horizontal); + + if (deltaY != 0) + QWindowSystemInterface::handleWheelEvent(m_window, qt_timestamp, qt_windowPoint, qt_windowPoint, deltaY, Qt::Vertical); + if (deltaZ != 0) + // Qt doesn't explicitly support wheels with a Z component. In a misguided attempt to + // try to be ahead of the pack, I'm adding this extra value. + QWindowSystemInterface::handleWheelEvent(m_window, qt_timestamp, qt_windowPoint, qt_windowPoint, deltaY, (Qt::Orientation)3); +} +#endif //QT_NO_WHEELEVENT + +- (int) convertKeyCode : (QChar)keyChar +{ + return qt_mac_cocoaKey2QtKey(keyChar); +} + +- (Qt::KeyboardModifiers) convertKeyModifiers : (ulong)modifierFlags +{ + Qt::KeyboardModifiers qtMods =Qt::NoModifier; + if (modifierFlags & NSShiftKeyMask) + qtMods |= Qt::ShiftModifier; + if (modifierFlags & NSControlKeyMask) + qtMods |= Qt::MetaModifier; + if (modifierFlags & NSAlternateKeyMask) + qtMods |= Qt::AltModifier; + if (modifierFlags & NSCommandKeyMask) + qtMods |= Qt::ControlModifier; + if (modifierFlags & NSNumericPadKeyMask) + qtMods |= Qt::KeypadModifier; + return qtMods; +} + +- (void)handleKeyEvent:(NSEvent *)theEvent eventType:(int)eventType +{ + NSTimeInterval timestamp = [theEvent timestamp]; + ulong qt_timestamp = timestamp * 1000; + QString characters = QString::fromUtf8([[theEvent characters] UTF8String]); + Qt::KeyboardModifiers modifiers = [self convertKeyModifiers : [theEvent modifierFlags]]; + QChar ch([[theEvent charactersIgnoringModifiers] characterAtIndex:0]); + int keyCode = [self convertKeyCode : ch]; + + QWindowSystemInterface::handleKeyEvent(m_window, qt_timestamp, QEvent::Type(eventType), keyCode, modifiers, characters); +} + +- (void)keyDown:(NSEvent *)theEvent +{ + [self handleKeyEvent : theEvent eventType :int(QEvent::KeyPress)]; +} + +- (void)keyUp:(NSEvent *)theEvent +{ + [self handleKeyEvent : theEvent eventType :int(QEvent::KeyRelease)]; +} @end diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.h b/src/plugins/platforms/cocoa/qnswindowdelegate.h index cf296c4a8b..5cd226a71d 100644 --- a/src/plugins/platforms/cocoa/qnswindowdelegate.h +++ b/src/plugins/platforms/cocoa/qnswindowdelegate.h @@ -46,6 +46,26 @@ #include "qcocoawindow.h" +#if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5 +@protocol NSWindowDelegate <NSObject> +//- (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize; +//- (void)windowDidMiniaturize:(NSNotification*)notification; +- (void)windowDidResize:(NSNotification *)notification; +- (void)windowWillClose:(NSNotification *)notification; +//- (NSRect)windowWillUseStandardFrame:(NSWindow *)window defaultFrame:(NSRect)defaultFrame; +- (void)windowDidMove:(NSNotification *)notification; +//- (BOOL)windowShouldClose:(id)window; +//- (void)windowDidDeminiaturize:(NSNotification *)notification; +//- (void)windowDidBecomeMain:(NSNotification*)notification; +//- (void)windowDidResignMain:(NSNotification*)notification; +//- (void)windowDidBecomeKey:(NSNotification*)notification; +//- (void)windowDidResignKey:(NSNotification*)notification; +//- (BOOL)window:(NSWindow *)window shouldPopUpDocumentPathMenu:(NSMenu *)menu; +//- (BOOL)window:(NSWindow *)window shouldDragDocumentWithEvent:(NSEvent *)event from:(NSPoint)dragImageLocation withPasteboard:(NSPasteboard *)pasteboard; +//- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame; +@end +#endif + @interface QNSWindowDelegate : NSObject <NSWindowDelegate> { QCocoaWindow *m_cocoaWindow; @@ -54,6 +74,7 @@ - (id)initWithQCocoaWindow: (QCocoaWindow *) cocoaWindow; - (void)windowDidResize:(NSNotification *)notification; +- (void)windowDidMove:(NSNotification *)notification; - (void)windowWillClose:(NSNotification *)notification; @end diff --git a/src/plugins/platforms/cocoa/qnswindowdelegate.mm b/src/plugins/platforms/cocoa/qnswindowdelegate.mm index 887b08f6d2..869ef7840b 100644 --- a/src/plugins/platforms/cocoa/qnswindowdelegate.mm +++ b/src/plugins/platforms/cocoa/qnswindowdelegate.mm @@ -64,10 +64,20 @@ } } +- (void)windowDidMove:(NSNotification *)notification +{ + Q_UNUSED(notification); + if (m_cocoaWindow) { + m_cocoaWindow->windowDidMove(); + } +} + - (void)windowWillClose:(NSNotification *)notification { Q_UNUSED(notification); - QWindowSystemInterface::handleCloseEvent(m_cocoaWindow->widget()); + if (m_cocoaWindow) { + m_cocoaWindow->windowWillClose(); + } } @end diff --git a/src/plugins/platforms/cocoa/qt_menu.nib/classes.nib b/src/plugins/platforms/cocoa/qt_menu.nib/classes.nib new file mode 100644 index 0000000000..0031e0e4e5 --- /dev/null +++ b/src/plugins/platforms/cocoa/qt_menu.nib/classes.nib @@ -0,0 +1,59 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IBClasses</key> + <array> + <dict> + <key>ACTIONS</key> + <dict> + <key>hide</key> + <string>id</string> + <key>hideOtherApplications</key> + <string>id</string> + <key>orderFrontStandardAboutPanel</key> + <string>id</string> + <key>qtDispatcherToQAction</key> + <string>id</string> + <key>terminate</key> + <string>id</string> + <key>unhideAllApplications</key> + <string>id</string> + </dict> + <key>CLASS</key> + <string>QCocoaMenuLoader</string> + <key>LANGUAGE</key> + <string>ObjC</string> + <key>OUTLETS</key> + <dict> + <key>aboutItem</key> + <string>NSMenuItem</string> + <key>aboutQtItem</key> + <string>NSMenuItem</string> + <key>appMenu</key> + <string>NSMenu</string> + <key>hideItem</key> + <string>NSMenuItem</string> + <key>preferencesItem</key> + <string>NSMenuItem</string> + <key>quitItem</key> + <string>NSMenuItem</string> + <key>theMenu</key> + <string>NSMenu</string> + </dict> + <key>SUPERCLASS</key> + <string>NSResponder</string> + </dict> + <dict> + <key>CLASS</key> + <string>FirstResponder</string> + <key>LANGUAGE</key> + <string>ObjC</string> + <key>SUPERCLASS</key> + <string>NSObject</string> + </dict> + </array> + <key>IBVersion</key> + <string>1</string> +</dict> +</plist> diff --git a/src/plugins/platforms/cocoa/qt_menu.nib/info.nib b/src/plugins/platforms/cocoa/qt_menu.nib/info.nib new file mode 100644 index 0000000000..02e5cca562 --- /dev/null +++ b/src/plugins/platforms/cocoa/qt_menu.nib/info.nib @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>IBFramework Version</key> + <string>672</string> + <key>IBOldestOS</key> + <integer>5</integer> + <key>IBOpenObjects</key> + <array> + <integer>57</integer> + </array> + <key>IBSystem Version</key> + <string>9L31a</string> + <key>targetFramework</key> + <string>IBCocoaFramework</string> +</dict> +</plist> diff --git a/src/plugins/platforms/cocoa/qt_menu.nib/keyedobjects.nib b/src/plugins/platforms/cocoa/qt_menu.nib/keyedobjects.nib Binary files differnew file mode 100644 index 0000000000..3edb0ed2eb --- /dev/null +++ b/src/plugins/platforms/cocoa/qt_menu.nib/keyedobjects.nib |