1 /*
2    Copyright (C) 2008 - 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 #define GETTEXT_DOMAIN "wesnoth-lib"
16 
17 #include "gui/widgets/grid_private.hpp"
18 
19 #include "gui/auxiliary/iterator/walker_grid.hpp"
20 #include "gui/core/event/message.hpp"
21 #include "gui/core/log.hpp"
22 #include "gui/core/layout_exception.hpp"
23 #include "gui/widgets/styled_widget.hpp"
24 #include "gui/widgets/window.hpp"
25 
26 #include <numeric>
27 
28 #define LOG_SCOPE_HEADER "grid [" + id() + "] " + __func__
29 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
30 #define LOG_IMPL_HEADER "grid [" + grid.id() + "] " + __func__ + ':'
31 
32 #define LOG_CHILD_SCOPE_HEADER                                                 \
33 	"grid::child [" + (widget_ ? widget_->id() : "-") + "] " + __func__
34 #define LOG_CHILD_HEADER LOG_CHILD_SCOPE_HEADER + ':'
35 
36 namespace gui2
37 {
38 
grid(const unsigned rows,const unsigned cols)39 grid::grid(const unsigned rows, const unsigned cols)
40 	: rows_(rows)
41 	, cols_(cols)
42 	, row_height_()
43 	, col_width_()
44 	, row_grow_factor_(rows)
45 	, col_grow_factor_(cols)
46 	, children_(rows * cols)
47 {
48 	connect_signal<event::REQUEST_PLACEMENT>(
49 		std::bind(&grid::request_placement, this,
50 			std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4),
51 		event::dispatcher::back_pre_child);
52 }
53 
~grid()54 grid::~grid()
55 {
56 }
57 
add_row(const unsigned count)58 unsigned grid::add_row(const unsigned count)
59 {
60 	assert(count);
61 
62 	// FIXME the warning in set_rows_cols should be killed.
63 
64 	unsigned result = rows_;
65 	set_rows_cols(rows_ + count, cols_);
66 	return result;
67 }
68 
set_child(widget * widget,const unsigned row,const unsigned col,const unsigned flags,const unsigned border_size)69 void grid::set_child(widget* widget,
70 					  const unsigned row,
71 					  const unsigned col,
72 					  const unsigned flags,
73 					  const unsigned border_size)
74 {
75 	assert(row < rows_ && col < cols_);
76 	assert(flags & VERTICAL_MASK);
77 	assert(flags & HORIZONTAL_MASK);
78 
79 	child& cell = get_child(row, col);
80 
81 	// clear old child if any
82 	if(cell.get_widget()) {
83 		// free a child when overwriting it
84 		WRN_GUI_G << LOG_HEADER << " child '" << cell.id() << "' at cell '"
85 				  << row << ',' << col << "' will be replaced.\n";
86 	}
87 
88 	// copy data
89 	cell.set_flags(flags);
90 	cell.set_border_size(border_size);
91 	cell.set_widget(widget);
92 	if(cell.get_widget()) {
93 		// make sure the new child is valid before deferring
94 		cell.get_widget()->set_parent(this);
95 	}
96 }
97 
swap_child(const std::string & id,widget * w,const bool recurse,widget * new_parent)98 std::unique_ptr<widget> grid::swap_child(const std::string& id,
99 						   widget* w,
100 						   const bool recurse,
101 						   widget* new_parent)
102 {
103 	assert(w);
104 
105 	for(auto & child : children_)
106 	{
107 		if(child.id() != id) {
108 
109 			if(recurse) {
110 				// decent in the nested grids.
111 				grid* g = dynamic_cast<grid*>(child.get_widget());
112 				if(g) {
113 
114 					std::unique_ptr<widget> old = g->swap_child(id, w, true);
115 					if(old) {
116 						return old;
117 					}
118 				}
119 			}
120 
121 			continue;
122 		}
123 
124 		// Free widget from cell and validate.
125 		std::unique_ptr<widget> old = child.free_widget();
126 		assert(old);
127 
128 		old->set_parent(new_parent);
129 
130 		w->set_parent(this);
131 		w->set_visible(old->get_visible());
132 
133 		child.set_widget(w);
134 		return old;
135 	}
136 
137 	return nullptr;
138 }
139 
remove_child(const unsigned row,const unsigned col)140 void grid::remove_child(const unsigned row, const unsigned col)
141 {
142 	assert(row < rows_ && col < cols_);
143 
144 	child& cell = get_child(row, col);
145 	cell.set_widget(nullptr);
146 }
147 
remove_child(const std::string & id,const bool find_all)148 void grid::remove_child(const std::string& id, const bool find_all)
149 {
150 	for(auto & child : children_)
151 	{
152 		if(child.id() == id) {
153 			child.set_widget(nullptr);
154 
155 			if(!find_all) {
156 				break;
157 			}
158 		}
159 	}
160 }
161 
set_active(const bool active)162 void grid::set_active(const bool active)
163 {
164 	for(auto & child : children_)
165 	{
166 
167 		widget* widget = child.get_widget();
168 		if(!widget) {
169 			continue;
170 		}
171 
172 		grid* g = dynamic_cast<grid*>(widget);
173 		if(g) {
174 			g->set_active(active);
175 			continue;
176 		}
177 
178 		styled_widget* control = dynamic_cast<styled_widget*>(widget);
179 		if(control) {
180 			control->set_active(active);
181 		}
182 	}
183 }
184 
layout_initialize(const bool full_initialization)185 void grid::layout_initialize(const bool full_initialization)
186 {
187 	// Inherited.
188 	widget::layout_initialize(full_initialization);
189 
190 	// Clear child caches.
191 	for(auto & child : children_)
192 	{
193 
194 		child.layout_initialize(full_initialization);
195 	}
196 }
197 
reduce_width(const unsigned maximum_width)198 void grid::reduce_width(const unsigned maximum_width)
199 {
200 	/***** ***** ***** ***** INIT ***** ***** ***** *****/
201 	log_scope2(log_gui_layout, LOG_SCOPE_HEADER);
202 	DBG_GUI_L << LOG_HEADER << " maximum width " << maximum_width << ".\n";
203 
204 	point size = get_best_size();
205 	if(size.x <= static_cast<int>(maximum_width)) {
206 		DBG_GUI_L << LOG_HEADER << " Already fits.\n";
207 		return;
208 	}
209 
210 	/***** ***** ***** ***** Request resize ***** ***** ***** *****/
211 
212 	request_reduce_width(maximum_width);
213 
214 	size = get_best_size();
215 	if(size.x <= static_cast<int>(maximum_width)) {
216 		DBG_GUI_L << LOG_HEADER << " Resize request honored.\n";
217 		return;
218 	}
219 
220 	/***** ***** ***** ***** Demand resize ***** ***** ***** *****/
221 
222 	/** @todo Implement. */
223 
224 	/***** ***** ***** ***** Acknowledge failure ***** ***** ***** *****/
225 
226 	DBG_GUI_L << LOG_HEADER << " Resizing failed.\n";
227 
228 	throw layout_exception_width_resize_failed();
229 }
230 
request_reduce_width(const unsigned maximum_width)231 void grid::request_reduce_width(const unsigned maximum_width)
232 {
233 	point size = get_best_size();
234 	if(size.x <= static_cast<int>(maximum_width)) {
235 		/** @todo this point shouldn't be reached, find out why it does. */
236 		return;
237 	}
238 
239 	const unsigned too_wide = size.x - maximum_width;
240 	unsigned reduced = 0;
241 	for(size_t col = 0; col < cols_; ++col) {
242 		if(too_wide - reduced >= col_width_[col]) {
243 			DBG_GUI_L << LOG_HEADER << " column " << col
244 					  << " is too small to be reduced.\n";
245 			continue;
246 		}
247 
248 		const unsigned wanted_width = col_width_[col] - (too_wide - reduced);
249 		const unsigned width
250 				= grid_implementation::column_request_reduce_width(
251 						*this, col, wanted_width);
252 
253 		if(width < col_width_[col]) {
254 			unsigned reduction = col_width_[col] - width;
255 
256 			DBG_GUI_L << LOG_HEADER << " reduced " << reduction
257 					  << " pixels for column " << col << ".\n";
258 
259 			size.x -= reduction;
260 			reduced += reduction;
261 		}
262 
263 		if(size.x <= static_cast<int>(maximum_width)) {
264 			break;
265 		}
266 	}
267 
268 	set_layout_size(calculate_best_size());
269 }
270 
demand_reduce_width(const unsigned)271 void grid::demand_reduce_width(const unsigned /*maximum_width*/)
272 {
273 	/** @todo Implement. */
274 }
275 
reduce_height(const unsigned maximum_height)276 void grid::reduce_height(const unsigned maximum_height)
277 {
278 	/***** ***** ***** ***** INIT ***** ***** ***** *****/
279 	log_scope2(log_gui_layout, LOG_SCOPE_HEADER);
280 	DBG_GUI_L << LOG_HEADER << " maximum height " << maximum_height << ".\n";
281 
282 	point size = get_best_size();
283 	if(size.y <= static_cast<int>(maximum_height)) {
284 		DBG_GUI_L << LOG_HEADER << " Already fits.\n";
285 		return;
286 	}
287 
288 	/***** ***** ***** ***** Request resize ***** ***** ***** *****/
289 
290 	request_reduce_height(maximum_height);
291 
292 	size = get_best_size();
293 	if(size.y <= static_cast<int>(maximum_height)) {
294 		DBG_GUI_L << LOG_HEADER << " Resize request honored.\n";
295 		return;
296 	}
297 
298 	/***** ***** ***** ***** Demand resize ***** ***** ***** *****/
299 
300 	/** @todo Implement. */
301 
302 	/***** ***** ***** ***** Acknowledge failure ***** ***** ***** *****/
303 
304 	DBG_GUI_L << LOG_HEADER << " Resizing failed.\n";
305 
306 	throw layout_exception_height_resize_failed();
307 }
308 
request_reduce_height(const unsigned maximum_height)309 void grid::request_reduce_height(const unsigned maximum_height)
310 {
311 	point size = get_best_size();
312 	if(size.y <= static_cast<int>(maximum_height)) {
313 		/** @todo this point shouldn't be reached, find out why it does. */
314 		return;
315 	}
316 
317 	const unsigned too_high = size.y - maximum_height;
318 	unsigned reduced = 0;
319 	for(size_t row = 0; row < rows_; ++row) {
320 		unsigned wanted_height = row_height_[row] - (too_high - reduced);
321 		/**
322 		 * @todo Improve this code.
323 		 *
324 		 * Now we try every item to be reduced, maybe items need a flag whether
325 		 * or not to try to reduce and also evaluate whether the force
326 		 * reduction is still needed.
327 		 */
328 		if(too_high - reduced >= row_height_[row]) {
329 			DBG_GUI_L << LOG_HEADER << " row " << row << " height "
330 					  << row_height_[row] << " want to reduce " << too_high
331 					  << " is too small to be reduced fully try 1 pixel.\n";
332 
333 			wanted_height = 1;
334 		}
335 
336 		/* Reducing the height of a widget causes the widget to save its new size
337 		in widget::layout_size_. After that, get_best_size() will return that
338 		size and not the originally calculated optimal size.
339 		Thus, it's perfectly correct that grid::calculate_best_size() that we
340 		call later calls get_best_size() for child widgets as if size reduction
341 		had never happened. */
342 		const unsigned height = grid_implementation::row_request_reduce_height(
343 				*this, row, wanted_height);
344 
345 		if(height < row_height_[row]) {
346 			unsigned reduction = row_height_[row] - height;
347 
348 			DBG_GUI_L << LOG_HEADER << " row " << row << " height "
349 					  << row_height_[row] << " want to reduce " << too_high
350 					  << " reduced " << reduction << " pixels.\n";
351 
352 			size.y -= reduction;
353 			reduced += reduction;
354 		}
355 
356 		if(size.y <= static_cast<int>(maximum_height)) {
357 			break;
358 		}
359 	}
360 
361 	size = calculate_best_size();
362 
363 	DBG_GUI_L << LOG_HEADER << " Requested maximum " << maximum_height
364 			  << " resulting height " << size.y << ".\n";
365 
366 	set_layout_size(size);
367 }
368 
demand_reduce_height(const unsigned)369 void grid::demand_reduce_height(const unsigned /*maximum_height*/)
370 {
371 	/** @todo Implement. */
372 }
373 
request_placement(dispatcher &,const event::ui_event,bool & handled,bool &)374 void grid::request_placement(dispatcher&, const event::ui_event, bool& handled, bool&)
375 {
376 	if (get_window()->invalidate_layout_blocked()) {
377 		handled = true;
378 		return;
379 	}
380 
381 	point size = get_size();
382 	point best_size = calculate_best_size();
383 	if(size.x >= best_size.x && size.y >= best_size.y) {
384 		place(get_origin(), size);
385 		handled = true;
386 		return;
387 	}
388 
389 	recalculate_best_size();
390 
391 	if(size.y >= best_size.y) {
392 		// We have enough space in the Y direction, but not in the X direction.
393 		// Try wrapping the content.
394 		request_reduce_width(size.x);
395 		best_size = get_best_size();
396 
397 		if(size.x >= best_size.x && size.y >= best_size.y) {
398 			// Wrapping succeeded, we still fit vertically.
399 			place(get_origin(), size);
400 			handled = true;
401 			return;
402 		} else {
403 			// Wrapping failed, we no longer fit.
404 			// Reset the sizes of child widgets.
405 			layout_initialize(true);
406 		}
407 	}
408 
409 	/*
410 	Not enough space.
411 	Let the event flow higher up.
412 	This is a pre-event handler, so the event flows upwards. */
413 }
414 
recalculate_best_size()415 point grid::recalculate_best_size()
416 {
417 	point best_size = calculate_best_size();
418 	set_layout_size(best_size);
419 	return best_size;
420 }
421 
calculate_best_size() const422 point grid::calculate_best_size() const
423 {
424 	log_scope2(log_gui_layout, LOG_SCOPE_HEADER);
425 
426 	// Reset the cached values.
427 	row_height_.clear();
428 	row_height_.resize(rows_, 0);
429 	col_width_.clear();
430 	col_width_.resize(cols_, 0);
431 
432 	// First get the sizes for all items.
433 	for(unsigned row = 0; row < rows_; ++row) {
434 		for(unsigned col = 0; col < cols_; ++col) {
435 
436 			const point size = get_child(row, col).get_best_size();
437 
438 			if(size.x > static_cast<int>(col_width_[col])) {
439 				col_width_[col] = size.x;
440 			}
441 
442 			if(size.y > static_cast<int>(row_height_[row])) {
443 				row_height_[row] = size.y;
444 			}
445 		}
446 	}
447 
448 	for(unsigned row = 0; row < rows_; ++row) {
449 		DBG_GUI_L << LOG_HEADER << " the row_height_ for row " << row
450 				  << " will be " << row_height_[row] << ".\n";
451 	}
452 
453 	for(unsigned col = 0; col < cols_; ++col) {
454 		DBG_GUI_L << LOG_HEADER << " the col_width_ for column " << col
455 				  << " will be " << col_width_[col] << ".\n";
456 	}
457 
458 	const point result(
459 			std::accumulate(col_width_.begin(), col_width_.end(), 0),
460 			std::accumulate(row_height_.begin(), row_height_.end(), 0));
461 
462 	DBG_GUI_L << LOG_HEADER << " returning " << result << ".\n";
463 	return result;
464 }
465 
can_wrap() const466 bool grid::can_wrap() const
467 {
468 	for(const auto & child : children_)
469 	{
470 		if(child.can_wrap()) {
471 			return true;
472 		}
473 	}
474 
475 	// Inherited.
476 	return widget::can_wrap();
477 }
478 
place(const point & origin,const point & size)479 void grid::place(const point& origin, const point& size)
480 {
481 	log_scope2(log_gui_layout, LOG_SCOPE_HEADER);
482 
483 	/***** INIT *****/
484 
485 	widget::place(origin, size);
486 
487 	if(!rows_ || !cols_) {
488 		return;
489 	}
490 
491 	// call the calculate so the size cache gets updated.
492 	const point best_size = calculate_best_size();
493 
494 	assert(row_height_.size() == rows_);
495 	assert(col_width_.size() == cols_);
496 	assert(row_grow_factor_.size() == rows_);
497 	assert(col_grow_factor_.size() == cols_);
498 
499 	DBG_GUI_L << LOG_HEADER << " best size " << best_size << " available size "
500 			  << size << ".\n";
501 
502 	/***** BEST_SIZE *****/
503 
504 	if(best_size == size) {
505 		layout(origin);
506 		return;
507 	}
508 
509 	if(best_size.x > size.x || best_size.y > size.y) {
510 		// The assertion below fails quite often so try to give as much information as possible.
511 		std::stringstream out;
512 		out << " Failed to place a grid, we have " << size << " space but we need " << best_size << " space.";
513 		out << " This happened at a grid with the id '" << id() << "'";
514 		widget* pw = parent();
515 		while(pw != nullptr) {
516 			out << " in a '" << typeid(*pw).name() << "' with the id '" << pw->id() << "'";
517 			pw = pw->parent();
518 		}
519 		ERR_GUI_L << LOG_HEADER << out.str() << ".\n";
520 
521 		return;
522 	}
523 
524 	/***** GROW *****/
525 
526 	// expand it.
527 	if(size.x > best_size.x) {
528 		const unsigned w = size.x - best_size.x;
529 		unsigned w_size = std::accumulate(
530 			col_grow_factor_.begin(), col_grow_factor_.end(), 0);
531 
532 		DBG_GUI_L << LOG_HEADER << " extra width " << w
533 			<< " will be divided amount " << w_size << " units in "
534 			<< cols_ << " columns.\n";
535 
536 		if(w_size == 0) {
537 			// If all sizes are 0 reset them to 1
538 			for(auto & val : col_grow_factor_)
539 			{
540 				val = 1;
541 			}
542 			w_size = cols_;
543 		}
544 		// We might have a bit 'extra' if the division doesn't fix exactly
545 		// but we ignore that part for now.
546 		const unsigned w_normal = w / w_size;
547 		for(unsigned i = 0; i < cols_; ++i) {
548 			col_width_[i] += w_normal * col_grow_factor_[i];
549 			DBG_GUI_L << LOG_HEADER << " column " << i
550 				<< " with grow factor " << col_grow_factor_[i]
551 			<< " set width to " << col_width_[i] << ".\n";
552 		}
553 	}
554 
555 	if(size.y > best_size.y) {
556 		const unsigned h = size.y - best_size.y;
557 		unsigned h_size = std::accumulate(
558 			row_grow_factor_.begin(), row_grow_factor_.end(), 0);
559 		DBG_GUI_L << LOG_HEADER << " extra height " << h
560 			<< " will be divided amount " << h_size << " units in "
561 			<< rows_ << " rows.\n";
562 
563 		if(h_size == 0) {
564 			// If all sizes are 0 reset them to 1
565 			for(auto & val : row_grow_factor_)
566 			{
567 				val = 1;
568 			}
569 			h_size = rows_;
570 		}
571 		// We might have a bit 'extra' if the division doesn't fix exactly
572 		// but we ignore that part for now.
573 		const unsigned h_normal = h / h_size;
574 		for(unsigned i = 0; i < rows_; ++i) {
575 			row_height_[i] += h_normal * row_grow_factor_[i];
576 			DBG_GUI_L << LOG_HEADER << " row " << i << " with grow factor "
577 				<< row_grow_factor_[i] << " set height to "
578 				<< row_height_[i] << ".\n";
579 		}
580 	}
581 
582 	layout(origin);
583 	return;
584 }
585 
set_origin(const point & origin)586 void grid::set_origin(const point& origin)
587 {
588 	const point movement {origin.x - get_x(), origin.y - get_y()};
589 
590 	// Inherited.
591 	widget::set_origin(origin);
592 
593 	for(auto & child : children_)
594 	{
595 
596 		widget* widget = child.get_widget();
597 		assert(widget);
598 
599 		widget->set_origin(point(widget->get_x() + movement.x, widget->get_y() + movement.y));
600 	}
601 }
602 
set_visible_rectangle(const SDL_Rect & rectangle)603 void grid::set_visible_rectangle(const SDL_Rect& rectangle)
604 {
605 	// Inherited.
606 	widget::set_visible_rectangle(rectangle);
607 
608 	for(auto & child : children_)
609 	{
610 
611 		widget* widget = child.get_widget();
612 		assert(widget);
613 
614 		widget->set_visible_rectangle(rectangle);
615 	}
616 }
617 
layout_children()618 void grid::layout_children()
619 {
620 	for(auto & child : children_)
621 	{
622 		assert(child.get_widget());
623 		child.get_widget()->layout_children();
624 	}
625 }
626 
child_populate_dirty_list(window & caller,const std::vector<widget * > & call_stack)627 void grid::child_populate_dirty_list(window& caller,
628 									  const std::vector<widget*>& call_stack)
629 {
630 	assert(!call_stack.empty() && call_stack.back() == this);
631 
632 	for(auto & child : children_)
633 	{
634 
635 		assert(child.get_widget());
636 
637 		std::vector<widget*> child_call_stack = call_stack;
638 		child.get_widget()->populate_dirty_list(caller, child_call_stack);
639 	}
640 }
641 
find_at(const point & coordinate,const bool must_be_active)642 widget* grid::find_at(const point& coordinate, const bool must_be_active)
643 {
644 	return grid_implementation::find_at<widget>(
645 			*this, coordinate, must_be_active);
646 }
647 
find_at(const point & coordinate,const bool must_be_active) const648 const widget* grid::find_at(const point& coordinate,
649 							  const bool must_be_active) const
650 {
651 	return grid_implementation::find_at<const widget>(
652 			*this, coordinate, must_be_active);
653 }
654 
find(const std::string & id,const bool must_be_active)655 widget* grid::find(const std::string& id, const bool must_be_active)
656 {
657 	return grid_implementation::find<widget>(*this, id, must_be_active);
658 }
659 
find(const std::string & id,const bool must_be_active) const660 const widget* grid::find(const std::string& id, const bool must_be_active)
661 		const
662 {
663 	return grid_implementation::find<const widget>(*this, id, must_be_active);
664 }
665 
has_widget(const widget & widget) const666 bool grid::has_widget(const widget& widget) const
667 {
668 	if(widget::has_widget(widget)) {
669 		return true;
670 	}
671 
672 	for(const auto & child : children_)
673 	{
674 		if(child.get_widget()->has_widget(widget)) {
675 			return true;
676 		}
677 	}
678 	return false;
679 }
680 
disable_click_dismiss() const681 bool grid::disable_click_dismiss() const
682 {
683 	if(get_visible() != widget::visibility::visible) {
684 		return false;
685 	}
686 
687 	for(const auto & child : children_)
688 	{
689 		const widget* widget = child.get_widget();
690 		assert(widget);
691 
692 		if(widget->disable_click_dismiss()) {
693 			return true;
694 		}
695 	}
696 	return false;
697 }
698 
create_walker()699 iteration::walker_base* grid::create_walker()
700 {
701 	return new gui2::iteration::grid(*this);
702 }
703 
set_rows(const unsigned rows)704 void grid::set_rows(const unsigned rows)
705 {
706 	if(rows == rows_) {
707 		return;
708 	}
709 
710 	set_rows_cols(rows, cols_);
711 }
712 
set_cols(const unsigned cols)713 void grid::set_cols(const unsigned cols)
714 {
715 	if(cols == cols_) {
716 		return;
717 	}
718 
719 	set_rows_cols(rows_, cols);
720 }
721 
set_rows_cols(const unsigned rows,const unsigned cols)722 void grid::set_rows_cols(const unsigned rows, const unsigned cols)
723 {
724 	if(rows == rows_ && cols == cols_) {
725 		return;
726 	}
727 
728 	if(!children_.empty()) {
729 		WRN_GUI_G << LOG_HEADER << " resizing a non-empty grid "
730 				  << " may give unexpected problems.\n";
731 	}
732 
733 	rows_ = rows;
734 	cols_ = cols;
735 	row_grow_factor_.resize(rows);
736 	col_grow_factor_.resize(cols);
737 	children_.resize(rows_ * cols_);
738 }
739 
get_best_size() const740 point grid::child::get_best_size() const
741 {
742 	log_scope2(log_gui_layout, LOG_CHILD_SCOPE_HEADER)
743 
744 	if(!widget_) {
745 		DBG_GUI_L << LOG_CHILD_HEADER << " has widget " << false
746 				  << " returning " << border_space() << ".\n";
747 		return border_space();
748 	}
749 
750 	if(widget_->get_visible() == widget::visibility::invisible) {
751 		DBG_GUI_L << LOG_CHILD_HEADER << " has widget " << true
752 				  << " widget visible " << false << " returning 0,0"
753 				  << ".\n";
754 		return point();
755 	}
756 
757 	const point best_size = widget_->get_best_size() + border_space();
758 
759 	DBG_GUI_L << LOG_CHILD_HEADER << " has widget " << true
760 			  << " widget visible " << true << " returning " << best_size
761 			  << ".\n";
762 	return best_size;
763 }
764 
place(point origin,point size)765 void grid::child::place(point origin, point size)
766 {
767 	assert(get_widget());
768 	if(get_widget()->get_visible() == widget::visibility::invisible) {
769 		return;
770 	}
771 
772 	if(border_size_) {
773 		if(flags_ & BORDER_TOP) {
774 			origin.y += border_size_;
775 			size.y -= border_size_;
776 		}
777 		if(flags_ & BORDER_BOTTOM) {
778 			size.y -= border_size_;
779 		}
780 
781 		if(flags_ & BORDER_LEFT) {
782 			origin.x += border_size_;
783 			size.x -= border_size_;
784 		}
785 		if(flags_ & BORDER_RIGHT) {
786 			size.x -= border_size_;
787 		}
788 	}
789 
790 	// If size smaller or equal to best size set that size.
791 	// No need to check > min size since this is what we got.
792 	const point best_size = get_widget()->get_best_size();
793 	if(size <= best_size) {
794 		DBG_GUI_L << LOG_CHILD_HEADER
795 				  << " in best size range setting widget to " << origin << " x "
796 				  << size << ".\n";
797 
798 		get_widget()->place(origin, size);
799 		return;
800 	}
801 
802 	const styled_widget* control = dynamic_cast<const styled_widget*>(get_widget());
803 	const point maximum_size = control ? control->get_config_maximum_size()
804 										: point();
805 
806 	if((flags_ & (HORIZONTAL_MASK | VERTICAL_MASK))
807 	   == (HORIZONTAL_GROW_SEND_TO_CLIENT | VERTICAL_GROW_SEND_TO_CLIENT)) {
808 
809 		if(maximum_size == point() || size <= maximum_size) {
810 
811 			DBG_GUI_L << LOG_CHILD_HEADER
812 					  << " in maximum size range setting widget to " << origin
813 					  << " x " << size << ".\n";
814 
815 			get_widget()->place(origin, size);
816 			return;
817 		}
818 	}
819 
820 	point widget_size = point(std::min(size.x, best_size.x), std::min(size.y, best_size.y));
821 	point widget_orig = origin;
822 
823 	const unsigned v_flag = flags_ & VERTICAL_MASK;
824 
825 	if(v_flag == VERTICAL_GROW_SEND_TO_CLIENT) {
826 		if(maximum_size.y) {
827 			widget_size.y = std::min(size.y, maximum_size.y);
828 		} else {
829 			widget_size.y = size.y;
830 		}
831 		DBG_GUI_L << LOG_CHILD_HEADER << " vertical growing from "
832 				  << best_size.y << " to " << widget_size.y << ".\n";
833 
834 	} else if(v_flag == VERTICAL_ALIGN_TOP) {
835 		// Do nothing.
836 
837 		DBG_GUI_L << LOG_CHILD_HEADER << " vertically aligned at the top.\n";
838 
839 	} else if(v_flag == VERTICAL_ALIGN_CENTER) {
840 
841 		widget_orig.y += (size.y - widget_size.y) / 2;
842 		DBG_GUI_L << LOG_CHILD_HEADER << " vertically centered.\n";
843 
844 	} else if(v_flag == VERTICAL_ALIGN_BOTTOM) {
845 
846 		widget_orig.y += (size.y - widget_size.y);
847 		DBG_GUI_L << LOG_CHILD_HEADER << " vertically aligned at the bottom.\n";
848 
849 	} else {
850 		ERR_GUI_L << LOG_CHILD_HEADER << " Invalid vertical alignment '"
851 				  << v_flag << "' specified.\n";
852 		assert(false);
853 	}
854 
855 	const unsigned h_flag = flags_ & HORIZONTAL_MASK;
856 
857 	if(h_flag == HORIZONTAL_GROW_SEND_TO_CLIENT) {
858 		if(maximum_size.x) {
859 			widget_size.x = std::min(size.x, maximum_size.x);
860 		} else {
861 			widget_size.x = size.x;
862 		}
863 		DBG_GUI_L << LOG_CHILD_HEADER << " horizontal growing from "
864 				  << best_size.x << " to " << widget_size.x << ".\n";
865 
866 	} else if(h_flag == HORIZONTAL_ALIGN_LEFT) {
867 		// Do nothing.
868 		DBG_GUI_L << LOG_CHILD_HEADER << " horizontally aligned at the left.\n";
869 
870 	} else if(h_flag == HORIZONTAL_ALIGN_CENTER) {
871 
872 		widget_orig.x += (size.x - widget_size.x) / 2;
873 		DBG_GUI_L << LOG_CHILD_HEADER << " horizontally centered.\n";
874 
875 	} else if(h_flag == HORIZONTAL_ALIGN_RIGHT) {
876 
877 		widget_orig.x += (size.x - widget_size.x);
878 		DBG_GUI_L << LOG_CHILD_HEADER
879 				  << " horizontally aligned at the right.\n";
880 
881 	} else {
882 		ERR_GUI_L << LOG_CHILD_HEADER << " No horizontal alignment '" << h_flag
883 				  << "' specified.\n";
884 		assert(false);
885 	}
886 
887 	DBG_GUI_L << LOG_CHILD_HEADER << " resize widget to " << widget_orig
888 			  << " x " << widget_size << ".\n";
889 
890 	get_widget()->place(widget_orig, widget_size);
891 }
892 
layout_initialize(const bool full_initialization)893 void grid::child::layout_initialize(const bool full_initialization)
894 {
895 	assert(widget_);
896 
897 	if(widget_->get_visible() != widget::visibility::invisible) {
898 		widget_->layout_initialize(full_initialization);
899 	}
900 }
901 
id() const902 const std::string& grid::child::id() const
903 {
904 	assert(widget_);
905 	return widget_->id();
906 }
907 
border_space() const908 point grid::child::border_space() const
909 {
910 	point result(0, 0);
911 
912 	if(border_size_) {
913 
914 		if(flags_ & BORDER_TOP)
915 			result.y += border_size_;
916 		if(flags_ & BORDER_BOTTOM)
917 			result.y += border_size_;
918 
919 		if(flags_ & BORDER_LEFT)
920 			result.x += border_size_;
921 		if(flags_ & BORDER_RIGHT)
922 			result.x += border_size_;
923 	}
924 
925 	return result;
926 }
927 
get_child(widget * w)928 grid::child* grid::get_child(widget* w)
929 {
930 	if(!w) {
931 		return nullptr;
932 	}
933 
934 	for(auto& child : children_) {
935 		if(w == child.get_widget()) {
936 			return &child;
937 		}
938 	}
939 
940 	return nullptr;
941 }
942 
set_child_alignment(widget * widget,unsigned set_flag,unsigned mode_mask)943 void grid::set_child_alignment(widget* widget, unsigned set_flag, unsigned mode_mask)
944 {
945 	grid::child* cell = get_child(widget);
946 	if(!cell) {
947 		return;
948 	}
949 
950 	unsigned flags = cell->get_flags();
951 
952 	if((flags & mode_mask) == HORIZONTAL_GROW_SEND_TO_CLIENT) {
953 		ERR_GUI_G << "Cannot set horizontal alignment (grid cell specifies dynamic growth)" << std::endl;
954 		return;
955 	}
956 
957 	if((flags & mode_mask) == VERTICAL_GROW_SEND_TO_CLIENT) {
958 		ERR_GUI_G << "Cannot set vertical alignment (grid cell specifies dynamic growth)" << std::endl;
959 		return;
960 	}
961 
962 	flags &= ~mode_mask;
963 	flags |= set_flag;
964 
965 	cell->set_flags(flags);
966 
967 	event::message message;
968 	fire(event::REQUEST_PLACEMENT, *this, message);
969 }
970 
layout(const point & origin)971 void grid::layout(const point& origin)
972 {
973 	point orig = origin;
974 	for(unsigned row = 0; row < rows_; ++row) {
975 		for(unsigned col = 0; col < cols_; ++col) {
976 
977 			const point size(col_width_[col], row_height_[row]);
978 			DBG_GUI_L << LOG_HEADER << " set widget at " << row << ',' << col
979 					  << " at origin " << orig << " with size " << size
980 					  << ".\n";
981 
982 			if(get_child(row, col).get_widget()) {
983 				get_child(row, col).place(orig, size);
984 			}
985 
986 			orig.x += col_width_[col];
987 		}
988 		orig.y += row_height_[row];
989 		orig.x = origin.x;
990 	}
991 }
992 
impl_draw_children(surface & frame_buffer,int x_offset,int y_offset)993 void grid::impl_draw_children(surface& frame_buffer, int x_offset, int y_offset)
994 {
995 	/*
996 	 * The call to SDL_PumpEvents seems a bit like black magic.
997 	 * With the call the resizing doesn't seem to lose resize events.
998 	 * But when added the queue still only contains one resize event and the
999 	 * internal SDL queue doesn't seem to overflow (rarely more than 50 pending
1000 	 * events).
1001 	 * Without the call when resizing larger a black area of remains, this is
1002 	 * the area not used for resizing the screen, this call `fixes' that.
1003 	 */
1004 
1005 	assert(get_visible() == widget::visibility::visible);
1006 	set_is_dirty(false);
1007 
1008 	for(auto & child : children_)
1009 	{
1010 
1011 		widget* widget = child.get_widget();
1012 		assert(widget);
1013 
1014 		if(widget->get_visible() != widget::visibility::visible) {
1015 			continue;
1016 		}
1017 
1018 		if(widget->get_drawing_action() == widget::redraw_action::none) {
1019 			continue;
1020 		}
1021 
1022 		widget->draw_background(frame_buffer, x_offset, y_offset);
1023 		widget->draw_children(frame_buffer, x_offset, y_offset);
1024 		widget->draw_foreground(frame_buffer, x_offset, y_offset);
1025 		widget->set_is_dirty(false);
1026 	}
1027 }
1028 
row_request_reduce_height(grid & grid,const unsigned row,const unsigned maximum_height)1029 unsigned grid_implementation::row_request_reduce_height(
1030 		grid& grid, const unsigned row, const unsigned maximum_height)
1031 {
1032 	// The minimum height required.
1033 	unsigned required_height = 0;
1034 
1035 	for(size_t x = 0; x < grid.cols_; ++x) {
1036 		grid::child& cell = grid.get_child(row, x);
1037 		cell_request_reduce_height(cell, maximum_height);
1038 
1039 		const point size(cell.get_best_size());
1040 
1041 		if(required_height == 0 || static_cast<size_t>(size.y)
1042 								   > required_height) {
1043 
1044 			required_height = size.y;
1045 		}
1046 	}
1047 
1048 	DBG_GUI_L << LOG_IMPL_HEADER << " maximum row height " << maximum_height
1049 			  << " returning " << required_height << ".\n";
1050 
1051 	return required_height;
1052 }
1053 
column_request_reduce_width(grid & grid,const unsigned column,const unsigned maximum_width)1054 unsigned grid_implementation::column_request_reduce_width(
1055 		grid& grid, const unsigned column, const unsigned maximum_width)
1056 {
1057 	// The minimum width required.
1058 	unsigned required_width = 0;
1059 
1060 	for(size_t y = 0; y < grid.rows_; ++y) {
1061 		grid::child& cell = grid.get_child(y, column);
1062 		cell_request_reduce_width(cell, maximum_width);
1063 
1064 		const point size(cell.get_best_size());
1065 
1066 		if(required_width == 0 || static_cast<size_t>(size.x)
1067 								  > required_width) {
1068 
1069 			required_width = size.x;
1070 		}
1071 	}
1072 
1073 	DBG_GUI_L << LOG_IMPL_HEADER << " maximum column width " << maximum_width
1074 			  << " returning " << required_width << ".\n";
1075 
1076 	return required_width;
1077 }
1078 
1079 void
cell_request_reduce_height(grid::child & child,const unsigned maximum_height)1080 grid_implementation::cell_request_reduce_height(grid::child& child,
1081 												 const unsigned maximum_height)
1082 {
1083 	assert(child.widget_);
1084 
1085 	if(child.widget_->get_visible() == widget::visibility::invisible) {
1086 		return;
1087 	}
1088 
1089 	child.widget_->request_reduce_height(maximum_height
1090 										 - child.border_space().y);
1091 }
1092 
1093 void
cell_request_reduce_width(grid::child & child,const unsigned maximum_width)1094 grid_implementation::cell_request_reduce_width(grid::child& child,
1095 												const unsigned maximum_width)
1096 {
1097 	assert(child.widget_);
1098 
1099 	if(child.widget_->get_visible() == widget::visibility::invisible) {
1100 		return;
1101 	}
1102 
1103 	child.widget_->request_reduce_width(maximum_width - child.border_space().x);
1104 }
1105 
set_single_child(grid & grid,widget * widget)1106 void set_single_child(grid& grid, widget* widget)
1107 {
1108 	grid.set_rows_cols(1, 1);
1109 	grid.set_child(widget,
1110 				   0,
1111 				   0,
1112 				   grid::HORIZONTAL_GROW_SEND_TO_CLIENT
1113 				   | grid::VERTICAL_GROW_SEND_TO_CLIENT,
1114 				   0);
1115 }
1116 
1117 } // namespace gui2
1118 
1119 
1120 /*WIKI
1121  * @page = GUILayout
1122  *
1123  * {{Autogenerated}}
1124  *
1125  * = Abstract =
1126  *
1127  * In the widget library the placement and sizes of elements is determined by
1128  * a grid. Therefore most widgets have no fixed size.
1129  *
1130  *
1131  * = Theory =
1132  *
1133  * We have two examples for the addon dialog, the first example the lower
1134  * buttons are in one grid, that means if the remove button gets wider
1135  * (due to translations) the connect button (4.1 - 2.2) will be aligned
1136  * to the left of the remove button. In the second example the connect
1137  * button will be partial underneath the remove button.
1138  *
1139  * A grid exists of x rows and y columns for all rows the number of columns
1140  * needs to be the same, there is no column (nor row) span. If spanning is
1141  * required place a nested grid to do so. In the examples every row has 1 column
1142  * but rows 3, 4 (and in the second 5) have a nested grid to add more elements
1143  * per row.
1144  *
1145  * In the grid every cell needs to have a widget, if no widget is wanted place
1146  * the special widget ''spacer''. This is a non-visible item which normally
1147  * shouldn't have a size. It is possible to give a spacer a size as well but
1148  * that is discussed elsewhere.
1149  *
1150  * Every row and column has a ''grow_factor'', since all columns in a grid are
1151  * aligned only the columns in the first row need to define their grow factor.
1152  * The grow factor is used to determine with the extra size available in a
1153  * dialog. The algorithm determines the extra size work like this:
1154  *
1155  * * determine the extra size
1156  * * determine the sum of the grow factors
1157  * * if this sum is 0 set the grow factor for every item to 1 and sum to sum of items.
1158  * * divide the extra size with the sum of grow factors
1159  * * for every item multiply the grow factor with the division value
1160  *
1161  * eg
1162  *  extra size 100
1163  *  grow factors 1, 1, 2, 1
1164  *  sum 5
1165  *  division 100 / 5 = 20
1166  *  extra sizes 20, 20, 40, 20
1167  *
1168  * Since we force the factors to 1 if all zero it's not possible to have non
1169  * growing cells. This can be solved by adding an extra cell with a spacer and a
1170  * grow factor of 1. This is used for the buttons in the examples.
1171  *
1172  * Every cell has a ''border_size'' and ''border'' the ''border_size'' is the
1173  * number of pixels in the cell which aren't available for the widget. This is
1174  * used to make sure the items in different cells aren't put side to side. With
1175  * ''border'' it can be determined which sides get the border. So a border is
1176  * either 0 or ''border_size''.
1177  *
1178  * If the widget doesn't grow when there's more space available the alignment
1179  * determines where in the cell the widget is placed.
1180  *
1181  * == Examples ==
1182  *
1183  *  |---------------------------------------|
1184  *  | 1.1                                   |
1185  *  |---------------------------------------|
1186  *  | 2.1                                   |
1187  *  |---------------------------------------|
1188  *  | |-----------------------------------| |
1189  *  | | 3.1 - 1.1          | 3.1 - 1.2    | |
1190  *  | |-----------------------------------| |
1191  *  |---------------------------------------|
1192  *  | |-----------------------------------| |
1193  *  | | 4.1 - 1.1 | 4.1 - 1.2 | 4.1 - 1.3 | |
1194  *  | |-----------------------------------| |
1195  *  | | 4.1 - 2.1 | 4.1 - 2.2 | 4.1 - 2.3 | |
1196  *  | |-----------------------------------| |
1197  *  |---------------------------------------|
1198  *
1199  *
1200  *  1.1       label : title
1201  *  2.1       label : description
1202  *  3.1 - 1.1 label : server
1203  *  3.1 - 1.2 text box : server to connect to
1204  *  4.1 - 1.1 spacer
1205  *  4.1 - 1.2 spacer
1206  *  4.1 - 1.3 button : remove addon
1207  *  4.1 - 2.1 spacer
1208  *  4.1 - 2.2 button : connect
1209  *  4.1 - 2.3 button : cancel
1210  *
1211  *
1212  *  |---------------------------------------|
1213  *  | 1.1                                   |
1214  *  |---------------------------------------|
1215  *  | 2.1                                   |
1216  *  |---------------------------------------|
1217  *  | |-----------------------------------| |
1218  *  | | 3.1 - 1.1          | 3.1 - 1.2    | |
1219  *  | |-----------------------------------| |
1220  *  |---------------------------------------|
1221  *  | |-----------------------------------| |
1222  *  | | 4.1 - 1.1         | 4.1 - 1.2     | |
1223  *  | |-----------------------------------| |
1224  *  |---------------------------------------|
1225  *  | |-----------------------------------| |
1226  *  | | 5.1 - 1.1 | 5.1 - 1.2 | 5.1 - 2.3 | |
1227  *  | |-----------------------------------| |
1228  *  |---------------------------------------|
1229  *
1230  *
1231  *  1.1       label : title
1232  *  2.1       label : description
1233  *  3.1 - 1.1 label : server
1234  *  3.1 - 1.2 text box : server to connect to
1235  *  4.1 - 1.1 spacer
1236  *  4.1 - 1.2 button : remove addon
1237  *  5.1 - 1.1 spacer
1238  *  5.1 - 1.2 button : connect
1239  *  5.1 - 1.3 button : cancel
1240  *
1241  * = Praxis =
1242  *
1243  * This is the code needed to create the skeleton for the structure the extra
1244  * flags are omitted.
1245  *
1246  *  	[grid]
1247  *  		[row]
1248  *  			[column]
1249  *  				[label]
1250  *  					# 1.1
1251  *  				[/label]
1252  *  			[/column]
1253  *  		[/row]
1254  *  		[row]
1255  *  			[column]
1256  *  				[label]
1257  *  					# 2.1
1258  *  				[/label]
1259  *  			[/column]
1260  *  		[/row]
1261  *  		[row]
1262  *  			[column]
1263  *  				[grid]
1264  *  					[row]
1265  *  						[column]
1266  *  							[label]
1267  *  								# 3.1 - 1.1
1268  *  							[/label]
1269  *  						[/column]
1270  *  						[column]
1271  *  							[text_box]
1272  *  								# 3.1 - 1.2
1273  *  							[/text_box]
1274  *  						[/column]
1275  *  					[/row]
1276  *  				[/grid]
1277  *  			[/column]
1278  *  		[/row]
1279  *  		[row]
1280  *  			[column]
1281  *  				[grid]
1282  *  					[row]
1283  *  						[column]
1284  *  							[spacer]
1285  *  								# 4.1 - 1.1
1286  *  							[/spacer]
1287  *  						[/column]
1288  *  						[column]
1289  *  							[spacer]
1290  *  								# 4.1 - 1.2
1291  *  							[/spacer]
1292  *  						[/column]
1293  *  						[column]
1294  *  							[button]
1295  *  								# 4.1 - 1.3
1296  *  							[/button]
1297  *  						[/column]
1298  *  					[/row]
1299  *  					[row]
1300  *  						[column]
1301  *  							[spacer]
1302  *  								# 4.1 - 2.1
1303  *  							[/spacer]
1304  *  						[/column]
1305  *  						[column]
1306  *  							[button]
1307  *  								# 4.1 - 2.2
1308  *  							[/button]
1309  *  						[/column]
1310  *  						[column]
1311  *  							[button]
1312  *  								# 4.1 - 2.3
1313  *  							[/button]
1314  *  						[/column]
1315  *  					[/row]
1316  *  				[/grid]
1317  *  			[/column]
1318  *  		[/row]
1319  *  	[/grid]
1320  *
1321  *
1322  * [[Category: WML Reference]]
1323  * [[Category: GUI WML Reference]]
1324  */
1325