| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtGui module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include <QtCore/qdebug.h> |
| #include <QtCore/qtimer.h> |
| #include <qpa/qplatformtheme.h> |
| |
| #include "qcocoacolordialoghelper.h" |
| #include "qcocoahelpers.h" |
| #include "qcocoaeventdispatcher.h" |
| |
| #import <AppKit/AppKit.h> |
| |
| QT_USE_NAMESPACE |
| |
| @interface QT_MANGLE_NAMESPACE(QNSColorPanelDelegate) : NSObject<NSWindowDelegate, QNSPanelDelegate> |
| - (void)restoreOriginalContentView; |
| - (void)updateQtColor; |
| - (void)finishOffWithCode:(NSInteger)code; |
| @end |
| |
| QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate); |
| |
| @implementation QNSColorPanelDelegate { |
| @public |
| NSColorPanel *mColorPanel; |
| QCocoaColorDialogHelper *mHelper; |
| NSView *mStolenContentView; |
| QNSPanelContentsWrapper *mPanelButtons; |
| QColor mQtColor; |
| NSInteger mResultCode; |
| BOOL mDialogIsExecuting; |
| BOOL mResultSet; |
| BOOL mClosingDueToKnownButton; |
| } |
| |
| - (instancetype)init |
| { |
| self = [super init]; |
| mColorPanel = [NSColorPanel sharedColorPanel]; |
| mHelper = nullptr; |
| mStolenContentView = nil; |
| mPanelButtons = nil; |
| mResultCode = NSModalResponseCancel; |
| mDialogIsExecuting = false; |
| mResultSet = false; |
| mClosingDueToKnownButton = false; |
| |
| [mColorPanel setRestorable:NO]; |
| |
| [[NSNotificationCenter defaultCenter] addObserver:self |
| selector:@selector(colorChanged:) |
| name:NSColorPanelColorDidChangeNotification |
| object:mColorPanel]; |
| |
| [[NSNotificationCenter defaultCenter] addObserver:self |
| selector:@selector(windowWillClose:) |
| name:NSWindowWillCloseNotification |
| object:mColorPanel]; |
| |
| [mColorPanel retain]; |
| return self; |
| } |
| |
| - (void)dealloc |
| { |
| [mStolenContentView release]; |
| [mColorPanel setDelegate:nil]; |
| [[NSNotificationCenter defaultCenter] removeObserver:self]; |
| |
| [super dealloc]; |
| } |
| |
| - (void)setDialogHelper:(QCocoaColorDialogHelper *)helper |
| { |
| mHelper = helper; |
| |
| if (mHelper->options()->testOption(QColorDialogOptions::NoButtons)) { |
| [self restoreOriginalContentView]; |
| } else if (!mStolenContentView) { |
| // steal the color panel's contents view |
| mStolenContentView = mColorPanel.contentView; |
| [mStolenContentView retain]; |
| mColorPanel.contentView = nil; |
| |
| // create a new content view and add the stolen one as a subview |
| mPanelButtons = [[QNSPanelContentsWrapper alloc] initWithPanelDelegate:self]; |
| [mPanelButtons addSubview:mStolenContentView]; |
| mColorPanel.contentView = mPanelButtons; |
| mColorPanel.defaultButtonCell = mPanelButtons.okButton.cell; |
| } |
| } |
| |
| - (void)closePanel |
| { |
| [mColorPanel close]; |
| } |
| |
| - (void)colorChanged:(NSNotification *)notification |
| { |
| Q_UNUSED(notification); |
| [self updateQtColor]; |
| } |
| |
| - (void)windowWillClose:(NSNotification *)notification |
| { |
| Q_UNUSED(notification); |
| if (mPanelButtons && mHelper && !mClosingDueToKnownButton) { |
| mClosingDueToKnownButton = true; // prevent repeating emit |
| emit mHelper->reject(); |
| } |
| } |
| |
| - (void)restoreOriginalContentView |
| { |
| if (mStolenContentView) { |
| // return stolen stuff to its rightful owner |
| [mStolenContentView removeFromSuperview]; |
| [mColorPanel setContentView:mStolenContentView]; |
| [mStolenContentView release]; |
| mStolenContentView = nil; |
| [mPanelButtons release]; |
| mPanelButtons = nil; |
| } |
| } |
| |
| - (void)onOkClicked |
| { |
| mClosingDueToKnownButton = true; |
| [mColorPanel close]; |
| [self updateQtColor]; |
| [self finishOffWithCode:NSModalResponseOK]; |
| } |
| |
| - (void)onCancelClicked |
| { |
| if (mPanelButtons) { |
| mClosingDueToKnownButton = true; |
| [mColorPanel close]; |
| mQtColor = QColor(); |
| [self finishOffWithCode:NSModalResponseCancel]; |
| } |
| } |
| |
| - (void)updateQtColor |
| { |
| NSColor *color = [mColorPanel color]; |
| NSString *colorSpaceName = [color colorSpaceName]; |
| if (colorSpaceName == NSDeviceCMYKColorSpace) { |
| CGFloat cyan = 0, magenta = 0, yellow = 0, black = 0, alpha = 0; |
| [color getCyan:&cyan magenta:&magenta yellow:&yellow black:&black alpha:&alpha]; |
| mQtColor.setCmykF(cyan, magenta, yellow, black, alpha); |
| } else if (colorSpaceName == NSCalibratedRGBColorSpace || colorSpaceName == NSDeviceRGBColorSpace) { |
| CGFloat red = 0, green = 0, blue = 0, alpha = 0; |
| [color getRed:&red green:&green blue:&blue alpha:&alpha]; |
| mQtColor.setRgbF(red, green, blue, alpha); |
| } else if (colorSpaceName == NSNamedColorSpace) { |
| NSColor *tmpColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; |
| CGFloat red = 0, green = 0, blue = 0, alpha = 0; |
| [tmpColor getRed:&red green:&green blue:&blue alpha:&alpha]; |
| mQtColor.setRgbF(red, green, blue, alpha); |
| } else { |
| NSColorSpace *colorSpace = [color colorSpace]; |
| if ([colorSpace colorSpaceModel] == NSCMYKColorSpaceModel && [color numberOfComponents] == 5){ |
| CGFloat components[5]; |
| [color getComponents:components]; |
| mQtColor.setCmykF(components[0], components[1], components[2], components[3], components[4]); |
| } else { |
| NSColor *tmpColor = [color colorUsingColorSpaceName:NSCalibratedRGBColorSpace]; |
| CGFloat red = 0, green = 0, blue = 0, alpha = 0; |
| [tmpColor getRed:&red green:&green blue:&blue alpha:&alpha]; |
| mQtColor.setRgbF(red, green, blue, alpha); |
| } |
| } |
| if (mHelper) |
| emit mHelper->currentColorChanged(mQtColor); |
| } |
| |
| - (void)showModelessPanel |
| { |
| mDialogIsExecuting = false; |
| mResultSet = false; |
| mClosingDueToKnownButton = false; |
| [mColorPanel makeKeyAndOrderFront:mColorPanel]; |
| } |
| |
| - (BOOL)runApplicationModalPanel |
| { |
| mDialogIsExecuting = true; |
| [mColorPanel setDelegate:self]; |
| [mColorPanel setContinuous:YES]; |
| // Call processEvents in case the event dispatcher has been interrupted, and needs to do |
| // cleanup of modal sessions. Do this before showing the native dialog, otherwise it will |
| // close down during the cleanup. |
| qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers); |
| |
| // Make sure we don't interrupt the runModalForWindow call. |
| QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag(); |
| |
| [NSApp runModalForWindow:mColorPanel]; |
| mDialogIsExecuting = false; |
| return (mResultCode == NSModalResponseOK); |
| } |
| |
| - (QPlatformDialogHelper::DialogCode)dialogResultCode |
| { |
| return (mResultCode == NSModalResponseOK) ? QPlatformDialogHelper::Accepted : QPlatformDialogHelper::Rejected; |
| } |
| |
| - (BOOL)windowShouldClose:(id)window |
| { |
| Q_UNUSED(window); |
| if (!mPanelButtons) |
| [self updateQtColor]; |
| if (mDialogIsExecuting) { |
| [self finishOffWithCode:NSModalResponseCancel]; |
| } else { |
| mResultSet = true; |
| if (mHelper) |
| emit mHelper->reject(); |
| } |
| return true; |
| } |
| |
| - (void)finishOffWithCode:(NSInteger)code |
| { |
| mResultCode = code; |
| if (mDialogIsExecuting) { |
| // We stop the current modal event loop. The control |
| // will then return inside -(void)exec below. |
| // It's important that the modal event loop is stopped before |
| // we accept/reject QColorDialog, since QColorDialog has its |
| // own event loop that needs to be stopped last. |
| [NSApp stopModalWithCode:code]; |
| } else { |
| // Since we are not in a modal event loop, we can safely close |
| // down QColorDialog |
| // Calling accept() or reject() can in turn call closeCocoaColorPanel. |
| // This check will prevent any such recursion. |
| if (!mResultSet) { |
| mResultSet = true; |
| if (mResultCode == NSModalResponseCancel) { |
| emit mHelper->reject(); |
| } else { |
| emit mHelper->accept(); |
| } |
| } |
| } |
| } |
| |
| @end |
| |
| QT_BEGIN_NAMESPACE |
| |
| class QCocoaColorPanel |
| { |
| public: |
| QCocoaColorPanel() |
| { |
| mDelegate = [[QNSColorPanelDelegate alloc] init]; |
| } |
| |
| ~QCocoaColorPanel() |
| { |
| [mDelegate release]; |
| } |
| |
| void init(QCocoaColorDialogHelper *helper) |
| { |
| [mDelegate setDialogHelper:helper]; |
| } |
| |
| void cleanup(QCocoaColorDialogHelper *helper) |
| { |
| if (mDelegate->mHelper == helper) |
| mDelegate->mHelper = nullptr; |
| } |
| |
| bool exec() |
| { |
| // Note: If NSApp is not running (which is the case if e.g a top-most |
| // QEventLoop has been interrupted, and the second-most event loop has not |
| // yet been reactivated (regardless if [NSApp run] is still on the stack)), |
| // showing a native modal dialog will fail. |
| return [mDelegate runApplicationModalPanel]; |
| } |
| |
| bool show(Qt::WindowModality windowModality, QWindow *parent) |
| { |
| Q_UNUSED(parent); |
| if (windowModality != Qt::WindowModal) |
| [mDelegate showModelessPanel]; |
| // no need to show a Qt::WindowModal dialog here, because it's necessary to call exec() in that case |
| return true; |
| } |
| |
| void hide() |
| { |
| [mDelegate closePanel]; |
| } |
| |
| QColor currentColor() const |
| { |
| return mDelegate->mQtColor; |
| } |
| |
| void setCurrentColor(const QColor &color) |
| { |
| // make sure that if ShowAlphaChannel option is set then also setShowsAlpha |
| // needs to be set, otherwise alpha value is omitted |
| if (color.alpha() < 255) |
| [mDelegate->mColorPanel setShowsAlpha:YES]; |
| |
| NSColor *nsColor; |
| const QColor::Spec spec = color.spec(); |
| if (spec == QColor::Cmyk) { |
| nsColor = [NSColor colorWithDeviceCyan:color.cyanF() |
| magenta:color.magentaF() |
| yellow:color.yellowF() |
| black:color.blackF() |
| alpha:color.alphaF()]; |
| } else { |
| nsColor = [NSColor colorWithCalibratedRed:color.redF() |
| green:color.greenF() |
| blue:color.blueF() |
| alpha:color.alphaF()]; |
| } |
| mDelegate->mQtColor = color; |
| [mDelegate->mColorPanel setColor:nsColor]; |
| } |
| |
| private: |
| QNSColorPanelDelegate *mDelegate; |
| }; |
| |
| Q_GLOBAL_STATIC(QCocoaColorPanel, sharedColorPanel) |
| |
| QCocoaColorDialogHelper::QCocoaColorDialogHelper() |
| { |
| } |
| |
| QCocoaColorDialogHelper::~QCocoaColorDialogHelper() |
| { |
| sharedColorPanel()->cleanup(this); |
| } |
| |
| void QCocoaColorDialogHelper::exec() |
| { |
| if (sharedColorPanel()->exec()) |
| emit accept(); |
| else |
| emit reject(); |
| } |
| |
| bool QCocoaColorDialogHelper::show(Qt::WindowFlags, Qt::WindowModality windowModality, QWindow *parent) |
| { |
| if (windowModality == Qt::WindowModal) |
| windowModality = Qt::ApplicationModal; |
| |
| // Workaround for Apple rdar://25792119: If you invoke |
| // -setShowsAlpha: multiple times before showing the color |
| // picker, its height grows irrevocably. Instead, only |
| // invoke it once, when we show the dialog. |
| [[NSColorPanel sharedColorPanel] setShowsAlpha: |
| options()->testOption(QColorDialogOptions::ShowAlphaChannel)]; |
| |
| sharedColorPanel()->init(this); |
| return sharedColorPanel()->show(windowModality, parent); |
| } |
| |
| void QCocoaColorDialogHelper::hide() |
| { |
| sharedColorPanel()->hide(); |
| } |
| |
| void QCocoaColorDialogHelper::setCurrentColor(const QColor &color) |
| { |
| sharedColorPanel()->init(this); |
| sharedColorPanel()->setCurrentColor(color); |
| } |
| |
| QColor QCocoaColorDialogHelper::currentColor() const |
| { |
| return sharedColorPanel()->currentColor(); |
| } |
| |
| QT_END_NAMESPACE |