diff --git a/5.6.2/cleanup.bat b/5.6.2/cleanup.bat new file mode 100644 index 0000000..9fc167d --- /dev/null +++ b/5.6.2/cleanup.bat @@ -0,0 +1,4 @@ +del *.obj /s /f +del *.ilk /s /f +del *.pch /s /f +del Makefile* /s /f \ No newline at end of file diff --git a/5.6.2/qt5vars.cmd b/5.6.2/qt5vars.cmd new file mode 100644 index 0000000..d4b17fc --- /dev/null +++ b/5.6.2/qt5vars.cmd @@ -0,0 +1,3 @@ +%comspec% /k ""C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat"" x86 +set PATH=c:\qt\qtbase\bin;c:\qt\gnuwin32\bin;%PATH% +set QMAKESPEC=win32-msvc2013 \ No newline at end of file diff --git a/5.6.2/qtbase/mkspecs/common/msvc-desktop.conf b/5.6.2/qtbase/mkspecs/common/msvc-desktop.conf new file mode 100644 index 0000000..9f9f6dd --- /dev/null +++ b/5.6.2/qtbase/mkspecs/common/msvc-desktop.conf @@ -0,0 +1,104 @@ +# +# qmake configuration for Microsoft Visual Studio C/C++ Compiler +# This mkspec is used for all win32-msvcXXXX specs +# + +isEmpty(MSC_VER)|isEmpty(MSVC_VER): error("Source mkspec must set both MSC_VER and MSVC_VER.") + +# +# Baseline: Visual Studio 2005 (8.0), VC++ 14.0 +# + +include(angle.conf) + +MAKEFILE_GENERATOR = MSVC.NET +QMAKE_PLATFORM = win32 +QMAKE_COMPILER = msvc +CONFIG += incremental flat precompile_header autogen_precompile_source debug_and_release debug_and_release_target embed_manifest_dll embed_manifest_exe +DEFINES += UNICODE WIN32 +QMAKE_COMPILER_DEFINES += _MSC_VER=$$MSC_VER _WIN32 +contains(QMAKE_TARGET.arch, x86_64) { + DEFINES += WIN64 + QMAKE_COMPILER_DEFINES += _WIN64 +} + +QMAKE_CC = cl +QMAKE_LEX = flex +QMAKE_LEXFLAGS = +QMAKE_YACC = bison -y +QMAKE_YACCFLAGS = -d +QMAKE_CFLAGS = -nologo -Zc:wchar_t +QMAKE_CFLAGS_WARN_ON = -W3 +QMAKE_CFLAGS_WARN_OFF = -W0 +QMAKE_CFLAGS_RELEASE = -O2 -MD -Zi +QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += -O2 -MD -Zi +QMAKE_CFLAGS_DEBUG = -Zi -MDd +QMAKE_CFLAGS_YACC = +QMAKE_CFLAGS_LTCG = -GL +QMAKE_CFLAGS_SSE2 = -arch:SSE2 +QMAKE_CFLAGS_SSE3 = -arch:SSE2 +QMAKE_CFLAGS_SSSE3 = -arch:SSE2 +QMAKE_CFLAGS_SSE4_1 = -arch:SSE2 +QMAKE_CFLAGS_SSE4_2 = -arch:SSE2 + +QMAKE_CXX = $$QMAKE_CC +QMAKE_CXXFLAGS = $$QMAKE_CFLAGS +QMAKE_CXXFLAGS_WARN_ON = $$QMAKE_CFLAGS_WARN_ON -w34100 -w34189 -w44996 +QMAKE_CXXFLAGS_WARN_OFF = $$QMAKE_CFLAGS_WARN_OFF +QMAKE_CXXFLAGS_RELEASE = $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO +QMAKE_CXXFLAGS_RELEASE_WITH_DEBUGINFO += $$QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO +QMAKE_CXXFLAGS_DEBUG = $$QMAKE_CFLAGS_DEBUG +QMAKE_CXXFLAGS_YACC = $$QMAKE_CFLAGS_YACC +QMAKE_CXXFLAGS_LTCG = $$QMAKE_CFLAGS_LTCG +QMAKE_CXXFLAGS_STL_ON = -EHsc +QMAKE_CXXFLAGS_STL_OFF = +QMAKE_CXXFLAGS_RTTI_ON = -GR +QMAKE_CXXFLAGS_RTTI_OFF = +QMAKE_CXXFLAGS_EXCEPTIONS_ON = -EHsc +QMAKE_CXXFLAGS_EXCEPTIONS_OFF = + +QMAKE_INCDIR = + +QMAKE_RUN_CC = $(CC) -c $(CFLAGS) $(INCPATH) -Fo$obj $src +QMAKE_RUN_CC_IMP = $(CC) -c $(CFLAGS) $(INCPATH) -Fo$@ $< +QMAKE_RUN_CC_IMP_BATCH = $(CC) -c $(CFLAGS) $(INCPATH) -Fo$@ @<< +QMAKE_RUN_CXX = $(CXX) -c $(CXXFLAGS) $(INCPATH) -Fo$obj $src +QMAKE_RUN_CXX_IMP = $(CXX) -c $(CXXFLAGS) $(INCPATH) -Fo$@ $< +QMAKE_RUN_CXX_IMP_BATCH = $(CXX) -c $(CXXFLAGS) $(INCPATH) -Fo$@ @<< + +QMAKE_LINK = link +QMAKE_LFLAGS = /NOLOGO /DYNAMICBASE /NXCOMPAT +QMAKE_LFLAGS_RELEASE = /INCREMENTAL:NO /DEBUG /OPT:REF +QMAKE_LFLAGS_RELEASE_WITH_DEBUGINFO = /DEBUG /OPT:REF /INCREMENTAL:NO +QMAKE_LFLAGS_DEBUG = /DEBUG +QMAKE_LFLAGS_CONSOLE = /SUBSYSTEM:CONSOLE +QMAKE_LFLAGS_WINDOWS = /SUBSYSTEM:WINDOWS +QMAKE_LFLAGS_EXE = \"/MANIFESTDEPENDENCY:type=\'win32\' name=\'Microsoft.Windows.Common-Controls\' version=\'6.0.0.0\' publicKeyToken=\'6595b64144ccf1df\' language=\'*\' processorArchitecture=\'*\'\" +QMAKE_LFLAGS_DLL = /DLL +QMAKE_LFLAGS_LTCG = /LTCG +QMAKE_PREFIX_SHLIB = +QMAKE_EXTENSION_SHLIB = dll +QMAKE_PREFIX_STATICLIB = +QMAKE_EXTENSION_STATICLIB = lib + +QMAKE_LIBS_CORE = kernel32.lib user32.lib shell32.lib uuid.lib ole32.lib advapi32.lib ws2_32.lib +QMAKE_LIBS_GUI = gdi32.lib comdlg32.lib oleaut32.lib imm32.lib winmm.lib ws2_32.lib ole32.lib user32.lib advapi32.lib +QMAKE_LIBS_NETWORK = ws2_32.lib +QMAKE_LIBS_OPENGL = glu32.lib opengl32.lib gdi32.lib user32.lib +QMAKE_LIBS_OPENGL_ES2 = $${LIBEGL_NAME}.lib $${LIBGLESV2_NAME}.lib gdi32.lib user32.lib +QMAKE_LIBS_OPENGL_ES2_DEBUG = $${LIBEGL_NAME}d.lib $${LIBGLESV2_NAME}d.lib gdi32.lib user32.lib +QMAKE_LIBS_COMPAT = advapi32.lib shell32.lib comdlg32.lib user32.lib gdi32.lib ws2_32.lib + +QMAKE_LIBS_QT_ENTRY = -lqtmain + +QMAKE_IDL = midl +QMAKE_LIB = lib /NOLOGO +QMAKE_RC = rc + +VCPROJ_EXTENSION = .vcproj +VCSOLUTION_EXTENSION = .sln +VCPROJ_KEYWORD = Qt4VSv1.0 + +include(msvc-base.conf) + +unset(MSC_VER) diff --git a/5.6.2/qtbase/src/plugins/platforms/cocoa/qcocoawindow.mm b/5.6.2/qtbase/src/plugins/platforms/cocoa/qcocoawindow.mm new file mode 100644 index 0000000..c0d5904 --- /dev/null +++ b/5.6.2/qtbase/src/plugins/platforms/cocoa/qcocoawindow.mm @@ -0,0 +1,1916 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ +#include "qcocoawindow.h" +#include "qcocoaintegration.h" +#include "qnswindowdelegate.h" +#include "qcocoaeventdispatcher.h" +#ifndef QT_NO_OPENGL +#include "qcocoaglcontext.h" +#endif +#include "qcocoahelpers.h" +#include "qcocoanativeinterface.h" +#include "qnsview.h" +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +enum { + defaultWindowWidth = 160, + defaultWindowHeight = 160 +}; + +static bool isMouseEvent(NSEvent *ev) +{ + switch ([ev type]) { + case NSLeftMouseDown: + case NSLeftMouseUp: + case NSRightMouseDown: + case NSRightMouseUp: + case NSMouseMoved: + case NSLeftMouseDragged: + case NSRightMouseDragged: + return true; + default: + return false; + } +} + +@implementation QNSWindowHelper + +@synthesize window = _window; +@synthesize platformWindow = _platformWindow; +@synthesize grabbingMouse = _grabbingMouse; +@synthesize releaseOnMouseUp = _releaseOnMouseUp; + +- (id)initWithNSWindow:(QCocoaNSWindow *)window platformWindow:(QCocoaWindow *)platformWindow +{ + self = [super init]; + if (self) { + _window = window; + _platformWindow.assign(platformWindow); + + _window.delegate = [[QNSWindowDelegate alloc] initWithQCocoaWindow:_platformWindow]; + + // 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. + [_window setReleasedWhenClosed:NO]; + } + + return self; +} + +- (void)handleWindowEvent:(NSEvent *)theEvent +{ + QCocoaWindow *pw = self.platformWindow; + if (pw && pw->m_forwardWindow) { + if (theEvent.type == NSLeftMouseUp || theEvent.type == NSLeftMouseDragged) { + QNSView *forwardView = pw->m_qtView; + if (theEvent.type == NSLeftMouseUp) { + [forwardView mouseUp:theEvent]; + pw->m_forwardWindow.clear(); + } else { + [forwardView mouseDragged:theEvent]; + } + } + + if (!pw->m_isNSWindowChild && theEvent.type == NSLeftMouseDown) { + pw->m_forwardWindow.clear(); + } + } + + if (theEvent.type == NSLeftMouseDown) { + self.grabbingMouse = YES; + } else if (theEvent.type == NSLeftMouseUp) { + self.grabbingMouse = NO; + if (self.releaseOnMouseUp) { + [self detachFromPlatformWindow]; + [self.window release]; + return; + } + } + + // The call to -[NSWindow sendEvent] may result in the window being deleted + // (e.g., when closing the window by pressing the title bar close button). + [self retain]; + [self.window superSendEvent:theEvent]; + bool windowStillAlive = self.window != nil; // We need to read before releasing + [self release]; + if (!windowStillAlive) + return; + + if (!self.window.delegate) + return; // Already detached, pending NSAppKitDefined event + + if (pw && pw->frameStrutEventsEnabled() && isMouseEvent(theEvent)) { + NSPoint loc = [theEvent locationInWindow]; + NSRect windowFrame = [self.window convertRectFromScreen:[self.window frame]]; + NSRect contentFrame = [[self.window contentView] frame]; + if (NSMouseInRect(loc, windowFrame, NO) && + !NSMouseInRect(loc, contentFrame, NO)) + { + QNSView *contentView = pw->m_qtView; + [contentView handleFrameStrutMouseEvent: theEvent]; + } + } +} + +- (void)detachFromPlatformWindow +{ + self.platformWindow.clear(); + [self.window.delegate release]; + self.window.delegate = nil; +} + +- (void)clearWindow +{ + if (_window) { + QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast(QGuiApplication::instance()->eventDispatcher()); + if (cocoaEventDispatcher) { + QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = static_cast(QObjectPrivate::get(cocoaEventDispatcher)); + cocoaEventDispatcherPrivate->removeQueuedUserInputEvents([_window windowNumber]); + } + + _window = nil; + } +} + +- (void)dealloc +{ + _window = nil; + self.platformWindow.clear(); + [super dealloc]; +} + +@end + +@implementation QNSWindow + +@synthesize helper = _helper; + +- (id)initWithContentRect:(NSRect)contentRect + styleMask:(NSUInteger)windowStyle + qPlatformWindow:(QCocoaWindow *)qpw +{ + self = [super initWithContentRect:contentRect + styleMask:windowStyle + 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) + + if (self) { + _helper = [[QNSWindowHelper alloc] initWithNSWindow:self platformWindow:qpw]; + } + return self; +} + +- (BOOL)canBecomeKeyWindow +{ + // Prevent child NSWindows from becoming the key window in + // order keep the active apperance of the top-level window. + QCocoaWindow *pw = self.helper.platformWindow; + if (!pw || pw->m_isNSWindowChild) + return NO; + + if (pw->shouldRefuseKeyWindowAndFirstResponder()) + return NO; + + // The default implementation returns NO for title-bar less windows, + // override and return yes here to make sure popup windows such as + // the combobox popup can become the key window. + return YES; +} + +- (BOOL)canBecomeMainWindow +{ + BOOL canBecomeMain = YES; // By default, windows can become the main window + + // Windows with a transient parent (such as combobox popup windows) + // cannot become the main window: + QCocoaWindow *pw = self.helper.platformWindow; + if (!pw || pw->m_isNSWindowChild || pw->window()->transientParent()) + canBecomeMain = NO; + + return canBecomeMain; +} + +- (void) sendEvent: (NSEvent*) theEvent +{ + [self.helper handleWindowEvent:theEvent]; +} + +- (void)superSendEvent:(NSEvent *)theEvent +{ + [super sendEvent:theEvent]; +} + +- (void)closeAndRelease +{ + [self close]; + + if (self.helper.grabbingMouse) { + self.helper.releaseOnMouseUp = YES; + } else { + [self.helper detachFromPlatformWindow]; + [self release]; + } +} + +- (void)dealloc +{ + [_helper clearWindow]; + [_helper release]; + _helper = nil; + [super dealloc]; +} + +@end + +@implementation QNSPanel + +@synthesize helper = _helper; + +- (id)initWithContentRect:(NSRect)contentRect + styleMask:(NSUInteger)windowStyle + qPlatformWindow:(QCocoaWindow *)qpw +{ + self = [super initWithContentRect:contentRect + styleMask:windowStyle + 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) + + if (self) { + _helper = [[QNSWindowHelper alloc] initWithNSWindow:self platformWindow:qpw]; + } + return self; +} + +- (BOOL)canBecomeKeyWindow +{ + QCocoaWindow *pw = self.helper.platformWindow; + if (!pw) + return NO; + + if (pw->shouldRefuseKeyWindowAndFirstResponder()) + return NO; + + // Only tool or dialog windows should become key: + Qt::WindowType type = pw->window()->type(); + if (type == Qt::Tool || type == Qt::Dialog) + return YES; + + return NO; +} + +- (void) sendEvent: (NSEvent*) theEvent +{ + [self.helper handleWindowEvent:theEvent]; +} + +- (void)superSendEvent:(NSEvent *)theEvent +{ + [super sendEvent:theEvent]; +} + +- (void)closeAndRelease +{ + [self.helper detachFromPlatformWindow]; + [self close]; + [self release]; +} + +- (void)dealloc +{ + [_helper clearWindow]; + [_helper release]; + _helper = nil; + [super dealloc]; +} + +@end + +void QCocoaWindowPointer::assign(QCocoaWindow *w) +{ + window = w; + watcher = &w->sentinel; +} + +void QCocoaWindowPointer::clear() +{ + window = Q_NULLPTR; + watcher.clear(); +} + +const int QCocoaWindow::NoAlertRequest = -1; + +QCocoaWindow::QCocoaWindow(QWindow *tlw) + : QPlatformWindow(tlw) + , m_contentView(nil) + , m_qtView(nil) + , m_nsWindow(0) + , m_contentViewIsEmbedded(false) + , m_contentViewIsToBeEmbedded(false) + , m_parentCocoaWindow(0) + , m_isNSWindowChild(false) + , m_effectivelyMaximized(false) + , m_synchedWindowState(Qt::WindowActive) + , m_windowModality(Qt::NonModal) + , m_windowUnderMouse(false) + , m_inConstructor(true) + , m_inSetVisible(false) + , m_inSetGeometry(false) + , m_inSetStyleMask(false) +#ifndef QT_NO_OPENGL + , m_glContext(0) +#endif + , m_menubar(0) + , m_windowCursor(0) + , m_hasModalSession(false) + , m_frameStrutEventsEnabled(false) + , m_geometryUpdateExposeAllowed(false) + , m_isExposed(false) + , m_registerTouchCount(0) + , m_resizableTransientParent(false) + , m_hiddenByClipping(false) + , m_hiddenByAncestor(false) + , m_alertRequest(NoAlertRequest) + , monitor(nil) + , m_drawContentBorderGradient(false) + , m_topContentBorderThickness(0) + , m_bottomContentBorderThickness(0) + , m_normalGeometry(QRect(0,0,-1,-1)) +{ +#ifdef QT_COCOA_ENABLE_WINDOW_DEBUG + qDebug() << "QCocoaWindow::QCocoaWindow" << this; +#endif + QMacAutoReleasePool pool; + + if (tlw->type() == Qt::ForeignWindow) { + NSView *foreignView = (NSView *)WId(tlw->property("_q_foreignWinId").value()); + setContentView(foreignView); + } else { + m_qtView = [[QNSView alloc] initWithQWindow:tlw platformWindow:this]; + m_contentView = m_qtView; + // Enable high-dpi OpenGL for retina displays. Enabling has the side + // effect that Cocoa will start calling glViewport(0, 0, width, height), + // overriding any glViewport calls in application code. This is usually not a + // problem, except if the appilcation wants to have a "custom" viewport. + // (like the hellogl example) + if (tlw->supportsOpenGL()) { + BOOL enable = qt_mac_resolveOption(YES, tlw, "_q_mac_wantsBestResolutionOpenGLSurface", + "QT_MAC_WANTS_BEST_RESOLUTION_OPENGL_SURFACE"); + [m_contentView setWantsBestResolutionOpenGLSurface:enable]; + } + BOOL enable = qt_mac_resolveOption(NO, tlw, "_q_mac_wantsLayer", + "QT_MAC_WANTS_LAYER"); + [m_contentView setWantsLayer:enable]; + } + setGeometry(tlw->geometry()); + recreateWindow(parent()); + tlw->setGeometry(geometry()); + if (tlw->isTopLevel()) + setWindowIcon(tlw->icon()); + m_inConstructor = false; +} + +QCocoaWindow::~QCocoaWindow() +{ +#ifdef QT_COCOA_ENABLE_WINDOW_DEBUG + qDebug() << "QCocoaWindow::~QCocoaWindow" << this; +#endif + + QMacAutoReleasePool pool; + [m_nsWindow makeFirstResponder:nil]; + [m_nsWindow setContentView:nil]; + [m_nsWindow.helper detachFromPlatformWindow]; + if (m_isNSWindowChild) { + if (m_parentCocoaWindow) + m_parentCocoaWindow->removeChildWindow(this); + } else if ([m_contentView superview]) { + [m_contentView removeFromSuperview]; + } + + removeMonitor(); + + // Make sure to disconnect observer in all case if view is valid + // to avoid notifications received when deleting when using Qt::AA_NativeWindows attribute + if (m_qtView) { + [[NSNotificationCenter defaultCenter] removeObserver:m_qtView]; + } + + // The QNSView object may outlive the corresponding QCocoaWindow object, + // for example during app shutdown when the QNSView is embedded in a + // foregin NSView hiearchy. Clear the pointers to the QWindow/QCocoaWindow + // here to make sure QNSView does not dereference stale pointers. + if (m_qtView) { + [m_qtView clearQWindowPointers]; + } + + // While it is unlikely that this window will be in the popup stack + // during deletetion we clear any pointers here to make sure. + if (QCocoaIntegration::instance()) { + QCocoaIntegration::instance()->popupWindowStack()->removeAll(this); + } + + foreach (QCocoaWindow *child, m_childWindows) { + [m_nsWindow removeChildWindow:child->m_nsWindow]; + child->m_parentCocoaWindow = 0; + } + + [m_contentView release]; + [m_nsWindow release]; + [m_windowCursor release]; +} + +QSurfaceFormat QCocoaWindow::format() const +{ + QSurfaceFormat format = window()->requestedFormat(); + + // Upgrade the default surface format to include an alpha channel. The default RGB format + // causes Cocoa to spend an unreasonable amount of time converting it to RGBA internally. + if (format == QSurfaceFormat()) + format.setAlphaBufferSize(8); + return format; +} + +void QCocoaWindow::setGeometry(const QRect &rectIn) +{ + QBoolBlocker inSetGeometry(m_inSetGeometry, true); + + QRect rect = rectIn; + // This means it is a call from QWindow::setFramePosition() and + // the coordinates include the frame (size is still the contents rectangle). + if (qt_window_private(const_cast(window()))->positionPolicy + == QWindowPrivate::WindowFrameInclusive) { + const QMargins margins = frameMargins(); + rect.moveTopLeft(rect.topLeft() + QPoint(margins.left(), margins.top())); + } + if (geometry() == rect) + return; +#ifdef QT_COCOA_ENABLE_WINDOW_DEBUG + qDebug() << "QCocoaWindow::setGeometry" << this << rect; +#endif + setCocoaGeometry(rect); +} + +QRect QCocoaWindow::geometry() const +{ + // QWindows that are embedded in a NSView hiearchy may be considered + // top-level from Qt's point of view but are not from Cocoa's point + // of view. Embedded QWindows get global (screen) geometry. + if (m_contentViewIsEmbedded) { + NSPoint windowPoint = [m_contentView convertPoint:NSMakePoint(0, 0) toView:nil]; + NSRect screenRect = [[m_contentView window] convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)]; + NSPoint screenPoint = screenRect.origin; + QPoint position = qt_mac_flipPoint(screenPoint).toPoint(); + QSize size = qt_mac_toQRect([m_contentView bounds]).size(); + return QRect(position, size); + } + + return QPlatformWindow::geometry(); +} + +void QCocoaWindow::setCocoaGeometry(const QRect &rect) +{ + QMacAutoReleasePool pool; + + if (m_contentViewIsEmbedded) { + if (m_qtView) { + [m_qtView setFrame:NSMakeRect(0, 0, rect.width(), rect.height())]; + } else { + QPlatformWindow::setGeometry(rect); + } + return; + } + + if (m_isNSWindowChild) { + QPlatformWindow::setGeometry(rect); + NSWindow *parentNSWindow = m_parentCocoaWindow->m_nsWindow; + NSRect parentWindowFrame = [parentNSWindow contentRectForFrameRect:parentNSWindow.frame]; + clipWindow(parentWindowFrame); + + // call this here: updateGeometry in qnsview.mm is a no-op for this case + QWindowSystemInterface::handleGeometryChange(window(), rect); + QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), rect.size())); + } else if (m_nsWindow) { + NSRect bounds = qt_mac_flipRect(rect); + [m_nsWindow setFrame:[m_nsWindow frameRectForContentRect:bounds] display:YES animate:NO]; + } else { + [m_contentView setFrame : NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height())]; + } + + if (!m_qtView) + QPlatformWindow::setGeometry(rect); + + // will call QPlatformWindow::setGeometry(rect) during resize confirmation (see qnsview.mm) +} + +void QCocoaWindow::clipChildWindows() +{ + foreach (QCocoaWindow *childWindow, m_childWindows) { + childWindow->clipWindow(m_nsWindow.frame); + } +} + +void QCocoaWindow::clipWindow(const NSRect &clipRect) +{ + if (!m_isNSWindowChild) + return; + + NSRect clippedWindowRect = NSZeroRect; + if (!NSIsEmptyRect(clipRect)) { + NSRect windowFrame = qt_mac_flipRect(QRect(window()->mapToGlobal(QPoint(0, 0)), geometry().size())); + clippedWindowRect = NSIntersectionRect(windowFrame, clipRect); + // Clipping top/left offsets the content. Move it back. + NSPoint contentViewOffset = NSMakePoint(qMax(CGFloat(0), NSMinX(clippedWindowRect) - NSMinX(windowFrame)), + qMax(CGFloat(0), NSMaxY(windowFrame) - NSMaxY(clippedWindowRect))); + [m_contentView setBoundsOrigin:contentViewOffset]; + } + + if (NSIsEmptyRect(clippedWindowRect)) { + if (!m_hiddenByClipping) { + // We dont call hide() here as we will recurse further down + [m_nsWindow orderOut:nil]; + m_hiddenByClipping = true; + } + } else { + [m_nsWindow setFrame:clippedWindowRect display:YES animate:NO]; + if (m_hiddenByClipping) { + m_hiddenByClipping = false; + if (!m_hiddenByAncestor) { + [m_nsWindow orderFront:nil]; + m_parentCocoaWindow->reinsertChildWindow(this); + } + } + } + + // recurse + foreach (QCocoaWindow *childWindow, m_childWindows) { + childWindow->clipWindow(clippedWindowRect); + } +} + +void QCocoaWindow::hide(bool becauseOfAncestor) +{ + bool visible = [m_nsWindow isVisible]; + + if (!m_hiddenByAncestor && !visible) // Already explicitly hidden + return; + if (m_hiddenByAncestor && becauseOfAncestor) // Trying to hide some child again + return; + + m_hiddenByAncestor = becauseOfAncestor; + + if (!visible) // Could have been clipped before + return; + + foreach (QCocoaWindow *childWindow, m_childWindows) + childWindow->hide(true); + + [m_nsWindow orderOut:nil]; +} + +void QCocoaWindow::show(bool becauseOfAncestor) +{ + if ([m_nsWindow isVisible]) + return; + + if (m_parentCocoaWindow && ![m_parentCocoaWindow->m_nsWindow isVisible]) { + m_hiddenByAncestor = true; // Parent still hidden, don't show now + } else if ((becauseOfAncestor == m_hiddenByAncestor) // Was NEITHER explicitly hidden + && !m_hiddenByClipping) { // ... NOR clipped + if (m_isNSWindowChild) { + m_hiddenByAncestor = false; + setCocoaGeometry(windowGeometry()); + } + if (!m_hiddenByClipping) { // setCocoaGeometry() can change the clipping status + [m_nsWindow orderFront:nil]; + if (m_isNSWindowChild) + m_parentCocoaWindow->reinsertChildWindow(this); + foreach (QCocoaWindow *childWindow, m_childWindows) + childWindow->show(true); + } + } +} + +void QCocoaWindow::setVisible(bool visible) +{ + if (m_isNSWindowChild && m_hiddenByClipping) + return; + + m_inSetVisible = true; + + QMacAutoReleasePool pool; + QCocoaWindow *parentCocoaWindow = 0; + if (window()->transientParent()) + parentCocoaWindow = static_cast(window()->transientParent()->handle()); +#ifdef QT_COCOA_ENABLE_WINDOW_DEBUG + qDebug() << "QCocoaWindow::setVisible" << window() << visible; +#endif + if (visible) { + // We need to recreate if the modality has changed as the style mask will need updating + if (m_windowModality != window()->modality()) + recreateWindow(parent()); + + // Register popup windows. The Cocoa platform plugin will forward mouse events + // to them and close them when needed. + if (window()->type() == Qt::Popup || window()->type() == Qt::ToolTip) + QCocoaIntegration::instance()->pushPopupWindow(this); + + if (parentCocoaWindow) { + // The parent window might have moved while this window was hidden, + // update the window geometry if there is a parent. + setGeometry(windowGeometry()); + + if (window()->type() == Qt::Popup) { + // QTBUG-30266: a window should not be resizable while a transient popup is open + // Since this isn't a native popup, the window manager doesn't close the popup when you click outside + NSUInteger parentStyleMask = [parentCocoaWindow->m_nsWindow styleMask]; + if ((m_resizableTransientParent = (parentStyleMask & NSResizableWindowMask)) + && !([parentCocoaWindow->m_nsWindow styleMask] & NSFullScreenWindowMask)) + [parentCocoaWindow->m_nsWindow setStyleMask:parentStyleMask & ~NSResizableWindowMask]; + } + + } + + // This call is here to handle initial window show correctly: + // - top-level windows need to have backing store content ready when the + // window is shown, sendin the expose event here makes that more likely. + // - QNSViews for child windows are initialy not hidden and won't get the + // viewDidUnhide message. + exposeWindow(); + + if (m_nsWindow) { + QWindowSystemInterface::flushWindowSystemEvents(QEventLoop::ExcludeUserInputEvents); + + // setWindowState might have been called while the window was hidden and + // will not change the NSWindow state in that case. Sync up here: + syncWindowState(window()->windowState()); + + if (window()->windowState() != Qt::WindowMinimized) { + if ((window()->modality() == Qt::WindowModal + || window()->type() == Qt::Sheet) + && parentCocoaWindow) { + // show the window as a sheet + [NSApp beginSheet:m_nsWindow modalForWindow:parentCocoaWindow->m_nsWindow modalDelegate:nil didEndSelector:nil contextInfo:nil]; + } else if (window()->modality() != Qt::NonModal) { + // show the window as application modal + QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast(QGuiApplication::instance()->eventDispatcher()); + Q_ASSERT(cocoaEventDispatcher != 0); + QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = static_cast(QObjectPrivate::get(cocoaEventDispatcher)); + cocoaEventDispatcherPrivate->beginModalSession(window()); + m_hasModalSession = true; + } else if ([m_nsWindow canBecomeKeyWindow]) { + [m_nsWindow makeKeyAndOrderFront:nil]; + foreach (QCocoaWindow *childWindow, m_childWindows) + childWindow->show(true); + } else { + show(); + } + + // We want the events to properly reach the popup, dialog, and tool + if ((window()->type() == Qt::Popup || window()->type() == Qt::Dialog || window()->type() == Qt::Tool) + && [m_nsWindow isKindOfClass:[NSPanel class]]) { + [(NSPanel *)m_nsWindow setWorksWhenModal:YES]; + if (!(parentCocoaWindow && window()->transientParent()->isActive()) && window()->type() == Qt::Popup) { + removeMonitor(); + monitor = [NSEvent addGlobalMonitorForEventsMatchingMask:NSLeftMouseDownMask|NSRightMouseDownMask|NSOtherMouseDownMask|NSMouseMovedMask handler:^(NSEvent *e) { + QPointF localPoint = qt_mac_flipPoint([NSEvent mouseLocation]); + QWindowSystemInterface::handleMouseEvent(window(), window()->mapFromGlobal(localPoint.toPoint()), localPoint, + cocoaButton2QtButton([e buttonNumber])); + }]; + } + } + } + } + // In some cases, e.g. QDockWidget, the content view is hidden before moving to its own + // Cocoa window, and then shown again. Therefore, we test for the view being hidden even + // if it's attached to an NSWindow. + if ([m_contentView isHidden]) + [m_contentView setHidden:NO]; + } else { + // qDebug() << "close" << this; +#ifndef QT_NO_OPENGL + if (m_glContext) + m_glContext->windowWasHidden(); +#endif + QCocoaEventDispatcher *cocoaEventDispatcher = qobject_cast(QGuiApplication::instance()->eventDispatcher()); + QCocoaEventDispatcherPrivate *cocoaEventDispatcherPrivate = 0; + if (cocoaEventDispatcher) + cocoaEventDispatcherPrivate = static_cast(QObjectPrivate::get(cocoaEventDispatcher)); + if (m_nsWindow) { + if (m_hasModalSession) { + if (cocoaEventDispatcherPrivate) + cocoaEventDispatcherPrivate->endModalSession(window()); + m_hasModalSession = false; + } else { + if ([m_nsWindow isSheet]) + [NSApp endSheet:m_nsWindow]; + } + + hide(); + if (m_nsWindow == [NSApp keyWindow] + && !(cocoaEventDispatcherPrivate && cocoaEventDispatcherPrivate->currentModalSession())) { + // Probably because we call runModalSession: outside [NSApp run] in QCocoaEventDispatcher + // (e.g., when show()-ing a modal QDialog instead of exec()-ing it), it can happen that + // the current NSWindow is still key after being ordered out. Then, after checking we + // don't have any other modal session left, it's safe to make the main window key again. + NSWindow *mainWindow = [NSApp mainWindow]; + if (mainWindow && [mainWindow canBecomeKeyWindow]) + [mainWindow makeKeyWindow]; + } + } else { + [m_contentView setHidden:YES]; + } + removeMonitor(); + + if (window()->type() == Qt::Popup || window()->type() == Qt::ToolTip) + QCocoaIntegration::instance()->popupWindowStack()->removeAll(this); + + if (parentCocoaWindow && window()->type() == Qt::Popup) { + if (m_resizableTransientParent + && !([parentCocoaWindow->m_nsWindow styleMask] & NSFullScreenWindowMask)) + // QTBUG-30266: a window should not be resizable while a transient popup is open + [parentCocoaWindow->m_nsWindow setStyleMask:[parentCocoaWindow->m_nsWindow styleMask] | NSResizableWindowMask]; + } + } + + m_inSetVisible = false; +} + +NSInteger QCocoaWindow::windowLevel(Qt::WindowFlags flags) +{ + Qt::WindowType type = static_cast(int(flags & Qt::WindowType_Mask)); + + NSInteger windowLevel = NSNormalWindowLevel; + + if (type == Qt::Tool) + windowLevel = NSFloatingWindowLevel; + else if ((type & Qt::Popup) == Qt::Popup) + windowLevel = NSPopUpMenuWindowLevel; + + // StayOnTop window should appear above Tool windows. + if (flags & Qt::WindowStaysOnTopHint) + windowLevel = NSModalPanelWindowLevel; + // Tooltips should appear above StayOnTop windows. + if (type == Qt::ToolTip) + windowLevel = NSScreenSaverWindowLevel; + + // Any "special" window should be in at least the same level as its parent. + if (type != Qt::Window) { + const QWindow * const transientParent = window()->transientParent(); + const QCocoaWindow * const transientParentWindow = transientParent ? static_cast(transientParent->handle()) : 0; + if (transientParentWindow) + windowLevel = qMax([transientParentWindow->m_nsWindow level], windowLevel); + } + + return windowLevel; +} + +NSUInteger QCocoaWindow::windowStyleMask(Qt::WindowFlags flags) +{ + Qt::WindowType type = static_cast(int(flags & Qt::WindowType_Mask)); + NSInteger styleMask = NSBorderlessWindowMask; + if (flags & Qt::FramelessWindowHint) + return styleMask; + if ((type & Qt::Popup) == Qt::Popup) { + if (!windowIsPopupType(type)) { + styleMask = NSUtilityWindowMask | NSResizableWindowMask; + if (!(flags & Qt::CustomizeWindowHint)) { + styleMask |= NSClosableWindowMask | NSMiniaturizableWindowMask | NSTitledWindowMask; + } else { + if (flags & Qt::WindowTitleHint) + styleMask |= NSTitledWindowMask; + if (flags & Qt::WindowCloseButtonHint) + styleMask |= NSClosableWindowMask; + if (flags & Qt::WindowMinimizeButtonHint) + styleMask |= NSMiniaturizableWindowMask; + } + } + } else { + if (type == Qt::Window && !(flags & Qt::CustomizeWindowHint)) { + styleMask = (NSResizableWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSTitledWindowMask); + } else if (type == Qt::Dialog) { + if (flags & Qt::CustomizeWindowHint) { + if (flags & Qt::WindowMaximizeButtonHint) + styleMask = NSResizableWindowMask; + if (flags & Qt::WindowTitleHint) + styleMask |= NSTitledWindowMask; + if (flags & Qt::WindowCloseButtonHint) + styleMask |= NSClosableWindowMask; + if (flags & Qt::WindowMinimizeButtonHint) + styleMask |= NSMiniaturizableWindowMask; + } else { + styleMask = NSResizableWindowMask | NSClosableWindowMask | NSTitledWindowMask; + } + } else { + if (flags & Qt::WindowMaximizeButtonHint) + styleMask |= NSResizableWindowMask; + if (flags & Qt::WindowTitleHint) + styleMask |= NSTitledWindowMask; + if (flags & Qt::WindowCloseButtonHint) + styleMask |= NSClosableWindowMask; + if (flags & Qt::WindowMinimizeButtonHint) + styleMask |= NSMiniaturizableWindowMask; + } + } + + if (m_drawContentBorderGradient) + styleMask |= NSTexturedBackgroundWindowMask; + +#ifdef QT_COCOA_ENABLE_WINDOW_DEBUG + qDebug("windowStyleMask of '%s': flags %X -> styleMask %lX", qPrintable(window()->title()), (int)flags, styleMask); +#endif + return styleMask; +} + +void QCocoaWindow::setWindowShadow(Qt::WindowFlags flags) +{ + bool keepShadow = !(flags & Qt::NoDropShadowWindowHint); + [m_nsWindow setHasShadow:(keepShadow ? YES : NO)]; +} + +void QCocoaWindow::setWindowZoomButton(Qt::WindowFlags flags) +{ + // Disable the zoom (maximize) button for fixed-sized windows and customized + // no-WindowMaximizeButtonHint windows. From a Qt perspective it migth be expected + // that the button would be removed in the latter case, but disabling it is more + // in line with the platform style guidelines. + bool fixedSizeNoZoom = (windowMinimumSize().isValid() && windowMaximumSize().isValid() + && windowMinimumSize() == windowMaximumSize()); + bool customizeNoZoom = ((flags & Qt::CustomizeWindowHint) && !(flags & Qt::WindowMaximizeButtonHint)); + [[m_nsWindow standardWindowButton:NSWindowZoomButton] setEnabled:!(fixedSizeNoZoom || customizeNoZoom)]; +} + +void QCocoaWindow::setWindowFlags(Qt::WindowFlags flags) +{ + if (m_nsWindow && !m_isNSWindowChild) { + NSUInteger styleMask = windowStyleMask(flags); + NSInteger level = this->windowLevel(flags); + // While setting style mask we can have -updateGeometry calls on a content + // view with null geometry, reporting an invalid coordinates as a result. + m_inSetStyleMask = true; + [m_nsWindow setStyleMask:styleMask]; + m_inSetStyleMask = false; + [m_nsWindow setLevel:level]; + setWindowShadow(flags); + if (!(flags & Qt::FramelessWindowHint)) { + setWindowTitle(window()->title()); + } + + Qt::WindowType type = window()->type(); + if ((type & Qt::Popup) != Qt::Popup && (type & Qt::Dialog) != Qt::Dialog) { + NSWindowCollectionBehavior behavior = [m_nsWindow collectionBehavior]; + if (flags & Qt::WindowFullscreenButtonHint) { + behavior |= NSWindowCollectionBehaviorFullScreenPrimary; + behavior &= ~NSWindowCollectionBehaviorFullScreenAuxiliary; + } else { + behavior |= NSWindowCollectionBehaviorFullScreenAuxiliary; + behavior &= ~NSWindowCollectionBehaviorFullScreenPrimary; + } + [m_nsWindow setCollectionBehavior:behavior]; + } + setWindowZoomButton(flags); + } + + m_windowFlags = flags; +} + +void QCocoaWindow::setWindowState(Qt::WindowState state) +{ + if (window()->isVisible()) + syncWindowState(state); // Window state set for hidden windows take effect when show() is called. +} + +void QCocoaWindow::setWindowTitle(const QString &title) +{ + QMacAutoReleasePool pool; + if (!m_nsWindow) + return; + + CFStringRef windowTitle = QCFString::toCFStringRef(title); + [m_nsWindow setTitle: const_cast(reinterpret_cast(windowTitle))]; + CFRelease(windowTitle); +} + +void QCocoaWindow::setWindowFilePath(const QString &filePath) +{ + QMacAutoReleasePool pool; + if (!m_nsWindow) + return; + + QFileInfo fi(filePath); + [m_nsWindow setRepresentedFilename: fi.exists() ? QCFString::toNSString(filePath) : @""]; +} + +void QCocoaWindow::setWindowIcon(const QIcon &icon) +{ + QMacAutoReleasePool pool; + + NSButton *iconButton = [m_nsWindow standardWindowButton:NSWindowDocumentIconButton]; + if (iconButton == nil) { + if (icon.isNull()) + return; + NSString *title = QCFString::toNSString(window()->title()); + [m_nsWindow setRepresentedURL:[NSURL fileURLWithPath:title]]; + iconButton = [m_nsWindow standardWindowButton:NSWindowDocumentIconButton]; + } + if (icon.isNull()) { + [iconButton setImage:nil]; + } else { + QPixmap pixmap = icon.pixmap(QSize(22, 22)); + NSImage *image = static_cast(qt_mac_create_nsimage(pixmap)); + [iconButton setImage:image]; + [image release]; + } +} + +void QCocoaWindow::setAlertState(bool enabled) +{ + if (m_alertRequest == NoAlertRequest && enabled) { + m_alertRequest = [NSApp requestUserAttention:NSCriticalRequest]; + } else if (m_alertRequest != NoAlertRequest && !enabled) { + [NSApp cancelUserAttentionRequest:m_alertRequest]; + m_alertRequest = NoAlertRequest; + } +} + +bool QCocoaWindow::isAlertState() const +{ + return m_alertRequest != NoAlertRequest; +} + +void QCocoaWindow::raise() +{ + //qDebug() << "raise" << this; + // ### handle spaces (see Qt 4 raise_sys in qwidget_mac.mm) + if (!m_nsWindow) + return; + if (m_isNSWindowChild) { + QList &siblings = m_parentCocoaWindow->m_childWindows; + siblings.removeOne(this); + siblings.append(this); + if (m_hiddenByClipping) + return; + } + if ([m_nsWindow isVisible]) { + if (m_isNSWindowChild) { + // -[NSWindow orderFront:] doesn't work with attached windows. + // The only solution is to remove and add the child window. + // This will place it on top of all the other NSWindows. + NSWindow *parentNSWindow = m_parentCocoaWindow->m_nsWindow; + [parentNSWindow removeChildWindow:m_nsWindow]; + [parentNSWindow addChildWindow:m_nsWindow ordered:NSWindowAbove]; + } else { + { + // Clean up autoreleased temp objects from orderFront immediately. + // Failure to do so has been observed to cause leaks also beyond any outer + // autorelease pool (for example around a complete QWindow + // construct-show-raise-hide-delete cyle), counter to expected autoreleasepool + // behavior. + QMacAutoReleasePool pool; + [m_nsWindow orderFront: m_nsWindow]; + } + static bool raiseProcess = qt_mac_resolveOption(true, "QT_MAC_SET_RAISE_PROCESS"); + if (raiseProcess) { + ProcessSerialNumber psn; + GetCurrentProcess(&psn); + SetFrontProcessWithOptions(&psn, kSetFrontProcessFrontWindowOnly); + } + } + } +} + +void QCocoaWindow::lower() +{ + if (!m_nsWindow) + return; + if (m_isNSWindowChild) { + QList &siblings = m_parentCocoaWindow->m_childWindows; + siblings.removeOne(this); + siblings.prepend(this); + if (m_hiddenByClipping) + return; + } + if ([m_nsWindow isVisible]) { + if (m_isNSWindowChild) { + // -[NSWindow orderBack:] doesn't work with attached windows. + // The only solution is to remove and add all the child windows except this one. + // This will keep the current window at the bottom while adding the others on top of it, + // hopefully in the same order (this is not documented anywhere in the Cocoa documentation). + NSWindow *parentNSWindow = m_parentCocoaWindow->m_nsWindow; + NSArray *children = [parentNSWindow.childWindows copy]; + for (NSWindow *child in children) + if (m_nsWindow != child) { + [parentNSWindow removeChildWindow:child]; + [parentNSWindow addChildWindow:child ordered:NSWindowAbove]; + } + } else { + [m_nsWindow orderBack: m_nsWindow]; + } + } +} + +bool QCocoaWindow::isExposed() const +{ + return m_isExposed; +} + +bool QCocoaWindow::isOpaque() const +{ + // OpenGL surfaces can be ordered either above(default) or below the NSWindow. + // When ordering below the window must be tranclucent. + static GLint openglSourfaceOrder = qt_mac_resolveOption(1, "QT_MAC_OPENGL_SURFACE_ORDER"); + + bool translucent = (window()->format().alphaBufferSize() > 0 + || window()->opacity() < 1 + || (m_qtView && [m_qtView hasMask])) + || (surface()->supportsOpenGL() && openglSourfaceOrder == -1); + return !translucent; +} + +void QCocoaWindow::propagateSizeHints() +{ + QMacAutoReleasePool pool; + if (!m_nsWindow) + return; + +#ifdef QT_COCOA_ENABLE_WINDOW_DEBUG + qDebug() << "QCocoaWindow::propagateSizeHints" << this; + qDebug() << " min/max" << windowMinimumSize() << windowMaximumSize(); + qDebug() << "size increment" << windowSizeIncrement(); + qDebug() << " basesize" << windowBaseSize(); + qDebug() << " geometry" << windowGeometry(); +#endif + + // Set the minimum content size. + const QSize minimumSize = windowMinimumSize(); + if (!minimumSize.isValid()) // minimumSize is (-1, -1) when not set. Make that (0, 0) for Cocoa. + [m_nsWindow setContentMinSize : NSMakeSize(0.0, 0.0)]; + [m_nsWindow setContentMinSize : NSMakeSize(minimumSize.width(), minimumSize.height())]; + + // Set the maximum content size. + const QSize maximumSize = windowMaximumSize(); + [m_nsWindow setContentMaxSize : NSMakeSize(maximumSize.width(), maximumSize.height())]; + + // The window may end up with a fixed size; in this case the zoom button should be disabled. + setWindowZoomButton(m_windowFlags); + + // sizeIncrement is observed to take values of (-1, -1) and (0, 0) for windows that should be + // resizable and that have no specific size increment set. Cocoa expects (1.0, 1.0) in this case. + const QSize sizeIncrement = windowSizeIncrement(); + if (!sizeIncrement.isEmpty()) + [m_nsWindow setResizeIncrements : qt_mac_toNSSize(sizeIncrement)]; + else + [m_nsWindow setResizeIncrements : NSMakeSize(1.0, 1.0)]; + + QRect rect = geometry(); + QSize baseSize = windowBaseSize(); + if (!baseSize.isNull() && baseSize.isValid()) { + [m_nsWindow setFrame:NSMakeRect(rect.x(), rect.y(), baseSize.width(), baseSize.height()) display:YES]; + } +} + +void QCocoaWindow::setOpacity(qreal level) +{ + if (m_nsWindow) { + [m_nsWindow setAlphaValue:level]; + [m_nsWindow setOpaque: isOpaque()]; + } +} + +void QCocoaWindow::setMask(const QRegion ®ion) +{ + if (m_nsWindow) + [m_nsWindow setBackgroundColor:[NSColor clearColor]]; + + [m_qtView setMaskRegion:®ion]; + [m_nsWindow setOpaque: isOpaque()]; +} + +bool QCocoaWindow::setKeyboardGrabEnabled(bool grab) +{ + if (!m_nsWindow) + return false; + + if (grab && ![m_nsWindow isKeyWindow]) + [m_nsWindow makeKeyWindow]; + + return true; +} + +bool QCocoaWindow::setMouseGrabEnabled(bool grab) +{ + if (!m_nsWindow) + return false; + + if (grab && ![m_nsWindow isKeyWindow]) + [m_nsWindow makeKeyWindow]; + + return true; +} + +WId QCocoaWindow::winId() const +{ + return WId(m_contentView); +} + +void QCocoaWindow::setParent(const QPlatformWindow *parentWindow) +{ + // recreate the window for compatibility + bool unhideAfterRecreate = parentWindow && !m_contentViewIsToBeEmbedded && ![m_contentView isHidden]; + recreateWindow(parentWindow); + if (unhideAfterRecreate) + [m_contentView setHidden:NO]; + setCocoaGeometry(geometry()); +} + +NSView *QCocoaWindow::contentView() const +{ + return m_contentView; +} + +void QCocoaWindow::setContentView(NSView *contentView) +{ + // Remove and release the previous content view + if (m_nsWindow) + [m_nsWindow setContentView:nil]; + else + [m_contentView removeFromSuperview]; + + [m_contentView release]; + + // Insert and retain the new content view + [contentView retain]; + m_contentView = contentView; + m_qtView = 0; // The new content view is not a QNSView. + recreateWindow(parent()); // Adds the content view to parent NSView +} + +QNSView *QCocoaWindow::qtView() const +{ + return m_qtView; +} + +NSWindow *QCocoaWindow::nativeWindow() const +{ + return m_nsWindow; +} + +void QCocoaWindow::setEmbeddedInForeignView(bool embedded) +{ + m_contentViewIsToBeEmbedded = embedded; + // Release any previosly created NSWindow. + [m_nsWindow closeAndRelease]; + m_nsWindow = 0; +} + +void QCocoaWindow::windowWillMove() +{ + // Close any open popups on window move + while (QCocoaWindow *popup = QCocoaIntegration::instance()->popPopupWindow()) { + QWindowSystemInterface::handleCloseEvent(popup->window()); + QWindowSystemInterface::flushWindowSystemEvents(); + } +} + +void QCocoaWindow::windowDidMove() +{ + if (m_isNSWindowChild) + return; + + [m_qtView updateGeometry]; +} + +void QCocoaWindow::windowDidResize() +{ + if (!m_nsWindow) + return; + + if (m_isNSWindowChild) + return; + + clipChildWindows(); + [m_qtView updateGeometry]; +} + +void QCocoaWindow::windowDidEndLiveResize() +{ + if (m_synchedWindowState == Qt::WindowMaximized && ![m_nsWindow isZoomed]) { + m_effectivelyMaximized = false; + [m_qtView notifyWindowStateChanged:Qt::WindowNoState]; + } +} + +bool QCocoaWindow::windowShouldClose() +{ + // This callback should technically only determine if the window + // should (be allowed to) close, but since our QPA API to determine + // that also involves actually closing the window we do both at the + // same time, instead of doing the latter in windowWillClose. + bool accepted = false; + QWindowSystemInterface::handleCloseEvent(window(), &accepted); + QWindowSystemInterface::flushWindowSystemEvents(); + return accepted; +} + +void QCocoaWindow::setSynchedWindowStateFromWindow() +{ + if (QWindow *w = window()) + m_synchedWindowState = w->windowState(); +} + +bool QCocoaWindow::windowIsPopupType(Qt::WindowType type) const +{ + if (type == Qt::Widget) + type = window()->type(); + if (type == Qt::Tool) + return false; // Qt::Tool has the Popup bit set but isn't, at least on Mac. + + return ((type & Qt::Popup) == Qt::Popup); +} + +#ifndef QT_NO_OPENGL +void QCocoaWindow::setCurrentContext(QCocoaGLContext *context) +{ + m_glContext = context; +} + +QCocoaGLContext *QCocoaWindow::currentContext() const +{ + return m_glContext; +} +#endif + +void QCocoaWindow::recreateWindow(const QPlatformWindow *parentWindow) +{ + bool wasNSWindowChild = m_isNSWindowChild; + BOOL requestNSWindowChild = qt_mac_resolveOption(NO, window(), "_q_platform_MacUseNSWindow", + "QT_MAC_USE_NSWINDOW"); + m_isNSWindowChild = parentWindow && requestNSWindowChild; + bool needsNSWindow = m_isNSWindowChild || !parentWindow; + + QCocoaWindow *oldParentCocoaWindow = m_parentCocoaWindow; + m_parentCocoaWindow = const_cast(static_cast(parentWindow)); + if (m_parentCocoaWindow && m_isNSWindowChild) { + QWindow *parentQWindow = m_parentCocoaWindow->window(); + if (!parentQWindow->property("_q_platform_MacUseNSWindow").toBool()) { + parentQWindow->setProperty("_q_platform_MacUseNSWindow", QVariant(true)); + m_parentCocoaWindow->recreateWindow(m_parentCocoaWindow->m_parentCocoaWindow); + } + } + + bool usesNSPanel = [m_nsWindow isKindOfClass:[QNSPanel class]]; + + // No child QNSWindow should notify its QNSView + if (m_nsWindow && m_qtView && m_parentCocoaWindow && !oldParentCocoaWindow) + [[NSNotificationCenter defaultCenter] removeObserver:m_qtView + name:nil object:m_nsWindow]; + + // Remove current window (if any) + if ((m_nsWindow && !needsNSWindow) || (usesNSPanel != shouldUseNSPanel())) { + [m_nsWindow closeAndRelease]; + if (wasNSWindowChild && oldParentCocoaWindow) + oldParentCocoaWindow->removeChildWindow(this); + m_nsWindow = 0; + } + + if (needsNSWindow) { + bool noPreviousWindow = m_nsWindow == 0; + if (noPreviousWindow) + m_nsWindow = createNSWindow(); + + // Only non-child QNSWindows should notify their QNSViews + // (but don't register more than once). + if (m_qtView && (noPreviousWindow || (wasNSWindowChild && !m_isNSWindowChild))) + [[NSNotificationCenter defaultCenter] addObserver:m_qtView + selector:@selector(windowNotification:) + name:nil // Get all notifications + object:m_nsWindow]; + + if (oldParentCocoaWindow) { + if (!m_isNSWindowChild || oldParentCocoaWindow != m_parentCocoaWindow) + oldParentCocoaWindow->removeChildWindow(this); + m_forwardWindow.assign(oldParentCocoaWindow); + } + + setNSWindow(m_nsWindow); + } + + if (m_contentViewIsToBeEmbedded) { + // An embedded window doesn't have its own NSWindow. + } else if (!parentWindow) { + // QPlatformWindow subclasses must sync up with QWindow on creation: + propagateSizeHints(); + setWindowFlags(window()->flags()); + setWindowTitle(window()->title()); + setWindowState(window()->windowState()); + } else if (m_isNSWindowChild) { + m_nsWindow.styleMask = NSBorderlessWindowMask; + m_nsWindow.hasShadow = NO; + m_nsWindow.level = NSNormalWindowLevel; + NSWindowCollectionBehavior collectionBehavior = + NSWindowCollectionBehaviorManaged | NSWindowCollectionBehaviorIgnoresCycle + | NSWindowCollectionBehaviorFullScreenAuxiliary; + m_nsWindow.animationBehavior = NSWindowAnimationBehaviorNone; + m_nsWindow.collectionBehavior = collectionBehavior; + setCocoaGeometry(windowGeometry()); + + QList &siblings = m_parentCocoaWindow->m_childWindows; + if (siblings.contains(this)) { + if (!m_hiddenByClipping) + m_parentCocoaWindow->reinsertChildWindow(this); + } else { + if (!m_hiddenByClipping) + [m_parentCocoaWindow->m_nsWindow addChildWindow:m_nsWindow ordered:NSWindowAbove]; + siblings.append(this); + } + } else { + // Child windows have no NSWindow, link the NSViews instead. + [m_parentCocoaWindow->m_contentView addSubview : m_contentView]; + QRect rect = windowGeometry(); + // Prevent setting a (0,0) window size; causes opengl context + // "Invalid Drawable" warnings. + if (rect.isNull()) + rect.setSize(QSize(1, 1)); + NSRect frame = NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height()); + [m_contentView setFrame:frame]; + [m_contentView setHidden: YES]; + } + + m_nsWindow.ignoresMouseEvents = + (window()->flags() & Qt::WindowTransparentForInput) == Qt::WindowTransparentForInput; + + const qreal opacity = qt_window_private(window())->opacity; + if (!qFuzzyCompare(opacity, qreal(1.0))) + setOpacity(opacity); + + // top-level QWindows may have an attached NSToolBar, call + // update function which will attach to the NSWindow. + if (!parentWindow) + updateNSToolbar(); +} + +void QCocoaWindow::reinsertChildWindow(QCocoaWindow *child) +{ + int childIndex = m_childWindows.indexOf(child); + Q_ASSERT(childIndex != -1); + + for (int i = childIndex; i < m_childWindows.size(); i++) { + NSWindow *nsChild = m_childWindows[i]->m_nsWindow; + if (i != childIndex) + [m_nsWindow removeChildWindow:nsChild]; + [m_nsWindow addChildWindow:nsChild ordered:NSWindowAbove]; + } +} + +void QCocoaWindow::requestActivateWindow() +{ + NSWindow *window = [m_contentView window]; + [ window makeFirstResponder : m_contentView ]; + [ window makeKeyWindow ]; +} + +bool QCocoaWindow::shouldUseNSPanel() +{ + Qt::WindowType type = window()->type(); + + return !m_isNSWindowChild && + ((type & Qt::Popup) == Qt::Popup || (type & Qt::Dialog) == Qt::Dialog); +} + +QCocoaNSWindow * QCocoaWindow::createNSWindow() +{ + QMacAutoReleasePool pool; + + QRect rect = initialGeometry(window(), windowGeometry(), defaultWindowWidth, defaultWindowHeight); + NSRect frame = qt_mac_flipRect(rect); + + Qt::WindowType type = window()->type(); + Qt::WindowFlags flags = window()->flags(); + + NSUInteger styleMask; + if (m_isNSWindowChild) { + styleMask = NSBorderlessWindowMask; + } else { + styleMask = windowStyleMask(flags); + } + QCocoaNSWindow *createdWindow = 0; + + // Use NSPanel for popup-type windows. (Popup, Tool, ToolTip, SplashScreen) + // and dialogs + if (shouldUseNSPanel()) { + QNSPanel *window; + window = [[QNSPanel alloc] initWithContentRect:frame + styleMask: styleMask + qPlatformWindow:this]; + if ((type & Qt::Popup) == Qt::Popup) + [window setHasShadow:YES]; + + // Qt::Tool windows hide on app deactivation, unless Qt::WA_MacAlwaysShowToolWindow is set. + QVariant showWithoutActivating = QPlatformWindow::window()->property("_q_macAlwaysShowToolWindow"); + bool shouldHideOnDeactivate = ((type & Qt::Tool) == Qt::Tool) && + !(showWithoutActivating.isValid() && showWithoutActivating.toBool()); + [window setHidesOnDeactivate: shouldHideOnDeactivate]; + + // Make popup windows show on the same desktop as the parent full-screen window. + [window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenAuxiliary]; + if ((type & Qt::Popup) == Qt::Popup) + [window setAnimationBehavior:NSWindowAnimationBehaviorUtilityWindow]; + + createdWindow = window; + } else { + QNSWindow *window; + window = [[QNSWindow alloc] initWithContentRect:frame + styleMask: styleMask + qPlatformWindow:this]; + createdWindow = window; + } + + if ([createdWindow respondsToSelector:@selector(setRestorable:)]) + [createdWindow setRestorable: NO]; + + NSInteger level = windowLevel(flags); + [createdWindow setLevel:level]; + + // OpenGL surfaces can be ordered either above(default) or below the NSWindow. + // When ordering below the window must be tranclucent and have a clear background color. + static GLint openglSourfaceOrder = qt_mac_resolveOption(1, "QT_MAC_OPENGL_SURFACE_ORDER"); + + bool isTranslucent = window()->format().alphaBufferSize() > 0 + || (surface()->supportsOpenGL() && openglSourfaceOrder == -1); + if (isTranslucent) { + [createdWindow setBackgroundColor:[NSColor clearColor]]; + [createdWindow setOpaque:NO]; + } + + m_windowModality = window()->modality(); + + applyContentBorderThickness(createdWindow); + + return createdWindow; +} + +void QCocoaWindow::setNSWindow(QCocoaNSWindow *window) +{ + if (window.contentView != m_contentView) { + [m_contentView setPostsFrameChangedNotifications: NO]; + [m_contentView retain]; + if (m_contentView.superview) // m_contentView comes from another NSWindow + [m_contentView removeFromSuperview]; + [window setContentView:m_contentView]; + [m_contentView release]; + [m_contentView setPostsFrameChangedNotifications: YES]; + } +} + +void QCocoaWindow::removeChildWindow(QCocoaWindow *child) +{ + m_childWindows.removeOne(child); + [m_nsWindow removeChildWindow:child->m_nsWindow]; +} + +void QCocoaWindow::removeMonitor() +{ + if (!monitor) + return; + [NSEvent removeMonitor:monitor]; + monitor = nil; +} + +// Returns the current global screen geometry for the nswindow associated with this window. +QRect QCocoaWindow::nativeWindowGeometry() const +{ + if (!m_nsWindow || m_isNSWindowChild) + return geometry(); + + 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(window()->transientParent()->handle()); + } + return 0; +} + +// Syncs the NSWindow minimize/maximize/fullscreen state with the current QWindow state +void QCocoaWindow::syncWindowState(Qt::WindowState newState) +{ + if (!m_nsWindow) + return; + // if content view width or height is 0 then the window animations will crash so + // do nothing except set the new state + NSRect contentRect = [contentView() frame]; + if (contentRect.size.width <= 0 || contentRect.size.height <= 0) { + qWarning("invalid window content view size, check your window geometry"); + m_synchedWindowState = newState; + return; + } + + Qt::WindowState predictedState = newState; + if ((m_synchedWindowState & Qt::WindowMaximized) != (newState & Qt::WindowMaximized)) { + const int styleMask = [m_nsWindow styleMask]; + const bool usePerform = styleMask & NSResizableWindowMask; + [m_nsWindow setStyleMask:styleMask | NSResizableWindowMask]; + if (usePerform) + [m_nsWindow performZoom : m_nsWindow]; // toggles + else + [m_nsWindow zoom : m_nsWindow]; // toggles + [m_nsWindow setStyleMask:styleMask]; + } + + if ((m_synchedWindowState & Qt::WindowMinimized) != (newState & Qt::WindowMinimized)) { + if (newState & Qt::WindowMinimized) { + if ([m_nsWindow styleMask] & NSMiniaturizableWindowMask) + [m_nsWindow performMiniaturize : m_nsWindow]; + else + [m_nsWindow miniaturize : m_nsWindow]; + } else { + [m_nsWindow deminiaturize : m_nsWindow]; + } + } + + const bool effMax = m_effectivelyMaximized; + if ((m_synchedWindowState & Qt::WindowMaximized) != (newState & Qt::WindowMaximized) || (m_effectivelyMaximized && newState == Qt::WindowNoState)) { + if ((m_synchedWindowState & Qt::WindowFullScreen) == (newState & Qt::WindowFullScreen)) { + [m_nsWindow zoom : m_nsWindow]; // toggles + m_effectivelyMaximized = !effMax; + } else if (!(newState & Qt::WindowMaximized)) { + // it would be nice to change the target geometry that toggleFullScreen will animate toward + // but there is no known way, so the maximized state is not possible at this time + predictedState = static_cast(static_cast(newState) | Qt::WindowMaximized); + m_effectivelyMaximized = true; + } + } + + if ((m_synchedWindowState & Qt::WindowFullScreen) != (newState & Qt::WindowFullScreen)) { + if (window()->flags() & Qt::WindowFullscreenButtonHint) { + if (m_effectivelyMaximized && m_synchedWindowState == Qt::WindowFullScreen) + predictedState = Qt::WindowMaximized; + [m_nsWindow toggleFullScreen : m_nsWindow]; + } else { + if (newState & Qt::WindowFullScreen) { + QScreen *screen = window()->screen(); + if (screen) { + if (m_normalGeometry.width() < 0) { + m_oldWindowFlags = m_windowFlags; + window()->setFlags(window()->flags() | Qt::FramelessWindowHint); + m_normalGeometry = nativeWindowGeometry(); + setGeometry(screen->geometry()); + m_presentationOptions = [NSApp presentationOptions]; + [NSApp setPresentationOptions : m_presentationOptions | NSApplicationPresentationAutoHideMenuBar | NSApplicationPresentationAutoHideDock]; + } + } + } else { + window()->setFlags(m_oldWindowFlags); + setGeometry(m_normalGeometry); + m_normalGeometry.setRect(0, 0, -1, -1); + [NSApp setPresentationOptions : m_presentationOptions]; + } + } + } + +#ifdef QT_COCOA_ENABLE_WINDOW_DEBUG + qDebug() << "QCocoaWindow::syncWindowState" << newState << "actual" << predictedState << "was" << m_synchedWindowState << "effectively maximized" << m_effectivelyMaximized; +#endif + + // New state is now the current synched state + m_synchedWindowState = predictedState; +} + +bool QCocoaWindow::setWindowModified(bool modified) +{ + if (!m_nsWindow) + return false; + [m_nsWindow setDocumentEdited:(modified?YES:NO)]; + return true; +} + +void QCocoaWindow::setMenubar(QCocoaMenuBar *mb) +{ + m_menubar = mb; +} + +QCocoaMenuBar *QCocoaWindow::menubar() const +{ + return m_menubar; +} + +void QCocoaWindow::setWindowCursor(NSCursor *cursor) +{ + // This function is called (via QCocoaCursor) by Qt to set + // the cursor for this window. It can be called for a window + // that is not currenly under the mouse pointer (for example + // for a popup window.) Qt expects the set cursor to "stick": + // it should be accociated with the window until a different + // cursor is set. + if (m_windowCursor != cursor) { + [m_windowCursor release]; + m_windowCursor = [cursor retain]; + } + + // Use the built in cursor rect API if the QCocoaWindow has a NSWindow. + // Othervise, set the cursor if this window is under the mouse. In + // this case QNSView::cursorUpdate will set the cursor as the pointer + // moves. + if (m_nsWindow && m_qtView) { + [m_nsWindow invalidateCursorRectsForView : m_qtView]; + } else { + if (m_windowUnderMouse) + [cursor set]; + } +} + +void QCocoaWindow::registerTouch(bool enable) +{ + m_registerTouchCount += enable ? 1 : -1; + if (enable && m_registerTouchCount == 1) + [m_contentView setAcceptsTouchEvents:YES]; + else if (m_registerTouchCount == 0) + [m_contentView setAcceptsTouchEvents:NO]; +} + +void QCocoaWindow::setContentBorderThickness(int topThickness, int bottomThickness) +{ + m_topContentBorderThickness = topThickness; + m_bottomContentBorderThickness = bottomThickness; + bool enable = (topThickness > 0 || bottomThickness > 0); + m_drawContentBorderGradient = enable; + + applyContentBorderThickness(m_nsWindow); +} + +void QCocoaWindow::registerContentBorderArea(quintptr identifier, int upper, int lower) +{ + m_contentBorderAreas.insert(identifier, BorderRange(identifier, upper, lower)); + applyContentBorderThickness(m_nsWindow); +} + +void QCocoaWindow::setContentBorderAreaEnabled(quintptr identifier, bool enable) +{ + m_enabledContentBorderAreas.insert(identifier, enable); + applyContentBorderThickness(m_nsWindow); +} + +void QCocoaWindow::setContentBorderEnabled(bool enable) +{ + m_drawContentBorderGradient = enable; + applyContentBorderThickness(m_nsWindow); +} + +void QCocoaWindow::applyContentBorderThickness(NSWindow *window) +{ + if (!window) + return; + + if (!m_drawContentBorderGradient) { + [window setStyleMask:[window styleMask] & ~NSTexturedBackgroundWindowMask]; + [[[window contentView] superview] setNeedsDisplay:YES]; + return; + } + + // Find consecutive registered border areas, starting from the top. + QList ranges = m_contentBorderAreas.values(); + std::sort(ranges.begin(), ranges.end()); + int effectiveTopContentBorderThickness = m_topContentBorderThickness; + foreach (BorderRange range, ranges) { + // Skip disiabled ranges (typically hidden tool bars) + if (!m_enabledContentBorderAreas.value(range.identifier, false)) + continue; + + // Is this sub-range adjacent to or overlaping the + // existing total border area range? If so merge + // it into the total range, + if (range.upper <= (effectiveTopContentBorderThickness + 1)) + effectiveTopContentBorderThickness = qMax(effectiveTopContentBorderThickness, range.lower); + else + break; + } + + int effectiveBottomContentBorderThickness = m_bottomContentBorderThickness; + + [window setStyleMask:[window styleMask] | NSTexturedBackgroundWindowMask]; + + [window setContentBorderThickness:effectiveTopContentBorderThickness forEdge:NSMaxYEdge]; + [window setAutorecalculatesContentBorderThickness:NO forEdge:NSMaxYEdge]; + + [window setContentBorderThickness:effectiveBottomContentBorderThickness forEdge:NSMinYEdge]; + [window setAutorecalculatesContentBorderThickness:NO forEdge:NSMinYEdge]; + + [[[window contentView] superview] setNeedsDisplay:YES]; +} + +void QCocoaWindow::updateNSToolbar() +{ + if (!m_nsWindow) + return; + + NSToolbar *toolbar = QCocoaIntegration::instance()->toolbar(window()); + + if ([m_nsWindow toolbar] == toolbar) + return; + + [m_nsWindow setToolbar: toolbar]; + [m_nsWindow setShowsToolbarButton:YES]; +} + +bool QCocoaWindow::testContentBorderAreaPosition(int position) const +{ + return m_nsWindow && m_drawContentBorderGradient && + 0 <= position && position < [m_nsWindow contentBorderThicknessForEdge: NSMaxYEdge]; +} + +qreal QCocoaWindow::devicePixelRatio() const +{ + // The documented way to observe the relationship between device-independent + // and device pixels is to use one for the convertToBacking functions. Other + // methods such as [NSWindow backingScaleFacor] might not give the correct + // result, for example if setWantsBestResolutionOpenGLSurface is not set or + // or ignored by the OpenGL driver. + NSSize backingSize = [m_contentView convertSizeToBacking:NSMakeSize(1.0, 1.0)]; + return backingSize.height; +} + +// Returns whether the window can be expose, which it can +// if it is on screen and has a valid geometry. +bool QCocoaWindow::isWindowExposable() +{ + QSize size = geometry().size(); + bool validGeometry = (size.width() > 0 && size.height() > 0); + bool validScreen = ([[m_contentView window] screen] != 0); + bool nonHiddenSuperView = ![[m_contentView superview] isHidden]; + return (validGeometry && validScreen && nonHiddenSuperView); +} + +// Exposes the window by posting an expose event to QWindowSystemInterface +void QCocoaWindow::exposeWindow() +{ + m_geometryUpdateExposeAllowed = true; + + if (!isWindowExposable()) + return; + + // Update the QWindow's screen property. This property is set + // to QGuiApplication::primaryScreen() at QWindow construciton + // time, and we won't get a NSWindowDidChangeScreenNotification + // on show. The case where the window is initially displayed + // on a non-primary screen needs special handling here. + NSUInteger screenIndex = [[NSScreen screens] indexOfObject:m_nsWindow.screen]; + if (screenIndex != NSNotFound) { + QCocoaScreen *cocoaScreen = QCocoaIntegration::instance()->screenAtIndex(screenIndex); + if (cocoaScreen) + window()->setScreen(cocoaScreen->screen()); + } + + if (!m_isExposed) { + m_isExposed = true; + m_exposedGeometry = geometry(); + m_exposedDevicePixelRatio = devicePixelRatio(); + QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), m_exposedGeometry.size())); + } +} + +// Obscures the window by posting an empty expose event to QWindowSystemInterface +void QCocoaWindow::obscureWindow() +{ + if (m_isExposed) { + m_geometryUpdateExposeAllowed = false; + m_isExposed = false; + QWindowSystemInterface::handleExposeEvent(window(), QRegion()); + } +} + +// Updates window geometry by posting an expose event to QWindowSystemInterface +void QCocoaWindow::updateExposedGeometry() +{ + // updateExposedGeometry is not allowed to send the initial expose. If you want + // that call exposeWindow(); + if (!m_geometryUpdateExposeAllowed) + return; + + // Do not send incorrect exposes in case the window is not even visible yet. + // We might get here as a result of a resize() from QWidget's show(), for instance. + if (!window()->isVisible()) + return; + + if (!isWindowExposable()) + return; + + if (m_exposedGeometry.size() == geometry().size() && m_exposedDevicePixelRatio == devicePixelRatio()) + return; + + m_isExposed = true; + m_exposedGeometry = geometry(); + m_exposedDevicePixelRatio = devicePixelRatio(); + QWindowSystemInterface::handleExposeEvent(window(), QRect(QPoint(0, 0), m_exposedGeometry.size())); +} + +QWindow *QCocoaWindow::childWindowAt(QPoint windowPoint) +{ + QWindow *targetWindow = window(); + foreach (QObject *child, targetWindow->children()) + if (QWindow *childWindow = qobject_cast(child)) + if (QPlatformWindow *handle = childWindow->handle()) + if (handle->isExposed() && childWindow->geometry().contains(windowPoint)) + targetWindow = static_cast(handle)->childWindowAt(windowPoint - childWindow->position()); + + return targetWindow; +} + +bool QCocoaWindow::shouldRefuseKeyWindowAndFirstResponder() +{ + // This function speaks up if there's any reason + // to refuse key window or first responder state. + + if (window()->flags() & Qt::WindowDoesNotAcceptFocus) + return true; + + if (m_inSetVisible) { + QVariant showWithoutActivating = window()->property("_q_showWithoutActivating"); + if (showWithoutActivating.isValid() && showWithoutActivating.toBool()) + return true; + } + + return false; +} + +QPoint QCocoaWindow::bottomLeftClippedByNSWindowOffsetStatic(QWindow *window) +{ + if (window->handle()) + return static_cast(window->handle())->bottomLeftClippedByNSWindowOffset(); + return QPoint(); +} + +QPoint QCocoaWindow::bottomLeftClippedByNSWindowOffset() const +{ + if (!m_contentView) + return QPoint(); + const NSPoint origin = [m_contentView isFlipped] ? NSMakePoint(0, [m_contentView frame].size.height) + : NSMakePoint(0, 0); + const NSRect visibleRect = [m_contentView visibleRect]; + + return QPoint(visibleRect.origin.x, -visibleRect.origin.y + (origin.y - visibleRect.size.height)); +} + +QMargins QCocoaWindow::frameMargins() const +{ + NSRect frameW = [m_nsWindow frame]; + NSRect frameC = [m_nsWindow contentRectForFrameRect:frameW]; + + return QMargins(frameW.origin.x - frameC.origin.x, + (frameW.origin.y + frameW.size.height) - (frameC.origin.y + frameC.size.height), + (frameW.origin.x + frameW.size.width) - (frameC.origin.x + frameC.size.width), + frameC.origin.y - frameW.origin.y); +} + +void QCocoaWindow::setFrameStrutEventsEnabled(bool enabled) +{ + m_frameStrutEventsEnabled = enabled; +} diff --git a/5.6.2/qtbase/src/plugins/platforms/cocoa/qnsview.h b/5.6.2/qtbase/src/plugins/platforms/cocoa/qnsview.h new file mode 100644 index 0000000..2d4ad7a --- /dev/null +++ b/5.6.2/qtbase/src/plugins/platforms/cocoa/qnsview.h @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QNSVIEW_H +#define QNSVIEW_H + +#include + +#include +#include +#include + +#include "private/qcore_mac_p.h" + +QT_BEGIN_NAMESPACE +class QCocoaWindow; +class QCocoaBackingStore; +class QCocoaGLContext; +QT_END_NAMESPACE + +Q_FORWARD_DECLARE_OBJC_CLASS(QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper)); + +@interface QT_MANGLE_NAMESPACE(QNSView) : NSView { + QCocoaBackingStore* m_backingStore; + QPoint m_backingStoreOffset; + CGImageRef m_maskImage; + uchar *m_maskData; + bool m_shouldInvalidateWindowShadow; + QPointer m_window; + QCocoaWindow *m_platformWindow; + NSTrackingArea *m_trackingArea; + Qt::MouseButtons m_buttons; + Qt::MouseButtons m_frameStrutButtons; + QString m_composingText; + bool m_sendKeyEvent; + QStringList *currentCustomDragTypes; + bool m_sendUpAsRightButton; + Qt::KeyboardModifiers currentWheelModifiers; + bool m_subscribesForGlobalFrameNotifications; +#ifndef QT_NO_OPENGL + QCocoaGLContext *m_glContext; + bool m_shouldSetGLContextinDrawRect; +#endif + NSString *m_inputSource; + QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) *m_mouseMoveHelper; + bool m_resendKeyEvent; + bool m_scrolling; + bool m_updatingDrag; + bool m_exposedOnMoveToWindow; + NSEvent *m_currentlyInterpretedKeyEvent; + bool m_isMenuView; +} + +- (id)init; +- (id)initWithQWindow:(QWindow *)window platformWindow:(QCocoaWindow *) platformWindow; +- (void) clearQWindowPointers; +#ifndef QT_NO_OPENGL +- (void)setQCocoaGLContext:(QCocoaGLContext *)context; +#endif +- (void)flushBackingStore:(QCocoaBackingStore *)backingStore region:(const QRegion &)region offset:(QPoint)offset; +- (void)clearBackingStore:(QCocoaBackingStore *)backingStore; +- (void)setMaskRegion:(const QRegion *)region; +- (void)invalidateWindowShadowIfNeeded; +- (void)drawRect:(NSRect)dirtyRect; +- (void)updateGeometry; +- (void)notifyWindowStateChanged:(Qt::WindowState)newState; +- (void)windowNotification : (NSNotification *) windowNotification; +- (void)notifyWindowWillZoom:(BOOL)willZoom; +- (void)textInputContextKeyboardSelectionDidChangeNotification : (NSNotification *) textInputContextKeyboardSelectionDidChangeNotification; +- (void)viewDidHide; +- (void)viewDidUnhide; +- (void)removeFromSuperview; + +- (BOOL)isFlipped; +- (BOOL)acceptsFirstResponder; +- (BOOL)becomeFirstResponder; +- (BOOL)hasMask; +- (BOOL)isOpaque; + +- (void)convertFromScreen:(NSPoint)mouseLocation toWindowPoint:(QPointF *)qtWindowPoint andScreenPoint:(QPointF *)qtScreenPoint; + +- (void)resetMouseButtons; + +- (void)handleMouseEvent:(NSEvent *)theEvent; +- (void)mouseDown:(NSEvent *)theEvent; +- (void)mouseDragged:(NSEvent *)theEvent; +- (void)mouseUp:(NSEvent *)theEvent; +- (void)mouseMovedImpl:(NSEvent *)theEvent; +- (void)mouseEnteredImpl:(NSEvent *)theEvent; +- (void)mouseExitedImpl:(NSEvent *)theEvent; +- (void)cursorUpdateImpl:(NSEvent *)theEvent; +- (void)rightMouseDown:(NSEvent *)theEvent; +- (void)rightMouseDragged:(NSEvent *)theEvent; +- (void)rightMouseUp:(NSEvent *)theEvent; +- (void)otherMouseDown:(NSEvent *)theEvent; +- (void)otherMouseDragged:(NSEvent *)theEvent; +- (void)otherMouseUp:(NSEvent *)theEvent; +- (void)handleFrameStrutMouseEvent:(NSEvent *)theEvent; + +- (bool)handleTabletEvent: (NSEvent *)theEvent; +- (void)tabletPoint: (NSEvent *)theEvent; +- (void)tabletProximity: (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; + +- (void)registerDragTypes; +- (NSDragOperation)handleDrag:(id )sender; + +@end + +QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSView); + +#endif //QNSVIEW_H diff --git a/5.6.2/qtbase/src/plugins/platforms/cocoa/qnsview.mm b/5.6.2/qtbase/src/plugins/platforms/cocoa/qnsview.mm new file mode 100644 index 0000000..c67bcfd --- /dev/null +++ b/5.6.2/qtbase/src/plugins/platforms/cocoa/qnsview.mm @@ -0,0 +1,2107 @@ +/**************************************************************************** +** +** Copyright (C) 2015 The Qt Company Ltd. +** Contact: http://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL21$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see http://www.qt.io/terms-conditions. For further +** information use the contact form at http://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 or version 3 as published by the Free +** Software Foundation and appearing in the file LICENSE.LGPLv21 and +** LICENSE.LGPLv3 included in the packaging of this file. Please review the +** following information to ensure the GNU Lesser General Public License +** requirements will be met: https://www.gnu.org/licenses/lgpl.html and +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** As a special exception, The Qt Company gives you certain additional +** rights. These rights are described in The Qt Company LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#include +#include + +#include "qnsview.h" +#include "qcocoawindow.h" +#include "qcocoahelpers.h" +#include "qmultitouch_mac_p.h" +#include "qcocoadrag.h" +#include "qcocoainputcontext.h" +#include + +#include +#include +#include +#include +#include +#include "qcocoabackingstore.h" +#ifndef QT_NO_OPENGL +#include "qcocoaglcontext.h" +#endif +#include "qcocoaintegration.h" + +#ifdef QT_COCOA_ENABLE_ACCESSIBILITY_INSPECTOR +#include +#endif + +Q_LOGGING_CATEGORY(lcQpaTouch, "qt.qpa.input.touch") +#ifndef QT_NO_GESTURES +Q_LOGGING_CATEGORY(lcQpaGestures, "qt.qpa.input.gestures") +#endif +Q_LOGGING_CATEGORY(lcQpaTablet, "qt.qpa.input.tablet") + +static QTouchDevice *touchDevice = 0; + +// ### HACK Remove once 10.8 is unsupported +static NSString *_q_NSWindowDidChangeOcclusionStateNotification = nil; + +static bool _q_dontOverrideCtrlLMB = false; + +@interface NSEvent (Qt_Compile_Leopard_DeviceDelta) + - (CGFloat)deviceDeltaX; + - (CGFloat)deviceDeltaY; + - (CGFloat)deviceDeltaZ; +@end + +@interface QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) : NSObject +{ + QNSView *view; +} + +- (id)initWithView:(QNSView *)theView; + +- (void)mouseMoved:(NSEvent *)theEvent; +- (void)mouseEntered:(NSEvent *)theEvent; +- (void)mouseExited:(NSEvent *)theEvent; +- (void)cursorUpdate:(NSEvent *)theEvent; + +@end + +@implementation QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) + +- (id)initWithView:(QNSView *)theView +{ + self = [super init]; + if (self) { + view = theView; + } + return self; +} + +- (void)mouseMoved:(NSEvent *)theEvent +{ + [view mouseMovedImpl:theEvent]; +} + +- (void)mouseEntered:(NSEvent *)theEvent +{ + [view mouseEnteredImpl:theEvent]; +} + +- (void)mouseExited:(NSEvent *)theEvent +{ + [view mouseExitedImpl:theEvent]; +} + +- (void)cursorUpdate:(NSEvent *)theEvent +{ + [view cursorUpdateImpl:theEvent]; +} + +@end + +@implementation QT_MANGLE_NAMESPACE(QNSView) + ++ (void)initialize +{ + NSString **notificationNameVar = (NSString **)dlsym(RTLD_NEXT, "NSWindowDidChangeOcclusionStateNotification"); + if (notificationNameVar) + _q_NSWindowDidChangeOcclusionStateNotification = *notificationNameVar; + + _q_dontOverrideCtrlLMB = qt_mac_resolveOption(false, "QT_MAC_DONT_OVERRIDE_CTRL_LMB"); +} + +- (id) init +{ + self = [super initWithFrame : NSMakeRect(0,0, 300,300)]; + if (self) { + m_backingStore = 0; + m_maskImage = 0; + m_shouldInvalidateWindowShadow = false; + m_window = 0; + m_buttons = Qt::NoButton; + m_frameStrutButtons = Qt::NoButton; + m_sendKeyEvent = false; + m_subscribesForGlobalFrameNotifications = false; +#ifndef QT_NO_OPENGL + m_glContext = 0; + m_shouldSetGLContextinDrawRect = false; +#endif + currentCustomDragTypes = 0; + m_sendUpAsRightButton = false; + m_inputSource = 0; + m_mouseMoveHelper = [[QT_MANGLE_NAMESPACE(QNSViewMouseMoveHelper) alloc] initWithView:self]; + m_resendKeyEvent = false; + m_scrolling = false; + m_updatingDrag = false; + m_currentlyInterpretedKeyEvent = 0; + + if (!touchDevice) { + touchDevice = new QTouchDevice; + touchDevice->setType(QTouchDevice::TouchPad); + touchDevice->setCapabilities(QTouchDevice::Position | QTouchDevice::NormalizedPosition | QTouchDevice::MouseEmulation); + QWindowSystemInterface::registerTouchDevice(touchDevice); + } + + m_isMenuView = false; + self.focusRingType = NSFocusRingTypeNone; + } + return self; +} + +- (void)dealloc +{ + CGImageRelease(m_maskImage); + [m_trackingArea release]; + m_maskImage = 0; + m_window = 0; + m_subscribesForGlobalFrameNotifications = false; + [m_inputSource release]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [m_mouseMoveHelper release]; + + delete currentCustomDragTypes; + + [super dealloc]; +} + +- (id)initWithQWindow:(QWindow *)window platformWindow:(QCocoaWindow *) platformWindow +{ + self = [self init]; + if (!self) + return 0; + + m_window = window; + m_platformWindow = platformWindow; + m_sendKeyEvent = false; + m_trackingArea = nil; + +#ifdef QT_COCOA_ENABLE_ACCESSIBILITY_INSPECTOR + // prevent rift in space-time continuum, disable + // accessibility for the accessibility inspector's windows. + static bool skipAccessibilityForInspectorWindows = false; + if (!skipAccessibilityForInspectorWindows) { + + // m_accessibleRoot = window->accessibleRoot(); + + AccessibilityInspector *inspector = new AccessibilityInspector(window); + skipAccessibilityForInspectorWindows = true; + inspector->inspectWindow(window); + skipAccessibilityForInspectorWindows = false; + } +#endif + + [self registerDragTypes]; + [self setPostsFrameChangedNotifications : YES]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(updateGeometry) + name:NSViewFrameDidChangeNotification + object:self]; + + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(textInputContextKeyboardSelectionDidChangeNotification:) + name:NSTextInputContextKeyboardSelectionDidChangeNotification + object:nil]; + + return self; +} + +- (void) clearQWindowPointers +{ + m_window = 0; + m_platformWindow = 0; +} + +#ifndef QT_NO_OPENGL +- (void) setQCocoaGLContext:(QCocoaGLContext *)context +{ + m_glContext = context; + [m_glContext->nsOpenGLContext() setView:self]; + if (![m_glContext->nsOpenGLContext() view]) { + //was unable to set view + m_shouldSetGLContextinDrawRect = true; + } + + if (!m_subscribesForGlobalFrameNotifications) { + // NSOpenGLContext expects us to repaint (or update) the view when + // it changes position on screen. Since this happens unnoticed for + // the view when the parent view moves, we need to register a special + // notification that lets us handle this case: + m_subscribesForGlobalFrameNotifications = true; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(globalFrameChanged:) + name:NSViewGlobalFrameDidChangeNotification + object:self]; + } +} +#endif + +- (void) globalFrameChanged:(NSNotification*)notification +{ + Q_UNUSED(notification); + m_platformWindow->updateExposedGeometry(); +} + +- (void)viewDidMoveToSuperview +{ + if (!(m_platformWindow->m_contentViewIsToBeEmbedded)) + return; + + if ([self superview]) { + m_platformWindow->m_contentViewIsEmbedded = true; + QWindowSystemInterface::handleGeometryChange(m_window, m_platformWindow->geometry()); + m_platformWindow->updateExposedGeometry(); + QWindowSystemInterface::flushWindowSystemEvents(); + } else { + m_platformWindow->m_contentViewIsEmbedded = false; + } +} + +- (void)viewDidMoveToWindow +{ + m_backingStore = Q_NULLPTR; + m_isMenuView = [self.window.className isEqualToString:@"NSCarbonMenuWindow"]; + if (self.window) { + // This is the case of QWidgetAction's generated QWidget inserted in an NSMenu. + // 10.9 and newer get the NSWindowDidChangeOcclusionStateNotification + if (!_q_NSWindowDidChangeOcclusionStateNotification && m_isMenuView) { + m_exposedOnMoveToWindow = true; + m_platformWindow->exposeWindow(); + } + } else if (m_exposedOnMoveToWindow) { + m_exposedOnMoveToWindow = false; + m_platformWindow->obscureWindow(); + } +} + +- (void)viewWillMoveToWindow:(NSWindow *)newWindow +{ + // ### Merge "normal" window code path with this one for 5.1. + if (!(m_window->type() & Qt::SubWindow)) + return; + + if (newWindow) { + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(windowNotification:) + name:nil // Get all notifications + object:newWindow]; + } + if ([self window]) + [[NSNotificationCenter defaultCenter] removeObserver:self name:nil object:[self window]]; +} + +- (QWindow *)topLevelWindow +{ + QWindow *focusWindow = m_window; + + // For widgets we need to do a bit of trickery as the window + // to activate is the window of the top-level widget. + if (qstrcmp(m_window->metaObject()->className(), "QWidgetWindow") == 0) { + while (focusWindow->parent()) { + focusWindow = focusWindow->parent(); + } + } + + return focusWindow; +} + +- (void)updateGeometry +{ + QRect geometry; + + if (m_platformWindow->m_isNSWindowChild) { + return; +#if 0 + //geometry = qt_mac_toQRect([self frame]); + qDebug() << "nsview updateGeometry" << m_platformWindow->window(); + QRect screenRect = qt_mac_toQRect([m_platformWindow->m_nsWindow convertRectToScreen:[self frame]]); + qDebug() << "screenRect" << screenRect; + + screenRect.moveTop(qt_mac_flipYCoordinate(screenRect.y() + screenRect.height())); + geometry = QRect(m_platformWindow->window()->parent()->mapFromGlobal(screenRect.topLeft()), screenRect.size()); + qDebug() << "geometry" << geometry; +#endif + //geometry = QRect(screenRect.origin.x, qt_mac_flipYCoordinate(screenRect.origin.y + screenRect.size.height), screenRect.size.width, screenRect.size.height); + } else if (m_platformWindow->m_nsWindow) { + // top level window, get window rect and flip y. + NSRect rect = [self frame]; + NSRect windowRect = [[self window] frame]; + geometry = QRect(windowRect.origin.x, qt_mac_flipYCoordinate(windowRect.origin.y + rect.size.height), rect.size.width, rect.size.height); + } else if (m_platformWindow->m_contentViewIsToBeEmbedded) { + // embedded child window, use the frame rect ### merge with case below + geometry = qt_mac_toQRect([self bounds]); + } else { + // child window, use the frame rect + geometry = qt_mac_toQRect([self frame]); + } + + if (m_platformWindow->m_nsWindow && geometry == m_platformWindow->geometry()) + return; + + const bool isResize = geometry.size() != m_platformWindow->geometry().size(); + + // It can happen that self.window is nil (if we are changing + // styleMask from/to borderless and content view is being re-parented) + // - this results in an invalid coordinates. + if (m_platformWindow->m_inSetStyleMask && !self.window) + return; + +#ifdef QT_COCOA_ENABLE_WINDOW_DEBUG + qDebug() << "QNSView::udpateGeometry" << m_platformWindow << geometry; +#endif + + // Call setGeometry on QPlatformWindow. (not on QCocoaWindow, + // doing that will initiate a geometry change it and possibly create + // an infinite loop when this notification is triggered again.) + m_platformWindow->QPlatformWindow::setGeometry(geometry); + + // Don't send the geometry change if the QWindow is designated to be + // embedded in a foreign view hiearchy but has not actually been + // embedded yet - it's too early. + if (m_platformWindow->m_contentViewIsToBeEmbedded && !m_platformWindow->m_contentViewIsEmbedded) + return; + + // Send a geometry change event to Qt, if it's ready to handle events + if (!m_platformWindow->m_inConstructor) { + QWindowSystemInterface::handleGeometryChange(m_window, geometry); + m_platformWindow->updateExposedGeometry(); + // Guard against processing window system events during QWindow::setGeometry + // calles, which Qt and Qt applications do not excpect. + if (!m_platformWindow->m_inSetGeometry) + QWindowSystemInterface::flushWindowSystemEvents(); + else if (isResize) + m_backingStore = 0; + } +} + +- (void)notifyWindowStateChanged:(Qt::WindowState)newState +{ + // If the window was maximized, then fullscreen, then tried to go directly to "normal" state, + // this notification will say that it is "normal", but it will still look maximized, and + // if you called performZoom it would actually take it back to "normal". + // So we should say that it is maximized because it actually is. + if (newState == Qt::WindowNoState && m_platformWindow->m_effectivelyMaximized) + newState = Qt::WindowMaximized; + QWindowSystemInterface::handleWindowStateChanged(m_window, newState); + // We want to read the window state back from the window, + // but the event we just sent may be asynchronous. + QWindowSystemInterface::flushWindowSystemEvents(); + m_platformWindow->setSynchedWindowStateFromWindow(); +} + +- (void)windowNotification : (NSNotification *) windowNotification +{ + //qDebug() << "windowNotification" << QCFString::toQString([windowNotification name]); + + NSString *notificationName = [windowNotification name]; + if (notificationName == NSWindowDidBecomeKeyNotification) { + if (!m_platformWindow->windowIsPopupType() && !m_isMenuView) + QWindowSystemInterface::handleWindowActivated(m_window); + } else if (notificationName == NSWindowDidResignKeyNotification) { + // key window will be non-nil if another window became key... do not + // set the active window to zero here, the new key window's + // NSWindowDidBecomeKeyNotification hander will change the active window + NSWindow *keyWindow = [NSApp keyWindow]; + if (!keyWindow) { + // no new key window, go ahead and set the active window to zero + if (!m_platformWindow->windowIsPopupType() && !m_isMenuView) + QWindowSystemInterface::handleWindowActivated(0); + } + } else if (notificationName == NSWindowDidMiniaturizeNotification + || notificationName == NSWindowDidDeminiaturizeNotification) { + Qt::WindowState newState = notificationName == NSWindowDidMiniaturizeNotification ? + Qt::WindowMinimized : Qt::WindowNoState; + [self notifyWindowStateChanged:newState]; + } else if ([notificationName isEqualToString: @"NSWindowDidOrderOffScreenNotification"]) { + m_platformWindow->obscureWindow(); + } else if ([notificationName isEqualToString: @"NSWindowDidOrderOnScreenAndFinishAnimatingNotification"]) { + m_platformWindow->exposeWindow(); + } else if (_q_NSWindowDidChangeOcclusionStateNotification + && [notificationName isEqualToString:_q_NSWindowDidChangeOcclusionStateNotification]) { +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_9 +// ### HACK Remove the enum declaration, the warning disabling and the cast further down once 10.8 is unsupported +QT_WARNING_PUSH +QT_WARNING_DISABLE_CLANG("-Wobjc-method-access") + enum { NSWindowOcclusionStateVisible = 1UL << 1 }; +#endif + // Several unit tests expect paint and/or expose events for windows that are + // sometimes (unpredictably) occluded and some unit tests depend on QWindow::isExposed - + // don't send Expose/Obscure events when running under QTestLib. + static const bool onTestLib = qt_mac_resolveOption(false, "QT_QTESTLIB_RUNNING"); + if (!onTestLib) { + if ((NSUInteger)[self.window occlusionState] & NSWindowOcclusionStateVisible) { + m_platformWindow->exposeWindow(); + } else { + // Send Obscure events on window occlusion to stop animations. + m_platformWindow->obscureWindow(); + } + } +#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_9 +QT_WARNING_POP +#endif + } else if (notificationName == NSWindowDidChangeScreenNotification) { + if (m_window) { + NSUInteger screenIndex = [[NSScreen screens] indexOfObject:self.window.screen]; + if (screenIndex != NSNotFound) { + QCocoaScreen *cocoaScreen = QCocoaIntegration::instance()->screenAtIndex(screenIndex); + if (cocoaScreen) + QWindowSystemInterface::handleWindowScreenChanged(m_window, cocoaScreen->screen()); + m_platformWindow->updateExposedGeometry(); + } + } + } else if (notificationName == NSWindowDidEnterFullScreenNotification + || notificationName == NSWindowDidExitFullScreenNotification) { + Qt::WindowState newState = notificationName == NSWindowDidEnterFullScreenNotification ? + Qt::WindowFullScreen : Qt::WindowNoState; + [self notifyWindowStateChanged:newState]; + } +} + +- (void)textInputContextKeyboardSelectionDidChangeNotification : (NSNotification *) textInputContextKeyboardSelectionDidChangeNotification +{ + Q_UNUSED(textInputContextKeyboardSelectionDidChangeNotification) + if (([NSApp keyWindow] == [self window]) && [[self window] firstResponder] == self) { + QCocoaInputContext *ic = qobject_cast(QCocoaIntegration::instance()->inputContext()); + ic->updateLocale(); + } +} + +- (void)notifyWindowWillZoom:(BOOL)willZoom +{ + Qt::WindowState newState = willZoom ? Qt::WindowMaximized : Qt::WindowNoState; + if (!willZoom) + m_platformWindow->m_effectivelyMaximized = false; + [self notifyWindowStateChanged:newState]; +} + +- (void)viewDidHide +{ + m_platformWindow->obscureWindow(); +} + +- (void)viewDidUnhide +{ + m_platformWindow->exposeWindow(); +} + +- (void)removeFromSuperview +{ + QMacAutoReleasePool pool; + [super removeFromSuperview]; +} + +- (void) flushBackingStore:(QCocoaBackingStore *)backingStore region:(const QRegion &)region offset:(QPoint)offset +{ + m_backingStore = backingStore; + m_backingStoreOffset = offset * m_backingStore->getBackingStoreDevicePixelRatio(); + foreach (QRect rect, region.rects()) + [self setNeedsDisplayInRect:NSMakeRect(rect.x(), rect.y(), rect.width(), rect.height())]; +} + +- (void)clearBackingStore:(QCocoaBackingStore *)backingStore +{ + if (backingStore == m_backingStore) + m_backingStore = 0; +} + +- (BOOL) hasMask +{ + return m_maskImage != 0; +} + +- (BOOL) isOpaque +{ + if (!m_platformWindow) + return true; + return m_platformWindow->isOpaque(); +} + +- (void) setMaskRegion:(const QRegion *)region +{ + m_shouldInvalidateWindowShadow = true; + if (m_maskImage) + CGImageRelease(m_maskImage); + if (region->isEmpty()) { + m_maskImage = 0; + return; + } + + const QRect &rect = region->boundingRect(); + QImage tmp(rect.size(), QImage::Format_RGB32); + tmp.fill(Qt::white); + QPainter p(&tmp); + p.setClipRegion(*region); + p.fillRect(rect, Qt::black); + p.end(); + QImage maskImage = QImage(rect.size(), QImage::Format_Indexed8); + for (int y=0; ym_nsWindow) { + [m_platformWindow->m_nsWindow invalidateShadow]; + m_shouldInvalidateWindowShadow = false; + } +} + +- (void) drawRect:(NSRect)dirtyRect +{ +#ifndef QT_NO_OPENGL + if (m_glContext && m_shouldSetGLContextinDrawRect) { + [m_glContext->nsOpenGLContext() setView:self]; + m_shouldSetGLContextinDrawRect = false; + } +#endif + + if (m_platformWindow->m_drawContentBorderGradient) + NSDrawWindowBackground(dirtyRect); + + if (!m_backingStore) + return; + + // Calculate source and target rects. The target rect is the dirtyRect: + CGRect dirtyWindowRect = NSRectToCGRect(dirtyRect); + + // The backing store source rect will be larger on retina displays. + // Scale dirtyRect by the device pixel ratio: + const qreal devicePixelRatio = m_backingStore->getBackingStoreDevicePixelRatio(); + CGRect dirtyBackingRect = CGRectMake(dirtyRect.origin.x * devicePixelRatio, + dirtyRect.origin.y * devicePixelRatio, + dirtyRect.size.width * devicePixelRatio, + dirtyRect.size.height * devicePixelRatio); + + NSGraphicsContext *nsGraphicsContext = [NSGraphicsContext currentContext]; + CGContextRef cgContext = (CGContextRef) [nsGraphicsContext graphicsPort]; + + // Translate coordiate system from CoreGraphics (bottom-left) to NSView (top-left): + CGContextSaveGState(cgContext); + int dy = dirtyWindowRect.origin.y + CGRectGetMaxY(dirtyWindowRect); + + CGContextTranslateCTM(cgContext, 0, dy); + CGContextScaleCTM(cgContext, 1, -1); + + // If a mask is set, modify the sub image accordingly: + CGImageRef subMask = 0; + if (m_maskImage) { + subMask = CGImageCreateWithImageInRect(m_maskImage, dirtyWindowRect); + CGContextClipToMask(cgContext, dirtyWindowRect, subMask); + } + + // Clip out and draw the correct sub image from the (shared) backingstore: + CGRect backingStoreRect = CGRectMake( + dirtyBackingRect.origin.x + m_backingStoreOffset.x(), + dirtyBackingRect.origin.y + m_backingStoreOffset.y(), + dirtyBackingRect.size.width, + dirtyBackingRect.size.height + ); + CGImageRef bsCGImage = qt_mac_toCGImage(m_backingStore->toImage()); + CGImageRef cleanImg = CGImageCreateWithImageInRect(bsCGImage, backingStoreRect); + + // Optimization: Copy frame buffer content instead of blending for + // top-level windows where Qt fills the entire window content area. + // (But don't overpaint the title-bar gradient) + if (m_platformWindow->m_nsWindow && !m_platformWindow->m_drawContentBorderGradient) + CGContextSetBlendMode(cgContext, kCGBlendModeCopy); + + CGContextDrawImage(cgContext, dirtyWindowRect, cleanImg); + + // Clean-up: + CGContextRestoreGState(cgContext); + CGImageRelease(cleanImg); + CGImageRelease(subMask); + CGImageRelease(bsCGImage); + + [self invalidateWindowShadowIfNeeded]; +} + +- (BOOL) isFlipped +{ + return YES; +} + +- (BOOL)becomeFirstResponder +{ + if (!m_window || !m_platformWindow) + return NO; + if (m_window->flags() & Qt::WindowTransparentForInput) + return NO; + if (!m_platformWindow->windowIsPopupType() && !m_isMenuView) + QWindowSystemInterface::handleWindowActivated([self topLevelWindow]); + return YES; +} + +- (BOOL)acceptsFirstResponder +{ + if (!m_window || !m_platformWindow) + return NO; + if (m_isMenuView) + return NO; + if (m_platformWindow->shouldRefuseKeyWindowAndFirstResponder()) + return NO; + if (m_window->flags() & Qt::WindowTransparentForInput) + return NO; + if ((m_window->flags() & Qt::ToolTip) == Qt::ToolTip) + return NO; + return YES; +} + +- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent +{ + Q_UNUSED(theEvent) + if (!m_window || !m_platformWindow) + return NO; + if (m_window->flags() & Qt::WindowTransparentForInput) + return NO; + return YES; +} + +- (void)convertFromScreen:(NSPoint)mouseLocation toWindowPoint:(QPointF *)qtWindowPoint andScreenPoint:(QPointF *)qtScreenPoint +{ + // Calculate the mouse position in the QWindow and Qt screen coordinate system, + // starting from coordinates in the NSWindow coordinate system. + // + // This involves translating according to the window location on screen, + // as well as inverting the y coordinate due to the origin change. + // + // Coordinate system overview, outer to innermost: + // + // Name Origin + // + // OS X screen bottom-left + // Qt screen top-left + // NSWindow bottom-left + // NSView/QWindow top-left + // + // NSView and QWindow are equal coordinate systems: the QWindow covers the + // entire NSView, and we've set the NSView's isFlipped property to true. + + NSWindow *window = [self window]; + NSPoint nsWindowPoint; + NSRect windowRect = [window convertRectFromScreen:NSMakeRect(mouseLocation.x, mouseLocation.y, 1, 1)]; + nsWindowPoint = windowRect.origin; // NSWindow coordinates + NSPoint nsViewPoint = [self convertPoint: nsWindowPoint fromView: nil]; // NSView/QWindow coordinates + *qtWindowPoint = QPointF(nsViewPoint.x, nsViewPoint.y); // NSView/QWindow coordinates + + *qtScreenPoint = QPointF(mouseLocation.x, qt_mac_flipYCoordinate(mouseLocation.y)); // Qt screen coordinates +} + +- (void)resetMouseButtons +{ + m_buttons = Qt::NoButton; + m_frameStrutButtons = Qt::NoButton; +} + +- (NSPoint) screenMousePoint:(NSEvent *)theEvent +{ + NSPoint screenPoint; + if (theEvent) { + NSPoint windowPoint = [theEvent locationInWindow]; + NSRect screenRect = [[theEvent window] convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 1, 1)]; + screenPoint = screenRect.origin; + } else { + screenPoint = [NSEvent mouseLocation]; + } + return screenPoint; +} + +- (void)handleMouseEvent:(NSEvent *)theEvent +{ + bool isTabletEvent = [self handleTabletEvent: theEvent]; + + QPointF qtWindowPoint; + QPointF qtScreenPoint; + QNSView *targetView = self; + if (m_platformWindow && m_platformWindow->m_forwardWindow) { + if (theEvent.type == NSLeftMouseDragged || theEvent.type == NSLeftMouseUp) + targetView = m_platformWindow->m_forwardWindow->m_qtView; + else + m_platformWindow->m_forwardWindow.clear(); + } + + // Popups implicitly grap mouse events; forward to the active popup if there is one + if (QCocoaWindow *popup = QCocoaIntegration::instance()->activePopupWindow()) { + // Tooltips must be transparent for mouse events + // The bug reference is QTBUG-46379 + if (!popup->m_windowFlags.testFlag(Qt::ToolTip)) { + if (QNSView *popupView = popup->qtView()) + targetView = popupView; + } + } + + [targetView convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&qtWindowPoint andScreenPoint:&qtScreenPoint]; + ulong timestamp = [theEvent timestamp] * 1000; + + QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); + nativeDrag->setLastMouseEvent(theEvent, self); + + Qt::KeyboardModifiers keyboardModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; + QWindowSystemInterface::handleMouseEvent(targetView->m_window, timestamp, qtWindowPoint, qtScreenPoint, m_buttons, keyboardModifiers, + isTabletEvent ? Qt::MouseEventSynthesizedByQt : Qt::MouseEventNotSynthesized); +} + +- (void)handleFrameStrutMouseEvent:(NSEvent *)theEvent +{ + // get m_buttons in sync + // Don't send frme strut events if we are in the middle of a mouse drag. + if (m_buttons != Qt::NoButton) + return; + + NSEventType ty = [theEvent type]; + switch (ty) { + case NSLeftMouseDown: + m_frameStrutButtons |= Qt::LeftButton; + break; + case NSLeftMouseUp: + m_frameStrutButtons &= ~Qt::LeftButton; + break; + case NSRightMouseDown: + m_frameStrutButtons |= Qt::RightButton; + break; + case NSLeftMouseDragged: + m_frameStrutButtons |= Qt::LeftButton; + break; + case NSRightMouseDragged: + m_frameStrutButtons |= Qt::RightButton; + break; + case NSRightMouseUp: + m_frameStrutButtons &= ~Qt::RightButton; + break; + case NSOtherMouseDown: + m_frameStrutButtons |= cocoaButton2QtButton([theEvent buttonNumber]); + break; + case NSOtherMouseUp: + m_frameStrutButtons &= ~cocoaButton2QtButton([theEvent buttonNumber]); + default: + break; + } + + NSWindow *window = [self window]; + NSPoint windowPoint = [theEvent locationInWindow]; + + int windowScreenY = [window frame].origin.y + [window frame].size.height; + NSPoint windowCoord = [self convertPoint:[self frame].origin toView:nil]; + int viewScreenY = [window convertRectToScreen:NSMakeRect(windowCoord.x, windowCoord.y, 0, 0)].origin.y; + int titleBarHeight = windowScreenY - viewScreenY; + + NSPoint nsViewPoint = [self convertPoint: windowPoint fromView: nil]; + QPoint qtWindowPoint = QPoint(nsViewPoint.x, titleBarHeight + nsViewPoint.y); + NSPoint screenPoint = [window convertRectToScreen:NSMakeRect(windowPoint.x, windowPoint.y, 0, 0)].origin; + QPoint qtScreenPoint = QPoint(screenPoint.x, qt_mac_flipYCoordinate(screenPoint.y)); + + ulong timestamp = [theEvent timestamp] * 1000; + QWindowSystemInterface::handleFrameStrutMouseEvent(m_window, timestamp, qtWindowPoint, qtScreenPoint, m_frameStrutButtons); +} + +- (void)mouseDown:(NSEvent *)theEvent +{ + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return [super mouseDown:theEvent]; + m_sendUpAsRightButton = false; + + // Handle any active poup windows; clicking outisde them should close them + // all. Don't do anything or clicks inside one of the menus, let Cocoa + // handle that case. Note that in practice many windows of the Qt::Popup type + // will actually close themselves in this case using logic implemented in + // that particular poup type (for example context menus). However, Qt expects + // that plain popup QWindows will also be closed, so we implement the logic + // here as well. + QList *popups = QCocoaIntegration::instance()->popupWindowStack(); + if (!popups->isEmpty()) { + // Check if the click is outside all popups. + bool inside = false; + QPointF qtScreenPoint = qt_mac_flipPoint([self screenMousePoint:theEvent]); + for (QList::const_iterator it = popups->begin(); it != popups->end(); ++it) { + if ((*it)->geometry().contains(qtScreenPoint.toPoint())) { + inside = true; + break; + } + } + // Close the popups if the click was outside. + if (!inside) { + Qt::WindowType type = QCocoaIntegration::instance()->activePopupWindow()->window()->type(); + while (QCocoaWindow *popup = QCocoaIntegration::instance()->popPopupWindow()) { + QWindowSystemInterface::handleCloseEvent(popup->window()); + QWindowSystemInterface::flushWindowSystemEvents(); + } + // Consume the mouse event when closing the popup, except for tool tips + // were it's expected that the event is processed normally. + if (type != Qt::ToolTip) + return; + } + } + + if ([self hasMarkedText]) { + [[NSTextInputContext currentInputContext] handleEvent:theEvent]; + } else { + if (!_q_dontOverrideCtrlLMB && [QNSView convertKeyModifiers:[theEvent modifierFlags]] & Qt::MetaModifier) { + m_buttons |= Qt::RightButton; + m_sendUpAsRightButton = true; + } else { + m_buttons |= Qt::LeftButton; + } + [self handleMouseEvent:theEvent]; + } +} + +- (void)mouseDragged:(NSEvent *)theEvent +{ + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return [super mouseDragged:theEvent]; + if (!(m_buttons & (m_sendUpAsRightButton ? Qt::RightButton : Qt::LeftButton))) + qCDebug(lcQpaCocoaWindow, "QNSView mouseDragged: Internal mouse button tracking invalid (missing Qt::LeftButton)"); + [self handleMouseEvent:theEvent]; +} + +- (void)mouseUp:(NSEvent *)theEvent +{ + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return [super mouseUp:theEvent]; + if (m_sendUpAsRightButton) { + m_buttons &= ~Qt::RightButton; + m_sendUpAsRightButton = false; + } else { + m_buttons &= ~Qt::LeftButton; + } + [self handleMouseEvent:theEvent]; +} + +- (void)updateTrackingAreas +{ + [super updateTrackingAreas]; + + QMacAutoReleasePool pool; + + // NSTrackingInVisibleRect keeps care of updating once the tracking is set up, so bail out early + if (m_trackingArea && [[self trackingAreas] containsObject:m_trackingArea]) + return; + + // Ideally, we shouldn't have NSTrackingMouseMoved events included below, it should + // only be turned on if mouseTracking, hover is on or a tool tip is set. + // Unfortunately, Qt will send "tooltip" events on mouse moves, so we need to + // turn it on in ALL case. That means EVERY QWindow gets to pay the cost of + // mouse moves delivered to it (Apple recommends keeping it OFF because there + // is a performance hit). So it goes. + NSUInteger trackingOptions = NSTrackingMouseEnteredAndExited | NSTrackingActiveInActiveApp + | NSTrackingInVisibleRect | NSTrackingMouseMoved | NSTrackingCursorUpdate; + [m_trackingArea release]; + m_trackingArea = [[NSTrackingArea alloc] initWithRect:[self frame] + options:trackingOptions + owner:m_mouseMoveHelper + userInfo:nil]; + [self addTrackingArea:m_trackingArea]; +} + +-(void)cursorUpdateImpl:(NSEvent *)theEvent +{ + Q_UNUSED(theEvent) + // Set the cursor manually if there is no NSWindow. + if (!m_platformWindow->m_nsWindow && m_platformWindow->m_windowCursor) + [m_platformWindow->m_windowCursor set]; + else + [super cursorUpdate:theEvent]; +} + +-(void)resetCursorRects +{ + // Use the cursor rect API if there is a NSWindow + if (m_platformWindow->m_nsWindow && m_platformWindow->m_windowCursor) + [self addCursorRect:[self visibleRect] cursor:m_platformWindow->m_windowCursor]; +} + +- (void)mouseMovedImpl:(NSEvent *)theEvent +{ + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return; + + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[self screenMousePoint:theEvent] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindow *childWindow = m_platformWindow->childWindowAt(windowPoint.toPoint()); + + // Top-level windows generate enter-leave events for sub-windows. + // Qt wants to know which window (if any) will be entered at the + // the time of the leave. This is dificult to accomplish by + // handling mouseEnter and mouseLeave envents, since they are sent + // individually to different views. + if (m_platformWindow->m_nsWindow && childWindow) { + if (childWindow != m_platformWindow->m_enterLeaveTargetWindow) { + QWindowSystemInterface::handleEnterLeaveEvent(childWindow, m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); + m_platformWindow->m_enterLeaveTargetWindow = childWindow; + } + } + + // Cocoa keeps firing mouse move events for obscured parent views. Qt should not + // send those events so filter them out here. + if (childWindow != m_window) + return; + + [self handleMouseEvent: theEvent]; +} + +- (void)mouseEnteredImpl:(NSEvent *)theEvent +{ + Q_UNUSED(theEvent) + m_platformWindow->m_windowUnderMouse = true; + + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return; + + // Top-level windows generate enter events for sub-windows. + if (!m_platformWindow->m_nsWindow) + return; + + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[NSEvent mouseLocation] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + m_platformWindow->m_enterLeaveTargetWindow = m_platformWindow->childWindowAt(windowPoint.toPoint()); + QWindowSystemInterface::handleEnterEvent(m_platformWindow->m_enterLeaveTargetWindow, windowPoint, screenPoint); +} + +- (void)mouseExitedImpl:(NSEvent *)theEvent +{ + Q_UNUSED(theEvent); + m_platformWindow->m_windowUnderMouse = false; + + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return; + + // Top-level windows generate leave events for sub-windows. + if (!m_platformWindow->m_nsWindow) + return; + + QWindowSystemInterface::handleLeaveEvent(m_platformWindow->m_enterLeaveTargetWindow); + m_platformWindow->m_enterLeaveTargetWindow = 0; +} + +- (void)rightMouseDown:(NSEvent *)theEvent +{ + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return [super rightMouseDown:theEvent]; + m_buttons |= Qt::RightButton; + m_sendUpAsRightButton = true; + [self handleMouseEvent:theEvent]; +} + +- (void)rightMouseDragged:(NSEvent *)theEvent +{ + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return [super rightMouseDragged:theEvent]; + if (!(m_buttons & Qt::RightButton)) + qCDebug(lcQpaCocoaWindow, "QNSView rightMouseDragged: Internal mouse button tracking invalid (missing Qt::RightButton)"); + [self handleMouseEvent:theEvent]; +} + +- (void)rightMouseUp:(NSEvent *)theEvent +{ + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return [super rightMouseUp:theEvent]; + m_buttons &= ~Qt::RightButton; + m_sendUpAsRightButton = false; + [self handleMouseEvent:theEvent]; +} + +- (void)otherMouseDown:(NSEvent *)theEvent +{ + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return [super otherMouseDown:theEvent]; + m_buttons |= cocoaButton2QtButton([theEvent buttonNumber]); + [self handleMouseEvent:theEvent]; +} + +- (void)otherMouseDragged:(NSEvent *)theEvent +{ + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return [super otherMouseDragged:theEvent]; + if (!(m_buttons & ~(Qt::LeftButton | Qt::RightButton))) + qCDebug(lcQpaCocoaWindow, "QNSView otherMouseDragged: Internal mouse button tracking invalid (missing Qt::MiddleButton or Qt::ExtraButton*)"); + [self handleMouseEvent:theEvent]; +} + +- (void)otherMouseUp:(NSEvent *)theEvent +{ + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return [super otherMouseUp:theEvent]; + m_buttons &= ~cocoaButton2QtButton([theEvent buttonNumber]); + [self handleMouseEvent:theEvent]; +} + +struct QCocoaTabletDeviceData +{ + QTabletEvent::TabletDevice device; + QTabletEvent::PointerType pointerType; + uint capabilityMask; + qint64 uid; +}; + +typedef QHash QCocoaTabletDeviceDataHash; +Q_GLOBAL_STATIC(QCocoaTabletDeviceDataHash, tabletDeviceDataHash) + +- (bool)handleTabletEvent: (NSEvent *)theEvent +{ + NSEventType eventType = [theEvent type]; + if (eventType != NSTabletPoint && [theEvent subtype] != NSTabletPointEventSubtype) + return false; // Not a tablet event. + + ulong timestamp = [theEvent timestamp] * 1000; + + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[NSEvent mouseLocation] toWindowPoint: &windowPoint andScreenPoint: &screenPoint]; + + uint deviceId = [theEvent deviceID]; + if (!tabletDeviceDataHash->contains(deviceId)) { + // Error: Unknown tablet device. Qt also gets into this state + // when running on a VM. This appears to be harmless; don't + // print a warning. + return false; + } + const QCocoaTabletDeviceData &deviceData = tabletDeviceDataHash->value(deviceId); + + bool down = (eventType != NSMouseMoved); + + qreal pressure; + if (down) { + pressure = [theEvent pressure]; + } else { + pressure = 0.0; + } + + NSPoint tilt = [theEvent tilt]; + int xTilt = qRound(tilt.x * 60.0); + int yTilt = qRound(tilt.y * -60.0); + Qt::MouseButtons buttons = static_cast(static_cast([theEvent buttonMask])); + qreal tangentialPressure = 0; + qreal rotation = 0; + int z = 0; + if (deviceData.capabilityMask & 0x0200) + z = [theEvent absoluteZ]; + + if (deviceData.capabilityMask & 0x0800) + tangentialPressure = ([theEvent tangentialPressure] * 2.0) - 1.0; + + rotation = 360.0 - [theEvent rotation]; + if (rotation > 180.0) + rotation -= 360.0; + + Qt::KeyboardModifiers keyboardModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; + + qCDebug(lcQpaTablet, "event on tablet %d with tool %d type %d unique ID %lld pos %6.1f, %6.1f root pos %6.1f, %6.1f buttons 0x%x pressure %4.2lf tilt %d, %d rotation %6.2lf", + deviceId, deviceData.device, deviceData.pointerType, deviceData.uid, + windowPoint.x(), windowPoint.y(), screenPoint.x(), screenPoint.y(), + static_cast(buttons), pressure, xTilt, yTilt, rotation); + + QWindowSystemInterface::handleTabletEvent(m_window, timestamp, windowPoint, screenPoint, + deviceData.device, deviceData.pointerType, buttons, pressure, xTilt, yTilt, + tangentialPressure, rotation, z, deviceData.uid, + keyboardModifiers); + return true; +} + +- (void)tabletPoint: (NSEvent *)theEvent +{ + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return [super tabletPoint:theEvent]; + + [self handleTabletEvent: theEvent]; +} + +static QTabletEvent::TabletDevice wacomTabletDevice(NSEvent *theEvent) +{ + qint64 uid = [theEvent uniqueID]; + uint bits = [theEvent vendorPointingDeviceType]; + if (bits == 0 && uid != 0) { + // Fallback. It seems that the driver doesn't always include all the information. + // High-End Wacom devices store their "type" in the uper bits of the Unique ID. + // I'm not sure how to handle it for consumer devices, but I'll test that in a bit. + bits = uid >> 32; + } + + QTabletEvent::TabletDevice device; + // Defined in the "EN0056-NxtGenImpGuideX" + // on Wacom's Developer Website (www.wacomeng.com) + if (((bits & 0x0006) == 0x0002) && ((bits & 0x0F06) != 0x0902)) { + device = QTabletEvent::Stylus; + } else { + switch (bits & 0x0F06) { + case 0x0802: + device = QTabletEvent::Stylus; + break; + case 0x0902: + device = QTabletEvent::Airbrush; + break; + case 0x0004: + device = QTabletEvent::FourDMouse; + break; + case 0x0006: + device = QTabletEvent::Puck; + break; + case 0x0804: + device = QTabletEvent::RotationStylus; + break; + default: + device = QTabletEvent::NoDevice; + } + } + return device; +} + +- (void)tabletProximity: (NSEvent *)theEvent +{ + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return [super tabletProximity:theEvent]; + + ulong timestamp = [theEvent timestamp] * 1000; + + QCocoaTabletDeviceData deviceData; + deviceData.uid = [theEvent uniqueID]; + deviceData.capabilityMask = [theEvent capabilityMask]; + + switch ([theEvent pointingDeviceType]) { + case NSUnknownPointingDevice: + default: + deviceData.pointerType = QTabletEvent::UnknownPointer; + break; + case NSPenPointingDevice: + deviceData.pointerType = QTabletEvent::Pen; + break; + case NSCursorPointingDevice: + deviceData.pointerType = QTabletEvent::Cursor; + break; + case NSEraserPointingDevice: + deviceData.pointerType = QTabletEvent::Eraser; + break; + } + + deviceData.device = wacomTabletDevice(theEvent); + + // The deviceID is "unique" while in the proximity, it's a key that we can use for + // linking up QCocoaTabletDeviceData to an event (especially if there are two devices in action). + bool entering = [theEvent isEnteringProximity]; + uint deviceId = [theEvent deviceID]; + if (entering) { + tabletDeviceDataHash->insert(deviceId, deviceData); + } else { + tabletDeviceDataHash->remove(deviceId); + } + + qCDebug(lcQpaTablet, "proximity change on tablet %d: current tool %d type %d unique ID %lld", + deviceId, deviceData.device, deviceData.pointerType, deviceData.uid); + + if (entering) { + QWindowSystemInterface::handleTabletEnterProximityEvent(timestamp, deviceData.device, deviceData.pointerType, deviceData.uid); + } else { + QWindowSystemInterface::handleTabletLeaveProximityEvent(timestamp, deviceData.device, deviceData.pointerType, deviceData.uid); + } +} + +- (bool) shouldSendSingleTouch +{ + // QtWidgets expects single-point touch events, QtDeclarative does not. + // Until there is an API we solve this by looking at the window class type. + return m_window->inherits("QWidgetWindow"); +} + +- (void)touchesBeganWithEvent:(NSEvent *)event +{ + const NSTimeInterval timestamp = [event timestamp]; + const QList points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); + qCDebug(lcQpaTouch) << "touchesBeganWithEvent" << points; + QWindowSystemInterface::handleTouchEvent(m_window, timestamp * 1000, touchDevice, points); +} + +- (void)touchesMovedWithEvent:(NSEvent *)event +{ + const NSTimeInterval timestamp = [event timestamp]; + const QList points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); + qCDebug(lcQpaTouch) << "touchesMovedWithEvent" << points; + QWindowSystemInterface::handleTouchEvent(m_window, timestamp * 1000, touchDevice, points); +} + +- (void)touchesEndedWithEvent:(NSEvent *)event +{ + const NSTimeInterval timestamp = [event timestamp]; + const QList points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); + qCDebug(lcQpaTouch) << "touchesEndedWithEvent" << points; + QWindowSystemInterface::handleTouchEvent(m_window, timestamp * 1000, touchDevice, points); +} + +- (void)touchesCancelledWithEvent:(NSEvent *)event +{ + const NSTimeInterval timestamp = [event timestamp]; + const QList points = QCocoaTouch::getCurrentTouchPointList(event, [self shouldSendSingleTouch]); + qCDebug(lcQpaTouch) << "touchesCancelledWithEvent" << points; + QWindowSystemInterface::handleTouchEvent(m_window, timestamp * 1000, touchDevice, points); +} + +#ifndef QT_NO_GESTURES + +- (bool)handleGestureAsBeginEnd:(NSEvent *)event +{ + if (QSysInfo::QSysInfo::MacintoshVersion < QSysInfo::MV_10_11) + return false; + + if ([event phase] == NSEventPhaseBegan) { + [self beginGestureWithEvent:event]; + return true; + } + + if ([event phase] == NSEventPhaseEnded) { + [self endGestureWithEvent:event]; + return true; + } + + return false; +} +- (void)magnifyWithEvent:(NSEvent *)event +{ + if ([self handleGestureAsBeginEnd:event]) + return; + + qCDebug(lcQpaGestures) << "magnifyWithEvent" << [event magnification]; + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[NSEvent mouseLocation] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindowSystemInterface::handleGestureEventWithRealValue(m_window, timestamp, Qt::ZoomNativeGesture, + [event magnification], windowPoint, screenPoint); +} + +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 +- (void)smartMagnifyWithEvent:(NSEvent *)event +{ + static bool zoomIn = true; + qCDebug(lcQpaGestures) << "smartMagnifyWithEvent" << zoomIn; + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[NSEvent mouseLocation] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindowSystemInterface::handleGestureEventWithRealValue(m_window, timestamp, Qt::SmartZoomNativeGesture, + zoomIn ? 1.0f : 0.0f, windowPoint, screenPoint); + zoomIn = !zoomIn; +} +#endif + +- (void)rotateWithEvent:(NSEvent *)event +{ + if ([self handleGestureAsBeginEnd:event]) + return; + + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[NSEvent mouseLocation] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindowSystemInterface::handleGestureEventWithRealValue(m_window, timestamp, Qt::RotateNativeGesture, + -[event rotation], windowPoint, screenPoint); +} + +- (void)swipeWithEvent:(NSEvent *)event +{ + qCDebug(lcQpaGestures) << "swipeWithEvent" << [event deltaX] << [event deltaY]; + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[NSEvent mouseLocation] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + + qreal angle = 0.0f; + if ([event deltaX] == 1) + angle = 180.0f; + else if ([event deltaX] == -1) + angle = 0.0f; + else if ([event deltaY] == 1) + angle = 90.0f; + else if ([event deltaY] == -1) + angle = 270.0f; + + QWindowSystemInterface::handleGestureEventWithRealValue(m_window, timestamp, Qt::SwipeNativeGesture, + angle, windowPoint, screenPoint); +} + +- (void)beginGestureWithEvent:(NSEvent *)event +{ + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[NSEvent mouseLocation] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + qCDebug(lcQpaGestures) << "beginGestureWithEvent @" << windowPoint; + QWindowSystemInterface::handleGestureEvent(m_window, timestamp, Qt::BeginNativeGesture, + windowPoint, screenPoint); +} + +- (void)endGestureWithEvent:(NSEvent *)event +{ + qCDebug(lcQpaGestures) << "endGestureWithEvent"; + const NSTimeInterval timestamp = [event timestamp]; + QPointF windowPoint; + QPointF screenPoint; + [self convertFromScreen:[NSEvent mouseLocation] toWindowPoint:&windowPoint andScreenPoint:&screenPoint]; + QWindowSystemInterface::handleGestureEvent(m_window, timestamp, Qt::EndNativeGesture, + windowPoint, screenPoint); +} +#endif // QT_NO_GESTURES + +#ifndef QT_NO_WHEELEVENT +- (void)scrollWheel:(NSEvent *)theEvent +{ + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return [super scrollWheel:theEvent]; + + QPoint angleDelta; + Qt::MouseEventSource source = Qt::MouseEventNotSynthesized; + if ([theEvent hasPreciseScrollingDeltas]) { + // The mouse device contains 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 + angleDelta.setX([theEvent scrollingDeltaX] * pixelsToDegrees); + angleDelta.setY([theEvent scrollingDeltaY] * pixelsToDegrees); + source = Qt::MouseEventSynthesizedBySystem; + } else { + // Remove acceleration, and use either -120 or 120 as delta: + angleDelta.setX(qBound(-120, int([theEvent deltaX] * 10000), 120)); + angleDelta.setY(qBound(-120, int([theEvent deltaY] * 10000), 120)); + } + + QPoint pixelDelta; + if ([theEvent hasPreciseScrollingDeltas]) { + pixelDelta.setX([theEvent scrollingDeltaX]); + pixelDelta.setY([theEvent scrollingDeltaY]); + } else { + // docs: "In the case of !hasPreciseScrollingDeltas, multiply the delta with the line width." + // scrollingDeltaX seems to return a minimum value of 0.1 in this case, map that to two pixels. + const CGFloat lineWithEstimate = 20.0; + pixelDelta.setX([theEvent scrollingDeltaX] * lineWithEstimate); + pixelDelta.setY([theEvent scrollingDeltaY] * lineWithEstimate); + } + + QPointF qt_windowPoint; + QPointF qt_screenPoint; + [self convertFromScreen:[NSEvent mouseLocation] toWindowPoint:&qt_windowPoint andScreenPoint:&qt_screenPoint]; + NSTimeInterval timestamp = [theEvent timestamp]; + ulong qt_timestamp = timestamp * 1000; + + // Prevent keyboard modifier state from changing during scroll event streams. + // A two-finger trackpad flick generates a stream of scroll events. We want + // the keyboard modifier state to be the state at the beginning of the + // flick in order to avoid changing the interpretation of the events + // mid-stream. One example of this happening would be when pressing cmd + // after scrolling in Qt Creator: not taking the phase into account causes + // the end of the event stream to be interpreted as font size changes. + NSEventPhase momentumPhase = [theEvent momentumPhase]; + if (momentumPhase == NSEventPhaseNone) { + currentWheelModifiers = [QNSView convertKeyModifiers:[theEvent modifierFlags]]; + } + + NSEventPhase phase = [theEvent phase]; + Qt::ScrollPhase ph = Qt::ScrollUpdate; +#if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8 + if (QSysInfo::QSysInfo::MacintoshVersion >= QSysInfo::MV_10_8) { + // On 10.8 and above, MayBegin is likely to happen. We treat it the same as an actual begin. + if (phase == NSEventPhaseMayBegin) { + m_scrolling = true; + ph = Qt::ScrollBegin; + } + } +#endif + if (phase == NSEventPhaseBegan) { + // If MayBegin did not happen, Began is the actual beginning. + if (!m_scrolling) + ph = Qt::ScrollBegin; + m_scrolling = true; + } else if (phase == NSEventPhaseEnded || phase == NSEventPhaseCancelled || + momentumPhase == NSEventPhaseEnded || momentumPhase == NSEventPhaseCancelled) { + ph = Qt::ScrollEnd; + m_scrolling = false; + } else if (phase == NSEventPhaseNone && momentumPhase == NSEventPhaseNone) { + ph = Qt::NoScrollPhase; + if (!QGuiApplicationPrivate::scrollNoPhaseAllowed) + ph = Qt::ScrollUpdate; + } + + QWindowSystemInterface::handleWheelEvent(m_window, qt_timestamp, qt_windowPoint, qt_screenPoint, pixelDelta, angleDelta, currentWheelModifiers, ph, source); +} +#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 *)nsevent eventType:(int)eventType +{ + ulong timestamp = [nsevent timestamp] * 1000; + ulong nativeModifiers = [nsevent modifierFlags]; + Qt::KeyboardModifiers modifiers = [QNSView convertKeyModifiers: nativeModifiers]; + NSString *charactersIgnoringModifiers = [nsevent charactersIgnoringModifiers]; + NSString *characters = [nsevent characters]; + if (m_inputSource != characters) { + [m_inputSource release]; + m_inputSource = [characters retain]; + } + + // There is no way to get the scan code from carbon/cocoa. But we cannot + // use the value 0, since it indicates that the event originates from somewhere + // else than the keyboard. + quint32 nativeScanCode = 1; + quint32 nativeVirtualKey = [nsevent keyCode]; + + QChar ch = QChar::ReplacementCharacter; + int keyCode = Qt::Key_unknown; + if ([characters length] != 0) { + if (((modifiers & Qt::MetaModifier) || (modifiers & Qt::AltModifier)) && ([charactersIgnoringModifiers length] != 0)) + ch = QChar([charactersIgnoringModifiers characterAtIndex:0]); + else + ch = QChar([characters characterAtIndex:0]); + keyCode = [self convertKeyCode:ch]; + } + + // we will send a key event unless the input method sets m_sendKeyEvent to false + m_sendKeyEvent = true; + QString text; + // ignore text for the U+F700-U+F8FF range. This is used by Cocoa when + // delivering function keys (e.g. arrow keys, backspace, F1-F35, etc.) + if (!(modifiers & (Qt::ControlModifier | Qt::MetaModifier)) && (ch.unicode() < 0xf700 || ch.unicode() > 0xf8ff)) + text = QCFString::toQString(characters); + + QWindow *window = [self topLevelWindow]; + + // Popups implicitly grab key events; forward to the active popup if there is one. + // This allows popups to e.g. intercept shortcuts and close the popup in response. + if (QCocoaWindow *popup = QCocoaIntegration::instance()->activePopupWindow()) { + if (!popup->m_windowFlags.testFlag(Qt::ToolTip)) + window = popup->window(); + } + + if (eventType == QEvent::KeyPress) { + + if (m_composingText.isEmpty()) { + m_sendKeyEvent = !QWindowSystemInterface::handleShortcutEvent(window, timestamp, keyCode, + modifiers, nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1); + } + + QObject *fo = QGuiApplication::focusObject(); + if (m_sendKeyEvent && fo) { + QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImHints); + if (QCoreApplication::sendEvent(fo, &queryEvent)) { + bool imEnabled = queryEvent.value(Qt::ImEnabled).toBool(); + Qt::InputMethodHints hints = static_cast(queryEvent.value(Qt::ImHints).toUInt()); + if (imEnabled && !(hints & Qt::ImhDigitsOnly || hints & Qt::ImhFormattedNumbersOnly || hints & Qt::ImhHiddenText)) { + // pass the key event to the input method. note that m_sendKeyEvent may be set to false during this call + m_currentlyInterpretedKeyEvent = nsevent; + [self interpretKeyEvents:[NSArray arrayWithObject:nsevent]]; + m_currentlyInterpretedKeyEvent = 0; + } + } + } + if (m_resendKeyEvent) + m_sendKeyEvent = true; + } + + if (m_sendKeyEvent && m_composingText.isEmpty()) + QWindowSystemInterface::handleExtendedKeyEvent(window, timestamp, QEvent::Type(eventType), keyCode, modifiers, + nativeScanCode, nativeVirtualKey, nativeModifiers, text, [nsevent isARepeat], 1, false); + + m_sendKeyEvent = false; + m_resendKeyEvent = false; +} + +- (void)keyDown:(NSEvent *)nsevent +{ + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return [super keyDown:nsevent]; + [self handleKeyEvent:nsevent eventType:int(QEvent::KeyPress)]; +} + +- (void)keyUp:(NSEvent *)nsevent +{ + if (m_window && (m_window->flags() & Qt::WindowTransparentForInput) ) + return [super keyUp:nsevent]; + [self handleKeyEvent:nsevent eventType:int(QEvent::KeyRelease)]; +} + +- (void)cancelOperation:(id)sender +{ + Q_UNUSED(sender); + + NSEvent *currentEvent = [NSApp currentEvent]; + if (!currentEvent || currentEvent.type != NSKeyDown) + return; + + // Handling the key event may recurse back here through interpretKeyEvents + // (when IM is enabled), so we need to guard against that. + if (currentEvent == m_currentlyInterpretedKeyEvent) + return; + + // Send Command+Key_Period and Escape as normal keypresses so that + // the key sequence is delivered through Qt. That way clients can + // intercept the shortcut and override its effect. + [self handleKeyEvent:currentEvent eventType:int(QEvent::KeyPress)]; +} + +- (void)flagsChanged:(NSEvent *)nsevent +{ + ulong timestamp = [nsevent timestamp] * 1000; + ulong modifiers = [nsevent modifierFlags]; + Qt::KeyboardModifiers qmodifiers = [QNSView convertKeyModifiers:modifiers]; + + // calculate the delta and remember the current modifiers for next time + static ulong m_lastKnownModifiers; + ulong lastKnownModifiers = m_lastKnownModifiers; + ulong delta = lastKnownModifiers ^ modifiers; + m_lastKnownModifiers = modifiers; + + struct qt_mac_enum_mapper + { + ulong mac_mask; + Qt::Key qt_code; + }; + static qt_mac_enum_mapper modifier_key_symbols[] = { + { NSShiftKeyMask, Qt::Key_Shift }, + { NSControlKeyMask, Qt::Key_Meta }, + { NSCommandKeyMask, Qt::Key_Control }, + { NSAlternateKeyMask, Qt::Key_Alt }, + { NSAlphaShiftKeyMask, Qt::Key_CapsLock }, + { 0ul, Qt::Key_unknown } }; + for (int i = 0; modifier_key_symbols[i].mac_mask != 0u; ++i) { + uint mac_mask = modifier_key_symbols[i].mac_mask; + if ((delta & mac_mask) == 0u) + continue; + + QWindowSystemInterface::handleKeyEvent(m_window, + timestamp, + (lastKnownModifiers & mac_mask) ? QEvent::KeyRelease : QEvent::KeyPress, + modifier_key_symbols[i].qt_code, + qmodifiers ^ [QNSView convertKeyModifiers:mac_mask]); + } +} + +- (void) insertNewline:(id)sender +{ + Q_UNUSED(sender); + m_resendKeyEvent = true; +} + +- (void) doCommandBySelector:(SEL)aSelector +{ + [self tryToPerform:aSelector with:self]; +} + +- (void) insertText:(id)aString replacementRange:(NSRange)replacementRange +{ + Q_UNUSED(replacementRange) + + if (m_sendKeyEvent && m_composingText.isEmpty() && [aString isEqualToString:m_inputSource]) { + // don't send input method events for simple text input (let handleKeyEvent send key events instead) + return; + } + + QString commitString; + if ([aString length]) { + if ([aString isKindOfClass:[NSAttributedString class]]) { + commitString = QCFString::toQString(reinterpret_cast([aString string])); + } else { + commitString = QCFString::toQString(reinterpret_cast(aString)); + }; + } + QObject *fo = QGuiApplication::focusObject(); + if (fo) { + QInputMethodQueryEvent queryEvent(Qt::ImEnabled); + if (QCoreApplication::sendEvent(fo, &queryEvent)) { + if (queryEvent.value(Qt::ImEnabled).toBool()) { + QInputMethodEvent e; + e.setCommitString(commitString); + QCoreApplication::sendEvent(fo, &e); + // prevent handleKeyEvent from sending a key event + m_sendKeyEvent = false; + } + } + } + + m_composingText.clear(); +} + +- (void) setMarkedText:(id)aString selectedRange:(NSRange)selectedRange replacementRange:(NSRange)replacementRange +{ + Q_UNUSED(replacementRange) + QString preeditString; + + QList attrs; + attrs<([aString string])); + int composingLength = preeditString.length(); + int index = 0; + // Create attributes for individual sections of preedit text + while (index < composingLength) { + NSRange effectiveRange; + NSRange range = NSMakeRange(index, composingLength-index); + NSDictionary *attributes = [aString attributesAtIndex:index + longestEffectiveRange:&effectiveRange + inRange:range]; + NSNumber *underlineStyle = [attributes objectForKey:NSUnderlineStyleAttributeName]; + if (underlineStyle) { + QColor clr (Qt::black); + NSColor *color = [attributes objectForKey:NSUnderlineColorAttributeName]; + if (color) { + clr = qt_mac_toQColor(color); + } + QTextCharFormat format; + format.setFontUnderline(true); + format.setUnderlineColor(clr); + attrs<(aString)); + } + + if (attrs.isEmpty()) { + QTextCharFormat format; + format.setFontUnderline(true); + attrs<((CFStringRef)string); + return [[[NSAttributedString alloc] initWithString:const_cast(tmpString)] autorelease]; +} + +- (NSRange) markedRange +{ + NSRange range; + if (!m_composingText.isEmpty()) { + range.location = 0; + range.length = m_composingText.length(); + } else { + range.location = NSNotFound; + range.length = 0; + } + return range; +} + +- (NSRange) selectedRange +{ + NSRange selectedRange = {NSNotFound, 0}; + selectedRange.location = NSNotFound; + selectedRange.length = 0; + + QObject *fo = QGuiApplication::focusObject(); + if (!fo) + return selectedRange; + QInputMethodQueryEvent queryEvent(Qt::ImEnabled | Qt::ImCurrentSelection); + if (!QCoreApplication::sendEvent(fo, &queryEvent)) + return selectedRange; + if (!queryEvent.value(Qt::ImEnabled).toBool()) + return selectedRange; + + QString selectedText = queryEvent.value(Qt::ImCurrentSelection).toString(); + + if (!selectedText.isEmpty()) { + selectedRange.location = 0; + selectedRange.length = selectedText.length(); + } + return selectedRange; +} + +- (NSRect) firstRectForCharacterRange:(NSRange)aRange actualRange:(NSRangePointer)actualRange +{ + Q_UNUSED(aRange) + Q_UNUSED(actualRange) + QObject *fo = QGuiApplication::focusObject(); + if (!fo) + return NSZeroRect; + + QInputMethodQueryEvent queryEvent(Qt::ImEnabled); + if (!QCoreApplication::sendEvent(fo, &queryEvent)) + return NSZeroRect; + if (!queryEvent.value(Qt::ImEnabled).toBool()) + return NSZeroRect; + + if (!m_window) + return NSZeroRect; + + // The returned rect is always based on the internal cursor. + QRect mr = qApp->inputMethod()->cursorRectangle().toRect(); + QPoint mp = m_window->mapToGlobal(mr.bottomLeft()); + + NSRect rect; + rect.origin.x = mp.x(); + rect.origin.y = qt_mac_flipYCoordinate(mp.y()); + rect.size.width = mr.width(); + rect.size.height = mr.height(); + return rect; +} + +- (NSUInteger)characterIndexForPoint:(NSPoint)aPoint +{ + // We don't support cursor movements using mouse while composing. + Q_UNUSED(aPoint); + return NSNotFound; +} + +- (NSArray*) validAttributesForMarkedText +{ + if (m_window != QGuiApplication::focusWindow()) + return nil; + + QObject *fo = QGuiApplication::focusObject(); + if (!fo) + return nil; + + QInputMethodQueryEvent queryEvent(Qt::ImEnabled); + if (!QCoreApplication::sendEvent(fo, &queryEvent)) + return nil; + if (!queryEvent.value(Qt::ImEnabled).toBool()) + return nil; + + // Support only underline color/style. + return [NSArray arrayWithObjects:NSUnderlineColorAttributeName, + NSUnderlineStyleAttributeName, nil]; +} + +-(void)registerDragTypes +{ + QMacAutoReleasePool pool; + QStringList customTypes = qt_mac_enabledDraggedTypes(); + if (currentCustomDragTypes == 0 || *currentCustomDragTypes != customTypes) { + if (currentCustomDragTypes == 0) + currentCustomDragTypes = new QStringList(); + *currentCustomDragTypes = customTypes; + const NSString* mimeTypeGeneric = @"com.trolltech.qt.MimeTypeName"; + NSMutableArray *supportedTypes = [NSMutableArray arrayWithObjects:NSColorPboardType, + NSFilenamesPboardType, NSStringPboardType, + NSFilenamesPboardType, NSPostScriptPboardType, NSTIFFPboardType, + NSRTFPboardType, NSTabularTextPboardType, NSFontPboardType, + NSRulerPboardType, NSFileContentsPboardType, NSColorPboardType, + NSRTFDPboardType, NSHTMLPboardType, + NSURLPboardType, NSPDFPboardType, NSVCardPboardType, + NSFilesPromisePboardType, NSInkTextPboardType, + NSMultipleTextSelectionPboardType, mimeTypeGeneric, nil]; + // Add custom types supported by the application. + for (int i = 0; i < customTypes.size(); i++) { + [supportedTypes addObject:QCFString::toNSString(customTypes[i])]; + } + [self registerForDraggedTypes:supportedTypes]; + } +} + +static QWindow *findEventTargetWindow(QWindow *candidate) +{ + while (candidate) { + if (!(candidate->flags() & Qt::WindowTransparentForInput)) + return candidate; + candidate = candidate->parent(); + } + return candidate; +} + +static QPoint mapWindowCoordinates(QWindow *source, QWindow *target, QPoint point) +{ + return target->mapFromGlobal(source->mapToGlobal(point)); +} + +- (NSDragOperation)draggingSession:(NSDraggingSession *)session + sourceOperationMaskForDraggingContext:(NSDraggingContext)context +{ + Q_UNUSED(session); + Q_UNUSED(context); + QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); + return qt_mac_mapDropActions(nativeDrag->currentDrag()->supportedActions()); +} + +- (BOOL)ignoreModifierKeysForDraggingSession:(NSDraggingSession *)session +{ + Q_UNUSED(session); + // According to the "Dragging Sources" chapter on Cocoa DnD Programming + // (https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/DragandDrop/Concepts/dragsource.html), + // if the control, option, or command key is pressed, the source’s + // operation mask is filtered to only contain a reduced set of operations. + // + // Since Qt already takes care of tracking the keyboard modifiers, we + // don't need (or want) Cocoa to filter anything. Instead, we'll let + // the application do the actual filtering. + return YES; +} + +- (BOOL)wantsPeriodicDraggingUpdates +{ + // From the documentation: + // + // "If the destination returns NO, these messages are sent only when the mouse moves + // or a modifier flag changes. Otherwise the destination gets the default behavior, + // where it receives periodic dragging-updated messages even if nothing changes." + // + // We do not want these constant drag update events while mouse is stationary, + // since we do all animations (autoscroll) with timers. + return NO; +} + +- (void)updateCursorFromDragResponse:(QPlatformDragQtResponse)response drag:(QCocoaDrag *)drag +{ + const QPixmap pixmapCursor = drag->currentDrag()->dragCursor(response.acceptedAction()); + NSCursor *nativeCursor = nil; + + if (pixmapCursor.isNull()) { + switch (response.acceptedAction()) { + case Qt::CopyAction: + nativeCursor = [NSCursor dragCopyCursor]; + break; + case Qt::LinkAction: + nativeCursor = [NSCursor dragLinkCursor]; + break; + case Qt::IgnoreAction: + // Uncomment the next lines if forbiden cursor wanted on non droppable targets. + /*nativeCursor = [NSCursor operationNotAllowedCursor]; + break;*/ + case Qt::MoveAction: + default: + nativeCursor = [NSCursor arrowCursor]; + break; + } + } + else { + NSImage *nsimage = qt_mac_create_nsimage(pixmapCursor); + nativeCursor = [[NSCursor alloc] initWithImage:nsimage hotSpot:NSZeroPoint]; + [nsimage release]; + } + + // change the cursor + [nativeCursor set]; + + // Make sure the cursor is updated correctly if the mouse does not move and window is under cursor + // by creating a fake move event + if (m_updatingDrag) + return; + + const QPoint mousePos(QCursor::pos()); + CGEventRef moveEvent(CGEventCreateMouseEvent( + NULL, kCGEventMouseMoved, + CGPointMake(mousePos.x(), mousePos.y()), + kCGMouseButtonLeft // ignored + )); + CGEventPost(kCGHIDEventTap, moveEvent); + CFRelease(moveEvent); +} + +- (NSDragOperation)draggingEntered:(id )sender +{ + return [self handleDrag : sender]; +} + +- (NSDragOperation)draggingUpdated:(id )sender +{ + m_updatingDrag = true; + const NSDragOperation ret([self handleDrag : sender]); + m_updatingDrag = false; + + return ret; +} + +// Sends drag update to Qt, return the action +- (NSDragOperation)handleDrag:(id )sender +{ + NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; + QPoint qt_windowPoint(windowPoint.x, windowPoint.y); + Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]); + + QWindow *target = findEventTargetWindow(m_window); + if (!target) + return NSDragOperationNone; + + // update these so selecting move/copy/link works + QGuiApplicationPrivate::modifier_buttons = [QNSView convertKeyModifiers: [[NSApp currentEvent] modifierFlags]]; + + QPlatformDragQtResponse response(false, Qt::IgnoreAction, QRect()); + QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); + if (nativeDrag->currentDrag()) { + // The drag was started from within the application + response = QWindowSystemInterface::handleDrag(target, nativeDrag->platformDropData(), mapWindowCoordinates(m_window, target, qt_windowPoint), qtAllowed); + [self updateCursorFromDragResponse:response drag:nativeDrag]; + } else { + QCocoaDropData mimeData([sender draggingPasteboard]); + response = QWindowSystemInterface::handleDrag(target, &mimeData, mapWindowCoordinates(m_window, target, qt_windowPoint), qtAllowed); + } + + return qt_mac_mapDropAction(response.acceptedAction()); +} + +- (void)draggingExited:(id )sender +{ + QWindow *target = findEventTargetWindow(m_window); + if (!target) + return; + + NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; + QPoint qt_windowPoint(windowPoint.x, windowPoint.y); + + // Send 0 mime data to indicate drag exit + QWindowSystemInterface::handleDrag(target, 0, mapWindowCoordinates(m_window, target, qt_windowPoint), Qt::IgnoreAction); +} + +// called on drop, send the drop to Qt and return if it was accepted. +- (BOOL)performDragOperation:(id )sender +{ + QWindow *target = findEventTargetWindow(m_window); + if (!target) + return false; + + NSPoint windowPoint = [self convertPoint: [sender draggingLocation] fromView: nil]; + QPoint qt_windowPoint(windowPoint.x, windowPoint.y); + Qt::DropActions qtAllowed = qt_mac_mapNSDragOperations([sender draggingSourceOperationMask]); + + QPlatformDropQtResponse response(false, Qt::IgnoreAction); + QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); + if (nativeDrag->currentDrag()) { + // The drag was started from within the application + response = QWindowSystemInterface::handleDrop(target, nativeDrag->platformDropData(), mapWindowCoordinates(m_window, target, qt_windowPoint), qtAllowed); + } else { + QCocoaDropData mimeData([sender draggingPasteboard]); + response = QWindowSystemInterface::handleDrop(target, &mimeData, mapWindowCoordinates(m_window, target, qt_windowPoint), qtAllowed); + } + if (response.isAccepted()) { + QCocoaDrag* nativeDrag = QCocoaIntegration::instance()->drag(); + nativeDrag->setAcceptedAction(response.acceptedAction()); + } + return response.isAccepted(); +} + +- (void)draggingSession:(NSDraggingSession *)session + endedAtPoint:(NSPoint)screenPoint + operation:(NSDragOperation)operation +{ + Q_UNUSED(session); + Q_UNUSED(operation); + QWindow *target = findEventTargetWindow(m_window); + if (!target) + return; + + // keep our state, and QGuiApplication state (buttons member) in-sync, + // or future mouse events will be processed incorrectly + NSUInteger pmb = [NSEvent pressedMouseButtons]; + for (int buttonNumber = 0; buttonNumber < 32; buttonNumber++) { // see cocoaButton2QtButton() for the 32 value + if (!(pmb & (1 << buttonNumber))) + m_buttons &= ~cocoaButton2QtButton(buttonNumber); + } + + NSPoint windowPoint = [self.window convertRectFromScreen:NSMakeRect(screenPoint.x, screenPoint.y, 1, 1)].origin; + QPoint qtWindowPoint(windowPoint.x, windowPoint.y); + + QPoint qtScreenPoint = QPoint(screenPoint.x, qt_mac_flipYCoordinate(screenPoint.y)); + + QWindowSystemInterface::handleMouseEvent(target, mapWindowCoordinates(m_window, target, qtWindowPoint), qtScreenPoint, m_buttons); +} + +@end