M8: the unsaved-changes question on close offers Save (#1170)

Closing a window with unsaved changes asked "Close the window and lose
them?" with only Close without saving and Cancel. It now asks
Don't Save / Cancel / Save, as PhotoCraft and VectorCraft do. Save saves
each file of the window with unsaved changes and closes; a file without
a path (or saved as .fig) gets Save as first, and closing goes on once
that saved it. A failed save or a cancelled Save as keeps the window
open with its changes. The two changed strings are updated in every
catalog.
This commit is contained in:
Gert Pellin
2026-10-22 22:23:11 +02:00
parent 80f916326f
commit 83dac86bc5
21 changed files with 255 additions and 44 deletions
+2 -2
View File
@@ -249,7 +249,6 @@ status = "draft — needs native review"
"Close tab" = "ট্যাব বন্ধ করুন"
"Close version history" = "সংস্করণ ইতিহাস বন্ধ করুন"
"Close when clicking outside" = "বাইরে ক্লিক করলে বন্ধ করুন"
"Close without saving" = "সেভ না করেই বন্ধ করুন"
"Code" = "কোড"
"Code syntax" = "কোড সিনট্যাক্স"
"Collapse" = "সংকুচিত করুন"
@@ -405,6 +404,7 @@ status = "draft — needs native review"
"Distribute horizontal spacing" = "অনুভূমিক ব্যবধান বিতরণ করুন"
"Distribute vertical spacing" = "উল্লম্ব ব্যবধান বিতরণ করুন"
"Divider" = "ডিভাইডার"
"Don't Save" = "সংরক্ষণ করবেন না"
"Don't truncate" = "ট্রাঙ্কেট করবেন না"
"Done" = "সম্পন্ন"
"Done editing" = "সম্পাদনা শেষ"
@@ -1430,7 +1430,7 @@ status = "draft — needs native review"
"Unread" = "অপঠিত"
"Unresolve" = "অমীমাংসিত করুন"
"Unsaved changes" = "সংরক্ষিত হয়নি এমন পরিবর্তন"
"Unsaved changes in {names}. Close the window and lose them?" = "{names}-এ সংরক্ষিত হয়নি এমন পরিবর্তন রয়েছে। উইন্ডোটি বন্ধ করলে কি সেগুলো হারিয়ে যাবে?"
"Unsaved changes in {names}. Save them before closing?" = "{names}-এ সংরক্ষিত হয়নি এমন পরিবর্তন রয়েছে। বন্ধ করার আগে সেগুলো সংরক্ষণ করবেন?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "সংরক্ষিত হয়নি এমন পরিবর্তন: প্রথমে সেভ করুন ({save}), অথবা বাতিল করতে {alt}-ক্লিক করে ট্যাব বন্ধ করুন"
"Unsaved changes: save first, or close again with {key} to discard" = "সংরক্ষিত হয়নি এমন পরিবর্তন: প্রথমে সেভ করুন, অথবা বাতিল করতে {key} দিয়ে আবার বন্ধ করুন"
"Up" = "উপরে"
+2 -2
View File
@@ -240,7 +240,6 @@ status = "draft — needs native review"
"Close tab" = "Zavřít kartu"
"Close version history" = "Zavřít historii verzí"
"Close when clicking outside" = "Zavřít při kliknutí mimo"
"Close without saving" = "Zavřít bez uložení"
"Code" = "Kód"
"Code syntax" = "Syntaxe kódu"
"Collapse" = "Sbalit"
@@ -395,6 +394,7 @@ status = "draft — needs native review"
"Distribute horizontal spacing" = "Rozmístit vodorovné rozestupy"
"Distribute vertical spacing" = "Rozmístit svislé rozestupy"
"Divider" = "Oddělovač"
"Don't Save" = "Neukládat"
"Don't truncate" = "Nezkracovat"
"Done" = "Hotovo"
"Done editing" = "Dokončit úpravy"
@@ -1399,7 +1399,7 @@ status = "draft — needs native review"
"Unread" = "Nepřečtené"
"Unresolve" = "Znovu otevřít"
"Unsaved changes" = "Neuložené změny"
"Unsaved changes in {names}. Close the window and lose them?" = "Neuložené změny v {names}. Zavřít okno a zahodit je?"
"Unsaved changes in {names}. Save them before closing?" = "Neuložené změny v {names}. Uložit je před zavřením?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "Neuložené změny: nejprve uložte ({save}) nebo zavřete kartu kliknutím s {alt} a zahoďte je"
"Unsaved changes: save first, or close again with {key} to discard" = "Neuložené změny: nejprve uložte nebo zavřete znovu s {key} a zahoďte je"
"Up" = "Nahoru"
+2 -2
View File
@@ -235,7 +235,6 @@ status = "draft — needs native review"
"Close tab" = "Tab schließen"
"Close version history" = "Versionsverlauf schließen"
"Close when clicking outside" = "Bei Klick außerhalb schließen"
"Close without saving" = "Ohne Speichern schließen"
"Code" = "Code"
"Code syntax" = "Code-Syntax"
"Collapse" = "Einklappen"
@@ -387,6 +386,7 @@ status = "draft — needs native review"
"Distribute horizontal spacing" = "Horizontale Abstände verteilen"
"Distribute vertical spacing" = "Vertikale Abstände verteilen"
"Divider" = "Trennlinie"
"Don't Save" = "Nicht speichern"
"Don't truncate" = "Nicht kürzen"
"Done" = "Fertig"
"Done editing" = "Bearbeitung beenden"
@@ -1352,7 +1352,7 @@ status = "draft — needs native review"
"Unread" = "Ungelesen"
"Unresolve" = "Wieder öffnen"
"Unsaved changes" = "Nicht gespeicherte Änderungen"
"Unsaved changes in {names}. Close the window and lose them?" = "Nicht gespeicherte Änderungen in {names}. Fenster schließen und Änderungen verwerfen?"
"Unsaved changes in {names}. Save them before closing?" = "Nicht gespeicherte Änderungen in {names}. Vor dem Schließen speichern?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "Nicht gespeicherte Änderungen: Speichern Sie zuerst ({save}) oder schließen Sie den Tab mit {alt}+Klick, um sie zu verwerfen"
"Unsaved changes: save first, or close again with {key} to discard" = "Nicht gespeicherte Änderungen: Speichern Sie zuerst oder schließen Sie erneut mit {key}, um sie zu verwerfen"
"Up" = "Nach oben"
+2 -2
View File
@@ -249,7 +249,6 @@ name = "English"
"Close tab" = "Close tab"
"Close version history" = "Close version history"
"Close when clicking outside" = "Close when clicking outside"
"Close without saving" = "Close without saving"
"Code" = "Code"
"Code syntax" = "Code syntax"
"Collapse" = "Collapse"
@@ -406,6 +405,7 @@ name = "English"
"Distribute horizontal spacing" = "Distribute horizontal spacing"
"Distribute vertical spacing" = "Distribute vertical spacing"
"Divider" = "Divider"
"Don't Save" = "Don't Save"
"Don't truncate" = "Don't truncate"
"Done" = "Done"
"Done editing" = "Done editing"
@@ -1443,7 +1443,7 @@ name = "English"
"Unread" = "Unread"
"Unresolve" = "Unresolve"
"Unsaved changes" = "Unsaved changes"
"Unsaved changes in {names}. Close the window and lose them?" = "Unsaved changes in {names}. Close the window and lose them?"
"Unsaved changes in {names}. Save them before closing?" = "Unsaved changes in {names}. Save them before closing?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard"
"Unsaved changes: save first, or close again with {key} to discard" = "Unsaved changes: save first, or close again with {key} to discard"
"Up" = "Up"
+2 -2
View File
@@ -250,7 +250,6 @@ status = "draft — needs native review"
"Close tab" = "Cerrar pestaña"
"Close version history" = "Cerrar historial de versiones"
"Close when clicking outside" = "Cerrar al hacer clic fuera"
"Close without saving" = "Cerrar sin guardar"
"Code" = "Código"
"Code syntax" = "Sintaxis de código"
"Collapse" = "Contraer"
@@ -407,6 +406,7 @@ status = "draft — needs native review"
"Distribute horizontal spacing" = "Distribuir espacio horizontal"
"Distribute vertical spacing" = "Distribuir espacio vertical"
"Divider" = "Separador"
"Don't Save" = "No guardar"
"Don't truncate" = "No truncar"
"Done" = "Listo"
"Done editing" = "Terminar edición"
@@ -1444,7 +1444,7 @@ status = "draft — needs native review"
"Unread" = "No leídos"
"Unresolve" = "Reabrir"
"Unsaved changes" = "Cambios sin guardar"
"Unsaved changes in {names}. Close the window and lose them?" = "Hay cambios sin guardar en {names}. ¿Cerrar la ventana y perderlos?"
"Unsaved changes in {names}. Save them before closing?" = "Hay cambios sin guardar en {names}. ¿Guardarlos antes de cerrar?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "Cambios sin guardar: guarda primero ({save}) o cierra la pestaña con {alt}+clic para descartarlos"
"Unsaved changes: save first, or close again with {key} to discard" = "Cambios sin guardar: guarda primero o vuelve a cerrar con {key} para descartarlos"
"Up" = "Arriba"
+2 -2
View File
@@ -245,7 +245,6 @@ status = "draft — needs native review"
"Close tab" = "Sulje välilehti"
"Close version history" = "Sulje versiohistoria"
"Close when clicking outside" = "Sulje napsautettaessa ulkopuolelle"
"Close without saving" = "Sulje tallentamatta"
"Code" = "Koodi"
"Code or invite link" = "Koodi tai kutsulinkki"
"Code syntax" = "Koodin syntaksi"
@@ -407,6 +406,7 @@ status = "draft — needs native review"
"Distribute horizontal spacing" = "Jaa vaakavälit tasan"
"Distribute vertical spacing" = "Jaa pystyvälit tasan"
"Divider" = "Erotin"
"Don't Save" = "Älä tallenna"
"Don't truncate" = "Älä lyhennä"
"Done" = "Valmis"
"Done editing" = "Lopeta muokkaus"
@@ -1434,7 +1434,7 @@ status = "draft — needs native review"
"Unread" = "Lukematon"
"Unresolve" = "Merkitse ratkaisemattomaksi"
"Unsaved changes" = "Tallentamattomat muutokset"
"Unsaved changes in {names}. Close the window and lose them?" = "Tallentamattomia muutoksia kohteissa {names}. Suljetaanko ikkuna ja hylätään ne?"
"Unsaved changes in {names}. Save them before closing?" = "Tallentamattomia muutoksia kohteissa {names}. Tallennetaanko ne ennen sulkemista?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "Tallentamattomia muutoksia: tallenna ensin ({save}) tai hylkää sulkemalla välilehti {alt}-napsautuksella"
"Unsaved changes: save first, or close again with {key} to discard" = "Tallentamattomia muutoksia: tallenna ensin tai hylkää sulkemalla uudelleen näppäimellä {key}"
"Up" = "Ylös"
+2 -2
View File
@@ -244,7 +244,6 @@ status = "draft — needs native review"
"Close tab" = "Fermer l’onglet"
"Close version history" = "Fermer l’historique des versions"
"Close when clicking outside" = "Fermer en cliquant à l’extérieur"
"Close without saving" = "Fermer sans enregistrer"
"Code" = "Code"
"Code syntax" = "Syntaxe du code"
"Collapse" = "Réduire"
@@ -404,6 +403,7 @@ status = "draft — needs native review"
"Distribute horizontal spacing" = "Répartir l’espacement horizontal"
"Distribute vertical spacing" = "Répartir l’espacement vertical"
"Divider" = "Séparateur"
"Don't Save" = "Ne pas enregistrer"
"Don't truncate" = "Ne pas tronquer"
"Done" = "Terminé"
"Done editing" = "Terminer la modification"
@@ -1428,7 +1428,7 @@ status = "draft — needs native review"
"Unread" = "Non lus"
"Unresolve" = "Rouvrir"
"Unsaved changes" = "Modifications non enregistrées"
"Unsaved changes in {names}. Close the window and lose them?" = "Modifications non enregistrées dans {names}. Fermer la fenêtre et les perdre ?"
"Unsaved changes in {names}. Save them before closing?" = "Modifications non enregistrées dans {names}. Les enregistrer avant de fermer ?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "Modifications non enregistrées : enregistrez d’abord ({save}) ou fermez l’onglet avec {alt}+clic pour les abandonner"
"Unsaved changes: save first, or close again with {key} to discard" = "Modifications non enregistrées : enregistrez d’abord ou fermez à nouveau avec {key} pour les abandonner"
"Up" = "Haut"
+2 -2
View File
@@ -244,7 +244,6 @@ status = "draft — needs native review"
"Close tab" = "Tutup tab"
"Close version history" = "Tutup riwayat versi"
"Close when clicking outside" = "Tutup saat klik di luar"
"Close without saving" = "Tutup tanpa menyimpan"
"Code" = "Kode"
"Code syntax" = "Sintaks kode"
"Collapse" = "Ciutkan"
@@ -399,6 +398,7 @@ status = "draft — needs native review"
"Distribute horizontal spacing" = "Sebarkan jarak horizontal"
"Distribute vertical spacing" = "Sebarkan jarak vertikal"
"Divider" = "Pembatas"
"Don't Save" = "Jangan simpan"
"Don't truncate" = "Jangan potong"
"Done" = "Selesai"
"Done editing" = "Selesai mengedit"
@@ -1403,7 +1403,7 @@ status = "draft — needs native review"
"Unread" = "Belum dibaca"
"Unresolve" = "Buka kembali"
"Unsaved changes" = "Perubahan belum disimpan"
"Unsaved changes in {names}. Close the window and lose them?" = "Perubahan belum disimpan di {names}. Tutup jendela dan buang perubahan?"
"Unsaved changes in {names}. Save them before closing?" = "Perubahan belum disimpan di {names}. Simpan sebelum menutup?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "Perubahan belum disimpan: simpan dulu ({save}), atau tutup tab dengan {alt}-klik untuk membuangnya"
"Unsaved changes: save first, or close again with {key} to discard" = "Perubahan belum disimpan: simpan dulu, atau tutup lagi dengan {key} untuk membuangnya"
"Up" = "Atas"
+2 -2
View File
@@ -245,7 +245,6 @@ status = "draft — needs native review"
"Close tab" = "タブを閉じる"
"Close version history" = "バージョン履歴を閉じる"
"Close when clicking outside" = "外側をクリックしたら閉じる"
"Close without saving" = "保存せずに閉じる"
"Code" = "コード"
"Code syntax" = "コード構文"
"Collapse" = "折りたたむ"
@@ -402,6 +401,7 @@ status = "draft — needs native review"
"Distribute horizontal spacing" = "水平方向に等間隔に分布"
"Distribute vertical spacing" = "垂直方向に等間隔に分布"
"Divider" = "区切り線"
"Don't Save" = "保存しない"
"Don't truncate" = "切り詰めない"
"Done" = "完了"
"Done editing" = "編集を完了"
@@ -1411,7 +1411,7 @@ status = "draft — needs native review"
"Unread" = "未読"
"Unresolve" = "未解決に戻す"
"Unsaved changes" = "未保存の変更"
"Unsaved changes in {names}. Close the window and lose them?" = "{names} に未保存の変更がある。ウィンドウを閉じて変更を破棄する?"
"Unsaved changes in {names}. Save them before closing?" = "{names} に未保存の変更がある。閉じる前に保存する?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "未保存の変更:先に保存する({save})か、{alt}-クリックでタブを閉じて破棄"
"Unsaved changes: save first, or close again with {key} to discard" = "未保存の変更:先に保存するか、もう一度 {key} で閉じて破棄"
"Up" = "上"
+2 -2
View File
@@ -251,7 +251,6 @@ status = "draft — reviewed by a native speaker, awaiting a maintainer's approv
"Close tab" = "ჩანართის დახურვა"
"Close version history" = "ვერსიების ისტორიის დახურვა"
"Close when clicking outside" = "დახურვა გარეთ დაწკაპუნებისას"
"Close without saving" = "დახურვა შენახვის გარეშე"
"Code" = "კოდი"
"Code syntax" = "კოდის სინტაქსი"
"Collapse" = "ჩაკეცვა"
@@ -407,6 +406,7 @@ status = "draft — reviewed by a native speaker, awaiting a maintainer's approv
"Distribute horizontal spacing" = "ჰორიზონტალური შუალედების განაწილება"
"Distribute vertical spacing" = "ვერტიკალური შუალედების განაწილება"
"Divider" = "გამყოფი"
"Don't Save" = "არ შეინახო"
"Don't truncate" = "შეკვეცის გარეშე"
"Done" = "მზადაა"
"Done editing" = "რედაქტირების დასრულება"
@@ -1432,7 +1432,7 @@ status = "draft — reviewed by a native speaker, awaiting a maintainer's approv
"Unread" = "წაუკითხავი"
"Unresolve" = "ხელახლა გახსნა"
"Unsaved changes" = "შეუნახავი ცვლილებები"
"Unsaved changes in {names}. Close the window and lose them?" = "შეუნახავი ცვლილებები: {names}. დაიხუროს ფანჯარა და დაიკარგოს ისინი?"
"Unsaved changes in {names}. Save them before closing?" = "შეუნახავი ცვლილებები: {names}. შევინახოთ დახურვამდე?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "შეუნახავი ცვლილებები: ჯერ შეინახეთ ({save}) ან გასაუქმებლად დახურეთ ჩანართი {alt}+დაწკაპუნებით"
"Unsaved changes: save first, or close again with {key} to discard" = "შეუნახავი ცვლილებები: ჯერ შეინახეთ ან გასაუქმებლად ხელახლა დახურეთ {key}-ით"
"Up" = "ზემოთ"
+2 -2
View File
@@ -235,7 +235,6 @@ status = "draft — needs native review"
"Close tab" = "탭 닫기"
"Close version history" = "버전 기록 닫기"
"Close when clicking outside" = "바깥쪽을 클릭하면 닫기"
"Close without saving" = "저장하지 않고 닫기"
"Code" = "코드"
"Code syntax" = "코드 구문"
"Collapse" = "접기"
@@ -386,6 +385,7 @@ status = "draft — needs native review"
"Distribute horizontal spacing" = "수평 간격 균등 분포"
"Distribute vertical spacing" = "수직 간격 균등 분포"
"Divider" = "구분선"
"Don't Save" = "저장 안 함"
"Don't truncate" = "자르지 않음"
"Done" = "완료"
"Done editing" = "편집 완료"
@@ -1343,7 +1343,7 @@ status = "draft — needs native review"
"Unread" = "읽지 않음"
"Unresolve" = "미해결로 되돌리기"
"Unsaved changes" = "저장되지 않은 변경 사항"
"Unsaved changes in {names}. Close the window and lose them?" = "{names}에 저장되지 않은 변경 사항이 있음. 창을 닫고 변경 사항을 버릴까요?"
"Unsaved changes in {names}. Save them before closing?" = "{names}에 저장되지 않은 변경 사항이 있음. 닫기 전에 저장할까요?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "저장되지 않은 변경 사항: 먼저 저장({save})하거나 {alt}-클릭으로 탭을 닫아 버리세요"
"Unsaved changes: save first, or close again with {key} to discard" = "저장되지 않은 변경 사항: 먼저 저장하거나 {key}로 다시 닫아 버리세요"
"Up" = "위"
+2 -2
View File
@@ -245,7 +245,6 @@ status = "draft — needs native review"
"Close tab" = "Tabblad sluiten"
"Close version history" = "Versiegeschiedenis sluiten"
"Close when clicking outside" = "Sluiten bij klikken erbuiten"
"Close without saving" = "Sluiten zonder opslaan"
"Code" = "Code"
"Code or invite link" = "Code of uitnodigingslink"
"Code syntax" = "Code-syntaxis"
@@ -407,6 +406,7 @@ status = "draft — needs native review"
"Distribute horizontal spacing" = "Horizontale afstand verdelen"
"Distribute vertical spacing" = "Verticale afstand verdelen"
"Divider" = "Scheidslijn"
"Don't Save" = "Niet opslaan"
"Don't truncate" = "Niet afkappen"
"Done" = "Klaar"
"Done editing" = "Klaar met bewerken"
@@ -1434,7 +1434,7 @@ status = "draft — needs native review"
"Unread" = "Ongelezen"
"Unresolve" = "Oplossing ongedaan maken"
"Unsaved changes" = "Niet-opgeslagen wijzigingen"
"Unsaved changes in {names}. Close the window and lose them?" = "Niet-opgeslagen wijzigingen in {names}. Het venster sluiten en ze verliezen?"
"Unsaved changes in {names}. Save them before closing?" = "Niet-opgeslagen wijzigingen in {names}. Opslaan voor het sluiten?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "Niet-opgeslagen wijzigingen: sla eerst op ({save}), of sluit het tabblad met {alt}-klik om ze te verwerpen"
"Unsaved changes: save first, or close again with {key} to discard" = "Niet-opgeslagen wijzigingen: sla eerst op, of sluit nogmaals met {key} om te verwerpen"
"Up" = "Omhoog"
+2 -2
View File
@@ -247,7 +247,6 @@ status = "draft — needs native review"
"Close tab" = "Zamknij kartę"
"Close version history" = "Zamknij historię wersji"
"Close when clicking outside" = "Zamknij przy kliknięciu poza obszarem"
"Close without saving" = "Zamknij bez zapisywania"
"Code" = "Kod"
"Code syntax" = "Składnia kodu"
"Collapse" = "Zwiń"
@@ -404,6 +403,7 @@ status = "draft — needs native review"
"Distribute horizontal spacing" = "Rozmieść poziomo"
"Distribute vertical spacing" = "Rozmieść pionowo"
"Divider" = "Rozdzielacz"
"Don't Save" = "Nie zapisuj"
"Don't truncate" = "Nie obcinaj tekstu"
"Done" = "Gotowe"
"Done editing" = "Zakończ edycję"
@@ -1429,7 +1429,7 @@ status = "draft — needs native review"
"Unread" = "Nieprzeczytane"
"Unresolve" = "Cofnij rozwiązanie"
"Unsaved changes" = "Niezapisane zmiany"
"Unsaved changes in {names}. Close the window and lose them?" = "Niezapisane zmiany w: {names}. Zamknąć okno i utracić je?"
"Unsaved changes in {names}. Save them before closing?" = "Niezapisane zmiany w: {names}. Zapisać je przed zamknięciem?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "Niezapisane zmiany: najpierw zapisz ({save}) lub zamknij kartę z {alt}-kliknięciem, aby odrzucić"
"Unsaved changes: save first, or close again with {key} to discard" = "Niezapisane zmiany: najpierw zapisz lub naciśnij ponownie {key}, aby odrzucić"
"Up" = "W górę"
+2 -2
View File
@@ -233,7 +233,6 @@ status = "draft — needs native review"
"Close tab" = "Fechar aba"
"Close version history" = "Fechar histórico de versões"
"Close when clicking outside" = "Fechar ao clicar fora"
"Close without saving" = "Fechar sem salvar"
"Code" = "Código"
"Code syntax" = "Sintaxe de código"
"Collapse" = "Recolher"
@@ -385,6 +384,7 @@ status = "draft — needs native review"
"Distribute horizontal spacing" = "Distribuir espaçamento horizontal"
"Distribute vertical spacing" = "Distribuir espaçamento vertical"
"Divider" = "Divisor"
"Don't Save" = "Não salvar"
"Don't truncate" = "Não truncar"
"Done" = "Concluído"
"Done editing" = "Concluir edição"
@@ -1342,7 +1342,7 @@ status = "draft — needs native review"
"Unread" = "Não lidos"
"Unresolve" = "Reabrir"
"Unsaved changes" = "Alterações não salvas"
"Unsaved changes in {names}. Close the window and lose them?" = "Há alterações não salvas em {names}. Fechar a janela e perdê-las?"
"Unsaved changes in {names}. Save them before closing?" = "Há alterações não salvas em {names}. Salvá-las antes de fechar?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "Alterações não salvas: salve primeiro ({save}) ou feche a aba com {alt}+clique para descartá-las"
"Unsaved changes: save first, or close again with {key} to discard" = "Alterações não salvas: salve primeiro ou feche de novo com {key} para descartá-las"
"Up" = "Para cima"
+2 -2
View File
@@ -247,7 +247,6 @@ name = "Русский"
"Close tab" = "Закрыть вкладку"
"Close version history" = "Закрыть историю версий"
"Close when clicking outside" = "Закрывать при нажатии снаружи"
"Close without saving" = "Закрыть без сохранения"
"Code" = "Код"
"Code syntax" = "Синтаксис кода"
"Collapse" = "Свернуть"
@@ -406,6 +405,7 @@ name = "Русский"
"Distribute horizontal spacing" = "Распределить по горизонтали"
"Distribute vertical spacing" = "Распределить по вертикали"
"Divider" = "Разделитель"
"Don't Save" = "Не сохранять"
"Don't truncate" = "Не обрезать"
"Done" = "Готово"
"Done editing" = "Завершить редактирование"
@@ -1443,7 +1443,7 @@ name = "Русский"
"Unread" = "Непрочитанные"
"Unresolve" = "Снова открыть"
"Unsaved changes" = "Несохранённые изменения"
"Unsaved changes in {names}. Close the window and lose them?" = "Несохранённые изменения в {names}. Закрыть окно и потерять их?"
"Unsaved changes in {names}. Save them before closing?" = "Несохранённые изменения в {names}. Сохранить их перед закрытием?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "Есть несохранённые изменения: сохраните ({save}) или закройте вкладку нажатием с {alt}, чтобы отбросить их"
"Unsaved changes: save first, or close again with {key} to discard" = "Есть несохранённые изменения: сохраните или закройте ещё раз с {key}, чтобы отбросить их"
"Up" = "Вверх"
+2 -2
View File
@@ -244,7 +244,6 @@ name = "Svenska"
"Close tab" = "Stäng flik"
"Close version history" = "Stäng versionshistorik"
"Close when clicking outside" = "Stäng vid klick utanför"
"Close without saving" = "Stäng utan att spara"
"Code" = "Kod"
"Code syntax" = "Kodsyntax"
"Collapse" = "Komprimera"
@@ -401,6 +400,7 @@ name = "Svenska"
"Distribute horizontal spacing" = "Fördela horisontellt avstånd"
"Distribute vertical spacing" = "Fördela vertikalt avstånd"
"Divider" = "Avdelare"
"Don't Save" = "Spara inte"
"Don't truncate" = "Korta inte av"
"Done" = "Klart"
"Done editing" = "Klar med redigeringen"
@@ -1418,7 +1418,7 @@ name = "Svenska"
"Unread" = "Olästa"
"Unresolve" = "Markera som olöst"
"Unsaved changes" = "Osparade ändringar"
"Unsaved changes in {names}. Close the window and lose them?" = "Osparade ändringar i {names}. Vill du stänga fönstret och förlora dem?"
"Unsaved changes in {names}. Save them before closing?" = "Osparade ändringar i {names}. Vill du spara dem innan du stänger?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "Osparade ändringar: spara först ({save}), eller stäng fliken med {alt}-klick för att slänga dem"
"Unsaved changes: save first, or close again with {key} to discard" = "Osparade ändringar: spara först, eller stäng igen med {key} för att slänga dem"
"Up" = "Upp"
+2 -2
View File
@@ -244,7 +244,6 @@ name = "Türkçe"
"Close tab" = "Sekmeyi kapat"
"Close version history" = "Sürüm geçmişini kapat"
"Close when clicking outside" = "Dışarı tıklanınca kapat"
"Close without saving" = "Kaydetmeden kapat"
"Code" = "Kod"
"Code syntax" = "Kod söz dizimi"
"Collapse" = "Daralt"
@@ -399,6 +398,7 @@ name = "Türkçe"
"Distribute horizontal spacing" = "Yatay aralığı dağıt"
"Distribute vertical spacing" = "Dikey aralığı dağıt"
"Divider" = "Ayırıcı"
"Don't Save" = "Kaydetme"
"Don't truncate" = "Kısaltma yapma"
"Done" = "Bitti"
"Done editing" = "Düzenlemeyi bitir"
@@ -1403,7 +1403,7 @@ name = "Türkçe"
"Unread" = "Okunmamış"
"Unresolve" = "Çözülmedi yap"
"Unsaved changes" = "Kaydedilmemiş değişiklikler"
"Unsaved changes in {names}. Close the window and lose them?" = "{names} içinde kaydedilmemiş değişiklikler var. Pencere kapatılıp bunlar kaybedilsin mi?"
"Unsaved changes in {names}. Save them before closing?" = "{names} içinde kaydedilmemiş değişiklikler var. Kapatmadan önce kaydedilsin mi?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "Kaydedilmemiş değişiklikler: önce kaydedin ({save}) veya atmak için sekmeyi {alt}-tıklamayla kapatın"
"Unsaved changes: save first, or close again with {key} to discard" = "Kaydedilmemiş değişiklikler: önce kaydedin veya atmak için {key} ile yeniden kapatın"
"Up" = "Yukarı"
+2 -2
View File
@@ -236,7 +236,6 @@ status = "draft — needs native review"
"Close tab" = "关闭标签页"
"Close version history" = "关闭版本历史"
"Close when clicking outside" = "点击外部时关闭"
"Close without saving" = "关闭但不保存"
"Code" = "代码"
"Code syntax" = "代码语法"
"Collapse" = "折叠"
@@ -387,6 +386,7 @@ status = "draft — needs native review"
"Distribute horizontal spacing" = "水平间距分布"
"Distribute vertical spacing" = "垂直间距分布"
"Divider" = "分隔线"
"Don't Save" = "不保存"
"Don't truncate" = "不截断"
"Done" = "完成"
"Done editing" = "完成编辑"
@@ -1344,7 +1344,7 @@ status = "draft — needs native review"
"Unread" = "未读"
"Unresolve" = "取消解决"
"Unsaved changes" = "未保存的更改"
"Unsaved changes in {names}. Close the window and lose them?" = "{names} 中有未保存的更改。要关闭窗口并丢弃这些更改吗?"
"Unsaved changes in {names}. Save them before closing?" = "{names} 中有未保存的更改。关闭前要保存吗?"
"Unsaved changes: save first ({save}), or close the tab with {alt}-click to discard" = "有未保存的更改:请先保存({save}),或按住 {alt} 点击关闭标签页以丢弃"
"Unsaved changes: save first, or close again with {key} to discard" = "有未保存的更改:请先保存,或再次按 {key} 关闭以丢弃"
"Up" = "向上"
+4
View File
@@ -657,6 +657,9 @@ pub struct UiState {
pub online: online::OnlineUi,
/// The window was asked to close with unsaved changes: the question shows.
pub confirm_close: bool,
/// The file a Save as started from the unsaved-changes question is saving: the window closes
/// once it is saved, and stays open if the dialog is cancelled or the save fails.
pub close_after_save_as: Option<u64>,
}
impl Default for UiState {
@@ -760,6 +763,7 @@ impl Default for UiState {
live: live::LiveUi::default(),
online: online::OnlineUi::default(),
confirm_close: false,
close_after_save_as: None,
}
}
}
+84 -8
View File
@@ -322,8 +322,32 @@ fn close_current(app: &mut FigApp, ctx: &Context) {
ctx.request_repaint_of(ViewportId::ROOT);
}
/// The unsaved-changes question when a window closes.
/// What the unsaved-changes question was answered with.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum CloseAnswer {
/// Save every file with unsaved changes, then close (Save as for a file without a path).
Save,
/// Close and lose the changes.
DontSave,
/// Keep the window open.
Cancel,
}
/// The unsaved-changes question when a window closes: Don't Save / Cancel / Save, as the other
/// Craft apps ask it.
pub(crate) fn confirm_close(app: &mut FigApp, ctx: &Context) {
// A Save as this question started: once its dialog is answered, closing goes on if the file
// was saved; cancelled or failed, the window stays open.
if let Some(uid) = app.ui.close_after_save_as {
if app.pending_dialog.is_some() {
return;
}
app.ui.close_after_save_as = None;
if app.session.docs.iter().find(|d| d.uid == uid).is_none_or(|d| !d.is_dirty()) {
request_close(app, ctx);
}
return;
}
if !app.ui.confirm_close {
return;
}
@@ -333,20 +357,72 @@ pub(crate) fn confirm_close(app: &mut FigApp, ctx: &Context) {
return;
}
let t = crate::theme::Tokens::get(ctx);
let (mut open, mut discard, mut cancel) = (true, false, false);
let mut open = true;
let mut answer = None;
crate::dialogs::window(tr("Unsaved changes"), ctx).order(egui::Order::Foreground).open(&mut open).show(ctx, |ui| {
ui.set_width(340.0);
crate::widgets::label(ui, &trf("Unsaved changes in {names}. Close the window and lose them?", &[("names", &names.join(", "))]), t.text);
crate::widgets::label(ui, &trf("Unsaved changes in {names}. Save them before closing?", &[("names", &names.join(", "))]), t.text);
ui.add_space(12.0);
ui.horizontal(|ui| {
discard = crate::widgets::button(ui, tr("Close without saving"), true, true).clicked();
cancel = crate::widgets::button(ui, tr("Cancel"), false, true).clicked();
if crate::widgets::button(ui, tr("Don't Save"), false, true).clicked() {
answer = Some(CloseAnswer::DontSave);
}
if crate::widgets::button(ui, tr("Cancel"), false, true).clicked() {
answer = Some(CloseAnswer::Cancel);
}
if crate::widgets::button(ui, tr("Save"), true, true).clicked() {
answer = Some(CloseAnswer::Save);
}
});
});
app.ui.confirm_close = open && !discard && !cancel;
if discard {
close_current(app, ctx);
if !open {
answer = Some(CloseAnswer::Cancel);
}
if let Some(a) = answer {
answer_close(app, ctx, a);
}
}
/// Act on the unsaved-changes question's answer.
pub(crate) fn answer_close(app: &mut FigApp, ctx: &Context, answer: CloseAnswer) {
app.ui.confirm_close = false;
match answer {
CloseAnswer::Cancel => {}
CloseAnswer::DontSave => close_current(app, ctx),
CloseAnswer::Save => save_then_close(app, ctx),
}
}
/// Save each file of this window with unsaved changes, then close it. A file without a path (or
/// one saved as .fig, which is never overwritten silently) asks where with Save as, and closing goes
/// on when that dialog saved it ([`confirm_close`]). A save that fails shows why and keeps the
/// window open.
fn save_then_close(app: &mut FigApp, ctx: &Context) {
let files: Vec<u64> = app.session.docs.iter().filter(|d| app.shows(d.uid) && d.is_dirty() && d.branch_of.is_none()).map(|d| d.uid).collect();
for uid in files {
let Some(i) = app.session.docs.iter().position(|d| d.uid == uid) else { continue };
app.session.active = i;
let native = app
.session
.docs
.get(i)
.and_then(|d| d.path.as_deref())
.is_some_and(|p| !std::path::Path::new(p).extension().and_then(|e| e.to_str()).is_some_and(|e| e.eq_ignore_ascii_case("fig")));
if !native {
app.ui.close_after_save_as = Some(uid);
app.save_as_dialog();
if app.pending_dialog.is_none() {
// No dialog could start: stay open.
app.ui.close_after_save_as = None;
}
ctx.request_repaint();
return;
}
if app.run("file.save", json!({})).is_none() {
return;
}
}
close_current(app, ctx);
}
/// Before the main window draws: windows asked to close close, closed files leave their windows
+131
View File
@@ -170,3 +170,134 @@ fn a_request_for_a_window_on_screen_wakes_it_while_the_main_window_is_hidden() {
assert!(ui.try_recv().is_err(), "answered in the other window's frame");
assert!(woken.lock().unwrap().contains(&other), "the other window is asked to draw: {:?}", woken.lock().unwrap());
}
/// Answers Save as with `path` (`None`: cancelled) and counts the dialogs asked for.
struct SaveAs {
path: Option<std::path::PathBuf>,
asked: std::rc::Rc<std::cell::Cell<usize>>,
}
impl crate::Services for SaveAs {
fn pick_save(&mut self, _title: &str, _name: &str, _exts: &[&str]) -> crate::DialogFuture {
self.asked.set(self.asked.get() + 1);
Box::pin(std::future::ready(self.path.take()))
}
}
fn temp_dir(tag: &str) -> std::path::PathBuf {
let d = std::env::temp_dir().join(format!("figcraft-close-save-{tag}-{}", std::process::id()));
let _ = std::fs::remove_dir_all(&d);
std::fs::create_dir_all(&d).unwrap();
d
}
/// The main window with one file that has unsaved changes, saved at `path` first when given, and
/// the window asked to close (the question shows).
fn asking(path: Option<&std::path::Path>, save_as: Option<std::path::PathBuf>) -> (egui::Context, FigApp, std::rc::Rc<std::cell::Cell<usize>>) {
let mut s = Session::new();
if let Some(p) = path {
s.execute("file.saveAs", &json!({"path": p.to_string_lossy()})).unwrap();
}
s.execute("shape.rectangle", &json!({"x": 0, "y": 0, "width": 10, "height": 10})).unwrap();
let asked = std::rc::Rc::new(std::cell::Cell::new(0));
let ctx = egui::Context::default();
let mut app = FigApp::new(s, Box::new(SaveAs { path: save_as, asked: asked.clone() }));
frame(&ctx, &mut app, false);
frame(&ctx, &mut app, true);
assert!(app.ui.confirm_close, "the question shows");
(ctx, app, asked)
}
fn rects_in(path: &std::path::Path) -> usize {
let mut s = Session::new();
s.execute("file.open", &json!({"path": path.to_string_lossy()})).unwrap();
s.doc().unwrap().nodes().filter(|n| n.name.starts_with("Rectangle")).count()
}
/// A few frames, as the window draws them.
fn settle(ctx: &egui::Context, app: &mut FigApp) {
for _ in 0..4 {
frame(ctx, app, false);
}
}
#[test]
fn the_unsaved_changes_question_offers_save_dont_save_and_cancel() {
// As the other Craft apps ask it (#1170): Don't Save, Cancel, and Save as the default.
let (ctx, mut app, _) = asking(None, None);
ctx.enable_accesskit();
frame(&ctx, &mut app, false);
let out = ctx.run_ui(RawInput::default(), |ui| app.ui(ui));
let nodes = out.platform_output.accesskit_update.expect("an AccessKit update").nodes;
let buttons: Vec<String> =
nodes.iter().filter(|(_, n)| n.role() == egui::accesskit::Role::Button).filter_map(|(_, n)| n.label().map(str::to_string)).collect();
for want in ["Don't Save", "Cancel", "Save"] {
assert!(buttons.iter().any(|b| b == want), "{want} in {buttons:?}");
}
assert!(!buttons.iter().any(|b| b == "Close without saving"));
}
#[test]
fn save_saves_a_file_with_a_path_and_then_closes() {
let d = temp_dir("path");
let path = d.join("a.figcraft");
let (ctx, mut app, asked) = asking(Some(&path), None);
assert_eq!(rects_in(&path), 0);
crate::windows::answer_close(&mut app, &ctx, crate::windows::CloseAnswer::Save);
settle(&ctx, &mut app);
assert_eq!(rects_in(&path), 1, "saved where it was");
assert_eq!(asked.get(), 0, "no Save as for a file with a path");
assert!(app.ui.quit, "then the window closes");
std::fs::remove_dir_all(&d).ok();
}
#[test]
fn save_asks_where_for_an_untitled_file_and_closes_once_saved() {
let d = temp_dir("untitled");
let path = d.join("new.figcraft");
let (ctx, mut app, asked) = asking(None, Some(path.clone()));
crate::windows::answer_close(&mut app, &ctx, crate::windows::CloseAnswer::Save);
settle(&ctx, &mut app);
assert_eq!(asked.get(), 1, "Save as asks where");
assert_eq!(rects_in(&path), 1, "saved there");
assert!(app.ui.quit, "then the window closes");
std::fs::remove_dir_all(&d).ok();
}
#[test]
fn a_cancelled_save_as_or_a_failed_save_keeps_the_window_open() {
// Save as cancelled: nothing is written, the file keeps its changes and the window stays.
let (ctx, mut app, asked) = asking(None, None);
crate::windows::answer_close(&mut app, &ctx, crate::windows::CloseAnswer::Save);
settle(&ctx, &mut app);
assert_eq!(asked.get(), 1);
assert!(!app.ui.quit && !app.ui.confirm_close, "open, and not asking again");
assert!(app.session.ds().unwrap().is_dirty());
assert_eq!(app.ui.close_after_save_as, None);
// The save fails (its folder is gone): the error shows and the window stays.
let d = temp_dir("gone");
let path = d.join("a.figcraft");
let (ctx, mut app, _) = asking(Some(&path), None);
std::fs::remove_dir_all(&d).unwrap();
crate::windows::answer_close(&mut app, &ctx, crate::windows::CloseAnswer::Save);
settle(&ctx, &mut app);
assert!(!app.ui.quit, "a failed save keeps the window open");
assert!(app.session.ds().unwrap().is_dirty());
}
#[test]
fn dont_save_closes_and_cancel_keeps_the_window() {
let d = temp_dir("answers");
let path = d.join("a.figcraft");
let (ctx, mut app, _) = asking(Some(&path), None);
crate::windows::answer_close(&mut app, &ctx, crate::windows::CloseAnswer::Cancel);
settle(&ctx, &mut app);
assert!(!app.ui.quit && !app.ui.confirm_close);
frame(&ctx, &mut app, true);
crate::windows::answer_close(&mut app, &ctx, crate::windows::CloseAnswer::DontSave);
settle(&ctx, &mut app);
assert!(app.ui.quit, "Don't Save closes");
assert_eq!(rects_in(&path), 0, "without saving");
std::fs::remove_dir_all(&d).ok();
}