The Battle for Wesnoth  1.15.2+dev
debug.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2018 by Mark de Wever <koraq@xs4all.nl>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16 
17 
18 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
19 
20 #include "gui/widgets/debug.hpp"
21 
22 #include "formatter.hpp"
24 #include "gui/widgets/listbox.hpp"
26 #include "gui/widgets/window.hpp"
28 
29 #include <fstream>
30 #include <iostream>
31 
32 namespace gui2
33 {
34 
35 namespace
36 {
37 
38 /**
39  * Gets the id of a grid child cell.
40  *
41  * @param parent_id The id of the parent grid.
42  * @param row Row number in the grid.
43  * @param col Column number in the grid.
44  *
45  * @returns The id of the child cell.
46  */
47 std::string get_child_id(const std::string& parent_id,
48  const unsigned row,
49  const unsigned col)
50 {
51  // Originally used this formatter function but it managed to return empty
52  // strings. No idea why so switched to using the good old lexical_cast
53  // instead.
54 
55  // return formatter() << parent_id << "_C_" << row << '_' << col;
56  std::string result = parent_id + "_C_" + std::to_string(row)
57  + '_' + std::to_string(col);
58 
59  return result;
60 }
61 
62 /**
63  * Gets the id of a widget in a grid child cell.
64  *
65  * @param parent_id The id of the parent grid.
66  * @param row Row number in the grid.
67  * @param col Column number in the grid.
68  *
69  * @returns The id of the widget.
70  */
71 std::string get_child_widget_id(const std::string& parent_id,
72  const unsigned row,
73  const unsigned col)
74 {
75  return get_child_id(parent_id, row, col) + "_W";
76 }
77 
78 /** Gets the prefix of the filename. */
79 std::string get_base_filename()
80 {
81  std::ostringstream ss;
82 
83  std::time_t t = std::time(nullptr);
84  ss << std::put_time(std::localtime(&t), "%Y%m%d_%H%M%S");
85 
86  static unsigned counter = 0;
87  ++counter;
88 
89  ss << '_' << counter << '_';
90 
91  return ss.str();
92 }
93 /***** ***** ***** ***** FLAGS ***** ***** ***** *****/
94 
95 const unsigned ALL = UINT_MAX; /**< All levels/domains */
96 
97 const unsigned SIZE_INFO = 1 << 0; /**<
98  * Shows the size info of
99  * children/widgets.
100  */
101 const unsigned STATE_INFO = 1 << 1; /**<
102  * Shows the state info of widgets.
103  */
104 unsigned level_ = 0;
105 unsigned domain_ = 0;
106 } // namespace
107 
108 debug_layout_graph::debug_layout_graph(const window* window)
109  : window_(window), sequence_number_(0), filename_base_(get_base_filename())
110 {
111 }
112 
113 void debug_layout_graph::set_level(const std::string& level)
114 {
115  if(level.empty()) {
116  level_ = ALL; /** @todo Should default to 0. */
117  return;
118  }
119 
120  std::vector<std::string> params = utils::split(level);
121 
122  for(const auto & param : params)
123  {
124  if(param == "all") {
125  level_ = ALL;
126  // No need to look further even though invalid items are now
127  // ignored.
128  return;
129  } else if(param == "size") {
130  level_ |= SIZE_INFO;
131  } else if(param == "state") {
132  level_ |= STATE_INFO;
133  } else {
134  // logging might not be up yet.
135  std::cerr << "Unknown level '" << param << "' is ignored.\n";
136  }
137  }
138 }
139 
140 void debug_layout_graph::set_domain(const std::string& domain)
141 {
142  if(domain.empty()) {
143  // return error and die
144  domain_ = ALL; /** @todo Should default to 0. */
145  return;
146  }
147 
148  std::vector<std::string> params = utils::split(domain);
149 
150  for(const auto & param : params)
151  {
152  if(param == "all") {
153  domain_ = ALL;
154  // No need to look further even though invalid items are now
155  // ignored.
156  return;
157  } else if(param == "show") {
158  domain_ |= SHOW;
159  } else if(param == "layout") {
160  domain_ |= LAYOUT;
161  } else {
162  // logging might not be up yet.
163  std::cerr << "Unknown domain '" << param << "' is ignored.\n";
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(
235  out, 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
static const unsigned BORDER_TOP
Definition: grid.hpp:61
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:65
static const unsigned VERTICAL_SHIFT
Definition: grid.hpp:47
static const unsigned HORIZONTAL_MASK
Definition: grid.hpp:59
std::vector< std::string > split(const std::string &val, const char c, const int flags)
Splits a (comma-)separated string into a vector of pieces.
Generic file dialog.
Definition: field-fwd.hpp:22
static const unsigned HORIZONTAL_SHIFT
Definition: grid.hpp:54
static const unsigned HORIZONTAL_ALIGN_RIGHT
Definition: grid.hpp:58
static const unsigned BORDER_BOTTOM
Definition: grid.hpp:62
void set_level(const std::string &value)
Definition: game.cpp:738
static const unsigned VERTICAL_ALIGN_TOP
Definition: grid.hpp:49
std::size_t i
Definition: function.cpp:933
static const unsigned BORDER_RIGHT
Definition: grid.hpp:64
static const unsigned VERTICAL_ALIGN_CENTER
Definition: grid.hpp:50
static const unsigned HORIZONTAL_ALIGN_CENTER
Definition: grid.hpp:57
static const unsigned VERTICAL_MASK
Definition: grid.hpp:52
static const unsigned HORIZONTAL_GROW_SEND_TO_CLIENT
Definition: grid.hpp:55
static const unsigned VERTICAL_GROW_SEND_TO_CLIENT
Definition: grid.hpp:48
rng * generator
This generator is automatically synced during synced context.
Definition: random.cpp:60
bool grid()
Definition: general.cpp:505
static const unsigned VERTICAL_ALIGN_BOTTOM
Definition: grid.hpp:51
double t
Definition: astarsearch.cpp:64
static const unsigned HORIZONTAL_ALIGN_LEFT
Definition: grid.hpp:56
static const unsigned BORDER_LEFT
Definition: grid.hpp:63