Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/editor_log.cpp
9821 views
1
/**************************************************************************/
2
/* editor_log.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 "editor_log.h"
32
33
#include "core/object/undo_redo.h"
34
#include "core/os/keyboard.h"
35
#include "core/version.h"
36
#include "editor/editor_node.h"
37
#include "editor/editor_string_names.h"
38
#include "editor/file_system/editor_paths.h"
39
#include "editor/settings/editor_settings.h"
40
#include "editor/themes/editor_scale.h"
41
#include "scene/gui/separator.h"
42
#include "scene/resources/font.h"
43
44
void EditorLog::_error_handler(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, bool p_editor_notify, ErrorHandlerType p_type) {
45
EditorLog *self = static_cast<EditorLog *>(p_self);
46
47
String err_str;
48
if (p_errorexp && p_errorexp[0]) {
49
err_str = String::utf8(p_errorexp);
50
} else {
51
err_str = String::utf8(p_file) + ":" + itos(p_line) + " - " + String::utf8(p_error);
52
}
53
54
MessageType message_type = p_type == ERR_HANDLER_WARNING ? MSG_TYPE_WARNING : MSG_TYPE_ERROR;
55
56
if (!Thread::is_main_thread()) {
57
MessageQueue::get_main_singleton()->push_callable(callable_mp(self, &EditorLog::add_message), err_str, message_type);
58
} else {
59
self->add_message(err_str, message_type);
60
}
61
}
62
63
void EditorLog::_update_theme() {
64
const Ref<Font> normal_font = get_theme_font(SNAME("output_source"), EditorStringName(EditorFonts));
65
if (normal_font.is_valid()) {
66
log->add_theme_font_override("normal_font", normal_font);
67
}
68
69
const Ref<Font> bold_font = get_theme_font(SNAME("output_source_bold"), EditorStringName(EditorFonts));
70
if (bold_font.is_valid()) {
71
log->add_theme_font_override("bold_font", bold_font);
72
}
73
74
const Ref<Font> italics_font = get_theme_font(SNAME("output_source_italic"), EditorStringName(EditorFonts));
75
if (italics_font.is_valid()) {
76
log->add_theme_font_override("italics_font", italics_font);
77
}
78
79
const Ref<Font> bold_italics_font = get_theme_font(SNAME("output_source_bold_italic"), EditorStringName(EditorFonts));
80
if (bold_italics_font.is_valid()) {
81
log->add_theme_font_override("bold_italics_font", bold_italics_font);
82
}
83
84
const Ref<Font> mono_font = get_theme_font(SNAME("output_source_mono"), EditorStringName(EditorFonts));
85
if (mono_font.is_valid()) {
86
log->add_theme_font_override("mono_font", mono_font);
87
}
88
89
// Disable padding for highlighted background/foreground to prevent highlights from overlapping on close lines.
90
// This also better matches terminal output, which does not use any form of padding.
91
log->add_theme_constant_override("text_highlight_h_padding", 0);
92
log->add_theme_constant_override("text_highlight_v_padding", 0);
93
94
const int font_size = get_theme_font_size(SNAME("output_source_size"), EditorStringName(EditorFonts));
95
log->begin_bulk_theme_override();
96
log->add_theme_font_size_override("normal_font_size", font_size);
97
log->add_theme_font_size_override("bold_font_size", font_size);
98
log->add_theme_font_size_override("italics_font_size", font_size);
99
log->add_theme_font_size_override("mono_font_size", font_size);
100
log->end_bulk_theme_override();
101
102
type_filter_map[MSG_TYPE_STD]->toggle_button->set_button_icon(get_editor_theme_icon(SNAME("Popup")));
103
type_filter_map[MSG_TYPE_ERROR]->toggle_button->set_button_icon(get_editor_theme_icon(SNAME("StatusError")));
104
type_filter_map[MSG_TYPE_WARNING]->toggle_button->set_button_icon(get_editor_theme_icon(SNAME("StatusWarning")));
105
type_filter_map[MSG_TYPE_EDITOR]->toggle_button->set_button_icon(get_editor_theme_icon(SNAME("Edit")));
106
107
type_filter_map[MSG_TYPE_STD]->toggle_button->set_theme_type_variation("EditorLogFilterButton");
108
type_filter_map[MSG_TYPE_ERROR]->toggle_button->set_theme_type_variation("EditorLogFilterButton");
109
type_filter_map[MSG_TYPE_WARNING]->toggle_button->set_theme_type_variation("EditorLogFilterButton");
110
type_filter_map[MSG_TYPE_EDITOR]->toggle_button->set_theme_type_variation("EditorLogFilterButton");
111
112
clear_button->set_button_icon(get_editor_theme_icon(SNAME("Clear")));
113
copy_button->set_button_icon(get_editor_theme_icon(SNAME("ActionCopy")));
114
collapse_button->set_button_icon(get_editor_theme_icon(SNAME("CombineLines")));
115
show_search_button->set_button_icon(get_editor_theme_icon(SNAME("Search")));
116
search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));
117
118
theme_cache.error_color = get_theme_color(SNAME("error_color"), EditorStringName(Editor));
119
theme_cache.error_icon = get_editor_theme_icon(SNAME("Error"));
120
theme_cache.warning_color = get_theme_color(SNAME("warning_color"), EditorStringName(Editor));
121
theme_cache.warning_icon = get_editor_theme_icon(SNAME("Warning"));
122
theme_cache.message_color = get_theme_color(SceneStringName(font_color), EditorStringName(Editor)) * Color(1, 1, 1, 0.6);
123
}
124
125
void EditorLog::_editor_settings_changed() {
126
int new_line_limit = int(EDITOR_GET("run/output/max_lines"));
127
if (new_line_limit != line_limit) {
128
line_limit = new_line_limit;
129
_rebuild_log();
130
}
131
}
132
133
void EditorLog::_notification(int p_what) {
134
switch (p_what) {
135
case NOTIFICATION_ENTER_TREE: {
136
_update_theme();
137
_load_state();
138
} break;
139
140
case NOTIFICATION_THEME_CHANGED: {
141
_update_theme();
142
_rebuild_log();
143
} break;
144
}
145
}
146
147
void EditorLog::_set_collapse(bool p_collapse) {
148
collapse = p_collapse;
149
_start_state_save_timer();
150
_rebuild_log();
151
}
152
153
void EditorLog::_start_state_save_timer() {
154
if (!is_loading_state) {
155
save_state_timer->start();
156
}
157
}
158
159
void EditorLog::_save_state() {
160
Ref<ConfigFile> config;
161
config.instantiate();
162
// Load and amend existing config if it exists.
163
config->load(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg"));
164
165
const String section = "editor_log";
166
for (const KeyValue<MessageType, LogFilter *> &E : type_filter_map) {
167
config->set_value(section, "log_filter_" + itos(E.key), E.value->is_active());
168
}
169
170
config->set_value(section, "collapse", collapse);
171
config->set_value(section, "show_search", search_box->is_visible());
172
173
config->save(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg"));
174
}
175
176
void EditorLog::_load_state() {
177
is_loading_state = true;
178
179
Ref<ConfigFile> config;
180
config.instantiate();
181
config->load(EditorPaths::get_singleton()->get_project_settings_dir().path_join("editor_layout.cfg"));
182
183
// Run the below code even if config->load returns an error, since we want the defaults to be set even if the file does not exist yet.
184
const String section = "editor_log";
185
for (const KeyValue<MessageType, LogFilter *> &E : type_filter_map) {
186
E.value->set_active(config->get_value(section, "log_filter_" + itos(E.key), true));
187
}
188
189
collapse = config->get_value(section, "collapse", false);
190
collapse_button->set_pressed(collapse);
191
bool show_search = config->get_value(section, "show_search", true);
192
search_box->set_visible(show_search);
193
show_search_button->set_pressed(show_search);
194
195
is_loading_state = false;
196
}
197
198
void EditorLog::_meta_clicked(const String &p_meta) {
199
OS::get_singleton()->shell_open(p_meta);
200
}
201
202
void EditorLog::_clear_request() {
203
log->clear();
204
messages.clear();
205
_reset_message_counts();
206
tool_button->set_button_icon(Ref<Texture2D>());
207
}
208
209
void EditorLog::_copy_request() {
210
String text = log->get_selected_text();
211
212
if (text.is_empty()) {
213
text = log->get_parsed_text();
214
}
215
216
if (!text.is_empty()) {
217
DisplayServer::get_singleton()->clipboard_set(text);
218
}
219
}
220
221
void EditorLog::clear() {
222
_clear_request();
223
}
224
225
void EditorLog::_process_message(const String &p_msg, MessageType p_type, bool p_clear) {
226
if (messages.size() > 0 && messages[messages.size() - 1].text == p_msg && messages[messages.size() - 1].type == p_type) {
227
// If previous message is the same as the new one, increase previous count rather than adding another
228
// instance to the messages list.
229
LogMessage &previous = messages.write[messages.size() - 1];
230
previous.count++;
231
232
_add_log_line(previous, collapse);
233
} else {
234
// Different message to the previous one received.
235
LogMessage message(p_msg, p_type, p_clear);
236
_add_log_line(message);
237
messages.push_back(message);
238
}
239
240
type_filter_map[p_type]->set_message_count(type_filter_map[p_type]->get_message_count() + 1);
241
}
242
243
void EditorLog::add_message(const String &p_msg, MessageType p_type) {
244
// Make text split by new lines their own message.
245
// See #41321 for reasoning. At time of writing, multiple print()'s in running projects
246
// get grouped together and sent to the editor log as one message. This can mess with the
247
// search functionality (see the comments on the PR above for more details). This behavior
248
// also matches that of other IDE's.
249
Vector<String> lines = p_msg.split("\n", true);
250
int line_count = lines.size();
251
252
for (int i = 0; i < line_count; i++) {
253
_process_message(lines[i], p_type, i == line_count - 1);
254
}
255
}
256
257
void EditorLog::set_tool_button(Button *p_tool_button) {
258
tool_button = p_tool_button;
259
}
260
261
void EditorLog::register_undo_redo(UndoRedo *p_undo_redo) {
262
p_undo_redo->set_commit_notify_callback(_undo_redo_cbk, this);
263
}
264
265
void EditorLog::_undo_redo_cbk(void *p_self, const String &p_name) {
266
EditorLog *self = static_cast<EditorLog *>(p_self);
267
self->add_message(p_name, EditorLog::MSG_TYPE_EDITOR);
268
}
269
270
void EditorLog::_rebuild_log() {
271
if (messages.is_empty()) {
272
return;
273
}
274
275
log->clear();
276
277
int line_count = 0;
278
int start_message_index = 0;
279
int initial_skip = 0;
280
281
// Search backward for starting place.
282
for (start_message_index = messages.size() - 1; start_message_index >= 0; start_message_index--) {
283
LogMessage msg = messages[start_message_index];
284
if (collapse) {
285
if (_check_display_message(msg)) {
286
line_count++;
287
}
288
} else {
289
// If not collapsing, log each instance on a line.
290
for (int i = 0; i < msg.count; i++) {
291
if (_check_display_message(msg)) {
292
line_count++;
293
}
294
}
295
}
296
if (line_count >= line_limit) {
297
initial_skip = line_count - line_limit;
298
break;
299
}
300
if (start_message_index == 0) {
301
break;
302
}
303
}
304
305
for (int msg_idx = start_message_index; msg_idx < messages.size(); msg_idx++) {
306
LogMessage msg = messages[msg_idx];
307
308
if (collapse) {
309
// If collapsing, only log one instance of the message.
310
_add_log_line(msg);
311
} else {
312
// If not collapsing, log each instance on a line.
313
for (int i = initial_skip; i < msg.count; i++) {
314
initial_skip = 0;
315
_add_log_line(msg);
316
}
317
}
318
}
319
}
320
321
bool EditorLog::_check_display_message(LogMessage &p_message) {
322
bool filter_active = type_filter_map[p_message.type]->is_active();
323
String search_text = search_box->get_text();
324
bool search_match = search_text.is_empty() || p_message.text.containsn(search_text);
325
return filter_active && search_match;
326
}
327
328
void EditorLog::_add_log_line(LogMessage &p_message, bool p_replace_previous) {
329
if (!is_inside_tree()) {
330
// The log will be built all at once when it enters the tree and has its theme items.
331
return;
332
}
333
334
if (unlikely(log->is_updating())) {
335
// The new message arrived during log RTL text processing/redraw (invalid BiDi control characters / font error), ignore it to avoid RTL data corruption.
336
return;
337
}
338
339
// Only add the message to the log if it passes the filters.
340
if (!_check_display_message(p_message)) {
341
return;
342
}
343
344
if (p_replace_previous) {
345
// Remove last line if replacing, as it will be replace by the next added line.
346
// Why "- 2"? RichTextLabel is weird. When you add a line with add_newline(), it also adds an element to the list of lines which is null/blank,
347
// but it still counts as a line. So if you remove the last line (count - 1) you are actually removing nothing...
348
log->remove_paragraph(log->get_paragraph_count() - 2);
349
}
350
351
switch (p_message.type) {
352
case MSG_TYPE_STD: {
353
} break;
354
case MSG_TYPE_STD_RICH: {
355
} break;
356
case MSG_TYPE_ERROR: {
357
log->push_color(theme_cache.error_color);
358
Ref<Texture2D> icon = theme_cache.error_icon;
359
log->add_image(icon);
360
log->push_bold();
361
log->add_text(" ERROR: ");
362
log->pop(); // bold
363
tool_button->set_button_icon(icon);
364
} break;
365
case MSG_TYPE_WARNING: {
366
log->push_color(theme_cache.warning_color);
367
Ref<Texture2D> icon = theme_cache.warning_icon;
368
log->add_image(icon);
369
log->push_bold();
370
log->add_text(" WARNING: ");
371
log->pop(); // bold
372
tool_button->set_button_icon(icon);
373
} break;
374
case MSG_TYPE_EDITOR: {
375
// Distinguish editor messages from messages printed by the project
376
log->push_color(theme_cache.message_color);
377
} break;
378
}
379
380
// If collapsing, add the count of this message in bold at the start of the line.
381
if (collapse && p_message.count > 1) {
382
log->push_bold();
383
log->add_text(vformat("(%s) ", itos(p_message.count)));
384
log->pop();
385
}
386
387
if (p_message.type == MSG_TYPE_STD_RICH) {
388
log->append_text(p_message.text);
389
} else {
390
log->add_text(p_message.text);
391
}
392
if (p_message.clear || p_message.type != MSG_TYPE_STD_RICH) {
393
log->pop_all(); // Pop all unclosed tags.
394
}
395
log->add_newline();
396
397
if (p_replace_previous) {
398
// Force sync last line update (skip if number of unprocessed log messages is too large to avoid editor lag).
399
if (log->get_pending_paragraphs() < 100) {
400
log->wait_until_finished();
401
}
402
}
403
404
while (log->get_paragraph_count() > line_limit + 1) {
405
log->remove_paragraph(0, true);
406
}
407
}
408
409
void EditorLog::_set_filter_active(bool p_active, MessageType p_message_type) {
410
type_filter_map[p_message_type]->set_active(p_active);
411
_start_state_save_timer();
412
_rebuild_log();
413
}
414
415
void EditorLog::_set_search_visible(bool p_visible) {
416
search_box->set_visible(p_visible);
417
if (p_visible) {
418
search_box->grab_focus();
419
}
420
_start_state_save_timer();
421
}
422
423
void EditorLog::_search_changed(const String &p_text) {
424
_rebuild_log();
425
}
426
427
void EditorLog::_reset_message_counts() {
428
for (const KeyValue<MessageType, LogFilter *> &E : type_filter_map) {
429
E.value->set_message_count(0);
430
}
431
}
432
433
EditorLog::EditorLog() {
434
save_state_timer = memnew(Timer);
435
save_state_timer->set_wait_time(2);
436
save_state_timer->set_one_shot(true);
437
save_state_timer->connect("timeout", callable_mp(this, &EditorLog::_save_state));
438
add_child(save_state_timer);
439
440
line_limit = int(EDITOR_GET("run/output/max_lines"));
441
EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &EditorLog::_editor_settings_changed));
442
443
HBoxContainer *hb = this;
444
445
VBoxContainer *vb_left = memnew(VBoxContainer);
446
vb_left->set_custom_minimum_size(Size2(0, 180) * EDSCALE);
447
vb_left->set_v_size_flags(SIZE_EXPAND_FILL);
448
vb_left->set_h_size_flags(SIZE_EXPAND_FILL);
449
hb->add_child(vb_left);
450
451
// Log - Rich Text Label.
452
log = memnew(RichTextLabel);
453
log->set_threaded(true);
454
log->set_use_bbcode(true);
455
log->set_scroll_follow(true);
456
log->set_selection_enabled(true);
457
log->set_context_menu_enabled(true);
458
log->set_focus_mode(FOCUS_CLICK);
459
log->set_v_size_flags(SIZE_EXPAND_FILL);
460
log->set_h_size_flags(SIZE_EXPAND_FILL);
461
log->set_deselect_on_focus_loss_enabled(false);
462
log->connect("meta_clicked", callable_mp(this, &EditorLog::_meta_clicked));
463
vb_left->add_child(log);
464
465
// Search box
466
search_box = memnew(LineEdit);
467
search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
468
search_box->set_placeholder(TTR("Filter Messages"));
469
search_box->set_accessibility_name(TTRC("Filter Messages"));
470
search_box->set_clear_button_enabled(true);
471
search_box->set_visible(true);
472
search_box->connect(SceneStringName(text_changed), callable_mp(this, &EditorLog::_search_changed));
473
vb_left->add_child(search_box);
474
475
VBoxContainer *vb_right = memnew(VBoxContainer);
476
hb->add_child(vb_right);
477
478
// Tools grid
479
HBoxContainer *hb_tools = memnew(HBoxContainer);
480
hb_tools->set_h_size_flags(SIZE_SHRINK_CENTER);
481
vb_right->add_child(hb_tools);
482
483
// Clear.
484
clear_button = memnew(Button);
485
clear_button->set_accessibility_name(TTRC("Clear Log"));
486
clear_button->set_theme_type_variation(SceneStringName(FlatButton));
487
clear_button->set_focus_mode(FOCUS_ACCESSIBILITY);
488
clear_button->set_shortcut(ED_SHORTCUT("editor/clear_output", TTRC("Clear Output"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::ALT | Key::K));
489
clear_button->connect(SceneStringName(pressed), callable_mp(this, &EditorLog::_clear_request));
490
hb_tools->add_child(clear_button);
491
492
// Copy.
493
copy_button = memnew(Button);
494
copy_button->set_accessibility_name(TTRC("Copy Selection"));
495
copy_button->set_theme_type_variation(SceneStringName(FlatButton));
496
copy_button->set_focus_mode(FOCUS_ACCESSIBILITY);
497
copy_button->set_shortcut(ED_SHORTCUT("editor/copy_output", TTRC("Copy Selection"), KeyModifierMask::CMD_OR_CTRL | Key::C));
498
copy_button->set_shortcut_context(this);
499
copy_button->connect(SceneStringName(pressed), callable_mp(this, &EditorLog::_copy_request));
500
hb_tools->add_child(copy_button);
501
502
// Separate toggle buttons from normal buttons.
503
vb_right->add_child(memnew(HSeparator));
504
505
// A second hbox to make a 2x2 grid of buttons.
506
HBoxContainer *hb_tools2 = memnew(HBoxContainer);
507
hb_tools2->set_h_size_flags(SIZE_SHRINK_CENTER);
508
vb_right->add_child(hb_tools2);
509
510
// Collapse.
511
collapse_button = memnew(Button);
512
collapse_button->set_theme_type_variation(SceneStringName(FlatButton));
513
collapse_button->set_focus_mode(FOCUS_ACCESSIBILITY);
514
collapse_button->set_tooltip_text(TTR("Collapse duplicate messages into one log entry. Shows number of occurrences."));
515
collapse_button->set_toggle_mode(true);
516
collapse_button->set_pressed(false);
517
collapse_button->connect(SceneStringName(toggled), callable_mp(this, &EditorLog::_set_collapse));
518
hb_tools2->add_child(collapse_button);
519
520
// Show Search.
521
show_search_button = memnew(Button);
522
show_search_button->set_accessibility_name(TTRC("Show Search"));
523
show_search_button->set_theme_type_variation(SceneStringName(FlatButton));
524
show_search_button->set_focus_mode(FOCUS_ACCESSIBILITY);
525
show_search_button->set_toggle_mode(true);
526
show_search_button->set_pressed(true);
527
show_search_button->set_shortcut(ED_SHORTCUT("editor/open_search", TTRC("Focus Search/Filter Bar"), KeyModifierMask::CMD_OR_CTRL | Key::F));
528
show_search_button->set_shortcut_context(this);
529
show_search_button->connect(SceneStringName(toggled), callable_mp(this, &EditorLog::_set_search_visible));
530
hb_tools2->add_child(show_search_button);
531
532
// Message Type Filters.
533
vb_right->add_child(memnew(HSeparator));
534
535
LogFilter *std_filter = memnew(LogFilter(MSG_TYPE_STD));
536
std_filter->initialize_button(TTRC("Standard Messages"), TTRC("Toggle visibility of standard output messages."), callable_mp(this, &EditorLog::_set_filter_active));
537
vb_right->add_child(std_filter->toggle_button);
538
type_filter_map.insert(MSG_TYPE_STD, std_filter);
539
type_filter_map.insert(MSG_TYPE_STD_RICH, std_filter);
540
541
LogFilter *error_filter = memnew(LogFilter(MSG_TYPE_ERROR));
542
error_filter->initialize_button(TTRC("Errors"), TTRC("Toggle visibility of errors."), callable_mp(this, &EditorLog::_set_filter_active));
543
vb_right->add_child(error_filter->toggle_button);
544
type_filter_map.insert(MSG_TYPE_ERROR, error_filter);
545
546
LogFilter *warning_filter = memnew(LogFilter(MSG_TYPE_WARNING));
547
warning_filter->initialize_button(TTRC("Warnings"), TTRC("Toggle visibility of warnings."), callable_mp(this, &EditorLog::_set_filter_active));
548
vb_right->add_child(warning_filter->toggle_button);
549
type_filter_map.insert(MSG_TYPE_WARNING, warning_filter);
550
551
LogFilter *editor_filter = memnew(LogFilter(MSG_TYPE_EDITOR));
552
editor_filter->initialize_button(TTRC("Editor Messages"), TTRC("Toggle visibility of editor messages."), callable_mp(this, &EditorLog::_set_filter_active));
553
vb_right->add_child(editor_filter->toggle_button);
554
type_filter_map.insert(MSG_TYPE_EDITOR, editor_filter);
555
556
add_message(GODOT_VERSION_FULL_NAME " (c) 2007-present Juan Linietsky, Ariel Manzur & Godot Contributors.");
557
558
eh.errfunc = _error_handler;
559
eh.userdata = this;
560
add_error_handler(&eh);
561
}
562
563
void EditorLog::deinit() {
564
remove_error_handler(&eh);
565
}
566
567
EditorLog::~EditorLog() {
568
for (const KeyValue<MessageType, LogFilter *> &E : type_filter_map) {
569
// MSG_TYPE_STD_RICH is connected to the std_filter button, so we do this
570
// to avoid it from being deleted twice, causing a crash on closing.
571
if (E.key != MSG_TYPE_STD_RICH) {
572
memdelete(E.value);
573
}
574
}
575
}
576
577