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