src/gui/dialogs/qfontdialog_mac.mm
changeset 30 5dc02b23752f
parent 18 2f34d5167611
child 37 758a864f9613
--- a/src/gui/dialogs/qfontdialog_mac.mm	Wed Jun 23 19:07:03 2010 +0300
+++ b/src/gui/dialogs/qfontdialog_mac.mm	Tue Jul 06 15:10:48 2010 +0300
@@ -58,6 +58,14 @@
 typedef float CGFloat;  // Should only not be defined on 32-bit platforms
 #endif
 
+QT_BEGIN_NAMESPACE
+
+extern void macStartInterceptNSPanelCtor();
+extern void macStopInterceptNSPanelCtor();
+extern NSButton *macCreateButton(const char *text, NSView *superview);
+extern bool qt_mac_is_macsheet(const QWidget *w); // qwidget_mac.mm
+
+QT_END_NAMESPACE
 QT_USE_NAMESPACE
 
 // should a priori be kept in sync with qcolordialog_mac.mm
@@ -74,7 +82,7 @@
 
 const int StyleMask = NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask;
 
-@class QCocoaFontPanelDelegate;
+@class QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate);
 
 
 #if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5
@@ -85,7 +93,7 @@
 
 #endif
 
-@interface QCocoaFontPanelDelegate : NSObject <NSWindowDelegate> {
+@interface QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) : NSObject <NSWindowDelegate> {
     NSFontPanel *mFontPanel;
     NSView *mStolenContentView;
     NSButton *mOkButton;
@@ -95,7 +103,8 @@
     BOOL mPanelHackedWithButtons;
     CGFloat mDialogExtraWidth;
     CGFloat mDialogExtraHeight;
-    NSModalSession mModalSession;
+    int mReturnCode;
+    BOOL mAppModal;
 }
 - (id)initWithFontPanel:(NSFontPanel *)panel
       stolenContentView:(NSView *)stolenContentView
@@ -104,9 +113,11 @@
                    priv:(QFontDialogPrivate *)priv
              extraWidth:(CGFloat)extraWidth
             extraHeight:(CGFloat)extraHeight;
+- (void)showModelessPanel;
+- (void)showWindowModalSheet:(QWidget *)docWidget;
+- (void)runApplicationModalPanel;
 - (void)changeFont:(id)sender;
 - (void)changeAttributes:(id)sender;
-- (void)setModalSession:(NSModalSession)session;
 - (BOOL)windowShouldClose:(id)window;
 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize;
 - (void)relayout;
@@ -145,7 +156,7 @@
     return newFont;
 }
 
-@implementation QCocoaFontPanelDelegate
+@implementation QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate)
 - (id)initWithFontPanel:(NSFontPanel *)panel
        stolenContentView:(NSView *)stolenContentView
                 okButton:(NSButton *)okButton
@@ -163,7 +174,8 @@
     mPanelHackedWithButtons = (okButton != 0);
     mDialogExtraWidth = extraWidth;
     mDialogExtraHeight = extraHeight;
-    mModalSession = 0;
+    mReturnCode = -1;
+    mAppModal = false;
 
     if (mPanelHackedWithButtons) {
         [self relayout];
@@ -174,6 +186,20 @@
         [cancelButton setAction:@selector(onCancelClicked)];
         [cancelButton setTarget:self];
     }
+
+#ifdef QT_MAC_USE_COCOA
+    // Stack the native dialog in front of its parent, if any:
+    QFontDialog *q = mPriv->fontDialog();
+    if (!qt_mac_is_macsheet(q)) {
+        if (QWidget *parent = q->parentWidget()) {
+            if (parent->isWindow()) {
+                [qt_mac_window_for(parent)
+                    addChildWindow:[mStolenContentView window] ordered:NSWindowAbove];
+            }
+        }
+    }
+#endif
+
     mQtFont = new QFont();
     return self;
 }
@@ -184,6 +210,50 @@
     [super dealloc];
 }
 
+- (void)showModelessPanel
+{
+    mAppModal = false;
+    NSWindow *ourPanel = [mStolenContentView window];
+    [ourPanel makeKeyAndOrderFront:self];
+}
+
+- (void)runApplicationModalPanel
+{
+    QBoolBlocker nativeDialogOnTop(QApplicationPrivate::native_modal_dialog_active);
+    mAppModal = true;
+    NSWindow *ourPanel = [mStolenContentView window];
+    [NSApp runModalForWindow:ourPanel];
+    QAbstractEventDispatcher::instance()->interrupt();
+
+    if (mReturnCode == NSOKButton)
+        mPriv->fontDialog()->accept();
+    else
+        mPriv->fontDialog()->reject();
+}
+
+- (void)showWindowModalSheet:(QWidget *)docWidget
+{
+#ifdef QT_MAC_USE_COCOA
+    NSWindow *window = qt_mac_window_for(docWidget);
+#else
+    WindowRef hiwindowRef = qt_mac_window_for(docWidget);
+    NSWindow *window = [[NSWindow alloc] initWithWindowRef:hiwindowRef];
+    CFRetain(hiwindowRef);
+#endif
+
+    mAppModal = false;
+    NSWindow *ourPanel = [mStolenContentView window];
+    [NSApp beginSheet:ourPanel
+        modalForWindow:window
+        modalDelegate:0
+        didEndSelector:0
+        contextInfo:0 ];
+
+#ifndef QT_MAC_USE_COCOA
+    CFRelease(hiwindowRef);
+#endif
+}
+
 - (void)changeFont:(id)sender
 {
     NSFont *dummyFont = [NSFont userFontOfSize:12.0];
@@ -216,12 +286,6 @@
         mPriv->updateSampleFont(*mQtFont);
 }
 
-- (void)setModalSession:(NSModalSession)session
-{
-    Q_ASSERT(!mModalSession);
-    mModalSession = session;
-}
-
 - (BOOL)windowShouldClose:(id)window
 {
     Q_UNUSED(window);
@@ -282,9 +346,8 @@
     NSSize cancelSizeHint = [mCancelButton frame].size;
 
     const CGFloat ButtonWidth = qMin(qMax(ButtonMinWidth,
-                                          qMax(okSizeHint.width, cancelSizeHint.width)),
-                                     CGFloat((frameSize.width - 2.0 * ButtonSideMargin
-                                              - ButtonSpacing) * 0.5));
+                qMax(okSizeHint.width, cancelSizeHint.width)),
+            CGFloat((frameSize.width - 2.0 * ButtonSideMargin - ButtonSpacing) * 0.5));
     const CGFloat ButtonHeight = qMax(ButtonMinHeight,
                                      qMax(okSizeHint.height, cancelSizeHint.height));
 
@@ -317,14 +380,12 @@
     NSFontManager *fontManager = [NSFontManager sharedFontManager];
     [self setQtFont:qfontForCocoaFont([fontManager convertFont:[fontManager selectedFont]],
                                       *mQtFont)];
-    [[mStolenContentView window] close];
     [self finishOffWithCode:NSOKButton];
 }
 
 - (void)onCancelClicked
 {
     Q_ASSERT(mPanelHackedWithButtons);
-    [[mStolenContentView window] close];
     [self finishOffWithCode:NSCancelButton];
 }
 
@@ -368,20 +429,26 @@
 
 - (void)finishOffWithCode:(NSInteger)code
 {
-    if (mPriv) {
-        if (mModalSession) {
-            [NSApp endModalSession:mModalSession];
-            mModalSession = 0;
+#ifdef QT_MAC_USE_COCOA
+    QFontDialog *q = mPriv->fontDialog();
+    if (QWidget *parent = q->parentWidget()) {
+        if (parent->isWindow()) {
+            [qt_mac_window_for(parent) removeChildWindow:[mStolenContentView window]];
         }
-        // Hack alert!
-        // Since this code path was never intended to be followed when starting from exec
-        // we need to force the dialog to communicate the new font, otherwise the signal
-        // won't get emitted.
-        if(code == NSOKButton)
-            mPriv->sampleEdit->setFont([self qtFont]);
-        mPriv->done((code == NSOKButton) ? QDialog::Accepted : QDialog::Rejected);
+    }
+#endif
+
+    if(code == NSOKButton)
+        mPriv->sampleEdit->setFont([self qtFont]);
+
+    if (mAppModal) {
+        mReturnCode = code;
+        [NSApp stopModalWithCode:code];
     } else {
-        [NSApp stopModalWithCode:code];
+        if (code == NSOKButton)
+            mPriv->fontDialog()->accept();
+        else
+            mPriv->fontDialog()->reject();
     }
 }
 
@@ -408,206 +475,16 @@
 
 QT_BEGIN_NAMESPACE
 
-extern void macStartInterceptNSPanelCtor();
-extern void macStopInterceptNSPanelCtor();
-extern NSButton *macCreateButton(const char *text, NSView *superview);
-
-void *QFontDialogPrivate::openCocoaFontPanel(const QFont &initial,
-        QWidget *parent, const QString &title, QFontDialog::FontDialogOptions options,
-        QFontDialogPrivate *priv)
-{
-    Q_UNUSED(parent);   // we would use the parent if only NSFontPanel could be a sheet
-    QMacCocoaAutoReleasePool pool;
-
-    /*
-        The standard Cocoa font panel has no OK or Cancel button and
-        is created as a utility window. For strange reasons (which seem
-        to stem from the fact that the font panel is based on a NIB
-        file), the approach we use for the color panel doesn't work for
-        the font panel (and, inversely, the approach we use here doesn't
-        quite work for color panel, and crashed last time I tried). So
-        instead, we take the following steps:
-
-         1. Constructs a plain NSPanel that looks the way we want it
-            to look. Specifically, if the NoButtons option is off, we
-            construct a panel without the NSUtilityWindowMask flag
-            and with buttons (OK and Cancel).
-
-         2. Steal the content view from the shared NSFontPanel and
-            put it inside our new NSPanel's content view, together
-            with the OK and Cancel buttons.
-
-         3. Lay out the original content view and the buttons when
-            the font panel is shown and whenever it is resized.
-
-         4. Clean up after ourselves.
-
-         PS. Some customization is also done in QCocoaApplication
-         validModesForFontPanel:.
-    */
-
-    Qt::WindowModality modality = Qt::ApplicationModal;
-    if (priv)
-        modality = priv->fontDialog()->windowModality();
-
-    bool needButtons = !(options & QFontDialog::NoButtons);
-    // don't need our own panel if the title bar isn't visible anyway (in a sheet)
-    bool needOwnPanel = (needButtons && modality != Qt::WindowModal);
-
-    bool sharedFontPanelExisted = [NSFontPanel sharedFontPanelExists];
-    NSFontPanel *sharedFontPanel = [NSFontPanel sharedFontPanel];
-    [sharedFontPanel setHidesOnDeactivate:false];
-
-    // hack to ensure that QCocoaApplication's validModesForFontPanel:
-    // implementation is honored
-    if (!sharedFontPanelExisted && needOwnPanel) {
-        [sharedFontPanel makeKeyAndOrderFront:sharedFontPanel];
-        [sharedFontPanel close];
-    }
-
-    NSPanel *ourPanel = 0;
-    NSView *stolenContentView = 0;
-    NSButton *okButton = 0;
-    NSButton *cancelButton = 0;
-
-    CGFloat dialogExtraWidth = 0.0;
-    CGFloat dialogExtraHeight = 0.0;
-
-    if (!needOwnPanel) {
-        // we can reuse the NSFontPanel unchanged
-        ourPanel = sharedFontPanel;
-    } else {
-        // compute dialogExtra{Width,Height}
-        dialogExtraWidth = 2.0 * DialogSideMargin;
-        dialogExtraHeight = DialogTopMargin + ButtonTopMargin + ButtonMinHeight
-                            + ButtonBottomMargin;
-
-        // compute initial contents rectangle
-        NSRect contentRect = [sharedFontPanel contentRectForFrameRect:[sharedFontPanel frame]];
-        contentRect.size.width += dialogExtraWidth;
-        contentRect.size.height += dialogExtraHeight;
-
-        // create the new panel
-        ourPanel = [[NSPanel alloc] initWithContentRect:contentRect
-                                              styleMask:StyleMask
-                                                backing:NSBackingStoreBuffered
-                                                  defer:YES];
-        [ourPanel setReleasedWhenClosed:YES];
-    }
-
-    stolenContentView = [sharedFontPanel contentView];
-
-    if (needButtons) {
-        // steal the font panel's contents view
-        [stolenContentView retain];
-        [sharedFontPanel setContentView:0];
-
-        // create a new content view and add the stolen one as a subview
-        NSRect frameRect = { { 0.0, 0.0 }, { 0.0, 0.0 } };
-        NSView *ourContentView = [[NSView alloc] initWithFrame:frameRect];
-        [ourContentView addSubview:stolenContentView];
-
-        // create OK and Cancel buttons and add these as subviews
-        okButton = macCreateButton("&OK", ourContentView);
-        cancelButton = macCreateButton("Cancel", ourContentView);
-
-        [ourPanel setContentView:ourContentView];
-        [ourPanel setDefaultButtonCell:[okButton cell]];
-    }
-
-    // create a delegate and set it
-    QCocoaFontPanelDelegate *delegate =
-            [[QCocoaFontPanelDelegate alloc] initWithFontPanel:sharedFontPanel
-                                             stolenContentView:stolenContentView
-                                                      okButton:okButton
-                                                  cancelButton:cancelButton
-                                                          priv:priv
-                                                    extraWidth:dialogExtraWidth
-                                                   extraHeight:dialogExtraHeight];
-    [ourPanel setDelegate:delegate];
-    [[NSFontManager sharedFontManager] setDelegate:delegate];
-#ifdef QT_MAC_USE_COCOA
-    [[NSFontManager sharedFontManager] setTarget:delegate];
-#endif
-    setFont(delegate, initial);
-
-    // hack to get correct initial layout
-    NSRect frameRect = [ourPanel frame];
-    frameRect.size.width += 1.0;
-    [ourPanel setFrame:frameRect display:NO];
-    frameRect.size.width -= 1.0;
-    frameRect.size = [delegate windowWillResize:ourPanel toSize:frameRect.size];
-    [ourPanel setFrame:frameRect display:NO];
-    [ourPanel center];
-
-    [ourPanel setTitle:(NSString*)(CFStringRef)QCFString(title)];
-
-    if (priv) {
-        switch (modality) {
-        case Qt::WindowModal:
-            if (parent) {
-#ifndef QT_MAC_USE_COCOA
-                WindowRef hiwindowRef = qt_mac_window_for(parent);
-                NSWindow *window =
-                    [[NSWindow alloc] initWithWindowRef:hiwindowRef];
-                // Cocoa docs say I should retain the Carbon ref.
-                CFRetain(hiwindowRef);
-#else
-                NSWindow *window = qt_mac_window_for(parent);
-#endif
-                [NSApp beginSheet:ourPanel
-                   modalForWindow:window
-                    modalDelegate:0
-                   didEndSelector:0
-                      contextInfo:0];
-#ifndef QT_MAC_USE_COCOA
-                [window release];
-#endif
-                break;
-            }
-            // fallthrough
-        case Qt::ApplicationModal:
-            [delegate setModalSession:[NSApp beginModalSessionForWindow:ourPanel]];
-            break;
-        default:
-            [ourPanel makeKeyAndOrderFront:ourPanel];
-        }
-    }
-    return delegate;
-}
-
-void QFontDialogPrivate::closeCocoaFontPanel(void *delegate)
+void QFontDialogPrivate::closeCocoaFontPanel()
 {
     QMacCocoaAutoReleasePool pool;
-    QCocoaFontPanelDelegate *theDelegate = static_cast<QCocoaFontPanelDelegate *>(delegate);
+    QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *theDelegate = static_cast<QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *>(delegate);
     NSWindow *ourPanel = [theDelegate actualPanel];
     [ourPanel close];
     [theDelegate cleanUpAfterMyself];
-    [theDelegate autorelease];
-}
-
-QFont QFontDialogPrivate::execCocoaFontPanel(bool *ok, const QFont &initial,
-        QWidget *parent, const QString &title, QFontDialog::FontDialogOptions options)
-{
-    QMacCocoaAutoReleasePool pool;
-    QCocoaFontPanelDelegate *delegate =
-            static_cast<QCocoaFontPanelDelegate *>(
-                openCocoaFontPanel(initial, parent, title, options));
-    NSWindow *ourPanel = [delegate actualPanel];
-    [ourPanel retain];
-    int rval = [NSApp runModalForWindow:ourPanel];
-    QFont font([delegate qtFont]);
-    [ourPanel release];
-    [delegate cleanUpAfterMyself];
-    [delegate release];
-    bool isOk = ((options & QFontDialog::NoButtons) || rval == NSOKButton);
-    if (ok)
-        *ok = isOk;
-    if (isOk) {
-        return font;
-    } else {
-        return initial;
-    }
+    [theDelegate release];
+    this->delegate = 0;
+    sharedFontPanelAvailable = true;
 }
 
 void QFontDialogPrivate::setFont(void *delegate, const QFont &font)
@@ -642,13 +519,16 @@
     }
 
     [mgr setSelectedFont:nsFont isMultiple:NO];
-    [static_cast<QCocoaFontPanelDelegate *>(delegate) setQtFont:font];
+    [static_cast<QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *>(delegate) setQtFont:font];
 }
 
-void *QFontDialogPrivate::_q_constructNativePanel()
+void QFontDialogPrivate::createNSFontPanelDelegate()
 {
+    if (delegate)
+        return;
+
+    sharedFontPanelAvailable = false;
     QMacCocoaAutoReleasePool pool;
-
     bool sharedFontPanelExisted = [NSFontPanel sharedFontPanelExists];
     NSFontPanel *sharedFontPanel = [NSFontPanel sharedFontPanel];
     [sharedFontPanel setHidesOnDeactivate:false];
@@ -670,8 +550,7 @@
 
     // compute dialogExtra{Width,Height}
     dialogExtraWidth = 2.0 * DialogSideMargin;
-    dialogExtraHeight = DialogTopMargin + ButtonTopMargin + ButtonMinHeight
-                        + ButtonBottomMargin;
+    dialogExtraHeight = DialogTopMargin + ButtonTopMargin + ButtonMinHeight + ButtonBottomMargin;
 
     // compute initial contents rectangle
     NSRect contentRect = [sharedFontPanel contentRectForFrameRect:[sharedFontPanel frame]];
@@ -684,7 +563,6 @@
                     backing:NSBackingStoreBuffered
                         defer:YES];
     [ourPanel setReleasedWhenClosed:YES];
-
     stolenContentView = [sharedFontPanel contentView];
 
     // steal the font panel's contents view
@@ -704,21 +582,23 @@
         [ourPanel setContentView:ourContentView];
         [ourPanel setDefaultButtonCell:[okButton cell]];
     }
-    // create a delegate and set it
-    QCocoaFontPanelDelegate *delegate =
-            [[QCocoaFontPanelDelegate alloc] initWithFontPanel:sharedFontPanel
+
+    // create the delegate and set it
+    QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *del = [[QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) alloc] initWithFontPanel:sharedFontPanel
                                              stolenContentView:stolenContentView
                                                       okButton:okButton
                                                   cancelButton:cancelButton
                                                           priv:this
                                                     extraWidth:dialogExtraWidth
                                                    extraHeight:dialogExtraHeight];
-    [ourPanel setDelegate:delegate];
-    [[NSFontManager sharedFontManager] setDelegate:delegate];
+    delegate = del;
+    [ourPanel setDelegate:del];
+
+    [[NSFontManager sharedFontManager] setDelegate:del];
 #ifdef QT_MAC_USE_COCOA
-    [[NSFontManager sharedFontManager] setTarget:delegate];
+    [[NSFontManager sharedFontManager] setTarget:del];
 #endif
-    setFont(delegate, QApplication::font());
+    setFont(del, q_func()->currentFont());
 
     {
         // hack to get correct initial layout
@@ -726,15 +606,12 @@
         frameRect.size.width += 1.0;
         [ourPanel setFrame:frameRect display:NO];
         frameRect.size.width -= 1.0;
-        frameRect.size = [delegate windowWillResize:ourPanel toSize:frameRect.size];
+        frameRect.size = [del windowWillResize:ourPanel toSize:frameRect.size];
         [ourPanel setFrame:frameRect display:NO];
         [ourPanel center];
     }
     NSString *title = @"Select font";
     [ourPanel setTitle:title];
-
-    [delegate setModalSession:[NSApp beginModalSessionForWindow:ourPanel]];
-    return delegate;
 }
 
 void QFontDialogPrivate::mac_nativeDialogModalHelp()
@@ -759,29 +636,47 @@
 // and "adding" the buttons.
 void QFontDialogPrivate::_q_macRunNativeAppModalPanel()
 {
-    QBoolBlocker nativeDialogOnTop(QApplicationPrivate::native_modal_dialog_active);
-    Q_Q(QFontDialog);
-    QCocoaFontPanelDelegate *delegate = (QCocoaFontPanelDelegate *)_q_constructNativePanel();
-    NSWindow *ourPanel = [delegate actualPanel];
-    [ourPanel retain];
-    int rval = [NSApp runModalForWindow:ourPanel];
-    QAbstractEventDispatcher::instance()->interrupt();
-    [ourPanel release];
-    [delegate cleanUpAfterMyself];
-    [delegate release];
-    bool isOk = (rval == NSOKButton);
-    if(isOk)
-        rescode = QDialog::Accepted;
-    else
-        rescode = QDialog::Rejected;
+    createNSFontPanelDelegate();
+    QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *del = static_cast<QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *>(delegate);
+    [del runApplicationModalPanel];
 }
 
+bool QFontDialogPrivate::showCocoaFontPanel()
+{
+    if (!sharedFontPanelAvailable)
+        return false;
+
+    Q_Q(QFontDialog);
+    QMacCocoaAutoReleasePool pool;
+    createNSFontPanelDelegate();
+    QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *del = static_cast<QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *>(delegate);
+    if (qt_mac_is_macsheet(q))
+        [del showWindowModalSheet:q->parentWidget()];
+    else
+        [del showModelessPanel];
+    return true;
+}
+
+bool QFontDialogPrivate::hideCocoaFontPanel()
+{
+    if (!delegate){
+        // Nothing to do. We return false to leave the question
+        // open regarding whether or not to go native:
+        return false;
+    } else {
+        closeCocoaFontPanel();
+        // Even when we hide it, we are still using a
+        // native dialog, so return true:
+        return true;
+    }
+}
 bool QFontDialogPrivate::setVisible_sys(bool visible)
 {
     Q_Q(QFontDialog);
     if (!visible == q->isHidden())
         return false;
-    return visible;
+
+    return visible ? showCocoaFontPanel() : hideCocoaFontPanel();
 }
 
 QT_END_NAMESPACE