From c3f2568c914237f39f03c76a0349f99b391f0447 Mon Sep 17 00:00:00 2001 From: KnifeLemon Date: Wed, 4 Nov 2026 13:08:18 +0900 Subject: [PATCH] Ask every time for the pages of a PDF preset (#981) A PDF dropped on a preset whose pages are set to "Ask every time" opens a small "Choose pages" window: it shows the page count, checks the range as it is typed (13-55, 1-3, 8, 12-15, 100-) and, for a PDF target, offers one PDF or one file per page. Enter starts it, Esc starts nothing. Several PDFs get a window each, so each can give its own chapter. - PdfOptions.AskPageRange, off by default: no settings migration. On for Split PDF in a new install only. - Preset editor: "Ask every time" under Pages (not for a merge). - PageRange.Check for ranges typed by hand, including other digits, dashes and commas other keyboards type. - filee convert --pages overrides the preset's range. - Strings in all 20 languages. Co-Authored-By: Claude Opus 5.5 --- src/Filee.App/Assets/i18n/ar.json | 11 + src/Filee.App/Assets/i18n/de.json | 11 + src/Filee.App/Assets/i18n/en.json | 11 + src/Filee.App/Assets/i18n/es.json | 11 + src/Filee.App/Assets/i18n/fr.json | 11 + src/Filee.App/Assets/i18n/hi.json | 11 + src/Filee.App/Assets/i18n/id.json | 11 + src/Filee.App/Assets/i18n/it.json | 11 + src/Filee.App/Assets/i18n/ja.json | 11 + src/Filee.App/Assets/i18n/ko.json | 11 + src/Filee.App/Assets/i18n/ne.json | 11 + src/Filee.App/Assets/i18n/pl.json | 11 + src/Filee.App/Assets/i18n/pt-BR.json | 11 + src/Filee.App/Assets/i18n/pt-PT.json | 11 + src/Filee.App/Assets/i18n/ru.json | 11 + src/Filee.App/Assets/i18n/th.json | 11 + src/Filee.App/Assets/i18n/tr.json | 11 + src/Filee.App/Assets/i18n/vi.json | 11 + src/Filee.App/Assets/i18n/zh-CN.json | 11 + src/Filee.App/Assets/i18n/zh-TW.json | 11 + src/Filee.App/Services/RadialController.cs | 89 ++++ src/Filee.App/Services/WindowService.cs | 44 ++ src/Filee.App/ViewModels/AskPagesViewModel.cs | 89 ++++ .../ViewModels/PresetEditorViewModel.cs | 17 +- src/Filee.App/Views/AskPagesWindow.axaml | 93 ++++ src/Filee.App/Views/AskPagesWindow.axaml.cs | 35 ++ src/Filee.App/Views/PresetEditorView.axaml | 7 +- src/Filee.Cli/Cli.cs | 3 + src/Filee.Cli/CliOptions.cs | 12 + src/Filee.Cli/Help.cs | 1 + src/Filee.Core/Conversion/PageRange.cs | 80 ++++ src/Filee.Core/Presets/AskedPages.cs | 48 ++ src/Filee.Core/Presets/BuiltInData.cs | 3 +- src/Filee.Core/Presets/Preset.cs | 9 + tests/Filee.App.Tests/AskPagesTests.cs | 420 ++++++++++++++++++ tests/Filee.Cli.Tests/CliTests.cs | 30 ++ tests/Filee.Core.Tests/AskedPagesTests.cs | 158 +++++++ 37 files changed, 1355 insertions(+), 3 deletions(-) create mode 100644 src/Filee.App/ViewModels/AskPagesViewModel.cs create mode 100644 src/Filee.App/Views/AskPagesWindow.axaml create mode 100644 src/Filee.App/Views/AskPagesWindow.axaml.cs create mode 100644 src/Filee.Core/Presets/AskedPages.cs create mode 100644 tests/Filee.App.Tests/AskPagesTests.cs create mode 100644 tests/Filee.Core.Tests/AskedPagesTests.cs diff --git a/src/Filee.App/Assets/i18n/ar.json b/src/Filee.App/Assets/i18n/ar.json index e65e9cb7c..194850d83 100644 --- a/src/Filee.App/Assets/i18n/ar.json +++ b/src/Filee.App/Assets/i18n/ar.json @@ -858,6 +858,17 @@ "presets.page_range": "الصفحات", "presets.page_range_hint": "كل الصفحات، أو مثلًا 1-3, 5", "presets.split": "ملف لكل صفحة", + "presets.ask_pages": "السؤال في كل مرة", + "presets.ask_pages_hint": "عند إفلات ملف PDF على شريط الأدوات الدائري، تُفتح نافذة صغيرة لاختيار صفحاته. تبدأ النافذة بالنطاق أعلاه، وهو أيضًا ما تستخدمه مجلدات المراقبة وسطر الأوامر.", + "pages.title": "اختيار الصفحات", + "pages.file_of": "الملف {0} من {1}", + "pages.count": "عدد صفحات ملف PDF هذا: {0}", + "pages.count_unknown": "تعذّر على Filee عدّ صفحات ملف PDF هذا.", + "pages.range_hint": "كل الصفحات، أو مثلًا 13-55 أو 1-3, 8, 12-15", + "pages.selected": "الصفحات المختارة: {0} من {1}", + "pages.invalid": "اكتب أرقام صفحات تبدأ من 1، مثل 13-55 أو 1-3, 8, 12-15.", + "pages.past_the_end": "ينتهي ملف PDF هذا عند الصفحة {0}.", + "pages.one_pdf": "ملف PDF واحد", "presets.pdf_tables_note": "من ملف PDF يصبح كل جدول مرسوم بخطوط ورقة مستقلة (ويبقى الجدول الممتد إلى الصفحة التالية ورقة واحدة)، وتصبح الأرقام والتواريخ قيمًا حقيقية. يُترك النص خارج الجداول.", "presets.pdf_compression": "الضغط", "presets.pdf_compression.None": "دون ضغط", diff --git a/src/Filee.App/Assets/i18n/de.json b/src/Filee.App/Assets/i18n/de.json index 23d4dd594..7db8635a1 100644 --- a/src/Filee.App/Assets/i18n/de.json +++ b/src/Filee.App/Assets/i18n/de.json @@ -858,6 +858,17 @@ "presets.page_range": "Seiten", "presets.page_range_hint": "Alle Seiten oder z. B. 1-3, 5", "presets.split": "Eine Datei pro Seite", + "presets.ask_pages": "Jedes Mal fragen", + "presets.ask_pages_hint": "Ziehst du eine PDF auf das Ringmenü, öffnet sich ein kleines Fenster, in dem du die Seiten auswählst. Es beginnt mit dem Bereich oben, den auch überwachte Ordner und die Befehlszeile verwenden.", + "pages.title": "Seiten auswählen", + "pages.file_of": "Datei {0} von {1}", + "pages.count": "Seiten in dieser PDF: {0}", + "pages.count_unknown": "Filee konnte die Seiten dieser PDF nicht zählen.", + "pages.range_hint": "Alle Seiten oder z. B. 13-55 oder 1-3, 8, 12-15", + "pages.selected": "Ausgewählte Seiten: {0} von {1}", + "pages.invalid": "Gib Seitenzahlen ab 1 ein, z. B. 13-55 oder 1-3, 8, 12-15.", + "pages.past_the_end": "Diese PDF endet mit Seite {0}.", + "pages.one_pdf": "Eine PDF", "presets.pdf_tables_note": "Jede Tabelle mit eingezeichneten Linien in einer PDF wird zu einem eigenen Tabellenblatt. Eine über mehrere Seiten fortgesetzte Tabelle bleibt auf einem Blatt. Zahlen und Datumsangaben bleiben echte Werte. Text außerhalb der Tabellen wird weggelassen.", "presets.pdf_compression": "Komprimieren", "presets.pdf_compression.None": "Nicht komprimieren", diff --git a/src/Filee.App/Assets/i18n/en.json b/src/Filee.App/Assets/i18n/en.json index 43c91786d..fbb60ebc3 100644 --- a/src/Filee.App/Assets/i18n/en.json +++ b/src/Filee.App/Assets/i18n/en.json @@ -873,6 +873,17 @@ "presets.page_range": "Pages", "presets.page_range_hint": "All pages, or e.g. 1-3, 5", "presets.split": "One file per page", + "presets.ask_pages": "Ask every time", + "presets.ask_pages_hint": "Dropping a PDF on the donut opens a small window to choose its pages. The range above is what the window starts with, and what watch folders and the command line use.", + "pages.title": "Choose pages", + "pages.file_of": "File {0} of {1}", + "pages.count": "Pages in this PDF: {0}", + "pages.count_unknown": "Filee couldn't count the pages of this PDF.", + "pages.range_hint": "All pages, or e.g. 13-55 or 1-3, 8, 12-15", + "pages.selected": "Pages chosen: {0} of {1}", + "pages.invalid": "Type page numbers from 1, like 13-55 or 1-3, 8, 12-15.", + "pages.past_the_end": "This PDF ends at page {0}.", + "pages.one_pdf": "One PDF", "presets.pdf_tables_note": "From a PDF, each table drawn with lines becomes its own sheet (a table that goes on over the next page stays one sheet), with numbers and dates as real values. Text outside the tables is left out.", "presets.pdf_compression": "Compress", "presets.pdf_compression.None": "Don't compress", diff --git a/src/Filee.App/Assets/i18n/es.json b/src/Filee.App/Assets/i18n/es.json index 280629611..74275fab8 100644 --- a/src/Filee.App/Assets/i18n/es.json +++ b/src/Filee.App/Assets/i18n/es.json @@ -858,6 +858,17 @@ "presets.page_range": "Páginas", "presets.page_range_hint": "Todas las páginas o, por ejemplo, 1-3, 5", "presets.split": "Un archivo por página", + "presets.ask_pages": "Preguntar cada vez", + "presets.ask_pages_hint": "Al soltar un PDF en el menú circular se abre una ventana pequeña para elegir sus páginas. Empieza con el rango de arriba, que también usan las carpetas supervisadas y la línea de comandos.", + "pages.title": "Elegir páginas", + "pages.file_of": "Archivo {0} de {1}", + "pages.count": "Páginas de este PDF: {0}", + "pages.count_unknown": "Filee no pudo contar las páginas de este PDF.", + "pages.range_hint": "Todas las páginas o, por ejemplo, 13-55 o 1-3, 8, 12-15", + "pages.selected": "Páginas elegidas: {0} de {1}", + "pages.invalid": "Escribe números de página desde 1, como 13-55 o 1-3, 8, 12-15.", + "pages.past_the_end": "Este PDF termina en la página {0}.", + "pages.one_pdf": "Un solo PDF", "presets.pdf_tables_note": "De un PDF, cada tabla dibujada con líneas pasa a su propia hoja (una tabla que sigue en la página siguiente queda en una sola hoja), con números y fechas como valores reales. El texto fuera de las tablas se omite.", "presets.pdf_compression": "Comprimir", "presets.pdf_compression.None": "No comprimir", diff --git a/src/Filee.App/Assets/i18n/fr.json b/src/Filee.App/Assets/i18n/fr.json index d691ebbaf..c1ac4dc1e 100644 --- a/src/Filee.App/Assets/i18n/fr.json +++ b/src/Filee.App/Assets/i18n/fr.json @@ -873,6 +873,17 @@ "presets.page_range": "Pages", "presets.page_range_hint": "Toutes les pages, ou par ex. 1-3, 5", "presets.split": "Un fichier par page", + "presets.ask_pages": "Demander à chaque fois", + "presets.ask_pages_hint": "Déposer un PDF sur le menu circulaire ouvre une petite fenêtre pour choisir ses pages. Elle s'ouvre avec la plage ci-dessus, qui sert aussi aux dossiers surveillés et à la ligne de commande.", + "pages.title": "Choisir les pages", + "pages.file_of": "Fichier {0} sur {1}", + "pages.count": "Pages de ce PDF : {0}", + "pages.count_unknown": "Filee n'a pas pu compter les pages de ce PDF.", + "pages.range_hint": "Toutes les pages, ou par ex. 13-55 ou 1-3, 8, 12-15", + "pages.selected": "Pages choisies : {0} sur {1}", + "pages.invalid": "Saisissez des numéros de page à partir de 1, comme 13-55 ou 1-3, 8, 12-15.", + "pages.past_the_end": "Ce PDF s'arrête à la page {0}.", + "pages.one_pdf": "Un seul PDF", "presets.pdf_tables_note": "D'un PDF, chaque tableau tracé avec des lignes devient une feuille (un tableau qui continue sur la page suivante reste une seule feuille), avec les nombres et les dates en vraies valeurs. Le texte hors des tableaux est omis.", "presets.pdf_compression": "Compresser", "presets.pdf_compression.None": "Ne pas compresser", diff --git a/src/Filee.App/Assets/i18n/hi.json b/src/Filee.App/Assets/i18n/hi.json index 91f9b7aac..050cae1d2 100644 --- a/src/Filee.App/Assets/i18n/hi.json +++ b/src/Filee.App/Assets/i18n/hi.json @@ -858,6 +858,17 @@ "presets.page_range": "पेज", "presets.page_range_hint": "सभी पेज, या जैसे 1-3, 5", "presets.split": "प्रति पेज एक फ़ाइल", + "presets.ask_pages": "हर बार पूछें", + "presets.ask_pages_hint": "डोनट टूलबार पर PDF छोड़ने पर पेज चुनने के लिए एक छोटी विंडो खुलती है। विंडो ऊपर दी गई रेंज से शुरू होती है, और वॉच फ़ोल्डर और कमांड लाइन भी यही रेंज इस्तेमाल करते हैं।", + "pages.title": "पेज चुनें", + "pages.file_of": "फ़ाइल {0} / {1}", + "pages.count": "इस PDF में पेज: {0}", + "pages.count_unknown": "Filee इस PDF के पेज नहीं गिन सका।", + "pages.range_hint": "सभी पेज, या जैसे 13-55 या 1-3, 8, 12-15", + "pages.selected": "चुने गए पेज: {0} / {1}", + "pages.invalid": "1 से शुरू होने वाले पेज नंबर लिखें, जैसे 13-55 या 1-3, 8, 12-15।", + "pages.past_the_end": "यह PDF पेज {0} पर खत्म होता है।", + "pages.one_pdf": "एक PDF", "presets.pdf_tables_note": "PDF से, रेखाओं से बनी हर तालिका अपनी अलग शीट बनती है (अगले पेज पर जारी तालिका एक ही शीट रहती है), और संख्याएँ व तारीखें असली मान बनती हैं। तालिकाओं के बाहर का टेक्स्ट छोड़ दिया जाता है।", "presets.pdf_compression": "कंप्रेस करें", "presets.pdf_compression.None": "कंप्रेस न करें", diff --git a/src/Filee.App/Assets/i18n/id.json b/src/Filee.App/Assets/i18n/id.json index 279df415d..2b07601a3 100644 --- a/src/Filee.App/Assets/i18n/id.json +++ b/src/Filee.App/Assets/i18n/id.json @@ -858,6 +858,17 @@ "presets.page_range": "Rentang halaman", "presets.page_range_hint": "Semua halaman, atau contoh: 1-3, 5", "presets.split": "Satu file per halaman", + "presets.ask_pages": "Tanyakan setiap kali", + "presets.ask_pages_hint": "Saat PDF dijatuhkan ke toolbar donut, jendela kecil terbuka untuk memilih halamannya. Jendela dimulai dengan rentang di atas, yang juga dipakai folder pantau dan baris perintah.", + "pages.title": "Pilih halaman", + "pages.file_of": "File {0} dari {1}", + "pages.count": "Jumlah halaman PDF ini: {0}", + "pages.count_unknown": "Filee tidak dapat menghitung halaman PDF ini.", + "pages.range_hint": "Semua halaman, atau contoh: 13-55 atau 1-3, 8, 12-15", + "pages.selected": "Halaman dipilih: {0} dari {1}", + "pages.invalid": "Ketik nomor halaman mulai dari 1, misalnya 13-55 atau 1-3, 8, 12-15.", + "pages.past_the_end": "PDF ini berakhir di halaman {0}.", + "pages.one_pdf": "Satu PDF", "presets.pdf_tables_note": "Dari sebuah PDF, setiap tabel yang digambar dengan garis pembatas menjadi lembar tersendiri (tabel yang berlanjut ke halaman berikutnya tetap menjadi satu lembar), dengan angka dan tanggal sebagai nilai asli. Teks di luar tabel diabaikan.", "presets.pdf_compression": "Kompresi", "presets.pdf_compression.None": "Jangan kompres", diff --git a/src/Filee.App/Assets/i18n/it.json b/src/Filee.App/Assets/i18n/it.json index 824b1cf8a..86b86db84 100644 --- a/src/Filee.App/Assets/i18n/it.json +++ b/src/Filee.App/Assets/i18n/it.json @@ -857,6 +857,17 @@ "presets.page_range": "Pagine", "presets.page_range_hint": "Tutte le pagine, oppure ad es. 1-3, 5", "presets.split": "Un file per pagina", +"presets.ask_pages": "Chiedi ogni volta", +"presets.ask_pages_hint": "Trascinando un PDF sulla barra donut si apre una piccola finestra per sceglierne le pagine. Parte dall'intervallo qui sopra, che usano anche le cartelle monitorate e la riga di comando.", +"pages.title": "Scegli le pagine", +"pages.file_of": "File {0} di {1}", +"pages.count": "Pagine di questo PDF: {0}", +"pages.count_unknown": "Filee non è riuscito a contare le pagine di questo PDF.", +"pages.range_hint": "Tutte le pagine, oppure ad es. 13-55 o 1-3, 8, 12-15", +"pages.selected": "Pagine scelte: {0} di {1}", +"pages.invalid": "Scrivi i numeri di pagina da 1, come 13-55 o 1-3, 8, 12-15.", +"pages.past_the_end": "Questo PDF finisce a pagina {0}.", +"pages.one_pdf": "Un solo PDF", "presets.pdf_tables_note": "Da un PDF, ogni tabella disegnata con linee diventa un foglio a sé (una tabella che continua nella pagina successiva resta un solo foglio), con numeri e date come valori veri. Il testo fuori dalle tabelle viene omesso.", "presets.pdf_compression": "Comprimi", "presets.pdf_compression.None": "Non comprimere", diff --git a/src/Filee.App/Assets/i18n/ja.json b/src/Filee.App/Assets/i18n/ja.json index 3cb7e576e..12d2249cc 100644 --- a/src/Filee.App/Assets/i18n/ja.json +++ b/src/Filee.App/Assets/i18n/ja.json @@ -858,6 +858,17 @@ "presets.page_range": "ページ", "presets.page_range_hint": "全ページ、または1-3, 5など", "presets.split": "1ページずつ別ファイルにする", + "presets.ask_pages": "毎回確認する", + "presets.ask_pages_hint": "ドーナツメニューにPDFをドロップすると、小さなウィンドウでページを選べます。上の範囲がウィンドウの初期値になり、監視フォルダーとコマンドラインではこの範囲が使われます。", + "pages.title": "ページの選択", + "pages.file_of": "ファイル {0}/{1}", + "pages.count": "このPDFのページ数: {0}", + "pages.count_unknown": "このPDFのページ数を数えられませんでした。", + "pages.range_hint": "全ページ、または13-55、1-3, 8, 12-15など", + "pages.selected": "選んだページ: {0} / {1}", + "pages.invalid": "13-55や1-3, 8, 12-15のように、1から始まるページ番号を入力してください。", + "pages.past_the_end": "このPDFは{0}ページまでです。", + "pages.one_pdf": "1つのPDFにする", "presets.pdf_tables_note": "PDF からは、罫線で描かれた表が 1 つずつシートになり(次のページに続く表は 1 つのシートのまま)、数値と日付は実際の値になります。表の外のテキストは含めません。", "presets.pdf_compression": "圧縮", "presets.pdf_compression.None": "圧縮しない", diff --git a/src/Filee.App/Assets/i18n/ko.json b/src/Filee.App/Assets/i18n/ko.json index 59b4bf87c..9ed04bc6e 100644 --- a/src/Filee.App/Assets/i18n/ko.json +++ b/src/Filee.App/Assets/i18n/ko.json @@ -873,6 +873,17 @@ "presets.page_range": "페이지", "presets.page_range_hint": "전체, 또는 예: 1-3, 5", "presets.split": "페이지마다 파일 하나", + "presets.ask_pages": "매번 묻기", + "presets.ask_pages_hint": "원형 툴바에 PDF를 놓으면 작은 창이 열려 페이지를 고를 수 있어요. 창은 위의 범위로 시작하고, 자동 변환 폴더와 명령줄은 이 범위를 그대로 써요.", + "pages.title": "페이지 선택", + "pages.file_of": "파일 {0}/{1}", + "pages.count": "이 PDF의 페이지 수: {0}", + "pages.count_unknown": "이 PDF의 페이지 수를 셀 수 없어요.", + "pages.range_hint": "전체, 또는 예: 13-55, 1-3, 8, 12-15", + "pages.selected": "고른 페이지: {0} / {1}", + "pages.invalid": "13-55나 1-3, 8, 12-15처럼 1부터 시작하는 페이지 번호를 입력해 주세요.", + "pages.past_the_end": "이 PDF는 {0}페이지까지 있어요.", + "pages.one_pdf": "PDF 하나로", "presets.pdf_tables_note": "PDF에서는 선으로 그린 표가 하나씩 시트가 되고(다음 페이지로 이어지는 표는 한 시트로 이어져요), 숫자와 날짜는 실제 값이 돼요. 표 밖의 텍스트는 빠져요.", "presets.pdf_compression": "압축", "presets.pdf_compression.None": "압축 안 함", diff --git a/src/Filee.App/Assets/i18n/ne.json b/src/Filee.App/Assets/i18n/ne.json index 656c8010c..7182971d3 100644 --- a/src/Filee.App/Assets/i18n/ne.json +++ b/src/Filee.App/Assets/i18n/ne.json @@ -858,6 +858,17 @@ "presets.page_range": "पृष्ठहरू", "presets.page_range_hint": "सबै पृष्ठहरू, वा जस्तै १-३, ५", "presets.split": "प्रति पृष्ठ एउटा फाइल", + "presets.ask_pages": "हरेक पटक सोध्नुहोस्", + "presets.ask_pages_hint": "डोनट टुलबारमा PDF छोड्दा पृष्ठ छान्न एउटा सानो विन्डो खुल्छ। विन्डो माथिको दायराबाट सुरु हुन्छ, र वाच फोल्डरहरू र कमान्ड लाइनले पनि यही दायरा प्रयोग गर्छन्।", + "pages.title": "पृष्ठ छान्नुहोस्", + "pages.file_of": "फाइल {0} / {1}", + "pages.count": "यो PDF मा पृष्ठ: {0}", + "pages.count_unknown": "Filee ले यो PDF का पृष्ठ गन्न सकेन।", + "pages.range_hint": "सबै पृष्ठहरू, वा जस्तै १३-५५ वा १-३, ८, १२-१५", + "pages.selected": "छानिएका पृष्ठ: {0} / {1}", + "pages.invalid": "१ बाट सुरु हुने पृष्ठ नम्बर लेख्नुहोस्, जस्तै १३-५५ वा १-३, ८, १२-१५।", + "pages.past_the_end": "यो PDF पृष्ठ {0} मा सकिन्छ।", + "pages.one_pdf": "एउटै PDF", "presets.pdf_tables_note": "PDF बाट, रेखाले बनाइएको हरेक तालिका आफ्नै शिट बन्छ (अर्को पेजमा जारी तालिका एउटै शिट रहन्छ), र अङ्क र मितिहरू वास्तविक मान बन्छन्। तालिकाबाहिरको पाठ छोडिन्छ।", "presets.pdf_compression": "कम्प्रेस गर्नुहोस्", "presets.pdf_compression.None": "कम्प्रेस नगर्नुहोस्", diff --git a/src/Filee.App/Assets/i18n/pl.json b/src/Filee.App/Assets/i18n/pl.json index 2412a143f..be30d555b 100644 --- a/src/Filee.App/Assets/i18n/pl.json +++ b/src/Filee.App/Assets/i18n/pl.json @@ -873,6 +873,17 @@ "presets.page_range": "Strony", "presets.page_range_hint": "Wszystkie strony lub np. 1-3, 5", "presets.split": "Jeden plik na stronę", + "presets.ask_pages": "Pytaj za każdym razem", + "presets.ask_pages_hint": "Upuszczenie pliku PDF na pasek-pierścień otwiera małe okno wyboru stron. Startuje ono z zakresem podanym wyżej, którego używają też obserwowane foldery i wiersz poleceń.", + "pages.title": "Wybierz strony", + "pages.file_of": "Plik {0} z {1}", + "pages.count": "Liczba stron w tym PDF: {0}", + "pages.count_unknown": "Filee nie może policzyć stron tego pliku PDF.", + "pages.range_hint": "Wszystkie strony lub np. 13-55 albo 1-3, 8, 12-15", + "pages.selected": "Wybrane strony: {0} z {1}", + "pages.invalid": "Wpisz numery stron od 1, np. 13-55 albo 1-3, 8, 12-15.", + "pages.past_the_end": "Ten PDF kończy się na stronie {0}.", + "pages.one_pdf": "Jeden PDF", "presets.pdf_tables_note": "Z PDF każda tabela narysowana liniami trafia na osobny arkusz (tabela ciągnąca się na następną stronę zostaje jednym arkuszem), a liczby i daty stają się prawdziwymi wartościami. Tekst poza tabelami jest pomijany.", "presets.pdf_compression": "Kompresja", "presets.pdf_compression.None": "Bez kompresji", diff --git a/src/Filee.App/Assets/i18n/pt-BR.json b/src/Filee.App/Assets/i18n/pt-BR.json index f5bdbb9fa..67f5b484e 100644 --- a/src/Filee.App/Assets/i18n/pt-BR.json +++ b/src/Filee.App/Assets/i18n/pt-BR.json @@ -873,6 +873,17 @@ "presets.page_range": "Páginas", "presets.page_range_hint": "Todas as páginas, ou por exemplo 1-3, 5", "presets.split": "Um arquivo por página", + "presets.ask_pages": "Perguntar sempre", + "presets.ask_pages_hint": "Ao soltar um PDF no menu circular, abre uma janela pequena para escolher as páginas. Ela começa com o intervalo acima, que também é usado pelas pastas monitoradas e pela linha de comando.", + "pages.title": "Escolher páginas", + "pages.file_of": "Arquivo {0} de {1}", + "pages.count": "Páginas deste PDF: {0}", + "pages.count_unknown": "O Filee não conseguiu contar as páginas deste PDF.", + "pages.range_hint": "Todas as páginas, ou por exemplo 13-55 ou 1-3, 8, 12-15", + "pages.selected": "Páginas escolhidas: {0} de {1}", + "pages.invalid": "Digite números de página a partir de 1, como 13-55 ou 1-3, 8, 12-15.", + "pages.past_the_end": "Este PDF termina na página {0}.", + "pages.one_pdf": "Um só PDF", "presets.pdf_tables_note": "De um PDF, cada tabela desenhada com linhas vira uma planilha própria (uma tabela que continua na página seguinte fica em uma só planilha), com números e datas como valores reais. O texto fora das tabelas fica de fora.", "presets.pdf_compression": "Comprimir", "presets.pdf_compression.None": "Não comprimir", diff --git a/src/Filee.App/Assets/i18n/pt-PT.json b/src/Filee.App/Assets/i18n/pt-PT.json index 56fb31491..65980e216 100644 --- a/src/Filee.App/Assets/i18n/pt-PT.json +++ b/src/Filee.App/Assets/i18n/pt-PT.json @@ -873,6 +873,17 @@ "presets.page_range": "Páginas", "presets.page_range_hint": "Todas as páginas, ou, por exemplo, 1-3, 5", "presets.split": "Um ficheiro por página", + "presets.ask_pages": "Perguntar sempre", + "presets.ask_pages_hint": "Ao largar um PDF na barra circular, abre-se uma pequena janela para escolher as páginas. Começa com o intervalo acima, que também é usado pelas pastas monitorizadas e pela linha de comandos.", + "pages.title": "Escolher páginas", + "pages.file_of": "Ficheiro {0} de {1}", + "pages.count": "Páginas deste PDF: {0}", + "pages.count_unknown": "O Filee não conseguiu contar as páginas deste PDF.", + "pages.range_hint": "Todas as páginas, ou, por exemplo, 13-55 ou 1-3, 8, 12-15", + "pages.selected": "Páginas escolhidas: {0} de {1}", + "pages.invalid": "Escreve números de página a partir de 1, como 13-55 ou 1-3, 8, 12-15.", + "pages.past_the_end": "Este PDF termina na página {0}.", + "pages.one_pdf": "Um só PDF", "presets.pdf_tables_note": "A partir de um PDF, cada tabela delimitada por linhas ocupa uma folha (uma tabela que continua na página seguinte mantém-se na mesma folha), com valores numéricos e de data. O texto fora das tabelas é omitido.", "presets.pdf_compression": "Comprimir", "presets.pdf_compression.None": "Não comprimir", diff --git a/src/Filee.App/Assets/i18n/ru.json b/src/Filee.App/Assets/i18n/ru.json index 2c701edb3..a57001a7d 100644 --- a/src/Filee.App/Assets/i18n/ru.json +++ b/src/Filee.App/Assets/i18n/ru.json @@ -873,6 +873,17 @@ "presets.page_range": "Страницы", "presets.page_range_hint": "Все страницы или, например, 1-3, 5", "presets.split": "По файлу на страницу", + "presets.ask_pages": "Спрашивать каждый раз", + "presets.ask_pages_hint": "Если перетащить PDF на круговое меню, откроется небольшое окно для выбора страниц. В нём сразу стоит диапазон, указанный выше; его же используют папки наблюдения и командная строка.", + "pages.title": "Выбор страниц", + "pages.file_of": "Файл {0} из {1}", + "pages.count": "Страниц в этом PDF: {0}", + "pages.count_unknown": "Filee не удалось посчитать страницы этого PDF.", + "pages.range_hint": "Все страницы или, например, 13-55 или 1-3, 8, 12-15", + "pages.selected": "Выбрано страниц: {0} из {1}", + "pages.invalid": "Введите номера страниц начиная с 1, например 13-55 или 1-3, 8, 12-15.", + "pages.past_the_end": "В этом PDF последняя страница — {0}.", + "pages.one_pdf": "Один PDF", "presets.pdf_tables_note": "Из PDF каждая таблица, нарисованная линиями, становится отдельным листом (таблица, продолжающаяся на следующей странице, остаётся одним листом), а числа и даты — настоящими значениями. Текст вне таблиц не сохраняется.", "presets.pdf_compression": "Сжатие", "presets.pdf_compression.None": "Не сжимать", diff --git a/src/Filee.App/Assets/i18n/th.json b/src/Filee.App/Assets/i18n/th.json index c16961f73..1e6be66e1 100644 --- a/src/Filee.App/Assets/i18n/th.json +++ b/src/Filee.App/Assets/i18n/th.json @@ -858,6 +858,17 @@ "presets.page_range": "หน้า", "presets.page_range_hint": "ทุกหน้า หรือระบุ เช่น 1-3, 5", "presets.split": "แยกหนึ่งไฟล์ต่อหนึ่งหน้า", + "presets.ask_pages": "ถามทุกครั้ง", + "presets.ask_pages_hint": "เมื่อวาง PDF บนแถบโดนัท หน้าต่างเล็ก ๆ จะเปิดขึ้นให้เลือกหน้า โดยเริ่มจากช่วงหน้าด้านบน ซึ่งโฟลเดอร์เฝ้าติดตามและบรรทัดคำสั่งก็ใช้ช่วงนี้เช่นกัน", + "pages.title": "เลือกหน้า", + "pages.file_of": "ไฟล์ {0} จาก {1}", + "pages.count": "จำนวนหน้าใน PDF นี้: {0}", + "pages.count_unknown": "Filee นับจำนวนหน้าของ PDF นี้ไม่ได้", + "pages.range_hint": "ทุกหน้า หรือระบุ เช่น 13-55 หรือ 1-3, 8, 12-15", + "pages.selected": "หน้าที่เลือก: {0} จาก {1}", + "pages.invalid": "พิมพ์เลขหน้าตั้งแต่ 1 เช่น 13-55 หรือ 1-3, 8, 12-15", + "pages.past_the_end": "PDF นี้มีถึงหน้า {0}", + "pages.one_pdf": "PDF ไฟล์เดียว", "presets.pdf_tables_note": "จาก PDF ตารางที่วาดด้วยเส้นแต่ละตารางจะเป็นชีตของตัวเอง (ตารางที่ต่อไปยังหน้าถัดไปยังคงเป็นชีตเดียว) และตัวเลขกับวันที่จะเป็นค่าจริง ข้อความนอกตารางจะถูกละไว้", "presets.pdf_compression": "บีบอัด", "presets.pdf_compression.None": "ไม่บีบอัด", diff --git a/src/Filee.App/Assets/i18n/tr.json b/src/Filee.App/Assets/i18n/tr.json index 85be7052d..346640b7d 100644 --- a/src/Filee.App/Assets/i18n/tr.json +++ b/src/Filee.App/Assets/i18n/tr.json @@ -858,6 +858,17 @@ "presets.page_range": "Sayfalar", "presets.page_range_hint": "Tüm sayfalar veya ör. 1-3, 5", "presets.split": "Sayfa başına bir dosya", + "presets.ask_pages": "Her seferinde sor", + "presets.ask_pages_hint": "Halka araç çubuğuna bir PDF bıraktığınızda sayfaları seçmek için küçük bir pencere açılır. Pencere yukarıdaki aralıkla açılır; izlenen klasörler ve komut satırı da bu aralığı kullanır.", + "pages.title": "Sayfaları seçin", + "pages.file_of": "Dosya {0}/{1}", + "pages.count": "Bu PDF'deki sayfa sayısı: {0}", + "pages.count_unknown": "Filee bu PDF'nin sayfalarını sayamadı.", + "pages.range_hint": "Tüm sayfalar veya ör. 13-55 ya da 1-3, 8, 12-15", + "pages.selected": "Seçilen sayfalar: {0}/{1}", + "pages.invalid": "13-55 ya da 1-3, 8, 12-15 gibi 1'den başlayan sayfa numaraları yazın.", + "pages.past_the_end": "Bu PDF {0}. sayfada bitiyor.", + "pages.one_pdf": "Tek PDF", "presets.pdf_tables_note": "PDF'deki çizgilerle oluşturulmuş her tablo ayrı çalışma sayfası olur (sonraki sayfada devam eden tablo aynı çalışma sayfasında kalır); sayı ve tarihler gerçek değerler olarak tutulur. Tabloların dışındaki metin çıkarılır.", "presets.pdf_compression": "Sıkıştır", "presets.pdf_compression.None": "Sıkıştırma", diff --git a/src/Filee.App/Assets/i18n/vi.json b/src/Filee.App/Assets/i18n/vi.json index 7cdafa4c8..ee618705a 100644 --- a/src/Filee.App/Assets/i18n/vi.json +++ b/src/Filee.App/Assets/i18n/vi.json @@ -873,6 +873,17 @@ "presets.page_range": "Trang", "presets.page_range_hint": "Tất cả các trang, hoặc ví dụ 1-3, 5", "presets.split": "Mỗi trang một tệp", + "presets.ask_pages": "Luôn hỏi", + "presets.ask_pages_hint": "Khi thả một tệp PDF lên thanh công cụ vòng tròn, một cửa sổ nhỏ sẽ mở ra để chọn trang. Cửa sổ bắt đầu với phạm vi ở trên, và thư mục theo dõi cùng dòng lệnh cũng dùng phạm vi này.", + "pages.title": "Chọn trang", + "pages.file_of": "Tệp {0}/{1}", + "pages.count": "Số trang của PDF này: {0}", + "pages.count_unknown": "Filee không đếm được số trang của PDF này.", + "pages.range_hint": "Tất cả các trang, hoặc ví dụ 13-55 hay 1-3, 8, 12-15", + "pages.selected": "Trang đã chọn: {0}/{1}", + "pages.invalid": "Hãy nhập số trang từ 1, ví dụ 13-55 hoặc 1-3, 8, 12-15.", + "pages.past_the_end": "PDF này chỉ có đến trang {0}.", + "pages.one_pdf": "Một tệp PDF", "presets.pdf_tables_note": "Từ PDF, mỗi bảng kẻ bằng đường kẻ thành một trang tính riêng (bảng kéo dài sang trang sau vẫn là một trang tính), số và ngày là giá trị thực. Văn bản bên ngoài bảng bị bỏ qua.", "presets.pdf_compression": "Nén", "presets.pdf_compression.None": "Không nén", diff --git a/src/Filee.App/Assets/i18n/zh-CN.json b/src/Filee.App/Assets/i18n/zh-CN.json index 3575d4b2e..f06c52b05 100644 --- a/src/Filee.App/Assets/i18n/zh-CN.json +++ b/src/Filee.App/Assets/i18n/zh-CN.json @@ -873,6 +873,17 @@ "presets.page_range": "页面", "presets.page_range_hint": "全部,或如 1-3, 5", "presets.split": "每页一个文件", + "presets.ask_pages": "每次询问", + "presets.ask_pages_hint": "将 PDF 拖放到环形工具栏上时,会打开一个小窗口来选择页面。窗口以上方的范围开始,自动转换文件夹和命令行也使用这个范围。", + "pages.title": "选择页面", + "pages.file_of": "第 {0} 个文件,共 {1} 个", + "pages.count": "此 PDF 的页数:{0}", + "pages.count_unknown": "Filee 无法统计此 PDF 的页数。", + "pages.range_hint": "全部,或如 13-55 或 1-3, 8, 12-15", + "pages.selected": "已选页面:{0} / {1}", + "pages.invalid": "请输入从 1 开始的页码,例如 13-55 或 1-3, 8, 12-15。", + "pages.past_the_end": "此 PDF 只到第 {0} 页。", + "pages.one_pdf": "合为一个 PDF", "presets.pdf_tables_note": "从 PDF 转换时,每个用线条画出的表格成为一个工作表(续到下一页的表格仍为一个工作表),数字和日期会成为真正的数值。表格外的文字不包含在内。", "presets.pdf_compression": "压缩", "presets.pdf_compression.None": "不压缩", diff --git a/src/Filee.App/Assets/i18n/zh-TW.json b/src/Filee.App/Assets/i18n/zh-TW.json index b907b11de..a7d85e453 100644 --- a/src/Filee.App/Assets/i18n/zh-TW.json +++ b/src/Filee.App/Assets/i18n/zh-TW.json @@ -873,6 +873,17 @@ "presets.page_range": "頁面", "presets.page_range_hint": "全部,或如 1-3, 5", "presets.split": "每頁一個檔案", + "presets.ask_pages": "每次詢問", + "presets.ask_pages_hint": "將 PDF 拖放到環形工具列上時,會開啟一個小視窗來選擇頁面。視窗以上方的範圍開始,自動轉換資料夾和命令列也使用這個範圍。", + "pages.title": "選擇頁面", + "pages.file_of": "第 {0} 個檔案,共 {1} 個", + "pages.count": "此 PDF 的頁數:{0}", + "pages.count_unknown": "Filee 無法計算此 PDF 的頁數。", + "pages.range_hint": "全部,或如 13-55 或 1-3, 8, 12-15", + "pages.selected": "已選頁面:{0} / {1}", + "pages.invalid": "請輸入從 1 開始的頁碼,例如 13-55 或 1-3, 8, 12-15。", + "pages.past_the_end": "此 PDF 只到第 {0} 頁。", + "pages.one_pdf": "合為一個 PDF", "presets.pdf_tables_note": "從 PDF 轉換時,每個以線條繪製的表格會成為一個工作表(延續到下一頁的表格仍為一個工作表),數字與日期會成為真正的數值。表格外的文字不包含在內。", "presets.pdf_compression": "壓縮", "presets.pdf_compression.None": "不壓縮", diff --git a/src/Filee.App/Services/RadialController.cs b/src/Filee.App/Services/RadialController.cs index 01b741c8d..9bbc87c0b 100644 --- a/src/Filee.App/Services/RadialController.cs +++ b/src/Filee.App/Services/RadialController.cs @@ -380,6 +380,16 @@ private void Convert(int index, int sub, IReadOnlyList files, WebDrop? w return; } + // "Ask every time" for the pages (#981): a small window per PDF first, then the save dialog when that asks too; the + // toast shows the jobs, the donut has closed for the window. + if (AskedPages.Asks(preset) && sources.Any(AskedPages.IsPdf)) + { + KeepKeyboard(); // for the pages window + Close(animate: false); + _ = AskPagesThenConvertAsync(sources, preset, web, _toClipboard); + return; + } + // "Ask every time" (#521): the save dialog first; the toast shows the job, the donut has closed for the dialog. // shortcut: files from a browser aren't asked about, they save into the download folder as "next to the source". if (preset.Output.AskEveryTime && (web is null || preset.IsCopyToFolder)) @@ -454,6 +464,85 @@ private void RememberMergeLayout(Preset chosen) } } + /// + /// "Choose pages" (#981): one window per PDF, in the order dropped, each with its own page count, so two PDFs can + /// give two different chapters. Cancelling any of them starts nothing. PDFs given the same pages convert as one + /// job; other files dropped with them convert as the preset says. Files from a browser are saved first, as for a + /// tool: the window counts the pages of the PDF itself. + /// + private async Task AskPagesThenConvertAsync(IReadOnlyList files, Preset preset, WebDrop? web, bool toClipboard) + { + try + { + if (web is not null) + { + var drop = web; + web = null; // saving it disposes it + files = await conversions.SaveBrowserFilesAsync(drop, (placeholder, ex) => + log.LogWarning(ex, "Saving {File} from a browser for {Preset} failed", Path.GetFileName(placeholder), preset.Id)); + } + var pdfs = files.Where(AskedPages.IsPdf).ToList(); + var chosen = new List<(string File, PageChoice Choice)>(); + var asked = preset; + for (var i = 0; i < pdfs.Count; i++) + { + if (await windows.AskPagesAsync(pdfs[i], asked, i, pdfs.Count) is not { } choice) + { + log.LogInformation("Choosing the pages for {Preset} was cancelled; nothing converts", preset.Id); + return; + } + chosen.Add((pdfs[i], choice)); + // The next PDF's window starts with this one's choice of one PDF or one file per page. + asked = AskedPages.Apply(preset, preset.Pdf.PageRange, choice.SplitPages); + } + if (chosen.Count == 0) + return; + RememberSplitChoice(preset, chosen[^1].Choice); + + var runs = new List<(IReadOnlyList Files, Preset Preset)>(); + var others = files.Where(file => !AskedPages.IsPdf(file)).ToList(); + if (others.Count > 0) + runs.Add((others, preset)); + runs.AddRange(chosen.GroupBy(c => c.Choice).Select(group => + ((IReadOnlyList)[.. group.Select(c => c.File)], AskedPages.Apply(preset, group.Key.Range, group.Key.SplitPages)))); + log.LogInformation("Converting {Count} PDF(s) with preset {Preset} on the pages chosen", pdfs.Count, preset.Id); + foreach (var (runFiles, runPreset) in runs) + { + if (runPreset.Output.AskEveryTime) + await AskThenConvertAsync(runFiles, runPreset, toClipboard); + else + conversions.Start(runFiles, runPreset, toClipboard: toClipboard); + } + } + catch (Exception ex) when (ex is not OutOfMemoryException) + { + log.LogError(ex, "Could not convert on the pages chosen"); + } + finally + { + web?.Dispose(); + } + } + + /// + /// "Choose pages": one PDF or one file per page, as chosen last (), goes into the preset as + /// saved, so the window starts with it next time (as "Merge images" keeps its layout). The range is not kept. + /// + private void RememberSplitChoice(Preset preset, PageChoice chosen) + { + if (!AskedPages.OffersSplit(preset) || store.FindPreset(preset.Id) is not { } saved || saved.Pdf.SplitPages == chosen.SplitPages) + return; + saved.Pdf.SplitPages = chosen.SplitPages; + try + { + store.SaveLibrary(); + } + catch (Exception ex) when (ex is IOException or UnauthorizedAccessException) + { + log.LogWarning(ex, "Could not remember one PDF or one file per page"); + } + } + private async Task AskThenConvertAsync(IReadOnlyList sources, Preset preset, bool toClipboard, WebDrop? web = null) { try diff --git a/src/Filee.App/Services/WindowService.cs b/src/Filee.App/Services/WindowService.cs index 5c48e9734..debc92d3e 100644 --- a/src/Filee.App/Services/WindowService.cs +++ b/src/Filee.App/Services/WindowService.cs @@ -91,6 +91,50 @@ public sealed class WindowService(ILocalizer loc, IServiceProvider services, Mem return dialog.Result; } + private AskPagesWindow? _askPages; + + internal AskPagesWindow? AskPages => _askPages; + + /// + /// "Choose pages" (#981) for one PDF dropped on : its page count (read first; none when the + /// file can't be read, then only the form of the range is checked), the range and, for a PDF target, one PDF or one + /// file per page. of PDFs dropped together. Null when cancelled. + /// + public async Task AskPagesAsync(string path, Preset preset, int index = 0, int total = 1) + { + _askPages?.Close(); + var count = await Task.Run(() => CountPages(path)); + var vm = new AskPagesViewModel(path, count, preset, loc, index, total); + var dialog = new AskPagesWindow { DataContext = vm }; + dialog.StartPages.Content = loc.DisplayName(preset); + PrepareWindow(dialog); + var closed = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + dialog.Closed += (_, _) => + { + if (_askPages == dialog) _askPages = null; + closed.TrySetResult(); + }; + _askPages = dialog; + dialog.Show(); + dialog.Activate(); + await closed.Task; + return dialog.Result; + } + + /// The pages of a PDF, or null when it can't be read (damaged, locked with a password, gone). + internal int? CountPages(string path) + { + try + { + return Filee.Engines.Pdf.PdfPageTools.PageCount(path); + } + catch (Exception ex) when (ex is not OutOfMemoryException) + { + services.GetService>()?.LogInformation(ex, "Could not count the pages of {File}", Path.GetFileName(path)); + return null; + } + } + /// /// "Ask every time" (#521): one save dialog for the batch, starting in the first source's folder. One result is /// saved under a name (), several go into a folder. Null when cancelled. diff --git a/src/Filee.App/ViewModels/AskPagesViewModel.cs b/src/Filee.App/ViewModels/AskPagesViewModel.cs new file mode 100644 index 000000000..6c784049e --- /dev/null +++ b/src/Filee.App/ViewModels/AskPagesViewModel.cs @@ -0,0 +1,89 @@ +// "Choose pages" (#981): the pages of one PDF dropped on a preset that asks for them (PdfOptions.AskPageRange), and for +// a PDF target whether they become one PDF or one file per page. The range is checked as it is typed. + +using CommunityToolkit.Mvvm.ComponentModel; +using Filee.Core.Conversion; +using Filee.Core.Localization; +using Filee.Core.Presets; + +namespace Filee.App.ViewModels; + +/// What the user chose: the range as it is used (empty for every page) and one file per page. +public sealed record PageChoice(string Range, bool SplitPages); + +public sealed partial class AskPagesViewModel : ObservableObject +{ + private readonly ILocalizer _loc; + + /// The PDF. + /// Its pages; null when they couldn't be counted (the range is then only checked for form). + /// The preset dropped on: its range and its one-file-per-page start the window. + /// Which of the PDFs dropped together this is, from 0. + /// How many PDFs were dropped together. + public AskPagesViewModel(string path, int? pageCount, Preset preset, ILocalizer loc, int index = 0, int total = 1) + { + _loc = loc; + FileName = Path.GetFileName(path); + PageCount = pageCount; + OffersSplit = AskedPages.OffersSplit(preset); + _splitPages = OffersSplit && preset.Pdf.SplitPages; + _range = preset.Pdf.PageRange; + Position = total > 1 ? loc.Format("pages.file_of", index + 1, total) : ""; + PageCountText = pageCount is { } count ? loc.Format("pages.count", count) : loc["pages.count_unknown"]; + Update(); + } + + public string FileName { get; } + public int? PageCount { get; } + + /// "File 2 of 3" when several PDFs were dropped together; empty for one. + public string Position { get; } + public bool ShowsPosition => Position.Length > 0; + + public string PageCountText { get; } + + /// One PDF or one file per page is offered (AskedPages.OffersSplit): a PDF target. + public bool OffersSplit { get; } + + [ObservableProperty] + private string _range; + + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(OnePdf))] + private bool _splitPages; + + /// The other choice of the switch: the pages stay together in one PDF. + public bool OnePdf + { + get => !SplitPages; + set => SplitPages = !value; + } + + /// The range checked; the button starts nothing while it isn't valid. + [ObservableProperty] + [NotifyPropertyChangedFor(nameof(IsValid))] + private PageRangeCheck _check; + + public bool IsValid => Check.IsValid; + + /// Below the box: how many pages the range takes, or what is wrong with it. + [ObservableProperty] + private string _status = ""; + + partial void OnRangeChanged(string value) => Update(); + + private void Update() + { + Check = PageRange.Check(Range, PageCount); + Status = Check.Problem switch + { + PageRangeProblem.Invalid => _loc["pages.invalid"], + PageRangeProblem.PastTheEnd => _loc.Format("pages.past_the_end", PageCount ?? 0), + _ when Check.Pages is { } pages && PageCount is { } count => _loc.Format("pages.selected", pages, count), + _ => "", + }; + } + + /// The choice, or null while the range isn't valid. + public PageChoice? Result() => IsValid ? new PageChoice(Check.Text, OffersSplit && SplitPages) : null; +} diff --git a/src/Filee.App/ViewModels/PresetEditorViewModel.cs b/src/Filee.App/ViewModels/PresetEditorViewModel.cs index 6802edcbd..edec844dd 100644 --- a/src/Filee.App/ViewModels/PresetEditorViewModel.cs +++ b/src/Filee.App/ViewModels/PresetEditorViewModel.cs @@ -283,6 +283,7 @@ .. WhisperLanguages.All _marginMm = _edit.Pdf.MarginMm; _renderDpi = _edit.Pdf.RenderDpi; _pageRange = _edit.Pdf.PageRange; + _askPageRange = _edit.Pdf.AskPageRange; _splitPages = _edit.Pdf.SplitPages; _combinePages = _edit.Pdf.CombinePages; _trimPageMargins = _edit.Pdf.TrimPageMargins; @@ -405,6 +406,7 @@ public async Task TestTranslationAsync() [NotifyPropertyChangedFor(nameof(ShowImage), nameof(ShowTurnForPdf), nameof(ShowQuality), nameof(ShowTiff), nameof(ShowMultiPageTiff), nameof(ShowWebp), nameof(ShowIco), nameof(ShowFitsTiff), nameof(ShowFitsDebayer), nameof(ShowZipResult), nameof(ShowPdf), nameof(ShowPdfBuild), nameof(ShowDocument), nameof(ShowLeaveOutPictures), nameof(ShowPdfSplit), nameof(ShowPdfCombine), + nameof(ShowAskPages), nameof(ShowAskPagesHint), nameof(ShowPdfCompression), nameof(ShowPdfCompressionOptions), nameof(ShowSheets), nameof(ShowOcr), nameof(ShowPageSize), nameof(ShowPdfTablesNote), nameof(ShowMedia), nameof(ShowMediaQuality), nameof(ShowMaxHeight), nameof(ShowVideoMaxSize), nameof(ShowAudioBitrate), nameof(ShowRemoveAudio), nameof(ShowAudioChannels), nameof(ShowWav), nameof(VideoCodecs), nameof(VideoCodecChoice), nameof(ShowVideoCodec), nameof(ShowHardwareEncoding), nameof(ShowCaption), @@ -668,10 +670,15 @@ public bool ColorProfileChecked [ObservableProperty] private bool _webpLossless; [ObservableProperty] private bool _multiPageTiff; [ObservableProperty] private string _icoSizes; - [ObservableProperty] private bool _merge; + [ObservableProperty, NotifyPropertyChangedFor(nameof(ShowAskPages), nameof(ShowAskPagesHint))] + private bool _merge; [ObservableProperty] private double _marginMm; [ObservableProperty] private int _renderDpi; [ObservableProperty] private string _pageRange; + + /// "Ask every time" for the pages (PdfOptions.AskPageRange, #981). + [ObservableProperty, NotifyPropertyChangedFor(nameof(ShowAskPagesHint))] + private bool _askPageRange; [ObservableProperty] private bool _splitPages; [ObservableProperty, NotifyPropertyChangedFor(nameof(ShowPdfCompressionOptions))] @@ -909,6 +916,13 @@ partial void OnAnimationSpeedChanged(Choice? oldValue, Choice ne public bool ShowPageSize => ShowPdfBuild && !Recognizes; public bool ShowPdfSplit => TargetId == "pdf" && !Recognizes; + /// + /// "Ask every time" under Pages: wherever the pages are, except a merge, where one range would go for every file + /// (AskedPages.Asks). + /// + public bool ShowAskPages => ShowPdf && !(TargetId == "pdf" && Merge); + public bool ShowAskPagesHint => ShowAskPages && AskPageRange; + /// Compression is for PDF → PDF; Ghostscript and metadata only matter while it is on. public bool ShowPdfCompression => TargetId == "pdf" && !Recognizes; @@ -1070,6 +1084,7 @@ public bool Apply() pdf.MarginMm = Math.Clamp(MarginMm, 0, 100); pdf.RenderDpi = Math.Clamp(RenderDpi, 36, 1200); pdf.PageRange = PageRange.Trim(); + pdf.AskPageRange = ShowAskPages && AskPageRange; pdf.SplitPages = SplitPages; pdf.CombinePages = CombinePages; pdf.TrimPageMargins = TrimPageMargins; diff --git a/src/Filee.App/Views/AskPagesWindow.axaml b/src/Filee.App/Views/AskPagesWindow.axaml new file mode 100644 index 000000000..d6ac7062d --- /dev/null +++ b/src/Filee.App/Views/AskPagesWindow.axaml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + +