2014-08-09 17:10:45 +00:00
|
|
|
/* HBPresetsViewController.m $
|
|
|
|
|
|
|
|
This file is part of the HandBrake source code.
|
|
|
|
Homepage: <http://handbrake.fr/>.
|
|
|
|
It may be used under the terms of the GNU General Public License. */
|
2014-08-07 13:54:14 +00:00
|
|
|
|
|
|
|
#import "HBPresetsViewController.h"
|
2017-11-10 14:27:22 +01:00
|
|
|
#import "HBAddCategoryController.h"
|
2025-01-02 13:30:09 +01:00
|
|
|
#import "HBRenamePresetController.h"
|
2020-01-13 17:19:43 +01:00
|
|
|
#import "HBFilePromiseProvider.h"
|
2025-01-02 13:30:09 +01:00
|
|
|
#import "HBOutlineView.h"
|
2020-01-13 17:19:43 +01:00
|
|
|
#import "NSArray+HBAdditions.h"
|
2016-02-26 09:50:16 +01:00
|
|
|
|
2017-11-30 16:52:38 +01:00
|
|
|
@import HandBrakeKit;
|
2014-08-07 13:54:14 +00:00
|
|
|
|
2017-12-20 22:06:38 +01:00
|
|
|
// KVO Context
|
|
|
|
static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext;
|
|
|
|
|
2015-12-05 10:21:23 +01:00
|
|
|
@interface HBPresetCellView : NSTableCellView
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation HBPresetCellView
|
|
|
|
|
|
|
|
- (void)setBackgroundStyle:(NSBackgroundStyle)backgroundStyle
|
|
|
|
{
|
|
|
|
[super setBackgroundStyle:backgroundStyle];
|
|
|
|
|
|
|
|
// Customize the built-in preset text color
|
|
|
|
if ([self.objectValue isBuiltIn])
|
|
|
|
{
|
|
|
|
if (backgroundStyle == NSBackgroundStyleDark)
|
|
|
|
{
|
|
|
|
self.textField.textColor = [NSColor selectedControlTextColor];
|
|
|
|
}
|
2021-01-04 18:19:19 +01:00
|
|
|
else if ([self.objectValue isSupported] == NO)
|
|
|
|
{
|
|
|
|
self.textField.textColor = [NSColor disabledControlTextColor];
|
|
|
|
}
|
2015-12-05 10:21:23 +01:00
|
|
|
else
|
|
|
|
{
|
2018-08-04 11:48:15 +02:00
|
|
|
self.textField.textColor = [NSColor systemBlueColor];
|
2015-12-05 10:21:23 +01:00
|
|
|
}
|
|
|
|
}
|
2021-01-04 18:19:19 +01:00
|
|
|
else if ([self.objectValue isSupported] == NO)
|
|
|
|
{
|
|
|
|
self.textField.textColor = [NSColor disabledControlTextColor];
|
|
|
|
}
|
2015-12-05 10:21:23 +01:00
|
|
|
else
|
|
|
|
{
|
|
|
|
self.textField.textColor = [NSColor controlTextColor];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2020-01-13 17:19:43 +01:00
|
|
|
@interface HBPresetsViewController () <NSOutlineViewDelegate, NSOutlineViewDataSource, NSFilePromiseProviderDelegate>
|
2014-08-07 13:54:14 +00:00
|
|
|
|
2025-01-02 13:30:09 +01:00
|
|
|
@property (nonatomic, strong) HBPresetsManager *manager;
|
2017-12-20 22:06:38 +01:00
|
|
|
@property (nonatomic, readwrite) HBPreset *selectedPresetInternal;
|
2024-02-27 19:38:06 +01:00
|
|
|
@property (nonatomic, weak) IBOutlet NSTreeController *treeController;
|
2020-10-08 10:43:05 +02:00
|
|
|
@property (nonatomic, weak) IBOutlet NSSegmentedControl *actionsControl;
|
2014-08-07 13:54:14 +00:00
|
|
|
|
2024-02-27 19:38:06 +01:00
|
|
|
@property (nonatomic, strong) IBOutlet NSTextField *headerLabel;
|
|
|
|
@property (nonatomic, strong) IBOutlet NSLayoutConstraint *headerBottomConstraint;
|
|
|
|
|
2014-08-07 13:54:14 +00:00
|
|
|
/**
|
|
|
|
* Helper var for drag & drop
|
|
|
|
*/
|
2020-01-13 17:19:43 +01:00
|
|
|
@property (nonatomic, strong, nullable) NSArray *dragNodesArray;
|
2014-08-07 13:54:14 +00:00
|
|
|
|
|
|
|
/**
|
2017-11-10 12:41:07 +01:00
|
|
|
* The status (expanded or not) of the categories.
|
2014-08-07 13:54:14 +00:00
|
|
|
*/
|
2015-03-17 13:56:21 +00:00
|
|
|
@property (nonatomic, strong) NSMutableArray *expandedNodes;
|
2014-08-07 13:54:14 +00:00
|
|
|
|
2025-01-02 13:30:09 +01:00
|
|
|
@property (nonatomic, unsafe_unretained) IBOutlet HBOutlineView *outlineView;
|
2017-12-20 22:06:38 +01:00
|
|
|
|
2014-08-07 13:54:14 +00:00
|
|
|
|
|
|
|
@end
|
|
|
|
|
|
|
|
@implementation HBPresetsViewController
|
|
|
|
|
|
|
|
- (instancetype)initWithPresetManager:(HBPresetsManager *)presetManager
|
|
|
|
{
|
|
|
|
self = [super initWithNibName:@"Presets" bundle:nil];
|
|
|
|
if (self)
|
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
_manager = presetManager;
|
2017-12-20 22:06:38 +01:00
|
|
|
_selectedPresetInternal = presetManager.defaultPreset;
|
2020-01-13 17:19:43 +01:00
|
|
|
_expandedNodes = [[NSArray arrayWithArray:[NSUserDefaults.standardUserDefaults
|
2014-08-07 13:54:14 +00:00
|
|
|
objectForKey:@"HBPreviewViewExpandedStatus"]] mutableCopy];
|
2024-02-27 19:38:06 +01:00
|
|
|
_showHeader = YES;
|
2014-08-07 13:54:14 +00:00
|
|
|
}
|
|
|
|
return self;
|
|
|
|
}
|
|
|
|
|
2018-07-10 20:06:09 +02:00
|
|
|
- (void)viewDidLoad
|
2014-08-07 13:54:14 +00:00
|
|
|
{
|
2018-07-10 20:06:09 +02:00
|
|
|
[super viewDidLoad];
|
2014-08-07 13:54:14 +00:00
|
|
|
|
|
|
|
// drag and drop support
|
2021-10-15 18:41:40 +02:00
|
|
|
[self.outlineView registerForDraggedTypes:@[kHandBrakeInternalPBoardType, NSPasteboardTypeFileURL]];
|
2020-01-13 17:19:43 +01:00
|
|
|
[self.outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO];
|
|
|
|
[self.outlineView setDraggingSourceOperationMask:NSDragOperationMove forLocal:YES];
|
2014-08-07 13:54:14 +00:00
|
|
|
|
|
|
|
// Re-expand the items
|
2020-01-21 11:33:29 +01:00
|
|
|
[self expandNodes:self.treeController.arrangedObjects.childNodes];
|
2014-08-07 13:54:14 +00:00
|
|
|
|
2024-02-27 19:38:06 +01:00
|
|
|
// Update header state
|
|
|
|
self.showHeader = _showHeader;
|
|
|
|
|
2025-01-02 13:30:09 +01:00
|
|
|
[self.treeController setSelectionIndexPath:[self.manager indexPathOfPreset:self.selectedPreset]];
|
2017-12-20 22:06:38 +01:00
|
|
|
[self.treeController addObserver:self forKeyPath:@"selectedObjects" options:NSKeyValueObservingOptionNew context:HBPresetsViewControllerContext];
|
2020-10-08 10:43:05 +02:00
|
|
|
|
|
|
|
[self.actionsControl setEnabled:self.enabled forSegment:0];
|
2017-12-20 22:06:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
|
|
|
|
{
|
|
|
|
if (context == HBPresetsViewControllerContext)
|
|
|
|
{
|
2020-01-20 12:12:27 +01:00
|
|
|
HBPreset *selectedNode = self.treeController.selectedObjects.firstObject;
|
2021-01-04 18:19:19 +01:00
|
|
|
if (selectedNode && selectedNode.isLeaf && selectedNode.isSupported && selectedNode != self.selectedPresetInternal)
|
2017-12-20 22:06:38 +01:00
|
|
|
{
|
|
|
|
self.selectedPresetInternal = selectedNode;
|
|
|
|
[self.delegate selectionDidChange];
|
|
|
|
}
|
2020-10-08 10:43:05 +02:00
|
|
|
[self.actionsControl setEnabled:selectedNode != nil forSegment:1];
|
2017-12-20 22:06:38 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
|
|
|
|
}
|
2014-08-07 13:54:14 +00:00
|
|
|
}
|
|
|
|
|
2025-01-02 13:30:09 +01:00
|
|
|
- (nullable HBPreset *)targetedItem
|
|
|
|
{
|
|
|
|
NSUInteger row = self.outlineView.targetedRowIndexes.firstIndex;
|
|
|
|
if (row != -1)
|
|
|
|
{
|
|
|
|
NSTreeNode *node = [self.outlineView itemAtRow:row];
|
|
|
|
return [node representedObject];
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (NSArray<HBPreset *> *)targetedItems
|
|
|
|
{
|
|
|
|
NSMutableArray *presets = [NSMutableArray array];
|
|
|
|
[self.outlineView.targetedRowIndexes enumerateIndexesUsingBlock:^(NSUInteger index, BOOL *stop) {
|
|
|
|
NSTreeNode *node = [self.outlineView itemAtRow:index];
|
|
|
|
[presets addObject:[node representedObject]];
|
|
|
|
}];
|
|
|
|
|
|
|
|
return presets;
|
|
|
|
}
|
|
|
|
|
2014-08-07 13:54:14 +00:00
|
|
|
- (BOOL)validateUserInterfaceItem:(id < NSValidatedUserInterfaceItem >)anItem
|
|
|
|
{
|
|
|
|
SEL action = anItem.action;
|
|
|
|
|
2020-01-27 12:34:02 +01:00
|
|
|
if (action == @selector(exportPreset:) ||
|
|
|
|
action == @selector(deletePreset:))
|
2015-05-16 07:08:39 +00:00
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
if (self.outlineView.targetedRowIndexes.count == 0)
|
2015-05-16 07:08:39 +00:00
|
|
|
{
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
}
|
2025-01-02 13:30:09 +01:00
|
|
|
if (action == @selector(renamePreset:))
|
2014-08-07 13:54:14 +00:00
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
HBPreset *preset = [self targetedItem];
|
|
|
|
if (!preset || preset.isBuiltIn)
|
|
|
|
{
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (action == @selector(setDefault:))
|
|
|
|
{
|
|
|
|
HBPreset *preset = [self targetedItem];
|
|
|
|
if (!preset || !preset.isLeaf || !preset.isSupported)
|
2014-08-07 13:54:14 +00:00
|
|
|
{
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return YES;
|
|
|
|
}
|
|
|
|
|
2020-10-08 10:43:05 +02:00
|
|
|
- (void)setEnabled:(BOOL)enabled
|
|
|
|
{
|
|
|
|
_enabled = enabled;
|
|
|
|
[self.actionsControl setEnabled:enabled forSegment:0];
|
|
|
|
}
|
|
|
|
|
2020-01-13 17:19:43 +01:00
|
|
|
#pragma mark - Import Export Preset(s)
|
|
|
|
|
|
|
|
- (nonnull NSString *)fileNameForPreset:(HBPreset *)preset
|
|
|
|
{
|
|
|
|
NSString *name = preset.name == nil || preset.name.length == 0 ? @"Unnamed preset" : preset.name;
|
|
|
|
return [name stringByAppendingPathExtension:@"json"];
|
|
|
|
}
|
2015-05-16 07:08:39 +00:00
|
|
|
|
2020-01-27 12:34:02 +01:00
|
|
|
- (nonnull NSURL *)lastPresetExportDirectoryURL
|
2015-05-16 07:08:39 +00:00
|
|
|
{
|
2020-01-27 12:34:02 +01:00
|
|
|
NSURL *defaultExportDirectory = [[NSURL fileURLWithPath:NSHomeDirectory()] URLByAppendingPathComponent:@"Desktop" isDirectory:YES];
|
|
|
|
NSURL *lastPresetExportDirectoryURL = [NSUserDefaults.standardUserDefaults URLForKey:@"LastPresetExportDirectoryURL"];
|
|
|
|
return lastPresetExportDirectoryURL ? lastPresetExportDirectoryURL : defaultExportDirectory;
|
|
|
|
}
|
2015-05-16 07:08:39 +00:00
|
|
|
|
2020-01-27 12:34:02 +01:00
|
|
|
- (void)doExportPresets:(NSArray<HBPreset *> *)presets
|
|
|
|
{
|
|
|
|
NSOpenPanel *panel = [NSOpenPanel openPanel];
|
2018-06-09 10:06:52 +02:00
|
|
|
panel.title = NSLocalizedString(@"Export presets", @"Export presets save panel title");
|
2020-01-27 12:34:02 +01:00
|
|
|
panel.directoryURL = self.lastPresetExportDirectoryURL;
|
|
|
|
panel.canChooseFiles = NO;
|
|
|
|
panel.canChooseDirectories = YES;
|
|
|
|
panel.allowsMultipleSelection = NO;
|
|
|
|
panel.prompt = NSLocalizedString(@"Save", @"Export presets panel prompt");
|
2015-05-17 06:30:22 +00:00
|
|
|
|
2020-01-27 12:34:02 +01:00
|
|
|
[panel beginWithCompletionHandler:^(NSInteger result)
|
|
|
|
{
|
|
|
|
if (result == NSModalResponseOK)
|
|
|
|
{
|
|
|
|
[NSUserDefaults.standardUserDefaults setURL:panel.URL forKey:@"LastPresetExportDirectoryURL"];
|
|
|
|
|
|
|
|
for (HBPreset *preset in presets)
|
|
|
|
{
|
|
|
|
NSError *error = NULL;
|
|
|
|
NSString *fileName = [self fileNameForPreset:preset];
|
2024-07-31 16:22:17 +02:00
|
|
|
NSURL *url = [panel.URL URLByAppendingPathComponent:fileName isDirectory:NO];
|
2020-01-27 12:34:02 +01:00
|
|
|
BOOL success = [preset writeToURL:url atomically:YES removeRoot:NO error:&error];
|
|
|
|
if (success == NO)
|
|
|
|
{
|
|
|
|
[self presentError:error];
|
|
|
|
}
|
|
|
|
}
|
2023-07-07 07:30:02 +02:00
|
|
|
[panel.URL stopAccessingSecurityScopedResource];
|
2020-01-27 12:34:02 +01:00
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)doExportPreset:(HBPreset *)preset
|
|
|
|
{
|
|
|
|
NSSavePanel *panel = [NSSavePanel savePanel];
|
|
|
|
panel.title = NSLocalizedString(@"Export preset", @"Export presets save panel title");
|
|
|
|
panel.directoryURL = self.lastPresetExportDirectoryURL;
|
|
|
|
panel.nameFieldStringValue = [self fileNameForPreset:preset];
|
2015-05-16 07:08:39 +00:00
|
|
|
|
|
|
|
[panel beginWithCompletionHandler:^(NSInteger result)
|
|
|
|
{
|
2018-06-10 09:12:18 +02:00
|
|
|
if (result == NSModalResponseOK)
|
2015-05-16 07:08:39 +00:00
|
|
|
{
|
|
|
|
NSURL *presetExportDirectory = [panel.URL URLByDeletingLastPathComponent];
|
2020-01-13 17:19:43 +01:00
|
|
|
[NSUserDefaults.standardUserDefaults setURL:presetExportDirectory forKey:@"LastPresetExportDirectoryURL"];
|
2015-05-16 07:08:39 +00:00
|
|
|
|
2020-01-13 17:19:43 +01:00
|
|
|
NSError *error = NULL;
|
2020-01-27 12:34:02 +01:00
|
|
|
BOOL success = [preset writeToURL:panel.URL atomically:YES removeRoot:NO error:&error];
|
2020-01-13 17:19:43 +01:00
|
|
|
if (success == NO)
|
|
|
|
{
|
|
|
|
[self presentError:error];
|
|
|
|
}
|
2015-05-16 07:08:39 +00:00
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2020-01-27 12:34:02 +01:00
|
|
|
- (IBAction)exportPreset:(id)sender
|
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
NSArray<HBPreset *> *selectedPresets = [self targetedItems];
|
2020-01-27 12:34:02 +01:00
|
|
|
if (selectedPresets.count == 1)
|
|
|
|
{
|
|
|
|
[self doExportPreset:selectedPresets.firstObject];
|
|
|
|
}
|
|
|
|
else if (selectedPresets.count > 1)
|
|
|
|
{
|
|
|
|
[self doExportPresets:selectedPresets];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-13 17:19:43 +01:00
|
|
|
- (void)doImportPreset:(NSArray<NSURL *> *)URLs atIndexPath:(nullable NSIndexPath *)indexPath
|
|
|
|
{
|
|
|
|
if (indexPath == nil)
|
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
for (HBPreset *preset in self.manager.root.children)
|
2020-01-13 17:19:43 +01:00
|
|
|
{
|
|
|
|
if (preset.isBuiltIn == NO && preset.isLeaf == NO)
|
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
indexPath = [[self.manager indexPathOfPreset:preset] indexPathByAddingIndex:0];
|
2020-01-13 17:19:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (indexPath == nil)
|
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
indexPath = [NSIndexPath indexPathWithIndex:self.manager.root.countOfChildren];
|
2020-01-13 17:19:43 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (NSURL *url in URLs)
|
|
|
|
{
|
|
|
|
NSError *error;
|
|
|
|
HBPreset *preset = [[HBPreset alloc] initWithContentsOfURL:url error:&error];
|
|
|
|
|
|
|
|
if (preset)
|
|
|
|
{
|
|
|
|
for (HBPreset *child in preset.children)
|
|
|
|
{
|
|
|
|
[self.treeController insertObject:child atArrangedObjectIndexPath:indexPath];
|
|
|
|
}
|
2025-01-02 13:30:09 +01:00
|
|
|
[self.manager savePresets];
|
2020-01-13 17:19:43 +01:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[self presentError:error];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-05-16 07:08:39 +00:00
|
|
|
- (IBAction)importPreset:(id)sender
|
|
|
|
{
|
|
|
|
NSOpenPanel *panel = [NSOpenPanel openPanel];
|
2018-06-09 10:06:52 +02:00
|
|
|
panel.title = NSLocalizedString(@"Import presets", @"Import preset open panel title");
|
2015-05-16 07:08:39 +00:00
|
|
|
panel.allowsMultipleSelection = YES;
|
|
|
|
panel.canChooseFiles = YES;
|
|
|
|
panel.canChooseDirectories = NO;
|
2024-03-30 19:53:18 +01:00
|
|
|
panel.allowedFileTypes = @[@"json"];
|
2015-05-16 07:08:39 +00:00
|
|
|
|
2020-01-13 17:19:43 +01:00
|
|
|
if ([NSUserDefaults.standardUserDefaults URLForKey:@"LastPresetImportDirectoryURL"])
|
2015-05-16 07:08:39 +00:00
|
|
|
{
|
2020-01-13 17:19:43 +01:00
|
|
|
panel.directoryURL = [NSUserDefaults.standardUserDefaults URLForKey:@"LastPresetImportDirectoryURL"];
|
2015-05-16 07:08:39 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2019-03-28 12:23:30 +01:00
|
|
|
panel.directoryURL = [[NSURL fileURLWithPath:NSHomeDirectory()] URLByAppendingPathComponent:@"Desktop" isDirectory:YES];
|
2015-05-16 07:08:39 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
[panel beginWithCompletionHandler:^(NSInteger result)
|
|
|
|
{
|
2020-01-13 17:19:43 +01:00
|
|
|
[NSUserDefaults.standardUserDefaults setURL:panel.directoryURL forKey:@"LastPresetImportDirectoryURL"];
|
2015-05-16 07:08:39 +00:00
|
|
|
|
2018-06-10 09:12:18 +02:00
|
|
|
if (result == NSModalResponseOK)
|
2015-05-16 07:08:39 +00:00
|
|
|
{
|
2020-01-13 17:19:43 +01:00
|
|
|
[self doImportPreset:panel.URLs atIndexPath:nil];
|
2015-05-16 07:08:39 +00:00
|
|
|
}
|
2023-07-07 07:30:02 +02:00
|
|
|
|
|
|
|
for (NSURL *url in panel.URLs)
|
|
|
|
{
|
|
|
|
[url stopAccessingSecurityScopedResource];
|
|
|
|
}
|
2015-05-16 07:08:39 +00:00
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
2014-08-07 13:54:14 +00:00
|
|
|
#pragma mark - UI Methods
|
|
|
|
|
2024-02-27 19:38:06 +01:00
|
|
|
- (void)setShowHeader:(BOOL)showHeader
|
|
|
|
{
|
|
|
|
_showHeader = showHeader;
|
|
|
|
|
|
|
|
self.headerLabel.hidden = !showHeader;
|
|
|
|
self.headerBottomConstraint.active = showHeader;
|
|
|
|
}
|
|
|
|
|
2014-08-07 13:54:14 +00:00
|
|
|
- (IBAction)clicked:(id)sender
|
|
|
|
{
|
2021-01-04 18:19:19 +01:00
|
|
|
if (self.delegate && [self.treeController.selectedObjects.firstObject isLeaf] && [self.treeController.selectedObjects.firstObject isSupported])
|
2014-08-07 13:54:14 +00:00
|
|
|
{
|
|
|
|
[self.delegate selectionDidChange];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-02-01 08:33:34 -05:00
|
|
|
- (IBAction)renamed:(id)sender
|
|
|
|
{
|
2020-01-20 12:12:27 +01:00
|
|
|
if (self.delegate && [self.treeController.selectedObjects.firstObject isLeaf])
|
2018-02-01 08:33:34 -05:00
|
|
|
{
|
|
|
|
[self.delegate selectionDidChange];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-08 10:43:05 +02:00
|
|
|
- (IBAction)segmentedActions:(NSSegmentedControl *)sender
|
|
|
|
{
|
|
|
|
if (sender.selectedSegment == 0)
|
|
|
|
{
|
|
|
|
[self addNewPreset:self];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
[self deletePreset:self];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-07 13:54:14 +00:00
|
|
|
- (IBAction)addNewPreset:(id)sender
|
|
|
|
{
|
|
|
|
if (self.delegate)
|
|
|
|
{
|
|
|
|
[self.delegate showAddPresetPanel:sender];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2025-01-02 13:30:09 +01:00
|
|
|
- (IBAction)renamePreset:(id)sender
|
|
|
|
{
|
|
|
|
HBPreset *preset = [self targetedItem];
|
|
|
|
__block HBRenamePresetController *renamePresetController = [[HBRenamePresetController alloc] initWithPreset:preset
|
|
|
|
presetManager:self.manager];
|
|
|
|
|
|
|
|
NSModalResponse returnCode = [NSApp runModalForWindow:renamePresetController.window];
|
|
|
|
if (returnCode == NSModalResponseOK)
|
|
|
|
{
|
|
|
|
if (self.delegate && preset.isLeaf)
|
|
|
|
{
|
|
|
|
[self.delegate selectionDidChange];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-08-07 13:54:14 +00:00
|
|
|
- (IBAction)deletePreset:(id)sender
|
|
|
|
{
|
2020-01-21 11:33:29 +01:00
|
|
|
if (self.treeController.canRemove)
|
2014-08-07 13:54:14 +00:00
|
|
|
{
|
2018-06-08 16:59:25 +02:00
|
|
|
// Alert user before deleting preset
|
2018-06-09 10:26:57 +02:00
|
|
|
NSAlert *alert = [[NSAlert alloc] init];
|
2020-01-20 12:12:27 +01:00
|
|
|
alert.alertStyle = NSAlertStyleCritical;
|
2018-06-09 10:06:52 +02:00
|
|
|
alert.informativeText = NSLocalizedString(@"You can't undo this action.", @"Delete preset alert -> informative text");
|
2020-01-20 12:12:27 +01:00
|
|
|
|
2025-01-02 13:30:09 +01:00
|
|
|
NSArray<HBPreset *> *presets = [self targetedItems];
|
|
|
|
|
|
|
|
if (presets.count > 1)
|
2020-01-20 12:12:27 +01:00
|
|
|
{
|
|
|
|
alert.messageText = NSLocalizedString(@"Are you sure you want to permanently delete the selected presets?", @"Delete preset alert -> message");
|
|
|
|
[alert addButtonWithTitle:NSLocalizedString(@"Delete Presets", @"Delete preset alert -> first button")];
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
alert.messageText = NSLocalizedString(@"Are you sure you want to permanently delete the selected preset?", @"Delete preset alert -> message");
|
|
|
|
[alert addButtonWithTitle:NSLocalizedString(@"Delete Preset", @"Delete preset alert -> first button")];
|
|
|
|
}
|
|
|
|
|
2020-08-03 16:33:02 +02:00
|
|
|
if (@available(macOS 11, *))
|
2020-06-27 14:13:12 +02:00
|
|
|
{
|
|
|
|
alert.buttons.lastObject.hasDestructiveAction = true;
|
|
|
|
}
|
2018-06-09 10:06:52 +02:00
|
|
|
[alert addButtonWithTitle:NSLocalizedString(@"Cancel", @"Delete preset alert -> second button")];
|
2014-08-07 13:54:14 +00:00
|
|
|
|
|
|
|
NSInteger status = [alert runModal];
|
2018-06-08 16:59:25 +02:00
|
|
|
if (status == NSAlertFirstButtonReturn)
|
2014-08-07 13:54:14 +00:00
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
for (HBPreset *preset in presets.reverseObjectEnumerator)
|
2020-01-13 17:19:43 +01:00
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
NSIndexPath *indexPath = [self.manager indexPathOfPreset:preset];
|
|
|
|
[self.manager deletePresetAtIndexPath:indexPath];
|
2020-01-13 17:19:43 +01:00
|
|
|
}
|
2025-01-02 13:30:09 +01:00
|
|
|
[self setSelection:self.manager.defaultPreset];
|
2014-08-07 13:54:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-10 12:41:07 +01:00
|
|
|
- (IBAction)insertCategory:(id)sender
|
2014-08-07 13:54:14 +00:00
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
HBAddCategoryController *addCategoryController = [[HBAddCategoryController alloc] initWithPresetManager:self.manager];
|
2017-11-10 14:27:22 +01:00
|
|
|
|
|
|
|
NSModalResponse returnCode = [NSApp runModalForWindow:addCategoryController.window];
|
|
|
|
if (returnCode == NSModalResponseOK)
|
2014-08-07 13:54:14 +00:00
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
NSIndexPath *indexPath = [self.manager indexPathOfPreset:addCategoryController.category];
|
2017-11-10 14:27:22 +01:00
|
|
|
[self.treeController setSelectionIndexPath:indexPath];
|
2014-08-07 13:54:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction)setDefault:(id)sender
|
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
HBPreset *selectedNode = [self targetedItem];
|
2017-12-20 22:06:38 +01:00
|
|
|
if (selectedNode.isLeaf)
|
2014-08-07 13:54:14 +00:00
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
self.manager.defaultPreset = selectedNode;
|
2014-08-07 13:54:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-20 22:06:38 +01:00
|
|
|
- (void)setSelectedPreset:(HBPreset *)selectedPreset
|
2014-08-07 13:54:14 +00:00
|
|
|
{
|
2017-12-20 22:06:38 +01:00
|
|
|
_selectedPresetInternal = selectedPreset;
|
|
|
|
[self setSelection:selectedPreset];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (HBPreset *)selectedPreset
|
|
|
|
{
|
|
|
|
return _selectedPresetInternal;
|
2014-08-07 13:54:14 +00:00
|
|
|
}
|
|
|
|
|
2014-08-17 06:10:35 +00:00
|
|
|
- (void)setSelection:(HBPreset *)preset
|
2014-08-07 13:54:14 +00:00
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
NSIndexPath *idx = [self.manager indexPathOfPreset:preset];
|
2014-08-07 13:54:14 +00:00
|
|
|
|
|
|
|
if (idx)
|
|
|
|
{
|
|
|
|
[self.treeController setSelectionIndexPath:idx];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (IBAction)updateBuiltInPresets:(id)sender
|
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
[self.manager generateBuiltInPresets];
|
2014-08-07 13:54:14 +00:00
|
|
|
|
|
|
|
// Re-expand the items
|
2020-01-21 11:33:29 +01:00
|
|
|
[self expandNodes:self.treeController.arrangedObjects.childNodes];
|
2014-08-07 13:54:14 +00:00
|
|
|
}
|
|
|
|
|
2015-01-09 07:41:26 +00:00
|
|
|
#pragma mark - Expanded node persistence methods
|
2014-08-07 13:54:14 +00:00
|
|
|
|
|
|
|
- (void)expandNodes:(NSArray *)childNodes
|
|
|
|
{
|
|
|
|
for (id node in childNodes)
|
|
|
|
{
|
|
|
|
[self expandNodes:[node childNodes]];
|
|
|
|
if ([self.expandedNodes containsObject:@([[node representedObject] hash])])
|
|
|
|
[self.outlineView expandItem:node expandChildren:YES];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)outlineViewItemDidExpand:(NSNotification *)notification
|
|
|
|
{
|
2020-01-13 17:19:43 +01:00
|
|
|
HBPreset *node = [notification.userInfo[@"NSObject"] representedObject];
|
2014-08-07 13:54:14 +00:00
|
|
|
if (![self.expandedNodes containsObject:@(node.hash)])
|
|
|
|
{
|
|
|
|
[self.expandedNodes addObject:@(node.hash)];
|
2020-01-13 17:19:43 +01:00
|
|
|
[NSUserDefaults.standardUserDefaults setObject:self.expandedNodes forKey:@"HBPreviewViewExpandedStatus"];
|
2014-08-07 13:54:14 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)outlineViewItemDidCollapse:(NSNotification *)notification
|
|
|
|
{
|
2020-01-13 17:19:43 +01:00
|
|
|
HBPreset *node = [notification.userInfo[@"NSObject"] representedObject];
|
2014-08-07 13:54:14 +00:00
|
|
|
[self.expandedNodes removeObject:@(node.hash)];
|
2020-01-13 17:19:43 +01:00
|
|
|
[NSUserDefaults.standardUserDefaults setObject:self.expandedNodes forKey:@"HBPreviewViewExpandedStatus"];
|
2014-08-07 13:54:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#pragma mark - Drag & Drops
|
|
|
|
|
2020-01-13 17:19:43 +01:00
|
|
|
- (void)outlineView:(NSOutlineView *)outlineView draggingSession:(NSDraggingSession *)session willBeginAtPoint:(NSPoint)screenPoint forItems:(NSArray *)draggedItems
|
2014-08-07 13:54:14 +00:00
|
|
|
{
|
2020-01-13 17:19:43 +01:00
|
|
|
self.dragNodesArray = draggedItems;
|
2014-08-07 13:54:14 +00:00
|
|
|
}
|
|
|
|
|
2020-01-13 17:19:43 +01:00
|
|
|
- (void)outlineView:(NSOutlineView *)outlineView draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation
|
2014-08-07 13:54:14 +00:00
|
|
|
{
|
2020-01-13 17:19:43 +01:00
|
|
|
self.dragNodesArray = nil;
|
|
|
|
}
|
2014-08-07 13:54:14 +00:00
|
|
|
|
2020-01-13 17:19:43 +01:00
|
|
|
- (id<NSPasteboardWriting>)outlineView:(NSOutlineView *)outlineView
|
2021-10-15 18:41:40 +02:00
|
|
|
pasteboardWriterForItem:(id)item
|
|
|
|
{
|
|
|
|
HBFilePromiseProvider *filePromise = [[HBFilePromiseProvider alloc] initWithFileType:@"public.text" delegate:self];
|
|
|
|
filePromise.userInfo = [item representedObject];
|
|
|
|
return filePromise;
|
2020-01-13 17:19:43 +01:00
|
|
|
}
|
2014-08-07 13:54:14 +00:00
|
|
|
|
2020-01-13 17:19:43 +01:00
|
|
|
- (nonnull NSString *)filePromiseProvider:(nonnull NSFilePromiseProvider *)filePromiseProvider fileNameForType:(nonnull NSString *)fileType
|
|
|
|
{
|
|
|
|
return [self fileNameForPreset:filePromiseProvider.userInfo];
|
|
|
|
}
|
2014-08-07 13:54:14 +00:00
|
|
|
|
2020-01-13 17:19:43 +01:00
|
|
|
- (void)filePromiseProvider:(nonnull NSFilePromiseProvider *)filePromiseProvider writePromiseToURL:(nonnull NSURL *)url completionHandler:(nonnull void (^)(NSError * _Nullable))completionHandler
|
|
|
|
{
|
|
|
|
NSError *error = NULL;
|
|
|
|
[filePromiseProvider.userInfo writeToURL:url atomically:YES removeRoot:NO error:&error];
|
|
|
|
completionHandler(error);
|
2014-08-07 13:54:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (NSDragOperation)outlineView:(NSOutlineView *)ov
|
|
|
|
validateDrop:(id <NSDraggingInfo>)info
|
|
|
|
proposedItem:(id)item
|
|
|
|
proposedChildIndex:(NSInteger)index
|
|
|
|
{
|
|
|
|
NSDragOperation result = NSDragOperationNone;
|
|
|
|
|
2020-01-13 17:19:43 +01:00
|
|
|
if (info.draggingSource == nil)
|
|
|
|
{
|
|
|
|
if ([[item representedObject] isBuiltIn] ||
|
|
|
|
([[item representedObject] isLeaf] && index == -1))
|
2014-08-09 18:56:29 +00:00
|
|
|
{
|
|
|
|
result = NSDragOperationNone;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2020-01-13 17:19:43 +01:00
|
|
|
result = NSDragOperationCopy;
|
2014-08-09 18:56:29 +00:00
|
|
|
}
|
2020-01-13 17:19:43 +01:00
|
|
|
}
|
2020-09-12 11:27:19 +02:00
|
|
|
else if ([self.dragNodesArray HB_allSatisfy:^BOOL(id _Nonnull object) { return [[object representedObject] isBuiltIn] == NO; }])
|
2020-01-13 17:19:43 +01:00
|
|
|
{
|
|
|
|
if ([[item representedObject] isBuiltIn] ||
|
|
|
|
([[item representedObject] isLeaf] && index == -1) ||
|
|
|
|
[self.dragNodesArray containsObject:item])
|
2014-08-07 13:54:14 +00:00
|
|
|
{
|
|
|
|
// don't allow dropping on a child
|
|
|
|
result = NSDragOperationNone;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// drop location is a container
|
|
|
|
result = NSDragOperationMove;
|
|
|
|
}
|
2020-01-13 17:19:43 +01:00
|
|
|
}
|
2014-08-07 13:54:14 +00:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)handleInternalDrops:(NSPasteboard *)pboard withIndexPath:(NSIndexPath *)indexPath
|
|
|
|
{
|
|
|
|
// user is doing an intra app drag within the outline view:
|
|
|
|
NSArray *newNodes = self.dragNodesArray;
|
|
|
|
|
|
|
|
// move the items to their new place (we do this backwards, otherwise they will end up in reverse order)
|
|
|
|
NSInteger idx;
|
2020-01-13 17:19:43 +01:00
|
|
|
for (idx = newNodes.count - 1; idx >= 0; idx--)
|
2014-08-07 13:54:14 +00:00
|
|
|
{
|
|
|
|
[self.treeController moveNode:newNodes[idx] toIndexPath:indexPath];
|
2014-08-09 17:10:45 +00:00
|
|
|
|
|
|
|
// Call manually this because the NSTreeController doesn't call
|
|
|
|
// the KVC accessors method for the root node.
|
|
|
|
if (indexPath.length == 1)
|
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
[self.manager performSelector:@selector(nodeDidChange:) withObject:nil];
|
2014-08-09 17:10:45 +00:00
|
|
|
}
|
2014-08-07 13:54:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// keep the moved nodes selected
|
|
|
|
NSMutableArray *indexPathList = [NSMutableArray array];
|
2020-01-13 17:19:43 +01:00
|
|
|
for (id node in newNodes)
|
|
|
|
{
|
|
|
|
[indexPathList addObject:[node indexPath]];
|
|
|
|
}
|
|
|
|
[self.treeController setSelectionIndexPaths:indexPathList];
|
|
|
|
}
|
|
|
|
|
|
|
|
- (void)handleExternalDrops:(NSPasteboard *)pboard withIndexPath:(NSIndexPath *)indexPath
|
|
|
|
{
|
|
|
|
NSArray<NSURL *> *URLs = [pboard readObjectsForClasses:@[[NSURL class]] options:nil];
|
2023-07-07 07:30:02 +02:00
|
|
|
|
2020-01-13 17:19:43 +01:00
|
|
|
[self doImportPreset:URLs atIndexPath:indexPath];
|
2023-07-07 07:30:02 +02:00
|
|
|
|
|
|
|
for (NSURL *url in URLs)
|
|
|
|
{
|
|
|
|
[url stopAccessingSecurityScopedResource];
|
|
|
|
}
|
2014-08-07 13:54:14 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (BOOL)outlineView:(NSOutlineView *)ov acceptDrop:(id <NSDraggingInfo>)info item:(id)targetItem childIndex:(NSInteger)index
|
|
|
|
{
|
|
|
|
BOOL result = NO;
|
|
|
|
|
2020-01-13 17:19:43 +01:00
|
|
|
// note that "targetItem" is a NSTreeNode proxy
|
2014-08-07 13:54:14 +00:00
|
|
|
// find the index path to insert our dropped object(s)
|
|
|
|
NSIndexPath *indexPath;
|
|
|
|
if (targetItem)
|
|
|
|
{
|
2020-01-13 17:19:43 +01:00
|
|
|
// drop down inside the tree node: fetch the index path to insert our dropped node
|
|
|
|
indexPath = index == -1 ? [[targetItem indexPath] indexPathByAddingIndex:0] : [[targetItem indexPath] indexPathByAddingIndex:index];
|
2014-08-07 13:54:14 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// drop at the top root level
|
2019-07-31 07:27:27 +02:00
|
|
|
if (index == -1) // drop area might be ambiguous (not at a particular location)
|
2020-01-13 17:19:43 +01:00
|
|
|
{
|
2025-01-02 13:30:09 +01:00
|
|
|
indexPath = [NSIndexPath indexPathWithIndex:self.manager.root.children.count]; // drop at the end of the top level
|
2020-01-13 17:19:43 +01:00
|
|
|
}
|
2014-08-07 13:54:14 +00:00
|
|
|
else
|
2020-01-13 17:19:43 +01:00
|
|
|
{
|
2014-08-07 13:54:14 +00:00
|
|
|
indexPath = [NSIndexPath indexPathWithIndex:index]; // drop at a particular place at the top level
|
2020-01-13 17:19:43 +01:00
|
|
|
}
|
2014-08-07 13:54:14 +00:00
|
|
|
}
|
|
|
|
|
2020-01-13 17:19:43 +01:00
|
|
|
NSPasteboard *pboard = info.draggingPasteboard;
|
2014-08-07 13:54:14 +00:00
|
|
|
|
2020-01-13 17:19:43 +01:00
|
|
|
// check the dragging type
|
|
|
|
if ([pboard availableTypeFromArray:@[kHandBrakeInternalPBoardType]])
|
2014-08-07 13:54:14 +00:00
|
|
|
{
|
|
|
|
// user is doing an intra-app drag within the outline view
|
|
|
|
[self handleInternalDrops:pboard withIndexPath:indexPath];
|
|
|
|
result = YES;
|
|
|
|
}
|
2021-10-15 18:41:40 +02:00
|
|
|
else if ([pboard availableTypeFromArray:@[NSPasteboardTypeFileURL]])
|
2020-01-13 17:19:43 +01:00
|
|
|
{
|
|
|
|
[self handleExternalDrops:pboard withIndexPath:indexPath];
|
|
|
|
result = YES;
|
|
|
|
}
|
2014-08-07 13:54:14 +00:00
|
|
|
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
@end
|