The Battle for Wesnoth  1.17.23+dev
listbox.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2008 - 2023
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 
18 #include "gui/widgets/listbox.hpp"
19 
20 #include "gettext.hpp"
23 #include "gui/core/log.hpp"
29 #include "gui/widgets/settings.hpp"
31 #include "gui/widgets/viewport.hpp"
33 #include "gui/widgets/window.hpp"
34 #include "sdl/rect.hpp"
35 #include "wml_exception.hpp"
36 #include <functional>
37 #include <optional>
38 
39 #define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
40 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
41 
42 namespace gui2
43 {
44 // ------------ WIDGET -----------{
45 
46 REGISTER_WIDGET(listbox)
47 REGISTER_WIDGET3(listbox_definition, horizontal_listbox, nullptr)
48 REGISTER_WIDGET3(listbox_definition, grid_listbox, nullptr)
49 
50 listbox::listbox(const implementation::builder_styled_widget& builder,
51  const generator_base::placement placement,
52  builder_grid_ptr list_builder)
53  : scrollbar_container(builder, type())
54  , generator_(nullptr)
55  , is_horizontal_(placement == generator_base::horizontal_list)
56  , list_builder_(list_builder)
57  , orders_()
58  , callback_order_change_()
59 {
60 }
61 
63 {
64  assert(generator_);
65  grid& row = generator_->create_item(index, *list_builder_, item, std::bind(&listbox::list_item_clicked, this, std::placeholders::_1));
66 
67  resize_content(row);
68 
69  return row;
70 }
71 
73 {
74  assert(generator_);
75  grid& row = generator_->create_item(index, *list_builder_, data, std::bind(&listbox::list_item_clicked, this, std::placeholders::_1));
76 
77  resize_content(row);
78 
79  return row;
80 }
81 
82 void listbox::remove_row(const unsigned row, unsigned count)
83 {
84  assert(generator_);
85 
86  if(row >= get_item_count()) {
87  return;
88  }
89 
90  if(!count || count + row > get_item_count()) {
91  count = get_item_count() - row;
92  }
93 
94  int height_reduced = 0;
95  int width_reduced = 0;
96 
97  // TODO: Fix this for horizontal listboxes
98  // Note the we have to use content_grid_ and cannot use "_list_grid" which is what generator_ uses.
99  int row_pos_y = is_horizontal_ ? -1 : generator_->item(row).get_y() - content_grid_->get_y();
100  int row_pos_x = is_horizontal_ ? -1 : 0;
101 
102  for(; count; --count) {
104  if(is_horizontal_) {
105  width_reduced += generator_->item(row).get_width();
106  } else {
107  height_reduced += generator_->item(row).get_height();
108  }
109  }
110 
111  generator_->delete_item(row);
112  }
113 
114  if((height_reduced != 0 || width_reduced != 0) && get_item_count() != 0) {
115  resize_content(-width_reduced, -height_reduced, row_pos_x, row_pos_y);
116  } else {
118  }
119 }
120 
122 {
123  generator_->clear();
125 }
126 
127 unsigned listbox::get_item_count() const
128 {
129  assert(generator_);
130  return generator_->get_item_count();
131 }
132 
133 void listbox::set_row_active(const unsigned row, const bool active)
134 {
135  assert(generator_);
136  generator_->item(row).set_active(active);
137 }
138 
139 void listbox::set_row_shown(const unsigned row, const bool shown)
140 {
141  assert(generator_);
142 
143  window* window = get_window();
144  assert(window);
145 
146  const int selected_row = get_selected_row();
147 
148  bool resize_needed = false;
149 
150  // Local scope for invalidate_layout_blocker
151  {
152  window::invalidate_layout_blocker invalidate_layout_blocker(*window);
153 
154  generator_->set_item_shown(row, shown);
155 
156  point best_size = generator_->calculate_best_size();
157  generator_->place(generator_->get_origin(), {std::max(best_size.x, content_visible_area().w), best_size.y});
158 
159  resize_needed = !content_resize_request();
160  }
161 
162  if(resize_needed) {
164  } else {
165  content_grid_->set_visible_rectangle(content_visible_area());
166  queue_redraw(); // TODO: draw_manager - does this get the right area?
167  }
168 
169  if(selected_row != get_selected_row()) {
170  fire(event::NOTIFY_MODIFIED, *this, nullptr);
171  }
172 }
173 
174 void listbox::set_row_shown(const boost::dynamic_bitset<>& shown)
175 {
176  assert(generator_);
177  assert(shown.size() == get_item_count());
178 
179  if(generator_->get_items_shown() == shown) {
180  LOG_GUI_G << LOG_HEADER << " returning early";
181  return;
182  }
183 
184  window* window = get_window();
185  assert(window);
186 
187  const int selected_row = get_selected_row();
188 
189  bool resize_needed = false;
190 
191  // Local scope for invalidate_layout_blocker
192  {
193  window::invalidate_layout_blocker invalidate_layout_blocker(*window);
194 
195  for(std::size_t i = 0; i < shown.size(); ++i) {
196  generator_->set_item_shown(i, shown[i]);
197  }
198 
199  point best_size = generator_->calculate_best_size();
200  generator_->place(generator_->get_origin(), {std::max(best_size.x, content_visible_area().w), best_size.y});
201 
202  resize_needed = !content_resize_request();
203  }
204 
205  if(resize_needed) {
207  } else {
208  content_grid_->set_visible_rectangle(content_visible_area());
209  queue_redraw(); // TODO: draw_manager - does this get the right area?
210  }
211 
212  if(selected_row != get_selected_row()) {
213  fire(event::NOTIFY_MODIFIED, *this, nullptr);
214  }
215 }
216 
217 boost::dynamic_bitset<> listbox::get_rows_shown() const
218 {
219  return generator_->get_items_shown();
220 }
221 
223 {
224  for(std::size_t i = 0; i < get_item_count(); i++) {
225  if(generator_->get_item_shown(i)) {
226  return true;
227  }
228  }
229 
230  return false;
231 }
232 
233 const grid* listbox::get_row_grid(const unsigned row) const
234 {
235  assert(generator_);
236  // rename this function and can we return a reference??
237  return &generator_->item(row);
238 }
239 
240 grid* listbox::get_row_grid(const unsigned row)
241 {
242  assert(generator_);
243  return &generator_->item(row);
244 }
245 
246 bool listbox::select_row(const unsigned row, const bool select)
247 {
248  if(row >= get_item_count()) {
249  throw std::invalid_argument("invalid listbox index");
250  }
251  assert(generator_);
252 
253  unsigned int before = generator_->get_selected_item_count();
254  generator_->select_item(row, select);
255 
256  return before != generator_->get_selected_item_count();
257 }
258 
259 bool listbox::select_row_at(const unsigned row, const bool select)
260 {
261  assert(generator_);
262  return select_row(generator_->get_item_at_ordered(row), select);
263 }
264 
265 bool listbox::row_selected(const unsigned row)
266 {
267  assert(generator_);
268  return generator_->is_selected(row);
269 }
270 
272 {
273  assert(generator_);
274  return generator_->get_selected_item();
275 }
276 
278 {
279  assert(generator_);
280 
281  /** @todo Hack to capture the keyboard focus. */
282  get_window()->keyboard_capture(this);
283 
284  for(std::size_t i = 0; i < generator_->get_item_count(); ++i) {
285  if(generator_->item(i).has_widget(caller)) {
286  toggle_button* checkbox = dynamic_cast<toggle_button*>(&caller);
287 
288  if(checkbox != nullptr) {
289  generator_->select_item(i, checkbox->get_value_bool());
290  } else {
292  }
293 
294  // TODO: enable this code once toggle_panel::set_value dispatches
295  // NOTIFY_MODIFED events. See comment in said function for more details.
296 #if 0
297  selectable_item& selectable = dynamic_cast<selectable_item&>(caller);
298 
299  generator_->select_item(i, selectable.get_value_bool());
300 #endif
301 
302  fire(event::NOTIFY_MODIFIED, *this, nullptr);
303  break;
304  }
305  }
306 
307  const int selected_item = generator_->get_selected_item();
308  if(selected_item == -1) {
309  return;
310  }
311 
312  const rect& visible = content_visible_area();
313  rect r = generator_->item(selected_item).get_rectangle();
314 
315  if(visible.overlaps(r)) {
316  r.x = visible.x;
317  r.w = visible.w;
318 
320  }
321 }
322 
323 void listbox::set_self_active(const bool /*active*/)
324 {
325  /* DO NOTHING */
326 }
327 
329 {
331  return true;
332  }
333 
334  if(get_size() == point()) {
335  return false;
336  }
337 
338  if(content_resize_request(true)) {
339  content_grid_->set_visible_rectangle(content_visible_area());
340  queue_redraw(); // TODO: draw_manager - does this get the right area?
341  return true;
342  }
343 
344  return false;
345 }
346 
347 void listbox::place(const point& origin, const point& size)
348 {
349  std::optional<unsigned> vertical_scrollbar_position, horizontal_scrollbar_position;
350 
351  // Check if this is the first time placing the list box
352  if(get_origin() != point {-1, -1}) {
353  vertical_scrollbar_position = get_vertical_scrollbar_item_position();
354  horizontal_scrollbar_position = get_horizontal_scrollbar_item_position();
355  }
356 
357  // Inherited.
359 
360  const int selected_item = generator_->get_selected_item();
361  if(vertical_scrollbar_position && horizontal_scrollbar_position) {
362  LOG_GUI_L << LOG_HEADER << " restoring scroll position";
363 
364  set_vertical_scrollbar_item_position(*vertical_scrollbar_position);
365  set_horizontal_scrollbar_item_position(*horizontal_scrollbar_position);
366  } else if(selected_item != -1) {
367  LOG_GUI_L << LOG_HEADER << " making the initially selected item visible";
368 
369  const SDL_Rect& visible = content_visible_area();
370  SDL_Rect rect = generator_->item(selected_item).get_rectangle();
371 
372  rect.x = visible.x;
373  rect.w = visible.w;
374 
376  }
377 }
378 
379 void listbox::resize_content(const int width_modification,
380  const int height_modification,
381  const int width_modification_pos,
382  const int height_modification_pos)
383 {
384  DBG_GUI_L << LOG_HEADER << " current size " << content_grid()->get_size() << " width_modification "
385  << width_modification << " height_modification " << height_modification << ".";
386 
388  width_modification, height_modification, width_modification_pos, height_modification_pos))
389  {
390  // Calculate new size.
392  size.x += width_modification;
393  size.y += height_modification;
394 
395  // Set new size.
397  update_layout();
398 
399  // If the content grows assume it "overwrites" the old content.
400  if(width_modification < 0 || height_modification < 0) {
401  queue_redraw();
402  }
403 
404  DBG_GUI_L << LOG_HEADER << " succeeded.";
405  } else {
406  DBG_GUI_L << LOG_HEADER << " failed.";
407  }
408 }
409 
411 {
412  if(row.get_visible() == visibility::invisible) {
413  return;
414  }
415 
416  DBG_GUI_L << LOG_HEADER << " current size " << content_grid()->get_size() << " row size " << row.get_best_size()
417  << ".";
418 
419  const point content = content_grid()->get_size();
420  point size = row.get_best_size();
421 
422  if(size.x < content.x) {
423  size.x = 0;
424  } else {
425  size.x -= content.x;
426  }
427 
428  resize_content(size.x, size.y);
429 }
430 
432 {
433  // Get the size from the base class, then add any extra space for the header and footer.
435 
436  if(const grid* header = find_widget<const grid>(&get_grid(), "_header_grid", false, false)) {
437  result.y += header->get_best_size().y;
438  }
439 
440  if(const grid* footer = find_widget<const grid>(&get_grid(), "_footer_grid", false, false)) {
441  result.y += footer->get_best_size().y;
442  }
443 
444  return result;
445 }
446 
448 {
449  const SDL_Rect& visible = content_visible_area();
451 
452  // When scrolling make sure the new items are visible...
453  if(direction == KEY_VERTICAL) {
454  // ...but leave the horizontal scrollbar position.
455  rect.x = visible.x;
456  rect.w = visible.w;
457  } else {
458  // ...but leave the vertical scrollbar position.
459  rect.y = visible.y;
460  rect.h = visible.h;
461  }
462 
464 
465  fire(event::NOTIFY_MODIFIED, *this, nullptr);
466 }
467 
468 void listbox::handle_key_up_arrow(SDL_Keymod modifier, bool& handled)
469 {
470  assert(generator_);
471 
472  generator_->handle_key_up_arrow(modifier, handled);
473 
474  if(handled) {
476  } else {
477  // Inherited.
478  scrollbar_container::handle_key_up_arrow(modifier, handled);
479  }
480 }
481 
482 void listbox::handle_key_down_arrow(SDL_Keymod modifier, bool& handled)
483 {
484  assert(generator_);
485 
486  generator_->handle_key_down_arrow(modifier, handled);
487 
488  if(handled) {
490  } else {
491  // Inherited.
492  scrollbar_container::handle_key_up_arrow(modifier, handled);
493  }
494 }
495 
496 void listbox::handle_key_left_arrow(SDL_Keymod modifier, bool& handled)
497 {
498  assert(generator_);
499 
500  generator_->handle_key_left_arrow(modifier, handled);
501 
502  // Inherited.
503  if(handled) {
505  } else {
507  }
508 }
509 
510 void listbox::handle_key_right_arrow(SDL_Keymod modifier, bool& handled)
511 {
512  assert(generator_);
513 
514  generator_->handle_key_right_arrow(modifier, handled);
515 
516  // Inherited.
517  if(handled) {
519  } else {
521  }
522 }
523 
524 void listbox::finalize(std::unique_ptr<generator_base> generator,
525  builder_grid_const_ptr header,
526  builder_grid_const_ptr footer,
527  const std::vector<widget_data>& list_data)
528 {
529  // "Inherited."
531 
532  if(header) {
533  swap_grid(&get_grid(), content_grid(), header->build(), "_header_grid");
534  }
535 
536  grid& p = find_widget<grid>(this, "_header_grid", false);
537 
538  for(unsigned i = 0, max = std::max(p.get_cols(), p.get_rows()); i < max; ++i) {
539  //
540  // TODO: I had to change this to case to a toggle_button in order to use a signal handler.
541  // Should probably look into a way to make it more general like it was before (used to be
542  // cast to selectable_item).
543  //
544  // - vultraz, 2017-08-23
545  //
546  if(toggle_button* selectable = find_widget<toggle_button>(&p, "sort_" + std::to_string(i), false, false)) {
547  // Register callback to sort the list.
548  connect_signal_notify_modified(*selectable, std::bind(&listbox::order_by_column, this, i, std::placeholders::_1));
549 
550  if(orders_.size() < max) {
551  orders_.resize(max);
552  }
553 
554  orders_[i].first = selectable;
555  }
556  }
557 
558  if(footer) {
559  swap_grid(&get_grid(), content_grid(), footer->build(), "_footer_grid");
560  }
561 
562  // Save our *non-owning* pointer before this gets moved into the grid.
563  generator_ = generator.get();
564  assert(generator_);
565 
566  generator->create_items(-1, *list_builder_, list_data, std::bind(&listbox::list_item_clicked, this, std::placeholders::_1));
567  swap_grid(nullptr, content_grid(), std::move(generator), "_list_grid");
568 }
569 
570 void listbox::order_by_column(unsigned column, widget& widget)
571 {
572  selectable_item& selectable = dynamic_cast<selectable_item&>(widget);
573  if(column >= orders_.size()) {
574  return;
575  }
576 
577  for(auto& pair : orders_) {
578  if(pair.first != nullptr && pair.first != &selectable) {
579  pair.first->set_value(static_cast<unsigned int>(sort_order::type::none));
580  }
581  }
582 
583  sort_order::type order = sort_order::get_enum(selectable.get_value()).value_or(sort_order::type::none);
584 
585  if(static_cast<unsigned int>(order) > orders_[column].second.size()) {
586  return;
587  }
588 
589  if(order == sort_order::type::none) {
590  order_by(std::less<unsigned>());
591  } else {
592  order_by(orders_[column].second[static_cast<unsigned int>(order) - 1]);
593  }
594 
595  if(callback_order_change_ != nullptr) {
596  callback_order_change_(column, order);
597  }
598 }
599 
601 {
602  generator_->set_order(func);
603 
604  update_layout();
605 }
606 
607 void listbox::set_column_order(unsigned col, const generator_sort_array& func)
608 {
609  if(col >= orders_.size()) {
610  orders_.resize(col + 1);
611  }
612 
613  orders_[col].second = func;
614 }
615 
617 {
618  set_column_order(col, {{
619  [f](int lhs, int rhs) { return translation::icompare(f(lhs), f(rhs)) < 0; },
620  [f](int lhs, int rhs) { return translation::icompare(f(lhs), f(rhs)) > 0; }
621  }});
622 }
623 
624 void listbox::set_active_sorting_option(const order_pair& sort_by, const bool select_first)
625 {
626  // TODO: should this be moved to a public header_grid() getter function?
627  grid& header_grid = find_widget<grid>(this, "_header_grid", false);
628 
629  selectable_item& w = find_widget<selectable_item>(&header_grid, "sort_" + std::to_string(sort_by.first), false);
630 
631  // Set the sorting toggle widgets' value (in this case, its state) to the given sorting
632  // order. This is necessary since the widget's value is used to determine the order in
633  // @ref order_by_column in lieu of a direction being passed directly.
634  w.set_value(static_cast<int>(sort_by.second));
635 
636  order_by_column(sort_by.first, dynamic_cast<widget&>(w));
637 
638  if(select_first && generator_->get_item_count() > 0) {
639  select_row_at(0);
640  }
641 }
642 
644 {
645  for(unsigned int column = 0; column < orders_.size(); ++column) {
646  selectable_item* w = orders_[column].first;
647  sort_order::type sort = sort_order::get_enum(w->get_value()).value_or(sort_order::type::none);
648 
649  if(w && sort != sort_order::type::none) {
650  return std::pair(column, sort);
651  }
652  }
653 
654  return std::pair(-1, sort_order::type::none);
655 }
656 
658 {
659  for(auto& pair : orders_) {
660  if(pair.first != nullptr) {
661  pair.first->set_value(static_cast<unsigned int>(sort_order::type::none));
662  }
663  }
664 }
665 
666 void listbox::set_content_size(const point& origin, const point& size)
667 {
668  /** @todo This function needs more testing. */
669  assert(content_grid());
670 
671  const int best_height = content_grid()->get_best_size().y;
672  const point s(size.x, size.y < best_height ? size.y : best_height);
673 
674  content_grid()->place(origin, s);
675 }
676 
678 {
679  assert(content_grid());
680 
681  // If we haven't initialized, or have no content, just return.
683  if(size.x <= 0 || size.y <= 0) {
684  return;
685  }
686 
688 
689  const SDL_Rect& visible = content_visible_area_;
691 
692  queue_redraw();
693 }
694 
695 // }---------- DEFINITION ---------{
696 
699 {
700  DBG_GUI_P << "Parsing listbox " << id;
701 
702  load_resolutions<resolution>(cfg);
703 }
704 
706  : resolution_definition(cfg)
707  , grid(nullptr)
708 {
709  // Note the order should be the same as the enum state_t in listbox.hpp.
710  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_enabled", _("Missing required state for listbox")));
711  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_disabled", _("Missing required state for listbox")));
712 
713  auto child = VALIDATE_WML_CHILD(cfg, "grid", _("No grid defined."));
714  grid = std::make_shared<builder_grid>(child);
715 }
716 
717 namespace implementation
718 {
719 static std::vector<widget_data> parse_list_data(const config& data, const unsigned int req_cols)
720 {
721  std::vector<widget_data> list_data;
722 
723  for(const auto& row : data.child_range("row")) {
724  auto cols = row.child_range("column");
725 
726  VALIDATE(static_cast<unsigned>(cols.size()) == req_cols,
727  _("'list_data' must have the same number of columns as the 'list_definition'.")
728  );
729 
730  for(const auto& c : cols) {
731  list_data.emplace_back();
732 
733  for(const auto& i : c.attribute_range()) {
734  list_data.back()[""][i.first] = i.second;
735  }
736 
737  for(const auto& w : c.child_range("widget")) {
738  VALIDATE(w.has_attribute("id"), missing_mandatory_wml_key("[list_data][row][column][widget]", "id"));
739 
740  for(const auto& i : w.attribute_range()) {
741  list_data.back()[w["id"]][i.first] = i.second;
742  }
743  }
744  }
745  }
746 
747  return list_data;
748 }
749 
750 builder_listbox::builder_listbox(const config& cfg)
751  : builder_styled_widget(cfg)
752  , vertical_scrollbar_mode(get_scrollbar_mode(cfg["vertical_scrollbar_mode"]))
753  , horizontal_scrollbar_mode(get_scrollbar_mode(cfg["horizontal_scrollbar_mode"]))
754  , header(nullptr)
755  , footer(nullptr)
756  , list_builder(nullptr)
757  , list_data()
758  , has_minimum_(cfg["has_minimum"].to_bool(true))
759  , has_maximum_(cfg["has_maximum"].to_bool(true))
760 {
761  if(auto h = cfg.optional_child("header")) {
762  header = std::make_shared<builder_grid>(*h);
763  }
764 
765  if(auto f = cfg.optional_child("footer")) {
766  footer = std::make_shared<builder_grid>(*f);
767  }
768 
769  auto l = cfg.optional_child("list_definition");
770 
771  VALIDATE(l, _("No list defined."));
772 
773  list_builder = std::make_shared<builder_grid>(*l);
774  assert(list_builder);
775 
776  VALIDATE(list_builder->rows == 1, _("A 'list_definition' should contain one row."));
777 
778  if(cfg.has_child("list_data")) {
779  list_data = parse_list_data(cfg.mandatory_child("list_data"), list_builder->cols);
780  }
781 }
782 
783 std::unique_ptr<widget> builder_listbox::build() const
784 {
785  auto widget = std::make_unique<listbox>(*this, generator_base::vertical_list, list_builder);
786 
787  widget->set_vertical_scrollbar_mode(vertical_scrollbar_mode);
788  widget->set_horizontal_scrollbar_mode(horizontal_scrollbar_mode);
789 
790  DBG_GUI_G << "Window builder: placed listbox '" << id << "' with definition '" << definition << "'.";
791 
792  const auto conf = widget->cast_config_to<listbox_definition>();
793  assert(conf);
794 
795  widget->init_grid(*conf->grid);
796 
798  widget->finalize(std::move(generator), header, footer, list_data);
799 
800  return widget;
801 }
802 
804  : builder_styled_widget(cfg)
805  , vertical_scrollbar_mode(get_scrollbar_mode(cfg["vertical_scrollbar_mode"]))
806  , horizontal_scrollbar_mode(get_scrollbar_mode(cfg["horizontal_scrollbar_mode"]))
807  , list_builder(nullptr)
808  , list_data()
809  , has_minimum_(cfg["has_minimum"].to_bool(true))
810  , has_maximum_(cfg["has_maximum"].to_bool(true))
811 {
812  auto l = cfg.optional_child("list_definition");
813 
814  VALIDATE(l, _("No list defined."));
815 
816  list_builder = std::make_shared<builder_grid>(*l);
817  assert(list_builder);
818 
819  VALIDATE(list_builder->rows == 1, _("A 'list_definition' should contain one row."));
820 
821  if(cfg.has_child("list_data")) {
822  list_data = parse_list_data(cfg.mandatory_child("list_data"), list_builder->cols);
823  }
824 }
825 
826 std::unique_ptr<widget> builder_horizontal_listbox::build() const
827 {
828  auto widget = std::make_unique<listbox>(*this, generator_base::horizontal_list, list_builder);
829 
830  widget->set_vertical_scrollbar_mode(vertical_scrollbar_mode);
831  widget->set_horizontal_scrollbar_mode(horizontal_scrollbar_mode);
832 
833  DBG_GUI_G << "Window builder: placed listbox '" << id << "' with definition '" << definition << "'.";
834 
835  const auto conf = widget->cast_config_to<listbox_definition>();
836  assert(conf);
837 
838  widget->init_grid(*conf->grid);
839 
841  widget->finalize(std::move(generator), nullptr, nullptr, list_data);
842 
843  return widget;
844 }
845 
847  : builder_styled_widget(cfg)
848  , vertical_scrollbar_mode(get_scrollbar_mode(cfg["vertical_scrollbar_mode"]))
849  , horizontal_scrollbar_mode(get_scrollbar_mode(cfg["horizontal_scrollbar_mode"]))
850  , list_builder(nullptr)
851  , list_data()
852  , has_minimum_(cfg["has_minimum"].to_bool(true))
853  , has_maximum_(cfg["has_maximum"].to_bool(true))
854 {
855  auto l = cfg.optional_child("list_definition");
856 
857  VALIDATE(l, _("No list defined."));
858 
859  list_builder = std::make_shared<builder_grid>(*l);
860  assert(list_builder);
861 
862  VALIDATE(list_builder->rows == 1, _("A 'list_definition' should contain one row."));
863 
864  if(cfg.has_child("list_data")) {
865  list_data = parse_list_data(cfg.mandatory_child("list_data"), list_builder->cols);
866  }
867 }
868 
869 std::unique_ptr<widget> builder_grid_listbox::build() const
870 {
871  auto widget = std::make_unique<listbox>(*this, generator_base::table, list_builder);
872 
873  widget->set_vertical_scrollbar_mode(vertical_scrollbar_mode);
874  widget->set_horizontal_scrollbar_mode(horizontal_scrollbar_mode);
875 
876  DBG_GUI_G << "Window builder: placed listbox '" << id << "' with definition '" << definition << "'.";
877 
878  const auto conf = widget->cast_config_to<listbox_definition>();
879  assert(conf);
880 
881  widget->init_grid(*conf->grid);
882 
884  widget->finalize(std::move(generator), nullptr, nullptr, list_data);
885 
886  return widget;
887 }
888 
889 } // namespace implementation
890 
891 // }------------ END --------------
892 
893 } // namespace gui2
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:161
config & mandatory_child(config_key_type key, int n=0)
Returns the nth child with the given key, or throws an error if there is none.
Definition: config.cpp:371
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:321
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Euivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:389
const grid & get_grid() const
bool fire(const ui_event event, widget &target)
Fires an event which has no extra parameters.
Definition: dispatcher.cpp:76
Abstract base class for the generator.
Definition: generator.hpp:40
std::function< bool(unsigned, unsigned)> order_func
Definition: generator.hpp:249
virtual void set_order(const order_func &order)=0
virtual void handle_key_left_arrow(SDL_Keymod modifier, bool &handled)=0
Left arrow key pressed.
virtual unsigned get_selected_item_count() const =0
Returns the number of selected items.
virtual void handle_key_right_arrow(SDL_Keymod modifier, bool &handled)=0
Right arrow key pressed.
virtual void set_item_shown(const unsigned index, const bool show)=0
Shows or hides an item.
virtual void delete_item(const unsigned index)=0
Deletes an item.
boost::dynamic_bitset get_items_shown() const
Returns the visibility of all the items as a bit set.
Definition: generator.hpp:121
virtual void handle_key_up_arrow(SDL_Keymod modifier, bool &handled)=0
Up arrow key pressed.
virtual void place(const point &origin, const point &size) override=0
See widget::place.
virtual grid & create_item(const int index, const builder_grid &list_builder, const widget_item &item_data, const std::function< void(widget &)> &callback)=0
Creates a new item.
virtual point calculate_best_size() const override=0
See widget::calculate_best_size.
virtual void handle_key_down_arrow(SDL_Keymod modifier, bool &handled)=0
Down arrow key pressed.
virtual void select_item(const unsigned index, const bool select)=0
(De)selects an item.
virtual grid & item(const unsigned index)=0
Gets the grid of an item.
virtual unsigned get_item_count() const =0
Returns the number of items.
static std::unique_ptr< generator_base > build(const bool has_minimum, const bool has_maximum, const placement placement, const bool select)
Create a new generator.
Definition: generator.cpp:1163
void toggle_item(const unsigned index)
Toggles the selection state of an item.
Definition: generator.hpp:97
virtual bool is_selected(const unsigned index) const =0
Returns whether the item is selected.
virtual bool get_item_shown(const unsigned index) const =0
Returns whether the item is shown.
virtual unsigned get_item_at_ordered(unsigned index_ordered) const =0
If a sort-order is being applied, maps from sorted to unsorted indicies.
virtual void clear()=0
Deletes all items.
virtual int get_selected_item() const =0
Returns the selected item.
Basic template class to generate new items.
virtual void create_items(const int index, const builder_grid &list_builder, const std::vector< widget_data > &data, const std::function< void(widget &)> &callback) override
Inherited from generator_base.
Base container class.
Definition: grid.hpp:32
void set_active(const bool active)
Activates all children.
Definition: grid.cpp:167
virtual bool has_widget(const widget &widget) const override
See widget::has_widget.
Definition: grid.cpp:656
virtual void place(const point &origin, const point &size) override
See widget::place.
Definition: grid.cpp:484
virtual void set_visible_rectangle(const SDL_Rect &rectangle) override
See widget::set_visible_rectangle.
Definition: grid.cpp:608
The listbox class.
Definition: listbox.hpp:46
void update_layout()
Updates internal layout.
Definition: listbox.cpp:677
const bool is_horizontal_
Definition: listbox.hpp:361
void list_item_clicked(widget &caller)
Function to call after the user clicked on a row.
Definition: listbox.cpp:277
void mark_as_unsorted()
Deactivates all sorting toggle buttons at the top, making the list look like it's not sorted.
Definition: listbox.cpp:657
virtual point calculate_best_size() const override
See widget::calculate_best_size.
Definition: listbox.cpp:431
void handle_key_right_arrow(SDL_Keymod modifier, bool &handled) override
Inherited from scrollbar_container.
Definition: listbox.cpp:510
void set_row_active(const unsigned row, const bool active)
Makes a row active or inactive.
Definition: listbox.cpp:133
torder_list orders_
Definition: listbox.hpp:367
void set_row_shown(const unsigned row, const bool shown)
Makes a row visible or invisible.
Definition: listbox.cpp:139
void set_active_sorting_option(const order_pair &sort_by, const bool select_first=false)
Sorts the listbox by a pre-set sorting option.
Definition: listbox.cpp:624
void handle_key_left_arrow(SDL_Keymod modifier, bool &handled) override
Inherited from scrollbar_container.
Definition: listbox.cpp:496
void order_by_column(unsigned column, widget &widget)
Definition: listbox.cpp:570
std::pair< int, sort_order::type > order_pair
Definition: listbox.hpp:276
void resize_content(const int width_modification, const int height_modification, const int width_modification_pos=-1, const int height_modification_pos=-1)
Resizes the content.
Definition: listbox.cpp:379
void order_by(const generator_base::order_func &func)
Definition: listbox.cpp:600
virtual void set_self_active(const bool active) override
See container_base::set_self_active.
Definition: listbox.cpp:323
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:62
std::function< std::string(const int)> translatable_sorter_func_t
Definition: listbox.hpp:271
const grid * get_row_grid(const unsigned row) const
Returns the grid of the wanted row.
Definition: listbox.cpp:233
void register_translatable_sorting_option(const int col, translatable_sorter_func_t f)
Registers a special sorting function specifically for translatable values.
Definition: listbox.cpp:616
virtual void set_content_size(const point &origin, const point &size) override
Inherited from scrollbar_container.
Definition: listbox.cpp:666
virtual void place(const point &origin, const point &size) override
See widget::place.
Definition: listbox.cpp:347
void update_visible_area_on_key_event(const KEY_SCROLL_DIRECTION direction)
Helper to update visible area after a key event.
Definition: listbox.cpp:447
generator_base * generator_
Contains a pointer to the generator.
Definition: listbox.hpp:359
std::function< void(unsigned, sort_order::type)> callback_order_change_
Definition: listbox.hpp:369
bool select_row(const unsigned row, const bool select=true)
Selects a row.
Definition: listbox.cpp:246
bool update_content_size()
Request to update the size of the content after changing the content.
Definition: listbox.cpp:328
boost::dynamic_bitset get_rows_shown() const
Returns a list of visible rows.
Definition: listbox.cpp:217
bool any_rows_shown() const
Definition: listbox.cpp:222
void finalize(std::unique_ptr< generator_base > generator, builder_grid_const_ptr header, builder_grid_const_ptr footer, const std::vector< widget_data > &list_data)
Finishes the building initialization of the widget.
Definition: listbox.cpp:524
void handle_key_down_arrow(SDL_Keymod modifier, bool &handled) override
Inherited from scrollbar_container.
Definition: listbox.cpp:482
void remove_row(const unsigned row, unsigned count=1)
Removes a row in the listbox.
Definition: listbox.cpp:82
void clear()
Removes all the rows in the listbox, clearing it.
Definition: listbox.cpp:121
void set_column_order(unsigned col, const generator_sort_array &func)
Definition: listbox.cpp:607
int get_selected_row() const
Returns the first selected row.
Definition: listbox.cpp:271
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:127
bool select_row_at(const unsigned row, const bool select=true)
Selects a row at the given position, regardless of sorting order.
Definition: listbox.cpp:259
const order_pair get_active_sorting_option()
Definition: listbox.cpp:643
builder_grid_const_ptr list_builder_
Contains the builder for the new items.
Definition: listbox.hpp:364
bool row_selected(const unsigned row)
Check if a row is selected.
Definition: listbox.cpp:265
void handle_key_up_arrow(SDL_Keymod modifier, bool &handled) override
Inherited from scrollbar_container.
Definition: listbox.cpp:468
Base class for creating containers with one or two scrollbar(s).
SDL_Rect content_visible_area_
Cache for the visible area for the content.
void set_horizontal_scrollbar_item_position(const unsigned position)
Move the horizontal scrollbar to a position.
void finalize_setup()
The builder needs to call us so we do our setup.
unsigned get_horizontal_scrollbar_item_position() const
Returns current position of the horizontal scrollbar.
virtual void place(const point &origin, const point &size) override
See widget::place.
unsigned get_vertical_scrollbar_item_position() const
Returns current position of the vertical scrollbar.
void set_vertical_scrollbar_item_position(const unsigned position)
Move the vertical scrollbar to a position.
virtual point calculate_best_size() const override
See widget::calculate_best_size.
std::unique_ptr< grid > content_grid_
The grid that holds the content.
bool content_resize_request(const bool force_sizing=false)
Notification if the content of a child needs a resize.
const SDL_Rect & content_visible_area() const
void show_content_rect(const SDL_Rect &rect)
Shows a certain part of the content.
virtual void handle_key_up_arrow(SDL_Keymod modifier, bool &handled)
Up arrow key pressed.
virtual void handle_key_left_arrow(SDL_Keymod modifier, bool &handled)
Left arrow key pressed.
Small abstract helper class.
virtual void set_value(unsigned value, bool fire_event=false)=0
Select the styled_widget.
virtual unsigned get_value() const =0
Is the styled_widget selected?
Class for a toggle button.
Base class for all widgets.
Definition: widget.hpp:54
point get_best_size() const
Gets the best size for the widget.
Definition: widget.cpp:194
void queue_redraw()
Indicates that this widget should be redrawn.
Definition: widget.cpp:456
visibility get_visible() const
Definition: widget.cpp:498
point get_origin() const
Returns the screen origin of the widget.
Definition: widget.cpp:303
unsigned get_width() const
Definition: widget.cpp:328
int get_y() const
Definition: widget.cpp:323
point get_size() const
Returns the size of the widget.
Definition: widget.cpp:308
unsigned get_height() const
Definition: widget.cpp:333
window * get_window()
Get the parent window.
Definition: widget.cpp:118
@ invisible
The user set the widget invisible, that means:
rect get_rectangle() const
Gets the bounding rectangle of the widget on the screen.
Definition: widget.cpp:313
virtual void set_size(const point &size)
Sets the size of the widget.
Definition: widget.cpp:228
Helper class to block invalidate_layout.
Definition: window.hpp:243
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:67
void keyboard_capture(widget *widget)
Definition: window.cpp:1224
void invalidate_layout()
Updates the size of the window.
Definition: window.cpp:776
std::size_t i
Definition: function.cpp:968
int w
static std::string _(const char *str)
Definition: gettext.hpp:93
Define the common log macros for the gui toolkit.
#define DBG_GUI_L
Definition: log.hpp:55
#define LOG_GUI_G
Definition: log.hpp:42
#define DBG_GUI_G
Definition: log.hpp:41
#define DBG_GUI_P
Definition: log.hpp:66
#define LOG_GUI_L
Definition: log.hpp:56
This file contains the window object, this object is a top level container which has the event manage...
Defines the exception classes for the layout algorithm.
#define LOG_HEADER
Definition: listbox.cpp:40
void point(int x, int y)
Draw a single point.
Definition: draw.cpp:203
@ NOTIFY_MODIFIED
Definition: handler.hpp:158
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
static std::vector< widget_data > parse_list_data(const config &data, const unsigned int req_cols)
Definition: listbox.cpp:719
scrollbar_mode get_scrollbar_mode(const std::string &scrollbar_mode)
Returns the scrollbar mode flags.
Definition: helper.cpp:121
Generic file dialog.
std::shared_ptr< builder_grid > builder_grid_ptr
std::array< generator_base::order_func, 2 > generator_sort_array
Definition: generator.hpp:381
std::map< std::string, widget_item > widget_data
Definition: widget.hpp:35
std::map< std::string, t_string > widget_item
Definition: widget.hpp:32
void swap_grid(grid *g, grid *content_grid, std::unique_ptr< widget > widget, const std::string &id)
Swaps an item in a grid for another one.
std::shared_ptr< const builder_grid > builder_grid_const_ptr
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:414
Contains the implementation details for lexical_cast and shouldn't be used directly.
int icompare(const std::string &s1, const std::string &s2)
Case-insensitive lexicographical comparison.
Definition: gettext.cpp:521
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
std::size_t size(const std::string &str)
Length in characters of a UTF-8 string.
Definition: unicode.cpp:87
std::string_view data
Definition: picture.cpp:199
Contains the SDL_Rect helper code.
#define REGISTER_WIDGET(id)
Wrapper for REGISTER_WIDGET3.
#define REGISTER_WIDGET3(type, id, key)
Registers a widget.
This file contains the settings handling of the widget library.
scrollbar_container::scrollbar_mode horizontal_scrollbar_mode
Definition: listbox.hpp:583
scrollbar_container::scrollbar_mode vertical_scrollbar_mode
Definition: listbox.hpp:582
virtual std::unique_ptr< widget > build() const override
Definition: listbox.cpp:869
std::vector< widget_data > list_data
Listbox data.
Definition: listbox.hpp:593
virtual std::unique_ptr< widget > build() const override
Definition: listbox.cpp:826
std::vector< widget_data > list_data
Listbox data.
Definition: listbox.hpp:547
scrollbar_container::scrollbar_mode vertical_scrollbar_mode
Definition: listbox.hpp:536
scrollbar_container::scrollbar_mode horizontal_scrollbar_mode
Definition: listbox.hpp:537
scrollbar_container::scrollbar_mode vertical_scrollbar_mode
Definition: listbox.hpp:488
std::vector< widget_data > list_data
Listbox data.
Definition: listbox.hpp:502
virtual std::unique_ptr< widget > build() const override
Definition: listbox.cpp:783
scrollbar_container::scrollbar_mode horizontal_scrollbar_mode
Definition: listbox.hpp:489
std::string definition
Parameters for the styled_widget.
listbox_definition(const config &cfg)
Definition: listbox.cpp:697
Base class of a resolution, contains the common keys for a resolution.
std::vector< state_definition > state
Holds a 2D point.
Definition: point.hpp:25
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:47
bool overlaps(const SDL_Rect &r) const
Whether the given rectangle and this rectangle overlap.
Definition: rect.cpp:74
static constexpr std::optional< enum_type > get_enum(const std::string_view value)
Converts a string into its enum equivalent.
Definition: enum_base.hpp:57
mock_char c
mock_party p
static map_location::DIRECTION s
std::string missing_mandatory_wml_key(const std::string &section, const std::string &key, const std::string &primary_key, const std::string &primary_value)
Returns a standard message for a missing wml key.
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define VALIDATE_WML_CHILD(cfg, key, message)
#define VALIDATE(cond, message)
The macro to use for the validation of WML.
#define h
#define f