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