1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtGui module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qcolordialog_p.h"
43 #if !defined(QT_NO_COLORDIALOG) && defined(Q_WS_MAC)
44 #include <qapplication.h>
46 #include <qdialogbuttonbox.h>
47 #include <qabstracteventdispatcher.h>
48 #include <private/qapplication_p.h>
49 #include <private/qt_mac_p.h>
51 #import <AppKit/AppKit.h>
52 #import <Foundation/Foundation.h>
55 typedef float CGFloat; // Should only not be defined on 32-bit platforms
59 #if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5
60 @protocol NSWindowDelegate <NSObject>
61 - (void)windowDidResize:(NSNotification *)notification;
62 - (BOOL)windowShouldClose:(id)window;
68 @class QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate);
70 @interface QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) : NSObject<NSWindowDelegate> {
71 NSColorPanel *mColorPanel;
72 NSView *mStolenContentView;
74 NSButton *mCancelButton;
75 QColorDialogPrivate *mPriv;
77 CGFloat mMinWidth; // currently unused
78 CGFloat mExtraHeight; // currently unused
80 NSInteger mResultCode;
81 BOOL mDialogIsExecuting;
84 - (id)initWithColorPanel:(NSColorPanel *)panel
85 stolenContentView:(NSView *)stolenContentView
86 okButton:(NSButton *)okButton
87 cancelButton:(NSButton *)cancelButton
88 priv:(QColorDialogPrivate *)priv;
89 - (void)colorChanged:(NSNotification *)notification;
92 - (void)onCancelClicked;
93 - (void)updateQtColor;
94 - (NSColorPanel *)colorPanel;
96 - (void)finishOffWithCode:(NSInteger)result;
97 - (void)showColorPanel;
99 - (void)setResultSet:(BOOL)result;
102 @implementation QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate)
103 - (id)initWithColorPanel:(NSColorPanel *)panel
104 stolenContentView:(NSView *)stolenContentView
105 okButton:(NSButton *)okButton
106 cancelButton:(NSButton *)cancelButton
107 priv:(QColorDialogPrivate *)priv
112 mStolenContentView = stolenContentView;
113 mOkButton = okButton;
114 mCancelButton = cancelButton;
118 mHackedPanel = (okButton != 0);
119 mResultCode = NSCancelButton;
120 mDialogIsExecuting = false;
126 [okButton setAction:@selector(onOkClicked)];
127 [okButton setTarget:self];
129 [cancelButton setAction:@selector(onCancelClicked)];
130 [cancelButton setTarget:self];
133 [[NSNotificationCenter defaultCenter] addObserver:self
134 selector:@selector(colorChanged:)
135 name:NSColorPanelColorDidChangeNotification
138 mQtColor = new QColor();
144 QMacCocoaAutoReleasePool pool;
146 NSView *ourContentView = [mColorPanel contentView];
148 // return stolen stuff to its rightful owner
149 [mStolenContentView removeFromSuperview];
150 [mColorPanel setContentView:mStolenContentView];
153 [mCancelButton release];
154 [ourContentView release];
156 [mColorPanel setDelegate:nil];
157 [[NSNotificationCenter defaultCenter] removeObserver:self];
162 - (void)setResultSet:(BOOL)result
167 - (BOOL)windowShouldClose:(id)window
171 [self updateQtColor];
172 if (mDialogIsExecuting) {
173 [self finishOffWithCode:NSCancelButton];
176 mPriv->colorDialog()->reject();
181 - (void)windowDidResize:(NSNotification *)notification
183 Q_UNUSED(notification);
188 - (void)colorChanged:(NSNotification *)notification
190 Q_UNUSED(notification);
191 [self updateQtColor];
196 Q_ASSERT(mHackedPanel);
198 NSRect rect = [[mStolenContentView superview] frame];
200 // should a priori be kept in sync with qfontdialog_mac.mm
201 const CGFloat ButtonMinWidth = 78.0; // 84.0 for Carbon
202 const CGFloat ButtonMinHeight = 32.0;
203 const CGFloat ButtonSpacing = 0.0;
204 const CGFloat ButtonTopMargin = 0.0;
205 const CGFloat ButtonBottomMargin = 7.0;
206 const CGFloat ButtonSideMargin = 9.0;
208 [mOkButton sizeToFit];
209 NSSize okSizeHint = [mOkButton frame].size;
211 [mCancelButton sizeToFit];
212 NSSize cancelSizeHint = [mCancelButton frame].size;
214 const CGFloat ButtonWidth = qMin(qMax(ButtonMinWidth,
215 qMax(okSizeHint.width, cancelSizeHint.width)),
216 CGFloat((rect.size.width - 2.0 * ButtonSideMargin - ButtonSpacing) * 0.5));
217 const CGFloat ButtonHeight = qMax(ButtonMinHeight,
218 qMax(okSizeHint.height, cancelSizeHint.height));
220 NSRect okRect = { { rect.size.width - ButtonSideMargin - ButtonWidth,
221 ButtonBottomMargin },
222 { ButtonWidth, ButtonHeight } };
223 [mOkButton setFrame:okRect];
224 [mOkButton setNeedsDisplay:YES];
226 NSRect cancelRect = { { okRect.origin.x - ButtonSpacing - ButtonWidth,
227 ButtonBottomMargin },
228 { ButtonWidth, ButtonHeight } };
229 [mCancelButton setFrame:cancelRect];
230 [mCancelButton setNeedsDisplay:YES];
232 const CGFloat Y = ButtonBottomMargin + ButtonHeight + ButtonTopMargin;
233 NSRect stolenCVRect = { { 0.0, Y },
234 { rect.size.width, rect.size.height - Y } };
235 [mStolenContentView setFrame:stolenCVRect];
236 [mStolenContentView setNeedsDisplay:YES];
238 [[mStolenContentView superview] setNeedsDisplay:YES];
239 mMinWidth = 2 * ButtonSideMargin + ButtonSpacing + 2 * ButtonWidth;
245 Q_ASSERT(mHackedPanel);
246 [[mStolenContentView window] close];
247 [self updateQtColor];
248 [self finishOffWithCode:NSOKButton];
251 - (void)onCancelClicked
254 [[mStolenContentView window] close];
256 mQtColor = new QColor();
257 [self finishOffWithCode:NSCancelButton];
261 - (void)updateQtColor
264 mQtColor = new QColor();
265 NSColor *color = [mColorPanel color];
266 NSString *colorSpaceName = [color colorSpaceName];
267 if (colorSpaceName == NSDeviceCMYKColorSpace) {
268 CGFloat cyan = 0, magenta = 0, yellow = 0, black = 0, alpha = 0;
269 [color getCyan:&cyan magenta:&magenta yellow:&yellow black:&black alpha:&alpha];
270 mQtColor->setCmykF(cyan, magenta, yellow, black, alpha);
271 } else if (colorSpaceName == NSCalibratedRGBColorSpace || colorSpaceName == NSDeviceRGBColorSpace) {
272 CGFloat red = 0, green = 0, blue = 0, alpha = 0;
273 [color getRed:&red green:&green blue:&blue alpha:&alpha];
274 mQtColor->setRgbF(red, green, blue, alpha);
275 } else if (colorSpaceName == NSNamedColorSpace) {
276 NSColor *tmpColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
277 CGFloat red = 0, green = 0, blue = 0, alpha = 0;
278 [tmpColor getRed:&red green:&green blue:&blue alpha:&alpha];
279 mQtColor->setRgbF(red, green, blue, alpha);
281 NSColorSpace *colorSpace = [color colorSpace];
282 if ([colorSpace colorSpaceModel] == NSCMYKColorSpaceModel && [color numberOfComponents] == 5){
283 CGFloat components[5];
284 [color getComponents:components];
285 mQtColor->setCmykF(components[0], components[1], components[2], components[3], components[4]);
287 NSColor *tmpColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
288 CGFloat red = 0, green = 0, blue = 0, alpha = 0;
289 [tmpColor getRed:&red green:&green blue:&blue alpha:&alpha];
290 mQtColor->setRgbF(red, green, blue, alpha);
294 mPriv->setCurrentQColor(*mQtColor);
297 - (NSColorPanel *)colorPanel
307 - (void)finishOffWithCode:(NSInteger)code
310 if (mDialogIsExecuting) {
311 // We stop the current modal event loop. The control
312 // will then return inside -(void)exec below.
313 // It's important that the modal event loop is stopped before
314 // we accept/reject QColorDialog, since QColorDialog has its
315 // own event loop that needs to be stopped last.
316 [NSApp stopModalWithCode:code];
318 // Since we are not in a modal event loop, we can safely close
320 // Calling accept() or reject() can in turn call closeCocoaColorPanel.
321 // This check will prevent any such recursion.
324 if (mResultCode == NSCancelButton) {
325 mPriv->colorDialog()->reject();
327 mPriv->colorDialog()->accept();
333 - (void)showColorPanel
335 mDialogIsExecuting = false;
336 [mColorPanel makeKeyAndOrderFront:mColorPanel];
341 QBoolBlocker nativeDialogOnTop(QApplicationPrivate::native_modal_dialog_active);
342 QMacCocoaAutoReleasePool pool;
343 mDialogIsExecuting = true;
344 bool modalEnded = false;
345 while (!modalEnded) {
346 #ifndef QT_NO_EXCEPTIONS
348 [NSApp runModalForWindow:mColorPanel];
350 } @catch (NSException *) {
351 // For some reason, NSColorPanel throws an exception when
352 // clicking on 'SelectedMenuItemColor' from the 'Developer'
353 // palette (tab three).
356 [NSApp runModalForWindow:mColorPanel];
361 QAbstractEventDispatcher::instance()->interrupt();
362 if (mResultCode == NSCancelButton)
363 mPriv->colorDialog()->reject();
365 mPriv->colorDialog()->accept();
372 extern void macStartInterceptNSPanelCtor();
373 extern void macStopInterceptNSPanelCtor();
374 extern NSButton *macCreateButton(const char *text, NSView *superview);
376 void QColorDialogPrivate::openCocoaColorPanel(const QColor &initial,
377 QWidget *parent, const QString &title, QColorDialog::ColorDialogOptions options)
379 Q_UNUSED(parent); // we would use the parent if only NSColorPanel could be a sheet
380 QMacCocoaAutoReleasePool pool;
384 The standard Cocoa color panel has no OK or Cancel button and
385 is created as a utility window, whereas we want something like
386 the Carbon color panel. We need to take the following steps:
388 1. Intercept the color panel constructor to turn off the
389 NSUtilityWindowMask flag. This is done by temporarily
390 replacing initWithContentRect:styleMask:backing:defer:
391 in NSPanel by our own method.
393 2. Modify the color panel so that its content view is part
394 of a new content view that contains it as well as two
395 buttons (OK and Cancel).
397 3. Lay out the original content view and the buttons when
398 the color panel is shown and whenever it is resized.
400 4. Clean up after ourselves.
403 bool hackColorPanel = !(options & QColorDialog::NoButtons);
406 macStartInterceptNSPanelCtor();
407 NSColorPanel *colorPanel = [NSColorPanel sharedColorPanel];
409 macStopInterceptNSPanelCtor();
411 [colorPanel setHidesOnDeactivate:false];
413 // set up the Cocoa color panel
414 [colorPanel setShowsAlpha:options & QColorDialog::ShowAlphaChannel];
415 [colorPanel setTitle:(NSString*)(CFStringRef)QCFString(title)];
417 NSView *stolenContentView = 0;
418 NSButton *okButton = 0;
419 NSButton *cancelButton = 0;
421 if (hackColorPanel) {
422 // steal the color panel's contents view
423 stolenContentView = [colorPanel contentView];
424 [stolenContentView retain];
425 [colorPanel setContentView:0];
427 // create a new content view and add the stolen one as a subview
428 NSRect frameRect = { { 0.0, 0.0 }, { 0.0, 0.0 } };
429 NSView *ourContentView = [[NSView alloc] initWithFrame:frameRect];
430 [ourContentView addSubview:stolenContentView];
432 // create OK and Cancel buttons and add these as subviews
433 okButton = macCreateButton("&OK", ourContentView);
434 cancelButton = macCreateButton("Cancel", ourContentView);
436 [colorPanel setContentView:ourContentView];
437 [colorPanel setDefaultButtonCell:[okButton cell]];
440 delegate = [[QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) alloc] initWithColorPanel:colorPanel
441 stolenContentView:stolenContentView
443 cancelButton:cancelButton
445 [colorPanel setDelegate:static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate)];
447 [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) setResultSet:NO];
448 setCocoaPanelColor(initial);
449 [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) showColorPanel];
452 void QColorDialogPrivate::closeCocoaColorPanel()
454 [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) onCancelClicked];
457 void QColorDialogPrivate::releaseCocoaColorPanelDelegate()
459 [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) release];
462 void QColorDialogPrivate::mac_nativeDialogModalHelp()
464 // Do a queued meta-call to open the native modal dialog so it opens after the new
465 // event loop has started to execute (in QDialog::exec). Using a timer rather than
466 // a queued meta call is intentional to ensure that the call is only delivered when
467 // [NSApp run] runs (timers are handeled special in cocoa). If NSApp is not
468 // running (which is the case if e.g a top-most QEventLoop has been
469 // interrupted, and the second-most event loop has not yet been reactivated (regardless
470 // if [NSApp run] is still on the stack)), showing a native modal dialog will fail.
473 QTimer::singleShot(1, q, SLOT(_q_macRunNativeAppModalPanel()));
477 void QColorDialogPrivate::_q_macRunNativeAppModalPanel()
479 [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) exec];
482 void QColorDialogPrivate::setCocoaPanelColor(const QColor &color)
484 QMacCocoaAutoReleasePool pool;
485 QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *theDelegate = static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate);
487 const QColor::Spec spec = color.spec();
488 if (spec == QColor::Cmyk) {
489 nsColor = [NSColor colorWithDeviceCyan:color.cyanF()
490 magenta:color.magentaF()
491 yellow:color.yellowF()
493 alpha:color.alphaF()];
495 nsColor = [NSColor colorWithCalibratedRed:color.redF()
498 alpha:color.alphaF()];
500 [[theDelegate colorPanel] setColor:nsColor];