Index: macosx/HBQueueController.h =================================================================== --- macosx/HBQueueController.h (revision 1055) +++ macosx/HBQueueController.h (working copy) @@ -9,41 +9,40 @@ #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. // -// (3) generate a drag image that incorporates more than just the first column +// (3) Generate a drag image that incorporates more than just the first column: // By default, NSTableView only drags an image of the first column. Change this to // drag an image of the queue's icon and desc columns. +// +// (4) Add suppport for contextual menu +// A delegate method is added that provides support for a per-column/row contextual +// menu. @interface HBQueueOutlineView : NSOutlineView { #if HB_QUEUE_DRAGGING -BOOL fIsDragging; + BOOL fIsDragging; #endif } #if HB_QUEUE_DRAGGING @@ -51,86 +50,38 @@ #endif @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; - +@interface NSObject(HBQueueOutlineViewDelegate) +- (NSMenu *) outlineView:(NSOutlineView *)outlineView menuForTableColumn:(NSTableColumn *)tableColumn item:(id)item; @end //------------------------------------------------------------------------------------ - -@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 - +// HBQueueController //------------------------------------------------------------------------------------ @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. + + unsigned int fPendingCount; // Number of various kinds of job groups in fJobGroups. + unsigned int fCompletedCount; // Don't access these directly as they may not always be up-to-date. + unsigned int fCanceledCount; // Use the accessor functions instead. + unsigned int fWorkingCount; + BOOL fJobGroupCountsNeedUpdating; + + NSAlert *fStopQueueAlert; // Alert asking user to confirm stoping of the queue. nil when no alert is displayed. + BOOL fStopQueueAlertDisplayed; + 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 @@ -171,19 +122,50 @@ } +// Initialization and setup - (void)setHandle: (hb_handle_t *)handle; - (void)setHBController: (HBController *)controller; -- (void)hblibJobListChanged; -- (void)hblibStateChanged: (hb_state_t &)state; -- (void)hblibWillStop; +// Notification from libhb +- (void)libhbStateChanged: (hb_state_t &)state; + +// Adding and removing items in the queue +- (void) addJobGroup: (HBJobGroup *) aJobGroup; +- (void) removeJobGroup: (HBJobGroup *) aJobGroup; + +// Getting the currently processing job group +- (HBJobGroup *) currentJobGroup; + +// Getting queue statistics +- (unsigned int) pendingCount; +- (unsigned int) completedCount; +- (unsigned int) canceledCount; +- (unsigned int) workingCount; + +- (BOOL) canStart; +- (BOOL) canStop; +- (BOOL) canPause; +- (BOOL) canResume; + +// Starting and stopping the queue +- (IBAction) startQueue: (id)sender; +- (IBAction) stopQueue: (id)sender; +- (void) stopQueue; +- (IBAction) pauseQueue: (id)sender; +- (IBAction) resumeQueue: (id)sender; + +// IB actions - (IBAction)showQueueWindow: (id)sender; - (IBAction)removeSelectedJobGroups: (id)sender; - (IBAction)revealSelectedJobGroups: (id)sender; - (IBAction)cancelCurrentJob: (id)sender; -- (IBAction)toggleStartCancel: (id)sender; -- (IBAction)togglePauseResume: (id)sender; +// Getting UI strings +- (NSString *) stateStringForState: (hb_state_t *)s; +- (NSString *) rateStringForState: (hb_state_t *)s; +- (NSString *) timeRemainingStringForState: (hb_state_t *)s; +- (NSString *) statusStringForState: (hb_state_t *)s; + #if HB_OUTLINE_METRIC_CONTROLS - (IBAction)imageSpacingChanged: (id)sender; - (IBAction)indentChanged: (id)sender; Index: macosx/Controller.mm =================================================================== --- macosx/Controller.mm (revision 1055) +++ 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 @@ -164,11 +166,11 @@ } // Warn if items still in the queue - else if ( hb_count( fHandle ) > 0 ) + else if ( [fQueueController pendingCount] > 0 ) { int result = NSRunCriticalAlertPanel( NSLocalizedString(@"Are you sure you want to quit HandBrake?", nil), - NSLocalizedString(@"One or more encodes are queued for encoding. Do you want to quit anyway?", nil), + NSLocalizedString(@"One or more encodes are queued. Do you want to quit anyway?", nil), NSLocalizedString(@"Quit", nil), NSLocalizedString(@"Don't Quit", nil), nil); if ( result == NSAlertDefaultReturn ) @@ -542,20 +544,9 @@ case HB_STATE_WORKING: { float progress_total; - NSMutableString * string; - /* Currently, p.job_cur and p.job_count get screwed up when adding - jobs during encoding, if they cannot be fixed in libhb, will implement a - nasty but working cocoa solution */ - /* Update text field */ - string = [NSMutableString stringWithFormat: _( @"Encoding: task %d of %d, %.2f %%" ), p.job_cur, p.job_count, 100.0 * p.progress]; - - if( p.seconds > -1 ) - { - [string appendFormat: - _( @" (%.2f fps, avg %.2f fps, ETA %02dh%02dm%02ds)" ), - p.rate_cur, p.rate_avg, p.hours, p.minutes, p.seconds]; - } - [fStatusField setStringValue: string]; + + /* Update status field */ + [fStatusField setStringValue: [fQueueController statusStringForState: &s]]; /* Update slider */ progress_total = ( p.progress + p.job_cur - 1 ) / p.job_count; @@ -585,7 +576,7 @@ // Has current job changed? That means the queue has probably changed as // well so update it - [fQueueController hblibStateChanged: s]; + [fQueueController libhbStateChanged: s]; break; } @@ -597,8 +588,7 @@ NSMutableString * string; /* Update text field */ - string = [NSMutableString stringWithFormat: - _( @"Muxing..." )]; + string = [NSMutableString stringWithFormat: _( @"Muxing..." )]; [fStatusField setStringValue: string]; /* Update slider */ @@ -609,7 +599,7 @@ [self UpdateDockIcon: 1.0]; // Pass along the info to HBQueueController - [fQueueController hblibStateChanged: s]; + [fQueueController libhbStateChanged: s]; break; } @@ -619,134 +609,54 @@ [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; } } /* Lets show the queue status here in the main window */ - int queue_count = hb_count( fHandle ); - if( queue_count ) + int queue_count = [fQueueController pendingCount]; + if( queue_count == 1) { - [fQueueStatus setStringValue: [NSString stringWithFormat: - @"%d pass%s in the queue", - queue_count, ( queue_count > 1 ) ? "es" : ""]]; + [fQueueStatus setStringValue: _( @"1 encode queued") ]; } + else if (queue_count > 1) + { + [fQueueStatus setStringValue: [NSString stringWithFormat: _( @"%d encodes queued" ), queue_count]]; + } else { [fQueueStatus setStringValue: @""]; @@ -918,7 +828,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"]; @@ -1019,9 +929,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 @@ -1029,6 +939,7 @@ clickContext:nil]; } + #pragma mark - #pragma mark Get New Source @@ -1258,7 +1169,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; @@ -1476,55 +1386,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; @@ -1565,8 +1437,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 @@ -1590,8 +1465,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 @@ -1600,42 +1478,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; } @@ -1658,20 +1585,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]; } } @@ -1685,155 +1612,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 1055) +++ 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 */; }; @@ -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; }; @@ -1551,7 +1573,7 @@ GCC_ENABLE_FIX_AND_CONTINUE = YES; GCC_GENERATE_DEBUGGING_SYMBOLS = YES; GCC_MODEL_TUNING = G5; - GCC_OPTIMIZATION_LEVEL = s; + GCC_OPTIMIZATION_LEVEL = 0; HEADER_SEARCH_PATHS = ../libhb; INSTALL_PATH = /usr/local/bin; LIBRARY_SEARCH_PATHS = ""; Index: macosx/Controller.h =================================================================== --- macosx/Controller.h (revision 1055) +++ macosx/Controller.h (working copy) @@ -174,9 +174,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; @@ -210,8 +207,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; @@ -219,26 +214,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; @@ -307,5 +288,6 @@ - (void)setupToolbar; - (void) remindUserOfSleepOrShutdown; +- (void) performPostProcessingTasks; @end Index: macosx/HBQueueController.mm =================================================================== --- macosx/HBQueueController.mm (revision 1055) +++ macosx/HBQueueController.mm (working copy) @@ -4,36 +4,26 @@ 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 // Pasteboard type for or drag operations #define HBQueuePboardType @"HBQueuePboardType" +// Button return codes passed to didEndSelector method of beginStopQueueAlertModalForWindow:::. +enum { + HBKeepEncodingButtonReturn = NSAlertFirstButtonReturn, + HBStopEncodingButtonReturn = NSAlertSecondButtonReturn +}; -//------------------------------------------------------------------------------------ -// NSMutableAttributedString (HBAdditions) -//------------------------------------------------------------------------------------ -@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 - +// HBQueueOutlineView //------------------------------------------------------------------------------------ @implementation HBQueueOutlineView @@ -79,703 +69,57 @@ } #endif -@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) +- (NSMenu *) menuForEvent:(NSEvent*)theEvent { - hb_job_t * job; - int count = 0; - int index = 0; - while( ( job = hb_job( h, index++ ) ) ) - { - if (job->sequence_id == 0) - count++; - } - return count; -} + NSPoint pt = [self convertPoint:[theEvent locationInWindow] fromView:nil]; + int row = [self rowAtPoint:pt]; + int column = [self columnAtPoint:pt]; -/** - * 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; -} + // Select the row being clicked. By default, right-clicks and control-clicks don't + // select the clicked on row. But we want that behavior. This code could be done + // in an override of rightMouseDown but that would not catch the case of a + // control-click. We can catch both those cases here in menuForEent. -/** - * 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]; + if (row == -1) + return [super menuForEvent:theEvent]; + else if (![self isRowSelected:row]) + [self selectRowIndexes:[NSIndexSet indexSetWithIndex:row] byExtendingSelection:NO]; - hb_title_t * title = hbJob->title; + // Ask delegate for a menu - // Attributes - static NSMutableParagraphStyle * ps = NULL; - if (!ps) + if (column >= 0 && row >= 0) { - 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]]; + id del = [self delegate]; + NSTableColumn *col = [[self tableColumns] objectAtIndex:column]; + if ([del respondsToSelector:@selector(outlineView:menuForTableColumn:item:)]) + return [del outlineView:self menuForTableColumn:col item:[self itemAtRow:row]]; } - - 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; + return nil; } @end #pragma mark - -//------------------------------------------------------------------------------------ -// HBJobGroup -//------------------------------------------------------------------------------------ +@interface HBQueueController (LibhbInteractions) -@implementation HBJobGroup +- (void) libhbCurrentJobChanged: (hb_job_t *) currentJob; +- (void) libhbAddJobGroup: (HBJobGroup *) aJobGroup; +- (BOOL) libhbAddNextJobGroup; -+ (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 +//------------------------------------------------------------------------------------ +// HBQueueController +//------------------------------------------------------------------------------------ // Toolbar identifiers static NSString* HBQueueToolbar = @"HBQueueToolbar1"; static NSString* HBQueueStartCancelToolbarIdentifier = @"HBQueueStartCancelToolbarIdentifier"; static NSString* HBQueuePauseResumeToolbarIdentifier = @"HBQueuePauseResumeToolbarIdentifier"; -#pragma mark - - @implementation HBQueueController //------------------------------------------------------------------------------------ @@ -793,11 +137,13 @@ 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"); + + // Register for HBJobGroup status changes + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(jobGroupStatusNotification:) name:HBJobGroupStatusNotification object:nil]; } return self; } @@ -811,12 +157,14 @@ if ([fQueueWindow delegate] == self) [fQueueWindow setDelegate:nil]; + [[NSNotificationCenter defaultCenter] removeObserver:self]; + [fJobGroups release]; - [fCompleted release]; [fCurrentJobGroup release]; [fSavedExpandedItems release]; [fSavedSelectedItems release]; - + [fStopQueueAlert release]; + [super dealloc]; } @@ -841,7 +189,6 @@ //------------------------------------------------------------------------------------ - (IBAction) showQueueWindow: (id)sender { - [self updateQueueUI]; [fQueueWindow makeKeyAndOrderFront: self]; [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"QueueWindowIsOpen"]; } @@ -892,222 +239,123 @@ fCurrentJobPaneShown = showPane; } +#pragma mark - +#pragma mark Queue Counts + //------------------------------------------------------------------------------------ -// Rebuilds the contents of fJobGroups which is a hierarchy of HBJobGroup and HBJobs. +// Sets a flag indicating that the values for fPendingCount, fCompletedCount, +// fCanceledCount, and fWorkingCount need to be recalculated. //------------------------------------------------------------------------------------ -- (void)rebuildJobGroups +- (void) setJobGroupCountsNeedUpdating: (BOOL)flag { - // Currently, job groups are rdered like this: - // Completed job groups - // Current job group - // Pending job groups - - [fJobGroups autorelease]; - fJobGroups = [[NSMutableArray arrayWithCapacity:0] retain]; + fJobGroupCountsNeedUpdating = flag; +} - // Add all the completed job groups - [fJobGroups addObjectsFromArray: fCompleted]; +//------------------------------------------------------------------------------------ +// Recalculates and stores new values in fPendingCount, fCompletedCount, +// fCanceledCount, and fWorkingCount. +//------------------------------------------------------------------------------------ +- (void) recalculateJobGroupCounts +{ + fPendingCount = 0; + fCompletedCount = 0; + fCanceledCount = 0; + fWorkingCount = 0; - // 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 ) + NSEnumerator * groupEnum = [fJobGroups objectEnumerator]; + HBJobGroup * aJobGroup; + while ( (aJobGroup = [groupEnum nextObject]) ) { - if (nextJob->sequence_id == 0) + switch ([aJobGroup status]) { - // 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]; - } + case HBStatusNone: + // We don't track these. + break; + case HBStatusPending: + fPendingCount++; + break; + case HBStatusCompleted: + fCompletedCount++; + break; + case HBStatusCanceled: + fCanceledCount++; + break; + case HBStatusWorking: + fWorkingCount++; + break; } - [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]; - } + fJobGroupCountsNeedUpdating = NO; } //------------------------------------------------------------------------------------ -// Adds aJobGroup to the list of completed job groups, marking its status as -// HBStatusComplete. +// Returns the number of job groups whose status is HBStatusPending. //------------------------------------------------------------------------------------ -- (void) addCompletedJobGroup: (HBJobGroup *)aJobGroup +- (unsigned int) pendingCount { - // 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]; + if (fJobGroupCountsNeedUpdating) + [self recalculateJobGroupCounts]; + return fPendingCount; } -- (void) setCurrentJobGroupFromJob: (hb_job_t *)aJob +//------------------------------------------------------------------------------------ +// Returns the number of job groups whose status is HBStatusComplete. +//------------------------------------------------------------------------------------ +- (unsigned int) completedCount { - HBJobGroup * aJobGroup = nil; - - // Try to find this new group in our existing job groups. - if (aJob) - { - BOOL found = NO; - NSEnumerator * groupEnum = [fJobGroups objectEnumerator]; - while ( !found && (aJobGroup = [groupEnum nextObject]) ) - { - HBJob * j; - NSEnumerator * jobEnum = [aJobGroup jobEnumerator]; - while ( !found && (j = [jobEnum nextObject]) ) - { - if ([j job] == aJob) - found = YES; - } - } - - // Or create the job group. - if (!aJobGroup) - { - aJobGroup = [HBJobGroup jobGroup]; - [aJobGroup addJob: [HBJob jobWithJob: aJob]]; - [aJobGroup setPath: [NSString stringWithUTF8String:aJob->file]]; - while ( (aJob = hb_next_job(fHandle, aJob)) && (aJob->sequence_id != 0) ) - [aJobGroup addJob: [HBJob jobWithJob: aJob]]; + if (fJobGroupCountsNeedUpdating) + [self recalculateJobGroupCounts]; + return fCompletedCount; +} - [aJobGroup updateDescriptionWithHBHandle: fHandle]; - } - - [aJobGroup setStatus: HBStatusWorking]; - } - - [aJobGroup retain]; - [fCurrentJobGroup release]; - fCurrentJobGroup = aJobGroup; +//------------------------------------------------------------------------------------ +// Returns the number of job groups whose status is HBStatusCanceled. +//------------------------------------------------------------------------------------ +- (unsigned int) canceledCount +{ + if (fJobGroupCountsNeedUpdating) + [self recalculateJobGroupCounts]; + return fCanceledCount; } +//------------------------------------------------------------------------------------ +// Returns the number of job groups whose status is HBStatusWorking. +//------------------------------------------------------------------------------------ +- (unsigned int) workingCount +{ + if (fJobGroupCountsNeedUpdating) + [self recalculateJobGroupCounts]; + return fWorkingCount; +} + #pragma mark - #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 +364,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]; } } @@ -1156,31 +401,28 @@ } //------------------------------------------------------------------------------------ -// Generate string to display in UI. +// Returns a string expressing the state of the current job. +// Examples: +// Encoding movie +// Muxing +// Done //------------------------------------------------------------------------------------ -- (NSString *) progressStatusStringForJob: (hb_job_t *)job state: (hb_state_t *)s +- (NSString *) stateStringForState: (hb_state_t *)s { if (s->state == HB_STATE_WORKING) { - NSString * msg; - if (job->pass == -1) - msg = NSLocalizedString( @"Deep Scan", nil ); + hb_job_t * job = fHandle ? hb_current_job(fHandle) : nil; + + if (!job) + return @""; // 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) + return NSLocalizedString( @"Deep Scan", nil ); else if (job->pass == 1) - msg = NSLocalizedString( @"Analyzing video", nil ); + return NSLocalizedString( @"Analyzing video", nil ); else if ((job->pass == 0) || (job->pass == 2)) - msg = NSLocalizedString( @"Encoding movie", nil ); + return NSLocalizedString( @"Encoding movie", nil ); else return @""; // unknown condition! - - if( s->param.working.seconds > -1 ) - { - return [NSString stringWithFormat: - NSLocalizedString( @"%@ (%.2f fps, avg %.2f fps)", nil ), - msg, s->param.working.rate_cur, s->param.working.rate_avg]; - } - else - return msg; - } else if (s->state == HB_STATE_MUXING) @@ -1196,12 +438,38 @@ } //------------------------------------------------------------------------------------ -// Generate string to display in UI. +// Returns a string expressing the rate of the current job. +// Example: +// 27.55 fps, avg 22.32 fps //------------------------------------------------------------------------------------ -- (NSString *) progressTimeRemainingStringForJob: (hb_job_t *)job state: (hb_state_t *)s +- (NSString *) rateStringForState: (hb_state_t *)s { if (s->state == HB_STATE_WORKING) { + if( s->param.working.seconds > -1 ) + { + return [NSString stringWithFormat: + NSLocalizedString( @"%.2f fps, avg %.2f fps", nil ), + s->param.working.rate_cur, s->param.working.rate_avg]; + } + else + return @""; + } + + return @""; +} + +//------------------------------------------------------------------------------------ +// Returns a string representation of the time remaining as specified in a hb_state_t. +// Examples: +// 2 hours 15 minutes remaining +// 1 minute 15 seconds remaining +// 15 seconds remaining +//------------------------------------------------------------------------------------ +- (NSString *) timeRemainingStringForState: (hb_state_t *)s +{ + if (s->state == HB_STATE_WORKING) + { #define p s->param.working if (p.seconds < 0) return @""; @@ -1268,15 +536,34 @@ } //------------------------------------------------------------------------------------ +// Returns a string expressing the status of the current job being processed. +// Examples: +// Encoding movie (27.55 fps, avg 22.32 fps) - 1 hour 15 minutes remaining +// Muxing +// Paused +// Done +//------------------------------------------------------------------------------------ +- (NSString *) statusStringForState: (hb_state_t *)s +{ + NSString * actionStr = [self stateStringForState:s]; + NSString * rateStr = [self rateStringForState:s]; + NSString * timeStr = [self timeRemainingStringForState:s]; + + NSMutableString * str = [NSMutableString stringWithCapacity:0]; + [str setString:actionStr]; + if ([rateStr length] > 0) + [str appendFormat:@" (%@)", rateStr]; + if ([timeStr length] > 0) + [str appendFormat:@" - %@", timeStr]; + return str; +} + +//------------------------------------------------------------------------------------ // Refresh progress bar (fProgressTextField) from current state. //------------------------------------------------------------------------------------ -- (void) updateProgressTextForJob: (hb_job_t *)job state: (hb_state_t *)s +- (void) updateProgressTextWithState: (hb_state_t *)s { - NSString * statusMsg = [self progressStatusStringForJob:job state:s]; - NSString * timeMsg = [self progressTimeRemainingStringForJob:job state:s]; - if ([timeMsg length] > 0) - statusMsg = [NSString stringWithFormat:@"%@ - %@", statusMsg, timeMsg]; - [fProgressTextField setStringValue:statusMsg]; + [fProgressTextField setStringValue:[self statusStringForState: s]]; } //------------------------------------------------------------------------------------ @@ -1318,13 +605,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 +634,7 @@ if (job) { - HBJob * currentJob = [HBJob jobWithJob: job]; + HBJob * currentJob = [HBJob jobWithLibhbJob: job]; switch (job->pass) { case -1: // Subtitle scan @@ -1401,6 +696,7 @@ withx264Info: YES withAudioInfo: YES withSubtitleInfo: YES]]; + break; } } @@ -1414,21 +710,94 @@ //------------------------------------------------------------------------------------ - (void)updateCurrentJobProgress { - hb_job_t * job = fHandle ? hb_current_job(fHandle) : nil; hb_state_t s; hb_get_state2( fHandle, &s ); - [self updateProgressTextForJob: job state: &s]; + [self updateProgressTextWithState:&s]; [self updateProgressBarWithState:&s]; } //------------------------------------------------------------------------------------ -// 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 +{ + [self setJobGroupCountsNeedUpdating:YES]; [fOutlineView noteNumberOfRowsChanged]; [fOutlineView reloadData]; [self restoreOutlineViewState]; @@ -1436,15 +805,223 @@ } #pragma mark - + +//------------------------------------------------------------------------------------ +// Displays a modal sheet window asking the user if they want to stop processing of +// the queue. The didEndSelector method is invoked after the modal session has ended +// and is passed a button return code and the caller specified contextInfo. The button +// return values passed to didEndSelector are: +// HBKeepEncodingButtonReturn +// HBStopEncodingButtonReturn +// The didEndSelector method MUST set fStopQueueAlertDisplayed to NO. To dismiss the +// alert prior to the user taking action, call endStopQueueAlert. +//------------------------------------------------------------------------------------ +- (void) beginStopQueueAlertModalForWindow:(NSWindow *)docWindow + didEndSelector:(SEL)didEndSelector + contextInfo:(void *)contextInfo +{ + NSAssert (didEndSelector != nil, @"A didEndSelector must be specified in beginStopQueueAlertModalForWindow"); + + // Create fStopQueueAlert the first time around. Subsequently, reuse the alert. + if (!fStopQueueAlert) + { + fStopQueueAlert = [[NSAlert alloc] init]; + [fStopQueueAlert setAlertStyle: NSCriticalAlertStyle]; + [fStopQueueAlert addButtonWithTitle: NSLocalizedString(@"Keep Encoding", nil)]; + [fStopQueueAlert addButtonWithTitle: NSLocalizedString(@"Stop Encoding", nil)]; + [fStopQueueAlert setInformativeText: NSLocalizedString(@"Your partially encoded movie may not be usable if you stop encoding.", nil)]; + } + + [fStopQueueAlert setMessageText: [NSString stringWithFormat:NSLocalizedString(@"Stop encoding %@?", nil), [fCurrentJobGroup name]]]; + + if (!fStopQueueAlertDisplayed) + { + [fStopQueueAlert beginSheetModalForWindow:docWindow modalDelegate:self didEndSelector:didEndSelector contextInfo:contextInfo]; + fStopQueueAlertDisplayed = YES; + } + else + { + NSBeep(); + [[fStopQueueAlert window] makeKeyAndOrderFront: nil]; + } + +} + +//------------------------------------------------------------------------------------ +// If the alert created by a previous call to beginStopQueueAlertModalForWindow::: is +// currently displayed, it is dismissed. Use this method to abort the alert prior to +// the user taking any action. +//------------------------------------------------------------------------------------ +- (void) endStopQueueAlert +{ + if (fStopQueueAlertDisplayed) + { + [NSApp endSheet:[fStopQueueAlert window] returnCode: HBKeepEncodingButtonReturn]; + fStopQueueAlertDisplayed = NO; + } +} + +#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); + } + } +} + +//------------------------------------------------------------------------------------ +// Advices the queue that it should stop processing jobs. If the queue is already +// stopped, nothing happens. Otherwise, the request is scheduled and control is +// returned immediately to the caller. Some time after stopQueue is called, the +// current job group will be canceled (it's state set to HBStatusCanceled) and the +// queue will stop processing any further jobs. +//------------------------------------------------------------------------------------ +- (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)) + { + // Set a flag so that we know this was a request to cancel the current job. + fStopQueueRequested = YES; + // Tell libhb to stop. We'll receive a HB_STATE_WORKDONE message when is has + // finished stopping. + 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(didEndStopQueueAlert:returnCode:contextInfo:) contextInfo:nil]; + + // didEndStopQueueAlert: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) didEndStopQueueAlert:(NSAlert *)alert returnCode:(int)returnCode contextInfo:(void *)contextInfo +{ + if (returnCode == HBStopEncodingButtonReturn) + [self stopQueue]; + fStopQueueAlertDisplayed = NO; +} + + +//------------------------------------------------------------------------------------ +// 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 +- (IBAction) removeSelectedJobGroups: (id)sender { if (!fHandle) return; + 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. didEndStopQueueAndRemoveSelected:::: + // will be called after the user responds to the alert. selectedRows is passed as + // context info so that didEndStopQueueAndRemoveSelected: knows what to delete. + if ([groupsToDelete containsObject:fCurrentJobGroup]) + { + [self beginStopQueueAlertModalForWindow: fQueueWindow didEndSelector:@selector(didEndStopQueueAndRemoveSelected:returnCode:contextInfo:) contextInfo:selectedRows]; + } + else + { + [self beginEditingJobGroupsArray]; + [fJobGroups removeObjectsAtIndexes: selectedRows]; + [selectedRows release]; + [self endEditingJobGroupsArray]; + } +} + +- (void) didEndStopQueueAndRemoveSelected:(NSAlert *)alert returnCode: (int)returnCode contextInfo: (void *)contextInfo +{ + if (returnCode == HBStopEncodingButtonReturn) + { + [self stopQueue]; + + NSIndexSet * selectedRows = (NSIndexSet*)contextInfo; + [self beginEditingJobGroupsArray]; + [fJobGroups removeObjectsAtIndexes: selectedRows]; + [selectedRows release]; + [self endEditingJobGroupsArray]; + } + fStopQueueAlertDisplayed = NO; +} + + +//------------------------------------------------------------------------------------ +// Reveals the file icons in the Finder of the selected job groups. +//------------------------------------------------------------------------------------ +- (IBAction) revealSelectedJobGroups: (id)sender +{ + if (!fHandle) return; + NSIndexSet * selectedRows = [fOutlineView selectedRowIndexes]; int row = [selectedRows firstIndex]; if (row != NSNotFound) @@ -1452,34 +1029,18 @@ 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; - } + if ([[jobGroup path] length]) + [[NSWorkspace sharedWorkspace] selectFile:[jobGroup path] inFileViewerRootedAtPath:nil]; row = [selectedRows indexGreaterThanIndex: row]; } - - [self hblibJobListChanged]; } } //------------------------------------------------------------------------------------ -// Reveals the file icons in the Finder of the selected job groups. +// Chnages the status of any selected anc canceled job groups to pending. //------------------------------------------------------------------------------------ -- (IBAction)revealSelectedJobGroups: (id)sender +- (IBAction) uncancelSelectedJobGroups: (id)sender { if (!fHandle) return; @@ -1490,9 +1051,8 @@ while (row != NSNotFound) { HBJobGroup * jobGroup = [fOutlineView itemAtRow: row]; - if ([[jobGroup path] length]) - [[NSWorkspace sharedWorkspace] selectFile:[jobGroup path] inFileViewerRootedAtPath:nil]; - + if ([jobGroup status] == HBStatusCanceled) + [jobGroup setStatus: HBStatusPending]; row = [selectedRows indexGreaterThanIndex: row]; } } @@ -1504,110 +1064,159 @@ // the alert. Later, when the user acknowledges the alert, HBController will call // hblib to cancel the job. //------------------------------------------------------------------------------------ -- (IBAction)cancelCurrentJob: (id)sender +// ************ is this necessary? YES, currently however cancelCurrentJob is the same +// as stopQueue. It cancels and stops teh whole queue. At one point, canceling the +// current job did not stop the rest of the queue. +- (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 job group currently being processed. +//------------------------------------------------------------------------------------ +- (HBJobGroup *) currentJobGroup +{ + return fCurrentJobGroup; } -#pragma mark - -#pragma mark Synchronizing with hblib - //------------------------------------------------------------------------------------ -// Notifies HBQueueController that hblib's current job has changed +// Returns the job group located at index in the list of job groups with status. If +// index is greater than or equal to the number of job groups with the specified +// status, nil is returned. //------------------------------------------------------------------------------------ -- (void)currentJobGroupChanged: (hb_job_t *) currentJob +- (HBJobGroup *) jobGroupWithStatus: (HBQueueJobGroupStatus)status atIndex: (unsigned int)index { - 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]; + 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; } +#pragma mark - +#pragma mark Debugging + +#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 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. +// Receives notification whenever an HBJobGroup's status is changed. //------------------------------------------------------------------------------------ -- (void)hblibWillStop +- (void) jobGroupStatusNotification:(NSNotification *)notification { - if (fCurrentJobGroup) - [fCurrentJobGroup setStatus: HBStatusCanceled]; + [self setJobGroupCountsNeedUpdating: YES]; +// HBQueueJobGroupStatus oldStatus = (HBQueueJobGroupStatus) [[[notification userInfo] objectForKey:@"HBOldJobGroupStatus"] intValue]; + HBJobGroup * jobGroup = [notification object]; + if (jobGroup) + [self updateJobGroupInQueue:jobGroup]; } +#pragma mark - + //------------------------------------------------------------------------------------ -// Notifies HBQueueController that hblib's job list has been modified +// Sets a new fCurrentJobGroup by locating the HBJobGroup associated with a libhb +// hb_job_t. //------------------------------------------------------------------------------------ -- (void)hblibJobListChanged +- (void) setCurrentJobGroupFromLibhbJob: (hb_job_t *)aJob { - // 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]; + HBJobGroup * aJobGroup = nil; + + // Try to find this new group in our existing job groups. + if (aJob) + { + BOOL found = NO; + NSEnumerator * groupEnum = [fJobGroups objectEnumerator]; + while ( !found && (aJobGroup = [groupEnum nextObject]) ) + { + HBJob * j; + NSEnumerator * jobEnum = [aJobGroup jobEnumerator]; + while ( !found && (j = [jobEnum nextObject]) ) + { + if ([j libhbJob] == aJob) + found = YES; + } + } + + NSAssert (aJobGroup != nil, @"HBQueueController could not find current libhb job."); + + [aJobGroup setStatus: HBStatusWorking]; + } + + [aJobGroup retain]; + [fCurrentJobGroup release]; + fCurrentJobGroup = aJobGroup; } //------------------------------------------------------------------------------------ // 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,27 +1244,46 @@ 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. + + // Close the "stop queue" alert if the user had caused it to be invoked. + [self endStopQueueAlert]; - } + // 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 -#if HB_OUTLINE_METRIC_CONTROLS -static float spacingWidth = 3.0; -- (IBAction)imageSpacingChanged: (id)sender; -{ - spacingWidth = [sender floatValue]; - [fOutlineView setNeedsDisplay: YES]; + 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; + } + break; + } + } } -- (IBAction)indentChanged: (id)sender -{ - [fOutlineView setIndentationPerLevel: [sender floatValue]]; - [fOutlineView setNeedsDisplay: YES]; -} -#endif #pragma mark - @@ -1702,13 +1330,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 +1350,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; @@ -1764,6 +1392,34 @@ nil]; } +- (BOOL) canStart +{ + if (!fCurrentJobGroup && ([self pendingCount] > 0)) + return YES; + else + return NO; +} + +- (BOOL) canStop +{ + return (fCurrentJobGroup != nil); +} + +- (BOOL) canPause +{ + hb_state_t s; + hb_get_state2 (fHandle, &s); + return fCurrentJobGroup && ((s.state == HB_STATE_WORKING) || (s.state == HB_STATE_MUXING)); +} + +- (BOOL) canResume +{ + hb_state_t s; + hb_get_state2 (fHandle, &s); + return (s.state == HB_STATE_PAUSED); +} + + //------------------------------------------------------------------------------------ // validateToolbarItem: //------------------------------------------------------------------------------------ @@ -1776,25 +1432,96 @@ BOOL enable = NO; - hb_state_t s; - hb_get_state2 (fHandle, &s); + if ([[toolbarItem itemIdentifier] isEqual: HBQueueStartCancelToolbarIdentifier]) + { + if ([self canStop]) + { + enable = YES; + [toolbarItem setImage:[NSImage imageNamed: @"Stop"]]; + [toolbarItem setLabel: @"Stop"]; + [toolbarItem setToolTip: @"Stop Encoding"]; + [toolbarItem setTarget: self]; + [toolbarItem setAction: @selector(stopQueue:)]; + } + + else if ([self canStart]) + { + enable = YES; + [toolbarItem setImage:[NSImage imageNamed: @"Play"]]; + [toolbarItem setLabel: @"Start"]; + [toolbarItem setToolTip: @"Start Encoding"]; + [toolbarItem setTarget: self]; + [toolbarItem setAction: @selector(startQueue:)]; + } + + else + { + enable = NO; + [toolbarItem setImage:[NSImage imageNamed: @"Play"]]; + [toolbarItem setLabel: @"Start"]; + [toolbarItem setToolTip: @"Start Encoding"]; + } + + } + + if ([[toolbarItem itemIdentifier] isEqual: HBQueuePauseResumeToolbarIdentifier]) + { + if ([self canResume]) + { + enable = YES; + [toolbarItem setImage:[NSImage imageNamed: @"Play"]]; + [toolbarItem setLabel: @"Resume"]; + [toolbarItem setToolTip: @"Resume Encoding"]; + [toolbarItem setTarget: self]; + [toolbarItem setAction: @selector(resumeQueue:)]; + } + + else if ([self canPause]) + { + enable = YES; + [toolbarItem setImage:[NSImage imageNamed: @"Pause"]]; + [toolbarItem setLabel: @"Pause"]; + [toolbarItem setToolTip: @"Pause Encoding"]; + [toolbarItem setTarget: self]; + [toolbarItem setAction: @selector(pauseQueue:)]; + } + + else + { + enable = NO; + [toolbarItem setImage:[NSImage imageNamed: @"Pause"]]; + [toolbarItem setLabel: @"Pause"]; + [toolbarItem setToolTip: @"Pause Encoding"]; + } + } + + return enable; + + + + + 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 +1535,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 +1554,9 @@ [toolbarItem setImage:[NSImage imageNamed: @"Pause"]]; [toolbarItem setLabel: @"Pause"]; [toolbarItem setToolTip: @"Pause Encoding"]; - } + [toolbarItem setTarget: self]; + [toolbarItem setAction: @selector(pauseQueue:)]; + } else { enable = NO; @@ -1982,8 +1716,12 @@ float width = [[outlineView tableColumnWithIdentifier: @"desc"] width]; // Column width is NOT what is ultimately used - width -= 47; // 26 pixels for disclosure triangle, 20 for icon, 1 for intercell spacing - + long sysVers; + Gestalt( gestaltSystemVersion, &sysVers ); + if (sysVers < 0x1050) + width -= 47; // 26 pixels for disclosure triangle, 20 for icon, 1 for intercell spacing + else + width -= 1; // 1 for intercell spacing float height = [item heightOfDescriptionForWidth: width withHBHandle: fHandle]; return height; } @@ -2002,12 +1740,15 @@ { switch ([(HBJobGroup*)item status]) { - case HBStatusComplete: + case HBStatusCompleted: return [NSImage imageNamed:@"EncodeComplete"]; break; case HBStatusWorking: return [NSImage imageNamed: [NSString stringWithFormat: @"EncodeWorking%d", fAnimationIndex]]; break; + case HBStatusCanceled: + return [NSImage imageNamed:@"EncodeCanceled"]; + break; default: return [NSImage imageNamed:@"JobSmall"]; break; @@ -2038,7 +1779,7 @@ { [cell setEnabled: YES]; BOOL highlighted = [outlineView isRowSelected:[outlineView rowForItem: item]] && [[outlineView window] isKeyWindow] && ([[outlineView window] firstResponder] == outlineView); - if ([(HBJobGroup*)item status] == HBStatusComplete) + if ([(HBJobGroup*)item status] == HBStatusCompleted) { [cell setAction: @selector(revealSelectedJobGroups:)]; if (highlighted) @@ -2077,7 +1818,7 @@ #pragma mark NSOutlineView delegate (dragging related) //------------------------------------------------------------------------------------ -// NSTableView delegate +// NSOutlineView delegate //------------------------------------------------------------------------------------ #if HB_QUEUE_DRAGGING @@ -2093,13 +1834,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 +1864,7 @@ } // Prevent dragging into the completed or current job. - int firstPendingIndex = [fCompleted count]; + int firstPendingIndex = [fJobGroups count] - [self pendingCount]; if (fCurrentJobGroup) firstPendingIndex++; index = MAX (index, firstPendingIndex); @@ -2145,14 +1887,125 @@ } // Rearrange the data and view - [self saveOutlineViewState]; + [self beginEditingJobGroupsArray]; [self moveObjectsInArray:fJobGroups fromIndexes:moveItems toIndex: index]; - [fOutlineView reloadData]; - [self restoreOutlineViewState]; + [self endEditingJobGroupsArray]; return YES; } #endif +#pragma mark - +#pragma mark HBQueueOutlineViewDelegate +//------------------------------------------------------------------------------------ +// HBQueueOutlineViewDelegate +//------------------------------------------------------------------------------------ + +- (NSMenu *) outlineView:(NSOutlineView *)outlineView menuForTableColumn:(NSTableColumn *)tableColumn item:(id)item +{ + NSIndexSet * selectedRows = [outlineView selectedRowIndexes]; + NSArray * selectedGroups = [fJobGroups objectsAtIndexes:selectedRows]; + HBJobGroup * jobGroup = (HBJobGroup*) item; + + NSMenu * menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; + + int numSelected = [[fOutlineView selectedRowIndexes] count]; + if (numSelected == 0) + return nil; + + // If current job group is selected, add a "Stop Encoding" menu item + if ([selectedGroups containsObject:fCurrentJobGroup]) + [menu addItemWithTitle:[NSString stringWithFormat:@"Stop Encoding %@", [fCurrentJobGroup name]] action:@selector(cancelCurrentJob:) keyEquivalent:@""]; + + // Add a "Remove" menu item + if (numSelected == 1) + [menu addItemWithTitle:[NSString stringWithFormat:@"Remove %@", [jobGroup name]] action:@selector(removeSelectedJobGroups:) keyEquivalent:@""]; + else + [menu addItemWithTitle:[NSString stringWithFormat:@"Remove %d Items", numSelected] action:@selector(removeSelectedJobGroups:) keyEquivalent:@""]; + + // Add a "Show in Finder" menu item + [menu addItemWithTitle:@"Show in Finder" action:@selector(revealSelectedJobGroups:) keyEquivalent:@""]; + + // Add a "Try Again" menu item + if ((numSelected == 1) && ([jobGroup status] == HBStatusCanceled)) + [menu addItemWithTitle:@"Try Again" action:@selector(uncancelSelectedJobGroups:) keyEquivalent:@""]; + + return menu; +} + @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 the user canceled, set status to canceled, else complete. + if (fStopQueueRequested) + [fCurrentJobGroup setStatus: HBStatusCanceled]; + else + [fCurrentJobGroup setStatus:HBStatusCompleted]; + } + [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) + +