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