The Battle for Wesnoth  1.19.0-dev
debug.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2024
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 
19 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
20 
21 #include "gui/widgets/debug.hpp"
22 
23 #include "formatter.hpp"
24 #include "log.hpp"
26 #include "gui/widgets/listbox.hpp"
28 #include "gui/widgets/window.hpp"
30 
31 #include <fstream>
32 #include <iomanip>
33 
34 namespace gui2
35 {
36 
37 namespace
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  */
49 std::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  */
73 std::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. */
81 std::string get_base_filename()
82 {
83  std::ostringstream ss;
84 
85  std::time_t t = std::time(nullptr);
86  ss << std::put_time(std::localtime(&t), "%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 
97 const unsigned ALL = UINT_MAX; /**< All levels/domains */
98 
99 const unsigned SIZE_INFO = 1 << 0; /**<
100  * Shows the size info of
101  * children/widgets.
102  */
103 const unsigned STATE_INFO = 1 << 1; /**<
104  * Shows the state info of widgets.
105  */
106 unsigned level_ = 0;
107 unsigned domain_ = 0;
108 } // namespace
109 
110 debug_layout_graph::debug_layout_graph(const window* window)
111  : window_(window), sequence_number_(0), filename_base_(get_base_filename())
112 {
113 }
114 
115 void 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 
141 void 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 
168 void 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 
197 void 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 
265 static 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 
277 void 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 
301 void 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 
349 void 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 
390 void 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 
441 void 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("
469  << ((flags & grid::VERTICAL_MASK) >> grid::VERTICAL_SHIFT)
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 
529 std::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
double t
Definition: astarsearch.cpp:63
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::size_t i
Definition: function.cpp:968
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:209
This file contains the window object, this object is a top level container which has the event manage...
#define PLAIN_LOG
Definition: log.hpp:295
Generic file dialog.
void set_level(const std::string &value)
Definition: game.cpp:693
bool grid()
Definition: general.cpp:565
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:60
std::vector< std::string > split(const config_attribute_value &val)