From: Damiano Galassi Date: Fri, 19 Jul 2019 19:17:27 +0000 (+0200) Subject: MacGui: refactor the queue and implement part of the new queue ui. X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=97734fff76d15a2da60f7956d82d463ac7ce6ece;p=handbrake MacGui: refactor the queue and implement part of the new queue ui. --- diff --git a/macosx/Base.lproj/HBQueueDetailsViewController.xib b/macosx/Base.lproj/HBQueueDetailsViewController.xib new file mode 100644 index 000000000..278805b95 --- /dev/null +++ b/macosx/Base.lproj/HBQueueDetailsViewController.xib @@ -0,0 +1,96 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macosx/Base.lproj/HBQueueTableViewController.xib b/macosx/Base.lproj/HBQueueTableViewController.xib new file mode 100644 index 000000000..8eb989790 --- /dev/null +++ b/macosx/Base.lproj/HBQueueTableViewController.xib @@ -0,0 +1,178 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/macosx/Base.lproj/MainMenu.xib b/macosx/Base.lproj/MainMenu.xib index 4c6e39b68..26adb990c 100644 --- a/macosx/Base.lproj/MainMenu.xib +++ b/macosx/Base.lproj/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -123,12 +123,12 @@ - + - + diff --git a/macosx/Base.lproj/MainWindow.xib b/macosx/Base.lproj/MainWindow.xib index 1dcd4f4d0..d691c3cb5 100644 --- a/macosx/Base.lproj/MainWindow.xib +++ b/macosx/Base.lproj/MainWindow.xib @@ -1,8 +1,8 @@ - + - + @@ -40,7 +40,7 @@ - + @@ -53,7 +53,7 @@ - + @@ -428,6 +428,11 @@ + + + NSIsNotNil + + @@ -435,11 +440,6 @@ IA - - - NSIsNotNil - - @@ -730,12 +730,12 @@ Blu-ray and DVD sources often have multiple titles, the longest of which is typi - + - + diff --git a/macosx/Base.lproj/Queue.xib b/macosx/Base.lproj/Queue.xib index bb379a101..bf105a3fa 100644 --- a/macosx/Base.lproj/Queue.xib +++ b/macosx/Base.lproj/Queue.xib @@ -1,18 +1,15 @@ - + - + - - - @@ -23,205 +20,11 @@ - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -237,9 +40,50 @@ + + + + + + + + + + + + + + + - + @@ -262,74 +106,41 @@ + + + + + + + - + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/macosx/HBAppDelegate.h b/macosx/HBAppDelegate.h index 387437b20..3762c62e9 100644 --- a/macosx/HBAppDelegate.h +++ b/macosx/HBAppDelegate.h @@ -6,11 +6,19 @@ #import +@class HBJob; + @interface HBAppDelegate : NSObject - (IBAction)showPreferencesWindow:(id)sender; - (IBAction)showOutputPanel:(id)sender; -- (void)reloadPreset:(id)sender; + +- (IBAction)toggleStartCancel:(id)sender; +- (IBAction)togglePauseResume:(id)sender; + +- (IBAction)reloadPreset:(id)sender; + +- (void)openJob:(HBJob *)job completionHandler:(void (^)(BOOL result))handler; @end diff --git a/macosx/HBAppDelegate.m b/macosx/HBAppDelegate.m index de227e153..d8790fb7c 100644 --- a/macosx/HBAppDelegate.m +++ b/macosx/HBAppDelegate.m @@ -6,6 +6,8 @@ #import "HBAppDelegate.h" +#import "HBQueue.h" + #import "HBUtilities.h" #import "HBPresetsManager.h" #import "HBPreset.h" @@ -28,6 +30,8 @@ @property (unsafe_unretained) IBOutlet NSMenu *presetsMenu; @property (nonatomic, strong) HBPreferencesController *preferencesController; + +@property (nonatomic, strong) HBQueue *queue; @property (nonatomic, strong) HBQueueController *queueController; @property (nonatomic, strong) HBOutputPanelController *outputPanel; @@ -59,9 +63,10 @@ _presetsManager = [[HBPresetsManager alloc] initWithURL:[appSupportURL URLByAppendingPathComponent:PRESET_FILE]]; // Queue - _queueController = [[HBQueueController alloc] initWithURL:[appSupportURL URLByAppendingPathComponent:QUEUE_FILE]]; + _queue = [[HBQueue alloc] initWithURL:[appSupportURL URLByAppendingPathComponent:QUEUE_FILE]]; + _queueController = [[HBQueueController alloc] initWithQueue:_queue]; _queueController.delegate = self; - _mainController = [[HBController alloc] initWithQueue:_queueController presetsManager:_presetsManager]; + _mainController = [[HBController alloc] initWithDelegate:self queue:_queue presetsManager:_presetsManager]; } return self; } @@ -116,13 +121,13 @@ } else { - [self.queueController setEncodingJobsAsPending]; - [self.queueController removeCompletedJobs]; + [self.queue setEncodingJobsAsPending]; + [self.queue removeCompletedAndCancelledItems]; } // Now we re-check the queue array to see if there are // any remaining encodes to be done - if (self.queueController.count) + if (self.queue.items.count) { [self showMainWindow:self]; [self showQueueWindow:self]; @@ -158,7 +163,7 @@ - (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication *)app { - if (self.queueController.core.state != HBStateIdle) + if (self.queue.isEncoding) { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:NSLocalizedString(@"Are you sure you want to quit HandBrake?", @"Quit Alert -> message")]; @@ -192,6 +197,7 @@ _mainController = nil; _queueController = nil; + _queue = nil; [HBCore closeGlobal]; } @@ -206,7 +212,7 @@ { SEL action = menuItem.action; - if (action == @selector(rip:) || action == @selector(pause:)) + if (action == @selector(toggleStartCancel:) || action == @selector(togglePauseResume:)) { // Delegate the validation to the queue controller return [self.queueController validateMenuItem:menuItem]; @@ -281,21 +287,28 @@ } } +#pragma mark - Rescan job + +- (void)openJob:(HBJob *)job completionHandler:(void (^)(BOOL result))handler +{ + [self.mainController openJob:job completionHandler:handler]; +} + #pragma mark - Menu actions -- (IBAction)rip:(id)sender +- (IBAction)toggleStartCancel:(id)sender { - [self.queueController rip:self]; + [self.queueController toggleStartCancel:sender]; } -- (IBAction)pause:(id)sender +- (IBAction)togglePauseResume:(id)sender { - [self.queueController togglePauseResume:self]; + [self.queueController togglePauseResume:sender]; } - (IBAction)browseSources:(id)sender { - [self.mainController browseSources:self]; + [self.mainController browseSources:sender]; } #pragma mark - Presets Menu actions @@ -310,7 +323,7 @@ - (IBAction)reloadPreset:(id)sender { - [self.mainController reloadPreset:self]; + [self.mainController reloadPreset:sender]; } #pragma mark - Show Window Menu Items @@ -325,7 +338,7 @@ _preferencesController = [[HBPreferencesController alloc] init]; } - [self.preferencesController showWindow:self]; + [self.preferencesController showWindow:sender]; } /** @@ -346,7 +359,7 @@ - (IBAction)showPreviewWindow:(id)sender { - [self.mainController showPreviewWindow:self]; + [self.mainController showPreviewWindow:sender]; } /** diff --git a/macosx/HBController.h b/macosx/HBController.h index 8fb6cfbc7..fa8a9ac42 100644 --- a/macosx/HBController.h +++ b/macosx/HBController.h @@ -6,14 +6,15 @@ #import -@class HBQueueController; +@class HBAppDelegate; +@class HBQueue; @class HBPresetsManager; @class HBJob; @interface HBController : NSWindowController -- (instancetype)initWithQueue:(HBQueueController *)queueController presetsManager:(HBPresetsManager *)manager; +- (instancetype)initWithDelegate:(HBAppDelegate *)delegate queue:(HBQueue *)queue presetsManager:(HBPresetsManager *)manager; - (void)launchAction; @@ -28,10 +29,8 @@ - (IBAction)addToQueue:(id)sender; - (IBAction)addAllTitlesToQueue:(id)sender; -- (void)setQueueInfo:(NSAttributedString *)info progress:(double)progress hidden:(BOOL)hidden; - -- (IBAction)rip:(id)sender; -- (IBAction)pause:(id)sender; +- (IBAction)toggleStartCancel:(id)sender; +- (IBAction)togglePauseResume:(id)sender; - (IBAction)selectPresetFromMenu:(id)sender; diff --git a/macosx/HBController.m b/macosx/HBController.m index 7dbbf7182..40b42ff9b 100644 --- a/macosx/HBController.m +++ b/macosx/HBController.m @@ -33,6 +33,7 @@ #import "HBRenamePresetController.h" #import "HBAutoNamer.h" +#import "HBQueue.h" @import HandBrakeKit; @@ -122,8 +123,11 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; /// The HBCore used for scanning. @property (nonatomic, strong) HBCore *core; -/// The queue controller. -@property (nonatomic, strong) HBQueueController *queue; +/// The app delegate. +@property (nonatomic, strong) HBAppDelegate *delegate; + +/// The queue. +@property (nonatomic, strong) HBQueue *queue; /// Whether the window is visible or occluded, /// useful to avoid updating the UI needlessly @@ -146,7 +150,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; @interface HBController (TouchBar) - (void)_touchBar_updateButtonsStateForScanCore:(HBState)state; -- (void)_touchBar_updateButtonsStateForQueueCore:(HBState)state; +- (void)_touchBar_updateQueueButtonsState; - (void)_touchBar_validateUserInterfaceItems; @end @@ -155,7 +159,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; @implementation HBController -- (instancetype)initWithQueue:(HBQueueController *)queueController presetsManager:(HBPresetsManager *)manager; +- (instancetype)initWithDelegate:(HBAppDelegate *)delegate queue:(HBQueue *)queue presetsManager:(HBPresetsManager *)manager; { self = [super initWithWindowNibName:@"MainWindow"]; if (self) @@ -168,8 +172,8 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; fPreviewController = [[HBPreviewController alloc] init]; fPreviewController.documentController = self; - _queue = queueController; - _queue.controller = self; + _delegate = delegate; + _queue = queue; presetManager = manager; _currentPreset = manager.defaultPreset; @@ -271,6 +275,8 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; fFiltersViewController = [[HBFiltersViewController alloc] init]; [fFiltersTab setView:[fFiltersViewController view]]; + // Add the observeers + [self.core addObserver:self forKeyPath:@"state" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:HBControllerScanCoreContext]; @@ -283,6 +289,28 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:HBControllerQueueCoreContext]; + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidStartNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + self.bottomConstrain.animator.constant = 0; + self->fRipIndicatorShown = YES; + self->fRipIndicator.hidden = NO; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidCompleteNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + self.bottomConstrain.animator.constant = -WINDOW_HEIGHT_OFFSET; + self->fRipIndicator.hidden = YES; + self->fRipIndicatorShown = NO; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueProgressNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + self.progressInfo = note.userInfo[HBQueueProgressNotificationInfoKey]; + self.progress = [note.userInfo[HBQueueProgressNotificationPercentKey] doubleValue]; + + if (self->_visible) + { + [self updateProgress]; + } + }]; + self.presetsMenuBuilder = [[HBPresetsMenuBuilder alloc] initWithMenu:self.presetsPopup.menu action:@selector(selectPresetFromMenu:) size:[NSFont smallSystemFontSize] @@ -349,12 +377,11 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; } else if (context == HBControllerQueueCoreContext) { - HBState state = self.queue.core.state; - [self updateToolbarButtonsStateForQueueCore:state]; + [self updateToolbarButtonsState]; [self.window.toolbar validateVisibleItems]; if (@available(macOS 10.12.2, *)) { - [self _touchBar_updateButtonsStateForQueueCore:state]; + [self _touchBar_updateQueueButtonsState]; [self _touchBar_validateUserInterfaceItems]; } NSUInteger count = self.queue.pendingItemsCount; @@ -382,9 +409,9 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; } } -- (void)updateToolbarButtonsStateForQueueCore:(HBState)state +- (void)updateToolbarButtonsState { - if (state == HBStatePaused) + if (self.queue.canResume) { _pauseToolbarItem.image = [NSImage imageNamed: @"encode"]; _pauseToolbarItem.label = NSLocalizedString(@"Resume", @"Toolbar Pause Item"); @@ -397,7 +424,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; _pauseToolbarItem.toolTip = NSLocalizedString(@"Pause Encoding", @"Toolbar Pause Item"); } - if (state == HBStateScanning || state == HBStateWorking || state == HBStateSearching || state == HBStateMuxing || state == HBStatePaused) + if (self.queue.isEncoding) { _ripToolbarItem.image = [NSImage imageNamed:@"stopencode"]; _ripToolbarItem.label = NSLocalizedString(@"Stop", @"Toolbar Start/Stop Item"); @@ -443,7 +470,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; { return YES; } - if (action == @selector(rip:) || action == @selector(addToQueue:)) + if (action == @selector(toggleStartCancel:) || action == @selector(addToQueue:)) { return NO; } @@ -453,30 +480,20 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; return YES; } - HBState queueState = _queue.core.state; - - if (action == @selector(rip:)) + if (action == @selector(toggleStartCancel:)) { - if (queueState == HBStateScanning || queueState == HBStateWorking || queueState == HBStateSearching || - queueState == HBStateMuxing || queueState == HBStatePaused) + if (self.queue.isEncoding) { return YES; } else { - return (self.job != nil || _queue.pendingItemsCount > 0); + return (self.job != nil || self.queue.canEncode); } } - if (action == @selector(pause:)) { - if (queueState == HBStatePaused) - { - return YES; - } - else - { - return (queueState == HBStateWorking || queueState == HBStateMuxing); - } + if (action == @selector(togglePauseResume:)) { + return self.queue.canPause || self.queue.canResume; } if (action == @selector(addToQueue:)) @@ -505,11 +522,11 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; { return self.window.attachedSheet == nil; } - if (action == @selector(pause:)) + if (action == @selector(togglePauseResume:)) { return [_queue validateMenuItem:menuItem]; } - if (action == @selector(rip:)) + if (action == @selector(toggleStartCancel:)) { BOOL result = [_queue validateMenuItem:menuItem]; @@ -1005,38 +1022,6 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; fRipIndicator.doubleValue = self.progress; } -- (void)setQueueInfo:(NSAttributedString *)info progress:(double)progress hidden:(BOOL)hidden -{ - self.progressInfo = info; - self.progress = progress; - - if (_visible) - { - [self updateProgress]; - } - - if (hidden) - { - if (fRipIndicatorShown) - { - self.bottomConstrain.animator.constant = -WINDOW_HEIGHT_OFFSET; - fRipIndicator.hidden = YES; - fRipIndicatorShown = NO; - } - } - else - { - // If progress bar hasn't been revealed at the bottom of the window, do - // that now. - if (!fRipIndicatorShown) - { - self.bottomConstrain.animator.constant = 0; - fRipIndicatorShown = YES; - fRipIndicator.hidden = NO; - } - } -} - #pragma mark - Job Handling /** @@ -1073,7 +1058,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; [alert beginSheetModalForWindow:self.window completionHandler:handler]; } - else if ([_queue jobExistAtURL:job.completeOutputURL]) + else if ([_queue itemExistAtURL:job.completeOutputURL]) { NSAlert *alert = [[NSAlert alloc] init]; [alert setMessageText:NSLocalizedString(@"There is already a queue item for this destination.", @"File already exists in queue alert -> message")]; @@ -1123,24 +1108,19 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; [self doAddToQueue]; } - [_queue rip:self]; + [_delegate toggleStartCancel:self]; } /** * Puts up an alert before ultimately calling doRip */ -- (IBAction)rip:(id)sender +- (IBAction)toggleStartCancel:(id)sender { // Rip or Cancel ? - if (_queue.core.state == HBStateWorking || _queue.core.state == HBStatePaused || _queue.core.state == HBStateSearching) + if (_queue.isEncoding || _queue.canEncode) { // Displays an alert asking user if the want to cancel encoding of current job. - [_queue cancelRip:self]; - } - // If there are pending jobs in the queue, then this is a rip the queue - else if (_queue.pendingItemsCount > 0) - { - [_queue rip:self]; + [_delegate toggleStartCancel:self]; } else { @@ -1156,9 +1136,9 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; } } -- (IBAction)pause:(id)sender +- (IBAction)togglePauseResume:(id)sender { - [_queue togglePauseResume:sender]; + [_delegate togglePauseResume:sender]; } #pragma mark - @@ -1213,7 +1193,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; [destinations addObject:job.completeOutputURL]; } - if ([[NSFileManager defaultManager] fileExistsAtPath:job.completeOutputURL.path] || [_queue jobExistAtURL:job.completeOutputURL]) + if ([[NSFileManager defaultManager] fileExistsAtPath:job.completeOutputURL.path] || [_queue itemExistAtURL:job.completeOutputURL]) { fileExists = YES; break; @@ -1248,13 +1228,13 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; [alert beginSheetModalForWindow:self.window completionHandler:^(NSModalResponse returnCode) { if (returnCode == NSAlertSecondButtonReturn) { - [self->_queue addJobsFromArray:jobs]; + [self->_queue addJobs:jobs]; } }]; } else { - [_queue addJobsFromArray:jobs]; + [_queue addJobs:jobs]; } } @@ -1537,7 +1517,7 @@ static NSTouchBarItemIdentifier HBTouchBarActivity = @"fr.handbrake.activity"; NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; item.customizationLabel = NSLocalizedString(@"Start/Stop Encoding", @"Touch bar"); - NSButton *button = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameTouchBarPlayTemplate] target:self action:@selector(rip:)]; + NSButton *button = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameTouchBarPlayTemplate] target:self action:@selector(toggleStartCancel:)]; item.view = button; return item; @@ -1547,7 +1527,7 @@ static NSTouchBarItemIdentifier HBTouchBarActivity = @"fr.handbrake.activity"; NSCustomTouchBarItem *item = [[NSCustomTouchBarItem alloc] initWithIdentifier:identifier]; item.customizationLabel = NSLocalizedString(@"Pause Encoding", @"Touch bar"); - NSButton *button = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameTouchBarPauseTemplate] target:self action:@selector(pause:)]; + NSButton *button = [NSButton buttonWithImage:[NSImage imageNamed:NSImageNameTouchBarPauseTemplate] target:self action:@selector(togglePauseResume:)]; item.view = button; return item; @@ -1592,24 +1572,26 @@ static NSTouchBarItemIdentifier HBTouchBarActivity = @"fr.handbrake.activity"; } } -- (void)_touchBar_updateButtonsStateForQueueCore:(HBState)state; +- (void)_touchBar_updateQueueButtonsState { NSButton *ripButton = (NSButton *)[[self.touchBar itemForIdentifier:HBTouchBarRip] view]; NSButton *pauseButton = (NSButton *)[[self.touchBar itemForIdentifier:HBTouchBarPause] view]; - if (state == HBStateScanning || state == HBStateWorking || state == HBStateSearching || state == HBStateMuxing) + if (self.queue.isEncoding) { ripButton.image = [NSImage imageNamed:NSImageNameTouchBarRecordStopTemplate]; - pauseButton.image = [NSImage imageNamed:NSImageNameTouchBarPauseTemplate]; } - else if (state == HBStatePaused) + else + { + ripButton.image = [NSImage imageNamed:NSImageNameTouchBarPlayTemplate]; + } + + if (self.queue.canResume) { - ripButton.image = [NSImage imageNamed:NSImageNameTouchBarRecordStopTemplate]; pauseButton.image = [NSImage imageNamed:NSImageNameTouchBarPlayTemplate]; } - else if (state == HBStateIdle) + else { - ripButton.image = [NSImage imageNamed:NSImageNameTouchBarPlayTemplate]; pauseButton.image = [NSImage imageNamed:NSImageNameTouchBarPauseTemplate]; } } diff --git a/macosx/HBJob+UIAdditions.h b/macosx/HBJob+UIAdditions.h index 99dcc10c8..7099f08b0 100644 --- a/macosx/HBJob+UIAdditions.h +++ b/macosx/HBJob+UIAdditions.h @@ -16,7 +16,9 @@ @property (nonatomic, readonly) NSArray *containers; @property (nonatomic, readonly) NSAttributedString *attributedTitleDescription; + @property (nonatomic, readonly) NSAttributedString *attributedDescription; +@property (nonatomic, readonly) NSAttributedString *attributedExpandedDescription; @property (nonatomic, readonly) NSString *shortDescription; @property (nonatomic, readonly) NSString *filtersShortDescription; diff --git a/macosx/HBJob+UIAdditions.m b/macosx/HBJob+UIAdditions.m index 4f711bacb..28c7edfe5 100644 --- a/macosx/HBJob+UIAdditions.m +++ b/macosx/HBJob+UIAdditions.m @@ -119,13 +119,8 @@ static NSDictionary *shortHeightAttr; } } -- (NSAttributedString *)titleAttributedDescription +- (NSString *)rangeDescription { - NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init]; - - // Job name - [attrString appendString:self.description withAttributes:titleAttr]; - // Range type NSString *startStopString = @""; if (self.range.type == HBRangeTypeChapters) @@ -163,16 +158,37 @@ static NSDictionary *shortHeightAttr; if (passesString.length) { - [attrString appendString:[NSString stringWithFormat:HBKitLocalizedString(@" (Title %d, %@, %@) ▸ %@\n", @"Title description"), - self.titleIdx, startStopString, passesString, self.outputFileName] - withAttributes:detailAttr]; + return [NSString stringWithFormat:HBKitLocalizedString(@"Title %d, %@, %@", @"Title description"), + self.titleIdx, startStopString, passesString]; } else { - [attrString appendString:[NSString stringWithFormat:HBKitLocalizedString(@" (Title %d, %@) ▸ %@\n", @"Title description"), - self.titleIdx, startStopString, self.outputFileName] - withAttributes:detailAttr]; + return [NSString stringWithFormat:HBKitLocalizedString(@"Title %d, %@", @"Title description"), + self.titleIdx, startStopString]; } +} + +- (NSAttributedString *)rangeAttributedDescription +{ + NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init]; + + [attrString appendString:@"\t" withAttributes:detailAttr]; + [attrString appendString:HBKitLocalizedString(@"Range:", @"Range description") withAttributes:detailBoldAttr]; + [attrString appendString:@" \t" withAttributes:detailAttr]; + [attrString appendString:self.rangeDescription withAttributes:detailAttr]; + [attrString appendString:@"\n" withAttributes:detailAttr]; + + return attrString; +} +- (NSAttributedString *)titleAttributedDescription +{ + NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init]; + + // Job name + [attrString appendString:self.description withAttributes:titleAttr]; + + [attrString appendString:[NSString stringWithFormat:@" (%@) ▸ %@\n", [self rangeDescription], self.outputFileName] + withAttributes:detailAttr]; return attrString; } @@ -231,6 +247,19 @@ static NSDictionary *shortHeightAttr; return attrString; } +- (NSAttributedString *)sourceAttributedDescription +{ + NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init]; + + [attrString appendString:@"\t" withAttributes:detailAttr]; + [attrString appendString:HBKitLocalizedString(@"Source:", @"Source description") withAttributes:detailBoldAttr]; + [attrString appendString:@" \t" withAttributes:detailAttr]; + [attrString appendString:self.fileURL.path withAttributes:detailAttr]; + [attrString appendString:@"\n" withAttributes:detailAttr]; + + return attrString; +} + - (NSAttributedString *)destinationAttributedDescription { NSMutableAttributedString *attrString = [[NSMutableAttributedString alloc] init]; @@ -629,21 +658,31 @@ static NSDictionary *shortHeightAttr; @autoreleasepool { - [attrString appendAttributedString:[self titleAttributedDescription]]; [attrString appendAttributedString:[self presetAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; + [attrString appendAttributedString:[self sourceAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; + [attrString appendAttributedString:[self destinationAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; [attrString appendAttributedString:[self formatAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; + [attrString appendAttributedString:[self rangeAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; [attrString appendAttributedString:[self dimensionsAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; [attrString appendAttributedString:[self filtersAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; [attrString appendAttributedString:[self videoAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; if (self.audio.countOfTracks > 1) { [attrString appendAttributedString:[self audioAttributedDescription]]; + [attrString appendString:@"\n" withAttributes: detailAttr]; } if (self.subtitles.countOfTracks > 1) { [attrString appendAttributedString:[self subtitlesAttributedDescription]]; } - [attrString appendAttributedString:[self destinationAttributedDescription]]; } [attrString deleteCharactersInRange:NSMakeRange(attrString.length - 1, 1)]; @@ -651,6 +690,7 @@ static NSDictionary *shortHeightAttr; return attrString; } + #pragma mark - Short descriptions - (NSString *)videoShortDescription diff --git a/macosx/HBQueue.h b/macosx/HBQueue.h new file mode 100644 index 000000000..0e176292b --- /dev/null +++ b/macosx/HBQueue.h @@ -0,0 +1,93 @@ +/* HBQueue.h $ + + This file is part of the HandBrake source code. + Homepage: . + It may be used under the terms of the GNU General Public License. */ + +#import + +#import "HBCore.h" +#import "HBDistributedArray.h" +#import "HBQueueItem.h" +#import "HBJobOutputFileWriter.h" + +NS_ASSUME_NONNULL_BEGIN + +extern NSString * const HBQueueDidAddItemNotification; +extern NSString * const HBQueueDidRemoveItemNotification; +extern NSString * const HBQueueDidChangeItemNotification; +extern NSString * const HBQueueItemNotificationIndexesKey; // NSIndexSet + +extern NSString * const HBQueueDidMoveItemNotification; +extern NSString * const HBQueueItemNotificationSourceIndexesKey; // NSArray +extern NSString * const HBQueueItemNotificationTargetIndexesKey; // NSArray + +extern NSString * const HBQueueReloadItemsNotification; + +extern NSString * const HBQueueLowSpaceAlertNotification; + +extern NSString * const HBQueueProgressNotification; +extern NSString * const HBQueueProgressNotificationPercentKey; // NSNumber - double +extern NSString * const HBQueueProgressNotificationInfoKey; // NSString + +extern NSString * const HBQueueDidStartNotification; +extern NSString * const HBQueueDidCompleteNotification; + +extern NSString * const HBQueueDidCompleteItemNotification; +extern NSString * const HBQueueDidCompleteItemNotificationItemKey; // HBQueueItem + +@interface HBQueue : NSObject + +- (instancetype)initWithURL:(NSURL *)queueURL; + +@property (nonatomic, readonly) NSURL *queueURL; + +@property (nonatomic, readonly) HBCore *core; +@property (nonatomic, readonly) HBDistributedArray *items; + +@property (nonatomic, nullable) HBQueueItem *currentItem; +@property (nonatomic, nullable) HBJobOutputFileWriter *currentLog; + +@property (nonatomic) NSUInteger pendingItemsCount; +@property (nonatomic) NSUInteger completedItemsCount; + +@property (nonatomic) NSUndoManager *undoManager; + +- (void)addJob:(HBJob *)job; +- (void)addJobs:(NSArray *)jobs; + +- (void)addQueueItems:(NSArray *)items atIndexes:(NSIndexSet *)indexes; +- (void)removeQueueItemAtIndex:(NSUInteger)index; +- (void)removeQueueItemsAtIndexes:(NSIndexSet *)indexes; +- (void)moveQueueItems:(NSArray *)items toIndex:(NSUInteger)index; + +- (BOOL)itemExistAtURL:(NSURL *)url; + +- (void)removeAllItems; +- (void)removeCompletedAndCancelledItems; +- (void)removeNotWorkingItems; +- (void)removeCompletedItems; + +- (void)resetItemsStateAtIndexes:(NSIndexSet *)indexes; +- (void)resetAllItems; +- (void)resetFailedItems; + +- (void)setEncodingJobsAsPending; + +@property (nonatomic, readonly) BOOL canEncode; +@property (nonatomic, readonly) BOOL isEncoding; + +- (void)start; +- (void)cancelCurrentItemAndContinue; +- (void)finishCurrentAndStop; +- (void)cancelCurrentItemAndStop; + +@property (nonatomic, readonly) BOOL canPause; +- (void)pause; + +@property (nonatomic, readonly) BOOL canResume; +- (void)resume; + +@end + +NS_ASSUME_NONNULL_END diff --git a/macosx/HBQueue.m b/macosx/HBQueue.m new file mode 100644 index 000000000..6aee92452 --- /dev/null +++ b/macosx/HBQueue.m @@ -0,0 +1,765 @@ +/* HBQueue.m $ + + This file is part of the HandBrake source code. + Homepage: . + It may be used under the terms of the GNU General Public License. */ + +#import "HBQueue.h" +#import "NSArray+HBAdditions.h" + +NSString * const HBQueueDidAddItemNotification = @"HBQueueDidAddItemNotification"; +NSString * const HBQueueDidRemoveItemNotification = @"HBQueueDidRemoveItemNotification"; +NSString * const HBQueueDidChangeItemNotification = @"HBQueueDidChangeItemNotification"; + +NSString * const HBQueueItemNotificationIndexesKey = @"HBQueueReloadItemsNotification"; + +NSString * const HBQueueDidMoveItemNotification = @"HBQueueDidMoveItemNotification"; +NSString * const HBQueueItemNotificationSourceIndexesKey = @"HBQueueItemNotificationSourceIndexesKey"; +NSString * const HBQueueItemNotificationTargetIndexesKey = @"HBQueueItemNotificationTargetIndexesKey"; + +NSString * const HBQueueReloadItemsNotification = @"HBQueueReloadItemsNotification"; + +NSString * const HBQueueLowSpaceAlertNotification = @"HBQueueLowSpaceAlertNotification"; + +NSString * const HBQueueProgressNotification = @"HBQueueProgressNotification"; +NSString * const HBQueueProgressNotificationPercentKey = @"HBQueueProgressNotificationPercentKey"; +NSString * const HBQueueProgressNotificationInfoKey = @"HBQueueProgressNotificationInfoKey"; + +NSString * const HBQueueDidStartNotification = @"HBQueueDidStartNotification"; +NSString * const HBQueueDidCompleteNotification = @"HBQueueDidCompleteNotification"; + +NSString * const HBQueueDidCompleteItemNotification = @"HBQueueDidCompleteItemNotification"; +NSString * const HBQueueDidCompleteItemNotificationItemKey = @"HBQueueDidCompleteItemNotificationItemKey"; + +@interface HBQueue () + +@property (nonatomic) BOOL stop; + +@end + +@implementation HBQueue + +- (instancetype)initWithURL:(NSURL *)queueURL +{ + self = [super init]; + if (self) + { + NSInteger loggingLevel = [NSUserDefaults.standardUserDefaults integerForKey:@"LoggingLevel"]; + + // Init a separate instance of libhb for the queue + _core = [[HBCore alloc] initWithLogLevel:loggingLevel name:@"QueueCore"]; + _core.automaticallyPreventSleep = NO; + + _items = [[HBDistributedArray alloc] initWithURL:queueURL class:[HBQueueItem class]]; + + // Set up the observers + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadQueue) name:HBDistributedArrayChanged object:_items]; + + [self updateStats]; + } + return self; +} + +#pragma mark - Public methods + +- (void)addJob:(HBJob *)item +{ + NSParameterAssert(item); + [self addJobs:@[item]]; +} + +- (void)addJobs:(NSArray *)jobs; +{ + NSParameterAssert(jobs); + + NSMutableArray *itemsToAdd = [NSMutableArray array]; + for (HBJob *job in jobs) + { + HBQueueItem *item = [[HBQueueItem alloc] initWithJob:job]; + [itemsToAdd addObject:item]; + } + if (itemsToAdd.count) + { + NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(self.items.count, itemsToAdd.count)]; + [self addQueueItems:itemsToAdd atIndexes:indexes]; + } +} + +- (BOOL)itemExistAtURL:(NSURL *)url +{ + NSParameterAssert(url); + + for (HBQueueItem *item in self.items) + { + if ((item.state == HBQueueItemStateReady || item.state == HBQueueItemStateWorking) + && [item.completeOutputURL isEqualTo:url]) + { + return YES; + } + } + return NO; +} + +- (NSUInteger)count +{ + return self.items.count; +} + +- (void)addQueueItems:(NSArray *)items atIndexes:(NSIndexSet *)indexes +{ + NSParameterAssert(items); + NSParameterAssert(indexes); + [self.items beginTransaction]; + + // Forward + NSUInteger currentIndex = indexes.firstIndex; + NSUInteger currentObjectIndex = 0; + while (currentIndex != NSNotFound) + { + [self.items insertObject:items[currentObjectIndex] atIndex:currentIndex]; + currentIndex = [indexes indexGreaterThanIndex:currentIndex]; + currentObjectIndex++; + } + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidAddItemNotification object:self userInfo:@{HBQueueItemNotificationIndexesKey: indexes}]; + + NSUndoManager *undo = self.undoManager; + [[undo prepareWithInvocationTarget:self] removeQueueItemsAtIndexes:indexes]; + + if (!undo.isUndoing) + { + if (items.count == 1) + { + [undo setActionName:NSLocalizedString(@"Add Job To Queue", @"Queue undo action name")]; + } + else + { + [undo setActionName:NSLocalizedString(@"Add Jobs To Queue", @"Queue undo action name")]; + } + } + + [self updateStats]; + [self.items commit]; +} + +- (void)removeQueueItemAtIndex:(NSUInteger)index +{ + [self removeQueueItemsAtIndexes:[NSIndexSet indexSetWithIndex:index]]; +} + +- (void)removeQueueItemsAtIndexes:(NSIndexSet *)indexes +{ + NSParameterAssert(indexes); + + if (indexes.count == 0) + { + return; + } + + [self.items beginTransaction]; + + NSArray *removeItems = [self.items objectsAtIndexes:indexes]; + + if (self.items.count > indexes.lastIndex) + { + [self.items removeObjectsAtIndexes:indexes]; + } + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidRemoveItemNotification object:self userInfo:@{HBQueueItemNotificationIndexesKey: indexes}]; + + NSUndoManager *undo = self.undoManager; + [[undo prepareWithInvocationTarget:self] addQueueItems:removeItems atIndexes:indexes]; + + if (!undo.isUndoing) + { + if (indexes.count == 1) + { + [undo setActionName:NSLocalizedString(@"Remove Job From Queue", @"Queue undo action name")]; + } + else + { + [undo setActionName:NSLocalizedString(@"Remove Jobs From Queue", @"Queue undo action name")]; + } + } + + [self updateStats]; + [self.items commit]; +} + +- (void)moveQueueItems:(NSArray *)items toIndex:(NSUInteger)index +{ + [self.items beginTransaction]; + + NSMutableArray *source = [NSMutableArray array]; + NSMutableArray *dest = [NSMutableArray array]; + + for (id object in items.reverseObjectEnumerator) + { + NSUInteger sourceIndex = [self.items indexOfObject:object]; + [self.items removeObjectAtIndex:sourceIndex]; + + if (sourceIndex < index) + { + index--; + } + + [self.items insertObject:object atIndex:index]; + + [source addObject:@(index)]; + [dest addObject:@(sourceIndex)]; + } + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidMoveItemNotification + object:self + userInfo:@{HBQueueItemNotificationSourceIndexesKey: dest, + HBQueueItemNotificationTargetIndexesKey: source}]; + + NSUndoManager *undo = self.undoManager; + [[undo prepareWithInvocationTarget:self] moveQueueItemsAtIndexes:source toIndexes:dest]; + + if (!undo.isUndoing) + { + if (items.count == 1) + { + [undo setActionName:NSLocalizedString(@"Move Job in Queue", @"Queue undo action name")]; + } + else + { + [undo setActionName:NSLocalizedString(@"Move Jobs in Queue", @"Queue undo action name")]; + } + } + + [self.items commit]; +} + +- (void)moveQueueItemsAtIndexes:(NSArray *)source toIndexes:(NSArray *)dest +{ + [self.items beginTransaction]; + + 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.items objectAtIndex:sourceIndex]; + [self.items removeObjectAtIndex:sourceIndex]; + [self.items insertObject:obj atIndex:destIndex]; + } + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidMoveItemNotification + object:self + userInfo:@{HBQueueItemNotificationSourceIndexesKey: newDest, + HBQueueItemNotificationTargetIndexesKey: newSource}]; + + NSUndoManager *undo = self.undoManager; + [[undo prepareWithInvocationTarget:self] moveQueueItemsAtIndexes:newSource toIndexes:newDest]; + + if (!undo.isUndoing) + { + if (source.count == 1) + { + [undo setActionName:NSLocalizedString(@"Move Job in Queue", @"Queue undo action name")]; + } + else + { + [undo setActionName:NSLocalizedString(@"Move Jobs in Queue", @"Queue undo action name")]; + } + } + + [self.items commit]; +} + +/** + * This method will clear the queue of any encodes that are not still pending + * this includes both successfully completed encodes as well as canceled encodes + */ +- (void)removeCompletedAndCancelledItems +{ + [self.items beginTransaction]; + NSIndexSet *indexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { + return (item.state == HBQueueItemStateCompleted || item.state == HBQueueItemStateCanceled); + }]; + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidRemoveItemNotification object:self userInfo:@{@"indexes": indexes}]; + [self.items commit]; +} + +/** + * This method will clear the queue of all encodes. effectively creating an empty queue + */ +- (void)removeAllItems +{ + [self.items beginTransaction]; + + [self removeQueueItemsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.items.count)]]; + [self.items commit]; +} + +- (void)removeNotWorkingItems +{ + [self.items beginTransaction]; + NSIndexSet *indexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { + return (item.state != HBQueueItemStateWorking); + }]; + [self removeQueueItemsAtIndexes:indexes]; + [self.items commit]; +} + +- (void)removeCompletedItems +{ + [self.items beginTransaction]; + NSIndexSet *indexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { + return (item.state == HBQueueItemStateCompleted); + }]; + [self removeQueueItemsAtIndexes:indexes]; + [self.items commit]; +} + +- (void)resetItemsStateAtIndexes:(NSIndexSet *)indexes +{ + if ([self.items beginTransaction] == HBDistributedArrayContentReload) + { + // Do not execture the action if the array changed. + [self.items commit]; + return; + } + + NSMutableIndexSet *updatedIndexes = [NSMutableIndexSet indexSet]; + + NSUInteger currentIndex = indexes.firstIndex; + while (currentIndex != NSNotFound) { + HBQueueItem *item = self.items[currentIndex]; + + if (item.state == HBQueueItemStateCanceled || item.state == HBQueueItemStateCompleted || item.state == HBQueueItemStateFailed) + { + item.state = HBQueueItemStateReady; + [updatedIndexes addIndex:currentIndex]; + } + currentIndex = [indexes indexGreaterThanIndex:currentIndex]; + } + + [self updateStats]; + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidChangeItemNotification object:self userInfo:@{HBQueueItemNotificationIndexesKey: indexes}]; + [self.items commit]; +} + +- (void)resetAllItems +{ + [self.items beginTransaction]; + NSIndexSet *indexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { + return (item.state != HBQueueItemStateWorking); + }]; + [self resetItemsStateAtIndexes:indexes]; + [self.items commit]; +} + +- (void)resetFailedItems +{ + [self.items beginTransaction]; + NSIndexSet *indexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { + return (item.state == HBQueueItemStateFailed); + }]; + [self resetItemsStateAtIndexes:indexes]; + [self.items commit]; +} + +/** + * This method will set any item marked as encoding back to pending + * currently used right after a queue reload + */ +- (void)setEncodingJobsAsPending +{ + [self.items beginTransaction]; + + NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; + NSUInteger idx = 0; + for (HBQueueItem *item in self.items) + { + // We want to keep any queue item that is pending or was previously being encoded + if (item.state == HBQueueItemStateWorking) + { + item.state = HBQueueItemStateReady; + [indexes addIndex:idx]; + } + idx++; + } + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidChangeItemNotification object:self userInfo:@{HBQueueItemNotificationIndexesKey: indexes}]; + [self.items commit]; +} + +- (BOOL)canEncode +{ + return self.pendingItemsCount > 0; +} + +- (BOOL)isEncoding +{ + HBState s = self.core.state; + return (s == HBStateScanning) || (s == HBStatePaused) || (s == HBStateWorking) || (s == HBStateMuxing) || (s == HBStateSearching); +} + +- (BOOL)canPause +{ + HBState s = self.core.state; + return (s == HBStateWorking || s == HBStateMuxing); +} + +- (void)pause +{ + [self.core pause]; + [self.core allowSleep]; +} + +- (BOOL)canResume +{ + return self.core.state == HBStatePaused; +} + +- (void)resume +{ + [self.core resume]; + [self.core preventSleep]; +} + +#pragma mark - Private queue editing methods + +/** + * Reloads the queue, this is called + * when another HandBrake instances modifies the queue + */ +- (void)reloadQueue +{ + [self updateStats]; + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueReloadItemsNotification object:self]; +} + +- (void)updateStats +{ + // lets get the stats on the status of the queue array + NSUInteger pendingCount = 0; + NSUInteger completedCount = 0; + + for (HBQueueItem *item in self.items) + { + if (item.state == HBQueueItemStateReady) + { + pendingCount++; + } + if (item.state == HBQueueItemStateCompleted) + { + completedCount++; + } + } + + self.pendingItemsCount = pendingCount; + self.completedItemsCount = completedCount; +} + +- (BOOL)_isDiskSpaceLowAtURL:(NSURL *)url +{ + if ([[NSUserDefaults standardUserDefaults] boolForKey:@"HBQueuePauseIfLowSpace"]) + { + NSURL *volumeURL = nil; + NSDictionary *attrs = [url resourceValuesForKeys:@[NSURLIsVolumeKey, NSURLVolumeURLKey] error:NULL]; + long long minCapacity = [[[NSUserDefaults standardUserDefaults] stringForKey:@"HBQueueMinFreeSpace"] longLongValue] * 1000000000; + + volumeURL = [attrs[NSURLIsVolumeKey] boolValue] ? url : attrs[NSURLVolumeURLKey]; + + if (volumeURL) + { + [volumeURL removeCachedResourceValueForKey:NSURLVolumeAvailableCapacityKey]; + attrs = [volumeURL resourceValuesForKeys:@[NSURLVolumeAvailableCapacityKey] error:NULL]; + + if (attrs[NSURLVolumeAvailableCapacityKey]) + { + if ([attrs[NSURLVolumeAvailableCapacityKey] longLongValue] < minCapacity) + { + return YES; + } + } + } + } + + return NO; +} + +/** + * Used to get the next pending queue item and return it if found + */ +- (HBQueueItem *)getNextPendingQueueItem +{ + for (HBQueueItem *item in self.items) + { + if (item.state == HBQueueItemStateReady) + { + return item; + } + } + return nil; +} + +- (void)start +{ + if (self.canEncode && self.core.state == HBStateIdle) + { + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidStartNotification object:self]; + [self.core preventSleep]; + [self encodeNextQueueItem]; + } +} + +/** + * Starts the queue + */ +- (void)encodeNextQueueItem +{ + [self.items beginTransaction]; + self.currentItem = nil; + + // since we have completed an encode, we go to the next + if (self.stop) + { + [HBUtilities writeToActivityLog:"Queue manually stopped"]; + + self.stop = NO; + [self.core allowSleep]; + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidCompleteNotification object:self]; + } + else + { + // Check to see if there are any more pending items in the queue + HBQueueItem *nextItem = [self getNextPendingQueueItem]; + + if (nextItem && [self _isDiskSpaceLowAtURL:nextItem.outputURL]) + { + // Disk space is low, show an alert + [HBUtilities writeToActivityLog:"Queue Stopped, low space on destination disk"]; + [self.core allowSleep]; + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidCompleteNotification object:self]; + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueLowSpaceAlertNotification object:self]; + } + // If we still have more pending items in our queue, lets go to the next one + else if (nextItem) + { + // now we mark the queue item as working so another instance can not come along and try to scan it while we are scanning + nextItem.state = HBQueueItemStateWorking; + + // Tell HB to output a new activity log file for this encode + self.currentLog = [[HBJobOutputFileWriter alloc] initWithJob:nextItem.job]; + if (self.currentLog) + { + [[HBOutputRedirect stderrRedirect] addListener:self.currentLog]; + [[HBOutputRedirect stdoutRedirect] addListener:self.currentLog]; + } + + self.currentItem = nextItem; + NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:[self.items indexOfObject:nextItem]]; + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidChangeItemNotification object:self userInfo:@{HBQueueItemNotificationIndexesKey: indexes}]; + + [self updateStats]; + + // now we can go ahead and scan the new pending queue item + [self encodeItem:nextItem]; + + // erase undo manager history + [self.undoManager removeAllActions]; + } + else + { + [HBUtilities writeToActivityLog:"Queue Done, there are no more pending encodes"]; + [self.core allowSleep]; + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidCompleteNotification object:self]; + } + } + [self.items commit]; +} + +- (void)completedItem:(HBQueueItem *)item result:(HBCoreResult)result; +{ + NSParameterAssert(item); + [self.items 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; + + // Mark the encode just finished + switch (result) { + case HBCoreResultDone: + item.state = HBQueueItemStateCompleted; + break; + case HBCoreResultCanceled: + item.state = HBQueueItemStateCanceled; + break; + default: + item.state = HBQueueItemStateFailed; + break; + } + + // Update UI + NSString *info = nil; + switch (result) { + case HBCoreResultDone: + info = NSLocalizedString(@"Encode Finished.", @"Queue status"); + break; + case HBCoreResultCanceled: + info = NSLocalizedString(@"Encode Canceled.", @"Queue status"); + break; + default: + info = NSLocalizedString(@"Encode Failed.", @"Queue status"); + break; + } + + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueProgressNotification object:self userInfo:@{HBQueueProgressNotificationPercentKey: @1.0, + HBQueueProgressNotificationInfoKey: info}]; + + NSInteger index = [self.items indexOfObject:item]; + NSIndexSet *indexes = index > -1 ? [NSIndexSet indexSetWithIndex:index] : [NSIndexSet indexSet]; + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueDidCompleteItemNotification object:self userInfo:@{HBQueueDidCompleteItemNotificationItemKey: item, + HBQueueItemNotificationIndexesKey: indexes}]; + + [self.items commit]; +} + +/** + * Here we actually tell hb_scan to perform the source scan, using the path to source and title number + */ +- (void)encodeItem:(HBQueueItem *)item +{ + NSParameterAssert(item); + + // Progress handler + void (^progressHandler)(HBState state, HBProgress progress, NSString *info) = ^(HBState state, HBProgress progress, NSString *info) + { + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueProgressNotification object:self userInfo:@{HBQueueProgressNotificationPercentKey: @0, + HBQueueProgressNotificationInfoKey: info}]; + }; + + // Completion handler + void (^completionHandler)(HBCoreResult result) = ^(HBCoreResult result) + { + if (result == HBCoreResultDone) + { + [self realEncodeItem:item]; + } + else + { + [self completedItem:item 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:item.fileURL + titleIndex:item.job.titleIdx + previews:10 + minDuration:0 + progressHandler:progressHandler + completionHandler:completionHandler]; +} + +/** + * This assumes that we have re-scanned and loaded up a new queue item to send to libhb + */ +- (void)realEncodeItem:(HBQueueItem *)item +{ + NSParameterAssert(item); + + HBJob *job = item.job; + + // Reset the title in the job. + job.title = self.core.titles.firstObject; + + NSParameterAssert(job); + + HBStateFormatter *formatter = [[HBStateFormatter alloc] init]; + formatter.title = job.outputFileName; + self.core.stateFormatter = formatter; + + // Progress handler + void (^progressHandler)(HBState state, HBProgress progress, NSString *info) = ^(HBState state, HBProgress progress, NSString *info) + { + if (state == HBStateMuxing) + { + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueProgressNotification + object:self + userInfo:@{HBQueueProgressNotificationPercentKey: @1, + HBQueueProgressNotificationInfoKey: info}]; + } + else + { + [NSNotificationCenter.defaultCenter postNotificationName:HBQueueProgressNotification + object:self + userInfo:@{HBQueueProgressNotificationPercentKey: @(progress.percent), + HBQueueProgressNotificationInfoKey: info}]; + } + }; + + // Completion handler + void (^completionHandler)(HBCoreResult result) = ^(HBCoreResult result) + { + [self completedItem:item result:result]; + [self encodeNextQueueItem]; + }; + + // We should be all setup so let 'er rip + [self.core encodeJob:job progressHandler:progressHandler completionHandler:completionHandler]; + + // We are done using the title, remove it from the job + job.title = nil; +} + +/** + * Cancels the current job + */ +- (void)doCancelCurrentItem +{ + if (self.core.state == HBStateScanning) + { + [self.core cancelScan]; + } + else + { + [self.core cancelEncode]; + } +} + +/** + * Cancels the current job and starts processing the next in queue. + */ +- (void)cancelCurrentItemAndContinue +{ + [self doCancelCurrentItem]; +} + +/** + * Cancels the current job and stops libhb from processing the remaining encodes. + */ +- (void)cancelCurrentItemAndStop +{ + self.stop = YES; + [self doCancelCurrentItem]; +} + +/** + * Finishes the current job and stops libhb from processing the remaining encodes. + */ +- (void)finishCurrentAndStop +{ + self.stop = YES; +} + +@end diff --git a/macosx/HBQueueController.h b/macosx/HBQueueController.h index 597b68a1d..722416cd1 100644 --- a/macosx/HBQueueController.h +++ b/macosx/HBQueueController.h @@ -10,36 +10,17 @@ NS_ASSUME_NONNULL_BEGIN @class HBAppDelegate; @class HBController; -@class HBOutputPanelController; -@class HBCore; -@class HBJob; +@class HBQueue; @interface HBQueueController : NSWindowController -- (instancetype)initWithURL:(NSURL *)queueURL; +- (instancetype)initWithQueue:(HBQueue *)queue; -/// The HBCore used for encoding. -@property (nonatomic, readonly) HBCore *core; +@property (nonatomic, readonly) HBQueue *queue; -@property (nonatomic, assign, nullable) HBController *controller; @property (nonatomic, weak, nullable) HBAppDelegate *delegate; -@property (nonatomic, readonly) NSUInteger count; -@property (nonatomic, readonly) NSUInteger pendingItemsCount; - -- (void)addJob:(HBJob *)item; -- (void)addJobsFromArray:(NSArray *)items; - -- (BOOL)jobExistAtURL:(NSURL *)url; - -- (void)removeAllJobs; -- (void)removeCompletedJobs; - -- (void)setEncodingJobsAsPending; - -- (IBAction)rip:(id)sender; -- (IBAction)cancelRip:(id)sender; - +- (IBAction)toggleStartCancel:(id)sender; - (IBAction)togglePauseResume:(id)sender; @end diff --git a/macosx/HBQueueController.m b/macosx/HBQueueController.m index 87d0ceefe..40ae17334 100644 --- a/macosx/HBQueueController.m +++ b/macosx/HBQueueController.m @@ -6,112 +6,65 @@ #import "HBQueueController.h" -#import "HBQueueItem.h" - -#import "HBController.h" #import "HBAppDelegate.h" -#import "HBTableView.h" -#import "HBQueueItemView.h" - -#import "NSArray+HBAdditions.h" -#import "HBUtilities.h" +#import "HBQueue.h" +#import "HBQueueTableViewController.h" +#import "HBQueueDetailsViewController.h" #import "HBDockTile.h" - -#import "HBOutputRedirect.h" -#import "HBJobOutputFileWriter.h" #import "HBPreferencesController.h" +#import "NSArray+HBAdditions.h" @import HandBrakeKit; -// Pasteboard type for or drag operations -#define DragDropSimplePboardType @"HBQueueCustomTableViewPboardType" - -// DockTile update frequency in total percent increment -#define dockTileUpdateFrequency 0.1f - static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; -@interface HBQueueController () +@interface HBQueueController () + +@property (weak) IBOutlet NSSplitView *splitView; +@property (nonatomic) NSSplitViewController *splitViewController; +@property (nonatomic) HBQueueTableViewController *tableViewController; +@property (nonatomic) HBQueueDetailsViewController *detailsViewController; /// Whether the window is visible or occluded, /// useful to avoid updating the UI needlessly @property (nonatomic) BOOL visible; -// Progress -@property (nonatomic, strong) NSAttributedString *progressInfo; -@property (nonatomic, strong) NSDictionary *monospacedAttr; - @property (nonatomic, readonly) HBDockTile *dockTile; -@property (nonatomic, readwrite) double dockIconProgress; - -@property (unsafe_unretained) IBOutlet NSTextField *progressTextField; -@property (unsafe_unretained) IBOutlet NSTextField *countTextField; -@property (unsafe_unretained) IBOutlet HBTableView *tableView; +@property (nonatomic) double dockIconProgress; @property (nonatomic) IBOutlet NSToolbarItem *ripToolbarItem; @property (nonatomic) IBOutlet NSToolbarItem *pauseToolbarItem; -@property (nonatomic) NSTableCellView *dummyCell; -@property (nonatomic) NSLayoutConstraint *dummyCellWidth; - -@property (nonatomic, readonly) HBDistributedArray *items; - -@property (nonatomic) HBQueueItem *currentItem; -@property (nonatomic) HBJobOutputFileWriter *currentLog; - -@property (nonatomic, readwrite) BOOL stop; - -@property (nonatomic, readwrite) NSUInteger pendingItemsCount; -@property (nonatomic, readwrite) NSUInteger completedItemsCount; - -@property (nonatomic) NSArray *dragNodesArray; - @end @interface HBQueueController (TouchBar) -- (void)_touchBar_updateButtonsStateForQueueCore:(HBState)state; +- (void)_touchBar_updateButtonsState; - (void)_touchBar_validateUserInterfaceItems; @end @implementation HBQueueController -- (instancetype)initWithURL:(NSURL *)queueURL; +- (instancetype)initWithQueue:(HBQueue *)queue { - NSParameterAssert(queueURL); + NSParameterAssert(queue); if (self = [super initWithWindowNibName:@"Queue"]) { // Load the dockTile and instiante initial text fields - _dockTile = [[HBDockTile alloc] initWithDockTile:[[NSApplication sharedApplication] dockTile] - image:[[NSApplication sharedApplication] applicationIconImage]]; - - NSInteger loggingLevel = [[NSUserDefaults standardUserDefaults] integerForKey:@"LoggingLevel"]; - - // Init a separate instance of libhb for the queue - _core = [[HBCore alloc] initWithLogLevel:loggingLevel name:@"QueueCore"]; - _core.automaticallyPreventSleep = NO; + _dockTile = [[HBDockTile alloc] initWithDockTile:NSApplication.sharedApplication.dockTile + image:NSApplication.sharedApplication.applicationIconImage]; - // Progress - _monospacedAttr = @{NSFontAttributeName: [NSFont monospacedDigitSystemFontOfSize:[NSFont smallSystemFontSize] weight:NSFontWeightRegular]}; - _progressInfo = [[NSAttributedString alloc] initWithString:@""]; + // Init state + _queue = queue; - // Load the queue from disk. - _items = [[HBDistributedArray alloc] initWithURL:queueURL class:[HBQueueItem class]]; - [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadQueue) name:HBDistributedArrayChanged object:_items]; - - [NSUserNotificationCenter defaultUserNotificationCenter].delegate = self; + NSUserNotificationCenter.defaultUserNotificationCenter.delegate = self; } return self; } -- (void)dealloc -{ - [[NSNotificationCenter defaultCenter] removeObserver:self]; -} - - (void)windowDidLoad { if (@available (macOS 10.12, *)) @@ -119,33 +72,116 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; self.window.tabbingMode = NSWindowTabbingModeDisallowed; } - // lets setup our queue list table view for drag and drop here - [self.tableView registerForDraggedTypes:@[DragDropSimplePboardType]]; - [self.tableView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES]; - [self.tableView setVerticalMotionCanBeginDrag:YES]; + _queue.undoManager = self.window.undoManager; + + // Set up the child view controllers + _splitViewController = [[NSSplitViewController alloc] init]; + _splitViewController.splitView = _splitView; + _splitViewController.view.wantsLayer = YES; + _splitViewController.splitView.vertical = YES; + _splitViewController.splitView.autosaveName = @"HBQueueSplitViewAutosave"; + _splitViewController.splitView.identifier = @"HBQueueSplitViewIdentifier"; + + _tableViewController = [[HBQueueTableViewController alloc] initWithQueue:self.queue delegate:self]; + _detailsViewController = [[HBQueueDetailsViewController alloc] initWithDelegate:self]; - [self updateQueueStats]; + NSSplitViewItem *tableItem = [NSSplitViewItem splitViewItemWithViewController:_tableViewController]; + tableItem.minimumThickness = 160; - [self.core addObserver:self forKeyPath:@"state" + [_splitViewController addSplitViewItem:tableItem]; + + NSSplitViewItem *detailsItem = [NSSplitViewItem splitViewItemWithViewController:_detailsViewController]; + detailsItem.canCollapse = YES; + detailsItem.minimumThickness = 240; + + [_splitViewController addSplitViewItem:detailsItem]; + + self.window.contentViewController = _splitViewController; + + self.window.frameAutosaveName = @"HBQueueWindowFrameAutosave"; + [self.window setFrameFromString: @"HBQueueWindowFrameAutosave"]; + + // Set up observers + [self.queue.core addObserver:self forKeyPath:@"state" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:HBControllerQueueCoreContext]; - [self addObserver:self forKeyPath:@"pendingItemsCount" + [self.queue addObserver:self forKeyPath:@"pendingItemsCount" options:NSKeyValueObservingOptionNew | NSKeyValueObservingOptionInitial context:HBControllerQueueCoreContext]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueLowSpaceAlertNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + [self queueLowDiskSpaceAlert]; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidCompleteNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + // Since there are no more items to encode, go to queueCompletedAlerts + // for user specified alerts after queue completed + [self queueCompletedAlerts]; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueProgressNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + // Update dock icon + double progress = [note.userInfo[HBQueueProgressNotificationPercentKey] doubleValue]; + double hours = 1; + double minutes = 1; + double seconds = 1; + +#define dockTileUpdateFrequency 0.1f + + if (self.dockIconProgress < 100.0 * progress) + { + [self.dockTile updateDockIcon:progress hours:hours minutes:minutes seconds:seconds]; + self.dockIconProgress += dockTileUpdateFrequency; + } + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidCompleteItemNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + // Restore dock icon + [self.dockTile updateDockIcon:-1.0 withETA:@""]; + self.dockIconProgress = 0; + + // Run the per item notification and actions + HBQueueItem *item = note.userInfo[HBQueueDidCompleteItemNotificationItemKey]; + if (item.state == HBQueueItemStateCompleted) + { + [self sendToExternalApp:item]; + } + + if (item.state == HBQueueItemStateCompleted || item.state == HBQueueItemStateFailed) + { + [self itemCompletedAlerts:item]; + } + }]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if (context == HBControllerQueueCoreContext) { - HBState state = self.core.state; - [self updateToolbarButtonsStateForQueueCore:state]; + [self updateToolbarButtonsState]; [self.window.toolbar validateVisibleItems]; + if (@available(macOS 10.12.2, *)) { - [self _touchBar_updateButtonsStateForQueueCore:state]; + [self _touchBar_updateButtonsState]; [self _touchBar_validateUserInterfaceItems]; } + + NSString *string; + if (self.queue.pendingItemsCount == 0) + { + string = NSLocalizedString(@"No encode pending", @"Queue status"); + } + else if (self.queue.pendingItemsCount == 1) + { + string = [NSString stringWithFormat: NSLocalizedString(@"%d encode pending", @"Queue status"), self.queue.pendingItemsCount]; + } + else + { + string = [NSString stringWithFormat: NSLocalizedString(@"%d encodes pending", @"Queue status"), self.queue.pendingItemsCount]; + } + + self.window.title = [NSString stringWithFormat: NSLocalizedString(@"Queue (%@)", @"Queue window title"), string]; } else { @@ -155,9 +191,9 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; #pragma mark Toolbar -- (void)updateToolbarButtonsStateForQueueCore:(HBState)state +- (void)updateToolbarButtonsState { - if (state == HBStatePaused) + if (self.queue.canResume) { _pauseToolbarItem.image = [NSImage imageNamed: @"encode"]; _pauseToolbarItem.label = NSLocalizedString(@"Resume", @"Toolbar Pause Item"); @@ -170,7 +206,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; _pauseToolbarItem.toolTip = NSLocalizedString(@"Pause Encoding", @"Toolbar Pause Item"); } - if (state == HBStateScanning || state == HBStateWorking || state == HBStateSearching || state == HBStateMuxing || state == HBStatePaused) + if (self.queue.isEncoding) { _ripToolbarItem.image = [NSImage imageNamed:@"stopencode"]; _ripToolbarItem.label = NSLocalizedString(@"Stop", @"Toolbar Start/Stop Item"); @@ -188,25 +224,23 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; { SEL action = menuItem.action; - if (action == @selector(rip:)) + if (action == @selector(toggleStartCancel:)) { - if (self.core.state == HBStateIdle) - { - menuItem.title = NSLocalizedString(@"Start Encoding", @"Queue -> start/stop menu"); - - return (self.pendingItemsCount > 0); - } - else if (self.core.state != HBStateIdle) + if (self.queue.isEncoding) { menuItem.title = NSLocalizedString(@"Stop Encoding", @"Queue -> start/stop menu"); - return YES; } + else + { + menuItem.title = NSLocalizedString(@"Start Encoding", @"Queue -> start/stop menu"); + return self.queue.canEncode; + } } - if (action == @selector(pause:)) + if (action == @selector(togglePauseResume:)) { - if (self.core.state != HBStatePaused) + if (self.queue.canPause) { menuItem.title = NSLocalizedString(@"Pause Encoding", @"Queue -> pause/resume menu"); } @@ -215,30 +249,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; menuItem.title = NSLocalizedString(@"Resume Encoding", @"Queue -> pause/resume men"); } - return (self.core.state == HBStateWorking || self.core.state == HBStatePaused); - } - - if (action == @selector(editSelectedQueueItem:) || - action == @selector(removeSelectedQueueItem:) || - action == @selector(revealSelectedQueueItems:) || - action == @selector(revealSelectedQueueItemsSources:)) - { - return (self.tableView.selectedRow != -1 || self.tableView.clickedRow != -1); - } - - if (action == @selector(resetJobState:)) - { - return self.tableView.targetedRowIndexes.count > 0; - } - - if (action == @selector(clearAll:)) - { - return self.items.count > 0; - } - - if (action == @selector(clearCompleted:)) - { - return self.completedItemsCount > 0; + return self.queue.canPause || self.queue.canResume; } return YES; @@ -246,30 +257,14 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; - (BOOL)validateUserIterfaceItemForAction:(SEL)action { - HBState s = self.core.state; - if (action == @selector(toggleStartCancel:)) { - if ((s == HBStateScanning) || (s == HBStatePaused) || (s == HBStateWorking) || (s == HBStateMuxing)) - { - return YES; - } - else - { - return (self.pendingItemsCount > 0); - } + return self.queue.isEncoding || self.queue.canEncode; } if (action == @selector(togglePauseResume:)) { - if (s == HBStatePaused) - { - return YES; - } - else - { - return (s == HBStateWorking || s == HBStateMuxing); - } + return self.queue.canPause || self.queue.canResume; } return NO; @@ -281,668 +276,160 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; return [self validateUserIterfaceItemForAction:action]; } -#pragma mark - Public methods - -- (void)addJob:(HBJob *)item -{ - NSParameterAssert(item); - [self addJobsFromArray:@[item]]; -} - -- (void)addJobsFromArray:(NSArray *)jobs; -{ - NSParameterAssert(jobs); - NSMutableArray *itemsToAdd = [NSMutableArray array]; - for (HBJob *job in jobs) - { - HBQueueItem *item = [[HBQueueItem alloc] initWithJob:job]; - [itemsToAdd addObject:item]; - } - if (itemsToAdd.count) - { - [self addQueueItems:itemsToAdd]; - } -} - -- (BOOL)jobExistAtURL:(NSURL *)url -{ - NSParameterAssert(url); - - for (HBQueueItem *item in self.items) - { - if ((item.state == HBQueueItemStateReady || item.state == HBQueueItemStateWorking) - && [item.completeOutputURL isEqualTo:url]) - { - return YES; - } - } - return NO; -} -- (NSUInteger)count -{ - return self.items.count; -} - -/** - * This method will clear the queue of any encodes that are not still pending - * this includes both successfully completed encodes as well as canceled encodes - */ -- (void)removeCompletedJobs -{ - [self.items beginTransaction]; - NSIndexSet *indexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { - return (item.state == HBQueueItemStateCompleted || item.state == HBQueueItemStateCanceled); - }]; - [self removeQueueItemsAtIndexes:indexes]; - [self.items commit]; -} - -/** - * This method will clear the queue of all encodes. effectively creating an empty queue - */ -- (void)removeAllJobs -{ - [self.items beginTransaction]; - [self removeQueueItemsAtIndexes:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self.items.count)]]; - [self.items commit]; -} - -/** - * This method will set any item marked as encoding back to pending - * currently used right after a queue reload - */ -- (void)setEncodingJobsAsPending +- (void)windowDidChangeOcclusionState:(NSNotification *)notification { - [self.items beginTransaction]; - - NSMutableIndexSet *indexes = [NSMutableIndexSet indexSet]; - NSUInteger idx = 0; - for (HBQueueItem *item in self.items) - { - // We want to keep any queue item that is pending or was previously being encoded - if (item.state == HBQueueItemStateWorking) - { - item.state = HBQueueItemStateReady; - [indexes addIndex:idx]; - } - idx++; - } - [self reloadQueueItemsAtIndexes:indexes]; - [self.items commit]; + self.visible = self.window.occlusionState & NSWindowOcclusionStateVisible ? YES : NO; } #pragma mark - Private queue editing methods /** - * Reloads the queue, this is called - * when another HandBrake instances modifies the queue + * Delete encodes from the queue window and accompanying array + * Also handling first cancelling the encode if in fact its currently encoding. */ -- (void)reloadQueue -{ - [self updateQueueStats]; - [self.tableView reloadData]; - [self.window.undoManager removeAllActions]; -} - -- (void)reloadQueueItemAtIndex:(NSUInteger)idx -{ - [self reloadQueueItemsAtIndexes:[NSIndexSet indexSetWithIndex:idx]]; -} - -- (void)reloadQueueItemsAtIndexes:(NSIndexSet *)indexes -{ - NSIndexSet *columnIndexes = [NSIndexSet indexSetWithIndex:0]; - [self.tableView reloadDataForRowIndexes:indexes columnIndexes:columnIndexes]; - [self updateQueueStats]; -} - -- (void)addQueueItems:(NSArray *)items -{ - NSParameterAssert(items); - NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(self.items.count, items.count)]; - [self addQueueItems:items atIndexes:indexes]; -} - -- (void)addQueueItems:(NSArray *)items atIndexes:(NSIndexSet *)indexes -{ - NSParameterAssert(items); - NSParameterAssert(indexes); - [self.items beginTransaction]; - [self.tableView beginUpdates]; - - // Forward - NSUInteger currentIndex = indexes.firstIndex; - NSUInteger currentObjectIndex = 0; - while (currentIndex != NSNotFound) - { - [self.items insertObject:items[currentObjectIndex] atIndex:currentIndex]; - currentIndex = [indexes indexGreaterThanIndex:currentIndex]; - currentObjectIndex++; - } - - [self.tableView insertRowsAtIndexes:indexes - withAnimation:NSTableViewAnimationSlideDown]; - - NSUndoManager *undo = self.window.undoManager; - [[undo prepareWithInvocationTarget:self] removeQueueItemsAtIndexes:indexes]; - - if (!undo.isUndoing) - { - if (items.count == 1) - { - [undo setActionName:NSLocalizedString(@"Add Job To Queue", @"Queue undo action name")]; - } - else - { - [undo setActionName:NSLocalizedString(@"Add Jobs To Queue", @"Queue undo action name")]; - } - } - - [self.tableView endUpdates]; - [self updateQueueStats]; - [self.items commit]; -} - -- (void)removeQueueItemAtIndex:(NSUInteger)index -{ - [self removeQueueItemsAtIndexes:[NSIndexSet indexSetWithIndex:index]]; -} - - (void)removeQueueItemsAtIndexes:(NSIndexSet *)indexes { - NSParameterAssert(indexes); - - if (indexes.count == 0) + if ([self.queue.items beginTransaction] == HBDistributedArrayContentReload) { + // Do not execture the action if the array changed. + [self.queue.items commit]; return; } - [self.items beginTransaction]; - [self.tableView beginUpdates]; - - NSArray *removeItems = [self.items objectsAtIndexes:indexes]; - - if (self.items.count > indexes.lastIndex) - { - [self.items removeObjectsAtIndexes:indexes]; - } - - [self.tableView removeRowsAtIndexes:indexes withAnimation:NSTableViewAnimationSlideUp]; - [self.tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:indexes.firstIndex] byExtendingSelection:NO]; - - NSUndoManager *undo = self.window.undoManager; - [[undo prepareWithInvocationTarget:self] addQueueItems:removeItems atIndexes:indexes]; - - if (!undo.isUndoing) - { - if (indexes.count == 1) - { - [undo setActionName:NSLocalizedString(@"Remove Job From Queue", @"Queue undo action name")]; - } - else - { - [undo setActionName:NSLocalizedString(@"Remove Jobs From Queue", @"Queue undo action name")]; - } - } - - [self.tableView endUpdates]; - [self updateQueueStats]; - [self.items commit]; -} - -- (void)moveQueueItems:(NSArray *)items toIndex:(NSUInteger)index -{ - [self.items beginTransaction]; - [self.tableView beginUpdates]; - - NSMutableArray *source = [NSMutableArray array]; - NSMutableArray *dest = [NSMutableArray array]; - - for (id object in items.reverseObjectEnumerator) - { - NSUInteger sourceIndex = [self.items indexOfObject:object]; - [self.items removeObjectAtIndex:sourceIndex]; - - - if (sourceIndex < index) - { - index--; - } - - [self.items insertObject:object atIndex:index]; - - [source addObject:@(index)]; - [dest addObject:@(sourceIndex)]; - - [self.tableView moveRowAtIndex:sourceIndex toIndex:index]; - } - - NSUndoManager *undo = self.window.undoManager; - [[undo prepareWithInvocationTarget:self] moveQueueItemsAtIndexes:source toIndexes:dest]; - - if (!undo.isUndoing) + if (indexes.count) { - if (items.count == 1) - { - [undo setActionName:NSLocalizedString(@"Move Job in Queue", @"Queue undo action name")]; - } - else - { - [undo setActionName:NSLocalizedString(@"Move Jobs in Queue", @"Queue undo action name")]; - } - } - - [self.tableView endUpdates]; - [self.items commit]; -} - -- (void)moveQueueItemsAtIndexes:(NSArray *)source toIndexes:(NSArray *)dest -{ - [self.items beginTransaction]; - [self.tableView 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.items objectAtIndex:sourceIndex]; - [self.items removeObjectAtIndex:sourceIndex]; - [self.items insertObject:obj atIndex:destIndex]; - - [self.tableView moveRowAtIndex:sourceIndex toIndex:destIndex]; - } - - 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", @"Queue undo action name")]; - } - else - { - [undo setActionName:NSLocalizedString(@"Move Jobs in Queue", @"Queue undo action name")]; - } - } - - [self.tableView endUpdates]; - [self.items commit]; -} - -- (void)windowDidChangeOcclusionState:(NSNotification *)notification -{ - if ([self.window occlusionState] & NSWindowOcclusionStateVisible) - { - self.visible = YES; - self.progressTextField.attributedStringValue = self.progressInfo; - } - else - { - self.visible = NO; - } -} - -- (void)updateProgress:(NSString *)info progress:(double)progress hidden:(BOOL)hidden -{ - self.progressInfo = [[NSAttributedString alloc] initWithString:info attributes:_monospacedAttr]; - if (self.visible) - { - self.progressTextField.attributedStringValue = _progressInfo; - } - [self.controller setQueueInfo:_progressInfo progress:progress hidden:hidden]; -} - -/** - * Updates the queue status label. - */ -- (void)updateQueueStats -{ - // lets get the stats on the status of the queue array - NSUInteger pendingCount = 0; - NSUInteger completedCount = 0; - - for (HBQueueItem *item in self.items) - { - if (item.state == HBQueueItemStateReady) - { - pendingCount++; - } - if (item.state == HBQueueItemStateCompleted) - { - completedCount++; - } - } - - NSString *string; - if (pendingCount == 0) - { - string = NSLocalizedString(@"No encode pending", @"Queue status"); - } - else if (pendingCount == 1) - { - string = [NSString stringWithFormat: NSLocalizedString(@"%d encode pending", @"Queue status"), pendingCount]; - } - else - { - string = [NSString stringWithFormat: NSLocalizedString(@"%d encodes pending", @"Queue status"), pendingCount]; - } - - self.countTextField.stringValue = string; - - self.pendingItemsCount = pendingCount; - self.completedItemsCount = completedCount; -} - -#pragma mark - Queue Job Processing - -- (BOOL)_isDiskSpaceLowAtURL:(NSURL *)url -{ - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"HBQueuePauseIfLowSpace"]) - { - NSURL *volumeURL = nil; - NSDictionary *attrs = [url resourceValuesForKeys:@[NSURLIsVolumeKey, NSURLVolumeURLKey] error:NULL]; - long long minCapacity = [[[NSUserDefaults standardUserDefaults] stringForKey:@"HBQueueMinFreeSpace"] longLongValue] * 1000000000; - - volumeURL = [attrs[NSURLIsVolumeKey] boolValue] ? url : attrs[NSURLVolumeURLKey]; + NSMutableIndexSet *mutableIndexes = [indexes mutableCopy]; + // 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 + NSIndexSet *workingIndexes = [self.queue.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { + return item.state == HBQueueItemStateWorking; + }]; - if (volumeURL) + if ([mutableIndexes containsIndexes:workingIndexes]) { - [volumeURL removeCachedResourceValueForKey:NSURLVolumeAvailableCapacityKey]; - attrs = [volumeURL resourceValuesForKeys:@[NSURLVolumeAvailableCapacityKey] error:NULL]; + [mutableIndexes removeIndexes:workingIndexes]; + NSArray *workingItems = [self.queue.items filteredArrayUsingBlock:^BOOL(HBQueueItem *item) { + return item.state == HBQueueItemStateWorking; + }]; - if (attrs[NSURLVolumeAvailableCapacityKey]) + if ([workingItems containsObject:self.queue.currentItem]) { - if ([attrs[NSURLVolumeAvailableCapacityKey] longLongValue] < minCapacity) - { - return YES; - } - } - } - } - - return NO; -} - -/** - * Used to get the next pending queue item and return it if found - */ -- (HBQueueItem *)getNextPendingQueueItem -{ - for (HBQueueItem *item in self.items) - { - if (item.state == HBQueueItemStateReady) - { - return item; - } - } - return nil; -} - -/** - * Starts the queue - */ -- (void)encodeNextQueueItem -{ - [self.items beginTransaction]; - self.currentItem = nil; + NSString *alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Remove It?", @"Queue Stop Alert -> stop and remove message")]; - // since we have completed an encode, we go to the next - if (self.stop) - { - [HBUtilities writeToActivityLog:"Queue manually stopped"]; + // Which window to attach the sheet to? + NSWindow *targetWindow = self.window; - self.stop = NO; - [self.core allowSleep]; - } - else - { - // Check to see if there are any more pending items in the queue - HBQueueItem *nextItem = [self getNextPendingQueueItem]; + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:alertTitle]; + [alert setInformativeText:NSLocalizedString(@"Your movie will be lost if you don't continue encoding.", @"Queue Stop Alert -> stop and remove informative text")]; + [alert addButtonWithTitle:NSLocalizedString(@"Keep Encoding", @"Queue Stop Alert -> stop and remove first button")]; + [alert addButtonWithTitle:NSLocalizedString(@"Stop Encoding and Delete", @"Queue Stop Alert -> stop and remove second button")]; + [alert setAlertStyle:NSAlertStyleCritical]; - if (nextItem && [self _isDiskSpaceLowAtURL:nextItem.outputURL]) - { - // Disk space is low, show an alert - [HBUtilities writeToActivityLog:"Queue Stopped, low space on destination disk"]; + [alert beginSheetModalForWindow:targetWindow completionHandler:^(NSModalResponse returnCode) { + if (returnCode == NSAlertSecondButtonReturn) + { + [self.queue.items beginTransaction]; - [self queueLowDiskSpaceAlert]; - } - // If we still have more pending items in our queue, lets go to the next one - else if (nextItem) - { - // now we mark the queue item as working so another instance can not come along and try to scan it while we are scanning - nextItem.state = HBQueueItemStateWorking; + NSInteger index = [self.queue.items indexOfObject:self.queue.currentItem]; + [self.queue cancelCurrentItemAndContinue]; - // Tell HB to output a new activity log file for this encode - self.currentLog = [[HBJobOutputFileWriter alloc] initWithJob:nextItem.job]; - if (self.currentLog) - { - [[HBOutputRedirect stderrRedirect] addListener:self.currentLog]; - [[HBOutputRedirect stdoutRedirect] addListener:self.currentLog]; + [self.queue removeQueueItemAtIndex:index]; + [self.queue.items commit]; + } + }]; } - - self.currentItem = nextItem; - [self reloadQueueItemAtIndex:[self.items indexOfObject:nextItem]]; - - // now we can go ahead and scan the new pending queue item - [self encodeItem:nextItem]; - - // 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.core allowSleep]; - } - } - [self.items commit]; -} - -- (void)completedItem:(HBQueueItem *)item result:(HBCoreResult)result; -{ - NSParameterAssert(item); - [self.items 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 canceled - // to determine if we should send it to external app. - if (result != HBCoreResultCanceled) - { - // Send to tagger - [self sendToExternalApp:item]; - } - - // Mark the encode just finished - switch (result) { - case HBCoreResultDone: - item.state = HBQueueItemStateCompleted; - break; - case HBCoreResultCanceled: - item.state = HBQueueItemStateCanceled; - break; - default: - item.state = HBQueueItemStateFailed; - break; - } - - if ([self.items containsObject:item]) - { - [self reloadQueueItemAtIndex:[self.items indexOfObject:item]]; - } - [self.window.toolbar validateVisibleItems]; - [self.items commit]; - - // Update UI - NSString *info = nil; - switch (result) { - case HBCoreResultDone: - info = NSLocalizedString(@"Encode Finished.", @"Queue status"); - [self itemCompletedAlerts:item result:result]; - break; - case HBCoreResultCanceled: - info = NSLocalizedString(@"Encode Canceled.", @"Queue status"); - break; - default: - info = NSLocalizedString(@"Encode Failed.", @"Queue status"); - [self itemCompletedAlerts:item result:result]; - break; + // remove the non working items immediately + [self.queue removeQueueItemsAtIndexes:mutableIndexes]; } - [self updateProgress:info progress:1.0 hidden:YES]; - - // Restore dock icon - [self.dockTile updateDockIcon:-1.0 withETA:@""]; - self.dockIconProgress = 0; + [self.queue.items commit]; } -/** - * Here we actually tell hb_scan to perform the source scan, using the path to source and title number - */ -- (void)encodeItem:(HBQueueItem *)item +- (void)doEditQueueItem:(HBQueueItem *)item { NSParameterAssert(item); + [self.queue.items beginTransaction]; - // Progress handler - void (^progressHandler)(HBState state, HBProgress progress, NSString *info) = ^(HBState state, HBProgress progress, NSString *info) + if (item != self.queue.currentItem) { - [self updateProgress:info progress:0 hidden:NO]; - }; - - // Completion handler - void (^completionHandler)(HBCoreResult result) = ^(HBCoreResult result) - { - if (result == HBCoreResultDone) - { - [self realEncodeItem:item]; - } - else - { - [self completedItem:item 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:item.fileURL - titleIndex:item.job.titleIdx - previews:10 - minDuration:0 - progressHandler:progressHandler - completionHandler:completionHandler]; -} - -/** - * This assumes that we have re-scanned and loaded up a new queue item to send to libhb - */ -- (void)realEncodeItem:(HBQueueItem *)item -{ - NSParameterAssert(item); - - HBJob *job = item.job; - - // Reset the title in the job. - job.title = self.core.titles.firstObject; - - NSParameterAssert(job); + item.state = HBQueueItemStateWorking; - HBStateFormatter *formatter = [[HBStateFormatter alloc] init]; - formatter.title = job.outputFileName; - self.core.stateFormatter = formatter; + // NSUInteger row = [self.queue.items indexOfObject:item]; + //FIXME + //[self reloadQueueItemAtIndex:row]; - // Progress handler - void (^progressHandler)(HBState state, HBProgress progress, NSString *info) = ^(HBState state, HBProgress progress, NSString *info) - { - if (state == HBStateWorking) - { - // Update dock icon - if (self.dockIconProgress < 100.0 * progress.percent) + [self.delegate openJob:[item.job copy] completionHandler:^(BOOL result) { + [self.queue.items beginTransaction]; + if (result) + { + // Now that source is loaded and settings applied, delete the queue item from the queue + NSInteger index = [self.queue.items indexOfObject:item]; + item.state = HBQueueItemStateReady; + [self.queue removeQueueItemAtIndex:index]; + } + else { - [self.dockTile updateDockIcon:progress.percent hours:progress.hours minutes:progress.minutes seconds:progress.seconds]; - self.dockIconProgress += dockTileUpdateFrequency; + item.state = HBQueueItemStateFailed; + NSBeep(); } - } - else if (state == HBStateMuxing) - { - [self.dockTile updateDockIcon:1.0 withETA:@""]; - } - - // Update UI - [self updateProgress:info progress:progress.percent hidden:NO]; - }; - - // Completion handler - void (^completionHandler)(HBCoreResult result) = ^(HBCoreResult result) + [self.queue.items commit]; + }]; + } + else { - [self completedItem:item result:result]; - [self encodeNextQueueItem]; - }; - - // We should be all setup so let 'er rip - [self.core encodeJob:job progressHandler:progressHandler completionHandler:completionHandler]; + NSBeep(); + } - // We are done using the title, remove it from the job - job.title = nil; + [self.queue.items commit]; } /** - * Cancels the current job + * Send the selected queue item back to the main window for rescan and possible edit. */ -- (void)doCancelCurrentItem +- (void)editQueueItem:(HBQueueItem *)item { - if (self.core.state == HBStateScanning) + if ([self.queue.items beginTransaction] == HBDistributedArrayContentReload) { - [self.core cancelScan]; + // Do not execture the action if the array changed. + [self.queue.items commit]; + return; } - else + + // if this is a currently encoding item, 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 + if (item == self.queue.currentItem) { - [self.core cancelEncode]; - } -} + NSString *alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Edit It?", @"Queue Edit Alert -> stop and edit message")]; -/** - * Cancels the current job and starts processing the next in queue. - */ -- (void)cancelCurrentItemAndContinue -{ - [self doCancelCurrentItem]; -} + // Which window to attach the sheet to? + NSWindow *docWindow = self.window; -/** - * Cancels the current job and stops libhb from processing the remaining encodes. - */ -- (void)cancelCurrentItemAndStop -{ - self.stop = YES; - [self doCancelCurrentItem]; -} + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText:alertTitle]; + [alert setInformativeText:NSLocalizedString(@"Your movie will be lost if you don't continue encoding.", @"Queue Edit Alert -> stop and edit informative text")]; + [alert addButtonWithTitle:NSLocalizedString(@"Keep Encoding", @"Queue Edit Alert -> stop and edit first button")]; + [alert addButtonWithTitle:NSLocalizedString(@"Stop Encoding and Edit", @"Queue Edit Alert -> stop and edit second button")]; + [alert setAlertStyle:NSAlertStyleCritical]; + + [alert beginSheetModalForWindow:docWindow completionHandler:^(NSModalResponse returnCode) { + if (returnCode == NSAlertSecondButtonReturn) + { + [self doEditQueueItem:item]; + } + }]; + } + else if (item.state != HBQueueItemStateWorking) + { + [self doEditQueueItem:item]; + } -/** - * Finishes the current job and stops libhb from processing the remaining encodes. - */ -- (void)finishCurrentAndStop -{ - self.stop = YES; + + [self.queue.items commit]; } #pragma mark - Encode Done Actions @@ -967,6 +454,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; notification.hasActionButton = YES; notification.actionButtonTitle = NSLocalizedString(@"Show", @"Notification -> Show in Finder"); notification.userInfo = @{ @"Path": fileURL.path }; + [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; } @@ -1012,18 +500,20 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; /** * Runs the alert for a single job */ -- (void)itemCompletedAlerts:(HBQueueItem *)item result:(HBCoreResult)result +- (void)itemCompletedAlerts:(HBQueueItem *)item { + NSUserDefaults *ud = NSUserDefaults.standardUserDefaults; + // Both the Notification and Sending to tagger can be done as encodes roll off the queue - if ([[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionNotification || - [[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionAlertAndNotification) + if ([ud integerForKey:@"HBAlertWhenDone"] == HBDoneActionNotification || + [ud integerForKey:@"HBAlertWhenDone"] == HBDoneActionAlertAndNotification) { // If Play System Alert has been selected in Preferences - bool playSound = [[NSUserDefaults standardUserDefaults] boolForKey:@"HBAlertWhenDoneSound"]; + bool playSound = [ud boolForKey:@"HBAlertWhenDoneSound"]; NSString *title; NSString *description; - if (result == HBCoreResultDone) + if (item.state == HBQueueItemStateCompleted) { title = NSLocalizedString(@"Put down that cocktail…", @"Queue notification alert message"); description = [NSString stringWithFormat:NSLocalizedString(@"Your encode %@ is done!", @"Queue done notification message"), @@ -1049,15 +539,16 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; */ - (void)queueCompletedAlerts { + NSUserDefaults *ud = NSUserDefaults.standardUserDefaults; // If Play System Alert has been selected in Preferences - if ([[NSUserDefaults standardUserDefaults] boolForKey:@"HBAlertWhenDoneSound"] == YES) + if ([ud boolForKey:@"HBAlertWhenDoneSound"] == YES) { NSBeep(); } // If Alert Window or Window and Notification has been selected - if ([[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionAlert || - [[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionAlertAndNotification) + if ([ud integerForKey:@"HBAlertWhenDone"] == HBDoneActionAlert || + [ud integerForKey:@"HBAlertWhenDone"] == HBDoneActionAlertAndNotification) { // On Screen Notification NSAlert *alert = [[NSAlert alloc] init]; @@ -1068,7 +559,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; } // If sleep has been selected - if ([[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionSleep) + if ([ud integerForKey:@"HBAlertWhenDone"] == HBDoneActionSleep) { // Sleep NSDictionary *errorDict; @@ -1077,7 +568,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; [scriptObject executeAndReturnError: &errorDict]; } // If Shutdown has been selected - if ([[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionShutDown) + if ([ud integerForKey:@"HBAlertWhenDone"] == HBDoneActionShutDown) { // Shut Down NSDictionary *errorDict; @@ -1095,114 +586,11 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; [alert runModal]; } -#pragma mark - Queue Item Controls - -/** - * Delete encodes from the queue window and accompanying array - * Also handling first cancelling the encode if in fact its currently encoding. - */ -- (IBAction)removeSelectedQueueItem:(id)sender -{ - if ([self.items beginTransaction] == HBDistributedArrayContentReload) - { - // Do not execture the action if the array changed. - [self.items commit]; - return; - } - - NSMutableIndexSet *targetedRows = [[self.tableView targetedRowIndexes] mutableCopy]; - - if (targetedRows.count) - { - // 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 - NSIndexSet *workingIndexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { - return item.state == HBQueueItemStateWorking; - }]; - - if ([targetedRows containsIndexes:workingIndexes]) - { - [targetedRows removeIndexes:workingIndexes]; - NSArray *workingItems = [self.items filteredArrayUsingBlock:^BOOL(HBQueueItem *item) { - return item.state == HBQueueItemStateWorking; - }]; - - if ([workingItems containsObject:self.currentItem]) - { - NSString *alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Remove It?", @"Queue Stop Alert -> stop and remove message")]; - - // Which window to attach the sheet to? - NSWindow *targetWindow = self.window; - if ([sender respondsToSelector: @selector(window)]) - { - targetWindow = [sender window]; - } - - NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText:alertTitle]; - [alert setInformativeText:NSLocalizedString(@"Your movie will be lost if you don't continue encoding.", @"Queue Stop Alert -> stop and remove informative text")]; - [alert addButtonWithTitle:NSLocalizedString(@"Keep Encoding", @"Queue Stop Alert -> stop and remove first button")]; - [alert addButtonWithTitle:NSLocalizedString(@"Stop Encoding and Delete", @"Queue Stop Alert -> stop and remove second button")]; - [alert setAlertStyle:NSAlertStyleCritical]; - - [alert beginSheetModalForWindow:targetWindow completionHandler:^(NSModalResponse returnCode) { - if (returnCode == NSAlertSecondButtonReturn) - { - [self.items beginTransaction]; - - NSInteger index = [self.items indexOfObject:self.currentItem]; - [self cancelCurrentItemAndContinue]; - - [self removeQueueItemAtIndex:index]; - [self.items commit]; - } - }]; - } - } - - // remove the non working items immediately - [self removeQueueItemsAtIndexes:targetedRows]; - } - [self.items commit]; -} - -/** - * Show the finished encode in the finder - */ -- (IBAction)revealSelectedQueueItems:(id)sender -{ - NSIndexSet *targetedRows = [self.tableView targetedRowIndexes]; - NSMutableArray *urls = [[NSMutableArray alloc] init]; - - NSUInteger currentIndex = [targetedRows firstIndex]; - while (currentIndex != NSNotFound) { - NSURL *url = [[self.items objectAtIndex:currentIndex] completeOutputURL]; - [urls addObject:url]; - currentIndex = [targetedRows indexGreaterThanIndex:currentIndex]; - } - - [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:urls]; -} - -- (IBAction)revealSelectedQueueItemsSources:(id)sender -{ - NSIndexSet *targetedRows = [self.tableView targetedRowIndexes]; - NSMutableArray *urls = [[NSMutableArray alloc] init]; - - NSUInteger currentIndex = [targetedRows firstIndex]; - while (currentIndex != NSNotFound) { - NSURL *url = [[self.items objectAtIndex:currentIndex] fileURL]; - [urls addObject:url]; - currentIndex = [targetedRows indexGreaterThanIndex:currentIndex]; - } - - [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:urls]; -} - - (void)remindUserOfSleepOrShutdown { - if ([[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionSleep) + NSUserDefaults *ud = NSUserDefaults.standardUserDefaults; + + if ([ud integerForKey:@"HBAlertWhenDone"] == HBDoneActionSleep) { // Warn that computer will sleep after encoding NSBeep(); @@ -1222,7 +610,7 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; [self promptForAppleEventAuthorization]; } - else if ([[NSUserDefaults standardUserDefaults] integerForKey:@"HBAlertWhenDone"] == HBDoneActionShutDown) + else if ([ud integerForKey:@"HBAlertWhenDone"] == HBDoneActionShutDown) { // Warn that computer will shut down after encoding NSBeep(); @@ -1253,32 +641,31 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; } } +#pragma mark - UI Actions + /** * Rip: puts up an alert before ultimately calling doRip */ -- (IBAction)rip:(id)sender +- (IBAction)toggleStartCancel:(id)sender { // Rip or Cancel ? - if (self.core.state == HBStateWorking || self.core.state == HBStatePaused || self.core.state == HBStateSearching) + if (self.queue.isEncoding) { [self cancelRip:sender]; } // If there are pending items in the queue, then this is a rip the queue - else if (self.pendingItemsCount > 0) + else if (self.queue.canEncode) { // We check to see if we need to warn the user that the computer will go to sleep // or shut down when encoding is finished [self remindUserOfSleepOrShutdown]; - - [self.core preventSleep]; - [self encodeNextQueueItem]; + [self.queue start]; } } /** +* Starts or cancels the processing of items depending on the current state * Displays an alert asking user if the want to cancel encoding of current item. - * Cancel: returns immediately after posting the alert. Later, when the user - * acknowledges the alert, doCancelCurrentItem is called. */ - (IBAction)cancelRip:(id)sender { @@ -1301,358 +688,82 @@ static void *HBControllerQueueCoreContext = &HBControllerQueueCoreContext; [alert beginSheetModalForWindow:window completionHandler:^(NSModalResponse returnCode) { if (returnCode == NSAlertSecondButtonReturn) { - [self cancelCurrentItemAndContinue]; + [self.queue cancelCurrentItemAndContinue]; } else if (returnCode == NSAlertThirdButtonReturn) { - [self finishCurrentAndStop]; + [self.queue finishCurrentAndStop]; } else if (returnCode == NSAlertThirdButtonReturn + 1) { - [self cancelCurrentItemAndStop]; + [self.queue cancelCurrentItemAndStop]; } }]; } -/** - * Starts or cancels the processing of items depending on the current state - */ -- (IBAction)toggleStartCancel:(id)sender -{ - HBState s = self.core.state; - if ((s == HBStatePaused) || (s == HBStateWorking) || (s == HBStateMuxing)) - { - [self cancelRip:self]; - } - else if (self.pendingItemsCount > 0) - { - [self rip:self]; - } -} - /** * Toggles the pause/resume state of libhb */ - (IBAction)togglePauseResume:(id)sender { - HBState s = self.core.state; - if (s == HBStatePaused) + if (self.queue.canResume) { - [self.core resume]; - [self.core preventSleep]; + [self.queue resume]; } - else if (s == HBStateWorking || s == HBStateMuxing) + else if (self.queue.canPause) { - [self.core pause]; - [self.core allowSleep]; + [self.queue pause]; } } -/** - * Resets the item state to ready. - */ -- (IBAction)resetJobState:(id)sender +- (IBAction)toggleDetails:(id)sender { - if ([self.items beginTransaction] == HBDistributedArrayContentReload) - { - // Do not execture the action if the array changed. - [self.items commit]; - return; - } - - NSIndexSet *targetedRows = [self.tableView targetedRowIndexes]; - NSMutableIndexSet *updatedIndexes = [NSMutableIndexSet indexSet]; - - NSUInteger currentIndex = [targetedRows firstIndex]; - while (currentIndex != NSNotFound) { - HBQueueItem *item = self.items[currentIndex]; - - if (item.state == HBQueueItemStateCanceled || item.state == HBQueueItemStateCompleted || item.state == HBQueueItemStateFailed) - { - item.state = HBQueueItemStateReady; - [updatedIndexes addIndex:currentIndex]; - } - currentIndex = [targetedRows indexGreaterThanIndex:currentIndex]; - } - - [self reloadQueueItemsAtIndexes:updatedIndexes]; - [self.items commit]; + NSSplitViewItem *detailsItem = self.splitViewController.splitViewItems[1]; + detailsItem.animator.collapsed = !detailsItem.isCollapsed; } -- (void)editQueueItem:(HBQueueItem *)item -{ - NSParameterAssert(item); - [self.items beginTransaction]; - - if (item != self.currentItem) - { - item.state = HBQueueItemStateWorking; - - NSUInteger row = [self.items indexOfObject:item]; - [self reloadQueueItemAtIndex:row]; - - [self.controller openJob:[item.job copy] completionHandler:^(BOOL result) { - [self.items beginTransaction]; - if (result) - { - // Now that source is loaded and settings applied, delete the queue item from the queue - NSInteger index = [self.items indexOfObject:item]; - item.state = HBQueueItemStateReady; - [self removeQueueItemAtIndex:index]; - } - else - { - item.state = HBQueueItemStateFailed; - NSBeep(); - } - [self.items commit]; - }]; - } - else - { - NSBeep(); - } - - [self.items commit]; -} +#pragma mark - table view controller delegate -/** - * Send the selected queue item back to the main window for rescan and possible edit. - */ -- (IBAction)editSelectedQueueItem:(id)sender +- (void)tableViewDidSelectItem:(HBQueueItem *)item { - if ([self.items beginTransaction] == HBDistributedArrayContentReload) - { - // Do not execture the action if the array changed. - [self.items commit]; - return; - } - - NSInteger row = self.tableView.clickedRow; - if (row != NSNotFound) - { - // if this is a currently encoding item, 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 - HBQueueItem *item = self.items[row]; - if (item == self.currentItem) - { - NSString *alertTitle = [NSString stringWithFormat:NSLocalizedString(@"Stop This Encode and Edit It?", @"Queue Edit Alert -> stop and edit message")]; - - // Which window to attach the sheet to? - NSWindow *docWindow = self.window; - if ([sender respondsToSelector: @selector(window)]) - { - docWindow = [sender window]; - } - - NSAlert *alert = [[NSAlert alloc] init]; - [alert setMessageText:alertTitle]; - [alert setInformativeText:NSLocalizedString(@"Your movie will be lost if you don't continue encoding.", @"Queue Edit Alert -> stop and edit informative text")]; - [alert addButtonWithTitle:NSLocalizedString(@"Keep Encoding", @"Queue Edit Alert -> stop and edit first button")]; - [alert addButtonWithTitle:NSLocalizedString(@"Stop Encoding and Edit", @"Queue Edit Alert -> stop and edit second button")]; - [alert setAlertStyle:NSAlertStyleCritical]; - - [alert beginSheetModalForWindow:docWindow completionHandler:^(NSModalResponse returnCode) { - if (returnCode == NSAlertSecondButtonReturn) - { - [self editQueueItem:item]; - } - }]; - } - else if (item.state != HBQueueItemStateWorking) - { - [self editQueueItem:item]; - } - } - - [self.items commit]; + self.detailsViewController.item = item; } -- (IBAction)clearAll:(id)sender +- (void)tableViewEditItem:(HBQueueItem *)item { - [self.items beginTransaction]; - NSIndexSet *indexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { - return (item.state != HBQueueItemStateWorking); - }]; - [self removeQueueItemsAtIndexes:indexes]; - [self.items commit]; + [self editQueueItem:item]; } -- (IBAction)clearCompleted:(id)sender -{ - [self.items beginTransaction]; - NSIndexSet *indexes = [self.items indexesOfObjectsUsingBlock:^BOOL(HBQueueItem *item) { - return (item.state == HBQueueItemStateCompleted); - }]; +- (void)tableViewRemoveItemsAtIndexes:(nonnull NSIndexSet *)indexes { [self removeQueueItemsAtIndexes:indexes]; - [self.items commit]; -} - -#pragma mark - NSTableView data source - -- (NSView *)tableView:(NSTableView *)tableView - viewForTableColumn:(NSTableColumn *)tableColumn - row:(NSInteger)row { - - HBQueueItemView *view = [tableView makeViewWithIdentifier:@"MainCell" owner:self]; - HBQueueItem *item = self.items[row]; - - view.delegate = self; - view.item = item; - - return view; } -- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView -{ - return self.items.count; -} - -- (NSTableCellView *)dummyCell -{ - if (!_dummyCell) { - _dummyCell = [self.tableView makeViewWithIdentifier:@"MainCellForSizing" owner: self]; - _dummyCellWidth = [NSLayoutConstraint constraintWithItem:_dummyCell - attribute:NSLayoutAttributeWidth - relatedBy:NSLayoutRelationEqual - toItem:nil - attribute:NSLayoutAttributeNotAnAttribute - multiplier:1.0f - constant:500]; - [_dummyCell addConstraint:_dummyCellWidth]; - } - return _dummyCell; -} - -- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row -{ - HBQueueItem *item = self.items[row]; - - if (item.expanded) - { - CGFloat width = tableView.frame.size.width; - self.dummyCellWidth.constant = width; - self.dummyCell.textField.preferredMaxLayoutWidth = width - 60; - self.dummyCell.textField.attributedStringValue = item.attributedDescription; - - CGFloat height = self.dummyCell.fittingSize.height; - return height; - } - else - { - return 20; - } -} - -- (void)toggleRowsAtIndexes:(NSIndexSet *)rowIndexes expand:(BOOL)expand -{ - NSMutableIndexSet *rowsToExpand = [NSMutableIndexSet indexSet]; - [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) { - HBQueueItem *item = self.items[index]; - BOOL expanded = item.expanded; - if (expanded != expand) - { - item.expanded = !expanded; - [rowsToExpand addIndex:index]; - } - - HBQueueItemView *itemView = (HBQueueItemView *)[self.tableView viewAtColumn:0 row:index makeIfNecessary:NO]; - if (expand) - { - [itemView expand]; - } - else - { - [itemView collapse]; - } - }]; - [self.tableView noteHeightOfRowsWithIndexesChanged:rowsToExpand]; -} - -#pragma mark NSQueueItemView delegate - -- (void)removeQueueItem:(nonnull HBQueueItem *)item -{ - NSUInteger index = [self.items indexOfObject:item]; - [self removeQueueItemAtIndex:index]; -} - -- (void)revealQueueItem:(nonnull HBQueueItem *)item -{ - [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[item.completeOutputURL]]; -} - -- (void)toggleQueueItemHeight:(nonnull HBQueueItem *)item -{ - NSInteger row = [self.items indexOfObject:item]; - [self toggleRowsAtIndexes:[NSIndexSet indexSetWithIndex:row] expand:!item.expanded]; -} - -#pragma mark NSTableView delegate - -- (void)HB_deleteSelectionFromTableView:(NSTableView *)tableView -{ - [self removeSelectedQueueItem:tableView]; +- (void)detailsViewEditItem:(nonnull HBQueueItem *)item { + [self editQueueItem:item]; } -- (void)HB_expandSelectionFromTableView:(NSTableView *)tableView -{ - NSIndexSet *rowIndexes = [self.tableView selectedRowIndexes]; - [self toggleRowsAtIndexes:rowIndexes expand:YES]; +- (void)detailsViewResetItem:(nonnull HBQueueItem *)item { + [self editQueueItem:item]; } -- (void)HB_collapseSelectionFromTableView:(NSTableView *)tableView; +- (IBAction)resetAll:(id)sender { - NSIndexSet *rowIndexes = [self.tableView selectedRowIndexes]; - [self toggleRowsAtIndexes:rowIndexes expand:NO]; + [self.queue resetAllItems]; } -- (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard; +- (IBAction)resetFailed:(id)sender { - NSArray *items = [self.items objectsAtIndexes:rowIndexes]; - // Dragging is only allowed of the pending items. - if (items[0].state != HBQueueItemStateReady) - { - return NO; - } - - self.dragNodesArray = items; - - // Provide data for our custom type, and simple NSStrings. - [pboard declareTypes:@[DragDropSimplePboardType] owner:self]; - - // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!. - [pboard setData:[NSData data] forType:DragDropSimplePboardType]; - - return YES; + [self.queue resetFailedItems]; } -- (NSDragOperation)tableView:(NSTableView *)tableView validateDrop:(id)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)dropOperation +- (IBAction)removeAll:(id)sender { - // Don't allow dropping ONTO an item since they can't really contain any children. - BOOL isOnDropTypeProposal = dropOperation == NSTableViewDropOn; - if (isOnDropTypeProposal) - { - return NSDragOperationNone; - } - - // We do not let the user drop a pending item before or *above* - // already finished or currently encoding items. - NSInteger encodingRow = [self.items indexOfObject:self.currentItem]; - if (encodingRow != NSNotFound && row <= encodingRow) - { - return NSDragOperationNone; - row = MAX(row, encodingRow); - } - - return NSDragOperationMove; + [self.queue removeNotWorkingItems]; } -- (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id)info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)dropOperation +- (IBAction)removeCompleted:(id)sender { - [self moveQueueItems:self.dragNodesArray toIndex:row]; - return YES; + [self.queue removeCompletedItems]; } @end @@ -1705,24 +816,26 @@ static NSTouchBarItemIdentifier HBTouchBarPause = @"fr.handbrake.pause"; return nil; } -- (void)_touchBar_updateButtonsStateForQueueCore:(HBState)state; +- (void)_touchBar_updateButtonsState; { NSButton *ripButton = (NSButton *)[[self.touchBar itemForIdentifier:HBTouchBarRip] view]; NSButton *pauseButton = (NSButton *)[[self.touchBar itemForIdentifier:HBTouchBarPause] view]; - if (state == HBStateScanning || state == HBStateWorking || state == HBStateSearching || state == HBStateMuxing) + if (self.queue.isEncoding) { ripButton.image = [NSImage imageNamed:NSImageNameTouchBarRecordStopTemplate]; - pauseButton.image = [NSImage imageNamed:NSImageNameTouchBarPauseTemplate]; } - else if (state == HBStatePaused) + else + { + ripButton.image = [NSImage imageNamed:NSImageNameTouchBarPlayTemplate]; + } + + if (self.queue.canResume) { - ripButton.image = [NSImage imageNamed:NSImageNameTouchBarRecordStopTemplate]; pauseButton.image = [NSImage imageNamed:NSImageNameTouchBarPlayTemplate]; } - else if (state == HBStateIdle) + else { - ripButton.image = [NSImage imageNamed:NSImageNameTouchBarPlayTemplate]; pauseButton.image = [NSImage imageNamed:NSImageNameTouchBarPauseTemplate]; } } diff --git a/macosx/HBQueueDetailsViewController.h b/macosx/HBQueueDetailsViewController.h new file mode 100644 index 000000000..8fca10a1a --- /dev/null +++ b/macosx/HBQueueDetailsViewController.h @@ -0,0 +1,27 @@ +/* HBQueueDetailsViewController.h $ + + This file is part of the HandBrake source code. + Homepage: . + It may be used under the terms of the GNU General Public License. */ + +#import +#import "HBQueueItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol HBQueueDetailsViewControllerDelegate + +- (void)detailsViewEditItem:(HBQueueItem *)item; +- (void)detailsViewResetItem:(HBQueueItem *)item; + +@end + +@interface HBQueueDetailsViewController : NSViewController + +- (instancetype)initWithDelegate:(id)delegate; + +@property (nonatomic, nullable) HBQueueItem *item; + +@end + +NS_ASSUME_NONNULL_END diff --git a/macosx/HBQueueDetailsViewController.m b/macosx/HBQueueDetailsViewController.m new file mode 100644 index 000000000..a779346e8 --- /dev/null +++ b/macosx/HBQueueDetailsViewController.m @@ -0,0 +1,65 @@ +/* HBQueueDetailsViewController.m $ + + This file is part of the HandBrake source code. + Homepage: . + It may be used under the terms of the GNU General Public License. */ + +#import "HBQueueDetailsViewController.h" + +@interface HBQueueDetailsViewController () + +@property (weak) IBOutlet NSTextField *detailsLabel; +@property (weak) IBOutlet NSScrollView *scrollView; + +@property (weak) id delegate; + +@end + +@implementation HBQueueDetailsViewController + +- (NSString *)nibName +{ + return @"HBQueueDetailsViewController"; +} + +- (instancetype)initWithDelegate:(id)delegate +{ + self = [super init]; + if (self) + { + _delegate = delegate; + } + return self; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + self.item = nil; +} + +- (void)setItem:(HBQueueItem *)item +{ + _item = item; + if (item) + { + self.detailsLabel.attributedStringValue = item.attributedDescription; + [self.scrollView flashScrollers]; + } + else + { + self.detailsLabel.stringValue = NSLocalizedString(@"No job selected", @""); + } +} + +- (IBAction)editItem:(id)sender +{ + [self.delegate detailsViewEditItem:self.item]; +} + +- (IBAction)resetItem:(id)sender +{ + [self.delegate detailsViewResetItem:self.item]; +} + + +@end diff --git a/macosx/HBQueueItem.m b/macosx/HBQueueItem.m index e0806d7e7..959167731 100644 --- a/macosx/HBQueueItem.m +++ b/macosx/HBQueueItem.m @@ -11,8 +11,8 @@ @implementation HBQueueItem @synthesize job = _job; -@synthesize attributedDescription = _attributedDescription; @synthesize attributedTitleDescription = _attributedTitleDescription; +@synthesize attributedDescription = _attributedDescription; @synthesize uuid = _uuid; @@ -49,14 +49,6 @@ return _job.completeOutputURL; } -- (NSAttributedString *)attributedDescription -{ - if (_attributedDescription == nil) { - _attributedDescription = _job.attributedDescription; - } - return _attributedDescription; -} - - (NSAttributedString *)attributedTitleDescription { if (_attributedTitleDescription == nil) { @@ -65,6 +57,14 @@ return _attributedTitleDescription; } +- (NSAttributedString *)attributedDescription +{ + if (_attributedDescription == nil) { + _attributedDescription = _job.attributedDescription; + } + return _attributedDescription; +} + #pragma mark - NSSecureCoding + (BOOL)supportsSecureCoding diff --git a/macosx/HBQueueItemView.h b/macosx/HBQueueItemView.h index 4dedf3766..e79ee51c8 100644 --- a/macosx/HBQueueItemView.h +++ b/macosx/HBQueueItemView.h @@ -23,9 +23,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, weak, nullable) HBQueueItem *item; @property (nonatomic, weak, nullable) id delegate; -- (void)expand; -- (void)collapse; - @end NS_ASSUME_NONNULL_END diff --git a/macosx/HBQueueItemView.m b/macosx/HBQueueItemView.m index d736fe59f..39fbdf4ce 100644 --- a/macosx/HBQueueItemView.m +++ b/macosx/HBQueueItemView.m @@ -29,22 +29,11 @@ [self HB_updateRightButton]; self.removeButton.target = self; - self.expandButton.target = self; - self.expandButton.action = @selector(toggleHeight:); } - (void)HB_updateLabel { - if (_item.expanded) - { - self.textField.attributedStringValue = _item.attributedDescription; - self.expandButton.state = NSOnState; - } - else - { - self.textField.attributedStringValue = _item.attributedTitleDescription; - self.expandButton.state = NSOffState; - } + self.textField.stringValue = _item.outputFileName; } - (void)HB_updateState @@ -114,18 +103,6 @@ [self HB_updateRightButton]; } -- (void)expand -{ - self.expandButton.state = NSOnState; - self.textField.attributedStringValue = _item.attributedDescription; -} - -- (void)collapse -{ - self.expandButton.state = NSOffState; - self.textField.attributedStringValue = _item.attributedTitleDescription; -} - - (BOOL)isFlipped { return YES; diff --git a/macosx/HBQueueTableViewController.h b/macosx/HBQueueTableViewController.h new file mode 100644 index 000000000..95ab53ac7 --- /dev/null +++ b/macosx/HBQueueTableViewController.h @@ -0,0 +1,29 @@ +/* HBQueueTableViewController.h $ + + This file is part of the HandBrake source code. + Homepage: . + It may be used under the terms of the GNU General Public License. */ + +#import + +#import "HBQueue.h" +#import "HBDistributedArray.h" +#import "HBQueueItem.h" + +NS_ASSUME_NONNULL_BEGIN + +@protocol HBQueueTableViewControllerDelegate + +- (void)tableViewDidSelectItem:(nullable HBQueueItem *)item; +- (void)tableViewEditItem:(HBQueueItem *)item; +- (void)tableViewRemoveItemsAtIndexes:(NSIndexSet *)indexes; + +@end + +@interface HBQueueTableViewController : NSViewController + +- (instancetype)initWithQueue:(HBQueue *)queue delegate:(id)delegate; + +@end + +NS_ASSUME_NONNULL_END diff --git a/macosx/HBQueueTableViewController.m b/macosx/HBQueueTableViewController.m new file mode 100644 index 000000000..d0b4753cb --- /dev/null +++ b/macosx/HBQueueTableViewController.m @@ -0,0 +1,339 @@ +/* HBQueueTableViewController.m $ + + This file is part of the HandBrake source code. + Homepage: . + It may be used under the terms of the GNU General Public License. */ + +#import "HBQueueTableViewController.h" + +#import "HBTableView.h" +#import "HBQueueItemView.h" + +// Pasteboard type for or drag operations +#define HBQueueDragDropPboardType @"HBQueueCustomTableViewPboardType" + +@interface HBQueueTableViewController () + +@property (nonatomic, readonly) HBQueue *queue; +@property (nonatomic) NSArray *dragNodesArray; + +@property (strong) id delegate; + +@property (weak) IBOutlet HBTableView *tableView; + +@end + +@implementation HBQueueTableViewController + +- (instancetype)initWithQueue:(HBQueue *)state delegate:(id)delegate +{ + self = [super init]; + if (self) + { + _queue = state; + _delegate = delegate; + } + return self; +} + +- (NSString *)nibName +{ + return @"HBQueueTableViewController"; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // lets setup our queue list table view for drag and drop here + [self.tableView registerForDraggedTypes:@[HBQueueDragDropPboardType]]; + [self.tableView setDraggingSourceOperationMask:NSDragOperationEvery forLocal:YES]; + [self.tableView setVerticalMotionCanBeginDrag:YES]; + + // Reloads the queue, this is called + // when another HandBrake instances modifies the queue + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueReloadItemsNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + [self.tableView reloadData]; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidAddItemNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + NSIndexSet *indexes = note.userInfo[HBQueueItemNotificationIndexesKey]; + [self.tableView insertRowsAtIndexes:indexes withAnimation:NSTableViewAnimationSlideDown]; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidRemoveItemNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + NSIndexSet *indexes = note.userInfo[HBQueueItemNotificationIndexesKey]; + [self.tableView removeRowsAtIndexes:indexes withAnimation:NSTableViewAnimationSlideUp]; + [self.tableView selectRowIndexes:[NSIndexSet indexSetWithIndex:indexes.firstIndex] byExtendingSelection:NO]; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidMoveItemNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + NSArray *source = note.userInfo[HBQueueItemNotificationSourceIndexesKey]; + NSArray *target = note.userInfo[HBQueueItemNotificationTargetIndexesKey]; + + [self.tableView beginUpdates]; + for (NSInteger idx = 0; idx < source.count; idx++) + { + [self.tableView moveRowAtIndex:source[idx].integerValue toIndex:target[idx].integerValue]; + } + [self.tableView endUpdates]; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidChangeItemNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + NSIndexSet *indexes = note.userInfo[HBQueueItemNotificationIndexesKey]; + NSIndexSet *columnIndexes = [NSIndexSet indexSetWithIndex:0]; + [self.tableView reloadDataForRowIndexes:indexes columnIndexes:columnIndexes]; + }]; + + [NSNotificationCenter.defaultCenter addObserverForName:HBQueueDidCompleteItemNotification object:_queue queue:NSOperationQueue.mainQueue usingBlock:^(NSNotification * _Nonnull note) { + NSIndexSet *indexes = note.userInfo[HBQueueItemNotificationIndexesKey]; + NSIndexSet *columnIndexes = [NSIndexSet indexSetWithIndex:0]; + if (indexes.count) + { + [self.tableView reloadDataForRowIndexes:indexes columnIndexes:columnIndexes]; + } + }]; +} + +#pragma mark - UI Actions + +/** + * Delete encodes from the queue window and accompanying array + * Also handling first cancelling the encode if in fact its currently encoding. + */ +- (IBAction)removeSelectedQueueItem:(id)sender +{ + NSMutableIndexSet *targetedRows = [[self.tableView targetedRowIndexes] mutableCopy]; + [self.delegate tableViewRemoveItemsAtIndexes:targetedRows]; +} + +/** + * Show the finished encode in the finder + */ +- (IBAction)revealSelectedQueueItems:(id)sender +{ + NSIndexSet *targetedRows = [self.tableView targetedRowIndexes]; + NSMutableArray *urls = [[NSMutableArray alloc] init]; + + NSUInteger currentIndex = [targetedRows firstIndex]; + while (currentIndex != NSNotFound) { + NSURL *url = [[self.queue.items objectAtIndex:currentIndex] completeOutputURL]; + [urls addObject:url]; + currentIndex = [targetedRows indexGreaterThanIndex:currentIndex]; + } + + [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:urls]; +} + +- (IBAction)revealSelectedQueueItemsSources:(id)sender +{ + NSIndexSet *targetedRows = [self.tableView targetedRowIndexes]; + NSMutableArray *urls = [[NSMutableArray alloc] init]; + + NSUInteger currentIndex = [targetedRows firstIndex]; + while (currentIndex != NSNotFound) { + NSURL *url = [[self.queue.items objectAtIndex:currentIndex] fileURL]; + [urls addObject:url]; + currentIndex = [targetedRows indexGreaterThanIndex:currentIndex]; + } + + [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:urls]; +} + +/** + * Resets the item state to ready. + */ +- (IBAction)resetJobState:(id)sender +{ + NSIndexSet *targetedRows = [self.tableView targetedRowIndexes]; + if (targetedRows.count) + { + [self.queue resetItemsStateAtIndexes:targetedRows]; + } +} + +/** + * Send the selected queue item back to the main window for rescan and possible edit. + */ +- (IBAction)editSelectedQueueItem:(id)sender +{ + NSInteger row = self.tableView.clickedRow; + HBQueueItem *item = [self.queue.items objectAtIndex:row]; + if (item) + { + [self.delegate tableViewEditItem:item]; + } +} + +- (IBAction)removeAll:(id)sender +{ + [self.queue removeNotWorkingItems]; +} + +- (IBAction)removeCompleted:(id)sender +{ + [self.queue removeCompletedItems]; +} + +#pragma mark - UI Validation + +- (BOOL)validateMenuItem:(NSMenuItem *)menuItem +{ + SEL action = menuItem.action; + + if (action == @selector(editSelectedQueueItem:) || + action == @selector(removeSelectedQueueItem:) || + action == @selector(revealSelectedQueueItems:) || + action == @selector(revealSelectedQueueItemsSources:)) + { + return (self.tableView.selectedRow != -1 || self.tableView.clickedRow != -1); + } + + if (action == @selector(resetJobState:)) + { + return self.tableView.targetedRowIndexes.count > 0; + } + + if (action == @selector(removeAll:)) + { + return self.queue.items.count > 0; + } + + if (action == @selector(removeCompleted:)) + { + return self.queue.completedItemsCount > 0; + } + + return YES; +} + +#pragma mark - NSTableView data source + +- (NSView *)tableView:(NSTableView *)tableView + viewForTableColumn:(NSTableColumn *)tableColumn + row:(NSInteger)row { + + HBQueueItemView *view = [tableView makeViewWithIdentifier:@"MainSimpleCell" owner:self]; + HBQueueItem *item = self.queue.items[row]; + + view.delegate = self; + view.item = item; + + return view; +} + +- (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView +{ + return self.queue.items.count; +} + +- (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row +{ + return 22; +} + +- (void)toggleRowsAtIndexes:(NSIndexSet *)rowIndexes expand:(BOOL)expand +{ + NSMutableIndexSet *rowsToExpand = [NSMutableIndexSet indexSet]; + [rowIndexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) { + HBQueueItem *item = self.queue.items[index]; + BOOL expanded = item.expanded; + if (expanded != expand) + { + item.expanded = !expanded; + [rowsToExpand addIndex:index]; + } + + //HBQueueItemView *itemView = (HBQueueItemView *)[self.tableView viewAtColumn:0 row:index makeIfNecessary:NO]; + //if (expand) + //{ + //[itemView expand]; + //} + //else + //{ + //[itemView collapse]; + //} + }]; + [self.tableView noteHeightOfRowsWithIndexesChanged:rowsToExpand]; +} + +#pragma mark NSQueueItemView delegate + +- (void)removeQueueItem:(nonnull HBQueueItem *)item +{ + NSUInteger index = [self.queue.items indexOfObject:item]; + [self.queue removeQueueItemAtIndex:index]; +} + +- (void)revealQueueItem:(nonnull HBQueueItem *)item +{ + [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[item.completeOutputURL]]; +} + +- (void)toggleQueueItemHeight:(nonnull HBQueueItem *)item +{ + NSInteger row = [self.queue.items indexOfObject:item]; + [self toggleRowsAtIndexes:[NSIndexSet indexSetWithIndex:row] expand:!item.expanded]; +} + +#pragma mark NSTableView delegate + +- (void)tableViewSelectionDidChange:(NSNotification *)notification +{ + NSInteger selectedRow = self.tableView.selectedRow; + HBQueueItem *selectedItem = selectedRow > -1 ? self.queue.items[selectedRow] : nil; + [self.delegate tableViewDidSelectItem:selectedItem]; +} + +- (void)HB_deleteSelectionFromTableView:(NSTableView *)tableView +{ + [self removeSelectedQueueItem:tableView]; +} + +- (BOOL)tableView:(NSTableView *)tableView writeRowsWithIndexes:(NSIndexSet *)rowIndexes toPasteboard:(NSPasteboard *)pboard; +{ + NSArray *items = [self.queue.items objectsAtIndexes:rowIndexes]; + // Dragging is only allowed of the pending items. + if (items[0].state != HBQueueItemStateReady) + { + return NO; + } + + self.dragNodesArray = items; + + // Provide data for our custom type, and simple NSStrings. + [pboard declareTypes:@[HBQueueDragDropPboardType] owner:self]; + + // the actual data doesn't matter since DragDropSimplePboardType drags aren't recognized by anyone but us!. + [pboard setData:[NSData data] forType:HBQueueDragDropPboardType]; + + return YES; +} + +- (NSDragOperation)tableView:(NSTableView *)tableView validateDrop:(id)info proposedRow:(NSInteger)row proposedDropOperation:(NSTableViewDropOperation)dropOperation +{ + // Don't allow dropping ONTO an item since they can't really contain any children. + BOOL isOnDropTypeProposal = dropOperation == NSTableViewDropOn; + if (isOnDropTypeProposal) + { + return NSDragOperationNone; + } + + // We do not let the user drop a pending item before or *above* + // already finished or currently encoding items. + NSInteger encodingRow = [self.queue.items indexOfObject:self.queue.currentItem]; + if (encodingRow != NSNotFound && row <= encodingRow) + { + return NSDragOperationNone; + row = MAX(row, encodingRow); + } + + return NSDragOperationMove; +} + +- (BOOL)tableView:(NSTableView *)tableView acceptDrop:(id)info row:(NSInteger)row dropOperation:(NSTableViewDropOperation)dropOperation +{ + [self.queue moveQueueItems:self.dragNodesArray toIndex:row]; + return YES; +} + +@end diff --git a/macosx/HandBrake.xcodeproj/project.pbxproj b/macosx/HandBrake.xcodeproj/project.pbxproj index bd8668d12..f63eec1bf 100644 --- a/macosx/HandBrake.xcodeproj/project.pbxproj +++ b/macosx/HandBrake.xcodeproj/project.pbxproj @@ -201,10 +201,14 @@ A95121E61B5F7BE700FD773D /* NSArray+HBAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = A95121E51B5F7BE700FD773D /* NSArray+HBAdditions.m */; }; A955128B1A320B02001BFC6F /* libjansson.a in Frameworks */ = {isa = PBXBuildFile; fileRef = A95512881A320A12001BFC6F /* libjansson.a */; }; A957EBCD218DBE5900007988 /* HBAutoNamer.m in Sources */ = {isa = PBXBuildFile; fileRef = A957EBCC218DBE5900007988 /* HBAutoNamer.m */; }; + A958EAC222E24D6400D83AF4 /* HBQueueTableViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A958EAC422E24D6400D83AF4 /* HBQueueTableViewController.xib */; }; + A958EAC522E24D6800D83AF4 /* HBQueueDetailsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = A958EAC722E24D6800D83AF4 /* HBQueueDetailsViewController.xib */; }; A95BA15D220C968500A2F9F9 /* HBQueueItem.m in Sources */ = {isa = PBXBuildFile; fileRef = A95BA15C220C968500A2F9F9 /* HBQueueItem.m */; }; A95BA161220CA5DB00A2F9F9 /* HBDistributedArray.m in Sources */ = {isa = PBXBuildFile; fileRef = A95BA160220CA5DB00A2F9F9 /* HBDistributedArray.m */; }; A95BC1E71CD2548A008D6A33 /* volHighTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A95BC1E51CD2548A008D6A33 /* volHighTemplate.pdf */; }; A95BC1E81CD2548A008D6A33 /* volLowTemplate.pdf in Resources */ = {isa = PBXBuildFile; fileRef = A95BC1E61CD2548A008D6A33 /* volLowTemplate.pdf */; }; + A96127DF22E0994E0086E6DC /* HBQueueDetailsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A96127DD22E0994E0086E6DC /* HBQueueDetailsViewController.m */; }; + A96127E422E09ADD0086E6DC /* HBQueueTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A96127E222E09ADD0086E6DC /* HBQueueTableViewController.m */; }; A96664B01CCE45BF00DA4A57 /* HBPlayerHUDController.m in Sources */ = {isa = PBXBuildFile; fileRef = A96664AE1CCE45BF00DA4A57 /* HBPlayerHUDController.m */; }; A96664B51CCE48F700DA4A57 /* HBPictureHUDController.m in Sources */ = {isa = PBXBuildFile; fileRef = A96664B31CCE48F700DA4A57 /* HBPictureHUDController.m */; }; A96664BA1CCE493D00DA4A57 /* HBEncodingProgressHUDController.m in Sources */ = {isa = PBXBuildFile; fileRef = A96664B81CCE493D00DA4A57 /* HBEncodingProgressHUDController.m */; }; @@ -218,6 +222,7 @@ A973E109216E74AC00D498EC /* HBImageUtilities.h in Headers */ = {isa = PBXBuildFile; fileRef = A9CE0A931F57EC4600724577 /* HBImageUtilities.h */; settings = {ATTRIBUTES = (Public, ); }; }; A973E10C216E74E900D498EC /* HBThumbnailItemView.m in Sources */ = {isa = PBXBuildFile; fileRef = A973E10B216E74E900D498EC /* HBThumbnailItemView.m */; }; A975B02220F7AF29004675CC /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = A975B02020F7AF29004675CC /* Localizable.strings */; }; + A97ECB8222E1D85500570935 /* HBQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = A97ECB8122E1D85500570935 /* HBQueue.m */; }; A98036CD1CCA91DD007661AA /* HBAVPlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = A98036CC1CCA91DD007661AA /* HBAVPlayer.m */; }; A98B8E241C7DD2A200B810C9 /* HBPresetCoding.h in Headers */ = {isa = PBXBuildFile; fileRef = A997D8EB1A4ABB0900E19B6F /* HBPresetCoding.h */; settings = {ATTRIBUTES = (Public, ); }; }; A98C29C41977B10600AF5DED /* HBLanguagesSelection.m in Sources */ = {isa = PBXBuildFile; fileRef = A98C29C31977B10600AF5DED /* HBLanguagesSelection.m */; }; @@ -542,6 +547,8 @@ A957EBCB218DBE5900007988 /* HBAutoNamer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBAutoNamer.h; sourceTree = ""; }; A957EBCC218DBE5900007988 /* HBAutoNamer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HBAutoNamer.m; sourceTree = ""; }; A958605B2216A5E5002092B1 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/Localizable.strings; sourceTree = ""; }; + A958EAC322E24D6400D83AF4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/HBQueueTableViewController.xib; sourceTree = ""; }; + A958EAC622E24D6800D83AF4 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/HBQueueDetailsViewController.xib; sourceTree = ""; }; A9597A281A49749D00007771 /* HBRange+UIAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "HBRange+UIAdditions.h"; sourceTree = ""; }; A9597A291A49749D00007771 /* HBRange+UIAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "HBRange+UIAdditions.m"; sourceTree = ""; }; A95BA15B220C968500A2F9F9 /* HBQueueItem.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBQueueItem.h; sourceTree = ""; }; @@ -551,6 +558,10 @@ A95BC1E51CD2548A008D6A33 /* volHighTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = volHighTemplate.pdf; sourceTree = ""; }; A95BC1E61CD2548A008D6A33 /* volLowTemplate.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = volLowTemplate.pdf; sourceTree = ""; }; A95CB2FB217B6D07001E9F51 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/InfoPlist.strings; sourceTree = ""; }; + A96127DC22E0994E0086E6DC /* HBQueueDetailsViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBQueueDetailsViewController.h; sourceTree = ""; }; + A96127DD22E0994E0086E6DC /* HBQueueDetailsViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HBQueueDetailsViewController.m; sourceTree = ""; }; + A96127E122E09ADD0086E6DC /* HBQueueTableViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBQueueTableViewController.h; sourceTree = ""; }; + A96127E222E09ADD0086E6DC /* HBQueueTableViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HBQueueTableViewController.m; sourceTree = ""; }; A9637D9120F7A252001EAE7C /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/HBEncodingProgressHUDController.strings; sourceTree = ""; }; A9637D9220F7A252001EAE7C /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/HBTitleSelection.strings; sourceTree = ""; }; A9637D9320F7A252001EAE7C /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Presets.strings; sourceTree = ""; }; @@ -600,6 +611,8 @@ A975B02120F7AF29004675CC /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Localizable.strings; sourceTree = ""; }; A975C08C1AE8C5270061870D /* HBStateFormatter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBStateFormatter.h; sourceTree = ""; }; A975C08D1AE8C5270061870D /* HBStateFormatter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBStateFormatter.m; sourceTree = ""; }; + A97ECB8022E1D85500570935 /* HBQueue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBQueue.h; sourceTree = ""; }; + A97ECB8122E1D85500570935 /* HBQueue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HBQueue.m; sourceTree = ""; }; A98036CB1CCA91DD007661AA /* HBAVPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBAVPlayer.h; sourceTree = ""; }; A98036CC1CCA91DD007661AA /* HBAVPlayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBAVPlayer.m; sourceTree = ""; }; A988AF9B1BC7C35F00932543 /* HBChapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBChapter.h; sourceTree = ""; }; @@ -1076,13 +1089,21 @@ A901C2401BC7CFDC00D77735 /* Queue */ = { isa = PBXGroup; children = ( + A97ECB8022E1D85500570935 /* HBQueue.h */, + A97ECB8122E1D85500570935 /* HBQueue.m */, A95BA15B220C968500A2F9F9 /* HBQueueItem.h */, A95BA15C220C968500A2F9F9 /* HBQueueItem.m */, - A9D3634F2209C08500D8EFEA /* HBQueueItemView.h */, - A9D363502209C08500D8EFEA /* HBQueueItemView.m */, A9AA447C1970726500D7DEFC /* HBQueueController.h */, A9906B2B1A710920001D82D5 /* HBQueueController.m */, A9A96BD220CAD63000A39AFB /* Queue.xib */, + A9D3634F2209C08500D8EFEA /* HBQueueItemView.h */, + A9D363502209C08500D8EFEA /* HBQueueItemView.m */, + A96127E122E09ADD0086E6DC /* HBQueueTableViewController.h */, + A96127E222E09ADD0086E6DC /* HBQueueTableViewController.m */, + A958EAC422E24D6400D83AF4 /* HBQueueTableViewController.xib */, + A96127DC22E0994E0086E6DC /* HBQueueDetailsViewController.h */, + A96127DD22E0994E0086E6DC /* HBQueueDetailsViewController.m */, + A958EAC722E24D6800D83AF4 /* HBQueueDetailsViewController.xib */, ); name = Queue; sourceTree = ""; @@ -1650,6 +1671,7 @@ D86C9DD51C6D372500F06F1B /* Assets.xcassets in Resources */, A9A96BCE20CAD61F00A39AFB /* SubtitlesDefaults.xib in Resources */, A9A96BDA20CAD64B00A39AFB /* PicturePreview.xib in Resources */, + A958EAC222E24D6400D83AF4 /* HBQueueTableViewController.xib in Resources */, A95BC1E71CD2548A008D6A33 /* volHighTemplate.pdf in Resources */, A9A96BB620CAD5D600A39AFB /* HBTitleSelection.xib in Resources */, A9E52CD8218DD52B00E17B86 /* ExceptionAlert.xib in Resources */, @@ -1670,6 +1692,7 @@ A9A96BC820CAD61000A39AFB /* AudioDefaults.xib in Resources */, A9A96BB920CAD5EE00A39AFB /* HBPictureViewController.xib in Resources */, A91943111FB5E39E001E9BB0 /* HBSummaryViewController.xib in Resources */, + A958EAC522E24D6800D83AF4 /* HBQueueDetailsViewController.xib in Resources */, A9A96BBF20CAD5F800A39AFB /* Video.xib in Resources */, A9E1468316BC2AD800C307BC /* PrevTemplate.pdf in Resources */, A937EECB1C6C7C0300EEAE6D /* dsa_pub.pem in Resources */, @@ -1745,6 +1768,7 @@ A916C9991C8449E200C7B560 /* main.mm in Sources */, A973E10C216E74E900D498EC /* HBThumbnailItemView.m in Sources */, A916C9981C8449DB00C7B560 /* HBTitleSelectionController.m in Sources */, + A96127E422E09ADD0086E6DC /* HBQueueTableViewController.m in Sources */, A903C5601CCE78060026B0ED /* NSWindow+HBAdditions.m in Sources */, A9A0CBE81CCEA3670045B3DF /* HBPlayerTrack.m in Sources */, A916C9971C8449CA00C7B560 /* HBAudioDefaultsController.m in Sources */, @@ -1785,6 +1809,8 @@ A95121E61B5F7BE700FD773D /* NSArray+HBAdditions.m in Sources */, A96664B51CCE48F700DA4A57 /* HBPictureHUDController.m in Sources */, A957EBCD218DBE5900007988 /* HBAutoNamer.m in Sources */, + A96127DF22E0994E0086E6DC /* HBQueueDetailsViewController.m in Sources */, + A97ECB8222E1D85500570935 /* HBQueue.m in Sources */, A92B148220CA9F7700146FD8 /* HBHUDView.m in Sources */, A914BCB31BC441C700157917 /* HBPreviewView.m in Sources */, 273F20B714ADBE670021BE6D /* HBPreviewController.m in Sources */, @@ -1944,6 +1970,22 @@ name = MainWindow.xib; sourceTree = ""; }; + A958EAC422E24D6400D83AF4 /* HBQueueTableViewController.xib */ = { + isa = PBXVariantGroup; + children = ( + A958EAC322E24D6400D83AF4 /* Base */, + ); + name = HBQueueTableViewController.xib; + sourceTree = ""; + }; + A958EAC722E24D6800D83AF4 /* HBQueueDetailsViewController.xib */ = { + isa = PBXVariantGroup; + children = ( + A958EAC622E24D6800D83AF4 /* Base */, + ); + name = HBQueueDetailsViewController.xib; + sourceTree = ""; + }; A975B02020F7AF29004675CC /* Localizable.strings */ = { isa = PBXVariantGroup; children = (