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