From 82fe186b659f5b4dc69cc6b394df043289faed2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Nov=C3=A1k?= <42614907+novhack@users.noreply.github.com> Date: Thu, 30 Apr 2020 19:33:24 +0200 Subject: [PATCH] Add autosave feature with backup of unsaved new projects (#221) * Add autosave feature with backup of unsaved new projects. * Fix wrong indentation on line 205. * Store backup for every opened file in user://. Some other improvements. * Remove unnecessary variable. * Update Translations.pot Co-authored-by: Manolis Papadeas <35376950+OverloadedOrama@users.noreply.github.com> --- Main.tscn | 9 + Prefabs/Dialogs/PreferencesDialog.tscn | 219 +++++++++++++++---------- Scripts/Dialogs/PreferencesDialog.gd | 25 +++ Scripts/Main.gd | 63 +++++-- Scripts/OpenSave.gd | 115 +++++++++++-- Translations/Translations.pot | 22 +++ 6 files changed, 343 insertions(+), 110 deletions(-) diff --git a/Main.tscn b/Main.tscn index 986758259..105397a57 100644 --- a/Main.tscn +++ b/Main.tscn @@ -1676,6 +1676,14 @@ visible = false [node name="RightCursor" type="Sprite" parent="."] visible = false + +[node name="BackupConfirmation" type="ConfirmationDialog" parent="."] +margin_right = 349.0 +margin_bottom = 88.0 +rect_min_size = Vector2( 250, 87.5 ) +popup_exclusive = true +dialog_text = "Autosaved backup for %s was found. +Do you want to reload it?" [connection signal="mouse_entered" from="MenuAndUI/UI/CanvasAndTimeline/HViewportContainer/ViewportAndRulers/HorizontalRuler" to="MenuAndUI/UI/CanvasAndTimeline/HViewportContainer/ViewportAndRulers/HorizontalRuler" method="_on_HorizontalRuler_mouse_entered"] [connection signal="pressed" from="MenuAndUI/UI/CanvasAndTimeline/HViewportContainer/ViewportAndRulers/HorizontalRuler" to="MenuAndUI/UI/CanvasAndTimeline/HViewportContainer/ViewportAndRulers/HorizontalRuler" method="_on_HorizontalRuler_pressed"] [connection signal="pressed" from="MenuAndUI/UI/CanvasAndTimeline/HViewportContainer/ViewportAndRulers/HSplitContainer/ViewportandVerticalRuler/VerticalRuler" to="MenuAndUI/UI/CanvasAndTimeline/HViewportContainer/ViewportAndRulers/HSplitContainer/ViewportandVerticalRuler/VerticalRuler" method="_on_VerticalRuler_pressed"] @@ -1762,6 +1770,7 @@ visible = false [connection signal="file_selected" from="PaletteImportFileDialog" to="MenuAndUI/UI/RightPanel/PreviewAndPalettes/ToolAndPaletteVSplit/PaletteVBoxContainer/ScrollPalette/CenterPalette/PaletteContainer" method="on_palette_import_file_selected"] [connection signal="popup_hide" from="PaletteImportFileDialog" to="MenuAndUI/UI/RightPanel/PreviewAndPalettes/ToolAndPaletteVSplit/PaletteVBoxContainer/ScrollPalette/CenterPalette/PaletteContainer" method="_on_NewPaletteDialog_popup_hide"] [connection signal="popup_hide" from="HSVDialog" to="." method="_can_draw_true"] +[connection signal="popup_hide" from="BackupConfirmation" to="." method="_can_draw_true"] [editable path="BrushesPopup/TabContainer/File/FileBrushContainer/CircleBrushButton"] diff --git a/Prefabs/Dialogs/PreferencesDialog.tscn b/Prefabs/Dialogs/PreferencesDialog.tscn index 6f8e30f0d..24440aaa3 100644 --- a/Prefabs/Dialogs/PreferencesDialog.tscn +++ b/Prefabs/Dialogs/PreferencesDialog.tscn @@ -49,39 +49,30 @@ size_flags_horizontal = 3 [node name="VBoxContainer" type="VBoxContainer" parent="HSplitContainer/ScrollContainer"] margin_right = 308.0 -margin_bottom = 1186.0 +margin_bottom = 1230.0 size_flags_horizontal = 3 [node name="General" type="VBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer"] margin_right = 306.0 -margin_bottom = 116.0 - -[node name="OpenLastProject" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"] -margin_right = 306.0 -margin_bottom = 24.0 -hint_tooltip = "Opens last opened project on startup" -mouse_default_cursor_shape = 2 -pressed = true -text = "Open last project on startup" +margin_bottom = 180.0 [node name="SmoothZoom" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"] -margin_top = 28.0 margin_right = 306.0 -margin_bottom = 52.0 +margin_bottom = 24.0 hint_tooltip = "Adds a smoother transition when zooming in or out" mouse_default_cursor_shape = 2 pressed = true text = "Smooth Zoom" [node name="HSeparator2" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"] -margin_top = 56.0 +margin_top = 28.0 margin_right = 306.0 -margin_bottom = 60.0 +margin_bottom = 32.0 [node name="GridContainer" type="GridContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"] -margin_top = 64.0 +margin_top = 36.0 margin_right = 306.0 -margin_bottom = 116.0 +margin_bottom = 88.0 custom_constants/vseparation = 4 custom_constants/hseparation = 4 columns = 2 @@ -125,6 +116,11 @@ size_flags_horizontal = 3 pressed = true text = "Show right tool icon" +[node name="HSeparator3" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"] +margin_top = 92.0 +margin_right = 306.0 +margin_bottom = 96.0 + [node name="PressureSentivity" type="HBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"] visible = false margin_top = 116.0 @@ -145,10 +141,56 @@ text = "Affect Brush's Alpha" items = [ "None", null, false, 0, null, "Affect Brush's Alpha", null, false, 1, null ] selected = 1 -[node name="Languages" type="VBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer"] -margin_top = 120.0 +[node name="OpenLastProject" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"] +margin_top = 100.0 margin_right = 306.0 -margin_bottom = 512.0 +margin_bottom = 124.0 +hint_tooltip = "Opens last opened project on startup" +mouse_default_cursor_shape = 2 +pressed = true +text = "Open last project on startup" + +[node name="EnableAutosave" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"] +margin_top = 128.0 +margin_right = 306.0 +margin_bottom = 152.0 +text = "Enable autosave" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="AutosaveInterval" type="HBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer/General"] +margin_top = 156.0 +margin_right = 306.0 +margin_bottom = 180.0 +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="AutosaveIntervalLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/General/AutosaveInterval"] +margin_top = 5.0 +margin_right = 115.0 +margin_bottom = 19.0 +text = "Autosave interval:" + +[node name="AutosaveInterval" type="SpinBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/General/AutosaveInterval"] +margin_left = 119.0 +margin_right = 306.0 +margin_bottom = 24.0 +size_flags_horizontal = 3 +min_value = 1.0 +max_value = 30.0 +value = 1.0 +align = 2 +suffix = "minute(s)" +__meta__ = { +"_edit_use_anchors_": false +} + +[node name="Languages" type="VBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer"] +margin_top = 184.0 +margin_right = 306.0 +margin_bottom = 576.0 [node name="System Language" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Languages"] margin_right = 306.0 @@ -252,9 +294,9 @@ custom_fonts/font = ExtResource( 3 ) text = "繁體中文 [zh_TW]" [node name="Themes" type="VBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer"] -margin_top = 516.0 +margin_top = 580.0 margin_right = 306.0 -margin_bottom = 652.0 +margin_bottom = 716.0 [node name="Dark Theme" type="CheckBox" parent="HSplitContainer/ScrollContainer/VBoxContainer/Themes"] margin_right = 306.0 @@ -291,9 +333,9 @@ mouse_default_cursor_shape = 2 text = "Light" [node name="Canvas" type="VBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer"] -margin_top = 656.0 +margin_top = 720.0 margin_right = 306.0 -margin_bottom = 848.0 +margin_bottom = 912.0 [node name="GuideOptions" type="GridContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer/Canvas"] margin_right = 306.0 @@ -465,9 +507,9 @@ mouse_default_cursor_shape = 2 color = Color( 1, 1, 1, 1 ) [node name="Image" type="VBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer"] -margin_top = 852.0 +margin_top = 916.0 margin_right = 306.0 -margin_bottom = 928.0 +margin_bottom = 992.0 [node name="ImageOptions" type="GridContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer/Image"] margin_right = 306.0 @@ -538,9 +580,9 @@ mouse_default_cursor_shape = 2 color = Color( 0, 0, 0, 0 ) [node name="Shortcuts" type="VBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer"] -margin_top = 932.0 +margin_top = 996.0 margin_right = 306.0 -margin_bottom = 1186.0 +margin_bottom = 1230.0 [node name="HBoxContainer" type="HBoxContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts"] margin_right = 306.0 @@ -571,7 +613,8 @@ margin_bottom = 28.0 [node name="Shortcuts" type="GridContainer" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts"] margin_top = 32.0 margin_right = 306.0 -margin_bottom = 254.0 +margin_bottom = 234.0 +custom_constants/vseparation = 2 custom_constants/hseparation = 5 columns = 3 @@ -598,9 +641,9 @@ text = "Right Tool:" align = 1 [node name="Empty2" type="Control" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] -margin_top = 18.0 +margin_top = 16.0 margin_right = 137.0 -margin_bottom = 22.0 +margin_bottom = 20.0 [node name="HSeparator" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] visible = false @@ -610,174 +653,174 @@ margin_bottom = 22.0 [node name="HSeparator2" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 142.0 -margin_top = 18.0 +margin_top = 16.0 margin_right = 221.0 -margin_bottom = 22.0 +margin_bottom = 20.0 [node name="HSeparator3" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 226.0 -margin_top = 18.0 +margin_top = 16.0 margin_right = 305.0 -margin_bottom = 22.0 +margin_bottom = 20.0 [node name="RectSelectLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] -margin_top = 29.0 +margin_top = 25.0 margin_right = 137.0 -margin_bottom = 43.0 +margin_bottom = 39.0 text = "Rectangular Selection" [node name="left_rectangle_select_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 142.0 -margin_top = 26.0 +margin_top = 22.0 margin_right = 221.0 -margin_bottom = 46.0 +margin_bottom = 42.0 size_flags_horizontal = 3 [node name="right_rectangle_select_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 226.0 -margin_top = 26.0 +margin_top = 22.0 margin_right = 305.0 -margin_bottom = 46.0 +margin_bottom = 42.0 size_flags_horizontal = 3 [node name="ZoomLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] -margin_top = 53.0 +margin_top = 47.0 margin_right = 137.0 -margin_bottom = 67.0 +margin_bottom = 61.0 text = "Zoom" [node name="left_zoom_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 142.0 -margin_top = 50.0 +margin_top = 44.0 margin_right = 221.0 -margin_bottom = 70.0 +margin_bottom = 64.0 size_flags_horizontal = 3 [node name="right_zoom_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 226.0 -margin_top = 50.0 +margin_top = 44.0 margin_right = 305.0 -margin_bottom = 70.0 +margin_bottom = 64.0 size_flags_horizontal = 3 [node name="ColorPickerLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] -margin_top = 77.0 +margin_top = 69.0 margin_right = 137.0 -margin_bottom = 91.0 +margin_bottom = 83.0 text = "Color Picker" [node name="left_colorpicker_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 142.0 -margin_top = 74.0 +margin_top = 66.0 margin_right = 221.0 -margin_bottom = 94.0 +margin_bottom = 86.0 [node name="right_colorpicker_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 226.0 -margin_top = 74.0 +margin_top = 66.0 margin_right = 305.0 -margin_bottom = 94.0 +margin_bottom = 86.0 [node name="PencilLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] -margin_top = 101.0 +margin_top = 91.0 margin_right = 137.0 -margin_bottom = 115.0 +margin_bottom = 105.0 text = "Pencil" [node name="left_pencil_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 142.0 -margin_top = 98.0 +margin_top = 88.0 margin_right = 221.0 -margin_bottom = 118.0 +margin_bottom = 108.0 [node name="right_pencil_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 226.0 -margin_top = 98.0 +margin_top = 88.0 margin_right = 305.0 -margin_bottom = 118.0 +margin_bottom = 108.0 [node name="EraserLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] -margin_top = 125.0 +margin_top = 113.0 margin_right = 137.0 -margin_bottom = 139.0 +margin_bottom = 127.0 text = "Eraser" [node name="left_eraser_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 142.0 -margin_top = 122.0 +margin_top = 110.0 margin_right = 221.0 -margin_bottom = 142.0 +margin_bottom = 130.0 [node name="right_eraser_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 226.0 -margin_top = 122.0 +margin_top = 110.0 margin_right = 305.0 -margin_bottom = 142.0 +margin_bottom = 130.0 [node name="BucketLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] -margin_top = 149.0 +margin_top = 135.0 margin_right = 137.0 -margin_bottom = 163.0 +margin_bottom = 149.0 text = "Bucket" [node name="left_fill_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 142.0 -margin_top = 146.0 +margin_top = 132.0 margin_right = 221.0 -margin_bottom = 166.0 +margin_bottom = 152.0 [node name="right_fill_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 226.0 -margin_top = 146.0 +margin_top = 132.0 margin_right = 305.0 -margin_bottom = 166.0 +margin_bottom = 152.0 [node name="LightenDarkenLabel" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] -margin_top = 173.0 +margin_top = 157.0 margin_right = 137.0 -margin_bottom = 187.0 +margin_bottom = 171.0 text = "Lighten/Darken" [node name="left_lightdark_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 142.0 -margin_top = 170.0 +margin_top = 154.0 margin_right = 221.0 -margin_bottom = 190.0 +margin_bottom = 174.0 [node name="right_lightdark_tool" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 226.0 -margin_top = 170.0 +margin_top = 154.0 margin_right = 305.0 -margin_bottom = 190.0 +margin_bottom = 174.0 [node name="HSeparator4" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] -margin_top = 194.0 +margin_top = 176.0 margin_right = 137.0 -margin_bottom = 198.0 +margin_bottom = 180.0 [node name="HSeparator5" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 142.0 -margin_top = 194.0 +margin_top = 176.0 margin_right = 221.0 -margin_bottom = 198.0 +margin_bottom = 180.0 [node name="HSeparator6" type="HSeparator" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 226.0 -margin_top = 194.0 +margin_top = 176.0 margin_right = 305.0 -margin_bottom = 198.0 +margin_bottom = 180.0 [node name="Switch Colors" type="Label" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] -margin_top = 205.0 +margin_top = 185.0 margin_right = 137.0 -margin_bottom = 219.0 +margin_bottom = 199.0 text = "Switch Colors" [node name="switch_colors" type="Button" parent="HSplitContainer/ScrollContainer/VBoxContainer/Shortcuts/Shortcuts"] margin_left = 142.0 -margin_top = 202.0 +margin_top = 182.0 margin_right = 221.0 -margin_bottom = 222.0 +margin_bottom = 202.0 [node name="Popups" type="Node" parent="."] @@ -805,13 +848,15 @@ __meta__ = { [connection signal="about_to_show" from="." to="." method="_on_PreferencesDialog_about_to_show"] [connection signal="popup_hide" from="." to="." method="_on_PreferencesDialog_popup_hide"] [connection signal="item_selected" from="HSplitContainer/Tree" to="." method="_on_Tree_item_selected"] -[connection signal="pressed" from="HSplitContainer/ScrollContainer/VBoxContainer/General/OpenLastProject" to="." method="_on_OpenLastProject_pressed"] [connection signal="pressed" from="HSplitContainer/ScrollContainer/VBoxContainer/General/SmoothZoom" to="." method="_on_SmoothZoom_pressed"] [connection signal="toggled" from="HSplitContainer/ScrollContainer/VBoxContainer/General/GridContainer/LeftIndicatorCheckbox" to="." method="_on_LeftIndicatorCheckbox_toggled"] [connection signal="toggled" from="HSplitContainer/ScrollContainer/VBoxContainer/General/GridContainer/RightIndicatorCheckbox" to="." method="_on_RightIndicatorCheckbox_toggled"] [connection signal="toggled" from="HSplitContainer/ScrollContainer/VBoxContainer/General/GridContainer/LeftToolIconCheckbox" to="." method="_on_LeftToolIconCheckbox_toggled"] [connection signal="toggled" from="HSplitContainer/ScrollContainer/VBoxContainer/General/GridContainer/RightToolIconCheckbox" to="." method="_on_RightToolIconCheckbox_toggled"] [connection signal="item_selected" from="HSplitContainer/ScrollContainer/VBoxContainer/General/PressureSentivity/PressureSensitivityOptionButton" to="." method="_on_PressureSensitivityOptionButton_item_selected"] +[connection signal="pressed" from="HSplitContainer/ScrollContainer/VBoxContainer/General/OpenLastProject" to="." method="_on_OpenLastProject_pressed"] +[connection signal="toggled" from="HSplitContainer/ScrollContainer/VBoxContainer/General/EnableAutosave" to="." method="_on_EnableAutosave_toggled"] +[connection signal="value_changed" from="HSplitContainer/ScrollContainer/VBoxContainer/General/AutosaveInterval/AutosaveInterval" to="." method="_on_AutosaveInterval_value_changed"] [connection signal="color_changed" from="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GuideOptions/GuideColor" to="." method="_on_GuideColor_color_changed"] [connection signal="value_changed" from="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions/GridWidthValue" to="." method="_on_GridWidthValue_value_changed"] [connection signal="value_changed" from="HSplitContainer/ScrollContainer/VBoxContainer/Canvas/GridOptions/GridHeightValue" to="." method="_on_GridHeightValue_value_changed"] diff --git a/Scripts/Dialogs/PreferencesDialog.gd b/Scripts/Dialogs/PreferencesDialog.gd index d91d892ae..e4a139c41 100644 --- a/Scripts/Dialogs/PreferencesDialog.gd +++ b/Scripts/Dialogs/PreferencesDialog.gd @@ -41,6 +41,9 @@ func _ready() -> void: # Disable input until the shortcut selector is displayed set_process_input(false) + # Replace OK with Close since preference changes are being applied immediately, not after OK confirmation + get_ok().text = tr("Close") + for child in languages.get_children(): if child is Button: child.connect("pressed", self, "_on_Language_pressed", [child]) @@ -76,6 +79,16 @@ func _ready() -> void: Global.show_right_tool_icon = Global.config_cache.get_value("preferences", "show_right_tool_icon") right_tool_icon.pressed = Global.show_right_tool_icon + # Get autosave settings + if Global.config_cache.has_section_key("preferences", "autosave_interval"): + var autosave_interval = Global.config_cache.get_value("preferences", "autosave_interval") + OpenSave.set_autosave_interval(autosave_interval) + general.get_node("AutosaveInterval/AutosaveInterval").value = autosave_interval + if Global.config_cache.has_section_key("preferences", "enable_autosave"): + var enable_autosave = Global.config_cache.get_value("preferences", "enable_autosave") + OpenSave.toggle_autosave(enable_autosave) + general.get_node("EnableAutosave").pressed = enable_autosave + # Set default values for Canvas options if Global.config_cache.has_section_key("preferences", "grid_size"): var grid_size = Global.config_cache.get_value("preferences", "grid_size") @@ -527,3 +540,15 @@ func _on_OpenLastProject_pressed(): Global.open_last_project = !Global.open_last_project Global.config_cache.set_value("preferences", "open_last_project", Global.open_last_project) Global.config_cache.save("user://cache.ini") + + +func _on_EnableAutosave_toggled(button_pressed : bool) -> void: + OpenSave.toggle_autosave(button_pressed) + Global.config_cache.set_value("preferences", "enable_autosave", button_pressed) + Global.config_cache.save("user://cache.ini") + + +func _on_AutosaveInterval_value_changed(value : float) -> void: + OpenSave.set_autosave_interval(value) + Global.config_cache.set_value("preferences", "autosave_interval", value) + Global.config_cache.save("user://cache.ini") diff --git a/Scripts/Main.gd b/Scripts/Main.gd index 05b1364bf..12144b04c 100644 --- a/Scripts/Main.gd +++ b/Scripts/Main.gd @@ -193,8 +193,28 @@ func _ready() -> void: Global.config_cache.set_value("preferences", "open_last_project", true) if Global.config_cache.get_value("preferences", "open_last_project"): Global.open_last_project = Global.config_cache.get_value("preferences", "open_last_project") + + # If backup file exists then Pixelorama was not closed properly (probably crashed) - reopen backup + $BackupConfirmation.get_cancel().text = tr("Delete") + if Global.config_cache.has_section("backups"): + var project_paths = Global.config_cache.get_section_keys("backups") + if project_paths.size() > 0: + # Get backup path + var backup_path = Global.config_cache.get_value("backups", project_paths[0]) + # Temporatily stop autosave until user confirms backup + OpenSave.autosave_timer.stop() + Global.can_draw = false + # For it's only possible to reload the first found backup + $BackupConfirmation.dialog_text = $BackupConfirmation.dialog_text % project_paths[0] + $BackupConfirmation.connect("confirmed", self, "_on_BackupConfirmation_confirmed", [project_paths[0], backup_path]) + $BackupConfirmation.get_cancel().connect("pressed", self, "_on_BackupConfirmation_delete", [project_paths[0], backup_path]) + $BackupConfirmation.popup_centered() + else: + load_last_project() + else: load_last_project() + func _input(event : InputEvent) -> void: Global.left_cursor.position = get_global_mouse_position() + Vector2(-32, 32) Global.left_cursor.texture = Global.left_cursor_tool_texture @@ -420,16 +440,17 @@ func help_menu_id_pressed(id : int) -> void: Global.can_draw = false func load_last_project(): - # Check if any project was saved or opened last time - if Global.config_cache.has_section_key("preferences", "last_project_path"): - # Check if file still exists on disk - var file_path = Global.config_cache.get_value("preferences", "last_project_path") - var file_check := File.new() - if file_check.file_exists(file_path): # If yes then load the file - _on_OpenSprite_file_selected(file_path) - else: - # If file doesn't exist on disk then warn user about this - $OpenLastProjectAlertDialog.popup_centered() + if Global.open_last_project: + # Check if any project was saved or opened last time + if Global.config_cache.has_section_key("preferences", "last_project_path"): + # Check if file still exists on disk + var file_path = Global.config_cache.get_value("preferences", "last_project_path") + var file_check := File.new() + if file_check.file_exists(file_path): # If yes then load the file + _on_OpenSprite_file_selected(file_path) + else: + # If file doesn't exist on disk then warn user about this + $OpenLastProjectAlertDialog.popup_centered() func _on_UnsavedCanvasDialog_confirmed() -> void: @@ -451,7 +472,7 @@ func _on_OpenSprite_file_selected(path : String) -> void: func _on_SaveSprite_file_selected(path : String) -> void: - OpenSave.save_pxo_file(path) + OpenSave.save_pxo_file(path, false) # Set last opened project path and save Global.config_cache.set_value("preferences", "last_project_path", path) @@ -782,16 +803,34 @@ func _on_QuitAndSaveDialog_custom_action(action : String) -> void: $SaveSprite.popup_centered() $QuitDialog.hide() Global.can_draw = false + OpenSave.remove_backup() func _on_QuitDialog_confirmed() -> void: # Darken the UI to denote that the application is currently exiting # (it won't respond to user input in this state). modulate = Color(0.5, 0.5, 0.5) - + OpenSave.remove_backup() get_tree().quit() +func _on_BackupConfirmation_confirmed(project_path : String, backup_path : String) -> void: + OpenSave.reload_backup_file(project_path, backup_path) + OpenSave.autosave_timer.start() + $ExportDialog.file_name = OpenSave.current_save_path.get_file().trim_suffix(".pxo") + $ExportDialog.directory_path = OpenSave.current_save_path.get_base_dir() + $ExportDialog.was_exported = false + file_menu.set_item_text(3, tr("Save") + " %s" % OpenSave.current_save_path.get_file()) + file_menu.set_item_text(6, tr("Export")) + + +func _on_BackupConfirmation_delete(project_path : String, backup_path : String) -> void: + OpenSave.remove_backup_by_path(project_path, backup_path) + OpenSave.autosave_timer.start() + # Reopen last project + load_last_project() + + func _on_LeftPixelPerfectMode_toggled(button_pressed) -> void: Global.left_pixel_perfect = button_pressed diff --git a/Scripts/OpenSave.gd b/Scripts/OpenSave.gd index af14c0efc..fabff87c0 100644 --- a/Scripts/OpenSave.gd +++ b/Scripts/OpenSave.gd @@ -1,17 +1,31 @@ extends Node var current_save_path := "" +# Stores a filename of a backup file in user:// until user saves manually +var backup_save_path = "" + +onready var autosave_timer : Timer +var default_autosave_interval := 5 # Minutes + +func _ready(): + autosave_timer = Timer.new() + autosave_timer.one_shot = false + autosave_timer.process_mode = Timer.TIMER_PROCESS_IDLE + autosave_timer.connect("timeout", self, "_on_Autosave_timeout") + add_child(autosave_timer) + set_autosave_interval(default_autosave_interval) + toggle_autosave(false) # Gets started from preferences dialog -func open_pxo_file(path : String) -> void: +func open_pxo_file(path : String, untitled_backup : bool = false) -> void: var file := File.new() var err := file.open_compressed(path, File.READ, File.COMPRESSION_ZSTD) if err == ERR_FILE_UNRECOGNIZED: err = file.open(path, File.READ) # If the file is not compressed open it raw (pre-v0.7) if err != OK: + Global.notification_label("File failed to open") file.close() - OS.alert("Can't load file") return var file_version := file.get_line() # Example, "v0.6" @@ -133,13 +147,13 @@ func open_pxo_file(path : String) -> void: file.close() - current_save_path = path - Global.window_title = path.get_file() + " - Pixelorama" + if not untitled_backup: + # Untitled backup should not change window title and save path + current_save_path = path + Global.window_title = path.get_file() + " - Pixelorama" -func save_pxo_file(path : String) -> void: - current_save_path = path - +func save_pxo_file(path : String, autosave : bool) -> void: var file := File.new() var err := file.open_compressed(path, File.WRITE, File.COMPRESSION_ZSTD) if err == OK: @@ -209,10 +223,89 @@ func save_pxo_file(path : String) -> void: file.store_8(tag[3]) # Tag "to", the last frame file.store_line("END_FRAME_TAGS") - if !Global.saved: + file.close() + + if !Global.saved and not autosave: Global.saved = true - Global.window_title = current_save_path.get_file() + " - Pixelorama" - Global.notification_label("File saved") - file.close() + if autosave: + Global.notification_label("File autosaved") + else: + # First remove backup then set current save path + remove_backup() + current_save_path = path + Global.notification_label("File saved") + + if backup_save_path == "": + Global.window_title = path.get_file() + " - Pixelorama" + + else: + Global.notification_label("File failed to save") + + +func toggle_autosave(enable : bool) -> void: + if enable: + autosave_timer.start() + else: + autosave_timer.stop() + + +func set_autosave_interval(interval : float) -> void: + autosave_timer.wait_time = interval * 60 # Interval parameter is in minutes, wait_time is seconds + autosave_timer.start() + + +func _on_Autosave_timeout() -> void: + if backup_save_path == "": + # Create a new backup file if it doesn't exist yet + backup_save_path = "user://backup-" + String(OS.get_unix_time()) + + store_backup_path() + save_pxo_file(backup_save_path, true) + + +# Backup paths are stored in two ways: +# 1) User already manually saved and defined a save path -> {current_save_path, backup_save_path} +# 2) User didn't manually saved, "untitled" backup is stored -> {backup_save_path, backup_save_path} +func store_backup_path() -> void: + if current_save_path != "": + # Remove "untitled" backup if it existed on this project instance + if Global.config_cache.has_section_key("backups", backup_save_path): + Global.config_cache.erase_section_key("backups", backup_save_path) + + Global.config_cache.set_value("backups", current_save_path, backup_save_path) + else: + Global.config_cache.set_value("backups", backup_save_path, backup_save_path) + + Global.config_cache.save("user://cache.ini") + + +func remove_backup() -> void: + # Remove backup file + if backup_save_path != "": + if current_save_path != "": + remove_backup_by_path(current_save_path, backup_save_path) + else: + # If manual save was not yet done - remove "untitled" backup + remove_backup_by_path(backup_save_path, backup_save_path) + backup_save_path = "" + + +func remove_backup_by_path(project_path : String, backup_path : String) -> void: + Directory.new().remove(backup_path) + Global.config_cache.erase_section_key("backups", project_path) + Global.config_cache.save("user://cache.ini") + + +func reload_backup_file(project_path : String, backup_path : String) -> void: + # If project path is the same as backup save path -> the backup was untitled + open_pxo_file(backup_path, project_path == backup_path) + backup_save_path = backup_path + + if project_path != backup_path: + current_save_path = project_path + Global.window_title = project_path.get_file() + " - Pixelorama(*)" + Global.saved = false + + Global.notification_label("Backup reloaded") diff --git a/Translations/Translations.pot b/Translations/Translations.pot index a26f99259..0e7fdd9a9 100644 --- a/Translations/Translations.pot +++ b/Translations/Translations.pot @@ -909,6 +909,15 @@ msgstr "" msgid "File saved" msgstr "" +msgid "File autosaved" +msgstr "" + +msgid "File failed to open" +msgstr "" + +msgid "File failed to save" +msgstr "" + msgid "File exported" msgstr "" @@ -1067,3 +1076,16 @@ msgstr "" msgid "Creates multiple files but every file is stored in different directory that corresponds to its frame tag" msgstr "" + +msgid "Close" +msgstr "" + +msgid "Delete" +msgstr "" + +msgid "Autosaved backup for %s was found." +"Do you want to reload it?" +msgstr "" + +msgid "Backup reloaded" +msgstr ""