Generated with [Devin](https://devin.ai) Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
163 lines
6.2 KiB
Swift
163 lines
6.2 KiB
Swift
import AppKit
|
|
import UniformTypeIdentifiers
|
|
|
|
/// Dedicated NSOpenPanel / NSSavePanel wrappers (issue #6) — one method
|
|
/// per purpose, matching the v1 `select_*` commands (docs/21 §Dialogs).
|
|
/// No call site shares a generic picker (#103/#210/#211).
|
|
@MainActor
|
|
final class FileDialogService {
|
|
|
|
static let shared = FileDialogService()
|
|
private init() {}
|
|
|
|
// MARK: - selectDirectory
|
|
|
|
/// `#btnBrowse` — working directory for Argyll artefacts.
|
|
/// Defaults to Documents (docs/06 §Empty cwd).
|
|
func selectDirectory(startingAt start: URL? = nil) -> URL? {
|
|
let panel = NSOpenPanel()
|
|
panel.canChooseDirectories = true
|
|
panel.canChooseFiles = false
|
|
panel.allowsMultipleSelection = false
|
|
panel.directoryURL = start
|
|
?? FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first
|
|
panel.prompt = "Choose"
|
|
return run(panel)
|
|
}
|
|
|
|
// MARK: - Dedicated open pickers
|
|
|
|
/// `selectTargetFile` — **save** panel for the new `.ti1` target.
|
|
func selectTargetFile(startingAt start: URL? = nil) -> URL? {
|
|
save(named: "target.ti1", extensions: ["ti1"], startingAt: start,
|
|
message: "Choose the .ti1 target file to create")
|
|
}
|
|
|
|
/// `selectExistingTarget` — open `.ti1`/`.ti2` (docs/06 §Resume, #140).
|
|
func selectExistingTarget(startingAt start: URL? = nil) -> URL? {
|
|
open(extensions: ["ti1", "ti2"], startingAt: start,
|
|
message: "Open an existing target (.ti1 or .ti2)")
|
|
}
|
|
|
|
/// `selectProfileFile` — `.icc`/`.icm`/`.mpp` only — **never** `.ti*`
|
|
/// (#172: the profile filter must not accept datasets).
|
|
func selectProfileFile(startingAt start: URL? = nil) -> URL? {
|
|
open(extensions: ["icc", "icm", "mpp"], startingAt: start,
|
|
message: "Choose an ICC/ICM profile or measurement preconditioning file")
|
|
}
|
|
|
|
/// `selectSpectrumFile` — `.sp` illuminant spectrum (colprof -i).
|
|
func selectSpectrumFile(startingAt start: URL? = nil) -> URL? {
|
|
open(extensions: ["sp"], startingAt: start,
|
|
message: "Choose a custom illuminant spectrum (.sp)")
|
|
}
|
|
|
|
/// `selectDatasetFile` — open a measured dataset (`.ti3`, `.txt`,
|
|
/// `.cgats`, `.csv`). Always an *open* dialog, never save (#211).
|
|
func selectDatasetFile(startingAt start: URL? = nil) -> URL? {
|
|
open(extensions: ["ti3", "txt", "cgats", "csv"], startingAt: start,
|
|
message: "Import a measured dataset")
|
|
}
|
|
|
|
/// `selectCsvSavePath` — verification-history CSV export.
|
|
func selectCsvSavePath(startingAt start: URL? = nil) -> URL? {
|
|
save(named: "verification-history.csv", extensions: ["csv"], startingAt: start)
|
|
}
|
|
|
|
/// `selectCalFile` — `.cal` calibration curves.
|
|
func selectCalFile(startingAt start: URL? = nil) -> URL? {
|
|
open(extensions: ["cal"], startingAt: start,
|
|
message: "Choose a calibration file (.cal)")
|
|
}
|
|
|
|
/// `selectGamutFile` — `.gam` surface mesh for the compare slot (#147).
|
|
func selectGamutFile(startingAt start: URL? = nil) -> URL? {
|
|
open(extensions: ["gam"], startingAt: start,
|
|
message: "Choose a .gam surface mesh",
|
|
allowsOtherFileTypes: false)
|
|
}
|
|
|
|
/// `selectTiffFile` — `.tif`/`.tiff` target page for gamut sampling (#147).
|
|
func selectTiffFile(startingAt start: URL? = nil) -> URL? {
|
|
open(extensions: ["tif", "tiff"], startingAt: start,
|
|
message: "Choose a target TIFF page")
|
|
}
|
|
|
|
/// `btnImportPreset` — open a `.json` preset file.
|
|
func selectPresetFile(startingAt start: URL? = nil) -> URL? {
|
|
open(extensions: ["json"], startingAt: start,
|
|
message: "Import a profiling preset (.json)")
|
|
}
|
|
|
|
/// `btnExportActivePreset` — save a `.json` preset file.
|
|
func selectPresetSavePath(name: String, startingAt start: URL? = nil) -> URL? {
|
|
save(named: "\(name).json", extensions: ["json"], startingAt: start,
|
|
message: "Export this preset as JSON")
|
|
}
|
|
|
|
/// `selectProjectFile` — open `.icceryproj` only (issue #149).
|
|
func selectProjectFile(startingAt start: URL? = nil) -> URL? {
|
|
open(extensions: ["icceryproj"], startingAt: start,
|
|
message: "Open ICCery Project",
|
|
title: "Open ICCery Project",
|
|
allowsOtherFileTypes: false)
|
|
}
|
|
|
|
/// `selectProjectSavePath` — save `.icceryproj`, suggested name
|
|
/// `{basename}.icceryproj` in the working folder (issue #149).
|
|
func selectProjectSavePath(
|
|
basename: String, startingAt start: URL? = nil
|
|
) -> URL? {
|
|
save(named: "\(basename).icceryproj", extensions: ["icceryproj"],
|
|
startingAt: start, message: "Save ICCery Project")
|
|
}
|
|
|
|
// MARK: - Internals (private — not a shared public picker API)
|
|
|
|
private func save(
|
|
named: String,
|
|
extensions: [String],
|
|
startingAt start: URL?,
|
|
message: String? = nil
|
|
) -> URL? {
|
|
let panel = NSSavePanel()
|
|
panel.nameFieldStringValue = named
|
|
panel.allowedContentTypes = utTypes(extensions)
|
|
panel.allowsOtherFileTypes = false
|
|
panel.directoryURL = start
|
|
if let message { panel.message = message }
|
|
return run(panel)
|
|
}
|
|
|
|
private func open(
|
|
extensions: [String],
|
|
startingAt start: URL?,
|
|
message: String?,
|
|
title: String? = nil,
|
|
allowsOtherFileTypes: Bool = true
|
|
) -> URL? {
|
|
let panel = NSOpenPanel()
|
|
panel.canChooseDirectories = false
|
|
panel.canChooseFiles = true
|
|
panel.allowsMultipleSelection = false
|
|
panel.allowedContentTypes = utTypes(extensions)
|
|
panel.allowsOtherFileTypes = allowsOtherFileTypes
|
|
panel.directoryURL = start
|
|
if let message { panel.message = message }
|
|
if let title { panel.title = title }
|
|
return run(panel)
|
|
}
|
|
|
|
private func utTypes(_ extensions: [String]) -> [UTType] {
|
|
extensions.compactMap { UTType(filenameExtension: $0) }
|
|
}
|
|
|
|
private func run(_ panel: NSOpenPanel) -> URL? {
|
|
panel.runModal() == .OK ? panel.url : nil
|
|
}
|
|
|
|
private func run(_ panel: NSSavePanel) -> URL? {
|
|
panel.runModal() == .OK ? panel.url : nil
|
|
}
|
|
}
|