Cocoa Dialog Helpers: Refactor OK-Cancel buttons view

Since virtually all the logic is shared between QNSColorPanelDelegate
and QNSFontPanelDelegate, we extract the added buttons and
layouting logic and move it into its own class. This requires
the two afore mentioned Objective C classes to satisfy the
QNSPanelDelegate protocol.

Change-Id: Ie26e758f5db71920896d930a4f3644b51a1ce3fa
Reviewed-by: Timur Pocheptsov <timur.pocheptsov@qt.io>
This commit is contained in:
Gabriel de Dietrich 2016-10-24 14:07:33 -07:00 committed by Timur Pocheptsov
parent 1a43199fce
commit 27aeeac6ee
4 changed files with 163 additions and 219 deletions

View File

@ -50,31 +50,13 @@
QT_USE_NAMESPACE
static NSButton *macCreateButton(const char *text, NSView *superview)
{
static const NSRect buttonFrameRect = { { 0.0, 0.0 }, { 0.0, 0.0 } };
NSButton *button = [[NSButton alloc] initWithFrame:buttonFrameRect];
[button setButtonType:NSMomentaryLightButton];
[button setBezelStyle:NSRoundedBezelStyle];
[button setTitle:(NSString*)(CFStringRef)QCFString(
QPlatformTheme::removeMnemonics(QCoreApplication::translate("QPlatformTheme", text)))];
[[button cell] setFont:[NSFont systemFontOfSize:
[NSFont systemFontSizeForControlSize:NSRegularControlSize]]];
[superview addSubview:button];
return button;
}
@class QT_MANGLE_NAMESPACE(QNSColorPanelDelegate);
@interface QT_MANGLE_NAMESPACE(QNSColorPanelDelegate) : NSObject<NSWindowDelegate>
@interface QT_MANGLE_NAMESPACE(QNSColorPanelDelegate) : NSObject<NSWindowDelegate, QT_MANGLE_NAMESPACE(QNSPanelDelegate)>
{
@public
NSColorPanel *mColorPanel;
QCocoaColorDialogHelper *mHelper;
NSView *mStolenContentView;
NSButton *mOkButton;
NSButton *mCancelButton;
QNSPanelContentsWrapper *mPanelButtons;
QColor mQtColor;
NSInteger mResultCode;
BOOL mDialogIsExecuting;
@ -82,7 +64,6 @@ static NSButton *macCreateButton(const char *text, NSView *superview)
BOOL mClosingDueToKnownButton;
};
- (void)restoreOriginalContentView;
- (void)relayout;
- (void)updateQtColor;
- (void)finishOffWithCode:(NSInteger)code;
@end
@ -97,8 +78,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate);
mColorPanel = [NSColorPanel sharedColorPanel];
mHelper = 0;
mStolenContentView = 0;
mOkButton = 0;
mCancelButton = 0;
mPanelButtons = nil;
mResultCode = NSCancelButton;
mDialogIsExecuting = false;
mResultSet = false;
@ -142,23 +122,10 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate);
[mColorPanel setContentView:0];
// create a new content view and add the stolen one as a subview
NSRect frameRect = { { 0.0, 0.0 }, { 0.0, 0.0 } };
NSView *ourContentView = [[NSView alloc] initWithFrame:frameRect];
[ourContentView addSubview:mStolenContentView];
// create OK and Cancel buttons and add these as subviews
mOkButton = macCreateButton("&OK", ourContentView);
mCancelButton = macCreateButton("Cancel", ourContentView);
[mColorPanel setContentView:ourContentView];
[mColorPanel setDefaultButtonCell:[mOkButton cell]];
[self relayout];
[mOkButton setAction:@selector(onOkClicked)];
[mOkButton setTarget:self];
[mCancelButton setAction:@selector(onCancelClicked)];
[mCancelButton setTarget:self];
mPanelButtons = [[QNSPanelContentsWrapper alloc] initWithPanelDelegate:self];
[mPanelButtons addSubview:mStolenContentView];
mColorPanel.contentView = mPanelButtons;
mColorPanel.defaultButtonCell = mPanelButtons.okButton.cell;
}
}
@ -167,12 +134,6 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate);
[mColorPanel close];
}
- (void)windowDidResize:(NSNotification *)notification
{
Q_UNUSED(notification);
[self relayout];
}
- (void)colorChanged:(NSNotification *)notification
{
Q_UNUSED(notification);
@ -182,7 +143,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate);
- (void)windowWillClose:(NSNotification *)notification
{
Q_UNUSED(notification);
if (mCancelButton && mHelper && !mClosingDueToKnownButton) {
if (mPanelButtons && mHelper && !mClosingDueToKnownButton) {
mClosingDueToKnownButton = true; // prevent repeating emit
emit mHelper->reject();
}
@ -191,68 +152,16 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate);
- (void)restoreOriginalContentView
{
if (mStolenContentView) {
NSView *ourContentView = [mColorPanel contentView];
// return stolen stuff to its rightful owner
[mStolenContentView removeFromSuperview];
[mColorPanel setContentView:mStolenContentView];
[mOkButton release];
[mCancelButton release];
[ourContentView release];
mOkButton = 0;
mCancelButton = 0;
mStolenContentView = 0;
[mStolenContentView release];
mStolenContentView = nil;
[mPanelButtons release];
mPanelButtons = nil;
}
}
- (void)relayout
{
if (!mOkButton)
return;
NSRect rect = [[mStolenContentView superview] frame];
// should a priori be kept in sync with qfontdialog_mac.mm
const CGFloat ButtonMinWidth = 78.0; // 84.0 for Carbon
const CGFloat ButtonMinHeight = 32.0;
const CGFloat ButtonSpacing = 0.0;
const CGFloat ButtonTopMargin = 0.0;
const CGFloat ButtonBottomMargin = 7.0;
const CGFloat ButtonSideMargin = 9.0;
[mOkButton sizeToFit];
NSSize okSizeHint = [mOkButton frame].size;
[mCancelButton sizeToFit];
NSSize cancelSizeHint = [mCancelButton frame].size;
const CGFloat ButtonWidth = qMin(qMax(ButtonMinWidth,
qMax(okSizeHint.width, cancelSizeHint.width)),
CGFloat((rect.size.width - 2.0 * ButtonSideMargin - ButtonSpacing) * 0.5));
const CGFloat ButtonHeight = qMax(ButtonMinHeight,
qMax(okSizeHint.height, cancelSizeHint.height));
NSRect okRect = { { rect.size.width - ButtonSideMargin - ButtonWidth,
ButtonBottomMargin },
{ ButtonWidth, ButtonHeight } };
[mOkButton setFrame:okRect];
[mOkButton setNeedsDisplay:YES];
NSRect cancelRect = { { okRect.origin.x - ButtonSpacing - ButtonWidth,
ButtonBottomMargin },
{ ButtonWidth, ButtonHeight } };
[mCancelButton setFrame:cancelRect];
[mCancelButton setNeedsDisplay:YES];
const CGFloat Y = ButtonBottomMargin + ButtonHeight + ButtonTopMargin;
NSRect stolenCVRect = { { 0.0, Y },
{ rect.size.width, rect.size.height - Y } };
[mStolenContentView setFrame:stolenCVRect];
[mStolenContentView setNeedsDisplay:YES];
[[mStolenContentView superview] setNeedsDisplay:YES];
}
- (void)onOkClicked
{
mClosingDueToKnownButton = true;
@ -263,7 +172,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate);
- (void)onCancelClicked
{
if (mOkButton) {
if (mPanelButtons) {
mClosingDueToKnownButton = true;
[mColorPanel close];
mQtColor = QColor();
@ -335,7 +244,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSColorPanelDelegate);
- (BOOL)windowShouldClose:(id)window
{
Q_UNUSED(window);
if (!mOkButton)
if (!mPanelButtons)
[self updateQtColor];
if (mDialogIsExecuting) {
[self finishOffWithCode:NSCancelButton];

View File

@ -58,33 +58,6 @@ typedef float CGFloat; // Should only not be defined on 32-bit platforms
QT_USE_NAMESPACE
// should a priori be kept in sync with qcolordialog_mac.mm
const CGFloat ButtonMinWidth = 78.0;
const CGFloat ButtonMinHeight = 32.0;
const CGFloat ButtonSpacing = 0.0;
const CGFloat ButtonTopMargin = 0.0;
const CGFloat ButtonBottomMargin = 7.0;
const CGFloat ButtonSideMargin = 9.0;
// looks better with some margins
const CGFloat DialogTopMargin = 7.0;
const CGFloat DialogSideMargin = 9.0;
static NSButton *macCreateButton(const char *text, NSView *superview)
{
static const NSRect buttonFrameRect = { { 0.0, 0.0 }, { 0.0, 0.0 } };
NSButton *button = [[NSButton alloc] initWithFrame:buttonFrameRect];
[button setButtonType:NSMomentaryLightButton];
[button setBezelStyle:NSRoundedBezelStyle];
[button setTitle:(NSString*)(CFStringRef)QCFString(
QPlatformTheme::removeMnemonics(QCoreApplication::translate("QDialogButtonBox", text)))];
[[button cell] setFont:[NSFont systemFontOfSize:
[NSFont systemFontSizeForControlSize:NSRegularControlSize]]];
[superview addSubview:button];
return button;
}
static QFont qfontForCocoaFont(NSFont *cocoaFont, const QFont &resolveFont)
{
QFont newFont;
@ -103,22 +76,19 @@ static QFont qfontForCocoaFont(NSFont *cocoaFont, const QFont &resolveFont)
@class QT_MANGLE_NAMESPACE(QNSFontPanelDelegate);
@interface QT_MANGLE_NAMESPACE(QNSFontPanelDelegate) : NSObject<NSWindowDelegate>
@interface QT_MANGLE_NAMESPACE(QNSFontPanelDelegate) : NSObject<NSWindowDelegate, QT_MANGLE_NAMESPACE(QNSPanelDelegate)>
{
@public
NSFontPanel *mFontPanel;
QCocoaFontDialogHelper *mHelper;
NSView *mStolenContentView;
NSButton *mOkButton;
NSButton *mCancelButton;
QNSPanelContentsWrapper *mPanelButtons;
QFont mQtFont;
NSInteger mResultCode;
BOOL mDialogIsExecuting;
BOOL mResultSet;
};
- (void)restoreOriginalContentView;
- (void)relayout;
- (void)relayoutToContentSize:(NSSize)frameSize;
- (void)updateQtFont;
- (void)changeFont:(id)sender;
- (void)finishOffWithCode:(NSInteger)code;
@ -134,8 +104,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate);
mFontPanel = [NSFontPanel sharedFontPanel];
mHelper = 0;
mStolenContentView = 0;
mOkButton = 0;
mCancelButton = 0;
mPanelButtons = 0;
mResultCode = NSCancelButton;
mDialogIsExecuting = false;
mResultSet = false;
@ -173,23 +142,11 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate);
[mFontPanel setContentView:0];
// create a new content view and add the stolen one as a subview
NSRect frameRect = { { 0.0, 0.0 }, { 0.0, 0.0 } };
NSView *ourContentView = [[NSView alloc] initWithFrame:frameRect];
[ourContentView addSubview:mStolenContentView];
// create OK and Cancel buttons and add these as subviews
mOkButton = macCreateButton("&OK", ourContentView);
mCancelButton = macCreateButton("Cancel", ourContentView);
[mFontPanel setContentView:ourContentView];
[mFontPanel setDefaultButtonCell:[mOkButton cell]];
[self relayoutToContentSize:[[mStolenContentView superview] frame].size];
[mOkButton setAction:@selector(onOkClicked)];
[mOkButton setTarget:self];
[mCancelButton setAction:@selector(onCancelClicked)];
[mCancelButton setTarget:self];
mPanelButtons = [[QNSPanelContentsWrapper alloc] initWithPanelDelegate:self];
[mPanelButtons addSubview:mStolenContentView];
mPanelButtons.panelContentsMargins = NSEdgeInsetsMake(0, 0, 7, 0);
mFontPanel.contentView = mPanelButtons;
mFontPanel.defaultButtonCell = mPanelButtons.okButton.cell;
}
}
@ -198,77 +155,18 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate);
[mFontPanel close];
}
- (void)windowDidResize:(NSNotification *)notification
{
Q_UNUSED(notification);
[self relayout];
}
- (void)restoreOriginalContentView
{
if (mStolenContentView) {
NSView *ourContentView = [mFontPanel contentView];
// return stolen stuff to its rightful owner
[mStolenContentView removeFromSuperview];
[mFontPanel setContentView:mStolenContentView];
[mOkButton release];
[mCancelButton release];
[ourContentView release];
mOkButton = 0;
mCancelButton = 0;
mStolenContentView = 0;
[mPanelButtons release];
mPanelButtons = nil;
}
}
- (void)relayout
{
if (!mOkButton)
return;
[self relayoutToContentSize:[[mStolenContentView superview] frame].size];
}
- (void)relayoutToContentSize:(NSSize)frameSize
{
Q_ASSERT(mOkButton);
[mOkButton sizeToFit];
NSSize okSizeHint = [mOkButton frame].size;
[mCancelButton sizeToFit];
NSSize cancelSizeHint = [mCancelButton frame].size;
const CGFloat ButtonWidth = qMin(qMax(ButtonMinWidth,
qMax(okSizeHint.width, cancelSizeHint.width)),
CGFloat((frameSize.width - 2.0 * ButtonSideMargin - ButtonSpacing) * 0.5));
const CGFloat ButtonHeight = qMax(ButtonMinHeight,
qMax(okSizeHint.height, cancelSizeHint.height));
const CGFloat X = DialogSideMargin;
const CGFloat Y = ButtonBottomMargin + ButtonHeight + ButtonTopMargin;
NSRect okRect = { { frameSize.width - ButtonSideMargin - ButtonWidth,
ButtonBottomMargin },
{ ButtonWidth, ButtonHeight } };
[mOkButton setFrame:okRect];
[mOkButton setNeedsDisplay:YES];
NSRect cancelRect = { { okRect.origin.x - ButtonSpacing - ButtonWidth,
ButtonBottomMargin },
{ ButtonWidth, ButtonHeight } };
[mCancelButton setFrame:cancelRect];
[mCancelButton setNeedsDisplay:YES];
NSRect stolenCVRect = { { X, Y },
{ frameSize.width - X - X, frameSize.height - Y - DialogTopMargin } };
[mStolenContentView setFrame:stolenCVRect];
[mStolenContentView setNeedsDisplay:YES];
[[mStolenContentView superview] setNeedsDisplay:YES];
}
- (void)onOkClicked
{
[mFontPanel close];
@ -277,7 +175,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate);
- (void)onCancelClicked
{
if (mOkButton) {
if (mPanelButtons) {
[mFontPanel close];
mQtFont = QFont();
[self finishOffWithCode:NSCancelButton];
@ -332,7 +230,7 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSFontPanelDelegate);
- (BOOL)windowShouldClose:(id)window
{
Q_UNUSED(window);
if (!mOkButton)
if (!mPanelButtons)
[self updateQtFont];
if (mDialogIsExecuting) {
[self finishOffWithCode:NSCancelButton];

View File

@ -158,7 +158,30 @@ T qt_mac_resolveOption(const T &fallback, QWindow *window, const QByteArray &pro
// return default value.
return fallback;
}
QT_END_NAMESPACE
@protocol QT_MANGLE_NAMESPACE(QNSPanelDelegate)
@required
- (void)onOkClicked;
- (void)onCancelClicked;
@end
@interface QT_MANGLE_NAMESPACE(QNSPanelContentsWrapper) : NSView
@property (nonatomic, readonly) NSButton *okButton;
@property (nonatomic, readonly) NSButton *cancelButton;
@property (nonatomic, readonly) NSView *panelContents; // ARC: unretained, make it weak
@property (nonatomic, assign) NSEdgeInsets panelContentsMargins;
- (instancetype)initWithPanelDelegate:(id<QT_MANGLE_NAMESPACE(QNSPanelDelegate)>)panelDelegate;
- (void)dealloc;
- (NSButton *)createButtonWithTitle:(const char *)title;
- (void)layout;
@end
QT_NAMESPACE_ALIAS_OBJC_CLASS(QNSPanelContentsWrapper);
#endif //QCOCOAHELPERS_H

View File

@ -300,3 +300,117 @@ QString qt_mac_removeAmpersandEscapes(QString s)
}
QT_END_NAMESPACE
/*! \internal
This NSView derived class is used to add OK/Cancel
buttons to NSColorPanel and NSFontPanel. It replaces
the panel's content view, while reparenting the former
content view into itself. It also takes care of setting
the target-action for the OK/Cancel buttons and making
sure the layout is consistent.
*/
@implementation QNSPanelContentsWrapper
- (instancetype)initWithPanelDelegate:(id<QT_MANGLE_NAMESPACE(QNSPanelDelegate)>)panelDelegate
{
if ((self = [super initWithFrame:NSZeroRect])) {
// create OK and Cancel buttons and add these as subviews
_okButton = [self createButtonWithTitle:"&OK"];
_okButton.action = @selector(onOkClicked);
_okButton.target = panelDelegate;
_cancelButton = [self createButtonWithTitle:"Cancel"];
_cancelButton.action = @selector(onCancelClicked);
_cancelButton.target = panelDelegate;
_panelContents = nil;
_panelContentsMargins = NSEdgeInsetsMake(0, 0, 0, 0);
}
return self;
}
- (void)dealloc
{
[_okButton release];
_okButton = nil;
[_cancelButton release];
_cancelButton = nil;
_panelContents = nil;
[super dealloc];
}
- (NSButton *)createButtonWithTitle:(const char *)title
{
NSButton *button = [[NSButton alloc] initWithFrame:NSZeroRect];
button.buttonType = NSMomentaryLightButton;
button.bezelStyle = NSRoundedBezelStyle;
const QString &cleanTitle = QPlatformTheme::removeMnemonics(QCoreApplication::translate("QDialogButtonBox", title));
// FIXME: Not obvious, from Cocoa's documentation, that QString::toNSString() makes a deep copy
button.title = (NSString *)cleanTitle.toCFString();
((NSButtonCell *)button.cell).font =
[NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]];
[self addSubview:button];
return button;
}
- (void)layout
{
static const CGFloat ButtonMinWidth = 78.0; // 84.0 for Carbon
static const CGFloat ButtonMinHeight = 32.0;
static const CGFloat ButtonSpacing = 0.0;
static const CGFloat ButtonTopMargin = 0.0;
static const CGFloat ButtonBottomMargin = 7.0;
static const CGFloat ButtonSideMargin = 9.0;
NSSize frameSize = self.frame.size;
[self.okButton sizeToFit];
NSSize okSizeHint = self.okButton.frame.size;
[self.cancelButton sizeToFit];
NSSize cancelSizeHint = self.cancelButton.frame.size;
const CGFloat buttonWidth = qMin(qMax(ButtonMinWidth,
qMax(okSizeHint.width, cancelSizeHint.width)),
CGFloat((frameSize.width - 2.0 * ButtonSideMargin - ButtonSpacing) * 0.5));
const CGFloat buttonHeight = qMax(ButtonMinHeight,
qMax(okSizeHint.height, cancelSizeHint.height));
NSRect okRect = { { frameSize.width - ButtonSideMargin - buttonWidth,
ButtonBottomMargin },
{ buttonWidth, buttonHeight } };
self.okButton.frame = okRect;
self.okButton.needsDisplay = YES;
NSRect cancelRect = { { okRect.origin.x - ButtonSpacing - buttonWidth,
ButtonBottomMargin },
{ buttonWidth, buttonHeight } };
self.cancelButton.frame = cancelRect;
self.cancelButton.needsDisplay = YES;
// The third view should be the original panel contents. Cache it.
if (!self.panelContents)
for (NSView *view in self.subviews)
if (view != self.okButton && view != self.cancelButton) {
_panelContents = view;
break;
}
const CGFloat buttonBoxHeight = ButtonBottomMargin + buttonHeight + ButtonTopMargin;
const NSRect panelContentsFrame = NSMakeRect(
self.panelContentsMargins.left,
buttonBoxHeight + self.panelContentsMargins.bottom,
frameSize.width - (self.panelContentsMargins.left + self.panelContentsMargins.right),
frameSize.height - buttonBoxHeight - (self.panelContentsMargins.top + self.panelContentsMargins.bottom));
self.panelContents.frame = panelContentsFrame;
self.panelContents.needsDisplay = YES;
self.needsDisplay = YES;
}
@end