/* * A hacked version of GNUstep's GSAlertPanel to run non-modally * Yanked from GNUstep's NSAlert.m. * * This is a bit awkward to put in the application, but I can't think * of a better way to do this. If I hack GSAlertPanel from the outside, * 1. it depends on delicate internals, and 2. it's not portable to Mac. */ /* Copyright (C) 1998, 2000, 2004 Free Software Foundation, Inc. Author: Fred Kiefer Date: July 2004 GSAlertPanel and alert panel functions implementation Author: Richard Frith-Macdonald Date: 1998 GSAlertPanel and alert panel functions cleanup and improvements (scroll view) Author: Pascal J. Bourguignon > Date: 2000-03-08 This file is part of the GNUstep GUI Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #import #import #import #import #ifndef GNUSTEP #define RELEASE(x) [(x) release] #endif @implementation NonModalAlertPanel static const float WinMinWidth = 362.0; static const float WinMinHeight = 161.0; static const float IconSide = 48.0; static const float IconBottom = -56.0; // from the top of the window. static const float IconLeft = 8.0; static const float TitleLeft = 64.0; static const float TitleMinRight = 8.0; static const float LineHeight = 2.0; static const float LineBottom = -66.0; // from the top of the window. static const float LineLeft = 0.0; static const float MessageHorzMargin = 8.0; // 5 is too little margin. static const float MessageVertMargin = 6.0; // from the top of the buttons. static const float MessageTop = -72; // from the top of the window; static const float ButtonBottom = 8.0; // from the bottom of the window. static const float ButtonMargin = 8.0; static const float ButtonInterspace = 10.0; static const float ButtonMinHeight = 24.0; static const float ButtonMinWidth = 72.0; #define MessageFont [NSFont messageFontOfSize: 14] - (void) dealloc { RELEASE(defButton); RELEASE(altButton); RELEASE(othButton); RELEASE(icoButton); RELEASE(titleField); RELEASE(messageField); RELEASE(scroll); [super dealloc]; } static NSScrollView* makeScrollViewWithRect(NSRect rect) { float lineHeight = [MessageFont boundingRectForFont].size.height; NSScrollView *scroll = [[NSScrollView alloc]initWithFrame: rect]; [scroll setBorderType: NSLineBorder]; [scroll setBackgroundColor: [NSColor controlBackgroundColor]]; [scroll setHasHorizontalScroller: YES]; [scroll setHasVerticalScroller: YES]; [scroll setScrollsDynamically: YES]; [scroll setLineScroll: lineHeight]; [scroll setPageScroll: lineHeight*10.0]; return scroll; } - (NSButton*) _makeButtonWithRect: (NSRect)rect { NSButton *button = [[NSButton alloc] initWithFrame: rect]; [button setAutoresizingMask: NSViewMinXMargin | NSViewMaxYMargin]; #ifdef GNUSTEP [button setButtonType: NSMomentaryPushButton]; #endif [button setTitle: @""]; [button setTarget: self]; [button setAction: @selector(buttonAction:)]; [button setFont: [NSFont systemFontOfSize: 0]]; return button; } #define useControl(control) ([control superview] != nil) static void setControl(NSView* content, id control, NSString *title) { if (title != nil) { if ([control respondsToSelector: @selector(setTitle:)]) { [control setTitle: title]; } else if ([control respondsToSelector: @selector(setStringValue:)]) { [control setStringValue: title]; } [control sizeToFit]; if (!useControl(control)) { [content addSubview: control]; } } else if (useControl(control)) { [control removeFromSuperview]; } } - (id) init { NSRect rect; NSImage *image; NSBox *box; NSView *content; NSRect r = NSMakeRect(0.0, 0.0, WinMinWidth, WinMinHeight); NSFont *titleFont = [NSFont systemFontOfSize: 18.0]; float titleHeight = [titleFont boundingRectForFont].size.height; self = [self initWithContentRect: r styleMask: NSTitledWindowMask backing: NSBackingStoreRetained defer: YES screen: nil]; if (self == nil) return nil; [self setTitle: @" "]; content = [self contentView]; // we're an ATTENTION panel, therefore: [self setHidesOnDeactivate: NO]; [self setBecomesKeyOnlyIfNeeded: NO]; // First, the subviews that will be positioned automatically. rect.size.height = IconSide; rect.size.width = IconSide; rect.origin.y = r.origin.y + r.size.height + IconBottom; rect.origin.x = IconLeft; icoButton = [[NSButton alloc] initWithFrame: rect]; [icoButton setAutoresizingMask: NSViewMaxXMargin|NSViewMinYMargin]; [icoButton setBordered: NO]; [icoButton setEnabled: NO]; [icoButton setImagePosition: NSImageOnly]; image = [[NSApplication sharedApplication] applicationIconImage]; [icoButton setImage: image]; [content addSubview: icoButton]; // Title rect.size.height = 0.0; // will be sized to fit anyway. rect.size.width = 0.0; // will be sized to fit anyway. rect.origin.y = r.origin.y + r.size.height + IconBottom + (IconSide - titleHeight)/2;; rect.origin.x = TitleLeft; titleField = [[NSTextField alloc] initWithFrame: rect]; [titleField setAutoresizingMask: NSViewMinYMargin]; [titleField setEditable: NO]; [titleField setSelectable: YES]; [titleField setBezeled: NO]; [titleField setDrawsBackground: NO]; [titleField setStringValue: @""]; [titleField setFont: titleFont]; // Horizontal line rect.size.height = LineHeight; rect.size.width = r.size.width; rect.origin.y = r.origin.y + r.size.height + LineBottom; rect.origin.x = LineLeft; box = [[NSBox alloc] initWithFrame: rect]; [box setAutoresizingMask: NSViewWidthSizable | NSViewMinYMargin]; [box setTitlePosition: NSNoTitle]; [box setBorderType: NSGrooveBorder]; [content addSubview: box]; RELEASE(box); // Then, make the subviews that'll be sized by sizePanelToFit; rect.size.height = 0.0; rect.size.width = 0.0; rect.origin.y = 0.0; rect.origin.x = 0.0; messageField = [[NSTextField alloc] initWithFrame: rect]; [messageField setEditable: NO]; [messageField setSelectable: YES]; /* PJB: How do you want the user to report an error message if it is not selectable? Any text visible on the screen should always be selectable for a copy-and-paste. Hence, setSelectable: YES. */ [messageField setBezeled: NO]; [messageField setDrawsBackground: YES]; [messageField setBackgroundColor: [NSColor controlBackgroundColor]]; [messageField setAlignment: NSCenterTextAlignment]; [messageField setStringValue: @""]; [messageField setFont: MessageFont]; defButton = [self _makeButtonWithRect: rect]; [defButton setKeyEquivalent: @"\r"]; #ifdef GNUSTEP [defButton setHighlightsBy: NSPushInCellMask | NSChangeGrayCellMask | NSContentsCellMask]; [defButton setImagePosition: NSImageRight]; [defButton setImage: [NSImage imageNamed: @"common_ret"]]; [defButton setAlternateImage: [NSImage imageNamed: @"common_retH"]]; #endif altButton = [self _makeButtonWithRect: rect]; othButton = [self _makeButtonWithRect: rect]; rect.size.height = 80.0; scroll = makeScrollViewWithRect(rect); result = NSAlertErrorReturn; return self; } - (void) sizePanelToFit { NSRect bounds; NSSize ssize; // screen size (corrected). NSSize bsize; // button size (max of the three). NSSize wsize = {0.0, 0.0}; // window size (computed). NSScreen *screen; NSView *content; NSButton *buttons[3]; float position = 0.0; int numberOfButtons; int i; BOOL needsScroll; BOOL couldNeedScroll; unsigned int mask = [self styleMask]; /* * Set size to the size of a content rectangle of a panel * that completely fills the screen. */ screen = [self screen]; if (screen == nil) { screen = [NSScreen mainScreen]; } bounds = [screen frame]; bounds = [NSWindow contentRectForFrameRect: bounds styleMask: mask]; ssize = bounds.size; // Let's size the title. if (useControl(titleField)) { NSRect rect = [titleField frame]; float width = TitleLeft + rect.size.width + TitleMinRight; if (wsize.width < width) { wsize.width = width; // ssize.width < width = > the title will be silently clipped. } } wsize.height = -LineBottom; // Let's count the buttons. bsize.width = ButtonMinWidth; bsize.height = ButtonMinHeight; buttons[0] = defButton; buttons[1] = altButton; buttons[2] = othButton; numberOfButtons = 0; for (i = 0; i < 3; i++) { if (useControl(buttons[i])) { NSRect rect = [buttons[i] frame]; if (bsize.width < rect.size.width) { bsize.width = rect.size.width; } if (bsize.height < rect.size.height) { bsize.height = rect.size.height; } numberOfButtons++; } } if (numberOfButtons > 0) { // (with NSGetAlertPanel, there could be zero buttons). float width = (bsize.width + ButtonInterspace) * numberOfButtons -ButtonInterspace + ButtonMargin * 2; /* * If the buttons are too wide or too high to fit in the screen, * then too bad! Thought it would be simple enough to put them * in the scroll view with the messageField. * TODO: See if we raise an exception here or if we let the * QA people detect this kind of problem. */ if (wsize.width < width) { wsize.width = width; } wsize.height += ButtonBottom + bsize.height; } // Let's see the size of the messageField and how to place it. needsScroll = NO; couldNeedScroll = useControl(messageField); if (couldNeedScroll) { NSRect rect = [messageField frame]; float width = rect.size.width + 2*MessageHorzMargin; if (wsize.width < width) { wsize.width = width; } // The title could be large too, without implying a scroll view. needsScroll = (ssize.width < width); /* * But only the messageField can impose a great height, therefore * we check it along in the next paragraph. */ wsize.height += rect.size.height + 2 * MessageVertMargin; } else { wsize.height += MessageVertMargin; } // Strategically placed here, we resize the window. if (ssize.height < wsize.height) { wsize.height = ssize.height; needsScroll = couldNeedScroll; } else if (wsize.height < WinMinHeight) { wsize.height = WinMinHeight; } if (needsScroll) { wsize.width += [NSScroller scrollerWidth] + 4.0; } if (ssize.width < wsize.width) { wsize.width = ssize.width; } else if (wsize.width < WinMinWidth) { wsize.width = WinMinWidth; } bounds = NSMakeRect(0, 0, wsize.width, wsize.height); bounds = [NSWindow frameRectForContentRect: bounds styleMask: mask]; [self setMaxSize: bounds.size]; [self setMinSize: bounds.size]; [self setContentSize: wsize]; content = [self contentView]; bounds = [content bounds]; // Now we can place the buttons. if (numberOfButtons > 0) { position = bounds.origin.x + bounds.size.width - ButtonMargin; for (i = 0; i < 3; i++) { if (useControl(buttons[i])) { NSRect rect; position -= bsize.width; rect.origin.x = position; rect.origin.y = bounds.origin.y + ButtonBottom; rect.size.width = bsize.width; rect.size.height = bsize.height; [buttons[i] setFrame: rect]; position -= ButtonInterspace; } } } // Finaly, place the message. if (useControl(messageField)) { NSRect mrect = [messageField frame]; if (needsScroll) { NSRect srect; // The scroll view takes all the space that is available. srect.origin.x = bounds.origin.x + MessageHorzMargin; if (numberOfButtons > 0) { srect.origin.y = bounds.origin.y + ButtonBottom + bsize.height + MessageVertMargin; } else { srect.origin.y = bounds.origin.y + MessageVertMargin; } srect.size.width = bounds.size.width - 2 * MessageHorzMargin; srect.size.height = bounds.origin.y + bounds.size.height + MessageTop - srect.origin.y; [scroll setFrame: srect]; if (!useControl(scroll)) { [content addSubview: scroll]; } [messageField removeFromSuperview]; mrect.origin.x = srect.origin.x + srect.size.width - mrect.size.width; mrect.origin.y = srect.origin.y + srect.size.height - mrect.size.height; [messageField setFrame: mrect]; [scroll setDocumentView: messageField]; [[scroll contentView] scrollToPoint: NSMakePoint(mrect.origin.x, mrect.origin.y + mrect.size.height - [[scroll contentView] bounds].size.height)]; [scroll reflectScrolledClipView: [scroll contentView]]; } else { float vmargin; /* * We must center vertically the messageField because * the window has a minimum size, thus may be greated * than expected. */ mrect.origin.x = (wsize.width - mrect.size.width)/2; vmargin = bounds.size.height + LineBottom-mrect.size.height; if (numberOfButtons > 0) { vmargin -= ButtonBottom + bsize.height; } vmargin/= 2.0; // if negative, it'll bite up and down. mrect.origin.y = bounds.origin.y + vmargin; if (numberOfButtons > 0) { mrect.origin.y += ButtonBottom + bsize.height; } [messageField setFrame: mrect]; } } else if (useControl(scroll)) { [scroll removeFromSuperview]; } [content display]; } - (void) buttonAction: (id)sender { if (sender == defButton) { result = NSAlertDefaultReturn; } else if (sender == altButton) { result = NSAlertAlternateReturn; } else if (sender == othButton) { result = NSAlertOtherReturn; } else { NSLog(@"alert panel buttonAction: from unknown sender - x%x\n", (unsigned)sender); } if (target && action && [target respondsToSelector:action]) [target performSelector:action withObject:(id)result]; [self close]; [self release]; } - (void) setTitle: (NSString*)title message: (NSString*)message def: (NSString*)defaultButton alt: (NSString*)alternateButton other: (NSString*)otherButton { NSView *content = [self contentView]; setControl(content, titleField, title); if (useControl(scroll)) { // TODO: Remove the following line once NSView is corrected. [scroll setDocumentView: nil]; [scroll removeFromSuperview]; [messageField removeFromSuperview]; } setControl(content, messageField, message); setControl(content, defButton, defaultButton); setControl(content, altButton, alternateButton); setControl(content, othButton, otherButton); if (useControl(defButton)) { [self makeFirstResponder: defButton]; } else { [self makeFirstResponder: self]; } /* a *working* nextKeyView chain: the trick is that the 3 buttons are not always used (displayed) so we have to set the nextKeyView *each* time. Maybe some optimisation in the logic of this block will be good, however it seems too risky for a (so) small reward */ { BOOL ud, ua, uo; ud = useControl(defButton); ua = useControl(altButton); uo = useControl(othButton); if (ud) { if (uo) [defButton setNextKeyView: othButton]; else if (ua) [defButton setNextKeyView: altButton]; else { #ifndef __APPLE__ [defButton setPreviousKeyView:nil]; #endif [defButton setNextKeyView: nil]; } } if (uo) { if (ua) [othButton setNextKeyView: altButton]; else if (ud) [othButton setNextKeyView: defButton]; else { #ifndef __APPLE__ [othButton setPreviousKeyView:nil]; #endif [othButton setNextKeyView: nil]; } } if (ua) { if (ud) [altButton setNextKeyView: defButton]; else if (uo) [altButton setNextKeyView: othButton]; else { #ifndef __APPLE__ [altButton setPreviousKeyView:nil]; #endif [altButton setNextKeyView: nil]; } } } result = NSAlertErrorReturn; /* If no button was pressed */ [self sizePanelToFit]; [self orderFront: self]; [self makeKeyWindow]; } - (void)setTarget:t { target = t; } - (void)setAction:(SEL)a { action = a; } - target { return target; } - (SEL)action { return action; } @end