The Battle for Wesnoth  1.17.17+dev
window.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2007 - 2023
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 "draw.hpp"
28 #include "events.hpp"
29 #include "formula/callable.hpp"
30 #include "formula/string_utils.hpp"
31 #include "gettext.hpp"
32 #include "log.hpp"
38 #include "gui/core/log.hpp"
40 #include "sdl/point.hpp"
43 #include "gui/dialogs/tooltip.hpp"
44 #include "gui/widgets/button.hpp"
48 #include "gui/widgets/grid.hpp"
49 #include "gui/widgets/helper.hpp"
50 #include "gui/widgets/panel.hpp"
51 #include "gui/widgets/settings.hpp"
52 #include "gui/widgets/widget.hpp"
53 #include "gui/widgets/window.hpp"
54 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
55 #include "gui/widgets/debug.hpp"
56 #endif
57 #include "preferences/general.hpp"
58 #include "preferences/display.hpp"
59 #include "sdl/rect.hpp"
60 #include "sdl/surface.hpp"
61 #include "sdl/texture.hpp"
62 #include "formula/variant.hpp"
63 #include "video.hpp" // only for toggle_fullscreen
64 #include "wml_exception.hpp"
65 #include "sdl/userevent.hpp"
66 #include "sdl/input.hpp" // get_mouse_button_mask
67 
68 #include <functional>
69 
70 #include <algorithm>
71 #include <iterator>
72 #include <stdexcept>
73 
74 namespace wfl { class function_symbol_table; }
75 namespace gui2 { class button; }
76 
77 static lg::log_domain log_gui("gui/layout");
78 #define ERR_GUI LOG_STREAM(err, log_gui)
79 
80 #define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
81 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
82 
83 #define LOG_IMPL_SCOPE_HEADER \
84  window.get_control_type() + " [" + window.id() + "] " + __func__
85 #define LOG_IMPL_HEADER LOG_IMPL_SCOPE_HEADER + ':'
86 
87 static lg::log_domain log_display("display");
88 #define DBG_DP LOG_STREAM(debug, log_display)
89 #define LOG_DP LOG_STREAM(info, log_display)
90 #define WRN_DP LOG_STREAM(warn, log_display)
91 
92 namespace gui2
93 {
94 
95 // ------------ WIDGET -----------{
96 
97 namespace implementation
98 {
99 /** @todo See whether this hack can be removed. */
100 // Needed to fix a compiler error in REGISTER_WIDGET.
102 {
103 public:
105  {
106  }
107 
109 
110  virtual std::unique_ptr<widget> build() const override
111  {
112  return nullptr;
113  }
114 };
115 
116 } // namespace implementation
117 REGISTER_WIDGET(window)
118 
119 namespace
120 {
121 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
122 const unsigned SHOW = debug_layout_graph::SHOW;
123 const unsigned LAYOUT = debug_layout_graph::LAYOUT;
124 #else
125 // values are irrelavant when DEBUG_WINDOW_LAYOUT_GRAPHS is not defined.
126 const unsigned SHOW = 0;
127 const unsigned LAYOUT = 0;
128 #endif
129 
130 /**
131  * SDL_AddTimer() callback for delay_event.
132  *
133  * @param event The event to push in the event queue.
134  *
135  * @return The new timer interval (always 0).
136  */
137 static uint32_t delay_event_callback(const uint32_t, void* event)
138 {
139  SDL_PushEvent(static_cast<SDL_Event*>(event));
140  delete static_cast<SDL_Event*>(event);
141  return 0;
142 }
143 
144 /**
145  * Allows an event to be delayed a certain amount of time.
146  *
147  * @note the delay is the minimum time, after the time has passed the event
148  * will be pushed in the SDL event queue, so it might delay more.
149  *
150  * @param event The event to delay.
151  * @param delay The number of ms to delay the event.
152  */
153 static void delay_event(const SDL_Event& event, const uint32_t delay)
154 {
155  SDL_AddTimer(delay, delay_event_callback, new SDL_Event(event));
156 }
157 
158 /**
159  * Adds a SHOW_HELPTIP event to the SDL event queue.
160  *
161  * The event is used to show the helptip for the currently focused widget.
162  */
163 static void helptip()
164 {
165  DBG_GUI_E << "Pushing SHOW_HELPTIP_EVENT event in queue.";
166 
167  SDL_Event event;
169 
170  event.type = SHOW_HELPTIP_EVENT;
171  event.user = data;
172 
173  SDL_PushEvent(&event);
174 }
175 
176 /**
177  * Small helper class to get an unique id for every window instance.
178  *
179  * This is used to send event to the proper window, this allows windows to post
180  * messages to themselves and let them delay for a certain amount of time.
181  */
182 class manager
183 {
184  manager();
185 
186 public:
187  static manager& instance();
188 
189  void add(window& window);
190 
191  void remove(window& window);
192 
193  unsigned get_id(window& window);
194 
195  window* get_window(const unsigned id);
196 
197 private:
198  // The number of active window should be rather small
199  // so keep it simple and don't add a reverse lookup map.
200  std::map<unsigned, window*> windows_;
201 };
202 
203 manager::manager() : windows_()
204 {
205 }
206 
207 manager& manager::instance()
208 {
209  static manager window_manager;
210  return window_manager;
211 }
212 
213 void manager::add(window& win)
214 {
215  static unsigned id;
216  ++id;
217  windows_[id] = &win;
218 }
219 
220 void manager::remove(window& win)
221 {
223  itor != windows_.end();
224  ++itor) {
225 
226  if(itor->second == &win) {
227  windows_.erase(itor);
228  return;
229  }
230  }
231  assert(false);
232 }
233 
234 unsigned manager::get_id(window& win)
235 {
237  itor != windows_.end();
238  ++itor) {
239 
240  if(itor->second == &win) {
241  return itor->first;
242  }
243  }
244  assert(false);
245 
246  return 0;
247 }
248 
249 window* manager::get_window(const unsigned id)
250 {
252 
253  if(itor == windows_.end()) {
254  return nullptr;
255  } else {
256  return itor->second;
257  }
258 }
259 
260 } // namespace
261 
262 window::window(const builder_window::window_resolution& definition)
263  : panel(implementation::builder_window(::config {"definition", definition.definition}), type())
264  , status_(status::NEW)
265  , show_mode_(show_mode::none)
266  , retval_(retval::NONE)
267  , owner_(nullptr)
268  , need_layout_(true)
269  , variables_()
270  , invalidate_layout_blocked_(false)
271  , hidden_(true)
272  , automatic_placement_(definition.automatic_placement)
273  , horizontal_placement_(definition.horizontal_placement)
274  , vertical_placement_(definition.vertical_placement)
275  , maximum_width_(definition.maximum_width)
276  , maximum_height_(definition.maximum_height)
277  , x_(definition.x)
278  , y_(definition.y)
279  , w_(definition.width)
280  , h_(definition.height)
281  , reevaluate_best_size_(definition.reevaluate_best_size)
282  , functions_(definition.functions)
283  , tooltip_(definition.tooltip)
284  , helptip_(definition.helptip)
285  , click_dismiss_(false)
286  , enter_disabled_(false)
287  , escape_disabled_(false)
288  , linked_size_()
289  , mouse_button_state_(0) /**< Needs to be initialized in @ref show. */
290 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
291  , debug_layout_(new debug_layout_graph(this))
292 #endif
293  , event_distributor_(new event::distributor(*this, event::dispatcher::front_child))
294  , exit_hook_([](window&)->bool { return true; })
295 {
296  manager::instance().add(*this);
297 
298  connect();
299 
300  connect_signal<event::SDL_VIDEO_RESIZE>(std::bind(
301  &window::signal_handler_sdl_video_resize, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5));
302 
303  connect_signal<event::SDL_ACTIVATE>(std::bind(
304  &event::distributor::initialize_state, event_distributor_.get()));
305 
306  connect_signal<event::SDL_LEFT_BUTTON_UP>(
307  std::bind(&window::signal_handler_click_dismiss,
308  this,
309  std::placeholders::_2,
310  std::placeholders::_3,
311  std::placeholders::_4,
312  SDL_BUTTON_LMASK),
313  event::dispatcher::front_child);
314  connect_signal<event::SDL_MIDDLE_BUTTON_UP>(
315  std::bind(&window::signal_handler_click_dismiss,
316  this,
317  std::placeholders::_2,
318  std::placeholders::_3,
319  std::placeholders::_4,
320  SDL_BUTTON_MMASK),
321  event::dispatcher::front_child);
322  connect_signal<event::SDL_RIGHT_BUTTON_UP>(
323  std::bind(&window::signal_handler_click_dismiss,
324  this,
325  std::placeholders::_2,
326  std::placeholders::_3,
327  std::placeholders::_4,
328  SDL_BUTTON_RMASK),
329  event::dispatcher::front_child);
330 
331  connect_signal<event::SDL_KEY_DOWN>(
332  std::bind(
333  &window::signal_handler_sdl_key_down, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5, std::placeholders::_6, true),
334  event::dispatcher::back_post_child);
335  connect_signal<event::SDL_KEY_DOWN>(std::bind(
336  &window::signal_handler_sdl_key_down, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5, std::placeholders::_6, false));
337 
338  connect_signal<event::MESSAGE_SHOW_TOOLTIP>(
339  std::bind(&window::signal_handler_message_show_tooltip,
340  this,
341  std::placeholders::_2,
342  std::placeholders::_3,
343  std::placeholders::_5),
344  event::dispatcher::back_pre_child);
345 
346  connect_signal<event::MESSAGE_SHOW_HELPTIP>(
347  std::bind(&window::signal_handler_message_show_helptip,
348  this,
349  std::placeholders::_2,
350  std::placeholders::_3,
351  std::placeholders::_5),
352  event::dispatcher::back_pre_child);
353 
354  connect_signal<event::REQUEST_PLACEMENT>(
355  std::bind(
356  &window::signal_handler_request_placement, this, std::placeholders::_2, std::placeholders::_3),
357  event::dispatcher::back_pre_child);
358 
359  connect_signal<event::CLOSE_WINDOW>(std::bind(&window::signal_handler_close_window, this));
360 
361  register_hotkey(hotkey::GLOBAL__HELPTIP, std::bind(gui2::helptip));
362 
363  /** @todo: should eventally become part of global hotkey handling. */
364  register_hotkey(hotkey::HOTKEY_FULLSCREEN,
365  std::bind(&video::toggle_fullscreen));
366 }
367 
368 window::~window()
369 {
370  /*
371  * We need to delete our children here instead of waiting for the grid to
372  * automatically do it. The reason is when the grid deletes its children
373  * they will try to unregister them self from the linked widget list. At
374  * this point the member of window are destroyed and we enter UB. (For
375  * some reason the bug didn't trigger on g++ but it does on MSVC.
376  */
377  for(unsigned row = 0; row < get_grid().get_rows(); ++row) {
378  for(unsigned col = 0; col < get_grid().get_cols(); ++col) {
379  get_grid().remove_child(row, col);
380  }
381  }
382 
383  /*
384  * The tip needs to be closed if the window closes and the window is
385  * not a tip. If we don't do that the tip will unrender in the next
386  * window and cause drawing glitches.
387  * Another issue is that on smallgui and an MP game the tooltip not
388  * unrendered properly can capture the mouse and make playing impossible.
389  */
390  if(show_mode_ == show_mode::modal) {
392  }
393 
394  manager::instance().remove(*this);
395 
396  // If we are currently shown, then queue an undraw.
397  if(!hidden_) {
398  queue_redraw();
399  }
400 
401 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
402 
403  delete debug_layout_;
404 
405 #endif
406 }
407 
408 window* window::window_instance(const unsigned handle)
409 {
410  return manager::instance().get_window(handle);
411 }
412 
413 retval window::get_retval_by_id(const std::string& id)
414 {
415  // Note it might change to a map later depending on the number
416  // of items.
417  if(id == "ok") {
418  return retval::OK;
419  } else if(id == "cancel" || id == "quit") {
420  return retval::CANCEL;
421  } else {
422  return retval::NONE;
423  }
424 }
425 
426 void window::finish_build(const builder_window::window_resolution& definition)
427 {
428  for(const auto& lg : definition.linked_groups) {
429  if(has_linked_size_group(lg.id)) {
430  t_string msg = VGETTEXT("Linked '$id' group has multiple definitions.", {{"id", lg.id}});
431 
432  FAIL(msg);
433  }
434 
435  init_linked_size_group(lg.id, lg.fixed_width, lg.fixed_height);
436  }
437 
438  set_click_dismiss(definition.click_dismiss);
439 
440  const auto conf = cast_config_to<window_definition>();
441  assert(conf);
442 
443  if(conf->grid) {
444  init_grid(*conf->grid);
445  finalize(*definition.grid);
446  } else {
447  init_grid(*definition.grid);
448  }
449 
450  add_to_keyboard_chain(this);
451 }
452 
453 void window::show_tooltip(/*const unsigned auto_close_timeout*/)
454 {
455  // Unhide in any case.
456  hidden_ = false;
457 
458  // Connect to the event handler, if not yet connected.
459  if(!is_connected()) {
460  LOG_DP << "connecting " << id() << " on show_tooltip";
461  connect();
462  }
463 
464  log_scope2(log_gui_draw, "Window: show as tooltip.");
465 
466  generate_dot_file("show", SHOW);
467 
468  assert(status_ == status::NEW);
469 
470  set_mouse_behavior(event::dispatcher::mouse_behavior::none);
471  set_want_keyboard_input(false);
472 
473  show_mode_ = show_mode::tooltip;
474 
475  /*
476  * Before show has been called, some functions might have done some testing
477  * on the window and called layout, which can give glitches. So
478  * reinvalidate the window to avoid those glitches.
479  */
480  invalidate_layout();
481  queue_redraw();
482  DBG_DP << "show tooltip queued to " << get_rectangle();
483 }
484 
485 void window::show_non_modal(/*const unsigned auto_close_timeout*/)
486 {
487  // Unhide in any case.
488  hidden_ = false;
489 
490  // Connect to the event handler, if not yet connected.
491  if(!is_connected()) {
492  LOG_DP << "connecting " << id() << " on show_non_modal";
493  connect();
494  }
495 
496  log_scope2(log_gui_draw, "Window: show non modal.");
497 
498  generate_dot_file("show", SHOW);
499 
500  assert(status_ == status::NEW);
501 
502  set_mouse_behavior(event::dispatcher::mouse_behavior::hit);
503 
504  show_mode_ = show_mode::modeless;
505 
506  /*
507  * Before show has been called, some functions might have done some testing
508  * on the window and called layout, which can give glitches. So
509  * reinvalidate the window to avoid those glitches.
510  */
511  invalidate_layout();
512  queue_redraw();
513 
514  DBG_DP << "show non-modal queued to " << get_rectangle();
515 
517 }
518 
519 int window::show(const unsigned auto_close_timeout)
520 {
521  // Unhide in any case.
522  hidden_ = false;
523 
524  // Connect to the event handler, if not yet connected.
525  if(!is_connected()) {
526  LOG_DP << "connecting " << id() << " on show";
527  connect();
528  }
529 
530  /*
531  * Removes the old tip if one shown. The show_tip doesn't remove
532  * the tip, since it's the tip.
533  */
535 
536  show_mode_ = show_mode::modal;
537 
539 
540  generate_dot_file("show", SHOW);
541 
542  //assert(status_ == status::NEW);
543 
544  /*
545  * Before show has been called, some functions might have done some testing
546  * on the window and called layout, which can give glitches. So
547  * reinvalidate the window to avoid those glitches.
548  */
549  invalidate_layout();
550  queue_redraw();
551 
552  // Make sure we display at least once in all cases.
553  events::draw();
554 
555  if(auto_close_timeout) {
556  SDL_Event event;
557  sdl::UserEvent data(CLOSE_WINDOW_EVENT, manager::instance().get_id(*this));
558 
559  event.type = CLOSE_WINDOW_EVENT;
560  event.user = data;
561 
562  delay_event(event, auto_close_timeout);
563  }
564 
565  try
566  {
567  // Start our loop drawing will happen here as well.
568  bool mouse_button_state_initialized = false;
569  for(status_ = status::SHOWING; status_ != status::CLOSED;) {
570  // Process and handle all pending events.
571  events::pump();
572 
573  if(!mouse_button_state_initialized) {
574  /*
575  * The state must be initialize when showing the dialog.
576  * However when initialized before this point there were random
577  * errors. This only happened when the 'click' was done fast; a
578  * slower click worked properly.
579  *
580  * So it seems the events need to be processed before SDL can
581  * return the proper button state. When initializing here all
582  * works fine.
583  */
584  mouse_button_state_ = sdl::get_mouse_button_mask();
585  mouse_button_state_initialized = true;
586  }
587 
588  // See if we should close.
589  if(status_ == status::REQUEST_CLOSE) {
590  status_ = exit_hook_(*this) ? status::CLOSED : status::SHOWING;
591  }
592 
593  // Update the display. This will rate limit to vsync.
594  events::draw();
595  }
596  }
597  catch(...)
598  {
599  // TODO: is this even necessary? What are we catching?
600  DBG_DP << "Caught general exception in show(): " << utils::get_unknown_exception_type();
601  hide();
602  throw;
603  }
604 
605  if(text_box_base* tb = dynamic_cast<text_box_base*>(event_distributor_->keyboard_focus())) {
606  tb->interrupt_composition();
607  }
608 
609  // The window may be kept around to be re-shown later. Hide it for now.
610  hide();
611 
612  return retval_;
613 }
614 
616 {
617  if(hidden_) {
618  return;
619  }
620 
621  // Draw background.
622  this->draw_background();
623 
624  // Draw children.
625  this->draw_children();
626 
627  // Draw foreground.
628  this->draw_foreground();
629 
630  return;
631 }
632 
633 void window::hide()
634 {
635  // Queue a redraw of the region if we were shown.
636  if(!hidden_) {
637  queue_redraw();
638  }
639 
640  // Disconnect from the event handler so we stop receiving events.
641  if(is_connected()) {
642  LOG_DP << "disconnecting " << id() << " on hide";
643  disconnect();
644  }
645 
646  hidden_ = true;
647 }
648 
649 bool window::expose(const rect& region)
650 {
651  DBG_DP << "window::expose " << region;
652  rect i = get_rectangle().intersect(region);
653  i.clip(draw::get_clip());
654  if (i.empty()) {
655  return false;
656  }
657  draw();
658  return true;
659 }
660 
661 rect window::screen_location()
662 {
663  if(hidden_) {
664  return {0,0,0,0};
665  }
666  return get_rectangle();
667 }
668 
669 window::invalidate_layout_blocker::invalidate_layout_blocker(window& window)
670  : window_(window)
671 {
674 }
675 
677 {
678  assert(window_.invalidate_layout_blocked_);
679  window_.invalidate_layout_blocked_ = false;
680 }
681 
683 {
685  need_layout_ = true;
686  }
687 }
688 widget* window::find_at(const point& coordinate, const bool must_be_active)
689 {
690  return panel::find_at(coordinate, must_be_active);
691 }
692 
694  const bool must_be_active) const
695 {
696  return panel::find_at(coordinate, must_be_active);
697 }
698 
699 widget* window::find(const std::string& id, const bool must_be_active)
700 {
701  return container_base::find(id, must_be_active);
702 }
703 
704 const widget* window::find(const std::string& id, const bool must_be_active)
705  const
706 {
707  return container_base::find(id, must_be_active);
708 }
709 
710 void window::init_linked_size_group(const std::string& id,
711  const bool fixed_width,
712  const bool fixed_height)
713 {
714  assert(fixed_width || fixed_height);
715  assert(!has_linked_size_group(id));
716 
717  linked_size_[id] = linked_size(fixed_width, fixed_height);
718 }
719 
720 bool window::has_linked_size_group(const std::string& id)
721 {
722  return linked_size_.find(id) != linked_size_.end();
723 }
724 
725 void window::add_linked_widget(const std::string& id, widget* wgt)
726 {
727  assert(wgt);
728  if(!has_linked_size_group(id)) {
729  ERR_GUI << "Unknown linked group '" << id << "'; skipping";
730  return;
731  }
732 
733  std::vector<widget*>& widgets = linked_size_[id].widgets;
734  if(std::find(widgets.begin(), widgets.end(), wgt) == widgets.end()) {
735  widgets.push_back(wgt);
736  }
737 }
738 
739 void window::remove_linked_widget(const std::string& id, const widget* wgt)
740 {
741  assert(wgt);
742  if(!has_linked_size_group(id)) {
743  return;
744  }
745 
746  std::vector<widget*>& widgets = linked_size_[id].widgets;
747 
749  = std::find(widgets.begin(), widgets.end(), wgt);
750 
751  if(itor != widgets.end()) {
752  widgets.erase(itor);
753 
754  assert(std::find(widgets.begin(), widgets.end(), wgt)
755  == widgets.end());
756  }
757 }
758 
760 {
761  if(!need_layout_) {
762  return;
763  }
764  DBG_DP << "window::layout";
765 
766  /***** Initialize. *****/
767 
768  const auto conf = cast_config_to<window_definition>();
769  assert(conf);
770 
772 
774  const point mouse = get_mouse_position();
775 
776  variables_.add("mouse_x", wfl::variant(mouse.x));
777  variables_.add("mouse_y", wfl::variant(mouse.y));
778  variables_.add("window_width", wfl::variant(0));
779  variables_.add("window_height", wfl::variant(0));
780  variables_.add("best_window_width", wfl::variant(size.x));
781  variables_.add("best_window_height", wfl::variant(size.y));
782  variables_.add("size_request_mode", wfl::variant("maximum"));
783 
785 
786  unsigned int maximum_width = maximum_width_(variables_, &functions_);
787  unsigned int maximum_height = maximum_height_(variables_, &functions_);
788 
790  if(maximum_width == 0 || maximum_width > settings::screen_width) {
791  maximum_width = settings::screen_width;
792  }
793 
794  if(maximum_height == 0 || maximum_height > settings::screen_height) {
795  maximum_height = settings::screen_height;
796  }
797  } else {
798  maximum_width = w_(variables_, &functions_);
799  maximum_height = h_(variables_, &functions_);
800  }
801 
802  /***** Handle click dismiss status. *****/
803  button* click_dismiss_button = nullptr;
804  if((click_dismiss_button
805  = find_widget<button>(this, "click_dismiss", false, false))) {
806 
807  click_dismiss_button->set_visible(widget::visibility::invisible);
808  }
809  if(click_dismiss_) {
810  button* btn = find_widget<button>(this, "ok", false, false);
811  if(btn) {
813  click_dismiss_button = btn;
814  }
815  VALIDATE(click_dismiss_button,
816  _("Click dismiss needs a 'click_dismiss' or 'ok' button."));
817  }
818 
819  /***** Layout. *****/
820  layout_initialize(true);
821  generate_dot_file("layout_initialize", LAYOUT);
822 
824 
825  try
826  {
827  window_implementation::layout(*this, maximum_width, maximum_height);
828  }
829  catch(const layout_exception_resize_failed&)
830  {
831 
832  /** @todo implement the scrollbars on the window. */
833 
834  std::stringstream sstr;
835  sstr << __FILE__ << ":" << __LINE__ << " in function '" << __func__
836  << "' found the following problem: Failed to size window;"
837  << " wanted size " << get_best_size() << " available size "
838  << maximum_width << ',' << maximum_height << " screen size "
840 
841  throw wml_exception(_("Failed to show a dialog, "
842  "which doesn't fit on the screen."),
843  sstr.str());
844  }
845 
846  /****** Validate click dismiss status. *****/
848  assert(click_dismiss_button);
849  click_dismiss_button->set_visible(widget::visibility::visible);
850 
852  *click_dismiss_button,
853  std::bind(&window::set_retval, this, retval::OK, true));
854 
855  layout_initialize(true);
856  generate_dot_file("layout_initialize", LAYOUT);
857 
859 
860  try
861  {
863  *this, maximum_width, maximum_height);
864  }
865  catch(const layout_exception_resize_failed&)
866  {
867 
868  /** @todo implement the scrollbars on the window. */
869 
870  std::stringstream sstr;
871  sstr << __FILE__ << ":" << __LINE__ << " in function '" << __func__
872  << "' found the following problem: Failed to size window;"
873  << " wanted size " << get_best_size() << " available size "
874  << maximum_width << ',' << maximum_height << " screen size "
876  << '.';
877 
878  throw wml_exception(_("Failed to show a dialog, "
879  "which doesn't fit on the screen."),
880  sstr.str());
881  }
882  }
883 
884  /***** Get the best location for the window *****/
885  size = get_best_size();
886  /* Although 0-size windows might not seem valid/useful, there are
887  a handful of windows that request 0 size just to get a position
888  chosen via the code below, so at least for now allow them:
889  */
890  assert(size.x >= 0 && static_cast<unsigned>(size.x) <= maximum_width
891  && size.y >= 0 && static_cast<unsigned>(size.y) <= maximum_height);
892 
893  point origin(0, 0);
894 
896 
897  switch(horizontal_placement_) {
899  // Do nothing
900  break;
902  origin.x = (settings::screen_width - size.x) / 2;
903  break;
905  origin.x = settings::screen_width - size.x;
906  break;
907  default:
908  assert(false);
909  }
910  switch(vertical_placement_) {
912  // Do nothing
913  break;
915  origin.y = (settings::screen_height - size.y) / 2;
916  break;
918  origin.y = settings::screen_height - size.y;
919  break;
920  default:
921  assert(false);
922  }
923  } else {
924 
925  variables_.add("window_width", wfl::variant(size.x));
926  variables_.add("window_height", wfl::variant(size.y));
927 
929  layout_initialize(true);
930 
934 
935  size = get_best_size();
936  variables_.add("window_width", wfl::variant(size.x));
937  variables_.add("window_height", wfl::variant(size.y));
938  }
939 
940  variables_.add("size_request_mode", wfl::variant("size"));
941 
942  size.x = w_(variables_, &functions_);
943  size.y = h_(variables_, &functions_);
944 
945  variables_.add("window_width", wfl::variant(size.x));
946  variables_.add("window_height", wfl::variant(size.y));
947 
948  origin.x = x_(variables_, &functions_);
949  origin.y = y_(variables_, &functions_);
950  }
951 
952  /***** Set the window size *****/
953  place(origin, size);
954 
955  generate_dot_file("layout_finished", LAYOUT);
956  need_layout_ = false;
957 
959 }
960 
962 {
963  // evaluate the group sizes
964  for(auto & linked_size : linked_size_)
965  {
966 
967  point max_size(0, 0);
968 
969  // Determine the maximum size.
970  for(auto widget : linked_size.second.widgets)
971  {
972 
973  const point size = widget->get_best_size();
974 
975  if(size.x > max_size.x) {
976  max_size.x = size.x;
977  }
978  if(size.y > max_size.y) {
979  max_size.y = size.y;
980  }
981  }
982  if(linked_size.second.width != -1) {
983  linked_size.second.width = max_size.x;
984  }
985  if(linked_size.second.height != -1) {
986  linked_size.second.height = max_size.y;
987  }
988 
989  // Set the maximum size.
990  for(auto widget : linked_size.second.widgets)
991  {
992 
994 
995  if(linked_size.second.width != -1) {
996  size.x = max_size.x;
997  }
998  if(linked_size.second.height != -1) {
999  size.y = max_size.y;
1000  }
1001 
1003  }
1004  }
1005 }
1006 
1007 bool window::click_dismiss(const int mouse_button_mask)
1008 {
1009  if(does_click_dismiss()) {
1010  if((mouse_button_state_ & mouse_button_mask) == 0) {
1012  } else {
1013  mouse_button_state_ &= ~mouse_button_mask;
1014  }
1015  return true;
1016  }
1017  return false;
1018 }
1019 
1020 void window::finalize(const builder_grid& content_grid)
1021 {
1022  auto widget = content_grid.build();
1023  assert(widget);
1024 
1025  static const std::string id = "_window_content_grid";
1026 
1027  // Make sure the new child has same id.
1028  widget->set_id(id);
1029 
1030  auto* parent_grid = find_widget<grid>(&get_grid(), id, true, false);
1031  assert(parent_grid);
1032 
1033  if(grid* grandparent_grid = dynamic_cast<grid*>(parent_grid->parent())) {
1034  grandparent_grid->swap_child(id, std::move(widget), false);
1035  } else if(container_base* c = dynamic_cast<container_base*>(parent_grid->parent())) {
1036  c->get_grid().swap_child(id, std::move(widget), true);
1037  } else {
1038  assert(false);
1039  }
1040 }
1041 
1042 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
1043 
1044 void window::generate_dot_file(const std::string& generator,
1045  const unsigned domain)
1046 {
1047  debug_layout_->generate_dot_file(generator, domain);
1048 }
1049 #endif
1050 
1052  const unsigned maximum_width,
1053  const unsigned maximum_height)
1054 {
1056 
1057  /*
1058  * For now we return the status, need to test later whether this can
1059  * entirely be converted to an exception based system as in 'promised' on
1060  * the algorithm page.
1061  */
1062 
1063  try
1064  {
1066 
1067  DBG_GUI_L << LOG_IMPL_HEADER << " best size : " << size
1068  << " maximum size : " << maximum_width << ','
1069  << maximum_height << ".";
1070  if(size.x <= static_cast<int>(maximum_width)
1071  && size.y <= static_cast<int>(maximum_height)) {
1072 
1073  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Fits, nothing to do.";
1074  return;
1075  }
1076 
1077  if(size.x > static_cast<int>(maximum_width)) {
1078  window.reduce_width(maximum_width);
1079 
1081  if(size.x > static_cast<int>(maximum_width)) {
1082  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resize width failed."
1083  << " Wanted width " << maximum_width
1084  << " resulting width " << size.x << ".";
1086  }
1088  << " Status: Resize width succeeded.";
1089  }
1090 
1091  if(size.y > static_cast<int>(maximum_height)) {
1092  window.reduce_height(maximum_height);
1093 
1095  if(size.y > static_cast<int>(maximum_height)) {
1096  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resize height failed."
1097  << " Wanted height " << maximum_height
1098  << " resulting height " << size.y << ".";
1100  }
1102  << " Status: Resize height succeeded.";
1103  }
1104 
1105  assert(size.x <= static_cast<int>(maximum_width)
1106  && size.y <= static_cast<int>(maximum_height));
1107 
1108 
1109  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resizing succeeded.";
1110  return;
1111  }
1112  catch(const layout_exception_width_modified&)
1113  {
1115  << " Status: Width has been modified, rerun.";
1116 
1117  window.layout_initialize(false);
1119  layout(window, maximum_width, maximum_height);
1120  return;
1121  }
1122 }
1123 
1124 void window::mouse_capture(const bool capture)
1125 {
1126  assert(event_distributor_);
1127  event_distributor_->capture_mouse(capture);
1128 }
1129 
1131 {
1132  assert(event_distributor_);
1133  event_distributor_->keyboard_capture(widget);
1134 }
1135 
1137 {
1138  assert(event_distributor_);
1139  event_distributor_->keyboard_add_to_chain(widget);
1140 }
1141 
1143 {
1144  assert(event_distributor_);
1145  event_distributor_->keyboard_remove_from_chain(widget);
1146 }
1147 
1149 {
1150  if(std::find(tab_order.begin(), tab_order.end(), widget) != tab_order.end()) {
1151  return;
1152  }
1153  assert(event_distributor_);
1154  if(tab_order.empty() && !event_distributor_->keyboard_focus()) {
1156  }
1157  if(at < 0 || at >= static_cast<int>(tab_order.size())) {
1158  tab_order.push_back(widget);
1159  } else {
1160  tab_order.insert(tab_order.begin() + at, widget);
1161  }
1162 }
1163 
1165  bool& handled,
1166  const point& new_size)
1167 {
1168  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1169 
1172  settings::screen_width = new_size.x;
1173  settings::screen_height = new_size.y;
1175 
1176  handled = true;
1177 }
1178 
1180  bool& handled,
1181  bool& halt,
1182  const int mouse_button_mask)
1183 {
1184  DBG_GUI_E << LOG_HEADER << ' ' << event << " mouse_button_mask "
1185  << static_cast<unsigned>(mouse_button_mask) << ".";
1186 
1187  handled = halt = click_dismiss(mouse_button_mask);
1188 }
1189 
1190 static bool is_active(const widget* wgt)
1191 {
1192  if(const styled_widget* control = dynamic_cast<const styled_widget*>(wgt)) {
1193  return control->get_active() && control->get_visible() == widget::visibility::visible;
1194  }
1195  return false;
1196 }
1197 
1199  bool& handled,
1200  const SDL_Keycode key,
1201  const SDL_Keymod mod,
1202  bool handle_tab)
1203 {
1204  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1205 
1206  if(text_box_base* tb = dynamic_cast<text_box_base*>(event_distributor_->keyboard_focus())) {
1207  if(tb->is_composing()) {
1208  if(handle_tab && !tab_order.empty() && key == SDLK_TAB) {
1209  tb->interrupt_composition();
1210  } else {
1211  return;
1212  }
1213  }
1214  }
1215  if(!enter_disabled_ && (key == SDLK_KP_ENTER || key == SDLK_RETURN)) {
1217  handled = true;
1218  } else if(key == SDLK_ESCAPE && !escape_disabled_) {
1220  handled = true;
1221  } else if(key == SDLK_SPACE) {
1222  handled = click_dismiss(0);
1223  } else if(handle_tab && !tab_order.empty() && key == SDLK_TAB) {
1224  assert(event_distributor_);
1225  widget* focus = event_distributor_->keyboard_focus();
1226  auto iter = std::find(tab_order.begin(), tab_order.end(), focus);
1227  do {
1228  if(mod & KMOD_SHIFT) {
1229  if(iter == tab_order.begin()) {
1230  iter = tab_order.end();
1231  }
1232  iter--;
1233  } else {
1234  if(iter == tab_order.end()) {
1235  iter = tab_order.begin();
1236  } else {
1237  iter++;
1238  if(iter == tab_order.end()) {
1239  iter = tab_order.begin();
1240  }
1241  }
1242  }
1243  } while(!is_active(*iter));
1244  keyboard_capture(*iter);
1245  handled = true;
1246  }
1247 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
1248  if(key == SDLK_F12) {
1249  debug_layout_->generate_dot_file("manual", debug_layout_graph::MANUAL);
1250  handled = true;
1251  }
1252 #endif
1253 }
1254 
1256  bool& handled,
1257  const event::message& message)
1258 {
1259  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1260 
1261  const event::message_show_tooltip& request
1262  = dynamic_cast<const event::message_show_tooltip&>(message);
1263 
1264  dialogs::tip::show(tooltip_.id, request.message, request.location, request.source_rect);
1265 
1266  handled = true;
1267 }
1268 
1270  bool& handled,
1271  const event::message& message)
1272 {
1273  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1274 
1275  const event::message_show_helptip& request
1276  = dynamic_cast<const event::message_show_helptip&>(message);
1277 
1278  dialogs::tip::show(helptip_.id, request.message, request.location, request.source_rect);
1279 
1280  handled = true;
1281 }
1282 
1284  bool& handled)
1285 {
1286  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1287 
1289 
1290  handled = true;
1291 }
1292 
1294 {
1296 }
1297 
1298 // }---------- DEFINITION ---------{
1299 
1302 {
1303  DBG_GUI_P << "Parsing window " << id;
1304 
1305  load_resolutions<resolution>(cfg);
1306 }
1307 
1309  : panel_definition::resolution(cfg), grid(nullptr)
1310 {
1311  auto child = cfg.optional_child("grid");
1312  // VALIDATE(child, _("No grid defined."));
1313 
1314  /** @todo Evaluate whether the grid should become mandatory. */
1315  if(child) {
1316  grid = std::make_shared<builder_grid>(*child);
1317  }
1318 }
1319 
1320 // }------------ END --------------
1321 
1322 } // namespace gui2
1323 
1324 
1325 /**
1326  * @page layout_algorithm Layout algorithm
1327  *
1328  * @section introduction-layout_algorithm Introduction
1329  *
1330  * This page describes how the layout engine for the dialogs works. First
1331  * a global overview of some terms used in this document.
1332  *
1333  * - @ref gui2::widget "Widget"; Any item which can be used in the widget
1334  * toolkit. Not all widgets are visible. In general widgets cannot be
1335  * sized directly, but this is controlled by a window. A widget has an
1336  * internal size cache and if the value in the cache is not equal to 0,0
1337  * that value is its best size. This value gets set when the widget can
1338  * honor a resize request. It will be set with the value which honors
1339  * the request.
1340  *
1341  * - @ref gui2::grid "Grid"; A grid is an invisible container which holds
1342  * one or more widgets. Several widgets have a grid in them to hold
1343  * multiple widgets eg panels and windows.
1344  *
1345  * - @ref gui2::grid::child "Grid cell"; Every widget which is in a grid is
1346  * put in a grid cell. These cells also hold the information about the gaps
1347  * between widgets the behavior on growing etc. All grid cells must have a
1348  * widget inside them.
1349  *
1350  * - @ref gui2::window "Window"; A window is a top level item which has a
1351  * grid with its children. The window handles the sizing of the window and
1352  * makes sure everything fits.
1353  *
1354  * - @ref gui2::window::linked_size "Shared size group"; A shared size
1355  * group is a number of widgets which share width and or height. These
1356  * widgets are handled separately in the layout algorithm. All grid cells
1357  * width such a widget will get the same height and or width and these
1358  * widgets won't be resized when there's not enough size. To be sure that
1359  * these widgets don't cause trouble for the layout algorithm, they must be
1360  * in a container with scrollbars so there will always be a way to properly
1361  * layout them. The engine must enforce this restriction so the shared
1362  * layout property must be set by the engine after validation.
1363  *
1364  * - All visible grid cells; A grid cell is visible when the widget inside
1365  * of it doesn't have the state visibility::invisible. Widgets which have the
1366  * state visibility::hidden are sized properly since when they become
1367  * visibility::visible the layout shouldn't be invalidated. A grid cell
1368  * that's invisible has size 0,0.
1369  *
1370  * - All resizable grid cells; A grid cell is resizable under the following
1371  * conditions:
1372  * - The widget is visibility::visible.
1373  * - The widget is not in a shared size group.
1374  *
1375  * There are two layout algorithms with a different purpose.
1376  *
1377  * - The Window algorithm; this algorithm's goal is it to make sure all grid
1378  * cells fit in the window. Sizing the grid cells depends on the widget
1379  * size as well, but this algorithm only sizes the grid cells and doesn't
1380  * handle the widgets inside them.
1381  *
1382  * - The Grid algorithm; after the Window algorithm made sure that all grid
1383  * cells fit this algorithm makes sure the widgets are put in the optimal
1384  * state in their grid cell.
1385  *
1386  * @section layout_algorithm_window Window
1387  *
1388  * Here is the algorithm used to layout the window:
1389  *
1390  * - Perform a full initialization
1391  * (@ref gui2::widget::layout_initialize (full_initialization = true)):
1392  * - Clear the internal best size cache for all widgets.
1393  * - For widgets with scrollbars hide them unless the
1394  * @ref gui2::scrollbar_container::scrollbar_mode "scrollbar_mode" is
1395  * ALWAYS_VISIBLE or AUTO_VISIBLE.
1396  * - Handle shared sizes:
1397  * - Height and width:
1398  * - Get the best size for all widgets that share height and width.
1399  * - Set the maximum of width and height as best size for all these
1400  * widgets.
1401  * - Width only:
1402  * - Get the best width for all widgets which share their width.
1403  * - Set the maximum width for all widgets, but keep their own height.
1404  * - Height only:
1405  * - Get the best height for all widgets which share their height.
1406  * - Set the maximum height for all widgets, but keep their own width.
1407  * - Start layout loop:
1408  * - Get best size.
1409  * - If width <= maximum_width && height <= maximum_height we're done.
1410  * - If width > maximum_width, optimize the width:
1411  * - For every grid cell in a grid row there will be a resize request
1412  * (@ref gui2::grid::reduce_width):
1413  * - Sort the widgets in the row on the resize priority.
1414  * - Loop through this priority queue until the row fits
1415  * - If priority != 0 try to share the extra width else all
1416  * widgets are tried to reduce the full size.
1417  * - Try to shrink the widgets by either wrapping or using a
1418  * scrollbar (@ref gui2::widget::request_reduce_width).
1419  * - If the row fits in the wanted width this row is done.
1420  * - Else try the next priority.
1421  * - All priorities done and the width still doesn't fit.
1422  * - Loop through this priority queue until the row fits.
1423  * - If priority != 0:
1424  * - try to share the extra width
1425  * -Else:
1426  * - All widgets are tried to reduce the full size.
1427  * - Try to shrink the widgets by sizing them smaller as really
1428  * wanted (@ref gui2::widget::demand_reduce_width).
1429  * For labels, buttons etc. they get ellipsized.
1430  * - If the row fits in the wanted width this row is done.
1431  * - Else try the next priority.
1432  * - All priorities done and the width still doesn't fit.
1433  * - Throw a layout width doesn't fit exception.
1434  * - If height > maximum_height, optimize the height
1435  * (@ref gui2::grid::reduce_height):
1436  * - For every grid cell in a grid column there will be a resize request:
1437  * - Sort the widgets in the column on the resize priority.
1438  * - Loop through this priority queue until the column fits:
1439  * - If priority != 0 try to share the extra height else all
1440  * widgets are tried to reduce the full size.
1441  * - Try to shrink the widgets by using a scrollbar
1442  * (@ref gui2::widget::request_reduce_height).
1443  * - If succeeded for a widget the width is influenced and the
1444  * width might be invalid.
1445  * - Throw a width modified exception.
1446  * - If the column fits in the wanted height this column is done.
1447  * - Else try the next priority.
1448  * - All priorities done and the height still doesn't fit.
1449  * - Loop through this priority queue until the column fits.
1450  * - If priority != 0 try to share the extra height else all
1451  * widgets are tried to reduce the full size.
1452  * - Try to shrink the widgets by sizing them smaller as really
1453  * wanted (@ref gui2::widget::demand_reduce_width).
1454  * For labels, buttons etc. they get ellipsized .
1455  * - If the column fits in the wanted height this column is done.
1456  * - Else try the next priority.
1457  * - All priorities done and the height still doesn't fit.
1458  * - Throw a layout height doesn't fit exception.
1459  * - End layout loop.
1460  *
1461  * - Catch @ref gui2::layout_exception_width_modified "width modified":
1462  * - Goto relayout.
1463  *
1464  * - Catch
1465  * @ref gui2::layout_exception_width_resize_failed "width resize failed":
1466  * - If the window has a horizontal scrollbar which isn't shown but can be
1467  * shown.
1468  * - Show the scrollbar.
1469  * - goto relayout.
1470  * - Else show a layout failure message.
1471  *
1472  * - Catch
1473  * @ref gui2::layout_exception_height_resize_failed "height resize failed":
1474  * - If the window has a vertical scrollbar which isn't shown but can be
1475  * shown:
1476  * - Show the scrollbar.
1477  * - goto relayout.
1478  * - Else:
1479  * - show a layout failure message.
1480  *
1481  * - Relayout:
1482  * - Initialize all widgets
1483  * (@ref gui2::widget::layout_initialize (full_initialization = false))
1484  * - Handle shared sizes, since the reinitialization resets that state.
1485  * - Goto start layout loop.
1486  *
1487  * @section grid Grid
1488  *
1489  * This section will be documented later.
1490  */
std::size_t w_
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:161
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Euivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:389
Simple push button.
Definition: button.hpp:37
A generic container base class.
bool disable_click_dismiss() const override
See widget::disable_click_dismiss.
void reduce_height(const unsigned maximum_height)
Tries to reduce the height of a container.
const grid & get_grid() const
virtual void layout_initialize(const bool full_initialization) override
See widget::layout_initialize.
void reduce_width(const unsigned maximum_width)
Tries to reduce the width of a container.
widget * find(const std::string &id, const bool must_be_active) override
See widget::find.
virtual widget * find_at(const point &coordinate, const bool must_be_active) override
See widget::find_at.
virtual void place(const point &origin, const point &size) override
See widget::place.
Main class to show messages to the user.
Definition: message.hpp:36
Class to show the tips.
Definition: tooltip.cpp:58
Basic template class to generate new items.
Base container class.
Definition: grid.hpp:32
static const unsigned HORIZONTAL_ALIGN_RIGHT
Definition: grid.hpp:59
static const unsigned VERTICAL_ALIGN_BOTTOM
Definition: grid.hpp:52
static const unsigned VERTICAL_ALIGN_CENTER
Definition: grid.hpp:51
static const unsigned HORIZONTAL_ALIGN_CENTER
Definition: grid.hpp:58
static const unsigned VERTICAL_ALIGN_TOP
Definition: grid.hpp:50
static const unsigned HORIZONTAL_ALIGN_LEFT
Definition: grid.hpp:57
builder_window(const config &cfg)
Definition: window.cpp:104
virtual std::unique_ptr< widget > build() const override
Definition: window.cpp:110
A panel is a visible container to hold multiple widgets.
Definition: panel.hpp:59
Base class for all visible items.
Abstract base class for text items.
Base class for all widgets.
Definition: widget.hpp:54
void set_layout_size(const point &size)
Definition: widget.cpp:336
point get_best_size() const
Gets the best size for the widget.
Definition: widget.cpp:194
void set_visible(const visibility visible)
Definition: widget.cpp:456
void set_id(const std::string &id)
Definition: widget.cpp:99
const std::string & id() const
Definition: widget.cpp:111
window * get_window()
Get the parent window.
Definition: widget.cpp:118
@ visible
The user sets the widget visible, that means:
@ invisible
The user set the widget invisible, that means:
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:67
bool need_layout_
When set the form needs a full layout redraw cycle.
Definition: window.hpp:456
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:358
const unsigned vertical_placement_
Sets the vertical placement.
Definition: window.hpp:484
bool invalidate_layout_blocked_
Is invalidate_layout blocked, see invalidate_layout_blocker.
Definition: window.hpp:462
typed_formula< bool > reevaluate_best_size_
The formula to determine whether the size is good.
Definition: window.hpp:505
typed_formula< unsigned > maximum_width_
The maximum width if automatic_placement_ is true.
Definition: window.hpp:487
void remove_from_keyboard_chain(widget *widget)
Remove the widget from the keyboard chain.
Definition: window.cpp:1142
void keyboard_capture(widget *widget)
Definition: window.cpp:1130
void invalidate_layout()
Updates the size of the window.
Definition: window.cpp:682
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1136
void signal_handler_message_show_tooltip(const event::ui_event event, bool &handled, const event::message &message)
Definition: window.cpp:1255
void mouse_capture(const bool capture=true)
Definition: window.cpp:1124
typed_formula< unsigned > maximum_height_
The maximum height if automatic_placement_ is true.
Definition: window.hpp:490
int mouse_button_state_
The state of the mouse button.
Definition: window.hpp:606
std::unique_ptr< event::distributor > event_distributor_
Definition: window.hpp:647
void signal_handler_sdl_video_resize(const event::ui_event event, bool &handled, const point &new_size)
Definition: window.cpp:1164
status
The status of the window.
Definition: window.hpp:169
builder_window::window_resolution::tooltip_info tooltip_
The settings for the tooltip.
Definition: window.hpp:511
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:1198
virtual void layout() override
Lays out the window.
Definition: window.cpp:759
void signal_handler_message_show_helptip(const event::ui_event event, bool &handled, const event::message &message)
Definition: window.cpp:1269
void layout_linked_widgets()
Layouts the linked widgets.
Definition: window.cpp:961
std::vector< widget * > tab_order
List of widgets in the tabbing order.
Definition: window.hpp:569
typed_formula< unsigned > h_
The formula to calculate the height of the dialog.
Definition: window.hpp:502
void signal_handler_request_placement(const event::ui_event event, bool &handled)
Definition: window.cpp:1283
widget * find(const std::string &id, const bool must_be_active) override
See widget::find.
Definition: window.cpp:699
bool escape_disabled_
Disable the escape key see our setter for more info.
Definition: window.hpp:539
bool does_click_dismiss() const
Does the window close easily?
Definition: window.hpp:273
void signal_handler_close_window()
Definition: window.cpp:1293
bool click_dismiss_
Do we want to have easy close behavior?
Definition: window.hpp:533
void add_linked_widget(const std::string &id, widget *widget)
Adds a widget to a linked size group.
Definition: window.cpp:725
const unsigned horizontal_placement_
Sets the horizontal placement.
Definition: window.hpp:476
void add_to_tab_order(widget *widget, int at=-1)
Add the widget to the tabbing order.
Definition: window.cpp:1148
const bool automatic_placement_
Do we wish to place the widget automatically?
Definition: window.hpp:468
void finalize(const builder_grid &content_grid)
Finishes the initialization of the grid.
Definition: window.cpp:1020
builder_window::window_resolution::tooltip_info helptip_
The settings for the helptip.
Definition: window.hpp:514
bool click_dismiss(const int mouse_button_mask)
Handles a mouse click event for dismissing the dialog.
Definition: window.cpp:1007
typed_formula< unsigned > x_
The formula to calculate the x value of the dialog.
Definition: window.hpp:493
typed_formula< unsigned > y_
The formula to calculate the y value of the dialog.
Definition: window.hpp:496
void generate_dot_file(const std::string &, const unsigned)
Definition: window.hpp:642
wfl::map_formula_callable variables_
The variables of the canvas.
Definition: window.hpp:459
void remove_linked_widget(const std::string &id, const widget *widget)
Removes a widget from a linked size group.
Definition: window.cpp:739
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 'event'.
Definition: window.cpp:1179
bool enter_disabled_
Disable the enter key see our setter for more info.
Definition: window.hpp:536
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:710
typed_formula< unsigned > w_
The formula to calculate the width of the dialog.
Definition: window.hpp:499
wfl::function_symbol_table functions_
The formula definitions available for the calculation formulas.
Definition: window.hpp:508
virtual widget * find_at(const point &coordinate, const bool must_be_active) override
See widget::find_at.
Definition: window.cpp:688
std::map< std::string, linked_size > linked_size_
List of the widgets, whose size are linked together.
Definition: window.hpp:566
bool has_linked_size_group(const std::string &id)
Is the linked size group defined for this window?
Definition: window.cpp:720
map_formula_callable & add(const std::string &key, const variant &value)
Definition: callable.hpp:253
This file contains the definitions for the gui2::event::message class.
Contains the event distributor.
Drawing functions, for drawing things on the screen.
#define CLOSE_WINDOW_EVENT
Definition: events.hpp:28
#define SHOW_HELPTIP_EVENT
Definition: events.hpp:29
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:968
static std::string _(const char *str)
Definition: gettext.hpp:93
Define the common log macros for the gui toolkit.
#define DBG_GUI_L
Definition: log.hpp:55
#define DBG_GUI_P
Definition: log.hpp:66
#define DBG_GUI_E
Definition: log.hpp:35
std::string tooltip
Shown when hovering over an entry in the filter's drop-down list.
Definition: manager.cpp:219
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:215
#define ERR_GUI
Definition: window.cpp:78
#define LOG_IMPL_HEADER
Definition: window.cpp:85
#define LOG_DP
Definition: window.cpp:89
#define LOG_IMPL_SCOPE_HEADER
Definition: window.cpp:83
#define LOG_HEADER
Definition: window.cpp:81
#define LOG_SCOPE_HEADER
Definition: window.cpp:80
std::map< unsigned, window * > windows_
Definition: window.cpp:200
static lg::log_domain log_display("display")
#define DBG_DP
Definition: window.cpp:88
static lg::log_domain log_gui("gui/layout")
This file contains the window object, this object is a top level container which has the event manage...
Contains functions for cleanly handling SDL input.
Defines the exception classes for the layout algorithm.
Standard logging facilities (interface).
#define log_scope2(domain, description)
Definition: log.hpp:241
static bool expose()
::rect get_clip()
Get the current clipping area, in draw coordinates.
Definition: draw.cpp:468
void draw()
Trigger a draw cycle.
Definition: events.cpp:743
void pump_and_draw()
pump() then immediately draw()
Definition: events.hpp:150
void pump()
Process all events currently in the queue.
Definition: events.cpp:478
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:81
void remove()
Removes a tip.
Definition: tooltip.cpp:111
ui_event
The event sent to the dispatcher.
Definition: handler.hpp:115
void connect_signal_mouse_left_click(dispatcher &dispatcher, const signal &signal)
Connects a signal handler for a left mouse button click.
Definition: dispatcher.cpp:179
void init_mouse_location()
Initializes the location of the mouse.
Definition: handler.cpp:883
unsigned screen_width
The screen resolution and pixel pitch should be available for all widgets since their drawing method ...
Definition: settings.cpp:27
unsigned screen_height
Definition: settings.cpp:28
unsigned gamemap_height
Definition: settings.cpp:40
unsigned gamemap_width
The size of the map area, if not available equal to the screen size.
Definition: settings.cpp:39
Generic file dialog.
void get_screen_size_variables(wfl::map_formula_callable &variable)
Gets a formula object with the screen size.
Definition: helper.cpp:96
static bool is_active(const widget *wgt)
Definition: window.cpp:1190
lg::log_domain log_gui_draw("gui/draw")
Definition: log.hpp:28
point get_mouse_position()
Returns the current mouse position.
Definition: helper.cpp:114
retval
Default window/dialog return values.
Definition: retval.hpp:30
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
@ AUTO_CLOSE
The dialog was closed automatically as its timeout had been reached.
Definition: retval.hpp:41
@ CANCEL
Dialog was closed with the CANCEL button.
Definition: retval.hpp:38
@ NONE
Default, unset return value.
Definition: retval.hpp:32
lg::log_domain log_gui_layout("gui/layout")
Definition: log.hpp:54
std::shared_ptr< halo_record > handle
Definition: halo.hpp:31
@ HOTKEY_FULLSCREEN
Contains the implementation details for lexical_cast and shouldn't be used directly.
Definition: pump.hpp:41
static std::string at(const std::string &file, int line)
uint32_t get_mouse_button_mask()
Returns the current mouse button mask.
Definition: input.cpp:49
map_location coordinate
Contains an x and y coordinate used for starting positions in maps.
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
std::string get_unknown_exception_type()
Utility function for finding the type of thing caught with catch(...).
Definition: general.cpp:23
SDL_Window * get_window()
Definition: video.cpp:642
void toggle_fullscreen()
Toggle fullscreen mode.
Definition: video.cpp:783
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
Definition: contexts.hpp:44
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:110
std::string_view data
Definition: picture.cpp:199
Contains the SDL_Rect helper code.
#define REGISTER_WIDGET(id)
Wrapper for REGISTER_WIDGET3.
This file contains the settings handling of the widget library.
virtual std::unique_ptr< widget > build() const override
Inherited from builder_widget.
std::vector< linked_group_definition > linked_groups
The message for MESSAGE_SHOW_HELPTIP.
Definition: message.hpp:77
const SDL_Rect source_rect
The size of the entity requesting to show a helptip.
Definition: message.hpp:90
const point location
The location where to show the helptip.
Definition: message.hpp:87
const std::string message
The message to show on the helptip.
Definition: message.hpp:84
The message for MESSAGE_SHOW_TOOLTIP.
Definition: message.hpp:59
const point location
The location where to show the tooltip.
Definition: message.hpp:69
const std::string message
The message to show on the tooltip.
Definition: message.hpp:66
const SDL_Rect source_rect
The size of the entity requesting to show a tooltip.
Definition: message.hpp:72
The message callbacks hold a reference to a message.
Definition: message.hpp:46
virtual std::unique_ptr< widget > build() const=0
Exception thrown when the height resizing has failed.
Basic exception when the layout doesn't fit.
Exception thrown when the width has been modified during resizing.
Exception thrown when the width resizing has failed.
Helper struct to force widgets the have the same size.
Definition: window.hpp:549
int height
The current height of all widgets in the group, -1 if the height is not linked.
Definition: window.hpp:562
int width
The current width of all widgets in the group, -1 if the width is not linked.
Definition: window.hpp:559
std::vector< widget * > widgets
The widgets linked.
Definition: window.hpp:556
window_definition(const config &cfg)
Definition: window.cpp:1300
static void layout(window &window, const unsigned maximum_width, const unsigned maximum_height)
Layouts the window.
Definition: window.cpp:1051
Holds a 2D point.
Definition: point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:47
Helper class, don't construct this directly.
mock_char c
Helper for header for the window.
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define FAIL(message)
#define VALIDATE(cond, message)
The macro to use for the validation of WML.