1496ba830067c368984d09078c6003968e97696c
[profile/ivi/qtbase.git] / src / widgets / dialogs / qcolordialog_mac.mm
1 /****************************************************************************
2 **
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
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.
16 **
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.
20 **
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.
28 **
29 ** Other Usage
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.
32 **
33 **
34 **
35 **
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qcolordialog_p.h"
43 #if !defined(QT_NO_COLORDIALOG) && defined(Q_WS_MAC)
44 #include <qapplication.h>
45 #include <qtimer.h>
46 #include <qdialogbuttonbox.h>
47 #include <qabstracteventdispatcher.h>
48 #include <private/qapplication_p.h>
49 #include <private/qt_mac_p.h>
50 #include <qdebug.h>
51 #import <AppKit/AppKit.h>
52 #import <Foundation/Foundation.h>
53
54 #if !CGFLOAT_DEFINED
55 typedef float CGFloat;  // Should only not be defined on 32-bit platforms
56 #endif
57
58
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;
63 @end
64 #endif
65
66 QT_USE_NAMESPACE
67
68 @class QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate);
69
70 @interface QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) : NSObject<NSWindowDelegate> {
71     NSColorPanel *mColorPanel;
72     NSView *mStolenContentView;
73     NSButton *mOkButton;
74     NSButton *mCancelButton;
75     QColorDialogPrivate *mPriv;
76     QColor *mQtColor;
77     CGFloat mMinWidth;  // currently unused
78     CGFloat mExtraHeight;   // currently unused
79     BOOL mHackedPanel;
80     NSInteger mResultCode;
81     BOOL mDialogIsExecuting;
82     BOOL mResultSet;
83 }
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;
90 - (void)relayout;
91 - (void)onOkClicked;
92 - (void)onCancelClicked;
93 - (void)updateQtColor;
94 - (NSColorPanel *)colorPanel;
95 - (QColor)qtColor;
96 - (void)finishOffWithCode:(NSInteger)result;
97 - (void)showColorPanel;
98 - (void)exec;
99 - (void)setResultSet:(BOOL)result;
100 @end
101
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
108 {
109     self = [super init];
110
111     mColorPanel = panel;
112     mStolenContentView = stolenContentView;
113     mOkButton = okButton;
114     mCancelButton = cancelButton;
115     mPriv = priv;
116     mMinWidth = 0.0;
117     mExtraHeight = 0.0;
118     mHackedPanel = (okButton != 0);
119     mResultCode = NSCancelButton;
120     mDialogIsExecuting = false;
121     mResultSet = false;
122
123     if (mHackedPanel) {
124         [self relayout];
125
126         [okButton setAction:@selector(onOkClicked)];
127         [okButton setTarget:self];
128
129         [cancelButton setAction:@selector(onCancelClicked)];
130         [cancelButton setTarget:self];
131     }
132
133     [[NSNotificationCenter defaultCenter] addObserver:self
134         selector:@selector(colorChanged:)
135         name:NSColorPanelColorDidChangeNotification
136         object:mColorPanel];
137
138     mQtColor = new QColor();
139     return self;
140 }
141
142 - (void)dealloc
143 {
144     QMacCocoaAutoReleasePool pool;
145     if (mHackedPanel) {
146         NSView *ourContentView = [mColorPanel contentView];
147
148         // return stolen stuff to its rightful owner
149         [mStolenContentView removeFromSuperview];
150         [mColorPanel setContentView:mStolenContentView];
151
152         [mOkButton release];
153         [mCancelButton release];
154         [ourContentView release];
155     }
156     [mColorPanel setDelegate:nil];
157     [[NSNotificationCenter defaultCenter] removeObserver:self];
158     delete mQtColor;
159     [super dealloc];
160 }
161
162 - (void)setResultSet:(BOOL)result
163 {
164     mResultSet = result;
165 }
166
167 - (BOOL)windowShouldClose:(id)window
168 {
169     Q_UNUSED(window);
170     if (!mHackedPanel)
171         [self updateQtColor];
172     if (mDialogIsExecuting) {
173         [self finishOffWithCode:NSCancelButton];
174     } else {
175         mResultSet = true;
176         mPriv->colorDialog()->reject();
177     }
178     return true;
179 }
180
181 - (void)windowDidResize:(NSNotification *)notification
182 {
183     Q_UNUSED(notification);
184     if (mHackedPanel)
185         [self relayout];
186 }
187
188 - (void)colorChanged:(NSNotification *)notification
189 {
190     Q_UNUSED(notification);
191     [self updateQtColor];
192 }
193
194 - (void)relayout
195 {
196     Q_ASSERT(mHackedPanel);
197
198     NSRect rect = [[mStolenContentView superview] frame];
199
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;
207
208     [mOkButton sizeToFit];
209     NSSize okSizeHint = [mOkButton frame].size;
210
211     [mCancelButton sizeToFit];
212     NSSize cancelSizeHint = [mCancelButton frame].size;
213
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));
219
220     NSRect okRect = { { rect.size.width - ButtonSideMargin - ButtonWidth,
221                         ButtonBottomMargin },
222                       { ButtonWidth, ButtonHeight } };
223     [mOkButton setFrame:okRect];
224     [mOkButton setNeedsDisplay:YES];
225
226     NSRect cancelRect = { { okRect.origin.x - ButtonSpacing - ButtonWidth,
227                             ButtonBottomMargin },
228                             { ButtonWidth, ButtonHeight } };
229     [mCancelButton setFrame:cancelRect];
230     [mCancelButton setNeedsDisplay:YES];
231
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];
237
238     [[mStolenContentView superview] setNeedsDisplay:YES];
239     mMinWidth = 2 * ButtonSideMargin + ButtonSpacing + 2 * ButtonWidth;
240     mExtraHeight = Y;
241 }
242
243 - (void)onOkClicked
244 {
245     Q_ASSERT(mHackedPanel);
246     [[mStolenContentView window] close];
247     [self updateQtColor];
248     [self finishOffWithCode:NSOKButton];
249 }
250
251 - (void)onCancelClicked
252 {
253     if (mHackedPanel) {
254         [[mStolenContentView window] close];
255         delete mQtColor;
256         mQtColor = new QColor();
257         [self finishOffWithCode:NSCancelButton];
258     }
259 }
260
261 - (void)updateQtColor
262 {
263     delete mQtColor;
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);
280     } else {
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]);
286         } else {
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);
291         }
292     }
293
294     mPriv->setCurrentQColor(*mQtColor);
295 }
296
297 - (NSColorPanel *)colorPanel
298 {
299     return mColorPanel;
300 }
301
302 - (QColor)qtColor
303 {
304     return *mQtColor;
305 }
306
307 - (void)finishOffWithCode:(NSInteger)code
308 {
309     mResultCode = 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];
317     } else {
318         // Since we are not in a modal event loop, we can safely close
319         // down QColorDialog
320         // Calling accept() or reject() can in turn call closeCocoaColorPanel.
321         // This check will prevent any such recursion.
322         if (!mResultSet) {
323             mResultSet = true;
324             if (mResultCode == NSCancelButton) {
325                 mPriv->colorDialog()->reject();
326             } else {
327                 mPriv->colorDialog()->accept();
328             }
329         } 
330     }
331 }
332
333 - (void)showColorPanel
334 {
335     mDialogIsExecuting = false;
336     [mColorPanel makeKeyAndOrderFront:mColorPanel];
337 }
338
339 - (void)exec
340 {
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
347         @try {
348             [NSApp runModalForWindow:mColorPanel];
349             modalEnded = true;
350         } @catch (NSException *) {
351             // For some reason, NSColorPanel throws an exception when
352             // clicking on 'SelectedMenuItemColor' from the 'Developer'
353             // palette (tab three).
354         }
355 #else
356         [NSApp runModalForWindow:mColorPanel];
357         modalEnded = true;
358 #endif
359     }
360
361     QAbstractEventDispatcher::instance()->interrupt();
362     if (mResultCode == NSCancelButton)
363         mPriv->colorDialog()->reject();
364     else
365         mPriv->colorDialog()->accept();
366 }
367
368 @end
369
370 QT_BEGIN_NAMESPACE
371
372 extern void macStartInterceptNSPanelCtor();
373 extern void macStopInterceptNSPanelCtor();
374 extern NSButton *macCreateButton(const char *text, NSView *superview);
375
376 void QColorDialogPrivate::openCocoaColorPanel(const QColor &initial,
377         QWidget *parent, const QString &title, QColorDialog::ColorDialogOptions options)
378 {
379     Q_UNUSED(parent);   // we would use the parent if only NSColorPanel could be a sheet
380     QMacCocoaAutoReleasePool pool;
381
382     if (!delegate) {
383         /*
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:
387
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.
392
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).
396
397            3. Lay out the original content view and the buttons when
398            the color panel is shown and whenever it is resized.
399
400            4. Clean up after ourselves.
401          */
402
403         bool hackColorPanel = !(options & QColorDialog::NoButtons);
404
405         if (hackColorPanel)
406             macStartInterceptNSPanelCtor();
407         NSColorPanel *colorPanel = [NSColorPanel sharedColorPanel];
408         if (hackColorPanel)
409             macStopInterceptNSPanelCtor();
410
411         [colorPanel setHidesOnDeactivate:false];
412
413         // set up the Cocoa color panel
414         [colorPanel setShowsAlpha:options & QColorDialog::ShowAlphaChannel];
415         [colorPanel setTitle:(NSString*)(CFStringRef)QCFString(title)];
416
417         NSView *stolenContentView = 0;
418         NSButton *okButton = 0;
419         NSButton *cancelButton = 0;
420
421         if (hackColorPanel) {
422             // steal the color panel's contents view
423             stolenContentView = [colorPanel contentView];
424             [stolenContentView retain];
425             [colorPanel setContentView:0];
426
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];
431
432             // create OK and Cancel buttons and add these as subviews
433             okButton = macCreateButton("&OK", ourContentView);
434             cancelButton = macCreateButton("Cancel", ourContentView);
435
436             [colorPanel setContentView:ourContentView];
437             [colorPanel setDefaultButtonCell:[okButton cell]];
438         }
439
440         delegate = [[QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) alloc] initWithColorPanel:colorPanel
441             stolenContentView:stolenContentView
442             okButton:okButton
443             cancelButton:cancelButton
444             priv:this];
445         [colorPanel setDelegate:static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate)];
446     }
447     [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) setResultSet:NO];
448     setCocoaPanelColor(initial);
449     [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) showColorPanel];
450 }
451
452 void QColorDialogPrivate::closeCocoaColorPanel()
453 {
454     [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) onCancelClicked];
455 }
456
457 void QColorDialogPrivate::releaseCocoaColorPanelDelegate()
458 {
459     [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) release];
460 }
461
462 void QColorDialogPrivate::mac_nativeDialogModalHelp()
463 {
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.
471     if (delegate){
472         Q_Q(QColorDialog);
473         QTimer::singleShot(1, q, SLOT(_q_macRunNativeAppModalPanel()));
474     }
475 }
476
477 void QColorDialogPrivate::_q_macRunNativeAppModalPanel()
478 {
479     [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) exec];
480 }
481
482 void QColorDialogPrivate::setCocoaPanelColor(const QColor &color)
483 {
484     QMacCocoaAutoReleasePool pool;
485     QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *theDelegate = static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate);
486     NSColor *nsColor;
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()
492                                          black:color.blackF()
493                                          alpha:color.alphaF()];
494     } else {
495         nsColor = [NSColor colorWithCalibratedRed:color.redF()
496                                             green:color.greenF()
497                                              blue:color.blueF()
498                                             alpha:color.alphaF()];
499     }
500     [[theDelegate colorPanel] setColor:nsColor];
501 }
502
503 QT_END_NAMESPACE
504
505 #endif