Initial commit
This commit is contained in:
@@ -0,0 +1,209 @@
|
||||
// Destinations.swift
|
||||
// GreenshotMac
|
||||
//
|
||||
// Port of Greenshot's IDestination implementations for macOS.
|
||||
|
||||
import AppKit
|
||||
import UniformTypeIdentifiers
|
||||
|
||||
@MainActor
|
||||
enum Destinations {
|
||||
|
||||
/// Sends a capture to a destination. `anchor` is used to position pickers and share sheets.
|
||||
@discardableResult
|
||||
static func export(_ capture: Capture,
|
||||
to destination: DestinationID,
|
||||
anchor: NSView? = nil) -> Bool {
|
||||
switch destination {
|
||||
case .picker:
|
||||
DestinationPickerWindowController.present(for: capture)
|
||||
return true
|
||||
case .editor:
|
||||
EditorWindowController.open(with: capture)
|
||||
return true
|
||||
case .clipboard:
|
||||
copyToClipboard(capture)
|
||||
Notifier.show(title: "Copied to clipboard", body: nil)
|
||||
return true
|
||||
case .file:
|
||||
return saveToFile(capture)
|
||||
case .fileWithDialog:
|
||||
return saveWithDialog(capture)
|
||||
case .printer:
|
||||
printCapture(capture)
|
||||
return true
|
||||
case .share:
|
||||
share(capture, anchor: anchor)
|
||||
return true
|
||||
case .email:
|
||||
sendAsEmail(capture, anchor: anchor)
|
||||
return true
|
||||
case .openWith:
|
||||
openWith(capture)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Clipboard
|
||||
|
||||
static func copyToClipboard(_ capture: Capture) {
|
||||
let pasteboard = NSPasteboard.general
|
||||
pasteboard.clearContents()
|
||||
let image = capture.nsImage
|
||||
pasteboard.writeObjects([image])
|
||||
if let data = ImageExporter.data(from: capture.image, format: .png, jpegQuality: 100) {
|
||||
pasteboard.setData(data, forType: .png)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Files
|
||||
|
||||
@discardableResult
|
||||
static func saveToFile(_ capture: Capture) -> Bool {
|
||||
let url = ImageExporter.suggestedURL(for: capture.details)
|
||||
do {
|
||||
try ImageExporter.write(capture.image, to: url, format: Configuration.shared.outputFileFormat)
|
||||
capture.details.filePath = url.path
|
||||
if Configuration.shared.copyPathToClipboard {
|
||||
NSPasteboard.general.clearContents()
|
||||
NSPasteboard.general.setString(url.path, forType: .string)
|
||||
}
|
||||
Notifier.show(title: "Screenshot saved", body: url.lastPathComponent, revealing: url)
|
||||
return true
|
||||
} catch {
|
||||
presentError(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
static func saveWithDialog(_ capture: Capture) -> Bool {
|
||||
let panel = NSSavePanel()
|
||||
let configuration = Configuration.shared
|
||||
panel.nameFieldStringValue = FilenamePattern.filename(pattern: configuration.outputFileFilenamePattern,
|
||||
details: capture.details,
|
||||
format: configuration.outputFileFormat)
|
||||
panel.directoryURL = URL(fileURLWithPath: configuration.outputFilePath, isDirectory: true)
|
||||
panel.allowedContentTypes = [.png, .jpeg, .tiff, .bmp, .gif]
|
||||
panel.canCreateDirectories = true
|
||||
panel.isExtensionHidden = false
|
||||
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
guard panel.runModal() == .OK, let url = panel.url else { return false }
|
||||
|
||||
let format = ImageExporter.format(forPathExtension: url.pathExtension)
|
||||
do {
|
||||
try ImageExporter.write(capture.image, to: url, format: format)
|
||||
capture.details.filePath = url.path
|
||||
return true
|
||||
} catch {
|
||||
presentError(error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Print
|
||||
|
||||
static func printCapture(_ capture: Capture) {
|
||||
let image = capture.nsImage
|
||||
let info = NSPrintInfo.shared
|
||||
info.horizontalPagination = .fit
|
||||
info.verticalPagination = .fit
|
||||
info.isHorizontallyCentered = true
|
||||
info.isVerticallyCentered = true
|
||||
info.orientation = image.size.width > image.size.height ? .landscape : .portrait
|
||||
|
||||
let imageView = NSImageView(frame: CGRect(origin: .zero, size: image.size))
|
||||
imageView.image = image
|
||||
imageView.imageScaling = .scaleProportionallyUpOrDown
|
||||
|
||||
let operation = NSPrintOperation(view: imageView, printInfo: info)
|
||||
operation.showsPrintPanel = true
|
||||
operation.showsProgressPanel = true
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
operation.run()
|
||||
}
|
||||
|
||||
// MARK: - Share
|
||||
|
||||
static func share(_ capture: Capture, anchor: NSView?) {
|
||||
guard let url = writeTemporaryFile(capture) else { return }
|
||||
let picker = NSSharingServicePicker(items: [url])
|
||||
if let anchor {
|
||||
picker.show(relativeTo: anchor.bounds, of: anchor, preferredEdge: .minY)
|
||||
} else if let window = anchorWindow() {
|
||||
let view = window.contentView ?? NSView()
|
||||
picker.show(relativeTo: view.bounds, of: view, preferredEdge: .minY)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Open with
|
||||
|
||||
static func openWith(_ capture: Capture) {
|
||||
guard let url = writeTemporaryFile(capture) else { return }
|
||||
let panel = NSOpenPanel()
|
||||
panel.message = "Choose an application to open the screenshot with"
|
||||
panel.directoryURL = URL(fileURLWithPath: "/Applications")
|
||||
panel.allowedContentTypes = [.application]
|
||||
panel.allowsMultipleSelection = false
|
||||
panel.canChooseDirectories = false
|
||||
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
guard panel.runModal() == .OK, let application = panel.url else { return }
|
||||
NSWorkspace.shared.open([url],
|
||||
withApplicationAt: application,
|
||||
configuration: NSWorkspace.OpenConfiguration()) { _, error in
|
||||
if let error {
|
||||
DispatchQueue.main.async { presentError(error) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Email
|
||||
|
||||
static func sendAsEmail(_ capture: Capture, anchor: NSView?) {
|
||||
guard let url = writeTemporaryFile(capture) else { return }
|
||||
let subject = capture.details.title.isEmpty ? "Screenshot" : "Screenshot: \(capture.details.title)"
|
||||
|
||||
if let service = NSSharingService(named: .composeEmail) {
|
||||
service.subject = subject
|
||||
service.perform(withItems: [url])
|
||||
} else {
|
||||
// No mail client is configured for the share service; fall back to the share sheet.
|
||||
share(capture, anchor: anchor)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
static func writeTemporaryFile(_ capture: Capture) -> URL? {
|
||||
let configuration = Configuration.shared
|
||||
let name = FilenamePattern.filename(pattern: configuration.outputFileFilenamePattern,
|
||||
details: capture.details,
|
||||
format: configuration.outputFileFormat)
|
||||
let directory = FileManager.default.temporaryDirectory
|
||||
.appendingPathComponent("GreenshotMac", isDirectory: true)
|
||||
let url = ImageExporter.uniqueURL(directory.appendingPathComponent(name))
|
||||
do {
|
||||
try ImageExporter.write(capture.image, to: url, format: configuration.outputFileFormat)
|
||||
return url
|
||||
} catch {
|
||||
presentError(error)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private static func anchorWindow() -> NSWindow? {
|
||||
NSApp.keyWindow ?? NSApp.windows.first { $0.isVisible }
|
||||
}
|
||||
|
||||
static func presentError(_ error: Error) {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "GreenshotMac"
|
||||
alert.informativeText = error.localizedDescription
|
||||
alert.alertStyle = .warning
|
||||
alert.addButton(withTitle: "OK")
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
alert.runModal()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user