Parcourir la source

Check for unsaved changes when closing a scene

kobewi il y a 2 ans
Parent
commit
b883f32188

+ 17 - 5
doc/classes/EditorPlugin.xml

@@ -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">

+ 39 - 13
editor/editor_node.cpp

@@ -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;
 					}
@@ -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;
@@ -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();

+ 3 - 3
editor/editor_plugin.cpp

@@ -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;
 }
 
@@ -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);

+ 2 - 2
editor/editor_plugin.h

@@ -88,7 +88,7 @@ protected:
 	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)
@@ -176,7 +176,7 @@ public:
 	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);

+ 28 - 3
editor/plugins/script_editor_plugin.cpp

@@ -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());
 		}
 	}
@@ -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) {

+ 1 - 1
editor/plugins/script_editor_plugin.h

@@ -573,7 +573,7 @@ public:
 	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;
 

+ 7 - 2
editor/plugins/shader_editor_plugin.cpp

@@ -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());
 			}

+ 1 - 1
editor/plugins/shader_editor_plugin.h

@@ -115,7 +115,7 @@ public:
 	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;