diff options
author | Damiano Galassi <[email protected]> | 2020-01-13 17:19:43 +0100 |
---|---|---|
committer | Damiano Galassi <[email protected]> | 2020-01-13 17:19:43 +0100 |
commit | 80127fdb821c1e795b486d957e36bdab3f5de1f6 (patch) | |
tree | cab65feadbe05d7d89abf7869245e4a9186d03a9 /macosx | |
parent | fce3bbbf92011ee2eb9a3805f6b426ff1ffb6eba (diff) |
MacGui: improve presets view to allow multiple selection, drag & drop from and to Finder.
Diffstat (limited to 'macosx')
-rw-r--r-- | macosx/Base.lproj/Presets.xib | 21 | ||||
-rw-r--r-- | macosx/HBFilePromiseProvider.h | 19 | ||||
-rw-r--r-- | macosx/HBFilePromiseProvider.m | 40 | ||||
-rw-r--r-- | macosx/HBPreset.h | 7 | ||||
-rw-r--r-- | macosx/HBPreset.m | 28 | ||||
-rw-r--r-- | macosx/HBPresetsManager.m | 2 | ||||
-rw-r--r-- | macosx/HBPresetsViewController.m | 239 | ||||
-rw-r--r-- | macosx/HandBrake.xcodeproj/project.pbxproj | 6 | ||||
-rw-r--r-- | macosx/NSArray+HBAdditions.h | 1 | ||||
-rw-r--r-- | macosx/NSArray+HBAdditions.m | 12 |
10 files changed, 248 insertions, 127 deletions
diff --git a/macosx/Base.lproj/Presets.xib b/macosx/Base.lproj/Presets.xib index 7487802b0..23f4cedf5 100644 --- a/macosx/Base.lproj/Presets.xib +++ b/macosx/Base.lproj/Presets.xib @@ -1,9 +1,8 @@ <?xml version="1.0" encoding="UTF-8"?> -<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="13771" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> +<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="15702" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES"> <dependencies> <deployment identifier="macosx"/> - <development version="8000" identifier="xcode"/> - <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="13771"/> + <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="15702"/> <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> </dependencies> <objects> @@ -31,21 +30,21 @@ Overrides all encode settings. Settings may be further adjusted after selecting <rect key="frame" x="1" y="1" width="222" height="264"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <subviews> - <outlineView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" multipleSelection="NO" autosaveColumns="NO" rowHeight="14" rowSizeStyle="automatic" viewBased="YES" indentationPerLevel="16" outlineTableColumn="jhC-ge-H1w" id="00W-tb-wgY"> + <outlineView focusRingType="none" verticalHuggingPriority="750" allowsExpansionToolTips="YES" columnReordering="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="14" rowSizeStyle="automatic" viewBased="YES" indentationPerLevel="16" outlineTableColumn="jhC-ge-H1w" id="00W-tb-wgY"> <rect key="frame" x="0.0" y="0.0" width="222" height="264"/> - <autoresizingMask key="autoresizingMask"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <size key="intercellSpacing" width="3" height="2"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> <color key="gridColor" name="gridColor" catalog="System" colorSpace="catalog"/> <tableColumns> <tableColumn identifier="name" width="219" minWidth="16" maxWidth="1000" id="jhC-ge-H1w"> <tableHeaderCell key="headerCell" lineBreakMode="truncatingTail" borderStyle="border" alignment="left"> - <font key="font" metaFont="smallSystem"/> + <font key="font" metaFont="label" size="11"/> <color key="textColor" name="headerTextColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" white="0.33333298560000002" alpha="1" colorSpace="calibratedWhite"/> </tableHeaderCell> <textFieldCell key="dataCell" controlSize="small" lineBreakMode="truncatingTail" selectable="YES" editable="YES" alignment="left" title="Text Cell" id="4tC-UE-40G"> - <font key="font" metaFont="smallSystem"/> + <font key="font" metaFont="label" size="11"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/> </textFieldCell> @@ -58,7 +57,7 @@ Overrides all encode settings. Settings may be further adjusted after selecting <textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" allowsCharacterPickerTouchBarItem="YES" translatesAutoresizingMaskIntoConstraints="NO" id="VdJ-Vk-fzJ"> <rect key="frame" x="0.0" y="0.0" width="219" height="14"/> <textFieldCell key="cell" controlSize="small" lineBreakMode="truncatingTail" sendsActionOnEndEditing="YES" title="Table View Cell" id="oBC-Nh-TwB"> - <font key="font" metaFont="smallSystem"/> + <font key="font" metaFont="label" size="11"/> <color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/> <color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/> <connections> @@ -125,14 +124,14 @@ Overrides all encode settings. Settings may be further adjusted after selecting <binding destination="-2" name="enabled" keyPath="self.enabled" id="lmV-Y3-JoF"/> </connections> </button> - <popUpButton toolTip="Show additional options." verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="Ybq-Zt-sta"> - <rect key="frame" x="58" y="3" width="35" height="23"/> + <popUpButton toolTip="Show additional options." translatesAutoresizingMaskIntoConstraints="NO" id="Ybq-Zt-sta"> + <rect key="frame" x="58" y="4" width="35" height="22"/> <constraints> <constraint firstAttribute="width" constant="35" id="ROP-Ic-SbK"/> </constraints> <popUpButtonCell key="cell" type="smallSquare" bezelStyle="smallSquare" imagePosition="only" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" pullsDown="YES" id="2JY-O9-FR6"> <behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/> - <font key="font" metaFont="menu"/> + <font key="font" metaFont="system"/> <menu key="menu" title="Presets Action Menu" id="LQk-kD-5sj"> <items> <menuItem state="on" image="NSActionTemplate" hidden="YES" id="KPx-Ep-mb4"/> diff --git a/macosx/HBFilePromiseProvider.h b/macosx/HBFilePromiseProvider.h new file mode 100644 index 000000000..0666a7afc --- /dev/null +++ b/macosx/HBFilePromiseProvider.h @@ -0,0 +1,19 @@ +// +// HBFilePromiseProvider.h +// HandBrake +// +// Created by Damiano Galassi on 09/01/2020. +// Copyright © 2020 HandBrake. All rights reserved. +// + +#import <Cocoa/Cocoa.h> + +NS_ASSUME_NONNULL_BEGIN + +#define kHandBrakeInternalPBoardType @"fr.handbrake.internalPBoardType" + +@interface HBFilePromiseProvider : NSFilePromiseProvider + +@end + +NS_ASSUME_NONNULL_END diff --git a/macosx/HBFilePromiseProvider.m b/macosx/HBFilePromiseProvider.m new file mode 100644 index 000000000..b4c9e356e --- /dev/null +++ b/macosx/HBFilePromiseProvider.m @@ -0,0 +1,40 @@ +// +// HBFilePromiseProvider.m +// HandBrake +// +// Created by Damiano Galassi on 09/01/2020. +// Copyright © 2020 HandBrake. All rights reserved. +// + +#import "HBFilePromiseProvider.h" + +@implementation HBFilePromiseProvider + +- (NSArray<NSPasteboardType> *)writableTypesForPasteboard:(NSPasteboard *)pasteboard +{ + NSMutableArray<NSPasteboardType> *types = [[super writableTypesForPasteboard:pasteboard] mutableCopy]; + [types addObject:kHandBrakeInternalPBoardType]; + return types; +} + +- (NSPasteboardWritingOptions)writingOptionsForType:(NSPasteboardType)type pasteboard:(NSPasteboard *)pasteboard +{ + if ([type isEqualToString:kHandBrakeInternalPBoardType]) + { + return 0; + } + + return [super writingOptionsForType:type pasteboard:pasteboard]; +} + +- (id)pasteboardPropertyListForType:(NSPasteboardType)type +{ + if ([type isEqualToString:kHandBrakeInternalPBoardType]) + { + return kHandBrakeInternalPBoardType; + } + + return [super pasteboardPropertyListForType:type]; +} + +@end diff --git a/macosx/HBPreset.h b/macosx/HBPreset.h index 3e95d3bab..75a8a6a42 100644 --- a/macosx/HBPreset.h +++ b/macosx/HBPreset.h @@ -9,10 +9,6 @@ NS_ASSUME_NONNULL_BEGIN -typedef NS_ENUM(NSUInteger, HBPresetFormat) { - HBPresetFormatPlist, - HBPresetFormatJson, -}; /** * HBPreset * Stores a preset dictionary. @@ -42,12 +38,11 @@ typedef NS_ENUM(NSUInteger, HBPresetFormat) { * * @param url The URL to which to write the preset. * @param atomically A flag that specifies whether the output should be written atomically. - * @param format the format of the file to write. * @param removeRoot whether the root preset should be written or not. * * @return YES if the location is written successfully, otherwise NO. */ -- (BOOL)writeToURL:(NSURL *)url atomically:(BOOL)atomically format:(HBPresetFormat)format removeRoot:(BOOL)removeRoot; +- (BOOL)writeToURL:(NSURL *)url atomically:(BOOL)atomically removeRoot:(BOOL)removeRoot error:(NSError * __autoreleasing *)outError; /** * The name of the preset. diff --git a/macosx/HBPreset.m b/macosx/HBPreset.m index 013e11b9c..f0fec1310 100644 --- a/macosx/HBPreset.m +++ b/macosx/HBPreset.m @@ -186,6 +186,7 @@ [self.children addObject:preset]; } } + [self resetBuiltInAndDefaultState]; return self; } else if (outError) @@ -246,7 +247,7 @@ return output; } -- (BOOL)writeToURL:(NSURL *)url atomically:(BOOL)atomically format:(HBPresetFormat)format removeRoot:(BOOL)removeRoot +- (BOOL)writeToURL:(NSURL *)url atomically:(BOOL)atomically removeRoot:(BOOL)removeRoot error:(NSError * __autoreleasing *)outError { BOOL success = NO; NSArray *presetList; @@ -268,16 +269,10 @@ @"VersionMinor": @(minor), @"VersionMicro": @(micro) }; - if (format == HBPresetFormatPlist) - { - success = [dict writeToURL:url atomically:atomically]; - } - else - { - NSUInteger sortKeys = (1UL << 1); // NSJSONWritingSortedKeys in 10.13 sdk; - NSData *jsonPreset = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted | sortKeys error:NULL]; - success = [jsonPreset writeToURL:url atomically:atomically]; - } + + NSUInteger sortKeys = (1UL << 1); // NSJSONWritingSortedKeys in 10.13 sdk; + NSData *jsonPreset = [NSJSONSerialization dataWithJSONObject:dict options:NSJSONWritingPrettyPrinted | sortKeys error:NULL]; + success = [jsonPreset writeToURL:url options:NSDataWritingAtomic error:outError]; return success; } @@ -300,6 +295,17 @@ } } +- (void)resetBuiltInAndDefaultState +{ + _isBuiltIn = NO; + _isDefault = NO; + + for (HBPreset *child in self.children) + { + [child resetBuiltInAndDefaultState]; + } +} + #pragma mark - NSCopying - (id)copyWithZone:(NSZone *)zone diff --git a/macosx/HBPresetsManager.m b/macosx/HBPresetsManager.m index 4692a37a6..f8aede8a1 100644 --- a/macosx/HBPresetsManager.m +++ b/macosx/HBPresetsManager.m @@ -248,7 +248,7 @@ typedef NS_ENUM(NSUInteger, HBPresetLoadingResult) { - (BOOL)savePresetsToURL:(NSURL *)url { - return [self.root writeToURL:url atomically:YES format:HBPresetFormatJson removeRoot:YES]; + return [self.root writeToURL:url atomically:YES removeRoot:YES error:NULL]; } - (BOOL)savePresets diff --git a/macosx/HBPresetsViewController.m b/macosx/HBPresetsViewController.m index bf49c91cf..f71c1bac4 100644 --- a/macosx/HBPresetsViewController.m +++ b/macosx/HBPresetsViewController.m @@ -6,12 +6,11 @@ #import "HBPresetsViewController.h" #import "HBAddCategoryController.h" +#import "HBFilePromiseProvider.h" +#import "NSArray+HBAdditions.h" @import HandBrakeKit; -// drag and drop pasteboard type -#define kHandBrakePresetPBoardType @"handBrakePresetPBoardType" - // KVO Context static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; @@ -44,7 +43,7 @@ static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; @end -@interface HBPresetsViewController () <NSOutlineViewDelegate> +@interface HBPresetsViewController () <NSOutlineViewDelegate, NSOutlineViewDataSource, NSFilePromiseProviderDelegate> @property (nonatomic, strong) HBPresetsManager *presets; @property (nonatomic, readwrite) HBPreset *selectedPresetInternal; @@ -56,7 +55,7 @@ static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; /** * Helper var for drag & drop */ -@property (nonatomic, strong) NSArray *dragNodesArray; +@property (nonatomic, strong, nullable) NSArray *dragNodesArray; /** * The status (expanded or not) of the categories. @@ -77,7 +76,7 @@ static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; { _presets = presetManager; _selectedPresetInternal = presetManager.defaultPreset; - _expandedNodes = [[NSArray arrayWithArray:[[NSUserDefaults standardUserDefaults] + _expandedNodes = [[NSArray arrayWithArray:[NSUserDefaults.standardUserDefaults objectForKey:@"HBPreviewViewExpandedStatus"]] mutableCopy]; _showHeader = YES; } @@ -94,7 +93,9 @@ static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; } // drag and drop support - [self.outlineView registerForDraggedTypes:@[kHandBrakePresetPBoardType]]; + [self.outlineView registerForDraggedTypes:@[kHandBrakeInternalPBoardType, (NSString *)kUTTypeFileURL]]; + [self.outlineView setDraggingSourceOperationMask:NSDragOperationCopy forLocal:NO]; + [self.outlineView setDraggingSourceOperationMask:NSDragOperationMove forLocal:YES]; // Re-expand the items [self expandNodes:[self.treeController.arrangedObjects childNodes]]; @@ -146,8 +147,13 @@ static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; return YES; } -#pragma mark - -#pragma mark Import Export Preset(s) +#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"]; +} - (IBAction)exportPreset:(id)sender { @@ -161,20 +167,63 @@ static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; // We get the current file name and path from the destination field here NSURL *defaultExportDirectory = [[NSURL fileURLWithPath:NSHomeDirectory()] URLByAppendingPathComponent:@"Desktop" isDirectory:YES]; panel.directoryURL = defaultExportDirectory; - panel.nameFieldStringValue = [NSString stringWithFormat:@"%@.json", selectedPreset.name]; + panel.nameFieldStringValue = [self fileNameForPreset:selectedPreset]; [panel beginWithCompletionHandler:^(NSInteger result) { if (result == NSModalResponseOK) { NSURL *presetExportDirectory = [panel.URL URLByDeletingLastPathComponent]; - [[NSUserDefaults standardUserDefaults] setURL:presetExportDirectory forKey:@"LastPresetExportDirectoryURL"]; + [NSUserDefaults.standardUserDefaults setURL:presetExportDirectory forKey:@"LastPresetExportDirectoryURL"]; - [selectedPreset writeToURL:panel.URL atomically:YES format:HBPresetFormatJson removeRoot:NO]; + NSError *error = NULL; + BOOL success = [selectedPreset writeToURL:panel.URL atomically:YES removeRoot:NO error:&error]; + if (success == NO) + { + [self presentError:error]; + } } }]; } +- (void)doImportPreset:(NSArray<NSURL *> *)URLs atIndexPath:(nullable NSIndexPath *)indexPath +{ + if (indexPath == nil) + { + for (HBPreset *preset in self.presets.root.children) + { + if (preset.isBuiltIn == NO && preset.isLeaf == NO) + { + indexPath = [[self.presets indexPathOfPreset:preset] indexPathByAddingIndex:0]; + } + } + + if (indexPath == nil) + { + indexPath = [NSIndexPath indexPathWithIndex:self.presets.root.countOfChildren]; + } + } + + 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]; + } + [self.presets savePresets]; + } + else + { + [self presentError:error]; + } + } +} + - (IBAction)importPreset:(id)sender { NSOpenPanel *panel = [NSOpenPanel openPanel]; @@ -184,9 +233,9 @@ static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; panel.canChooseDirectories = NO; panel.allowedFileTypes = @[@"plist", @"xml", @"json"]; - if ([[NSUserDefaults standardUserDefaults] URLForKey:@"LastPresetImportDirectoryURL"]) + if ([NSUserDefaults.standardUserDefaults URLForKey:@"LastPresetImportDirectoryURL"]) { - panel.directoryURL = [[NSUserDefaults standardUserDefaults] URLForKey:@"LastPresetImportDirectoryURL"]; + panel.directoryURL = [NSUserDefaults.standardUserDefaults URLForKey:@"LastPresetImportDirectoryURL"]; } else { @@ -195,25 +244,11 @@ static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; [panel beginWithCompletionHandler:^(NSInteger result) { - [[NSUserDefaults standardUserDefaults] setURL:panel.directoryURL forKey:@"LastPresetImportDirectoryURL"]; + [NSUserDefaults.standardUserDefaults setURL:panel.directoryURL forKey:@"LastPresetImportDirectoryURL"]; if (result == NSModalResponseOK) { - for (NSURL *url in panel.URLs) - { - NSError *error; - HBPreset *import = [[HBPreset alloc] initWithContentsOfURL:url error:&error]; - - if (import == nil) - { - [self presentError:error]; - } - - for (HBPreset *child in import.children) - { - [self.presets addPreset:child]; - } - } + [self doImportPreset:panel.URLs atIndexPath:nil]; } }]; } @@ -241,7 +276,7 @@ static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; if (self.delegate && [[self.treeController.selectedObjects firstObject] isLeaf]) { [self.delegate selectionDidChange]; - [[NSNotificationCenter defaultCenter] postNotificationName:HBPresetsChangedNotification object:nil]; + [NSNotificationCenter.defaultCenter postNotificationName:HBPresetsChangedNotification object:nil]; } } @@ -269,7 +304,10 @@ static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; if (status == NSAlertFirstButtonReturn) { - [self.presets deletePresetAtIndexPath:[self.treeController selectionIndexPath]]; + for (NSIndexPath *indexPath in self.treeController.selectionIndexPaths.reverseObjectEnumerator) + { + [self.presets deletePresetAtIndexPath:indexPath]; + } [self setSelection:self.presets.defaultPreset]; } } @@ -293,7 +331,7 @@ static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; if (selectedNode.isLeaf) { self.presets.defaultPreset = selectedNode; - [[NSNotificationCenter defaultCenter] postNotificationName:HBPresetsChangedNotification object:nil]; + [NSNotificationCenter.defaultCenter postNotificationName:HBPresetsChangedNotification object:nil]; } } @@ -340,55 +378,59 @@ static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; - (void)outlineViewItemDidExpand:(NSNotification *)notification { - HBPreset *node = [[[notification userInfo] valueForKey:@"NSObject"] representedObject]; + HBPreset *node = [notification.userInfo[@"NSObject"] representedObject]; if (![self.expandedNodes containsObject:@(node.hash)]) { [self.expandedNodes addObject:@(node.hash)]; - [[NSUserDefaults standardUserDefaults] setObject:self.expandedNodes forKey:@"HBPreviewViewExpandedStatus"]; + [NSUserDefaults.standardUserDefaults setObject:self.expandedNodes forKey:@"HBPreviewViewExpandedStatus"]; } } - (void)outlineViewItemDidCollapse:(NSNotification *)notification { - HBPreset *node = [[[notification userInfo] valueForKey:@"NSObject"] representedObject]; + HBPreset *node = [notification.userInfo[@"NSObject"] representedObject]; [self.expandedNodes removeObject:@(node.hash)]; - [[NSUserDefaults standardUserDefaults] setObject:self.expandedNodes forKey:@"HBPreviewViewExpandedStatus"]; + [NSUserDefaults.standardUserDefaults setObject:self.expandedNodes forKey:@"HBPreviewViewExpandedStatus"]; } #pragma mark - Drag & Drops -/** - * draggingSourceOperationMaskForLocal <NSDraggingSource override> - */ -- (NSDragOperation)draggingSession:(NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context +- (void)outlineView:(NSOutlineView *)outlineView draggingSession:(NSDraggingSession *)session willBeginAtPoint:(NSPoint)screenPoint forItems:(NSArray *)draggedItems { - return NSDragOperationMove; + self.dragNodesArray = draggedItems; } -/** - * outlineView:writeItems:toPasteboard - */ -- (BOOL)outlineView:(NSOutlineView *)ov writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard +- (void)outlineView:(NSOutlineView *)outlineView draggingSession:(NSDraggingSession *)session endedAtPoint:(NSPoint)screenPoint operation:(NSDragOperation)operation { - // Return no if we are trying to drag a built-in preset - for (id item in items) { - if ([[item representedObject] isBuiltIn]) - return NO; - } + self.dragNodesArray = nil; +} - [pboard declareTypes:@[kHandBrakePresetPBoardType] owner:self]; +- (id<NSPasteboardWriting>)outlineView:(NSOutlineView *)outlineView + pasteboardWriterForItem:(id)item { + if (@available(macOS 10.12, *)) + { + HBFilePromiseProvider *filePromise = [[HBFilePromiseProvider alloc] initWithFileType:@"public.text" delegate:self]; + filePromise.userInfo = [item representedObject]; + return filePromise; + } + else + { + return [[NSPasteboardItem alloc] initWithPasteboardPropertyList:@1 ofType:kHandBrakeInternalPBoardType]; + } +} - // keep track of this nodes for drag feedback in "validateDrop" - self.dragNodesArray = items; +- (nonnull NSString *)filePromiseProvider:(nonnull NSFilePromiseProvider *)filePromiseProvider fileNameForType:(nonnull NSString *)fileType +{ + return [self fileNameForPreset:filePromiseProvider.userInfo]; +} - return YES; +- (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); } -/** - * outlineView:validateDrop:proposedItem:proposedChildrenIndex: - * - * This method is used by NSOutlineView to determine a valid drop target. - */ - (NSDragOperation)outlineView:(NSOutlineView *)ov validateDrop:(id <NSDraggingInfo>)info proposedItem:(id)item @@ -396,22 +438,23 @@ static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; { NSDragOperation result = NSDragOperationNone; - if (!item) - { - if (index == 0) + if (info.draggingSource == nil) + { + if ([[item representedObject] isBuiltIn] || + ([[item representedObject] isLeaf] && index == -1)) { - // don't allow to drop on top result = NSDragOperationNone; } else { - // no item to drop on - result = NSDragOperationGeneric; + result = NSDragOperationCopy; } - } - else - { - if (index == -1 || [[item representedObject] isBuiltIn] || [self.dragNodesArray containsObject:item]) + } + else if ([self.dragNodesArray allSatisfy:^BOOL(id _Nonnull object) { return [[object representedObject] isBuiltIn] == NO; }]) + { + if ([[item representedObject] isBuiltIn] || + ([[item representedObject] isLeaf] && index == -1) || + [self.dragNodesArray containsObject:item]) { // don't allow dropping on a child result = NSDragOperationNone; @@ -421,16 +464,11 @@ static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; // drop location is a container result = NSDragOperationMove; } - } + } return result; } -/** - * handleInternalDrops:pboard:withIndexPath: - * - * The user is doing an intra-app drag within the outline view. - */ - (void)handleInternalDrops:(NSPasteboard *)pboard withIndexPath:(NSIndexPath *)indexPath { // user is doing an intra app drag within the outline view: @@ -438,7 +476,7 @@ static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; // move the items to their new place (we do this backwards, otherwise they will end up in reverse order) NSInteger idx; - for (idx = ([newNodes count] - 1); idx >= 0; idx--) + for (idx = newNodes.count - 1; idx >= 0; idx--) { [self.treeController moveNode:newNodes[idx] toIndexPath:indexPath]; @@ -452,53 +490,58 @@ static void *HBPresetsViewControllerContext = &HBPresetsViewControllerContext; // keep the moved nodes selected NSMutableArray *indexPathList = [NSMutableArray array]; - for (NSUInteger i = 0; i < [newNodes count]; i++) - { - [indexPathList addObject:[newNodes[i] indexPath]]; - } - [self.treeController setSelectionIndexPaths: indexPathList]; + 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]; + [self doImportPreset:URLs atIndexPath:indexPath]; } -/** - * outlineView:acceptDrop:item:childIndex - * - * This method is called when the mouse is released over an outline view that previously decided to allow a drop - * via the validateDrop method. The data source should incorporate the data from the dragging pasteboard at this time. - * 'index' is the location to insert the data as a child of 'item', and are the values previously set in the validateDrop: method. - * - */ - (BOOL)outlineView:(NSOutlineView *)ov acceptDrop:(id <NSDraggingInfo>)info item:(id)targetItem childIndex:(NSInteger)index { - // note that "targetItem" is a NSTreeNode proxy - // BOOL result = NO; + // note that "targetItem" is a NSTreeNode proxy // find the index path to insert our dropped object(s) NSIndexPath *indexPath; if (targetItem) { - // drop down inside the tree node: - // feth the index path to insert our dropped node - indexPath = [[targetItem indexPath] indexPathByAddingIndex:index]; + // 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]; } else { // drop at the top root level if (index == -1) // drop area might be ambiguous (not at a particular location) + { indexPath = [NSIndexPath indexPathWithIndex:self.presets.root.children.count]; // drop at the end of the top level + } else + { indexPath = [NSIndexPath indexPathWithIndex:index]; // drop at a particular place at the top level + } } - NSPasteboard *pboard = [info draggingPasteboard]; // get the pasteboard + NSPasteboard *pboard = info.draggingPasteboard; - // check the dragging type - - if ([pboard availableTypeFromArray:@[kHandBrakePresetPBoardType]]) + // check the dragging type + if ([pboard availableTypeFromArray:@[kHandBrakeInternalPBoardType]]) { // user is doing an intra-app drag within the outline view [self handleInternalDrops:pboard withIndexPath:indexPath]; result = YES; } + else if ([pboard availableTypeFromArray:@[(NSString *)kUTTypeFileURL]]) + { + [self handleExternalDrops:pboard withIndexPath:indexPath]; + result = YES; + } return result; } diff --git a/macosx/HandBrake.xcodeproj/project.pbxproj b/macosx/HandBrake.xcodeproj/project.pbxproj index 637460b40..e12062dd1 100644 --- a/macosx/HandBrake.xcodeproj/project.pbxproj +++ b/macosx/HandBrake.xcodeproj/project.pbxproj @@ -277,6 +277,7 @@ A9B6B9F1217B408E00B957AE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = A9B6B9EF217B408E00B957AE /* InfoPlist.strings */; }; A9B6B9F4217B408E00B957AE /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = A9B6B9F2217B408E00B957AE /* Localizable.strings */; }; A9BC24C91A69293E007DC41A /* HBAttributedStringAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = A9BC24C81A69293E007DC41A /* HBAttributedStringAdditions.m */; }; + A9C3A50123C7A66100269CA3 /* HBFilePromiseProvider.m in Sources */ = {isa = PBXBuildFile; fileRef = A9C3A50023C7A66100269CA3 /* HBFilePromiseProvider.m */; }; A9C61F9E22E31CDB00C28E7C /* HBFlippedClipView.m in Sources */ = {isa = PBXBuildFile; fileRef = A9C61F9D22E31CDB00C28E7C /* HBFlippedClipView.m */; }; A9CE0A921F57EC3400724577 /* HBImageUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = A9CE0A911F57EC3400724577 /* HBImageUtilities.m */; }; A9CF25F71990D6820023F727 /* HBPresetsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = A9CF25F61990D6820023F727 /* HBPresetsViewController.m */; }; @@ -732,6 +733,8 @@ A9C183941A716B8F00C897C2 /* HBTitleSelectionController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = HBTitleSelectionController.m; sourceTree = "<group>"; }; A9C313B222F98F0D0001C438 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/HBQueueInfoViewController.strings; sourceTree = "<group>"; }; A9C313B322F98F0D0001C438 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/HBQueueTableViewController.strings; sourceTree = "<group>"; }; + A9C3A4FF23C7A66100269CA3 /* HBFilePromiseProvider.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBFilePromiseProvider.h; sourceTree = "<group>"; }; + A9C3A50023C7A66100269CA3 /* HBFilePromiseProvider.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HBFilePromiseProvider.m; sourceTree = "<group>"; }; A9C61F9C22E31CDB00C28E7C /* HBFlippedClipView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HBFlippedClipView.h; sourceTree = "<group>"; }; A9C61F9D22E31CDB00C28E7C /* HBFlippedClipView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HBFlippedClipView.m; sourceTree = "<group>"; }; A9CAC26E1CCB6B0F00A39E72 /* HBPlayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HBPlayer.h; sourceTree = "<group>"; }; @@ -1280,6 +1283,8 @@ A9706CB31AC1436F00BAEAA8 /* HBApplication.m */, A964D3A622FDE91A00DFCAEA /* HBRemoteCore.h */, A964D3A722FDE91B00DFCAEA /* HBRemoteCore.m */, + A9C3A4FF23C7A66100269CA3 /* HBFilePromiseProvider.h */, + A9C3A50023C7A66100269CA3 /* HBFilePromiseProvider.m */, A95BA15F220CA5DB00A2F9F9 /* HBDistributedArray.h */, A95BA160220CA5DB00A2F9F9 /* HBDistributedArray.m */, A9706CB51AC1437800BAEAA8 /* HBExceptionAlertController.h */, @@ -1949,6 +1954,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + A9C3A50123C7A66100269CA3 /* HBFilePromiseProvider.m in Sources */, A954010322FF397000F83A5F /* HBRedirect.m in Sources */, A95BA161220CA5DB00A2F9F9 /* HBDistributedArray.m in Sources */, A916C99B1C844A0800C7B560 /* HBTableView.m in Sources */, diff --git a/macosx/NSArray+HBAdditions.h b/macosx/NSArray+HBAdditions.h index ee7a6bdc2..414dcf1dd 100644 --- a/macosx/NSArray+HBAdditions.h +++ b/macosx/NSArray+HBAdditions.h @@ -10,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN @interface NSArray (HBAdditions) +- (BOOL)allSatisfy:(BOOL (^)(id object))block; - (NSArray *)filteredArrayUsingBlock:(BOOL (^)(id object))block; - (NSIndexSet *)indexesOfObjectsUsingBlock:(BOOL (^)(id object))block; diff --git a/macosx/NSArray+HBAdditions.m b/macosx/NSArray+HBAdditions.m index cad90ba70..540c571b9 100644 --- a/macosx/NSArray+HBAdditions.m +++ b/macosx/NSArray+HBAdditions.m @@ -8,6 +8,18 @@ @implementation NSArray (HBAdditions) +- (BOOL)allSatisfy:(BOOL (^)(id object))block +{ + for (id object in self) + { + if (block(object) == NO) + { + return NO; + } + } + return YES; +} + - (NSArray *)filteredArrayUsingBlock:(BOOL (^)(id object))block { NSMutableArray *filteredArray = [NSMutableArray array]; |