Re: Customize NSAlert?
Re: Customize NSAlert?
- Subject: Re: Customize NSAlert?
- From: Jerry Krinock <email@hidden>
- Date: Sun, 11 Feb 2007 20:49:07 -0800
- Thread-topic: Customize NSAlert?
This post summarizes the many ways that were proposed to resolve this
problem.
on 07/02/05 12:37, Nick Zitzmann at email@hidden wrote:
> Something like this works for me:
> [alert addButtonWithTitle:@"Don't show this warning again"];
Thanks, but this gives a button, not a checkbox.
on 07/02/05 9:36, Matt Neuburg at email@hidden (and others) wrote:
> Initializing a button with "init" is certainly "something wrong" (though
> this does not go to the heart of your original question).
Thanks, Matt. Yes; and yes. Moving on to the original question...
on 07/02/05 9:45, Sam DeVore at email@hidden wrote:
> http://www.cocoadev.com/index.pl?NSAlertCheckbox
This code written by Tristan O'Tierney does exactly what I want to do, and
works perfectly. O'Tierney's recipe is, fundamentally, the same as mine,
except his ^works^ because of a secret ingredient: Subclassing and
over-riding these two methods:
- (id)buildAlertStyle:(int)fp8 title:(id)fp12 message:(id)fp16
first:(id)fp20 second:(id)fp24 third:(id)fp28 oldStyle:(BOOL)fp32 args:(char
*)fp36
- (id)buildAlertStyle:(int)fp8 title:(id)fp12 formattedMsg:(id)fp16
first:(id)fp20 second:(id)fp24 third:(id)fp28 oldStyle:(BOOL)fp32
But neither of these two methods appear when I do an API search in Xcode :(
on 07/02/05 13:30, Finlay Dobbie at email@hidden wrote:
> At this point, you might want to consider rolling your own alert
> window. Hacking NSAlert by messing with its contentView is probably
> not safe/supported.
Well, the NSAlert documentation does not say that. It does have the
strangely ambiguous and tempting statement that it is "not ^designed^ for
subclassing", which is why I didn't try it.
Now for the Grand Finale...
on 07/02/07 01:32, Dave Jewell <email@hidden> wrote:
> The simplest way to add a "Don't show me this again"
> checkbox to NSAlert is (somewhat inevitably) to use an undocumented
> API. There are two messages which are relevant. Firstly:
>
> - (void) _setDontWarnMessage: (NSString *) message;
> - (BOOL) _dontWarnAgain;
> This...is needed so frequently that this stuff really SHOULD
> be part of the official API.
See Dave's post for the undocumented "documentation". Yes. Yes. For God's
sake, Apple must even have the checkbox text localized!
on 07/02/05 15:10, Michael Babin at email@hidden wrote:
> I have code in one of my apps that uses the latter method successfully (use
> NSGetAlertPanel and customize).
Before I had read Dave Jewell's post, I had decided this was the way to go,
using some lines from Michael's code. The final result is a factory method
that puts up an alert with 1-3 buttons, with or without informative text,
with or without a Help button, and with or without a checkbox.
It doesn't use any undocumented methods, although it places the Help button
and checkbox relative to the existing buttons and image view, so it will get
screwed up if Apple ever decides to change this layout. But, hey, these
layouts are "documented"! I'm not sure if this is legally binding, but
Apple does provide screenshots of these layouts on pg. 9 this document:
http://developer.apple.com/documentation/Cocoa/Conceptual/Dialog/Dialog.pdf
I also submitted a little documentation bug report on NSAlert documentation,
because, in the list archives, I see that this documentation led people who
are way smarter than me to the incorrect conclusion that you can "just" add
stuff to the contentView of NSAlert's -window. In fact, as I said in my
original post, anything you do to that contentView is blown away when the
alert is -runModal, so it ain't good for much.
Thanks for all the help,
Jerry
********* SSAlert.h ******************
#import <Cocoa/Cocoa.h>
@interface SSAlert : NSObject {
NSButton* _helpButton ;
NSString* _helpButtonAnchor ;
NSButton* _checkbox ;
NSPanel* _panel ;
}
+ (void)runModalBadgeCritical:(BOOL)critical
bigTopText:(NSString*)bigTopText
smallBottomText:(NSString*)smallBottomText
defaultButtonTitle:(NSString*)defaultButtonTitle
leftButtonTitle:(NSString*)leftButtonTitle
middleButtonTitle:(NSString*)middleButtonTitle
helpButtonAnchor:(NSString*)helpButtonAnchor
checkboxTitle:(NSString*)checkboxTitle
checkboxState:(NSCellStateValue*)checkboxState
alertReturn:(int*)alertReturn ;
@end
/* Any or all of the arguments can be nil or NULL. Default usage:
[SSAlert runModalBadgeCritical:NO
bigTopText:nil
smallBottomText:nil
defaultButtonTitle:nil
leftButtonTitle:nil
middleButtonTitle:nil
helpButtonAnchor:nil
checkboxTitle:nil
checkboxState:NULL
alertReturn:NULL ];
*/
/* helpButtonAnchor requires that the applications Help Book contains
a tag, which in this case would be something like:
<a name="helpButtonAnchor"></a>
and also that the Help Book be indexed by Help Book Indexer. */
****** SSAlert.m ************************
#import "SSAlert.h"
@implementation SSAlert
- (void)setHelpButton:(NSButton*)helpButton {
[helpButton retain] ;
[_helpButton release] ;
_helpButton = helpButton ;
}
- (NSButton*)helpButton {
return _helpButton ;
}
- (void)setHelpButtonAnchor:(NSString*)helpButtonAnchor {
[helpButtonAnchor retain] ;
[_helpButtonAnchor release] ;
_helpButtonAnchor = helpButtonAnchor ;
}
- (NSString*)helpButtonAnchor {
return _helpButtonAnchor ;
}
- (void)setCheckbox:(NSButton*)checkbox {
[checkbox retain] ;
[_checkbox release] ;
_checkbox = checkbox ;
}
- (NSButton*)checkbox {
return _checkbox ;
}
- (void)setPanel:(NSPanel*)panel {
[panel retain] ;
[_panel release] ;
_panel = panel ;
}
- (NSPanel*)panel {
return _panel ;
}
- (void)addCheckboxWithTitle:(NSString*)title {
// Most of this method was written by Tristan O'Tierney and was
// ripped from http://www.cocoadev.com/index.pl?NSAlertCheckbox
float checkboxPadding = 14.0f; // according to the apple HIG
NSWindow *window = [self panel];
NSView *content = [window contentView];
// Find the position of the lower (small-fonted) text field
NSArray *subviews = [content subviews];
NSEnumerator *en = [subviews objectEnumerator];
NSView *subview = nil;
NSTextField *messageText = nil;
int count = 0;
while (subview = [en nextObject]) {
if ([subview isKindOfClass:[NSTextField class]]) {
count++;
if (count == 2) {
messageText = (NSTextField *)subview;
}
}
}
NSButton* checkbox = [[NSButton alloc] initWithFrame:NSZeroRect] ;
[self setCheckbox:checkbox] ;
[checkbox release] ;
[checkbox setButtonType:NSSwitchButton] ;
[checkbox setTitle:title] ;
if (messageText) {
// Make the checkbox font match the text area above it
[checkbox setFont:[messageText font]];
[checkbox sizeToFit];
// Expand the window
NSRect windowFrame = [window frame];
NSRect checkboxFrame = [checkbox frame];
windowFrame.size.height += checkboxFrame.size.height +
checkboxPadding;
[window setFrame:windowFrame display:YES];
checkboxFrame.origin.y = [messageText frame].origin.y -
checkboxFrame.size.height - checkboxPadding;
checkboxFrame.origin.x = [messageText frame].origin.x;
[checkbox setFrame:checkboxFrame];
}
[content addSubview:checkbox] ;
}
- (void)addHelpButton {
NSWindow *window = [self panel];
NSView *content = [window contentView];
// Find the position of other buttons
NSArray *subviews = [content subviews];
NSEnumerator *en = [subviews objectEnumerator];
NSView *subview = nil;
// Find the image which is on the left (we'll use its frame.origin.x,
// and find any of the other buttons (we'll use its frame.origin.y)
NSImageView* imageView = nil;
NSButton* otherButton = nil;
while (subview = [en nextObject]) {
if ([subview isKindOfClass:[NSImageView class]]) {
imageView = (NSImageView*)subview ;
}
else if ([subview isKindOfClass:[NSButton class]]) {
otherButton = (NSButton*)subview ;
}
}
// Create an initial frame of correct size but position zero
NSRect frame ;
frame.origin = NSZeroPoint ;
// This is the size of a Help button in Interface Builder:
frame.size.width = 21.0 ;
frame.size.height = 23.0 ;
// Create and set the button
NSButton* helpButton = [[NSButton alloc] initWithFrame:frame] ;
[self setHelpButton:helpButton] ;
[helpButton release] ;
// Calculate x position
float x = 0.0 ;
if (imageView) {
x = [imageView frame].origin.x + [imageView frame].size.width/2 ;
}
frame.origin.x = x - frame.size.width/2 ;
// Calculate y position
float y = 0.0 ;
if (otherButton) {
y = [otherButton frame].origin.y + [otherButton frame].size.height/2
;
}
frame.origin.y = y - frame.size.height/2 ;
[helpButton setFrame:frame] ;
[helpButton setBezelStyle:NSHelpButtonBezelStyle] ;
[helpButton setTarget:self] ;
[helpButton setAction:@selector(help:)] ;
[helpButton setTitle:@""] ;
[content addSubview:helpButton];
}
- (IBAction)help:(id)sender {
NSString *locBookName = [[NSBundle mainBundle]
objectForInfoDictionaryKey: @"CFBundleHelpBookName"];
[[NSHelpManager sharedHelpManager] openHelpAnchor:[self
helpButtonAnchor] inBook:locBookName]; }
- (void) dealloc {
[self setHelpButton:nil] ;
[self setHelpButtonAnchor:nil] ;
[self setCheckbox:nil] ;
NSReleaseAlertPanel([self panel]) ;
[self setPanel:nil] ;
[super dealloc];
}
+ (void)runModalBadgeCritical:(BOOL)critical
bigTopText:(NSString*)bigTopText
smallBottomText:(NSString*)smallBottomText
defaultButtonTitle:(NSString*)defaultButtonTitle
leftButtonTitle:(NSString*)leftButtonTitle
middleButtonTitle:(NSString*)middleButtonTitle
helpButtonAnchor:(NSString*)helpButtonAnchor
checkboxTitle:(NSString*)checkboxTitle
checkboxState:(NSCellStateValue*)pCheckboxState
alertReturn:(int*)pAlertReturn {
SSAlert* instance = [[super alloc] init] ;
NSPanel* panel ;
if (!defaultButtonTitle) {
defaultButtonTitle = @"OK" ;
}
if (!smallBottomText) {
// Golly, thanks, Apple!! It's so thoughtful of you to
// raise an exception when I try and create an alert with no
// "informative text" by passing nil as the second argument.
// Maybe this is what I really wanted, though....
smallBottomText = @"" ;
}
if (critical) {
panel = NSGetCriticalAlertPanel(bigTopText,
smallBottomText,
defaultButtonTitle,
leftButtonTitle,
middleButtonTitle) ;
}
else {
panel = NSGetAlertPanel(bigTopText,
smallBottomText,
defaultButtonTitle,
leftButtonTitle,
middleButtonTitle) ;
}
[instance setPanel:panel] ;
if (helpButtonAnchor) {
[instance setHelpButtonAnchor:helpButtonAnchor] ;
[instance addHelpButton] ;
}
int checkboxState ;
if (pCheckboxState) {
checkboxState = *pCheckboxState ;
}
if (checkboxTitle) {
[instance addCheckboxWithTitle:checkboxTitle] ;
// Set initial state of checkbox
[[instance checkbox] setState:checkboxState] ;
}
// Run the sucker
int alertReturn = [NSApp runModalForWindow:panel];
// Clean up and self-destruct
[panel orderOut:self] ;
if (pCheckboxState) {
*pCheckboxState = [[instance checkbox] state] ;
}
if (pAlertReturn) {
*pAlertReturn = alertReturn ;
}
[instance release] ;
[NSApp stopModal] ;
}
@end
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden