src/gui/kernel/qcocoasharedwindowmethods_mac_p.h
changeset 30 5dc02b23752f
parent 18 2f34d5167611
child 33 3e2da88830cd
--- a/src/gui/kernel/qcocoasharedwindowmethods_mac_p.h	Wed Jun 23 19:07:03 2010 +0300
+++ b/src/gui/kernel/qcocoasharedwindowmethods_mac_p.h	Tue Jul 06 15:10:48 2010 +0300
@@ -57,8 +57,31 @@
 QT_BEGIN_NAMESPACE
 extern Qt::MouseButton cocoaButton2QtButton(NSInteger buttonNum); // qcocoaview.mm
 extern QPointer<QWidget> qt_button_down; //qapplication_mac.cpp
+extern const QStringList& qEnabledDraggedTypes(); // qmime_mac.cpp
+
+Q_GLOBAL_STATIC(QPointer<QWidget>, currentDragTarget);
+
 QT_END_NAMESPACE
 
+- (id)initWithContentRect:(NSRect)contentRect
+    styleMask:(NSUInteger)windowStyle
+    backing:(NSBackingStoreType)bufferingType
+    defer:(BOOL)deferCreation
+{
+    self = [super initWithContentRect:contentRect styleMask:windowStyle
+        backing:bufferingType defer:deferCreation];
+    if (self) {
+        currentCustomDragTypes = 0;
+    }
+    return self;
+}
+
+- (void)dealloc
+{
+    delete currentCustomDragTypes;
+    [super dealloc];
+}
+
 - (BOOL)canBecomeKeyWindow
 {
     QWidget *widget = [self QT_MANGLE_NAMESPACE(qt_qwidget)];
@@ -68,32 +91,33 @@
     return !(isPopup || isToolTip);
 }
 
+- (BOOL)canBecomeMainWindow
+{
+    QWidget *widget = [self QT_MANGLE_NAMESPACE(qt_qwidget)];
+
+    bool isToolTip = (widget->windowType() == Qt::ToolTip);
+    bool isPopup = (widget->windowType() == Qt::Popup);
+    bool isTool = (widget->windowType() == Qt::Tool);
+    return !(isPopup || isToolTip || isTool);
+}
+
+- (void)becomeMainWindow
+{
+    [super becomeMainWindow];
+    // Cocoa sometimes tell a hidden window to become the
+    // main window (and as such, show it). This can e.g
+    // happend when the application gets activated. If
+    // this is the case, we tell it to hide again:
+    if (![self isVisible])
+        [self orderOut:self];
+}
+
 - (void)toggleToolbarShown:(id)sender
 {
     macSendToolbarChangeEvent([self QT_MANGLE_NAMESPACE(qt_qwidget)]);
     [super toggleToolbarShown:sender];
 }
 
-/*
-    The methods keyDown, keyUp, and flagsChanged... These really shouldn't ever
-    get hit. We automatically say we can be first responder if we are a window.
-    So, the handling should get handled by the view. This is here more as a
-    last resort (i.e., this is code that can potentially be removed).
- */
-- (void)keyDown:(NSEvent *)theEvent
-{
-    bool keyOK = qt_dispatchKeyEvent(theEvent, [self QT_MANGLE_NAMESPACE(qt_qwidget)]);
-    if (!keyOK)
-        [super keyDown:theEvent];
-}
-
-- (void)keyUp:(NSEvent *)theEvent
-{
-    bool keyOK = qt_dispatchKeyEvent(theEvent, [self QT_MANGLE_NAMESPACE(qt_qwidget)]);
-    if (!keyOK)
-        [super keyUp:theEvent];
-}
-
 - (void)flagsChanged:(NSEvent *)theEvent
 {
     qt_dispatchModifiersChanged(theEvent, [self QT_MANGLE_NAMESPACE(qt_qwidget)]);
@@ -106,10 +130,37 @@
     qt_dispatchTabletProximityEvent(tabletEvent);
 }
 
+- (void)qtDispatcherToQAction:(id)sender
+{
+    // If this window is modal, the menu bar will be modally shaddowed.
+    // In that case, since the window will be in the first responder chain,
+    // we can still catch the trigger here and forward it to the menu bar.
+    // This is needed as a single modal dialog on Qt should be able to access
+    // the application menu (e.g. quit).
+    [[NSApp QT_MANGLE_NAMESPACE(qt_qcocoamenuLoader)] qtDispatcherToQAction:sender];
+}
+
+- (void)terminate:(id)sender
+{
+    // This function is called from the quit item in the menubar when this window
+    // is in the first responder chain (see also qtDispatcherToQAction above)
+    [NSApp terminate:sender];
+}
+
 - (void)sendEvent:(NSEvent *)event
 {
+    if ([event type] == NSApplicationDefined) {
+        switch ([event subtype]) {
+            case QtCocoaEventSubTypePostMessage:
+                [NSApp qt_sendPostedMessage:event];
+                return;
+            default:
+                break;
+        }
+        return;
+    }
+
     QWidget *widget = [[QT_MANGLE_NAMESPACE(QCocoaWindowDelegate) sharedDelegate] qt_qwidgetForWindow:self];
-
     // Cocoa can hold onto the window after we've disavowed its knowledge. So,
     // if we get sent an event afterwards just have it go through the super's
     // version and don't do any stuff with Qt.
@@ -133,7 +184,7 @@
                 qt_button_down = widget;
             handled = qt_mac_handleMouseEvent(view, event, QEvent::MouseButtonPress, mouseButton);
             // Don't call super here. This prevents us from getting the mouseUp event,
-            // which we need to send even if the mouseDown event was not accepted. 
+            // which we need to send even if the mouseDown event was not accepted.
             // (this is standard Qt behavior.)
             break;
         case NSRightMouseDown:
@@ -188,6 +239,115 @@
     return [super frameViewClassForStyleMask:styleMask];
 }
 
+-(void)registerDragTypes
+{
+    // Calling registerForDraggedTypes below is slow, so only do
+    // it once for each window, or when the custom types change.
+    QMacCocoaAutoReleasePool pool;
+    const QStringList& customTypes = qEnabledDraggedTypes();
+    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, NSPICTPboardType,
+                       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:reinterpret_cast<const NSString *>(QCFString::toCFStringRef(customTypes[i]))];
+        }
+        [self registerForDraggedTypes:supportedTypes];
+    }
+}
+
+- (QWidget *)dragTargetHitTest:(id <NSDraggingInfo>)sender
+{
+    // Do a hittest to find the NSView under the
+    // mouse, and return the corresponding QWidget:
+    NSPoint windowPoint = [sender draggingLocation];
+    NSView *candidateView = [[self contentView] hitTest:windowPoint];
+    if (![candidateView isKindOfClass:[QT_MANGLE_NAMESPACE(QCocoaView) class]])
+        return 0;
+    return [static_cast<QT_MANGLE_NAMESPACE(QCocoaView) *>(candidateView) qt_qwidget];
+}
+
+- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
+{
+    // The user dragged something into the window. Send a draggingEntered message
+    // to the QWidget under the mouse. As the drag moves over the window, and over
+    // different widgets, we will handle enter and leave events from within
+    // draggingUpdated below. The reason why we handle this ourselves rather than
+    // subscribing for drag events directly in QCocoaView is that calling
+    // registerForDraggedTypes on the views will severly degrade initialization time
+    // for an application that uses a lot of drag subscribing widgets.
+
+    QWidget *target = [self dragTargetHitTest:sender];
+    if (!target)
+        return [super draggingEntered:sender];
+    if (target->testAttribute(Qt::WA_DropSiteRegistered) == false)
+        return NSDragOperationNone;
+
+    *currentDragTarget() = target;
+    return [reinterpret_cast<NSView *>((*currentDragTarget())->winId()) draggingEntered:sender];
+ }
+
+- (NSDragOperation)draggingUpdated:(id < NSDraggingInfo >)sender
+{
+    QWidget *target = [self dragTargetHitTest:sender];
+    if (!target)
+        return [super draggingUpdated:sender];
+
+    if (target == *currentDragTarget()) {
+        // The drag continues to move over the widget that we have sendt
+        // a draggingEntered message to. So just update the view:
+        return [reinterpret_cast<NSView *>((*currentDragTarget())->winId()) draggingUpdated:sender];
+    } else {
+        // The widget under the mouse has changed.
+        // So we need to fake enter/leave events:
+        if (*currentDragTarget())
+            [reinterpret_cast<NSView *>((*currentDragTarget())->winId()) draggingExited:sender];
+        if (target->testAttribute(Qt::WA_DropSiteRegistered) == false) {
+            *currentDragTarget() = 0;
+            return NSDragOperationNone;
+        }
+        *currentDragTarget() = target;
+        return [reinterpret_cast<NSView *>((*currentDragTarget())->winId()) draggingEntered:sender];
+    }
+}
+
+- (void)draggingExited:(id < NSDraggingInfo >)sender
+{
+    QWidget *target = [self dragTargetHitTest:sender];
+    if (!target)
+        return [super draggingExited:sender];
+
+    if (*currentDragTarget()) {
+        [reinterpret_cast<NSView *>((*currentDragTarget())->winId()) draggingExited:sender];
+        *currentDragTarget() = 0;
+    }
+}
+
+- (BOOL)performDragOperation:(id < NSDraggingInfo >)sender
+{
+    QWidget *target = [self dragTargetHitTest:sender];
+    if (!target)
+        return [super performDragOperation:sender];
+
+    BOOL dropResult = NO;
+    if (*currentDragTarget()) {
+        dropResult = [reinterpret_cast<NSView *>((*currentDragTarget())->winId()) performDragOperation:sender];
+        *currentDragTarget() = 0;
+    }
+    return dropResult;
+}
+
 - (void)displayIfNeeded
 {
 
@@ -204,4 +364,56 @@
     [super displayIfNeeded];
 }
 
+// This is a hack and it should be removed once we find the real cause for
+// the painting problems.
+// We have a static variable that signals if we have been called before or not.
+static bool firstDrawingInvocation = true;
 
+// The method below exists only as a workaround to draw/not draw the baseline
+// in the title bar. This is to support unifiedToolbar look.
+
+// This method is very special. To begin with, it is a
+// method that will get called only if we enable documentMode.
+// Furthermore, it won't get called as a normal method, we swap
+// this method with the normal implementation of drawRect in
+// _NSThemeFrame. When this method is active, its mission is to
+// first call the original drawRect implementation so the widget
+// gets proper painting. After that, it needs to detect if there
+// is a toolbar or not, in order to decide how to handle the unified
+// look. The distinction is important since the presence and
+// visibility of a toolbar change the way we enter into unified mode.
+// When there is a toolbar and that toolbar is visible, the problem
+// is as simple as to tell the toolbar not to draw its baseline.
+// However when there is not toolbar or the toolbar is not visible,
+// we need to draw a line on top of the baseline, because the baseline
+// in that case will belong to the title. For this case we need to draw
+// a line on top of the baseline.
+// As usual, there is a special case. When we first are called, we might
+// need to repaint ourselves one more time. We only need that if we
+// didn't get the activation, i.e. when we are launched via the command
+// line. And this only if the toolbar is visible from the beginning,
+// so we have a special flag that signals if we need to repaint or not.
+- (void)drawRectSpecial:(NSRect)rect
+{
+    // Call the original drawing method.
+    [self drawRectOriginal:rect];
+    NSWindow *window = [self window];
+    NSToolbar *toolbar = [window toolbar];
+    if(!toolbar) {
+        // There is no toolbar, we have to draw a line on top of the line drawn by Cocoa.
+        macDrawRectOnTop((void *)window);
+    } else {
+        if([toolbar isVisible]) {
+            // We tell Cocoa to avoid drawing the line at the end.
+            if(firstDrawingInvocation) {
+                firstDrawingInvocation = false;
+                macSyncDrawingOnFirstInvocation((void *)window);
+            } else
+                [toolbar setShowsBaselineSeparator:NO];
+        } else {
+            // There is a toolbar but it is not visible so
+            // we have to draw a line on top of the line drawn by Cocoa.
+            macDrawRectOnTop((void *)window);
+        }
+    }
+}