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