Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcocoafontdialoghelper.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// Qt-Security score:significant reason:default
4
5#include <AppKit/AppKit.h>
6
7#include <QtCore/qtimer.h>
8#include <QtGui/qfontdatabase.h>
9#include <qpa/qplatformtheme.h>
10
11#include <private/qfont_p.h>
12#include <private/qfontengine_p.h>
13#include <private/qfontengine_coretext_p.h>
14
16#include "qcocoahelpers.h"
18#include "qnsview.h"
19
20#if !CGFLOAT_DEFINED
21typedef float CGFloat; // Should only not be defined on 32-bit platforms
22#endif
23
24QT_USE_NAMESPACE
25
26static QFont qfontForCocoaFont(NSFont *cocoaFont, const QFont &resolveFont)
27{
28 QFont newFont;
29 if (cocoaFont) {
30 int pSize = qRound([cocoaFont pointSize]);
31 QCFType<CTFontDescriptorRef> font(CTFontCopyFontDescriptor((CTFontRef)cocoaFont));
32 QString family(QCFString((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontFamilyNameAttribute)));
33 QString style(QCFString(((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontStyleNameAttribute))));
34
35 newFont = QFontDatabase::font(family, style, pSize);
36 newFont.setUnderline(resolveFont.underline());
37 newFont.setStrikeOut(resolveFont.strikeOut());
38 }
39 return newFont;
40}
41
42@interface QT_MANGLE_NAMESPACE(QNSFontPanelDelegate) : NSObject<NSWindowDelegate, QNSPanelDelegate>
43- (void)restoreOriginalContentView;
44- (void)updateQtFont;
45- (void)changeFont:(id)sender;
46- (void)finishOffWithCode:(NSInteger)code;
47@end
48
49QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate);
50
51@implementation QNSFontPanelDelegate {
52 @public
53 NSFontPanel *mFontPanel;
54 QCocoaFontDialogHelper *mHelper;
55 NSView *mStolenContentView;
56 QNSPanelContentsWrapper *mPanelButtons;
57 QFont mQtFont;
58 NSInteger mResultCode;
59 BOOL mDialogIsExecuting;
60 BOOL mResultSet;
61}
62
63- (instancetype)init
64{
65 if ((self = [super init])) {
66 mFontPanel = [NSFontPanel sharedFontPanel];
67 mHelper = nullptr;
68 mStolenContentView = nil;
69 mPanelButtons = nil;
70 mResultCode = NSModalResponseCancel;
71 mDialogIsExecuting = false;
72 mResultSet = false;
73
74 [mFontPanel setRestorable:NO];
75 [mFontPanel setDelegate:self];
76
77 [mFontPanel retain];
78 }
79 return self;
80}
81
82- (void)dealloc
83{
84 [mStolenContentView release];
85 [mFontPanel setDelegate:nil];
86 [[NSNotificationCenter defaultCenter] removeObserver:self];
87
88 [super dealloc];
89}
90
91- (void)setDialogHelper:(QCocoaFontDialogHelper *)helper
92{
93 mHelper = helper;
94
95 [mFontPanel setTitle:helper->options()->windowTitle().toNSString()];
96
97 if (mHelper->options()->testOption(QFontDialogOptions::NoButtons)) {
98 [self restoreOriginalContentView];
99 } else if (!mStolenContentView) {
100 // steal the font panel's contents view
101 mStolenContentView = mFontPanel.contentView;
102 [mStolenContentView retain];
103 mFontPanel.contentView = nil;
104
105 // create a new content view and add the stolen one as a subview
106 mPanelButtons = [[QNSPanelContentsWrapper alloc] initWithPanelDelegate:self];
107 [mPanelButtons addSubview:mStolenContentView];
108 mPanelButtons.panelContentsMargins = NSEdgeInsetsMake(0, 0, 7, 0);
109 mFontPanel.contentView = mPanelButtons;
110 mFontPanel.defaultButtonCell = mPanelButtons.okButton.cell;
111 }
112}
113
114- (void)closePanel
115{
116 [mFontPanel close];
117}
118
119- (void)restoreOriginalContentView
120{
121 if (mStolenContentView) {
122 // return stolen stuff to its rightful owner
123 [mStolenContentView removeFromSuperview];
124 [mFontPanel setContentView:mStolenContentView];
125 mStolenContentView = nil;
126 [mPanelButtons release];
127 mPanelButtons = nil;
128 }
129}
130
131- (void)onOkClicked
132{
133 [mFontPanel close];
134 [self finishOffWithCode:NSModalResponseOK];
135}
136
137- (void)onCancelClicked
138{
139 if (mPanelButtons) {
140 [mFontPanel close];
141 mQtFont = QFont();
142 [self finishOffWithCode:NSModalResponseCancel];
143 }
144}
145
146- (void)changeFont:(id)sender
147{
148 Q_UNUSED(sender);
149 [self updateQtFont];
150}
151
152- (void)updateQtFont
153{
154 // Get selected font
155 NSFontManager *fontManager = [NSFontManager sharedFontManager];
156 NSFont *selectedFont = [fontManager selectedFont];
157 if (!selectedFont) {
158 selectedFont = [NSFont systemFontOfSize:[NSFont systemFontSize]];
159 }
160 NSFont *panelFont = [fontManager convertFont:selectedFont];
161 mQtFont = qfontForCocoaFont(panelFont, mQtFont);
162
163 if (mHelper)
164 emit mHelper->currentFontChanged(mQtFont);
165}
166
167- (void)showModelessPanel
168{
169 mDialogIsExecuting = false;
170 mResultSet = false;
171 // Make this an asynchronous call, so the panel is made key only
172 // in the next event loop run. This is to make sure that by
173 // the time the modal loop is run in runModalForWindow below,
174 // which internally also sets the panel to key window,
175 // the panel is not yet key, and the NSApp still has the right
176 // reference to the _previousKeyWindow. Otherwise both NSApp.key
177 // and NSApp._previousKeyWindow would wrongly point to the panel,
178 // loosing any reference to the window that was key before.
179 dispatch_async(dispatch_get_main_queue(), ^{
180 [mFontPanel makeKeyAndOrderFront:mFontPanel];
181 });
182}
183
184- (BOOL)runApplicationModalPanel
185{
186 mDialogIsExecuting = true;
187 // Call processEvents in case the event dispatcher has been interrupted, and needs to do
188 // cleanup of modal sessions. Do this before showing the native dialog, otherwise it will
189 // close down during the cleanup.
190 qApp->processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
191
192 // Make sure we don't interrupt the runModalForWindow call.
193 QCocoaEventDispatcher::clearCurrentThreadCocoaEventDispatcherInterruptFlag();
194
195 [NSApp runModalForWindow:mFontPanel];
196 mDialogIsExecuting = false;
197
198 // Wake up the event dispatcher so it can check whether the
199 // current event loop should continue spinning or not.
200 QCoreApplication::eventDispatcher()->wakeUp();
201
202 return (mResultCode == NSModalResponseOK);
203}
204
205// Future proofing in case _NSTargetForSendAction checks this
206// property before sending us the changeFont: message.
207- (BOOL)worksWhenModal
208{
209 return YES;
210}
211
212- (QPlatformDialogHelper::DialogCode)dialogResultCode
213{
214 return (mResultCode == NSModalResponseOK) ? QPlatformDialogHelper::Accepted : QPlatformDialogHelper::Rejected;
215}
216
217- (BOOL)windowShouldClose:(id)window
218{
219 Q_UNUSED(window);
220 if (!mPanelButtons)
221 [self updateQtFont];
222 if (mDialogIsExecuting) {
223 [self finishOffWithCode:NSModalResponseCancel];
224 } else {
225 mResultSet = true;
226 if (mHelper)
227 emit mHelper->reject();
228 }
229 return true;
230}
231
232- (void)finishOffWithCode:(NSInteger)code
233{
234 mResultCode = code;
235 if (mDialogIsExecuting) {
236 // We stop the current modal event loop. The control
237 // will then return inside -(void)exec below.
238 // It's important that the modal event loop is stopped before
239 // we accept/reject QFontDialog, since QFontDialog has its
240 // own event loop that needs to be stopped last.
241 [NSApp stopModalWithCode:code];
242 } else {
243 // Since we are not in a modal event loop, we can safely close
244 // down QFontDialog
245 // Calling accept() or reject() can in turn call closeCocoaFontPanel.
246 // This check will prevent any such recursion.
247 if (!mResultSet) {
248 mResultSet = true;
249 if (mResultCode == NSModalResponseCancel) {
250 emit mHelper->reject();
251 } else {
252 emit mHelper->accept();
253 }
254 }
255 }
256}
257
258@end
259
260@interface QNSView (FontPanel)
261- (void)changeFont:(id)sender;
262@end
263
264@implementation QNSView (FontPanel)
265- (void)changeFont:(id)sender
266{
267 if (auto *delegate = qt_objc_cast<QNSFontPanelDelegate*>(NSFontPanel.sharedFontPanel.delegate))
268 [delegate changeFont:sender];
269}
270@end
271
272QT_BEGIN_NAMESPACE
273
274class QCocoaFontPanel
275{
276public:
277 QCocoaFontPanel()
278 {
279 mDelegate = [[QNSFontPanelDelegate alloc] init];
280 }
281
282 ~QCocoaFontPanel()
283 {
284 [mDelegate release];
285 }
286
287 void init(QCocoaFontDialogHelper *helper)
288 {
289 [mDelegate setDialogHelper:helper];
290 }
291
292 void cleanup(QCocoaFontDialogHelper *helper)
293 {
294 if (mDelegate->mHelper == helper)
295 mDelegate->mHelper = nullptr;
296 }
297
298 bool exec()
299 {
300 // Note: If NSApp is not running (which is the case if e.g a top-most
301 // QEventLoop has been interrupted, and the second-most event loop has not
302 // yet been reactivated (regardless if [NSApp run] is still on the stack)),
303 // showing a native modal dialog will fail.
304 return [mDelegate runApplicationModalPanel];
305 }
306
307 bool show(Qt::WindowModality windowModality, QWindow *parent)
308 {
309 Q_UNUSED(parent);
310 if (windowModality != Qt::ApplicationModal)
311 [mDelegate showModelessPanel];
312 // no need to show a Qt::ApplicationModal dialog here, because it will be shown in runApplicationModalPanel
313 return true;
314 }
315
316 void hide()
317 {
318 [mDelegate closePanel];
319 }
320
321 QFont currentFont() const
322 {
323 return mDelegate->mQtFont;
324 }
325
326 void setCurrentFont(const QFont &font)
327 {
328 NSFontManager *mgr = [NSFontManager sharedFontManager];
329 NSFont *nsFont = nil;
330
331 int weight = 5;
332 NSFontTraitMask mask = 0;
333 if (font.style() == QFont::StyleItalic) {
334 mask |= NSItalicFontMask;
335 }
336 if (font.weight() == QFont::Bold) {
337 weight = 9;
338 mask |= NSBoldFontMask;
339 }
340
341 QFontInfo fontInfo(font);
342 nsFont = [mgr fontWithFamily:fontInfo.family().toNSString()
343 traits:mask
344 weight:weight
345 size:fontInfo.pointSize()];
346
347 [mgr setSelectedFont:nsFont isMultiple:NO];
348 mDelegate->mQtFont = font;
349 }
350
351private:
352 QNSFontPanelDelegate *mDelegate;
353};
354
356
357QCocoaFontDialogHelper::QCocoaFontDialogHelper()
358{
359}
360
361QCocoaFontDialogHelper::~QCocoaFontDialogHelper()
362{
363 sharedFontPanel()->cleanup(this);
364}
365
366void QCocoaFontDialogHelper::exec()
367{
368 if (sharedFontPanel()->exec())
369 emit accept();
370 else
371 emit reject();
372}
373
374bool QCocoaFontDialogHelper::show(Qt::WindowFlags, Qt::WindowModality windowModality, QWindow *parent)
375{
376 if (windowModality == Qt::ApplicationModal)
377 windowModality = Qt::WindowModal;
378 sharedFontPanel()->init(this);
379 return sharedFontPanel()->show(windowModality, parent);
380}
381
382void QCocoaFontDialogHelper::hide()
383{
384 sharedFontPanel()->hide();
385}
386
387void QCocoaFontDialogHelper::setCurrentFont(const QFont &font)
388{
389 sharedFontPanel()->init(this);
390 sharedFontPanel()->setCurrentFont(font);
391}
392
393QFont QCocoaFontDialogHelper::currentFont() const
394{
395 return sharedFontPanel()->currentFont();
396}
397
398QT_END_NAMESPACE
Q_GLOBAL_STATIC(QReadWriteLock, g_updateMutex)