]> granicus.if.org Git - handbrake/commitdiff
MacGui: improved queue with undo/redo support for adding/removing jobs, and added...
authorDamiano Galassi <damiog@gmail.com>
Mon, 19 Oct 2015 15:57:49 +0000 (17:57 +0200)
committerDamiano Galassi <damiog@gmail.com>
Mon, 19 Oct 2015 15:57:49 +0000 (17:57 +0200)
14 files changed:
macosx/English.lproj/Queue.xib
macosx/HBAppDelegate.m
macosx/HBDockTile.h
macosx/HBDockTile.m
macosx/HBJob+UIAdditions.m
macosx/HBQueueController.h
macosx/HBQueueController.m
macosx/HandBrake.xcodeproj/project.pbxproj
macosx/NSArray+HBAdditions.h
macosx/NSArray+HBAdditions.m
macosx/icons/EncodeCanceled.png
macosx/icons/EncodeCanceled@2x.png
macosx/icons/EncodeFailed.png [new file with mode: 0644]
macosx/icons/EncodeFailed@2x.png [new file with mode: 0644]

index f33d6701325fd600e1624157a34f092a5628f5de..9220cc0cb68da1bb913c5e125e0db596d8b1295e 100644 (file)
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="8164.2" systemVersion="15A225f" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9058" systemVersion="15B38b" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
     <dependencies>
         <deployment identifier="macosx"/>
         <development version="6300" identifier="xcode"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="8164.2"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9058"/>
     </dependencies>
     <objects>
         <customObject id="-2" userLabel="File's Owner" customClass="HBQueueController">
@@ -34,7 +34,7 @@
                             <rect key="frame" x="1" y="1" width="532" height="336"/>
                             <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
                             <subviews>
-                                <outlineView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" autosaveColumns="NO" indentationPerLevel="16" autoresizesOutlineColumn="YES" outlineTableColumn="2624" id="2597" customClass="HBQueueOutlineView">
+                                <outlineView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" alternatingRowBackgroundColors="YES" columnReordering="NO" columnResizing="NO" autosaveColumns="NO" indentationPerLevel="16" outlineTableColumn="2624" id="2597" customClass="HBQueueOutlineView">
                                     <rect key="frame" x="0.0" y="0.0" width="532" height="336"/>
                                     <autoresizingMask key="autoresizingMask"/>
                                     <animations/>
                             <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
                         </clipView>
                         <animations/>
-                        <scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" horizontal="YES" id="2644">
+                        <scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="2644">
                             <rect key="frame" x="-100" y="-100" width="282" height="15"/>
                             <autoresizingMask key="autoresizingMask"/>
                             <animations/>
                         </scroller>
-                        <scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="2643">
+                        <scroller key="verticalScroller" verticalHuggingPriority="750" horizontal="NO" id="2643">
                             <rect key="frame" x="517" y="1" width="16" height="336"/>
                             <autoresizingMask key="autoresizingMask"/>
                             <animations/>
index 32251f96fe964535d528be3442c2d23cd2039dc9..6c21b1937c809f2d6e0f35fdc62d0b024d4bbfc9 100644 (file)
@@ -17,6 +17,7 @@
 #import "HBController.h"
 
 #define PRESET_FILE @"UserPresets.json"
+#define QUEUE_FILE @"Queue.hbqueue"
 
 @interface HBAppDelegate ()
 
         _outputPanel = [[HBOutputPanelController alloc] init];
 
         // we init the HBPresetsManager
-        NSURL *presetsURL = [[HBUtilities appSupportURL] URLByAppendingPathComponent:PRESET_FILE];
-        _presetsManager = [[HBPresetsManager alloc] initWithURL:presetsURL];
+        NSURL *appSupportURL = [HBUtilities appSupportURL];
+        _presetsManager = [[HBPresetsManager alloc] initWithURL:[appSupportURL URLByAppendingPathComponent:PRESET_FILE]];
 
-        _queueController = [[HBQueueController alloc] init];
+        // Queue
+        _queueController = [[HBQueueController alloc] initWithURL:[appSupportURL URLByAppendingPathComponent:QUEUE_FILE]];
         _queueController.delegate = self;
         _mainController = [[HBController alloc] initWithQueue:_queueController presetsManager:_presetsManager];
 
index 15117ab21237d338036394d48ffcbbcaf0d7c3bd..caac2844e22d1cce5410121f7834c899c620ddb9 100644 (file)
  */
 - (void)updateDockIcon:(double)progress withETA:(NSString *)etaStr;
 
+
+/**
+ *  Updates two DockTextFields on the dockTile,
+ *  one with total percentage, the other one with the ETA.
+ *
+ *  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 (~)
+ */
+- (void)updateDockIcon:(double)progress hours:(NSInteger)hours minutes:(NSInteger)minutes seconds:(NSInteger)seconds;
+
 @end
index 1b61d71c56009209641649dc93e1f027686bab63..dfad7d5ec053fa71994220470b5859781da9ef4c 100644 (file)
@@ -68,4 +68,30 @@ NSString *dockTilePercentFormat = @"%2.1f%%";
     [_dockTile display];
 }
 
+- (void)updateDockIcon:(double)progress hours:(NSInteger)hours minutes:(NSInteger)minutes seconds:(NSInteger)seconds
+{
+    // 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 (~)
+
+    NSString *etaStr;
+    if (hours > 0)
+    {
+        etaStr = [NSString stringWithFormat:@"%ld:%02ld:%02ld", (long)hours, (long)minutes, (long)seconds];
+    }
+    else if (minutes > 0 || seconds > 0)
+    {
+        etaStr = [NSString stringWithFormat:@"%ld:%02ld", (long)minutes, (long)seconds];
+    }
+    else
+    {
+        etaStr = @"~";
+    }
+
+    [self updateDockIcon:progress withETA:etaStr];
+
+}
+
+
 @end
index 7eae8a773a1850bbce1753a324ef7ed29b0ae045..c7a7fec1fc0f62fef64529641bae16623c60dbda 100644 (file)
@@ -84,10 +84,10 @@ static NSDictionary            *shortHeightAttr;
         [ps setTabStops:@[]];    // clear all tabs
         [ps addTabStop: [[NSTextTab alloc] initWithType: NSLeftTabStopType location: 20.0]];
 
-        detailAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:10.0],
+        detailAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont smallSystemFontSize]],
                         NSParagraphStyleAttributeName: ps};
 
-        detailBoldAttr = @{NSFontAttributeName: [NSFont boldSystemFontOfSize:10.0],
+        detailBoldAttr = @{NSFontAttributeName: [NSFont boldSystemFontOfSize:[NSFont smallSystemFontSize]],
                             NSParagraphStyleAttributeName: ps};
 
         titleAttr = @{NSFontAttributeName: [NSFont systemFontOfSize:[NSFont systemFontSize]],
@@ -474,7 +474,9 @@ static NSDictionary            *shortHeightAttr;
             i++;
         }
     }
-    
+
+    [finalString deleteCharactersInRange:NSMakeRange(finalString.length - 1, 1)];
+
     return finalString;
 }
 
index ca14104ba134c6eca79c1b0c85f91706ae2bfc6b..ff8ca5aafa0e7cfb5b22c6d2bf640915d83904f2 100644 (file)
@@ -17,6 +17,8 @@ NS_ASSUME_NONNULL_BEGIN
 
 @interface HBQueueController : NSWindowController <NSToolbarDelegate, NSWindowDelegate, GrowlApplicationBridgeDelegate>
 
+- (instancetype)initWithURL:(NSURL *)queueURL;
+
 /// The HBCore used for encoding.
 @property (nonatomic, readonly) HBCore *core;
 
index 7ae19da3e74d75b81a92fca942371e81eb5fb218..5c99507b043af38816c7badb819b9dc335d1d7e0 100644 (file)
@@ -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;
 @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;
     [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
     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
 
 - (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;
     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
     return nil;
 }
 
-/**
- * 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;
 }
 
 /**
  */
 - (void)doCancelCurrentJob
 {
-    self.currentJob.state = HBJobStateCanceled;
-
     if (self.core.state == HBStateScanning)
     {
         [self.core cancelScan];
  */
 - (void)cancelCurrentJobAndContinue
 {
-    [self.jobs beginTransaction];
-
     [self doCancelCurrentJob];
-
-    [self.jobs commit];
-    [self reloadQueue];
 }
 
 /**
  */
 - (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
                                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"]];
     }
 }
 
-- (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 ||
         {
             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
  */
 - (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
                 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];
 
         // 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
 {
     if (returnCode == NSAlertSecondButtonReturn)
     {
+        [self.jobs beginTransaction];
+
         NSInteger index = [self.jobs indexOfObject:self.currentJob];
         [self cancelCurrentJobAndContinue];
 
-        [self.jobs beginTransaction];
         [self removeQueueItemAtIndex:index];
         [self.jobs commit];
     }
 /**
  * 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];
         // or shut down when encoding is finished
         [self remindUserOfSleepOrShutdown];
 
-        [self.jobs beginTransaction];
-
         [self encodeNextQueueItem];
-
-        [self.jobs commit];
-        [self reloadQueue];
     }
 }
 
 - (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)];
     [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
     {
         [self cancelCurrentJobAndContinue];
     }
+    else if (returnCode == NSAlertThirdButtonReturn + 1)
+    {
+        [self finishCurrentAndStop];
+    }
 }
 
 /**
     }
 }
 
+/**
+ *  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];
     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];
         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];
 }
  */
 - (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,
             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];
         }
         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];
         }
     }
 
     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 -
     // top-level items.
     if (item == nil)
     {
-        return [self.jobs count];
+        return self.jobs.count;
     }
     else
     {
     [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])
         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
     {
     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"])
     {
         {
             return [NSImage imageNamed:@"EncodeCanceled"];
         }
+        else if (job.state == HBJobStateFailed)
+        {
+            return [NSImage imageNamed:@"EncodeFailed"];
+        }
         else
         {
             return [NSImage imageNamed:@"JobSmall"];
                 [cell setAlternateImage:[NSImage imageNamed:@"RevealHighlightPressed"]];
             }
             else
+            {
                 [cell setImage:[NSImage imageNamed:@"Reveal"]];
+            }
         }
         else
         {
                 [cell setAlternateImage:[NSImage imageNamed:@"DeleteHighlightPressed"]];
             }
             else
+            {
                 [cell setImage:[NSImage imageNamed:@"Delete"]];
+            }
         }
     }
 }
     }
 }
 
-#pragma mark NSOutlineView delegate (dragging related)
+#pragma mark NSOutlineView drag & drop
 
 - (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard
 {
     // 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;
     }
 
     if (encodingIndex != NSNotFound && index <= encodingIndex)
     {
         return NSDragOperationNone;
-        index = MAX (index, encodingIndex);
+        index = MAX(index, encodingIndex);
        }
 
     [outlineView setDropItem:item dropChildIndex:index];
 
 - (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;
 }
 
index 53dbc1ebc4007baa4efb78219e067c39a4661c00..654be76b1e8e3b011256e7a8e8341f9b54a9deae 100644 (file)
                A990D9071A64562200139032 /* HBJob+HBJobConversion.m in Sources */ = {isa = PBXBuildFile; fileRef = A990D9061A64562200139032 /* HBJob+HBJobConversion.m */; };
                A9935213196F38A70069C6B7 /* ChaptersTitles.xib in Resources */ = {isa = PBXBuildFile; fileRef = A9935211196F38A70069C6B7 /* ChaptersTitles.xib */; };
                A99422E01B1887B000DDB077 /* NSJSONSerialization+HBAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = A99422DF1B1887B000DDB077 /* NSJSONSerialization+HBAdditions.m */; };
+               A996557D1BD2D32500BA50FA /* EncodeFailed.png in Resources */ = {isa = PBXBuildFile; fileRef = A996557B1BD2D32500BA50FA /* EncodeFailed.png */; };
+               A996557E1BD2D32500BA50FA /* EncodeFailed@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = A996557C1BD2D32500BA50FA /* EncodeFailed@2x.png */; };
                A99F40CF1B624E7E00750170 /* HBPictureViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A99F40CD1B624E7E00750170 /* HBPictureViewController.m */; };
                A99F40D31B624EA500750170 /* HBPictureViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A99F40D11B624EA500750170 /* HBPictureViewController.xib */; };
                A9A24B2D1B09F6FD00AD1FAB /* HBPresetsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = A9A24B2C1B09F6FD00AD1FAB /* HBPresetsTests.m */; };
                A9935212196F38A70069C6B7 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = ChaptersTitles.xib; sourceTree = "<group>"; };
                A99422DE1B1887B000DDB077 /* NSJSONSerialization+HBAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSJSONSerialization+HBAdditions.h"; sourceTree = "<group>"; };
                A99422DF1B1887B000DDB077 /* NSJSONSerialization+HBAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSJSONSerialization+HBAdditions.m"; sourceTree = "<group>"; };
+               A996557B1BD2D32500BA50FA /* EncodeFailed.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = EncodeFailed.png; sourceTree = "<group>"; };
+               A996557C1BD2D32500BA50FA /* EncodeFailed@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "EncodeFailed@2x.png"; sourceTree = "<group>"; };
                A997D8EB1A4ABB0900E19B6F /* HBPresetCoding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBPresetCoding.h; sourceTree = "<group>"; };
                A99F40CC1B624E7E00750170 /* HBPictureViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBPictureViewController.h; sourceTree = "<group>"; };
                A99F40CD1B624E7E00750170 /* HBPictureViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBPictureViewController.m; sourceTree = "<group>"; };
                                273F212814ADCBF70021BE6D /* DeletePressed.png */,
                                273F212A14ADCBF80021BE6D /* EncodeCanceled.png */,
                                A967E4B91A16768200DF1DFC /* EncodeCanceled@2x.png */,
+                               A996557B1BD2D32500BA50FA /* EncodeFailed.png */,
+                               A996557C1BD2D32500BA50FA /* EncodeFailed@2x.png */,
                                A91C02501A165EA200DEA6F3 /* EncodeComplete.png */,
                                A91C02511A165EA200DEA6F3 /* EncodeComplete@2x.png */,
                                273F212C14ADCBF80021BE6D /* EncodeWorking0.png */,
                                A922687B1A6E569B00A8D5C5 /* MainWindow.xib in Resources */,
                                273F219114ADDDA10021BE6D /* Queue.xib in Resources */,
                                3490BCB41614CF8D002A5AD7 /* HandBrake.icns in Resources */,
+                               A996557D1BD2D32500BA50FA /* EncodeFailed.png in Resources */,
                                A9E1468016BC2AD800C307BC /* next-p.pdf in Resources */,
                                A9E1468116BC2AD800C307BC /* pause-p.pdf in Resources */,
                                A9E1468216BC2AD800C307BC /* play-p.pdf in Resources */,
                                A99F40D31B624EA500750170 /* HBPictureViewController.xib in Resources */,
                                A9252C0A1A173D4800B8B7F8 /* RevealHighlight@2x.png in Resources */,
                                D2BCB11516F5152C0084604C /* picturesettings.png in Resources */,
+                               A996557E1BD2D32500BA50FA /* EncodeFailed@2x.png in Resources */,
                                A93E0ED71972958C00FD67FB /* Video.xib in Resources */,
                                A9252C0B1A173D4800B8B7F8 /* RevealHighlightPressed@2x.png in Resources */,
                                D2BCB11616F5152C0084604C /* picturesettings@2x.png in Resources */,
index deb2796a4e1e617c2a895da7e5f0de9ae6879c73..b423e1e2bc4e71bcce66d4c9d2b7a6b74819de96 100644 (file)
@@ -8,13 +8,6 @@
 
 #import <Foundation/Foundation.h>
 
-@interface NSMutableArray (HBAdditions)
-
-- (void)removeObjectsUsingBlock:(BOOL (^)(id object))block;
-
-@end
-
-
 @interface NSArray (HBAdditions)
 
 - (NSArray *)filteredArrayUsingBlock:(BOOL (^)(id object))block;
index babcc9f15a6996d7fd0dd692a533e0c0af7f4d62..c541470982b185675bcbe8d064cf9424ac573858 100644 (file)
@@ -8,23 +8,6 @@
 
 #import "NSArray+HBAdditions.h"
 
-@implementation NSMutableArray (HBAdditions)
-
-- (void)removeObjectsUsingBlock:(BOOL (^)(id object))block
-{
-    NSMutableArray *objectsToRemove = [NSMutableArray array];
-    for (id object in self)
-    {
-        if (block(object))
-        {
-            [objectsToRemove addObject:object];
-        }
-    }
-    [self removeObjectsInArray:objectsToRemove];
-}
-
-@end
-
 @implementation NSArray (HBAdditions)
 
 - (NSArray *)filteredArrayUsingBlock:(BOOL (^)(id object))block
index 66c5bf7c988b4ba07edf1a0efaca463d3a9d7fbc..6516035d5ec304261502ff9dad9e687bac57c0ee 100644 (file)
Binary files a/macosx/icons/EncodeCanceled.png and b/macosx/icons/EncodeCanceled.png differ
index 915ec17e0e5417e801239632e8573298695c82fe..89e4fd3979ece3531ce177add06358f33e1bf8ad 100644 (file)
Binary files a/macosx/icons/EncodeCanceled@2x.png and b/macosx/icons/EncodeCanceled@2x.png differ
diff --git a/macosx/icons/EncodeFailed.png b/macosx/icons/EncodeFailed.png
new file mode 100644 (file)
index 0000000..2c7712f
Binary files /dev/null and b/macosx/icons/EncodeFailed.png differ
diff --git a/macosx/icons/EncodeFailed@2x.png b/macosx/icons/EncodeFailed@2x.png
new file mode 100644 (file)
index 0000000..3740dfd
Binary files /dev/null and b/macosx/icons/EncodeFailed@2x.png differ