The Battle for Wesnoth  1.17.0-dev
drop_down_menu.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2021
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  : parent_(parent)
87  , items_(items.begin(), items.end())
88  , button_pos_(parent->get_rectangle())
89  , selected_item_(selected_item)
90  , use_markup_(parent->get_use_markup())
91  , keep_open_(keep_open)
92  , mouse_down_happened_(false)
93 {
94  set_restore(true);
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)
98  : parent_(nullptr)
99  , items_(items.begin(), items.end())
100  , button_pos_(button_pos)
101  , selected_item_(selected_item)
102  , use_markup_(use_markup)
103  , keep_open_(keep_open)
104  , mouse_down_happened_(false)
105 {
106  set_restore(true);
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  SDL_Rect rect = get_window()->get_rectangle();
148  if(!sdl::point_in_rect(coordinate, rect)) {
150  } else if(!keep_open_) {
152  }
153 }
154 
156 {
157  mouse_down_happened_ = true;
158 }
159 
161 {
162  window.set_variable("button_x", wfl::variant(button_pos_.x));
163  window.set_variable("button_y", wfl::variant(button_pos_.y));
164  window.set_variable("button_w", wfl::variant(button_pos_.w));
165  window.set_variable("button_h", wfl::variant(button_pos_.h));
166 
167  listbox& list = find_widget<listbox>(&window, "list", true);
168 
169  for(const auto& entry : items_) {
170  std::map<std::string, string_map> data;
172 
173  //
174  // These widgets can be initialized here since they don't require widget type swapping.
175  //
176  item["use_markup"] = utils::bool_string(use_markup_);
177  if(!entry.checkbox) {
178  item["label"] = entry.icon;
179  data.emplace("icon", item);
180  }
181 
182  if(!entry.image) {
183  item["label"] = entry.label;
184  data.emplace("label", item);
185  }
186 
187  if(entry.details) {
188  item["label"] = *entry.details;
189  data.emplace("details", item);
190  }
191 
192  grid& new_row = list.add_row(data);
193  grid& mi_grid = find_widget<grid>(&new_row, "menu_item", false);
194 
195  // Set the tooltip on the whole panel
196  find_widget<toggle_panel>(&new_row, "panel", false).set_tooltip(entry.tooltip);
197 
198  if(entry.checkbox) {
199  toggle_button* checkbox = build_single_widget_instance<toggle_button>();
200  checkbox->set_id("checkbox");
201  checkbox->set_value_bool(*entry.checkbox);
202 
203  // Fire a NOTIFIED_MODIFIED event in the parent widget when the toggle state changes
204  if(parent_) {
205  connect_signal_notify_modified(*checkbox, std::bind([this]() {
207  }));
208  }
209 
210  mi_grid.swap_child("icon", checkbox, false);
211  }
212 
213  if(entry.image) {
214  image* img = build_single_widget_instance<image>();
215  img->set_label(*entry.image);
216 
217  mi_grid.swap_child("label", img, false);
218  }
219  }
220 
221  if(selected_item_ >= 0 && static_cast<unsigned>(selected_item_) < list.get_item_count()) {
223  }
224 
225  window.keyboard_capture(&list);
226 
227  // Dismiss on clicking outside the window.
228  window.connect_signal<event::SDL_LEFT_BUTTON_UP>(
229  std::bind(&drop_down_menu::mouse_up_callback, this, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), event::dispatcher::front_child);
230 
231  window.connect_signal<event::SDL_RIGHT_BUTTON_UP>(
232  std::bind(&drop_down_menu::mouse_up_callback, this, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5), event::dispatcher::front_child);
233 
234  window.connect_signal<event::SDL_LEFT_BUTTON_DOWN>(
236 
237  // Dismiss on resize.
238  window.connect_signal<event::SDL_VIDEO_RESIZE>(
239  std::bind(&resize_callback, std::ref(window)), event::dispatcher::front_child);
240 
241  // Handle embedded button toggling.
243  std::bind(&callback_flip_embedded_toggle, std::ref(window)));
244 }
245 
247 {
248  selected_item_ = find_widget<listbox>(&window, "list", true).get_selected_row();
249 }
250 
251 boost::dynamic_bitset<> drop_down_menu::get_toggle_states() const
252 {
253  const listbox& list = find_widget<const listbox>(get_window(), "list", true);
254 
255  boost::dynamic_bitset<> states;
256 
257  for(unsigned i = 0; i < list.get_item_count(); ++i) {
258  const grid* row_grid = list.get_row_grid(i);
259 
260  if(const toggle_button* checkbox = find_widget<const toggle_button>(row_grid, "checkbox", false, false)) {
261  states.push_back(checkbox->get_value_bool());
262  } else {
263  states.push_back(false);
264  }
265  }
266 
267  return states;
268 }
269 
270 } // namespace dialogs
scrollbar_base * vertical_scrollbar()
Dialog was closed with the CANCEL button.
Definition: retval.hpp:38
void set_value_bool(bool value, bool fire_event=false)
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...
An SDL left mouse button down event.
Definition: handler.hpp:59
An SDL resize request, coordinate is the new window size.
Definition: handler.hpp:52
This file contains the window object, this object is a top level container which has the event manage...
SDL_Rect get_rectangle() const
Gets the bounding rectangle of the widget on the screen.
Definition: widget.cpp:310
styled_widget * parent_
The widget that invoked this dialog, if applicable.
window * get_window() const
Returns a pointer to the dialog&#39;s window.
const std::vector< std::string > items
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:277
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:252
Sent by a widget to notify others its contents or state are modified.
Definition: handler.hpp:89
virtual void set_label(const t_string &label)
The listbox class.
Definition: listbox.hpp:43
Base container class.
Definition: grid.hpp:31
void connect_signal_notify_modified(dispatcher &dispatcher, const signal_notification_function &signal)
Connects a signal handler for getting a notification upon modification.
Definition: dispatcher.cpp:187
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:133
bool point_in_rect(int x, int y, const SDL_Rect &rect)
Tests whether a point is inside a rectangle.
Definition: rect.cpp:23
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;.
std::map< std::string, t_string > string_map
Definition: widget.hpp:26
Holds a 2D point.
Definition: point.hpp:24
grid & add_row(const string_map &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:68
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).
An SDL left mouse button up event.
Definition: handler.hpp:60
const grid * get_row_grid(const unsigned row) const
Returns the grid of the wanted row.
Definition: listbox.cpp:239
Contains the SDL_Rect helper code.
std::unique_ptr< widget > swap_child(const std::string &id, widget *w, const bool recurse, widget *new_parent=nullptr)
Exchanges a child in the grid.
Definition: grid.cpp:99
virtual unsigned get_state() const override
See styled_widget::get_state.
Definition: scrollbar.cpp:147
void set_id(const std::string &id)
Definition: widget.cpp:98
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:69
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:61
An SDL right mouse button up event.
Definition: handler.hpp:74
void set_retval(int retval)
Convenience wrapper to set the window&#39;s exit code.
base class of top level items, the only item which needs to store the final canvases to draw on...
Definition: window.hpp:65
SDL_Rect button_pos_
The screen location of the menu_button button that triggered this droplist.
Class for a toggle button.
void set_restore(const bool restore)
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:410
void set_variable(const std::string &key, const wfl::variant &value)
Definition: window.hpp:385