API  0.9.6
 All Classes Files Functions Variables Macros Groups Pages
CPAlert.j
Go to the documentation of this file.
1 /*
2  * CPAlert.j
3  * AppKit
4  *
5  * Created by Jake MacMullin.
6  * Copyright 2008, Jake MacMullin.
7  *
8  * 11/10/2008 Ross Boucher
9  * - Make it conform to style guidelines, general cleanup and enhancements
10  * 11/10/2010 Antoine Mercadal
11  * - Enhancements, better compliance with Cocoa API
12  *
13  * This library is free software; you can redistribute it and/or
14  * modify it under the terms of the GNU Lesser General Public
15  * License as published by the Free Software Foundation; either
16  * version 2.1 of the License, or (at your option) any later version.
17  *
18  * This library is distributed in the hope that it will be useful,
19  * but WITHOUT ANY WARRANTY; without even the implied warranty of
20  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21  * Lesser General Public License for more details.
22  *
23  * You should have received a copy of the GNU Lesser General Public
24  * License along with this library; if not, write to the Free Software
25  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
26  */
27 
28 
29 
30 /*
31  @global
32  @group CPAlertStyle
33 */
35 /*
36  @global
37  @group CPAlertStyle
38 */
40 /*
41  @global
42  @group CPAlertStyle
43 */
45 
68 @implementation CPAlert : CPObject
69 {
70  BOOL _showHelp;
71  BOOL _showSuppressionButton;
72 
73  CPAlertStyle _alertStyle;
74  CPString _title;
75  CPView _accessoryView;
76  CPImage _icon;
77 
78  CPArray _buttons;
79  CPCheckBox _suppressionButton;
80 
81  id _delegate;
82  id _modalDelegate;
83  SEL _didEndSelector;
84 
85  _CPAlertThemeView _themeView;
86  CPWindow _window;
87  int _defaultWindowStyle;
88 
89  CPImageView _alertImageView;
90  CPTextField _informativeLabel;
91  CPTextField _messageLabel;
92  CPButton _alertHelpButton;
93 
94  BOOL _needsLayout;
95 }
96 
97 #pragma mark Creating Alerts
98 
109 + (CPAlert)alertWithMessageText:(CPString)aMessage defaultButton:(CPString)defaultButtonTitle alternateButton:(CPString)alternateButtonTitle otherButton:(CPString)otherButtonTitle informativeTextWithFormat:(CPString)informativeText
110 {
111  var newAlert = [[self alloc] init];
112 
113  [newAlert setMessageText:aMessage];
114  [newAlert addButtonWithTitle:defaultButtonTitle];
115 
116  if (alternateButtonTitle)
117  [newAlert addButtonWithTitle:alternateButtonTitle];
118 
119  if (otherButtonTitle)
120  [newAlert addButtonWithTitle:otherButtonTitle];
121 
122  if (informativeText)
123  [newAlert setInformativeText:informativeText];
124 
125  return newAlert;
126 }
127 
134 + (CPAlert)alertWithError:(CPString)anErrorMessage
135 {
136  var newAlert = [[self alloc] init];
137 
138  [newAlert setMessageText:anErrorMessage];
139  [newAlert setAlertStyle:CPCriticalAlertStyle];
140 
141  return newAlert;
142 }
143 
147 - (id)init
148 {
149  self = [super init];
150 
151  if (self)
152  {
153  _buttons = [];
154  _alertStyle = CPWarningAlertStyle;
155  _showHelp = NO;
156  _needsLayout = YES;
157  _defaultWindowStyle = CPTitledWindowMask;
158  _themeView = [_CPAlertThemeView new];
159 
160  _messageLabel = [CPTextField labelWithTitle:@"Alert"];
161  _alertImageView = [[CPImageView alloc] init];
162  _informativeLabel = [[CPTextField alloc] init];
163  _suppressionButton = [CPCheckBox checkBoxWithTitle:@"Do not show this message again"];
164 
165  _alertHelpButton = [[CPButton alloc] initWithFrame:CGRectMake(0.0, 0.0, 16.0, 16.0)];
166  [_alertHelpButton setTarget:self];
167  [_alertHelpButton setAction:@selector(_showHelp:)];
168  }
169 
170  return self;
171 }
172 
173 #pragma mark Accessors
174 
175 - (CPTheme)theme
176 {
177  return [_themeView theme];
178 }
179 
185 - (void)setTheme:(CPTheme)aTheme
186 {
187  if (aTheme === [self theme])
188  return;
189 
190  if (aTheme === [CPTheme defaultHudTheme])
191  _defaultWindowStyle = CPTitledWindowMask | CPHUDBackgroundWindowMask;
192  else
193  _defaultWindowStyle = CPTitledWindowMask;
194 
195  _window = nil; // will be regenerated at next layout
196  _needsLayout = YES;
197  [_themeView setTheme:aTheme];
198 }
199 
200 - (void)setValue:(id)aValue forThemeAttribute:(CPString)aName
201 {
202  [_themeView setValue:aValue forThemeAttribute:aName];
203 }
204 
205 - (void)setValue:(id)aValue forThemeAttribute:(CPString)aName inState:(CPThemeState)aState
206 {
207  [_themeView setValue:aValue forThemeAttribute:aName inState:aState];
208 }
209 
210 
213 - (void)setWindowStyle:(int)aStyle
214 {
215  CPLog.warn("DEPRECATED: setWindowStyle: is deprecated. use setTheme: instead");
216  [self setTheme:(aStyle === CPHUDBackgroundWindowMask) ? [CPTheme defaultHudTheme] : [CPTheme defaultTheme]];
217 }
218 
221 - (int)windowStyle
222 {
223  CPLog.warn("DEPRECATED: windowStyle: is deprecated. use theme instead");
224  return _defaultWindowStyle;
225 }
226 
227 
233 - (void)setMessageText:(CPString)aText
234 {
235  [_messageLabel setStringValue:aText];
236  _needsLayout = YES;
237 }
238 
243 - (CPString)messageText
244 {
245  return [_messageLabel stringValue];
246 }
247 
253 - (void)setInformativeText:(CPString)aText
254 {
255  [_informativeLabel setStringValue:aText];
256  _needsLayout = YES;
257 }
258 
264 - (CPString)informativeText
265 {
266  return [_informativeLabel stringValue];
267 }
268 
274 - (void)setTitle:(CPString)aTitle
275 {
276  _title = aTitle;
277  [_window setTitle:aTitle];
278 }
279 
285 - (void)setAccessoryView:(CPView)aView
286 {
287  _accessoryView = aView;
288  _needsLayout = YES;
289 }
290 
296 - (void)setShowsSuppressionButton:(BOOL)shouldShowSuppressionButton
297 {
298  _showSuppressionButton = shouldShowSuppressionButton;
299  _needsLayout = YES;
300 }
301 
302 #pragma mark Accessing Buttons
303 
316 - (void)addButtonWithTitle:(CPString)aTitle
317 {
318  var bounds = [[_window contentView] bounds],
319  count = [_buttons count],
320 
321  button = [[CPButton alloc] initWithFrame:CGRectMakeZero()];
322 
323  [button setTitle:aTitle];
324  [button setTag:count];
325  [button setTarget:self];
326  [button setAction:@selector(_takeReturnCodeFrom:)];
327 
328  [[_window contentView] addSubview:button];
329 
330  if (count == 0)
331  [button setKeyEquivalent:CPCarriageReturnCharacter];
332  else if ([aTitle lowercaseString] === @"cancel")
333  [button setKeyEquivalent:CPEscapeFunctionKey];
334 
335  [_buttons insertObject:button atIndex:0];
336 }
337 
338 #pragma mark Layout
339 
343 - (void)_layoutMessageView
344 {
345  var inset = [_themeView currentValueForThemeAttribute:@"content-inset"],
346  sizeWithFontCorrection = 6.0,
347  messageLabelWidth,
348  messageLabelTextSize;
349 
350  [_messageLabel setTextColor:[_themeView currentValueForThemeAttribute:@"message-text-color"]];
351  [_messageLabel setFont:[_themeView currentValueForThemeAttribute:@"message-text-font"]];
352  [_messageLabel setTextShadowColor:[_themeView currentValueForThemeAttribute:@"message-text-shadow-color"]];
353  [_messageLabel setTextShadowOffset:[_themeView currentValueForThemeAttribute:@"message-text-shadow-offset"]];
354  [_messageLabel setAlignment:[_themeView currentValueForThemeAttribute:@"message-text-alignment"]];
355  [_messageLabel setLineBreakMode:CPLineBreakByWordWrapping];
356 
357  messageLabelWidth = CGRectGetWidth([[_window contentView] frame]) - inset.left - inset.right;
358  messageLabelTextSize = [[_messageLabel stringValue] sizeWithFont:[_messageLabel font] inWidth:messageLabelWidth];
359 
360  [_messageLabel setFrame:CGRectMake(inset.left, inset.top, messageLabelTextSize.width, messageLabelTextSize.height + sizeWithFontCorrection)];
361 }
362 
366 - (void)_layoutInformativeView
367 {
368  var inset = [_themeView currentValueForThemeAttribute:@"content-inset"],
369  defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"],
370  sizeWithFontCorrection = 6.0,
371  informativeLabelWidth,
372  informativeLabelOriginY,
373  informativeLabelTextSize;
374 
375  [_informativeLabel setTextColor:[_themeView currentValueForThemeAttribute:@"informative-text-color"]];
376  [_informativeLabel setFont:[_themeView currentValueForThemeAttribute:@"informative-text-font"]];
377  [_informativeLabel setTextShadowColor:[_themeView currentValueForThemeAttribute:@"informative-text-shadow-color"]];
378  [_informativeLabel setTextShadowOffset:[_themeView currentValueForThemeAttribute:@"informative-text-shadow-offset"]];
379  [_informativeLabel setAlignment:[_themeView currentValueForThemeAttribute:@"informative-text-alignment"]];
380  [_informativeLabel setLineBreakMode:CPLineBreakByWordWrapping];
381 
382  informativeLabelWidth = CGRectGetWidth([[_window contentView] frame]) - inset.left - inset.right;
383  informativeLabelOriginY = [_messageLabel frameOrigin].y + [_messageLabel frameSize].height + defaultElementsMargin;
384  informativeLabelTextSize = [[_informativeLabel stringValue] sizeWithFont:[_informativeLabel font] inWidth:informativeLabelWidth];
385 
386  [_informativeLabel setFrame:CGRectMake(inset.left, informativeLabelOriginY, informativeLabelTextSize.width, informativeLabelTextSize.height + sizeWithFontCorrection)];
387 }
388 
392 - (void)_layoutAccessoryView
393 {
394  if (!_accessoryView)
395  return;
396 
397  var inset = [_themeView currentValueForThemeAttribute:@"content-inset"],
398  defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"],
399  accessoryViewWidth = CGRectGetWidth([[_window contentView] frame]) - inset.left - inset.right,
400  accessoryViewOriginY = CGRectGetMaxY([_informativeLabel frame]) + defaultElementsMargin;
401 
402  [_accessoryView setFrameOrigin:CGPointMake(inset.left, accessoryViewOriginY)];
403  [[_window contentView] addSubview:_accessoryView];
404 }
405 
409 - (void)_layoutSuppressionButton
410 {
411  if (!_showSuppressionButton)
412  return;
413 
414  var inset = [_themeView currentValueForThemeAttribute:@"content-inset"],
415  suppressionViewXOffset = [_themeView currentValueForThemeAttribute:@"suppression-button-x-offset"],
416  suppressionViewYOffset = [_themeView currentValueForThemeAttribute:@"suppression-button-y-offset"],
417  defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"],
418  suppressionButtonViewOriginY = CGRectGetMaxY([(_accessoryView || _informativeLabel) frame]) + defaultElementsMargin + suppressionViewYOffset;
419 
420  [_suppressionButton setTextColor:[_themeView currentValueForThemeAttribute:@"suppression-button-text-color"]];
421  [_suppressionButton setFont:[_themeView currentValueForThemeAttribute:@"suppression-button-text-font"]];
422  [_suppressionButton setTextShadowColor:[_themeView currentValueForThemeAttribute:@"suppression-button-text-shadow-color"]];
423  [_suppressionButton setTextShadowOffset:[_themeView currentValueForThemeAttribute:@"suppression-button-text-shadow-offset"]];
424  [_suppressionButton sizeToFit];
425 
426  [_suppressionButton setFrameOrigin:CGPointMake(inset.left + suppressionViewXOffset, suppressionButtonViewOriginY)];
427  [[_window contentView] addSubview:_suppressionButton];
428 }
429 
433 - (CGSize)_layoutButtonsFromView:(CPView)lastView
434 {
435  var inset = [_themeView currentValueForThemeAttribute:@"content-inset"],
436  minimumSize = [_themeView currentValueForThemeAttribute:@"size"],
437  buttonOffset = [_themeView currentValueForThemeAttribute:@"button-offset"],
438  helpLeftOffset = [_themeView currentValueForThemeAttribute:@"help-image-left-offset"],
439  aRepresentativeButton = [_buttons objectAtIndex:0],
440  defaultElementsMargin = [_themeView currentValueForThemeAttribute:@"default-elements-margin"],
441  panelSize = [[_window contentView] frame].size,
442  buttonsOriginY,
443  offsetX;
444 
445  [aRepresentativeButton setTheme:[self theme]];
446  [aRepresentativeButton sizeToFit];
447 
448  panelSize.height = CGRectGetMaxY([lastView frame]) + defaultElementsMargin + [aRepresentativeButton frameSize].height;
449  if (panelSize.height < minimumSize.height)
450  panelSize.height = minimumSize.height;
451 
452  buttonsOriginY = panelSize.height - [aRepresentativeButton frameSize].height + buttonOffset;
453  offsetX = panelSize.width - inset.right;
454 
455  for (var i = [_buttons count] - 1; i >= 0 ; i--)
456  {
457  var button = _buttons[i];
458  [button setTheme:[self theme]];
459  [button sizeToFit];
460 
461  var buttonFrame = [button frame],
462  width = MAX(80.0, CGRectGetWidth(buttonFrame)),
463  height = CGRectGetHeight(buttonFrame);
464 
465  offsetX -= width;
466  [button setFrame:CGRectMake(offsetX, buttonsOriginY, width, height)];
467  offsetX -= 10;
468  }
469 
470  if (_showHelp)
471  {
472  var helpImage = [_themeView currentValueForThemeAttribute:@"help-image"],
473  helpImagePressed = [_themeView currentValueForThemeAttribute:@"help-image-pressed"],
474  helpImageSize = helpImage ? [helpImage size] : CGSizeMakeZero(),
475  helpFrame = CGRectMake(helpLeftOffset, buttonsOriginY, helpImageSize.width, helpImageSize.height);
476 
477  [_alertHelpButton setImage:helpImage];
478  [_alertHelpButton setAlternateImage:helpImagePressed];
479  [_alertHelpButton setBordered:NO];
480  [_alertHelpButton setFrame:helpFrame];
481  }
482 
483  panelSize.height += [aRepresentativeButton frameSize].height + inset.bottom + buttonOffset;
484  return panelSize;
485 }
486 
490 - (void)layout
491 {
492  if (!_needsLayout)
493  return;
494 
495  if (!_window)
496  [self _createWindowWithStyle:nil];
497 
498  var iconOffset = [_themeView currentValueForThemeAttribute:@"image-offset"],
499  theImage = _icon,
500  finalSize;
501 
502  if (!theImage)
503  switch (_alertStyle)
504  {
505  case CPWarningAlertStyle:
506  theImage = [_themeView currentValueForThemeAttribute:@"warning-image"];
507  break;
509  theImage = [_themeView currentValueForThemeAttribute:@"information-image"];
510  break;
512  theImage = [_themeView currentValueForThemeAttribute:@"error-image"];
513  break;
514  }
515 
516  [_alertImageView setImage:theImage];
517 
518  var imageSize = theImage ? [theImage size] : CGSizeMakeZero();
519  [_alertImageView setFrame:CGRectMake(iconOffset.x, iconOffset.y, imageSize.width, imageSize.height)];
520 
521  [self _layoutMessageView];
522  [self _layoutInformativeView];
523  [self _layoutAccessoryView];
524  [self _layoutSuppressionButton];
525 
526  var lastView = _informativeLabel;
527  if (_showSuppressionButton)
528  lastView = _suppressionButton;
529  else if (_accessoryView)
530  lastView = _accessoryView;
531 
532  finalSize = [self _layoutButtonsFromView:lastView];
533  if ([_window styleMask] & CPDocModalWindowMask)
534  finalSize.height -= 26; // adjust the absence of title bar
535 
536  [_window setFrameSize:finalSize];
537  [_window center];
538 
539  _needsLayout = NO;
540 }
541 
542 #pragma mark Displaying Alerts
543 
549 - (void)runModal
550 {
551  if (!([_window styleMask] & _defaultWindowStyle))
552  {
553  _needsLayout = YES;
554  [self _createWindowWithStyle:_defaultWindowStyle];
555  }
556 
557  [self layout];
558  [CPApp runModalForWindow:_window];
559 }
560 
569 - (void)beginSheetModalForWindow:(CPWindow)aWindow modalDelegate:(id)modalDelegate didEndSelector:(SEL)alertDidEndSelector contextInfo:(id)contextInfo
570 {
571  if (!([_window styleMask] & CPDocModalWindowMask))
572  {
573  _needsLayout = YES;
574  [self _createWindowWithStyle:CPDocModalWindowMask];
575  }
576 
577  [self layout];
578 
579  _modalDelegate = modalDelegate;
580  _didEndSelector = alertDidEndSelector;
581 
582  [CPApp beginSheet:_window modalForWindow:aWindow modalDelegate:self didEndSelector:@selector(_alertDidEnd:returnCode:contextInfo:) contextInfo:contextInfo];
583 }
584 
590 - (void)beginSheetModalForWindow:(CPWindow)aWindow
591 {
593 }
594 
595 #pragma mark Private
596 
600 - (void)_createWindowWithStyle:(int)forceStyle
601 {
602  var frame = CGRectMakeZero();
603  frame.size = [_themeView currentValueForThemeAttribute:@"size"];
604 
605  _window = [[CPWindow alloc] initWithContentRect:frame styleMask:forceStyle || _defaultWindowStyle];
606  [_window setLevel:CPStatusWindowLevel];
607 
608  if (_title)
609  [_window setTitle:_title];
610 
611  var contentView = [_window contentView],
612  count = [_buttons count];
613 
614  if (count)
615  while (count--)
616  [contentView addSubview:_buttons[count]];
617  else
618  [self addButtonWithTitle:@"OK"];
619 
620  [contentView addSubview:_messageLabel];
621  [contentView addSubview:_alertImageView];
622  [contentView addSubview:_informativeLabel];
623 
624  if (_showHelp)
625  [contentView addSubview:_alertHelpButton];
626 }
627 
631 - (@action)_showHelp:(id)aSender
632 {
633  if ([_delegate respondsToSelector:@selector(alertShowHelp:)])
634  [_delegate alertShowHelp:self];
635 }
636 
637 /*
638  @ignore
639 */
640 - (@action)_takeReturnCodeFrom:(id)aSender
641 {
642  if ([_window isSheet])
643  {
644  [_window orderOut:nil];
645  [CPApp endSheet:_window returnCode:[aSender tag]];
646  }
647  else
648  {
649  [CPApp abortModal];
650  [_window close];
651 
652  [self _alertDidEnd:_window returnCode:[aSender tag] contextInfo:nil];
653  }
654 }
655 
659 - (void)_alertDidEnd:(CPWindow)aWindow returnCode:(int)returnCode contextInfo:(id)contextInfo
660 {
661  if (_didEndSelector)
662  objj_msgSend(_modalDelegate, _didEndSelector, self, returnCode, contextInfo);
663 
664  _modalDelegate = nil;
665  _didEndSelector = nil;
666 
667  if ([_delegate respondsToSelector:@selector(alertDidEnd:returnCode:)])
668  [_delegate alertDidEnd:self returnCode:returnCode];
669 }
670 
671 @end
672 @implementation _CPAlertThemeView : CPView
673 {
674  id __doxygen__;
675 }
676 
677 + (CPString)defaultThemeClass
678 {
679  return @"alert";
680 }
681 
682 + (id)themeAttributes
683 {
685  CGSizeMake(400.0, 110.0),
686  CGInsetMake(15, 15, 15, 50), 6, 10,
687  CPJustifiedTextAlignment,
690  [CPNull null], CGSizeMakeZero(),
691  CPJustifiedTextAlignment,
693  [CPFont systemFontOfSize:12.0],
694  [CPNull null], CGSizeMakeZero(),
695  CGPointMake(15, 12),
696  [CPNull null],
697  [CPNull null],
698  [CPNull null],
699  [CPNull null],
700  [CPNull null],
701  [CPNull null],
702  0.0,
703  0.0,
704  3.0,
706  [CPFont systemFontOfSize:12.0],
707  [CPNull null],
708  0.0
709  ]
710  forKeys:[
711  @"size",
712  @"content-inset",
713  @"informative-offset",
714  @"button-offset",
715  @"message-text-alignment",
716  @"message-text-color",
717  @"message-text-font",
718  @"message-text-shadow-color",
719  @"message-text-shadow-offset",
720  @"informative-text-alignment",
721  @"informative-text-color",
722  @"informative-text-font",
723  @"informative-text-shadow-color",
724  @"informative-text-shadow-offset",
725  @"image-offset",
726  @"information-image",
727  @"warning-image",
728  @"error-image",
729  @"help-image",
730  @"help-image-left-offset",
731  @"help-image-pressed",
732  @"suppression-button-y-offset",
733  @"suppression-button-x-offset",
734  @"default-elements-margin",
735  @"suppression-button-text-color",
736  @"suppression-button-text-font",
737  @"suppression-button-text-shadow-color",
738  @"suppression-button-text-shadow-offset"
739  ]
740  ];
741 }
742 
743 @end
744 
746 
750 - (BOOL)showsHelp
751 {
752  return _showHelp;
753 }
754 
758 - (void)setShowsHelp:(BOOL)aValue
759 {
760  _showHelp = aValue;
761 }
762 
766 - (BOOL)showsSuppressionButton
767 {
768  return _showSuppressionButton;
769 }
770 
774 - (void)setShowsSuppressionButton:(BOOL)aValue
775 {
776  _showSuppressionButton = aValue;
777 }
778 
782 - (CPAlertStyle)alertStyle
783 {
784  return _alertStyle;
785 }
786 
790 - (void)setAlertStyle:(CPAlertStyle)aValue
791 {
792  _alertStyle = aValue;
793 }
794 
798 - (CPString)title
799 {
800  return _title;
801 }
802 
806 - (void)setTitle:(CPString)aValue
807 {
808  _title = aValue;
809 }
810 
814 - (CPView)accessoryView
815 {
816  return _accessoryView;
817 }
818 
822 - (void)setAccessoryView:(CPView)aValue
823 {
824  _accessoryView = aValue;
825 }
826 
830 - (CPImage)icon
831 {
832  return _icon;
833 }
834 
838 - (void)setIcon:(CPImage)aValue
839 {
840  _icon = aValue;
841 }
842 
846 - (CPArray)buttons
847 {
848  return _buttons;
849 }
850 
854 - (CPCheckBox)suppressionButton
855 {
856  return _suppressionButton;
857 }
858 
862 - (id)delegate
863 {
864  return _delegate;
865 }
866 
870 - (void)setDelegate:(id)aValue
871 {
872  _delegate = aValue;
873 }
874 
878 - (_CPAlertThemeView)themeView
879 {
880  return _themeView;
881 }
882 
886 - (CPWindow)window
887 {
888  return _window;
889 }
890 
891 @end