]> git.sesse.net Git - vlc/blob - modules/gui/macosx/misc.m
macosx: redesigned info panel to HUD
[vlc] / modules / gui / macosx / misc.m
1 /*****************************************************************************
2  * misc.m: code not specific to vlc
3  *****************************************************************************
4  * Copyright (C) 2003-2011 the VideoLAN team
5  * $Id$
6  *
7  * Authors: Jon Lech Johansen <jon-vl@nanocrew.net>
8  *          Felix Paul Kühne <fkuehne at videolan dot org>
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
23  *****************************************************************************/
24
25 #import <Cocoa/Cocoa.h>
26 #import <Carbon/Carbon.h>
27
28 #import "intf.h"                                          /* VLCApplication */
29 #import "MainWindow.h"
30 #import "misc.h"
31 #import "playlist.h"
32 #import "controls.h"
33 #import <vlc_url.h>
34
35 /*****************************************************************************
36  * NSAnimation (VLCAdditions)
37  *
38  *  Missing extension to NSAnimation
39  *****************************************************************************/
40
41 @implementation NSAnimation (VLCAdditions)
42 /* fake class attributes  */
43 static NSMapTable *VLCAdditions_userInfo = NULL;
44
45 + (void)load
46 {
47     /* init our fake object attribute */
48     VLCAdditions_userInfo = NSCreateMapTable(NSNonRetainedObjectMapKeyCallBacks, NSObjectMapValueCallBacks, 16);
49 }
50
51 - (void)dealloc
52 {
53     NSMapRemove(VLCAdditions_userInfo, self);
54     [super dealloc];
55 }
56
57 - (void)setUserInfo: (void *)userInfo
58 {
59     NSMapInsert(VLCAdditions_userInfo, self, (void*)userInfo);
60 }
61
62 - (void *)userInfo
63 {
64     return NSMapGet(VLCAdditions_userInfo, self);
65 }
66 @end
67
68 /*****************************************************************************
69  * NSScreen (VLCAdditions)
70  *
71  *  Missing extension to NSScreen
72  *****************************************************************************/
73
74 @implementation NSScreen (VLCAdditions)
75
76 static NSMutableArray *blackoutWindows = NULL;
77
78 + (void)load
79 {
80     /* init our fake object attribute */
81     blackoutWindows = [[NSMutableArray alloc] initWithCapacity:1];
82 }
83
84 + (NSScreen *)screenWithDisplayID: (CGDirectDisplayID)displayID
85 {
86     int i;
87
88     for( i = 0; i < [[NSScreen screens] count]; i++ )
89     {
90         NSScreen *screen = [[NSScreen screens] objectAtIndex: i];
91         if([screen displayID] == displayID)
92             return screen;
93     }
94     return nil;
95 }
96
97 - (BOOL)isMainScreen
98 {
99     return ([self displayID] == [[[NSScreen screens] objectAtIndex:0] displayID]);
100 }
101
102 - (BOOL)isScreen: (NSScreen*)screen
103 {
104     return ([self displayID] == [screen displayID]);
105 }
106
107 - (CGDirectDisplayID)displayID
108 {
109         return (CGDirectDisplayID)[[[self deviceDescription] objectForKey: @"NSScreenNumber"] intValue];
110 }
111
112 - (void)blackoutOtherScreens
113 {
114     unsigned int i;
115
116     /* Free our previous blackout window (follow blackoutWindow alloc strategy) */
117     [blackoutWindows makeObjectsPerformSelector:@selector(close)];
118     [blackoutWindows removeAllObjects];
119
120     for(i = 0; i < [[NSScreen screens] count]; i++)
121     {
122         NSScreen *screen = [[NSScreen screens] objectAtIndex: i];
123         VLCWindow *blackoutWindow;
124         NSRect screen_rect;
125
126         if([self isScreen: screen])
127             continue;
128
129         screen_rect = [screen frame];
130         screen_rect.origin.x = screen_rect.origin.y = 0;
131
132         /* blackoutWindow alloc strategy
133             - The NSMutableArray blackoutWindows has the blackoutWindow references
134             - blackoutOtherDisplays is responsible for alloc/releasing its Windows
135         */
136         blackoutWindow = [[VLCWindow alloc] initWithContentRect: screen_rect styleMask: NSBorderlessWindowMask
137                 backing: NSBackingStoreBuffered defer: NO screen: screen];
138         [blackoutWindow setBackgroundColor:[NSColor blackColor]];
139         [blackoutWindow setLevel: NSFloatingWindowLevel]; /* Disappear when Expose is triggered */
140
141         [blackoutWindow displayIfNeeded];
142         [blackoutWindow orderFront: self animate: YES];
143
144         [blackoutWindows addObject: blackoutWindow];
145         [blackoutWindow release];
146
147         if( [screen isMainScreen ] )
148            SetSystemUIMode( kUIModeAllHidden, kUIOptionAutoShowMenuBar);
149     }
150 }
151
152 + (void)unblackoutScreens
153 {
154     unsigned int i;
155
156     for(i = 0; i < [blackoutWindows count]; i++)
157     {
158         VLCWindow *blackoutWindow = [blackoutWindows objectAtIndex: i];
159         [blackoutWindow closeAndAnimate: YES];
160     }
161
162    SetSystemUIMode( kUIModeNormal, 0);
163 }
164
165 @end
166
167 /*****************************************************************************
168  * VLCWindow
169  *
170  *  Missing extension to NSWindow
171  *****************************************************************************/
172
173 @implementation VLCWindow
174 - (id)initWithContentRect:(NSRect)contentRect styleMask:(NSUInteger)styleMask
175     backing:(NSBackingStoreType)backingType defer:(BOOL)flag
176 {
177     self = [super initWithContentRect:contentRect styleMask:styleMask backing:backingType defer:flag];
178     if( self )
179     {
180         b_isset_canBecomeKeyWindow = NO;
181         /* we don't want this window to be restored on relaunch */
182         if ([self respondsToSelector:@selector(setRestorable:)])
183             [self setRestorable:NO];
184     }
185     return self;
186 }
187 - (void)setCanBecomeKeyWindow: (BOOL)canBecomeKey
188 {
189     b_isset_canBecomeKeyWindow = YES;
190     b_canBecomeKeyWindow = canBecomeKey;
191 }
192
193 - (BOOL)canBecomeKeyWindow
194 {
195     if(b_isset_canBecomeKeyWindow)
196         return b_canBecomeKeyWindow;
197
198     return [super canBecomeKeyWindow];
199 }
200
201 - (void)closeAndAnimate: (BOOL)animate
202 {
203     NSInvocation *invoc;
204
205     if (!animate)
206     {
207         [super close];
208         return;
209     }
210
211     invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(close)]];
212     [invoc setTarget: self];
213
214     if (![self isVisible] || [self alphaValue] == 0.0)
215     {
216         [super close];
217         return;
218     }
219
220     [self orderOut: self animate: YES callback: invoc];
221 }
222
223 - (void)orderOut: (id)sender animate: (BOOL)animate
224 {
225     NSInvocation *invoc = [NSInvocation invocationWithMethodSignature:[super methodSignatureForSelector:@selector(orderOut:)]];
226     [invoc setTarget: self];
227     [invoc setArgument: sender atIndex: 0];
228     [self orderOut: sender animate: animate callback: invoc];
229 }
230
231 - (void)orderOut: (id)sender animate: (BOOL)animate callback:(NSInvocation *)callback
232 {
233     NSViewAnimation *anim;
234     NSViewAnimation *current_anim;
235     NSMutableDictionary *dict;
236
237     if (!animate)
238     {
239         [self orderOut: sender];
240         return;
241     }
242
243     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
244
245     [dict setObject:self forKey:NSViewAnimationTargetKey];
246
247     [dict setObject:NSViewAnimationFadeOutEffect forKey:NSViewAnimationEffectKey];
248     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]];
249     [dict release];
250
251     [anim setAnimationBlockingMode:NSAnimationNonblocking];
252     [anim setDuration:0.9];
253     [anim setFrameRate:30];
254     [anim setUserInfo: callback];
255
256     @synchronized(self) {
257         current_anim = self->animation;
258
259         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeOutEffect && [current_anim isAnimating])
260         {
261             [anim release];
262         }
263         else
264         {
265             if (current_anim)
266             {
267                 [current_anim stopAnimation];
268                 [anim setCurrentProgress:1.0-[current_anim currentProgress]];
269                 [current_anim release];
270             }
271             else
272                 [anim setCurrentProgress:1.0 - [self alphaValue]];
273             self->animation = anim;
274             [self setDelegate: self];
275             [anim startAnimation];
276         }
277     }
278 }
279
280 - (void)orderFront: (id)sender animate: (BOOL)animate
281 {
282     NSViewAnimation *anim;
283     NSViewAnimation *current_anim;
284     NSMutableDictionary *dict;
285
286     if (!animate)
287     {
288         [super orderFront: sender];
289         [self setAlphaValue: 1.0];
290         return;
291     }
292
293     if (![self isVisible])
294     {
295         [self setAlphaValue: 0.0];
296         [super orderFront: sender];
297     }
298     else if ([self alphaValue] == 1.0)
299     {
300         [super orderFront: self];
301         return;
302     }
303
304     dict = [[NSMutableDictionary alloc] initWithCapacity:2];
305
306     [dict setObject:self forKey:NSViewAnimationTargetKey];
307
308     [dict setObject:NSViewAnimationFadeInEffect forKey:NSViewAnimationEffectKey];
309     anim = [[NSViewAnimation alloc] initWithViewAnimations:[NSArray arrayWithObjects:dict, nil]];
310     [dict release];
311
312     [anim setAnimationBlockingMode:NSAnimationNonblocking];
313     [anim setDuration:0.5];
314     [anim setFrameRate:30];
315
316     @synchronized(self) {
317         current_anim = self->animation;
318
319         if ([[[current_anim viewAnimations] objectAtIndex:0] objectForKey: NSViewAnimationEffectKey] == NSViewAnimationFadeInEffect && [current_anim isAnimating])
320         {
321             [anim release];
322         }
323         else
324         {
325             if (current_anim)
326             {
327                 [current_anim stopAnimation];
328                 [anim setCurrentProgress:1.0 - [current_anim currentProgress]];
329                 [current_anim release];
330             }
331             else
332                 [anim setCurrentProgress:[self alphaValue]];
333             self->animation = anim;
334             [self setDelegate: self];
335             [self orderFront: sender];
336             [anim startAnimation];
337         }
338     }
339 }
340
341 - (void)animationDidEnd:(NSAnimation*)anim
342 {
343     if ([self alphaValue] <= 0.0)
344     {
345         NSInvocation * invoc;
346         [super orderOut: nil];
347         [self setAlphaValue: 1.0];
348         if ((invoc = [anim userInfo]))
349             [invoc invoke];
350     }
351 }
352 @end
353
354 /*****************************************************************************
355  * VLCControllerView
356  *****************************************************************************/
357
358 @implementation VLCControllerView
359
360 - (void)dealloc
361 {
362     [self unregisterDraggedTypes];
363     [super dealloc];
364 }
365
366 - (void)awakeFromNib
367 {
368     [self registerForDraggedTypes:[NSArray arrayWithObjects:NSTIFFPboardType,
369         NSFilenamesPboardType, nil]];
370 }
371
372 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
373 {
374     if ((NSDragOperationGeneric & [sender draggingSourceOperationMask])
375                 == NSDragOperationGeneric)
376     {
377         return NSDragOperationGeneric;
378     }
379     else
380     {
381         return NSDragOperationNone;
382     }
383 }
384
385 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
386 {
387     return YES;
388 }
389
390 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
391 {
392     NSPasteboard *o_paste = [sender draggingPasteboard];
393     NSArray *o_types = [NSArray arrayWithObjects: NSFilenamesPboardType, nil];
394     NSString *o_desired_type = [o_paste availableTypeFromArray:o_types];
395     NSData *o_carried_data = [o_paste dataForType:o_desired_type];
396
397     if( o_carried_data )
398     {
399         if ([o_desired_type isEqualToString:NSFilenamesPboardType])
400         {
401             int i;
402             NSArray *o_array = [NSArray array];
403             NSArray *o_values = [[o_paste propertyListForType: NSFilenamesPboardType]
404                         sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
405
406             for( i = 0; i < (int)[o_values count]; i++)
407             {
408                 NSDictionary *o_dic;
409                 char *psz_uri = make_URI([[o_values objectAtIndex:i] UTF8String], NULL);
410                 if( !psz_uri )
411                     continue;
412
413                 o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
414
415                 free( psz_uri );
416                 o_array = [o_array arrayByAddingObject: o_dic];
417             }
418             [(VLCPlaylist *)[[VLCMain sharedInstance] playlist] appendArray: o_array atPos: -1 enqueue:NO];
419             return YES;
420         }
421     }
422     [self setNeedsDisplay:YES];
423     return YES;
424 }
425
426 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
427 {
428     [self setNeedsDisplay:YES];
429 }
430
431 @end
432
433 /*****************************************************************************
434  * VLBrushedMetalImageView
435  *****************************************************************************/
436
437 @implementation VLBrushedMetalImageView
438
439 - (BOOL)mouseDownCanMoveWindow
440 {
441     return YES;
442 }
443
444 - (void)dealloc
445 {
446     [self unregisterDraggedTypes];
447     [super dealloc];
448 }
449
450 - (void)awakeFromNib
451 {
452     [self registerForDraggedTypes:[NSArray arrayWithObjects:NSTIFFPboardType,
453         NSFilenamesPboardType, nil]];
454 }
455
456 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
457 {
458     if ((NSDragOperationGeneric & [sender draggingSourceOperationMask])
459                 == NSDragOperationGeneric)
460     {
461         return NSDragOperationGeneric;
462     }
463     else
464     {
465         return NSDragOperationNone;
466     }
467 }
468
469 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
470 {
471     return YES;
472 }
473
474 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
475 {
476     NSPasteboard *o_paste = [sender draggingPasteboard];
477     NSArray *o_types = [NSArray arrayWithObjects: NSFilenamesPboardType, nil];
478     NSString *o_desired_type = [o_paste availableTypeFromArray:o_types];
479     NSData *o_carried_data = [o_paste dataForType:o_desired_type];
480     BOOL b_autoplay = config_GetInt( VLCIntf, "macosx-autoplay" );
481
482     if( o_carried_data )
483     {
484         if ([o_desired_type isEqualToString:NSFilenamesPboardType])
485         {
486             int i;
487             NSArray *o_array = [NSArray array];
488             NSArray *o_values = [[o_paste propertyListForType: NSFilenamesPboardType]
489                         sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
490
491             for( i = 0; i < (int)[o_values count]; i++)
492             {
493                 NSDictionary *o_dic;
494                 char *psz_uri = make_URI([[o_values objectAtIndex:i] UTF8String], NULL);
495                 if( !psz_uri )
496                     continue;
497
498                 o_dic = [NSDictionary dictionaryWithObject:[NSString stringWithCString:psz_uri encoding:NSUTF8StringEncoding] forKey:@"ITEM_URL"];
499                 free( psz_uri );
500
501                 o_array = [o_array arrayByAddingObject: o_dic];
502             }
503             if( b_autoplay )
504                 [[[VLCMain sharedInstance] playlist] appendArray: o_array atPos: -1 enqueue:NO];
505             else
506                 [[[VLCMain sharedInstance] playlist] appendArray: o_array atPos: -1 enqueue:YES];
507             return YES;
508         }
509     }
510     [self setNeedsDisplay:YES];
511     return YES;
512 }
513
514 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
515 {
516     [self setNeedsDisplay:YES];
517 }
518
519 @end
520
521
522 /*****************************************************************************
523  * MPSlider
524  *****************************************************************************/
525 @implementation MPSlider
526
527 void _drawKnobInRect(NSRect knobRect)
528 {
529     // Center knob in given rect
530     knobRect.origin.x += (int)((float)(knobRect.size.width - 7)/2.0);
531     knobRect.origin.y += (int)((float)(knobRect.size.height - 7)/2.0);
532
533     // Draw diamond
534     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 3, knobRect.origin.y + 6, 1, 1), NSCompositeSourceOver);
535     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 2, knobRect.origin.y + 5, 3, 1), NSCompositeSourceOver);
536     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 1, knobRect.origin.y + 4, 5, 1), NSCompositeSourceOver);
537     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 0, knobRect.origin.y + 3, 7, 1), NSCompositeSourceOver);
538     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 1, knobRect.origin.y + 2, 5, 1), NSCompositeSourceOver);
539     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 2, knobRect.origin.y + 1, 3, 1), NSCompositeSourceOver);
540     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 3, knobRect.origin.y + 0, 1, 1), NSCompositeSourceOver);
541 }
542
543 void _drawFrameInRect(NSRect frameRect)
544 {
545     // Draw frame
546     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width, 1), NSCompositeSourceOver);
547     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y + frameRect.size.height-1, frameRect.size.width, 1), NSCompositeSourceOver);
548     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y, 1, frameRect.size.height), NSCompositeSourceOver);
549     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x+frameRect.size.width-1, frameRect.origin.y, 1, frameRect.size.height), NSCompositeSourceOver);
550 }
551
552 - (void)drawRect:(NSRect)rect
553 {
554     // Draw default to make sure the slider behaves correctly
555     [[NSGraphicsContext currentContext] saveGraphicsState];
556     NSRectClip(NSZeroRect);
557     [super drawRect:rect];
558     [[NSGraphicsContext currentContext] restoreGraphicsState];
559
560     // Full size
561     rect = [self bounds];
562     int diff = (int)(([[self cell] knobThickness] - 7.0)/2.0) - 1;
563     rect.origin.x += diff-1;
564     rect.origin.y += diff;
565     rect.size.width -= 2*diff-2;
566     rect.size.height -= 2*diff;
567
568     // Draw dark
569     NSRect knobRect = [[self cell] knobRectFlipped:NO];
570     [[[NSColor blackColor] colorWithAlphaComponent:0.6] set];
571     _drawFrameInRect(rect);
572     _drawKnobInRect(knobRect);
573
574     // Draw shadow
575     [[[NSColor blackColor] colorWithAlphaComponent:0.1] set];
576     rect.origin.x++;
577     rect.origin.y++;
578     knobRect.origin.x++;
579     knobRect.origin.y++;
580     _drawFrameInRect(rect);
581     _drawKnobInRect(knobRect);
582 }
583
584 @end
585
586 /*****************************************************************************
587  * TimeLineSlider
588  *****************************************************************************/
589
590 @implementation TimeLineSlider
591
592 - (void)drawKnobInRect:(NSRect)knobRect
593 {
594     NSRect image_rect;
595     NSImage *img = [NSImage imageNamed:@"progression-knob"];
596     image_rect.size = [img size];
597     image_rect.origin.x = 0;
598     image_rect.origin.y = 0;
599     knobRect.origin.x += (knobRect.size.width - image_rect.size.width) / 2;
600     knobRect.size.width = image_rect.size.width;
601     knobRect.size.height = image_rect.size.height;
602     [img drawInRect:knobRect fromRect:image_rect operation:NSCompositeSourceOver fraction:1];
603 }
604
605 - (void)drawRect:(NSRect)rect
606 {
607     /* Draw default to make sure the slider behaves correctly */
608     [[NSGraphicsContext currentContext] saveGraphicsState];
609     NSRectClip(NSZeroRect);
610     [super drawRect:rect];
611     [[NSGraphicsContext currentContext] restoreGraphicsState];
612
613     NSRect knobRect = [[self cell] knobRectFlipped:NO];
614     knobRect.origin.y+=1;
615     [self drawKnobInRect: knobRect];
616 }
617
618 @end
619
620 /*****************************************************************************
621  * ITSlider
622  *****************************************************************************/
623
624 @implementation ITSlider
625
626 - (void)drawKnobInRect:(NSRect)knobRect
627 {
628     NSRect image_rect;
629     NSImage *img = [NSImage imageNamed:@"volume-slider-knob"];
630     image_rect.size = [img size];
631     image_rect.origin.x = 0;
632     image_rect.origin.y = 0;
633     knobRect.origin.x += (knobRect.size.width - image_rect.size.width) / 2;
634     knobRect.size.width = image_rect.size.width;
635     knobRect.size.height = image_rect.size.height;
636     [img drawInRect:knobRect fromRect:image_rect operation:NSCompositeSourceOver fraction:1];
637 }
638
639 - (void)drawRect:(NSRect)rect
640 {
641     /* Draw default to make sure the slider behaves correctly */
642     [[NSGraphicsContext currentContext] saveGraphicsState];
643     NSRectClip(NSZeroRect);
644     [super drawRect:rect];
645     [[NSGraphicsContext currentContext] restoreGraphicsState];
646
647     NSRect knobRect = [[self cell] knobRectFlipped:NO];
648     knobRect.origin.y+=2;
649     [self drawKnobInRect: knobRect];
650 }
651
652 @end
653
654 /*****************************************************************************
655  * VLCTimeField implementation
656  *****************************************************************************
657  * we need this to catch our click-event in the controller window
658  *****************************************************************************/
659
660 @implementation VLCTimeField
661 + (void)initialize{
662     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
663     NSDictionary *appDefaults = [NSDictionary dictionaryWithObject:@"NO" forKey:@"DisplayTimeAsTimeRemaining"];
664
665     [defaults registerDefaults:appDefaults];
666 }
667
668 - (void)mouseDown: (NSEvent *)ourEvent
669 {
670     if( [ourEvent clickCount] > 1 )
671         [[[VLCMain sharedInstance] controls] goToSpecificTime: nil];
672     else
673     {
674         if ([[NSUserDefaults standardUserDefaults] boolForKey:@"DisplayTimeAsTimeRemaining"])
675             [[NSUserDefaults standardUserDefaults] setObject:@"NO" forKey:@"DisplayTimeAsTimeRemaining"];
676         else
677             [[NSUserDefaults standardUserDefaults] setObject:@"YES" forKey:@"DisplayTimeAsTimeRemaining"];
678     }
679 }
680
681 - (BOOL)timeRemaining
682 {
683     return [[NSUserDefaults standardUserDefaults] boolForKey:@"DisplayTimeAsTimeRemaining"];
684 }
685 @end