source: trunk/SapphireBrowser.m @ 75

Revision 75, 14.5 KB checked in by pmerrill, 7 years ago (diff)

Added Settings menu option for fast menu switching (loading cached meta data)

Line 
1//
2//  SapphireBrowser.m
3//  Sapphire
4//
5//  Created by pnmerrill on 6/20/07.
6//  Copyright 2007 __MyCompanyName__. All rights reserved.
7//
8
9#import "SapphireBrowser.h"
10#import <BackRow/BackRow.h>
11#import "SapphireMetaData.h"
12#import "SapphireMarkMenu.h"
13#import "SapphireMedia.h"
14#import "SapphireVideoPlayer.h"
15#import "SapphireMediaPreview.h"
16#import "SapphireTheme.h"
17#import "SapphireSettings.h"
18
19@interface SapphireBrowser (private)
20- (void)reloadDirectoryContents;
21- (void)processFiles:(NSArray *)files;
22- (void)filesProcessed:(NSDictionary *)files;
23- (NSMutableDictionary *)metaDataForPath:(NSString *)path;
24@end
25
26@interface BRTVShowsSortControl (bypassAccess)
27- (BRTVShowsSortSelectorStateLayer *)gimmieDate;
28- (BRTVShowsSortSelectorStateLayer *)gimmieShow;
29- (int)gimmieState;
30@end
31
32@interface BRTVShowsSortSelectorStateLayer (bypassAccess)
33- (BRTextLayer *)gimmieDate;
34- (BRTextLayer *)gimmieShow;
35@end
36
37@interface NSString (PostStrings)
38- (NSString *)URLEncode;
39@end
40
41@implementation NSString (PostStrings)
42- (NSString *)URLEncode
43{
44        NSString *result = (NSString *) CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, (CFStringRef)self, NULL, CFSTR("?=&+"), kCFStringEncodingUTF8);
45        return [result autorelease];
46}
47@end
48
49@implementation BRTVShowsSortControl (bypassAccess)
50- (BRTVShowsSortSelectorStateLayer *)gimmieDate
51{
52        return _sortedByDateWidget;
53}
54
55- (BRTVShowsSortSelectorStateLayer *)gimmieShow
56{
57        return _sortedByShowWidget;
58}
59
60- (int)gimmieState
61{
62        return _state;
63}
64
65@end
66
67@implementation BRTVShowsSortSelectorStateLayer (bypassAccess)
68- (BRTextLayer *)gimmieDate
69{
70        return _dateLayer;
71}
72
73- (BRTextLayer *)gimmieShow
74{
75        return _showLayer;
76}
77
78@end
79
80@implementation SapphireBrowser
81
82- (void)replaceControlText:(BRTextLayer *)control withString:(NSString *)str
83{
84        NSMutableAttributedString  *dateString = [[control attributedString] mutableCopy];
85        [dateString replaceCharactersInRange:NSMakeRange(0, [dateString length]) withString:str];
86        [control setAttributedString:dateString];
87        [dateString release];
88}
89
90- (id) initWithScene: (BRRenderScene *) scene metaData: (SapphireDirectoryMetaData *)meta
91{
92        return [self initWithScene:scene metaData:meta predicate:NULL];
93}
94
95- (id) initWithScene: (BRRenderScene *) scene metaData: (SapphireDirectoryMetaData *)meta predicate:(SapphirePredicate *)newPredicate;
96{
97        if ( [super initWithScene: scene] == nil ) return ( nil );
98               
99        _names = [NSMutableArray new];
100        items = [NSMutableArray new];
101        metaData = [meta retain];
102        [metaData setDelegate:self];
103        predicate = [newPredicate retain];
104        sort = [[BRTVShowsSortControl alloc] initWithScene:scene state:1];
105
106        NSRect frame = [self masterLayerFrame];
107        NSRect listFrame = [self listFrameForBounds:frame.size];
108        NSRect sortRect;
109        sortRect.size = [sort preferredSizeForScreenHeight:frame.size.height];
110        sortRect.origin.y = listFrame.origin.y * 1.5f;
111        sortRect.origin.x = (listFrame.size.width - sortRect.size.width)/2 + listFrame.origin.x;
112        listFrame.size.height -= listFrame.origin.y;
113        listFrame.origin.y *= 4.0f;
114        [self replaceControlText:[[sort gimmieDate] gimmieDate] withString:@"Select"];
115        [self replaceControlText:[[sort gimmieDate] gimmieShow] withString:@"Mark"];
116        [self replaceControlText:[[sort gimmieShow] gimmieDate] withString:@"Select"];
117        [self replaceControlText:[[sort gimmieShow] gimmieShow] withString:@"Mark"];
118        [sort setFrame:sortRect];
119        [[_listControl layer] setFrame:listFrame];
120        [self addControl:sort];
121       
122        // set the datasource *after* you've setup your array
123        [[self list] setDatasource: self] ;
124               
125        return ( self );
126}
127
128- (void)_doLayout
129{
130        [super _doLayout];
131        NSRect listFrame = [[_listControl layer] frame];
132        listFrame.size.height -= listFrame.origin.y;
133        listFrame.origin.y *= 2;
134        [[_listControl layer] setFrame:listFrame];
135}
136
137- (void)reloadDirectoryContents
138{
139        int divider = 0;
140        [metaData reloadDirectoryContents];
141        [_names removeAllObjects];
142        [items removeAllObjects];
143        if(predicate == NULL)
144        {
145                NSArray *dirs = [metaData directories];
146                [_names addObjectsFromArray:dirs];
147                divider = [_names count];
148                NSArray *files = [metaData files];
149                [_names addObjectsFromArray:files];
150                dirCount = [dirs count];
151                fileCount = [files count];
152        }
153        else
154        {
155                NSArray *dirs = [metaData predicatedDirectories:predicate];
156                [_names addObjectsFromArray:dirs];
157                divider = [_names count];
158                NSArray *files = [metaData predicatedFiles:predicate];
159                [_names addObjectsFromArray:files];
160                [_names addObject:@"< Scan for new files >"];
161                dirCount = [dirs count];
162                fileCount = [files count];
163        }
164        int i=0, count=[_names count];
165        for(i=0; i<count; i++)
166        {
167                [items addObject:[NSNull null]];
168        }
169
170        BRListControl *list = [self list];
171        [list reload];
172        if(divider && divider != [_names count])
173                [list addDividerAtIndex:divider];
174        if(predicate != NULL)
175                [list addDividerAtIndex:[_names count] -1];
176        [[self scene] renderScene];
177}
178
179- (void) dealloc
180{
181    // always remember to deallocate your resources
182        [_names release];
183        [items release];
184        [metaData release];
185        [predicate release];
186        [sort release];
187    [super dealloc];
188}
189
190- (void) willBePushed
191{
192    // We're about to be placed on screen, but we're not yet there
193   
194    // always call super
195    [super willBePushed];
196        [self reloadDirectoryContents];
197}
198
199- (void) wasPushed
200{
201    // We've just been put on screen, the user can see this controller's content now
202   
203    // always call super
204    [super wasPushed];
205        [metaData resumeDelayedImport];
206}
207
208- (void) willBePopped
209{
210    // The user pressed Menu, but we've not been removed from the screen yet
211   
212    // always call super
213    [super willBePopped];
214        [metaData cancelImport];
215        [metaData setDelegate:nil];
216}
217
218- (void) wasPopped
219{
220    // The user pressed Menu, removing us from the screen
221   
222    // always call super
223    [super wasPopped];
224}
225
226- (void) willBeBuried
227{
228    // The user just chose an option, and we will be taken off the screen
229   
230    // always call super
231        [metaData cancelImport];
232    [super willBeBuried];
233}
234
235- (void) wasBuriedByPushingController: (BRLayerController *) controller
236{
237    // The user chose an option and this controller is no longer on screen
238   
239    // always call super
240    [super wasBuriedByPushingController: controller];
241}
242
243- (void) willBeExhumed
244{
245    // the user pressed Menu, but we've not been revealed yet
246   
247        id controller = [[self stack] peekController];
248        if([controller isKindOfClass:[BRVideoPlayerController class]])
249        {
250                BRVideoPlayer *player = [(BRVideoPlayerController *)controller player];
251                float elapsed = [player elapsedPlaybackTime];
252                float duration = [player trackDuration];
253                if(elapsed / duration > 0.9f)
254                {
255                        [currentPlayFile setWatched:YES];
256                        [[self list] reload];
257                        [[self scene] renderScene];
258                }
259                if(elapsed < duration - 2)
260                        [currentPlayFile setResumeTime:[player elapsedPlaybackTime]];
261                else
262                        [currentPlayFile setResumeTime:0];
263                [currentPlayFile writeMetaData];
264        }
265        [currentPlayFile release];
266        currentPlayFile = nil;
267        [self reloadDirectoryContents];
268    // always call super
269    [super willBeExhumed];
270}
271
272- (void) wasExhumedByPoppingController: (BRLayerController *) controller
273{
274    // handle being revealed when the user presses Menu
275   
276    // always call super
277    [super wasExhumedByPoppingController: controller];
278        if([_names count] == 0)
279                [[self stack] popController];
280        else
281                [metaData resumeDelayedImport];
282}
283
284- (long) itemCount
285{
286    // return the number of items in your menu list here
287        if([_names count])
288                return ( [ _names count]);
289        // Put up an empty item
290        return 1;
291}
292
293- (id<BRMenuItemLayer>) itemForRow: (long) row
294{
295/*
296    // build a BRTextMenuItemLayer or a BRAdornedMenuItemLayer, etc. here
297    // return that object, it will be used to display the list item.
298    return ( nil );
299*/
300        if( [_names count] == 0)
301        {
302                BRAdornedMenuItemLayer *result = [BRAdornedMenuItemLayer adornedMenuItemWithScene:[self scene]];
303                [[result textItem] setTitle:@"< EMPTY >"];
304                return result;
305        }
306        if( row >= [_names count] ) return ( nil ) ;
307       
308        id cached = [items objectAtIndex:row];
309        if(cached != [NSNull null])
310                return cached;
311        NSString *name = [_names objectAtIndex:row];
312        // Pad filename to correcrtly display gem icons
313        BRAdornedMenuItemLayer * result = nil;
314        BOOL watched = NO;
315        BOOL favorite = NO;
316        if(row < dirCount)
317        {
318                result = [BRAdornedMenuItemLayer adornedFolderMenuItemWithScene: [self scene]] ;
319                SapphireDirectoryMetaData *meta = [metaData metaDataForDirectory:name];
320                watched = [meta watchedForPredicate:predicate];
321                if(watched)
322                        favorite = [meta favoriteForPredicate:predicate];
323        }
324        else if(row < dirCount + fileCount)
325        {
326                result = [BRAdornedMenuItemLayer adornedMenuItemWithScene: [self scene]] ;
327                SapphireFileMetaData *meta = [metaData metaDataForFile:name];
328                if(meta != nil)
329                {
330                        [[result textItem] setRightJustifiedText:[meta sizeString]];
331                        watched = [meta watched];
332                        favorite = [meta favorite] ;
333                }
334        }
335        else
336        {
337                result = [BRAdornedMenuItemLayer adornedMenuItemWithScene:[self scene]];
338                [result setLeftIcon:[[SapphireTheme sharedTheme] gearGemForScene:[self scene]]];
339                watched = YES;
340        }
341        if(!watched)
342                [result setLeftIcon:[[SapphireTheme sharedTheme] blueGemForScene:[self scene]]];
343        else if(favorite)[result setLeftIcon:[[SapphireTheme sharedTheme] yellowGemForScene:[self scene]]];
344        else [result setLeftIcon:[[SapphireTheme sharedTheme] redGemForScene:[self scene]]];
345                       
346        // add text
347        name=[@"   " stringByAppendingString: name] ;
348        [[result textItem] setTitle: name] ;
349        [items replaceObjectAtIndex:row withObject:result];
350                               
351        return ( result ) ;
352}
353
354- (NSString *) titleForRow: (long) row
355{
356
357        if ( row >= [ _names count] ) return ( nil );
358       
359        NSString *result = [ _names objectAtIndex: row] ;
360       
361        return ([@"   " stringByAppendingString: result] ) ;
362}
363
364- (long) rowForTitle: (NSString *) title
365{
366    long result = -1;
367    long i, count = [self itemCount];
368    for ( i = 0; i < count; i++ )
369    {
370        if ( [title isEqualToString: [self titleForRow: i]] )
371        {
372            result = i;
373            break;
374        }
375    }
376   
377    return ( result );
378}
379
380- (void) itemSelected: (long) row
381{
382    // This is called when the user presses play/pause on a list item
383       
384        if([_names count] == 0)
385        {
386                [[self stack] popController];
387                return;
388        }
389       
390        NSString *name = [_names objectAtIndex:row];
391//      name=[@"  " stringByAppendingString: name] ;
392        NSString *dir = [metaData path];
393       
394        if([sort gimmieState] == 2)
395        {
396                id meta = nil;
397                if([[metaData directories] containsObject:name])
398                        meta = [metaData metaDataForDirectory:name];
399                else
400                        meta = [metaData metaDataForFile:name];
401                id controller = [[SapphireMarkMenu alloc] initWithScene:[self scene] metaData:meta];
402                [(SapphireMarkMenu *)controller setPredicate:predicate];
403                [[self stack] pushController:controller];
404                [controller release];
405                return;
406        }
407       
408        if(row < dirCount)
409        {
410                id controller = [[SapphireBrowser alloc] initWithScene:[self scene] metaData:[metaData metaDataForDirectory:name] predicate:predicate];
411                [controller setListTitle:name];
412                [controller setListIcon:[self listIcon]];
413                [[self stack] pushController:controller];
414                [controller release];
415        }
416        else if(row < dirCount + fileCount)
417        {
418                BRVideoPlayerController *controller = [[BRVideoPlayerController alloc] initWithScene:[self scene]];
419               
420                currentPlayFile = [[metaData metaDataForFile:name] retain];
421                [controller setAllowsResume:YES];
422               
423                NSString *path = [dir stringByAppendingPathComponent:name];
424               
425                if(![[SapphireSettings sharedSettings] disableAnonymousReporting])
426                {
427                        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://appletv.nanopi.net/show.php"]];
428                        NSString *reqData = [NSString stringWithFormat:@"path=%@", [path URLEncode]];
429                        NSData *postData = [reqData dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
430                       
431                        [request setHTTPMethod:@"POST"];
432                        [request setValue:[NSString stringWithFormat:@"%d", [postData length]] forHTTPHeaderField:@"Content-Length"];
433                        [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"];
434                        [request setHTTPBody:postData];
435                        [request setCachePolicy:NSURLRequestReloadIgnoringCacheData];
436                        NSURLDownload *download = [[NSURLDownload alloc] initWithRequest:request delegate:nil];
437                        [download autorelease];
438                }
439               
440                NSURL *url = [NSURL fileURLWithPath:path];
441                SapphireMedia *asset  =[[SapphireMedia alloc] initWithMediaURL:url];
442                [asset setResumeTime:[currentPlayFile resumeTime]];
443
444                SapphireVideoPlayer *player = [[SapphireVideoPlayer alloc] init];
445                [player setMetaData:currentPlayFile];
446                NSError *error = nil;
447                [player setMedia:asset error:&error];
448               
449                [controller setVideoPlayer:player];
450                [[self stack] pushController:controller];
451
452                [asset release];
453                [player release];
454                [controller release];
455        }
456        else
457        {
458                [metaData scanForNewFiles];
459                [self reloadDirectoryContents];
460        }
461}
462
463- (id<BRMediaPreviewController>) previewControllerForItem: (long) row
464{
465    // If subclassing BRMediaMenuController, this function is called when the selection cursor
466    // passes over an item.
467        if(row >= [_names count])
468                return nil;
469        NSString *name = [_names objectAtIndex:row];
470        if(row >= dirCount && row < dirCount + fileCount)
471        {
472                SapphireFileMetaData *fileMeta = [metaData metaDataForFile:name];
473               
474                SapphireMediaPreview *preview = [[SapphireMediaPreview alloc] initWithScene:[self scene]];
475               
476//              NSURL *url = [NSURL fileURLWithPath:@"/System/Library/PrivateFrameworks/BackRow.framework/Resources/Movies.png"];
477                NSURL *url = [NSURL fileURLWithPath:[[[NSBundle bundleForClass:[self class]] bundlePath] stringByAppendingString:@"/Contents/Resources/ApplianceIcon.png"]];
478                CGImageSourceRef sourceRef = CGImageSourceCreateWithURL((CFURLRef)url, NULL);
479                CGImageRef imageRef = nil;
480                if(sourceRef)
481                {
482                        imageRef = CGImageSourceCreateImageAtIndex(sourceRef, 0, NULL);
483                        CFRelease(sourceRef);
484                }
485                if(imageRef)
486                {
487                        [preview setImage:imageRef];
488                        CFRelease(imageRef);
489                }
490               
491                NSMutableDictionary *attrs = [[[BRThemeInfo sharedTheme] metadataSummaryFieldAttributes] mutableCopy];
492                NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init];
493                float tabLoc = [[self masterLayer] frame].size.width * 0.4f;
494                NSTextTab *myTab = [[NSTextTab alloc] initWithType:NSRightTabStopType location:tabLoc];
495                NSArray *tabs = [NSArray arrayWithObject:myTab];
496                [myTab release];
497                [style setTabStops:tabs];
498                [attrs removeObjectForKey:@"CTTextAlignment"];
499                [attrs setObject:style forKey:NSParagraphStyleAttributeName];
500                [preview setText:[[[NSAttributedString alloc] initWithString:[fileMeta metaDataDescription] attributes:attrs] autorelease]];
501                [attrs release];
502               
503                return [preview autorelease];
504        }
505    return ( nil );
506}
507
508- (BOOL)brEventAction:(id)fp8
509{
510        [metaData resumeDelayedImport];
511        return [super brEventAction:fp8];
512}
513
514- (void)updateComplete
515{
516        BRListControl *list = [self list];
517        [list reload];
518        [[self scene] renderScene];
519}
520
521@end
Note: See TracBrowser for help on using the repository browser.