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