3b659a9294efbe8f1f8a22aa44ed2164b57819a6
[vlc.git] / modules / gui / macosx / misc.m
1 /*****************************************************************************
2  * misc.m: code not specific to vlc
3  *****************************************************************************
4  * Copyright (C) 2003-2015 VLC authors and VideoLAN
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 "CompatibilityFixes.h"
26 #import "misc.h"
27 #import "VLCMain.h"                                          /* VLCApplication */
28 #import "VLCMainWindow.h"
29 #import "VLCMainMenu.h"
30 #import "VLCControlsBarCommon.h"
31 #import "VLCCoreInteraction.h"
32 #import <vlc_keys.h>
33
34 /*****************************************************************************
35  * VLCDragDropView
36  *****************************************************************************/
37
38 @implementation VLCDropDisabledImageView
39
40 - (void)awakeFromNib
41 {
42     [self unregisterDraggedTypes];
43 }
44
45 @end
46
47 /*****************************************************************************
48  * VLCDragDropView
49  *****************************************************************************/
50
51 @interface VLCDragDropView()
52 {
53     bool b_activeDragAndDrop;
54 }
55 @end
56
57 @implementation VLCDragDropView
58
59 - (id)initWithFrame:(NSRect)frame
60 {
61     self = [super initWithFrame:frame];
62     if (self) {
63         // default value
64         [self setDrawBorder:YES];
65     }
66
67     return self;
68 }
69
70 - (void)enablePlaylistItems
71 {
72     [self registerForDraggedTypes:[NSArray arrayWithObjects:NSFilenamesPboardType, @"VLCPlaylistItemPboardType", nil]];
73 }
74
75 - (BOOL)mouseDownCanMoveWindow
76 {
77     return YES;
78 }
79
80 - (void)dealloc
81 {
82     [self unregisterDraggedTypes];
83 }
84
85 - (void)awakeFromNib
86 {
87     [self registerForDraggedTypes:[NSArray arrayWithObject:NSFilenamesPboardType]];
88 }
89
90 - (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
91 {
92     if ((NSDragOperationGeneric & [sender draggingSourceOperationMask]) == NSDragOperationGeneric) {
93         b_activeDragAndDrop = YES;
94         [self setNeedsDisplay:YES];
95
96         return NSDragOperationCopy;
97     }
98
99     return NSDragOperationNone;
100 }
101
102 - (void)draggingEnded:(id < NSDraggingInfo >)sender
103 {
104     b_activeDragAndDrop = NO;
105     [self setNeedsDisplay:YES];
106 }
107
108 - (void)draggingExited:(id < NSDraggingInfo >)sender
109 {
110     b_activeDragAndDrop = NO;
111     [self setNeedsDisplay:YES];
112 }
113
114 - (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
115 {
116     return YES;
117 }
118
119 - (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
120 {
121     BOOL b_returned;
122
123     if (_dropHandler && [_dropHandler respondsToSelector:@selector(performDragOperation:)])
124         b_returned = [_dropHandler performDragOperation:sender];
125     else // default
126         b_returned = [[VLCCoreInteraction sharedInstance] performDragOperation:sender];
127
128     [self setNeedsDisplay:YES];
129     return b_returned;
130 }
131
132 - (void)concludeDragOperation:(id <NSDraggingInfo>)sender
133 {
134     [self setNeedsDisplay:YES];
135 }
136
137 - (void)drawRect:(NSRect)dirtyRect
138 {
139     if ([self drawBorder] && b_activeDragAndDrop) {
140         NSRect frameRect = [self bounds];
141
142         [[NSColor selectedControlColor] set];
143         NSFrameRectWithWidthUsingOperation(frameRect, 2., NSCompositeSourceOver);
144     }
145
146     [super drawRect:dirtyRect];
147 }
148
149 @end
150
151
152 /*****************************************************************************
153  * MPSlider
154  *****************************************************************************/
155 @implementation MPSlider
156
157 void _drawKnobInRect(NSRect knobRect)
158 {
159     // Center knob in given rect
160     knobRect.origin.x += (int)((float)(knobRect.size.width - 7)/2.0);
161     knobRect.origin.y += (int)((float)(knobRect.size.height - 7)/2.0);
162
163     // Draw diamond
164     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 3, knobRect.origin.y + 6, 1, 1), NSCompositeSourceOver);
165     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 2, knobRect.origin.y + 5, 3, 1), NSCompositeSourceOver);
166     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 1, knobRect.origin.y + 4, 5, 1), NSCompositeSourceOver);
167     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 0, knobRect.origin.y + 3, 7, 1), NSCompositeSourceOver);
168     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 1, knobRect.origin.y + 2, 5, 1), NSCompositeSourceOver);
169     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 2, knobRect.origin.y + 1, 3, 1), NSCompositeSourceOver);
170     NSRectFillUsingOperation(NSMakeRect(knobRect.origin.x + 3, knobRect.origin.y + 0, 1, 1), NSCompositeSourceOver);
171 }
172
173 void _drawFrameInRect(NSRect frameRect)
174 {
175     // Draw frame
176     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y, frameRect.size.width, 1), NSCompositeSourceOver);
177     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y + frameRect.size.height-1, frameRect.size.width, 1), NSCompositeSourceOver);
178     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x, frameRect.origin.y, 1, frameRect.size.height), NSCompositeSourceOver);
179     NSRectFillUsingOperation(NSMakeRect(frameRect.origin.x+frameRect.size.width-1, frameRect.origin.y, 1, frameRect.size.height), NSCompositeSourceOver);
180 }
181
182 - (void)drawRect:(NSRect)rect
183 {
184     // Draw default to make sure the slider behaves correctly
185     [[NSGraphicsContext currentContext] saveGraphicsState];
186     NSRectClip(NSZeroRect);
187     [super drawRect:rect];
188     [[NSGraphicsContext currentContext] restoreGraphicsState];
189
190     // Full size
191     rect = [self bounds];
192     int diff = (int)(([[self cell] knobThickness] - 7.0)/2.0) - 1;
193     rect.origin.x += diff-1;
194     rect.origin.y += diff;
195     rect.size.width -= 2*diff-2;
196     rect.size.height -= 2*diff;
197
198     // Draw dark
199     NSRect knobRect = [[self cell] knobRectFlipped:NO];
200     [[[NSColor blackColor] colorWithAlphaComponent:0.6] set];
201     _drawFrameInRect(rect);
202     _drawKnobInRect(knobRect);
203
204     // Draw shadow
205     [[[NSColor blackColor] colorWithAlphaComponent:0.1] set];
206     rect.origin.x++;
207     rect.origin.y++;
208     knobRect.origin.x++;
209     knobRect.origin.y++;
210     _drawFrameInRect(rect);
211     _drawKnobInRect(knobRect);
212 }
213
214 @end
215
216 /*****************************************************************************
217  * ProgressView
218  *****************************************************************************/
219
220 @implementation VLCProgressView : NSView
221
222 - (void)scrollWheel:(NSEvent *)o_event
223 {
224     BOOL b_forward = NO;
225     CGFloat f_deltaY = [o_event deltaY];
226     CGFloat f_deltaX = [o_event deltaX];
227
228     if ([o_event isDirectionInvertedFromDevice])
229         f_deltaX = -f_deltaX; // optimisation, actually double invertion of f_deltaY here
230     else
231         f_deltaY = -f_deltaY;
232
233     // positive for left / down, negative otherwise
234     CGFloat f_delta = f_deltaX + f_deltaY;
235     CGFloat f_abs;
236     int i_vlckey;
237
238     if (f_delta > 0.0f)
239         f_abs = f_delta;
240     else {
241         b_forward = YES;
242         f_abs = -f_delta;
243     }
244
245     for (NSUInteger i = 0; i < (int)(f_abs/4.+1.) && f_abs > 0.05 ; i++) {
246         if (b_forward)
247             [[VLCCoreInteraction sharedInstance] forwardExtraShort];
248         else
249             [[VLCCoreInteraction sharedInstance] backwardExtraShort];
250     }
251 }
252
253 - (BOOL)acceptsFirstResponder
254 {
255     return YES;
256 }
257
258 @end
259
260 /*****************************************************************************
261  * TimeLineSlider
262  *****************************************************************************/
263
264 @interface TimeLineSlider()
265 {
266     NSImage *o_knob_img;
267     NSRect img_rect;
268     BOOL b_dark;
269 }
270 @end
271
272 @implementation TimeLineSlider
273
274 - (void)awakeFromNib
275 {
276     if (config_GetInt( getIntf(), "macosx-interfacestyle" )) {
277         o_knob_img = imageFromRes(@"progression-knob_dark");
278         b_dark = YES;
279     } else {
280         o_knob_img = imageFromRes(@"progression-knob");
281         b_dark = NO;
282     }
283     img_rect.size = [o_knob_img size];
284     img_rect.origin.x = img_rect.origin.y = 0;
285 }
286
287 - (CGFloat)knobPosition
288 {
289     NSRect knobRect = [[self cell] knobRectFlipped:NO];
290     knobRect.origin.x += knobRect.size.width / 2;
291     return knobRect.origin.x;
292 }
293
294 - (void)drawKnobInRect:(NSRect)knobRect
295 {
296     knobRect.origin.x += (knobRect.size.width - img_rect.size.width) / 2;
297     knobRect.size.width = img_rect.size.width;
298     knobRect.size.height = img_rect.size.height;
299     [o_knob_img drawInRect:knobRect fromRect:img_rect operation:NSCompositeSourceOver fraction:1];
300 }
301
302 - (void)drawRect:(NSRect)rect
303 {
304     [[(VLCVideoWindowCommon *)[self window] controlsBar] drawFancyGradientEffectForTimeSlider];
305     msleep(10000); //wait for the gradient to draw completely
306
307     /* Draw default to make sure the slider behaves correctly */
308     [[NSGraphicsContext currentContext] saveGraphicsState];
309     NSRectClip(NSZeroRect);
310     [super drawRect:rect];
311     [[NSGraphicsContext currentContext] restoreGraphicsState];
312
313     NSRect knobRect = [[self cell] knobRectFlipped:NO];
314     knobRect.origin.y+=1;
315     [self drawKnobInRect: knobRect];
316 }
317
318 @end
319
320 /*****************************************************************************
321  * VLCVolumeSliderCommon
322  *****************************************************************************/
323
324 @implementation VLCVolumeSliderCommon : NSSlider
325
326 - (void)scrollWheel:(NSEvent *)o_event
327 {
328     BOOL b_up = NO;
329     CGFloat f_deltaY = [o_event deltaY];
330     CGFloat f_deltaX = [o_event deltaX];
331
332     if ([o_event isDirectionInvertedFromDevice])
333         f_deltaX = -f_deltaX; // optimisation, actually double invertion of f_deltaY here
334     else
335         f_deltaY = -f_deltaY;
336
337     // positive for left / down, negative otherwise
338     CGFloat f_delta = f_deltaX + f_deltaY;
339     CGFloat f_abs;
340
341     if (f_delta > 0.0f)
342         f_abs = f_delta;
343     else {
344         b_up = YES;
345         f_abs = -f_delta;
346     }
347
348     for (NSUInteger i = 0; i < (int)(f_abs/4.+1.) && f_abs > 0.05 ; i++) {
349         if (b_up)
350             [[VLCCoreInteraction sharedInstance] volumeUp];
351         else
352             [[VLCCoreInteraction sharedInstance] volumeDown];
353     }
354 }
355
356 - (void)drawFullVolumeMarker
357 {
358     CGFloat maxAudioVol = self.maxValue / AOUT_VOLUME_DEFAULT;
359     if (maxAudioVol < 1.)
360         return;
361
362     NSColor *drawingColor;
363     // for bright artwork, a black color is used and vice versa
364     if (_usesBrightArtwork)
365         drawingColor = [[NSColor blackColor] colorWithAlphaComponent:.4];
366     else
367         drawingColor = [[NSColor whiteColor] colorWithAlphaComponent:.4];
368
369     NSBezierPath* bezierPath = [NSBezierPath bezierPath];
370     [self drawFullVolBezierPath:bezierPath];
371     [bezierPath closePath];
372
373     bezierPath.lineWidth = 1.;
374     [drawingColor setStroke];
375     [bezierPath stroke];
376 }
377
378 - (CGFloat)fullVolumePos
379 {
380     CGFloat maxAudioVol = self.maxValue / AOUT_VOLUME_DEFAULT;
381     CGFloat sliderRange = [self frame].size.width - [self knobThickness];
382     CGFloat sliderOrigin = [self knobThickness] / 2.;
383
384     return 1. / maxAudioVol * sliderRange + sliderOrigin;
385 }
386
387 - (void)drawFullVolBezierPath:(NSBezierPath*)bezierPath
388 {
389     CGFloat fullVolPos = [self fullVolumePos];
390     [bezierPath moveToPoint:NSMakePoint(fullVolPos, [self frame].size.height - 3.)];
391     [bezierPath lineToPoint:NSMakePoint(fullVolPos, 2.)];
392 }
393
394 @end
395
396 @implementation VolumeSliderCell
397
398 - (BOOL)continueTracking:(NSPoint)lastPoint at:(NSPoint)currentPoint inView:(NSView *)controlView
399 {
400     VLCVolumeSliderCommon *o_slider = (VLCVolumeSliderCommon *)controlView;
401     CGFloat fullVolumePos = [o_slider fullVolumePos] + 2.;
402
403     CGPoint snapToPoint = currentPoint;
404     if (ABS(fullVolumePos - currentPoint.x) <= 4.)
405         snapToPoint.x = fullVolumePos;
406
407     return [super continueTracking:lastPoint at:snapToPoint inView:controlView];
408 }
409
410 @end
411
412 /*****************************************************************************
413  * ITSlider
414  *****************************************************************************/
415
416 @interface ITSlider()
417 {
418     NSImage *img;
419     NSRect image_rect;
420 }
421 @end
422
423 @implementation ITSlider
424
425 - (void)awakeFromNib
426 {
427     BOOL b_dark = config_GetInt( getIntf(), "macosx-interfacestyle" );
428     if (b_dark)
429         img = imageFromRes(@"volume-slider-knob_dark");
430     else
431         img = imageFromRes(@"volume-slider-knob");
432
433     image_rect.size = [img size];
434     image_rect.origin.x = 0;
435
436     if (b_dark)
437         image_rect.origin.y = -1;
438     else
439         image_rect.origin.y = 0;
440 }
441
442 - (void)drawKnobInRect:(NSRect)knobRect
443 {
444     knobRect.origin.x += (knobRect.size.width - image_rect.size.width) / 2;
445     knobRect.size.width = image_rect.size.width;
446     knobRect.size.height = image_rect.size.height;
447     [img drawInRect:knobRect fromRect:image_rect operation:NSCompositeSourceOver fraction:1];
448 }
449
450 - (void)drawRect:(NSRect)rect
451 {
452     /* Draw default to make sure the slider behaves correctly */
453     [[NSGraphicsContext currentContext] saveGraphicsState];
454     NSRectClip(NSZeroRect);
455     [super drawRect:rect];
456     [[NSGraphicsContext currentContext] restoreGraphicsState];
457
458     [self drawFullVolumeMarker];
459
460     NSRect knobRect = [[self cell] knobRectFlipped:NO];
461     knobRect.origin.y+=2;
462     [self drawKnobInRect: knobRect];
463 }
464
465 @end
466
467 /*****************************************************************************
468  * VLCTimeField implementation
469  *****************************************************************************
470  * we need this to catch our click-event in the controller window
471  *****************************************************************************/
472
473 @interface VLCTimeField ()
474 {
475     NSString *o_remaining_identifier;
476     BOOL b_time_remaining;
477 }
478 @end
479
480 @implementation VLCTimeField
481 + (void)initialize
482 {
483     NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
484     NSDictionary *appDefaults = [NSDictionary dictionaryWithObjectsAndKeys:
485                                  @"NO", @"DisplayTimeAsTimeRemaining",
486                                  @"YES", @"DisplayFullscreenTimeAsTimeRemaining",
487                                  nil];
488
489     [defaults registerDefaults:appDefaults];
490 }
491
492
493 - (void)setRemainingIdentifier:(NSString *)o_string
494 {
495     o_remaining_identifier = o_string;
496     b_time_remaining = [[NSUserDefaults standardUserDefaults] boolForKey:o_remaining_identifier];
497 }
498
499 - (void)mouseDown: (NSEvent *)ourEvent
500 {
501     if ( [ourEvent clickCount] > 1 )
502         [[[VLCMain sharedInstance] mainMenu] goToSpecificTime: nil];
503     else
504     {
505         if (o_remaining_identifier) {
506             b_time_remaining = [[NSUserDefaults standardUserDefaults] boolForKey:o_remaining_identifier];
507             b_time_remaining = !b_time_remaining;
508             [[NSUserDefaults standardUserDefaults] setObject:(b_time_remaining ? @"YES" : @"NO") forKey:o_remaining_identifier];
509         } else {
510             b_time_remaining = !b_time_remaining;
511         }
512     }
513
514     [[self nextResponder] mouseDown:ourEvent];
515 }
516
517 - (BOOL)timeRemaining
518 {
519     if (o_remaining_identifier)
520         return [[NSUserDefaults standardUserDefaults] boolForKey:o_remaining_identifier];
521     else
522         return b_time_remaining;
523 }
524
525 @end
526
527 /*****************************************************************************
528  * VLCMainWindowSplitView implementation
529  * comment 1 + 2 taken from NSSplitView.h (10.7 SDK)
530  *****************************************************************************/
531 @implementation VLCMainWindowSplitView : NSSplitView
532 /* Return the color of the dividers that the split view is drawing between subviews. The default implementation of this method returns [NSColor clearColor] for the thick divider style. It will also return [NSColor clearColor] for the thin divider style when the split view is in a textured window. All other thin dividers are drawn with a color that looks good between two white panes. You can override this method to change the color of dividers.
533  */
534 - (NSColor *)dividerColor
535 {
536     return [NSColor colorWithCalibratedRed:.60 green:.60 blue:.60 alpha:1.];
537 }
538
539 /* Return the thickness of the dividers that the split view is drawing between subviews. The default implementation returns a value that depends on the divider style. You can override this method to change the size of dividers.
540  */
541 - (CGFloat)dividerThickness
542 {
543     return 1.0;
544 }
545 @end
546
547 /*****************************************************************************
548  * VLCThreePartImageView interface
549  *****************************************************************************/
550
551 @interface VLCThreePartImageView()
552 {
553     NSImage *_left_img;
554     NSImage *_middle_img;
555     NSImage *_right_img;
556 }
557 @end
558
559 @implementation VLCThreePartImageView
560
561 - (void)setImagesLeft:(NSImage *)left middle: (NSImage *)middle right:(NSImage *)right
562 {
563     _left_img = left;
564     _middle_img = middle;
565     _right_img = right;
566 }
567
568 - (void)drawRect:(NSRect)rect
569 {
570     NSRect bnds = [self bounds];
571     NSDrawThreePartImage( bnds, _left_img, _middle_img, _right_img, NO, NSCompositeSourceOver, 1, NO );
572 }
573
574 @end
575
576 @interface PositionFormatter()
577 {
578     NSCharacterSet *o_forbidden_characters;
579 }
580 @end
581
582 @implementation PositionFormatter
583
584 - (id)init
585 {
586     self = [super init];
587     NSMutableCharacterSet *nonNumbers = [[[NSCharacterSet decimalDigitCharacterSet] invertedSet] mutableCopy];
588     [nonNumbers removeCharactersInString:@"-:"];
589     o_forbidden_characters = [nonNumbers copy];
590
591     return self;
592 }
593
594 - (NSString*)stringForObjectValue:(id)obj
595 {
596     if([obj isKindOfClass:[NSString class]])
597         return obj;
598     if([obj isKindOfClass:[NSNumber class]])
599         return [obj stringValue];
600
601     return nil;
602 }
603
604 - (BOOL)getObjectValue:(id*)obj forString:(NSString*)string errorDescription:(NSString**)error
605 {
606     *obj = [string copy];
607     return YES;
608 }
609
610 - (BOOL)isPartialStringValid:(NSString*)partialString newEditingString:(NSString**)newString errorDescription:(NSString**)error
611 {
612     if ([partialString rangeOfCharacterFromSet:o_forbidden_characters options:NSLiteralSearch].location != NSNotFound) {
613         return NO;
614     } else {
615         return YES;
616     }
617 }
618
619 @end
620
621 @implementation NSView (EnableSubviews)
622
623 - (void)enableSubviews:(BOOL)b_enable
624 {
625     for (NSView *o_view in [self subviews]) {
626         [o_view enableSubviews:b_enable];
627
628         // enable NSControl
629         if ([o_view respondsToSelector:@selector(setEnabled:)]) {
630             [(NSControl *)o_view setEnabled:b_enable];
631         }
632         // also "enable / disable" text views
633         if ([o_view respondsToSelector:@selector(setTextColor:)]) {
634             if (b_enable == NO) {
635                 [(NSTextField *)o_view setTextColor:[NSColor disabledControlTextColor]];
636             } else {
637                 [(NSTextField *)o_view setTextColor:[NSColor controlTextColor]];
638             }
639         }
640
641     }
642 }
643
644 @end
645
646 /*****************************************************************************
647  * VLCByteCountFormatter addition
648  *****************************************************************************/
649
650 @implementation VLCByteCountFormatter
651
652 + (NSString *)stringFromByteCount:(long long)byteCount countStyle:(NSByteCountFormatterCountStyle)countStyle
653 {
654     // Use native implementation on >= mountain lion
655     Class byteFormatterClass = NSClassFromString(@"NSByteCountFormatter");
656     if (byteFormatterClass && [byteFormatterClass respondsToSelector:@selector(stringFromByteCount:countStyle:)]) {
657         return [byteFormatterClass stringFromByteCount:byteCount countStyle:NSByteCountFormatterCountStyleFile];
658     }
659
660     float devider = 0.;
661     float returnValue = 0.;
662     NSString *suffix;
663
664     NSNumberFormatter *theFormatter = [[NSNumberFormatter alloc] init];
665     [theFormatter setLocale:[NSLocale currentLocale]];
666     [theFormatter setAllowsFloats:YES];
667
668     NSString *returnString = @"";
669
670     if (countStyle != NSByteCountFormatterCountStyleDecimal)
671         devider = 1024.;
672     else
673         devider = 1000.;
674
675     if (byteCount < 1000) {
676         returnValue = byteCount;
677         suffix = _NS("B");
678         [theFormatter setMaximumFractionDigits:0];
679         goto end;
680     }
681
682     if (byteCount < 1000000) {
683         returnValue = byteCount / devider;
684         suffix = _NS("KB");
685         [theFormatter setMaximumFractionDigits:0];
686         goto end;
687     }
688
689     if (byteCount < 1000000000) {
690         returnValue = byteCount / devider / devider;
691         suffix = _NS("MB");
692         [theFormatter setMaximumFractionDigits:1];
693         goto end;
694     }
695
696     [theFormatter setMaximumFractionDigits:2];
697     if (byteCount < 1000000000000) {
698         returnValue = byteCount / devider / devider / devider;
699         suffix = _NS("GB");
700         goto end;
701     }
702
703     returnValue = byteCount / devider / devider / devider / devider;
704     suffix = _NS("TB");
705
706 end:
707     returnString = [NSString stringWithFormat:@"%@ %@", [theFormatter stringFromNumber:[NSNumber numberWithFloat:returnValue]], suffix];
708
709     return returnString;
710 }
711
712 @end