The Battle for Wesnoth  1.17.0-dev
window.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2007 - 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 /**
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 
65 #include <functional>
66 
67 #include <algorithm>
68 #include <iterator>
69 #include <stdexcept>
70 
71 namespace wfl { class function_symbol_table; }
72 namespace gui2 { class button; }
73 
74 static lg::log_domain log_gui("gui/layout");
75 #define ERR_GUI LOG_STREAM(err, log_gui)
76 
77 #define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
78 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
79 
80 #define LOG_IMPL_SCOPE_HEADER \
81  window.get_control_type() + " [" + window.id() + "] " + __func__
82 #define LOG_IMPL_HEADER LOG_IMPL_SCOPE_HEADER + ':'
83 
84 namespace gui2
85 {
86 
87 // ------------ WIDGET -----------{
88 
89 namespace implementation
90 {
91 /** @todo See whether this hack can be removed. */
92 // Needed to fix a compiler error in REGISTER_WIDGET.
94 {
95 public:
97  {
98  }
99 
101 
102  virtual widget* build() const override
103  {
104  return nullptr;
105  }
106 };
107 
108 } // namespace implementation
110 
111 namespace
112 {
113 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
114 const unsigned SHOW = debug_layout_graph::SHOW;
115 const unsigned LAYOUT = debug_layout_graph::LAYOUT;
116 #else
117 // values are irrelavant when DEBUG_WINDOW_LAYOUT_GRAPHS is not defined.
118 const unsigned SHOW = 0;
119 const unsigned LAYOUT = 0;
120 #endif
121 
122 /**
123  * Pushes a single draw event to the queue. To be used before calling
124  * events::pump when drawing windows.
125  *
126  * @todo: in the future we should simply call draw functions directly
127  * from events::pump and do away with the custom drawing events, but
128  * that's a 1.15 target. For now, this will have to do.
129  */
130 static void push_draw_event()
131 {
132  // DBG_GUI_E << "Pushing draw event in queue.\n";
133 
134  SDL_Event event;
136 
137  event.type = DRAW_EVENT;
138  event.user = data;
139 
140  SDL_PushEvent(&event);
141 }
142 
143 /**
144  * SDL_AddTimer() callback for delay_event.
145  *
146  * @param event The event to push in the event queue.
147  *
148  * @return The new timer interval (always 0).
149  */
150 static uint32_t delay_event_callback(const uint32_t, void* event)
151 {
152  SDL_PushEvent(static_cast<SDL_Event*>(event));
153  delete static_cast<SDL_Event*>(event);
154  return 0;
155 }
156 
157 /**
158  * Allows an event to be delayed a certain amount of time.
159  *
160  * @note the delay is the minimum time, after the time has passed the event
161  * will be pushed in the SDL event queue, so it might delay more.
162  *
163  * @param event The event to delay.
164  * @param delay The number of ms to delay the event.
165  */
166 static void delay_event(const SDL_Event& event, const uint32_t delay)
167 {
168  SDL_AddTimer(delay, delay_event_callback, new SDL_Event(event));
169 }
170 
171 /**
172  * Adds a SHOW_HELPTIP event to the SDL event queue.
173  *
174  * The event is used to show the helptip for the currently focused widget.
175  */
176 static void helptip()
177 {
178  DBG_GUI_E << "Pushing SHOW_HELPTIP_EVENT event in queue.\n";
179 
180  SDL_Event event;
182 
183  event.type = SHOW_HELPTIP_EVENT;
184  event.user = data;
185 
186  SDL_PushEvent(&event);
187 }
188 
189 /**
190  * Small helper class to get an unique id for every window instance.
191  *
192  * This is used to send event to the proper window, this allows windows to post
193  * messages to themselves and let them delay for a certain amount of time.
194  */
195 class manager
196 {
197  manager();
198 
199 public:
200  static manager& instance();
201 
202  void add(window& window);
203 
204  void remove(window& window);
205 
206  unsigned get_id(window& window);
207 
208  window* get_window(const unsigned id);
209 
210 private:
211  // The number of active window should be rather small
212  // so keep it simple and don't add a reverse lookup map.
213  std::map<unsigned, window*> windows_;
214 };
215 
217 {
218 }
219 
220 manager& manager::instance()
221 {
222  static manager window_manager;
223  return window_manager;
224 }
225 
226 void manager::add(window& win)
227 {
228  static unsigned id;
229  ++id;
230  windows_[id] = &win;
231 }
232 
233 void manager::remove(window& win)
234 {
236  itor != windows_.end();
237  ++itor) {
238 
239  if(itor->second == &win) {
240  windows_.erase(itor);
241  return;
242  }
243  }
244  assert(false);
245 }
246 
247 unsigned manager::get_id(window& win)
248 {
250  itor != windows_.end();
251  ++itor) {
252 
253  if(itor->second == &win) {
254  return itor->first;
255  }
256  }
257  assert(false);
258 
259  return 0;
260 }
261 
262 window* manager::get_window(const unsigned id)
263 {
265 
266  if(itor == windows_.end()) {
267  return nullptr;
268  } else {
269  return itor->second;
270  }
271 }
272 
273 } // namespace
274 
275 window::window(const builder_window::window_resolution& definition)
276  : panel(implementation::builder_window(::config {"definition", definition.definition}), type())
281  , owner_(nullptr)
282  , need_layout_(true)
283  , variables_()
285  , suspend_drawing_(true)
286  , restore_(true)
288  , restorer_()
289  , automatic_placement_(definition.automatic_placement)
290  , horizontal_placement_(definition.horizontal_placement)
291  , vertical_placement_(definition.vertical_placement)
292  , maximum_width_(definition.maximum_width)
293  , maximum_height_(definition.maximum_height)
294  , x_(definition.x)
295  , y_(definition.y)
296  , w_(definition.width)
297  , h_(definition.height)
298  , reevaluate_best_size_(definition.reevaluate_best_size)
299  , functions_(definition.functions)
300  , tooltip_(definition.tooltip)
301  , helptip_(definition.helptip)
302  , click_dismiss_(false)
303  , enter_disabled_(false)
304  , escape_disabled_(false)
305  , linked_size_()
306  , mouse_button_state_(0) /**< Needs to be initialized in @ref show. */
307  , dirty_list_()
308 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
309  , debug_layout_(new debug_layout_graph(this))
310 #endif
312  , exit_hook_([](window&)->bool { return true; })
313  , callback_next_draw_(nullptr)
314 {
315  manager::instance().add(*this);
316 
317  connect();
318 
319  if (!video_.faked())
320  {
321  connect_signal<event::DRAW>(std::bind(&window::draw, this));
322  }
323 
324  connect_signal<event::SDL_VIDEO_RESIZE>(std::bind(
325  &window::signal_handler_sdl_video_resize, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5));
326 
327  connect_signal<event::SDL_ACTIVATE>(std::bind(
329 
330  connect_signal<event::SDL_LEFT_BUTTON_UP>(
332  this,
333  std::placeholders::_2,
334  std::placeholders::_3,
335  std::placeholders::_4,
336  SDL_BUTTON_LMASK),
338  connect_signal<event::SDL_MIDDLE_BUTTON_UP>(
340  this,
341  std::placeholders::_2,
342  std::placeholders::_3,
343  std::placeholders::_4,
344  SDL_BUTTON_MMASK),
346  connect_signal<event::SDL_RIGHT_BUTTON_UP>(
348  this,
349  std::placeholders::_2,
350  std::placeholders::_3,
351  std::placeholders::_4,
352  SDL_BUTTON_RMASK),
354 
355  connect_signal<event::SDL_KEY_DOWN>(
356  std::bind(
357  &window::signal_handler_sdl_key_down, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5, std::placeholders::_6, true),
359  connect_signal<event::SDL_KEY_DOWN>(std::bind(
360  &window::signal_handler_sdl_key_down, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5, std::placeholders::_6, false));
361 
362  connect_signal<event::MESSAGE_SHOW_TOOLTIP>(
364  this,
365  std::placeholders::_2,
366  std::placeholders::_3,
367  std::placeholders::_5),
369 
370  connect_signal<event::MESSAGE_SHOW_HELPTIP>(
372  this,
373  std::placeholders::_2,
374  std::placeholders::_3,
375  std::placeholders::_5),
377 
378  connect_signal<event::REQUEST_PLACEMENT>(
379  std::bind(
380  &window::signal_handler_request_placement, this, std::placeholders::_2, std::placeholders::_3),
382 
383  connect_signal<event::CLOSE_WINDOW>(std::bind(&window::signal_handler_close_window, this));
384 
385  register_hotkey(hotkey::GLOBAL__HELPTIP, std::bind(gui2::helptip));
386 
387  /** @todo: should eventally become part of global hotkey handling. */
389  std::bind(&CVideo::toggle_fullscreen, std::ref(video_)));
390 }
391 
393 {
394  /*
395  * We need to delete our children here instead of waiting for the grid to
396  * automatically do it. The reason is when the grid deletes its children
397  * they will try to unregister them self from the linked widget list. At
398  * this point the member of window are destroyed and we enter UB. (For
399  * some reason the bug didn't trigger on g++ but it does on MSVC.
400  */
401  for(unsigned row = 0; row < get_grid().get_rows(); ++row) {
402  for(unsigned col = 0; col < get_grid().get_cols(); ++col) {
403  get_grid().remove_child(row, col);
404  }
405  }
406 
407  /*
408  * The tip needs to be closed if the window closes and the window is
409  * not a tip. If we don't do that the tip will unrender in the next
410  * window and cause drawing glitches.
411  * Another issue is that on smallgui and an MP game the tooltip not
412  * unrendered properly can capture the mouse and make playing impossible.
413  */
416  }
417 
418  manager::instance().remove(*this);
419 
420 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
421 
422  delete debug_layout_;
423 
424 #endif
425 }
426 
428 {
429  return manager::instance().get_window(handle);
430 }
431 
432 retval window::get_retval_by_id(const std::string& id)
433 {
434  // Note it might change to a map later depending on the number
435  // of items.
436  if(id == "ok") {
437  return retval::OK;
438  } else if(id == "cancel" || id == "quit") {
439  return retval::CANCEL;
440  } else {
441  return retval::NONE;
442  }
443 }
444 
445 void window::show_tooltip(/*const unsigned auto_close_timeout*/)
446 {
447  log_scope2(log_gui_draw, "Window: show as tooltip.");
448 
449  generate_dot_file("show", SHOW);
450 
451  assert(status_ == status::NEW);
452 
455 
457 
458  /*
459  * Before show has been called, some functions might have done some testing
460  * on the window and called layout, which can give glitches. So
461  * reinvalidate the window to avoid those glitches.
462  */
464  suspend_drawing_ = false;
465 }
466 
467 void window::show_non_modal(/*const unsigned auto_close_timeout*/)
468 {
469  log_scope2(log_gui_draw, "Window: show non modal.");
470 
471  generate_dot_file("show", SHOW);
472 
473  assert(status_ == status::NEW);
474 
476 
478 
479  /*
480  * Before show has been called, some functions might have done some testing
481  * on the window and called layout, which can give glitches. So
482  * reinvalidate the window to avoid those glitches.
483  */
485  suspend_drawing_ = false;
486 
487  push_draw_event();
488 
489  events::pump();
490 }
491 
492 int window::show(const bool restore, const unsigned auto_close_timeout)
493 {
494  /*
495  * Removes the old tip if one shown. The show_tip doesn't remove
496  * the tip, since it's the tip.
497  */
499 
501  restore_ = restore;
502 
504 
505  generate_dot_file("show", SHOW);
506 
507  assert(status_ == status::NEW);
508 
509  /*
510  * Before show has been called, some functions might have done some testing
511  * on the window and called layout, which can give glitches. So
512  * reinvalidate the window to avoid those glitches.
513  */
515  suspend_drawing_ = false;
516 
517  if(auto_close_timeout) {
518  // Make sure we're drawn before we try to close ourselves, which can
519  // happen if the timeout is small.
520  draw();
521 
522  SDL_Event event;
523  sdl::UserEvent data(CLOSE_WINDOW_EVENT, manager::instance().get_id(*this));
524 
525  event.type = CLOSE_WINDOW_EVENT;
526  event.user = data;
527 
528  delay_event(event, auto_close_timeout);
529  }
530 
531 
532  try
533  {
534  // Start our loop drawing will happen here as well.
535  bool mouse_button_state_initialized = false;
537  push_draw_event();
538 
539  // process installed callback if valid, to allow e.g. network
540  // polling
541  events::pump();
542 
543  if(!mouse_button_state_initialized) {
544  /*
545  * The state must be initialize when showing the dialog.
546  * However when initialized before this point there were random
547  * errors. This only happened when the 'click' was done fast; a
548  * slower click worked properly.
549  *
550  * So it seems the events need to be processed before SDL can
551  * return the proper button state. When initializing here all
552  * works fine.
553  */
554  mouse_button_state_ = SDL_GetMouseState(nullptr, nullptr);
555  mouse_button_state_initialized = true;
556  }
557 
560  }
561 
562  // Add a delay so we don't keep spinning if there's no event.
563  if(status_ != status::CLOSED) {
564  SDL_Delay(10);
565  }
566  }
567  }
568  catch(...)
569  {
570  /**
571  * @todo Clean up the code duplication.
572  *
573  * In the future the restoring shouldn't be needed so the duplication
574  * doesn't hurt too much but keep this todo as a reminder.
575  */
576  suspend_drawing_ = true;
577 
578  // restore area
579  if(restore_) {
580  SDL_Rect rect = get_rectangle();
581  sdl_blit(restorer_, 0, video_.getSurface(), &rect);
583  }
584  throw;
585  }
586 
587  suspend_drawing_ = true;
588 
589  // restore area
590  if(restore_) {
591  SDL_Rect rect = get_rectangle();
592  sdl_blit(restorer_, 0, video_.getSurface(), &rect);
594  }
595 
596  if(text_box_base* tb = dynamic_cast<text_box_base*>(event_distributor_->keyboard_focus())) {
597  tb->interrupt_composition();
598  }
599 
600  return retval_;
601 }
602 
604 {
605  /***** ***** ***** ***** Init ***** ***** ***** *****/
606  // Prohibited from drawing?
607  if(suspend_drawing_) {
608  return;
609  }
610 
611  surface& frame_buffer = video_.getSurface();
612 
613  /***** ***** Layout and get dirty list ***** *****/
614  if(need_layout_) {
615  // Restore old surface. In the future this phase will not be needed
616  // since all will be redrawn when needed with dirty rects. Since that
617  // doesn't work yet we need to undraw the window.
618  if(restore_ && restorer_) {
619  SDL_Rect rect = get_rectangle();
620  sdl_blit(restorer_, 0, frame_buffer, &rect);
621  }
622 
623  layout();
624 
625  // Get new surface for restoring
626  SDL_Rect rect = get_rectangle();
627 
628  // We want the labels underneath the window so draw them and use them
629  // as restore point.
630  if(is_toplevel_) {
631  font::draw_floating_labels(frame_buffer);
632  }
633 
634  if(restore_) {
635  restorer_ = get_surface_portion(frame_buffer, rect);
636  }
637 
638  // Need full redraw so only set ourselves dirty.
639  dirty_list_.emplace_back(1, this);
640  } else {
641 
642  // Let widgets update themselves, which might dirty some things.
643  layout_children();
644 
645  // Now find the widgets that are dirty.
646  std::vector<widget*> call_stack;
647  if(!new_widgets) {
648  populate_dirty_list(*this, call_stack);
649  } else {
650  /* Force to update and redraw the entire screen */
651  dirty_list_.clear();
652  dirty_list_.emplace_back(1, this);
653  }
654  }
655 
656  if (dirty_list_.empty()) {
658  return;
659  }
660 
662  if(consecutive_changed_frames_ >= 100u && id_ == "title_screen") {
663  /* The title screen has changed in 100 consecutive frames, i.e. every
664  frame for two seconds. It looks like the screen is constantly changing
665  or at least marking widgets as dirty.
666 
667  That's a severe problem. Every time the title screen changes, all
668  other GUI windows need to be fully redrawn, with huge CPU usage cost.
669  For that reason, this situation is a hard error. */
670  throw std::logic_error("The title screen is constantly changing, "
671  "which has a huge CPU usage cost. See the code comment.");
672  }
673 
674  for(auto & item : dirty_list_)
675  {
676 
677  assert(!item.empty());
678 
679  const SDL_Rect dirty_rect
681  : item.back()->get_dirty_rectangle();
682 
683 // For testing we disable the clipping rect and force the entire screen to
684 // update. This way an item rendered at the wrong place is directly visible.
685 #if 0
686  dirty_list_.clear();
687  dirty_list_.emplace_back(1, this);
688 #else
689  clip_rect_setter clip(frame_buffer, &dirty_rect);
690 #endif
691 
692  /*
693  * The actual update routine does the following:
694  * - Restore the background.
695  *
696  * - draw [begin, end) the back ground of all widgets.
697  *
698  * - draw the children of the last item in the list, if this item is
699  * a container it's children get a full redraw. If it's not a
700  * container nothing happens.
701  *
702  * - draw [rbegin, rend) the fore ground of all widgets. For items
703  * which have two layers eg window or panel it draws the foreground
704  * layer. For other widgets it's a nop.
705  *
706  * Before drawing there needs to be determined whether a dirty widget
707  * really needs to be redrawn. If the widget doesn't need to be
708  * redrawing either being not visibility::visible or has status
709  * widget::redraw_action::none. If it's not drawn it's still set not
710  * dirty to avoid it keep getting on the dirty list.
711  */
712 
713  for(std::vector<widget*>::iterator itor = item.begin();
714  itor != item.end();
715  ++itor) {
716 
717  if((**itor).get_visible() != widget::visibility::visible
718  || (**itor).get_drawing_action()
720 
721  for(std::vector<widget*>::iterator citor = itor;
722  citor != item.end();
723  ++citor) {
724 
725  (**citor).set_is_dirty(false);
726  }
727 
728  item.erase(itor, item.end());
729  break;
730  }
731  }
732 
733  // Restore.
734  if(restore_) {
735  SDL_Rect rect = get_rectangle();
736  sdl_blit(restorer_, 0, frame_buffer, &rect);
737  }
738 
739  // Background.
740  for(std::vector<widget*>::iterator itor = item.begin();
741  itor != item.end();
742  ++itor) {
743 
744  (**itor).draw_background(frame_buffer, 0, 0);
745  }
746 
747  // Children.
748  if(!item.empty()) {
749  item.back()->draw_children(frame_buffer, 0, 0);
750  }
751 
752  // Foreground.
753  for(std::vector<widget*>::reverse_iterator ritor = item.rbegin();
754  ritor != item.rend();
755  ++ritor) {
756 
757  (**ritor).draw_foreground(frame_buffer, 0, 0);
758  (**ritor).set_is_dirty(false);
759  }
760  }
761 
762  dirty_list_.clear();
763 
765 
766  std::vector<widget*> call_stack;
767  populate_dirty_list(*this, call_stack);
768  assert(dirty_list_.empty());
769 
770  if(callback_next_draw_ != nullptr) {
772  callback_next_draw_ = nullptr;
773  }
774 }
775 
777 {
778  if(restore_ && restorer_) {
779  SDL_Rect rect = get_rectangle();
780  sdl_blit(restorer_, 0, video_.getSurface(), &rect);
781  // Since the old area might be bigger as the new one, invalidate
782  // 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 
1132 namespace
1133 {
1134 
1135 /**
1136  * Swaps an item in a grid for another one.
1137  * This differs slightly from the standard swap_grid utility, so it's defined by itself here.
1138  */
1139 void window_swap_grid(grid* g,
1140  grid* content_grid,
1141  widget* widget,
1142  const std::string& id)
1143 {
1144  assert(content_grid);
1145  assert(widget);
1146 
1147  // Make sure the new child has same id.
1148  widget->set_id(id);
1149 
1150  // Get the container containing the wanted widget.
1151  grid* parent_grid = nullptr;
1152  if(g) {
1153  parent_grid = find_widget<grid>(g, id, false, false);
1154  }
1155  if(!parent_grid) {
1156  parent_grid = find_widget<grid>(content_grid, id, true, false);
1157  assert(parent_grid);
1158  }
1159  if(grid* grandparent_grid = dynamic_cast<grid*>(parent_grid->parent())) {
1160  grandparent_grid->swap_child(id, widget, false);
1161  } else if(container_base* c
1162  = dynamic_cast<container_base*>(parent_grid->parent())) {
1163 
1164  c->get_grid().swap_child(id, widget, true);
1165  } else {
1166  assert(false);
1167  }
1168 }
1169 
1170 } // namespace
1171 
1173 {
1174  std::vector<dispatcher*>& dispatchers = event::get_all_dispatchers();
1175  auto me = std::find(dispatchers.begin(), dispatchers.end(), this);
1176 
1177  for(auto it = std::next(me); it != dispatchers.end(); ++it) {
1178  // Note that setting an entire window dirty like this is expensive.
1179  dynamic_cast<widget&>(**it).set_is_dirty(true);
1180  }
1181 }
1182 
1183 void window::finalize(const builder_grid& content_grid)
1184 {
1185  window_swap_grid(nullptr, &get_grid(), content_grid.build(), "_window_content_grid");
1186 }
1187 
1188 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
1189 
1190 void window::generate_dot_file(const std::string& generator,
1191  const unsigned domain)
1192 {
1193  debug_layout_->generate_dot_file(generator, domain);
1194 }
1195 #endif
1196 
1198  const unsigned maximum_width,
1199  const unsigned maximum_height)
1200 {
1202 
1203  /*
1204  * For now we return the status, need to test later whether this can
1205  * entirely be converted to an exception based system as in 'promised' on
1206  * the algorithm page.
1207  */
1208 
1209  try
1210  {
1211  point size = window.get_best_size();
1212 
1213  DBG_GUI_L << LOG_IMPL_HEADER << " best size : " << size
1214  << " maximum size : " << maximum_width << ','
1215  << maximum_height << ".\n";
1216  if(size.x <= static_cast<int>(maximum_width)
1217  && size.y <= static_cast<int>(maximum_height)) {
1218 
1219  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Fits, nothing to do.\n";
1220  return;
1221  }
1222 
1223  if(size.x > static_cast<int>(maximum_width)) {
1224  window.reduce_width(maximum_width);
1225 
1226  size = window.get_best_size();
1227  if(size.x > static_cast<int>(maximum_width)) {
1228  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resize width failed."
1229  << " Wanted width " << maximum_width
1230  << " resulting width " << size.x << ".\n";
1232  }
1234  << " Status: Resize width succeeded.\n";
1235  }
1236 
1237  if(size.y > static_cast<int>(maximum_height)) {
1238  window.reduce_height(maximum_height);
1239 
1240  size = window.get_best_size();
1241  if(size.y > static_cast<int>(maximum_height)) {
1242  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resize height failed."
1243  << " Wanted height " << maximum_height
1244  << " resulting height " << size.y << ".\n";
1246  }
1248  << " Status: Resize height succeeded.\n";
1249  }
1250 
1251  assert(size.x <= static_cast<int>(maximum_width)
1252  && size.y <= static_cast<int>(maximum_height));
1253 
1254 
1255  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resizing succeeded.\n";
1256  return;
1257  }
1258  catch(const layout_exception_width_modified&)
1259  {
1261  << " Status: Width has been modified, rerun.\n";
1262 
1263  window.layout_initialize(false);
1264  window.layout_linked_widgets();
1265  layout(window, maximum_width, maximum_height);
1266  return;
1267  }
1268 }
1269 
1270 void window::mouse_capture(const bool capture)
1271 {
1272  assert(event_distributor_);
1273  event_distributor_->capture_mouse(capture);
1274 }
1275 
1277 {
1278  assert(event_distributor_);
1279  event_distributor_->keyboard_capture(widget);
1280 }
1281 
1283 {
1284  assert(event_distributor_);
1285  event_distributor_->keyboard_add_to_chain(widget);
1286 }
1287 
1289 {
1290  assert(event_distributor_);
1291  event_distributor_->keyboard_remove_from_chain(widget);
1292 }
1293 
1295 {
1296  if(std::find(tab_order.begin(), tab_order.end(), widget) != tab_order.end()) {
1297  return;
1298  }
1299  assert(event_distributor_);
1300  if(tab_order.empty() && !event_distributor_->keyboard_focus()) {
1301  keyboard_capture(widget);
1302  }
1303  if(at < 0 || at >= static_cast<int>(tab_order.size())) {
1304  tab_order.push_back(widget);
1305  } else {
1306  tab_order.insert(tab_order.begin() + at, widget);
1307  }
1308 }
1309 
1311  bool& handled,
1312  const point& new_size)
1313 {
1314  DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
1315 
1318  settings::screen_width = new_size.x;
1319  settings::screen_height = new_size.y;
1321 
1322  handled = true;
1323 }
1324 
1326  bool& handled,
1327  bool& halt,
1328  const int mouse_button_mask)
1329 {
1330  DBG_GUI_E << LOG_HEADER << ' ' << event << " mouse_button_mask "
1331  << static_cast<unsigned>(mouse_button_mask) << ".\n";
1332 
1333  handled = halt = click_dismiss(mouse_button_mask);
1334 }
1335 
1336 static bool is_active(const widget* wgt)
1337 {
1338  if(const styled_widget* control = dynamic_cast<const styled_widget*>(wgt)) {
1339  return control->get_active() && control->get_visible() == widget::visibility::visible;
1340  }
1341  return false;
1342 }
1343 
1345  bool& handled,
1346  const SDL_Keycode key,
1347  const SDL_Keymod mod,
1348  bool handle_tab)
1349 {
1350  DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
1351 
1352  if(text_box_base* tb = dynamic_cast<text_box_base*>(event_distributor_->keyboard_focus())) {
1353  if(tb->is_composing()) {
1354  if(handle_tab && !tab_order.empty() && key == SDLK_TAB) {
1355  tb->interrupt_composition();
1356  } else {
1357  return;
1358  }
1359  }
1360  }
1361  if(!enter_disabled_ && (key == SDLK_KP_ENTER || key == SDLK_RETURN)) {
1363  handled = true;
1364  } else if(key == SDLK_ESCAPE && !escape_disabled_) {
1366  handled = true;
1367  } else if(key == SDLK_SPACE) {
1368  handled = click_dismiss(0);
1369  } else if(handle_tab && !tab_order.empty() && key == SDLK_TAB) {
1370  assert(event_distributor_);
1371  widget* focus = event_distributor_->keyboard_focus();
1372  auto iter = std::find(tab_order.begin(), tab_order.end(), focus);
1373  do {
1374  if(mod & KMOD_SHIFT) {
1375  if(iter == tab_order.begin()) {
1376  iter = tab_order.end();
1377  }
1378  iter--;
1379  } else {
1380  if(iter == tab_order.end()) {
1381  iter = tab_order.begin();
1382  } else {
1383  iter++;
1384  if(iter == tab_order.end()) {
1385  iter = tab_order.begin();
1386  }
1387  }
1388  }
1389  } while(!is_active(*iter));
1390  keyboard_capture(*iter);
1391  handled = true;
1392  }
1393 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
1394  if(key == SDLK_F12) {
1395  debug_layout_->generate_dot_file("manual", debug_layout_graph::MANUAL);
1396  handled = true;
1397  }
1398 #endif
1399 }
1400 
1402  bool& handled,
1403  const event::message& message)
1404 {
1405  DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
1406 
1407  const event::message_show_tooltip& request
1408  = dynamic_cast<const event::message_show_tooltip&>(message);
1409 
1410  dialogs::tip::show(tooltip_.id, request.message, request.location, request.source_rect);
1411 
1412  handled = true;
1413 }
1414 
1416  bool& handled,
1417  const event::message& message)
1418 {
1419  DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
1420 
1421  const event::message_show_helptip& request
1422  = dynamic_cast<const event::message_show_helptip&>(message);
1423 
1424  dialogs::tip::show(helptip_.id, request.message, request.location, request.source_rect);
1425 
1426  handled = true;
1427 }
1428 
1430  bool& handled)
1431 {
1432  DBG_GUI_E << LOG_HEADER << ' ' << event << ".\n";
1433 
1435 
1436  handled = true;
1437 }
1438 
1440 {
1442 }
1443 
1444 // }---------- DEFINITION ---------{
1445 
1448 {
1449  DBG_GUI_P << "Parsing window " << id << '\n';
1450 
1451  load_resolutions<resolution>(cfg);
1452 }
1453 
1455  : panel_definition::resolution(cfg), grid(nullptr)
1456 {
1457  const config& child = cfg.child("grid");
1458  // VALIDATE(child, _("No grid defined."));
1459 
1460  /** @todo Evaluate whether the grid should become mandatory. */
1461  if(child) {
1462  grid = std::make_shared<builder_grid>(child);
1463  }
1464 }
1465 
1466 // }------------ END --------------
1467 
1468 } // namespace gui2
1469 
1470 
1471 /**
1472  * @page layout_algorithm Layout algorithm
1473  *
1474  * @section introduction-layout_algorithm Introduction
1475  *
1476  * This page describes how the layout engine for the dialogs works. First
1477  * a global overview of some terms used in this document.
1478  *
1479  * - @ref gui2::widget "Widget"; Any item which can be used in the widget
1480  * toolkit. Not all widgets are visible. In general widgets cannot be
1481  * sized directly, but this is controlled by a window. A widget has an
1482  * internal size cache and if the value in the cache is not equal to 0,0
1483  * that value is its best size. This value gets set when the widget can
1484  * honor a resize request. It will be set with the value which honors
1485  * the request.
1486  *
1487  * - @ref gui2::grid "Grid"; A grid is an invisible container which holds
1488  * one or more widgets. Several widgets have a grid in them to hold
1489  * multiple widgets eg panels and windows.
1490  *
1491  * - @ref gui2::grid::child "Grid cell"; Every widget which is in a grid is
1492  * put in a grid cell. These cells also hold the information about the gaps
1493  * between widgets the behavior on growing etc. All grid cells must have a
1494  * widget inside them.
1495  *
1496  * - @ref gui2::window "Window"; A window is a top level item which has a
1497  * grid with its children. The window handles the sizing of the window and
1498  * makes sure everything fits.
1499  *
1500  * - @ref gui2::window::linked_size "Shared size group"; A shared size
1501  * group is a number of widgets which share width and or height. These
1502  * widgets are handled separately in the layout algorithm. All grid cells
1503  * width such a widget will get the same height and or width and these
1504  * widgets won't be resized when there's not enough size. To be sure that
1505  * these widgets don't cause trouble for the layout algorithm, they must be
1506  * in a container with scrollbars so there will always be a way to properly
1507  * layout them. The engine must enforce this restriction so the shared
1508  * layout property must be set by the engine after validation.
1509  *
1510  * - All visible grid cells; A grid cell is visible when the widget inside
1511  * of it doesn't have the state visibility::invisible. Widgets which have the
1512  * state visibility::hidden are sized properly since when they become
1513  * visibility::visible the layout shouldn't be invalidated. A grid cell
1514  * that's invisible has size 0,0.
1515  *
1516  * - All resizable grid cells; A grid cell is resizable under the following
1517  * conditions:
1518  * - The widget is visibility::visible.
1519  * - The widget is not in a shared size group.
1520  *
1521  * There are two layout algorithms with a different purpose.
1522  *
1523  * - The Window algorithm; this algorithm's goal is it to make sure all grid
1524  * cells fit in the window. Sizing the grid cells depends on the widget
1525  * size as well, but this algorithm only sizes the grid cells and doesn't
1526  * handle the widgets inside them.
1527  *
1528  * - The Grid algorithm; after the Window algorithm made sure that all grid
1529  * cells fit this algorithm makes sure the widgets are put in the optimal
1530  * state in their grid cell.
1531  *
1532  * @section layout_algorithm_window Window
1533  *
1534  * Here is the algorithm used to layout the window:
1535  *
1536  * - Perform a full initialization
1537  * (@ref gui2::widget::layout_initialize (full_initialization = true)):
1538  * - Clear the internal best size cache for all widgets.
1539  * - For widgets with scrollbars hide them unless the
1540  * @ref gui2::scrollbar_container::scrollbar_mode "scrollbar_mode" is
1541  * ALWAYS_VISIBLE or AUTO_VISIBLE.
1542  * - Handle shared sizes:
1543  * - Height and width:
1544  * - Get the best size for all widgets that share height and width.
1545  * - Set the maximum of width and height as best size for all these
1546  * widgets.
1547  * - Width only:
1548  * - Get the best width for all widgets which share their width.
1549  * - Set the maximum width for all widgets, but keep their own height.
1550  * - Height only:
1551  * - Get the best height for all widgets which share their height.
1552  * - Set the maximum height for all widgets, but keep their own width.
1553  * - Start layout loop:
1554  * - Get best size.
1555  * - If width <= maximum_width && height <= maximum_height we're done.
1556  * - If width > maximum_width, optimize the width:
1557  * - For every grid cell in a grid row there will be a resize request
1558  * (@ref gui2::grid::reduce_width):
1559  * - Sort the widgets in the row on the resize priority.
1560  * - Loop through this priority queue until the row fits
1561  * - If priority != 0 try to share the extra width else all
1562  * widgets are tried to reduce the full size.
1563  * - Try to shrink the widgets by either wrapping or using a
1564  * scrollbar (@ref gui2::widget::request_reduce_width).
1565  * - If the row fits in the wanted width this row is done.
1566  * - Else try the next priority.
1567  * - All priorities done and the width still doesn't fit.
1568  * - Loop through this priority queue until the row fits.
1569  * - If priority != 0:
1570  * - try to share the extra width
1571  * -Else:
1572  * - All widgets are tried to reduce the full size.
1573  * - Try to shrink the widgets by sizing them smaller as really
1574  * wanted (@ref gui2::widget::demand_reduce_width).
1575  * For labels, buttons etc. they get ellipsized.
1576  * - If the row fits in the wanted width this row is done.
1577  * - Else try the next priority.
1578  * - All priorities done and the width still doesn't fit.
1579  * - Throw a layout width doesn't fit exception.
1580  * - If height > maximum_height, optimize the height
1581  * (@ref gui2::grid::reduce_height):
1582  * - For every grid cell in a grid column there will be a resize request:
1583  * - Sort the widgets in the column on the resize priority.
1584  * - Loop through this priority queue until the column fits:
1585  * - If priority != 0 try to share the extra height else all
1586  * widgets are tried to reduce the full size.
1587  * - Try to shrink the widgets by using a scrollbar
1588  * (@ref gui2::widget::request_reduce_height).
1589  * - If succeeded for a widget the width is influenced and the
1590  * width might be invalid.
1591  * - Throw a width modified exception.
1592  * - If the column fits in the wanted height this column is done.
1593  * - Else try the next priority.
1594  * - All priorities done and the height still doesn't fit.
1595  * - Loop through this priority queue until the column fits.
1596  * - If priority != 0 try to share the extra height else all
1597  * widgets are tried to reduce the full size.
1598  * - Try to shrink the widgets by sizing them smaller as really
1599  * wanted (@ref gui2::widget::demand_reduce_width).
1600  * For labels, buttons etc. they get ellipsized .
1601  * - If the column fits in the wanted height this column is done.
1602  * - Else try the next priority.
1603  * - All priorities done and the height still doesn't fit.
1604  * - Throw a layout height doesn't fit exception.
1605  * - End layout loop.
1606  *
1607  * - Catch @ref gui2::layout_exception_width_modified "width modified":
1608  * - Goto relayout.
1609  *
1610  * - Catch
1611  * @ref gui2::layout_exception_width_resize_failed "width resize failed":
1612  * - If the window has a horizontal scrollbar which isn't shown but can be
1613  * shown.
1614  * - Show the scrollbar.
1615  * - goto relayout.
1616  * - Else show a layout failure message.
1617  *
1618  * - Catch
1619  * @ref gui2::layout_exception_height_resize_failed "height resize failed":
1620  * - If the window has a vertical scrollbar which isn't shown but can be
1621  * shown:
1622  * - Show the scrollbar.
1623  * - goto relayout.
1624  * - Else:
1625  * - show a layout failure message.
1626  *
1627  * - Relayout:
1628  * - Initialize all widgets
1629  * (@ref gui2::widget::layout_initialize (full_initialization = false))
1630  * - Handle shared sizes, since the reinitialization resets that state.
1631  * - Goto start layout loop.
1632  *
1633  * @section grid Grid
1634  *
1635  * This section will be documented later.
1636  */
Define the common log macros for the gui toolkit.
bool click_dismiss_
Do we want to have easy close behavior?
Definition: window.hpp:547
const std::string message
The message to show on the tooltip.
Definition: message.hpp:66
void keyboard_capture(widget *widget)
Definition: window.cpp:1276
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:522
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:1325
#define ERR_GUI
Definition: window.cpp:75
virtual grid * build() const override
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:1183
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:504
bool does_click_dismiss() const
Does the window close easily?
Definition: window.hpp:273
std::function< bool(window &)> exit_hook_
Definition: window.hpp:764
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:1344
#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:335
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:516
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.
const unsigned horizontal_placement_
Sets the horizontal placement.
Definition: window.hpp:490
virtual void place(const point &origin, const point &size) override
See widget::place.
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:437
The window is new and not yet shown.
void show_non_modal()
Shows the window non modal.
Definition: window.cpp:467
void generate_dot_file(const std::string &, const unsigned)
Definition: window.hpp:674
Helper struct to force widgets the have the same size.
Definition: window.hpp:562
#define LOG_SCOPE_HEADER
Definition: window.cpp:77
const std::string & id() const
Definition: widget.cpp:110
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:653
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:603
SDL_Rect get_rectangle() const
Gets the bounding rectangle of the widget on the screen.
Definition: widget.cpp:310
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:1310
#define LOG_IMPL_HEADER
Definition: window.cpp:82
static CVideo & get_singleton()
Definition: video.hpp:49
static const std::string & type()
Static type getter that does not rely on the widget being constructed.
virtual widget * build() const override
Definition: window.cpp:102
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:1282
surface get_surface_portion(const surface &src, SDL_Rect &area)
Get a portion of the screen.
Definition: utils.cpp:2158
typed_formula< unsigned > w_
The formula to calculate the width of the dialog.
Definition: window.hpp:513
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:1401
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
surface & getSurface()
Returns a reference to the framebuffer.
Definition: video.cpp:484
int mouse_button_state_
The state of the mouse button.
Definition: window.hpp:630
void get_screen_size_variables(wfl::map_formula_callable &variable)
Gets a formula object with the screen size.
Definition: helper.cpp:100
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:573
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:1336
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:417
status status_
The status of the window.
Definition: window.hpp:440
#define LOG_HEADER
Definition: window.cpp:78
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:570
surface restorer_
When the window closes this surface is used to undraw the window.
Definition: window.hpp:479
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:193
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:470
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:550
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:467
void set_visible(const visibility visible)
Definition: widget.cpp:476
bool need_layout_
When set the form needs a full layout redraw cycle.
Definition: window.hpp:461
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal_function &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:172
void set_is_dirty(const bool is_dirty)
Definition: widget.cpp:466
void signal_handler_close_window()
Definition: window.cpp:1439
typed_formula< bool > reevaluate_best_size_
The formula to determine whether the size is good.
Definition: window.hpp:519
builder_window::window_resolution::tooltip_info helptip_
The settings for the helptip.
Definition: window.hpp:528
bool is_toplevel_
Whether the window has other windows behind it.
Definition: window.hpp:476
unsigned gamemap_height
Definition: settings.cpp:35
widget * parent()
Definition: widget.cpp:160
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:510
Exception thrown when the width resizing has failed.
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:482
typed_formula< unsigned > maximum_width_
The maximum width if automatic_placement_ is true.
Definition: window.hpp:501
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:507
std::vector< std::vector< widget * > > dirty_list_
The list with dirty items in the window.
Definition: window.hpp:646
void set_mouse_behavior(const mouse_behavior mouse_behavior)
Definition: dispatcher.hpp:829
bool escape_disabled_
Disable the escape key see our setter for more info.
Definition: window.hpp:553
void pump()
Definition: events.cpp:473
The window has been requested to be closed but still needs to evaluate the request.
#define REGISTER_WIDGET(id)
Wrapper for REGISTER_WIDGET3.
static window * window_instance(const unsigned handle)
Returns the instance of a window.
Definition: window.cpp:427
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:1288
bool faked() const
Definition: video.hpp:66
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:492
std::map< unsigned, window * > windows_
Definition: window.cpp:213
#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:573
The user set the widget invisible, that means:
window * get_window()
Get the parent window.
Definition: widget.cpp:117
double g
Definition: astarsearch.cpp:65
const unsigned vertical_placement_
Sets the vertical placement.
Definition: window.hpp:498
void undraw_floating_labels(surface screen)
std::vector< widget * > tab_order
List of widgets in the tabbing order.
Definition: window.hpp:583
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:1429
builder_window::window_resolution::tooltip_info tooltip_
The settings for the tooltip.
Definition: window.hpp:525
show_mode show_mode_
The mode in which the window is shown.
Definition: window.hpp:447
static void layout(window &window, const unsigned maximum_width, const unsigned maximum_height)
Layouts the window.
Definition: window.cpp:1197
window_definition(const config &cfg)
Definition: window.cpp:1446
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:432
#define next(ls)
Definition: llex.cpp:32
point get_mouse_position()
Returns the current mouse position.
Definition: helper.cpp:118
void undraw()
Undraws the window.
Definition: window.cpp:776
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:141
void show_tooltip()
Shows the window as a tooltip.
Definition: window.cpp:445
unsigned screen_height
Definition: settings.cpp:30
Definition: contexts.hpp:44
void set_id(const std::string &id)
Definition: widget.cpp:98
void mouse_capture(const bool capture=true)
Definition: window.cpp:1270
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:473
void redraw_windows_on_top() const
Schedules windows on top of us (if any) to redraw.
Definition: window.cpp:1172
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:679
void signal_handler_message_show_helptip(const event::ui_event event, bool &handled, const event::message &message)
Definition: window.cpp:1415
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:453
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:576
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:61
void add_to_tab_order(widget *widget, int at=-1)
Add the widget to the tabbing order.
Definition: window.cpp:1294
mock_char c
SDL_Rect screen_area(bool as_pixels=true) const
Returns the current window renderer area, either in pixels or screen coordinates. ...
Definition: video.cpp:277
void set_want_keyboard_input(const bool want_keyboard_input)
Definition: dispatcher.hpp:839
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:80
std::map< std::string, linked_size > linked_size_
List of the widgets, whose size are linked together.
Definition: window.hpp:580
wfl::map_formula_callable variables_
The variables of the canvas.
Definition: window.hpp:464
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:96
Contains the implementation details for lexical_cast and shouldn&#39;t be used directly.
void draw_floating_labels(surface screen)
ui_event
The event send to the dispatcher.
Definition: handler.hpp:48
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:765
Basic exception when the layout doesn&#39;t fit.