diff --git a/Base.lproj/GeneralPrefsViewController.xib b/Base.lproj/GeneralPrefsViewController.xib index 5c56fd4..13e196e 100755 --- a/Base.lproj/GeneralPrefsViewController.xib +++ b/Base.lproj/GeneralPrefsViewController.xib @@ -8,6 +8,7 @@ + @@ -25,11 +26,11 @@ - + - + @@ -220,6 +221,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -237,7 +279,13 @@ - + + + + + + + diff --git a/Classes/GeneralPrefsViewController.swift b/Classes/GeneralPrefsViewController.swift index 8ddd8d0..219fb6a 100644 --- a/Classes/GeneralPrefsViewController.swift +++ b/Classes/GeneralPrefsViewController.swift @@ -23,6 +23,79 @@ class GeneralPrefsViewController: NSViewController { return "GeneralPrefsViewController" } + // MARK: Save As location popUp + + @IBOutlet var saveLocationPopUp: NSPopUpButton! + + private let customLocationTag = 2 + + override func viewDidLoad() { + super.viewDidLoad() + prepareSaveLocationPopUp() + } + + private func menuItem(url: URL) -> NSMenuItem { + let icon = NSWorkspace.shared.icon(forFile: url.path) + icon.size = NSSize(width: 16, height: 16) + let item = NSMenuItem(title: url.lastPathComponent, action: #selector(setSaveLocation(_:)), keyEquivalent: "") + item.image = icon + item.target = self + item.representedObject = url + item.toolTip = url.path + item.tag = customLocationTag + return item + } + + /// Rebuilds the custom folder item, then selects the current location. + private func prepareSaveLocationPopUp() { + guard let menu = saveLocationPopUp.menu else { return } + + if let item = menu.items.first(where: { $0.tag == customLocationTag }) { + menu.removeItem(item) + } + + if let url = Prefs.saveAsCustomLocation { + menu.insertItem(menuItem(url: url), at: 2) + } + + selectCurrentSaveLocation() + } + + private func selectCurrentSaveLocation() { + let location = Prefs.saveAsLocation + + if location == .custom && Prefs.saveAsCustomLocation == nil { + saveLocationPopUp.selectItem(withTag: SaveAsLocation.lastUsed.rawValue) + } else { + saveLocationPopUp.selectItem(withTag: location.rawValue) + } + } + + @IBAction func setSaveLocation(_ sender: NSMenuItem) { + if let url = sender.representedObject as? URL { + Prefs.saveAsCustomLocation = url + Prefs.saveAsLocation = .custom + } else { + Prefs.saveAsLocation = SaveAsLocation(rawValue: sender.tag) ?? .lastUsed + } + } + + @IBAction func chooseSaveLocation(_ sender: Any) { + let panel = NSOpenPanel() + panel.allowsMultipleSelection = false + panel.canChooseFiles = false + panel.canChooseDirectories = true + panel.canCreateDirectories = true + + panel.begin { response in + if response == .OK, let url = panel.url { + Prefs.saveAsCustomLocation = url + Prefs.saveAsLocation = .custom + } + self.prepareSaveLocationPopUp() + } + } + @IBAction func clearRecentSearches(_ sender: Any) { MetadataSearchController.clearRecentSearches() } diff --git a/Classes/MP42FileAdditions.swift b/Classes/MP42FileAdditions.swift index 1e25644..5e63a45 100644 --- a/Classes/MP42FileAdditions.swift +++ b/Classes/MP42FileAdditions.swift @@ -91,6 +91,10 @@ extension MP42File { } } + func preferredFileDirectory() -> URL? { + return tracks.compactMap { $0.url }.first?.deletingLastPathComponent() + } + func formattedFileName() -> String? { guard let mediaKindValue = metadata.metadataItemsFiltered(byIdentifier: MP42MetadataKeyMediaKind).first?.numberValue?.intValue, let mediaKind = MediaKind(rawValue: mediaKindValue) diff --git a/Classes/Prefs.swift b/Classes/Prefs.swift index 290ad15..aaeba5d 100644 --- a/Classes/Prefs.swift +++ b/Classes/Prefs.swift @@ -6,6 +6,7 @@ // import Foundation +import MP42Foundation private let ud = UserDefaults.standard @@ -70,6 +71,16 @@ struct StoredCodable : Registable { } } +/// The folder the Save As… panel opens at. +enum SaveAsLocation: Int { + /// The last folder used, the standard AppKit behaviour. + case lastUsed = 0 + /// The folder holding the file the tracks were imported from. + case sameAsFile = 1 + /// A folder picked by the user. + case custom = 2 +} + enum Prefs { static func register() { @@ -78,7 +89,7 @@ enum Prefs { _audioBitrate, _audioDRC, _audioConvertAC3, _audioKeepAC3, _audioConvertDts, _audioDtsOptions, _subtitleConvertBitmap, _ratingsCountry, _chaptersPreviewPosition, _chaptersPreviewTrack, _mp464bitOffset, _mp464bitTimes, _mp4SaveAsOptimize, _forceHvc1, - _logFormat]) + _logFormat, _saveAsLocationValue]) } @Stored(key: "NSApplicationCrashOnException", defaultValue: true) @@ -161,6 +172,40 @@ enum Prefs { @Stored(key: "SBLogFormat", defaultValue: 0) static var logFormat: Int // 0 = Time Only, 1 = Date and Time + + @Stored(key: "SBSaveAsLocationMode", defaultValue: 0) + private static var saveAsLocationValue: Int + + static var saveAsLocation: SaveAsLocation { + get { SaveAsLocation(rawValue: saveAsLocationValue) ?? .lastUsed } + set { saveAsLocationValue = newValue.rawValue } + } + + private static let saveAsCustomLocationKey = "SBSaveAsCustomLocation" + + /// The folder used when saveAsLocation is set to custom, stored as a + /// security scoped bookmark so it stays reachable after a relaunch. + static var saveAsCustomLocation: URL? { + get { + guard let bookmark = ud.data(forKey: saveAsCustomLocationKey) else { return nil } + + var stale = ObjCBool(false) + guard let url = try? MP42SecurityAccessToken.url(fromBookmark: bookmark, bookmarkDataIsStale: &stale) else { return nil } + + if stale.boolValue, let refreshed = try? MP42SecurityAccessToken.bookmark(from: url) { + ud.set(refreshed, forKey: saveAsCustomLocationKey) + } + + return url + } + set { + if let url = newValue, let bookmark = try? MP42SecurityAccessToken.bookmark(from: url) { + ud.set(bookmark, forKey: saveAsCustomLocationKey) + } else { + ud.removeObject(forKey: saveAsCustomLocationKey) + } + } + } } enum MetadataPrefs { diff --git a/Classes/SaveOptions.swift b/Classes/SaveOptions.swift index bd6a074..9793247 100644 --- a/Classes/SaveOptions.swift +++ b/Classes/SaveOptions.swift @@ -50,6 +50,19 @@ final class SaveOptions: NSViewController { } } + /// The folder the save panel should open at, or nil to leave it + /// at the last folder used. + private func preferredDirectory(for doc: Document) -> URL? { + switch Prefs.saveAsLocation { + case .lastUsed: + return nil + case .sameAsFile: + return doc.fileURL?.deletingLastPathComponent() ?? doc.mp4.preferredFileDirectory() + case .custom: + return Prefs.saveAsCustomLocation + } + } + override func viewDidLoad() { super.viewDidLoad() @@ -65,6 +78,10 @@ final class SaveOptions: NSViewController { savePanel?.nameFieldStringValue = filename } + if let directory = preferredDirectory(for: doc) { + savePanel?.directoryURL = directory + } + setFileType(filenameExtension: fileType) _64bit_data.state = Prefs.mp464bitOffset ? .on : .off