diff --git a/apps/vectorcraft/src/host_services.rs b/apps/vectorcraft/src/host_services.rs index 2568ae03b..2ed5f5530 100644 --- a/apps/vectorcraft/src/host_services.rs +++ b/apps/vectorcraft/src/host_services.rs @@ -300,8 +300,8 @@ pub(crate) fn services(parent: Parent, ui: egui::Context) -> Services { reveal: Some(Box::new(reveal)), // Links panel: Edit Original; Package: Show Package. Relink to Folder and Package pick folders. open_file: Some(Box::new(open_file)), - // Edit Original with Preferences › File Handling › Edit Original With set (#5236). - open_file_with: Some(Box::new(open_file_with)), + // Edit Original of a raster image with Use System Defaults for Edit Original off (#5236). + open_in_photocraft: Some(Box::new(open_in_photocraft)), pick_folder: Some(Box::new(move || pick_now(PickRequest::Folder, &p4).into_iter().next())), // Sample Color from Screen: the system's color sampler. #[cfg(target_os = "macos")] @@ -337,14 +337,14 @@ fn open_file(path: &str) -> Result<(), String> { c.spawn().map(|_| ()).map_err(|e| format!("can't open {path}: {e}")) } -/// Edit Original with Preferences › File Handling › Edit Original With set (#5236): open `path` in -/// `app` (`photocraft`, or an application's path). The app must be an application ([`check_app`]); -/// it gets the path as its only argument and is never started through a shell. -fn open_file_with(app: &str, path: &str) -> Result<(), String> { +/// Edit Original with Preferences › File Handling › Use System Defaults for Edit Original off +/// (#5236): open the raster image `path` in PhotoCraft, found only where it installs +/// ([`find_photocraft`]); `Ok(false)` when it isn't installed. PhotoCraft gets the path as its only +/// argument and is never started through a shell. +fn open_in_photocraft(path: &str) -> Result { explorer_safe(path)?; let home = std::env::var_os(if cfg!(windows) { "USERPROFILE" } else { "HOME" }).map(std::path::PathBuf::from); - let app = resolve_app(app, home.as_deref(), std::env::var_os("PATH").as_deref())?; - check_app(&app)?; + let Some(app) = find_photocraft(home.as_deref(), std::env::var_os("PATH").as_deref()) else { return Ok(false) }; #[cfg(target_os = "macos")] let mut c = { let mut c = std::process::Command::new("open"); @@ -369,7 +369,7 @@ fn open_file_with(app: &str, path: &str) -> Result<(), String> { let started = std::time::Instant::now(); loop { match child.try_wait() { - Ok(Some(status)) if status.success() => return Ok(()), + Ok(Some(status)) if status.success() => return Ok(true), Ok(Some(status)) => { let mut why = String::new(); if let Some(mut err) = child.stderr.take() { @@ -385,26 +385,19 @@ fn open_file_with(app: &str, path: &str) -> Result<(), String> { std::thread::spawn(move || { let _ = child.wait(); }); - return Ok(()); + return Ok(true); } Err(e) => return Err(fail(e.to_string())), } } } #[cfg(not(target_os = "macos"))] - c.spawn().map(|_| ()).map_err(|e| format!("can't open {path} in {}: {e}", app.display())) + c.spawn().map(|_| true).map_err(|e| format!("can't open {path} in {}: {e}", app.display())) } -/// The application Edit Original With names: PhotoCraft where it is installed for `photocraft` -/// ([`photocraft_candidates`]), else the path as given. -pub(crate) fn resolve_app(app: &str, home: Option<&std::path::Path>, path_var: Option<&std::ffi::OsStr>) -> Result { - if app.trim().eq_ignore_ascii_case("photocraft") { - return photocraft_candidates(home, path_var) - .into_iter() - .find(|p| check_app(p).is_ok()) - .ok_or_else(|| "PhotoCraft isn't installed: choose another app in Preferences › File Handling › Edit Original With".to_string()); - } - Ok(std::path::PathBuf::from(app.trim())) +/// PhotoCraft where it is installed: the first of [`photocraft_candidates`] that is an application. +pub(crate) fn find_photocraft(home: Option<&std::path::Path>, path_var: Option<&std::ffi::OsStr>) -> Option { + photocraft_candidates(home, path_var).into_iter().find(|p| check_app(p).is_ok()) } /// Where PhotoCraft may be installed: the Applications folders on macOS, else a `photocraft` @@ -421,7 +414,7 @@ pub(crate) fn photocraft_candidates(home: Option<&std::path::Path>, path_var: Op /// Is `app` an application Edit Original may start: an `.app` bundle on macOS, an `.exe` on /// Windows, an executable file elsewhere? Anything else (a document, a folder, a script on -/// Windows) is refused, so the preference can only ever open an installed app. +/// Windows) found where PhotoCraft installs is not taken for it. pub(crate) fn check_app(app: &std::path::Path) -> Result<(), String> { let ext = app.extension().map(|e| e.to_string_lossy().to_ascii_lowercase()).unwrap_or_default(); let ok = if cfg!(target_os = "macos") { @@ -447,7 +440,7 @@ fn executable_file(p: &std::path::Path) -> bool { #[cfg(test)] mod open_with_tests { - use super::{check_app, photocraft_candidates, resolve_app}; + use super::{check_app, find_photocraft, photocraft_candidates}; /// A fresh, empty folder under the system's temporary folder. fn scratch(name: &str) -> std::path::PathBuf { @@ -457,8 +450,8 @@ mod open_with_tests { d } - /// #5236: Edit Original With only ever starts an application, never a document, a folder or a - /// missing path. + /// #5236: only an application counts as PhotoCraft, never a document, a folder or a missing + /// path. #[test] fn only_applications_are_started() { let d = scratch("check"); @@ -484,22 +477,21 @@ mod open_with_tests { let _ = std::fs::remove_dir_all(&d); } - /// `photocraft` is looked for where PhotoCraft installs; any other value is taken as a path. + /// PhotoCraft is looked for only where it installs. #[test] fn photocraft_is_found_where_it_installs() { let d = scratch("find"); - assert_eq!(resolve_app("/Apps/Other.app", None, None).unwrap(), std::path::PathBuf::from("/Apps/Other.app")); if cfg!(target_os = "macos") { let candidates = photocraft_candidates(Some(&d), None); assert_eq!(candidates.first().map(|p| p.to_string_lossy().into_owned()).as_deref(), Some("/Applications/PhotoCraft.app")); assert!(candidates.contains(&d.join("Applications/PhotoCraft.app")), "{candidates:?}"); std::fs::create_dir_all(d.join("Applications/PhotoCraft.app")).unwrap(); - assert!(resolve_app("PhotoCraft", Some(&d), None).unwrap().ends_with("PhotoCraft.app")); + assert!(find_photocraft(Some(&d), None).unwrap().ends_with("PhotoCraft.app")); } else { let bin = d.join("bin"); std::fs::create_dir_all(&bin).unwrap(); let path_var = std::env::join_paths([&bin]).unwrap(); - assert!(resolve_app("photocraft", None, Some(&path_var)).is_err(), "not installed"); + assert!(find_photocraft(None, Some(&path_var)).is_none(), "not installed"); let exe = bin.join(if cfg!(windows) { "photocraft.exe" } else { "photocraft" }); std::fs::write(&exe, "").unwrap(); #[cfg(unix)] @@ -507,7 +499,7 @@ mod open_with_tests { use std::os::unix::fs::PermissionsExt as _; std::fs::set_permissions(&exe, std::fs::Permissions::from_mode(0o755)).unwrap(); } - assert_eq!(resolve_app("photocraft", None, Some(&path_var)).unwrap(), exe); + assert_eq!(find_photocraft(None, Some(&path_var)), Some(exe)); } let _ = std::fs::remove_dir_all(&d); } diff --git a/crates/engine/src/cmd/prefscmds.rs b/crates/engine/src/cmd/prefscmds.rs index d1aff0ca5..ad24afacb 100644 --- a/crates/engine/src/cmd/prefscmds.rs +++ b/crates/engine/src/cmd/prefscmds.rs @@ -372,8 +372,8 @@ pub const PREF_SPECS: &[PrefSpec] = &[ p!("newArtBasic", "General", "Appearance Panel", "New Art Has Basic Appearance", bool), // File Handling (continued) p!("templatesFolder", "File Handling", "Files", "Templates Folder", text), - // "" (the system's default app), `photocraft` or an application's path (#5236). - p!("editOriginalApp", "File Handling", "Files", "Edit Original With", text), + // Off: raster images open in PhotoCraft where it is installed (#5236). + p!("editOriginalSystemDefaults", "File Handling", "Files", "Use System Defaults for Edit Original", bool), p!("appendConverted", "File Handling", "Files", "Mark Older Files as [Converted] When Opened", bool), // Native saves (`document.save {compress}`) p!("useCompression", "File Handling", "Files", "Use Compression", bool), diff --git a/crates/engine/src/prefs.rs b/crates/engine/src/prefs.rs index 46c3d111f..5deb00ddd 100644 --- a/crates/engine/src/prefs.rs +++ b/crates/engine/src/prefs.rs @@ -253,10 +253,10 @@ pub struct Prefs { // File Handling (continued) /// Where Save as Template and New from Template start ("" = `Documents/VectorCraft Templates`). pub templates_folder: String, - /// The app Links panel › Edit Original opens a linked file in (#5236): "" = the system's - /// default app for the file, `photocraft` = PhotoCraft wherever it is installed, else the path - /// of an application. - pub edit_original_app: String, + /// File Handling → Use System Defaults for Edit Original (#5236): Links panel › Edit Original + /// opens a linked file in the system's default app for it; off, in the app that made it + /// (PhotoCraft for a raster image, where it is installed; the system's app otherwise). + pub edit_original_system_defaults: bool, /// Open older native files as " [Converted]" so Save asks for a new name. pub append_converted: bool, /// File Handling → Use Compression: native saves are gzip-compressed (`document.save @@ -468,7 +468,7 @@ impl Default for Prefs { pdf_presets: vec![], screen_export_presets: vec![], templates_folder: String::new(), - edit_original_app: String::new(), + edit_original_system_defaults: true, append_converted: true, use_compression: false, web_export_presets: vec![], diff --git a/crates/ui-egui/src/i18n/bg.tsv b/crates/ui-egui/src/i18n/bg.tsv index 614054f41..d64cd5117 100644 --- a/crates/ui-egui/src/i18n/bg.tsv +++ b/crates/ui-egui/src/i18n/bg.tsv @@ -1799,6 +1799,7 @@ font-technology Variable Променливи Manually Ръчно Mark as trapped Маркиране като прехванато Mark Older Files as [Converted] When Opened Маркиране на по-стари файлове като [Конвертирани] при отваряне + Use System Defaults for Edit Original Системни настройки по подразбиране за „Редактиране на оригинал“ Marker… Маркер… Marks Маркери Marks and Bleed Маркери и изтичане diff --git a/crates/ui-egui/src/i18n/bs.tsv b/crates/ui-egui/src/i18n/bs.tsv index a0b4e236d..7e29b1908 100644 --- a/crates/ui-egui/src/i18n/bs.tsv +++ b/crates/ui-egui/src/i18n/bs.tsv @@ -3560,6 +3560,7 @@ theme Appearance Izgled List Preferences Izlistaj postavke Manually Ručno Mark Older Files as [Converted] When Opened Označi starije datoteke kao [Pretvoreno] pri otvaranju + Use System Defaults for Edit Original Koristi sistemske zadane postavke za Uredi original Match User Interface Brightness Uskladi sa svjetlinom korisničkog interfejsa Light Gray Svijetlosiva Medium Gray Srednje siva diff --git a/crates/ui-egui/src/i18n/ca.tsv b/crates/ui-egui/src/i18n/ca.tsv index 2a1f0a518..2c9ea1267 100644 --- a/crates/ui-egui/src/i18n/ca.tsv +++ b/crates/ui-egui/src/i18n/ca.tsv @@ -2330,6 +2330,7 @@ digits Malayalam Malaiàlam Manually Manualment Mark as trapped Marca com a reventat Mark Older Files as [Converted] When Opened Marca els fitxers antics com a [Convertit] en obrir-los + Use System Defaults for Edit Original Utilitza els valors per defecte del sistema per a Edita l’original Marker… Marcador… Marks Marques Marks and Bleed Marques i sangrat diff --git a/crates/ui-egui/src/i18n/cs.tsv b/crates/ui-egui/src/i18n/cs.tsv index b9e6fd740..dede785a7 100644 --- a/crates/ui-egui/src/i18n/cs.tsv +++ b/crates/ui-egui/src/i18n/cs.tsv @@ -3601,6 +3601,7 @@ theme Appearance Motiv List Preferences Vypsat předvolby Manually Ručně Mark Older Files as [Converted] When Opened Při otevření označit starší soubory jako [Converted] + Use System Defaults for Edit Original Pro Upravit originál použít výchozí aplikace systému Match User Interface Brightness Odpovídat jasu uživatelského rozhraní Light Gray Světle šedá Medium Gray Středně šedá diff --git a/crates/ui-egui/src/i18n/da.tsv b/crates/ui-egui/src/i18n/da.tsv index 20a0a4dfe..6d307f60f 100644 --- a/crates/ui-egui/src/i18n/da.tsv +++ b/crates/ui-egui/src/i18n/da.tsv @@ -3328,6 +3328,7 @@ theme Appearance Udseende List Preferences Oplist indstillinger Manually Manuelt Mark Older Files as [Converted] When Opened Markér ældre filer som [konverteret] ved åbning + Use System Defaults for Edit Original Anvend systemets standarder til Redigér original Match User Interface Brightness Match lysstyrken i brugergrænsefladen Light Gray Lysegrå Medium Gray Mellemgrå diff --git a/crates/ui-egui/src/i18n/de.tsv b/crates/ui-egui/src/i18n/de.tsv index 94430d21e..f1aece539 100644 --- a/crates/ui-egui/src/i18n/de.tsv +++ b/crates/ui-egui/src/i18n/de.tsv @@ -1861,6 +1861,7 @@ file-filter Templates Vorlagen Manually Manuell Mark as trapped Als überfüllt kennzeichnen Mark Older Files as [Converted] When Opened Ältere Dateien beim Öffnen als [Konvertiert] kennzeichnen + Use System Defaults for Edit Original Systemstandards für „Original bearbeiten“ verwenden Marker… Markierung… Marks Marken Marks and Bleed Marken und Anschnitt diff --git a/crates/ui-egui/src/i18n/el.tsv b/crates/ui-egui/src/i18n/el.tsv index 0a7d2be71..56e14d395 100644 --- a/crates/ui-egui/src/i18n/el.tsv +++ b/crates/ui-egui/src/i18n/el.tsv @@ -2651,6 +2651,7 @@ theme Appearance Εμφάνιση Manually Χειροκίνητα Mark as trapped Σήμανση ως επικαλυπτόμενου Mark Older Files as [Converted] When Opened Σήμανση παλαιότερων αρχείων ως [Μετατράπηκε] κατά το άνοιγμα + Use System Defaults for Edit Original Χρήση προεπιλογών συστήματος για την Επεξεργασία πρωτοτύπου Marker… Δείκτης… Marks Σημάδια Marks and Bleed Σημάδια και ξάκρισμα diff --git a/crates/ui-egui/src/i18n/es.tsv b/crates/ui-egui/src/i18n/es.tsv index 4e5ba28d4..84b860be9 100644 --- a/crates/ui-egui/src/i18n/es.tsv +++ b/crates/ui-egui/src/i18n/es.tsv @@ -1864,6 +1864,7 @@ font-technology Variable Variables Manually Manualmente Mark as trapped Marcar como reventado Mark Older Files as [Converted] When Opened Marcar archivos antiguos como [Convertido] al abrirlos + Use System Defaults for Edit Original Usar valores predeterminados del sistema para Editar original Marker… Marcador… Marks Marcas Marks and Bleed Marcas y sangrado diff --git a/crates/ui-egui/src/i18n/fi.tsv b/crates/ui-egui/src/i18n/fi.tsv index c75f15ba5..14d0957e0 100644 --- a/crates/ui-egui/src/i18n/fi.tsv +++ b/crates/ui-egui/src/i18n/fi.tsv @@ -3525,6 +3525,7 @@ theme Appearance Ulkoasu List Preferences Luettele asetukset Manually Manuaalisesti Mark Older Files as [Converted] When Opened Merkitse vanhemmat tiedostot [Muunnettu] avattaessa + Use System Defaults for Edit Original Käytä järjestelmän oletuksia toiminnossa Muokkaa alkuperäistä Match User Interface Brightness Sovita käyttöliittymän kirkkauteen Light Gray Vaaleanharmaa Medium Gray Keskiharmaa diff --git a/crates/ui-egui/src/i18n/fr.tsv b/crates/ui-egui/src/i18n/fr.tsv index 20b608bb4..0e8434dc4 100644 --- a/crates/ui-egui/src/i18n/fr.tsv +++ b/crates/ui-egui/src/i18n/fr.tsv @@ -1858,6 +1858,7 @@ font-technology Variable Variables Manually Manuellement Mark as trapped Marquer comme grossi-maigri Mark Older Files as [Converted] When Opened Marquer les anciens fichiers comme [Converti] à l’ouverture + Use System Defaults for Edit Original Utiliser les applications par défaut du système pour Modifier l’original Marker… Marqueur… Marks Repères Marks and Bleed Repères et fond perdu diff --git a/crates/ui-egui/src/i18n/hu.tsv b/crates/ui-egui/src/i18n/hu.tsv index 12e8b0ec1..e3fee02cb 100644 --- a/crates/ui-egui/src/i18n/hu.tsv +++ b/crates/ui-egui/src/i18n/hu.tsv @@ -2351,6 +2351,7 @@ file-filter Templates Sablonok Manually Kézzel Mark as trapped Megjelölés csapdázottként Mark Older Files as [Converted] When Opened Régebbi fájlok megjelölése [Átalakítva] címkével megnyitáskor + Use System Defaults for Edit Original Rendszer alapértelmezéseinek használata az Eredeti szerkesztéséhez Marker… Jelölő… Marks Jelek Marks and Bleed Jelek és kifutó diff --git a/crates/ui-egui/src/i18n/id.tsv b/crates/ui-egui/src/i18n/id.tsv index 8e4aa69f2..06cf4da3a 100644 --- a/crates/ui-egui/src/i18n/id.tsv +++ b/crates/ui-egui/src/i18n/id.tsv @@ -3328,6 +3328,7 @@ theme Appearance Tampilan List Preferences Daftarkan preferensi Manually Secara manual Mark Older Files as [Converted] When Opened Tandai file lama sebagai [Converted] saat dibuka + Use System Defaults for Edit Original Gunakan default sistem untuk Edit asli Match User Interface Brightness Sesuaikan dengan kecerahan antarmuka pengguna Light Gray Abu-abu terang Medium Gray Abu-abu sedang diff --git a/crates/ui-egui/src/i18n/it.tsv b/crates/ui-egui/src/i18n/it.tsv index 6fd38b0f7..742624e12 100644 --- a/crates/ui-egui/src/i18n/it.tsv +++ b/crates/ui-egui/src/i18n/it.tsv @@ -1893,6 +1893,7 @@ file-filter Templates Modelli Manually Manualmente Mark as trapped Contrassegna come sottoposto a trapping Mark Older Files as [Converted] When Opened Contrassegna i file meno recenti come [Convertito] all'apertura + Use System Defaults for Edit Original Usa le impostazioni predefinite di sistema per Modifica originale Marker… Indicatore… Marks Segni Marks and Bleed Segni e abbondanza diff --git a/crates/ui-egui/src/i18n/ja.tsv b/crates/ui-egui/src/i18n/ja.tsv index 5096fef13..1584618d1 100644 --- a/crates/ui-egui/src/i18n/ja.tsv +++ b/crates/ui-egui/src/i18n/ja.tsv @@ -2094,6 +2094,7 @@ digits Malayalam マラヤーラム数字 Manage Workspaces ワークスペースを管理 Manually 手動 Mark Older Files as [Converted] When Opened 古いファイルを開くときに[変換]と表示 + Use System Defaults for Edit Original 「オリジナルを編集」にシステムのデフォルトを使用 Mark as trapped トラップ済みとしてマーク Marks トンボ Match User Interface Brightness ユーザーインターフェイスの明るさに合わせる diff --git a/crates/ui-egui/src/i18n/ka.tsv b/crates/ui-egui/src/i18n/ka.tsv index 5389e1f09..fabc4a369 100644 --- a/crates/ui-egui/src/i18n/ka.tsv +++ b/crates/ui-egui/src/i18n/ka.tsv @@ -3138,6 +3138,7 @@ theme Appearance იერსახე List Preferences პარამეტრების სია Manually ხელით Mark Older Files as [Converted] When Opened ძველი ფაილების მარკირება როგორც [გარდაქმნილი] გახსნისას + Use System Defaults for Edit Original სისტემის ნაგულისხმევის გამოყენება ორიგინალის რედაქტირებისთვის Match User Interface Brightness ინტერფეისის სიკაშკაშესთან შესაბამისობა Light Gray ღია ნაცრისფერი Medium Gray საშუალო ნაცრისფერი diff --git a/crates/ui-egui/src/i18n/ko.tsv b/crates/ui-egui/src/i18n/ko.tsv index 8596570c7..7376f14d8 100644 --- a/crates/ui-egui/src/i18n/ko.tsv +++ b/crates/ui-egui/src/i18n/ko.tsv @@ -2084,6 +2084,7 @@ digits Malayalam 말라얄람어 Manage Workspaces 작업 영역 관리 Manually 수동 Mark Older Files as [Converted] When Opened 열 때 이전 파일을 [Converted]로 표시 + Use System Defaults for Edit Original 원본 편집에 시스템 기본값 사용 Mark as trapped 트래핑됨으로 표시 Marks 표시 Match User Interface Brightness 사용자 인터페이스 밝기에 맞춤 diff --git a/crates/ui-egui/src/i18n/nl.tsv b/crates/ui-egui/src/i18n/nl.tsv index 4cde5d403..c2670658d 100644 --- a/crates/ui-egui/src/i18n/nl.tsv +++ b/crates/ui-egui/src/i18n/nl.tsv @@ -3342,6 +3342,7 @@ theme Appearance Vormgeving List Preferences Voorkeuren weergeven Manually Handmatig Mark Older Files as [Converted] When Opened Oudere bestanden bij het openen markeren als [geconverteerd] + Use System Defaults for Edit Original Systeemstandaarden gebruiken voor Origineel bewerken Match User Interface Brightness Aanpassen aan helderheid van gebruikersinterface Light Gray Lichtgrijs Medium Gray Middelgrijs diff --git a/crates/ui-egui/src/i18n/pl.tsv b/crates/ui-egui/src/i18n/pl.tsv index 52800ade0..10ebc96bd 100644 --- a/crates/ui-egui/src/i18n/pl.tsv +++ b/crates/ui-egui/src/i18n/pl.tsv @@ -3464,6 +3464,7 @@ theme Appearance Wygląd List Preferences Wyświetl preferencje Manually Ręcznie Mark Older Files as [Converted] When Opened Oznaczaj starsze pliki jako [Przekonwertowane] przy otwieraniu + Use System Defaults for Edit Original Używaj ustawień domyślnych systemu dla polecenia Edytuj oryginał Match User Interface Brightness Dopasuj do jasności interfejsu Light Gray Jasnoszary Medium Gray Średnio szary diff --git a/crates/ui-egui/src/i18n/pt-br.tsv b/crates/ui-egui/src/i18n/pt-br.tsv index 8369f4d23..f43b74b97 100644 --- a/crates/ui-egui/src/i18n/pt-br.tsv +++ b/crates/ui-egui/src/i18n/pt-br.tsv @@ -1585,6 +1585,7 @@ Manage Workspaces… Gerenciar Espaços de Trabalho… Manually Manualmente Mark Older Files as [Converted] When Opened Marcar Arquivos Antigos como [Convertido] Ao Abrir + Use System Defaults for Edit Original Usar Padrões do Sistema para Editar Original Mark as trapped Marcar como sobreimpresso Marker… Marcador… Marks Marcas diff --git a/crates/ui-egui/src/i18n/ru.tsv b/crates/ui-egui/src/i18n/ru.tsv index e6bf3f77c..0a42894b6 100644 --- a/crates/ui-egui/src/i18n/ru.tsv +++ b/crates/ui-egui/src/i18n/ru.tsv @@ -1890,6 +1890,7 @@ file-filter Templates Шаблоны Manually Вручную Mark as trapped Пометить как треппинг Mark Older Files as [Converted] When Opened Помечать старые файлы как [Преобразованные] при открытии + Use System Defaults for Edit Original Использовать системные значения по умолчанию для «Редактировать оригинал» Marker… Маркер… Marks Метки Marks and Bleed Метки и вылет diff --git a/crates/ui-egui/src/i18n/sv.tsv b/crates/ui-egui/src/i18n/sv.tsv index f12351040..214b19aed 100644 --- a/crates/ui-egui/src/i18n/sv.tsv +++ b/crates/ui-egui/src/i18n/sv.tsv @@ -1805,6 +1805,7 @@ font-technology Variable Variabla Manually Manuellt Mark as trapped Märk som överfyllt Mark Older Files as [Converted] When Opened Märk äldre filer som [Konverterad] när de öppnas + Use System Defaults for Edit Original Använd systemets standardinställningar för Redigera original Marker… Markör… Marks Märken Marks and Bleed Märken och utfall diff --git a/crates/ui-egui/src/i18n/tr.tsv b/crates/ui-egui/src/i18n/tr.tsv index b198e1af1..cdf14d3f5 100644 --- a/crates/ui-egui/src/i18n/tr.tsv +++ b/crates/ui-egui/src/i18n/tr.tsv @@ -1699,6 +1699,7 @@ font-technology Variable Değişken Manually Manuel olarak Mark as trapped Sıkışmış olarak işaretle Mark Older Files as [Converted] When Opened Eski Dosyaları Açıldığında [Dönüştürülmüş] Olarak İşaretle + Use System Defaults for Edit Original Orijinali Düzenle için Sistem Varsayılanlarını Kullan Marker… işaretleyici… Marks İşaretler Marks and Bleed İşaretler ve Taşma Payı diff --git a/crates/ui-egui/src/i18n/uk.tsv b/crates/ui-egui/src/i18n/uk.tsv index d468a63d7..112ed5840 100644 --- a/crates/ui-egui/src/i18n/uk.tsv +++ b/crates/ui-egui/src/i18n/uk.tsv @@ -1887,6 +1887,7 @@ file-filter Templates Шаблони Manually Вручну Mark as trapped Позначити як трепінг Mark Older Files as [Converted] When Opened Помічати старі файли як [Перетворені] при відкритті + Use System Defaults for Edit Original Використовувати системні типові програми для «Редагувати оригінал» Marker… Маркер… Marks Мітки Marks and Bleed Мітки та виліт diff --git a/crates/ui-egui/src/i18n/vi.tsv b/crates/ui-egui/src/i18n/vi.tsv index 9d4acddcb..05b1dda5c 100644 --- a/crates/ui-egui/src/i18n/vi.tsv +++ b/crates/ui-egui/src/i18n/vi.tsv @@ -2172,6 +2172,7 @@ digits Hindi Hindi Manually Thủ công Mark as trapped Đánh dấu đã chồng màu Mark Older Files as [Converted] When Opened Đánh dấu tệp cũ là [Đã chuyển đổi] khi mở + Use System Defaults for Edit Original Dùng mặc định hệ thống cho Chỉnh sửa bản gốc Marker… Ký hiệu đánh dấu… Marks Dấu in Marks and Bleed Dấu in và chảy tràn diff --git a/crates/ui-egui/src/i18n/zh-hans.tsv b/crates/ui-egui/src/i18n/zh-hans.tsv index 0426b415f..336057c43 100644 --- a/crates/ui-egui/src/i18n/zh-hans.tsv +++ b/crates/ui-egui/src/i18n/zh-hans.tsv @@ -1774,6 +1774,7 @@ file-filter Templates 模板 Manually 手动 Mark as trapped 标示为已陷印 Mark Older Files as [Converted] When Opened 打开旧文件时标记为 [已转换] + Use System Defaults for Edit Original “编辑原稿”使用系统默认设置 Marker… 标记… Marks 标记 Marks and Bleed 标记和出血 diff --git a/crates/ui-egui/src/i18n/zh-hant.tsv b/crates/ui-egui/src/i18n/zh-hant.tsv index 92a1a9808..4ddbf0f69 100644 --- a/crates/ui-egui/src/i18n/zh-hant.tsv +++ b/crates/ui-egui/src/i18n/zh-hant.tsv @@ -2728,6 +2728,7 @@ file-filter Templates 範本 Manually 手動 Mark as trapped 標示為已陷印 Mark Older Files as [Converted] When Opened 開啟舊檔時標示為 [已轉換] + Use System Defaults for Edit Original 「編輯原稿」使用系統預設值 Marker… 標記… Marks 標記 Marks and Bleed 標記與出血 diff --git a/crates/ui-egui/src/io/export.rs b/crates/ui-egui/src/io/export.rs index 592d60ec0..f32323151 100644 --- a/crates/ui-egui/src/io/export.rs +++ b/crates/ui-egui/src/io/export.rs @@ -217,14 +217,20 @@ pub(crate) fn open_in_app(app: &mut VectorcraftApp, path: &str) -> Result<(), St app.services.open_file.as_mut().ok_or("no app to open files here")?(path) } -/// Edit Original: open `path` in the app Preferences › File Handling › Edit Original With names -/// (#5236), or in the system's default app for it when that is System Default (""). +/// Edit Original: open `path` in the system's default app for it, or with Preferences › File +/// Handling › Use System Defaults for Edit Original off, in the app that made it (#5236): PhotoCraft +/// for a raster image where it is installed. Any other file, or no PhotoCraft, uses the system's app. pub(crate) fn open_original(app: &mut VectorcraftApp, path: &str) -> Result<(), String> { - let with = app.session.prefs.edit_original_app.trim().to_string(); - if with.is_empty() { - return open_in_app(app, path); + let ext = std::path::Path::new(path).extension().map(|e| e.to_string_lossy().into_owned()).unwrap_or_default(); + let raster = vectorcraft_engine::cmd::fileio::readable_format(&ext).is_some_and(|f| f.raster); + if !app.session.prefs.edit_original_system_defaults + && raster + && let Some(open) = app.services.open_in_photocraft.as_mut() + && open(path)? + { + return Ok(()); } - app.services.open_file_with.as_mut().ok_or("no app to open files in here")?(&with, path) + open_in_app(app, path) } /// File → Export for Screens with `document.exportForScreens` params: the files go into `folder`; diff --git a/crates/ui-egui/src/panels/tests_links.rs b/crates/ui-egui/src/panels/tests_links.rs index fb323122c..215cf7148 100644 --- a/crates/ui-egui/src/panels/tests_links.rs +++ b/crates/ui-egui/src/panels/tests_links.rs @@ -1,7 +1,7 @@ //! The Links panel, its flyout, Link Info, Edit Original / Show in Folder, the Placement Options //! dialog and the Properties panel's image section, drawn headless. -use std::cell::RefCell; +use std::cell::{Cell, RefCell}; use std::rc::Rc; use serde_json::json; @@ -168,44 +168,40 @@ fn edit_original_and_show_in_folder_hand_the_file_to_the_system() { assert!(app.run("links.editOriginal", json!({"id": linked})).is_err()); } -/// #5236: Preferences › File Handling › Edit Original With picks the app Edit Original opens the -/// file in: System Default ("") hands it to the system's default app as before, PhotoCraft or an -/// application's path to that app. +/// #5236: with Preferences › File Handling › Use System Defaults for Edit Original off, Edit +/// Original opens a raster image in the app that made it, PhotoCraft, and falls back to the +/// system's default app when PhotoCraft isn't installed or the file isn't a raster image. #[test] -fn edit_original_opens_the_file_in_the_chosen_app() { - /// What each open asked for: (the app, `None` for the system's default, and the path). - type Opened = Rc, String)>>>; +fn edit_original_opens_raster_images_in_photocraft_without_system_defaults() { + /// What each open asked for: (`true` for PhotoCraft, the path). + type Opened = Rc>>; let dir = Folder::new("with"); let (mut app, linked, _) = app(&dir); let opened: Opened = Rc::default(); - let (o, w) = (opened.clone(), opened.clone()); + let installed = Rc::new(Cell::new(true)); + let (o, w, i) = (opened.clone(), opened.clone(), installed.clone()); app.services.open_file = Some(Box::new(move |p: &str| { - o.borrow_mut().push((None, p.to_string())); + o.borrow_mut().push((false, p.to_string())); Ok(()) })); - app.services.open_file_with = Some(Box::new(move |with: &str, p: &str| { - w.borrow_mut().push((Some(with.to_string()), p.to_string())); - Ok(()) + app.services.open_in_photocraft = Some(Box::new(move |p: &str| { + if i.get() { + w.borrow_mut().push((true, p.to_string())); + } + Ok(i.get()) })); + // On (the default): the system's app. let path = app.run("links.editOriginal", json!({"id": linked})).unwrap()["path"].as_str().unwrap().to_string(); - for value in ["photocraft", "/Applications/Example.app"] { - app.run("prefs.set", json!({"key": "editOriginalApp", "value": value})).unwrap(); - app.run("links.editOriginal", json!({"id": linked})).unwrap(); - } - app.run("prefs.set", json!({"key": "editOriginalApp", "value": ""})).unwrap(); + app.run("prefs.set", json!({"key": "editOriginalSystemDefaults", "value": false})).unwrap(); app.run("links.editOriginal", json!({"id": linked})).unwrap(); - assert_eq!( - *opened.borrow(), - [ - (None, path.clone()), - (Some("photocraft".into()), path.clone()), - (Some("/Applications/Example.app".into()), path.clone()), - (None, path.clone()), - ] - ); - // An app the host can't open in (the web) is an error, not the default app instead. - app.run("prefs.set", json!({"key": "editOriginalApp", "value": "photocraft"})).unwrap(); - app.services.open_file_with = None; + installed.set(false); + app.run("links.editOriginal", json!({"id": linked})).unwrap(); + installed.set(true); + // A file that isn't a raster image goes to the system's app. + crate::io::open_original(&mut app, "/Art/plan.emf").unwrap(); + assert_eq!(*opened.borrow(), [(false, path.clone()), (true, path.clone()), (false, path), (false, "/Art/plan.emf".to_string())]); + // PhotoCraft failing to open it is said, not hidden behind the system's app. + app.services.open_in_photocraft = Some(Box::new(|_: &str| Err("PhotoCraft didn't start".to_string()))); assert!(app.run("links.editOriginal", json!({"id": linked})).is_err()); assert_eq!(opened.borrow().len(), 4); } diff --git a/crates/ui-egui/src/prefs_dialog.rs b/crates/ui-egui/src/prefs_dialog.rs index ef18c3ec5..b91c149f4 100644 --- a/crates/ui-egui/src/prefs_dialog.rs +++ b/crates/ui-egui/src/prefs_dialog.rs @@ -176,7 +176,6 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) { let Some(mut d) = app.ui.dialog.clone() else { return }; let t = Tokens::get(ctx); let (mut ok, mut cancel, mut reset) = (false, false, false); - d.fields.insert(CAN_PICK_APP.into(), json!(crate::picks::can(app, &crate::picks::PickRequest::Open(app_pick())))); let cat = d.str("__category"); let cat = PREF_CATEGORIES.iter().find(|c| **c == cat).copied().unwrap_or(PREF_CATEGORIES[0]); crate::dialogs::modal::show(ctx, tl!("Preferences"), egui::Id::unique("dialog-preferences"), -20.0, 20, |ui| { @@ -242,10 +241,6 @@ pub fn show(app: &mut VectorcraftApp, ctx: &egui::Context) { for (k, v) in Prefs::default().to_json().as_object().cloned().unwrap_or_default() { d.fields.insert(k, v); } - d.fields.remove(CHOSE_OTHER_APP); - } - if d.fields.remove(CHOOSE_APP).is_some() { - choose_app(app, &mut d); } app.ui.dialog = Some(d); if cancel { @@ -385,7 +380,6 @@ fn category_fields(ui: &mut egui::Ui, d: &mut Dialog, cat: &str) { } }); } - PrefKind::Text if sp.key == "editOriginalApp" => edit_original_row(ui, d, sp.key, sp.label, &v), PrefKind::Text => { labeled(ui, sp.label, |ui| { let mut s = v.as_str().unwrap_or("").to_string(); @@ -416,94 +410,6 @@ fn choice_row(ui: &mut egui::Ui, d: &mut Dialog, key: &str, label: &str, opts: & }); } -/// File Handling › Edit Original With (#5236): System Default (""), PhotoCraft (`photocraft`) or -/// Other Application, which shows a field for the application's path. -fn edit_original_row(ui: &mut egui::Ui, d: &mut Dialog, key: &str, label: &str, v: &Value) { - const OPTIONS: [&str; 3] = ["System Default", "PhotoCraft", "Other Application"]; - // The field shows the value as stored, spaces included (the host trims it when it opens). - let raw = v.as_str().unwrap_or("").to_string(); - let cur = raw.trim().to_string(); - let photocraft = cur.eq_ignore_ascii_case("photocraft"); - // Other Application with no path typed yet is still System Default: the dialog remembers the - // choice in a field of its own, which a new dialog and Reset Preferences start without. - let mut other = !photocraft && (!cur.is_empty() || d.bool(CHOSE_OTHER_APP)); - // A path (saved or typed) also counts as the choice, so clearing it to type another keeps the - // field. - if other && !d.bool(CHOSE_OTHER_APP) { - d.fields.insert(CHOSE_OTHER_APP.into(), json!(true)); - } - let at = if photocraft { - 1 - } else if other { - 2 - } else { - 0 - }; - labeled(ui, label, |ui| { - if let Some(i) = widgets::dropdown(ui, key, OPTIONS[at], &OPTIONS, 260.0) { - other = i == 2; - d.fields.insert(CHOSE_OTHER_APP.into(), json!(other)); - let new = match i { - 0 => String::new(), - 1 => "photocraft".to_string(), - _ if photocraft => String::new(), - _ => raw.clone(), - }; - d.fields.insert(key.into(), json!(new)); - } - }); - if other { - let can_pick = d.bool(CAN_PICK_APP); - labeled(ui, "Application Path", |ui| { - let mut s = if photocraft { String::new() } else { raw.clone() }; - let width = if can_pick { 186.0 } else { 260.0 }; - let field = egui::TextEdit::singleline(&mut s).align(egui::Align2::LEFT_CENTER).desired_width(width); - let hint = if cfg!(target_os = "macos") { "/Applications/Example.app" } else { "" }; - if ui.add(field.hint_text(hint)).changed() { - d.fields.insert(key.into(), json!(s)); - } - // The open dialog runs from `show`, which has the app ([`choose_app`]). - if can_pick && widgets::flat_button(ui, tl!("Choose…"), 68.0).clicked() { - d.fields.insert(CHOOSE_APP.into(), json!(true)); - } - }); - } -} - -/// UI-only dialog fields for Edit Original With's Choose… button: whether the host shows an open -/// dialog, and that the button was clicked this frame. -const CAN_PICK_APP: &str = "__canPickApp"; -const CHOOSE_APP: &str = "__chooseApp"; -/// UI-only dialog field: Other Application was chosen, though no path is typed yet. -const CHOSE_OTHER_APP: &str = "__choseOtherApp"; - -/// The open dialog Edit Original With's Choose… shows: applications (`.app` bundles in -/// Applications on macOS, `.exe` files on Windows, any file elsewhere). -fn app_pick() -> crate::FilePick { - let filters: Vec = if cfg!(target_os = "macos") { - vec![("Applications".into(), vec!["app".into()])] - } else if cfg!(windows) { - vec![("Programs".into(), vec!["exe".into()])] - } else { - vec![] - }; - let folder = cfg!(target_os = "macos").then(|| "/Applications".to_string()); - crate::FilePick { name: String::new(), folder, filters } -} - -/// Edit Original With › Choose…: the picked application into the dialog's `editOriginalApp`. Shown -/// off the UI thread, the answer fills the dialog as it is then ([`crate::picks::in_dialog`]). -fn choose_app(app: &mut VectorcraftApp, d: &mut Dialog) { - let r = crate::picks::in_dialog(app, d, |app, d| { - if let Some(path) = crate::picks::open(app, &app_pick()) { - d.fields.insert("editOriginalApp".into(), json!(path)); - } - Ok(()) - }); - // Picking a file doesn't fail. - let _ = r; -} - fn bool_row(ui: &mut egui::Ui, d: &mut Dialog, key: &str, label: &str) { // The app's outlined checkbox (it translates `label`): egui's own has no border at rest. let on = d.bool(key); diff --git a/crates/ui-egui/src/prefs_dialog/tests.rs b/crates/ui-egui/src/prefs_dialog/tests.rs index c2557aede..2e4711505 100644 --- a/crates/ui-egui/src/prefs_dialog/tests.rs +++ b/crates/ui-egui/src/prefs_dialog/tests.rs @@ -392,56 +392,17 @@ fn hyphenation_exceptions_dialog_ok_reaches_the_hyphenator() { assert_eq!(vectorcraft_text::hyphen::hyphen_points("happen"), vec![3]); } -/// #5236: File Handling › Edit Original With offers System Default, PhotoCraft and Other -/// Application, whose Choose… puts the picked application into the preference. +/// #5236: File Handling › Use System Defaults for Edit Original is a checkbox, on by default, and +/// OK saves it off. #[test] -fn edit_original_with_takes_a_chosen_application() { +fn use_system_defaults_for_edit_original_is_a_checkbox() { let mut a = VectorcraftApp::new(Session::new(), Default::default()); + assert!(a.session.prefs.edit_original_system_defaults, "on by default"); open(&mut a, Some("File Handling")); let text = crate::tests_labels::painted_text(&mut a, |a, ui| show(a, ui.ctx())); - assert!(text.contains("Edit Original With") && text.contains("System Default"), "{text}"); - let asked: std::rc::Rc>> = Default::default(); - let seen = asked.clone(); - a.services.pick_open = Some(Box::new(move |p: &crate::FilePick| { - seen.borrow_mut().push(p.clone()); - Some("/Applications/Editor.app".into()) - })); - let mut d = a.ui.dialog.clone().unwrap(); - choose_app(&mut a, &mut d); - assert_eq!(d.str("editOriginalApp"), "/Applications/Editor.app"); - let pick = asked.borrow()[0].clone(); - if cfg!(target_os = "macos") { - assert_eq!((pick.folder.as_deref(), pick.filters[0].1.clone()), (Some("/Applications"), vec!["app".to_string()])); - } - // OK saves it; the dialog's own fields (`__…`) are not preferences. - a.ui.dialog = Some(d); + assert!(text.contains("Use System Defaults for Edit Original"), "{text}"); + assert!(a.ui.dialog.as_ref().unwrap().bool("editOriginalSystemDefaults")); + a.ui.dialog.as_mut().unwrap().fields.insert("editOriginalSystemDefaults".into(), json!(false)); confirm(&mut a).unwrap(); - assert_eq!(a.session.prefs.edit_original_app, "/Applications/Editor.app"); -} - -/// #5327 review: choosing Other Application before a path is typed is remembered by the dialog -/// itself, so a dialog opened again (or reset) starts at System Default, and a stored path keeps -/// its spaces. -#[test] -fn edit_original_with_other_is_the_dialogs_own_choice() { - let mut a = VectorcraftApp::new(Session::new(), Default::default()); - open(&mut a, Some("File Handling")); - assert!(!a.ui.dialog.as_ref().unwrap().bool(CHOSE_OTHER_APP), "a new dialog starts without the choice"); - let shown = |a: &mut VectorcraftApp| crate::tests_labels::painted_text(a, |a, ui| show(a, ui.ctx())).contains("Application Path"); - assert!(!shown(&mut a), "System Default shows no path field"); - a.ui.dialog.as_mut().unwrap().fields.insert(CHOSE_OTHER_APP.into(), json!(true)); - assert!(shown(&mut a), "Other Application with no path yet shows the field"); - a.ui.dialog = None; - open(&mut a, Some("File Handling")); - assert!(!shown(&mut a), "opened again: System Default"); - // A saved path counts as the choice: clearing it to type another keeps the field. - a.ui.dialog.as_mut().unwrap().fields.insert("editOriginalApp".into(), json!("/Applications/Old.app")); - assert!(shown(&mut a)); - a.ui.dialog.as_mut().unwrap().fields.insert("editOriginalApp".into(), json!("")); - assert!(shown(&mut a), "the path cleared: still Other Application"); - // A path with spaces is kept as typed. - let path = "/Applications/Visual Studio Code.app"; - a.ui.dialog.as_mut().unwrap().fields.insert("editOriginalApp".into(), json!(path)); - confirm(&mut a).unwrap(); - assert_eq!(a.session.prefs.edit_original_app, path); + assert!(!a.session.prefs.edit_original_system_defaults); } diff --git a/crates/ui-egui/src/services.rs b/crates/ui-egui/src/services.rs index 7e00322bc..3d3524788 100644 --- a/crates/ui-egui/src/services.rs +++ b/crates/ui-egui/src/services.rs @@ -65,8 +65,8 @@ pub(crate) fn browsable_url(url: &str) -> Result<(), String> { /// Shows a file in the system file manager, or opens it in its app. pub type RevealFn = Box Result<(), String>>; -/// Opens a file in a chosen app: `(app, path)`. -pub type OpenWithFn = Box Result<(), String>>; +/// Opens a file in a fixed app; `Ok(false)` when that app isn't installed. +pub type OpenInFn = Box Result>; /// Platform services injected by the host app (desktop or web). #[derive(Default)] @@ -119,10 +119,10 @@ pub struct Services { /// Open a file (or a folder) in the system's default app for it (desktop: Edit Original, Show /// Package). Without it (the web) those answer with an error. pub open_file: Option, - /// Open a file in a chosen app (desktop: Edit Original with Preferences › File Handling › Edit - /// Original With set): `(app, path)`, where `app` is `photocraft` or an application's path. - /// Without it (the web) Edit Original with an app set answers with an error. - pub open_file_with: Option, + /// Open a file in PhotoCraft where it is installed (desktop: Edit Original of a raster image + /// with Preferences › File Handling › Use System Defaults for Edit Original off); `Ok(false)` + /// when it isn't. Without it Edit Original uses `open_file`. + pub open_in_photocraft: Option, /// Show a folder picker; returns its path (desktop: Relink to Folder, Package). pub pick_folder: Option Option>>, /// Show the system's color sampler, which picks a color anywhere on the screen (macOS, and diff --git a/docs/control-protocol.md b/docs/control-protocol.md index 8c624dcb5..b7f63de17 100644 --- a/docs/control-protocol.md +++ b/docs/control-protocol.md @@ -668,10 +668,10 @@ one undo step. The Links panel (`window.panel {panel: "links"}`) shows `links.li `links.info`; `links.editOriginal` and `links.reveal` (Edit › Edit Original, Show in Folder) hand the linked file to the system and return `{path}`. Edit Original opens only a regular file whose extension is a format `document.open` reads, and answers a control request with an error unless the file is a placed document (which opens in a tab). -The preference `editOriginalApp` (Preferences › File Handling › Edit Original With, #5236) picks the app: `""` (System -Default, the system's default app for the file), `"photocraft"` (PhotoCraft where it is installed) or an application's -path. The app must be an application (an `.app` bundle on macOS, an `.exe` on Windows, an executable file elsewhere), -and gets the file's path as its only argument. +The preference `editOriginalSystemDefaults` (Preferences › File Handling › Use System Defaults for Edit Original, on +by default, #5236) opens the file in the system's default app for it; off, in the app that made it: a raster image in +PhotoCraft, looked for only where it installs (the Applications folders on macOS, the `PATH` elsewhere), and any other +file, or a raster image without PhotoCraft installed, in the system's default app. Package: File → Package… (`ui.packageDialog`) opens the `package` dialog for a saved document (a document never saved gets a `confirm` dialog whose OK runs `file.saveAs`). Fields: `folder` (the location), `name` (the package folder), diff --git a/docs/mcp.md b/docs/mcp.md index 2befc0205..bbd4451f5 100644 --- a/docs/mcp.md +++ b/docs/mcp.md @@ -4216,9 +4216,9 @@ file is missing stays linked: relink it first); `links.unembed {id, path}` write clip?}` decides how a relinked or updated file takes an image's place (default `bounds`: stretched into the old bounds; `clip` puts it in a clip group of the old bounds when it is larger). The UI commands `links.editOriginal` and `links.reveal` open the linked file in its app or show it in its folder (desktop). Edit Original runs only from the app's window, -except for a placed document, which opens in a tab. The preference `editOriginalApp` (Preferences › File Handling › -Edit Original With) picks the app Edit Original opens the file in: `""` (the system's default app), `"photocraft"` or an -application's path (#5236). +except for a placed document, which opens in a tab. With the preference `editOriginalSystemDefaults` (Preferences › +File Handling › Use System Defaults for Edit Original, on by default) off, Edit Original opens a raster image in the app +that made it, PhotoCraft, where it is installed, and anything else in the system's default app (#5236). ```json {"name":"run_command","arguments":{"command":"links.list","params":{"show":"missing"}}}