src/gui/kernel/qt_cocoa_helpers_mac.mm
changeset 30 5dc02b23752f
parent 23 89e065397ea6
child 33 3e2da88830cd
--- a/src/gui/kernel/qt_cocoa_helpers_mac.mm	Wed Jun 23 19:07:03 2010 +0300
+++ b/src/gui/kernel/qt_cocoa_helpers_mac.mm	Tue Jul 06 15:10:48 2010 +0300
@@ -83,6 +83,7 @@
 #include <private/qt_cocoa_helpers_mac_p.h>
 #include <private/qt_mac_p.h>
 #include <private/qapplication_p.h>
+#include <private/qcocoaapplication_mac_p.h>
 #include <private/qcocoawindow_mac_p.h>
 #include <private/qcocoaview_mac_p.h>
 #include <private/qkeymapper_p.h>
@@ -137,9 +138,8 @@
 }
 
 extern bool qt_sendSpontaneousEvent(QObject *receiver, QEvent *event); // qapplication.cpp;
-extern Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum); // qcocoaview.mm
 extern QWidget * mac_mouse_grabber;
-extern QPointer<QWidget> qt_button_down; //qapplication_mac.cpp
+extern QWidget *qt_button_down; //qapplication_mac.cpp
 
 void macWindowFade(void * /*OSWindowRef*/ window, float durationSeconds)
 {
@@ -369,6 +369,16 @@
 }
 
 #ifdef QT_MAC_USE_COCOA
+
+// Clears the QWidget pointer that each QCocoaView holds.
+void qt_mac_clearCocoaViewQWidgetPointers(QWidget *widget)
+{
+    QT_MANGLE_NAMESPACE(QCocoaView) *cocoaView = reinterpret_cast<QT_MANGLE_NAMESPACE(QCocoaView) *>(qt_mac_nativeview_for(widget));
+    if (cocoaView && [cocoaView respondsToSelector:@selector(qt_qwidget)]) {
+        [cocoaView qt_clearQWidget];
+    }
+}
+
 void qt_dispatchTabletProximityEvent(void * /*NSEvent * */ tabletEvent)
 {
     NSEvent *proximityEvent = static_cast<NSEvent *>(tabletEvent);
@@ -642,11 +652,25 @@
     UInt32 macScanCode = 1;
     QKeyEventEx ke(cocoaEvent2QtEvent([event type]), qtKey, keyMods, text, [event isARepeat], qMax(1, keyLength),
                    macScanCode, [event keyCode], [event modifierFlags]);
-    qt_sendSpontaneousEvent(widgetToGetEvent, &ke);
-    return ke.isAccepted();
+    return qt_sendSpontaneousEvent(widgetToGetEvent, &ke) && ke.isAccepted();
 }
 #endif
 
+Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum)
+{
+    if (buttonNum == 0)
+        return Qt::LeftButton;
+    if (buttonNum == 1)
+        return Qt::RightButton;
+    if (buttonNum == 2)
+        return Qt::MidButton;
+    if (buttonNum == 3)
+        return Qt::XButton1;
+    if (buttonNum == 4)
+        return Qt::XButton2;
+    return Qt::NoButton;
+}
+
 // Helper to share code between QCocoaWindow and QCocoaView
 bool qt_dispatchKeyEvent(void * /*NSEvent * */ keyEvent, QWidget *widgetToGetEvent)
 {
@@ -659,8 +683,16 @@
     EventRef key_event = static_cast<EventRef>(const_cast<void *>([event eventRef]));
     Q_ASSERT(key_event);
     if ([event type] == NSKeyDown) {
-        qt_keymapper_private()->updateKeyMap(0, key_event, 0);
+        NSString *characters = [event characters];
+        unichar value = [characters characterAtIndex:0];
+        qt_keymapper_private()->updateKeyMap(0, key_event, (void *)&value);
     }
+
+    // Redirect keys to alien widgets.
+    if (widgetToGetEvent->testAttribute(Qt::WA_NativeWindow) == false) {
+        widgetToGetEvent = qApp->focusWidget();
+    }
+
     if (widgetToGetEvent == 0)
         return false;
 
@@ -670,8 +702,8 @@
     if (mustUseCocoaKeyEvent())
         return qt_dispatchKeyEventWithCocoa(keyEvent, widgetToGetEvent);
     bool isAccepted;
-    qt_keymapper_private()->translateKeyEvent(widgetToGetEvent, 0, key_event, &isAccepted, true);
-    return isAccepted;
+    bool consumed = qt_keymapper_private()->translateKeyEvent(widgetToGetEvent, 0, key_event, &isAccepted, true);
+    return consumed && isAccepted;
 #endif
 }
 
@@ -850,7 +882,15 @@
         }
     }
 
-    QMouseEvent qme(eventType, qlocalPoint, qglobalPoint, button, button, keyMods);
+    Qt::MouseButtons buttons = 0;
+    {
+        UInt32 mac_buttons;
+        if (GetEventParameter((EventRef)[event eventRef], kEventParamMouseChord, typeUInt32, 0,
+                              sizeof(mac_buttons), 0, &mac_buttons) == noErr)
+            buttons = qt_mac_get_buttons(mac_buttons);
+    }
+
+    QMouseEvent qme(eventType, qlocalPoint, qglobalPoint, button, buttons, keyMods);
     qt_sendSpontaneousEvent(widgetToGetEvent, &qme);
 
     // We don't need to set the implicit grab widget here because we won't
@@ -915,7 +955,7 @@
                 [static_cast<QT_MANGLE_NAMESPACE(QCocoaView) *>(tmpView) qt_qwidget];
         }
     } else {
-        extern QPointer<QWidget> qt_button_down; //qapplication_mac.cpp
+        extern QWidget * qt_button_down; //qapplication_mac.cpp
         QPoint pos;
         widgetToGetMouse = QApplicationPrivate::pickMouseReceiver(qwidget, qglobalPoint,
                                                                   pos, eventType,
@@ -927,7 +967,20 @@
         return false;
 
     NSPoint localPoint = [tmpView convertPoint:windowPoint fromView:nil];
-    QPoint qlocalPoint(localPoint.x, localPoint.y);
+    QPoint qlocalPoint = QPoint(localPoint.x, localPoint.y);
+
+    // Search for alien child widgets (either on this qwidget or on the popup)
+    if (widgetToGetMouse->testAttribute(Qt::WA_NativeWindow) == false || qt_widget_private(widgetToGetMouse)->hasAlienChildren) {
+        QPoint qScreenPoint = flipPoint(globalPoint).toPoint();
+#ifdef ALIEN_DEBUG
+        qDebug() << "alien mouse event" << qScreenPoint << possibleAlien;
+#endif
+        QWidget *possibleAlien =  widgetToGetMouse->childAt(qlocalPoint);
+        if (possibleAlien) {
+            qlocalPoint = possibleAlien->mapFromGlobal(widgetToGetMouse->mapToGlobal(qlocalPoint));
+            widgetToGetMouse = possibleAlien;
+        }
+    }
 
     EventRef carbonEvent = static_cast<EventRef>(const_cast<void *>([theEvent eventRef]));
     if (qt_mac_sendMacEventToWidget(widgetToGetMouse, carbonEvent))
@@ -955,7 +1008,7 @@
 #ifndef QT_NAMESPACE
         Q_ASSERT(clickCount > 0);
 #endif
-        if (clickCount % 2 == 0)
+        if (clickCount % 2 == 0 && buttons == button)
             eventType = QEvent::MouseButtonDblClick;
         if (button == Qt::LeftButton && (keyMods & Qt::MetaModifier)) {
             button = Qt::RightButton;
@@ -967,11 +1020,24 @@
             button = Qt::RightButton;
             [theView qt_setLeftButtonIsRightButton: false];
         }
+        qt_button_down = 0;
         break;
     }
     [QT_MANGLE_NAMESPACE(QCocoaView) currentMouseEvent]->localPoint = localPoint;
     QMouseEvent qme(eventType, qlocalPoint, qglobalPoint, button, buttons, keyMods);
-    qt_sendSpontaneousEvent(widgetToGetMouse, &qme);
+
+#ifdef ALIEN_DEBUG
+    qDebug() << "sending mouse event to" << widgetToGetMouse;
+#endif
+    extern QWidget *qt_button_down;
+    extern QPointer<QWidget> qt_last_mouse_receiver;
+
+    if (qwidget->testAttribute(Qt::WA_NativeWindow) && qt_widget_private(qwidget)->hasAlienChildren == false)
+        qt_sendSpontaneousEvent(widgetToGetMouse, &qme);
+    else
+        QApplicationPrivate::sendMouseEvent(widgetToGetMouse, &qme, widgetToGetMouse, qwidget, &qt_button_down,
+                                            qt_last_mouse_receiver);
+
     if (eventType == QEvent::MouseButtonPress && button == Qt::RightButton) {
         QContextMenuEvent qcme(QContextMenuEvent::Mouse, qlocalPoint, qglobalPoint, keyMods);
         qt_sendSpontaneousEvent(widgetToGetMouse, &qcme);
@@ -1097,15 +1163,81 @@
 #endif
 }
 
+#if QT_MAC_USE_COCOA
+void qt_mac_replaceDrawRect(void * /*OSWindowRef */window, QWidgetPrivate *widget)
+{
+    QMacCocoaAutoReleasePool pool;
+    OSWindowRef theWindow = static_cast<OSWindowRef>(window);
+    if(!theWindow)
+        return;
+    id theClass = [[[theWindow contentView] superview] class];
+    // What we do here is basically to add a new selector to NSThemeFrame called
+    // "drawRectOriginal:" which will contain the original implementation of
+    // "drawRect:". After that we get the new implementation from QCocoaWindow
+    // and exchange them. The new implementation is called drawRectSpecial.
+    // We cannot just add the method because it might have been added before and since
+    // we cannot remove a method once it has been added we need to ask QCocoaWindow if
+    // we did the swap or not.
+    if(!widget->drawRectOriginalAdded) {
+        Method m2 = class_getInstanceMethod(theClass, @selector(drawRect:));
+        if(!m2) {
+            // This case is pretty extreme, no drawRect means no drawing!
+            return;
+        }
+        class_addMethod(theClass, @selector(drawRectOriginal:), method_getImplementation(m2), method_getTypeEncoding(m2));
+        widget->drawRectOriginalAdded = true;
+    }
+    if(widget->originalDrawMethod) {
+        Method m0 = class_getInstanceMethod([theWindow class], @selector(drawRectSpecial:));
+        if(!m0) {
+            // Ok, this means the methods were never swapped. Just ignore
+            return;
+        }
+        Method m1 = class_getInstanceMethod(theClass, @selector(drawRect:));
+        if(!m1) {
+            // Ok, this means the methods were never swapped. Just ignore
+            return;
+        }
+        // We have the original method here. Proceed and swap the methods.
+        method_exchangeImplementations(m1, m0);
+        widget->originalDrawMethod = false;
+        [window display];
+    }
+}
+
+void qt_mac_replaceDrawRectOriginal(void * /*OSWindowRef */window, QWidgetPrivate *widget)
+{
+    QMacCocoaAutoReleasePool pool;
+    OSWindowRef theWindow = static_cast<OSWindowRef>(window);
+    id theClass = [[[theWindow contentView] superview] class];
+    // Now we need to revert the methods to their original state.
+    // We cannot remove the method, so we just keep track of it in QCocoaWindow.
+    Method m0 = class_getInstanceMethod([theWindow class], @selector(drawRectSpecial:));
+    if(!m0) {
+        // Ok, this means the methods were never swapped. Just ignore
+        return;
+    }
+    Method m1 = class_getInstanceMethod(theClass, @selector(drawRect:));
+    if(!m1) {
+        // Ok, this means the methods were never swapped. Just ignore
+        return;
+    }
+    method_exchangeImplementations(m1, m0);
+    widget->originalDrawMethod = true;
+    [window display];
+}
+#endif // QT_MAC_USE_COCOA
+
 void qt_mac_showBaseLineSeparator(void * /*OSWindowRef */window, bool show)
 {
+    if(!window)
+        return;
 #if QT_MAC_USE_COCOA
     QMacCocoaAutoReleasePool pool;
     OSWindowRef theWindow = static_cast<OSWindowRef>(window);
     NSToolbar *macToolbar = [theWindow toolbar];
-    if (macToolbar)
-        [macToolbar setShowsBaselineSeparator: show];
-#endif
+    [macToolbar setShowsBaselineSeparator:show];
+#endif // QT_MAC_USE_COCOA
 }
 
 QStringList qt_mac_NSArrayToQStringList(void *nsarray)
@@ -1290,6 +1422,41 @@
     QMacCocoaAutoReleasePool pool;
     [static_cast<NSCursor *>(qt_mac_nsCursorForQCursor(cursor)) set];
 }
+
+//  WARNING: If Qt did not create NSApplication (e.g. in case it is
+//  used as a plugin), and at the same time, there is no window on
+//  screen (or the window that the event is sendt to becomes hidden etc
+//  before the event gets delivered), the message will not be performed.
+bool qt_cocoaPostMessage(id target, SEL selector)
+{
+    if (!target)
+        return false;
+
+    NSInteger windowNumber = 0;
+    if (![NSApp isMemberOfClass:[QNSApplication class]]) {
+        // INVARIANT: Cocoa is not using our NSApplication subclass. That means
+        // we don't control the main event handler either. So target the event
+        // for one of the windows on screen:
+        NSWindow *nswin = [NSApp mainWindow];
+        if (!nswin) {
+            nswin = [NSApp keyWindow];
+            if (!nswin)
+                return false;
+        }
+        windowNumber = [nswin windowNumber];
+    }
+
+    // WARNING: data1 and data2 is truncated to from 64-bit to 32-bit on OS 10.5! 
+    // That is why we need to split the address in two parts:
+    QCocoaPostMessageArgs *args = new QCocoaPostMessageArgs(target, selector);
+    quint32 lower = quintptr(args);
+    quint32 upper = quintptr(args) >> 32;
+    NSEvent *e = [NSEvent otherEventWithType:NSApplicationDefined
+        location:NSZeroPoint modifierFlags:0 timestamp:0 windowNumber:windowNumber
+        context:nil subtype:QtCocoaEventSubTypePostMessage data1:lower data2:upper];
+    [NSApp postEvent:e atStart:NO];
+    return true;
+}
 #endif
 
 QMacCocoaAutoReleasePool::QMacCocoaAutoReleasePool()
@@ -1305,4 +1472,60 @@
     [(NSAutoreleasePool*)pool release];
 }
 
+void qt_mac_post_retranslateAppMenu()
+{
+#ifdef QT_MAC_USE_COCOA
+    QMacCocoaAutoReleasePool pool;
+    qt_cocoaPostMessage([NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)], @selector(qtTranslateApplicationMenu));
+#endif
+}
+
+#ifdef QT_MAC_USE_COCOA
+// This method implements the magic for the drawRectSpecial method.
+// We draw a line at the upper edge of the content view in order to
+// override the title baseline.
+void macDrawRectOnTop(void * /*OSWindowRef */window)
+{
+    OSWindowRef theWindow = static_cast<OSWindowRef>(window);
+    NSView *contentView = [theWindow contentView];
+    if(!contentView)
+        return;
+    // Get coordinates of the content view
+    NSRect contentRect = [contentView frame];
+    // Draw a line on top of the already drawn line.
+    // We need to check if we are active or not to use the proper color.
+    if([window isKeyWindow] || [window isMainWindow]) {
+        [[NSColor colorWithCalibratedRed:1.0 green:1.0 blue:1.0 alpha:1.0] set];
+    } else {
+        [[NSColor colorWithCalibratedRed:1.0 green:1.0 blue:1.0 alpha:1.0] set];
+    }
+    NSPoint origin = NSMakePoint(0, contentRect.size.height);
+    NSPoint end = NSMakePoint(contentRect.size.width, contentRect.size.height);
+    [NSBezierPath strokeLineFromPoint:origin toPoint:end];
+}
+
+// This method will (or at least should) get called only once.
+// Its mission is to find out if we are active or not. If we are active
+// we assume that we were launched via finder, otherwise we assume
+// we were called from the command line. The distinction is important,
+// since in the first case we don't need to trigger a paintEvent, while
+// in the second case we do.
+void macSyncDrawingOnFirstInvocation(void * /*OSWindowRef */window)
+{
+    OSWindowRef theWindow = static_cast<OSWindowRef>(window);
+    NSApplication *application = [NSApplication sharedApplication];
+    NSToolbar *toolbar = [window toolbar];
+    if([application isActive]) {
+        // Launched from finder
+        [toolbar setShowsBaselineSeparator:NO];
+    } else {
+        // Launched from commandline
+        [toolbar setVisible:false];
+        [toolbar setShowsBaselineSeparator:NO];
+        [toolbar setVisible:true];
+        [theWindow display];
+    }
+}
+#endif // QT_MAC_USE_COCOA
+
 QT_END_NAMESPACE