1/**************************************************************************/
2/* history_dock.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "history_dock.h"
32
33#include "editor/editor_node.h"
34#include "editor/editor_string_names.h"
35#include "editor/editor_undo_redo_manager.h"
36#include "scene/gui/check_box.h"
37#include "scene/gui/item_list.h"
38
39struct SortActionsByTimestamp {
40 bool operator()(const EditorUndoRedoManager::Action &l, const EditorUndoRedoManager::Action &r) const {
41 return l.timestamp > r.timestamp;
42 }
43};
44
45void HistoryDock::on_history_changed() {
46 if (is_visible_in_tree()) {
47 refresh_history();
48 } else {
49 need_refresh = true;
50 }
51}
52
53void HistoryDock::refresh_history() {
54 action_list->clear();
55 bool include_scene = current_scene_checkbox->is_pressed();
56 bool include_global = global_history_checkbox->is_pressed();
57
58 if (!include_scene && !include_global) {
59 action_list->add_item(TTR("The Beginning"));
60 return;
61 }
62
63 const EditorUndoRedoManager::History &current_scene_history = ur_manager->get_or_create_history(EditorNode::get_editor_data().get_current_edited_scene_history_id());
64 const EditorUndoRedoManager::History &global_history = ur_manager->get_or_create_history(EditorUndoRedoManager::GLOBAL_HISTORY);
65
66 Vector<EditorUndoRedoManager::Action> full_history;
67 {
68 int full_size = 0;
69 if (include_scene) {
70 full_size += current_scene_history.redo_stack.size() + current_scene_history.undo_stack.size();
71 }
72 if (include_global) {
73 full_size += global_history.redo_stack.size() + global_history.undo_stack.size();
74 }
75 full_history.resize(full_size);
76 }
77
78 int i = 0;
79 if (include_scene) {
80 for (const EditorUndoRedoManager::Action &E : current_scene_history.redo_stack) {
81 full_history.write[i] = E;
82 i++;
83 }
84 for (const EditorUndoRedoManager::Action &E : current_scene_history.undo_stack) {
85 full_history.write[i] = E;
86 i++;
87 }
88 }
89 if (include_global) {
90 for (const EditorUndoRedoManager::Action &E : global_history.redo_stack) {
91 full_history.write[i] = E;
92 i++;
93 }
94 for (const EditorUndoRedoManager::Action &E : global_history.undo_stack) {
95 full_history.write[i] = E;
96 i++;
97 }
98 }
99
100 full_history.sort_custom<SortActionsByTimestamp>();
101 for (const EditorUndoRedoManager::Action &E : full_history) {
102 action_list->add_item(E.action_name);
103 if (E.history_id == EditorUndoRedoManager::GLOBAL_HISTORY) {
104 action_list->set_item_custom_fg_color(-1, get_theme_color(SNAME("accent_color"), EditorStringName(Editor)));
105 }
106 }
107
108 action_list->add_item(TTR("The Beginning"));
109 refresh_version();
110}
111
112void HistoryDock::on_version_changed() {
113 if (is_visible_in_tree()) {
114 refresh_version();
115 } else {
116 need_refresh = true;
117 }
118}
119
120void HistoryDock::refresh_version() {
121 int idx = 0;
122 bool include_scene = current_scene_checkbox->is_pressed();
123 bool include_global = global_history_checkbox->is_pressed();
124
125 if (!include_scene && !include_global) {
126 current_version = idx;
127 action_list->set_current(idx);
128 return;
129 }
130
131 const EditorUndoRedoManager::History &current_scene_history = ur_manager->get_or_create_history(EditorNode::get_editor_data().get_current_edited_scene_history_id());
132 const EditorUndoRedoManager::History &global_history = ur_manager->get_or_create_history(EditorUndoRedoManager::GLOBAL_HISTORY);
133 double newest_undo_timestamp = 0;
134
135 if (include_scene && !current_scene_history.undo_stack.is_empty()) {
136 newest_undo_timestamp = current_scene_history.undo_stack.front()->get().timestamp;
137 }
138
139 if (include_global && !global_history.undo_stack.is_empty()) {
140 double global_undo_timestamp = global_history.undo_stack.front()->get().timestamp;
141 if (global_undo_timestamp > newest_undo_timestamp) {
142 newest_undo_timestamp = global_undo_timestamp;
143 }
144 }
145
146 if (include_scene) {
147 int skip = 0;
148 for (const EditorUndoRedoManager::Action &E : current_scene_history.redo_stack) {
149 if (E.timestamp < newest_undo_timestamp) {
150 skip++;
151 } else {
152 break;
153 }
154 }
155 idx += current_scene_history.redo_stack.size() - skip;
156 }
157
158 if (include_global) {
159 int skip = 0;
160 for (const EditorUndoRedoManager::Action &E : global_history.redo_stack) {
161 if (E.timestamp < newest_undo_timestamp) {
162 skip++;
163 } else {
164 break;
165 }
166 }
167 idx += global_history.redo_stack.size() - skip;
168 }
169
170 current_version = idx;
171 action_list->set_current(idx);
172}
173
174void HistoryDock::seek_history(int p_index) {
175 bool include_scene = current_scene_checkbox->is_pressed();
176 bool include_global = global_history_checkbox->is_pressed();
177
178 if (!include_scene && !include_global) {
179 return;
180 }
181 int current_scene_id = EditorNode::get_editor_data().get_current_edited_scene_history_id();
182
183 while (current_version < p_index) {
184 if (include_scene) {
185 if (include_global) {
186 ur_manager->undo();
187 } else {
188 ur_manager->undo_history(current_scene_id);
189 }
190 } else {
191 ur_manager->undo_history(EditorUndoRedoManager::GLOBAL_HISTORY);
192 }
193 }
194
195 while (current_version > p_index) {
196 if (include_scene) {
197 if (include_global) {
198 ur_manager->redo();
199 } else {
200 ur_manager->redo_history(current_scene_id);
201 }
202 } else {
203 ur_manager->redo_history(EditorUndoRedoManager::GLOBAL_HISTORY);
204 }
205 }
206}
207
208void HistoryDock::_notification(int p_notification) {
209 switch (p_notification) {
210 case NOTIFICATION_READY: {
211 EditorNode::get_singleton()->connect("scene_changed", callable_mp(this, &HistoryDock::on_history_changed));
212 } break;
213
214 case NOTIFICATION_VISIBILITY_CHANGED: {
215 if (is_visible_in_tree() && need_refresh) {
216 refresh_history();
217 }
218 } break;
219 }
220}
221
222HistoryDock::HistoryDock() {
223 set_name("History");
224
225 ur_manager = EditorUndoRedoManager::get_singleton();
226 ur_manager->connect("history_changed", callable_mp(this, &HistoryDock::on_history_changed));
227 ur_manager->connect("version_changed", callable_mp(this, &HistoryDock::on_version_changed));
228
229 HBoxContainer *mode_hb = memnew(HBoxContainer);
230 add_child(mode_hb);
231
232 current_scene_checkbox = memnew(CheckBox);
233 mode_hb->add_child(current_scene_checkbox);
234 current_scene_checkbox->set_flat(true);
235 current_scene_checkbox->set_pressed(true);
236 current_scene_checkbox->set_text(TTR("Scene"));
237 current_scene_checkbox->set_h_size_flags(SIZE_EXPAND_FILL);
238 current_scene_checkbox->set_clip_text(true);
239 current_scene_checkbox->connect("toggled", callable_mp(this, &HistoryDock::refresh_history).unbind(1));
240
241 global_history_checkbox = memnew(CheckBox);
242 mode_hb->add_child(global_history_checkbox);
243 global_history_checkbox->set_flat(true);
244 global_history_checkbox->set_pressed(true);
245 global_history_checkbox->set_text(TTR("Global"));
246 global_history_checkbox->set_h_size_flags(SIZE_EXPAND_FILL);
247 global_history_checkbox->set_clip_text(true);
248 global_history_checkbox->connect("toggled", callable_mp(this, &HistoryDock::refresh_history).unbind(1));
249
250 action_list = memnew(ItemList);
251 add_child(action_list);
252 action_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
253 action_list->connect("item_selected", callable_mp(this, &HistoryDock::seek_history));
254}
255