Skip to content

Commit

Permalink
Check for unsaved changes when closing a scene
Browse files Browse the repository at this point in the history
  • Loading branch information
KoBeWi committed Jul 18, 2023
1 parent 000471e commit b883f32
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 30 deletions.
22 changes: 17 additions & 5 deletions doc/classes/EditorPlugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -282,18 +282,30 @@
</method>
<method name="_get_unsaved_status" qualifiers="virtual const">
<return type="String" />
<param index="0" name="for_scene" type="String" />
<description>
Override this method to provide a custom message that lists unsaved changes. The editor will call this method on exit and display it in a confirmation dialog. Return empty string if the plugin has no unsaved changes.
Override this method to provide a custom message that lists unsaved changes. The editor will call this method when exiting or when closing a scene, and display the returned string in a confirmation dialog. Return empty string if the plugin has no unsaved changes.
When closing a scene, [param for_scene] is the path to the scene being closed. You can use it to handle built-in resources in that scene.
If the user confirms saving, [method _save_external_data] will be called, before closing the editor.
[codeblock]
func _get_unsaved_status():
if unsaved:
func _get_unsaved_status(for_scene):
if not unsaved:
return ""

if for_scene.is_empty():
return "Save changes in MyCustomPlugin before closing?"
return ""

else:
return "Scene %s has changes from MyCustomPlugin. Save before closing?" % for_scene.get_file()

func _save_external_data():
unsaved = false
[/codeblock]
If the plugin has no scene-specific changes, you can ignore the calls when closing scenes:
[codeblock]
func _get_unsaved_status(for_scene):
if not for_scene.is_empty():
return ""
[/codeblock]
</description>
</method>
<method name="_get_window_layout" qualifiers="virtual">
Expand Down
52 changes: 39 additions & 13 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2797,24 +2797,25 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
save_confirmation->popup_centered();
break;
}

plugin_to_save = nullptr;
for (int i = 0; i < editor_data.get_editor_plugin_count(); i++) {
const String unsaved_status = editor_data.get_editor_plugin(i)->get_unsaved_status();
if (!unsaved_status.is_empty()) {
if (p_option == RELOAD_CURRENT_PROJECT) {
save_confirmation->set_ok_button_text(TTR("Save & Reload"));
save_confirmation->set_text(RTR(unsaved_status));
save_confirmation->set_text(unsaved_status);
} else {
save_confirmation->set_ok_button_text(TTR("Save & Quit"));
save_confirmation->set_text(RTR(unsaved_status));
save_confirmation->set_text(unsaved_status);
}
save_confirmation->reset_size();
save_confirmation->popup_centered();
plugin_to_save = editor_data.get_editor_plugin(i);
break;
}
}

if (plugin_to_save) {
break;
}
Expand Down Expand Up @@ -3057,13 +3058,21 @@ int EditorNode::_next_unsaved_scene(bool p_valid_filename, int p_start) {
if (!editor_data.get_edited_scene_root(i)) {
continue;
}

String scene_filename = editor_data.get_edited_scene_root(i)->get_scene_file_path();
if (p_valid_filename && scene_filename.is_empty()) {
continue;
}

bool unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(editor_data.get_scene_history_id(i));
if (unsaved) {
String scene_filename = editor_data.get_edited_scene_root(i)->get_scene_file_path();
if (p_valid_filename && scene_filename.is_empty()) {
continue;
}
return i;
} else {
for (int j = 0; j < editor_data.get_editor_plugin_count(); j++) {
if (!editor_data.get_editor_plugin(j)->get_unsaved_status(scene_filename).is_empty()) {
return i;
}
}
}
}
return -1;
Expand Down Expand Up @@ -5574,19 +5583,36 @@ void EditorNode::_scene_tab_closed(int p_tab, int p_option) {
return;
}

bool unsaved = EditorUndoRedoManager::get_singleton()->is_history_unsaved(editor_data.get_scene_history_id(p_tab));
if (unsaved) {
String scene_filename = scene->get_scene_file_path();
String unsaved_message;

if (EditorUndoRedoManager::get_singleton()->is_history_unsaved(editor_data.get_scene_history_id(p_tab))) {
if (scene_filename.is_empty()) {
unsaved_message = TTR("This scene was never saved.");
} else {
unsaved_message = vformat(TTR("Scene \"%s\" has unsaved changes."), scene_filename);
}
} else {
// Check if any plugin has unsaved changes in that scene.
for (int i = 0; i < editor_data.get_editor_plugin_count(); i++) {
unsaved_message = editor_data.get_editor_plugin(i)->get_unsaved_status(scene_filename);
if (!unsaved_message.is_empty()) {
break;
}
}
}

if (!unsaved_message.is_empty()) {
if (get_current_tab() != p_tab) {
set_current_scene(p_tab);
}

String scene_filename = scene->get_scene_file_path();
if (current_menu_option == RELOAD_CURRENT_PROJECT) {
save_confirmation->set_ok_button_text(TTR("Save & Reload"));
save_confirmation->set_text(vformat(TTR("Save changes to '%s' before reloading?"), !scene_filename.is_empty() ? scene_filename : "unsaved scene"));
save_confirmation->set_text(unsaved_message + "\n\n" + TTR("Save before reloading?"));
} else {
save_confirmation->set_ok_button_text(TTR("Save & Close"));
save_confirmation->set_text(vformat(TTR("Save changes to '%s' before closing?"), !scene_filename.is_empty() ? scene_filename : "unsaved scene"));
save_confirmation->set_text(unsaved_message + "\n\n" + TTR("Save before closing?"));
}
save_confirmation->reset_size();
save_confirmation->popup_centered();
Expand Down
6 changes: 3 additions & 3 deletions editor/editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,9 +341,9 @@ void EditorPlugin::clear() {
GDVIRTUAL_CALL(_clear);
}

String EditorPlugin::get_unsaved_status() const {
String EditorPlugin::get_unsaved_status(const String &p_for_scene) const {
String ret;
GDVIRTUAL_CALL(_get_unsaved_status, ret);
GDVIRTUAL_CALL(_get_unsaved_status, p_for_scene, ret);
return ret;
}

Expand Down Expand Up @@ -599,7 +599,7 @@ void EditorPlugin::_bind_methods() {
GDVIRTUAL_BIND(_get_state);
GDVIRTUAL_BIND(_set_state, "state");
GDVIRTUAL_BIND(_clear);
GDVIRTUAL_BIND(_get_unsaved_status);
GDVIRTUAL_BIND(_get_unsaved_status, "for_scene");
GDVIRTUAL_BIND(_save_external_data);
GDVIRTUAL_BIND(_apply_changes);
GDVIRTUAL_BIND(_get_breakpoints);
Expand Down
4 changes: 2 additions & 2 deletions editor/editor_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class EditorPlugin : public Node {
GDVIRTUAL0RC(Dictionary, _get_state)
GDVIRTUAL1(_set_state, Dictionary)
GDVIRTUAL0(_clear)
GDVIRTUAL0RC(String, _get_unsaved_status)
GDVIRTUAL1RC(String, _get_unsaved_status, String)
GDVIRTUAL0(_save_external_data)
GDVIRTUAL0(_apply_changes)
GDVIRTUAL0RC(Vector<String>, _get_breakpoints)
Expand Down Expand Up @@ -176,7 +176,7 @@ class EditorPlugin : public Node {
virtual Dictionary get_state() const; //save editor state so it can't be reloaded when reloading scene
virtual void set_state(const Dictionary &p_state); //restore editor state (likely was saved with the scene)
virtual void clear(); // clear any temporary data in the editor, reset it (likely new scene or load another scene)
virtual String get_unsaved_status() const;
virtual String get_unsaved_status(const String &p_for_scene = "") const;
virtual void save_external_data(); // if editor references external resources/scenes, save them
virtual void apply_changes(); // if changes are pending in editor, apply them
virtual void get_breakpoints(List<String> *p_breakpoints);
Expand Down
31 changes: 28 additions & 3 deletions editor/plugins/script_editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2440,7 +2440,7 @@ PackedStringArray ScriptEditor::get_unsaved_scripts() const {

for (int i = 0; i < tab_container->get_tab_count(); i++) {
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_tab_control(i));
if (se->is_unsaved()) {
if (se && se->is_unsaved()) {
unsaved_list.append(se->get_name());
}
}
Expand Down Expand Up @@ -4219,15 +4219,40 @@ void ScriptEditorPlugin::selected_notify() {
_focus_another_editor();
}

String ScriptEditorPlugin::get_unsaved_status() const {
String ScriptEditorPlugin::get_unsaved_status(const String &p_for_scene) const {
const PackedStringArray unsaved_scripts = script_editor->get_unsaved_scripts();
if (unsaved_scripts.is_empty()) {
return String();
}

PackedStringArray message;
if (!p_for_scene.is_empty()) {
PackedStringArray unsaved_built_in_scripts;

const String scene_file = p_for_scene.get_file();
for (const String &E : unsaved_scripts) {
if (!E.is_resource_file() && E.contains(scene_file)) {
unsaved_built_in_scripts.append(E);
}
}

if (unsaved_built_in_scripts.is_empty()) {
return String();
} else {
message.resize(unsaved_built_in_scripts.size() + 1);
message.write[0] = TTR("There are unsaved changes in the following built-in script(s):");

int i = 1;
for (const String &E : unsaved_built_in_scripts) {
message.write[i] = E.trim_suffix("(*)");
i++;
}
return String("\n").join(message);
}
}

message.resize(unsaved_scripts.size() + 1);
message.write[0] = "Save changes to the following script(s) before quitting?";
message.write[0] = TTR("Save changes to the following script(s) before quitting?");

int i = 1;
for (const String &E : unsaved_scripts) {
Expand Down
2 changes: 1 addition & 1 deletion editor/plugins/script_editor_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -573,7 +573,7 @@ class ScriptEditorPlugin : public EditorPlugin {
virtual void make_visible(bool p_visible) override;
virtual void selected_notify() override;

virtual String get_unsaved_status() const override;
virtual String get_unsaved_status(const String &p_for_scene) const override;
virtual void save_external_data() override;
virtual void apply_changes() override;

Expand Down
9 changes: 7 additions & 2 deletions editor/plugins/shader_editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -287,14 +287,19 @@ void ShaderEditorPlugin::get_window_layout(Ref<ConfigFile> p_layout) {
p_layout->set_value("ShaderEditor", "selected_shader", selected_shader);
}

String ShaderEditorPlugin::get_unsaved_status() const {
String ShaderEditorPlugin::get_unsaved_status(const String &p_for_scene) const {
if (!p_for_scene.is_empty()) {
// TODO: handle built-in shaders.
return String();
}

// TODO: This should also include visual shaders and shader includes, but save_external_data() doesn't seem to save them...
PackedStringArray unsaved_shaders;
for (uint32_t i = 0; i < edited_shaders.size(); i++) {
if (edited_shaders[i].shader_editor) {
if (edited_shaders[i].shader_editor->is_unsaved()) {
if (unsaved_shaders.is_empty()) {
unsaved_shaders.append("Save changes to the following shaders(s) before quitting?");
unsaved_shaders.append(TTR("Save changes to the following shaders(s) before quitting?"));
}
unsaved_shaders.append(edited_shaders[i].shader_editor->get_name());
}
Expand Down
2 changes: 1 addition & 1 deletion editor/plugins/shader_editor_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ class ShaderEditorPlugin : public EditorPlugin {
virtual void set_window_layout(Ref<ConfigFile> p_layout) override;
virtual void get_window_layout(Ref<ConfigFile> p_layout) override;

virtual String get_unsaved_status() const override;
virtual String get_unsaved_status(const String &p_for_scene) const override;
virtual void save_external_data() override;
virtual void apply_changes() override;

Expand Down

0 comments on commit b883f32

Please sign in to comment.