A Drag of different kinds showed an empty wheel, because the catalog only answers for a single category. Filee's answer is a mixed profile, so add Zip (one archive, flat names) and Merge (non-PDFs are converted to PDF first); a multi-file drag of one kind gets Zip too.
112 lines
4.2 KiB
Swift
112 lines
4.2 KiB
Swift
import AppKit
|
|
import Foundation
|
|
|
|
/// Native menu fallback for the paths where there is no drag to follow: the
|
|
/// menu bar's "Konvert Files…" item and the Finder Quick Action.
|
|
enum QuickMenu {
|
|
private final class Choice: NSObject {
|
|
let handler: () -> Void
|
|
|
|
init(_ handler: @escaping () -> Void) {
|
|
self.handler = handler
|
|
}
|
|
|
|
@objc func fire() {
|
|
handler()
|
|
}
|
|
}
|
|
|
|
/// The menu items hold their target weakly, so the choices are retained
|
|
/// here for the duration of the (synchronous) popup.
|
|
private static var retained: [Choice] = []
|
|
|
|
static func show(urls: [URL], at point: CGPoint) {
|
|
guard !urls.isEmpty else { return }
|
|
retained.removeAll()
|
|
|
|
let menu = NSMenu()
|
|
menu.autoenablesItems = false
|
|
|
|
let header = NSMenuItem(
|
|
title: WheelBuilder.summary(for: urls),
|
|
action: nil,
|
|
keyEquivalent: ""
|
|
)
|
|
header.isEnabled = false
|
|
menu.addItem(header)
|
|
menu.addItem(.separator())
|
|
|
|
let category = FileKind.category(of: urls[0])
|
|
|
|
// The convert wheel also carries the combine tools, so it is empty only
|
|
// when there is genuinely nothing to offer.
|
|
let conversions = WheelBuilder.items(kind: .convert, urls: urls)
|
|
if conversions.isEmpty {
|
|
let item = NSMenuItem(title: "No conversions for this selection", action: nil, keyEquivalent: "")
|
|
item.isEnabled = false
|
|
menu.addItem(item)
|
|
} else {
|
|
let convert = NSMenu()
|
|
for item in conversions {
|
|
guard case .format(let target) = item.payload else { continue }
|
|
let entry = NSMenuItem(title: item.title, action: #selector(Choice.fire), keyEquivalent: "")
|
|
entry.target = makeChoice {
|
|
JobStore.shared.submit(urls: urls, target: .format(target))
|
|
}
|
|
entry.isEnabled = item.isEnabled
|
|
if let reason = item.availability.reason { entry.toolTip = reason }
|
|
convert.addItem(entry)
|
|
}
|
|
if !convert.items.isEmpty {
|
|
let submenu = NSMenuItem(title: "Convert", action: nil, keyEquivalent: "")
|
|
submenu.submenu = convert
|
|
menu.addItem(submenu)
|
|
}
|
|
}
|
|
|
|
let tools = WheelBuilder.items(kind: .tools, urls: urls)
|
|
if category != .unknown, !tools.isEmpty {
|
|
let submenu = NSMenu()
|
|
for item in tools {
|
|
guard case .tool(let tool) = item.payload else { continue }
|
|
let entry = NSMenuItem(title: tool.title, action: #selector(Choice.fire), keyEquivalent: "")
|
|
entry.target = makeChoice {
|
|
if tool.fields.isEmpty {
|
|
JobStore.shared.submit(urls: urls, target: .tool(tool))
|
|
} else {
|
|
OptionsWindowController.shared.present(tool: tool, urls: urls, origin: point)
|
|
}
|
|
}
|
|
entry.isEnabled = item.isEnabled
|
|
if let reason = item.availability.reason { entry.toolTip = reason }
|
|
submenu.addItem(entry)
|
|
}
|
|
let wrapper = NSMenuItem(title: "Tools", action: nil, keyEquivalent: "")
|
|
wrapper.submenu = submenu
|
|
menu.addItem(wrapper)
|
|
}
|
|
|
|
let actions = NSMenu()
|
|
for item in WheelBuilder.items(kind: .actions, urls: urls) {
|
|
guard case .action(let action) = item.payload else { continue }
|
|
let entry = NSMenuItem(title: action.title, action: #selector(Choice.fire), keyEquivalent: "")
|
|
entry.target = makeChoice {
|
|
JobStore.shared.submit(urls: urls, target: .action(action))
|
|
}
|
|
actions.addItem(entry)
|
|
}
|
|
let actionsWrapper = NSMenuItem(title: "Actions", action: nil, keyEquivalent: "")
|
|
actionsWrapper.submenu = actions
|
|
menu.addItem(actionsWrapper)
|
|
|
|
menu.popUp(positioning: nil, at: point, in: nil)
|
|
retained.removeAll()
|
|
}
|
|
|
|
private static func makeChoice(_ handler: @escaping () -> Void) -> Choice {
|
|
let choice = Choice(handler)
|
|
retained.append(choice)
|
|
return choice
|
|
}
|
|
}
|