| 1 | /* Copyright (C) 2008-2009 Peter Speck |
|---|
| 2 | * |
|---|
| 3 | * This program is free software: you can redistribute it and/or modify |
|---|
| 4 | * it under the terms of the GNU General Public License as published by |
|---|
| 5 | * the Free Software Foundation, either version 3 of the License, or |
|---|
| 6 | * (at your option) any later version. |
|---|
| 7 | * |
|---|
| 8 | * This program is distributed in the hope that it will be useful, |
|---|
| 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|---|
| 11 | * GNU General Public License for more details. |
|---|
| 12 | * |
|---|
| 13 | * You should have received a copy of the GNU General Public License |
|---|
| 14 | * along with this program. If not, see <http://www.gnu.org/licenses/>. |
|---|
| 15 | */ |
|---|
| 16 | |
|---|
| 17 | #import "AlertHelper.h" |
|---|
| 18 | #import "GBDebug.h" |
|---|
| 19 | |
|---|
| 20 | @interface AlertHelper () |
|---|
| 21 | |
|---|
| 22 | - (void)releaseDelegate; |
|---|
| 23 | - (BOOL)checkHasPreparedAlert:(NSString*)doingWhat; |
|---|
| 24 | |
|---|
| 25 | |
|---|
| 26 | - (void)simpleAlertDidEnd:(NSAlert *)alert returnCode:(int)returnCode |
|---|
| 27 | contextInfo:(void *)contextInfo; |
|---|
| 28 | |
|---|
| 29 | @end |
|---|
| 30 | |
|---|
| 31 | //======================================================================================================= |
|---|
| 32 | |
|---|
| 33 | @implementation AlertHelper |
|---|
| 34 | |
|---|
| 35 | - (id)initWindowView:(NSView*)windowView |
|---|
| 36 | withGbBundle:(NSBundle*)gbBundle |
|---|
| 37 | { |
|---|
| 38 | if ((self = [super init])) { |
|---|
| 39 | _windowView = [windowView retain]; |
|---|
| 40 | NSString* iconPath = [gbBundle pathForResource:@"tangerine" ofType:@"icns"]; |
|---|
| 41 | NSAssert(iconPath, @"Missing tangerine.icns"); |
|---|
| 42 | _glimmerIcon = [[[NSImage alloc] initWithContentsOfFile:iconPath] retain]; |
|---|
| 43 | } |
|---|
| 44 | return self; |
|---|
| 45 | } |
|---|
| 46 | |
|---|
| 47 | - (void)dealloc |
|---|
| 48 | { |
|---|
| 49 | [_glimmerIcon release]; |
|---|
| 50 | [_windowView release]; |
|---|
| 51 | [_accessoryView release]; |
|---|
| 52 | [super dealloc]; |
|---|
| 53 | } |
|---|
| 54 | |
|---|
| 55 | - (NSWindow*)window |
|---|
| 56 | { |
|---|
| 57 | return [_windowView window]; |
|---|
| 58 | } |
|---|
| 59 | |
|---|
| 60 | - (BOOL)canDisplayAlert |
|---|
| 61 | { |
|---|
| 62 | return !_alertIsOpen && [_windowView window]; |
|---|
| 63 | } |
|---|
| 64 | |
|---|
| 65 | #pragma mark ------------------------ private |
|---|
| 66 | |
|---|
| 67 | - (void)releaseDelegate |
|---|
| 68 | { |
|---|
| 69 | [_callbackDelegate release]; |
|---|
| 70 | _callbackDelegate = NULL; |
|---|
| 71 | _callbackSelector = NULL; |
|---|
| 72 | } |
|---|
| 73 | |
|---|
| 74 | - (void)dismissAlert |
|---|
| 75 | { |
|---|
| 76 | DebugNSLog(@"dismissAlert, open = %d", _alertIsOpen); |
|---|
| 77 | [self dismissAlertWithInvokeDelegate:NO withReturnCode:0]; |
|---|
| 78 | } |
|---|
| 79 | |
|---|
| 80 | - (void)dismissAlertWithInvokeDelegate:(BOOL)callDelegate withReturnCode:(NSInteger)returnCode |
|---|
| 81 | { |
|---|
| 82 | if (!_alertIsOpen) |
|---|
| 83 | return; |
|---|
| 84 | DebugNSLog(@"dismissAlertWithInvokeDelegate, isOpen=%d, callDelegate=%d, returnCode=%ld", _alertIsOpen, callDelegate, (long)returnCode); |
|---|
| 85 | _alertIsOpen = NO; // otherwise the method is called recursively by simpleAlertDidEnd |
|---|
| 86 | _lastAlertReturnCode = returnCode; |
|---|
| 87 | /* |
|---|
| 88 | Make the alert sheet go away before the delegate is called, so the delegate is able to show another sheet. |
|---|
| 89 | Must call [NSApp endSheet] to get the sheet really gone, otherwise we can't show another sheet. |
|---|
| 90 | [orderOut] performs the animation. |
|---|
| 91 | */ |
|---|
| 92 | if (_usingSheet) { |
|---|
| 93 | [NSApp endSheet:[_alert window]]; |
|---|
| 94 | [[_alert window] orderOut:self]; |
|---|
| 95 | _usingSheet = NO; |
|---|
| 96 | } |
|---|
| 97 | if (callDelegate && _callbackDelegate) { |
|---|
| 98 | // http://theocacao.com/document.page/264 |
|---|
| 99 | NSMethodSignature* sig = [[_callbackDelegate class] instanceMethodSignatureForSelector:_callbackSelector]; |
|---|
| 100 | NSInvocation* invo = [NSInvocation invocationWithMethodSignature:sig]; |
|---|
| 101 | [invo setTarget:_callbackDelegate]; |
|---|
| 102 | [invo setSelector:_callbackSelector]; |
|---|
| 103 | [invo setArgument:&returnCode atIndex:2]; |
|---|
| 104 | [invo invoke]; |
|---|
| 105 | [self releaseDelegate]; |
|---|
| 106 | } |
|---|
| 107 | [_alert autorelease]; |
|---|
| 108 | _alert = NULL; |
|---|
| 109 | [_accessoryView autorelease]; |
|---|
| 110 | _accessoryView = NULL; |
|---|
| 111 | } |
|---|
| 112 | |
|---|
| 113 | - (NSInteger)lastAlertReturnCode |
|---|
| 114 | { |
|---|
| 115 | return _lastAlertReturnCode; |
|---|
| 116 | } |
|---|
| 117 | |
|---|
| 118 | - (void)simpleAlertDidEnd:(NSAlert *)alert returnCode:(int)returnCode |
|---|
| 119 | contextInfo:(void *)contextInfo |
|---|
| 120 | { |
|---|
| 121 | if (alert == _alert) |
|---|
| 122 | [self dismissAlertWithInvokeDelegate:YES withReturnCode:returnCode]; |
|---|
| 123 | } |
|---|
| 124 | |
|---|
| 125 | - (BOOL)checkHasPreparedAlert:(NSString*)doingWhat |
|---|
| 126 | { |
|---|
| 127 | if (!_alert) { |
|---|
| 128 | NSLog(@"Can't %@ when no alert has been prepared.", doingWhat); |
|---|
| 129 | [GBDebug debugDumpStackframe]; |
|---|
| 130 | return NO; |
|---|
| 131 | } |
|---|
| 132 | if (_alertIsOpen) { |
|---|
| 133 | NSLog(@"Can't %@ when alert already is open.", doingWhat); |
|---|
| 134 | [GBDebug debugDumpStackframe]; |
|---|
| 135 | return NO; |
|---|
| 136 | } |
|---|
| 137 | return YES; |
|---|
| 138 | } |
|---|
| 139 | |
|---|
| 140 | |
|---|
| 141 | #pragma mark ------------------------ building blocks |
|---|
| 142 | |
|---|
| 143 | - (void)prepareWarningAlertWithTitle:(NSString*)title |
|---|
| 144 | withInformativeText:(NSString*)infoText |
|---|
| 145 | { |
|---|
| 146 | [self prepareAlertWithTitle:title withInformativeText:infoText withStyle:NSWarningAlertStyle]; |
|---|
| 147 | } |
|---|
| 148 | |
|---|
| 149 | - (void)prepareCriticalAlertWithTitle:(NSString*)title |
|---|
| 150 | withInformativeText:(NSString*)infoText |
|---|
| 151 | { |
|---|
| 152 | [self prepareAlertWithTitle:title withInformativeText:infoText withStyle:NSCriticalAlertStyle]; |
|---|
| 153 | } |
|---|
| 154 | |
|---|
| 155 | - (void)prepareInformativeAlertWithTitle:(NSString*)title |
|---|
| 156 | withInformativeText:(NSString*)infoText |
|---|
| 157 | { |
|---|
| 158 | [self prepareAlertWithTitle:title withInformativeText:infoText withStyle:NSInformationalAlertStyle]; |
|---|
| 159 | } |
|---|
| 160 | |
|---|
| 161 | - (void)prepareAlertWithTitle:(NSString*)title |
|---|
| 162 | withInformativeText:(NSString*)infoText |
|---|
| 163 | withStyle:(NSAlertStyle)alertStyle |
|---|
| 164 | { |
|---|
| 165 | if (_alertIsOpen) { |
|---|
| 166 | [GBDebug debugDumpStackframe]; |
|---|
| 167 | NSLog(@"Can't prepare another alert while an alert is open."); |
|---|
| 168 | return; |
|---|
| 169 | } |
|---|
| 170 | if (!title) { |
|---|
| 171 | [GBDebug debugDumpStackframe]; |
|---|
| 172 | NSLog(@"the title must not be NULL"); |
|---|
| 173 | title = @"?"; // better than crashing with NSAssert. |
|---|
| 174 | } |
|---|
| 175 | if (!_alert) { |
|---|
| 176 | _alert = [[NSAlert alloc] init]; // release in delegate |
|---|
| 177 | [_alert setIcon:_glimmerIcon]; |
|---|
| 178 | } |
|---|
| 179 | [_alert setMessageText:title]; |
|---|
| 180 | if (infoText) // NSAlert can't handle NULL |
|---|
| 181 | [_alert setInformativeText:infoText]; |
|---|
| 182 | } |
|---|
| 183 | |
|---|
| 184 | - (void)setDelegate:(NSObject*)callbackDelegate |
|---|
| 185 | withSelector:(SEL)callbackSelector |
|---|
| 186 | { |
|---|
| 187 | NSAssert((_callbackDelegate != NULL) == (_callbackSelector != NULL), @"_callbackDelegate / _callbackSelector"); |
|---|
| 188 | if (![self checkHasPreparedAlert:@"set delegate"]) |
|---|
| 189 | return; |
|---|
| 190 | [self releaseDelegate]; |
|---|
| 191 | _callbackDelegate = [callbackDelegate retain]; |
|---|
| 192 | _callbackSelector = callbackSelector; |
|---|
| 193 | } |
|---|
| 194 | |
|---|
| 195 | - (NSButton*)addButtonWithTitle:(NSString*)title |
|---|
| 196 | { |
|---|
| 197 | if (!title) { |
|---|
| 198 | [GBDebug debugDumpStackframe]; |
|---|
| 199 | NSLog(@"the button title must not be NULL"); |
|---|
| 200 | title = @"?"; // better than crashing with NSAssert. |
|---|
| 201 | } |
|---|
| 202 | if (![self checkHasPreparedAlert:@"add button"]) |
|---|
| 203 | return NULL; |
|---|
| 204 | return [_alert addButtonWithTitle:title]; |
|---|
| 205 | } |
|---|
| 206 | |
|---|
| 207 | - (void)setAccessoryView:(NSView*)view |
|---|
| 208 | { |
|---|
| 209 | if (!view) { |
|---|
| 210 | [GBDebug debugDumpStackframe]; |
|---|
| 211 | NSLog(@"the view param must not be NULL"); |
|---|
| 212 | return; |
|---|
| 213 | } |
|---|
| 214 | if (![self checkHasPreparedAlert:@"set accessory view"]) |
|---|
| 215 | return; |
|---|
| 216 | if (view == _accessoryView) |
|---|
| 217 | return; |
|---|
| 218 | NSView* oldView = _accessoryView; |
|---|
| 219 | _accessoryView = [view retain]; |
|---|
| 220 | [_alert setAccessoryView:view]; |
|---|
| 221 | [oldView release]; |
|---|
| 222 | } |
|---|
| 223 | |
|---|
| 224 | - (void)layoutAlert |
|---|
| 225 | { |
|---|
| 226 | if (![self checkHasPreparedAlert:@"layout alert"]) |
|---|
| 227 | return; |
|---|
| 228 | [_alert layout]; |
|---|
| 229 | } |
|---|
| 230 | |
|---|
| 231 | - (void)showPreparedAlert |
|---|
| 232 | { |
|---|
| 233 | if (![self checkHasPreparedAlert:@"show alert"]) |
|---|
| 234 | return; |
|---|
| 235 | NSWindow* window = [self window]; |
|---|
| 236 | _alertIsOpen = YES; |
|---|
| 237 | if (!window) { |
|---|
| 238 | NSInteger returnCode = [_alert runModal]; |
|---|
| 239 | [self dismissAlertWithInvokeDelegate:YES withReturnCode:returnCode]; |
|---|
| 240 | return; |
|---|
| 241 | } |
|---|
| 242 | _usingSheet = YES; |
|---|
| 243 | [_alert beginSheetModalForWindow:window |
|---|
| 244 | modalDelegate:self |
|---|
| 245 | didEndSelector:@selector(simpleAlertDidEnd:returnCode:contextInfo:) |
|---|
| 246 | contextInfo:NULL]; |
|---|
| 247 | } |
|---|
| 248 | |
|---|
| 249 | @end |
|---|