The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
debug.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2008 - 2025
3 by Mark de Wever <koraq@xs4all.nl>
4 Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY.
12
13 See the COPYING file for more details.
14*/
15
16#define GETTEXT_DOMAIN "wesnoth-lib"
17
18#ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
19
20#include "gui/widgets/debug.hpp"
21
22#include "formatter.hpp"
23#include "log.hpp"
30
31#include <fstream>
32#include <iomanip>
33
34namespace gui2
35{
36
37namespace
38{
39
40/**
41 * Gets the id of a grid child cell.
42 *
43 * @param parent_id The id of the parent grid.
44 * @param row Row number in the grid.
45 * @param col Column number in the grid.
46 *
47 * @returns The id of the child cell.
48 */
49std::string get_child_id(const std::string& parent_id,
50 const unsigned row,
51 const unsigned col)
52{
53 // Originally used this formatter function but it managed to return empty
54 // strings. No idea why so switched to using the good old lexical_cast
55 // instead.
56
57 // return formatter() << parent_id << "_C_" << row << '_' << col;
58 std::string result = parent_id + "_C_" + std::to_string(row)
59 + '_' + std::to_string(col);
60
61 return result;
62}
63
64/**
65 * Gets the id of a widget in a grid child cell.
66 *
67 * @param parent_id The id of the parent grid.
68 * @param row Row number in the grid.
69 * @param col Column number in the grid.
70 *
71 * @returns The id of the widget.
72 */
73std::string get_child_widget_id(const std::string& parent_id,
74 const unsigned row,
75 const unsigned col)
76{
77 return get_child_id(parent_id, row, col) + "_W";
78}
79
80/** Gets the prefix of the filename. */
81std::string get_base_filename()
82{
83 std::ostringstream ss;
84
85 const auto now = std::chrono::system_clock::now();
86 ss << chrono::format_local_timestamp(now, "%Y%m%d_%H%M%S");
87
88 static unsigned counter = 0;
89 ++counter;
90
91 ss << '_' << counter << '_';
92
93 return ss.str();
94}
95/***** ***** ***** ***** FLAGS ***** ***** ***** *****/
96
97const unsigned ALL = std::numeric_limits<unsigned>::max(); /**< All levels/domains */
98
99const unsigned SIZE_INFO = 1 << 0; /**<
100 * Shows the size info of
101 * children/widgets.
102 */
103const unsigned STATE_INFO = 1 << 1; /**<
104 * Shows the state info of widgets.
105 */
106unsigned level_ = 0;
107unsigned domain_ = 0;
108} // namespace
109
110debug_layout_graph::debug_layout_graph(const window* window)
111 : window_(window), sequence_number_(0), filename_base_(get_base_filename())
112{
113}
114
115void debug_layout_graph::set_level(const std::string& level)
116{
117 if(level.empty()) {
118 level_ = ALL; /** @todo Should default to 0. */
119 return;
120 }
121
122 std::vector<std::string> params = utils::split(level);
123
124 for(const auto & param : params)
125 {
126 if(param == "all") {
127 level_ = ALL;
128 // No need to look further even though invalid items are now
129 // ignored.
130 return;
131 } else if(param == "size") {
132 level_ |= SIZE_INFO;
133 } else if(param == "state") {
134 level_ |= STATE_INFO;
135 } else {
136 PLAIN_LOG << "Unknown level '" << param << "' is ignored.";
137 }
138 }
139}
140
141void debug_layout_graph::set_domain(const std::string& domain)
142{
143 if(domain.empty()) {
144 // return error and die
145 domain_ = ALL; /** @todo Should default to 0. */
146 return;
147 }
148
149 std::vector<std::string> params = utils::split(domain);
150
151 for(const auto & param : params)
152 {
153 if(param == "all") {
154 domain_ = ALL;
155 // No need to look further even though invalid items are now
156 // ignored.
157 return;
158 } else if(param == "show") {
159 domain_ |= SHOW;
160 } else if(param == "layout") {
161 domain_ |= LAYOUT;
162 } else {
163 PLAIN_LOG << "Unknown domain '" << param << "' is ignored.";
164 }
165 }
166}
167
168void debug_layout_graph::generate_dot_file(const std::string& generator,
169 const unsigned domain)
170{
171 // domain == 0 must also evaluate to true.
172 if((domain_ & domain) != domain) {
173 return;
174 }
175
176 std::string id = window_->id();
177 if(!id.empty()) {
178 id += '_';
179 }
180 const std::string filename = filename_base_ + id
181 + std::to_string(++sequence_number_)
182 + "-" + generator + ".dot";
183
184 std::ofstream file(filename.c_str());
185
186 file << "//Basic layout graph for window id '" << window_->id()
187 << "' using definition '" << window_->definition_ << "'.\n"
188 << "digraph window {\n"
189 << "\tnode [shape=record, style=filled, fillcolor=\"bisque\"];\n"
190 << "\trankdir=LR;\n";
191
192 widget_generate_info(file, window_, "root");
193
194 file << "}\n";
195}
196
197void debug_layout_graph::widget_generate_info(std::ostream& out,
198 const widget* widget,
199 const std::string& id,
200 const bool embedded) const
201{
202 assert(!id.empty());
203
204 out << "\t" << id
205 << " [label=<<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">";
206
207 widget_generate_basic_info(out, widget);
208 if(level_ & STATE_INFO)
209 widget_generate_state_info(out, widget);
210 if(level_ & SIZE_INFO)
211 widget_generate_size_info(out, widget);
212
213 out << "</table>>";
214 if(embedded) {
215 out << ", fillcolor=\"palegoldenrod\"";
216 }
217 out << "];\n";
218
219 const grid* grid = dynamic_cast<const class grid*>(widget);
220 if(!grid) {
221 const container_base* container = dynamic_cast<const container_base*>(widget);
222
223 if(container) {
224
225 widget_generate_info(out, &container->get_grid(), id + "_G", true);
226 out << "\t" << id << " -> " << id << "_G"
227 << " [label=\"(grid)\"];\n";
228 }
229
230 const scrollbar_container* scrollbar_container
231 = dynamic_cast<const class scrollbar_container*>(widget);
232
233 if(scrollbar_container) {
234 widget_generate_info(out,
235 scrollbar_container->content_grid_.get(), id + "_C", true);
236 out << "\t" << id << " -> " << id << "_C"
237 << " [label=\"(content)\"];\n";
238 }
239
240 const listbox* listbox = dynamic_cast<const class listbox*>(widget);
241 if(listbox) {
242 assert(listbox->generator_);
243 }
244
245 const generator_base* generator = dynamic_cast<const generator_base*>(widget);
246
247 if(generator) {
248 for(std::size_t i = 0; i < generator->get_item_count(); ++i) {
249
250 const std::string child_id = id + "_I_"
251 + std::to_string(i);
252
253 widget_generate_info(out, &generator->item(i), child_id, true);
254
255 out << "\t" << id << " -> " << child_id
256 << " [label=\"(item)\"];\n";
257 }
258 }
259 }
260 if(grid) {
261 grid_generate_info(out, grid, id);
262 }
263}
264
265static std::string format_label(std::string label)
266{
267 if(label.size() > 50) {
268 label = label.substr(0, 50) + "...";
269 }
270
271 // Replace characters that break the dot file/
272 std::replace(label.begin(), label.end(), '>', '_');
273
274 return label;
275}
276
277void debug_layout_graph::widget_generate_basic_info(std::ostream& out,
278 const widget* widget)
279 const
280{
281 std::string header_background
282 = level_ & (SIZE_INFO | STATE_INFO) ? " bgcolor=\"gray\"" : "";
283 const styled_widget* control = dynamic_cast<const class styled_widget*>(widget);
284
285 out << "<tr><td" << header_background << ">" << '\n'
286 << "type=" << get_type(widget) << '\n' << "</td></tr>" << '\n'
287 << "<tr><td" << header_background << ">" << '\n'
288 << "id=" << widget->id() << '\n' << "</td></tr>" << '\n' << "<tr><td"
289 << header_background << ">" << '\n' << "address=" << widget << '\n'
290 << "</td></tr>" << '\n' << "<tr><td" << header_background << ">" << '\n'
291 << "parent=" << widget->parent_ << '\n' << "</td></tr>" << '\n';
292 if(control) {
293 out << "<tr><td" << header_background << ">" << '\n'
294 << "label=" << format_label(control->get_label()) << '\n' << "<tr><td"
295 << header_background << ">" << '\n'
296 << "definition=" << control->definition_ << '\n' << "</td></tr>"
297 << '\n' << "</td></tr>\n";
298 }
299}
300
301void debug_layout_graph::widget_generate_state_info(std::ostream& out,
302 const widget* widget)
303 const
304{
305 const styled_widget* control = dynamic_cast<const styled_widget*>(widget);
306 if(!control) {
307 return;
308 }
309
310 out << "<tr><td>\n"
311 << "tooltip=" << control->tooltip() << '\n' << "</td></tr>\n"
312 << "<tr><td>\n"
313 << "help message" << control->help_message() << '\n'
314 // FIXME add value and other specific items
315 << "</td></tr>\n"
316 << "<tr><td>\n"
317 << "active=" << control->get_active() << '\n' << "</td></tr>\n"
318 << "<tr><td>\n"
319 << "visible=" << static_cast<int>(control->get_visible()) << '\n' << "</td></tr>\n"
320 << "<tr><td>\n"
321 << "drawing action=" << static_cast<int>(control->get_drawing_action()) << '\n'
322 << "</td></tr>\n"
323 << "<tr><td>\n"
324 << "clip rect=" << control->clipping_rectangle_ << '\n'
325 << "</td></tr>\n"
326 << "<tr><td>\n"
327 << "use tooltip on label overflow="
328 << control->get_use_tooltip_on_label_overflow() << '\n'
329 << "</td></tr>\n"
330 << "<tr><td>\n"
331 << "does block click dismiss=" << control->disable_click_dismiss()
332 << '\n' << "</td></tr>\n";
333
334 const scrollbar_container* scrollbar_container
335 = dynamic_cast<const class scrollbar_container*>(widget);
336
337 if(scrollbar_container) {
338 out << "<tr><td>\n"
339 << "vertical_scrollbar_mode_="
340 << scrollbar_container->vertical_scrollbar_mode_ << '\n'
341 << "</td></tr>\n"
342 << "<tr><td>\n"
343 << "horizontal_scrollbar_mode_="
344 << scrollbar_container->horizontal_scrollbar_mode_ << '\n'
345 << "</td></tr>\n";
346 }
347}
348
349void debug_layout_graph::widget_generate_size_info(std::ostream& out,
350 const widget* widget) const
351{
352 out << "<tr><td>\n"
353 << "can wrap=" << widget->can_wrap() << '\n' << "</td></tr>\n"
354 << "<tr><td>\n"
355 << "size=" << widget->get_size() << '\n' << "</td></tr>\n"
356 << "<tr><td>\n"
357 << "position=" << widget->get_origin() << '\n' << "</td></tr>\n"
358 << "<tr><td>\n"
359 << "last_best_size_=" << widget->last_best_size_ << '\n'
360 << "</td></tr>\n"
361 << "<tr><td>\n"
362 << "layout_size_=" << widget->layout_size_ << '\n' << "</td></tr>\n";
363
364
365 const styled_widget* control = dynamic_cast<const styled_widget*>(widget);
366
367 if(control) {
368 out << "<tr><td>\n"
369 << "minimum config size=" << control->get_config_minimum_size()
370 << '\n' << "</td></tr>\n"
371 << "<tr><td>\n"
372 << "default config size=" << control->get_config_default_size()
373 << '\n' << "</td></tr>\n"
374 << "<tr><td>\n"
375 << "maximum config size=" << control->get_config_maximum_size()
376 << '\n' << "</td></tr>\n"
377 << "<tr><td>\n"
378 << "shrunken_=" << control->shrunken_ << '\n' << "</td></tr>\n";
379 }
380
381 const container_base* container = dynamic_cast<const container_base*>(widget);
382
383 if(container) {
384 out << "<tr><td>\n"
385 << "border_space=" << container->border_space() << '\n'
386 << "</td></tr>\n";
387 }
388}
389
390void debug_layout_graph::grid_generate_info(std::ostream& out,
391 const grid* grid,
392 const std::string& parent_id) const
393{
394 assert(!parent_id.empty());
395
396 // maybe change the order to links, child, widgets so the output of the
397 // dot file might look better.
398
399 out << "\n\n\t// The children of " << parent_id << ".\n";
400
401 for(unsigned row = 0; row < grid->get_rows(); ++row) {
402 for(unsigned col = 0; col < grid->get_cols(); ++col) {
403
404 const widget* widget = grid->get_widget(row, col);
405 assert(widget);
406
407 widget_generate_info(
408 out, widget, get_child_widget_id(parent_id, row, col));
409 }
410 }
411
412 out << "\n\t// The grid child data of " << parent_id << ".\n";
413
414 for(unsigned row = 0; row < grid->get_rows(); ++row) {
415 for(unsigned col = 0; col < grid->get_cols(); ++col) {
416
417 child_generate_info(out,
418 grid->get_child(row, col),
419 get_child_id(parent_id, row, col));
420 }
421 }
422
423
424 out << "\n\t// The links of " << parent_id << ".\n";
425
426 for(unsigned row = 0; row < grid->get_rows(); ++row) {
427 for(unsigned col = 0; col < grid->get_cols(); ++col) {
428
429 // grid -> child
430 out << "\t" << parent_id << " -> "
431 << get_child_id(parent_id, row, col) << " [label=\"(" << row
432 << ',' << col << ")\"];\n";
433
434 // child -> widget
435 out << "\t" << get_child_id(parent_id, row, col) << " -> "
436 << get_child_widget_id(parent_id, row, col) << ";\n";
437 }
438 }
439}
440
441void debug_layout_graph::child_generate_info(std::ostream& out,
442 const grid::child& child,
443 const std::string& id) const
444{
445 assert(!id.empty());
446
447 unsigned flags = child.get_flags();
448
449 out << "\t" << id << " [style=\"\", label=<<table border=\"0\" "
450 "cellborder=\"1\" cellspacing=\"0\">\n";
451 out << "<tr><td>\n"
452 << "vertical flag=";
453
454 switch(flags & grid::VERTICAL_MASK) {
456 out << "send to client";
457 break;
459 out << "align to top";
460 break;
462 out << "center";
463 break;
465 out << "align to bottom";
466 break;
467 default:
468 out << "unknown value("
470 << ")";
471 }
472
473 out << "\n</td></tr>\n"
474 << "<tr><td>\n"
475 << "horizontal flag=";
476
477 switch(flags & grid::HORIZONTAL_MASK) {
479 out << "send to client";
480 break;
482 out << "align to left";
483 break;
485 out << "center";
486 break;
488 out << "align to right";
489 break;
490 default:
491 out << "unknown value("
493 << ")";
494 }
495
496 out << "\n</td></tr>\n"
497 << "<tr><td>\n"
498 << "border location=";
499
500 if((flags & grid::BORDER_ALL) == 0) {
501 out << "none";
502 } else if((flags & grid::BORDER_ALL) == grid::BORDER_ALL) {
503 out << "all";
504 } else {
505 std::string result;
506 if(flags & grid::BORDER_TOP)
507 result += "top, ";
508 if(flags & grid::BORDER_BOTTOM)
509 result += "bottom, ";
510 if(flags & grid::BORDER_LEFT)
511 result += "left, ";
512 if(flags & grid::BORDER_RIGHT)
513 result += "right, ";
514
515 if(!result.empty()) {
516 result.resize(result.size() - 2);
517 }
518
519 out << result;
520 }
521
522 out << "\n</td></tr>\n"
523 << "<tr><td>\n"
524 << "border_size=" << child.get_border_size() << "\n</td></tr>\n";
525
526 out << "</table>>];\n";
527}
528
529std::string debug_layout_graph::get_type(const widget* widget) const
530{
531 const styled_widget* control = dynamic_cast<const styled_widget*>(widget);
532 if(control) {
533 return control->get_control_type();
534 } else {
535 const grid* grid = dynamic_cast<const class grid*>(widget);
536 const generator_base* generator = dynamic_cast<const generator_base*>(widget);
537
538 if(grid) {
539 return "grid";
540 } else if(generator) {
541 return "generator";
542 } else {
543 return "unknown";
544 }
545 }
546}
547
548} // namespace gui2
549#endif
static const unsigned HORIZONTAL_GROW_SEND_TO_CLIENT
Definition grid.hpp:56
static const unsigned HORIZONTAL_ALIGN_RIGHT
Definition grid.hpp:59
static const unsigned HORIZONTAL_SHIFT
Definition grid.hpp:55
static const unsigned HORIZONTAL_MASK
Definition grid.hpp:60
static const unsigned VERTICAL_ALIGN_BOTTOM
Definition grid.hpp:52
static const unsigned BORDER_TOP
Definition grid.hpp:62
static const unsigned VERTICAL_ALIGN_CENTER
Definition grid.hpp:51
static const unsigned VERTICAL_GROW_SEND_TO_CLIENT
Definition grid.hpp:49
static const unsigned BORDER_BOTTOM
Definition grid.hpp:63
static const unsigned BORDER_RIGHT
Definition grid.hpp:65
static const unsigned HORIZONTAL_ALIGN_CENTER
Definition grid.hpp:58
static const unsigned VERTICAL_MASK
Definition grid.hpp:53
static const unsigned VERTICAL_ALIGN_TOP
Definition grid.hpp:50
static const unsigned BORDER_LEFT
Definition grid.hpp:64
static const unsigned BORDER_ALL
Definition grid.hpp:66
static const unsigned HORIZONTAL_ALIGN_LEFT
Definition grid.hpp:57
static const unsigned VERTICAL_SHIFT
Definition grid.hpp:48
std::string label
What to show in the filter's drop-down list.
Definition manager.cpp:201
This file contains the window object, this object is a top level container which has the event manage...
#define PLAIN_LOG
Definition log.hpp:296
auto format_local_timestamp(const std::chrono::system_clock::time_point &time, std::string_view format="%F %T")
Definition chrono.hpp:72
Generic file dialog.
std::vector< std::string > split(const config_attribute_value &val)
std::string filename
Filename.
#define i