Index: libhb/hb.c =================================================================== --- libhb/hb.c (revision 1043) +++ libhb/hb.c (working copy) @@ -692,11 +692,14 @@ } /** - * Adds a job to the job list. + * Allocates and returns a copy of the job. The copy will have a reference count of 1. + * The caller is responsible for calling hb_job_dereference when the copy is no longer + * needed. * @param h Handle to hb_handle_t. * @param job Handle to hb_job_t. + * @returns Handle to hb_job_t of the copy. */ -void hb_add( hb_handle_t * h, hb_job_t * job ) +hb_job_t * hb_job_copy( hb_job_t * job ) { hb_job_t * job_copy; hb_title_t * title, * title_copy; @@ -903,8 +906,8 @@ title_copy->job = job_copy; job_copy->title = title_copy; job_copy->file = strdup( job->file ); - job_copy->h = h; - job_copy->pause = h->pause_lock; + job_copy->h = NULL; + job_copy->pause = NULL; /* Copy the job filter list */ if( job->filters ) @@ -919,10 +922,32 @@ } } + job_copy->reference_count = 1; + + return( job_copy ); +} + +/** + * Adds a copy of a job to the job list. + * @param h Handle to hb_handle_t. + * @param job Handle to hb_job_t. + * @returns Handle to hb_job_t of the copied job. + */ +hb_job_t * hb_add( hb_handle_t * h, hb_job_t * job ) +{ + hb_job_t * job_copy; + + job_copy = hb_job_copy ( job ); + + job_copy->h = h; + job_copy->pause = h->pause_lock; + /* Add the job to the list */ hb_list_add( h->jobs, job_copy ); h->job_count = hb_count(h); h->job_count_permanent++; + + return( job_copy ); } /** @@ -939,6 +964,7 @@ h->job_count_permanent--; /* XXX free everything XXX */ + hb_job_dereference( job ); } /** @@ -1207,3 +1233,31 @@ hb_unlock( h->state_lock ); hb_unlock( h->pause_lock ); } + +/* + * Register an interest in referencing this job, it won't be deleted + * until this party deregisters its interest in it. + */ +void hb_job_reference( const hb_job_t * job_copy ) +{ + hb_job_t * job = (hb_job_t * )job_copy; + + job->reference_count++; +} + +void hb_job_dereference( const hb_job_t * job_copy ) +{ + hb_job_t * job = (hb_job_t * )job_copy; + + job->reference_count--; + + if( job->reference_count <= 0 ) + { + hb_log( "Freeing job 0x%x", job ); + +// travistex has added this call to hb_title_close because it was removed from do_job. + hb_title_close( &job->title ); + + free( job ); + } +} Index: libhb/hb.h =================================================================== --- libhb/hb.h (revision 1043) +++ libhb/hb.h (working copy) @@ -84,7 +84,8 @@ int hb_count( hb_handle_t * ); hb_job_t * hb_job( hb_handle_t *, int ); hb_job_t * hb_current_job( hb_handle_t * h ); -void hb_add( hb_handle_t *, hb_job_t * ); +hb_job_t * hb_job_copy( hb_job_t * job ); +hb_job_t * hb_add( hb_handle_t *, hb_job_t * ); void hb_rem( hb_handle_t *, hb_job_t * ); void hb_start( hb_handle_t * ); @@ -105,6 +106,9 @@ Aborts all current jobs if any, frees memory. */ void hb_close( hb_handle_t ** ); +void hb_job_reference( const hb_job_t * job_copy ); +void hb_job_dereference( const hb_job_t * job_copy ); + #ifdef __cplusplus } #endif Index: libhb/work.c =================================================================== --- libhb/work.c (revision 1043) +++ libhb/work.c (working copy) @@ -702,8 +702,11 @@ hb_buffer_pool_free(); - hb_title_close( &job->title ); - free( job ); +// travistex is requesting that all code to dealloc a job be moved to hb_job_dereference +// so that all data in the job can continued to be referenced until the job is really +// deallocated. I don't know if any of the other code above falls in this same category. +// hb_title_close( &job->title ); + hb_job_dereference( job ); } /** Index: libhb/common.h =================================================================== --- libhb/common.h (revision 1043) +++ libhb/common.h (working copy) @@ -105,6 +105,12 @@ *****************************************************************************/ struct hb_job_s { + /* + * Number of folks interested in this job, only free when equal to + * 0. + */ + int reference_count; + /* ID assigned by UI so it can groups job passes together */ int sequence_id; Index: macosx/HBQueueController.h =================================================================== --- macosx/HBQueueController.h (revision 1043) +++ macosx/HBQueueController.h (working copy) @@ -9,30 +9,25 @@ #include "hb.h" @class HBController; +@class HBJobGroup; -#define HB_QUEUE_DRAGGING 0 // <--- NOT COMPLETELY FUNCTIONAL YET +#define HB_QUEUE_DRAGGING 1 // <--- NOT COMPLETELY FUNCTIONAL YET #define HB_OUTLINE_METRIC_CONTROLS 1 // for tweaking the outline cell spacings -typedef enum _HBQueueJobGroupStatus -{ - HBStatusNone = 0, - HBStatusPending = 1, - HBStatusWorking = 2, - HBStatusComplete = 3, - HBStatusCanceled = 4 -} HBQueueJobGroupStatus; - //------------------------------------------------------------------------------------ -// As usual, we need to subclass NSOutlineView to handle a few special cases: +// HBQueueOutlineView +//------------------------------------------------------------------------------------ +// We need to subclass NSOutlineView to handle a few special cases: // -// (1) variable row heights during live resizes -// HBQueueOutlineView exists solely to get around a bug in variable row height outline -// views in which row heights get messed up during live resizes. See this discussion: +// (1) Variable row heights during live resizes +// There's a bug/issue in Tiger and Leopard where variable row height outline view row +// heights get messed up (or not recalculated) during live resizes. See this +// discussion: // http://lists.apple.com/archives/cocoa-dev/2005/Oct/msg00871.html // However, the recommeneded fix (override drawRect:) does not work. Instead, this // subclass implements viewDidEndLiveResize in order to recalculate all row heights. // -// (2) prevent expanding of items during drags +// (2) Prevent expanding of items during drags // During dragging operations, we don't want outline items to expand, since a queue // doesn't really have children items. // @@ -43,7 +38,7 @@ @interface HBQueueOutlineView : NSOutlineView { #if HB_QUEUE_DRAGGING -BOOL fIsDragging; + BOOL fIsDragging; #endif } #if HB_QUEUE_DRAGGING @@ -52,85 +47,23 @@ @end //------------------------------------------------------------------------------------ - -@interface HBJob : NSObject -{ - hb_job_t *hbJob; -} - -+ (HBJob*) jobWithJob: (hb_job_t *) job; -- (id) initWithJob: (hb_job_t *) job; -- (hb_job_t *) job; -- (NSMutableAttributedString *) attributedDescriptionWithHBHandle: (hb_handle_t *)handle - withIcon: (BOOL)withIcon - withTitle: (BOOL)withTitle - withPassName: (BOOL)withPassName - withFormatInfo: (BOOL)withFormatInfo - withDestination: (BOOL)withDestination - withPictureInfo: (BOOL)withPictureInfo - withVideoInfo: (BOOL)withVideoInfo - withx264Info: (BOOL)withx264Info - withAudioInfo: (BOOL)withAudioInfo - withSubtitleInfo: (BOOL)withSubtitleInfo; - -@end - +// HBQueueController //------------------------------------------------------------------------------------ -@interface HBJobGroup : NSObject -{ - NSMutableArray *fJobs; // array of HBJob - NSMutableAttributedString *fDescription; - BOOL fNeedsDescription; - float fLastDescriptionHeight; - float fLastDescriptionWidth; - HBQueueJobGroupStatus fStatus; - NSString *fPath; -} - -// Creating a job group -+ (HBJobGroup *) jobGroup; - -// Adding jobs -- (void) addJob: (HBJob *)aJob; - -// Removing jobs -- (void) removeAllJobs; - -// Querying a job group -- (unsigned int) count; -- (HBJob *) jobAtIndex: (unsigned)index; -- (unsigned) indexOfJob: (HBJob *)aJob; -- (NSEnumerator *) jobEnumerator; -- (void) setStatus: (HBQueueJobGroupStatus)status; -- (HBQueueJobGroupStatus) status; -- (void) setPath: (NSString *)path; -- (NSString *) path; - -// Creating a description -- (void) setNeedsDescription: (BOOL)flag; -- (NSMutableAttributedString *) attributedDescriptionWithHBHandle: (hb_handle_t *)handle; -- (float) heightOfDescriptionForWidth:(float)width withHBHandle: (hb_handle_t *)handle; -- (float) lastDescriptionHeight; - -@end - -//------------------------------------------------------------------------------------ - @interface HBQueueController : NSObject { hb_handle_t *fHandle; // reference to hblib HBController *fHBController; // reference to HBController - NSMutableArray *fJobGroups; // hblib's job list organized in a hierarchy of HBJobGroup and HBJob - HBJobGroup *fCurrentJobGroup; // the HJobGroup current being processed by hblib + NSMutableArray *fJobGroups; // List of all job groups tracked by the controller. Ordered the way they are to be presented in the UI. + HBJobGroup *fCurrentJobGroup; // HBJobGroup current being processed by hblib. This also appear in fJobGroups. + BOOL fStopQueueRequested; // YES when hb_stop has been called. Set back to NO once libhb has performed the stop. BOOL fCurrentJobPaneShown; // NO when fCurrentJobPane has been shifted out of view (see showCurrentJobPane) - hb_job_t *fLastKnownCurrentJob; // this is how we track when hbib has started processing a different job - NSMutableIndexSet *fSavedExpandedItems; // used by save/restoreOutlineViewState to preserve which items are expanded - NSMutableIndexSet *fSavedSelectedItems; // used by save/restoreOutlineViewState to preserve which items are selected + hb_job_t *fLastKnownLibHBJob; // this is how we track when hbib has started processing a different job + NSMutableArray *fSavedExpandedItems; // used by save/restoreOutlineViewState to preserve which items are expanded + NSArray *fSavedSelectedItems; // used by save/restoreOutlineViewState to preserve which items are selected #if HB_QUEUE_DRAGGING NSArray *fDraggedNodes; #endif - NSMutableArray *fCompleted; // HBJobGroups that have been completed. These also appear in fJobGroups. NSTimer *fAnimationTimer; // animates the icon of the current job in the queue outline view int fAnimationIndex; // used to generate name of image used to animate the current job in the queue outline view @@ -173,16 +106,26 @@ - (void)setHandle: (hb_handle_t *)handle; - (void)setHBController: (HBController *)controller; -- (void)hblibJobListChanged; -- (void)hblibStateChanged: (hb_state_t &)state; -- (void)hblibWillStop; +- (void)libhbStateChanged: (hb_state_t &)state; +- (void) addJobGroup: (HBJobGroup *) aJobGroup; +- (void) removeJobGroup: (HBJobGroup *) aJobGroup; + +- (unsigned int) pendingCount; +- (unsigned int) completedCount; +- (unsigned int) canceledCount; +- (unsigned int) workingCount; + +- (IBAction) startQueue: (id)sender; +- (IBAction) stopQueue: (id)sender; +- (void) stopQueue; +- (IBAction) pauseQueue: (id)sender; +- (IBAction) resumeQueue: (id)sender; + - (IBAction)showQueueWindow: (id)sender; - (IBAction)removeSelectedJobGroups: (id)sender; - (IBAction)revealSelectedJobGroups: (id)sender; - (IBAction)cancelCurrentJob: (id)sender; -- (IBAction)toggleStartCancel: (id)sender; -- (IBAction)togglePauseResume: (id)sender; #if HB_OUTLINE_METRIC_CONTROLS - (IBAction)imageSpacingChanged: (id)sender; Index: macosx/Controller.mm =================================================================== --- macosx/Controller.mm (revision 1043) +++ macosx/Controller.mm (working copy) @@ -8,6 +8,8 @@ #include "a52dec/a52.h" #import "HBOutputPanelController.h" #import "HBPreferencesController.h" +#import "HBJobGroup.h" +#import "HBJob.h" /* Added to integrate scanning into HBController */ #include #include @@ -156,7 +158,7 @@ if (result == NSAlertDefaultReturn) { - [self doCancelCurrentJob]; + [fQueueController stopQueue]; return NSTerminateNow; } else @@ -584,7 +586,7 @@ // Has current job changed? That means the queue has probably changed as // well so update it - [fQueueController hblibStateChanged: s]; + [fQueueController libhbStateChanged: s]; break; } @@ -608,7 +610,7 @@ [self UpdateDockIcon: 1.0]; // Pass along the info to HBQueueController - [fQueueController hblibStateChanged: s]; + [fQueueController libhbStateChanged: s]; break; } @@ -618,122 +620,40 @@ [fStatusField setStringValue: _( @"Paused" )]; // Pass along the info to HBQueueController - [fQueueController hblibStateChanged: s]; + [fQueueController libhbStateChanged: s]; break; case HB_STATE_WORKDONE: { - // HB_STATE_WORKDONE happpens as a result of hblib finishing all its jobs - // or someone calling hb_stop. In the latter case, hb_stop does not clear - // out the remaining passes/jobs in the queue. We'll do that here. - - // Delete all remaining scans of this job, ie, delete whole encodes. - hb_job_t * job; - while( ( job = hb_job( fHandle, 0 ) ) && (job->sequence_id != 0) ) - hb_rem( fHandle, job ); + // Pass along the info to HBQueueController. It may pass another job into + // libhb. + [fQueueController libhbStateChanged: s]; - // Start processing back up if jobs still left in queue - if (hb_count(fHandle) > 0) + // If HBQueueController didn't start anything else up, then we are really done. + hb_get_state2( fHandle, &s ); + if ((s.state == HB_STATE_WORKDONE) || (s.state == HB_STATE_IDLE)) { - hb_start(fHandle); - fEncodeState = 1; - // Validate the toolbar (hack). The toolbar will usually get autovalidated - // before we had the chance to restart the queue, hence it will now be in - // the wrong state. + [fStatusField setStringValue: _( @"Done." )]; + [fRipIndicator setIndeterminate: NO]; + [fRipIndicator setDoubleValue: 0.0]; [toolbar validateVisibleItems]; - break; - } - [fStatusField setStringValue: _( @"Done." )]; - [fRipIndicator setIndeterminate: NO]; - [fRipIndicator setDoubleValue: 0.0]; - [toolbar validateVisibleItems]; + // Restore dock icon + [self UpdateDockIcon: -1.0]; - /* Restore dock icon */ - [self UpdateDockIcon: -1.0]; - - if (fRipIndicatorShown) - { - NSRect frame = [fWindow frame]; - if (frame.size.width <= 591) - frame.size.width = 591; - frame.size.height += -36; - frame.origin.y -= -36; - [fWindow setFrame:frame display:YES animate:YES]; - fRipIndicatorShown = NO; - } - - // Pass along the info to HBQueueController - [fQueueController hblibStateChanged: s]; - - /* Check to see if the encode state has not been cancelled - to determine if we should check for encode done notifications */ - if (fEncodeState != 2) { - /* If Growl Notification or Window and Growl has been selected */ - if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Growl Notification"] || - [[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Alert Window And Growl"]) + if (fRipIndicatorShown) { - /*Growl Notification*/ - [self showGrowlDoneNotification: NULL]; + NSRect frame = [fWindow frame]; + if (frame.size.width <= 591) + frame.size.width = 591; + frame.size.height += -36; + frame.origin.y -= -36; + [fWindow setFrame:frame display:YES animate:YES]; + fRipIndicatorShown = NO; } - /* If Alert Window or Window and Growl has been selected */ - if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Alert Window"] || - [[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Alert Window And Growl"]) - { - /*On Screen Notification*/ - int status; - NSBeep(); - status = NSRunAlertPanel(@"Put down that cocktail...",@"Your HandBrake encode is done!", @"OK", nil, nil); - [NSApp requestUserAttention:NSCriticalRequest]; - if ( status == NSAlertDefaultReturn ) - { - [self enableUI: YES]; - } - } - else - { - [self enableUI: YES]; - } - /* If sleep has been selected */ - if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Put Computer To Sleep"]) - { - /* Sleep */ - NSDictionary* errorDict; - NSAppleEventDescriptor* returnDescriptor = NULL; - NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource: - @"tell application \"Finder\" to sleep"]; - returnDescriptor = [scriptObject executeAndReturnError: &errorDict]; - [scriptObject release]; - [self enableUI: YES]; - } - /* If Shutdown has been selected */ - if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Shut Down Computer"]) - { - /* Shut Down */ - NSDictionary* errorDict; - NSAppleEventDescriptor* returnDescriptor = NULL; - NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource: - @"tell application \"Finder\" to shut down"]; - returnDescriptor = [scriptObject executeAndReturnError: &errorDict]; - [scriptObject release]; - [self enableUI: YES]; - } - - // MetaX insertion via AppleScript - if([[NSUserDefaults standardUserDefaults] boolForKey: @"sendToMetaX"] == YES) - { - NSAppleScript *myScript = [[NSAppleScript alloc] initWithSource: [NSString stringWithFormat: @"%@%@%@", @"tell application \"MetaX\" to open (POSIX file \"", [fDstFile2Field stringValue], @"\")"]]; - [myScript executeAndReturnError: nil]; - [myScript release]; - } - - - } - else - { - [self enableUI: YES]; - } + + } break; } } @@ -917,7 +837,7 @@ if ([ident isEqualToString: StartEncodingIdentifier]) { [toolbarItem setImage: [NSImage imageNamed: @"Play"]]; - if (hb_count(fHandle) > 0) + if ([fQueueController pendingCount] > 0) [toolbarItem setLabel: @"Start Queue"]; else [toolbarItem setLabel: @"Start"]; @@ -1018,9 +938,9 @@ -(IBAction)showGrowlDoneNotification:(id)sender { - [GrowlApplicationBridge - notifyWithTitle:@"Put down that cocktail..." - description:@"your HandBrake encode is done!" + [GrowlApplicationBridge + notifyWithTitle:@"HandBrake has finished its encoding tasks." + description:@"More information is available in the Queue window." notificationName:SERVICE_NAME iconData:nil priority:0 @@ -1028,6 +948,7 @@ clickContext:nil]; } + #pragma mark - #pragma mark Get New Source @@ -1257,7 +1178,6 @@ hb_title_t * title = (hb_title_t *) hb_list_item( list, [fSrcTitlePopUp indexOfSelectedItem] ); hb_job_t * job = title->job; - //int i; /* Chapter selection */ job->chapter_start = [fSrcChapterStartPopUp indexOfSelectedItem] + 1; @@ -1458,55 +1378,17 @@ } - - -/* addToQueue: puts up an alert before ultimately calling doAddToQueue -*/ -- (IBAction) addToQueue: (id) sender -{ - /* We get the destination directory from the destination field here */ - NSString *destinationDirectory = [[fDstFile2Field stringValue] stringByDeletingLastPathComponent]; - /* We check for a valid destination here */ - if ([[NSFileManager defaultManager] fileExistsAtPath:destinationDirectory] == 0) - { - NSRunAlertPanel(@"Warning!", @"This is not a valid destination directory!", @"OK", nil, nil); - return; - } - - /* We check for duplicate name here */ - if( [[NSFileManager defaultManager] fileExistsAtPath: - [fDstFile2Field stringValue]] ) - { - NSBeginCriticalAlertSheet( _( @"File already exists" ), - _( @"Cancel" ), _( @"Overwrite" ), NULL, fWindow, self, - @selector( overwriteAddToQueueAlertDone:returnCode:contextInfo: ), - NULL, NULL, [NSString stringWithFormat: - _( @"Do you want to overwrite %@?" ), - [fDstFile2Field stringValue]] ); - // overwriteAddToQueueAlertDone: will be called when the alert is dismissed. - } - else - { - [self doAddToQueue]; - } -} - -/* overwriteAddToQueueAlertDone: called from the alert posted by addToQueue that asks - the user if they want to overwrite an exiting movie file. -*/ -- (void) overwriteAddToQueueAlertDone: (NSWindow *) sheet - returnCode: (int) returnCode contextInfo: (void *) contextInfo -{ - if( returnCode == NSAlertAlternateReturn ) - [self doAddToQueue]; -} - - (void) doAddToQueue { hb_list_t * list = hb_get_titles( fHandle ); hb_title_t * title = (hb_title_t *) hb_list_item( list, [fSrcTitlePopUp indexOfSelectedItem] ); hb_job_t * job = title->job; + // Create a job group for adding the individual job passes. The job group will + // then be added to the queue. + HBJobGroup * jobGroup = [HBJobGroup jobGroup]; + hb_job_t * job_copy; + // Assign a sequence number, starting at zero, to each job added so they can // be lumped together in the UI. job->sequence_id = -1; @@ -1547,8 +1429,11 @@ * Add the pre-scan job */ job->sequence_id++; // for job grouping - hb_add( fHandle, job ); - + + job_copy = hb_job_copy(job); // make a copy for jobWithLibhbJob to use + [jobGroup addJob:[HBJob jobWithLibhbJob:job_copy]]; // add it to the queue + hb_job_dereference (job_copy); // delete our reference + job->x264opts = x264opts_tmp; } else @@ -1572,8 +1457,11 @@ job->pass = 1; job->sequence_id++; // for job grouping - hb_add( fHandle, job ); + job_copy = hb_job_copy(job); // make a copy for jobWithLibhbJob to use + [jobGroup addJob:[HBJob jobWithLibhbJob:job_copy]]; // add it to the queue + hb_job_dereference (job_copy); // delete our reference + job->pass = 2; job->sequence_id++; // for job grouping @@ -1582,42 +1470,91 @@ job->select_subtitle = subtitle_tmp; - hb_add( fHandle, job ); + job_copy = hb_job_copy(job); // make a copy for jobWithLibhbJob to use + [jobGroup addJob:[HBJob jobWithLibhbJob:job_copy]]; // add it to the queue + hb_job_dereference (job_copy); // delete our reference } else { job->indepth_scan = 0; job->pass = 0; job->sequence_id++; // for job grouping - hb_add( fHandle, job ); + + job_copy = hb_job_copy(job); // make a copy for jobWithLibhbJob to use + [jobGroup addJob:[HBJob jobWithLibhbJob:job_copy]]; // add it to the queue + hb_job_dereference (job_copy); // delete our reference } NSString *destinationDirectory = [[fDstFile2Field stringValue] stringByDeletingLastPathComponent]; [[NSUserDefaults standardUserDefaults] setObject:destinationDirectory forKey:@"LastDestinationDirectory"]; - // Notify the queue - [fQueueController hblibJobListChanged]; + // Add job group to the queue + [fQueueController addJobGroup:jobGroup]; } -/* Rip: puts up an alert before ultimately calling doRip + +/* addToQueue: puts up an alert before ultimately calling doAddToQueue */ +- (IBAction) addToQueue: (id) sender +{ + /* We get the destination directory from the destination field here */ + NSString *destinationDirectory = [[fDstFile2Field stringValue] stringByDeletingLastPathComponent]; + /* We check for a valid destination here */ + if ([[NSFileManager defaultManager] fileExistsAtPath:destinationDirectory] == 0) + { + NSRunAlertPanel(@"Warning!", @"This is not a valid destination directory!", @"OK", nil, nil); + return; + } + + /* We check for duplicate name here */ + if( [[NSFileManager defaultManager] fileExistsAtPath: + [fDstFile2Field stringValue]] ) + { + NSBeginCriticalAlertSheet( _( @"File already exists" ), + _( @"Cancel" ), _( @"Overwrite" ), NULL, fWindow, self, + @selector( overwriteAddToQueueAlertDone:returnCode:contextInfo: ), + NULL, NULL, [NSString stringWithFormat: + _( @"Do you want to overwrite %@?" ), + [fDstFile2Field stringValue]] ); + // overwriteAddToQueueAlertDone: will be called when the alert is dismissed. + } + else + { + [self doAddToQueue]; + } +} + +/* overwriteAddToQueueAlertDone: called from the alert posted by addToQueue that asks + the user if they want to overwrite an exiting movie file. +*/ +- (void) overwriteAddToQueueAlertDone: (NSWindow *) sheet + returnCode: (int) returnCode contextInfo: (void *) contextInfo +{ + if( returnCode == NSAlertAlternateReturn ) + [self doAddToQueue]; +} + +/* Rip: puts up an alert before ultimately calling HBQueueController startQueue:. +*/ - (IBAction) Rip: (id) sender { /* Rip or Cancel ? */ hb_state_t s; hb_get_state2( fHandle, &s ); - if(s.state == HB_STATE_WORKING || s.state == HB_STATE_PAUSED) + // If processing jobs, then stop the queue + + if(s.state == HB_STATE_WORKING || s.state == HB_STATE_MUXING || s.state == HB_STATE_PAUSED) { - [self Cancel: sender]; + [fQueueController stopQueue: fWindow]; return; } - // If there are jobs in the queue, then this is a rip the queue + // If there are jobs in the queue, then start the queue - if (hb_count( fHandle ) > 0) + if ([fQueueController pendingCount] > 0) { - [self doRip]; + [fQueueController startQueue: sender]; return; } @@ -1640,20 +1577,20 @@ _( @"Do you want to overwrite %@?" ), [fDstFile2Field stringValue]] ); - // overWriteAlertDone: will be called when the alert is dismissed. It will call doRip. + // overWriteAlertDone: will be called when the alert is dismissed. It will call startQueue. } else { /* if there are no jobs in the queue, then add this one to the queue and rip otherwise, just rip the queue */ - if( hb_count( fHandle ) == 0) + if( [fQueueController pendingCount] == 0) { [self doAddToQueue]; } NSString *destinationDirectory = [[fDstFile2Field stringValue] stringByDeletingLastPathComponent]; [[NSUserDefaults standardUserDefaults] setObject:destinationDirectory forKey:@"LastDestinationDirectory"]; - [self doRip]; + [fQueueController startQueue: sender]; } } @@ -1667,155 +1604,117 @@ { /* if there are no jobs in the queue, then add this one to the queue and rip otherwise, just rip the queue */ - if( hb_count( fHandle ) == 0 ) + if( [fQueueController pendingCount] == 0 ) { [self doAddToQueue]; } NSString *destinationDirectory = [[fDstFile2Field stringValue] stringByDeletingLastPathComponent]; [[NSUserDefaults standardUserDefaults] setObject:destinationDirectory forKey:@"LastDestinationDirectory"]; - [self doRip]; + [fQueueController startQueue: nil]; } } -- (void) remindUserOfSleepOrShutdown +//------------------------------------------------------------------------------------ +// Calls HBQueueController stopQueue: to request stopping of the queue. Cancel: +// returns immediately after making the request. +//------------------------------------------------------------------------------------ +- (IBAction)Cancel: (id)sender { - if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Put Computer To Sleep"]) - { - /*Warn that computer will sleep after encoding*/ - int reminduser; - NSBeep(); - reminduser = NSRunAlertPanel(@"The computer will sleep after encoding is done.",@"You have selected to sleep the computer after encoding. To turn off sleeping, go to the HandBrake preferences.", @"OK", @"Preferences...", nil); - [NSApp requestUserAttention:NSCriticalRequest]; - if ( reminduser == NSAlertAlternateReturn ) - { - [self showPreferencesWindow:NULL]; - } - } - else if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Shut Down Computer"]) - { - /*Warn that computer will shut down after encoding*/ - int reminduser; - NSBeep(); - reminduser = NSRunAlertPanel(@"The computer will shut down after encoding is done.",@"You have selected to shut down the computer after encoding. To turn off shut down, go to the HandBrake preferences.", @"OK", @"Preferences...", nil); - [NSApp requestUserAttention:NSCriticalRequest]; - if ( reminduser == NSAlertAlternateReturn ) - { - [self showPreferencesWindow:NULL]; - } - } - + [fQueueController stopQueue: fWindow]; } - -- (void) doRip -{ - /* Let libhb do the job */ - hb_start( fHandle ); - /*set the fEncodeState State */ - fEncodeState = 1; -} - - - - //------------------------------------------------------------------------------------ -// Removes all jobs from the queue. Does not cancel the current processing job. +// Calls HBQueueController pauseQueue: or resumeQueue: depending on the current state +// of libhb. //------------------------------------------------------------------------------------ -- (void) doDeleteQueuedJobs +- (IBAction) Pause: (id) sender { - hb_job_t * job; - while( ( job = hb_job( fHandle, 0 ) ) ) - hb_rem( fHandle, job ); + hb_state_t s; + hb_get_state2( fHandle, &s ); + + if( s.state == HB_STATE_PAUSED ) + [fQueueController resumeQueue:sender]; + else + [fQueueController pauseQueue:sender]; } //------------------------------------------------------------------------------------ -// Cancels the current job and proceeds with the next one in the queue. +// Displays alert reminder user that their computer will be put to sleep or shutdown +// after the current set of encodes completes. //------------------------------------------------------------------------------------ -- (void) doCancelCurrentJob +- (void) remindUserOfSleepOrShutdown { - // Stop the current job. hb_stop will only cancel the current pass and then set - // its state to HB_STATE_WORKDONE. It also does this asynchronously. So when we - // see the state has changed to HB_STATE_WORKDONE (in updateUI), we'll delete the - // remaining passes of the job and then start the queue back up if there are any - // remaining jobs. - - [fQueueController hblibWillStop]; - hb_stop( fHandle ); - fEncodeState = 2; // don't alert at end of processing since this was a cancel - + if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Put Computer To Sleep"]) + { + /*Warn that computer will sleep after encoding*/ + int reminduser; + NSBeep(); + reminduser = NSRunAlertPanel(@"Your computer will sleep after encoding has completed.", + @"You have selected to sleep the computer after encoding. To turn off sleeping, use HandBrake preferences.", + nil, @"Preferences...", nil); + if ( reminduser == NSAlertAlternateReturn ) + [self showPreferencesWindow:NULL]; + } + else if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Shut Down Computer"]) + { + /*Warn that computer will shut down after encoding*/ + int reminduser; + NSBeep(); + reminduser = NSRunAlertPanel(@"Your computer will shut down after encoding has completed.", + @"You have selected to shut down the computer after encoding. To turn off shut down, use HandBrake preferences.", + nil, @"Preferences...", nil); + if ( reminduser == NSAlertAlternateReturn ) + [self showPreferencesWindow:NULL]; + } } //------------------------------------------------------------------------------------ -// Displays an alert asking user if the want to cancel encoding of current job. -// Cancel: returns immediately after posting the alert. Later, when the user -// acknowledges the alert, doCancelCurrentJob is called. +// Performs actions required after encodes have been completed. //------------------------------------------------------------------------------------ -- (IBAction)Cancel: (id)sender +- (void) performPostProcessingTasks { - if (!fHandle) return; + // If Growl Notification or Window and Growl has been selected + if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Growl Notification"] || + [[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Alert Window And Growl"]) + { + [self showGrowlDoneNotification: NULL]; + } - hb_job_t * job = hb_current_job(fHandle); - if (!job) return; - - NSString * alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Do you want to stop encoding of %@?", nil), - [NSString stringWithUTF8String:job->title->name]]; + // If Alert Window or Window and Growl has been selected + if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Alert Window"] || + [[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Alert Window And Growl"]) + { + NSBeep(); + NSRunAlertPanel(@"HandBrake has finished its encoding tasks.", @"More information is available in the Queue window.", nil, nil, nil); + } - // Which window to attach the sheet to? - NSWindow * docWindow; - if ([sender respondsToSelector: @selector(window)]) - docWindow = [sender window]; - else - docWindow = fWindow; - - NSBeginCriticalAlertSheet( - alertTitle, - NSLocalizedString(@"Keep Encoding", nil), - NSLocalizedString(@"Delete All", nil), - NSLocalizedString(@"Stop Encoding", nil), - docWindow, self, - nil, @selector(didDimissCancelCurrentJob:returnCode:contextInfo:), nil, - NSLocalizedString(@"Your movie will be lost if you don't continue encoding.", nil), - [NSString stringWithUTF8String:job->title->name]); + // If sleep has been selected + if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Put Computer To Sleep"]) + { + NSDictionary* errorDict; + NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource: @"tell application \"Finder\" to sleep"]; + [scriptObject executeAndReturnError: &errorDict]; + [scriptObject release]; + } - // didDimissCancelCurrentJob:returnCode:contextInfo: will be called when the dialog is dismissed - - // N.B.: didDimissCancelCurrentJob:returnCode:contextInfo: is designated as the dismiss - // selector to prevent a crash. As a dismiss selector, the alert window will - // have already be dismissed. If we don't do it this way, the dismissing of - // the alert window will cause the table view to be redrawn at a point where - // current job has been deleted by hblib but we don't know about it yet. This - // is a prime example of wy we need to NOT be relying on hb_current_job!!!! -} - -- (void) didDimissCancelCurrentJob: (NSWindow *)sheet returnCode: (int)returnCode contextInfo: (void *)contextInfo -{ - if (returnCode == NSAlertOtherReturn) - [self doCancelCurrentJob]; - else if (returnCode == NSAlertAlternateReturn) - { - [self doDeleteQueuedJobs]; - [self doCancelCurrentJob]; + // If Shutdown has been selected + if ([[[NSUserDefaults standardUserDefaults] stringForKey:@"AlertWhenDone"] isEqualToString: @"Shut Down Computer"]) + { + NSDictionary* errorDict; + NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource: @"tell application \"Finder\" to shut down"]; + [scriptObject executeAndReturnError: &errorDict]; + [scriptObject release]; } -} - - - - -- (IBAction) Pause: (id) sender -{ - hb_state_t s; - hb_get_state2( fHandle, &s ); - - if( s.state == HB_STATE_PAUSED ) + // MetaX insertion via AppleScript + if([[NSUserDefaults standardUserDefaults] boolForKey: @"sendToMetaX"] == YES) { - hb_resume( fHandle ); + NSAppleScript *myScript = [[NSAppleScript alloc] initWithSource: [NSString stringWithFormat: @"%@%@%@", @"tell application \"MetaX\" to open (POSIX file \"", [fDstFile2Field stringValue], @"\")"]]; + [myScript executeAndReturnError: nil]; + [myScript release]; } - else - { - hb_pause( fHandle ); - } + } #pragma mark - Index: macosx/HandBrake.xcodeproj/project.pbxproj =================================================================== --- macosx/HandBrake.xcodeproj/project.pbxproj (revision 1043) +++ macosx/HandBrake.xcodeproj/project.pbxproj (working copy) @@ -145,6 +145,10 @@ E37167A90C92FAA50072B384 /* JobPassUnknownSmall.png in Resources */ = {isa = PBXBuildFile; fileRef = E37167A70C92FAA50072B384 /* JobPassUnknownSmall.png */; }; E37172670C977D340072B384 /* HBImageAndTextCell.m in Sources */ = {isa = PBXBuildFile; fileRef = E37172650C977D340072B384 /* HBImageAndTextCell.m */; }; E37172680C977D340072B384 /* HBImageAndTextCell.h in Headers */ = {isa = PBXBuildFile; fileRef = E37172660C977D340072B384 /* HBImageAndTextCell.h */; }; + E3724BEA0CD2280800010454 /* HBJob.h in Headers */ = {isa = PBXBuildFile; fileRef = E3724BE90CD2280800010454 /* HBJob.h */; }; + E3724BF90CD22A8400010454 /* HBJob.m in Sources */ = {isa = PBXBuildFile; fileRef = E3724BF80CD22A8400010454 /* HBJob.m */; }; + E37274450CD4113000CFE13E /* HBJobGroup.h in Headers */ = {isa = PBXBuildFile; fileRef = E37274440CD4113000CFE13E /* HBJobGroup.h */; }; + E372744D0CD412E100CFE13E /* HBJobGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = E372744C0CD412E100CFE13E /* HBJobGroup.m */; }; E37C89410C83988F00C1B919 /* DeleteHighlight.png in Resources */ = {isa = PBXBuildFile; fileRef = E37C893D0C83988F00C1B919 /* DeleteHighlight.png */; }; E37C89420C83988F00C1B919 /* Delete.png in Resources */ = {isa = PBXBuildFile; fileRef = E37C893E0C83988F00C1B919 /* Delete.png */; }; E37C89430C83988F00C1B919 /* JobSmall.png in Resources */ = {isa = PBXBuildFile; fileRef = E37C893F0C83988F00C1B919 /* JobSmall.png */; }; @@ -163,6 +167,7 @@ E3C844F80CA6B3F90013B683 /* RevealHighlight.png in Resources */ = {isa = PBXBuildFile; fileRef = E3C844F40CA6B3F90013B683 /* RevealHighlight.png */; }; E3C844F90CA6B3F90013B683 /* Reveal.png in Resources */ = {isa = PBXBuildFile; fileRef = E3C844F50CA6B3F90013B683 /* Reveal.png */; }; E3C845870CA6E9080013B683 /* EncodeComplete.png in Resources */ = {isa = PBXBuildFile; fileRef = E3C845860CA6E9080013B683 /* EncodeComplete.png */; }; + E3EFC1320CD750FE0068DF25 /* EncodeCanceled.png in Resources */ = {isa = PBXBuildFile; fileRef = E3EFC1310CD750FE0068DF25 /* EncodeCanceled.png */; }; EAA526930C3B25D200944FF2 /* stream.c in Sources */ = {isa = PBXBuildFile; fileRef = EAA526920C3B25D200944FF2 /* stream.c */; }; EAA526940C3B25D200944FF2 /* stream.c in Sources */ = {isa = PBXBuildFile; fileRef = EAA526920C3B25D200944FF2 /* stream.c */; }; FC8519500C59A02C0073812C /* denoise.c in Sources */ = {isa = PBXBuildFile; fileRef = FC85194C0C59A02C0073812C /* denoise.c */; }; @@ -215,7 +220,7 @@ /* Begin PBXFileReference section */ 089C165DFE840E0CC02AAC07 /* English */ = {isa = PBXFileReference; fileEncoding = 10; lastKnownFileType = text.plist.strings; name = English; path = English.lproj/InfoPlist.strings; sourceTree = ""; }; 0D096DFF0B707D1200A845D4 /* libhb.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = libhb.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; - 0D6E35760B6BD4F0005AABB3 /* HandBrake.app */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.application; path = HandBrake.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 0D6E35760B6BD4F0005AABB3 /* HandBrake.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = HandBrake.app; sourceTree = BUILT_PRODUCTS_DIR; }; 0DF377970B7BF99A00115CB0 /* fakexcode.cpp */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.cpp; name = fakexcode.cpp; path = ../test/fakexcode.cpp; sourceTree = SOURCE_ROOT; }; 0DFA5C7A0B8DD1E90020BC09 /* HandBrake.icns */ = {isa = PBXFileReference; lastKnownFileType = image.icns; path = HandBrake.icns; sourceTree = ""; }; 0DFA5C7E0B8DD3B60020BC09 /* declpcm.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = declpcm.c; path = ../libhb/declpcm.c; sourceTree = SOURCE_ROOT; }; @@ -321,6 +326,10 @@ E37167A70C92FAA50072B384 /* JobPassUnknownSmall.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = JobPassUnknownSmall.png; sourceTree = ""; }; E37172650C977D340072B384 /* HBImageAndTextCell.m */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.objc; path = HBImageAndTextCell.m; sourceTree = ""; }; E37172660C977D340072B384 /* HBImageAndTextCell.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = HBImageAndTextCell.h; sourceTree = ""; }; + E3724BE90CD2280800010454 /* HBJob.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBJob.h; sourceTree = ""; }; + E3724BF80CD22A8400010454 /* HBJob.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBJob.m; sourceTree = ""; }; + E37274440CD4113000CFE13E /* HBJobGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBJobGroup.h; sourceTree = ""; }; + E372744C0CD412E100CFE13E /* HBJobGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBJobGroup.m; sourceTree = ""; }; E37C893D0C83988F00C1B919 /* DeleteHighlight.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = DeleteHighlight.png; sourceTree = ""; }; E37C893E0C83988F00C1B919 /* Delete.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Delete.png; sourceTree = ""; }; E37C893F0C83988F00C1B919 /* JobSmall.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = JobSmall.png; sourceTree = ""; }; @@ -339,6 +348,7 @@ E3C844F40CA6B3F90013B683 /* RevealHighlight.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = RevealHighlight.png; sourceTree = ""; }; E3C844F50CA6B3F90013B683 /* Reveal.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = Reveal.png; sourceTree = ""; }; E3C845860CA6E9080013B683 /* EncodeComplete.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = EncodeComplete.png; sourceTree = ""; }; + E3EFC1310CD750FE0068DF25 /* EncodeCanceled.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = EncodeCanceled.png; sourceTree = ""; }; EAA526920C3B25D200944FF2 /* stream.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = stream.c; path = ../libhb/stream.c; sourceTree = SOURCE_ROOT; }; FC85194C0C59A02C0073812C /* denoise.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = denoise.c; path = ../libhb/denoise.c; sourceTree = SOURCE_ROOT; }; FC85194D0C59A02C0073812C /* deinterlace.c */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.c; name = deinterlace.c; path = ../libhb/deinterlace.c; sourceTree = SOURCE_ROOT; }; @@ -544,6 +554,10 @@ A9AC41DD0C918DB500DDF9B8 /* HBAdvancedController.m */, E37172660C977D340072B384 /* HBImageAndTextCell.h */, E37172650C977D340072B384 /* HBImageAndTextCell.m */, + E3724BE90CD2280800010454 /* HBJob.h */, + E3724BF80CD22A8400010454 /* HBJob.m */, + E37274440CD4113000CFE13E /* HBJobGroup.h */, + E372744C0CD412E100CFE13E /* HBJobGroup.m */, ); name = "HandBrake Sources"; sourceTree = ""; @@ -600,6 +614,7 @@ E3997A260CAB58BC00287239 /* EncodeWorking3.png */, E3997A270CAB58BC00287239 /* EncodeWorking4.png */, E3997A2B0CAB58BC00287239 /* EncodeWorking5.png */, + E3EFC1310CD750FE0068DF25 /* EncodeCanceled.png */, ); path = icons; sourceTree = ""; @@ -637,6 +652,8 @@ E37C89480C83989F00C1B919 /* HBQueueController.h in Headers */, A9AC41E00C918DB500DDF9B8 /* HBAdvancedController.h in Headers */, E37172680C977D340072B384 /* HBImageAndTextCell.h in Headers */, + E3724BEA0CD2280800010454 /* HBJob.h in Headers */, + E37274450CD4113000CFE13E /* HBJobGroup.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -738,9 +755,11 @@ 29B97313FDCFA39411CA2CEA /* Project object */ = { isa = PBXProject; buildConfigurationList = 4D4E7BC4087804870051572B /* Build configuration list for PBXProject "HandBrake" */; + compatibilityVersion = "Xcode 2.4"; hasScannedForEncodings = 1; mainGroup = 29B97314FDCFA39411CA2CEA /* HandBrake */; projectDirPath = ""; + projectRoot = ""; targets = ( 4DD93F8D082036E8008E1322 /* HandBrake */, 4D1EA2D90993B01000FDC1A2 /* Instant HandBrake */, @@ -814,6 +833,7 @@ E3997A2F0CAB58BC00287239 /* EncodeWorking0.png in Resources */, E3997A300CAB58BC00287239 /* EncodeWorking2.png in Resources */, E3997A310CAB58BC00287239 /* EncodeWorking5.png in Resources */, + E3EFC1320CD750FE0068DF25 /* EncodeCanceled.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -945,6 +965,8 @@ E37C89470C83989F00C1B919 /* HBQueueController.mm in Sources */, A9AC41DF0C918DB500DDF9B8 /* HBAdvancedController.m in Sources */, E37172670C977D340072B384 /* HBImageAndTextCell.m in Sources */, + E3724BF90CD22A8400010454 /* HBJob.m in Sources */, + E372744D0CD412E100CFE13E /* HBJobGroup.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; Index: macosx/Controller.h =================================================================== --- macosx/Controller.h (revision 1043) +++ macosx/Controller.h (working copy) @@ -172,9 +172,6 @@ hb_handle_t * fHandle; hb_title_t * fTitle; - /* integer to set to determine the previous state - of encode 0==idle, 1==encoding, 2==cancelled*/ - int fEncodeState; int currentScanCount; int currentSuccessfulScanCount; BOOL SuccessfulScan; @@ -208,8 +205,6 @@ - (IBAction) subtitleSelectionChanged: (id) sender; - (IBAction) browseFile: (id) sender; -- (void) browseFileDone: (NSSavePanel *) sheet - returnCode: (int) returnCode contextInfo: (void *) contextInfo; - (IBAction) videoMatrixChanged: (id) sender; - (IBAction) qualitySliderChanged: (id) sender; @@ -217,26 +212,12 @@ - (IBAction) showPicturePanel: (id) sender; - (IBAction) calculatePictureSizing: (id) sender; - (IBAction) openMainWindow: (id) sender; - -- (IBAction) addToQueue: (id) sender; -- (void) overwriteAddToQueueAlertDone: (NSWindow *) sheet - returnCode: (int) returnCode contextInfo: (void *) contextInfo; -- (void) doAddToQueue; - - (IBAction) showQueueWindow:(id)sender; +- (IBAction) showPreferencesWindow:(id)sender; -- (IBAction)showPreferencesWindow:(id)sender; - +- (IBAction) addToQueue: (id) sender; - (IBAction) Rip: (id) sender; -- (void) overWriteAlertDone: (NSWindow *) sheet - returnCode: (int) returnCode contextInfo: (void *) contextInfo; -- (void) updateAlertDone: (NSWindow *) sheet - returnCode: (int) returnCode contextInfo: (void *) contextInfo; -- (void) doRip; - - (IBAction) Cancel: (id) sender; -- (void) doCancelCurrentJob; - - (IBAction) Pause: (id) sender; - (IBAction) calculateBitrate: (id) sender; @@ -305,5 +286,6 @@ - (void)setupToolbar; - (void) remindUserOfSleepOrShutdown; +- (void) performPostProcessingTasks; @end Index: macosx/HBQueueController.mm =================================================================== --- macosx/HBQueueController.mm (revision 1043) +++ macosx/HBQueueController.mm (working copy) @@ -4,9 +4,11 @@ Homepage: . It may be used under the terms of the GNU General Public License. */ -#include "HBQueueController.h" -#include "Controller.h" +#import "HBQueueController.h" +#import "Controller.h" #import "HBImageAndTextCell.h" +#import "HBJob.h" +#import "HBJobGroup.h" #define HB_ROW_HEIGHT_TITLE_ONLY 17.0 @@ -15,27 +17,9 @@ //------------------------------------------------------------------------------------ -// NSMutableAttributedString (HBAdditions) +// HBQueueOutlineView //------------------------------------------------------------------------------------ -@interface NSMutableAttributedString (HBAdditions) -- (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary; -@end - -@implementation NSMutableAttributedString (HBAdditions) -- (void) appendString: (NSString*)aString withAttributes: (NSDictionary *)aDictionary -{ - NSAttributedString * s = [[[NSAttributedString alloc] - initWithString: aString - attributes: aDictionary] autorelease]; - [self appendAttributedString: s]; -} -@end - -//------------------------------------------------------------------------------------ -#pragma mark - -//------------------------------------------------------------------------------------ - @implementation HBQueueOutlineView - (void)viewDidEndLiveResize @@ -81,701 +65,27 @@ @end -//------------------------------------------------------------------------------------ #pragma mark - -#pragma mark Job group functions -//------------------------------------------------------------------------------------ -// These could be part of hblib if we think hblib should have knowledge of groups. -// For now, I see groups as a metaphor that HBQueueController provides. -/** - * Returns the number of jobs groups in the queue. - * @param h Handle to hb_handle_t. - * @return Number of job groups. - */ -static int hb_group_count(hb_handle_t * h) -{ - hb_job_t * job; - int count = 0; - int index = 0; - while( ( job = hb_job( h, index++ ) ) ) - { - if (job->sequence_id == 0) - count++; - } - return count; -} +@interface HBQueueController (LibhbInteractions) -/** - * Returns handle to the first job in the i-th group within the job list. - * @param h Handle to hb_handle_t. - * @param i Index of group. - * @returns Handle to hb_job_t of desired job. - */ -static hb_job_t * hb_group(hb_handle_t * h, int i) -{ - hb_job_t * job; - int count = 0; - int index = 0; - while( ( job = hb_job( h, index++ ) ) ) - { - if (job->sequence_id == 0) - { - if (count == i) - return job; - count++; - } - } - return NULL; -} +- (void) libhbCurrentJobChanged: (hb_job_t *) currentJob; +- (void) libhbAddJobGroup: (HBJobGroup *) aJobGroup; +- (BOOL) libhbAddNextJobGroup; -/** - * Removes a groups of jobs from the job list. - * @param h Handle to hb_handle_t. - * @param job Handle to the first job in the group. - */ -static void hb_rem_group( hb_handle_t * h, hb_job_t * job ) -{ - // Find job in list - hb_job_t * j; - int index = 0; - while( ( j = hb_job( h, index ) ) ) - { - if (j == job) - { - // Delete this job plus the following ones in the sequence - hb_rem( h, job ); - while( ( j = hb_job( h, index ) ) && (j->sequence_id != 0) ) - hb_rem( h, j ); - return; - } - else - index++; - } -} - -/** - * Returns handle to the next job after the given job. - * @param h Handle to hb_handle_t. - * @param job Handle to the a job in the group. - * @returns Handle to hb_job_t of desired job or NULL if no such job. - */ -static hb_job_t * hb_next_job( hb_handle_t * h, hb_job_t * job ) -{ - hb_job_t * j = NULL; - int index = 0; - while( ( j = hb_job( h, index++ ) ) ) - { - if (j == job) - return hb_job( h, index ); - } - return NULL; -} - -#pragma mark - - -//------------------------------------------------------------------------------------ -// HBJob -//------------------------------------------------------------------------------------ - -@implementation HBJob - -+ (HBJob*) jobWithJob: (hb_job_t *) job -{ - return [[[HBJob alloc] initWithJob:job] autorelease]; -} - -- (id) initWithJob: (hb_job_t *) job -{ - if (self = [super init]) - { - // job is not owned by HBJob. It does not get dealloacted when HBJob is released. - hbJob = job; - } - return self; -} - -- (hb_job_t*) job -{ - return hbJob; -} - -//------------------------------------------------------------------------------------ -// Generate string to display in UI. -//------------------------------------------------------------------------------------ - -- (NSMutableAttributedString *) attributedDescriptionWithHBHandle: (hb_handle_t *)handle - withIcon: (BOOL)withIcon - withTitle: (BOOL)withTitle - withPassName: (BOOL)withPassName - withFormatInfo: (BOOL)withFormatInfo - withDestination: (BOOL)withDestination - withPictureInfo: (BOOL)withPictureInfo - withVideoInfo: (BOOL)withVideoInfo - withx264Info: (BOOL)withx264Info - withAudioInfo: (BOOL)withAudioInfo - withSubtitleInfo: (BOOL)withSubtitleInfo - -{ - NSMutableAttributedString * finalString = [[[NSMutableAttributedString alloc] initWithString: @""] autorelease]; - - hb_title_t * title = hbJob->title; - - // Attributes - static NSMutableParagraphStyle * ps = NULL; - if (!ps) - { - ps = [[[NSParagraphStyle defaultParagraphStyle] mutableCopy] retain]; - [ps setHeadIndent: 40.0]; - [ps setParagraphSpacing: 1.0]; - [ps setTabStops:[NSArray array]]; // clear all tabs - [ps addTabStop: [[[NSTextTab alloc] initWithType: NSLeftTabStopType location: 20.0] autorelease]]; - } - - static NSDictionary* detailAttribute = [[NSDictionary dictionaryWithObjectsAndKeys: - [NSFont systemFontOfSize:10.0], NSFontAttributeName, - ps, NSParagraphStyleAttributeName, - nil] retain]; - static NSDictionary* detailBoldAttribute = [[NSDictionary dictionaryWithObjectsAndKeys: - [NSFont boldSystemFontOfSize:10.0], NSFontAttributeName, - ps, NSParagraphStyleAttributeName, - nil] retain]; - static NSDictionary* titleAttribute = [[NSDictionary dictionaryWithObjectsAndKeys: - [NSFont systemFontOfSize:[NSFont systemFontSize]], NSFontAttributeName, - ps, NSParagraphStyleAttributeName, - nil] retain]; - static NSDictionary* shortHeightAttribute = [[NSDictionary dictionaryWithObjectsAndKeys: - [NSFont systemFontOfSize:2.0], NSFontAttributeName, - nil] retain]; - - // Title with summary - if (withTitle) - { - if (withIcon) - { - NSFileWrapper * wrapper = [[[NSFileWrapper alloc] initWithPath:[[NSBundle mainBundle] pathForImageResource: @"JobSmall"]] autorelease]; - NSTextAttachment * imageAttachment = [[[NSTextAttachment alloc] initWithFileWrapper:wrapper] autorelease]; - - NSDictionary* imageAttributes = [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithFloat: -2.0], NSBaselineOffsetAttributeName, - imageAttachment, NSAttachmentAttributeName, - ps, NSParagraphStyleAttributeName, - nil]; - - NSAttributedString * imageAsString = [[[NSAttributedString alloc] - initWithString: [NSString stringWithFormat:@"%C%C", NSAttachmentCharacter, NSTabCharacter] - attributes: imageAttributes] autorelease]; - - [finalString appendAttributedString:imageAsString]; - } - - // Note: use title->name instead of title->dvd since name is just the chosen - // folder, instead of dvd which is the full path - [finalString appendString:[NSString stringWithUTF8String:title->name] withAttributes:titleAttribute]; - - NSString * summaryInfo; - - NSString * chapterString = (hbJob->chapter_start == hbJob->chapter_end) ? - [NSString stringWithFormat:@"Chapter %d", hbJob->chapter_start] : - [NSString stringWithFormat:@"Chapters %d through %d", hbJob->chapter_start, hbJob->chapter_end]; - - BOOL hasIndepthScan = (hbJob->pass == -1); - int numVideoPasses = 0; - - // To determine number of video passes, we need to skip past the subtitle scan. - if (hasIndepthScan) - { - // When job is the one currently being processed, then the next in its group - // is the the first job in the queue. - hb_job_t * nextjob; - if (hbJob == hb_current_job(handle)) - nextjob = hb_job(handle, 0); - else - nextjob = hb_next_job(handle, hbJob); - if (nextjob) // Overly cautious in case there is no next job! - numVideoPasses = MIN( 2, nextjob->pass + 1 ); - } - else - numVideoPasses = MIN( 2, hbJob->pass + 1 ); - - if (hasIndepthScan && numVideoPasses == 1) - summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Deep Scan, Single Video Pass)", title->index, chapterString]; - else if (hasIndepthScan && numVideoPasses > 1) - summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Deep Scan, %d Video Passes)", title->index, chapterString, numVideoPasses]; - else if (numVideoPasses == 1) - summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, Single Video Pass)", title->index, chapterString]; - else - summaryInfo = [NSString stringWithFormat: @" (Title %d, %@, %d Video Passes)", title->index, chapterString, numVideoPasses]; - - [finalString appendString:[NSString stringWithFormat:@"%@\n", summaryInfo] withAttributes:detailAttribute]; - - // Insert a short-in-height line to put some white space after the title - [finalString appendString:@"\n" withAttributes:shortHeightAttribute]; - } - - // End of title stuff - - - // Pass Name - if (withPassName) - { - if (withIcon) - { - NSString * imageName; - switch (hbJob->pass) - { - case -1: imageName = @"JobPassSubtitleSmall"; break; - case 0: imageName = @"JobPassFirstSmall"; break; - case 1: imageName = @"JobPassFirstSmall"; break; - case 2: imageName = @"JobPassSecondSmall"; break; - default: imageName = @"JobPassUnknownSmall"; break; - } - - NSFileWrapper * wrapper = [[[NSFileWrapper alloc] initWithPath:[[NSBundle mainBundle] pathForImageResource: imageName]] autorelease]; - NSTextAttachment * imageAttachment = [[[NSTextAttachment alloc] initWithFileWrapper:wrapper] autorelease]; - - NSDictionary* imageAttributes = [NSDictionary dictionaryWithObjectsAndKeys: - [NSNumber numberWithFloat: -2.0], NSBaselineOffsetAttributeName, - imageAttachment, NSAttachmentAttributeName, - ps, NSParagraphStyleAttributeName, - nil]; - - NSAttributedString * imageAsString = [[[NSAttributedString alloc] - initWithString: [NSString stringWithFormat:@"%C%C", NSAttachmentCharacter, NSTabCharacter] - attributes: imageAttributes] autorelease]; - - [finalString appendAttributedString:imageAsString]; - } - - NSString * jobPassName; - if (hbJob->pass == -1) - jobPassName = NSLocalizedString (@"Deep Scan", nil); - else - { - int passNum = MAX( 1, hbJob->pass ); - if (passNum == 0) - jobPassName = NSLocalizedString (@"1st Pass", nil); - else if (passNum == 1) - jobPassName = NSLocalizedString (@"1st Pass", nil); - else if (passNum == 2) - jobPassName = NSLocalizedString (@"2nd Pass", nil); - else - jobPassName = [NSString stringWithFormat: NSLocalizedString(@"Pass %d", nil), passNum]; - } - [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPassName] withAttributes:detailBoldAttribute]; - } - - // Video Codec needed by FormatInfo and withVideoInfo - NSString * jobVideoCodec = nil; - if (withFormatInfo || withVideoInfo) - { - // 2097152 - // Video Codec settings (Encoder in the gui) - if (hbJob->vcodec == HB_VCODEC_FFMPEG) - jobVideoCodec = @"FFmpeg"; // HB_VCODEC_FFMPEG - else if (hbJob->vcodec == HB_VCODEC_XVID) - jobVideoCodec = @"XviD"; // HB_VCODEC_XVID - else if (hbJob->vcodec == HB_VCODEC_X264) - { - // Deterimine for sure how we are now setting iPod uuid atom - if (hbJob->h264_level) // We are encoding for iPod - jobVideoCodec = @"x264 (H.264 iPod)"; // HB_VCODEC_X264 - else - jobVideoCodec = @"x264 (H.264 Main)"; // HB_VCODEC_X264 - } - } - if (jobVideoCodec == nil) - jobVideoCodec = @"unknown"; - - // Audio Codec needed by FormatInfo and AudioInfo - NSString * jobAudioCodec = nil; - if (withFormatInfo || withAudioInfo) - { - if (hbJob->acodec == 256) - jobAudioCodec = @"AAC"; // HB_ACODEC_FAAC - else if (hbJob->acodec == 512) - jobAudioCodec = @"MP3"; // HB_ACODEC_LAME - else if (hbJob->acodec == 1024) - jobAudioCodec = @"Vorbis"; // HB_ACODEC_VORBIS - else if (hbJob->acodec == 2048) - jobAudioCodec = @"AC3"; // HB_ACODEC_AC3 - } - if (jobAudioCodec == nil) - jobAudioCodec = @"unknown"; - - - if (withFormatInfo) - { - NSString * jobFormatInfo; - // Muxer settings (File Format in the gui) - if (hbJob->mux == 65536 || hbJob->mux == 131072 || hbJob->mux == 1048576) - jobFormatInfo = @"MP4"; // HB_MUX_MP4,HB_MUX_PSP,HB_MUX_IPOD - else if (hbJob->mux == 262144) - jobFormatInfo = @"AVI"; // HB_MUX_AVI - else if (hbJob->mux == 524288) - jobFormatInfo = @"OGM"; // HB_MUX_OGM - else if (hbJob->mux == 2097152) - jobFormatInfo = @"MKV"; // HB_MUX_MKV - else - jobFormatInfo = @"unknown"; - - if (hbJob->chapter_markers == 1) - jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio, Chapter Markers\n", jobFormatInfo, jobVideoCodec, jobAudioCodec]; - else - jobFormatInfo = [NSString stringWithFormat:@"%@ Container, %@ Video + %@ Audio\n", jobFormatInfo, jobVideoCodec, jobAudioCodec]; - - [finalString appendString: @"Format: " withAttributes:detailBoldAttribute]; - [finalString appendString: jobFormatInfo withAttributes:detailAttribute]; - } - - if (withDestination) - { - [finalString appendString: @"Destination: " withAttributes:detailBoldAttribute]; - [finalString appendString:[NSString stringWithFormat:@"%@\n", [NSString stringWithUTF8String:hbJob->file]] withAttributes:detailAttribute]; - } - - - if (withPictureInfo) - { - NSString * jobPictureInfo; - // integers for picture values deinterlace, crop[4], keep_ratio, grayscale, pixel_ratio, pixel_aspect_width, pixel_aspect_height, - // maxWidth, maxHeight - if (hbJob->pixel_ratio == 1) - { - int titlewidth = title->width - hbJob->crop[2] - hbJob->crop[3]; - int displayparwidth = titlewidth * hbJob->pixel_aspect_width / hbJob->pixel_aspect_height; - int displayparheight = title->height - hbJob->crop[0] - hbJob->crop[1]; - jobPictureInfo = [NSString stringWithFormat:@"%dx%d (%dx%d Anamorphic)", displayparwidth, displayparheight, hbJob->width, displayparheight]; - } - else - jobPictureInfo = [NSString stringWithFormat:@"%dx%d", hbJob->width, hbJob->height]; - if (hbJob->keep_ratio == 1) - jobPictureInfo = [jobPictureInfo stringByAppendingString:@" Keep Aspect Ratio"]; - - if (hbJob->grayscale == 1) - jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Grayscale"]; - - if (hbJob->deinterlace == 1) - jobPictureInfo = [jobPictureInfo stringByAppendingString:@", Deinterlace"]; - if (withIcon) // implies indent the info - [finalString appendString: @"\t" withAttributes:detailBoldAttribute]; - [finalString appendString: @"Picture: " withAttributes:detailBoldAttribute]; - [finalString appendString:[NSString stringWithFormat:@"%@\n", jobPictureInfo] withAttributes:detailAttribute]; - } - - if (withVideoInfo) - { - NSString * jobVideoQuality; - NSString * jobVideoDetail; - - if (hbJob->vquality <= 0 || hbJob->vquality >= 1) - jobVideoQuality = [NSString stringWithFormat:@"%d kbps", hbJob->vbitrate]; - else - { - NSNumber * vidQuality; - vidQuality = [NSNumber numberWithInt:hbJob->vquality * 100]; - // this is screwed up kind of. Needs to be formatted properly. - if (hbJob->crf == 1) - jobVideoQuality = [NSString stringWithFormat:@"%@%% CRF", vidQuality]; - else - jobVideoQuality = [NSString stringWithFormat:@"%@%% CQP", vidQuality]; - } - - if (hbJob->vrate_base == 1126125) - { - // NTSC FILM 23.976 - jobVideoDetail = [NSString stringWithFormat:@"%@, %@, 23.976 fps", jobVideoCodec, jobVideoQuality]; - } - else if (hbJob->vrate_base == 900900) - { - // NTSC 29.97 - jobVideoDetail = [NSString stringWithFormat:@"%@, %@, 29.97 fps", jobVideoCodec, jobVideoQuality]; - } - else - { - // Everything else - jobVideoDetail = [NSString stringWithFormat:@"%@, %@, %d fps", jobVideoCodec, jobVideoQuality, hbJob->vrate / hbJob->vrate_base]; - } - if (withIcon) // implies indent the info - [finalString appendString: @"\t" withAttributes:detailBoldAttribute]; - [finalString appendString: @"Video: " withAttributes:detailBoldAttribute]; - [finalString appendString:[NSString stringWithFormat:@"%@\n", jobVideoDetail] withAttributes:detailAttribute]; - } - - if (withx264Info) - { - if (hbJob->vcodec == HB_VCODEC_X264 && hbJob->x264opts) - { - if (withIcon) // implies indent the info - [finalString appendString: @"\t" withAttributes:detailBoldAttribute]; - [finalString appendString: @"x264 Options: " withAttributes:detailBoldAttribute]; - [finalString appendString:[NSString stringWithFormat:@"%@\n", [NSString stringWithUTF8String:hbJob->x264opts]] withAttributes:detailAttribute]; - } - } - - if (withAudioInfo) - { - NSString * jobAudioInfo; - if ([jobAudioCodec isEqualToString: @"AC3"]) - jobAudioInfo = [NSString stringWithFormat:@"%@, Pass-Through", jobAudioCodec]; - else - jobAudioInfo = [NSString stringWithFormat:@"%@, %d kbps, %d Hz", jobAudioCodec, hbJob->abitrate, hbJob->arate]; - - // we now get the audio mixdown info for each of the two gui audio tracks - // lets do it the long way here to get a handle on things. - // Hardcoded for two tracks for gui: audio_mixdowns[i] audio_mixdowns[i] - int ai; // counter for each audios [] , macgui only allows for two audio tracks currently - for( ai = 0; ai < 2; ai++ ) - { - if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_MONO) - jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Mono", ai + 1]]; - if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_STEREO) - jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Stereo", ai + 1]]; - if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBY) - jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Surround", ai + 1]]; - if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_DOLBYPLII) - jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: Dolby Pro Logic II", ai + 1]]; - if (hbJob->audio_mixdowns[ai] == HB_AMIXDOWN_6CH) - jobAudioInfo = [jobAudioInfo stringByAppendingString:[NSString stringWithFormat:@", Track %d: 6-channel discreet", ai + 1]]; - } - if (withIcon) // implies indent the info - [finalString appendString: @"\t" withAttributes:detailBoldAttribute]; - [finalString appendString: @"Audio: " withAttributes:detailBoldAttribute]; - [finalString appendString:[NSString stringWithFormat:@"%@\n", jobAudioInfo] withAttributes:detailAttribute]; - } - - if (withSubtitleInfo) - { - // hbJob->subtitle can == -1 in two cases: - // autoselect: when pass == -1 - // none: when pass != -1 - if ((hbJob->subtitle == -1) && (hbJob->pass == -1)) - { - if (withIcon) // implies indent the info - [finalString appendString: @"\t" withAttributes:detailBoldAttribute]; - [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttribute]; - [finalString appendString: @"Autoselect " withAttributes:detailAttribute]; - } - else if (hbJob->subtitle >= 0) - { - hb_subtitle_t * subtitle = (hb_subtitle_t *) hb_list_item( title->list_subtitle, 0 ); - if (subtitle) - { - if (withIcon) // implies indent the info - [finalString appendString: @"\t" withAttributes:detailBoldAttribute]; - [finalString appendString: @"Subtitles: " withAttributes:detailBoldAttribute]; - [finalString appendString: [NSString stringWithCString: subtitle->lang] withAttributes:detailAttribute]; - } - } - } - - - if ([[finalString string] hasSuffix: @"\n"]) - [finalString deleteCharactersInRange: NSMakeRange([[finalString string] length]-1, 1)]; - - return finalString; -} - @end #pragma mark - //------------------------------------------------------------------------------------ -// HBJobGroup +// HBQueueController //------------------------------------------------------------------------------------ -@implementation HBJobGroup - -+ (HBJobGroup *) jobGroup; -{ - return [[[HBJobGroup alloc] init] autorelease]; -} - -- (id) init -{ - if (self = [super init]) - { - fJobs = [[NSMutableArray arrayWithCapacity:0] retain]; - fDescription = [[NSMutableAttributedString alloc] initWithString: @""]; - [self setNeedsDescription: NO]; - fStatus = HBStatusNone; - } - return self; -} - -- (void) dealloc -{ - [fJobs release]; - [fPath release]; - [super dealloc]; -} - -- (unsigned int) count -{ - return [fJobs count]; -} - -- (void) addJob: (HBJob *)aJob -{ - [fJobs addObject: aJob]; - [self setNeedsDescription: YES]; - fLastDescriptionHeight = 0; - fLastDescriptionWidth = 0; -} - -- (void) removeAllJobs -{ - [fJobs removeAllObjects]; -} - -- (HBJob *) jobAtIndex: (unsigned)index -{ - return [fJobs objectAtIndex: index]; -} - -- (unsigned) indexOfJob: (HBJob *)aJob; -{ - return [fJobs indexOfObject: aJob]; -} - -- (NSEnumerator *) jobEnumerator -{ - return [fJobs objectEnumerator]; -} - -- (void) setNeedsDescription: (BOOL)flag -{ - fNeedsDescription = flag; -} - -- (void) updateDescriptionWithHBHandle: (hb_handle_t *)handle -{ - fNeedsDescription = NO; - - [fDescription deleteCharactersInRange: NSMakeRange(0, [fDescription length])]; - - if ([self count] == 0) - { - NSAssert(NO, @" jobgroup with no jobs"); - return; - } - - HBJob * job = [self jobAtIndex:0]; - - [fDescription appendAttributedString: [job attributedDescriptionWithHBHandle: handle - withIcon: NO - withTitle: YES - withPassName: NO - withFormatInfo: YES - withDestination: YES - withPictureInfo: NO - withVideoInfo: NO - withx264Info: NO - withAudioInfo: NO - withSubtitleInfo: NO]]; - - static NSAttributedString * carriageReturn = [[NSAttributedString alloc] initWithString:@"\n"]; - - NSEnumerator * e = [self jobEnumerator]; - while ( (job = [e nextObject]) ) - { - int pass = [job job]->pass; - [fDescription appendAttributedString:carriageReturn]; - [fDescription appendAttributedString: - [job attributedDescriptionWithHBHandle: handle - withIcon: YES - withTitle: NO - withPassName: YES - withFormatInfo: NO - withDestination: NO - withPictureInfo: pass != -1 - withVideoInfo: pass != -1 - withx264Info: pass != -1 - withAudioInfo: pass == 0 || pass == 2 - withSubtitleInfo: YES]]; - } - -} - -- (NSMutableAttributedString *) attributedDescriptionWithHBHandle: (hb_handle_t *)handle -{ - if (fNeedsDescription) - [self updateDescriptionWithHBHandle: handle]; - return fDescription; -} - -- (float) heightOfDescriptionForWidth:(float)width withHBHandle: (hb_handle_t *)handle -{ - // Try to return the cached value if no changes have happened since the last time - if ((width == fLastDescriptionWidth) && (fLastDescriptionHeight != 0) && !fNeedsDescription) - return fLastDescriptionHeight; - - if (fNeedsDescription) - [self updateDescriptionWithHBHandle: handle]; - - // Calculate the height - NSRect bounds = [fDescription boundingRectWithSize:NSMakeSize(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin]; - fLastDescriptionHeight = bounds.size.height + 6.0; // add some border to bottom - fLastDescriptionWidth = width; - return fLastDescriptionHeight; - -/* supposedly another way to do this, in case boundingRectWithSize isn't working - NSTextView* tmpView = [[NSTextView alloc] initWithFrame:NSMakeRect(0, 0, width, 1)]; - [[tmpView textStorage] setAttributedString:aString]; - [tmpView setHorizontallyResizable:NO]; - [tmpView setVerticallyResizable:YES]; -// [[tmpView textContainer] setHeightTracksTextView: YES]; -// [[tmpView textContainer] setContainerSize: NSMakeSize(width, 10000)]; - [tmpView sizeToFit]; - float height = [tmpView frame].size.height; - [tmpView release]; - return height; -*/ -} - -- (float) lastDescriptionHeight -{ - return fLastDescriptionHeight; -} - -- (void) setStatus: (HBQueueJobGroupStatus)status -{ - self->fStatus = status; -} - -- (HBQueueJobGroupStatus) status -{ - return self->fStatus; -} - -- (void) setPath: (NSString *)path -{ - [path retain]; - [fPath release]; - fPath = path; -} - -- (NSString *) path -{ - return fPath; -} - -@end - - -#pragma mark - - -@interface HBQueueController (Private) -- (void)updateQueueUI; -@end - // Toolbar identifiers static NSString* HBQueueToolbar = @"HBQueueToolbar1"; static NSString* HBQueueStartCancelToolbarIdentifier = @"HBQueueStartCancelToolbarIdentifier"; static NSString* HBQueuePauseResumeToolbarIdentifier = @"HBQueuePauseResumeToolbarIdentifier"; -#pragma mark - - @implementation HBQueueController //------------------------------------------------------------------------------------ @@ -793,8 +103,7 @@ nil]]; fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain]; - fCompleted = [[NSMutableArray arrayWithCapacity:0] retain]; - + BOOL loadSucceeded = [NSBundle loadNibNamed:@"Queue" owner:self] && fQueueWindow; NSAssert(loadSucceeded, @"Could not open Queue nib"); NSAssert(fQueueWindow, @"fQueueWindow not found in Queue nib"); @@ -812,7 +121,6 @@ [fQueueWindow setDelegate:nil]; [fJobGroups release]; - [fCompleted release]; [fCurrentJobGroup release]; [fSavedExpandedItems release]; [fSavedSelectedItems release]; @@ -841,7 +149,6 @@ //------------------------------------------------------------------------------------ - (IBAction) showQueueWindow: (id)sender { - [self updateQueueUI]; [fQueueWindow makeKeyAndOrderFront: self]; [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"QueueWindowIsOpen"]; } @@ -892,75 +199,8 @@ fCurrentJobPaneShown = showPane; } -//------------------------------------------------------------------------------------ -// Rebuilds the contents of fJobGroups which is a hierarchy of HBJobGroup and HBJobs. -//------------------------------------------------------------------------------------ -- (void)rebuildJobGroups +- (void) setCurrentJobGroupFromLibhbJob: (hb_job_t *)aJob { - // Currently, job groups are rdered like this: - // Completed job groups - // Current job group - // Pending job groups - - [fJobGroups autorelease]; - fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain]; - - // Add all the completed job groups - [fJobGroups addObjectsFromArray: fCompleted]; - - // Add all the completed job groups - if (fCurrentJobGroup) - [fJobGroups addObject: fCurrentJobGroup]; - - // Add all the pending job groups. These come from hblib - HBJobGroup * aJobGroup = [HBJobGroup jobGroup]; - - // If hblib is currently processing something, hb_group will skip over that group. - // And that's exactly what we want -- fJobGroups contains only pending job groups. - - hb_job_t * nextJob = hb_group( fHandle, 0 ); - while( nextJob ) - { - if (nextJob->sequence_id == 0) - { - // Encountered a new group. Add the current one to fJobGroups and then start a new one. - if ([aJobGroup count] > 0) - { - [aJobGroup setStatus: HBStatusPending]; - [fJobGroups addObject: aJobGroup]; - aJobGroup = [HBJobGroup jobGroup]; - } - } - [aJobGroup addJob: [HBJob jobWithJob:nextJob]]; - [aJobGroup setPath: [NSString stringWithUTF8String:nextJob->file]]; - nextJob = hb_next_job (fHandle, nextJob); - } - if ([aJobGroup count] > 0) - { - [aJobGroup setStatus: HBStatusPending]; - [fJobGroups addObject:aJobGroup]; - } -} - -//------------------------------------------------------------------------------------ -// Adds aJobGroup to the list of completed job groups, marking its status as -// HBStatusComplete. -//------------------------------------------------------------------------------------ -- (void) addCompletedJobGroup: (HBJobGroup *)aJobGroup -{ - // Once hblib has completed its work, the hb_job_t objects will be freed, so we - // can't keep a reference to them. - [aJobGroup removeAllJobs]; - - [aJobGroup setStatus: HBStatusComplete]; - - // Put the group in the completed list for permanent storage, and also rebuild - // the master job group list which contains completed and pending groups. - [fCompleted addObject: aJobGroup]; -} - -- (void) setCurrentJobGroupFromJob: (hb_job_t *)aJob -{ HBJobGroup * aJobGroup = nil; // Try to find this new group in our existing job groups. @@ -974,7 +214,7 @@ NSEnumerator * jobEnum = [aJobGroup jobEnumerator]; while ( !found && (j = [jobEnum nextObject]) ) { - if ([j job] == aJob) + if ([j libhbJob] == aJob) found = YES; } } @@ -982,13 +222,16 @@ // Or create the job group. if (!aJobGroup) { + NSAssert (NO, @"HBQueueController could not find current libhb job."); +/* aJobGroup = [HBJobGroup jobGroup]; - [aJobGroup addJob: [HBJob jobWithJob: aJob]]; + [aJobGroup addJob: [HBJob jobWithLibhbJob: aJob]]; [aJobGroup setPath: [NSString stringWithUTF8String:aJob->file]]; while ( (aJob = hb_next_job(fHandle, aJob)) && (aJob->sequence_id != 0) ) - [aJobGroup addJob: [HBJob jobWithJob: aJob]]; + [aJobGroup addJob: [HBJob jobWithLibhbJob: aJob]]; [aJobGroup updateDescriptionWithHBHandle: fHandle]; +*/ } [aJobGroup setStatus: HBStatusWorking]; @@ -1003,111 +246,30 @@ #pragma mark UI Updating //------------------------------------------------------------------------------------ -// Saves the state of the items that are currently expanded. Calling restoreOutlineViewState -// will restore the state of all items to match what was saved by saveOutlineViewState. +// Marks the icon region of a job group in the queue view as needing display. //------------------------------------------------------------------------------------ -- (void) saveOutlineViewState +- (void) updateJobGroupIconInQueue:(HBJobGroup*)aJobGroup { - if (!fSavedExpandedItems) - fSavedExpandedItems = [[NSMutableIndexSet alloc] init]; - else - [fSavedExpandedItems removeAllIndexes]; - - // NB: This code is stuffing the address of each job into an index set. While it - // works 99.9% of the time, it's not the ideal solution. We need unique ids in - // each job, possibly using the existing sequence_id field. Could use the high - // word as a unique encode id and the low word the sequence number. - - HBJobGroup * aJobGroup; - NSEnumerator * e = [fJobGroups objectEnumerator]; - while ( (aJobGroup = [e nextObject]) ) + int row = [fOutlineView rowForItem: aJobGroup]; + int col = [fOutlineView columnWithIdentifier: @"icon"]; + if (row != -1 && col != -1) { - if ([fOutlineView isItemExpanded: aJobGroup]) - { - if ([aJobGroup status] == HBStatusComplete) - [fSavedExpandedItems addIndex: (unsigned int)aJobGroup]; - else - [fSavedExpandedItems addIndex: (unsigned int)[[aJobGroup jobAtIndex:0] job]]; - } + NSRect frame = [fOutlineView frameOfCellAtColumn:col row:row]; + [fOutlineView setNeedsDisplayInRect: frame]; } - - // Save the selection also. This is really UGLY code. Since I have to rebuild the - // entire outline hierachy every time hblib changes its job list, there's no easy - // way for me to remember the selection state other than saving off the first - // hb_job_t item in each selected group. This is done by saving the object's - // address. This could go away if I'd save a unique id in each job object. - - if (!fSavedSelectedItems) - fSavedSelectedItems = [[NSMutableIndexSet alloc] init]; - else - [fSavedSelectedItems removeAllIndexes]; - - NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes]; - int row = [selectedRows firstIndex]; - while (row != NSNotFound) - { - aJobGroup = [fOutlineView itemAtRow: row]; - if ([aJobGroup status] == HBStatusComplete) - [fSavedSelectedItems addIndex: (unsigned int)aJobGroup]; - else - [fSavedSelectedItems addIndex: (unsigned int)[[aJobGroup jobAtIndex:0] job]]; - row = [selectedRows indexGreaterThanIndex: row]; - } - } //------------------------------------------------------------------------------------ -// Restores the expanded state of items in the outline view to match those saved by a -// previous call to saveOutlineViewState. +// Marks the entire region of a job group in the queue view as needing display. //------------------------------------------------------------------------------------ -- (void) restoreOutlineViewState +- (void) updateJobGroupInQueue:(HBJobGroup*)aJobGroup { - if (fSavedExpandedItems) + int row = [fOutlineView rowForItem: aJobGroup]; + if (row != -1) { - HBJobGroup * aJobGroup; - NSEnumerator * e = [fJobGroups objectEnumerator]; - while ( (aJobGroup = [e nextObject]) ) - { - if ([aJobGroup status] == HBStatusComplete) - { - if ([fSavedExpandedItems containsIndex: (unsigned int)aJobGroup]) - [fOutlineView expandItem: aJobGroup]; - } - else - { - hb_job_t * j = [[aJobGroup jobAtIndex:0] job]; - if ([fSavedExpandedItems containsIndex: (unsigned int)j]) - [fOutlineView expandItem: aJobGroup]; - } - } + NSRect frame = [fOutlineView rectOfRow:row]; + [fOutlineView setNeedsDisplayInRect: frame]; } - - if (fSavedSelectedItems) - { - // Ugh. Have to cycle through each row looking for the previously selected job. - // See the explanation in saveOutlineViewState about the logic here. - - NSMutableIndexSet * rowsToSelect = [[[NSMutableIndexSet alloc] init] autorelease]; - for (int i = 0; i < [fOutlineView numberOfRows]; i++) - { - HBJobGroup * aJobGroup = [fOutlineView itemAtRow: i]; - // Test to see if the group or the group's first job is a match - if ([aJobGroup status] == HBStatusComplete) - { - if ([fSavedSelectedItems containsIndex: (unsigned int)aJobGroup]) - [rowsToSelect addIndex: i]; - } - else - { - if ([fSavedSelectedItems containsIndex: (unsigned int)[[aJobGroup jobAtIndex:0] job]]) - [rowsToSelect addIndex: i]; - } - } - if ([rowsToSelect count] == 0) - [fOutlineView deselectAll: nil]; - else - [fOutlineView selectRowIndexes:rowsToSelect byExtendingSelection:NO]; - } } //------------------------------------------------------------------------------------ @@ -1116,14 +278,11 @@ //------------------------------------------------------------------------------------ - (void) animateCurrentJobGroupInQueue:(NSTimer*)theTimer { - int row = [fOutlineView rowForItem: fCurrentJobGroup]; - int col = [fOutlineView columnWithIdentifier: @"icon"]; - if (row != -1 && col != -1) + if (fCurrentJobGroup) { fAnimationIndex++; fAnimationIndex %= 6; // there are 6 animation images; see outlineView:objectValueForTableColumn:byItem: below. - NSRect frame = [fOutlineView frameOfCellAtColumn:col row:row]; - [fOutlineView setNeedsDisplayInRect: frame]; + [self updateJobGroupIconInQueue: fCurrentJobGroup]; } } @@ -1163,7 +322,9 @@ if (s->state == HB_STATE_WORKING) { NSString * msg; - if (job->pass == -1) + if (!job) + msg = @""; // no current job, even though state says working. Means there's a transition from one job to another going on. + else if (job->pass == -1) msg = NSLocalizedString( @"Deep Scan", nil ); else if (job->pass == 1) msg = NSLocalizedString( @"Analyzing video", nil ); @@ -1180,7 +341,6 @@ } else return msg; - } else if (s->state == HB_STATE_MUXING) @@ -1318,13 +478,21 @@ - (void)updateQueueCountField { NSString * msg; - int jobCount; - - jobCount = fHandle ? hb_group_count(fHandle) : 0; - if (jobCount == 1) - msg = NSLocalizedString(@"1 pending encode", nil); + int jobCount = [fJobGroups count]; + int pendingCount = [self pendingCount]; + if (jobCount == 0) + msg = NSLocalizedString(@"0 encodes", nil); + else if ((jobCount == 1) && (pendingCount == 0)) + msg = NSLocalizedString(@"1 encode", nil); + else if (jobCount == pendingCount) + { + if (jobCount == 1) + msg = NSLocalizedString(@"1 pending encode", nil); + else + msg = [NSString stringWithFormat:NSLocalizedString(@"%d pending encodes", nil), pendingCount]; + } else - msg = [NSString stringWithFormat:NSLocalizedString(@"%d pending encodes", nil), jobCount]; + msg = [NSString stringWithFormat:NSLocalizedString(@"%d encodes (%d pending)", nil), jobCount, pendingCount]; [fQueueCountField setStringValue:msg]; } @@ -1339,7 +507,7 @@ if (job) { - HBJob * currentJob = [HBJob jobWithJob: job]; + HBJob * currentJob = [HBJob jobWithLibhbJob: job]; switch (job->pass) { case -1: // Subtitle scan @@ -1422,13 +590,86 @@ } //------------------------------------------------------------------------------------ -// Refresh the UI in the queue pane. Should be called whenever the content of HB's job -// list has changed so that HBQueueController can sync up. +// Saves the state of the items that are currently expanded and selected. Calling +// restoreOutlineViewState will restore the outline view to the saved state. //------------------------------------------------------------------------------------ -- (void)updateQueueUI +- (void) saveOutlineViewState { + // Save the expanded job groups + if (!fSavedExpandedItems) + fSavedExpandedItems = [[NSMutableArray arrayWithCapacity:0] retain]; + else + [fSavedExpandedItems removeAllObjects]; + + HBJobGroup * aJobGroup; + NSEnumerator * e = [fJobGroups objectEnumerator]; + while ( (aJobGroup = [e nextObject]) ) + { + if ([fOutlineView isItemExpanded: aJobGroup]) + [fSavedExpandedItems addObject:aJobGroup]; + } + + // Save the selected job groups + [fSavedSelectedItems release]; + NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes]; + fSavedSelectedItems = [[fJobGroups objectsAtIndexes:selectedRows] retain]; +} + +//------------------------------------------------------------------------------------ +// Restores the expanded state of items in the outline view to match those saved by a +// previous call to saveOutlineViewState. +//------------------------------------------------------------------------------------ +- (void) restoreOutlineViewState +{ + // Restore the expanded job groups + if (fSavedExpandedItems) + { + HBJobGroup * aJobGroup; + NSEnumerator * e = [fJobGroups objectEnumerator]; + while ( (aJobGroup = [e nextObject]) ) + { + if ([fSavedExpandedItems containsObject: aJobGroup]) + [fOutlineView expandItem: aJobGroup]; + } + [fSavedExpandedItems release]; + fSavedExpandedItems = nil; + } + + // Restore the selected job groups + if (fSavedSelectedItems) + { + NSMutableIndexSet * rowsToSelect = [[[NSMutableIndexSet alloc] init] autorelease]; + for (int i = 0; i < [fOutlineView numberOfRows]; i++) + { + HBJobGroup * aJobGroup = [fOutlineView itemAtRow: i]; + if ([fSavedSelectedItems containsObject: aJobGroup]) + [rowsToSelect addIndex: i]; + } + if ([rowsToSelect count] == 0) + [fOutlineView deselectAll: nil]; + else + [fOutlineView selectRowIndexes:rowsToSelect byExtendingSelection:NO]; + [fSavedSelectedItems release]; + fSavedSelectedItems = nil; + } +} + +//------------------------------------------------------------------------------------ +// Notifies HBQueuecontroller that the contents of fJobGroups is about to be modified. +// HBQueuecontroller remembers the state of the UI (selection and expanded items). +//------------------------------------------------------------------------------------ +- (void) beginEditingJobGroupsArray +{ [self saveOutlineViewState]; - [self rebuildJobGroups]; +} + +//------------------------------------------------------------------------------------ +// Notifies HBQueuecontroller that modifications to fJobGroups as indicated by a prior +// call to beginEditingJobGroupsArray have been completed. HBQueuecontroller reloads +// the queue view and restores the state of the UI (selection and expanded items). +//------------------------------------------------------------------------------------ +- (void) endEditingJobGroupsArray +{ [fOutlineView noteNumberOfRowsChanged]; [fOutlineView reloadData]; [self restoreOutlineViewState]; @@ -1436,46 +677,186 @@ } #pragma mark - + +//------------------------------------------------------------------------------------ +// Displays a modal sheet window asking the user if they want to cancel processing of +// the queue. If the didEndSelector method is specified, it is invoked after the modal +// session has ended and is passed a return code and caller specified in contextInfo. +//------------------------------------------------------------------------------------ +- (void) beginStopQueueAlertModalForWindow:(NSWindow *)docWindow + didEndSelector:(SEL)didEndSelector + contextInfo:(void *)contextInfo +{ + NSString * alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Do you want to stop encoding of %@?", nil), + [fCurrentJobGroup name]]; + + NSBeginCriticalAlertSheet( + alertTitle, + NSLocalizedString(@"Keep Encoding", nil), + nil, //NSLocalizedString(@"Delete All", nil), + NSLocalizedString(@"Stop Encoding", nil), + docWindow, self, + nil, didEndSelector, contextInfo, + NSLocalizedString(@"Your movie will be lost if you don't continue encoding.", nil), + [fCurrentJobGroup name]); +} + +#pragma mark - #pragma mark Actions //------------------------------------------------------------------------------------ +// Starts processing of job groups in the pending list. If processing has already been +// started, nothing happens. +//------------------------------------------------------------------------------------ +- (IBAction) startQueue: (id)sender +{ + // hb_start will begin processing. We will get messages from libhb as its state + // changes. + hb_state_t s; + hb_get_state2 (fHandle, &s); + if ((s.state == HB_STATE_IDLE) || (s.state == HB_STATE_WORKDONE)) + { + if ([self libhbAddNextJobGroup]) + { + fStopQueueRequested = NO; + hb_start (fHandle); + } + } +} + +//------------------------------------------------------------------------------------ +// Stops processing of job groups in the queue. The current job group will be canceled +// and its status set to HBStatusCanceled. This call returns immediately after +// scheduling the stop. +//------------------------------------------------------------------------------------ +- (void) stopQueue +{ + hb_state_t s; + hb_get_state2 (fHandle, &s); + if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING) || (s.state == HB_STATE_PAUSED)) + { + // Mark the current job group as canceled. Otherwise, we'd have no way of + // knowing if the job group was canceled or completed sucessfully. + // ************** This may not be needed anymore because there is now the + // fStopQueueRequested flag which is used in libhbCurrentJobChanged to + // determine if a job was canceled or not. + if (fCurrentJobGroup) + [fCurrentJobGroup setStatus: HBStatusCanceled]; + + // Tell libhb to stop. We'll receive a HB_STATE_WORKDONE message when is has + // finished stopping. + fStopQueueRequested = YES; + hb_stop (fHandle); + } +} + +//------------------------------------------------------------------------------------ +// Prompts the user to verify that they want the queue to be stopped. If acknowledged, +// stopQueue will be called to cancel the current job group and stop the queue. To +// stop the queue without asking the user, use stopQueue. +//------------------------------------------------------------------------------------ +- (IBAction) stopQueue: (id)sender +{ + hb_state_t s; + hb_get_state2 (fHandle, &s); + if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING) || (s.state == HB_STATE_PAUSED)) + { + if (fCurrentJobGroup) + { + // Which window to attach the sheet to? + NSWindow * docWindow; + if ([sender isKindOfClass:[NSWindow class]]) + docWindow = sender; + else if ([sender respondsToSelector: @selector(window)]) + docWindow = [sender window]; + else + docWindow = fQueueWindow; + + [self beginStopQueueAlertModalForWindow: docWindow didEndSelector:@selector(didDimissStopQueueAlert:returnCode:contextInfo:) contextInfo:nil]; + + // didDimissStopQueueAlert:returnCode:contextInfo: will be called when the dialog is dismissed + + } + + // If there wasn't a current job group (which really would be a programmatic + // error), just call hb_stop directly. + else + hb_stop (fHandle); + } +} + +- (void) didDimissStopQueueAlert: (NSWindow *)sheet returnCode: (int)returnCode contextInfo: (void *)contextInfo +{ + if (returnCode == NSAlertOtherReturn) + [self stopQueue]; +} + + +//------------------------------------------------------------------------------------ +// Pauses the queue if it is currently active. +//------------------------------------------------------------------------------------ +- (IBAction) pauseQueue: (id)sender +{ + hb_state_t s; + hb_get_state2 (fHandle, &s); + if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING)) + hb_pause (fHandle); +} + + +//------------------------------------------------------------------------------------ +// Resumes the queue if it had previously been paused. +//------------------------------------------------------------------------------------ +- (IBAction) resumeQueue: (id)sender +{ + hb_state_t s; + hb_get_state2(fHandle, &s); + if (s.state == HB_STATE_PAUSED) + hb_resume (fHandle); +} + + +//------------------------------------------------------------------------------------ // Deletes the selected jobs from HB and the queue UI //------------------------------------------------------------------------------------ - (IBAction)removeSelectedJobGroups: (id)sender { if (!fHandle) return; - NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes]; - int row = [selectedRows firstIndex]; - if (row != NSNotFound) + NSIndexSet * selectedRows = [[fOutlineView selectedRowIndexes] retain]; + NSArray * groupsToDelete = [fJobGroups objectsAtIndexes:selectedRows]; + + // Send a message to cancel processing of the current job, if it is being deleted. + // The cancel will not happen immediately. stopQueueAndRemoveSelected:::: will be + // called after the user responds to the alert. selectedRows is passed as content + // info so that stopQueueAndRemoveSelected: knows wht to delete. + if ([groupsToDelete containsObject:fCurrentJobGroup]) { - while (row != NSNotFound) - { - HBJobGroup * jobGroup = [fOutlineView itemAtRow: row]; - switch ([jobGroup status]) - { - case HBStatusComplete: - case HBStatusCanceled: - [fCompleted removeObject: jobGroup]; - break; - case HBStatusWorking: - [self cancelCurrentJob: sender]; - break; - case HBStatusPending: - hb_job_t * job = [[jobGroup jobAtIndex: 0] job]; - hb_rem_group( fHandle, job ); - break; - case HBStatusNone: - break; - } - - row = [selectedRows indexGreaterThanIndex: row]; - } + [self beginStopQueueAlertModalForWindow: fQueueWindow didEndSelector:@selector(didDismissStopQueueAndRemoveSelected:returnCode:contextInfo:) contextInfo:selectedRows]; + return; + } + + [self beginEditingJobGroupsArray]; + [fJobGroups removeObjectsAtIndexes: selectedRows]; + [selectedRows release]; + [self endEditingJobGroupsArray]; +} - [self hblibJobListChanged]; - } +- (void) didDismissStopQueueAndRemoveSelected: (NSWindow *)sheet returnCode: (int)returnCode contextInfo: (void *)contextInfo +{ + if (returnCode == NSAlertOtherReturn) + { + [self stopQueue]; + + NSIndexSet * selectedRows = (NSIndexSet*)contextInfo; + [self beginEditingJobGroupsArray]; + [fJobGroups removeObjectsAtIndexes: selectedRows]; + [selectedRows release]; + [self endEditingJobGroupsArray]; + } } + //------------------------------------------------------------------------------------ // Reveals the file icons in the Finder of the selected job groups. //------------------------------------------------------------------------------------ @@ -1504,110 +885,150 @@ // the alert. Later, when the user acknowledges the alert, HBController will call // hblib to cancel the job. //------------------------------------------------------------------------------------ +// ************ is this necessary? - (IBAction)cancelCurrentJob: (id)sender { - [fHBController Cancel:sender]; + [self stopQueue: sender]; } +#pragma mark - + //------------------------------------------------------------------------------------ -// Starts or cancels the processing of jobs depending on the current state +// Queues a job group. The job group's status is set to HBStatusPending. //------------------------------------------------------------------------------------ -- (IBAction)toggleStartCancel: (id)sender +- (void) addJobGroup: (HBJobGroup *) aJobGroup { - if (!fHandle) return; + NSAssert(![fJobGroups containsObject:aJobGroup], @"Duplicate job group"); + [aJobGroup setStatus:HBStatusPending]; - hb_state_t s; - hb_get_state2 (fHandle, &s); - - if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING)) - [fHBController Cancel: fQueuePane]; // sender == fQueuePane so that warning alert shows up on queue window - - else if (hb_group_count(fHandle) > 0) - [fHBController doRip]; + [self beginEditingJobGroupsArray]; + [fJobGroups addObject:aJobGroup]; + [self endEditingJobGroupsArray]; } //------------------------------------------------------------------------------------ -// Toggles the pause/resume state of hblib +// Removes a job from the queue. //------------------------------------------------------------------------------------ -- (IBAction)togglePauseResume: (id)sender +- (void) removeJobGroup: (HBJobGroup *) aJobGroup { - if (!fHandle) return; + // Removal of the current job being processed is akin to stopping the queue. + if (aJobGroup == fCurrentJobGroup) + [self stopQueue]; + + [self beginEditingJobGroupsArray]; + [fJobGroups removeObject: aJobGroup]; + [self endEditingJobGroupsArray]; - hb_state_t s; - hb_get_state2 (fHandle, &s); +} - if (s.state == HB_STATE_PAUSED) - hb_resume (fHandle); - else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING)) - hb_pause (fHandle); +//------------------------------------------------------------------------------------ +// Returns the number of job groups with a specified status. +//------------------------------------------------------------------------------------ +- (unsigned int) jopGroupCountWithStatus: (HBQueueJobGroupStatus) status +{ + unsigned int count = 0; + NSEnumerator * groupEnum = [fJobGroups objectEnumerator]; + HBJobGroup * aJobGroup; + while ( (aJobGroup = [groupEnum nextObject]) ) + { + if ([aJobGroup status] == status) + count++; + } + return count; } -#pragma mark - -#pragma mark Synchronizing with hblib +//------------------------------------------------------------------------------------ +// Returns the job group located at index in the list of job groups with status. If +// index is greater than or equal to the value returned by jopGroupCountWithStatus:, +// nil is retunred. +//------------------------------------------------------------------------------------ +- (HBJobGroup *) jobGroupWithStatus: (HBQueueJobGroupStatus)status atIndex: (unsigned int)index +{ + unsigned int count = 0; + NSEnumerator * groupEnum = [fJobGroups objectEnumerator]; + HBJobGroup * aJobGroup = nil; + while ( (aJobGroup = [groupEnum nextObject]) ) + { + if ([aJobGroup status] == status) + { + if (count == index) + return aJobGroup; + count++; + } + } + return aJobGroup; +} //------------------------------------------------------------------------------------ -// Notifies HBQueueController that hblib's current job has changed +// Returns the number of job groups whose status is HBStatusPending. //------------------------------------------------------------------------------------ -- (void)currentJobGroupChanged: (hb_job_t *) currentJob +- (unsigned int) pendingCount { - if (fCurrentJobGroup && [fCurrentJobGroup status] != HBStatusCanceled) - [self addCompletedJobGroup: fCurrentJobGroup]; - [self setCurrentJobGroupFromJob: currentJob]; - [self updateCurrentJobDescription]; - [self updateCurrentJobProgress]; - [self showCurrentJobPane: fCurrentJobGroup != nil]; - if (fCurrentJobGroup) - [self startAnimatingCurrentJobGroupInQueue]; - else - [self stopAnimatingCurrentJobGroupInQueue]; + // This value is calculated each time, not cached. + return [self jopGroupCountWithStatus: HBStatusPending]; } //------------------------------------------------------------------------------------ -// Notifies HBQueueController that hb_stop is about to be called. This signals us that -// the current job is going to be canceled and deleted. This is somewhat of a hack to -// let HBQueueController know when a job group has been cancelled. Otherwise, we'd -// have no way of knowing if a job was canceled or completed sucessfully. +// Returns the number of job groups whose status is HBStatusComplete. //------------------------------------------------------------------------------------ -- (void)hblibWillStop +- (unsigned int) completedCount { - if (fCurrentJobGroup) - [fCurrentJobGroup setStatus: HBStatusCanceled]; + // This value is calculated each time, not cached. + return [self jopGroupCountWithStatus: HBStatusComplete]; } //------------------------------------------------------------------------------------ -// Notifies HBQueueController that hblib's job list has been modified +// Returns the number of job groups whose status is HBStatusCanceled. //------------------------------------------------------------------------------------ -- (void)hblibJobListChanged +- (unsigned int) canceledCount { - // This message is received from HBController after it has added a job group to - // hblib's job list. It is also received from self when a job group is deleted by - // the user. - [self updateQueueUI]; + // This value is calculated each time, not cached. + return [self jopGroupCountWithStatus: HBStatusCanceled]; } //------------------------------------------------------------------------------------ +// Returns the number of job groups whose status is HBStatusWorking. +//------------------------------------------------------------------------------------ +- (unsigned int) workingCount +{ + // This value is calculated each time, not cached. + return [self jopGroupCountWithStatus: HBStatusWorking]; +} + +#if HB_OUTLINE_METRIC_CONTROLS +static float spacingWidth = 3.0; +- (IBAction)imageSpacingChanged: (id)sender; +{ + spacingWidth = [sender floatValue]; + [fOutlineView setNeedsDisplay: YES]; +} +- (IBAction)indentChanged: (id)sender +{ + [fOutlineView setIndentationPerLevel: [sender floatValue]]; + [fOutlineView setNeedsDisplay: YES]; +} +#endif + +#pragma mark - + +//------------------------------------------------------------------------------------ // Notifies HBQueueController that hblib's state has changed //------------------------------------------------------------------------------------ -- (void)hblibStateChanged: (hb_state_t &)state +- (void)libhbStateChanged: (hb_state_t &)state { // First check to see if hblib has moved on to another job. We get no direct // message when this happens, so we have to detect it ourself. The new job could // be either just the next job in the current group, or the start of a new group. - if (fLastKnownCurrentJob != hb_current_job(fHandle)) + // When state is HB_STATE_WORKDONE, this detection is delayed until it is known + // whether a new job group is going to be started up. + if (state.state != HB_STATE_WORKDONE) { hb_job_t * currentJob = hb_current_job(fHandle); - if (!currentJob || currentJob->sequence_id == 0) // start of a new group + if (fLastKnownLibHBJob != currentJob) { - [self currentJobGroupChanged: currentJob]; - [self hblibJobListChanged]; + [self libhbCurrentJobChanged: currentJob]; + fLastKnownLibHBJob = currentJob; } - else - { - [self updateCurrentJobDescription]; - [self updateCurrentJobProgress]; - } - - fLastKnownCurrentJob = currentJob; } switch( state.state ) @@ -1635,29 +1056,44 @@ case HB_STATE_WORKDONE: { // HB_STATE_WORKDONE means that hblib has finished processing all the jobs - // in *its* queue. This message is NOT sent as each individual job is + // in *its* queue. This message is NOT sent as each individual job/pass is // completed. - } + + // HB_STATE_WORKDONE also happpens as a result of someone calling hb_stop. + // In this latter case, hb_stop does not clear out the remaining + // passes/jobs in the queue. We'll do that here. + + // Delete all remaining items in libhb's queue. + hb_job_t * job; + while( ( job = hb_job(fHandle, 0) ) ) + hb_rem( fHandle, job ); - } + // Start processing the next job group in our queue + BOOL restartQueue = !fStopQueueRequested && ([self pendingCount] > 0); + if (restartQueue && [self libhbAddNextJobGroup]) + { + fStopQueueRequested = NO; + hb_start(fHandle); + } + else + { + [self libhbCurrentJobChanged: nil]; + fLastKnownLibHBJob = nil; + + // Notify user. Doing this via performSelectorOnMainThread so that + // HBController gets a chance to clean up from state HB_STATE_WORKDONE + // before a possibly modal notification happens in performPostProcessingTasks. + if (!fStopQueueRequested) + [fHBController performSelectorOnMainThread:@selector(performPostProcessingTasks) withObject:nil waitUntilDone:NO]; + + fStopQueueRequested = NO; + } + } + } } -#if HB_OUTLINE_METRIC_CONTROLS -static float spacingWidth = 3.0; -- (IBAction)imageSpacingChanged: (id)sender; -{ - spacingWidth = [sender floatValue]; - [fOutlineView setNeedsDisplay: YES]; -} -- (IBAction)indentChanged: (id)sender -{ - [fOutlineView setIndentationPerLevel: [sender floatValue]]; - [fOutlineView setNeedsDisplay: YES]; -} -#endif - #pragma mark - #pragma mark Toolbar @@ -1702,13 +1138,13 @@ [toolbarItem setLabel: @"Start"]; [toolbarItem setPaletteLabel: @"Start/Cancel"]; - // Set up a reasonable tooltip, and image + // Set up a reasonable tooltip, and image. These will change dynamically by validateToolbarItem. [toolbarItem setToolTip: @"Start Encoding"]; [toolbarItem setImage: [NSImage imageNamed: @"Play"]]; - - // Tell the item what message to send when it is clicked + + // Set a default target/action. These will change dynamically by validateToolbarItem. [toolbarItem setTarget: self]; - [toolbarItem setAction: @selector(toggleStartCancel:)]; + [toolbarItem setAction: @selector(startQueue:)]; } if ([itemIdentifier isEqual: HBQueuePauseResumeToolbarIdentifier]) @@ -1722,10 +1158,10 @@ // Set up a reasonable tooltip, and image [toolbarItem setToolTip: @"Pause Encoding"]; [toolbarItem setImage: [NSImage imageNamed: @"Pause"]]; - - // Tell the item what message to send when it is clicked + + // Set a default target/action. These will change dynamically by validateToolbarItem. [toolbarItem setTarget: self]; - [toolbarItem setAction: @selector(togglePauseResume:)]; + [toolbarItem setAction: @selector(pauseQueue:)]; } return toolbarItem; @@ -1776,25 +1212,26 @@ BOOL enable = NO; - hb_state_t s; - hb_get_state2 (fHandle, &s); - if ([[toolbarItem itemIdentifier] isEqual: HBQueueStartCancelToolbarIdentifier]) { - if ((s.state == HB_STATE_PAUSED) || (s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING)) + if (fCurrentJobGroup) { enable = YES; [toolbarItem setImage:[NSImage imageNamed: @"Stop"]]; [toolbarItem setLabel: @"Stop"]; [toolbarItem setToolTip: @"Stop Encoding"]; + [toolbarItem setTarget: self]; + [toolbarItem setAction: @selector(stopQueue:)]; } - else if (hb_count(fHandle) > 0) + else if ([self pendingCount] > 0) { enable = YES; [toolbarItem setImage:[NSImage imageNamed: @"Play"]]; [toolbarItem setLabel: @"Start"]; [toolbarItem setToolTip: @"Start Encoding"]; + [toolbarItem setTarget: self]; + [toolbarItem setAction: @selector(startQueue:)]; } else @@ -1808,13 +1245,18 @@ if ([[toolbarItem itemIdentifier] isEqual: HBQueuePauseResumeToolbarIdentifier]) { + hb_state_t s; + hb_get_state2 (fHandle, &s); + if (s.state == HB_STATE_PAUSED) { enable = YES; [toolbarItem setImage:[NSImage imageNamed: @"Play"]]; [toolbarItem setLabel: @"Resume"]; [toolbarItem setToolTip: @"Resume Encoding"]; - } + [toolbarItem setTarget: self]; + [toolbarItem setAction: @selector(resumeQueue:)]; + } else if ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING)) { @@ -1822,7 +1264,9 @@ [toolbarItem setImage:[NSImage imageNamed: @"Pause"]]; [toolbarItem setLabel: @"Pause"]; [toolbarItem setToolTip: @"Pause Encoding"]; - } + [toolbarItem setTarget: self]; + [toolbarItem setAction: @selector(pauseQueue:)]; + } else { enable = NO; @@ -2008,6 +1452,9 @@ case HBStatusWorking: return [NSImage imageNamed: [NSString stringWithFormat: @"EncodeWorking%d", fAnimationIndex]]; break; + case HBStatusCanceled: + return [NSImage imageNamed:@"EncodeCanceled"]; + break; default: return [NSImage imageNamed:@"JobSmall"]; break; @@ -2093,13 +1540,14 @@ } // Don't retain since this is just holding temporaral drag information, and it is - //only used during a drag! We could put this in the pboard actually. + // only used during a drag! We could put this in the pboard actually. fDraggedNodes = items; // Provide data for our custom type, and simple NSStrings. [pboard declareTypes:[NSArray arrayWithObjects: HBQueuePboardType, nil] owner:self]; - // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!. + // the actual data doesn't matter since DragDropSimplePboardType drags aren't + // recognized by anyone but us. [pboard setData:[NSData data] forType:HBQueuePboardType]; return YES; @@ -2122,7 +1570,7 @@ } // Prevent dragging into the completed or current job. - int firstPendingIndex = [fCompleted count]; + int firstPendingIndex = [self completedCount]; if (fCurrentJobGroup) firstPendingIndex++; index = MAX (index, firstPendingIndex); @@ -2145,10 +1593,9 @@ } // Rearrange the data and view - [self saveOutlineViewState]; + [self beginEditingJobGroupsArray]; [self moveObjectsInArray:fJobGroups fromIndexes:moveItems toIndex: index]; - [fOutlineView reloadData]; - [self restoreOutlineViewState]; + [self endEditingJobGroupsArray]; return YES; } @@ -2156,3 +1603,84 @@ @end + + +#pragma mark - + +@implementation HBQueueController (LibhbInteractions) + +//------------------------------------------------------------------------------------ +// Notifies HBQueueController that hblib's current job has changed +//------------------------------------------------------------------------------------ +- (void)libhbCurrentJobChanged: (hb_job_t *) currentJob +{ + if (!currentJob || currentJob->sequence_id == 0) // start of a new group + { + // Here's our only chance to change the current job group's status after it is + // finished. + if (fCurrentJobGroup) + { + // If user canceled, reset the current job group back to pending so it + // is in the pendng queue. + if (fStopQueueRequested) + { + [fCurrentJobGroup setStatus: HBStatusPending]; + [self updateJobGroupInQueue: fCurrentJobGroup]; + } + else + { + [fCurrentJobGroup setStatus:HBStatusComplete]; + [self updateJobGroupInQueue: fCurrentJobGroup]; + } + } + [self setCurrentJobGroupFromLibhbJob: currentJob]; + [self updateCurrentJobDescription]; + [self updateCurrentJobProgress]; + [self updateQueueCountField]; + [self showCurrentJobPane: fCurrentJobGroup != nil]; + if (fCurrentJobGroup) + [self startAnimatingCurrentJobGroupInQueue]; + else + [self stopAnimatingCurrentJobGroupInQueue]; + } + else + { + [self updateCurrentJobDescription]; + [self updateCurrentJobProgress]; + } +} + +//------------------------------------------------------------------------------------ +// Adds the individual jobs in a job group to libhb's list of jobs to process. +//------------------------------------------------------------------------------------ +- (void) libhbAddJobGroup: (HBJobGroup *) aJobGroup +{ + NSEnumerator * jobEnum = [aJobGroup jobEnumerator]; + HBJob * aJob; + while ( (aJob = [jobEnum nextObject]) ) + { + // Note that hb_add generates a copy of the libhb job. + hb_job_t * job_copy = hb_add (fHandle, [aJob libhbJob]); + [aJob setLibhbJob: job_copy]; + } +} + +//------------------------------------------------------------------------------------ +// Adds the individual jobs in the first pending job group to libhb's list of jobs to +// process. Return YES if a job group was added, NO if no pending job groups exist. +//------------------------------------------------------------------------------------ +- (BOOL) libhbAddNextJobGroup +{ + if ([self pendingCount] > 0) + { + HBJobGroup * aJobGroup = [self jobGroupWithStatus: HBStatusPending atIndex: 0]; + [self libhbAddJobGroup: aJobGroup]; + return YES; + } + else + return NO; +} + +@end // HBQueueController (LibhbInteractions) + +