The Battle for Wesnoth  1.17.17+dev
multimenu_button.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2023
3  by Mark de Wever <koraq@xs4all.nl>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define GETTEXT_DOMAIN "wesnoth-lib"
17 
19 
20 #include "gui/core/log.hpp"
25 #include "gui/widgets/settings.hpp"
26 #include "gui/widgets/window.hpp"
27 #include "sound.hpp"
28 
29 #include "formula/string_utils.hpp"
30 #include <functional>
31 #include "gettext.hpp"
32 #include "wml_exception.hpp"
33 
34 #define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
35 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
36 
37 namespace gui2
38 {
39 
40 // ------------ WIDGET -----------{
41 
42 REGISTER_WIDGET(multimenu_button)
43 
44 multimenu_button::multimenu_button(const implementation::builder_multimenu_button& builder)
45  : styled_widget(builder, type())
46  , state_(ENABLED)
47  , max_shown_(1)
48  , values_()
49  , toggle_states_()
50  , droplist_(nullptr)
51 {
52  values_.emplace_back("label", this->get_label());
53 
54  connect_signal<event::MOUSE_ENTER>(
55  std::bind(&multimenu_button::signal_handler_mouse_enter, this, std::placeholders::_2, std::placeholders::_3));
56  connect_signal<event::MOUSE_LEAVE>(
57  std::bind(&multimenu_button::signal_handler_mouse_leave, this, std::placeholders::_2, std::placeholders::_3));
58 
59  connect_signal<event::LEFT_BUTTON_DOWN>(
60  std::bind(&multimenu_button::signal_handler_left_button_down, this, std::placeholders::_2, std::placeholders::_3));
61  connect_signal<event::LEFT_BUTTON_UP>(
62  std::bind(&multimenu_button::signal_handler_left_button_up, this, std::placeholders::_2, std::placeholders::_3));
63  connect_signal<event::LEFT_BUTTON_CLICK>(
64  std::bind(&multimenu_button::signal_handler_left_button_click, this, std::placeholders::_2, std::placeholders::_3));
65 
66  // TODO: might need to position this differently in the queue if it's called after
67  // dialog-specific callbacks.
68  connect_signal<event::NOTIFY_MODIFIED>(
70 }
71 
72 void multimenu_button::set_active(const bool active)
73 {
74  if(get_active() != active) {
75  set_state(active ? ENABLED : DISABLED);
76  }
77 }
78 
80 {
81  return state_ != DISABLED;
82 }
83 
85 {
86  return state_;
87 }
88 
90 {
91  if(state != state_) {
92  state_ = state;
93  queue_redraw();
94  }
95 }
96 
98 {
99  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
100 
102  handled = true;
103 }
104 
106 {
107  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
108 
110  handled = true;
111 }
112 
114 {
115  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
116 
117  window* window = get_window();
118  if(window) {
120  }
121 
123  handled = true;
124 }
125 
127 {
128  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
129 
131  handled = true;
132 }
133 
135 {
136  assert(get_window());
137  DBG_GUI_E << LOG_HEADER << ' ' << event << ".";
138 
140 
141  // If a button has a retval do the default handling.
142  dialogs::drop_down_menu droplist(this, values_, -1, true);
143 
144  droplist_ = &droplist;
145  droplist.show();
146  droplist_ = nullptr;
147 
148  /* In order to allow toggle button states to be specified by various dialogs in the values config, we write the state
149  * bools to the values_ config here, but only if a checkbox= key was already provided. The value of the checkbox= key
150  * is handled by the drop_down_menu widget.
151  *
152  * Passing the dynamic_bitset directly to the drop_down_menu ctor would mean bool values would need to be passed to this
153  * class independently of the values config by dialogs that use this widget. However, the bool states are also saved
154  * in a dynamic_bitset class member which can be fetched for other uses if necessary.
155  */
157 
158  handled = true;
159 }
160 
162 {
163  std::vector<t_string> selected;
164  for(std::size_t i = 0; i < toggle_states_.size() && i < values_.size(); i++) {
165  if(!toggle_states_[i]) {
166  continue;
167  }
168 
169  selected.push_back(values_[i]["label"]);
170  }
171 
172  if(selected.size() == values_.size()) {
173  set_label(_("multimenu^All Selected"));
174  } else {
175  if(selected.size() > max_shown_) {
176  const unsigned excess = selected.size() - max_shown_;
177  selected.resize(max_shown_ + 1);
178  // TRANSLATORS: In a drop-down menu that's a list of toggle-boxes, this becomes part
179  // of the text on the button when many of the boxes are selected. The text becomes
180  // "x, y and 1 other", "x, y and 2 others", etc.
181  selected.back() = VNGETTEXT("multimenu^$excess other", "$excess others", excess, {{"excess", std::to_string(excess)}});
182  }
183  set_label(utils::format_conjunct_list(_("multimenu^None Selected"), selected));
184  }
185 }
186 
188 {
189  for(unsigned i = 0; i < values_.size(); i++) {
190  ::config& c = values_[i];
191 
192  c["checkbox"] = toggle_states_[i];
193  }
194 }
195 
197 {
198  toggle_states_.reset();
200  update_label();
201 }
202 
204 {
205  assert(droplist_ != nullptr);
206 
208  update_label();
209 }
210 
211 void multimenu_button::select_option(const unsigned option, const bool selected)
212 {
213  assert(option < values_.size());
214 
215  if(option < toggle_states_.size()) {
216  toggle_states_.resize(option + 1);
217  }
218  toggle_states_[option] = selected;
220  update_label();
221 }
222 
223 void multimenu_button::select_options(boost::dynamic_bitset<> states)
224 {
225  assert(states.size() == values_.size());
226  toggle_states_ = states;
228  update_label();
229 }
230 
231 void multimenu_button::set_values(const std::vector<::config>& values)
232 {
233  queue_redraw(); // TODO: draw_manager - does this need a relayout first?
234 
235  values_ = values;
236  toggle_states_.resize(values_.size(), false);
237  toggle_states_.reset();
238 
239  set_label(_("multimenu^None Selected"));
240 }
241 
242 // }---------- DEFINITION ---------{
243 
246 {
247  DBG_GUI_P << "Parsing multimenu_button " << id;
248 
249  load_resolutions<resolution>(cfg);
250 }
251 
253  : resolution_definition(cfg)
254 {
255  // Note the order should be the same as the enum state_t in multimenu_button.hpp.
256  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_enabled", _("Missing required state for multimenu button")));
257  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_disabled", _("Missing required state for multimenu button")));
258  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_pressed", _("Missing required state for multimenu button")));
259  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_focused", _("Missing required state for multimenu button")));
260 }
261 
262 // }---------- BUILDER -----------{
263 
264 namespace implementation
265 {
266 
267 builder_multimenu_button::builder_multimenu_button(const config& cfg)
268  : builder_styled_widget(cfg)
269  , max_shown_(cfg["maximum_shown"].to_unsigned(1))
270  , options_()
271 {
272  for(const auto& option : cfg.child_range("option")) {
273  options_.push_back(option);
274  }
275 }
276 
277 std::unique_ptr<widget> builder_multimenu_button::build() const
278 {
279  auto widget = std::make_unique<multimenu_button>(*this);
280 
281  widget->set_max_shown(max_shown_);
282  if(!options_.empty()) {
283  widget->set_values(options_);
284  }
285 
286  DBG_GUI_G << "Window builder: placed multimenu_button '" << id
287  << "' with definition '" << definition << "'.";
288 
289  return widget;
290 }
291 
292 } // namespace implementation
293 
294 // }------------ END --------------
295 
296 } // namespace gui2
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:161
child_itors child_range(config_key_type key)
Definition: config.cpp:277
Used by the menu_button widget.
boost::dynamic_bitset get_toggle_states() const
If a toggle button widget is present, returns the toggled state of each row's button.
bool show(const unsigned auto_close_time=0)
Shows the window.
A multimenu_button is a styled_widget to choose an element from a list of elements.
boost::dynamic_bitset toggle_states_
void signal_handler_mouse_enter(const event::ui_event event, bool &handled)
state_t state_
Current state of the widget.
void select_options(boost::dynamic_bitset<> states)
Set the options selected in the menu.
void signal_handler_left_button_click(const event::ui_event event, bool &handled)
void reset_toggle_states()
Deselect all the menu options.
void set_values(const std::vector<::config > &values)
Set the available menu options.
void signal_handler_left_button_up(const event::ui_event event, bool &handled)
std::vector<::config > values_
virtual bool get_active() const override
See styled_widget::get_active.
state_t
Possible states of the widget.
virtual void set_active(const bool active) override
See styled_widget::set_active.
void signal_handler_left_button_down(const event::ui_event event, bool &handled)
void select_option(const unsigned option, const bool selected=true)
Select an option in the menu.
dialogs::drop_down_menu * droplist_
void signal_handler_mouse_leave(const event::ui_event event, bool &handled)
unsigned max_shown_
The maximum number of selected states to list in the label.
void set_state(const state_t state)
virtual unsigned get_state() const override
See styled_widget::get_state.
Base class for all visible items.
virtual void set_label(const t_string &label)
Base class for all widgets.
Definition: widget.hpp:54
void queue_redraw()
Indicates that this widget should be redrawn.
Definition: widget.cpp:442
window * get_window()
Get the parent window.
Definition: widget.cpp:118
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:67
void mouse_capture(const bool capture=true)
Definition: window.cpp:1124
#define VNGETTEXT(msgid, msgid_plural, count,...)
std::size_t i
Definition: function.cpp:968
static std::string _(const char *str)
Definition: gettext.hpp:93
Define the common log macros for the gui toolkit.
#define DBG_GUI_G
Definition: log.hpp:41
#define DBG_GUI_P
Definition: log.hpp:66
#define DBG_GUI_E
Definition: log.hpp:35
This file contains the window object, this object is a top level container which has the event manage...
#define LOG_HEADER
std::string selected
ui_event
The event sent to the dispatcher.
Definition: handler.hpp:115
std::string sound_button_click
Definition: settings.cpp:48
Generic file dialog.
Contains the implementation details for lexical_cast and shouldn't be used directly.
void play_UI_sound(const std::string &files)
Definition: sound.cpp:1066
std::string format_conjunct_list(const t_string &empty, const std::vector< t_string > &elems)
Format a conjunctive list.
#define REGISTER_WIDGET(id)
Wrapper for REGISTER_WIDGET3.
This file contains the settings handling of the widget library.
virtual std::unique_ptr< widget > build() const override
std::string definition
Parameters for the styled_widget.
multimenu_button_definition(const config &cfg)
Base class of a resolution, contains the common keys for a resolution.
std::vector< state_definition > state
mock_char c
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define VALIDATE_WML_CHILD(cfg, key, message)