// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include #include #include #include #include #include #include #include "qcocoafontdialoghelper.h" #include "qcocoahelpers.h" #include "qcocoaeventdispatcher.h" #if !CGFLOAT_DEFINED typedef float CGFloat; // Should only not be defined on 32-bit platforms #endif QT_USE_NAMESPACE static QFont qfontForCocoaFont(NSFont *cocoaFont, const QFont &resolveFont) { QFont newFont; if (cocoaFont) { int pSize = qRound([cocoaFont pointSize]); QCFType font(CTFontCopyFontDescriptor((CTFontRef)cocoaFont)); QString family(QCFString((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontFamilyNameAttribute))); QString style(QCFString(((CFStringRef)CTFontDescriptorCopyAttribute(font, kCTFontStyleNameAttribute)))); newFont = QFontDatabase::font(family, style, pSize); newFont.setUnderline(resolveFont.underline()); newFont.setStrikeOut(resolveFont.strikeOut()); } return newFont; } @interface QT_MANGLE_NAMESPACE(QNSFontPanelDelegate) : NSObject - (void)restoreOriginalContentView; - (void)updateQtFont; - (void)changeFont:(id)sender; - (void)finishOffWithCode:(NSInteger)code; @end QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate); @implementation QNSFontPanelDelegate { @public NSFontPanel *mFontPanel; QCocoaFontDialogHelper *mHelper; NSView *mStolenContentView; QNSPanelContentsWrapper *mPanelButtons; QFont mQtFont; NSInteger mResultCode; BOOL mDialogIsExecuting; BOOL mResultSet; } - (instancetype)init { if ((self = [super init])) { mFontPanel = [NSFontPanel sharedFontPanel]; mHelper = nullptr; mStolenContentView = nil; mPanelButtons = nil; mResultCode = NSModalResponseCancel; mDialogIsExecuting = false; mResultSet = false; [mFontPanel setRestorable:NO]; [mFontPanel setDelegate:self]; [mFontPanel retain]; } return self; } - (void)dealloc { [mStolenContentView release]; [mFontPanel setDelegate:nil]; [[NSNotificationCenter defaultCenter] removeObserver:self]; [super dealloc]; } - (void)setDialogHelper:(QCocoaFontDialogHelper *)helper { mHelper = helper; [mFontPanel setTitle:helper->options()->windowTitle().toNSString()]; if (mHelper->options()->testOption(QFontDialogOptions::NoButtons)) { [self restoreOriginalContentView]; } else if (!mStolenContentView) { // steal the font panel's contents view mStolenContentView = mFontPanel.contentView; [mStolenContentView retain]; mFontPanel.contentView = nil; // create a new content view and add the stolen one as a subview mPanelButtons = [[QNSPanelContentsWrapper alloc] initWithPanelDelegate:self]; [mPanelButtons addSubview:mStolenContentView]; mPanelButtons.panelContentsMargins = NSEdgeInsetsMake(0, 0, 7, 0); mFontPanel.contentView = mPanelButtons; mFontPanel.defaultButtonCell = mPanelButtons.okButton.cell; } } - (void)closePanel { [mFontPanel close]; } - (void)restoreOriginalContentView { if (mStolenContentView) { // return stolen stuff to its rightful owner [mStolenContentView removeFromSuperview]; [mFontPanel setContentView:mStolenContentView]; mStolenContentView = nil; [mPanelButtons release]; mPanelButtons = nil; } } - (void)onOkClicked { [mFontPanel close]; [self finishOffWithCode:NSModalResponseOK]; } - (void)onCancelClicked { if (mPanelButtons) { [mFontPanel close]; mQtFont = QFont(); [self finishOffWithCode:NSModalResponseCancel]; } } - (void)changeFont:(id)sender { Q_UNUSED(sender); [self updateQtFont]; } - (void)updateQtFont { // Get selected font NSFontManager *fontManager = [NSFontManager sharedFontManager]; NSFont *selectedFont = [fontManager selectedFont]; if (!selectedFont) { selectedFont = [NSFont systemFontOfSize:[NSFont systemFontSize]]; } NSFont *panelFont = [fontManager convertFont:selectedFont]; mQtFont = qfontForCocoaFont(panelFont, mQtFont); if (mHelper) emit mHelper->currentFontChanged(mQtFont); } - (void)showModelessPanel { mDialogIsExecuting = false; mResultSet = false; // Make this an asynchronous call, so the panel is made key only // in the next event loop run. This is to make sure that by // the time the modal loop is run in runModalForWindow below, // which internally also sets the panel to key window, // the panel is not yet key, and the NSApp still has the right // reference to the _previousKeyWindow. Otherwise both NSApp.key // and NSApp._previousKeyWindow would wrongly point to the panel, // loosing any reference to the window that was key before. dispatch_async(dispatch_get_main_queue(), ^{ [mFontPanel makeKeyAndOrderFront:mFontPanel]; }); } - (BOOL)runApplicationModalPanel { mDialogIsExecuting = true; // 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:mFontPanel]; mDialogIsExecuting = false; // Wake up the event dispatcher so it can check whether the // current event loop should continue spinning or not. QCoreApplication::eventDispatcher()->wakeUp(); return (mResultCode == NSModalResponseOK); } // Future proofing in case _NSTargetForSendAction checks this // property before sending us the changeFont: message. - (BOOL)worksWhenModal { return YES; } - (QPlatformDialogHelper::DialogCode)dialogResultCode { return (mResultCode == NSModalResponseOK) ? QPlatformDialogHelper::Accepted : QPlatformDialogHelper::Rejected; } - (BOOL)windowShouldClose:(id)window { Q_UNUSED(window); if (!mPanelButtons) [self updateQtFont]; 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 QFontDialog, since QFontDialog 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 QFontDialog // Calling accept() or reject() can in turn call closeCocoaFontPanel. // 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 QCocoaFontPanel { public: QCocoaFontPanel() { mDelegate = [[QNSFontPanelDelegate alloc] init]; } ~QCocoaFontPanel() { [mDelegate release]; } void init(QCocoaFontDialogHelper *helper) { [mDelegate setDialogHelper:helper]; } void cleanup(QCocoaFontDialogHelper *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::ApplicationModal) [mDelegate showModelessPanel]; // no need to show a Qt::ApplicationModal dialog here, because it will be shown in runApplicationModalPanel return true; } void hide() { [mDelegate closePanel]; } QFont currentFont() const { return mDelegate->mQtFont; } void setCurrentFont(const QFont &font) { NSFontManager *mgr = [NSFontManager sharedFontManager]; NSFont *nsFont = nil; int weight = 5; NSFontTraitMask mask = 0; if (font.style() == QFont::StyleItalic) { mask |= NSItalicFontMask; } if (font.weight() == QFont::Bold) { weight = 9; mask |= NSBoldFontMask; } QFontInfo fontInfo(font); nsFont = [mgr fontWithFamily:fontInfo.family().toNSString() traits:mask weight:weight size:fontInfo.pointSize()]; [mgr setSelectedFont:nsFont isMultiple:NO]; mDelegate->mQtFont = font; } private: QNSFontPanelDelegate *mDelegate; }; Q_GLOBAL_STATIC(QCocoaFontPanel, sharedFontPanel) QCocoaFontDialogHelper::QCocoaFontDialogHelper() { } QCocoaFontDialogHelper::~QCocoaFontDialogHelper() { sharedFontPanel()->cleanup(this); } void QCocoaFontDialogHelper::exec() { if (sharedFontPanel()->exec()) emit accept(); else emit reject(); } bool QCocoaFontDialogHelper::show(Qt::WindowFlags, Qt::WindowModality windowModality, QWindow *parent) { if (windowModality == Qt::ApplicationModal) windowModality = Qt::WindowModal; sharedFontPanel()->init(this); return sharedFontPanel()->show(windowModality, parent); } void QCocoaFontDialogHelper::hide() { sharedFontPanel()->hide(); } void QCocoaFontDialogHelper::setCurrentFont(const QFont &font) { sharedFontPanel()->init(this); sharedFontPanel()->setCurrentFont(font); } QFont QCocoaFontDialogHelper::currentFont() const { return sharedFontPanel()->currentFont(); } QT_END_NAMESPACE