1 /****************************************************************************
3 ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/
6 ** This file is part of the QtGui module of the Qt Toolkit.
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** GNU Lesser General Public License Usage
10 ** This file may be used under the terms of the GNU Lesser General Public
11 ** License version 2.1 as published by the Free Software Foundation and
12 ** appearing in the file LICENSE.LGPL included in the packaging of this
13 ** file. Please review the following information to ensure the GNU Lesser
14 ** General Public License version 2.1 requirements will be met:
15 ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17 ** In addition, as a special exception, Nokia gives you certain additional
18 ** rights. These rights are described in the Nokia Qt LGPL Exception
19 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21 ** GNU General Public License Usage
22 ** Alternatively, this file may be used under the terms of the GNU General
23 ** Public License version 3.0 as published by the Free Software Foundation
24 ** and appearing in the file LICENSE.GPL included in the packaging of this
25 ** file. Please review the following information to ensure the GNU General
26 ** Public License version 3.0 requirements will be met:
27 ** http://www.gnu.org/copyleft/gpl.html.
30 ** Alternatively, this file may be used in accordance with the terms and
31 ** conditions contained in a signed written agreement between you and Nokia.
40 ****************************************************************************/
42 #include "qfontdialog_p.h"
43 #if !defined(QT_NO_FONTDIALOG) && defined(Q_WS_MAC)
44 #include <qapplication.h>
45 #include <qdialogbuttonbox.h>
46 #include <qlineedit.h>
47 #include <private/qapplication_p.h>
48 #include <private/qfont_p.h>
49 #include <private/qfontengine_p.h>
50 #include <private/qt_cocoa_helpers_mac_p.h>
51 #include <private/qt_mac_p.h>
52 #include <qabstracteventdispatcher.h>
54 #include <private/qfontengine_coretext_p.h>
55 #import <AppKit/AppKit.h>
56 #import <Foundation/Foundation.h>
59 typedef float CGFloat; // Should only not be defined on 32-bit platforms
64 extern void macStartInterceptNSPanelCtor();
65 extern void macStopInterceptNSPanelCtor();
66 extern NSButton *macCreateButton(const char *text, NSView *superview);
67 extern bool qt_mac_is_macsheet(const QWidget *w); // qwidget_mac.mm
72 // should a priori be kept in sync with qcolordialog_mac.mm
73 const CGFloat ButtonMinWidth = 78.0;
74 const CGFloat ButtonMinHeight = 32.0;
75 const CGFloat ButtonSpacing = 0.0;
76 const CGFloat ButtonTopMargin = 0.0;
77 const CGFloat ButtonBottomMargin = 7.0;
78 const CGFloat ButtonSideMargin = 9.0;
80 // looks better with some margins
81 const CGFloat DialogTopMargin = 7.0;
82 const CGFloat DialogSideMargin = 9.0;
84 const int StyleMask = NSTitledWindowMask | NSClosableWindowMask | NSResizableWindowMask;
86 @class QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate);
89 #if MAC_OS_X_VERSION_MAX_ALLOWED <= MAC_OS_X_VERSION_10_5
91 @protocol NSWindowDelegate <NSObject>
92 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize;
97 @interface QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) : NSObject <NSWindowDelegate> {
98 NSFontPanel *mFontPanel;
99 NSView *mStolenContentView;
101 NSButton *mCancelButton;
102 QFontDialogPrivate *mPriv;
104 BOOL mPanelHackedWithButtons;
105 CGFloat mDialogExtraWidth;
106 CGFloat mDialogExtraHeight;
110 - (id)initWithFontPanel:(NSFontPanel *)panel
111 stolenContentView:(NSView *)stolenContentView
112 okButton:(NSButton *)okButton
113 cancelButton:(NSButton *)cancelButton
114 priv:(QFontDialogPrivate *)priv
115 extraWidth:(CGFloat)extraWidth
116 extraHeight:(CGFloat)extraHeight;
117 - (void)showModelessPanel;
118 - (void)showWindowModalSheet:(QWidget *)docWidget;
119 - (void)runApplicationModalPanel;
121 - (void)changeFont:(id)sender;
122 - (void)changeAttributes:(id)sender;
123 - (BOOL)windowShouldClose:(id)window;
124 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize;
126 - (void)relayoutToContentSize:(NSSize)frameSize;
128 - (void)onCancelClicked;
129 - (NSFontPanel *)fontPanel;
130 - (NSWindow *)actualPanel;
131 - (NSSize)dialogExtraSize;
132 - (void)setQtFont:(const QFont &)newFont;
134 - (void)finishOffWithCode:(NSInteger)result;
135 - (void)cleanUpAfterMyself;
136 - (void)setSubwindowStacking;
139 static QFont qfontForCocoaFont(NSFont *cocoaFont, const QFont &resolveFont)
143 int pSize = qRound([cocoaFont pointSize]);
144 QString family(qt_mac_NSStringToQString([cocoaFont familyName]));
145 QString typeface(qt_mac_NSStringToQString([cocoaFont fontName]));
147 int hyphenPos = typeface.indexOf(QLatin1Char('-'));
148 if (hyphenPos != -1) {
149 typeface.remove(0, hyphenPos + 1);
151 typeface = QLatin1String("Normal");
154 newFont = QFontDatabase().font(family, typeface, pSize);
155 newFont.setUnderline(resolveFont.underline());
156 newFont.setStrikeOut(resolveFont.strikeOut());
162 @implementation QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate)
163 - (id)initWithFontPanel:(NSFontPanel *)panel
164 stolenContentView:(NSView *)stolenContentView
165 okButton:(NSButton *)okButton
166 cancelButton:(NSButton *)cancelButton
167 priv:(QFontDialogPrivate *)priv
168 extraWidth:(CGFloat)extraWidth
169 extraHeight:(CGFloat)extraHeight
173 mStolenContentView = stolenContentView;
174 mOkButton = okButton;
175 mCancelButton = cancelButton;
177 mPanelHackedWithButtons = (okButton != 0);
178 mDialogExtraWidth = extraWidth;
179 mDialogExtraHeight = extraHeight;
183 if (mPanelHackedWithButtons) {
186 [okButton setAction:@selector(onOkClicked)];
187 [okButton setTarget:self];
189 [cancelButton setAction:@selector(onCancelClicked)];
190 [cancelButton setTarget:self];
193 mQtFont = new QFont();
197 - (void)setSubwindowStacking
199 // Stack the native dialog in front of its parent, if any:
200 QFontDialog *q = mPriv->fontDialog();
201 if (!qt_mac_is_macsheet(q)) {
202 if (QWidget *parent = q->parentWidget()) {
203 if (parent->isWindow()) {
204 [qt_mac_window_for(parent)
205 addChildWindow:[mStolenContentView window] ordered:NSWindowAbove];
217 - (void)showModelessPanel
220 NSWindow *ourPanel = [mStolenContentView window];
221 [ourPanel makeKeyAndOrderFront:self];
224 - (void)runApplicationModalPanel
226 QBoolBlocker nativeDialogOnTop(QApplicationPrivate::native_modal_dialog_active);
228 NSWindow *ourPanel = [mStolenContentView window];
229 [ourPanel setReleasedWhenClosed:NO];
230 [NSApp runModalForWindow:ourPanel];
231 QAbstractEventDispatcher::instance()->interrupt();
233 if (mReturnCode == NSOKButton)
234 mPriv->fontDialog()->accept();
236 mPriv->fontDialog()->reject();
244 - (void)showWindowModalSheet:(QWidget *)docWidget
246 NSWindow *window = qt_mac_window_for(docWidget);
249 NSWindow *ourPanel = [mStolenContentView window];
250 [NSApp beginSheet:ourPanel
251 modalForWindow:window
258 - (void)changeFont:(id)sender
260 NSFont *dummyFont = [NSFont userFontOfSize:12.0];
261 [self setQtFont:qfontForCocoaFont([sender convertFont:dummyFont], *mQtFont)];
263 mPriv->updateSampleFont(*mQtFont);
266 - (void)changeAttributes:(id)sender
268 NSDictionary *dummyAttribs = [NSDictionary dictionary];
269 NSDictionary *attribs = [sender convertAttributes:dummyAttribs];
271 for (id key in attribs) {
272 NSNumber *number = static_cast<NSNumber *>([attribs objectForKey:key]);
273 if ([key isEqual:NSUnderlineStyleAttributeName]) {
274 mQtFont->setUnderline([number intValue] != NSUnderlineStyleNone);
275 } else if ([key isEqual:NSStrikethroughStyleAttributeName]) {
276 mQtFont->setStrikeOut([number intValue] != NSUnderlineStyleNone);
281 mPriv->updateSampleFont(*mQtFont);
284 - (BOOL)windowShouldClose:(id)window
287 if (mPanelHackedWithButtons) {
288 [self onCancelClicked];
290 [self finishOffWithCode:NSCancelButton];
295 - (NSSize)windowWillResize:(NSWindow *)window toSize:(NSSize)proposedFrameSize
297 if (mFontPanel == window) {
298 proposedFrameSize = [static_cast<id <NSWindowDelegate> >(mFontPanel) windowWillResize:mFontPanel toSize:proposedFrameSize];
301 Ugly hack: NSFontPanel rearranges the layout of its main
302 component in windowWillResize:toSize:. So we temporarily
303 restore the stolen content view to its rightful owner,
304 call windowWillResize:toSize:, and steal the content view
307 [mStolenContentView removeFromSuperview];
308 [mFontPanel setContentView:mStolenContentView];
309 NSSize extraSize = [self dialogExtraSize];
310 proposedFrameSize.width -= extraSize.width;
311 proposedFrameSize.height -= extraSize.height;
312 proposedFrameSize = [static_cast<id <NSWindowDelegate> >(mFontPanel) windowWillResize:mFontPanel toSize:proposedFrameSize];
313 NSRect frameRect = { { 0.0, 0.0 }, proposedFrameSize };
314 [mFontPanel setFrame:frameRect display:NO];
315 [mFontPanel setContentView:0];
316 [[window contentView] addSubview:mStolenContentView];
317 proposedFrameSize.width += extraSize.width;
318 proposedFrameSize.height += extraSize.height;
320 if (mPanelHackedWithButtons) {
321 NSRect frameRect = { { 0.0, 0.0 }, proposedFrameSize };
322 NSRect contentRect = [NSWindow contentRectForFrameRect:frameRect styleMask:[window styleMask]];
323 [self relayoutToContentSize:contentRect.size];
325 return proposedFrameSize;
330 [self relayoutToContentSize:[[mStolenContentView superview] frame].size];
333 - (void)relayoutToContentSize:(NSSize)frameSize
335 Q_ASSERT(mPanelHackedWithButtons);
337 [mOkButton sizeToFit];
338 NSSize okSizeHint = [mOkButton frame].size;
340 [mCancelButton sizeToFit];
341 NSSize cancelSizeHint = [mCancelButton frame].size;
343 const CGFloat ButtonWidth = qMin(qMax(ButtonMinWidth,
344 qMax(okSizeHint.width, cancelSizeHint.width)),
345 CGFloat((frameSize.width - 2.0 * ButtonSideMargin - ButtonSpacing) * 0.5));
346 const CGFloat ButtonHeight = qMax(ButtonMinHeight,
347 qMax(okSizeHint.height, cancelSizeHint.height));
349 const CGFloat X = DialogSideMargin;
350 const CGFloat Y = ButtonBottomMargin + ButtonHeight + ButtonTopMargin;
352 NSRect okRect = { { frameSize.width - ButtonSideMargin - ButtonWidth,
353 ButtonBottomMargin },
354 { ButtonWidth, ButtonHeight } };
355 [mOkButton setFrame:okRect];
356 [mOkButton setNeedsDisplay:YES];
358 NSRect cancelRect = { { okRect.origin.x - ButtonSpacing - ButtonWidth,
359 ButtonBottomMargin },
360 { ButtonWidth, ButtonHeight } };
361 [mCancelButton setFrame:cancelRect];
362 [mCancelButton setNeedsDisplay:YES];
364 NSRect stolenCVRect = { { X, Y },
365 { frameSize.width - X - X, frameSize.height - Y - DialogTopMargin } };
366 [mStolenContentView setFrame:stolenCVRect];
367 [mStolenContentView setNeedsDisplay:YES];
369 [[mStolenContentView superview] setNeedsDisplay:YES];
374 Q_ASSERT(mPanelHackedWithButtons);
375 NSFontManager *fontManager = [NSFontManager sharedFontManager];
376 [self setQtFont:qfontForCocoaFont([fontManager convertFont:[fontManager selectedFont]],
378 [self finishOffWithCode:NSOKButton];
381 - (void)onCancelClicked
383 Q_ASSERT(mPanelHackedWithButtons);
384 [self finishOffWithCode:NSCancelButton];
387 - (NSFontPanel *)fontPanel
392 - (NSWindow *)actualPanel
394 return [mStolenContentView window];
397 - (NSSize)dialogExtraSize
399 // this must be recomputed each time, because sometimes the
400 // NSFontPanel has the NSDocModalWindowMask flag set, and sometimes
401 // not -- which affects the frame rect vs. content rect measurements
403 // take the different frame rectangles into account for dialogExtra{Width,Height}
404 NSRect someRect = { { 0.0, 0.0 }, { 100000.0, 100000.0 } };
405 NSRect sharedFontPanelContentRect = [mFontPanel contentRectForFrameRect:someRect];
406 NSRect ourPanelContentRect = [NSWindow contentRectForFrameRect:someRect styleMask:StyleMask];
408 NSSize result = { mDialogExtraWidth, mDialogExtraHeight };
409 result.width -= ourPanelContentRect.size.width - sharedFontPanelContentRect.size.width;
410 result.height -= ourPanelContentRect.size.height - sharedFontPanelContentRect.size.height;
414 - (void)setQtFont:(const QFont &)newFont
417 mQtFont = new QFont(newFont);
425 - (void)finishOffWithCode:(NSInteger)code
427 QFontDialog *q = mPriv->fontDialog();
428 if (QWidget *parent = q->parentWidget()) {
429 if (parent->isWindow()) {
430 [qt_mac_window_for(parent) removeChildWindow:[mStolenContentView window]];
434 if(code == NSOKButton)
435 mPriv->sampleEdit->setFont([self qtFont]);
439 [NSApp stopModalWithCode:code];
441 if (code == NSOKButton)
442 mPriv->fontDialog()->accept();
444 mPriv->fontDialog()->reject();
448 - (void)cleanUpAfterMyself
450 if (mPanelHackedWithButtons) {
451 NSView *ourContentView = [mFontPanel contentView];
453 // return stolen stuff to its rightful owner
454 [mStolenContentView removeFromSuperview];
455 [mFontPanel setContentView:mStolenContentView];
458 [mCancelButton release];
459 [ourContentView release];
461 [mFontPanel setDelegate:nil];
462 [[NSFontManager sharedFontManager] setDelegate:nil];
463 [[NSFontManager sharedFontManager] setTarget:nil];
469 void QFontDialogPrivate::closeCocoaFontPanel()
471 QMacCocoaAutoReleasePool pool;
472 QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *theDelegate = static_cast<QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *>(delegate);
473 NSWindow *ourPanel = [theDelegate actualPanel];
475 if ([theDelegate isAppModal])
477 [theDelegate cleanUpAfterMyself];
478 [theDelegate release];
480 sharedFontPanelAvailable = true;
483 void QFontDialogPrivate::setFont(void *delegate, const QFont &font)
485 QMacCocoaAutoReleasePool pool;
486 QFontEngine *fe = font.d->engineForScript(QUnicodeTables::Common);
487 NSFontManager *mgr = [NSFontManager sharedFontManager];
488 const NSFont *nsFont = 0;
490 #if MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_5
491 if (qstrcmp(fe->name(), "CoreText") == 0) {
492 nsFont = reinterpret_cast<const NSFont *>(static_cast<QCoreTextFontEngineMulti *>(fe)->ctfont);
497 NSFontTraitMask mask = 0;
498 if (font.style() == QFont::StyleItalic) {
499 mask |= NSItalicFontMask;
501 if (font.weight() == QFont::Bold) {
503 mask |= NSBoldFontMask;
506 NSFontManager *mgr = [NSFontManager sharedFontManager];
507 QFontInfo fontInfo(font);
508 nsFont = [mgr fontWithFamily:qt_mac_QStringToNSString(fontInfo.family())
511 size:fontInfo.pointSize()];
514 [mgr setSelectedFont:const_cast<NSFont *>(nsFont) isMultiple:NO];
515 [static_cast<QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *>(delegate) setQtFont:font];
518 void QFontDialogPrivate::createNSFontPanelDelegate()
523 sharedFontPanelAvailable = false;
524 QMacCocoaAutoReleasePool pool;
525 bool sharedFontPanelExisted = [NSFontPanel sharedFontPanelExists];
526 NSFontPanel *sharedFontPanel = [NSFontPanel sharedFontPanel];
527 [sharedFontPanel setHidesOnDeactivate:false];
529 // hack to ensure that QCocoaApplication's validModesForFontPanel:
530 // implementation is honored
531 if (!sharedFontPanelExisted) {
532 [sharedFontPanel makeKeyAndOrderFront:sharedFontPanel];
533 [sharedFontPanel close];
536 NSPanel *ourPanel = 0;
537 NSView *stolenContentView = 0;
538 NSButton *okButton = 0;
539 NSButton *cancelButton = 0;
541 CGFloat dialogExtraWidth = 0.0;
542 CGFloat dialogExtraHeight = 0.0;
544 // compute dialogExtra{Width,Height}
545 dialogExtraWidth = 2.0 * DialogSideMargin;
546 dialogExtraHeight = DialogTopMargin + ButtonTopMargin + ButtonMinHeight + ButtonBottomMargin;
548 // compute initial contents rectangle
549 NSRect contentRect = [sharedFontPanel contentRectForFrameRect:[sharedFontPanel frame]];
550 contentRect.size.width += dialogExtraWidth;
551 contentRect.size.height += dialogExtraHeight;
553 // create the new panel
554 ourPanel = [[NSPanel alloc] initWithContentRect:contentRect
556 backing:NSBackingStoreBuffered
558 [ourPanel setReleasedWhenClosed:YES];
559 stolenContentView = [sharedFontPanel contentView];
561 // steal the font panel's contents view
562 [stolenContentView retain];
563 [sharedFontPanel setContentView:0];
566 // create a new content view and add the stolen one as a subview
567 NSRect frameRect = { { 0.0, 0.0 }, { 0.0, 0.0 } };
568 NSView *ourContentView = [[NSView alloc] initWithFrame:frameRect];
569 [ourContentView addSubview:stolenContentView];
571 // create OK and Cancel buttons and add these as subviews
572 okButton = macCreateButton("&OK", ourContentView);
573 cancelButton = macCreateButton("Cancel", ourContentView);
575 [ourPanel setContentView:ourContentView];
576 [ourPanel setDefaultButtonCell:[okButton cell]];
579 // create the delegate and set it
580 QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *del = [[QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) alloc] initWithFontPanel:sharedFontPanel
581 stolenContentView:stolenContentView
583 cancelButton:cancelButton
585 extraWidth:dialogExtraWidth
586 extraHeight:dialogExtraHeight];
588 [ourPanel setDelegate:del];
590 [[NSFontManager sharedFontManager] setDelegate:del];
591 [[NSFontManager sharedFontManager] setTarget:del];
592 setFont(del, q_func()->currentFont());
595 // hack to get correct initial layout
596 NSRect frameRect = [ourPanel frame];
597 frameRect.size.width += 1.0;
598 [ourPanel setFrame:frameRect display:NO];
599 frameRect.size.width -= 1.0;
600 frameRect.size = [del windowWillResize:ourPanel toSize:frameRect.size];
601 [ourPanel setFrame:frameRect display:NO];
604 [del setSubwindowStacking];
605 NSString *title = @"Select font";
606 [ourPanel setTitle:title];
609 void QFontDialogPrivate::mac_nativeDialogModalHelp()
611 // Copied from QFileDialogPrivate
612 // Do a queued meta-call to open the native modal dialog so it opens after the new
613 // event loop has started to execute (in QDialog::exec). Using a timer rather than
614 // a queued meta call is intentional to ensure that the call is only delivered when
615 // [NSApp run] runs (timers are handeled special in cocoa). If NSApp is not
616 // running (which is the case if e.g a top-most QEventLoop has been
617 // interrupted, and the second-most event loop has not yet been reactivated (regardless
618 // if [NSApp run] is still on the stack)), showing a native modal dialog will fail.
619 if (nativeDialogInUse) {
621 QTimer::singleShot(1, q, SLOT(_q_macRunNativeAppModalPanel()));
625 // The problem with the native font dialog is that OS X does not
626 // offer a proper dialog, but a panel (i.e. without Ok and Cancel buttons).
627 // This means we need to "construct" a native dialog by taking the panel
628 // and "adding" the buttons.
629 void QFontDialogPrivate::_q_macRunNativeAppModalPanel()
631 createNSFontPanelDelegate();
632 QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *del = static_cast<QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *>(delegate);
633 [del runApplicationModalPanel];
636 bool QFontDialogPrivate::showCocoaFontPanel()
638 if (!sharedFontPanelAvailable)
642 QMacCocoaAutoReleasePool pool;
643 createNSFontPanelDelegate();
644 QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *del = static_cast<QT_MANGLE_NAMESPACE(QCocoaFontPanelDelegate) *>(delegate);
645 if (qt_mac_is_macsheet(q))
646 [del showWindowModalSheet:q->parentWidget()];
648 [del showModelessPanel];
652 bool QFontDialogPrivate::hideCocoaFontPanel()
655 // Nothing to do. We return false to leave the question
656 // open regarding whether or not to go native:
659 closeCocoaFontPanel();
660 // Even when we hide it, we are still using a
661 // native dialog, so return true:
665 bool QFontDialogPrivate::setVisible_sys(bool visible)
668 if (!visible == q->isHidden())
671 return visible ? showCocoaFontPanel() : hideCocoaFontPanel();