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