The Battle for Wesnoth  1.19.0-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"
37 #include "gui/core/log.hpp"
39 #include "sdl/point.hpp"
41 #include "gui/dialogs/tooltip.hpp"
42 #include "gui/widgets/button.hpp"
46 #include "gui/widgets/grid.hpp"
47 #include "gui/widgets/helper.hpp"
48 #include "gui/widgets/panel.hpp"
49 #include "gui/widgets/settings.hpp"
50 #include "gui/widgets/widget.hpp"
51 #include "gui/widgets/window.hpp"
52 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
53 #include "gui/widgets/debug.hpp"
54 #endif
55 #include "sdl/rect.hpp"
56 #include "sdl/texture.hpp"
57 #include "formula/variant.hpp"
58 #include "video.hpp" // only for toggle_fullscreen
59 #include "wml_exception.hpp"
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_(false)
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  connect_signal<event::SDL_VIDEO_RESIZE>(std::bind(
292  &window::signal_handler_sdl_video_resize, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5));
293 
294  connect_signal<event::SDL_ACTIVATE>(std::bind(
295  &event::distributor::initialize_state, event_distributor_.get()));
296 
297  connect_signal<event::SDL_LEFT_BUTTON_UP>(
298  std::bind(&window::signal_handler_click_dismiss,
299  this,
300  std::placeholders::_2,
301  std::placeholders::_3,
302  std::placeholders::_4,
303  SDL_BUTTON_LMASK),
304  event::dispatcher::front_child);
305  connect_signal<event::SDL_MIDDLE_BUTTON_UP>(
306  std::bind(&window::signal_handler_click_dismiss,
307  this,
308  std::placeholders::_2,
309  std::placeholders::_3,
310  std::placeholders::_4,
311  SDL_BUTTON_MMASK),
312  event::dispatcher::front_child);
313  connect_signal<event::SDL_RIGHT_BUTTON_UP>(
314  std::bind(&window::signal_handler_click_dismiss,
315  this,
316  std::placeholders::_2,
317  std::placeholders::_3,
318  std::placeholders::_4,
319  SDL_BUTTON_RMASK),
320  event::dispatcher::front_child);
321 
322  connect_signal<event::SDL_KEY_DOWN>(
323  std::bind(
324  &window::signal_handler_sdl_key_down, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5, std::placeholders::_6, true),
325  event::dispatcher::back_post_child);
326  connect_signal<event::SDL_KEY_DOWN>(std::bind(
327  &window::signal_handler_sdl_key_down, this, std::placeholders::_2, std::placeholders::_3, std::placeholders::_5, std::placeholders::_6, false));
328 
329  connect_signal<event::MESSAGE_SHOW_TOOLTIP>(
330  std::bind(&window::signal_handler_message_show_tooltip,
331  this,
332  std::placeholders::_2,
333  std::placeholders::_3,
334  std::placeholders::_5),
335  event::dispatcher::back_pre_child);
336 
337  connect_signal<event::MESSAGE_SHOW_HELPTIP>(
338  std::bind(&window::signal_handler_message_show_helptip,
339  this,
340  std::placeholders::_2,
341  std::placeholders::_3,
342  std::placeholders::_5),
343  event::dispatcher::back_pre_child);
344 
345  connect_signal<event::REQUEST_PLACEMENT>(
346  std::bind(
347  &window::signal_handler_request_placement, this, std::placeholders::_2, std::placeholders::_3),
348  event::dispatcher::back_pre_child);
349 
350  connect_signal<event::CLOSE_WINDOW>(std::bind(&window::signal_handler_close_window, this));
351 
352  register_hotkey(hotkey::GLOBAL__HELPTIP, std::bind(gui2::helptip));
353 
354  /** @todo: should eventally become part of global hotkey handling. */
355  register_hotkey(hotkey::HOTKEY_FULLSCREEN,
356  std::bind(&video::toggle_fullscreen));
357 }
358 
359 window::~window()
360 {
361  /*
362  * We need to delete our children here instead of waiting for the grid to
363  * automatically do it. The reason is when the grid deletes its children
364  * they will try to unregister them self from the linked widget list. At
365  * this point the member of window are destroyed and we enter UB. (For
366  * some reason the bug didn't trigger on g++ but it does on MSVC.
367  */
368  for(unsigned row = 0; row < get_grid().get_rows(); ++row) {
369  for(unsigned col = 0; col < get_grid().get_cols(); ++col) {
370  get_grid().remove_child(row, col);
371  }
372  }
373 
374  /*
375  * The tip needs to be closed if the window closes and the window is
376  * not a tip. If we don't do that the tip will unrender in the next
377  * window and cause drawing glitches.
378  * Another issue is that on smallgui and an MP game the tooltip not
379  * unrendered properly can capture the mouse and make playing impossible.
380  */
381  if(show_mode_ == show_mode::modal) {
383  }
384 
385  manager::instance().remove(*this);
386 
387  // If we are currently shown, then queue an undraw.
388  if(!hidden_) {
389  queue_redraw();
390  }
391 
392 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
393 
394  delete debug_layout_;
395 
396 #endif
397 }
398 
399 window* window::window_instance(const unsigned handle)
400 {
401  return manager::instance().get_window(handle);
402 }
403 
404 retval window::get_retval_by_id(const std::string& id)
405 {
406  // Note it might change to a map later depending on the number
407  // of items.
408  if(id == "ok") {
409  return retval::OK;
410  } else if(id == "cancel" || id == "quit") {
411  return retval::CANCEL;
412  } else {
413  return retval::NONE;
414  }
415 }
416 
417 void window::finish_build(const builder_window::window_resolution& definition)
418 {
419  for(const auto& lg : definition.linked_groups) {
420  if(has_linked_size_group(lg.id)) {
421  t_string msg = VGETTEXT("Linked '$id' group has multiple definitions.", {{"id", lg.id}});
422 
423  FAIL(msg);
424  }
425 
426  init_linked_size_group(lg.id, lg.fixed_width, lg.fixed_height);
427  }
428 
429  set_click_dismiss(definition.click_dismiss);
430 
431  const auto conf = cast_config_to<window_definition>();
432  assert(conf);
433 
434  if(conf->grid) {
435  init_grid(*conf->grid);
436  finalize(*definition.grid);
437  } else {
438  init_grid(*definition.grid);
439  }
440 
441  add_to_keyboard_chain(this);
442 }
443 
444 void window::show_tooltip(/*const unsigned auto_close_timeout*/)
445 {
446  // Unhide in any case.
447  hidden_ = false;
448 
449  // Connect to the event handler, if not yet connected.
450  if(!is_connected()) {
451  LOG_DP << "connecting " << id() << " on show_tooltip";
452  connect();
453  }
454 
455  log_scope2(log_gui_draw, "Window: show as tooltip.");
456 
457  generate_dot_file("show", SHOW);
458 
459  assert(status_ == status::NEW);
460 
461  set_mouse_behavior(event::dispatcher::mouse_behavior::none);
462  set_want_keyboard_input(false);
463 
464  show_mode_ = show_mode::tooltip;
465 
466  /*
467  * Before show has been called, some functions might have done some testing
468  * on the window and called layout, which can give glitches. So
469  * reinvalidate the window to avoid those glitches.
470  */
471  invalidate_layout();
472  queue_redraw();
473  DBG_DP << "show tooltip queued to " << get_rectangle();
474 }
475 
476 void window::show_non_modal(/*const unsigned auto_close_timeout*/)
477 {
478  // Unhide in any case.
479  hidden_ = false;
480 
481  // Connect to the event handler, if not yet connected.
482  if(!is_connected()) {
483  LOG_DP << "connecting " << id() << " on show_non_modal";
484  connect();
485  }
486 
487  log_scope2(log_gui_draw, "Window: show non modal.");
488 
489  generate_dot_file("show", SHOW);
490 
491  assert(status_ == status::NEW);
492 
493  set_mouse_behavior(event::dispatcher::mouse_behavior::hit);
494 
495  show_mode_ = show_mode::modeless;
496 
497  /*
498  * Before show has been called, some functions might have done some testing
499  * on the window and called layout, which can give glitches. So
500  * reinvalidate the window to avoid those glitches.
501  */
502  invalidate_layout();
503  queue_redraw();
504 
505  DBG_DP << "show non-modal queued to " << get_rectangle();
506 
508 }
509 
510 int window::show(const unsigned auto_close_timeout)
511 {
512  // Unhide in any case.
513  hidden_ = false;
514 
515  // Connect to the event handler, if not yet connected.
516  if(!is_connected()) {
517  LOG_DP << "connecting " << id() << " on show";
518  connect();
519  }
520 
521  /*
522  * Removes the old tip if one shown. The show_tip doesn't remove
523  * the tip, since it's the tip.
524  */
526 
527  show_mode_ = show_mode::modal;
528 
530 
531  generate_dot_file("show", SHOW);
532 
533  //assert(status_ == status::NEW);
534 
535  /*
536  * Before show has been called, some functions might have done some testing
537  * on the window and called layout, which can give glitches. So
538  * reinvalidate the window to avoid those glitches.
539  */
540  invalidate_layout();
541  queue_redraw();
542 
543  // Make sure we display at least once in all cases.
544  events::draw();
545 
546  if(auto_close_timeout) {
547  SDL_Event event;
548  sdl::UserEvent data(CLOSE_WINDOW_EVENT, manager::instance().get_id(*this));
549 
550  event.type = CLOSE_WINDOW_EVENT;
551  event.user = data;
552 
553  delay_event(event, auto_close_timeout);
554  }
555 
556  try
557  {
558  // Start our loop drawing will happen here as well.
559  bool mouse_button_state_initialized = false;
560  for(status_ = status::SHOWING; status_ != status::CLOSED;) {
561  // Process and handle all pending events.
562  events::pump();
563 
564  if(!mouse_button_state_initialized) {
565  /*
566  * The state must be initialize when showing the dialog.
567  * However when initialized before this point there were random
568  * errors. This only happened when the 'click' was done fast; a
569  * slower click worked properly.
570  *
571  * So it seems the events need to be processed before SDL can
572  * return the proper button state. When initializing here all
573  * works fine.
574  */
575  mouse_button_state_ = sdl::get_mouse_button_mask();
576  mouse_button_state_initialized = true;
577  }
578 
579  // See if we should close.
580  if(status_ == status::REQUEST_CLOSE) {
581  status_ = exit_hook_(*this) ? status::CLOSED : status::SHOWING;
582  }
583 
584  // Update the display. This will rate limit to vsync.
585  events::draw();
586  }
587  }
588  catch(...)
589  {
590  // TODO: is this even necessary? What are we catching?
591  DBG_DP << "Caught general exception in show(): " << utils::get_unknown_exception_type();
592  hide();
593  throw;
594  }
595 
596  if(text_box_base* tb = dynamic_cast<text_box_base*>(event_distributor_->keyboard_focus())) {
597  tb->interrupt_composition();
598  }
599 
600  // The window may be kept around to be re-shown later. Hide it for now.
601  hide();
602 
603  return retval_;
604 }
605 
607 {
608  if(hidden_) {
609  return;
610  }
611 
612  // Draw background.
613  if(!this->draw_background()) {
614  // We may need to blur the background behind the window,
615  // but at this point it hasn't been rendered yet.
616  // We thus defer rendering to next frame so we can snapshot what
617  // is underneath the window without drawing over it.
618  defer_region(get_rectangle());
619  return;
620  }
621 
622  // Draw children.
623  this->draw_children();
624 
625  // Draw foreground.
626  if(!this->draw_foreground()) {
627  defer_region(get_rectangle());
628  }
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 void window::update_render_textures()
650 {
651  point draw = get_size();
653 
654  // Check that the render buffer size is correct.
655  point buf_raw = render_buffer_.get_raw_size();
656  point buf_draw = render_buffer_.draw_size();
657  bool raw_size_changed = buf_raw.x != render.x || buf_raw.y != render.y;
658  bool draw_size_changed = buf_draw.x != draw.x || buf_draw.y != draw.y;
659  if (!raw_size_changed && !draw_size_changed) {
660  // buffers are fine
661  return;
662  }
663 
664  if(raw_size_changed) {
665  LOG_DP << "regenerating window render buffer as " << render;
666  render_buffer_ = texture(render.x, render.y, SDL_TEXTUREACCESS_TARGET);
667  }
668  if(raw_size_changed || draw_size_changed) {
669  LOG_DP << "updating window render buffer draw size to " << draw;
670  render_buffer_.set_draw_size(draw);
671  }
672 
673  // Clear the entire texture.
674  {
675  auto setter = draw::set_render_target(render_buffer_);
676  draw::clear();
677  }
678 
679  // Rerender everything.
680  queue_rerender();
681 }
682 
683 void window::queue_rerender()
684 {
685  queue_rerender(get_rectangle());
686 }
687 
688 void window::queue_rerender(const rect& screen_region)
689 {
690  // More than one region updating per-frame should be rare.
691  // Just rerender the minimal area that covers everything.
692  rect local_region = screen_region.intersect(get_rectangle());
693  local_region.shift(-get_origin());
694  awaiting_rerender_.expand_to_cover(local_region);
695 }
696 
697 void window::defer_region(const rect& screen_region)
698 {
699  LOG_DP << "deferring region " << screen_region;
700  deferred_regions_.push_back(screen_region);
701 }
702 
704 {
705  // Ensure our render texture is correctly sized.
706  update_render_textures();
707 
708  // Mark regions that were previously deferred for rerender and repaint.
709  for(auto& region : deferred_regions_) {
710  queue_redraw(region);
711  }
712  deferred_regions_.clear();
713 
714  // Render the portion of the window awaiting rerender (if any).
715  if (awaiting_rerender_.empty()) {
716  return;
717  }
718  DBG_DP << "window::render() local " << awaiting_rerender_;
719  auto target_setter = draw::set_render_target(render_buffer_);
720  auto clip_setter = draw::override_clip(awaiting_rerender_);
721  draw();
722  awaiting_rerender_ = sdl::empty_rect;
723 }
724 
725 bool window::expose(const rect& region)
726 {
727  DBG_DP << "window::expose " << region;
728 
729  // Calculate the destination region we need to draw.
730  rect dst = get_rectangle().intersect(region);
731  dst.clip(draw::get_clip());
732  if (dst.empty()) {
733  return false;
734  }
735 
736  // Blit from the pre-rendered buffer.
737  rect src = dst;
738  src.shift(-get_origin());
739  render_buffer_.set_src(src);
740  draw::blit(render_buffer_, dst);
741  render_buffer_.clear_src();
742 
743  return true;
744 }
745 
746 rect window::screen_location()
747 {
748  if(hidden_) {
749  return {0,0,0,0};
750  }
751  return get_rectangle();
752 }
753 
754 window::invalidate_layout_blocker::invalidate_layout_blocker(window& window)
755  : window_(window)
756 {
759 }
760 
762 {
763  assert(window_.invalidate_layout_blocked_);
764  window_.invalidate_layout_blocked_ = false;
765 }
766 
768 {
770  need_layout_ = true;
771  }
772 }
773 widget* window::find_at(const point& coordinate, const bool must_be_active)
774 {
775  return panel::find_at(coordinate, must_be_active);
776 }
777 
779  const bool must_be_active) const
780 {
781  return panel::find_at(coordinate, must_be_active);
782 }
783 
784 widget* window::find(const std::string& id, const bool must_be_active)
785 {
786  return container_base::find(id, must_be_active);
787 }
788 
789 const widget* window::find(const std::string& id, const bool must_be_active)
790  const
791 {
792  return container_base::find(id, must_be_active);
793 }
794 
795 void window::init_linked_size_group(const std::string& id,
796  const bool fixed_width,
797  const bool fixed_height)
798 {
799  assert(fixed_width || fixed_height);
800  assert(!has_linked_size_group(id));
801 
802  linked_size_[id] = linked_size(fixed_width, fixed_height);
803 }
804 
805 bool window::has_linked_size_group(const std::string& id)
806 {
807  return linked_size_.find(id) != linked_size_.end();
808 }
809 
810 void window::add_linked_widget(const std::string& id, widget* wgt)
811 {
812  assert(wgt);
813  if(!has_linked_size_group(id)) {
814  ERR_GUI << "Unknown linked group '" << id << "'; skipping";
815  return;
816  }
817 
818  std::vector<widget*>& widgets = linked_size_[id].widgets;
819  if(std::find(widgets.begin(), widgets.end(), wgt) == widgets.end()) {
820  widgets.push_back(wgt);
821  }
822 }
823 
824 void window::remove_linked_widget(const std::string& id, const widget* wgt)
825 {
826  assert(wgt);
827  if(!has_linked_size_group(id)) {
828  return;
829  }
830 
831  std::vector<widget*>& widgets = linked_size_[id].widgets;
832 
834  = std::find(widgets.begin(), widgets.end(), wgt);
835 
836  if(itor != widgets.end()) {
837  widgets.erase(itor);
838 
839  assert(std::find(widgets.begin(), widgets.end(), wgt)
840  == widgets.end());
841  }
842 }
843 
845 {
846  if(!need_layout_) {
847  return;
848  }
849  DBG_DP << "window::layout";
850 
851  /***** Initialize. *****/
852 
853  const auto conf = cast_config_to<window_definition>();
854  assert(conf);
855 
857 
859  const point mouse = get_mouse_position();
860 
861  variables_.add("mouse_x", wfl::variant(mouse.x));
862  variables_.add("mouse_y", wfl::variant(mouse.y));
863  variables_.add("window_width", wfl::variant(0));
864  variables_.add("window_height", wfl::variant(0));
865  variables_.add("best_window_width", wfl::variant(size.x));
866  variables_.add("best_window_height", wfl::variant(size.y));
867  variables_.add("size_request_mode", wfl::variant("maximum"));
868 
870 
871  unsigned int maximum_width = maximum_width_(variables_, &functions_);
872  unsigned int maximum_height = maximum_height_(variables_, &functions_);
873 
875  if(maximum_width == 0 || maximum_width > settings::screen_width) {
876  maximum_width = settings::screen_width;
877  }
878 
879  if(maximum_height == 0 || maximum_height > settings::screen_height) {
880  maximum_height = settings::screen_height;
881  }
882  } else {
883  maximum_width = w_(variables_, &functions_);
884  maximum_height = h_(variables_, &functions_);
885  }
886 
887  /***** Handle click dismiss status. *****/
888  button* click_dismiss_button = nullptr;
889  if((click_dismiss_button
890  = find_widget<button>(this, "click_dismiss", false, false))) {
891 
892  click_dismiss_button->set_visible(widget::visibility::invisible);
893  }
894  if(click_dismiss_) {
895  button* btn = find_widget<button>(this, "ok", false, false);
896  if(btn) {
898  click_dismiss_button = btn;
899  }
900  VALIDATE(click_dismiss_button,
901  _("Click dismiss needs a 'click_dismiss' or 'ok' button."));
902  }
903 
904  /***** Layout. *****/
905  layout_initialize(true);
906  generate_dot_file("layout_initialize", LAYOUT);
907 
909 
910  try
911  {
912  window_implementation::layout(*this, maximum_width, maximum_height);
913  }
914  catch(const layout_exception_resize_failed&)
915  {
916 
917  /** @todo implement the scrollbars on the window. */
918 
919  std::stringstream sstr;
920  sstr << __FILE__ << ":" << __LINE__ << " in function '" << __func__
921  << "' found the following problem: Failed to size window;"
922  << " wanted size " << get_best_size() << " available size "
923  << maximum_width << ',' << maximum_height << " screen size "
925 
926  throw wml_exception(_("Failed to show a dialog, "
927  "which doesn't fit on the screen."),
928  sstr.str());
929  }
930 
931  /****** Validate click dismiss status. *****/
933  assert(click_dismiss_button);
934  click_dismiss_button->set_visible(widget::visibility::visible);
935 
937  *click_dismiss_button,
938  std::bind(&window::set_retval, this, retval::OK, true));
939 
940  layout_initialize(true);
941  generate_dot_file("layout_initialize", LAYOUT);
942 
944 
945  try
946  {
948  *this, maximum_width, maximum_height);
949  }
950  catch(const layout_exception_resize_failed&)
951  {
952 
953  /** @todo implement the scrollbars on the window. */
954 
955  std::stringstream sstr;
956  sstr << __FILE__ << ":" << __LINE__ << " in function '" << __func__
957  << "' found the following problem: Failed to size window;"
958  << " wanted size " << get_best_size() << " available size "
959  << maximum_width << ',' << maximum_height << " screen size "
961  << '.';
962 
963  throw wml_exception(_("Failed to show a dialog, "
964  "which doesn't fit on the screen."),
965  sstr.str());
966  }
967  }
968 
969  /***** Get the best location for the window *****/
970  size = get_best_size();
971  /* Although 0-size windows might not seem valid/useful, there are
972  a handful of windows that request 0 size just to get a position
973  chosen via the code below, so at least for now allow them:
974  */
975  assert(size.x >= 0 && static_cast<unsigned>(size.x) <= maximum_width
976  && size.y >= 0 && static_cast<unsigned>(size.y) <= maximum_height);
977 
978  point origin(0, 0);
979 
981 
982  switch(horizontal_placement_) {
984  // Do nothing
985  break;
987  origin.x = (settings::screen_width - size.x) / 2;
988  break;
990  origin.x = settings::screen_width - size.x;
991  break;
992  default:
993  assert(false);
994  }
995  switch(vertical_placement_) {
997  // Do nothing
998  break;
1000  origin.y = (settings::screen_height - size.y) / 2;
1001  break;
1003  origin.y = settings::screen_height - size.y;
1004  break;
1005  default:
1006  assert(false);
1007  }
1008  } else {
1009 
1010  variables_.add("window_width", wfl::variant(size.x));
1011  variables_.add("window_height", wfl::variant(size.y));
1012 
1014  layout_initialize(true);
1015 
1018  h_(variables_, &functions_));
1019 
1020  size = get_best_size();
1021  variables_.add("window_width", wfl::variant(size.x));
1022  variables_.add("window_height", wfl::variant(size.y));
1023  }
1024 
1025  variables_.add("size_request_mode", wfl::variant("size"));
1026 
1027  size.x = w_(variables_, &functions_);
1028  size.y = h_(variables_, &functions_);
1029 
1030  variables_.add("window_width", wfl::variant(size.x));
1031  variables_.add("window_height", wfl::variant(size.y));
1032 
1033  origin.x = x_(variables_, &functions_);
1034  origin.y = y_(variables_, &functions_);
1035  }
1036 
1037  /***** Set the window size *****/
1038  place(origin, size);
1039 
1040  generate_dot_file("layout_finished", LAYOUT);
1041  need_layout_ = false;
1042 
1044 }
1045 
1047 {
1048  // evaluate the group sizes
1049  for(auto & linked_size : linked_size_)
1050  {
1051 
1052  point max_size(0, 0);
1053 
1054  // Determine the maximum size.
1055  for(auto widget : linked_size.second.widgets)
1056  {
1057 
1058  const point size = widget->get_best_size();
1059 
1060  if(size.x > max_size.x) {
1061  max_size.x = size.x;
1062  }
1063  if(size.y > max_size.y) {
1064  max_size.y = size.y;
1065  }
1066  }
1067  if(linked_size.second.width != -1) {
1068  linked_size.second.width = max_size.x;
1069  }
1070  if(linked_size.second.height != -1) {
1071  linked_size.second.height = max_size.y;
1072  }
1073 
1074  // Set the maximum size.
1075  for(auto widget : linked_size.second.widgets)
1076  {
1077 
1079 
1080  if(linked_size.second.width != -1) {
1081  size.x = max_size.x;
1082  }
1083  if(linked_size.second.height != -1) {
1084  size.y = max_size.y;
1085  }
1086 
1088  }
1089  }
1090 }
1091 
1092 bool window::click_dismiss(const int mouse_button_mask)
1093 {
1094  if(does_click_dismiss()) {
1095  if((mouse_button_state_ & mouse_button_mask) == 0) {
1097  } else {
1098  mouse_button_state_ &= ~mouse_button_mask;
1099  }
1100  return true;
1101  }
1102  return false;
1103 }
1104 
1105 void window::finalize(const builder_grid& content_grid)
1106 {
1107  auto widget = content_grid.build();
1108  assert(widget);
1109 
1110  static const std::string id = "_window_content_grid";
1111 
1112  // Make sure the new child has same id.
1113  widget->set_id(id);
1114 
1115  auto* parent_grid = find_widget<grid>(&get_grid(), id, true, false);
1116  assert(parent_grid);
1117 
1118  if(grid* grandparent_grid = dynamic_cast<grid*>(parent_grid->parent())) {
1119  grandparent_grid->swap_child(id, std::move(widget), false);
1120  } else if(container_base* c = dynamic_cast<container_base*>(parent_grid->parent())) {
1121  c->get_grid().swap_child(id, std::move(widget), true);
1122  } else {
1123  assert(false);
1124  }
1125 }
1126 
1127 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
1128 
1129 void window::generate_dot_file(const std::string& generator,
1130  const unsigned domain)
1131 {
1132  debug_layout_->generate_dot_file(generator, domain);
1133 }
1134 #endif
1135 
1137  const unsigned maximum_width,
1138  const unsigned maximum_height)
1139 {
1141 
1142  /*
1143  * For now we return the status, need to test later whether this can
1144  * entirely be converted to an exception based system as in 'promised' on
1145  * the algorithm page.
1146  */
1147 
1148  try
1149  {
1151 
1152  DBG_GUI_L << LOG_IMPL_HEADER << " best size : " << size
1153  << " maximum size : " << maximum_width << ','
1154  << maximum_height << ".";
1155  if(size.x <= static_cast<int>(maximum_width)
1156  && size.y <= static_cast<int>(maximum_height)) {
1157 
1158  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Fits, nothing to do.";
1159  return;
1160  }
1161 
1162  if(size.x > static_cast<int>(maximum_width)) {
1163  window.reduce_width(maximum_width);
1164 
1166  if(size.x > static_cast<int>(maximum_width)) {
1167  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resize width failed."
1168  << " Wanted width " << maximum_width
1169  << " resulting width " << size.x << ".";
1171  }
1173  << " Status: Resize width succeeded.";
1174  }
1175 
1176  if(size.y > static_cast<int>(maximum_height)) {
1177  window.reduce_height(maximum_height);
1178 
1180  if(size.y > static_cast<int>(maximum_height)) {
1181  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resize height failed."
1182  << " Wanted height " << maximum_height
1183  << " resulting height " << size.y << ".";
1185  }
1187  << " Status: Resize height succeeded.";
1188  }
1189 
1190  assert(size.x <= static_cast<int>(maximum_width)
1191  && size.y <= static_cast<int>(maximum_height));
1192 
1193 
1194  DBG_GUI_L << LOG_IMPL_HEADER << " Result: Resizing succeeded.";
1195  return;
1196  }
1197  catch(const layout_exception_width_modified&)
1198  {
1200  << " Status: Width has been modified, rerun.";
1201 
1202  window.layout_initialize(false);
1204  layout(window, maximum_width, maximum_height);
1205  return;
1206  }
1207 }
1208 
1209 void window::mouse_capture(const bool capture)
1210 {
1211  assert(event_distributor_);
1212  event_distributor_->capture_mouse(capture);
1213 }
1214 
1216 {
1217  assert(event_distributor_);
1218  event_distributor_->keyboard_capture(widget);
1219 }
1220 
1222 {
1223  assert(event_distributor_);
1224  event_distributor_->keyboard_add_to_chain(widget);
1225 }
1226 
1228 {
1229  assert(event_distributor_);
1230  event_distributor_->keyboard_remove_from_chain(widget);
1231 }
1232 
1234 {
1235  if(std::find(tab_order.begin(), tab_order.end(), widget) != tab_order.end()) {
1236  return;
1237  }
1238  assert(event_distributor_);
1239  if(tab_order.empty() && !event_distributor_->keyboard_focus()) {
1241  }
1242  if(at < 0 || at >= static_cast<int>(tab_order.size())) {
1243  tab_order.push_back(widget);
1244  } else {
1245  tab_order.insert(tab_order.begin() + at, widget);
1246  }
1247 }
1248 
1250  bool& handled,
1251  const point& new_size)
1252 {
1253  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1254 
1257  settings::screen_width = new_size.x;
1258  settings::screen_height = new_size.y;
1260 
1261  handled = true;
1262 }
1263 
1265  bool& handled,
1266  bool& halt,
1267  const int mouse_button_mask)
1268 {
1269  DBG_GUI_E << LOG_HEADER << ' ' << event << " mouse_button_mask "
1270  << static_cast<unsigned>(mouse_button_mask) << ".";
1271 
1272  handled = halt = click_dismiss(mouse_button_mask);
1273 }
1274 
1275 static bool is_active(const widget* wgt)
1276 {
1277  if(const styled_widget* control = dynamic_cast<const styled_widget*>(wgt)) {
1278  return control->get_active() && control->get_visible() == widget::visibility::visible;
1279  }
1280  return false;
1281 }
1282 
1284  bool& handled,
1285  const SDL_Keycode key,
1286  const SDL_Keymod mod,
1287  bool handle_tab)
1288 {
1289  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1290 
1291  if(text_box_base* tb = dynamic_cast<text_box_base*>(event_distributor_->keyboard_focus())) {
1292  if(tb->is_composing()) {
1293  if(handle_tab && !tab_order.empty() && key == SDLK_TAB) {
1294  tb->interrupt_composition();
1295  } else {
1296  return;
1297  }
1298  }
1299  }
1300  if(key == SDLK_KP_ENTER || key == SDLK_RETURN) {
1301  if (mod & (KMOD_CTRL | KMOD_ALT | KMOD_GUI | KMOD_SHIFT)) {
1302  // Don't handle if modifier is pressed
1303  handled = false;
1304  } else {
1305  // Trigger window OK button only if Enter enabled,
1306  // otherwise pass handling to widget
1307  if (!enter_disabled_) {
1309  handled = true;
1310  }
1311  }
1312  } else if(key == SDLK_ESCAPE && !escape_disabled_) {
1314  handled = true;
1315  } else if(key == SDLK_SPACE) {
1316  handled = click_dismiss(0);
1317  } else if(handle_tab && !tab_order.empty() && key == SDLK_TAB) {
1318  assert(event_distributor_);
1319  widget* focus = event_distributor_->keyboard_focus();
1320  auto iter = std::find(tab_order.begin(), tab_order.end(), focus);
1321  do {
1322  if(mod & KMOD_SHIFT) {
1323  if(iter == tab_order.begin()) {
1324  iter = tab_order.end();
1325  }
1326  iter--;
1327  } else {
1328  if(iter == tab_order.end()) {
1329  iter = tab_order.begin();
1330  } else {
1331  iter++;
1332  if(iter == tab_order.end()) {
1333  iter = tab_order.begin();
1334  }
1335  }
1336  }
1337  } while(!is_active(*iter));
1338  keyboard_capture(*iter);
1339  handled = true;
1340  }
1341 #ifdef DEBUG_WINDOW_LAYOUT_GRAPHS
1342  if(key == SDLK_F12) {
1343  debug_layout_->generate_dot_file("manual", debug_layout_graph::MANUAL);
1344  handled = true;
1345  }
1346 #endif
1347 }
1348 
1350  bool& handled,
1351  const event::message& message)
1352 {
1353  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1354 
1355  const event::message_show_tooltip& request
1356  = dynamic_cast<const event::message_show_tooltip&>(message);
1357 
1358  dialogs::tip::show(tooltip_.id, request.message, request.location, request.source_rect);
1359 
1360  handled = true;
1361 }
1362 
1364  bool& handled,
1365  const event::message& message)
1366 {
1367  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1368 
1369  const event::message_show_helptip& request
1370  = dynamic_cast<const event::message_show_helptip&>(message);
1371 
1372  dialogs::tip::show(helptip_.id, request.message, request.location, request.source_rect);
1373 
1374  handled = true;
1375 }
1376 
1378  bool& handled)
1379 {
1380  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
1381 
1383 
1384  handled = true;
1385 }
1386 
1388 {
1390 }
1391 
1392 // }---------- DEFINITION ---------{
1393 
1396 {
1397  DBG_GUI_P << "Parsing window " << id;
1398 
1399  load_resolutions<resolution>(cfg);
1400 }
1401 
1403  : panel_definition::resolution(cfg), grid(nullptr)
1404 {
1405  auto child = cfg.optional_child("grid");
1406  // VALIDATE(child, _("No grid defined."));
1407 
1408  /** @todo Evaluate whether the grid should become mandatory. */
1409  if(child) {
1410  grid = std::make_shared<builder_grid>(*child);
1411  }
1412 }
1413 
1414 // }------------ END --------------
1415 
1416 } // namespace gui2
1417 
1418 
1419 /**
1420  * @page layout_algorithm Layout algorithm
1421  *
1422  * @section introduction-layout_algorithm Introduction
1423  *
1424  * This page describes how the layout engine for the dialogs works. First
1425  * a global overview of some terms used in this document.
1426  *
1427  * - @ref gui2::widget "Widget"; Any item which can be used in the widget
1428  * toolkit. Not all widgets are visible. In general widgets cannot be
1429  * sized directly, but this is controlled by a window. A widget has an
1430  * internal size cache and if the value in the cache is not equal to 0,0
1431  * that value is its best size. This value gets set when the widget can
1432  * honor a resize request. It will be set with the value which honors
1433  * the request.
1434  *
1435  * - @ref gui2::grid "Grid"; A grid is an invisible container which holds
1436  * one or more widgets. Several widgets have a grid in them to hold
1437  * multiple widgets eg panels and windows.
1438  *
1439  * - @ref gui2::grid::child "Grid cell"; Every widget which is in a grid is
1440  * put in a grid cell. These cells also hold the information about the gaps
1441  * between widgets the behavior on growing etc. All grid cells must have a
1442  * widget inside them.
1443  *
1444  * - @ref gui2::window "Window"; A window is a top level item which has a
1445  * grid with its children. The window handles the sizing of the window and
1446  * makes sure everything fits.
1447  *
1448  * - @ref gui2::window::linked_size "Shared size group"; A shared size
1449  * group is a number of widgets which share width and or height. These
1450  * widgets are handled separately in the layout algorithm. All grid cells
1451  * width such a widget will get the same height and or width and these
1452  * widgets won't be resized when there's not enough size. To be sure that
1453  * these widgets don't cause trouble for the layout algorithm, they must be
1454  * in a container with scrollbars so there will always be a way to properly
1455  * layout them. The engine must enforce this restriction so the shared
1456  * layout property must be set by the engine after validation.
1457  *
1458  * - All visible grid cells; A grid cell is visible when the widget inside
1459  * of it doesn't have the state visibility::invisible. Widgets which have the
1460  * state visibility::hidden are sized properly since when they become
1461  * visibility::visible the layout shouldn't be invalidated. A grid cell
1462  * that's invisible has size 0,0.
1463  *
1464  * - All resizable grid cells; A grid cell is resizable under the following
1465  * conditions:
1466  * - The widget is visibility::visible.
1467  * - The widget is not in a shared size group.
1468  *
1469  * There are two layout algorithms with a different purpose.
1470  *
1471  * - The Window algorithm; this algorithm's goal is it to make sure all grid
1472  * cells fit in the window. Sizing the grid cells depends on the widget
1473  * size as well, but this algorithm only sizes the grid cells and doesn't
1474  * handle the widgets inside them.
1475  *
1476  * - The Grid algorithm; after the Window algorithm made sure that all grid
1477  * cells fit this algorithm makes sure the widgets are put in the optimal
1478  * state in their grid cell.
1479  *
1480  * @section layout_algorithm_window Window
1481  *
1482  * Here is the algorithm used to layout the window:
1483  *
1484  * - Perform a full initialization
1485  * (@ref gui2::widget::layout_initialize (full_initialization = true)):
1486  * - Clear the internal best size cache for all widgets.
1487  * - For widgets with scrollbars hide them unless the
1488  * @ref gui2::scrollbar_container::scrollbar_mode "scrollbar_mode" is
1489  * ALWAYS_VISIBLE or AUTO_VISIBLE.
1490  * - Handle shared sizes:
1491  * - Height and width:
1492  * - Get the best size for all widgets that share height and width.
1493  * - Set the maximum of width and height as best size for all these
1494  * widgets.
1495  * - Width only:
1496  * - Get the best width for all widgets which share their width.
1497  * - Set the maximum width for all widgets, but keep their own height.
1498  * - Height only:
1499  * - Get the best height for all widgets which share their height.
1500  * - Set the maximum height for all widgets, but keep their own width.
1501  * - Start layout loop:
1502  * - Get best size.
1503  * - If width <= maximum_width && height <= maximum_height we're done.
1504  * - If width > maximum_width, optimize the width:
1505  * - For every grid cell in a grid row there will be a resize request
1506  * (@ref gui2::grid::reduce_width):
1507  * - Sort the widgets in the row on the resize priority.
1508  * - Loop through this priority queue until the row fits
1509  * - If priority != 0 try to share the extra width else all
1510  * widgets are tried to reduce the full size.
1511  * - Try to shrink the widgets by either wrapping or using a
1512  * scrollbar (@ref gui2::widget::request_reduce_width).
1513  * - If the row fits in the wanted width this row is done.
1514  * - Else try the next priority.
1515  * - All priorities done and the width still doesn't fit.
1516  * - Loop through this priority queue until the row fits.
1517  * - If priority != 0:
1518  * - try to share the extra width
1519  * -Else:
1520  * - All widgets are tried to reduce the full size.
1521  * - Try to shrink the widgets by sizing them smaller as really
1522  * wanted (@ref gui2::widget::demand_reduce_width).
1523  * For labels, buttons etc. they get ellipsized.
1524  * - If the row fits in the wanted width this row is done.
1525  * - Else try the next priority.
1526  * - All priorities done and the width still doesn't fit.
1527  * - Throw a layout width doesn't fit exception.
1528  * - If height > maximum_height, optimize the height
1529  * (@ref gui2::grid::reduce_height):
1530  * - For every grid cell in a grid column there will be a resize request:
1531  * - Sort the widgets in the column on the resize priority.
1532  * - Loop through this priority queue until the column fits:
1533  * - If priority != 0 try to share the extra height else all
1534  * widgets are tried to reduce the full size.
1535  * - Try to shrink the widgets by using a scrollbar
1536  * (@ref gui2::widget::request_reduce_height).
1537  * - If succeeded for a widget the width is influenced and the
1538  * width might be invalid.
1539  * - Throw a width modified exception.
1540  * - If the column fits in the wanted height this column is done.
1541  * - Else try the next priority.
1542  * - All priorities done and the height still doesn't fit.
1543  * - Loop through this priority queue until the column fits.
1544  * - If priority != 0 try to share the extra height else all
1545  * widgets are tried to reduce the full size.
1546  * - Try to shrink the widgets by sizing them smaller as really
1547  * wanted (@ref gui2::widget::demand_reduce_width).
1548  * For labels, buttons etc. they get ellipsized .
1549  * - If the column fits in the wanted height this column is done.
1550  * - Else try the next priority.
1551  * - All priorities done and the height still doesn't fit.
1552  * - Throw a layout height doesn't fit exception.
1553  * - End layout loop.
1554  *
1555  * - Catch @ref gui2::layout_exception_width_modified "width modified":
1556  * - Goto relayout.
1557  *
1558  * - Catch
1559  * @ref gui2::layout_exception_width_resize_failed "width resize failed":
1560  * - If the window has a horizontal scrollbar which isn't shown but can be
1561  * shown.
1562  * - Show the scrollbar.
1563  * - goto relayout.
1564  * - Else show a layout failure message.
1565  *
1566  * - Catch
1567  * @ref gui2::layout_exception_height_resize_failed "height resize failed":
1568  * - If the window has a vertical scrollbar which isn't shown but can be
1569  * shown:
1570  * - Show the scrollbar.
1571  * - goto relayout.
1572  * - Else:
1573  * - show a layout failure message.
1574  *
1575  * - Relayout:
1576  * - Initialize all widgets
1577  * (@ref gui2::widget::layout_initialize (full_initialization = false))
1578  * - Handle shared sizes, since the reinitialization resets that state.
1579  * - Goto start layout loop.
1580  *
1581  * @section grid Grid
1582  *
1583  * This section will be documented later.
1584  */
std::size_t w_
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:159
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:385
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 &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:56
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
A panel is a visible container to hold multiple widgets.
Definition: panel.hpp:55
Base class for all visible items.
Abstract base class for text items.
Base class for all widgets.
Definition: widget.hpp:53
void set_layout_size(const point &size)
Definition: widget.cpp:337
point get_best_size() const
Gets the best size for the widget.
Definition: widget.cpp:193
void set_visible(const visibility visible)
Definition: widget.cpp:470
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:
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:63
bool need_layout_
When set the form needs a full layout redraw cycle.
Definition: window.hpp:497
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:399
const unsigned vertical_placement_
Sets the vertical placement.
Definition: window.hpp:525
bool invalidate_layout_blocked_
Is invalidate_layout blocked, see invalidate_layout_blocker.
Definition: window.hpp:503
typed_formula< bool > reevaluate_best_size_
The formula to determine whether the size is good.
Definition: window.hpp:546
typed_formula< unsigned > maximum_width_
The maximum width if automatic_placement_ is true.
Definition: window.hpp:528
void remove_from_keyboard_chain(widget *widget)
Remove the widget from the keyboard chain.
Definition: window.cpp:1227
void keyboard_capture(widget *widget)
Definition: window.cpp:1215
void invalidate_layout()
Updates the size of the window.
Definition: window.cpp:767
void add_to_keyboard_chain(widget *widget)
Adds the widget to the keyboard chain.
Definition: window.cpp:1221
void signal_handler_message_show_tooltip(const event::ui_event event, bool &handled, const event::message &message)
Definition: window.cpp:1349
void mouse_capture(const bool capture=true)
Definition: window.cpp:1209
typed_formula< unsigned > maximum_height_
The maximum height if automatic_placement_ is true.
Definition: window.hpp:531
int mouse_button_state_
The state of the mouse button.
Definition: window.hpp:647
std::unique_ptr< event::distributor > event_distributor_
Definition: window.hpp:688
void signal_handler_sdl_video_resize(const event::ui_event event, bool &handled, const point &new_size)
Definition: window.cpp:1249
status
The status of the window.
Definition: window.hpp:210
builder_window::window_resolution::tooltip_info tooltip_
The settings for the tooltip.
Definition: window.hpp:552
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:1283
virtual void layout() override
Lays out the window.
Definition: window.cpp:844
void signal_handler_message_show_helptip(const event::ui_event event, bool &handled, const event::message &message)
Definition: window.cpp:1363
void layout_linked_widgets()
Layouts the linked widgets.
Definition: window.cpp:1046
std::vector< widget * > tab_order
List of widgets in the tabbing order.
Definition: window.hpp:610
typed_formula< unsigned > h_
The formula to calculate the height of the dialog.
Definition: window.hpp:543
void signal_handler_request_placement(const event::ui_event event, bool &handled)
Definition: window.cpp:1377
widget * find(const std::string &id, const bool must_be_active) override
See widget::find.
Definition: window.cpp:784
bool escape_disabled_
Disable the escape key see our setter for more info.
Definition: window.hpp:580
bool does_click_dismiss() const
Does the window close easily?
Definition: window.hpp:314
void signal_handler_close_window()
Definition: window.cpp:1387
bool click_dismiss_
Do we want to have easy close behavior?
Definition: window.hpp:574
void add_linked_widget(const std::string &id, widget *widget)
Adds a widget to a linked size group.
Definition: window.cpp:810
const unsigned horizontal_placement_
Sets the horizontal placement.
Definition: window.hpp:517
void add_to_tab_order(widget *widget, int at=-1)
Add the widget to the tabbing order.
Definition: window.cpp:1233
const bool automatic_placement_
Do we wish to place the widget automatically?
Definition: window.hpp:509
void finalize(const builder_grid &content_grid)
Finishes the initialization of the grid.
Definition: window.cpp:1105
builder_window::window_resolution::tooltip_info helptip_
The settings for the helptip.
Definition: window.hpp:555
bool click_dismiss(const int mouse_button_mask)
Handles a mouse click event for dismissing the dialog.
Definition: window.cpp:1092
typed_formula< unsigned > x_
The formula to calculate the x value of the dialog.
Definition: window.hpp:534
typed_formula< unsigned > y_
The formula to calculate the y value of the dialog.
Definition: window.hpp:537
void generate_dot_file(const std::string &, const unsigned)
Definition: window.hpp:683
wfl::map_formula_callable variables_
The variables of the canvas.
Definition: window.hpp:500
void remove_linked_widget(const std::string &id, const widget *widget)
Removes a widget from a linked size group.
Definition: window.cpp:824
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:1264
bool enter_disabled_
Disable the enter key see our setter for more info.
Definition: window.hpp:577
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:795
typed_formula< unsigned > w_
The formula to calculate the width of the dialog.
Definition: window.hpp:540
wfl::function_symbol_table functions_
The formula definitions available for the calculation formulas.
Definition: window.hpp:549
virtual widget * find_at(const point &coordinate, const bool must_be_active) override
See widget::find_at.
Definition: window.cpp:773
std::map< std::string, linked_size > linked_size_
List of the widgets, whose size are linked together.
Definition: window.hpp:607
bool has_linked_size_group(const std::string &id)
Is the linked size group defined for this window?
Definition: window.cpp:805
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
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:211
std::string id
Text to match against addon_info.tags()
Definition: manager.cpp:207
#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:275
static void render()
static bool expose()
Definition: draw.hpp:40
render_target_setter set_render_target(const texture &t)
Set the given texture as the active render target.
Definition: draw.cpp:616
clip_setter override_clip(const SDL_Rect &clip)
Override the clipping area.
Definition: draw.cpp:452
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:477
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:79
void remove()
Removes a tip.
Definition: tooltip.cpp:109
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:880
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:94
static bool is_active(const widget *wgt)
Definition: window.cpp:1275
lg::log_domain log_gui_draw("gui/draw")
Definition: log.hpp:28
point get_mouse_position()
Returns the current mouse position.
Definition: helper.cpp:112
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:813
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(const std::string &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
SDL_Window * get_window()
Definition: video.cpp:655
int get_pixel_scale()
Get the current active pixel scale multiplier.
Definition: video.cpp:483
void toggle_fullscreen()
Toggle fullscreen mode.
Definition: video.cpp:796
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
std::string_view data
Definition: picture.cpp:194
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:590
int height
The current height of all widgets in the group, -1 if the height is not linked.
Definition: window.hpp:603
int width
The current width of all widgets in the group, -1 if the width is not linked.
Definition: window.hpp:600
std::vector< widget * > widgets
The widgets linked.
Definition: window.hpp:597
window_definition(const config &cfg)
Definition: window.cpp:1394
static void layout(window &window, const unsigned maximum_width, const unsigned maximum_height)
Layouts the window.
Definition: window.cpp:1136
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:47
void clip(const SDL_Rect &r)
Clip this rectangle by the given rectangle.
Definition: rect.cpp:99
void shift(const point &p)
Shift the rectangle by the given relative position.
Definition: rect.cpp:104
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:90
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.