source: trunk/SapphireFrappliance/MetaDataImporting/SapphireImporterDataMenu.m @ 1282

Revision 1282, 19.8 KB checked in by gbooker, 4 years ago (diff)

Correct the URL loading count. Instead of doing a separate tracking, just update to the real value

Line 
1/*
2 * SapphireImporterDataMenu.m
3 * Sapphire
4 *
5 * Created by pnmerrill on Jun. 24, 2007.
6 * Copyright 2007 Sapphire Development Team and/or www.nanopi.net
7 * All rights reserved.
8 *
9 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
10 * General Public License as published by the Free Software Foundation; either version 3 of the License,
11 * or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
14 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
15 * Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along with this program; if not,
18 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 */
20
21#import "SapphireImporterDataMenu.h"
22#import <BackRow/BackRow.h>
23#import "SapphireDirectoryMetaData.h"
24#import "SapphireFileMetaData.h"
25#import "SapphireCollectionDirectory.h"
26#import <SapphireCompatClasses/SapphireFrontRowCompat.h>
27#import "SapphireImportHelper.h"
28#import "SapphireApplianceController.h"
29#import "NSString-Extensions.h"
30#import "SapphireMetaDataSupport.h"
31#import "NSManagedObject-Extensions.h"
32#import "SapphireFileSymLink.h"
33#import "SapphireChooser.h"
34
35#define updateFreq 0.05
36
37@interface SapphireImportChooserQueueItem : NSObject
38{
39        BRLayerController <SapphireChooser>     *chooser;
40        id <SapphireImporter>                           importer;
41        id                                                                      context;
42}
43- (id)initWithChooser:(BRLayerController <SapphireChooser> *)chooser forImporter:(id <SapphireImporter>)importer withContext:(id)context;
44- (BRLayerController <SapphireChooser> *)chooser;
45- (id <SapphireImporter>)importer;
46- (id)context;
47@end
48
49@implementation SapphireImportChooserQueueItem
50
51- (id)initWithChooser:(BRLayerController <SapphireChooser>*)aChooser forImporter:(id <SapphireImporter>)aImporter withContext:(id)aContext
52{
53        self = [super init];
54        if(!self)
55                return self;
56       
57        chooser = [aChooser retain];
58        importer = [aImporter retain];
59        context = [aContext retain];
60       
61        return self;
62}
63
64- (void) dealloc
65{
66        [chooser release];
67        [importer release];
68        [context release];
69        [super dealloc];
70}
71
72- (BRLayerController <SapphireChooser> *)chooser
73{
74        return chooser;
75}
76
77- (id <SapphireImporter>)importer
78{
79        return importer;
80}
81
82- (id)context
83{
84        return context;
85}
86
87@end
88
89@implementation SapphireImportStateData
90
91- (id)initWithFile:(SapphireFileMetaData *)aFile atPath:(NSString *)aPath
92{
93        self = [super init];
94        if(!self)
95                return self;
96       
97        file = [aFile retain];
98        path = [aPath retain];
99       
100        return self;
101}
102
103- (void) dealloc
104{
105        [file release];
106        [path release];
107        [lookupName release];
108        [super dealloc];
109}
110
111- (void)setLookupName:(NSString *)aName
112{
113        [lookupName autorelease];
114        lookupName = [aName retain];
115}
116
117@end
118
119
120@interface BRLayerController (compatounth)
121- (NSRect)controllerFrame;  /*technically wrong; it is really a CGRect*/
122@end
123
124@interface SapphireImporterDataMenu ()
125- (void)layoutFrame;
126- (void)setFileProgress:(NSString *)updateFileProgress;
127- (void)resetUIElements;
128- (void)itemImportBackgrounded;
129- (void)updateDisplay;
130@end
131
132@implementation SapphireImporterDataMenu
133- (id) initWithScene: (BRRenderScene *) scene context:(NSManagedObjectContext *)context  importer:(id <SapphireImporter>)import;
134{
135        self = [super initWithScene:scene];
136        if(self == nil)
137                return self;
138       
139        moc = [context retain];
140        importer = [import retain];
141        [importer setDelegate:self];
142        importItems = [[NSMutableArray alloc] init];
143        allItems = nil;
144        skipSet = nil;
145        choosers = [[NSMutableArray alloc] init];
146        /*Setup the Header Control with default contents*/
147        [self setListTitle:BRLocalizedString(@"Populate Show Data", @"Do a file metadata import")];
148
149        /*Setup the text entry control*/
150        text = [SapphireFrontRowCompat newTextControlWithScene:scene];
151        fileProgress = [SapphireFrontRowCompat newTextControlWithScene:scene];
152        currentFile = [SapphireFrontRowCompat newTextControlWithScene:scene];
153       
154        /*Setup the progress bar*/
155        bar = [SapphireFrontRowCompat newProgressBarWidgetWithScene:scene];
156        [self layoutFrame];
157       
158        [[self list] setDatasource:self];
159       
160        /*add controls*/
161        [self addControl: text];
162        [self addControl: fileProgress] ;
163        [self addControl: currentFile] ;
164        [SapphireFrontRowCompat addSublayer:bar toControl:self];
165       
166        [SapphireLayoutManager setCustomLayoutOnControl:self];
167       
168    return self;
169}
170
171- (void) dealloc
172{
173        [text release];
174        [fileProgress release];
175        [currentFile release];
176        [bar release];
177        [moc release];
178        [collectionDirectories release];
179        [skipSet release];
180        [importItems release];
181        [importTimer invalidate];
182        [importer setDelegate:nil];
183        [importer release];
184        [buttonTitle release];
185        [allItems release];
186        [updateTimer invalidate];
187        [currentFilename release];
188        [choosers release];
189        [super dealloc];
190}
191
192- (void)layoutFrame
193{
194        /*title*/
195        NSRect frame = [SapphireFrontRowCompat frameOfController:self];
196        frame.origin.y += frame.size.height * 5.0f / 16.0f;
197        frame.origin.x = frame.size.width / 6.0f;
198        frame.size.height = frame.size.height / 16.0f;
199        frame.size.width = frame.size.width * 2.0f / 3.0f;
200        [bar setFrame: frame] ;
201}
202
203/*!
204 * @brief Sets the informative text
205 *
206 * @param theText The text to set
207 */
208- (void)setText:(NSString *)theText
209{
210        [SapphireFrontRowCompat setText:theText withAtrributes:[SapphireFrontRowCompat paragraphTextAttributes] forControl:text];
211       
212        NSRect master = [SapphireFrontRowCompat frameOfController:self];
213        NSSize txtSize = [SapphireFrontRowCompat textControl:text renderedSizeWithMaxSize:NSMakeSize(master.size.width * 2.0f/3.0f, master.size.height * 0.4f)];
214       
215        NSRect frame;
216        frame.origin.x = (master.size.width - txtSize.width) * 0.5f;
217        frame.origin.y = (master.size.height * 0.4f - txtSize.height) + master.size.height * 0.3f/0.8f + master.origin.y;
218        frame.size = txtSize;
219        [text setFrame:frame];
220}
221
222/*!
223 * @brief Sets the file progress string
224 *
225 * @param theFileProgress The file progress string to display
226 */
227- (void)setFileProgress:(NSString *)theFileProgress
228{
229        [SapphireFrontRowCompat setText:theFileProgress withAtrributes:[SapphireFrontRowCompat paragraphTextAttributes] forControl:fileProgress];
230       
231        NSRect master = [SapphireFrontRowCompat frameOfController:self];
232        NSSize progressSize = [SapphireFrontRowCompat textControl:fileProgress renderedSizeWithMaxSize:NSMakeSize(master.size.width * 1.0f/2.0f, master.size.height * 0.3f)];
233       
234        NSRect frame;
235        frame.origin.x =  (master.size.width) * 0.1f;
236        frame.origin.y = (master.size.height * 0.12f) + master.origin.y;
237        frame.size = progressSize;
238        [fileProgress setFrame:frame];
239}
240
241- (void)realSetCurrentFile:(NSString *)theCurrentFile
242{
243        [SapphireFrontRowCompat setText:theCurrentFile withAtrributes:[SapphireFrontRowCompat paragraphTextAttributes] forControl:currentFile];
244       
245        NSRect master = [SapphireFrontRowCompat frameOfController:self];
246        NSSize currentFileSize = [SapphireFrontRowCompat textControl:currentFile renderedSizeWithMaxSize:NSMakeSize(master.size.width * 9.0f/10.0f, master.size.height * 0.3f)];
247       
248        NSRect frame;
249        frame.origin.x =  (master.size.width) * 0.1f;
250        frame.origin.y = (master.size.height * 0.09f) + master.origin.y;
251        frame.size = currentFileSize;
252        [currentFile setFrame:frame];
253}
254
255- (void)updateDisplayOfCurrentFile
256{
257        updateTimer = nil;
258        [self realSetCurrentFile:currentFilename];
259}
260
261/*!
262 * @brief Sets the display of the current file being processed
263 *
264 * @param theCurrentFile The current file being proccessed
265 */
266- (void)setCurrentFile:(NSString *)theCurrentFile
267{
268        [currentFilename release];
269        currentFilename = [theCurrentFile retain];
270        if(updateTimer == nil)
271                updateTimer = [NSTimer scheduledTimerWithTimeInterval:updateFreq target:self selector:@selector(updateDisplayOfCurrentFile) userInfo:nil repeats:NO];
272}
273
274/*!
275 * @brief Get the list of all files to process
276 */
277- (void)getItems
278{
279        SapphireCollectionDirectory *collection = [collectionDirectories objectAtIndex:collectionIndex];
280        if([collection isDeleted] || [collection skipValue])
281        {
282                collectionIndex++;
283                [self performSelector:@selector(gotSubFiles:) withObject:[NSArray array] afterDelay:0.0];
284                return;
285        }
286        SapphireDirectoryMetaData *meta = [collection directory];
287        //Prefetch
288/*      NSPredicate *fetchPredicate = [NSPredicate predicateWithFormat:@"path BEGINSWITH %@", [[meta path] stringByAppendingString:@"/"]];
289        NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"path" ascending:YES];
290        doSortedFetchRequest(SapphireDirectoryMetaDataName, moc, fetchPredicate, sort);
291        doSortedFetchRequest(SapphireFileMetaDataName, moc, fetchPredicate, sort);
292        doSortedFetchRequest(SapphireDirectorySymLinkName, moc, fetchPredicate, sort);
293        doSortedFetchRequest(SapphireFileSymLinkName, moc, fetchPredicate, sort);
294        [sort release];*/
295
296        [meta getSubFileMetasWithDelegate:self skipDirectories:skipSet];
297        collectionIndex++;
298}
299
300- (void)setButtonTitle:(NSString *)title
301{
302        if(title != nil)
303        {
304                [buttonTitle release];
305                buttonTitle = [title retain];
306        }
307        BRListControl *list = [self list];
308       
309        [list setHidden:(title == nil)];
310       
311        [list reload];
312}
313
314/*!
315 * @brief Start the import process
316 */
317- (void)import
318{
319        @try {
320                /*Change display*/
321                [self setButtonTitle:BRLocalizedString(@"Cancel Import", @"Cancel the import process")];
322                action = @selector(cancel);
323                [self setFileProgress:BRLocalizedString(@"Initializing...", @"The import is starting")];
324                [SapphireFrontRowCompat renderScene:[self scene]];
325                /*Initialize the import process*/
326                SapphireURLLoader *loader = [SapphireApplianceController urlLoader];
327                [loader addDelegate:self];
328                pendingURLCount = [loader loadingURLCount];
329                canceled = NO;
330                collectionIndex = 0;
331                [collectionDirectories release];
332                collectionDirectories = [[SapphireCollectionDirectory availableCollectionDirectoriesInContext:moc includeHiddenOverSkipped:YES] retain];
333                NSArray *skipCol = [SapphireCollectionDirectory skippedCollectionDirectoriesInContext:moc];
334                [skipSet release];
335                skipSet = [[NSMutableSet alloc] initWithSet:[skipCol valueForKeyPath:@"directory.path"]];
336                if([collectionDirectories count])
337                        [self getItems];
338                else
339                        [self gotSubFiles:[NSArray array]];
340        }
341        @catch (NSException * e) {
342                [SapphireApplianceController logException:e];
343                [e raise];
344        }
345}
346
347/*!
348 * @brief Metadata delegate method to return final list of files
349 *
350 * @param subs The files which are children of the current directory
351 */
352- (void)gotSubFiles:(NSArray *)subs
353{
354        [importItems addObjectsFromArray:subs];
355        if(collectionIndex != [collectionDirectories count])
356        {
357                [self getItems];
358                return;
359        }
360        [SapphireMetaDataSupport save:moc];
361        [allItems release];
362        allItems = [importItems copy];
363        updated = 0 ;
364        current = 0;
365        max = [importItems count];
366        if(!canceled)
367        {
368                /*Update the display*/
369                [self updateDisplay];
370                importTimer = [NSTimer scheduledTimerWithTimeInterval:0.0f target:self selector:@selector(importNextItem:) userInfo:nil repeats:NO];           
371        }
372}
373
374/*!
375 * @brief Metadata delegate method to inform on its scanning progress
376 *
377 * @param dir The current directory it is scanning
378 */
379- (void)scanningDir:(NSString *)dir
380{
381        [self setCurrentFile:[NSString stringWithFormat:BRLocalizedString(@"Scanning Directory: %@", "Current scan import process format, directory"),[NSString stringByCroppingDirectoryPath:dir toLength:3]]];
382        [SapphireFrontRowCompat renderScene:[self scene]];
383}
384
385/*!
386 * @brief Ask if we should cancel the fetching of file listing
387 *
388 * @return YES if the file listing should be canceled, NO otherwise
389 */
390- (BOOL)getSubFilesCanceled
391{
392        return canceled;
393}
394
395/*!
396 * @brief Import a single item
397 *
398 * @return YES if any data was imported, NO otherwise
399 */
400- (BOOL)doImport
401{
402        BOOL ret = NO;
403        id meta = [importItems objectAtIndex:0];
404        SapphireFileMetaData *file;
405        if([meta isKindOfClass:[SapphireFileSymLink class]])
406                file = [(SapphireFileSymLink *)meta file];
407        else
408                file = (SapphireFileMetaData *)meta;
409        if(file.joinedToFile != nil)
410                return NO;
411        @try {
412                ImportState result = [importer importMetaData:file path:[meta path]];
413                switch(result)
414                {
415                        case ImportStateUpdated:
416                                ret = YES;
417                                SapphireLog(SAPPHIRE_LOG_IMPORT, SAPPHIRE_LOG_LEVEL_DETAIL, @"Updated %@", [file path]);
418                                break;
419                        case ImportStateMultipleSuspend:
420                        case ImportStateBackground:
421                                [self itemImportBackgrounded];
422                                ret = NO;
423                                break;
424                }
425                if(ret)
426                        [SapphireMetaDataSupport save:moc];
427        }
428        @catch (NSException * e) {
429                [SapphireApplianceController logException:e];
430                [e raise];
431        }
432        @finally {
433                return ret;
434        }
435}
436
437/*!
438 * @brief Change the display to show the completion text
439 */
440- (void)setCompletionText
441{
442        [self setText:[importer completionText]];
443}
444
445- (void)realUpdateDisplay
446{
447        updateTimer = nil;
448        /*Check for completion*/
449        if(current == max)
450        {
451                [self setListTitle:BRLocalizedString(@"Import Complete", @"The import is complete")];
452                [self setFileProgress:[NSString stringWithFormat:BRLocalizedString(@"Updated %0.0f Entries.", @"Import complete format with number updated"), updated]];
453                [self realSetCurrentFile:@""];
454                [self setCompletionText];
455                [bar setPercentage:100.0f];
456                [self setButtonTitle:nil];
457                action = NULL;
458
459                [SapphireMetaDataSupport save:moc];
460               
461                NSManagedObject *obj;
462                NSEnumerator *objEnum = [allItems objectEnumerator];
463                while((obj = [objEnum nextObject]) != nil)
464                        [obj faultOjbectInContext:moc];
465                [allItems release];
466                allItems = nil;
467        }
468        else
469        {
470                if([importItems count])
471                {
472                        SapphireFileMetaData *fileMeta = [importItems objectAtIndex:0];
473                        NSString * fileName=[[fileMeta path] lastPathComponent] ;
474                        [self realSetCurrentFile:[NSString stringWithFormat:BRLocalizedString(@"Fetching For: %@", "Current TV Show import process format, filename"),fileName]];               
475                }
476                else
477                {
478                        [self realSetCurrentFile:[NSString stringWithFormat:BRLocalizedString(@"Waiting for background import to complete: %d URLs remaining to load", @"The import is complete, just waiting on background processes, parameter is number of URLs remaining to load"), pendingURLCount]];
479                }
480                [self setFileProgress:[NSString stringWithFormat:BRLocalizedString(@"Finished Processing: %0.0f / %0.0f", @"Import progress format, current and the max"), current, max,updated]];
481                [bar setPercentage:current/max * 100.0f];
482        }
483        [SapphireFrontRowCompat renderScene:[self scene]];             
484}
485
486- (void)updateDisplay
487{
488        if(updateTimer == nil)
489                updateTimer = [NSTimer scheduledTimerWithTimeInterval:updateFreq target:self selector:@selector(realUpdateDisplay) userInfo:nil repeats:NO];
490}
491
492/*!
493 * @brief Timer function to start the import of the next file
494 *
495 * @param timer The timer that triggered this
496 */
497- (void)importNextItem:(NSTimer *)timer
498{
499        @try
500        {
501                importTimer = nil;
502
503                if([importItems count])
504                {
505                        current++ ;
506                        /*Update the imported count*/
507                        if([self doImport] && !backgrounded)
508                                updated++;             
509                       
510                        /*Check for a background import*/
511                        if(backgrounded)
512                        {
513                                backgrounded = NO;
514                                current--;
515                        }
516                       
517                        /*Start with the first item*/
518                        [importItems removeObjectAtIndex:0];
519                        importTimer = [NSTimer scheduledTimerWithTimeInterval:0.0f target:self selector:@selector(importNextItem:) userInfo:nil repeats:NO];
520                }
521                /*Update the display*/
522                [self updateDisplay];
523        }
524        @catch(NSException *e)
525        {
526                [SapphireApplianceController logException:e];
527        }
528}
529
530/*!
531 * @brief Cancel the import process
532 */
533- (void)cancel
534{
535        /*Kill the timer*/
536        canceled = YES;
537        [importTimer invalidate];
538        importTimer = nil;
539        [importItems removeAllObjects];
540        [importer cancelImports];
541        [choosers removeAllObjects];
542        /*Reset the display and write data*/
543        [[SapphireApplianceController urlLoader] removeDelegate:self];
544        [self resetUIElements];
545        [SapphireMetaDataSupport save:moc];
546}
547
548- (void)itemImportBackgrounded
549{
550        backgrounded = YES;
551}
552
553- (void)skipNextItem
554{
555        /*Remove the next item from the queue*/
556        if([importItems count])
557                [importItems removeObjectAtIndex:0];
558        current++;
559}
560
561- (void)backgroundImporter:(id <SapphireImporter>)aImporter completedImportOnPath:(NSString *)path withState:(ImportState)state
562{
563        if(state == ImportStateUpdated)
564                updated++;
565
566        current++;
567        [self updateDisplay];
568}
569
570- (void)urlLoaderFinisedResource:(SapphireURLLoader *)loader
571{
572        pendingURLCount = [loader loadingURLCount];
573        [self updateDisplay];
574}
575
576- (void)urlLoaderCancelledResource:(SapphireURLLoader *)loader
577{
578        pendingURLCount = [loader loadingURLCount];
579        [self updateDisplay];
580}
581
582- (void)urlLoaderAddedResource:(SapphireURLLoader *)loader
583{
584        pendingURLCount = [loader loadingURLCount];
585        [self updateDisplay];
586}
587
588- (BOOL)canDisplayChooser
589{
590        return YES;
591}
592
593- (id)chooserScene
594{
595        return [self scene];
596}
597
598static SapphireImportChooserQueueItem *findNextChooser(NSMutableArray *choosers)
599{
600        SapphireImportChooserQueueItem *candidate = [choosers objectAtIndex:0];
601        while(![[candidate importer] stillNeedsDisplayOfChooser:[candidate chooser] withContext:[candidate context]])
602        {
603                [choosers removeObjectAtIndex:0];
604                if(![choosers count])
605                        return nil;
606                candidate = [choosers objectAtIndex:0];
607        }
608        return candidate;
609}
610
611- (void)displayNextChooser
612{
613        if(![choosers count])
614                return;
615       
616        SapphireImportChooserQueueItem *queueItem = findNextChooser(choosers);
617        if(queueItem)
618                [[self stack] pushController:[queueItem chooser]];
619}
620
621- (void)displayChooser:(BRLayerController <SapphireChooser> *)chooser forImporter:(id <SapphireImporter>)aImporter withContext:(id)context
622{
623        SapphireImportChooserQueueItem *queueItem = [[SapphireImportChooserQueueItem alloc] initWithChooser:chooser forImporter:aImporter withContext:context];
624        [choosers addObject:queueItem];
625        [queueItem release];
626        if([choosers count] == 1)
627                [[self stack] pushController:chooser];
628}
629
630/*!
631 * @brief Reset the UI after an import completion or cancel
632 */
633- (void)resetUIElements
634{
635        [self setFileProgress:@" "];
636        [self realSetCurrentFile:@" "] ;
637        [bar setPercentage:0.0f];
638        action = @selector(import);
639        [self setListTitle:[importer initialText]];
640        [self setText:[importer informativeText]];
641        [self setButtonTitle:[importer buttonTitle]];
642}
643
644- (void)doMyLayout
645{
646        if(!layoutDone)
647        {
648                [self layoutFrame];
649                [self resetUIElements];
650                layoutDone = YES;
651        }
652}
653
654- (void)wasPushed
655{
656        [self layoutFrame];
657        [self resetUIElements];
658        [super wasPushed];
659}
660
661- (void)wasPopped
662{
663        /*Someone hit menu, so cancel*/
664        [self cancel];
665        [super wasPopped];
666}
667
668- (void)wasExhumed
669{
670        SapphireImportChooserQueueItem *queueItem = [choosers objectAtIndex:0];
671        BRLayerController <SapphireChooser> *chooser = [queueItem chooser];
672        [[queueItem importer] exhumedChooser:chooser withContext:[queueItem context]];
673        [choosers removeObjectAtIndex:0];
674        if([choosers count])
675        {
676                if([chooser selection] == SapphireChooserChoiceCancel)
677                        [self performSelector:@selector(displayNextChooser) withObject:nil afterDelay:0.5];
678                else
679                        [self displayNextChooser];
680        }
681        [super wasExhumed];
682}
683
684- (BOOL)brEventAction:(BREvent *)event{
685        BREventRemoteAction remoteAction = [SapphireFrontRowCompat remoteActionForEvent:event];
686       
687        if([(BRControllerStack *)[self stack] peekController] != self || action == NULL)
688                remoteAction = 0;
689       
690        switch(remoteAction)
691        {
692                case kBREventRemoteActionPlay:
693                case kBREventRemoteActionPlayHold:
694                        [self performSelector:action];
695                        return YES;
696                        break;
697                case kBREventRemoteActionMenu:
698                        [self cancel];
699                        break;
700        }
701        return [super brEventAction:event];
702}
703
704- (long) itemCount
705{
706        return 1;
707}
708
709- (id<BRMenuItemLayer>) itemForRow: (long) row
710{
711        BRAdornedMenuItemLayer *result = [SapphireFrontRowCompat textMenuItemForScene:[self scene] folder:NO];
712        [SapphireFrontRowCompat setTitle:buttonTitle forMenu:result];
713       
714        return result;
715}
716
717- (NSString *) titleForRow: (long) row
718{
719       
720        if ( row >= 1 ) return ( nil );
721       
722        NSString *result = buttonTitle ;
723       
724        return [NSString stringWithFormat:@"  ????? %@", result];
725}
726
727- (long) rowForTitle: (NSString *) aTitle
728{
729    long result = -1;
730    long i, count = [self itemCount];
731    for ( i = 0; i < count; i++ )
732    {
733        if ( [aTitle isEqualToString: [self titleForRow: i]] )
734        {
735            result = i;
736            break;
737        }
738    }
739   
740    return ( result );
741}
742
743- (NSRect)listRectWithSize:(NSRect)listFrame inMaster:(NSRect)master
744{
745        listFrame.size.height = master.size.height * 3.0f / 16.0f;
746        listFrame.origin.y = master.size.height / 8.0f;
747        listFrame.size.width = master.size.width / 3.0f;
748        listFrame.origin.x = master.size.width / 3.0f;
749        return listFrame;
750}
751
752@end
Note: See TracBrowser for help on using the repository browser.