The Battle for Wesnoth  1.15.2+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 #include "gui/widgets/listbox.hpp"
21 #include "gui/widgets/image.hpp"
24 #include "gui/widgets/settings.hpp"
27 #include "gui/widgets/window.hpp"
28 
29 #include "sdl/rect.hpp"
30 #include "utils/functional.hpp"
31 
32 namespace gui2
33 {
34 namespace dialogs
35 {
36 REGISTER_DIALOG(drop_down_menu)
37 
38 namespace
39 {
40  void callback_flip_embedded_toggle(window& window)
41  {
42  listbox& list = find_widget<listbox>(&window, "list", true);
43 
44  /* If the currently selected row has a toggle button, toggle it.
45  * Note this cannot be handled in mouse_up_callback since at that point the new row selection has not registered,
46  * meaning the currently selected row's button is toggled.
47  */
48  grid* row_grid = list.get_row_grid(list.get_selected_row());
49  if(toggle_button* checkbox = find_widget<toggle_button>(row_grid, "checkbox", false, false)) {
50  checkbox->set_value_bool(!checkbox->get_value_bool(), true);
51  }
52  }
53 
54  void resize_callback(window& window)
55  {
56  window.set_retval(retval::CANCEL);
57  }
58 }
59 
61 {
63  return;
64  }
65 
66  listbox& list = find_widget<listbox>(&window, "list", true);
67 
68  /* Disregard clicks on scrollbars and toggle buttons so the dropdown menu can be scrolled or have an embedded
69  * toggle button selected without the menu closing.
70  *
71  * This works since this mouse_up_callback function is called before widgets' left-button-up handlers.
72  *
73  * Additionally, this is done before row deselection so selecting/deselecting a toggle button doesn't also leave
74  * the list with no row visually selected. Oddly, the visial deselection doesn't seem to cause any crashes, and
75  * the previously selected row is reselected when the menu is opened again. Still, it's odd to see your selection
76  * vanish.
77  */
79  return;
80  }
81 
82  if(dynamic_cast<toggle_button*>(window.find_at(coordinate, true)) != nullptr) {
83  return;
84  }
85 
86  /* FIXME: This dialog uses a listbox with 'has_minimum = false'. This allows a listbox to have 0 or 1 selections,
87  * and selecting the same entry toggles that entry's state (ie, if it was selected, it will be deselected). Because
88  * of this, selecting the same entry in the dropdown list essentially sets the list's selected row to -1, causing problems.
89  *
90  * In order to work around this, we first manually deselect the selected entry here. This handler is called *before*
91  * the listbox's click handler, and as such the selected item will remain toggled on when the click handler fires.
92  */
93  const int sel = list.get_selected_row();
94  if(sel >= 0) {
95  list.select_row(sel, false);
96  }
97 
98  SDL_Rect rect = window.get_rectangle();
99  if(!sdl::point_in_rect(coordinate, rect)) {
100  window.set_retval(retval::CANCEL);
101  } else if(!keep_open_) {
102  window.set_retval(retval::OK);
103  }
104 }
105 
107 {
108  mouse_down_happened_ = true;
109 }
110 
112 {
113  window.set_variable("button_x", wfl::variant(button_pos_.x));
114  window.set_variable("button_y", wfl::variant(button_pos_.y));
115  window.set_variable("button_w", wfl::variant(button_pos_.w));
116  window.set_variable("button_h", wfl::variant(button_pos_.h));
117 
118  listbox& list = find_widget<listbox>(&window, "list", true);
119 
120  /* Each row's config can have the following keys. Note the containing [entry]
121  * tag is for illustrative purposes and isn't actually present in the configs.
122  *
123  * [entry]
124  * icon = "path/to/image.png" | Column 1 OR
125  * checkbox = yes/no | Column 1
126  * image = "path/to/image.png" | Column 2 OR
127  * label = "text" | Column 2
128  * details = "text" | Column 3
129  * tooltip = "text" | Entire row
130  * [/entry]
131  */
132  for(const auto& entry : items_) {
133  std::map<std::string, string_map> data;
135 
136  const bool has_image_key = entry.has_attribute("image");
137  const bool has_ckbox_key = entry.has_attribute("checkbox");
138 
139  //
140  // These widgets can be initialized here since they don't require widget type swapping.
141  //
142  item["use_markup"] = utils::bool_string(use_markup_);
143 
144  if(!has_ckbox_key) {
145  item["label"] = entry["icon"];
146  data.emplace("icon", item);
147  }
148 
149  if(!has_image_key) {
150  item["label"] = entry["label"];
151  data.emplace("label", item);
152  }
153 
154  if(entry.has_attribute("details")) {
155  item["label"] = entry["details"];
156  data.emplace("details", item);
157  }
158 
159  grid& new_row = list.add_row(data);
160  grid& mi_grid = find_widget<grid>(&new_row, "menu_item", false);
161 
162  // Set the tooltip on the whole panel
163  find_widget<toggle_panel>(&new_row, "panel", false).set_tooltip(entry["tooltip"]);
164 
165  if(has_ckbox_key) {
166  toggle_button* checkbox = build_single_widget_instance<toggle_button>();
167  checkbox->set_id("checkbox");
168  checkbox->set_value_bool(entry["checkbox"].to_bool(false));
169 
170  if(callback_toggle_state_change_ != nullptr) {
172  }
173 
174  mi_grid.swap_child("icon", checkbox, false);
175  }
176 
177  if(has_image_key) {
178  image* img = build_single_widget_instance<image>();
179  img->set_label(entry["image"]);
180 
181  mi_grid.swap_child("label", img, false);
182  }
183  }
184 
185  if(selected_item_ >= 0 && static_cast<unsigned>(selected_item_) < list.get_item_count()) {
187  }
188 
189  window.keyboard_capture(&list);
190 
191  // Dismiss on clicking outside the window.
192  window.connect_signal<event::SDL_LEFT_BUTTON_UP>(
193  std::bind(&drop_down_menu::mouse_up_callback, this, std::ref(window), _3, _4, _5), event::dispatcher::front_child);
194 
195  window.connect_signal<event::SDL_RIGHT_BUTTON_UP>(
196  std::bind(&drop_down_menu::mouse_up_callback, this, std::ref(window), _3, _4, _5), event::dispatcher::front_child);
197 
198  window.connect_signal<event::SDL_LEFT_BUTTON_DOWN>(
200 
201  // Dismiss on resize.
202  window.connect_signal<event::SDL_VIDEO_RESIZE>(
203  std::bind(&resize_callback, std::ref(window)), event::dispatcher::front_child);
204 
205  // Handle embedded button toggling.
207  std::bind(&callback_flip_embedded_toggle, std::ref(window)));
208 }
209 
211 {
212  selected_item_ = find_widget<listbox>(&window, "list", true).get_selected_row();
213 }
214 
215 boost::dynamic_bitset<> drop_down_menu::get_toggle_states() const
216 {
217  const listbox& list = find_widget<const listbox>(get_window(), "list", true);
218 
219  boost::dynamic_bitset<> states;
220 
221  for(unsigned i = 0; i < list.get_item_count(); ++i) {
222  const grid* row_grid = list.get_row_grid(i);
223 
224  if(const toggle_button* checkbox = find_widget<const toggle_button>(row_grid, "checkbox", false, false)) {
225  states.push_back(checkbox->get_value_bool());
226  } else {
227  states.push_back(false);
228  }
229  }
230 
231  return states;
232 }
233 
234 } // namespace dialogs
235 } // namespace gui2
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)
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:66
An SDL resize request, coordinate is the new window size.
Definition: handler.hpp:59
This file contains the window object, this object is a top level container which has the event manage...
window * get_window() const
Returns a pointer to the dialog&#39;s window.
int get_selected_row() const
Returns the first selected row.
Definition: listbox.cpp:272
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:250
Generic file dialog.
Definition: field-fwd.hpp:22
virtual void set_label(const t_string &label)
The listbox class.
Definition: listbox.hpp:40
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:248
This file contains the settings handling of the widget library.
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:131
Various uncategorised dialogs.
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:933
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
Inherited from modal_dialog.
std::function< void()> callback_toggle_state_change_
If toggle buttons are used, this callback is called whenever the state of any toggle button changes...
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:24
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:66
void mouse_up_callback(window &window, bool &, bool &, const point &coordinate)
bool grid()
Definition: general.cpp:505
An SDL left mouse button up event.
Definition: handler.hpp:67
const grid * get_row_grid(const unsigned row) const
Returns the grid of the wanted row.
Definition: listbox.cpp:237
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:95
this module manages the cache of images.
std::vector< config > items_
Configuration of each row.
virtual void post_show(window &window) override
Inherited from modal_dialog.
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.
An SDL right mouse button up event.
Definition: handler.hpp:81
base class of top level items, the only item which needs to store the final canvases to draw on ...
Definition: window.hpp:62
SDL_Rect button_pos_
The screen location of the menu_button button that triggered this droplist.
Class for a toggle button.
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:371
void set_variable(const std::string &key, const wfl::variant &value)
Definition: window.hpp:390