The Battle for Wesnoth  1.17.10+dev
listbox.hpp
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 #pragma once
17 
20 
23 
24 #include "preferences/general.hpp"
25 
26 #include <boost/dynamic_bitset.hpp>
27 #include <functional>
28 
29 namespace gui2
30 {
31 // ------------ WIDGET -----------{
32 
33 class selectable_item;
34 namespace implementation
35 {
36 struct builder_listbox;
37 struct builder_horizontal_listbox;
38 struct builder_grid_listbox;
39 struct builder_styled_widget;
40 }
41 
42 class generator_base;
43 
44 /** The listbox class. */
46 {
50 
51  friend class debug_layout_graph;
52 
53 public:
54  /**
55  * Constructor.
56  *
57  * @param builder The builder for the appropriate listbox variant.
58  * @param placement How are the items placed.
59  * @param list_builder Grid builder for the listbox definition grid.
60  */
62  const generator_base::placement placement,
63  builder_grid_ptr list_builder);
64 
65  /***** ***** ***** ***** Row handling. ***** ***** ****** *****/
66 
67  /**
68  * When an item in the list is selected by the user we need to
69  * update the state. We installed a callback handler which
70  * calls us.
71  *
72  * @param item The data send to the set_members of the
73  * widgets.
74  * @param index The item before which to add the new item,
75  * 0 == begin, -1 == end.
76  */
77  grid& add_row(const widget_item& item, const int index = -1);
78 
79  /**
80  * Adds single row to the grid.
81  *
82  * This function expect a row to have multiple widgets (either multiple
83  * columns or one column with multiple widgets).
84  *
85  *
86  * @param data The data to send to the set_members of the
87  * widgets. If the member id is not an empty
88  * string it is only send to the widget that has
89  * the wanted id (if any). If the member id is an
90  * empty string, it is send to all members.
91  * Having both empty and non-empty id's gives
92  * undefined behavior.
93  * @param index The item before which to add the new item,
94  * 0 == begin, -1 == end.
95  */
96  grid& add_row(const widget_data& data, const int index = -1);
97 
98  /**
99  * Removes a row in the listbox.
100  *
101  * @param row The row to remove, when not in
102  * range the function is ignored.
103  * @param count The number of rows to remove, 0 means all
104  * rows (starting from row).
105  */
106  void remove_row(const unsigned row, unsigned count = 1);
107 
108  /** Removes all the rows in the listbox, clearing it. */
109  void clear();
110 
111  /** Returns the number of items in the listbox. */
112  unsigned get_item_count() const;
113 
114  /**
115  * Makes a row active or inactive.
116  *
117  * NOTE this doesn't change the select status of the row.
118  *
119  * @param row The row to (de)activate.
120  * @param active true activate, false deactivate.
121  */
122  void set_row_active(const unsigned row, const bool active);
123 
124  /**
125  * Makes a row visible or invisible.
126  *
127  * @param row The row to show or hide.
128  * @param shown true visible, false invisible.
129  */
130  void set_row_shown(const unsigned row, const bool shown);
131 
132  /**
133  * Makes a row visible or invisible.
134  *
135  * Use this version if you want to show hide multiple items since it's
136  * optimized for that purpose, for one it calls the selection changed
137  * callback only once instead of several times.
138  *
139  * @param shown A vector with the show hide status for every
140  * row. The number of items in the vector must
141  * be equal to the number of items in the
142  * listbox.
143  */
144  void set_row_shown(const boost::dynamic_bitset<>& shown);
145 
146  /**
147  * Returns a list of visible rows
148  *
149  * @returns A mask indicating which rows are visible
150  */
151  boost::dynamic_bitset<> get_rows_shown() const;
152 
153  bool any_rows_shown() const;
154 
155  /**
156  * Returns the grid of the wanted row.
157  *
158  * There's only a const version since allowing callers to modify the grid
159  * behind our backs might give problems. We return a pointer instead of a
160  * reference since dynamic casting of pointers is easier (no try catch
161  * needed).
162  *
163  * @param row The row to get the grid from, the caller has
164  * to make sure the row is a valid row.
165  * @returns The grid of the wanted row.
166  */
167  const grid* get_row_grid(const unsigned row) const;
168 
169  /**
170  * The possibly-giving-problems nonconst version of get_row_grid
171  *
172  * @param row The row to get the grid from, the caller has
173  * to make sure the row is a valid row.
174  * @returns The grid of the wanted row.
175  */
176  grid* get_row_grid(const unsigned row);
177 
178  /**
179  * Selects a row.
180  *
181  * @param row The row to select.
182  * @param select Select or deselect the row.
183  * @returns True if the operation succeeded.
184  */
185  bool select_row(const unsigned row, const bool select = true);
186 
187  /**
188  * Does exactly as advertised: selects the list's last row.
189  *
190  * @param select Select or deselect the row.
191  */
192  bool select_last_row(const bool select = true)
193  {
194  return select_row(get_item_count() - 1, select);
195  }
196 
197  /**
198  * Selects a row at the given position, regardless of sorting order.
199  *
200  * When using @ref select_row the relevant row is located by index regardless
201  * of its actual position in the list, which could differ if the list had been
202  * sorted. In that case, `select_row(0)` would not select the list's first row
203  * as displayed.
204  *
205  * This function allows row selection based on position. `select_row_at(0)` will
206  * always select the list's first row, regardless of sorting order.
207  *
208  * @param row The row to select.
209  * @param select Select or deselect the row.
210  *
211  * @returns True if the operation succeeded.
212  */
213  bool select_row_at(const unsigned row, const bool select = true);
214 
215  /**
216  * Check if a row is selected
217  * @param row The row to test
218  * @returns True if it is selected.
219  */
220  bool row_selected(const unsigned row);
221 
222  /**
223  * Returns the first selected row
224  *
225  * @returns The first selected row, or -1 if no row is selected.
226  */
227  int get_selected_row() const;
228 
229  /** Function to call after the user clicked on a row. */
230  void list_item_clicked(widget& caller);
231 
232  /** See @ref container_base::set_self_active. */
233  virtual void set_self_active(const bool active) override;
234 
235  /**
236  * Request to update the size of the content after changing the content.
237  *
238  * When a resize is required the container first can try to handle it
239  * itself. If it can't honor the request the function will call @ref
240  * window::invalidate_layout().
241  *
242  * @note Calling this function on a widget with size == (0, 0) results
243  * false but doesn't call invalidate_layout, the engine expects to be in
244  * build up phase with the layout already invalidated.
245  *
246  * @returns True if the resizing succeeded, false
247  * otherwise.
248  */
249  bool update_content_size();
250 
251  /***** ***** ***** ***** inherited ***** ***** ****** *****/
252 
253  /** See @ref widget::place. */
254  virtual void place(const point& origin, const point& size) override;
255 
256  /***** ***** ***** setters / getters for members ***** ****** *****/
257 
258  void order_by(const generator_base::order_func& func);
259 
260  void set_column_order(unsigned col, const generator_sort_array& func);
261 
262  template<typename Func>
263  void register_sorting_option(const int col, const Func& f)
264  {
265  set_column_order(col, {{
266  [f](int lhs, int rhs) { return f(lhs) < f(rhs); },
267  [f](int lhs, int rhs) { return f(lhs) > f(rhs); }
268  }});
269  }
270 
271  using translatable_sorter_func_t = std::function<std::string(const int)>;
272 
273  /** Registers a special sorting function specifically for translatable values. */
274  void register_translatable_sorting_option(const int col, translatable_sorter_func_t f);
275 
276  using order_pair = std::pair<int, sort_order::type>;
277 
278  /**
279  * Sorts the listbox by a pre-set sorting option. The corresponding header widget will also be toggled.
280  * The sorting option should already have been registered by @ref listbox::register_sorting_option().
281  *
282  * @param sort_by Pair of column index and sort direction. The column (first arguemnt)
283  * argument will be sorted in the specified direction (second argument)
284  *
285  * @param select_first If true, the first row post-sort will be selected. If false (default),
286  * the selected row will be maintained post-sort as per standard sorting
287  * functionality.
288  */
289  void set_active_sorting_option(const order_pair& sort_by, const bool select_first = false);
290 
291  const order_pair get_active_sorting_option();
292 
293  /** Deactivates all sorting toggle buttons at the top, making the list look like it's not sorted. */
294  void mark_as_unsorted();
295 
296  /** Registers a callback to be called when the active sorting option changes. */
297  void set_callback_order_change(std::function<void(unsigned, sort_order::type)> callback)
298  {
299  callback_order_change_ = callback;
300  }
301 
302 protected:
303  /***** ***** ***** ***** keyboard functions ***** ***** ***** *****/
304 
305  /** Inherited from scrollbar_container. */
306  void handle_key_up_arrow(SDL_Keymod modifier, bool& handled) override;
307 
308  /** Inherited from scrollbar_container. */
309  void handle_key_down_arrow(SDL_Keymod modifier, bool& handled) override;
310 
311  /** Inherited from scrollbar_container. */
312  void handle_key_left_arrow(SDL_Keymod modifier, bool& handled) override;
313 
314  /** Inherited from scrollbar_container. */
315  void handle_key_right_arrow(SDL_Keymod modifier, bool& handled) override;
316 
317 private:
318  /** See @ref widget::calculate_best_size. */
319  virtual point calculate_best_size() const override;
320 
321  enum KEY_SCROLL_DIRECTION { KEY_VERTICAL, KEY_HORIZONTAL };
322 
323  /** Helper to update visible area after a key event. */
324  void update_visible_area_on_key_event(const KEY_SCROLL_DIRECTION direction);
325 
326  /**
327  * @todo A listbox must have the following config parameters in the
328  * instantiation:
329  * - fixed row height?
330  * - fixed column width?
331  * and if so the following ways to set them
332  * - fixed depending on header ids
333  * - fixed depending on footer ids
334  * - fixed depending on first row ids
335  * - fixed depending on list (the user has to enter a list of ids)
336  *
337  * For now it's always fixed width depending on the first row.
338  */
339 
340  /**
341  * Finishes the building initialization of the widget.
342  *
343  * @param generator Generator for the list
344  * @param header Builder for the header.
345  * @param footer Builder for the footer.
346  * @param list_data The initial data to fill the listbox with.
347  */
348  void finalize(std::unique_ptr<generator_base> generator,
349  builder_grid_const_ptr header,
350  builder_grid_const_ptr footer,
351  const std::vector<widget_data>& list_data);
352 
353  /**
354  * Contains a pointer to the generator.
355  *
356  * The pointer is not owned by this class, it's stored in the content_grid_
357  * of the scrollbar_container super class and freed when it's grid is freed.
358  */
360 
361  const bool is_horizontal_;
362 
363  /** Contains the builder for the new items. */
365 
366  typedef std::vector<std::pair<selectable_item*, generator_sort_array>> torder_list;
367  torder_list orders_;
368 
369  std::function<void(unsigned, sort_order::type)> callback_order_change_;
370 
371  /**
372  * Resizes the content.
373  *
374  * The resize either happens due to resizing the content or invalidate the
375  * layout of the window.
376  *
377  * @param width_modification The wanted modification to the width:
378  * * negative values reduce width.
379  * * zero leave width as is.
380  * * positive values increase width.
381  * @param height_modification The wanted modification to the height:
382  * * negative values reduce height.
383  * * zero leave height as is.
384  * * positive values increase height.
385  * @param width_modification_pos
386  * @param height_modification_pos
387  */
388  void resize_content(const int width_modification,
389  const int height_modification,
390  const int width_modification_pos = -1,
391  const int height_modification_pos = -1);
392 
393  /**
394  * Resizes the content.
395  *
396  * The resize happens when a new row is added to the contents.
397  *
398  * @param row The new row added to the listbox.
399  */
400  void resize_content(const widget& row);
401 
402  /** Updates internal layout. */
403  void update_layout();
404 
405  /** Inherited from scrollbar_container. */
406  virtual void set_content_size(const point& origin, const point& size) override;
407 
408 public:
409  /** Static type getter that does not rely on the widget being constructed. */
410  static const std::string& type();
411 
412 private:
413  /** Inherited from styled_widget, implemented by REGISTER_WIDGET. */
414  virtual const std::string& get_control_type() const override;
415 
416  void order_by_column(unsigned column, widget& widget);
417 };
418 
419 // }---------- DEFINITION ---------{
420 
422 {
423  explicit listbox_definition(const config& cfg);
424 
426  {
427  explicit resolution(const config& cfg);
428 
430  };
431 };
432 
433 // }---------- BUILDER -----------{
434 
435 namespace implementation
436 {
437 /**
438  * @ingroup GUIWidgetWML
439  *
440  * A listbox is a control that holds several items of the same type.
441  * Normally the items in a listbox are ordered in rows, this version might allow more options for ordering the items in the future.
442  * The definition of a listbox contains the definition of its scrollbar:
443  * Key |Type |Default |Description
444  * -------------------------|------------------------------------|------------|-------------
445  * scrollbar | @ref guivartype_section "section" |mandatory |A grid containing the widgets for the scrollbar. The scrollbar has some special widgets so it can make default behavior for certain widgets.
446  * The resolution for a listbox also contains the following keys:
447  * ID (return value) |Type |Mandatory |Description
448  * -------------------------|--------------------|------------|-------------
449  * _begin | clickable |no |Moves the position to the beginning of the list.
450  * _line_up | clickable |no |Move the position one item up. (NOTE: if too many items to move per item it might be more items.)
451  * _half_page_up | clickable |no |Move the position half the number of the visible items up. (See note at _line_up.)
452  * _page_up | clickable |no |Move the position the number of visible items up. (See note at _line_up.)
453  * _end | clickable |no |Moves the position to the end of the list.
454  * _line_down | clickable |no |Move the position one item down.(See note at _line_up.)
455  * _half_page_down | clickable |no |Move the position half the number of the visible items down. (See note at _line_up.)
456  * _page_down | clickable |no |Move the position the number of visible items down. (See note at _line_up.)
457  * _scrollbar | vertical_scrollbar |yes |This is the scrollbar so the user can scroll through the list.
458  * A clickable is one of:
459  * * button
460  * * repeating_button
461  * The following states exist:
462  * * state_enabled - the listbox is enabled.
463  * * state_disabled - the listbox is disabled.
464  * List with the listbox specific variables:
465  * Key |Type |Default |Description
466  * -------------------------|------------------------------------------------|------------|-------------
467  * vertical_scrollbar_mode | @ref guivartype_scrollbar_mode "scrollbar_mode"|initial_auto|Determines whether or not to show the scrollbar.
468  * horizontal_scrollbar_mode| @ref guivartype_scrollbar_mode "scrollbar_mode"|initial_auto|Determines whether or not to show the scrollbar.
469  * header | @ref guivartype_grid "grid" |[] |Defines the grid for the optional header. (This grid will automatically get the id _header_grid.)
470  * footer | @ref guivartype_grid "grid" |[] |Defines the grid for the optional footer. (This grid will automatically get the id _footer_grid.)
471  * list_definition | @ref guivartype_section "section" |mandatory |This defines how a listbox item looks. It must contain the grid definition for 1 row of the list.
472  * list_data | @ref guivartype_section "section" |[] |A grid alike section which stores the initial data for the listbox. Every row must have the same number of columns as the 'list_definition'.
473  * has_minimum | @ref guivartype_bool "bool" |true |If false, less than one row can be selected.
474  * has_maximum | @ref guivartype_bool "bool" |true |If false, more than one row can be selected.
475  * In order to force widgets to be the same size inside a listbox, the widgets need to be inside a linked_group. Inside the list section there are only the following widgets allowed:
476  * * grid (to nest)
477  * * toggle_button
478  * * toggle_panel
479  */
481 {
482  explicit builder_listbox(const config& cfg);
483 
485 
486  virtual std::unique_ptr<widget> build() const override;
487 
490 
493 
495 
496  /**
497  * Listbox data.
498  *
499  * Contains a vector with the data to set in every cell, it's used to
500  * serialize the data in the config, so the config is no longer required.
501  */
502  std::vector<widget_data> list_data;
503 
504  bool has_minimum_, has_maximum_;
505 };
506 
507 /**
508  * @ingroup GUIWidgetWML
509  *
510  * A horizontal listbox is a control that holds several items of the same type. Normally the items in a listbox are ordered in rows, this version orders them in columns instead. The definition of a horizontal listbox is the same as for a normal listbox.
511  *
512  * List with the horizontal listbox specific variables:
513  * Key |Type |Default |Description
514  * -------------------------|------------------------------------------------|------------|-------------
515  * vertical_scrollbar_mode | @ref guivartype_scrollbar_mode "scrollbar_mode"|initial_auto|Determines whether or not to show the scrollbar.
516  * horizontal_scrollbar_mode| @ref guivartype_scrollbar_mode "scrollbar_mode"|initial_auto|Determines whether or not to show the scrollbar.
517  * list_definition | @ref guivartype_section "section" |mandatory |This defines how a listbox item looks. It must contain the grid definition for 1 column of the list.
518  * list_data | @ref guivartype_section "section" |[] |A grid alike section which stores the initial data for the listbox. Every row must have the same number of columns as the 'list_definition'.
519  * has_minimum | @ref guivartype_bool "bool" |true |If false, less than one row can be selected.
520  * has_maximum | @ref guivartype_bool "bool" |true |If false, more than one row can be selected.
521  *
522  * In order to force widgets to be the same size inside a horizontal listbox, the widgets need to be inside a linked_group.
523  * Inside the list section there are only the following widgets allowed:
524  * * grid (to nest)
525  * * toggle_button
526  * * toggle_panel
527  */
529 {
530  explicit builder_horizontal_listbox(const config& cfg);
531 
533 
534  virtual std::unique_ptr<widget> build() const override;
535 
538 
540 
541  /**
542  * Listbox data.
543  *
544  * Contains a vector with the data to set in every cell, it's used to
545  * serialize the data in the config, so the config is no longer required.
546  */
547  std::vector<widget_data> list_data;
548 
549  bool has_minimum_, has_maximum_;
550 };
551 
552 /**
553  * @ingroup GUIWidgetWML
554  *
555  * A grid listbox is a styled_widget that holds several items of the same type.
556  * Normally the items in a listbox are ordered in rows, this version orders them in a grid instead.
557  *
558  * List with the grid listbox specific variables:
559  * Key |Type |Default |Description
560  * -------------------------|------------------------------------------------|------------|-------------
561  * vertical_scrollbar_mode | @ref guivartype_scrollbar_mode "scrollbar_mode"|initial_auto|Determines whether or not to show the scrollbar.
562  * horizontal_scrollbar_mode| @ref guivartype_scrollbar_mode "scrollbar_mode"|initial_auto|Determines whether or not to show the scrollbar.
563  * list_definition | @ref guivartype_section "section" |mandatory |This defines how a listbox item looks. It must contain the grid definition for 1 column of the list.
564  * list_data | @ref guivartype_section "section" |[] |A grid alike section which stores the initial data for the listbox. Every row must have the same number of columns as the 'list_definition'.
565  * has_minimum | @ref guivartype_bool "bool" |true |If false, less than one row can be selected.
566  * has_maximum | @ref guivartype_bool "bool" |true |If false, more than one row can be selected.
567  *
568  * In order to force widgets to be the same size inside a horizontal listbox, the widgets need to be inside a linked_group.
569  * Inside the list section there are only the following widgets allowed:
570  * * grid (to nest)
571  * * toggle_button
572  * * toggle_panel
573  */
575 {
576  explicit builder_grid_listbox(const config& cfg);
577 
579 
580  virtual std::unique_ptr<widget> build() const override;
581 
584 
586 
587  /**
588  * Listbox data.
589  *
590  * Contains a vector with the data to set in every cell, it's used to
591  * serialize the data in the config, so the config is no longer required.
592  */
593  std::vector<widget_data> list_data;
594 
595  bool has_minimum_, has_maximum_;
596 };
597 
598 } // namespace implementation
599 
600 // }------------ END --------------
601 
602 } // namespace gui2
Base class of a resolution, contains the common keys for a resolution.
std::vector< widget_data > list_data
Listbox data.
Definition: listbox.hpp:547
scrollbar_container::scrollbar_mode horizontal_scrollbar_mode
Definition: listbox.hpp:489
A grid listbox is a styled_widget that holds several items of the same type.
Definition: listbox.hpp:574
scrollbar_container::scrollbar_mode horizontal_scrollbar_mode
Definition: listbox.hpp:537
std::pair< int, sort_order::type > order_pair
Definition: listbox.hpp:276
Base class for all widgets.
Definition: widget.hpp:53
std::function< bool(unsigned, unsigned)> order_func
Definition: generator.hpp:249
std::string_view data
Definition: picture.cpp:206
void clear(const std::string &key)
Definition: general.cpp:190
std::function< void(unsigned, sort_order::type)> callback_order_change_
Definition: listbox.hpp:369
bool select_last_row(const bool select=true)
Does exactly as advertised: selects the list&#39;s last row.
Definition: listbox.hpp:192
A horizontal listbox is a control that holds several items of the same type.
Definition: listbox.hpp:528
torder_list orders_
Definition: listbox.hpp:367
Generic file dialog.
The listbox class.
Definition: listbox.hpp:45
Base container class.
Definition: grid.hpp:31
scrollbar_container::scrollbar_mode vertical_scrollbar_mode
Definition: listbox.hpp:582
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
std::map< std::string, t_string > widget_item
Definition: widget.hpp:32
scrollbar_container::scrollbar_mode horizontal_scrollbar_mode
Definition: listbox.hpp:583
Abstract base class for the generator.
Definition: generator.hpp:39
std::vector< std::pair< selectable_item *, generator_sort_array > > torder_list
Definition: listbox.hpp:366
builder_grid_const_ptr list_builder_
Contains the builder for the new items.
Definition: listbox.hpp:364
placement
Determines how the items are placed.
Definition: generator.hpp:49
Basic template class to generate new items.
virtual std::unique_ptr< widget > build() const =0
std::vector< widget_data > list_data
Listbox data.
Definition: listbox.hpp:502
Base class for creating containers with one or two scrollbar(s).
A listbox is a control that holds several items of the same type.
Definition: listbox.hpp:480
std::vector< widget_data > list_data
Listbox data.
Definition: listbox.hpp:593
std::shared_ptr< const builder_grid > builder_grid_const_ptr
std::array< generator_base::order_func, 2 > generator_sort_array
Definition: generator.hpp:381
Holds a 2D point.
Definition: point.hpp:24
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:72
void set_callback_order_change(std::function< void(unsigned, sort_order::type)> callback)
Registers a callback to be called when the active sorting option changes.
Definition: listbox.hpp:297
std::shared_ptr< builder_grid > builder_grid_ptr
scrollbar_mode
The way to handle the showing or hiding of the scrollbar.
#define f
const bool is_horizontal_
Definition: listbox.hpp:361
point resolution()
Definition: general.cpp:395
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:35
std::unique_ptr< window > build(const builder_window::window_resolution &definition)
Builds a window.
generator_base * generator_
Contains a pointer to the generator.
Definition: listbox.hpp:359
void register_sorting_option(const int col, const Func &f)
Definition: listbox.hpp:263
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:60
std::function< std::string(const int)> translatable_sorter_func_t
Definition: listbox.hpp:271
scrollbar_container::scrollbar_mode vertical_scrollbar_mode
Definition: listbox.hpp:536
scrollbar_container::scrollbar_mode vertical_scrollbar_mode
Definition: listbox.hpp:488
Contains the implementation details for lexical_cast and shouldn&#39;t be used directly.
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:414