mirror of
https://github.com/storytold/pdfcraft.git
synced 2026-10-14 11:17:10 -04:00
* feat(ui): add PDF tab context menu (#1233) * Tab menu: reveal through D-Bus off the UI thread `dbus-send --print-reply --reply-timeout=2000` (and the xdg-open fallback) could block the frame for 2 s or more; on Linux and FreeBSD the reveal now runs on a `pdfcraft-reveal` thread and a failure is logged. The existence check stays on the UI thread and is still reported; Windows and macOS launches are unchanged. Refreshes the catalog hashes after the main merge. Co-Authored-By: Claude Opus 5.5 <[email protected]> --------- Co-authored-by: srt8hellcat2-ui <[email protected]> Co-authored-by: Brandon Thomas <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
25 files changed
+452
-22
No files matched your search
+20
-20
@@ -7,7 +7,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Greek UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "d7ad8f46f105638aff5b91423fe74077af5bf1f3246eeb9e066df8e4df0e222e"
|
||||
sha256 = "f158bd9cc2557593365171f28f76fc38b95e7ce07faf2ff8b651d99a540f82a6"
|
||||
|
||||
# ATTRIBUTION.toml: one entry per asset (AGENTS.md §1). The source of truth for provenance and licences.
|
||||
# ATTRIBUTION.md is generated from this file: `cargo xtask assets --write`. Checked by `cargo xtask assets` (part of `xtask ci`).
|
||||
@@ -30,7 +30,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Simplified Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "7df6e3aebce85e8eaac46465ab46d0c8447f314cdcdbe41ba4fc0188a2105f14"
|
||||
sha256 = "26c2f0ead5f4dd87d946ba11b4c2b8d442e87d54b2ec8dc15f7abfe187eccc3f"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/zh-hant.tsv"
|
||||
@@ -41,7 +41,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Traditional Chinese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "49eccf2decfc53724250e685bb448c41ccadcf6906fa56ba9b13d8047329ab99"
|
||||
sha256 = "658f1c4a24029fd767cabecb8f8f3be2eacc0d09f710eae0e7aceb03cc628f32"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/cs.tsv"
|
||||
@@ -52,7 +52,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Czech UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "6abec13d351d38a12e4634f64bbf6b884c8102af1f97da7e08cbcd29de14a5ba"
|
||||
sha256 = "640636e260b8bc726d40d6d7101698640de5028df0d67935a16741491fb3cb5f"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/pt-br.tsv"
|
||||
@@ -63,7 +63,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Brazilian Portuguese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "afa953e566e7b9533df28845b52a768f77c7d8aa254cd3bbe1928ddf28019efa"
|
||||
sha256 = "bf92cc2ebf7a1a7ae1f7c4fd270ca7643530bc6f8b38d5cba4da3594498f2889"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/es.tsv"
|
||||
@@ -74,7 +74,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Spanish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "1de0258b9b1ea7c375ef71d8c4cbeeebbc0f976cb6aead04bdd1d06d18f7c717"
|
||||
sha256 = "7c12f6bf221a654ce22e77fc723e621daf8dd52ceafd6bff075b7b070d484aa2"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/fr.tsv"
|
||||
@@ -85,7 +85,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "French UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "59ac5e705d8d74273ce774cb2dd8020313277ae2a376ae2967031d60ae590745"
|
||||
sha256 = "3d16bf1a86f7d8108ec64569e18db00eafba174092f27ca93c8281ffb93a8c39"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/de.tsv"
|
||||
@@ -96,7 +96,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "German UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "36e814e0bec4fab77c7a0face806fa14269c1964b9850d19765d4b41b4503300"
|
||||
sha256 = "64cfe3444b8c0cf1bc91938567b52ed52fccd9124e2a567f8ac2d710510b7c30"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/te.tsv"
|
||||
@@ -107,7 +107,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Telugu UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "22c8d5335c7b7be2e7a735e94b076ab9aead4817335953387f1122691b00e04e"
|
||||
sha256 = "7e1c2cebada9fd1d065d10496e77e6c0ae4b08ef9145292b1ce216fe476036ba"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/it.tsv"
|
||||
@@ -118,7 +118,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Italian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "3c2740ffdf370e1dc1cd3fbfd4da05185a0d247233fcb143c63c2f560a072cd2"
|
||||
sha256 = "4ed39c62cf09008372d5f079458bb7ab80ed62786c58e8193ded3d35e8fea521"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/pl.tsv"
|
||||
@@ -129,7 +129,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Polish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "755fadd87a23d56ff3319ed58b4651b386603a86ceed1a74919403a9a1f5698b"
|
||||
sha256 = "92ceee115cfb37b94dd7c5aad674896d3529b1f5637b1fd9529458b7d91ab8df"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ru.tsv"
|
||||
@@ -140,7 +140,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Russian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "3455f5598efb906ad12921cb9264761d7eb8f180d26ef02e8c3ab041d5f63f76"
|
||||
sha256 = "1e1524a817f7ee2b53aa7d48e0d755173578060d2fae9fa3d9c7ecd18ec3b0f0"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/hu.tsv"
|
||||
@@ -151,7 +151,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Hungarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "0f9ae32a12fd0285d5e11a523019f410894533669a20ba16ad6ded4907ee8f55"
|
||||
sha256 = "faee788eefd418d93caded400a81ea33a5ec5ea20ae62bf36f344a694027c7b5"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/bg.tsv"
|
||||
@@ -162,7 +162,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Bulgarian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "3e1386f1e8c6412a5a685b127b4871c5ea5e7e1c91428a1a402dca61b785f103"
|
||||
sha256 = "d88ae2bc2ad294ef9daa783768feb9827c3a90bf60f0c022cf714ddd8d5e2606"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/kk.tsv"
|
||||
@@ -173,7 +173,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Kazakh UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "134d6c9787407e8634c2749adcf40c8f15f89e6262e902547f8ef0022e548f72"
|
||||
sha256 = "203245e46fdddd99a6e9c2fac1b05db6f6e3a3347369a2d0dc5846dcd0293c32"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ja.tsv"
|
||||
@@ -184,7 +184,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Japanese UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "6dfc2b9e312a888fd0a11426129d38654e40f59888c2c90b1be1751b5893daa3"
|
||||
sha256 = "d9e8acae91cb85b22abd2f8e7d285709d200f8b9b60e09370e3c1919328607c0"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/uk.tsv"
|
||||
@@ -195,7 +195,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Ukrainian UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "46f7addc24737c5700b8dff69392d16b24c67b92f0bf92444b806f3f9e107195"
|
||||
sha256 = "bcb35726d63d4f154df5a389d3be46b53b6de9836e79d79b5fb13cd5aa9c5157"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/ar.tsv"
|
||||
@@ -206,7 +206,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Arabic UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "a680284dc350985b9f0742e258ff6e8d05e2d30513a0d9444c915d250ba1ed9a"
|
||||
sha256 = "7550018b4737b4ee689d38c31b5ea2093e26a18ccd717661b80a68778a0fb652"
|
||||
|
||||
[[asset]]
|
||||
path = "crates/ui-egui/src/i18n/tr.tsv"
|
||||
@@ -217,7 +217,7 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Turkish UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "1a1db9cd9a6d3ba59eeb739a8961e57183320dd11ca9f8750c6683ed5bc243a3"
|
||||
sha256 = "45580772203b30915881c8945d33e6edc6363d7b5583fc0eff5acface163b37b"
|
||||
|
||||
[[asset]]
|
||||
path = "assets/icons/accessibility.svg"
|
||||
@@ -3328,4 +3328,4 @@ licence = "MIT OR Apache-2.0"
|
||||
licence_file = "LICENSE-MIT"
|
||||
kind = "translation"
|
||||
usage = "Slovene UI strings, compiled in by crates/ui-egui/src/i18n/mod.rs"
|
||||
sha256 = "499b2ea8dbb49c4d59f4ac360e72a64ac4f3eb2760521c4e9b8f135297180823"
|
||||
sha256 = "a9c8fd94278a41acda913e7d3242da627701207a1ac187f2bf2b5a9fddcf6aa8"
|
||||
@@ -133,6 +133,8 @@ Search the whole document as you type, step through matches with <kbd>⌘G</kbd>
|
||||
|
||||
## Navigate long documents
|
||||
|
||||
Right-click a PDF tab to close it, reopen the last closed file-backed tab, or reveal its file. Closing still asks about unsaved changes. Reopening reads the saved file from disk; it does not restore discarded edits. Reveal selects the file in Explorer or Finder. On Linux and FreeBSD it requests `org.freedesktop.FileManager1.ShowItems`, falling back to opening the containing directory with `xdg-open` when that service is unavailable. Reopen and Reveal are unavailable on the web; Reveal is also unavailable for pathless documents.
|
||||
|
||||
Bookmarks, page thumbnails and the document's own page labels (i, ii, 1, 2…) keep you oriented in long documents. Search bookmark titles in the Bookmarks panel to find nested entries even when their parents are collapsed. Matches keep their ancestors for context; Clear restores the unfiltered tree without changing its expansion state.
|
||||
|
||||
<table>
|
||||
|
||||
@@ -88,6 +88,7 @@ pub fn tab_strip(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
|
||||
data.insert_temp(id, state);
|
||||
changed
|
||||
});
|
||||
let mut action = None;
|
||||
let mut close = None;
|
||||
ui.scope(|ui| {
|
||||
ui.style_mut().always_scroll_the_only_direction = true;
|
||||
@@ -119,12 +120,34 @@ pub fn tab_strip(app: &mut PdfCraftApp, ui: &mut egui::Ui) {
|
||||
if changed && app.active == Some(i) && !app.combine_showing() {
|
||||
response.scroll_to_me(Some(Align::Center));
|
||||
}
|
||||
let id = app.views[i].id;
|
||||
let reveal = cfg!(not(target_arch = "wasm32")) && doc.path.is_some();
|
||||
response.context_menu(|ui| {
|
||||
if ui.button(tl!("Close Tab")).clicked() {
|
||||
action = Some(crate::tab_context::Action::Close(id));
|
||||
ui.close();
|
||||
}
|
||||
if ui.add_enabled(!app.closed_tabs.is_empty(), egui::Button::new(tl!("Reopen Closed Tab"))).clicked() {
|
||||
action = Some(crate::tab_context::Action::Reopen);
|
||||
ui.close();
|
||||
}
|
||||
let label = if cfg!(target_os = "macos") { tl!("Reveal in Finder") } else { tl!("Reveal in File Explorer") };
|
||||
if ui.add_enabled(reveal, egui::Button::new(label)).clicked() {
|
||||
action = Some(crate::tab_context::Action::Reveal(id));
|
||||
ui.close();
|
||||
}
|
||||
});
|
||||
if response.clicked() {
|
||||
app.active = Some(i);
|
||||
}
|
||||
}
|
||||
if let Some(i) = close {
|
||||
app.request_close_tab(i);
|
||||
if let Some(i) = close
|
||||
&& let Some(view) = app.views.get(i)
|
||||
{
|
||||
action.get_or_insert(crate::tab_context::Action::Close(view.id));
|
||||
}
|
||||
if let Some(action) = action {
|
||||
app.tab_action(action);
|
||||
}
|
||||
if app.combine_tab.open {
|
||||
// After the document tabs; its index can't clash with theirs.
|
||||
|
||||
@@ -2337,6 +2337,12 @@ animation effects Off معطّلة
|
||||
Ink sensitivity حساسية الحبر
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. لم يُعثر على توقيع داخل الإطار. حرّك الإطار فوق التوقيع أو ارفع حساسية الحبر.
|
||||
Couldn't use the photo: {e} تعذّر استخدام الصورة: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab إغلاق علامة التبويب
|
||||
Reopen Closed Tab إعادة فتح علامة التبويب المغلقة
|
||||
Reveal in Finder إظهار في Finder
|
||||
Reveal in File Explorer إظهار في مستكشف الملفات
|
||||
Other colour… لون آخر…
|
||||
Recent colours الألوان الأخيرة
|
||||
Couldn't follow linked PDF: {reason} تعذّر فتح ملف PDF المرتبط: {reason}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2279,6 +2279,12 @@ animation effects Off Изключени
|
||||
Ink sensitivity Чувствителност към мастило
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. В рамката не е открит подпис. Преместете рамката върху подписа или увеличете чувствителността към мастило.
|
||||
Couldn't use the photo: {e} Снимката не може да се използва: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab Затваряне на раздела
|
||||
Reopen Closed Tab Отваряне на затворения раздел
|
||||
Reveal in Finder Показване във Finder
|
||||
Reveal in File Explorer Показване във файловия мениджър
|
||||
Other colour… Друг цвят…
|
||||
Recent colours Последни цветове
|
||||
Couldn't follow linked PDF: {reason} Свързаният PDF не можа да бъде отворен: {reason}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 8 and column 16.
|
@@ -2390,6 +2390,12 @@ animation effects Off Vypnuto
|
||||
Ink sensitivity Citlivost na inkoust
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. V rámečku nebyl nalezen žádný podpis. Přesuňte rámeček na podpis nebo zvyšte citlivost na inkoust.
|
||||
Couldn't use the photo: {e} Fotografii nelze použít: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab Zavřít kartu
|
||||
Reopen Closed Tab Znovu otevřít zavřenou kartu
|
||||
Reveal in Finder Zobrazit ve Finderu
|
||||
Reveal in File Explorer Zobrazit v Průzkumníku souborů
|
||||
Other colour… Jiná barva…
|
||||
Recent colours Nedávné barvy
|
||||
Couldn't follow linked PDF: {reason} Odkazovaný PDF nelze otevřít: {reason}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2317,6 +2317,12 @@ animation effects Off Aus
|
||||
Ink sensitivity Tintenempfindlichkeit
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. Im Rahmen wurde keine Unterschrift gefunden. Ziehen Sie den Rahmen über die Unterschrift oder erhöhen Sie die Tintenempfindlichkeit.
|
||||
Couldn't use the photo: {e} Das Foto kann nicht verwendet werden: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab Tab schließen
|
||||
Reopen Closed Tab Geschlossenen Tab wieder öffnen
|
||||
Reveal in Finder Im Finder anzeigen
|
||||
Reveal in File Explorer Im Datei-Explorer anzeigen
|
||||
Other colour… Andere Farbe…
|
||||
Recent colours Zuletzt verwendete Farben
|
||||
Couldn't follow linked PDF: {reason} Verknüpfte PDF konnte nicht geöffnet werden: {reason}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 51.
|
@@ -2321,6 +2321,12 @@ animation effects Off Ανενεργά
|
||||
Ink sensitivity Ευαισθησία μελανιού
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. Δεν βρέθηκε υπογραφή στο πλαίσιο. Μετακινήστε το πλαίσιο πάνω από την υπογραφή ή αυξήστε την ευαισθησία μελανιού.
|
||||
Couldn't use the photo: {e} Δεν ήταν δυνατή η χρήση της φωτογραφίας: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab Κλείσιμο καρτέλας
|
||||
Reopen Closed Tab Επαναφορά κλειστής καρτέλας
|
||||
Reveal in Finder Εμφάνιση στο Finder
|
||||
Reveal in File Explorer Εμφάνιση στη διαχείριση αρχείων
|
||||
Other colour… Άλλο χρώμα…
|
||||
Recent colours Πρόσφατα χρώματα
|
||||
Reading colours Χρώματα ανάγνωσης
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 479 and column 12.
|
@@ -2435,6 +2435,12 @@ animation effects Off Desactivados
|
||||
Ink sensitivity Sensibilidad de la tinta
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. No se encontró ninguna firma en el marco. Mueva el marco sobre la firma o aumente la sensibilidad de la tinta.
|
||||
Couldn't use the photo: {e} No se puede usar la foto: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab Cerrar pestaña
|
||||
Reopen Closed Tab Reabrir pestaña cerrada
|
||||
Reveal in Finder Mostrar en Finder
|
||||
Reveal in File Explorer Mostrar en el explorador de archivos
|
||||
Other colour… Otro color…
|
||||
Recent colours Colores recientes
|
||||
Reading colours Colores de lectura
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 60.
|
@@ -2316,6 +2316,12 @@ animation effects Off Désactivés
|
||||
Ink sensitivity Sensibilité à l’encre
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. Aucune signature trouvée dans le cadre. Placez le cadre sur la signature ou augmentez la sensibilité à l’encre.
|
||||
Couldn't use the photo: {e} Impossible d’utiliser la photo : {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab Fermer l’onglet
|
||||
Reopen Closed Tab Rouvrir l’onglet fermé
|
||||
Reveal in Finder Afficher dans le Finder
|
||||
Reveal in File Explorer Afficher dans l’explorateur de fichiers
|
||||
Other colour… Autre couleur…
|
||||
Recent colours Couleurs récentes
|
||||
Couldn't follow linked PDF: {reason} Impossible de suivre le PDF lié : {reason}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 39.
|
@@ -2315,6 +2315,12 @@ animation effects Off Ki
|
||||
Ink sensitivity Tintaérzékenység
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. A keretben nem található aláírás. Húzza a keretet az aláírásra, vagy növelje a tintaérzékenységet.
|
||||
Couldn't use the photo: {e} A fénykép nem használható: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab Lap bezárása
|
||||
Reopen Closed Tab Bezárt lap újranyitása
|
||||
Reveal in Finder Megjelenítés a Finderben
|
||||
Reveal in File Explorer Megjelenítés a fájlkezelőben
|
||||
Other colour… Egyéb szín…
|
||||
Recent colours Legutóbbi színek
|
||||
Couldn't follow linked PDF: {reason} A hivatkozott PDF nem nyitható meg: {reason}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 7 and column 44.
|
@@ -2325,6 +2325,12 @@ animation effects Off Disattivati
|
||||
Ink sensitivity Sensibilità all’inchiostro
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. Nessuna firma trovata nella cornice. Sposta la cornice sulla firma o aumenta la sensibilità all’inchiostro.
|
||||
Couldn't use the photo: {e} Impossibile usare la foto: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab Chiudi scheda
|
||||
Reopen Closed Tab Riapri scheda chiusa
|
||||
Reveal in Finder Mostra nel Finder
|
||||
Reveal in File Explorer Mostra in Esplora file
|
||||
Other colour… Altro colore…
|
||||
Recent colours Colori recenti
|
||||
Couldn't follow linked PDF: {reason} Impossibile aprire il PDF collegato: {reason}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2316,6 +2316,12 @@ animation effects Off オフ
|
||||
Ink sensitivity インクの感度
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. 枠内に署名が見つかりません。枠を署名に合わせるか、インクの感度を上げてください。
|
||||
Couldn't use the photo: {e} 写真を使用できません: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab タブを閉じる
|
||||
Reopen Closed Tab 閉じたタブを再度開く
|
||||
Reveal in Finder Finderで表示
|
||||
Reveal in File Explorer ファイルエクスプローラーで表示
|
||||
Other colour… その他の色…
|
||||
Recent colours 最近使った色
|
||||
Couldn't follow linked PDF: {reason} リンク先の PDF を開けませんでした: {reason}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2304,6 +2304,12 @@ animation effects Off Өшірулі
|
||||
Ink sensitivity Сия сезімталдығы
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. Жақтауда қолтаңба табылмады. Жақтауды қолтаңбаның үстіне жылжытыңыз немесе сия сезімталдығын арттырыңыз.
|
||||
Couldn't use the photo: {e} Суретті пайдалану мүмкін болмады: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab Қойындыны жабу
|
||||
Reopen Closed Tab Жабылған қойындыны қайта ашу
|
||||
Reveal in Finder Finder ішінде көрсету
|
||||
Reveal in File Explorer Файл шолғышында көрсету
|
||||
Other colour… Басқа түс…
|
||||
Recent colours Соңғы түстер
|
||||
Reading colours Оқу түстері
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 820 and column 12.
|
@@ -2285,6 +2285,12 @@ animation effects Off Wyłączone
|
||||
Ink sensitivity Czułość na atrament
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. W ramce nie znaleziono podpisu. Przesuń ramkę na podpis lub zwiększ czułość na atrament.
|
||||
Couldn't use the photo: {e} Nie można użyć zdjęcia: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab Zamknij kartę
|
||||
Reopen Closed Tab Otwórz ponownie zamkniętą kartę
|
||||
Reveal in Finder Pokaż w Finderze
|
||||
Reveal in File Explorer Pokaż w Eksploratorze plików
|
||||
Other colour… Inny kolor…
|
||||
Recent colours Ostatnie kolory
|
||||
Reading colours Kolory do czytania
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2330,6 +2330,12 @@ animation effects Off Desativados
|
||||
Ink sensitivity Sensibilidade à tinta
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. Nenhuma assinatura encontrada no quadro. Mova o quadro sobre a assinatura ou aumente a sensibilidade à tinta.
|
||||
Couldn't use the photo: {e} Não foi possível usar a foto: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab Fechar aba
|
||||
Reopen Closed Tab Reabrir aba fechada
|
||||
Reveal in Finder Mostrar no Finder
|
||||
Reveal in File Explorer Mostrar no explorador de arquivos
|
||||
Other colour… Outra cor…
|
||||
Recent colours Cores recentes
|
||||
Couldn't follow linked PDF: {reason} Não foi possível abrir o PDF vinculado: {reason}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2229,6 +2229,12 @@ animation effects Off Выкл
|
||||
Ink sensitivity Чувствительность к чернилам
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. В рамке не найдена подпись. Наведите рамку на подпись или увеличьте чувствительность к чернилам.
|
||||
Couldn't use the photo: {e} Не удалось использовать фото: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab Закрыть вкладку
|
||||
Reopen Closed Tab Открыть закрытую вкладку
|
||||
Reveal in Finder Показать в Finder
|
||||
Reveal in File Explorer Показать в проводнике
|
||||
Other colour… Другой цвет…
|
||||
Recent colours Недавние цвета
|
||||
Couldn't follow linked PDF: {reason} Не удалось открыть связанный PDF: {reason}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2321,6 +2321,12 @@ animation effects Off Izklopljeni
|
||||
Ink sensitivity Občutljivost za črnilo
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. V okvirju ni bilo najdenega podpisa. Premaknite okvir nad podpis ali povečajte občutljivost za črnilo.
|
||||
Couldn't use the photo: {e} Fotografije ni bilo mogoče uporabiti: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab Zapri zavihek
|
||||
Reopen Closed Tab Znova odpri zaprti zavihek
|
||||
Reveal in Finder Prikaži v Finderju
|
||||
Reveal in File Explorer Prikaži v upravljalniku datotek
|
||||
Other colour… Druga barva…
|
||||
Recent colours Nedavne barve
|
||||
Couldn't follow linked PDF: {reason} Povezanega PDF-ja ni bilo mogoče odpreti: {reason}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2218,6 +2218,12 @@ animation effects Off ఆఫ్
|
||||
Ink sensitivity ఇంక్ సున్నితత్వం
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. ఫ్రేమ్లో సంతకం కనబడలేదు. ఫ్రేమ్ను సంతకం మీదకు జరపండి లేదా ఇంక్ సున్నితత్వాన్ని పెంచండి.
|
||||
Couldn't use the photo: {e} ఫోటోను ఉపయోగించలేకపోయాం: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab ట్యాబ్ను మూసివేయి
|
||||
Reopen Closed Tab మూసివేసిన ట్యాబ్ను మళ్లీ తెరువు
|
||||
Reveal in Finder Finderలో చూపు
|
||||
Reveal in File Explorer ఫైల్ ఎక్స్ప్లోరర్లో చూపు
|
||||
Other colour… ఇతర రంగు…
|
||||
Recent colours ఇటీవలి రంగులు
|
||||
Reading colours చదవడానికి రంగులు
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 13 and column 16.
|
@@ -2302,6 +2302,12 @@ animation effects Off Kapalı
|
||||
Ink sensitivity Mürekkep hassasiyeti
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. Çerçevede imza bulunamadı. Çerçeveyi imzanın üzerine taşıyın veya mürekkep hassasiyetini artırın.
|
||||
Couldn't use the photo: {e} Fotoğraf kullanılamadı: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab Sekmeyi kapat
|
||||
Reopen Closed Tab Kapatılan sekmeyi yeniden aç
|
||||
Reveal in Finder Finder’da göster
|
||||
Reveal in File Explorer Dosya Gezgini’nde göster
|
||||
Other colour… Diğer renk…
|
||||
Recent colours Son kullanılan renkler
|
||||
Reading colours Okuma renkleri
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 819 and column 12.
|
@@ -2338,6 +2338,12 @@ animation effects Off Вимкнено
|
||||
Ink sensitivity Чутливість до чорнила
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. У рамці не знайдено підпису. Наведіть рамку на підпис або збільште чутливість до чорнила.
|
||||
Couldn't use the photo: {e} Не вдалося використати фото: {e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab Закрити вкладку
|
||||
Reopen Closed Tab Відкрити закриту вкладку
|
||||
Reveal in Finder Показати у Finder
|
||||
Reveal in File Explorer Показати у файловому провіднику
|
||||
Other colour… Інший колір…
|
||||
Recent colours Нещодавні кольори
|
||||
Couldn't follow linked PDF: {reason} Не вдалося відкрити пов’язаний PDF: {reason}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 6 and column 47.
|
@@ -2280,6 +2280,12 @@ animation effects Off 关
|
||||
Ink sensitivity 墨迹灵敏度
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. 框内未找到签名。请将框移到签名上,或提高墨迹灵敏度。
|
||||
Couldn't use the photo: {e} 无法使用该照片:{e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab 关闭标签页
|
||||
Reopen Closed Tab 重新打开已关闭的标签页
|
||||
Reveal in Finder 在 Finder 中显示
|
||||
Reveal in File Explorer 在文件资源管理器中显示
|
||||
Other colour… 其他颜色…
|
||||
Recent colours 最近使用的颜色
|
||||
Couldn't follow linked PDF: {reason} 无法打开链接的 PDF:{reason}
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -2179,6 +2179,12 @@ animation effects Off 關
|
||||
Ink sensitivity 墨跡靈敏度
|
||||
No signature was found in the frame. Move the frame over the signature or raise the ink sensitivity. 框內找不到簽名。請將框移到簽名上,或提高墨跡靈敏度。
|
||||
Couldn't use the photo: {e} 無法使用此相片:{e}
|
||||
|
||||
# PDF tab context menu (#1233)
|
||||
Close Tab 關閉分頁
|
||||
Reopen Closed Tab 重新開啟已關閉的分頁
|
||||
Reveal in Finder 在 Finder 中顯示
|
||||
Reveal in File Explorer 在檔案總管中顯示
|
||||
Other colour… 其他顏色…
|
||||
Recent colours 最近使用的顏色
|
||||
Reading colours 閱讀色彩
|
||||
|
||||
|
Can't render this file because it contains an unexpected character in line 9 and column 16.
|
@@ -122,6 +122,7 @@ mod recovery;
|
||||
pub mod scroll_speed;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
mod system_fonts;
|
||||
mod tab_context;
|
||||
pub mod theme;
|
||||
pub mod ui_scale;
|
||||
pub mod updates;
|
||||
@@ -434,6 +435,7 @@ pub struct PdfCraftApp {
|
||||
pub(crate) pending_panel_reveals: Vec<docking::Panel>,
|
||||
pub session: Session,
|
||||
pub views: Vec<DocView>,
|
||||
pub(crate) closed_tabs: Vec<String>,
|
||||
/// `None` shows the Home tab.
|
||||
pub active: Option<usize>,
|
||||
pub mode: Mode,
|
||||
@@ -784,6 +786,7 @@ impl PdfCraftApp {
|
||||
Self {
|
||||
session: Session::new(),
|
||||
views: Vec::new(),
|
||||
closed_tabs: Vec::new(),
|
||||
active: None,
|
||||
mode: Mode::AllTools,
|
||||
default_mode: Mode::AllTools,
|
||||
@@ -1430,6 +1433,13 @@ impl PdfCraftApp {
|
||||
return;
|
||||
}
|
||||
let id = self.views.remove(index).id;
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Some(path) = self.session.get(id).and_then(|d| d.path.clone()) {
|
||||
if self.closed_tabs.len() == 10 {
|
||||
self.closed_tabs.remove(0);
|
||||
}
|
||||
self.closed_tabs.push(path);
|
||||
}
|
||||
self.forget_recovery(id);
|
||||
self.session.close(id);
|
||||
self.active = match self.active {
|
||||
|
||||
@@ -0,0 +1,275 @@
|
||||
//! PDF-tab actions. Closed history contains paths only, never bytes or passwords.
|
||||
use pdfcraft_engine::DocId;
|
||||
|
||||
pub(crate) enum Action {
|
||||
Close(DocId),
|
||||
Reopen,
|
||||
Reveal(DocId),
|
||||
}
|
||||
|
||||
impl crate::PdfCraftApp {
|
||||
pub(crate) fn tab_action(&mut self, action: Action) {
|
||||
match action {
|
||||
Action::Close(id) => {
|
||||
if let Some(i) = self.views.iter().position(|v| v.id == id) {
|
||||
self.request_close_tab(i);
|
||||
}
|
||||
}
|
||||
Action::Reopen => {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Some(path) = self.closed_tabs.last().cloned() {
|
||||
// A tab may already have been reopened through File > Open.
|
||||
if let Some(index) = self.views.iter().position(|v| self.session.get(v.id).is_some_and(|d| d.path.as_deref() == Some(&path))) {
|
||||
self.active = Some(index);
|
||||
self.closed_tabs.pop();
|
||||
} else {
|
||||
let tabs = self.views.len();
|
||||
self.open_path(&path);
|
||||
// open_path reports errors through notifications, not a Result.
|
||||
// Keep the entry until an actual document tab has been opened.
|
||||
if self.views.len() > tabs {
|
||||
self.closed_tabs.pop();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Action::Reveal(id) => {
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
if let Some(path) = self.session.get(id).and_then(|d| d.path.clone())
|
||||
&& let Err(e) = reveal(std::path::Path::new(&path))
|
||||
{
|
||||
self.notify_error(e);
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
let _ = id;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn reveal(path: &std::path::Path) -> Result<(), String> {
|
||||
// Absolute paths also prevent a filename starting with '-' becoming an option.
|
||||
let path = std::path::absolute(path).map_err(|e| e.to_string())?;
|
||||
if !path.try_exists().map_err(|e| e.to_string())? {
|
||||
return Err("The file no longer exists".into());
|
||||
}
|
||||
let platform = std::env::consts::OS;
|
||||
let run = |program: &str, args: &[std::ffi::OsString]| {
|
||||
std::process::Command::new(program).args(args).output().map(|o| o.status.success()).map_err(|e| e.to_string())
|
||||
};
|
||||
if matches!(platform, "linux" | "freebsd") {
|
||||
// `dbus-send --print-reply` waits up to 2 s for the file manager (then xdg-open may run),
|
||||
// so it runs off the UI thread; a failure there is only logged.
|
||||
let spawned = std::thread::Builder::new().name("pdfcraft-reveal".into()).spawn(move || {
|
||||
if let Err(e) = reveal_via(platform, &path, run) {
|
||||
log::warn!("reveal {}: {e}", path.display());
|
||||
}
|
||||
});
|
||||
return spawned.map(|_| ()).map_err(|e| e.to_string());
|
||||
}
|
||||
reveal_via(platform, &path, run)
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn reveal_via(
|
||||
platform: &str,
|
||||
path: &std::path::Path,
|
||||
mut run: impl FnMut(&str, &[std::ffi::OsString]) -> Result<bool, String>,
|
||||
) -> Result<(), String> {
|
||||
let (program, args) = reveal_command(platform, path)?;
|
||||
if run(program, &args).unwrap_or(false) {
|
||||
return Ok(());
|
||||
}
|
||||
if matches!(platform, "linux" | "freebsd") {
|
||||
// FileManager1 is optional. If absent, open the containing folder with xdg-open.
|
||||
let parent = path.parent().ok_or("The file has no parent directory")?;
|
||||
if run("xdg-open", &[parent.as_os_str().into()])? {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
Err("Could not reveal the file in the system file manager".into())
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
fn reveal_command(platform: &str, path: &std::path::Path) -> Result<(&'static str, Vec<std::ffi::OsString>), String> {
|
||||
match platform {
|
||||
"windows" => {
|
||||
// Explorer uses /select,<object>, rather than a separate option value.
|
||||
let mut selection = std::ffi::OsString::from("/select,");
|
||||
selection.push(path);
|
||||
Ok(("explorer.exe", vec![selection]))
|
||||
}
|
||||
"macos" => Ok(("open", vec!["-R".into(), path.as_os_str().into()])),
|
||||
"linux" | "freebsd" => {
|
||||
let mut uri = String::from("array:string:file://");
|
||||
for b in path.as_os_str().as_encoded_bytes() {
|
||||
if b.is_ascii_alphanumeric() || matches!(b, b'/' | b'-' | b'_' | b'.' | b'~') {
|
||||
uri.push(char::from(*b));
|
||||
} else {
|
||||
use std::fmt::Write as _;
|
||||
let _ = write!(uri, "%{b:02X}");
|
||||
}
|
||||
}
|
||||
Ok((
|
||||
"dbus-send",
|
||||
vec![
|
||||
"--session".into(),
|
||||
"--print-reply".into(),
|
||||
"--reply-timeout=2000".into(),
|
||||
"--dest=org.freedesktop.FileManager1".into(),
|
||||
"/org/freedesktop/FileManager1".into(),
|
||||
"org.freedesktop.FileManager1.ShowItems".into(),
|
||||
uri.into(),
|
||||
"string:".into(),
|
||||
],
|
||||
))
|
||||
}
|
||||
_ => Err("File reveal is not available on this platform".into()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(target_arch = "wasm32")))]
|
||||
mod tests {
|
||||
use super::*;
|
||||
const PDF: &[u8] = include_bytes!("../tests/data/form.pdf");
|
||||
|
||||
#[test]
|
||||
fn confirmed_close_uses_id_and_cancel_keeps_history_empty() {
|
||||
let mut app = crate::PdfCraftApp::new();
|
||||
app.open_bytes("first.pdf", Some("/tmp/first.pdf".into()), PDF.to_vec()).unwrap();
|
||||
let first = app.views[0].id;
|
||||
app.open_bytes("second.pdf", None, PDF.to_vec()).unwrap();
|
||||
app.session.apply(first, pdfcraft_engine::Edit::InsertBlankPage { at: 0, width: 300.0, height: 400.0 }).unwrap();
|
||||
app.tab_action(Action::Close(first));
|
||||
assert_eq!(app.views.len(), 2);
|
||||
assert!(app.closed_tabs.is_empty());
|
||||
app.resolve_close(&egui::Context::default(), None);
|
||||
assert!(app.closed_tabs.is_empty());
|
||||
app.tab_action(Action::Close(first));
|
||||
app.resolve_close(&egui::Context::default(), Some(false));
|
||||
assert_eq!(app.views.len(), 1);
|
||||
assert_eq!(app.closed_tabs, ["/tmp/first.pdf"]);
|
||||
assert_eq!(app.active, Some(0));
|
||||
app.close_tab(0);
|
||||
assert_eq!(app.closed_tabs.len(), 1, "pathless tabs are not recorded");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reopen_reads_saved_file_and_history_is_bounded() {
|
||||
use std::io::Write as _;
|
||||
let nonce = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos();
|
||||
let path = std::env::temp_dir().join(format!("pdfcraft-tab-{}-{nonce}-年度 report.pdf", std::process::id()));
|
||||
let mut file = std::fs::OpenOptions::new().write(true).create_new(true).open(&path).unwrap();
|
||||
file.write_all(PDF).unwrap();
|
||||
drop(file);
|
||||
let mut app = crate::PdfCraftApp::new();
|
||||
let path_text = path.to_str().unwrap();
|
||||
for _ in 0..12 {
|
||||
app.open_path(path_text);
|
||||
app.close_tab(0);
|
||||
}
|
||||
assert_eq!(app.closed_tabs.len(), 10);
|
||||
app.tab_action(Action::Reopen);
|
||||
assert_eq!(app.views.len(), 1);
|
||||
let doc = app.session.get(app.views[0].id).unwrap();
|
||||
assert_eq!(doc.path.as_deref(), Some(path_text));
|
||||
assert!(!doc.dirty);
|
||||
assert_eq!(app.closed_tabs.len(), 9);
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn failed_reopen_keeps_history_and_retry_does_not_duplicate_tabs() {
|
||||
use std::io::Write as _;
|
||||
let nonce = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).unwrap().as_nanos();
|
||||
let path = std::env::temp_dir().join(format!("pdfcraft-retry-{}-{nonce}.pdf", std::process::id()));
|
||||
let text = path.to_str().unwrap();
|
||||
let mut file = std::fs::OpenOptions::new().create_new(true).write(true).open(&path).unwrap();
|
||||
file.write_all(PDF).unwrap();
|
||||
drop(file);
|
||||
let mut app = crate::PdfCraftApp::new();
|
||||
app.open_path(text);
|
||||
app.close_tab(0);
|
||||
std::fs::remove_file(&path).unwrap();
|
||||
app.tab_action(Action::Reopen);
|
||||
assert!(app.views.is_empty());
|
||||
assert_eq!(app.closed_tabs, [text]);
|
||||
std::fs::write(&path, PDF).unwrap();
|
||||
app.tab_action(Action::Reopen);
|
||||
assert_eq!(app.views.len(), 1);
|
||||
assert!(app.closed_tabs.is_empty());
|
||||
app.closed_tabs.push(text.into());
|
||||
app.tab_action(Action::Reopen);
|
||||
assert_eq!(app.views.len(), 1, "an already-open file is selected, not duplicated");
|
||||
assert!(app.closed_tabs.is_empty());
|
||||
std::fs::remove_file(path).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unavailable_file_manager_falls_back_safely_and_reports_failure() {
|
||||
let path = std::path::Path::new("/tmp/年度 & reports/file.pdf");
|
||||
let mut calls = Vec::new();
|
||||
reveal_via("linux", path, |program, args| {
|
||||
calls.push((program.to_string(), args.to_vec()));
|
||||
if program == "dbus-send" { Err("FileManager1 unavailable".into()) } else { Ok(true) }
|
||||
})
|
||||
.unwrap();
|
||||
assert_eq!(calls.len(), 2);
|
||||
assert_eq!(calls[1], ("xdg-open".into(), vec![std::ffi::OsString::from("/tmp/年度 & reports")]));
|
||||
assert!(reveal_via("linux", path, |_, _| Ok(false)).is_err());
|
||||
assert!(reveal_via("linux", path, |_, _| Err("command missing".into())).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reveal_arguments_preserve_spaces_and_unicode() {
|
||||
let path = std::path::Path::new("/tmp/年度 report.pdf");
|
||||
let (exe, args) = reveal_command("macos", path).unwrap();
|
||||
assert_eq!(exe, "open");
|
||||
assert_eq!(args, [std::ffi::OsString::from("-R"), path.as_os_str().into()]);
|
||||
let (exe, args) = reveal_command("windows", path).unwrap();
|
||||
assert_eq!(exe, "explorer.exe");
|
||||
assert_eq!(args, [std::ffi::OsString::from("/select,/tmp/年度 report.pdf")]);
|
||||
let special = std::path::Path::new(r"C:\Reports\年度 report, #1 & (draft).pdf");
|
||||
let (_, windows) = reveal_command("windows", special).unwrap();
|
||||
assert_eq!(windows, [std::ffi::OsString::from(r"/select,C:\Reports\年度 report, #1 & (draft).pdf")]);
|
||||
let (exe, args) = reveal_command("linux", path).unwrap();
|
||||
assert_eq!(exe, "dbus-send");
|
||||
assert_eq!(args[6], "array:string:file:///tmp/%E5%B9%B4%E5%BA%A6%20report.pdf");
|
||||
let (_, args) = reveal_command("linux", std::path::Path::new("/tmp/年度 report,#&'$.pdf")).unwrap();
|
||||
assert_eq!(args[6], "array:string:file:///tmp/%E5%B9%B4%E5%BA%A6%20report%2C%23%26%27%24.pdf");
|
||||
assert_eq!(args[5], "org.freedesktop.FileManager1.ShowItems");
|
||||
assert_eq!(args[7], "string:");
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, not(target_arch = "wasm32")))]
|
||||
mod ui_tests {
|
||||
use crate::PdfCraftApp;
|
||||
use egui_kittest::{
|
||||
Harness,
|
||||
kittest::{NodeT, Queryable},
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn inactive_tab_menu_closes_target_and_disables_unavailable_actions() {
|
||||
let mut h = Harness::builder().with_size(egui::vec2(1400.0, 900.0)).build_eframe(|_| {
|
||||
let mut app = PdfCraftApp::new();
|
||||
app.set_option("language", "en").unwrap();
|
||||
for name in ["first.pdf", "second.pdf"] {
|
||||
app.open_bytes(name, None, include_bytes!("../tests/data/form.pdf").to_vec()).unwrap();
|
||||
}
|
||||
app
|
||||
});
|
||||
h.run_steps(8);
|
||||
h.get_by_role_and_label(egui::accesskit::Role::Tab, "first.pdf").click_secondary();
|
||||
h.run_steps(2);
|
||||
assert!(h.get_by_label("Reopen Closed Tab").accesskit_node().is_disabled());
|
||||
assert!(h.get_by_label("Reveal in File Explorer").accesskit_node().is_disabled());
|
||||
assert_eq!(h.state().active, Some(1));
|
||||
h.get_by_label("Close Tab").click();
|
||||
h.run_steps(3);
|
||||
assert_eq!(h.state().views.len(), 1);
|
||||
assert_eq!(h.state().session.get(h.state().views[0].id).unwrap().name, "second.pdf");
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user