The Battle for Wesnoth  1.17.0-dev
drop_down_menu.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2018 by Mark de Wever <koraq@xs4all.nl>
3  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
4 
5  This program is free software; you can redistribute it and/or modify
6  it under the terms of the GNU General Public License as published by
7  the Free Software Foundation; either version 2 of the License, or
8  (at your option) any later version.
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY.
11 
12  See the COPYING file for more details.
13 */
14 
15 #define GETTEXT_DOMAIN "wesnoth-lib"
16 
18 
22 #include "gui/widgets/image.hpp"
24 #include "gui/widgets/listbox.hpp"
26 #include "gui/widgets/settings.hpp"
30 #include "gui/widgets/window.hpp"
31 
32 #include "sdl/rect.hpp"
33 #include <functional>
34 
35 namespace gui2::dialogs
36 {
37 REGISTER_DIALOG(drop_down_menu)
38 
40  : checkbox()
41  , icon(cfg["icon"].str())
42  , image()
43  , label(cfg["label"].t_str())
44  , details()
45  , tooltip(cfg["tooltip"].t_str())
46 {
47  // Checkboxes take precedence in column 1
48  if(cfg.has_attribute("checkbox")) {
49  checkbox = cfg["checkbox"].to_bool(false);
50  }
51 
52  // Images take precedence in column 2
53  if(cfg.has_attribute("image")) {
54  image = cfg["image"].str();
55  }
56 
57  if(cfg.has_attribute("details")) {
58  details = cfg["details"].t_str();
59  }
60 }
61 
62 namespace
63 {
64  void callback_flip_embedded_toggle(window& window)
65  {
66  listbox& list = find_widget<listbox>(&window, "list", true);
67 
68  /* If the currently selected row has a toggle button, toggle it.
69  * Note this cannot be handled in mouse_up_callback since at that point the new row selection has not registered,
70  * meaning the currently selected row's button is toggled.
71  */
72  grid* row_grid = list.get_row_grid(list.get_selected_row());
73  if(toggle_button* checkbox = find_widget<toggle_button>(row_grid, "checkbox", false, false)) {
74  checkbox->set_value_bool(!checkbox->get_value_bool(), true);
75  }
76  }
77 
78  void resize_callback(window& window)
79  {
80  window.set_retval(retval::CANCEL);
81  }
82 }
83 
84 drop_down_menu::drop_down_menu(styled_widget* parent, const std::vector<config>& items, int selected_item, bool keep_open)
85  : parent_(parent)
86  , items_(items.begin(), items.end())
87  , button_pos_(parent->get_rectangle())
88  , selected_item_(selected_item)
89  , use_markup_(parent->get_use_markup())
90  , keep_open_(keep_open)
91  , mouse_down_happened_(false)
92 {
93  set_restore(true);
94 }
95 
96 drop_down_menu::drop_down_menu(SDL_Rect button_pos, const std::vector<config>& items, int selected_item, bool use_markup, bool keep_open)
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  set_restore(true);
106 }
107 
109 {
110  if(!mouse_down_happened_) {
111  return;
112  }
113 
114  listbox& list = find_widget<listbox>(get_window(), "list", true);
115 
116  /* Disregard clicks on scrollbars and toggle buttons so the dropdown menu can be scrolled or have an embedded
117  * toggle button selected without the menu closing.
118  *
119  * This works since this mouse_up_callback function is called before widgets' left-button-up handlers.
120  *
121  * Additionally, this is done before row deselection so selecting/deselecting a toggle button doesn't also leave
122  * the list with no row visually selected. Oddly, the visual deselection doesn't seem to cause any crashes, and
123  * the previously selected row is reselected when the menu is opened again. Still, it's odd to see your selection
124  * vanish.
125  */
127  return;
128  }
129 
130  if(dynamic_cast<toggle_button*>(get_window()->find_at(coordinate, true)) != nullptr) {
131  return;
132  }
133 
134  /* FIXME: This dialog uses a listbox with 'has_minimum = false'. This allows a listbox to have 0 or 1 selections,
135  * and selecting the same entry toggles that entry's state (ie, if it was selected, it will be deselected). Because
136  * of this, selecting the same entry in the dropdown list essentially sets the list's selected row to -1, causing problems.
137  *
138  * In order to work around this, we first manually deselect the selected entry here. This handler is called *before*
139  * the listbox's click handler, and as such the selected item will remain toggled on when the click handler fires.
140  */
141  const int sel = list.get_selected_row();
142  if(sel >= 0) {
143  list.select_row(sel, false);
144  }
145 
146  SDL_Rect rect = get_window()->get_rectangle();
147  if(!sdl::point_in_rect(coordinate, rect)) {
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_) {
169  std::map<std::string, string_map> data;
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  toggle_button* 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", checkbox, false);
210  }
211 
212  if(entry.image) {
213  image* img = build_single_widget_instance<image>();
214  img->set_label(*entry.image);
215 
216  mi_grid.swap_child("label", 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()
Dialog was closed with the CANCEL button.
Definition: retval.hpp:37
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:58
An SDL resize request, coordinate is the new window size.
Definition: handler.hpp:51
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:309
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:56
int get_selected_row() const
Returns the first selected row.
Definition: listbox.cpp:276
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:251
Sent by a widget to notify others its contents or state are modified.
Definition: handler.hpp:88
virtual void set_label(const t_string &label)
The listbox class.
Definition: listbox.hpp:42
Base container class.
Definition: grid.hpp:30
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:186
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:132
bool point_in_rect(int x, int y, const SDL_Rect &rect)
Tests whether a point is inside a rectangle.
Definition: rect.cpp:22
std::size_t i
Definition: function.cpp:940
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:25
Holds a 2D point.
Definition: point.hpp:23
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:67
void set_retval(const int retval, const bool close_window=true)
Sets there return value of the window.
Definition: window.hpp:357
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:59
const grid * get_row_grid(const unsigned row) const
Returns the grid of the wanted row.
Definition: listbox.cpp:238
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:98
virtual unsigned get_state() const override
See styled_widget::get_state.
Definition: scrollbar.cpp:146
void set_id(const std::string &id)
Definition: widget.cpp:97
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:56
bool fire(const ui_event event, widget &target)
Fires an event which has no extra parameters.
Definition: dispatcher.cpp:68
Dialog was closed with the OK button.
Definition: retval.hpp:34
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:59
An SDL right mouse button up event.
Definition: handler.hpp:73
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:64
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:409
void set_variable(const std::string &key, const wfl::variant &value)
Definition: window.hpp:384