The Battle for Wesnoth  1.19.8+dev
drop_down_menu.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2024
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 
22 #include "gui/widgets/image.hpp"
23 #include "gui/widgets/listbox.hpp"
28 #include "gui/widgets/window.hpp"
29 
30 #include "sdl/rect.hpp"
31 #include <functional>
32 
33 namespace gui2::dialogs
34 {
35 REGISTER_DIALOG(drop_down_menu)
36 
38  : checkbox()
39  , icon(cfg["icon"].str())
40  , image()
41  , label(cfg["label"].t_str())
42  , details()
43  , tooltip(cfg["tooltip"].t_str())
44 {
45  // Checkboxes take precedence in column 1
46  if(cfg.has_attribute("checkbox")) {
47  checkbox = cfg["checkbox"].to_bool(false);
48  }
49 
50  // Images take precedence in column 2
51  if(cfg.has_attribute("image")) {
52  image = cfg["image"].str();
53  }
54 
55  if(cfg.has_attribute("details")) {
56  details = cfg["details"].t_str();
57  }
58 }
59 
60 namespace
61 {
62  void callback_flip_embedded_toggle(window& window)
63  {
64  listbox& list = window.find_widget<listbox>("list", true);
65 
66  /* If the currently selected row has a toggle button, toggle it.
67  * Note this cannot be handled in mouse_up_callback since at that point the new row selection has not registered,
68  * meaning the currently selected row's button is toggled.
69  */
70  grid* row_grid = list.get_row_grid(list.get_selected_row());
71  if(toggle_button* checkbox = row_grid->find_widget<toggle_button>("checkbox", false, false)) {
72  checkbox->set_value_bool(!checkbox->get_value_bool(), true);
73  }
74  }
75 
76  void resize_callback(window& window)
77  {
79  }
80 }
81 
82 drop_down_menu::drop_down_menu(styled_widget* parent, const std::vector<config>& items, int selected_item, bool keep_open)
83  : modal_dialog(window_id())
84  , parent_(parent)
85  , items_(items.begin(), items.end())
86  , button_pos_(parent->get_rectangle())
87  , selected_item_(selected_item)
88  , selected_item_pos_(-1, -1)
89  , use_markup_(parent->get_use_markup())
90  , keep_open_(keep_open)
91  , mouse_down_happened_(false)
92 {
93 }
94 
95 drop_down_menu::drop_down_menu(SDL_Rect button_pos, const std::vector<config>& items, int selected_item, bool use_markup, bool keep_open)
96  : modal_dialog(window_id())
97  , parent_(nullptr)
98  , items_(items.begin(), items.end())
99  , button_pos_(button_pos)
100  , selected_item_(selected_item)
101  , use_markup_(use_markup)
102  , keep_open_(keep_open)
103  , mouse_down_happened_(false)
104 {
105 }
106 
108 {
109  if(!mouse_down_happened_) {
110  return;
111  }
112 
113  listbox& list = find_widget<listbox>("list", true);
114 
115  /* Disregard clicks on scrollbars and toggle buttons so the dropdown menu can be scrolled or have an embedded
116  * toggle button selected without the menu closing.
117  *
118  * This works since this mouse_up_callback function is called before widgets' left-button-up handlers.
119  *
120  * Additionally, this is done before row deselection so selecting/deselecting a toggle button doesn't also leave
121  * the list with no row visually selected. Oddly, the visual deselection doesn't seem to cause any crashes, and
122  * the previously selected row is reselected when the menu is opened again. Still, it's odd to see your selection
123  * vanish.
124  */
126  return;
127  }
128 
129  if(dynamic_cast<toggle_button*>(find_at(coordinate, true)) != nullptr) {
130  return;
131  }
132 
133  /* FIXME: This dialog uses a listbox with 'has_minimum = false'. This allows a listbox to have 0 or 1 selections,
134  * and selecting the same entry toggles that entry's state (ie, if it was selected, it will be deselected). Because
135  * of this, selecting the same entry in the dropdown list essentially sets the list's selected row to -1, causing problems.
136  *
137  * In order to work around this, we first manually deselect the selected entry here. This handler is called *before*
138  * the listbox's click handler, and as such the selected item will remain toggled on when the click handler fires.
139  */
140  const int sel = list.get_selected_row();
141  if(sel >= 0) {
142  list.select_row(sel, false);
143  }
144 
147  } else if(!keep_open_) {
149  }
150 }
151 
153 {
154  mouse_down_happened_ = true;
155 }
156 
158 {
159  set_variable("button_x", wfl::variant(button_pos_.x));
160  set_variable("button_y", wfl::variant(button_pos_.y));
161  set_variable("button_w", wfl::variant(button_pos_.w));
162  set_variable("button_h", wfl::variant(button_pos_.h));
163 
164  listbox& list = find_widget<listbox>("list", true);
165 
166  for(const auto& entry : items_) {
168  widget_item item;
169 
170  //
171  // These widgets can be initialized here since they don't require widget type swapping.
172  //
173  item["use_markup"] = utils::bool_string(use_markup_);
174  if(!entry.checkbox) {
175  item["label"] = entry.icon;
176  data.emplace("icon", item);
177  }
178 
179  if(!entry.image) {
180  item["label"] = entry.label;
181  data.emplace("label", item);
182  }
183 
184  if(entry.details) {
185  item["label"] = *entry.details;
186  data.emplace("details", item);
187  }
188 
189  grid& new_row = list.add_row(data);
190  grid& mi_grid = new_row.find_widget<grid>("menu_item");
191 
192  // Set the tooltip on the whole panel
193  new_row.find_widget<toggle_panel>("panel").set_tooltip(entry.tooltip);
194 
195  if(entry.checkbox) {
196  auto checkbox = build_single_widget_instance<toggle_button>(config{"definition", "no_label"});
197  checkbox->set_id("checkbox");
198  checkbox->set_value_bool(*entry.checkbox);
199 
200  // Fire a NOTIFIED_MODIFIED event in the parent widget when the toggle state changes
201  if(parent_) {
203  *checkbox, [this](auto&&...) { parent_->fire(event::NOTIFY_MODIFIED, *parent_, nullptr); });
204  }
205 
206  mi_grid.swap_child("icon", std::move(checkbox), false);
207  }
208 
209  if(entry.image) {
210  auto img = build_single_widget_instance<image>();
211  img->set_label(*entry.image);
212 
213  mi_grid.swap_child("label", std::move(img), false);
214  }
215  }
216 
217  if(selected_item_ >= 0 && static_cast<unsigned>(selected_item_) < list.get_item_count()) {
219  }
220 
221  keyboard_capture(&list);
222 
223  // Dismiss on clicking outside the window.
224  connect_signal<event::SDL_LEFT_BUTTON_UP>(
225  std::bind(&drop_down_menu::mouse_up_callback, this, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), event::dispatcher::front_child);
226 
227  connect_signal<event::SDL_RIGHT_BUTTON_UP>(
228  std::bind(&drop_down_menu::mouse_up_callback, this, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), event::dispatcher::front_child);
229 
230  connect_signal<event::SDL_LEFT_BUTTON_DOWN>(
232 
233  // Dismiss on resize.
234  connect_signal<event::SDL_VIDEO_RESIZE>(
235  [this](auto&&...){ resize_callback(*this); }, event::dispatcher::front_child);
236 
237  // Handle embedded button toggling.
239  [this](auto&&...){ callback_flip_embedded_toggle(*this); });
240 }
241 
243 {
244  const listbox& list = find_widget<listbox>("list", true);
246  if(selected_item_ != -1) {
247  const grid* row_grid = list.get_row_grid(selected_item_);
248  if(row_grid) {
249  selected_item_pos_.x = row_grid->get_x();
250  selected_item_pos_.y = row_grid->get_y();
251  }
252  } else {
254  }
255 }
256 
257 boost::dynamic_bitset<> drop_down_menu::get_toggle_states() const
258 {
259  const listbox& list = find_widget<const listbox>("list", true);
260 
261  boost::dynamic_bitset<> states;
262 
263  for(unsigned i = 0; i < list.get_item_count(); ++i) {
264  const grid* row_grid = list.get_row_grid(i);
265 
266  if(const toggle_button* checkbox = row_grid->find_widget<const toggle_button>("checkbox", false, false)) {
267  states.push_back(checkbox->get_value_bool());
268  } else {
269  states.push_back(false);
270  }
271  }
272 
273  return states;
274 }
275 
276 } // namespace dialogs
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
Used by the menu_button widget.
std::vector< entry_data > items_
Configuration of each row.
virtual void post_show() override
Actions to be taken after the window has been shown.
SDL_Rect button_pos_
The screen location of the menu_button button that triggered this droplist.
bool keep_open_
Whether to keep this dialog open after a click occurs not handled by special exceptions such as scrol...
virtual void pre_show() override
Actions to be taken before showing the window.
void mouse_up_callback(bool &, bool &, const point &coordinate)
drop_down_menu(styled_widget *parent, const std::vector< config > &items, int selected_item, bool keep_open)
Menu was invoked from a widget (currently a [multi]menu_button).
boost::dynamic_bitset get_toggle_states() const
If a toggle button widget is present, returns the toggled state of each row's button.
bool mouse_down_happened_
When menu is invoked on a long-touch timer, a following mouse-up event will close it.
styled_widget * parent_
The widget that invoked this dialog, if applicable.
Abstract base class for all modal dialogs.
At the moment two kinds of tips are known:
Definition: tooltip.cpp:41
bool fire(const ui_event event, widget &target)
Fires an event which has no extra parameters.
Definition: dispatcher.cpp:74
Base container class.
Definition: grid.hpp:32
std::unique_ptr< widget > swap_child(const std::string &id, std::unique_ptr< widget > w, const bool recurse, widget *new_parent=nullptr)
Exchanges a child in the grid.
Definition: grid.cpp:101
The listbox class.
Definition: listbox.hpp:41
grid & add_row(const widget_item &item, const int index=-1)
When an item in the list is selected by the user we need to update the state.
Definition: listbox.cpp:92
const grid * get_row_grid(const unsigned row) const
Returns the grid of the wanted row.
Definition: listbox.cpp:267
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:280
int get_selected_row() const
Returns the first selected row.
Definition: listbox.cpp:305
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:159
virtual unsigned get_state() const override
See styled_widget::get_state.
Definition: scrollbar.cpp:147
scrollbar_base * vertical_scrollbar()
void set_tooltip(const t_string &tooltip)
int get_x() const
Definition: widget.cpp:326
int get_y() const
Definition: widget.cpp:331
rect get_rectangle() const
Gets the bounding rectangle of the widget on the screen.
Definition: widget.cpp:321
T * find_widget(const std::string_view id, const bool must_be_active, const bool must_exist)
Gets a widget with the wanted id.
Definition: widget.hpp:747
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:61
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:395
void keyboard_capture(widget *widget)
Definition: window.cpp:1193
void set_variable(const std::string &key, const wfl::variant &value)
Definition: window.hpp:422
virtual widget * find_at(const point &coordinate, const bool must_be_active) override
See widget::find_at.
Definition: window.cpp:758
std::size_t i
Definition: function.cpp:1029
This file contains the window object, this object is a top level container which has the event manage...
REGISTER_DIALOG(editor_edit_unit)
@ NOTIFY_MODIFIED
Definition: handler.hpp:158
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:203
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:36
std::map< std::string, t_string > widget_item
Definition: widget.hpp:33
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
@ CANCEL
Dialog was closed with the CANCEL button.
Definition: retval.hpp:38
Functions to load and save images from/to disk.
std::string img(const std::string &src, const std::string &align, bool floating)
Generates a Help markup tag corresponding to an image.
Definition: markup.cpp:31
map_location coordinate
Contains an x and y coordinate used for starting positions in maps.
bool contains(const Container &container, const Value &value)
Returns true iff value is found in container.
Definition: general.hpp:86
std::string bool_string(const bool value)
Converts a bool value to 'true' or 'false'.
std::string_view data
Definition: picture.cpp:178
Contains the SDL_Rect helper code.
Holds a 2D point.
Definition: point.hpp:25