Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add _get_unsaved_status() method to EditorPlugin and implement it for script and shader editors #67503

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions doc/classes/EditorPlugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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" />
Expand Down
70 changes: 61 additions & 9 deletions editor/editor_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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();
KoBeWi marked this conversation as resolved.
Show resolved Hide resolved
plugin_to_save = editor_data.get_editor_plugin(i);
break;
}
}

if (plugin_to_save) {
break;
}

_discard_changes();
break;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down
1 change: 1 addition & 0 deletions editor/editor_node.h
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,7 @@ class EditorNode : public Node {
AcceptDialog *save_accept = nullptr;
EditorAbout *about = nullptr;
AcceptDialog *warning = nullptr;
EditorPlugin *plugin_to_save = nullptr;

int overridden_default_layout = -1;
Ref<ConfigFile> default_layout;
Expand Down
8 changes: 7 additions & 1 deletion editor/editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down
2 changes: 2 additions & 0 deletions editor/editor_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class EditorPlugin : public Node {
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)
Expand Down Expand Up @@ -175,6 +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 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
55 changes: 55 additions & 0 deletions editor/plugins/script_editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand Down Expand Up @@ -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();
}
Expand Down
2 changes: 2 additions & 0 deletions editor/plugins/script_editor_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ class ScriptEditor : public PanelContainer {

void get_breakpoints(List<String> *p_breakpoints);

PackedStringArray get_unsaved_scripts() const;
void save_current_script();
void save_all_scripts();

Expand Down Expand Up @@ -572,6 +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 String &p_for_scene) const override;
virtual void save_external_data() override;
virtual void apply_changes() override;

Expand Down
21 changes: 21 additions & 0 deletions editor/plugins/shader_editor_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions editor/plugins/shader_editor_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +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 String &p_for_scene) const override;
virtual void save_external_data() override;
virtual void apply_changes() override;

Expand Down