Changed versions to 1.7.
[MenuTunes.git] / StatusWindow.m
1 //
2 //  StatusWindow.m
3 //  MenuTunes
4 //
5 //  Created by Matt L. Judy on Sat Feb 22 2003.
6 //  Copyright (c) 2003 NibFile.com. All rights reserved.
7 //
8
9 #import "StatusWindow.h"
10
11
12 #define SW_PAD             24.00
13 #define SW_SPACE           24.00
14 #define SW_MINW           211.00
15 #define SW_BORDER          32.00
16 #define SW_METER_PAD        4.00
17 #define SW_BUTTON_PAD_R    30.00
18 #define SW_BUTTON_PAD_B    24.00
19 #define SW_BUTTON_DIV      12.00
20 #define SW_BUTTON_EXTRA_W   8.00
21 #define SW_SHADOW_SAT       1.25
22
23 @interface StatusWindow (Private)
24 - (NSRect)setupWindowWithDataSize:(NSSize)dataSize;
25 @end
26
27
28 @implementation StatusWindow
29
30
31 /*************************************************************************/
32 #pragma mark -
33 #pragma mark INITIALIZATION / DEALLOCATION METHODS
34 /*************************************************************************/
35
36 - (id)initWithContentView:(NSView *)contentView
37                  exitMode:(ITTransientStatusWindowExitMode)exitMode
38            backgroundType:(ITTransientStatusWindowBackgroundType)backgroundType
39 {
40     if ( ( self = [super initWithContentView:contentView
41                                exitMode:exitMode
42                          backgroundType:backgroundType] ) ) {
43      // Set default values.
44         _image  = [[NSImage imageNamed:@"NSApplicationIcon"] retain];
45         _locked = NO;
46         _sizing = ITTransientStatusWindowRegular;
47     }
48     
49     return self;
50 }
51
52 - (void)dealloc
53 {
54     [_image release];
55     [super dealloc];
56 }
57
58
59 /*************************************************************************/
60 #pragma mark -
61 #pragma mark ACCESSOR METHODS
62 /*************************************************************************/
63
64 - (void)setImage:(NSImage *)newImage
65 {
66     [_image autorelease];
67     _image = [newImage copy];
68 }
69
70 - (void)setLocked:(BOOL)flag
71 {
72     _locked = flag;
73     [self setExitMode:(flag ? ITTransientStatusWindowExitOnCommand : ITTransientStatusWindowExitAfterDelay)];
74 }
75
76 - (void)setSizing:(ITTransientStatusWindowSizing)newSizing
77 {
78     _sizing = newSizing;
79 }
80
81 /*************************************************************************/
82 #pragma mark -
83 #pragma mark INSTANCE METHODS
84 /*************************************************************************/
85
86 - (void)appear:(id)sender
87 {
88     if ( ! _locked ) {
89         [super appear:sender];
90     }
91 }
92
93 - (void)vanish:(id)sender
94 {
95     if ( ! _locked ) {
96         [super vanish:sender];
97     }
98 }
99
100 - (NSRect)setupWindowWithDataSize:(NSSize)dataSize
101 {
102     float        divisor       = 1.0;
103     NSRect       imageRect;
104     float        imageWidth    = 0.0;
105     float        imageHeight   = 0.0;
106     float        dataWidth     = dataSize.width;
107     float        dataHeight    = dataSize.height;
108     float        contentHeight = 0.0;
109     float        windowWidth   = 0.0;
110     float        windowHeight  = 0.0;
111     NSRect       visibleFrame  = [[self screen] visibleFrame];
112     NSPoint      screenOrigin  = visibleFrame.origin;
113     float        screenWidth   = visibleFrame.size.width;
114     float        screenHeight  = visibleFrame.size.height;
115     float        maxWidth      = ( screenWidth  - (SW_BORDER * 2) );
116     float        maxHeight     = ( screenHeight - (SW_BORDER * 2) );
117     float        excessWidth   = 0.0;
118     float        excessHeight  = 0.0;
119     NSPoint      windowOrigin  = NSZeroPoint;
120     ITImageView *imageView;
121     BOOL         shouldAnimate = ( ! (([self visibilityState] == ITWindowAppearingState) ||
122                                       ([self visibilityState] == ITWindowVanishingState)) );
123         
124     if ( _sizing == ITTransientStatusWindowSmall ) {
125         divisor = SMALL_DIVISOR;
126     } else if ( _sizing == ITTransientStatusWindowMini ) {
127         divisor = MINI_DIVISOR;
128     }
129
130 //  Get image width and height.
131     imageWidth  = ( [_image size].width  / divisor );
132     imageHeight = ( [_image size].height / divisor );
133     
134 //  Set the content height to the greater of the text and image heights.
135     contentHeight = ( ( imageHeight > dataHeight ) ? imageHeight : dataHeight );
136
137 //  Setup the Window, and remove all its contentview's subviews.
138     windowWidth  = ( (SW_PAD / divisor) + imageWidth + ((dataWidth > 0) ? (SW_SPACE / divisor) + dataWidth : 0) + (SW_PAD / divisor) );
139     windowHeight = ( (SW_PAD / divisor) + contentHeight + (SW_PAD / divisor) );
140     
141 //  Constrain size to max limits.  Adjust data sizes accordingly.
142     excessWidth  = (windowWidth  - maxWidth );
143     excessHeight = (windowHeight - maxHeight);
144
145     if ( excessWidth > 0.0 ) {
146         windowWidth = maxWidth;
147         dataWidth -= excessWidth;
148     }
149     
150     if ( excessHeight > 0.0 ) {
151         windowHeight = maxHeight;
152         dataHeight -= excessHeight;
153     }
154     
155     if ( [self horizontalPosition] == ITWindowPositionLeft ) {
156         windowOrigin.x = ( SW_BORDER + screenOrigin.x );
157     } else if ( [self horizontalPosition] == ITWindowPositionCenter ) {
158         windowOrigin.x = ( screenOrigin.x + (screenWidth / 2) - (windowWidth / 2) );
159     } else if ( [self horizontalPosition] == ITWindowPositionRight ) {
160         windowOrigin.x = ( screenOrigin.x + screenWidth - (windowWidth + SW_BORDER) );
161     }
162     
163     if ( [self verticalPosition] == ITWindowPositionTop ) {
164         windowOrigin.y = ( screenOrigin.y + screenHeight - (windowHeight + SW_BORDER) );
165     } else if ( [self verticalPosition] == ITWindowPositionMiddle ) {
166 //      Middle-oriented windows should be slightly proud of the screen's middle.
167         windowOrigin.y = ( (screenOrigin.y + (screenHeight / 2) - (windowHeight / 2)) + (screenHeight / 8) );
168     } else if ( [self verticalPosition] == ITWindowPositionBottom ) {
169         windowOrigin.y = ( SW_BORDER + screenOrigin.y );
170     }
171     
172     [self setFrame:NSMakeRect( windowOrigin.x,
173                                windowOrigin.y,
174                                windowWidth,
175                                windowHeight) display:YES animate:shouldAnimate];
176
177     [[[self contentView] subviews] makeObjectsPerformSelector:@selector(removeFromSuperview)];
178     
179 //  Setup, position, fill, and add the image view to the content view.
180     imageRect = NSMakeRect( (SW_PAD / divisor) + ((dataWidth > 0) ? 4 : 0),
181                             ((SW_PAD / divisor) + ((contentHeight - imageHeight) / 2)),
182                             imageWidth,
183                             imageHeight );
184     imageView = [[[ITImageView alloc] initWithFrame:imageRect] autorelease];
185     [imageView setAutoresizingMask:(NSViewMinYMargin | NSViewMaxYMargin)];
186     [imageView setImage:_image];
187     [imageView setCastsShadow:YES];
188     [[self contentView] addSubview:imageView];
189
190     return NSMakeRect( ((SW_PAD / divisor) + imageWidth + (SW_SPACE / divisor)),
191                        ((SW_PAD / divisor) + ((contentHeight - dataHeight) / 2)),
192                        dataWidth,
193                        dataHeight);
194 }
195
196 - (void)buildImageWindowWithImage:(NSImage *)image
197 {
198         if (!_locked) {
199                 float divisor = 1.0;
200                 NSRect dataRect;
201                 
202                 if (_sizing == ITTransientStatusWindowSmall) {
203                         divisor = SMALL_DIVISOR;
204                 } else if (_sizing == ITTransientStatusWindowMini) {
205                         divisor = MINI_DIVISOR;
206                 }
207                 
208                 [self setImage:image];
209                 dataRect = [self setupWindowWithDataSize:NSMakeSize(0, 0)]; //We have no text, so there is no data
210                 [[self contentView] setNeedsDisplay:YES];
211         }
212 }
213
214 - (void)buildTextWindowWithString:(id)text
215 {
216     if ( ! _locked ) {
217
218         float         divisor       = 1.0;
219         float         dataWidth     = 0.0;
220         float         dataHeight    = 0.0;
221         NSRect        dataRect;
222         NSArray      *lines                     = [(([text isKindOfClass:[NSString class]]) ? text : [text mutableString]) componentsSeparatedByString:@"\n"];
223         id                        oneLine       = nil;
224         NSEnumerator *lineEnum          = [lines objectEnumerator];
225         float         baseFontSize  = 18.0;
226         ITTextField  *textField;
227         NSFont       *font;
228         NSDictionary *attr;
229
230         if ( _sizing == ITTransientStatusWindowSmall ) {
231             divisor = SMALL_DIVISOR;
232         } else if ( _sizing == ITTransientStatusWindowMini ) {
233             divisor = MINI_DIVISOR;
234         }
235                 
236                 font = [NSFont fontWithName:@"LucidaGrande-Bold" size:(baseFontSize / divisor)];
237                 attr = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
238                 
239 //      Iterate over each line to get text width and height
240         while ( (oneLine = [lineEnum nextObject]) ) {
241 //          Get the width of one line, adding 8.0 because Apple sucks donkey rectum.
242             float oneLineWidth = ( [oneLine sizeWithAttributes:attr].width + 8.0 );
243 //          Add the height of this line to the total text height
244             dataHeight += [oneLine sizeWithAttributes:attr].height;
245 //          If this line wider than the last one, set it as the text width.
246             dataWidth = ( ( dataWidth > oneLineWidth ) ? dataWidth : oneLineWidth );
247         }
248         
249 //      Add 4.0 to the final dataHeight to accomodate the shadow.
250         dataHeight += 4.0;
251
252         dataRect = [self setupWindowWithDataSize:NSMakeSize(dataWidth, dataHeight)];
253         
254 //      Create, position, setup, fill, and add the text view to the content view.
255         textField = [[[ITTextField alloc] initWithFrame:dataRect] autorelease];
256         [textField setAutoresizingMask:(NSViewHeightSizable | NSViewWidthSizable)];
257         [textField setEditable:NO];
258         [textField setSelectable:NO];
259         [textField setBordered:NO];
260         [textField setDrawsBackground:NO];
261         [textField setFont:font];
262         [textField setTextColor:[NSColor whiteColor]];
263         [textField setCastsShadow:YES];
264         [[textField cell] setWraps:NO];
265                 
266                 if ([text isKindOfClass:[NSString class]]) {
267                         [textField setStringValue:text];
268                 } else {
269                         [textField setAttributedStringValue:text];
270                 }
271                 
272         [textField setShadowSaturation:SW_SHADOW_SAT];
273         [[self contentView] addSubview:textField];
274         
275 //      Display the window.
276         [[self contentView] setNeedsDisplay:YES];
277                 _textField = textField;
278     }
279 }
280
281 - (void)buildMeterWindowWithCharacter:(NSString *)character
282                                  size:(float)size
283                                 count:(int)count
284                                active:(int)active
285 {
286     if ( ! _locked ) {
287
288         float         divisor     = 1.0;
289         NSFont       *font;
290         NSDictionary *attr;
291         NSSize        charSize;
292         float         cellHeight;
293         float         cellWidth;
294         float         dataWidth;
295         NSRect        dataRect;
296         NSEnumerator *cellEnum    = nil;
297         id            aCell       = nil;
298         int           activeCount = 0;
299         NSColor      *onColor     = [NSColor whiteColor];
300         NSColor      *offColor    = [NSColor colorWithCalibratedWhite:0.15 alpha:0.50];
301         NSMatrix     *volMatrix;
302         
303         if ( _sizing == ITTransientStatusWindowSmall ) {
304             divisor = SMALL_DIVISOR;
305         } else if ( _sizing == ITTransientStatusWindowMini ) {
306             divisor = MINI_DIVISOR;
307         }
308         
309         font        = [NSFont fontWithName:@"AppleGothic" size:( size / divisor )];
310         attr        = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
311         charSize    = [character sizeWithAttributes:attr];
312         cellHeight  = ( charSize.height + 4.0 );  // Add 4.0 for shadow
313         cellWidth   = ( (charSize.width) + (SW_METER_PAD / divisor) );
314         dataWidth   = ( cellWidth * count );
315         dataRect    = [self setupWindowWithDataSize:NSMakeSize(dataWidth, cellHeight)];
316         volMatrix   = [[[NSMatrix alloc] initWithFrame:dataRect
317                                                   mode:NSHighlightModeMatrix
318                                              cellClass:NSClassFromString(@"ITTextFieldCell")
319                                           numberOfRows:1
320                                        numberOfColumns:count] autorelease];
321         
322         [volMatrix setCellSize:NSMakeSize(cellWidth, cellHeight)];
323         [volMatrix setIntercellSpacing:NSMakeSize(0, 0)];
324         [volMatrix setAutoresizingMask:(NSViewHeightSizable | NSViewWidthSizable)];
325
326         cellEnum = [[volMatrix cells] objectEnumerator];
327
328         while ( (aCell = [cellEnum nextObject]) ) {
329             [aCell setEditable:NO];
330             [aCell setSelectable:NO];
331             [aCell setBordered:NO];
332             [aCell setDrawsBackground:NO];
333             [aCell setAlignment:NSCenterTextAlignment];
334             [aCell setFont:font];
335             [aCell setStringValue:character];
336             [aCell setShadowSaturation:SW_SHADOW_SAT];
337
338             activeCount ++;
339
340             if ( active >= activeCount ) {
341                 [aCell setCastsShadow:YES];
342                 [aCell setTextColor:onColor];
343             } else {
344                 [aCell setCastsShadow:NO];
345                 [aCell setTextColor:offColor];
346             }
347
348         }
349
350         [[self contentView] addSubview:volMatrix];
351         [[self contentView] setNeedsDisplay:YES];
352         
353     }
354 }
355
356 - (void)buildDialogWindowWithMessage:(NSString *)message
357                        defaultButton:(NSString *)defaultTitle
358                      alternateButton:(NSString *)alternateTitle
359                               target:(id)target
360                        defaultAction:(SEL)okAction
361                      alternateAction:(SEL)alternateAction
362 {
363     if ( ! _locked ) {
364
365         float         divisor       = 1.0;
366         float         textWidth     = 0.0;
367         float         textHeight    = 0.0;
368         float         okWidth       = 0.0;
369         float         cancelWidth   = 0.0;
370         float         wideButtonW   = 0.0;
371         float         buttonWidth   = 0.0;
372         float         dataHeight    = 0.0;
373         float         dataWidth     = 0.0;
374         NSRect        dataRect;
375         float         textY         = 0.0;
376         NSRect        textRect;
377         float         textAddBelow  = 32.0;
378         float         dataMinH      = 92.0;
379         float         textMinH      = 48.0;
380         NSArray      *lines         = [message componentsSeparatedByString:@"\n"];
381         id                        oneLine       = nil;
382         NSEnumerator *lineEnum      = [lines objectEnumerator];
383         float         baseFontSize  = 18.0;
384         ITTextField  *textField;
385         ITButton     *okButton;
386         ITButton     *cancelButton;
387         NSColor      *textColor     = [NSColor whiteColor];
388         NSFont       *font;
389         NSDictionary *attr;
390         NSFont       *buttonFont;
391         NSDictionary *buttonAttr;
392         
393         if ( _sizing == ITTransientStatusWindowSmall ) {
394             divisor = SMALL_DIVISOR;
395         } else if ( _sizing == ITTransientStatusWindowMini ) {
396             divisor = MINI_DIVISOR;
397         }
398         
399         font = [NSFont fontWithName:@"LucidaGrande-Bold" size:(baseFontSize / divisor)];
400         attr = [NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName];
401         buttonFont = [NSFont fontWithName:@"LucidaGrande-Bold" size:(14 / divisor)];
402         buttonAttr = [NSDictionary dictionaryWithObjectsAndKeys:
403             buttonFont , NSFontAttributeName,
404             textColor  , NSForegroundColorAttributeName, 
405             nil];
406         
407 //      Iterate over each line to get text width and height
408         while ( (oneLine = [lineEnum nextObject]) ) {
409 //          Get the width of one line, adding 8.0 because Apple sucks donkey rectum.
410             float oneLineWidth = ( [oneLine sizeWithAttributes:attr].width + 8.0 );
411 //          Add the height of this line to the total text height
412             textHeight += [oneLine sizeWithAttributes:attr].height;
413 //          If this line wider than the last one, set it as the text width.
414             textWidth = ( ( textWidth > oneLineWidth ) ? textWidth : oneLineWidth );
415         }
416         
417 //      Add 4.0 to the final dataHeight to accomodate the shadow.
418         textHeight += 4.0;
419         
420 //      Add extra padding below the text
421         dataHeight = (textHeight + textAddBelow);
422         
423 //      Test to see if data height is tall enough
424         if ( dataHeight < dataMinH ) {
425             dataHeight = dataMinH;
426         }
427         
428 //      Make the buttons, set the titles, and size them to fit their titles
429         okButton     = [[[ITButton alloc] initWithFrame:NSMakeRect(0, 0, 300, 24)] autorelease];
430         cancelButton = [[[ITButton alloc] initWithFrame:NSMakeRect(0, 0, 300, 24)] autorelease];
431         [okButton     setTarget:target];
432         [cancelButton setTarget:target];
433         [okButton     setAction:okAction];
434         [cancelButton setAction:alternateAction];
435         [okButton     setBezelStyle:ITGrayRoundedBezelStyle];
436         [cancelButton setBezelStyle:ITGrayRoundedBezelStyle];
437         [okButton     setAlignment:NSRightTextAlignment];
438         [cancelButton setAlignment:NSCenterTextAlignment];
439         [okButton     setImagePosition:NSNoImage];
440         [cancelButton setImagePosition:NSNoImage];
441         [okButton     setAttributedTitle:[[[NSAttributedString alloc] initWithString:defaultTitle
442                                                                           attributes:buttonAttr] autorelease]];
443         [cancelButton setAttributedTitle:[[[NSAttributedString alloc] initWithString:alternateTitle
444                                                                           attributes:buttonAttr] autorelease]];
445         [okButton     sizeToFit];
446         [cancelButton sizeToFit];
447         
448 //      Get the button widths.  Add any extra width here.
449         okWidth     = ([okButton     frame].size.width + SW_BUTTON_EXTRA_W);
450         cancelWidth = ([cancelButton frame].size.width + SW_BUTTON_EXTRA_W);
451         
452 //      Figure out which button is wider.
453         wideButtonW = ( (okWidth > cancelWidth) ? okWidth : cancelWidth );
454
455 //      Get the total width of the buttons. Add the divider space.
456         buttonWidth = ( (wideButtonW * 2) + SW_BUTTON_DIV );
457
458 //      Set the dataWidth to whichever is greater: text width or button width.
459         dataWidth = ( (textWidth > buttonWidth) ? textWidth : buttonWidth);
460         
461 //      Setup the window
462         dataRect = [self setupWindowWithDataSize:NSMakeSize(dataWidth, dataHeight)];
463         
464 //      Set an initial vertical point for the textRect's origin.
465         textY = dataRect.origin.y + textAddBelow;
466         
467 //      Move that point up if the minimimum height of the text area is not occupied.
468         if ( textHeight < textMinH ) {
469             textY += ( (textMinH - textHeight) / 2 );
470         }
471         
472 //      Build the text rect.
473         textRect = NSMakeRect(dataRect.origin.x,
474                               textY,
475                               textWidth,
476                               textHeight);
477         
478 //      Create, position, setup, fill, and add the text view to the content view.
479         textField = [[[ITTextField alloc] initWithFrame:textRect] autorelease];
480         [textField setEditable:NO];
481         [textField setSelectable:NO];
482         [textField setBordered:NO];
483         [textField setDrawsBackground:NO];
484         [textField setFont:font];
485         [textField setTextColor:textColor];
486         [textField setCastsShadow:YES];
487         [textField setStringValue:message];
488         [textField setShadowSaturation:SW_SHADOW_SAT];
489         [[self contentView] addSubview:textField];
490         
491 //      Set the button frames, and add them to the content view.
492         [okButton setFrame:NSMakeRect( ([[self contentView] frame].size.width - (wideButtonW + SW_BUTTON_PAD_R) ),
493                                        SW_BUTTON_PAD_B,
494                                        wideButtonW,
495                                        24.0)];
496         [cancelButton setFrame:NSMakeRect( ([[self contentView] frame].size.width - ((wideButtonW * 2) + SW_BUTTON_DIV + SW_BUTTON_PAD_R) ),
497                                            SW_BUTTON_PAD_B,
498                                            wideButtonW,
499                                            24.0)];
500         [[self contentView] addSubview:okButton];
501         if (alternateTitle) {
502             [[self contentView] addSubview:cancelButton];
503         }
504
505         [self setIgnoresMouseEvents:NO];
506   
507 //      Display the window.
508         [[self contentView] setNeedsDisplay:YES];
509     }
510 }
511
512 - (void)updateTime:(NSString *)time range:(NSRange)range
513 {
514         NSMutableAttributedString *string = [[_textField attributedStringValue] mutableCopy];
515         [string replaceCharactersInRange:range withString:time];
516         [_textField setAttributedStringValue:[string autorelease]];
517         [[self contentView] setNeedsDisplay:YES];
518 }
519
520 - (NSTimeInterval)animationResizeTime:(NSRect)newFrame
521 {
522     return (NSTimeInterval)0.25;
523 }
524
525 @end