Merge pull request #67503 from KoBeWi/are_you_saving_this_or_are_you_not_saving_this
Add `_get_unsaved_status()` method to EditorPlugin and implement it for script and shader editors
This commit is contained in:
commit
77d96b24ca
9 changed files with 178 additions and 10 deletions
|
@ -280,6 +280,34 @@
|
|||
[/codeblock]
|
||||
</description>
|
||||
</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 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(for_scene):
|
||||
if not unsaved:
|
||||
return ""
|
||||
|
||||
if for_scene.is_empty():
|
||||
return "Save changes in MyCustomPlugin before closing?"
|
||||
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">
|
||||
<return type="void" />
|
||||
<param index="0" name="configuration" type="ConfigFile" />
|
||||
|
|
|
@ -2777,6 +2777,11 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
|
|||
case FILE_QUIT:
|
||||
case RUN_PROJECT_MANAGER:
|
||||
case RELOAD_CURRENT_PROJECT: {
|
||||
if (p_confirmed && plugin_to_save) {
|
||||
plugin_to_save->save_external_data();
|
||||
p_confirmed = false;
|
||||
}
|
||||
|
||||
if (!p_confirmed) {
|
||||
bool save_each = EDITOR_GET("interface/editor/save_each_scene_on_quit");
|
||||
if (_next_unsaved_scene(!save_each) == -1) {
|
||||
|
@ -2793,6 +2798,28 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
|
|||
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(unsaved_status);
|
||||
} else {
|
||||
save_confirmation->set_ok_button_text(TTR("Save & Quit"));
|
||||
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;
|
||||
}
|
||||
|
||||
_discard_changes();
|
||||
break;
|
||||
}
|
||||
|
@ -3031,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;
|
||||
|
@ -5548,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();
|
||||
|
|
|
@ -382,6 +382,7 @@ private:
|
|||
AcceptDialog *save_accept = nullptr;
|
||||
EditorAbout *about = nullptr;
|
||||
AcceptDialog *warning = nullptr;
|
||||
EditorPlugin *plugin_to_save = nullptr;
|
||||
|
||||
int overridden_default_layout = -1;
|
||||
Ref<ConfigFile> default_layout;
|
||||
|
|
|
@ -341,7 +341,12 @@ void EditorPlugin::clear() {
|
|||
GDVIRTUAL_CALL(_clear);
|
||||
}
|
||||
|
||||
// if editor references external resources/scenes, save them
|
||||
String EditorPlugin::get_unsaved_status(const String &p_for_scene) const {
|
||||
String ret;
|
||||
GDVIRTUAL_CALL(_get_unsaved_status, p_for_scene, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void EditorPlugin::save_external_data() {
|
||||
GDVIRTUAL_CALL(_save_external_data);
|
||||
}
|
||||
|
@ -594,6 +599,7 @@ void EditorPlugin::_bind_methods() {
|
|||
GDVIRTUAL_BIND(_get_state);
|
||||
GDVIRTUAL_BIND(_set_state, "state");
|
||||
GDVIRTUAL_BIND(_clear);
|
||||
GDVIRTUAL_BIND(_get_unsaved_status, "for_scene");
|
||||
GDVIRTUAL_BIND(_save_external_data);
|
||||
GDVIRTUAL_BIND(_apply_changes);
|
||||
GDVIRTUAL_BIND(_get_breakpoints);
|
||||
|
|
|
@ -88,6 +88,7 @@ protected:
|
|||
GDVIRTUAL0RC(Dictionary, _get_state)
|
||||
GDVIRTUAL1(_set_state, Dictionary)
|
||||
GDVIRTUAL0(_clear)
|
||||
GDVIRTUAL1RC(String, _get_unsaved_status, String)
|
||||
GDVIRTUAL0(_save_external_data)
|
||||
GDVIRTUAL0(_apply_changes)
|
||||
GDVIRTUAL0RC(Vector<String>, _get_breakpoints)
|
||||
|
@ -175,6 +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 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);
|
||||
|
|
|
@ -2435,6 +2435,18 @@ bool ScriptEditor::edit(const Ref<Resource> &p_resource, int p_line, int p_col,
|
|||
return true;
|
||||
}
|
||||
|
||||
PackedStringArray ScriptEditor::get_unsaved_scripts() const {
|
||||
PackedStringArray unsaved_list;
|
||||
|
||||
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 && se->is_unsaved()) {
|
||||
unsaved_list.append(se->get_name());
|
||||
}
|
||||
}
|
||||
return unsaved_list;
|
||||
}
|
||||
|
||||
void ScriptEditor::save_current_script() {
|
||||
ScriptEditorBase *current = _get_current_editor();
|
||||
if (!current || _test_script_times_on_disk()) {
|
||||
|
@ -4207,6 +4219,49 @@ void ScriptEditorPlugin::selected_notify() {
|
|||
_focus_another_editor();
|
||||
}
|
||||
|
||||
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] = TTR("Save changes to the following script(s) before quitting?");
|
||||
|
||||
int i = 1;
|
||||
for (const String &E : unsaved_scripts) {
|
||||
message.write[i] = E.trim_suffix("(*)");
|
||||
i++;
|
||||
}
|
||||
return String("\n").join(message);
|
||||
}
|
||||
|
||||
void ScriptEditorPlugin::save_external_data() {
|
||||
script_editor->save_all_scripts();
|
||||
}
|
||||
|
|
|
@ -512,6 +512,7 @@ public:
|
|||
|
||||
void get_breakpoints(List<String> *p_breakpoints);
|
||||
|
||||
PackedStringArray get_unsaved_scripts() const;
|
||||
void save_current_script();
|
||||
void save_all_scripts();
|
||||
|
||||
|
@ -572,6 +573,7 @@ public:
|
|||
virtual void make_visible(bool p_visible) override;
|
||||
virtual void selected_notify() override;
|
||||
|
||||
virtual String get_unsaved_status(const String &p_for_scene) const override;
|
||||
virtual void save_external_data() override;
|
||||
virtual void apply_changes() override;
|
||||
|
||||
|
|
|
@ -287,6 +287,27 @@ 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 &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(TTR("Save changes to the following shaders(s) before quitting?"));
|
||||
}
|
||||
unsaved_shaders.append(edited_shaders[i].shader_editor->get_name());
|
||||
}
|
||||
}
|
||||
}
|
||||
return String("\n").join(unsaved_shaders);
|
||||
}
|
||||
|
||||
void ShaderEditorPlugin::save_external_data() {
|
||||
for (EditedShader &edited_shader : edited_shaders) {
|
||||
if (edited_shader.shader_editor) {
|
||||
|
|
|
@ -115,6 +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 String &p_for_scene) const override;
|
||||
virtual void save_external_data() override;
|
||||
virtual void apply_changes() override;
|
||||
|
||||
|
|
Loading…
Reference in a new issue