The Battle for Wesnoth  1.17.0-dev
debug.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2021
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"
25 #include "gui/widgets/listbox.hpp"
27 #include "gui/widgets/window.hpp"
29 
30 #include <fstream>
31 #include <iomanip>
32 #include <iostream>
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  // logging might not be up yet.
137  std::cerr << "Unknown level '" << param << "' is ignored.\n";
138  }
139  }
140 }
141 
142 void debug_layout_graph::set_domain(const std::string& domain)
143 {
144  if(domain.empty()) {
145  // return error and die
146  domain_ = ALL; /** @todo Should default to 0. */
147  return;
148  }
149 
150  std::vector<std::string> params = utils::split(domain);
151 
152  for(const auto & param : params)
153  {
154  if(param == "all") {
155  domain_ = ALL;
156  // No need to look further even though invalid items are now
157  // ignored.
158  return;
159  } else if(param == "show") {
160  domain_ |= SHOW;
161  } else if(param == "layout") {
162  domain_ |= LAYOUT;
163  } else {
164  // logging might not be up yet.
165  std::cerr << "Unknown domain '" << param << "' is ignored.\n";
166  }
167  }
168 }
169 
170 void debug_layout_graph::generate_dot_file(const std::string& generator,
171  const unsigned domain)
172 {
173  // domain == 0 must also evaluate to true.
174  if((domain_ & domain) != domain) {
175  return;
176  }
177 
178  std::string id = window_->id();
179  if(!id.empty()) {
180  id += '_';
181  }
182  const std::string filename = filename_base_ + id
183  + std::to_string(++sequence_number_)
184  + "-" + generator + ".dot";
185 
186  std::ofstream file(filename.c_str());
187 
188  file << "//Basic layout graph for window id '" << window_->id()
189  << "' using definition '" << window_->definition_ << "'.\n"
190  << "digraph window {\n"
191  << "\tnode [shape=record, style=filled, fillcolor=\"bisque\"];\n"
192  << "\trankdir=LR;\n";
193 
194  widget_generate_info(file, window_, "root");
195 
196  file << "}\n";
197 }
198 
199 void debug_layout_graph::widget_generate_info(std::ostream& out,
200  const widget* widget,
201  const std::string& id,
202  const bool embedded) const
203 {
204  assert(!id.empty());
205 
206  out << "\t" << id
207  << " [label=<<table border=\"0\" cellborder=\"1\" cellspacing=\"0\">";
208 
209  widget_generate_basic_info(out, widget);
210  if(level_ & STATE_INFO)
211  widget_generate_state_info(out, widget);
212  if(level_ & SIZE_INFO)
213  widget_generate_size_info(out, widget);
214 
215  out << "</table>>";
216  if(embedded) {
217  out << ", fillcolor=\"palegoldenrod\"";
218  }
219  out << "];\n";
220 
221  const grid* grid = dynamic_cast<const class grid*>(widget);
222  if(!grid) {
223  const container_base* container = dynamic_cast<const container_base*>(widget);
224 
225  if(container) {
226 
227  widget_generate_info(out, &container->get_grid(), id + "_G", true);
228  out << "\t" << id << " -> " << id << "_G"
229  << " [label=\"(grid)\"];\n";
230  }
231 
232  const scrollbar_container* scrollbar_container
233  = dynamic_cast<const class scrollbar_container*>(widget);
234 
235  if(scrollbar_container) {
236  widget_generate_info(
237  out, scrollbar_container->content_grid_.get(), id + "_C", true);
238  out << "\t" << id << " -> " << id << "_C"
239  << " [label=\"(content)\"];\n";
240  }
241 
242  const listbox* listbox = dynamic_cast<const class listbox*>(widget);
243  if(listbox) {
244  assert(listbox->generator_);
245  }
246 
247  const generator_base* generator = dynamic_cast<const generator_base*>(widget);
248 
249  if(generator) {
250  for(std::size_t i = 0; i < generator->get_item_count(); ++i) {
251 
252  const std::string child_id = id + "_I_"
253  + std::to_string(i);
254 
255  widget_generate_info(out, &generator->item(i), child_id, true);
256 
257  out << "\t" << id << " -> " << child_id
258  << " [label=\"(item)\"];\n";
259  }
260  }
261  }
262  if(grid) {
263  grid_generate_info(out, grid, id);
264  }
265 }
266 
267 static std::string format_label(std::string label)
268 {
269  if(label.size() > 50) {
270  label = label.substr(0, 50) + "...";
271  }
272 
273  // Replace characters that break the dot file/
274  std::replace(label.begin(), label.end(), '>', '_');
275 
276  return label;
277 }
278 
279 void debug_layout_graph::widget_generate_basic_info(std::ostream& out,
280  const widget* widget)
281  const
282 {
283  std::string header_background
284  = level_ & (SIZE_INFO | STATE_INFO) ? " bgcolor=\"gray\"" : "";
285  const styled_widget* control = dynamic_cast<const class styled_widget*>(widget);
286 
287  out << "<tr><td" << header_background << ">" << '\n'
288  << "type=" << get_type(widget) << '\n' << "</td></tr>" << '\n'
289  << "<tr><td" << header_background << ">" << '\n'
290  << "id=" << widget->id() << '\n' << "</td></tr>" << '\n' << "<tr><td"
291  << header_background << ">" << '\n' << "address=" << widget << '\n'
292  << "</td></tr>" << '\n' << "<tr><td" << header_background << ">" << '\n'
293  << "parent=" << widget->parent_ << '\n' << "</td></tr>" << '\n';
294  if(control) {
295  out << "<tr><td" << header_background << ">" << '\n'
296  << "label=" << format_label(control->get_label()) << '\n' << "<tr><td"
297  << header_background << ">" << '\n'
298  << "definition=" << control->definition_ << '\n' << "</td></tr>"
299  << '\n' << "</td></tr>\n";
300  }
301 }
302 
303 void debug_layout_graph::widget_generate_state_info(std::ostream& out,
304  const widget* widget)
305  const
306 {
307  const styled_widget* control = dynamic_cast<const styled_widget*>(widget);
308  if(!control) {
309  return;
310  }
311 
312  out << "<tr><td>\n"
313  << "tooltip=" << control->tooltip() << '\n' << "</td></tr>\n"
314  << "<tr><td>\n"
315  << "help message" << control->help_message() << '\n'
316  // FIXME add value and other specific items
317  << "</td></tr>\n"
318  << "<tr><td>\n"
319  << "active=" << control->get_active() << '\n' << "</td></tr>\n"
320  << "<tr><td>\n"
321  << "visible=" << static_cast<int>(control->get_visible()) << '\n' << "</td></tr>\n"
322  << "<tr><td>\n"
323  << "drawing action=" << static_cast<int>(control->get_drawing_action()) << '\n'
324  << "</td></tr>\n"
325  << "<tr><td>\n"
326  << "clip rect=" << control->clipping_rectangle_ << '\n'
327  << "</td></tr>\n"
328  << "<tr><td>\n"
329  << "use tooltip on label overflow="
330  << control->get_use_tooltip_on_label_overflow() << '\n'
331  << "</td></tr>\n"
332  << "<tr><td>\n"
333  << "does block click dismiss=" << control->disable_click_dismiss()
334  << '\n' << "</td></tr>\n";
335 
336  const scrollbar_container* scrollbar_container
337  = dynamic_cast<const class scrollbar_container*>(widget);
338 
339  if(scrollbar_container) {
340  out << "<tr><td>\n"
341  << "vertical_scrollbar_mode_="
342  << scrollbar_container->vertical_scrollbar_mode_ << '\n'
343  << "</td></tr>\n"
344  << "<tr><td>\n"
345  << "horizontal_scrollbar_mode_="
346  << scrollbar_container->horizontal_scrollbar_mode_ << '\n'
347  << "</td></tr>\n";
348  }
349 }
350 
351 void debug_layout_graph::widget_generate_size_info(std::ostream& out,
352  const widget* widget) const
353 {
354  out << "<tr><td>\n"
355  << "can wrap=" << widget->can_wrap() << '\n' << "</td></tr>\n"
356  << "<tr><td>\n"
357  << "size=" << widget->get_size() << '\n' << "</td></tr>\n"
358  << "<tr><td>\n"
359  << "position=" << widget->get_origin() << '\n' << "</td></tr>\n"
360  << "<tr><td>\n"
361  << "last_best_size_=" << widget->last_best_size_ << '\n'
362  << "</td></tr>\n"
363  << "<tr><td>\n"
364  << "layout_size_=" << widget->layout_size_ << '\n' << "</td></tr>\n";
365 
366 
367  const styled_widget* control = dynamic_cast<const styled_widget*>(widget);
368 
369  if(control) {
370  out << "<tr><td>\n"
371  << "minimum config size=" << control->get_config_minimum_size()
372  << '\n' << "</td></tr>\n"
373  << "<tr><td>\n"
374  << "default config size=" << control->get_config_default_size()
375  << '\n' << "</td></tr>\n"
376  << "<tr><td>\n"
377  << "maximum config size=" << control->get_config_maximum_size()
378  << '\n' << "</td></tr>\n"
379  << "<tr><td>\n"
380  << "shrunken_=" << control->shrunken_ << '\n' << "</td></tr>\n";
381  }
382 
383  const container_base* container = dynamic_cast<const container_base*>(widget);
384 
385  if(container) {
386  out << "<tr><td>\n"
387  << "border_space=" << container->border_space() << '\n'
388  << "</td></tr>\n";
389  }
390 }
391 
392 void debug_layout_graph::grid_generate_info(std::ostream& out,
393  const grid* grid,
394  const std::string& parent_id) const
395 {
396  assert(!parent_id.empty());
397 
398  // maybe change the order to links, child, widgets so the output of the
399  // dot file might look better.
400 
401  out << "\n\n\t// The children of " << parent_id << ".\n";
402 
403  for(unsigned row = 0; row < grid->get_rows(); ++row) {
404  for(unsigned col = 0; col < grid->get_cols(); ++col) {
405 
406  const widget* widget = grid->get_widget(row, col);
407  assert(widget);
408 
409  widget_generate_info(
410  out, widget, get_child_widget_id(parent_id, row, col));
411  }
412  }
413 
414  out << "\n\t// The grid child data of " << parent_id << ".\n";
415 
416  for(unsigned row = 0; row < grid->get_rows(); ++row) {
417  for(unsigned col = 0; col < grid->get_cols(); ++col) {
418 
419  child_generate_info(out,
420  grid->get_child(row, col),
421  get_child_id(parent_id, row, col));
422  }
423  }
424 
425 
426  out << "\n\t// The links of " << parent_id << ".\n";
427 
428  for(unsigned row = 0; row < grid->get_rows(); ++row) {
429  for(unsigned col = 0; col < grid->get_cols(); ++col) {
430 
431  // grid -> child
432  out << "\t" << parent_id << " -> "
433  << get_child_id(parent_id, row, col) << " [label=\"(" << row
434  << ',' << col << ")\"];\n";
435 
436  // child -> widget
437  out << "\t" << get_child_id(parent_id, row, col) << " -> "
438  << get_child_widget_id(parent_id, row, col) << ";\n";
439  }
440  }
441 }
442 
443 void debug_layout_graph::child_generate_info(std::ostream& out,
444  const grid::child& child,
445  const std::string& id) const
446 {
447  assert(!id.empty());
448 
449  unsigned flags = child.get_flags();
450 
451  out << "\t" << id << " [style=\"\", label=<<table border=\"0\" "
452  "cellborder=\"1\" cellspacing=\"0\">\n";
453  out << "<tr><td>\n"
454  << "vertical flag=";
455 
456  switch(flags & grid::VERTICAL_MASK) {
458  out << "send to client";
459  break;
461  out << "align to top";
462  break;
464  out << "center";
465  break;
467  out << "align to bottom";
468  break;
469  default:
470  out << "unknown value("
471  << ((flags & grid::VERTICAL_MASK) >> grid::VERTICAL_SHIFT)
472  << ")";
473  }
474 
475  out << "\n</td></tr>\n"
476  << "<tr><td>\n"
477  << "horizontal flag=";
478 
479  switch(flags & grid::HORIZONTAL_MASK) {
481  out << "send to client";
482  break;
484  out << "align to left";
485  break;
487  out << "center";
488  break;
490  out << "align to right";
491  break;
492  default:
493  out << "unknown value("
495  << ")";
496  }
497 
498  out << "\n</td></tr>\n"
499  << "<tr><td>\n"
500  << "border location=";
501 
502  if((flags & grid::BORDER_ALL) == 0) {
503  out << "none";
504  } else if((flags & grid::BORDER_ALL) == grid::BORDER_ALL) {
505  out << "all";
506  } else {
507  std::string result;
508  if(flags & grid::BORDER_TOP)
509  result += "top, ";
510  if(flags & grid::BORDER_BOTTOM)
511  result += "bottom, ";
512  if(flags & grid::BORDER_LEFT)
513  result += "left, ";
514  if(flags & grid::BORDER_RIGHT)
515  result += "right, ";
516 
517  if(!result.empty()) {
518  result.resize(result.size() - 2);
519  }
520 
521  out << result;
522  }
523 
524  out << "\n</td></tr>\n"
525  << "<tr><td>\n"
526  << "border_size=" << child.get_border_size() << "\n</td></tr>\n";
527 
528  out << "</table>>];\n";
529 }
530 
531 std::string debug_layout_graph::get_type(const widget* widget) const
532 {
533  const styled_widget* control = dynamic_cast<const styled_widget*>(widget);
534  if(control) {
535  return control->get_control_type();
536  } else {
537  const grid* grid = dynamic_cast<const class grid*>(widget);
538  const generator_base* generator = dynamic_cast<const generator_base*>(widget);
539 
540  if(grid) {
541  return "grid";
542  } else if(generator) {
543  return "generator";
544  } else {
545  return "unknown";
546  }
547  }
548 }
549 
550 } // namespace gui2
551 #endif
static const unsigned BORDER_TOP
Definition: grid.hpp:62
This file contains the window object, this object is a top level container which has the event manage...
static const unsigned BORDER_ALL
Definition: grid.hpp:66
static const unsigned VERTICAL_SHIFT
Definition: grid.hpp:48
static const unsigned HORIZONTAL_MASK
Definition: grid.hpp:60
Generic file dialog.
Definition: field-fwd.hpp:23
static const unsigned HORIZONTAL_SHIFT
Definition: grid.hpp:55
std::string label
What to show in the filter&#39;s drop-down list.
Definition: manager.cpp:217
static const unsigned HORIZONTAL_ALIGN_RIGHT
Definition: grid.hpp:59
static const unsigned BORDER_BOTTOM
Definition: grid.hpp:63
void set_level(const std::string &value)
Definition: game.cpp:710
static const unsigned VERTICAL_ALIGN_TOP
Definition: grid.hpp:50
std::size_t i
Definition: function.cpp:967
static const unsigned BORDER_RIGHT
Definition: grid.hpp:65
static const unsigned VERTICAL_ALIGN_CENTER
Definition: grid.hpp:51
static const unsigned HORIZONTAL_ALIGN_CENTER
Definition: grid.hpp:58
static const unsigned VERTICAL_MASK
Definition: grid.hpp:53
static const unsigned HORIZONTAL_GROW_SEND_TO_CLIENT
Definition: grid.hpp:56
static const unsigned VERTICAL_GROW_SEND_TO_CLIENT
Definition: grid.hpp:49
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:61
bool grid()
Definition: general.cpp:535
static const unsigned VERTICAL_ALIGN_BOTTOM
Definition: grid.hpp:52
double t
Definition: astarsearch.cpp:65
std::vector< std::string > split(const config_attribute_value &val)
static const unsigned HORIZONTAL_ALIGN_LEFT
Definition: grid.hpp:57
static const unsigned BORDER_LEFT
Definition: grid.hpp:64