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