diff options
author | Damiano Galassi <[email protected]> | 2015-10-19 17:57:49 +0200 |
---|---|---|
committer | Damiano Galassi <[email protected]> | 2015-10-19 17:57:49 +0200 |
commit | 333a81d49982c504bbc3ba7bcb88787442a3a8c4 (patch) | |
tree | 2136c41bfb66fd26578b5305f39ec9a6637e9282 /macosx/HBQueueController.m | |
parent | e5b59be057da16e782d970e41299d78beef9e878 (diff) |
MacGui: improved queue with undo/redo support for adding/removing jobs, and added a icon for failed encode.
Diffstat (limited to 'macosx/HBQueueController.m')
-rw-r--r-- | macosx/HBQueueController.m | 834 |
1 files changed, 512 insertions, 322 deletions
diff --git a/macosx/HBQueueController.m b/macosx/HBQueueController.m index 7ae19da3e..5c99507b0 100644 --- a/macosx/HBQueueController.m +++ b/macosx/HBQueueController.m @@ -1,4 +1,3 @@ - /* HBQueueController This file is part of the HandBrake source code. @@ -34,8 +33,6 @@ // DockTile update freqency in total percent increment #define dockTileUpdateFrequency 0.1f -#define HB_ROW_HEIGHT_TITLE_ONLY 17.0 - @interface HBQueueController () <NSOutlineViewDataSource, HBQueueOutlineViewDelegate> @property (nonatomic, readonly) HBDockTile *dockTile; @@ -48,24 +45,27 @@ @property (nonatomic, readonly) NSMutableDictionary *descriptions; @property (nonatomic, readonly) HBDistributedArray *jobs; -@property (nonatomic, strong) HBJob *currentJob; -@property (nonatomic, strong) HBJobOutputFileWriter *currentLog; +@property (nonatomic) HBJob *currentJob; +@property (nonatomic) HBJobOutputFileWriter *currentLog; @property (nonatomic, readwrite) BOOL stop; @property (nonatomic, readwrite) NSUInteger pendingItemsCount; @property (nonatomic, readwrite) NSUInteger completedItemsCount; -@property (nonatomic, strong) NSArray *dragNodesArray; +@property (nonatomic) NSArray *dragNodesArray; @end @implementation HBQueueController -- (instancetype)init +- (instancetype)initWithURL:(NSURL *)queueURL; { + NSParameterAssert(queueURL); + if (self = [super initWithWindowNibName:@"Queue"]) { + // Cached queue items descriptions _descriptions = [[NSMutableDictionary alloc] init]; // Load the dockTile and instiante initial text fields @@ -77,7 +77,9 @@ // Init a separate instance of libhb for the queue _core = [[HBCore alloc] initWithLogLevel:loggingLevel name:@"QueueCore"]; - [self loadQueueFile]; + // Load the queue from disk. + _jobs = [[HBDistributedArray alloc] initWithURL:queueURL]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadQueue) name:HBDistributedArrayChanged object:_jobs]; } return self; @@ -95,11 +97,7 @@ [self.outlineView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES]; [self.outlineView setVerticalMotionCanBeginDrag:YES]; - // Don't allow autoresizing of main column, else the "delete" column will get - // pushed out of view. - [self.outlineView setAutoresizesOutlineColumn: NO]; - - [self getQueueStats]; + [self updateQueueStats]; } #pragma mark Toolbar @@ -207,53 +205,157 @@ return NO; } -#pragma mark - -#pragma mark Queue File +#pragma mark - Public methods -- (void)reloadQueue +- (void)addJob:(HBJob *)item { - [self getQueueStats]; - [self.outlineView reloadData]; + NSParameterAssert(item); + [self addJobsFromArray:@[item]]; } -- (void)loadQueueFile +- (void)addJobsFromArray:(NSArray *)items; { - NSURL *queueURL = [[HBUtilities appSupportURL] URLByAppendingPathComponent:@"Queue.hbqueue"]; - _jobs = [[HBDistributedArray alloc] initWithURL:queueURL]; + NSParameterAssert(items); + [self addQueueItems:items]; +} - [self reloadQueue]; +- (BOOL)jobExistAtURL:(NSURL *)url +{ + NSParameterAssert(url); - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadQueue) name:HBDistributedArrayChanged object:_jobs]; + for (HBJob *item in self.jobs) + { + if ([item.destURL isEqualTo:url]) + { + return YES; + } + } + return NO; } -- (void)addJob:(HBJob *)item +- (NSUInteger)count +{ + return self.jobs.count; +} + +/** + * This method will clear the queue of any encodes that are not still pending + * this includes both successfully completed encodes as well as cancelled encodes + */ +- (void)removeCompletedJobs { [self.jobs beginTransaction]; - [self.jobs addObject:item]; + NSIndexSet *indexes = [self.jobs indexesOfObjectsUsingBlock:^BOOL(HBJob *item) { + return (item.state == HBJobStateCompleted || item.state == HBJobStateCanceled); + }]; + [self removeQueueItemsAtIndexes:indexes]; [self.jobs commit]; +} - [self reloadQueue]; +/** + * This method will clear the queue of all encodes. effectively creating an empty queue + */ +- (void)removeAllJobs +{ + [self.jobs beginTransaction]; + [self removeQueueItemsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.jobs.count)]]; + [self.jobs commit]; } -- (void)addJobsFromArray:(NSArray *)items; +/** + * This method will set any item marked as encoding back to pending + * currently used right after a queue reload + */ +- (void)setEncodingJobsAsPending { [self.jobs beginTransaction]; - [self.jobs addObjectsFromArray:items]; + + NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; + NSUInteger idx = 0; + for (HBJob *job in self.jobs) + { + // We want to keep any queue item that is pending or was previously being encoded + if (job.state == HBJobStateWorking) + { + job.state = HBJobStateReady; + [indexes addIndex:idx]; + } + idx++; + } + [self reloadQueueItemsAtIndexes:indexes]; [self.jobs commit]; +} + +#pragma mark - Private queue editing methods + +/** + * Reloads the queue, this is called + * when another HandBrake instances modifies the queue + */ +- (void)reloadQueue +{ + [self updateQueueStats]; + [self.outlineView reloadData]; + [self.window.undoManager removeAllActions]; +} + +- (void)reloadQueueItemAtIndex:(NSUInteger)idx +{ + [self reloadQueueItemsAtIndexes:[NSIndexSet indexSetWithIndex:idx]]; +} - [self reloadQueue]; +- (void)reloadQueueItemsAtIndexes:(NSIndexSet *)indexes +{ + [self.outlineView reloadDataForRowIndexes:indexes columnIndexes:[NSIndexSet indexSetWithIndex:0]]; + [self updateQueueStats]; } -- (BOOL)jobExistAtURL:(NSURL *)url +- (void)addQueueItems:(NSArray *)items { - for (HBJob *item in self.jobs) + NSParameterAssert(items); + NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(self.jobs.count, items.count)]; + [self addQueueItems:items atIndexes:indexes]; +} + +- (void)addQueueItems:(NSArray *)items atIndexes:(NSIndexSet *)indexes +{ + NSParameterAssert(items); + NSParameterAssert(indexes); + [self.jobs beginTransaction]; + [self.outlineView beginUpdates]; + + // Forward + NSUInteger currentIndex = indexes.firstIndex; + NSUInteger currentObjectIndex = 0; + while (currentIndex != NSNotFound) { - if ([item.destURL isEqualTo:url]) + [self.jobs insertObject:items[currentObjectIndex] atIndex:currentIndex]; + currentIndex = [indexes indexGreaterThanIndex:currentIndex]; + currentObjectIndex++; + } + + [self.outlineView insertItemsAtIndexes:indexes + inParent:nil + withAnimation:NSTableViewAnimationSlideDown]; + + NSUndoManager *undo = self.window.undoManager; + [[undo prepareWithInvocationTarget:self] removeQueueItemsAtIndexes:indexes]; + + if (!undo.isUndoing) + { + if (items.count == 1) { - return YES; + [undo setActionName:NSLocalizedString(@"Add Job To Queue", nil)]; + } + else + { + [undo setActionName:NSLocalizedString(@"Add Jobs To Queue", nil)]; } } - return NO; + + [self.outlineView endUpdates]; + [self updateQueueStats]; + [self.jobs commit]; } - (void)removeQueueItemAtIndex:(NSUInteger)index @@ -263,16 +365,138 @@ - (void)removeQueueItemsAtIndexes:(NSIndexSet *)indexes { - if (self.jobs.count > [indexes lastIndex]) + NSParameterAssert(indexes); + + if (indexes.count == 0) + { + return; + } + + [self.jobs beginTransaction]; + [self.outlineView beginUpdates]; + + NSArray *removeJobs = [self.jobs objectsAtIndexes:indexes]; + + if (self.jobs.count > indexes.lastIndex) { [self.jobs removeObjectsAtIndexes:indexes]; } + + [self.outlineView removeItemsAtIndexes:indexes inParent:nil withAnimation:NSTableViewAnimationSlideUp]; + [self.outlineView selectRowIndexes:[NSIndexSet indexSetWithIndex:indexes.firstIndex] byExtendingSelection:NO]; + + NSUndoManager *undo = self.window.undoManager; + [[undo prepareWithInvocationTarget:self] addQueueItems:removeJobs atIndexes:indexes]; + + if (!undo.isUndoing) + { + if (indexes.count == 1) + { + [undo setActionName:NSLocalizedString(@"Remove Job From Queue", nil)]; + } + else + { + [undo setActionName:NSLocalizedString(@"Remove Jobs From Queue", nil)]; + } + } + + [self.outlineView endUpdates]; + [self updateQueueStats]; + [self.jobs commit]; +} + +- (void)moveQueueItems:(NSArray *)items toIndex:(NSUInteger)index +{ + [self.jobs beginTransaction]; + [self.outlineView beginUpdates]; + + NSMutableArray *source = [NSMutableArray array]; + NSMutableArray *dest = [NSMutableArray array]; + + for (id object in items.reverseObjectEnumerator) + { + NSUInteger sourceIndex = [self.jobs indexOfObject:object]; + [self.jobs removeObjectAtIndex:sourceIndex]; + + + if (sourceIndex < index) + { + index--; + } + + [self.jobs insertObject:object atIndex:index]; + + [source addObject:@(index)]; + [dest addObject:@(sourceIndex)]; + + [self.outlineView moveItemAtIndex:sourceIndex inParent:nil toIndex:index inParent:nil]; + } + + NSUndoManager *undo = self.window.undoManager; + [[undo prepareWithInvocationTarget:self] moveQueueItemsAtIndexes:source toIndexes:dest]; + + if (!undo.isUndoing) + { + if (items.count == 1) + { + [undo setActionName:NSLocalizedString(@"Move Job in Queue", nil)]; + } + else + { + [undo setActionName:NSLocalizedString(@"Move Jobs in Queue", nil)]; + } + } + + [self.outlineView endUpdates]; + [self.jobs commit]; +} + +- (void)moveQueueItemsAtIndexes:(NSArray *)source toIndexes:(NSArray *)dest +{ + [self.jobs beginTransaction]; + [self.outlineView beginUpdates]; + + NSMutableArray *newSource = [NSMutableArray array]; + NSMutableArray *newDest = [NSMutableArray array]; + + for (NSInteger idx = source.count - 1; idx >= 0; idx--) + { + NSUInteger sourceIndex = [source[idx] integerValue]; + NSUInteger destIndex = [dest[idx] integerValue]; + + [newSource addObject:@(destIndex)]; + [newDest addObject:@(sourceIndex)]; + + id obj = [self.jobs objectAtIndex:sourceIndex]; + [self.jobs removeObjectAtIndex:sourceIndex]; + [self.jobs insertObject:obj atIndex:destIndex]; + + [self.outlineView moveItemAtIndex:sourceIndex inParent:nil toIndex:destIndex inParent:nil]; + } + + NSUndoManager *undo = self.window.undoManager; + [[undo prepareWithInvocationTarget:self] moveQueueItemsAtIndexes:newSource toIndexes:newDest]; + + if (!undo.isUndoing) + { + if (source.count == 1) + { + [undo setActionName:NSLocalizedString(@"Move Job in Queue", nil)]; + } + else + { + [undo setActionName:NSLocalizedString(@"Move Jobs in Queue", nil)]; + } + } + + [self.outlineView endUpdates]; + [self.jobs commit]; } /** * Updates the queue status label. */ -- (void)getQueueStats +- (void)updateQueueStats { // lets get the stats on the status of the queue array NSUInteger pendingCount = 0; @@ -311,10 +535,8 @@ self.completedItemsCount = completedCount; } -- (NSUInteger)count -{ - return self.jobs.count; -} +#pragma mark - +#pragma mark Queue Job Processing /** * Used to get the next pending queue item and return it if found @@ -332,232 +554,197 @@ } /** - * This method will set any item marked as encoding back to pending - * currently used right after a queue reload - */ -- (void)setEncodingJobsAsPending -{ - [self.jobs beginTransaction]; - for (HBJob *job in self.jobs) - { - // We want to keep any queue item that is pending or was previously being encoded - if (job.state == HBJobStateWorking) - { - job.state = HBJobStateReady; - } - } - [self.jobs commit]; - - [self reloadQueue]; -} - -/** - * This method will clear the queue of any encodes that are not still pending - * this includes both successfully completed encodes as well as cancelled encodes - */ -- (void)removeCompletedJobs -{ - [self.jobs beginTransaction]; - [self.jobs removeObjectsUsingBlock:^BOOL(HBJob *item) { - return (item.state == HBJobStateCompleted || item.state == HBJobStateCanceled); - }]; - [self.jobs commit]; - [self reloadQueue]; -} - -/** - * This method will clear the queue of all encodes. effectively creating an empty queue - */ -- (void)removeAllJobs -{ - [self.jobs beginTransaction]; - [self.jobs removeAllObjects]; - [self.jobs commit]; - - [self reloadQueue]; -} - -#pragma mark - -#pragma mark Queue Job Processing - -/** * Starts the queue */ - (void)encodeNextQueueItem { - // Check to see if there are any more pending items in the queue - HBJob *nextJob = [self getNextPendingQueueItem]; + [self.jobs beginTransaction]; + self.currentJob = nil; - // If we still have more pending items in our queue, lets go to the next one - if (nextJob) + // since we have completed an encode, we go to the next + if (self.stop) { - self.currentJob = nextJob; - // now we mark the queue item as working so another instance can not come along and try to scan it while we are scanning - self.currentJob.state = HBJobStateWorking; - - // Tell HB to output a new activity log file for this encode - self.currentLog = [[HBJobOutputFileWriter alloc] initWithJob:self.currentJob]; - [[HBOutputRedirect stderrRedirect] addListener:self.currentLog]; - [[HBOutputRedirect stdoutRedirect] addListener:self.currentLog]; - - // now we can go ahead and scan the new pending queue item - [self performScan:self.currentJob.fileURL titleIdx:self.currentJob.titleIdx]; + self.stop = NO; } else { - self.currentJob = nil; + // Check to see if there are any more pending items in the queue + HBJob *nextJob = [self getNextPendingQueueItem]; + + // If we still have more pending items in our queue, lets go to the next one + if (nextJob) + { + // now we mark the queue item as working so another instance can not come along and try to scan it while we are scanning + nextJob.state = HBJobStateWorking; + + // Tell HB to output a new activity log file for this encode + self.currentLog = [[HBJobOutputFileWriter alloc] initWithJob:nextJob]; + [[HBOutputRedirect stderrRedirect] addListener:self.currentLog]; + [[HBOutputRedirect stdoutRedirect] addListener:self.currentLog]; - [HBUtilities writeToActivityLog:"Queue Done, there are no more pending encodes"]; + self.currentJob = nextJob; + [self reloadQueueItemAtIndex:[self.jobs indexOfObject:nextJob]]; - // Since there are no more items to encode, go to queueCompletedAlerts - // for user specified alerts after queue completed - [self queueCompletedAlerts]; + // now we can go ahead and scan the new pending queue item + [self encodeJob:nextJob]; + + // erase undo manager history + [self.window.undoManager removeAllActions]; + } + else + { + [HBUtilities writeToActivityLog:"Queue Done, there are no more pending encodes"]; + + // Since there are no more items to encode, go to queueCompletedAlerts + // for user specified alerts after queue completed + [self queueCompletedAlerts]; + } } + [self.jobs commit]; } -- (void)encodeCompleted +- (void)completedJob:(HBJob *)job result:(HBCoreResult)result; { + NSParameterAssert(job); + [self.jobs beginTransaction]; + // Since we are done with this encode, tell output to stop writing to the // individual encode log. [[HBOutputRedirect stderrRedirect] removeListener:self.currentLog]; [[HBOutputRedirect stdoutRedirect] removeListener:self.currentLog]; + self.currentLog = nil; // Check to see if the encode state has not been cancelled // to determine if we should check for encode done notifications. - if (self.currentJob.state != HBJobStateCanceled) + if (result != HBCoreResultCancelled) { - [self jobCompletedAlerts]; - - // Mark the encode just finished as done - self.currentJob.state = HBJobStateCompleted; - + [self jobCompletedAlerts:job]; // Send to tagger - [self sendToExternalApp:self.currentJob.destURL]; + [self sendToExternalApp:job.destURL]; } - self.currentJob = nil; + // Mark the encode just finished + switch (result) { + case HBCoreResultDone: + job.state = HBJobStateCompleted; + break; + case HBCoreResultCancelled: + job.state = HBJobStateCanceled; + break; + default: + job.state = HBJobStateFailed; + break; + } - // since we have successfully completed an encode, we go to the next - if (!self.stop) + if ([self.jobs containsObject:job]) { - [self encodeNextQueueItem]; + [self reloadQueueItemAtIndex:[self.jobs indexOfObject:job]]; } - self.stop = NO; - [self.window.toolbar validateVisibleItems]; - [self reloadQueue]; + [self.jobs commit]; } /** * Here we actually tell hb_scan to perform the source scan, using the path to source and title number */ -- (void)performScan:(NSURL *)scanURL titleIdx:(NSInteger)index +- (void)encodeJob:(HBJob *)job { + NSParameterAssert(job); HBStateFormatter *formatter = [[HBStateFormatter alloc] init]; + // Progress handler + void (^progressHandler)(HBState state, hb_state_t hb_state) = ^(HBState state, hb_state_t hb_state) + { + NSString *status = [formatter stateToString:hb_state title:nil]; + self.progressTextField.stringValue = status; + [self.controller setQueueInfo:status progress:0 hidden:NO]; + }; + + // Completion handler + void (^completionHandler)(HBCoreResult result) = ^(HBCoreResult result) + { + if (result == HBCoreResultDone) + { + [self realEncodeJob:job]; + } + else + { + [self completedJob:job result:result]; + [self encodeNextQueueItem]; + } + }; + // Only scan 10 previews before an encode - additional previews are // only useful for autocrop and static previews, which are already taken care of at this point - [self.core scanURL:scanURL - titleIndex:index + [self.core scanURL:job.fileURL + titleIndex:job.titleIdx previews:10 minDuration:0 - progressHandler:^(HBState state, hb_state_t hb_state) { - NSString *status = [formatter stateToString:hb_state title:nil]; - - self.progressTextField.stringValue = status; - [self.controller setQueueInfo:status progress:0 hidden:NO]; - } - completionHandler:^(HBCoreResult result) { - if (result == HBCoreResultDone) - { - [self doEncodeQueueItem]; - } - else - { - [self.jobs beginTransaction]; - - self.currentJob.state = HBJobStateCanceled; - [self encodeCompleted]; - - [self.jobs commit]; - [self reloadQueue]; - } - - [self.window.toolbar validateVisibleItems]; - }]; + progressHandler:progressHandler + completionHandler:completionHandler]; } /** * This assumes that we have re-scanned and loaded up a new queue item to send to libhb */ -- (void)doEncodeQueueItem +- (void)realEncodeJob:(HBJob *)job { + NSParameterAssert(job); + // Reset the title in the job. - self.currentJob.title = self.core.titles[0]; + job.title = self.core.titles[0]; HBStateFormatter *converter = [[HBStateFormatter alloc] init]; - NSString *destinationName = self.currentJob.destURL.lastPathComponent; + NSString *destinationName = job.destURL.lastPathComponent; + + // Progress handler + void (^progressHandler)(HBState state, hb_state_t hb_state) = ^(HBState state, hb_state_t hb_state) + { + NSString *string = [converter stateToString:hb_state title:destinationName]; + CGFloat progress = [converter stateToPercentComplete:hb_state]; + + if (state == HBStateWorking) + { + // Update dock icon + if (self.dockIconProgress < 100.0 * progress) + { + #define p hb_state.param.working + [self.dockTile updateDockIcon:progress hours:p.hours minutes:p.minutes seconds:p.seconds]; + #undef p + self.dockIconProgress += dockTileUpdateFrequency; + } + } + else if (state == HBStateMuxing) + { + [self.dockTile updateDockIcon:1.0 withETA:@""]; + } + + // Update text field + self.progressTextField.stringValue = string; + [self.controller setQueueInfo:string progress:progress hidden:NO]; + }; + + // Completion handler + void (^completionHandler)(HBCoreResult result) = ^(HBCoreResult result) + { + NSString *info = NSLocalizedString(@"Encode Finished.", @""); + self.progressTextField.stringValue = info; + [self.controller setQueueInfo:info progress:1.0 hidden:YES]; + + // Restore dock icon + [self.dockTile updateDockIcon:-1.0 withETA:@""]; + self.dockIconProgress = 0; + + [self completedJob:job result:result]; + [self encodeNextQueueItem]; + }; // We should be all setup so let 'er rip - [self.core encodeJob:self.currentJob - progressHandler:^(HBState state, hb_state_t hb_state) { - NSString *string = [converter stateToString:hb_state title:destinationName]; - CGFloat progress = [converter stateToPercentComplete:hb_state]; - - if (state == HBStateWorking) - { - // Update dock icon - if (self.dockIconProgress < 100.0 * progress) - { - // ETA format is [XX]X:XX:XX when ETA is greater than one hour - // [X]X:XX when ETA is greater than 0 (minutes or seconds) - // When these conditions doesn't applied (eg. when ETA is undefined) - // we show just a tilde (~) - - #define p hb_state.param.working - NSString *etaStr; - if (p.hours > 0) - etaStr = [NSString stringWithFormat:@"%d:%02d:%02d", p.hours, p.minutes, p.seconds]; - else if (p.minutes > 0 || p.seconds > 0) - etaStr = [NSString stringWithFormat:@"%d:%02d", p.minutes, p.seconds]; - else - etaStr = @"~"; - #undef p - - [self.dockTile updateDockIcon:progress withETA:etaStr]; - self.dockIconProgress += dockTileUpdateFrequency; - } - } - else if (state == HBStateMuxing) - { - [self.dockTile updateDockIcon:1.0 withETA:@""]; - } - - // Update text field - self.progressTextField.stringValue = string; - [self.controller setQueueInfo:string progress:progress hidden:NO]; - } - completionHandler:^(HBCoreResult result) { - NSString *info = NSLocalizedString(@"Encode Finished.", @""); - - self.progressTextField.stringValue = info; - [self.controller setQueueInfo:info progress:1.0 hidden:YES]; - - // Restore dock icon - [self.dockTile updateDockIcon:-1.0 withETA:@""]; - self.dockIconProgress = 0; - - [self.jobs beginTransaction]; - - [self encodeCompleted]; - - [self.jobs commit]; - [self reloadQueue]; - }]; + [self.core encodeJob:job progressHandler:progressHandler completionHandler:completionHandler]; // We are done using the title, remove it from the job - self.currentJob.title = nil; + job.title = nil; } /** @@ -565,8 +752,6 @@ */ - (void)doCancelCurrentJob { - self.currentJob.state = HBJobStateCanceled; - if (self.core.state == HBStateScanning) { [self.core cancelScan]; @@ -582,12 +767,7 @@ */ - (void)cancelCurrentJobAndContinue { - [self.jobs beginTransaction]; - [self doCancelCurrentJob]; - - [self.jobs commit]; - [self reloadQueue]; } /** @@ -595,13 +775,16 @@ */ - (void)cancelCurrentJobAndStop { - [self.jobs beginTransaction]; - self.stop = YES; [self doCancelCurrentJob]; +} - [self.jobs commit]; - [self reloadQueue]; +/** + * Finishes the current job and stops libhb from processing the remaining encodes. + */ +- (void)finishCurrentAndStop +{ + self.stop = YES; } #pragma mark - Encode Done Actions @@ -632,10 +815,16 @@ clickContext:nil]; } +/** + * Sends the URL to the external app + * selected in the preferences. + * + * @param fileURL the URL of the file to send + */ - (void)sendToExternalApp:(NSURL *)fileURL { // This end of encode action is called as each encode rolls off of the queue - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"sendToMetaX"] == YES) + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"SendCompletedEncodeToApp"] == YES) { NSWorkspace *workspace = [NSWorkspace sharedWorkspace]; NSString *sendToApp = [workspace fullPathForApplication:[[NSUserDefaults standardUserDefaults] objectForKey:@"SendCompletedEncodeToApp"]]; @@ -654,7 +843,10 @@ } } -- (void)jobCompletedAlerts +/** + * Runs the alert for a single job + */ +- (void)jobCompletedAlerts:(HBJob *)job { // Both the Notification and Sending to tagger can be done as encodes roll off the queue if ([[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionNotification || @@ -665,10 +857,13 @@ { NSBeep(); } - [self showDoneNotification:self.currentJob.destURL]; + [self showDoneNotification:job.destURL]; } } +/** + * Runs the global queue completed alerts + */ - (void)queueCompletedAlerts { // If Play System Alert has been selected in Preferences @@ -721,12 +916,17 @@ */ - (IBAction)removeSelectedQueueItem:(id)sender { + if ([self.jobs beginTransaction] == HBDistributedArrayContentReload) + { + // Do not execture the action if the array changed. + [self.jobs commit]; + return; + } + NSMutableIndexSet *targetedRows = [[self.outlineView targetedRowIndexes] mutableCopy]; if (targetedRows.count) { - [self.jobs beginTransaction]; - // if this is a currently encoding job, we need to be sure to alert the user, // to let them decide to cancel it first, then if they do, we can come back and // remove it @@ -746,7 +946,7 @@ NSString *alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Remove It?", nil)]; // Which window to attach the sheet to? - NSWindow *targetWindow = nil; + NSWindow *targetWindow = self.window; if ([sender respondsToSelector: @selector(window)]) { targetWindow = [sender window]; @@ -768,14 +968,8 @@ // remove the non working items immediately [self removeQueueItemsAtIndexes:targetedRows]; - [self getQueueStats]; - - [self.outlineView beginUpdates]; - [self.outlineView removeItemsAtIndexes:targetedRows inParent:nil withAnimation:NSTableViewAnimationEffectFade]; - [self.outlineView endUpdates]; - - [self.jobs commit]; } + [self.jobs commit]; } - (void)didDimissCancelCurrentJob:(NSAlert *)alert @@ -784,10 +978,11 @@ { if (returnCode == NSAlertSecondButtonReturn) { + [self.jobs beginTransaction]; + NSInteger index = [self.jobs indexOfObject:self.currentJob]; [self cancelCurrentJobAndContinue]; - [self.jobs beginTransaction]; [self removeQueueItemAtIndex:index]; [self.jobs commit]; } @@ -796,7 +991,7 @@ /** * Show the finished encode in the finder */ -- (IBAction)revealSelectedQueueItems: (id)sender +- (IBAction)revealSelectedQueueItems:(id)sender { NSIndexSet *targetedRows = [self.outlineView targetedRowIndexes]; NSMutableArray *urls = [[NSMutableArray alloc] init]; @@ -868,12 +1063,7 @@ // or shut down when encoding is finished [self remindUserOfSleepOrShutdown]; - [self.jobs beginTransaction]; - [self encodeNextQueueItem]; - - [self.jobs commit]; - [self reloadQueue]; } } @@ -885,15 +1075,11 @@ - (IBAction)cancelRip:(id)sender { // Which window to attach the sheet to? - NSWindow *window; + NSWindow *window = self.window; if ([sender respondsToSelector:@selector(window)]) { window = [sender window]; } - else - { - window = self.window; - } NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:NSLocalizedString(@"You are currently encoding. What would you like to do?", nil)]; @@ -901,6 +1087,7 @@ [alert addButtonWithTitle:NSLocalizedString(@"Continue Encoding", nil)]; [alert addButtonWithTitle:NSLocalizedString(@"Cancel Current and Stop", nil)]; [alert addButtonWithTitle:NSLocalizedString(@"Cancel Current and Continue", nil)]; + [alert addButtonWithTitle:NSLocalizedString(@"Finish Current and Stop", nil)]; [alert setAlertStyle:NSCriticalAlertStyle]; [alert beginSheetModalForWindow:window @@ -921,6 +1108,10 @@ { [self cancelCurrentJobAndContinue]; } + else if (returnCode == NSAlertThirdButtonReturn + 1) + { + [self finishCurrentAndStop]; + } } /** @@ -955,9 +1146,17 @@ } } +/** + * Resets the job state to ready. + */ - (IBAction)resetJobState:(id)sender { - [self.jobs beginTransaction]; + if ([self.jobs beginTransaction] == HBDistributedArrayContentReload) + { + // Do not execture the action if the array changed. + [self.jobs commit]; + return; + } NSIndexSet *targetedRows = [self.outlineView targetedRowIndexes]; NSMutableIndexSet *updatedIndexes = [NSMutableIndexSet indexSet]; @@ -966,7 +1165,7 @@ while (currentIndex != NSNotFound) { HBJob *job = self.jobs[currentIndex]; - if (job.state == HBJobStateCanceled || job.state == HBJobStateCompleted) + if (job.state == HBJobStateCanceled || job.state == HBJobStateCompleted || job.state == HBJobStateFailed) { job.state = HBJobStateReady; [updatedIndexes addIndex:currentIndex]; @@ -974,8 +1173,32 @@ currentIndex = [targetedRows indexGreaterThanIndex:currentIndex]; } - [self.outlineView reloadDataForRowIndexes:updatedIndexes columnIndexes:[NSIndexSet indexSetWithIndex:0]]; - [self getQueueStats]; + [self reloadQueueItemsAtIndexes:updatedIndexes]; + [self.jobs commit]; +} + +- (void)editQueueItem:(HBJob *)job +{ + NSParameterAssert(job); + [self.jobs beginTransaction]; + + NSInteger index = [self.jobs indexOfObject:job]; + + // Cancel the encode if it's the current item + if (job == self.currentJob) + { + [self cancelCurrentJobAndContinue]; + } + + if ([self.controller openJob:job]) + { + // Now that source is loaded and settings applied, delete the queue item from the queue + [self removeQueueItemAtIndex:index]; + } + else + { + NSBeep(); + } [self.jobs commit]; } @@ -985,9 +1208,14 @@ */ - (IBAction)editSelectedQueueItem:(id)sender { - [self.jobs beginTransaction]; + if ([self.jobs beginTransaction] == HBDistributedArrayContentReload) + { + // Do not execture the action if the array changed. + [self.jobs commit]; + return; + } - NSInteger row = [self.outlineView clickedRow]; + NSInteger row = self.outlineView.clickedRow; if (row != NSNotFound) { // if this is a currently encoding job, we need to be sure to alert the user, @@ -999,7 +1227,7 @@ NSString *alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Edit It?", nil)]; // Which window to attach the sheet to? - NSWindow *docWindow = nil; + NSWindow *docWindow = self.window; if ([sender respondsToSelector: @selector(window)]) { docWindow = [sender window]; @@ -1019,18 +1247,7 @@ } else if (job.state != HBJobStateWorking) { - // since we are not a currently encoding item, we can just be edit it - HBJob *item = [[self.jobs[row] representedObject] copy]; - if ([self.controller openJob:item]) - { - // Now that source is loaded and settings applied, delete the queue item from the queue - [self.outlineView beginUpdates]; - [self removeQueueItemAtIndex:row]; - [self.outlineView removeItemsAtIndexes:[NSIndexSet indexSetWithIndex:row] inParent:nil withAnimation:NSTableViewAnimationEffectFade]; - [self.outlineView endUpdates]; - - [self getQueueStats]; - } + [self editQueueItem:job]; } } @@ -1044,42 +1261,28 @@ if (returnCode == NSAlertSecondButtonReturn) { HBJob *job = (__bridge HBJob *)contextInfo; - NSInteger index = [self.jobs indexOfObject:job]; - - if (job == self.currentJob) - { - [self cancelCurrentJobAndContinue]; - } - - if ([self.controller openJob:job]) - { - [self.jobs beginTransaction]; - [self.controller openJob:job]; - [self removeQueueItemAtIndex:index]; - [self.jobs commit]; - [self getQueueStats]; - } + [self editQueueItem:job]; } } - (IBAction)clearAll:(id)sender { [self.jobs beginTransaction]; - [self.jobs removeObjectsUsingBlock:^BOOL(HBJob *item) { + NSIndexSet *indexes = [self.jobs indexesOfObjectsUsingBlock:^BOOL(HBJob *item) { return (item.state != HBJobStateWorking); }]; + [self removeQueueItemsAtIndexes:indexes]; [self.jobs commit]; - [self reloadQueue]; } - (IBAction)clearCompleted:(id)sender { [self.jobs beginTransaction]; - [self.jobs removeObjectsUsingBlock:^BOOL(HBJob *item) { + NSIndexSet *indexes = [self.jobs indexesOfObjectsUsingBlock:^BOOL(HBJob *item) { return (item.state == HBJobStateCompleted); }]; + [self removeQueueItemsAtIndexes:indexes]; [self.jobs commit]; - [self reloadQueue]; } #pragma mark - @@ -1117,7 +1320,7 @@ // top-level items. if (item == nil) { - return [self.jobs count]; + return self.jobs.count; } else { @@ -1141,6 +1344,9 @@ [self.outlineView noteHeightOfRowsWithIndexesChanged:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(row,1)]]; } +#define HB_ROW_HEIGHT_TITLE_ONLY 17.0 +#define HB_ROW_HEIGHT_PADDING 6.0 + - (CGFloat)outlineView:(NSOutlineView *)outlineView heightOfRowByItem:(id)item { if ([outlineView isItemExpanded:item]) @@ -1153,14 +1359,14 @@ NSCell *cell = [outlineView preparedCellAtColumn:columnToWrap row:[outlineView rowForItem:item]]; // See how tall it naturally would want to be if given a restricted with, but unbound height - NSRect constrainedBounds = NSMakeRect(0, 0, [tableColumnToWrap width], CGFLOAT_MAX); + NSRect constrainedBounds = NSMakeRect(0, 0, tableColumnToWrap.width, CGFLOAT_MAX); NSSize naturalSize = [cell cellSizeForBounds:constrainedBounds]; // Make sure we have a minimum height -- use the table's set height as the minimum. - if (naturalSize.height > [outlineView rowHeight]) - return naturalSize.height; + if (naturalSize.height > outlineView.rowHeight) + return naturalSize.height + HB_ROW_HEIGHT_PADDING; else - return [outlineView rowHeight]; + return outlineView.rowHeight; } else { @@ -1173,17 +1379,15 @@ if ([tableColumn.identifier isEqualToString:@"desc"]) { HBJob *job = item; + NSAttributedString *description = self.descriptions[@(job.hash)]; - if (self.descriptions[@(job.hash)]) + if (description == nil) { - return self.descriptions[@(job.hash)]; + description = job.attributedDescription; + self.descriptions[@(job.hash)] = description; } - NSAttributedString *finalString = job.attributedDescription; - - self.descriptions[@(job.hash)] = finalString;; - - return finalString; + return description; } else if ([tableColumn.identifier isEqualToString:@"icon"]) { @@ -1200,6 +1404,10 @@ { return [NSImage imageNamed:@"EncodeCanceled"]; } + else if (job.state == HBJobStateFailed) + { + return [NSImage imageNamed:@"EncodeFailed"]; + } else { return [NSImage imageNamed:@"JobSmall"]; @@ -1231,7 +1439,9 @@ [cell setAlternateImage:[NSImage imageNamed:@"RevealHighlightPressed"]]; } else + { [cell setImage:[NSImage imageNamed:@"Reveal"]]; + } } else { @@ -1242,7 +1452,9 @@ [cell setAlternateImage:[NSImage imageNamed:@"DeleteHighlightPressed"]]; } else + { [cell setImage:[NSImage imageNamed:@"Delete"]]; + } } } } @@ -1261,7 +1473,7 @@ } } -#pragma mark NSOutlineView delegate (dragging related) +#pragma mark NSOutlineView drag & drop - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard { @@ -1294,7 +1506,7 @@ // Don't allow dropping INTO an item since they can't really contain any children. if (item != nil) { - index = [self.outlineView rowForItem: item] + 1; + index = [self.outlineView rowForItem:item] + 1; item = nil; } @@ -1304,7 +1516,7 @@ if (encodingIndex != NSNotFound && index <= encodingIndex) { return NSDragOperationNone; - index = MAX (index, encodingIndex); + index = MAX(index, encodingIndex); } [outlineView setDropItem:item dropChildIndex:index]; @@ -1313,29 +1525,7 @@ - (BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)info item:(id)item childIndex:(NSInteger)index { - [self.jobs beginTransaction]; - [self.outlineView beginUpdates]; - - for (id object in self.dragNodesArray.reverseObjectEnumerator) - { - NSUInteger sourceIndex = [self.jobs indexOfObject:object]; - [self.jobs removeObjectAtIndex:sourceIndex]; - - if (sourceIndex < index) - { - index--; - } - - [self.jobs insertObject:object atIndex:index]; - - NSUInteger destIndex = [self.jobs indexOfObject:object]; - - [self.outlineView moveItemAtIndex:sourceIndex inParent:nil toIndex:destIndex inParent:nil]; - } - - [self.outlineView endUpdates]; - [self.jobs commit]; - + [self moveQueueItems:self.dragNodesArray toIndex:index]; return YES; } |