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