Qt 4.8
qcolordialog_mac.mm
Go to the documentation of this file.
1 /****************************************************************************
2 **
3 ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the QtGui module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Digia. For licensing terms and
14 ** conditions see http://qt.digia.com/licensing. For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights. These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file. Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
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>
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 
67 
69 
70 @interface QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) : NSObject<NSWindowDelegate> {
71  NSColorPanel *mColorPanel;
73  NSButton *mOkButton;
74  NSButton *mCancelButton;
77  CGFloat mMinWidth; // currently unused
78  CGFloat mExtraHeight; // currently unused
80  NSInteger mResultCode;
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 MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
125  [mColorPanel setRestorable:NO];
126 #endif
127 
128  if (mHackedPanel) {
129  [self relayout];
130 
131  [okButton setAction:@selector(onOkClicked)];
132  [okButton setTarget:self];
133 
134  [cancelButton setAction:@selector(onCancelClicked)];
135  [cancelButton setTarget:self];
136  }
137 
138  [[NSNotificationCenter defaultCenter] addObserver:self
139  selector:@selector(colorChanged:)
140  name:NSColorPanelColorDidChangeNotification
141  object:mColorPanel];
142 
143  mQtColor = new QColor();
144  return self;
145 }
146 
147 - (void)dealloc
148 {
150  if (mHackedPanel) {
151  NSView *ourContentView = [mColorPanel contentView];
152 
153  // return stolen stuff to its rightful owner
154  [mStolenContentView removeFromSuperview];
155  [mColorPanel setContentView:mStolenContentView];
156 
157  [mOkButton release];
158  [mCancelButton release];
159  [ourContentView release];
160  }
161  [mColorPanel setDelegate:nil];
162  [[NSNotificationCenter defaultCenter] removeObserver:self];
163  delete mQtColor;
164  [super dealloc];
165 }
166 
167 - (void)setResultSet:(BOOL)result
168 {
169  mResultSet = result;
170 }
171 
172 - (BOOL)windowShouldClose:(id)window
173 {
174  Q_UNUSED(window);
175  if (!mHackedPanel)
176  [self updateQtColor];
177  if (mDialogIsExecuting) {
178  [self finishOffWithCode:NSCancelButton];
179  } else {
180  mResultSet = true;
181  mPriv->colorDialog()->reject();
182  }
183  return true;
184 }
185 
186 - (void)windowDidResize:(NSNotification *)notification
187 {
188  Q_UNUSED(notification);
189  if (mHackedPanel)
190  [self relayout];
191 }
192 
193 - (void)colorChanged:(NSNotification *)notification
194 {
195  Q_UNUSED(notification);
196  [self updateQtColor];
197 }
198 
199 - (void)relayout
200 {
201  Q_ASSERT(mHackedPanel);
202 
203  NSRect rect = [[mStolenContentView superview] frame];
204 
205  // should a priori be kept in sync with qfontdialog_mac.mm
206  const CGFloat ButtonMinWidth = 78.0; // 84.0 for Carbon
207  const CGFloat ButtonMinHeight = 32.0;
208  const CGFloat ButtonSpacing = 0.0;
209  const CGFloat ButtonTopMargin = 0.0;
210  const CGFloat ButtonBottomMargin = 7.0;
211  const CGFloat ButtonSideMargin = 9.0;
212 
213  [mOkButton sizeToFit];
214  NSSize okSizeHint = [mOkButton frame].size;
215 
216  [mCancelButton sizeToFit];
217  NSSize cancelSizeHint = [mCancelButton frame].size;
218 
219  const CGFloat ButtonWidth = qMin(qMax(ButtonMinWidth,
220  qMax(okSizeHint.width, cancelSizeHint.width)),
221  CGFloat((rect.size.width - 2.0 * ButtonSideMargin - ButtonSpacing) * 0.5));
222  const CGFloat ButtonHeight = qMax(ButtonMinHeight,
223  qMax(okSizeHint.height, cancelSizeHint.height));
224 
225  NSRect okRect = { { rect.size.width - ButtonSideMargin - ButtonWidth,
227  { ButtonWidth, ButtonHeight } };
228  [mOkButton setFrame:okRect];
229  [mOkButton setNeedsDisplay:YES];
230 
231  NSRect cancelRect = { { okRect.origin.x - ButtonSpacing - ButtonWidth,
233  { ButtonWidth, ButtonHeight } };
234  [mCancelButton setFrame:cancelRect];
235  [mCancelButton setNeedsDisplay:YES];
236 
237  const CGFloat Y = ButtonBottomMargin + ButtonHeight + ButtonTopMargin;
238  NSRect stolenCVRect = { { 0.0, Y },
239  { rect.size.width, rect.size.height - Y } };
240  [mStolenContentView setFrame:stolenCVRect];
241  [mStolenContentView setNeedsDisplay:YES];
242 
243  [[mStolenContentView superview] setNeedsDisplay:YES];
244  mMinWidth = 2 * ButtonSideMargin + ButtonSpacing + 2 * ButtonWidth;
245  mExtraHeight = Y;
246 }
247 
248 - (void)onOkClicked
249 {
250  Q_ASSERT(mHackedPanel);
251  [[mStolenContentView window] close];
252  [self updateQtColor];
253  [self finishOffWithCode:NSOKButton];
254 }
255 
256 - (void)onCancelClicked
257 {
258  if (mHackedPanel) {
259  [[mStolenContentView window] close];
260  delete mQtColor;
261  mQtColor = new QColor();
262  [self finishOffWithCode:NSCancelButton];
263  }
264 }
265 
266 - (void)updateQtColor
267 {
268  delete mQtColor;
269  mQtColor = new QColor();
270  NSColor *color = [mColorPanel color];
271  NSString *colorSpaceName = [color colorSpaceName];
272  if (colorSpaceName == NSDeviceCMYKColorSpace) {
273  CGFloat cyan = 0, magenta = 0, yellow = 0, black = 0, alpha = 0;
274  [color getCyan:&cyan magenta:&magenta yellow:&yellow black:&black alpha:&alpha];
275  mQtColor->setCmykF(cyan, magenta, yellow, black, alpha);
276  } else if (colorSpaceName == NSCalibratedRGBColorSpace || colorSpaceName == NSDeviceRGBColorSpace) {
277  CGFloat red = 0, green = 0, blue = 0, alpha = 0;
278  [color getRed:&red green:&green blue:&blue alpha:&alpha];
279  mQtColor->setRgbF(red, green, blue, alpha);
280  } else if (colorSpaceName == NSNamedColorSpace) {
281  NSColor *tmpColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
282  CGFloat red = 0, green = 0, blue = 0, alpha = 0;
283  [tmpColor getRed:&red green:&green blue:&blue alpha:&alpha];
284  mQtColor->setRgbF(red, green, blue, alpha);
285  } else {
286  NSColorSpace *colorSpace = [color colorSpace];
287  if ([colorSpace colorSpaceModel] == NSCMYKColorSpaceModel && [color numberOfComponents] == 5){
288  CGFloat components[5];
289  [color getComponents:components];
290  mQtColor->setCmykF(components[0], components[1], components[2], components[3], components[4]);
291  } else {
292  NSColor *tmpColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
293  CGFloat red = 0, green = 0, blue = 0, alpha = 0;
294  [tmpColor getRed:&red green:&green blue:&blue alpha:&alpha];
295  mQtColor->setRgbF(red, green, blue, alpha);
296  }
297  }
298 
299  mPriv->setCurrentQColor(*mQtColor);
300 }
301 
302 - (NSColorPanel *)colorPanel
303 {
304  return mColorPanel;
305 }
306 
307 - (QColor)qtColor
308 {
309  return *mQtColor;
310 }
311 
312 - (void)finishOffWithCode:(NSInteger)code
313 {
314  mResultCode = code;
315  if (mDialogIsExecuting) {
316  // We stop the current modal event loop. The control
317  // will then return inside -(void)exec below.
318  // It's important that the modal event loop is stopped before
319  // we accept/reject QColorDialog, since QColorDialog has its
320  // own event loop that needs to be stopped last.
321  [NSApp stopModalWithCode:code];
322  } else {
323  // Since we are not in a modal event loop, we can safely close
324  // down QColorDialog
325  // Calling accept() or reject() can in turn call closeCocoaColorPanel.
326  // This check will prevent any such recursion.
327  if (!mResultSet) {
328  mResultSet = true;
329  if (mResultCode == NSCancelButton) {
330  mPriv->colorDialog()->reject();
331  } else {
332  mPriv->colorDialog()->accept();
333  }
334  }
335  }
336 }
337 
338 - (void)showColorPanel
339 {
340  mDialogIsExecuting = false;
341  [mColorPanel makeKeyAndOrderFront:mColorPanel];
342 }
343 
344 - (void)exec
345 {
348  mDialogIsExecuting = true;
349  bool modalEnded = false;
350  while (!modalEnded) {
351 #ifndef QT_NO_EXCEPTIONS
352  @try {
353  [NSApp runModalForWindow:mColorPanel];
354  modalEnded = true;
355  } @catch (NSException *) {
356  // For some reason, NSColorPanel throws an exception when
357  // clicking on 'SelectedMenuItemColor' from the 'Developer'
358  // palette (tab three).
359  }
360 #else
361  [NSApp runModalForWindow:mColorPanel];
362  modalEnded = true;
363 #endif
364  }
365 
367  if (mResultCode == NSCancelButton)
368  mPriv->colorDialog()->reject();
369  else
370  mPriv->colorDialog()->accept();
371 }
372 
373 @end
374 
376 
377 extern void macStartInterceptNSPanelCtor();
378 extern void macStopInterceptNSPanelCtor();
379 extern NSButton *macCreateButton(const char *text, NSView *superview);
380 
382  QWidget *parent, const QString &title, QColorDialog::ColorDialogOptions options)
383 {
384  Q_UNUSED(parent); // we would use the parent if only NSColorPanel could be a sheet
386 
387  if (!delegate) {
388  /*
389  The standard Cocoa color panel has no OK or Cancel button and
390  is created as a utility window, whereas we want something like
391  the Carbon color panel. We need to take the following steps:
392 
393  1. Intercept the color panel constructor to turn off the
394  NSUtilityWindowMask flag. This is done by temporarily
395  replacing initWithContentRect:styleMask:backing:defer:
396  in NSPanel by our own method.
397 
398  2. Modify the color panel so that its content view is part
399  of a new content view that contains it as well as two
400  buttons (OK and Cancel).
401 
402  3. Lay out the original content view and the buttons when
403  the color panel is shown and whenever it is resized.
404 
405  4. Clean up after ourselves.
406  */
407 
408  bool hackColorPanel = !(options & QColorDialog::NoButtons);
409 
410  if (hackColorPanel)
412  NSColorPanel *colorPanel = [NSColorPanel sharedColorPanel];
413  if (hackColorPanel)
415 
416  [colorPanel setHidesOnDeactivate:false];
417 
418  // set up the Cocoa color panel
419  [colorPanel setShowsAlpha:options & QColorDialog::ShowAlphaChannel];
420  [colorPanel setTitle:(NSString*)(CFStringRef)QCFString(title)];
421 
422  NSView *stolenContentView = 0;
423  NSButton *okButton = 0;
424  NSButton *cancelButton = 0;
425 
426  if (hackColorPanel) {
427  // steal the color panel's contents view
428  stolenContentView = [colorPanel contentView];
429  [stolenContentView retain];
430  [colorPanel setContentView:0];
431 
432  // create a new content view and add the stolen one as a subview
433  NSRect frameRect = { { 0.0, 0.0 }, { 0.0, 0.0 } };
434  NSView *ourContentView = [[NSView alloc] initWithFrame:frameRect];
435  [ourContentView addSubview:stolenContentView];
436 
437  // create OK and Cancel buttons and add these as subviews
438  okButton = macCreateButton("&OK", ourContentView);
439  cancelButton = macCreateButton("Cancel", ourContentView);
440 
441  [colorPanel setContentView:ourContentView];
442  [colorPanel setDefaultButtonCell:[okButton cell]];
443  }
444 
445  delegate = [[QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) alloc] initWithColorPanel:colorPanel
446  stolenContentView:stolenContentView
447  okButton:okButton
448  cancelButton:cancelButton
449  priv:this];
450  [colorPanel setDelegate:static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate)];
451  }
452  [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) setResultSet:NO];
453  setCocoaPanelColor(initial);
454  [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) showColorPanel];
455 }
456 
458 {
459  [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) onCancelClicked];
460 }
461 
463 {
464  [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) release];
465 }
466 
468 {
469  // Do a queued meta-call to open the native modal dialog so it opens after the new
470  // event loop has started to execute (in QDialog::exec). Using a timer rather than
471  // a queued meta call is intentional to ensure that the call is only delivered when
472  // [NSApp run] runs (timers are handeled special in cocoa). If NSApp is not
473  // running (which is the case if e.g a top-most QEventLoop has been
474  // interrupted, and the second-most event loop has not yet been reactivated (regardless
475  // if [NSApp run] is still on the stack)), showing a native modal dialog will fail.
476  if (delegate){
477  Q_Q(QColorDialog);
478  QTimer::singleShot(1, q, SLOT(_q_macRunNativeAppModalPanel()));
479  }
480 }
481 
483 {
484  [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) exec];
485 }
486 
488 {
491  NSColor *nsColor;
492  const QColor::Spec spec = color.spec();
493  if (spec == QColor::Cmyk) {
494  nsColor = [NSColor colorWithDeviceCyan:color.cyanF()
495  magenta:color.magentaF()
496  yellow:color.yellowF()
497  black:color.blackF()
498  alpha:color.alphaF()];
499  } else {
500  nsColor = [NSColor colorWithCalibratedRed:color.redF()
501  green:color.greenF()
502  blue:color.blueF()
503  alpha:color.alphaF()];
504  }
505  [[theDelegate colorPanel] setColor:nsColor];
506 }
507 
509 
510 #endif
The QColor class provides colors based on RGB, HSV or CMYK values.
Definition: qcolor.h:67
const CGFloat ButtonSideMargin
virtual void interrupt()=0
Interrupts event dispatching; i.
void macStartInterceptNSPanelCtor()
Q_DECL_CONSTEXPR const T & qMin(const T &a, const T &b)
Definition: qglobal.h:1215
#define QT_END_NAMESPACE
This macro expands to.
Definition: qglobal.h:90
const CGFloat ButtonBottomMargin
void macStopInterceptNSPanelCtor()
static QAbstractEventDispatcher * instance(QThread *thread=0)
Returns a pointer to the event dispatcher object for the specified thread.
Spec
The type of color specified, either RGB, HSV, CMYK or HSL.
Definition: qcolor.h:70
#define SLOT(a)
Definition: qobjectdefs.h:226
void setCocoaPanelColor(const QColor &color)
The QWidget class is the base class of all user interface objects.
Definition: qwidget.h:150
The QString class provides a Unicode character string.
Definition: qstring.h:83
const CGFloat ButtonSpacing
#define Q_ASSERT(cond)
Definition: qglobal.h:1823
Q_DECL_CONSTEXPR const T & qMax(const T &a, const T &b)
Definition: qglobal.h:1217
#define Q_Q(Class)
Definition: qglobal.h:2483
NSWindow * window
#define QT_BEGIN_NAMESPACE
This macro expands to.
Definition: qglobal.h:89
The QColorDialog class provides a dialog widget for specifying colors.
Definition: qcolordialog.h:57
QColorDialogPrivate * mPriv
const CGFloat ButtonTopMargin
void openCocoaColorPanel(const QColor &initial, QWidget *parent, const QString &title, QColorDialog::ColorDialogOptions options)
const CGFloat ButtonMinWidth
const CGFloat ButtonMinHeight
#define QT_MANGLE_NAMESPACE(name)
Definition: qglobal.h:106
bool singleShot
This static function calls a slot after a given time interval.
Definition: qtimer.h:59
static const QMetaObjectPrivate * priv(const uint *data)
static bool native_modal_dialog_active
#define QT_USE_NAMESPACE
This macro expands to using QT_NAMESPACE if QT_NAMESPACE is defined and nothing otherwise.
Definition: qglobal.h:88
static const MacVersion MacintoshVersion
the version of the Macintosh operating system on which the application is run (Mac only)...
Definition: qglobal.h:1646
void finishOffWithCode:(NSInteger result)
#define Q_UNUSED(x)
Indicates to the compiler that the parameter with the specified name is not used in the body of a fun...
Definition: qglobal.h:1729
NSButton * macCreateButton(const char *text, NSView *superview)
#define text
Definition: qobjectdefs.h:80
float CGFloat
Spec spec() const
Returns how the color was specified.
Definition: qcolor.h:88