![]() |
API 0.9.5
|
00001 /* 00002 * CPAlert.j 00003 * AppKit 00004 * 00005 * Created by Jake MacMullin. 00006 * Copyright 2008, Jake MacMullin. 00007 * 00008 * 11/10/2008 Ross Boucher 00009 * - Make it conform to style guidelines, general cleanup and enhancements 00010 * 11/10/2010 Antoine Mercadal 00011 * - Enhancements, better compliance with Cocoa API 00012 * 00013 * This library is free software; you can redistribute it and/or 00014 * modify it under the terms of the GNU Lesser General Public 00015 * License as published by the Free Software Foundation; either 00016 * version 2.1 of the License, or (at your option) any later version. 00017 * 00018 * This library is distributed in the hope that it will be useful, 00019 * but WITHOUT ANY WARRANTY; without even the implied warranty of 00020 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 00021 * Lesser General Public License for more details. 00022 * 00023 * You should have received a copy of the GNU Lesser General Public 00024 * License along with this library; if not, write to the Free Software 00025 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 00026 */ 00027 00028 00029 00030 /* 00031 @global 00032 @group CPAlertStyle 00033 */ 00034 CPWarningAlertStyle = 0; 00035 /* 00036 @global 00037 @group CPAlertStyle 00038 */ 00039 CPInformationalAlertStyle = 1; 00040 /* 00041 @global 00042 @group CPAlertStyle 00043 */ 00044 CPCriticalAlertStyle = 2; 00045 00068 @implementation CPAlert : CPView 00069 { 00070 BOOL _showHelp; 00071 BOOL _showSuppressionButton; 00072 00073 CPAlertStyle _alertStyle; 00074 CPString _title; 00075 CPView _accessoryView; 00076 CPImage _icon; 00077 00078 CPArray _buttons; 00079 CPCheckBox _suppressionButton; 00080 00081 id _delegate; 00082 id _modalDelegate; 00083 SEL _didEndSelector; 00084 00085 CPWindow _window; 00086 int _defaultWindowStyle; 00087 00088 CPImageView _alertImageView; 00089 CPTextField _informativeLabel; 00090 CPTextField _messageLabel; 00091 CPButton _alertHelpButton; 00092 00093 BOOL _needsLayout; 00094 } 00095 00096 #pragma mark Creating Alerts 00097 00108 + (CPAlert)alertWithMessageText:(CPString)aMessage defaultButton:(CPString)defaultButtonTitle alternateButton:(CPString)alternateButtonTitle otherButton:(CPString)otherButtonTitle informativeTextWithFormat:(CPString)informativeText 00109 { 00110 var alert = [[self alloc] init]; 00111 00112 [alert setMessageText:aMessage]; 00113 [alert addButtonWithTitle:defaultButtonTitle]; 00114 00115 if (alternateButtonTitle) 00116 [alert addButtonWithTitle:alternateButtonTitle]; 00117 00118 if (otherButtonTitle) 00119 [alert addButtonWithTitle:otherButtonTitle]; 00120 00121 if (informativeText) 00122 [alert setInformativeText:informativeText]; 00123 00124 return alert; 00125 } 00126 00133 + (CPAlert)alertWithError:(CPString)anErrorMessage 00134 { 00135 var alert = [[self alloc] init]; 00136 00137 [alert setMessageText:anErrorMessage]; 00138 [alert setAlertStyle:CPCriticalAlertStyle]; 00139 00140 return alert; 00141 } 00142 00146 - (id)init 00147 { 00148 self = [super init]; 00149 00150 if (self) 00151 { 00152 _buttons = []; 00153 _alertStyle = CPWarningAlertStyle; 00154 _showHelp = NO; 00155 _needsLayout = YES; 00156 _defaultWindowStyle = CPTitledWindowMask; 00157 00158 _messageLabel = [CPTextField labelWithTitle:@"Alert"]; 00159 _alertImageView = [[CPImageView alloc] init]; 00160 _informativeLabel = [[CPTextField alloc] init]; 00161 _suppressionButton = [CPCheckBox checkBoxWithTitle:@"Do not show this message again"]; 00162 00163 _alertHelpButton = [[CPButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 16.0, 16.0)]; 00164 [_alertHelpButton setTarget:self]; 00165 [_alertHelpButton setAction:@selector(_showHelp:)]; 00166 } 00167 00168 return self; 00169 } 00170 00171 #pragma mark Accessors 00172 00178 - (void)setTheme:(CPTheme)aTheme 00179 { 00180 if (aTheme === [self theme]) 00181 return; 00182 00183 if (aTheme === [CPTheme defaultHudTheme]) 00184 _defaultWindowStyle = CPTitledWindowMask | CPHUDBackgroundWindowMask; 00185 else 00186 _defaultWindowStyle = CPTitledWindowMask; 00187 00188 _window = nil; // will be regenerated at next layout 00189 _needsLayout = YES; 00190 [super setTheme:aTheme]; 00191 } 00192 00195 - (void)setWindowStyle:(int)aStyle 00196 { 00197 CPLog.warn("DEPRECATED: setWindowStyle: is deprecated. use setTheme: instead"); 00198 [self setTheme:(aStyle === CPHUDBackgroundWindowMask) ? [CPTheme defaultHudTheme] : [CPTheme defaultTheme]]; 00199 } 00200 00203 - (int)windowStyle 00204 { 00205 CPLog.warn("DEPRECATED: windowStyle: is deprecated. use theme instead"); 00206 return _defaultWindowStyle; 00207 } 00208 00209 00215 - (void)setMessageText:(CPString)aText 00216 { 00217 [_messageLabel setStringValue:aText]; 00218 _needsLayout = YES; 00219 } 00220 00225 - (CPString)messageText 00226 { 00227 return [_messageLabel stringValue]; 00228 } 00229 00235 - (void)setInformativeText:(CPString)aText 00236 { 00237 [_informativeLabel setStringValue:aText]; 00238 _needsLayout = YES; 00239 } 00240 00246 - (CPString)informativeText 00247 { 00248 return [_informativeLabel stringValue]; 00249 } 00250 00256 - (void)setTitle:(CPString)aTitle 00257 { 00258 _title = aTitle; 00259 [_window setTitle:aTitle]; 00260 } 00261 00267 - (void)setAccessoryView:(CPView)aView 00268 { 00269 _accessoryView = aView; 00270 _needsLayout = YES; 00271 } 00272 00278 - (void)setShowsSuppressionButton:(BOOL)shouldShowSuppressionButton 00279 { 00280 _showSuppressionButton = shouldShowSuppressionButton; 00281 _needsLayout = YES; 00282 } 00283 00284 #pragma mark Accessing Buttons 00285 00298 - (void)addButtonWithTitle:(CPString)aTitle 00299 { 00300 var bounds = [[_window contentView] bounds], 00301 count = [_buttons count], 00302 00303 button = [[CPButton alloc] initWithFrame:CGRectMakeZero()]; 00304 00305 [button setTitle:aTitle]; 00306 [button setTag:count]; 00307 [button setTarget:self]; 00308 [button setAction:@selector(_takeReturnCodeFrom:)]; 00309 00310 [[_window contentView] addSubview:button]; 00311 00312 if (count == 0) 00313 [button setKeyEquivalent:CPCarriageReturnCharacter]; 00314 else if ([aTitle lowercaseString] === @"cancel") 00315 [button setKeyEquivalent:CPEscapeFunctionKey]; 00316 00317 [_buttons insertObject:button atIndex:0]; 00318 } 00319 00320 #pragma mark Layout 00321 00325 - (void)_layoutMessageView 00326 { 00327 var inset = [self currentValueForThemeAttribute:@"content-inset"], 00328 sizeWithFontCorrection = 6.0, 00329 messageLabelWidth, 00330 messageLabelTextSize; 00331 00332 [_messageLabel setTextColor:[self currentValueForThemeAttribute:@"message-text-color"]]; 00333 [_messageLabel setFont:[self currentValueForThemeAttribute:@"message-text-font"]]; 00334 [_messageLabel setTextShadowColor:[self currentValueForThemeAttribute:@"message-text-shadow-color"]]; 00335 [_messageLabel setTextShadowOffset:[self currentValueForThemeAttribute:@"message-text-shadow-offset"]]; 00336 [_messageLabel setAlignment:[self currentValueForThemeAttribute:@"message-text-alignment"]]; 00337 [_messageLabel setLineBreakMode:CPLineBreakByWordWrapping]; 00338 00339 messageLabelWidth = CGRectGetWidth([[_window contentView] frame]) - inset.left - inset.right; 00340 messageLabelTextSize = [[_messageLabel stringValue] sizeWithFont:[_messageLabel font] inWidth:messageLabelWidth]; 00341 00342 [_messageLabel setFrame:CGRectMake(inset.left, inset.top, messageLabelTextSize.width, messageLabelTextSize.height + sizeWithFontCorrection)]; 00343 } 00344 00348 - (void)_layoutInformativeView 00349 { 00350 var inset = [self currentValueForThemeAttribute:@"content-inset"], 00351 defaultElementsMargin = [self currentValueForThemeAttribute:@"default-elements-margin"], 00352 sizeWithFontCorrection = 6.0, 00353 informativeLabelWidth, 00354 informativeLabelOriginY, 00355 informativeLabelTextSize; 00356 00357 [_informativeLabel setTextColor:[self currentValueForThemeAttribute:@"informative-text-color"]]; 00358 [_informativeLabel setFont:[self currentValueForThemeAttribute:@"informative-text-font"]]; 00359 [_informativeLabel setTextShadowColor:[self currentValueForThemeAttribute:@"informative-text-shadow-color"]]; 00360 [_informativeLabel setTextShadowOffset:[self currentValueForThemeAttribute:@"informative-text-shadow-offset"]]; 00361 [_informativeLabel setAlignment:[self currentValueForThemeAttribute:@"informative-text-alignment"]]; 00362 [_informativeLabel setLineBreakMode:CPLineBreakByWordWrapping]; 00363 00364 informativeLabelWidth = CGRectGetWidth([[_window contentView] frame]) - inset.left - inset.right; 00365 informativeLabelOriginY = [_messageLabel frameOrigin].y + [_messageLabel frameSize].height + defaultElementsMargin; 00366 informativeLabelTextSize = [[_informativeLabel stringValue] sizeWithFont:[_informativeLabel font] inWidth:informativeLabelWidth]; 00367 00368 [_informativeLabel setFrame:CGRectMake(inset.left, informativeLabelOriginY, informativeLabelTextSize.width, informativeLabelTextSize.height + sizeWithFontCorrection)]; 00369 } 00370 00374 - (void)_layoutAccessoryView 00375 { 00376 if (!_accessoryView) 00377 return; 00378 00379 var inset = [self currentValueForThemeAttribute:@"content-inset"], 00380 defaultElementsMargin = [self currentValueForThemeAttribute:@"default-elements-margin"], 00381 accessoryViewWidth = CGRectGetWidth([[_window contentView] frame]) - inset.left - inset.right, 00382 accessoryViewOriginY = CGRectGetMaxY([_informativeLabel frame]) + defaultElementsMargin; 00383 00384 [_accessoryView setFrameOrigin:CGPointMake(inset.left, accessoryViewOriginY)]; 00385 [[_window contentView] addSubview:_accessoryView]; 00386 } 00387 00391 - (void)_layoutSuppressionButton 00392 { 00393 if (!_showSuppressionButton) 00394 return; 00395 00396 var inset = [self currentValueForThemeAttribute:@"content-inset"], 00397 suppressionViewXOffset = [self currentValueForThemeAttribute:@"suppression-button-x-offset"], 00398 suppressionViewYOffset = [self currentValueForThemeAttribute:@"suppression-button-y-offset"], 00399 defaultElementsMargin = [self currentValueForThemeAttribute:@"default-elements-margin"], 00400 suppressionButtonViewOriginY = CGRectGetMaxY([(_accessoryView || _informativeLabel) frame]) + defaultElementsMargin + suppressionViewYOffset; 00401 00402 [_suppressionButton setTextColor:[self currentValueForThemeAttribute:@"suppression-button-text-color"]]; 00403 [_suppressionButton setFont:[self currentValueForThemeAttribute:@"suppression-button-text-font"]]; 00404 [_suppressionButton setTextShadowColor:[self currentValueForThemeAttribute:@"suppression-button-text-shadow-color"]]; 00405 [_suppressionButton setTextShadowOffset:[self currentValueForThemeAttribute:@"suppression-button-text-shadow-offset"]]; 00406 [_suppressionButton sizeToFit]; 00407 00408 [_suppressionButton setFrameOrigin:CGPointMake(inset.left + suppressionViewXOffset, suppressionButtonViewOriginY)]; 00409 [[_window contentView] addSubview:_suppressionButton]; 00410 } 00411 00415 - (CGSize)_layoutButtonsFromView:(CPView)lastView 00416 { 00417 var inset = [self currentValueForThemeAttribute:@"content-inset"], 00418 minimumSize = [self currentValueForThemeAttribute:@"size"], 00419 buttonOffset = [self currentValueForThemeAttribute:@"button-offset"], 00420 helpLeftOffset = [self currentValueForThemeAttribute:@"help-image-left-offset"], 00421 aRepresentativeButton = [_buttons objectAtIndex:0], 00422 defaultElementsMargin = [self currentValueForThemeAttribute:@"default-elements-margin"], 00423 panelSize = [[_window contentView] frame].size, 00424 buttonsOriginY, 00425 offsetX; 00426 00427 [aRepresentativeButton setTheme:[self theme]]; 00428 [aRepresentativeButton sizeToFit]; 00429 00430 panelSize.height = CGRectGetMaxY([lastView frame]) + defaultElementsMargin + [aRepresentativeButton frameSize].height; 00431 if (panelSize.height < minimumSize.height) 00432 panelSize.height = minimumSize.height; 00433 00434 buttonsOriginY = panelSize.height - [aRepresentativeButton frameSize].height + buttonOffset; 00435 offsetX = panelSize.width - inset.right; 00436 00437 for (var i = [_buttons count] - 1; i >= 0 ; i--) 00438 { 00439 var button = _buttons[i]; 00440 [button setTheme:[self theme]]; 00441 [button sizeToFit]; 00442 00443 var buttonFrame = [button frame], 00444 width = MAX(80.0, CGRectGetWidth(buttonFrame)), 00445 height = CGRectGetHeight(buttonFrame); 00446 00447 offsetX -= width; 00448 [button setFrame:CGRectMake(offsetX, buttonsOriginY, width, height)]; 00449 offsetX -= 10; 00450 } 00451 00452 if (_showHelp) 00453 { 00454 var helpImage = [self currentValueForThemeAttribute:@"help-image"], 00455 helpImagePressed = [self currentValueForThemeAttribute:@"help-image-pressed"], 00456 helpImageSize = helpImage ? [helpImage size] : CGSizeMakeZero(), 00457 helpFrame = CGRectMake(helpLeftOffset, buttonsOriginY, helpImageSize.width, helpImageSize.height); 00458 00459 [_alertHelpButton setImage:helpImage]; 00460 [_alertHelpButton setAlternateImage:helpImagePressed]; 00461 [_alertHelpButton setBordered:NO]; 00462 [_alertHelpButton setFrame:helpFrame]; 00463 } 00464 00465 panelSize.height += [aRepresentativeButton frameSize].height + inset.bottom + buttonOffset; 00466 return panelSize; 00467 } 00468 00472 - (void)layout 00473 { 00474 if (!_needsLayout) 00475 return; 00476 00477 if (!_window) 00478 [self _createWindowWithStyle:nil]; 00479 00480 var iconOffset = [self currentValueForThemeAttribute:@"image-offset"], 00481 theImage = _icon, 00482 finalSize; 00483 00484 if (!theImage) 00485 switch (_alertStyle) 00486 { 00487 case CPWarningAlertStyle: 00488 theImage = [self currentValueForThemeAttribute:@"warning-image"]; 00489 break; 00490 case CPInformationalAlertStyle: 00491 theImage = [self currentValueForThemeAttribute:@"information-image"]; 00492 break; 00493 case CPCriticalAlertStyle: 00494 theImage = [self currentValueForThemeAttribute:@"error-image"]; 00495 break; 00496 } 00497 00498 [_alertImageView setImage:theImage]; 00499 00500 var imageSize = theImage ? [theImage size] : CGSizeMakeZero(); 00501 [_alertImageView setFrame:CGRectMake(iconOffset.x, iconOffset.y, imageSize.width, imageSize.height)]; 00502 00503 [self _layoutMessageView]; 00504 [self _layoutInformativeView]; 00505 [self _layoutAccessoryView]; 00506 [self _layoutSuppressionButton]; 00507 00508 var lastView = _informativeLabel; 00509 if (_showSuppressionButton) 00510 lastView = _suppressionButton; 00511 else if (_accessoryView) 00512 lastView = _accessoryView 00513 00514 finalSize = [self _layoutButtonsFromView:lastView]; 00515 if ([_window styleMask] & CPDocModalWindowMask) 00516 finalSize.height -= 26; // adjust the absence of title bar 00517 00518 [_window setFrameSize:finalSize]; 00519 [_window center]; 00520 00521 _needsLayout = NO; 00522 } 00523 00524 #pragma mark Displaying Alerts 00525 00531 - (void)runModal 00532 { 00533 if (!([_window styleMask] & _defaultWindowStyle)) 00534 { 00535 _needsLayout = YES; 00536 [self _createWindowWithStyle:_defaultWindowStyle]; 00537 } 00538 00539 [self layout]; 00540 [CPApp runModalForWindow:_window]; 00541 } 00542 00551 - (void)beginSheetModalForWindow:(CPWindow)aWindow modalDelegate:(id)modalDelegate didEndSelector:(SEL)alertDidEndSelector contextInfo:(id)contextInfo 00552 { 00553 if (!([_window styleMask] & CPDocModalWindowMask)) 00554 { 00555 _needsLayout = YES; 00556 [self _createWindowWithStyle:CPDocModalWindowMask]; 00557 } 00558 00559 [self layout]; 00560 00561 _modalDelegate = modalDelegate; 00562 _didEndSelector = alertDidEndSelector; 00563 00564 [CPApp beginSheet:_window modalForWindow:aWindow modalDelegate:self didEndSelector:@selector(_alertDidEnd:returnCode:contextInfo:) contextInfo:contextInfo]; 00565 } 00566 00572 - (void)beginSheetModalForWindow:(CPWindow)aWindow 00573 { 00574 [self beginSheetModalForWindow:aWindow modalDelegate:nil didEndSelector:nil contextInfo:nil]; 00575 } 00576 00577 #pragma mark Private 00578 00582 - (void)_createWindowWithStyle:(int)forceStyle 00583 { 00584 var frame = CGRectMakeZero(); 00585 frame.size = [self currentValueForThemeAttribute:@"size"]; 00586 00587 _window = [[CPWindow alloc] initWithContentRect:frame styleMask:forceStyle || _defaultWindowStyle]; 00588 00589 if (_title) 00590 [_window setTitle:_title]; 00591 00592 var contentView = [_window contentView], 00593 count = [_buttons count]; 00594 00595 if (count) 00596 while (count--) 00597 [contentView addSubview:_buttons[count]]; 00598 else 00599 [self addButtonWithTitle:@"OK"]; 00600 00601 [contentView addSubview:_messageLabel]; 00602 [contentView addSubview:_alertImageView]; 00603 [contentView addSubview:_informativeLabel]; 00604 00605 if (_showHelp) 00606 [contentView addSubview:_alertHelpButton]; 00607 } 00608 00612 - (@action)_showHelp:(id)aSender 00613 { 00614 if ([_delegate respondsToSelector:@selector(alertShowHelp:)]) 00615 [_delegate alertShowHelp:self]; 00616 } 00617 00618 /* 00619 @ignore 00620 */ 00621 - (@action)_takeReturnCodeFrom:(id)aSender 00622 { 00623 if ([_window isSheet]) 00624 [CPApp endSheet:_window returnCode:[aSender tag]]; 00625 else 00626 { 00627 [CPApp abortModal]; 00628 [_window close]; 00629 00630 [self _alertDidEnd:_window returnCode:[aSender tag] contextInfo:nil]; 00631 } 00632 } 00633 00637 - (void)_alertDidEnd:(CPWindow)aWindow returnCode:(int)returnCode contextInfo:(id)contextInfo 00638 { 00639 if ([_delegate respondsToSelector:@selector(alertDidEnd:returnCode:)]) 00640 [_delegate alertDidEnd:self returnCode:returnCode]; 00641 00642 if (_didEndSelector) 00643 objj_msgSend(_modalDelegate, _didEndSelector, self, returnCode, contextInfo); 00644 00645 _modalDelegate = nil; 00646 _didEndSelector = nil; 00647 } 00648 00649 #pragma mark Theme Attributes 00650 00651 + (CPString)defaultThemeClass 00652 { 00653 return @"alert"; 00654 } 00655 00656 + (id)themeAttributes 00657 { 00658 return [CPDictionary dictionaryWithObjects:[CGSizeMake(400.0, 110.0), CGInsetMake(15, 15, 15, 50), 6, 10, 00659 CPJustifiedTextAlignment, [CPColor blackColor], [CPFont boldSystemFontOfSize:13.0], [CPNull null], CGSizeMakeZero(), 00660 CPJustifiedTextAlignment, [CPColor blackColor], [CPFont systemFontOfSize:12.0], [CPNull null], CGSizeMakeZero(), 00661 CGPointMake(15, 12), 00662 [CPNull null], 00663 [CPNull null], 00664 [CPNull null], 00665 [CPNull null], 00666 [CPNull null], 00667 [CPNull null], 00668 0.0, 00669 0.0, 00670 3.0, 00671 [CPColor blackColor], 00672 [CPFont systemFontOfSize:12.0], 00673 [CPNull null], 00674 0.0 00675 ] 00676 forKeys:[@"size", @"content-inset", @"informative-offset", @"button-offset", 00677 @"message-text-alignment", @"message-text-color", @"message-text-font", @"message-text-shadow-color", @"message-text-shadow-offset", 00678 @"informative-text-alignment", @"informative-text-color", @"informative-text-font", @"informative-text-shadow-color", @"informative-text-shadow-offset", 00679 @"image-offset", 00680 @"information-image", 00681 @"warning-image", 00682 @"error-image", 00683 @"help-image", 00684 @"help-image-left-offset", 00685 @"help-image-pressed", 00686 @"suppression-button-y-offset", 00687 @"suppression-button-x-offset", 00688 @"default-elements-margin", 00689 @"suppression-button-text-color", 00690 @"suppression-button-text-font", 00691 @"suppression-button-text-shadow-color", 00692 @"suppression-button-text-shadow-offset" 00693 ]]; 00694 } 00695 00696 @end 00697 00698 @implementation CPAlert (CPSynthesizedAccessors) 00699 00703 - (BOOL)showsHelp 00704 { 00705 return _showHelp; 00706 } 00707 00711 - (void)setShowsHelp:(BOOL)aValue 00712 { 00713 _showHelp = aValue; 00714 } 00715 00719 - (BOOL)showsSuppressionButton 00720 { 00721 return _showSuppressionButton; 00722 } 00723 00727 - (void)setShowsSuppressionButton:(BOOL)aValue 00728 { 00729 _showSuppressionButton = aValue; 00730 } 00731 00735 - (CPAlertStyle)alertStyle 00736 { 00737 return _alertStyle; 00738 } 00739 00743 - (void)setAlertStyle:(CPAlertStyle)aValue 00744 { 00745 _alertStyle = aValue; 00746 } 00747 00751 - (CPString)title 00752 { 00753 return _title; 00754 } 00755 00759 - (void)setTitle:(CPString)aValue 00760 { 00761 _title = aValue; 00762 } 00763 00767 - (CPView)accessoryView 00768 { 00769 return _accessoryView; 00770 } 00771 00775 - (void)setAccessoryView:(CPView)aValue 00776 { 00777 _accessoryView = aValue; 00778 } 00779 00783 - (CPImage)icon 00784 { 00785 return _icon; 00786 } 00787 00791 - (void)setIcon:(CPImage)aValue 00792 { 00793 _icon = aValue; 00794 } 00795 00799 - (CPArray)buttons 00800 { 00801 return _buttons; 00802 } 00803 00807 - (CPCheckBox)suppressionButton 00808 { 00809 return _suppressionButton; 00810 } 00811 00815 - (id)delegate 00816 { 00817 return _delegate; 00818 } 00819 00823 - (void)setDelegate:(id)aValue 00824 { 00825 _delegate = aValue; 00826 } 00827 00831 - (CPWindow)window 00832 { 00833 return _window; 00834 } 00835 00836 @end