Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qcocoacolordialoghelper.mm
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <AppKit/AppKit.h>
5
6#include <QtCore/qdebug.h>
7#include <QtCore/qtimer.h>
8#include <qpa/qplatformtheme.h>
9
11#include "qcocoahelpers.h"
13#include "private/qcoregraphics_p.h"
14
16
17@interface QT_MANGLE_NAMESPACE(QNSColorPanelDelegate) : NSObject<NSWindowDelegate, QNSPanelDelegate>
20- (void)finishOffWithCode:(NSInteger)code;
21@end
22
24
25@implementation QNSColorPanelDelegate {
26 @public
27 NSColorPanel *mColorPanel;
36}
37
38- (instancetype)init
39{
40 self = [super init];
41 mColorPanel = [NSColorPanel sharedColorPanel];
42 mHelper = nullptr;
44 mPanelButtons = nil;
45 mResultCode = NSModalResponseCancel;
46 mDialogIsExecuting = false;
47 mResultSet = false;
49
50 [mColorPanel setRestorable:NO];
51
52 [[NSNotificationCenter defaultCenter] addObserver:self
53 selector:@selector(colorChanged:)
54 name:NSColorPanelColorDidChangeNotification
55 object:mColorPanel];
56
57 [[NSNotificationCenter defaultCenter] addObserver:self
58 selector:@selector(windowWillClose:)
59 name:NSWindowWillCloseNotification
60 object:mColorPanel];
61
62 [mColorPanel retain];
63 return self;
64}
65
66- (void)dealloc
67{
68 [mStolenContentView release];
69 [mColorPanel setDelegate:nil];
70 [[NSNotificationCenter defaultCenter] removeObserver:self];
71
72 [super dealloc];
73}
74
75- (void)setDialogHelper:(QCocoaColorDialogHelper *)helper
76{
77 mHelper = helper;
78
79 if (mHelper->options()->testOption(QColorDialogOptions::NoButtons)) {
80 [self restoreOriginalContentView];
81 } else if (!mStolenContentView) {
82 // steal the color panel's contents view
83 mStolenContentView = mColorPanel.contentView;
84 [mStolenContentView retain];
85 mColorPanel.contentView = nil;
86
87 // create a new content view and add the stolen one as a subview
88 mPanelButtons = [[QNSPanelContentsWrapper alloc] initWithPanelDelegate:self];
89 [mPanelButtons addSubview:mStolenContentView];
90 mColorPanel.contentView = mPanelButtons;
91 mColorPanel.defaultButtonCell = mPanelButtons.okButton.cell;
92 }
93}
94
95- (void)closePanel
96{
97 [mColorPanel close];
98}
99
100- (void)colorChanged:(NSNotification *)notification
101{
102 Q_UNUSED(notification);
103 [self updateQtColor];
104}
105
106- (void)windowWillClose:(NSNotification *)notification
107{
108 Q_UNUSED(notification);
110 mClosingDueToKnownButton = true; // prevent repeating emit
112 }
113}
114
115- (void)restoreOriginalContentView
116{
117 if (mStolenContentView) {
118 // return stolen stuff to its rightful owner
119 [mStolenContentView removeFromSuperview];
120 [mColorPanel setContentView:mStolenContentView];
121 [mStolenContentView release];
122 mStolenContentView = nil;
123 [mPanelButtons release];
124 mPanelButtons = nil;
125 }
126}
127
128- (void)onOkClicked
129{
131 [mColorPanel close];
132 [self updateQtColor];
133 [self finishOffWithCode:NSModalResponseOK];
134}
135
136- (void)onCancelClicked
137{
138 if (mPanelButtons) {
140 [mColorPanel close];
141 mQtColor = QColor();
142 [self finishOffWithCode:NSModalResponseCancel];
143 }
144}
145
146- (void)updateQtColor
147{
148 // Discard the color space and pass the color components to QColor. This
149 // is a good option as long as QColor is color-unmanaged: we preserve the
150 // exact RGB value from the color picker, which is predictable. Further,
151 // painting with the color will reproduce the same color on-screen, as
152 // long as the the same screen is used for selecting the color.
153 NSColor *componentColor = [[mColorPanel color] colorUsingType:NSColorTypeComponentBased];
154 switch (componentColor.colorSpace.colorSpaceModel)
155 {
156 case NSColorSpaceModelGray: {
157 CGFloat white = 0, alpha = 0;
158 [componentColor getWhite:&white alpha:&alpha];
159 mQtColor.setRgbF(white, white, white, alpha);
160 } break;
161 case NSColorSpaceModelRGB: {
162 CGFloat red = 0, green = 0, blue = 0, alpha = 0;
163 [componentColor getRed:&red green:&green blue:&blue alpha:&alpha];
165 } break;
166 case NSColorSpaceModelCMYK: {
167 CGFloat cyan = 0, magenta = 0, yellow = 0, black = 0, alpha = 0;
168 [componentColor getCyan:&cyan magenta:&magenta yellow:&yellow black:&black alpha:&alpha];
169 mQtColor.setCmykF(cyan, magenta, yellow, black, alpha);
170 } break;
171 default:
172 qWarning("QNSColorPanelDelegate: Unsupported color space model");
173 break;
174 }
175
176 if (mHelper)
178}
179
180- (void)showModelessPanel
181{
182 mDialogIsExecuting = false;
183 mResultSet = false;
185 // Make this an asynchronous call, so the panel is made key only
186 // in the next event loop run. This is to make sure that by
187 // the time the modal loop is run in runModalForWindow below,
188 // which internally also sets the panel to key window,
189 // the panel is not yet key, and the NSApp still has the right
190 // reference to the _previousKeyWindow. Otherwise both NSApp.key
191 // and NSApp._previousKeyWindow would wrongly point to the panel,
192 // loosing any reference to the window that was key before.
193 dispatch_async(dispatch_get_main_queue(), ^{
194 [mColorPanel makeKeyAndOrderFront:mColorPanel];
195 });
196}
197
198- (BOOL)runApplicationModalPanel
199{
200 mDialogIsExecuting = true;
201 [mColorPanel setDelegate:self];
202 [mColorPanel setContinuous:YES];
203 // Call processEvents in case the event dispatcher has been interrupted, and needs to do
204 // cleanup of modal sessions. Do this before showing the native dialog, otherwise it will
205 // close down during the cleanup.
207
208 // Make sure we don't interrupt the runModalForWindow call.
210
211 [NSApp runModalForWindow:mColorPanel];
212 mDialogIsExecuting = false;
213
214 // Wake up the event dispatcher so it can check whether the
215 // current event loop should continue spinning or not.
217
218 return (mResultCode == NSModalResponseOK);
219}
220
221- (QPlatformDialogHelper::DialogCode)dialogResultCode
222{
224}
225
226- (BOOL)windowShouldClose:(id)window
227{
229 if (!mPanelButtons)
230 [self updateQtColor];
231 if (mDialogIsExecuting) {
232 [self finishOffWithCode:NSModalResponseCancel];
233 } else {
234 mResultSet = true;
235 if (mHelper)
237 }
238 return true;
239}
240
241- (void)finishOffWithCode:(NSInteger)code
242{
244 if (mDialogIsExecuting) {
245 // We stop the current modal event loop. The control
246 // will then return inside -(void)exec below.
247 // It's important that the modal event loop is stopped before
248 // we accept/reject QColorDialog, since QColorDialog has its
249 // own event loop that needs to be stopped last.
250 [NSApp stopModalWithCode:code];
251 } else {
252 // Since we are not in a modal event loop, we can safely close
253 // down QColorDialog
254 // Calling accept() or reject() can in turn call closeCocoaColorPanel.
255 // This check will prevent any such recursion.
256 if (!mResultSet) {
257 mResultSet = true;
258 if (mResultCode == NSModalResponseCancel) {
260 } else {
262 }
263 }
264 }
265}
266
267@end
268
270
272{
273public:
275 {
276 mDelegate = [[QNSColorPanelDelegate alloc] init];
277 }
278
280 {
281 [mDelegate release];
282 }
283
285 {
286 [mDelegate setDialogHelper:helper];
287 }
288
290 {
291 if (mDelegate->mHelper == helper)
292 mDelegate->mHelper = nullptr;
293 }
294
295 bool exec()
296 {
297 // Note: If NSApp is not running (which is the case if e.g a top-most
298 // QEventLoop has been interrupted, and the second-most event loop has not
299 // yet been reactivated (regardless if [NSApp run] is still on the stack)),
300 // showing a native modal dialog will fail.
301 return [mDelegate runApplicationModalPanel];
302 }
303
304 bool show(Qt::WindowModality windowModality, QWindow *parent)
305 {
306 Q_UNUSED(parent);
307 if (windowModality != Qt::ApplicationModal)
308 [mDelegate showModelessPanel];
309 // no need to show a Qt::ApplicationModal dialog here, because it will be shown in runApplicationModalPanel
310 return true;
311 }
312
313 void hide()
314 {
315 [mDelegate closePanel];
316 }
317
319 {
320 return mDelegate->mQtColor;
321 }
322
324 {
325 // make sure that if ShowAlphaChannel option is set then also setShowsAlpha
326 // needs to be set, otherwise alpha value is omitted
327 if (color.alpha() < 255)
328 [mDelegate->mColorPanel setShowsAlpha:YES];
329
330 NSColor *nsColor;
331 const QColor::Spec spec = color.spec();
332 if (spec == QColor::Cmyk) {
333 nsColor = [NSColor colorWithDeviceCyan:color.cyanF()
334 magenta:color.magentaF()
335 yellow:color.yellowF()
336 black:color.blackF()
337 alpha:color.alphaF()];
338 } else {
339 nsColor = [NSColor colorWithCalibratedRed:color.redF()
340 green:color.greenF()
341 blue:color.blueF()
342 alpha:color.alphaF()];
343 }
344 mDelegate->mQtColor = color;
345 [mDelegate->mColorPanel setColor:nsColor];
346 }
347
348private:
349 QNSColorPanelDelegate *mDelegate;
350};
351
352Q_GLOBAL_STATIC(QCocoaColorPanel, sharedColorPanel)
353
357
359{
360 sharedColorPanel()->cleanup(this);
361}
362
364{
365 if (sharedColorPanel()->exec())
366 emit accept();
367 else
368 emit reject();
369}
370
371bool QCocoaColorDialogHelper::show(Qt::WindowFlags, Qt::WindowModality windowModality, QWindow *parent)
372{
373 if (windowModality == Qt::ApplicationModal)
374 windowModality = Qt::WindowModal;
375 // Workaround for Apple rdar://25792119: If you invoke
376 // -setShowsAlpha: multiple times before showing the color
377 // picker, its height grows irrevocably. Instead, only
378 // invoke it once, when we show the dialog.
379 [[NSColorPanel sharedColorPanel] setShowsAlpha:
380 options()->testOption(QColorDialogOptions::ShowAlphaChannel)];
381
382 sharedColorPanel()->init(this);
383 return sharedColorPanel()->show(windowModality, parent);
384}
385
387{
388 sharedColorPanel()->hide();
389}
390
392{
393 sharedColorPanel()->init(this);
394 sharedColorPanel()->setCurrentColor(color);
395}
396
398{
399 return sharedColorPanel()->currentColor();
400}
401
bool show(Qt::WindowFlags windowFlags, Qt::WindowModality windowModality, QWindow *parent) override
void setCurrentColor(const QColor &) override
QColor currentColor() const override
void cleanup(QCocoaColorDialogHelper *helper)
void init(QCocoaColorDialogHelper *helper)
bool show(Qt::WindowModality windowModality, QWindow *parent)
void setCurrentColor(const QColor &color)
static void clearCurrentThreadCocoaEventDispatcherInterruptFlag()
The QColor class provides colors based on RGB, HSV or CMYK values.
Definition qcolor.h:31
Spec
The type of color specified, either RGB, extended RGB, HSV, CMYK or HSL.
Definition qcolor.h:35
@ Cmyk
Definition qcolor.h:35
void setRgbF(float r, float g, float b, float a=1.0)
Sets the color channels of this color to r (red), g (green), b (blue) and a (alpha,...
Definition qcolor.cpp:1317
void setCmykF(float c, float m, float y, float k, float a=1.0)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition qcolor.cpp:2712
static QAbstractEventDispatcher * eventDispatcher()
Returns a pointer to the event dispatcher object for the main thread.
@ ExcludeSocketNotifiers
Definition qeventloop.h:28
@ ExcludeUserInputEvents
Definition qeventloop.h:27
QObject * parent() const
Returns a pointer to the parent object.
Definition qobject.h:346
const QSharedPointer< QColorDialogOptions > & options() const
void currentColorChanged(const QColor &color)
The QPlatformDialogHelper class allows for platform-specific customization of dialogs.
\inmodule QtGui
Definition qwindow.h:63
void colorChanged()
Combined button and popup list for selecting options.
WindowModality
@ WindowModal
@ ApplicationModal
@ cyan
Definition qnamespace.h:38
@ white
Definition qnamespace.h:31
@ magenta
Definition qnamespace.h:39
@ yellow
Definition qnamespace.h:40
QString self
Definition language.cpp:58
BOOL mClosingDueToKnownButton
QNSPanelContentsWrapper * mPanelButtons
NSInteger mResultCode
QColor mQtColor
BOOL mResultSet
QCocoaColorDialogHelper * mHelper
BOOL mDialogIsExecuting
NSView * mStolenContentView
float CGFloat
long NSInteger
#define QT_NAMESPACE_ALIAS_OBJC_CLASS(__KLASS__)
Definition qcore_mac_p.h:58
#define qApp
DBusConnection const char DBusError DBusBusType DBusError return DBusConnection DBusHandleMessageFunction void DBusFreeFunction return DBusConnection return DBusConnection return const char DBusError return DBusConnection DBusMessage dbus_uint32_t return DBusConnection dbus_bool_t DBusConnection DBusAddWatchFunction DBusRemoveWatchFunction DBusWatchToggledFunction void DBusFreeFunction return DBusConnection DBusDispatchStatusFunction void DBusFreeFunction DBusTimeout return DBusTimeout return DBusWatch return DBusWatch unsigned int return DBusError const DBusError return const DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessage return DBusMessageIter int const void return DBusMessageIter DBusMessageIter return DBusMessageIter void DBusMessageIter void int return DBusMessage DBusMessageIter return DBusMessageIter return DBusMessageIter DBusMessageIter const char const char const char const char return DBusMessage return DBusMessage const char return DBusMessage dbus_bool_t return DBusMessage dbus_uint32_t return DBusMessage void
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
#define qWarning
Definition qlogging.h:166
GLuint color
[2]
GLbyte GLbyte blue
Definition qopenglext.h:385
GLfloat GLfloat GLfloat alpha
Definition qopenglext.h:418
GLbyte green
Definition qopenglext.h:385
static QT_BEGIN_NAMESPACE void init(QTextBoundaryFinder::BoundaryType type, QStringView str, QCharAttributes *attributes)
#define emit
#define Q_UNUSED(x)
aWidget window() -> setWindowTitle("New Window Title")
[2]
QGraphicsSvgItem * red
QGraphicsSvgItem * black