The Battle for Wesnoth  1.17.4+dev
window.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2007 - 2022
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 /**
17  * @file
18  * Implementation of window.hpp.
19  */
20 
21 #define GETTEXT_DOMAIN "wesnoth-lib"
22 
24 
25 #include "config.hpp"
26 #include "cursor.hpp"
27 #include "events.hpp"
28 #include "floating_label.hpp"
29 #include "formula/callable.hpp"
30 #include "gettext.hpp"
31 #include "log.hpp"
37 #include "gui/core/log.hpp"
39 #include "sdl/point.hpp"
42 #include "gui/dialogs/tooltip.hpp"
43 #include "gui/widgets/button.hpp"
47 #include "gui/widgets/grid.hpp"
48 #include "gui/widgets/helper.hpp"
49 #include "gui/widgets/panel.hpp"
50 #include "gui/widgets/settings.hpp"
51 #include "gui/widgets/widget.hpp"
52 #include "gui/widgets/window.hpp"
53 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
54 #include "gui/widgets/debug.hpp"
55 #endif
56 #include "preferences/general.hpp"
57 #include "preferences/display.hpp"
58 #include "sdl/rect.hpp"
59 #include "sdl/surface.hpp"
60 #include "formula/variant.hpp"
61 #include "video.hpp"
62 #include "wml_exception.hpp"
63 #include "sdl/userevent.hpp"
64 #include "sdl/input.hpp" // get_mouse_button_mask
65 
66 #include <functional>
67 
68 #include <algorithm>
69 #include <iterator>
70 #include <stdexcept>
71 
72 namespace wfl { class function_symbol_table; }
73 namespace gui2 { class button; }
74 
75 static lg::log_domain log_gui("gui/layout");
76 #define ERR_GUI LOG_STREAM(err, log_gui)
77 
78 #define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
79 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
80 
81 #define LOG_IMPL_SCOPE_HEADER \
82  window.get_control_type() + " [" + window.id() + "] " + __func__
83 #define LOG_IMPL_HEADER LOG_IMPL_SCOPE_HEADER + ':'
84 
85 namespace gui2
86 {
87 
88 // ------------ WIDGET -----------{
89 
90 namespace implementation
91 {
92 /** @todo See whether this hack can be removed. */
93 // Needed to fix a compiler error in REGISTER_WIDGET.
95 {
96 public:
98  {
99  }
100 
102 
103  virtual std::unique_ptr<widget> build() const override
104  {
105  return nullptr;
106  }
107 };
108 
109 } // namespace implementation
111 
112 namespace
113 {
114 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
115 const unsigned SHOW = debug_layout_graph::SHOW;
116 const unsigned LAYOUT = debug_layout_graph::LAYOUT;
117 #else
118 // values are irrelavant when DEBUG_WINDOW_LAYOUT_GRAPHS is not defined.
119 const unsigned SHOW = 0;
120 const unsigned LAYOUT = 0;
121 #endif
122 
123 /**
124  * Pushes a single draw event to the queue. To be used before calling
125  * events::pump when drawing windows.
126  *
127  * @todo: in the future we should simply call draw functions directly
128  * from events::pump and do away with the custom drawing events, but
129  * that's a 1.15 target. For now, this will have to do.
130  */
131 static void push_draw_event()
132 {
133  // DBG_GUI_E << "Pushing draw event in queue.\n";
134 
135  SDL_Event event;
137 
138  event.type = DRAW_EVENT;
139  event.user = data;
140 
141  SDL_PushEvent(&event);
142 }
143 
144 /**
145  * SDL_AddTimer() callback for delay_event.
146  *
147  * @param event The event to push in the event queue.
148  *
149  * @return The new timer interval (always 0).
150  */
151 static uint32_t delay_event_callback(const uint32_t, void* event)
152 {
153  SDL_PushEvent(static_cast<SDL_Event*>(event));
154  delete static_cast<SDL_Event*>(event);
155  return 0;
156 }
157 
158 /**
159  * Allows an event to be delayed a certain amount of time.
160  *
161  * @note the delay is the minimum time, after the time has passed the event
162  * will be pushed in the SDL event queue, so it might delay more.
163  *
164  * @param event The event to delay.
165  * @param delay The number of ms to delay the event.
166  */
167 static void delay_event(const SDL_Event& event, const uint32_t delay)
168 {
169  SDL_AddTimer(delay, delay_event_callback, new SDL_Event(event));
170 }
171 
172 /**
173  * Adds a SHOW_HELPTIP event to the SDL event queue.
174  *
175  * The event is used to show the helptip for the currently focused widget.
176  */
177 static void helptip()
178 {
179  DBG_GUI_E << "Pushing SHOW_HELPTIP_EVENT event in queue.\n";
180 
181  SDL_Event event;
183 
184  event.type = SHOW_HELPTIP_EVENT;
185  event.user = data;
186 
187  SDL_PushEvent(&event);
188 }
189 
190 /**
191  * Small helper class to get an unique id for every window instance.
192  *
193  * This is used to send event to the proper window, this allows windows to post
194  * messages to themselves and let them delay for a certain amount of time.
195  */
196 class manager
197 {
198  manager();
199 
200 public:
201  static manager& instance();
202 
203  void add(window& window);
204 
205  void remove(window& window);
206 
207  unsigned get_id(window& window);
208 
209  window* get_window(const unsigned id);
210 
211 private:
212  // The number of active window should be rather small
213  // so keep it simple and don't add a reverse lookup map.
214  std::map<unsigned, window*> windows_;
215 };
216 
218 {
219 }
220 
221 manager& manager::instance()
222 {
223  static manager window_manager;
224  return window_manager;
225 }
226 
227 void manager::add(window& win)
228 {
229  static unsigned id;
230  ++id;
231  windows_[id] = &win;
232 }
233 
234 void manager::remove(window& win)
235 {
237  itor != windows_.end();
238  ++itor) {
239 
240  if(itor->second == &win) {
241  windows_.erase(itor);
242  return;
243  }
244  }
245  assert(false);
246 }
247 
248 unsigned manager::get_id(window& win)
249 {
251  itor != windows_.end();
252  ++itor) {
253 
254  if(itor->second == &win) {
255  return itor->first;
256  }
257  }
258  assert(false);
259 
260  return 0;
261 }
262 
263 window* manager::get_window(const unsigned id)
264 {
266 
267  if(itor == windows_.end()) {
268  return nullptr;
269  } else {
270  return itor->second;
271  }
272 }
273 
274 } // namespace
275 
276 window::window(const builder_window::window_resolution& definition)
277  : panel(implementation::builder_window(::config {"definition", definition.definition}), type())
282  , owner_(nullptr)
283  , need_layout_(true)
284  , variables_()
286  , suspend_drawing_(true)
287  , restore_(true)
289  , restorer_()
290  , automatic_placement_(definition.automatic_placement)
291  , horizontal_placement_(definition.horizontal_placement)
292  , vertical_placement_(definition.vertical_placement)
293  , maximum_width_(definition.maximum_width)
294  , maximum_height_(definition.maximum_height)
295  , x_(definition.x)
296  , y_(definition.y)
297  , w_(definition.width)
298  , h_(definition.height)
299  , reevaluate_best_size_(definition.reevaluate_best_size)
300  , functions_(definition.functions)
301  , tooltip_(definition.tooltip)
302  , helptip_(definition.helptip)
303  , click_dismiss_(false)
304  , enter_disabled_(false)
305  , escape_disabled_(false)
306  , linked_size_()
307  , mouse_button_state_(0) /**< Needs to be initialized in @ref show. */
308  , dirty_list_()
309 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
310  , debug_layout_(new debug_layout_graph(this))
311 #endif
313  , exit_hook_([](window&)->bool { return true; })
314  , callback_next_draw_(nullptr)
315 {
316  manager::instance().add(*this);
317 
318  connect();
319 
320  if (!video_.faked())
321  {
322  connect_signal<event::DRAW>(std::bind(&window::draw, this));
323  }
324 
325  connect_signal<event::SDL_VIDEO_RESIZE>(std::bind(
326  &window::signal_handler_sdl_video_resize, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5));
327 
328  connect_signal<event::SDL_ACTIVATE>(std::bind(
330 
331  connect_signal<event::SDL_LEFT_BUTTON_UP>(
333  this,
334  std::placeholders::_2,
335  std::placeholders::_3,
336  std::placeholders::_4,
337  SDL_BUTTON_LMASK),
339  connect_signal<event::SDL_MIDDLE_BUTTON_UP>(
341  this,
342  std::placeholders::_2,
343  std::placeholders::_3,
344  std::placeholders::_4,
345  SDL_BUTTON_MMASK),
347  connect_signal<event::SDL_RIGHT_BUTTON_UP>(
349  this,
350  std::placeholders::_2,
351  std::placeholders::_3,
352  std::placeholders::_4,
353  SDL_BUTTON_RMASK),
355 
356  connect_signal<event::SDL_KEY_DOWN>(
357  std::bind(
358  &window::signal_handler_sdl_key_down, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5, std::placeholders::_6, true),
360  connect_signal<event::SDL_KEY_DOWN>(std::bind(
361  &window::signal_handler_sdl_key_down, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5, std::placeholders::_6, false));
362 
363  connect_signal<event::MESSAGE_SHOW_TOOLTIP>(
365  this,
366  std::placeholders::_2,
367  std::placeholders::_3,
368  std::placeholders::_5),
370 
371  connect_signal<event::MESSAGE_SHOW_HELPTIP>(
373  this,
374  std::placeholders::_2,
375  std::placeholders::_3,
376  std::placeholders::_5),
378 
379  connect_signal<event::REQUEST_PLACEMENT>(
380  std::bind(
381  &window::signal_handler_request_placement, this, std::placeholders::_2, std::placeholders::_3),
383 
384  connect_signal<event::CLOSE_WINDOW>(std::bind(&window::signal_handler_close_window, this));
385 
386  register_hotkey(hotkey::GLOBAL__HELPTIP, std::bind(gui2::helptip));
387 
388  /** @todo: should eventally become part of global hotkey handling. */
390  std::bind(&CVideo::toggle_fullscreen, std::ref(video_)));
391 }
392 
394 {
395  /*
396  * We need to delete our children here instead of waiting for the grid to
397  * automatically do it. The reason is when the grid deletes its children
398  * they will try to unregister them self from the linked widget list. At
399  * this point the member of window are destroyed and we enter UB. (For
400  * some reason the bug didn't trigger on g++ but it does on MSVC.
401  */
402  for(unsigned row = 0; row < get_grid().get_rows(); ++row) {
403  for(unsigned col = 0; col < get_grid().get_cols(); ++col) {
404  get_grid().remove_child(row, col);
405  }
406  }
407 
408  /*
409  * The tip needs to be closed if the window closes and the window is
410  * not a tip. If we don't do that the tip will unrender in the next
411  * window and cause drawing glitches.
412  * Another issue is that on smallgui and an MP game the tooltip not
413  * unrendered properly can capture the mouse and make playing impossible.
414  */
417  }
418 
419  manager::instance().remove(*this);
420 
421 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
422 
423  delete debug_layout_;
424 
425 #endif
426 }
427 
429 {
430  return manager::instance().get_window(handle);
431 }
432 
433 retval window::get_retval_by_id(const std::string& id)
434 {
435  // Note it might change to a map later depending on the number
436  // of items.
437  if(id == "ok") {
438  return retval::OK;
439  } else if(id == "cancel" || id == "quit") {
440  return retval::CANCEL;
441  } else {
442  return retval::NONE;
443  }
444 }
445 
446 void window::show_tooltip(/*const unsigned auto_close_timeout*/)
447 {
448  log_scope2(log_gui_draw, "Window: show as tooltip.");
449 
450  generate_dot_file("show", SHOW);
451 
452  assert(status_ == status::NEW);
453 
456 
458 
459  /*
460  * Before show has been called, some functions might have done some testing
461  * on the window and called layout, which can give glitches. So
462  * reinvalidate the window to avoid those glitches.
463  */
465  suspend_drawing_ = false;
466 }
467 
468 void window::show_non_modal(/*const unsigned auto_close_timeout*/)
469 {
470  log_scope2(log_gui_draw, "Window: show non modal.");
471 
472  generate_dot_file("show", SHOW);
473 
474  assert(status_ == status::NEW);
475 
477 
479 
480  /*
481  * Before show has been called, some functions might have done some testing
482  * on the window and called layout, which can give glitches. So
483  * reinvalidate the window to avoid those glitches.
484  */
486  suspend_drawing_ = false;
487 
488  push_draw_event();
489 
490  events::pump();
491 }
492 
493 int window::show(const bool restore, const unsigned auto_close_timeout)
494 {
495  /*
496  * Removes the old tip if one shown. The show_tip doesn't remove
497  * the tip, since it's the tip.
498  */
500 
502  restore_ = restore;
503 
505 
506  generate_dot_file("show", SHOW);
507 
508  assert(status_ == status::NEW);
509 
510  /*
511  * Before show has been called, some functions might have done some testing
512  * on the window and called layout, which can give glitches. So
513  * reinvalidate the window to avoid those glitches.
514  */
516  suspend_drawing_ = false;
517 
518  if(auto_close_timeout) {
519  // Make sure we're drawn before we try to close ourselves, which can
520  // happen if the timeout is small.
521  draw();
522 
523  SDL_Event event;
524  sdl::UserEvent data(CLOSE_WINDOW_EVENT, manager::instance().get_id(*this));
525 
526  event.type = CLOSE_WINDOW_EVENT;
527  event.user = data;
528 
529  delay_event(event, auto_close_timeout);
530  }
531 
532 
533  try
534  {
535  // Start our loop drawing will happen here as well.
536  bool mouse_button_state_initialized = false;
538  push_draw_event();
539 
540  // process installed callback if valid, to allow e.g. network
541  // polling
542  events::pump();
543 
544  if(!mouse_button_state_initialized) {
545  /*
546  * The state must be initialize when showing the dialog.
547  * However when initialized before this point there were random
548  * errors. This only happened when the 'click' was done fast; a
549  * slower click worked properly.
550  *
551  * So it seems the events need to be processed before SDL can
552  * return the proper button state. When initializing here all
553  * works fine.
554  */
556  mouse_button_state_initialized = true;
557  }
558 
561  }
562 
563  // Add a delay so we don't keep spinning if there's no event.
564  if(status_ != status::CLOSED) {
565  SDL_Delay(10);
566  }
567  }
568  }
569  catch(...)
570  {
571  /**
572  * @todo Clean up the code duplication.
573  *
574  * In the future the restoring shouldn't be needed so the duplication
575  * doesn't hurt too much but keep this todo as a reminder.
576  */
577  suspend_drawing_ = true;
578 
579  // restore area
580  if(restore_) {
581  SDL_Rect rect = get_rectangle();
584  }
585  throw;
586  }
587 
588  suspend_drawing_ = true;
589 
590  // restore area
591  if(restore_) {
592  SDL_Rect rect = get_rectangle();
595  }
596 
597  if(text_box_base* tb = dynamic_cast<text_box_base*>(event_distributor_->keyboard_focus())) {
598  tb->interrupt_composition();
599  }
600 
601  return retval_;
602 }
603 
605 {
606  /***** ***** ***** ***** Init ***** ***** ***** *****/
607  // Prohibited from drawing?
608  if(suspend_drawing_) {
609  return;
610  }
611 
612  surface& drawing_surface = video_.getDrawingSurface();
613 
614  /***** ***** Layout and get dirty list ***** *****/
615  if(need_layout_) {
616  // Restore old surface. In the future this phase will not be needed
617  // since all will be redrawn when needed with dirty rects. Since that
618  // doesn't work yet we need to undraw the window.
619  if(restore_ && restorer_) {
620  SDL_Rect rect = get_rectangle();
621  sdl_blit(restorer_, 0, drawing_surface, &rect);
622  }
623 
624  layout();
625 
626  // Get new surface for restoring
627  SDL_Rect rect = get_rectangle();
628 
629  // We want the labels underneath the window so draw them and use them
630  // as restore point.
631  if(is_toplevel_) {
632  font::draw_floating_labels(drawing_surface);
633  }
634 
635  if(restore_) {
636  restorer_ = get_surface_portion(drawing_surface, rect);
637  }
638 
639  // Need full redraw so only set ourselves dirty.
640  dirty_list_.emplace_back(1, this);
641  } else {
642 
643  // Let widgets update themselves, which might dirty some things.
644  layout_children();
645 
646  // Now find the widgets that are dirty.
647  std::vector<widget*> call_stack;
648  if(!new_widgets) {
649  populate_dirty_list(*this, call_stack);
650  } else {
651  /* Force to update and redraw the entire screen */
652  dirty_list_.clear();
653  dirty_list_.emplace_back(1, this);
654  }
655  }
656 
657  if (dirty_list_.empty()) {
659  return;
660  }
661 
663  if(consecutive_changed_frames_ >= 100u && id_ == "title_screen") {
664  /* The title screen has changed in 100 consecutive frames, i.e. every
665  frame for two seconds. It looks like the screen is constantly changing
666  or at least marking widgets as dirty.
667 
668  That's a severe problem. Every time the title screen changes, all
669  other GUI windows need to be fully redrawn, with huge CPU usage cost.
670  For that reason, this situation is a hard error. */
671  throw std::logic_error("The title screen is constantly changing, "
672  "which has a huge CPU usage cost. See the code comment.");
673  }
674 
675  for(auto & item : dirty_list_)
676  {
677 
678  assert(!item.empty());
679 
680  const SDL_Rect dirty_rect
682  : item.back()->get_dirty_rectangle();
683 
684 // For testing we disable the clipping rect and force the entire screen to
685 // update. This way an item rendered at the wrong place is directly visible.
686 #if 0
687  dirty_list_.clear();
688  dirty_list_.emplace_back(1, this);
689 #else
690  clip_rect_setter clip(drawing_surface, &dirty_rect);
691 #endif
692 
693  /*
694  * The actual update routine does the following:
695  * - Restore the background.
696  *
697  * - draw [begin, end) the back ground of all widgets.
698  *
699  * - draw the children of the last item in the list, if this item is
700  * a container it's children get a full redraw. If it's not a
701  * container nothing happens.
702  *
703  * - draw [rbegin, rend) the fore ground of all widgets. For items
704  * which have two layers eg window or panel it draws the foreground
705  * layer. For other widgets it's a nop.
706  *
707  * Before drawing there needs to be determined whether a dirty widget
708  * really needs to be redrawn. If the widget doesn't need to be
709  * redrawing either being not visibility::visible or has status
710  * widget::redraw_action::none. If it's not drawn it's still set not
711  * dirty to avoid it keep getting on the dirty list.
712  */
713 
714  for(std::vector<widget*>::iterator itor = item.begin();
715  itor != item.end();
716  ++itor) {
717 
718  if((**itor).get_visible() != widget::visibility::visible
719  || (**itor).get_drawing_action()
721 
722  for(std::vector<widget*>::iterator citor = itor;
723  citor != item.end();
724  ++citor) {
725 
726  (**citor).set_is_dirty(false);
727  }
728 
729  item.erase(itor, item.end());
730  break;
731  }
732  }
733 
734  // Restore.
735  if(restore_) {
736  SDL_Rect rect = get_rectangle();
737  sdl_blit(restorer_, 0, drawing_surface, &rect);
738  }
739 
740  // Background.
741  for(std::vector<widget*>::iterator itor = item.begin();
742  itor != item.end();
743  ++itor) {
744 
745  (**itor).draw_background(0, 0);
746  }
747 
748  // Children.
749  if(!item.empty()) {
750  item.back()->draw_children(0, 0);
751  }
752 
753  // Foreground.
754  for(std::vector<widget*>::reverse_iterator ritor = item.rbegin();
755  ritor != item.rend();
756  ++ritor) {
757 
758  (**ritor).draw_foreground(0, 0);
759  (**ritor).set_is_dirty(false);
760  }
761  }
762 
763  dirty_list_.clear();
764 
766 
767  std::vector<widget*> call_stack;
768  populate_dirty_list(*this, call_stack);
769  assert(dirty_list_.empty());
770 
771  if(callback_next_draw_ != nullptr) {
773  callback_next_draw_ = nullptr;
774  }
775 }
776 
778 {
779  if(restore_ && restorer_) {
780  SDL_Rect rect = get_rectangle();
782  // Since the old area might be bigger than the new one, invalidate it.
783  }
784 }
785 
787  : window_(window)
788 {
791 }
792 
794 {
797 }
798 
800 {
802  need_layout_ = true;
803  }
804 }
805 widget* window::find_at(const point& coordinate, const bool must_be_active)
806 {
807  return panel::find_at(coordinate, must_be_active);
808 }
809 
811  const bool must_be_active) const
812 {
813  return panel::find_at(coordinate, must_be_active);
814 }
815 
816 widget* window::find(const std::string& id, const bool must_be_active)
817 {
818  return container_base::find(id, must_be_active);
819 }
820 
821 const widget* window::find(const std::string& id, const bool must_be_active)
822  const
823 {
824  return container_base::find(id, must_be_active);
825 }
826 
827 void window::init_linked_size_group(const std::string& id,
828  const bool fixed_width,
829  const bool fixed_height)
830 {
831  assert(fixed_width || fixed_height);
832  assert(!has_linked_size_group(id));
833 
834  linked_size_[id] = linked_size(fixed_width, fixed_height);
835 }
836 
837 bool window::has_linked_size_group(const std::string& id)
838 {
839  return linked_size_.find(id) != linked_size_.end();
840 }
841 
842 void window::add_linked_widget(const std::string& id, widget* wgt)
843 {
844  assert(wgt);
845  if(!has_linked_size_group(id)) {
846  ERR_GUI << "Unknown linked group '" << id << "'; skipping\n";
847  return;
848  }
849 
850  std::vector<widget*>& widgets = linked_size_[id].widgets;
851  if(std::find(widgets.begin(), widgets.end(), wgt) == widgets.end()) {
852  widgets.push_back(wgt);
853  }
854 }
855 
856 void window::remove_linked_widget(const std::string& id, const widget* wgt)
857 {
858  assert(wgt);
859  if(!has_linked_size_group(id)) {
860  return;
861  }
862 
863  std::vector<widget*>& widgets = linked_size_[id].widgets;
864 
866  = std::find(widgets.begin(), widgets.end(), wgt);
867 
868  if(itor != widgets.end()) {
869  widgets.erase(itor);
870 
871  assert(std::find(widgets.begin(), widgets.end(), wgt)
872  == widgets.end());
873  }
874 }
875 
877 {
878  /***** Initialize. *****/
879 
880  const auto conf = cast_config_to<window_definition>();
881  assert(conf);
882 
884 
886  const point mouse = get_mouse_position();
887 
888  variables_.add("mouse_x", wfl::variant(mouse.x));
889  variables_.add("mouse_y", wfl::variant(mouse.y));
890  variables_.add("window_width", wfl::variant(0));
891  variables_.add("window_height", wfl::variant(0));
892  variables_.add("best_window_width", wfl::variant(size.x));
893  variables_.add("best_window_height", wfl::variant(size.y));
894  variables_.add("size_request_mode", wfl::variant("maximum"));
895 
897 
898  unsigned int maximum_width = maximum_width_(variables_, &functions_);
899  unsigned int maximum_height = maximum_height_(variables_, &functions_);
900 
902  if(maximum_width == 0 || maximum_width > settings::screen_width) {
903  maximum_width = settings::screen_width;
904  }
905 
906  if(maximum_height == 0 || maximum_height > settings::screen_height) {
907  maximum_height = settings::screen_height;
908  }
909  } else {
910  maximum_width = w_(variables_, &functions_);
911  maximum_height = h_(variables_, &functions_);
912  }
913 
914  /***** Handle click dismiss status. *****/
915  button* click_dismiss_button = nullptr;
916  if((click_dismiss_button
917  = find_widget<button>(this, "click_dismiss", false, false))) {
918 
919  click_dismiss_button->set_visible(widget::visibility::invisible);
920  }
921  if(click_dismiss_) {
922  button* btn = find_widget<button>(this, "ok", false, false);
923  if(btn) {
925  click_dismiss_button = btn;
926  }
927  VALIDATE(click_dismiss_button,
928  _("Click dismiss needs a 'click_dismiss' or 'ok' button."));
929  }
930 
931  /***** Layout. *****/
932  layout_initialize(true);
933  generate_dot_file("layout_initialize", LAYOUT);
934 
936 
937  try
938  {
939  window_implementation::layout(*this, maximum_width, maximum_height);
940  }
941  catch(const layout_exception_resize_failed&)
942  {
943 
944  /** @todo implement the scrollbars on the window. */
945 
946  std::stringstream sstr;
947  sstr << __FILE__ << ":" << __LINE__ << " in function '" << __func__
948  << "' found the following problem: Failed to size window;"
949  << " wanted size " << get_best_size() << " available size "
950  << maximum_width << ',' << maximum_height << " screen size "
952 
953  throw wml_exception(_("Failed to show a dialog, "
954  "which doesn't fit on the screen."),
955  sstr.str());
956  }
957 
958  /****** Validate click dismiss status. *****/
960  assert(click_dismiss_button);
961  click_dismiss_button->set_visible(widget::visibility::visible);
962 
964  *click_dismiss_button,
965  std::bind(&window::set_retval, this, retval::OK, true));
966 
967  layout_initialize(true);
968  generate_dot_file("layout_initialize", LAYOUT);
969 
971 
972  try
973  {
975  *this, maximum_width, maximum_height);
976  }
977  catch(const layout_exception_resize_failed&)
978  {
979 
980  /** @todo implement the scrollbars on the window. */
981 
982  std::stringstream sstr;
983  sstr << __FILE__ << ":" << __LINE__ << " in function '" << __func__
984  << "' found the following problem: Failed to size window;"
985  << " wanted size " << get_best_size() << " available size "
986  << maximum_width << ',' << maximum_height << " screen size "
988  << '.';
989 
990  throw wml_exception(_("Failed to show a dialog, "
991  "which doesn't fit on the screen."),
992  sstr.str());
993  }
994  }
995 
996  /***** Get the best location for the window *****/
997  size = get_best_size();
998  /* Although 0-size windows might not seem valid/useful, there are
999  a handful of windows that request 0 size just to get a position
1000  chosen via the code below, so at least for now allow them:
1001  */
1002  assert(size.x >= 0 && static_cast<unsigned>(size.x) <= maximum_width
1003  && size.y >= 0 && static_cast<unsigned>(size.y) <= maximum_height);
1004 
1005  point origin(0, 0);
1006 
1007  if(automatic_placement_) {
1008 
1009  switch(horizontal_placement_) {
1011  // Do nothing
1012  break;
1014  origin.x = (settings::screen_width - size.x) / 2;
1015  break;
1017  origin.x = settings::screen_width - size.x;
1018  break;
1019  default:
1020  assert(false);
1021  }
1022  switch(vertical_placement_) {
1024  // Do nothing
1025  break;
1027  origin.y = (settings::screen_height - size.y) / 2;
1028  break;
1030  origin.y = settings::screen_height - size.y;
1031  break;
1032  default:
1033  assert(false);
1034  }
1035  } else {
1036 
1037  variables_.add("window_width", wfl::variant(size.x));
1038  variables_.add("window_height", wfl::variant(size.y));
1039 
1041  layout_initialize(true);
1042 
1045  h_(variables_, &functions_));
1046 
1047  size = get_best_size();
1048  variables_.add("window_width", wfl::variant(size.x));
1049  variables_.add("window_height", wfl::variant(size.y));
1050  }
1051 
1052  variables_.add("size_request_mode", wfl::variant("size"));
1053 
1054  size.x = w_(variables_, &functions_);
1055  size.y = h_(variables_, &functions_);
1056 
1057  variables_.add("window_width", wfl::variant(size.x));
1058  variables_.add("window_height", wfl::variant(size.y));
1059 
1060  origin.x = x_(variables_, &functions_);
1061  origin.y = y_(variables_, &functions_);
1062  }
1063 
1064  /***** Set the window size *****/
1065  place(origin, size);
1066 
1067  generate_dot_file("layout_finished", LAYOUT);
1068  need_layout_ = false;
1069 
1071 }
1072 
1074 {
1075  // evaluate the group sizes
1076  for(auto & linked_size : linked_size_)
1077  {
1078 
1079  point max_size(0, 0);
1080 
1081  // Determine the maximum size.
1082  for(auto widget : linked_size.second.widgets)
1083  {
1084 
1085  const point size = widget->get_best_size();
1086 
1087  if(size.x > max_size.x) {
1088  max_size.x = size.x;
1089  }
1090  if(size.y > max_size.y) {
1091  max_size.y = size.y;
1092  }
1093  }
1094  if(linked_size.second.width != -1) {
1095  linked_size.second.width = max_size.x;
1096  }
1097  if(linked_size.second.height != -1) {
1098  linked_size.second.height = max_size.y;
1099  }
1100 
1101  // Set the maximum size.
1102  for(auto widget : linked_size.second.widgets)
1103  {
1104 
1106 
1107  if(linked_size.second.width != -1) {
1108  size.x = max_size.x;
1109  }
1110  if(linked_size.second.height != -1) {
1111  size.y = max_size.y;
1112  }
1113 
1114  widget->set_layout_size(size);
1115  }
1116  }
1117 }
1118 
1119 bool window::click_dismiss(const int mouse_button_mask)
1120 {
1121  if(does_click_dismiss()) {
1122  if((mouse_button_state_ & mouse_button_mask) == 0) {
1124  } else {
1125  mouse_button_state_ &= ~mouse_button_mask;
1126  }
1127  return true;
1128  }
1129  return false;
1130 }
1131 
1133 {
1134  std::vector<dispatcher*>& dispatchers = event::get_all_dispatchers();
1135  auto me = std::find(dispatchers.begin(), dispatchers.end(), this);
1136 
1137  for(auto it = std::next(me); it != dispatchers.end(); ++it) {
1138  // Note that setting an entire window dirty like this is expensive.
1139  dynamic_cast<widget&>(**it).set_is_dirty(true);
1140  }
1141 }
1142 
1143 void window::finalize(const builder_grid& content_grid)
1144 {
1145  auto widget = content_grid.build();
1146  assert(widget);
1147 
1148  static const std::string id = "_window_content_grid";
1149 
1150  // Make sure the new child has same id.
1151  widget->set_id(id);
1152 
1153  auto* parent_grid = find_widget<grid>(&get_grid(), id, true, false);
1154  assert(parent_grid);
1155 
1156  if(grid* grandparent_grid = dynamic_cast<grid*>(parent_grid->parent())) {
1157  grandparent_grid->swap_child(id, std::move(widget), false);
1158  } else if(container_base* c = dynamic_cast<container_base*>(parent_grid->parent())) {
1159  c->get_grid().swap_child(id, std::move(widget), true);
1160  } else {
1161  assert(false);
1162  }
1163 }
1164 
1165 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
1166 
1167 void window::generate_dot_file(const std::string& generator,
1168  const unsigned domain)
1169 {
1170  debug_layout_->generate_dot_file(generator, domain);
1171 }
1172 #endif
1173 
1175  const unsigned maximum_width,
1176  const unsigned maximum_height)
1177 {
1179 
1180  /*
1181  * For now we return the status, need to test later whether this can
1182  * entirely be converted to an exception based system as in 'promised' on
1183  * the algorithm page.
1184  */
1185 
1186  try
1187  {
1188  point size = window.get_best_size();
1189 
1190  DBG_GUI_L << LOG_IMPL_HEADER << " best size : " << size
1191  << " maximum size : " << maximum_width << ','
1192  << maximum_height << ".\n";
1193  if(size.x <= static_cast<int>(maximum_width)
1194  && size.y <= static_cast<int>(maximum_height)) {
1195 
1196  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Fits, nothing to do.\n";
1197  return;
1198  }
1199 
1200  if(size.x > static_cast<int>(maximum_width)) {
1201  window.reduce_width(maximum_width);
1202 
1203  size = window.get_best_size();
1204  if(size.x > static_cast<int>(maximum_width)) {
1205  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resize width failed."
1206  << " Wanted width " << maximum_width
1207  << " resulting width " << size.x << ".\n";
1209  }
1211  << " Status: Resize width succeeded.\n";
1212  }
1213 
1214  if(size.y > static_cast<int>(maximum_height)) {
1215  window.reduce_height(maximum_height);
1216 
1217  size = window.get_best_size();
1218  if(size.y > static_cast<int>(maximum_height)) {
1219  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resize height failed."
1220  << " Wanted height " << maximum_height
1221  << " resulting height " << size.y << ".\n";
1223  }
1225  << " Status: Resize height succeeded.\n";
1226  }
1227 
1228  assert(size.x <= static_cast<int>(maximum_width)
1229  && size.y <= static_cast<int>(maximum_height));
1230 
1231 
1232  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resizing succeeded.\n";
1233  return;
1234  }
1235  catch(const layout_exception_width_modified&)
1236  {
1238  << " Status: Width has been modified, rerun.\n";
1239 
1240  window.layout_initialize(false);
1241  window.layout_linked_widgets();
1242  layout(window, maximum_width, maximum_height);
1243  return;
1244  }
1245 }
1246 
1247 void window::mouse_capture(const bool capture)
1248 {
1249  assert(event_distributor_);
1250  event_distributor_->capture_mouse(capture);
1251 }
1252 
1254 {
1255  assert(event_distributor_);
1256  event_distributor_->keyboard_capture(widget);
1257 }
1258 
1260 {
1261  assert(event_distributor_);
1262  event_distributor_->keyboard_add_to_chain(widget);
1263 }
1264 
1266 {
1267  assert(event_distributor_);
1268  event_distributor_->keyboard_remove_from_chain(widget);
1269 }
1270 
1272 {
1273  if(std::find(tab_order.begin(), tab_order.end(), widget) != tab_order.end()) {
1274  return;
1275  }
1276  assert(event_distributor_);
1277  if(tab_order.empty() && !event_distributor_->keyboard_focus()) {
1278  keyboard_capture(widget);
1279  }
1280  if(at < 0 || at >= static_cast<int>(tab_order.size())) {
1281  tab_order.push_back(widget);
1282  } else {
1283  tab_order.insert(tab_order.begin() + at, widget);
1284  }
1285 }
1286 
1288  bool& handled,
1289  const point& new_size)
1290 {
1291  DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
1292 
1295  settings::screen_width = new_size.x;
1296  settings::screen_height = new_size.y;
1298 
1299  handled = true;
1300 }
1301 
1303  bool& handled,
1304  bool& halt,
1305  const int mouse_button_mask)
1306 {
1307  DBG_GUI_E << LOG_HEADER << ' ' << event << " mouse_button_mask "
1308  << static_cast<unsigned>(mouse_button_mask) << ".\n";
1309 
1310  handled = halt = click_dismiss(mouse_button_mask);
1311 }
1312 
1313 static bool is_active(const widget* wgt)
1314 {
1315  if(const styled_widget* control = dynamic_cast<const styled_widget*>(wgt)) {
1316  return control->get_active() && control->get_visible() == widget::visibility::visible;
1317  }
1318  return false;
1319 }
1320 
1322  bool& handled,
1323  const SDL_Keycode key,
1324  const SDL_Keymod mod,
1325  bool handle_tab)
1326 {
1327  DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
1328 
1329  if(text_box_base* tb = dynamic_cast<text_box_base*>(event_distributor_->keyboard_focus())) {
1330  if(tb->is_composing()) {
1331  if(handle_tab && !tab_order.empty() && key == SDLK_TAB) {
1332  tb->interrupt_composition();
1333  } else {
1334  return;
1335  }
1336  }
1337  }
1338  if(!enter_disabled_ && (key == SDLK_KP_ENTER || key == SDLK_RETURN)) {
1340  handled = true;
1341  } else if(key == SDLK_ESCAPE && !escape_disabled_) {
1343  handled = true;
1344  } else if(key == SDLK_SPACE) {
1345  handled = click_dismiss(0);
1346  } else if(handle_tab && !tab_order.empty() && key == SDLK_TAB) {
1347  assert(event_distributor_);
1348  widget* focus = event_distributor_->keyboard_focus();
1349  auto iter = std::find(tab_order.begin(), tab_order.end(), focus);
1350  do {
1351  if(mod & KMOD_SHIFT) {
1352  if(iter == tab_order.begin()) {
1353  iter = tab_order.end();
1354  }
1355  iter--;
1356  } else {
1357  if(iter == tab_order.end()) {
1358  iter = tab_order.begin();
1359  } else {
1360  iter++;
1361  if(iter == tab_order.end()) {
1362  iter = tab_order.begin();
1363  }
1364  }
1365  }
1366  } while(!is_active(*iter));
1367  keyboard_capture(*iter);
1368  handled = true;
1369  }
1370 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
1371  if(key == SDLK_F12) {
1372  debug_layout_->generate_dot_file("manual", debug_layout_graph::MANUAL);
1373  handled = true;
1374  }
1375 #endif
1376 }
1377 
1379  bool& handled,
1380  const event::message& message)
1381 {
1382  DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
1383 
1384  const event::message_show_tooltip& request
1385  = dynamic_cast<const event::message_show_tooltip&>(message);
1386 
1387  dialogs::tip::show(tooltip_.id, request.message, request.location, request.source_rect);
1388 
1389  handled = true;
1390 }
1391 
1393  bool& handled,
1394  const event::message& message)
1395 {
1396  DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
1397 
1398  const event::message_show_helptip& request
1399  = dynamic_cast<const event::message_show_helptip&>(message);
1400 
1401  dialogs::tip::show(helptip_.id, request.message, request.location, request.source_rect);
1402 
1403  handled = true;
1404 }
1405 
1407  bool& handled)
1408 {
1409  DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
1410 
1412 
1413  handled = true;
1414 }
1415 
1417 {
1419 }
1420 
1421 // }---------- DEFINITION ---------{
1422 
1425 {
1426  DBG_GUI_P << "Parsing window " << id << '\n';
1427 
1428  load_resolutions<resolution>(cfg);
1429 }
1430 
1432  : panel_definition::resolution(cfg), grid(nullptr)
1433 {
1434  const config& child = cfg.child("grid");
1435  // VALIDATE(child, _("No grid defined."));
1436 
1437  /** @todo Evaluate whether the grid should become mandatory. */
1438  if(child) {
1439  grid = std::make_shared<builder_grid>(child);
1440  }
1441 }
1442 
1443 // }------------ END --------------
1444 
1445 } // namespace gui2
1446 
1447 
1448 /**
1449  * @page layout_algorithm Layout algorithm
1450  *
1451  * @section introduction-layout_algorithm Introduction
1452  *
1453  * This page describes how the layout engine for the dialogs works. First
1454  * a global overview of some terms used in this document.
1455  *
1456  * - @ref gui2::widget "Widget"; Any item which can be used in the widget
1457  * toolkit. Not all widgets are visible. In general widgets cannot be
1458  * sized directly, but this is controlled by a window. A widget has an
1459  * internal size cache and if the value in the cache is not equal to 0,0
1460  * that value is its best size. This value gets set when the widget can
1461  * honor a resize request. It will be set with the value which honors
1462  * the request.
1463  *
1464  * - @ref gui2::grid "Grid"; A grid is an invisible container which holds
1465  * one or more widgets. Several widgets have a grid in them to hold
1466  * multiple widgets eg panels and windows.
1467  *
1468  * - @ref gui2::grid::child "Grid cell"; Every widget which is in a grid is
1469  * put in a grid cell. These cells also hold the information about the gaps
1470  * between widgets the behavior on growing etc. All grid cells must have a
1471  * widget inside them.
1472  *
1473  * - @ref gui2::window "Window"; A window is a top level item which has a
1474  * grid with its children. The window handles the sizing of the window and
1475  * makes sure everything fits.
1476  *
1477  * - @ref gui2::window::linked_size "Shared size group"; A shared size
1478  * group is a number of widgets which share width and or height. These
1479  * widgets are handled separately in the layout algorithm. All grid cells
1480  * width such a widget will get the same height and or width and these
1481  * widgets won't be resized when there's not enough size. To be sure that
1482  * these widgets don't cause trouble for the layout algorithm, they must be
1483  * in a container with scrollbars so there will always be a way to properly
1484  * layout them. The engine must enforce this restriction so the shared
1485  * layout property must be set by the engine after validation.
1486  *
1487  * - All visible grid cells; A grid cell is visible when the widget inside
1488  * of it doesn't have the state visibility::invisible. Widgets which have the
1489  * state visibility::hidden are sized properly since when they become
1490  * visibility::visible the layout shouldn't be invalidated. A grid cell
1491  * that's invisible has size 0,0.
1492  *
1493  * - All resizable grid cells; A grid cell is resizable under the following
1494  * conditions:
1495  * - The widget is visibility::visible.
1496  * - The widget is not in a shared size group.
1497  *
1498  * There are two layout algorithms with a different purpose.
1499  *
1500  * - The Window algorithm; this algorithm's goal is it to make sure all grid
1501  * cells fit in the window. Sizing the grid cells depends on the widget
1502  * size as well, but this algorithm only sizes the grid cells and doesn't
1503  * handle the widgets inside them.
1504  *
1505  * - The Grid algorithm; after the Window algorithm made sure that all grid
1506  * cells fit this algorithm makes sure the widgets are put in the optimal
1507  * state in their grid cell.
1508  *
1509  * @section layout_algorithm_window Window
1510  *
1511  * Here is the algorithm used to layout the window:
1512  *
1513  * - Perform a full initialization
1514  * (@ref gui2::widget::layout_initialize (full_initialization = true)):
1515  * - Clear the internal best size cache for all widgets.
1516  * - For widgets with scrollbars hide them unless the
1517  * @ref gui2::scrollbar_container::scrollbar_mode "scrollbar_mode" is
1518  * ALWAYS_VISIBLE or AUTO_VISIBLE.
1519  * - Handle shared sizes:
1520  * - Height and width:
1521  * - Get the best size for all widgets that share height and width.
1522  * - Set the maximum of width and height as best size for all these
1523  * widgets.
1524  * - Width only:
1525  * - Get the best width for all widgets which share their width.
1526  * - Set the maximum width for all widgets, but keep their own height.
1527  * - Height only:
1528  * - Get the best height for all widgets which share their height.
1529  * - Set the maximum height for all widgets, but keep their own width.
1530  * - Start layout loop:
1531  * - Get best size.
1532  * - If width <= maximum_width && height <= maximum_height we're done.
1533  * - If width > maximum_width, optimize the width:
1534  * - For every grid cell in a grid row there will be a resize request
1535  * (@ref gui2::grid::reduce_width):
1536  * - Sort the widgets in the row on the resize priority.
1537  * - Loop through this priority queue until the row fits
1538  * - If priority != 0 try to share the extra width else all
1539  * widgets are tried to reduce the full size.
1540  * - Try to shrink the widgets by either wrapping or using a
1541  * scrollbar (@ref gui2::widget::request_reduce_width).
1542  * - If the row fits in the wanted width this row is done.
1543  * - Else try the next priority.
1544  * - All priorities done and the width still doesn't fit.
1545  * - Loop through this priority queue until the row fits.
1546  * - If priority != 0:
1547  * - try to share the extra width
1548  * -Else:
1549  * - All widgets are tried to reduce the full size.
1550  * - Try to shrink the widgets by sizing them smaller as really
1551  * wanted (@ref gui2::widget::demand_reduce_width).
1552  * For labels, buttons etc. they get ellipsized.
1553  * - If the row fits in the wanted width this row is done.
1554  * - Else try the next priority.
1555  * - All priorities done and the width still doesn't fit.
1556  * - Throw a layout width doesn't fit exception.
1557  * - If height > maximum_height, optimize the height
1558  * (@ref gui2::grid::reduce_height):
1559  * - For every grid cell in a grid column there will be a resize request:
1560  * - Sort the widgets in the column on the resize priority.
1561  * - Loop through this priority queue until the column fits:
1562  * - If priority != 0 try to share the extra height else all
1563  * widgets are tried to reduce the full size.
1564  * - Try to shrink the widgets by using a scrollbar
1565  * (@ref gui2::widget::request_reduce_height).
1566  * - If succeeded for a widget the width is influenced and the
1567  * width might be invalid.
1568  * - Throw a width modified exception.
1569  * - If the column fits in the wanted height this column is done.
1570  * - Else try the next priority.
1571  * - All priorities done and the height still doesn't fit.
1572  * - Loop through this priority queue until the column fits.
1573  * - If priority != 0 try to share the extra height else all
1574  * widgets are tried to reduce the full size.
1575  * - Try to shrink the widgets by sizing them smaller as really
1576  * wanted (@ref gui2::widget::demand_reduce_width).
1577  * For labels, buttons etc. they get ellipsized .
1578  * - If the column fits in the wanted height this column is done.
1579  * - Else try the next priority.
1580  * - All priorities done and the height still doesn't fit.
1581  * - Throw a layout height doesn't fit exception.
1582  * - End layout loop.
1583  *
1584  * - Catch @ref gui2::layout_exception_width_modified "width modified":
1585  * - Goto relayout.
1586  *
1587  * - Catch
1588  * @ref gui2::layout_exception_width_resize_failed "width resize failed":
1589  * - If the window has a horizontal scrollbar which isn't shown but can be
1590  * shown.
1591  * - Show the scrollbar.
1592  * - goto relayout.
1593  * - Else show a layout failure message.
1594  *
1595  * - Catch
1596  * @ref gui2::layout_exception_height_resize_failed "height resize failed":
1597  * - If the window has a vertical scrollbar which isn't shown but can be
1598  * shown:
1599  * - Show the scrollbar.
1600  * - goto relayout.
1601  * - Else:
1602  * - show a layout failure message.
1603  *
1604  * - Relayout:
1605  * - Initialize all widgets
1606  * (@ref gui2::widget::layout_initialize (full_initialization = false))
1607  * - Handle shared sizes, since the reinitialization resets that state.
1608  * - Goto start layout loop.
1609  *
1610  * @section grid Grid
1611  *
1612  * This section will be documented later.
1613  */
Define the common log macros for the gui toolkit.
bool click_dismiss_
Do we want to have easy close behavior?
Definition: window.hpp:552
const std::string message
The message to show on the tooltip.
Definition: message.hpp:66
void keyboard_capture(widget *widget)
Definition: window.cpp:1253
Dialog was closed with the CANCEL button.
Definition: retval.hpp:38
bool new_widgets
Do we wish to use the new library or not.
Definition: settings.cpp:25
#define DBG_GUI_P
Definition: log.hpp:66
wfl::function_symbol_table functions_
The formula definitions available for the calculation formulas.
Definition: window.hpp:527
void remove()
Removes a tip.
Definition: tooltip.cpp:175
Defines the exception classes for the layout algorithm.
void signal_handler_click_dismiss(const event::ui_event event, bool &handled, bool &halt, const int mouse_button_mask)
The handler for the click dismiss mouse &#39;event&#39;.
Definition: window.cpp:1302
#define ERR_GUI
Definition: window.cpp:76
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:402
void finalize(const builder_grid &content_grid)
Finishes the initialization of the grid.
Definition: window.cpp:1143
void reduce_width(const unsigned maximum_width)
Tries to reduce the width of a container.
Helper for header for the window.
typed_formula< unsigned > maximum_height_
The maximum height if automatic_placement_ is true.
Definition: window.hpp:509
bool does_click_dismiss() const
Does the window close easily?
Definition: window.hpp:273
std::function< bool(window &)> exit_hook_
Definition: window.hpp:769
const std::string message
The message to show on the helptip.
Definition: message.hpp:84
Abstract base class for text items.
void signal_handler_sdl_key_down(const event::ui_event event, bool &handled, const SDL_Keycode key, const SDL_Keymod mod, bool handle_tab)
Definition: window.cpp:1321
#define DBG_GUI_L
Definition: log.hpp:55
static lg::log_domain log_gui("gui/layout")
void set_layout_size(const point &size)
Definition: widget.cpp:336
virtual widget * find_at(const point &coordinate, const bool must_be_active) override
See widget::find_at.
Definition: window.cpp:805
typed_formula< unsigned > h_
The formula to calculate the height of the dialog.
Definition: window.hpp:521
widget * find(const std::string &id, const bool must_be_active) override
See widget::find.
Key Type Default Description window_width unsigned 0 Width of the application window.
ui_event
The event sent to the dispatcher.
Definition: handler.hpp:115
const unsigned horizontal_placement_
Sets the horizontal placement.
Definition: window.hpp:495
virtual void place(const point &origin, const point &size) override
See widget::place.
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:172
const grid & get_grid() const
const point location
The location where to show the tooltip.
Definition: message.hpp:69
A panel is a visible container to hold multiple widgets.
Definition: panel.hpp:58
Add a special kind of assert to validate whether the input from WML doesn&#39;t contain any problems that...
Main class to show messages to the user.
Definition: message.hpp:35
void register_hotkey(const hotkey::HOTKEY_COMMAND id, const hotkey_function &function)
Registers a hotkey.
Definition: dispatcher.cpp:142
CVideo & video_
Needed so we can change what&#39;s drawn on the screen.
Definition: window.hpp:442
The window is new and not yet shown.
void show_non_modal()
Shows the window non modal.
Definition: window.cpp:468
void generate_dot_file(const std::string &, const unsigned)
Definition: window.hpp:679
Helper struct to force widgets the have the same size.
Definition: window.hpp:567
#define LOG_SCOPE_HEADER
Definition: window.cpp:78
const std::string & id() const
Definition: widget.cpp:111
unsigned int get_rows() const
Definition: grid.hpp:308
unsigned int consecutive_changed_frames_
In how many consecutive frames the window has changed.
Definition: window.hpp:658
Exception thrown when the height resizing has failed.
This file contains the window object, this object is a top level container which has the event manage...
void draw()
Draws the window.
Definition: window.cpp:604
SDL_Rect get_rectangle() const
Gets the bounding rectangle of the widget on the screen.
Definition: widget.cpp:311
The message for MESSAGE_SHOW_HELPTIP.
Definition: message.hpp:76
Base class for all widgets.
Definition: widget.hpp:49
bool is_in_dialog()
Is a dialog open?
Definition: handler.cpp:1116
#define DRAW_EVENT
Definition: events.hpp:27
lg::log_domain log_gui_layout("gui/layout")
Definition: log.hpp:54
const SDL_Rect source_rect
The size of the entity requesting to show a tooltip.
Definition: message.hpp:72
void signal_handler_sdl_video_resize(const event::ui_event event, bool &handled, const point &new_size)
Definition: window.cpp:1287
#define LOG_IMPL_HEADER
Definition: window.cpp:83
static CVideo & get_singleton()
Definition: video.hpp:52
static const std::string & type()
Static type getter that does not rely on the widget being constructed.
Contains the event distributor.
unsigned gamemap_width
The size of the map area, if not available equal to the screen size.
Definition: settings.cpp:34
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1259
surface get_surface_portion(const surface &src, SDL_Rect &area)
Get a portion of the screen.
Definition: utils.cpp:2134
typed_formula< unsigned > w_
The formula to calculate the width of the dialog.
Definition: window.hpp:518
static std::string _(const char *str)
Definition: gettext.hpp:93
void signal_handler_message_show_tooltip(const event::ui_event event, bool &handled, const event::message &message)
Definition: window.cpp:1378
Definitions for the interface to Wesnoth Markup Language (WML).
const point location
The location where to show the helptip.
Definition: message.hpp:87
std::string id_
The id is the unique name of the widget in a certain context.
Definition: widget.hpp:178
Contains functions for cleanly handling SDL input.
int mouse_button_state_
The state of the mouse button.
Definition: window.hpp:635
void get_screen_size_variables(wfl::map_formula_callable &variable)
Gets a formula object with the screen size.
Definition: helper.cpp:101
int x
x coordinate.
Definition: point.hpp:45
void init_mouse_location()
Initializes the location of the mouse.
Definition: handler.cpp:911
Generic file dialog.
Definition: field-fwd.hpp:23
int width
The current width of all widgets in the group, -1 if the width is not linked.
Definition: window.hpp:578
SDL_Rect draw_area() const
Returns the size and location of the current drawing area in pixels.
Definition: video.cpp:376
The message callbacks hold a reference to a message.
Definition: message.hpp:45
#define CLOSE_WINDOW_EVENT
Definition: events.hpp:28
static bool is_active(const widget *wgt)
Definition: window.cpp:1313
The event handler class for the widget library.
Base container class.
Definition: grid.hpp:31
void connect()
Connects the dispatcher to the event handler.
Definition: dispatcher.cpp:51
static std::string at(const std::string &file, int line)
void populate_dirty_list(window &caller, std::vector< widget *> &call_stack)
Adds a widget to the dirty list if it is dirty.
Definition: widget.cpp:418
status status_
The status of the window.
Definition: window.hpp:445
#define LOG_HEADER
Definition: window.cpp:79
void reduce_height(const unsigned maximum_height)
Tries to reduce the height of a container.
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
std::vector< widget * > widgets
The widgets linked.
Definition: window.hpp:575
surface restorer_
When the window closes this surface is used to undraw the window.
Definition: window.hpp:484
Exception thrown when the width has been modified during resizing.
point get_best_size() const
Gets the best size for the widget.
Definition: widget.cpp:194
The dialog was closed automatically as its timeout had been reached.
Definition: retval.hpp:41
#define VALIDATE(cond, message)
The macro to use for the validation of WML.
bool suspend_drawing_
Avoid drawing the window.
Definition: window.hpp:475
This file contains the settings handling of the widget library.
map_formula_callable & add(const std::string &key, const variant &value)
Definition: callable.hpp:253
bool enter_disabled_
Disable the enter key see our setter for more info.
Definition: window.hpp:555
const SDL_Rect source_rect
The size of the entity requesting to show a helptip.
Definition: message.hpp:90
bool invalidate_layout_blocked_
Is invalidate_layout blocked, see invalidate_layout_blocker.
Definition: window.hpp:472
void set_visible(const visibility visible)
Definition: widget.cpp:477
bool need_layout_
When set the form needs a full layout redraw cycle.
Definition: window.hpp:466
void set_is_dirty(const bool is_dirty)
Definition: widget.cpp:467
void signal_handler_close_window()
Definition: window.cpp:1416
typed_formula< bool > reevaluate_best_size_
The formula to determine whether the size is good.
Definition: window.hpp:524
builder_window::window_resolution::tooltip_info helptip_
The settings for the helptip.
Definition: window.hpp:533
bool is_toplevel_
Whether the window has other windows behind it.
Definition: window.hpp:481
unsigned gamemap_height
Definition: settings.cpp:35
std::vector< dispatcher * > & get_all_dispatchers()
Gets all event dispatchers in the Z order.
Definition: handler.cpp:905
The widget is not visible.
static const unsigned HORIZONTAL_ALIGN_RIGHT
Definition: grid.hpp:59
static thread_local std::deque< std::string > call_stack
For printing error messages when WFL parsing or evaluation fails, this contains the names of the WFL ...
Definition: function.cpp:47
The window is being shown.
This file contains the definitions for the gui2::event::message class.
The message for MESSAGE_SHOW_TOOLTIP.
Definition: message.hpp:58
unsigned int get_cols() const
Definition: grid.hpp:314
typed_formula< unsigned > y_
The formula to calculate the y value of the dialog.
Definition: window.hpp:515
Exception thrown when the width resizing has failed.
virtual std::unique_ptr< widget > build() const override
Definition: window.cpp:103
Basic template class to generate new items.
#define log_scope2(domain, description)
Definition: log.hpp:219
const bool automatic_placement_
Do we wish to place the widget automatically?
Definition: window.hpp:487
typed_formula< unsigned > maximum_width_
The maximum width if automatic_placement_ is true.
Definition: window.hpp:506
void init_linked_size_group(const std::string &id, const bool fixed_width, const bool fixed_height)
Initializes a linked size group.
Definition: window.cpp:827
typed_formula< unsigned > x_
The formula to calculate the x value of the dialog.
Definition: window.hpp:512
std::vector< std::vector< widget * > > dirty_list_
The list with dirty items in the window.
Definition: window.hpp:651
void set_mouse_behavior(const mouse_behavior mouse_behavior)
Definition: dispatcher.hpp:403
uint32_t get_mouse_button_mask()
Returns the current mouse button mask.
Definition: input.cpp:45
bool escape_disabled_
Disable the escape key see our setter for more info.
Definition: window.hpp:558
void pump()
Definition: events.cpp:473
The window has been requested to be closed but still needs to evaluate the request.
virtual std::unique_ptr< widget > build() const override
Inherited from builder_widget.
#define REGISTER_WIDGET(id)
Wrapper for REGISTER_WIDGET3.
static window * window_instance(const unsigned handle)
Returns the instance of a window.
Definition: window.cpp:428
void add_linked_widget(const std::string &id, widget *widget)
Adds a widget to a linked size group.
Definition: window.cpp:842
widget * find(const std::string &id, const bool must_be_active) override
See widget::find.
Definition: window.cpp:816
Helper class, don&#39;t construct this directly.
static const unsigned VERTICAL_ALIGN_TOP
Definition: grid.hpp:50
void remove_from_keyboard_chain(widget *widget)
Remove the widget from the keyboard chain.
Definition: window.cpp:1265
bool faked() const
Definition: video.hpp:69
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:215
int show(const bool restore=true, const unsigned auto_close_timeout=0)
Shows the window.
Definition: window.cpp:493
std::map< unsigned, window * > windows_
Definition: window.cpp:214
surface & getDrawingSurface()
Returns a reference to the drawing surface.
Definition: video.cpp:607
#define DBG_GUI_E
Definition: log.hpp:35
Default, unset return value.
Definition: retval.hpp:32
#define SHOW_HELPTIP_EVENT
Definition: events.hpp:29
virtual void layout_initialize(const bool full_initialization) override
See widget::layout_initialize.
void toggle_fullscreen()
Definition: video.cpp:696
The user set the widget invisible, that means:
window * get_window()
Get the parent window.
Definition: widget.cpp:118
const unsigned vertical_placement_
Sets the vertical placement.
Definition: window.hpp:503
void undraw_floating_labels(surface screen)
std::vector< widget * > tab_order
List of widgets in the tabbing order.
Definition: window.hpp:588
static const unsigned VERTICAL_ALIGN_CENTER
Definition: grid.hpp:51
bool has_linked_size_group(const std::string &id)
Is the linked size group defined for this window?
Definition: window.cpp:837
bool click_dismiss(const int mouse_button_mask)
Handles a mouse click event for dismissing the dialog.
Definition: window.cpp:1119
static const unsigned HORIZONTAL_ALIGN_CENTER
Definition: grid.hpp:58
Holds a 2D point.
Definition: point.hpp:24
unsigned screen_width
The screen resolution and pixel pitch should be available for all widgets since their drawing method ...
Definition: settings.cpp:29
The window has been closed.
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:358
lg::log_domain log_gui_draw("gui/draw")
Definition: log.hpp:28
Base class for all visible items.
A generic container base class.
void signal_handler_request_placement(const event::ui_event event, bool &handled)
Definition: window.cpp:1406
builder_window::window_resolution::tooltip_info tooltip_
The settings for the tooltip.
Definition: window.hpp:530
show_mode show_mode_
The mode in which the window is shown.
Definition: window.hpp:452
static void layout(window &window, const unsigned maximum_width, const unsigned maximum_height)
Layouts the window.
Definition: window.cpp:1174
window_definition(const config &cfg)
Definition: window.cpp:1423
static const unsigned VERTICAL_ALIGN_BOTTOM
Definition: grid.hpp:52
static retval get_retval_by_id(const std::string &id)
Gets the retval for the default buttons.
Definition: window.cpp:433
point get_mouse_position()
Returns the current mouse position.
Definition: helper.cpp:119
void undraw()
Undraws the window.
Definition: window.cpp:777
Contains the SDL_Rect helper code.
The user sets the widget visible, that means:
void remove_child(const unsigned row, const unsigned col)
Removes and frees a widget in a cell.
Definition: grid.cpp:145
void show_tooltip()
Shows the window as a tooltip.
Definition: window.cpp:446
unsigned screen_height
Definition: settings.cpp:30
Definition: contexts.hpp:44
void set_id(const std::string &id)
Definition: widget.cpp:99
void mouse_capture(const bool capture=true)
Definition: window.cpp:1247
bool disable_click_dismiss() const override
See widget::disable_click_dismiss.
bool restore_
Whether the window should undraw the window using restorer_.
Definition: window.hpp:478
void redraw_windows_on_top() const
Schedules windows on top of us (if any) to redraw.
Definition: window.cpp:1132
void layout()
Layouts the window.
Definition: window.cpp:876
Simple push button.
Definition: button.hpp:36
std::unique_ptr< event::distributor > event_distributor_
Definition: window.hpp:684
void signal_handler_message_show_helptip(const event::ui_event event, bool &handled, const event::message &message)
Definition: window.cpp:1392
void invalidate_layout()
Updates the size of the window.
Definition: window.cpp:799
friend class debug_layout_graph
Definition: window.hpp:67
dialogs::modal_dialog * owner_
The dialog that owns the window.
Definition: window.hpp:458
retval
Default window/dialog return values.
Definition: retval.hpp:29
virtual void layout_children() override
See widget::layout_children.
static const unsigned HORIZONTAL_ALIGN_LEFT
Definition: grid.hpp:57
std::unique_ptr< window > build(const builder_window::window_resolution &definition)
Builds a window.
Dialog was closed with the OK button.
Definition: retval.hpp:35
virtual widget * find_at(const point &coordinate, const bool must_be_active) override
See widget::find_at.
void remove_linked_widget(const std::string &id, const widget *widget)
Removes a widget from a linked size group.
Definition: window.cpp:856
map_location coordinate
Contains an x and y coordinate used for starting positions in maps.
void sdl_blit(const surface &src, SDL_Rect *src_rect, surface &dst, SDL_Rect *dst_rect)
Definition: utils.hpp:32
int height
The current height of all widgets in the group, -1 if the height is not linked.
Definition: window.hpp:581
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:60
void add_to_tab_order(widget *widget, int at=-1)
Add the widget to the tabbing order.
Definition: window.cpp:1271
mock_char c
void set_want_keyboard_input(const bool want_keyboard_input)
Definition: dispatcher.hpp:413
std::shared_ptr< halo_record > handle
Definition: halo.hpp:30
int y
y coordinate.
Definition: point.hpp:48
base class of top level items, the only item which needs to store the final canvases to draw on...
Definition: window.hpp:65
#define LOG_IMPL_SCOPE_HEADER
Definition: window.cpp:81
std::map< std::string, linked_size > linked_size_
List of the widgets, whose size are linked together.
Definition: window.hpp:585
wfl::map_formula_callable variables_
The variables of the canvas.
Definition: window.hpp:469
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
void initialize_state()
Initializes the state of the keyboard and mouse.
HOTKEY_COMMAND get_id(const std::string &command)
returns get_hotkey_command(command).id
builder_window(const config &cfg)
Definition: window.cpp:97
Contains the implementation details for lexical_cast and shouldn&#39;t be used directly.
void draw_floating_labels(surface screen)
void layout_linked_widgets()
Layouts the linked widgets.
Definition: window.cpp:1073
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:410
void show(const std::string &window_id, const t_string &message, const point &mouse, const SDL_Rect &source_rect)
Shows a tip.
Definition: tooltip.cpp:140
std::function< void()> callback_next_draw_
Definition: window.hpp:770
Basic exception when the layout doesn&#39;t fit.