1 /*
2    Copyright (C) 2008 - 2018 The Battle for Wesnoth Project https://www.wesnoth.org/
3 
4    This program is free software; you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation; either version 2 of the License, or
7    (at your option) any later version.
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY.
10 
11    See the COPYING file for more details.
12 */
13 
14 #pragma once
15 
16 #include "gui/core/event/dispatcher.hpp"
17 #include "gui/core/log.hpp"
18 #include "gui/widgets/styled_widget.hpp"
19 #include "gui/widgets/selectable_item.hpp"
20 #include "gui/widgets/widget.hpp"
21 #include "utils/functional.hpp"
22 
23 #include <map>
24 #include <vector>
25 
26 namespace gui2
27 {
28 
29 template<class T>
30 class group
31 {
32 	using group_map    = std::map<T, selectable_item*>;
33 	using order_vector = std::vector<styled_widget*>;
34 
35 public:
36 	/**
37 	 * Adds a widget/value pair to the group map. A callback is set that toggles each members'
38 	 * state to false when clicked. This happens before individual widget handlers fire, ensuring
39 	 * that the clicked widget will remain the only one selected.
40 	 */
add_member(selectable_item * w,const T & value)41 	void add_member(selectable_item* w, const T& value)
42 	{
43 		bool success;
44 		std::tie(std::ignore, success) = members_.emplace(value, w);
45 
46 		if(!success) {
47 			ERR_GUI_G << "Group member with value " << value << "already exists." << std::endl;
48 			return;
49 		}
50 
51 		dynamic_cast<widget&>(*w).connect_signal<event::LEFT_BUTTON_CLICK>(
52 			std::bind(&group::group_operator, this), event::dispatcher::front_child);
53 
54 		member_order_.push_back(dynamic_cast<styled_widget*>(w));
55 	}
56 
57 	/**
58 	 * Removes a member from the group map.
59 	 */
remove_member(const T & value)60 	void remove_member(const T& value)
61 	{
62 		members_.erase(value);
63 	}
64 
65 	/**
66 	 * Clears the entire group of members.
67 	 */
clear()68 	void clear()
69 	{
70 		members_.clear();
71 	}
72 
73 	/**
74 	 * Group member getters
75 	 */
members()76 	group_map& members()
77 	{
78 		return members_;
79 	}
80 
members() const81 	const group_map& members() const
82 	{
83 		return members_;
84 	}
85 
86 	template<typename W>
member(const T & value)87 	W& member(const T& value)
88 	{
89 		return dynamic_cast<W&>(*members_.at(value));
90 	}
91 
92 	/**
93 	 * Returns the value paired with the currently actively toggled member of the group.
94 	 */
get_active_member_value()95 	T get_active_member_value()
96 	{
97 		for(auto& member : members_) {
98 			if(member.second->get_value_bool()) {
99 				return member.first;
100 			}
101 		}
102 
103 		return T();
104 	}
105 
106 	/**
107 	 * Sets the toggle values for all widgets besides the one associated
108 	 * with the specified value to false.
109 	 */
set_member_states(const T & value)110 	void set_member_states(const T& value)
111 	{
112 		for(auto& member : members_) {
113 			member.second->set_value(member.first == value);
114 		}
115 	}
116 
117 	/**
118 	 * Sets a common callback function for all members.
119 	 */
set_callback_on_value_change(std::function<void (widget &)> func)120 	void set_callback_on_value_change(std::function<void(widget&)> func)
121 	{
122 		// Ensure this callback is only called on the member being activated
123 		const auto callback = [func](widget& widget)->void {
124 			if(dynamic_cast<selectable_item&>(widget).get_value_bool()) {
125 				func(widget);
126 			}
127 		};
128 
129 		for(auto& member : members_) {
130 			event::connect_signal_notify_modified(dynamic_cast<widget&>(*member.second), std::bind(callback, _1));
131 		}
132 	}
133 
134 	/**
135 	 * Wrapper for enabling or disabling member widgets.
136 	 * Each member widget will be enabled or disabled based on the result of the specified
137 	 * predicate, which takes its associated value.
138 	 *
139 	 * If a selected widget is to be disabled, it is deselected and the first active member
140 	 * selected instead. The same happens if no members were previously active at all.
141 	 */
set_members_enabled(std::function<bool (const T &)> predicate)142 	void set_members_enabled(std::function<bool(const T&)> predicate)
143 	{
144 		bool do_reselect = true;
145 
146 		for(auto& member : members_) {
147 			const bool res = predicate(member.first);
148 
149 			selectable_item& w = *member.second;
150 			dynamic_cast<styled_widget&>(w).set_active(res);
151 
152 			// Only select another member if this was selected
153 			if(w.get_value_bool()) {
154 				do_reselect = !res;
155 
156 				if(do_reselect) {
157 					w.set_value_bool(false);
158 				}
159 			}
160 		}
161 
162 		if(!do_reselect) {
163 			return;
164 		}
165 
166 		// Look for the first active member to select
167 		for(auto& member : member_order_) {
168 			if(member->get_active()) {
169 				dynamic_cast<selectable_item&>(*member).set_value_bool(true);
170 				break;
171 			}
172 		}
173 	}
174 
175 private:
176 	/**
177 	 * Container map for group members, organized by member value, associated widget.
178 	 */
179 	group_map members_;
180 
181 	/**
182 	 * Since iterating over std::map is specified by operator< for it's key values, we can't
183 	 * guarantee the order would line up with the logical order - ie, that which the widgets
184 	 * appear in in a specific dialog. Keeping a separate vector here allows iterating over
185 	 * members in the order which they are added to the group.
186 	 */
187 	order_vector member_order_;
188 
189 	/**
190 	 * The default actions to take when clicking on one of the widgets in the group.
191 	 */
group_operator()192 	void group_operator()
193 	{
194 		for(auto& member : members_) {
195 			member.second->set_value(false);
196 		}
197 	}
198 };
199 
200 } // namespace gui2
201