1/**************************************************************************** 2** 3** Copyright (C) 2015 The Qt Company Ltd. 4** Contact: http://www.qt.io/licensing/ 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 The Qt Company. For licensing terms 14** and conditions see http://www.qt.io/terms-conditions. For further 15** information use the contact form at http://www.qt.io/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 or version 3 as published by the Free 20** Software Foundation and appearing in the file LICENSE.LGPLv21 and 21** LICENSE.LGPLv3 included in the packaging of this file. Please review the 22** following information to ensure the GNU Lesser General Public License 23** requirements will be met: https://www.gnu.org/licenses/lgpl.html and 24** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. 25** 26** As a special exception, The Qt Company gives you certain additional 27** rights. These rights are described in The Qt Company LGPL Exception 28** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. 29** 30** GNU General Public License Usage 31** Alternatively, this file may be used under the terms of the GNU 32** General Public License version 3.0 as published by the Free Software 33** Foundation and appearing in the file LICENSE.GPL included in the 34** packaging of this file. Please review the following information to 35** ensure the GNU General Public License version 3.0 requirements will be 36** met: http://www.gnu.org/copyleft/gpl.html. 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 55typedef 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 66QT_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 MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7 124 if (QSysInfo::MacintoshVersion >= QSysInfo::MV_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{ 149 QMacCocoaAutoReleasePool pool; 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, 226 ButtonBottomMargin }, 227 { ButtonWidth, ButtonHeight } }; 228 [mOkButton setFrame:okRect]; 229 [mOkButton setNeedsDisplay:YES]; 230 231 NSRect cancelRect = { { okRect.origin.x - ButtonSpacing - ButtonWidth, 232 ButtonBottomMargin }, 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 [[NSApplication sharedApplication] 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{ 346 QBoolBlocker nativeDialogOnTop(QApplicationPrivate::native_modal_dialog_active); 347 QMacCocoaAutoReleasePool pool; 348 mDialogIsExecuting = true; 349 bool modalEnded = false; 350 while (!modalEnded) { 351#ifndef QT_NO_EXCEPTIONS 352 @try { 353 [[NSApplication sharedApplication] 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 [[NSApplication sharedApplication] runModalForWindow:mColorPanel]; 362 modalEnded = true; 363#endif 364 } 365 366 QAbstractEventDispatcher::instance()->interrupt(); 367 if (mResultCode == NSCancelButton) 368 mPriv->colorDialog()->reject(); 369 else 370 mPriv->colorDialog()->accept(); 371} 372 373@end 374 375QT_BEGIN_NAMESPACE 376 377extern void macStartInterceptNSPanelCtor(); 378extern void macStopInterceptNSPanelCtor(); 379extern NSButton *macCreateButton(const char *text, NSView *superview); 380 381void QColorDialogPrivate::openCocoaColorPanel(const QColor &initial, 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 385 QMacCocoaAutoReleasePool pool; 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) 411 macStartInterceptNSPanelCtor(); 412 NSColorPanel *colorPanel = [NSColorPanel sharedColorPanel]; 413 if (hackColorPanel) 414 macStopInterceptNSPanelCtor(); 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 457void QColorDialogPrivate::closeCocoaColorPanel() 458{ 459 [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) onCancelClicked]; 460} 461 462void QColorDialogPrivate::releaseCocoaColorPanelDelegate() 463{ 464 [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) release]; 465} 466 467void QColorDialogPrivate::mac_nativeDialogModalHelp() 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 // [NSApplication run] runs (timers are handeled special in cocoa). If NSApplication 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 [NSApplication 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 482void QColorDialogPrivate::_q_macRunNativeAppModalPanel() 483{ 484 [static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate) exec]; 485} 486 487void QColorDialogPrivate::setCocoaPanelColor(const QColor &color) 488{ 489 QMacCocoaAutoReleasePool pool; 490 QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *theDelegate = static_cast<QT_MANGLE_NAMESPACE(QCocoaColorPanelDelegate) *>(delegate); 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 508QT_END_NAMESPACE 509 510#endif 511