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  sort_order::type sort = sort_order::get_enum(w->get_value()).value_or(sort_order::type::none);
645 
646  if(w && sort != sort_order::type::none) {
647  return std::pair(column, sort);
648  }
649  }
650 
651  return std::pair(-1, sort_order::type::none);
652 }
653 
655 {
656  for(auto& pair : orders_) {
657  if(pair.first != nullptr) {
658  pair.first->set_value(static_cast<unsigned int>(sort_order::type::none));
659  }
660  }
661 }
662 
663 void listbox::set_content_size(const point& origin, const point& size)
664 {
665  /** @todo This function needs more testing. */
666  assert(content_grid());
667 
668  const int best_height = content_grid()->get_best_size().y;
669  const point s(size.x, size.y < best_height ? size.y : best_height);
670 
671  content_grid()->place(origin, s);
672 }
673 
675 {
676  assert(content_grid());
677 
678  // If we haven't initialized, or have no content, just return.
680  if(size.x <= 0 || size.y <= 0) {
681  return;
682  }
683 
685 
686  const SDL_Rect& visible = content_visible_area_;
688 
689  queue_redraw();
690 }
691 
692 // }---------- DEFINITION ---------{
693 
696 {
697  DBG_GUI_P << "Parsing listbox " << id;
698 
699  load_resolutions<resolution>(cfg);
700 }
701 
703  : resolution_definition(cfg)
704  , grid(nullptr)
705 {
706  // Note the order should be the same as the enum state_t in listbox.hpp.
707  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_enabled", missing_mandatory_wml_tag("listbox_definition][resolution", "state_enabled")));
708  state.emplace_back(VALIDATE_WML_CHILD(cfg, "state_disabled", missing_mandatory_wml_tag("listbox_definition][resolution", "state_disabled")));
709 
710  auto child = VALIDATE_WML_CHILD(cfg, "grid", missing_mandatory_wml_tag("listbox_definition][resolution", "grid"));
711  grid = std::make_shared<builder_grid>(child);
712 }
713 
714 namespace implementation
715 {
716 static std::vector<widget_data> parse_list_data(const config& data, const unsigned int req_cols)
717 {
718  std::vector<widget_data> list_data;
719 
720  for(const auto& row : data.child_range("row")) {
721  auto cols = row.child_range("column");
722 
723  VALIDATE(static_cast<unsigned>(cols.size()) == req_cols,
724  _("'list_data' must have the same number of columns as the 'list_definition'.")
725  );
726 
727  for(const auto& c : cols) {
728  list_data.emplace_back();
729 
730  for(const auto& i : c.attribute_range()) {
731  list_data.back()[""][i.first] = i.second;
732  }
733 
734  for(const auto& w : c.child_range("widget")) {
735  VALIDATE(w.has_attribute("id"), missing_mandatory_wml_key("[list_data][row][column][widget]", "id"));
736 
737  for(const auto& i : w.attribute_range()) {
738  list_data.back()[w["id"]][i.first] = i.second;
739  }
740  }
741  }
742  }
743 
744  return list_data;
745 }
746 
747 builder_listbox::builder_listbox(const config& cfg)
748  : builder_styled_widget(cfg)
749  , vertical_scrollbar_mode(get_scrollbar_mode(cfg["vertical_scrollbar_mode"]))
750  , horizontal_scrollbar_mode(get_scrollbar_mode(cfg["horizontal_scrollbar_mode"]))
751  , header(nullptr)
752  , footer(nullptr)
753  , list_builder(nullptr)
754  , list_data()
755  , has_minimum_(cfg["has_minimum"].to_bool(true))
756  , has_maximum_(cfg["has_maximum"].to_bool(true))
757 {
758  if(auto h = cfg.optional_child("header")) {
759  header = std::make_shared<builder_grid>(*h);
760  }
761 
762  if(auto f = cfg.optional_child("footer")) {
763  footer = std::make_shared<builder_grid>(*f);
764  }
765 
766  auto l = cfg.optional_child("list_definition");
767 
768  VALIDATE(l, _("No list defined."));
769 
770  list_builder = std::make_shared<builder_grid>(*l);
771  assert(list_builder);
772 
773  VALIDATE(list_builder->rows == 1, _("A 'list_definition' should contain one row."));
774 
775  if(cfg.has_child("list_data")) {
776  list_data = parse_list_data(cfg.mandatory_child("list_data"), list_builder->cols);
777  }
778 }
779 
780 std::unique_ptr<widget> builder_listbox::build() const
781 {
782  auto widget = std::make_unique<listbox>(*this, generator_base::vertical_list, list_builder);
783 
784  widget->set_vertical_scrollbar_mode(vertical_scrollbar_mode);
785  widget->set_horizontal_scrollbar_mode(horizontal_scrollbar_mode);
786 
787  DBG_GUI_G << "Window builder: placed listbox '" << id << "' with definition '" << definition << "'.";
788 
789  const auto conf = widget->cast_config_to<listbox_definition>();
790  assert(conf);
791 
792  widget->init_grid(*conf->grid);
793 
795  widget->finalize(std::move(generator), header, footer, list_data);
796 
797  return widget;
798 }
799 
801  : builder_styled_widget(cfg)
802  , vertical_scrollbar_mode(get_scrollbar_mode(cfg["vertical_scrollbar_mode"]))
803  , horizontal_scrollbar_mode(get_scrollbar_mode(cfg["horizontal_scrollbar_mode"]))
804  , list_builder(nullptr)
805  , list_data()
806  , has_minimum_(cfg["has_minimum"].to_bool(true))
807  , has_maximum_(cfg["has_maximum"].to_bool(true))
808 {
809  auto l = cfg.optional_child("list_definition");
810 
811  VALIDATE(l, _("No list defined."));
812 
813  list_builder = std::make_shared<builder_grid>(*l);
814  assert(list_builder);
815 
816  VALIDATE(list_builder->rows == 1, _("A 'list_definition' should contain one row."));
817 
818  if(cfg.has_child("list_data")) {
819  list_data = parse_list_data(cfg.mandatory_child("list_data"), list_builder->cols);
820  }
821 }
822 
823 std::unique_ptr<widget> builder_horizontal_listbox::build() const
824 {
825  auto widget = std::make_unique<listbox>(*this, generator_base::horizontal_list, list_builder);
826 
827  widget->set_vertical_scrollbar_mode(vertical_scrollbar_mode);
828  widget->set_horizontal_scrollbar_mode(horizontal_scrollbar_mode);
829 
830  DBG_GUI_G << "Window builder: placed listbox '" << id << "' with definition '" << definition << "'.";
831 
832  const auto conf = widget->cast_config_to<listbox_definition>();
833  assert(conf);
834 
835  widget->init_grid(*conf->grid);
836 
838  widget->finalize(std::move(generator), nullptr, nullptr, list_data);
839 
840  return widget;
841 }
842 
844  : builder_styled_widget(cfg)
845  , vertical_scrollbar_mode(get_scrollbar_mode(cfg["vertical_scrollbar_mode"]))
846  , horizontal_scrollbar_mode(get_scrollbar_mode(cfg["horizontal_scrollbar_mode"]))
847  , list_builder(nullptr)
848  , list_data()
849  , has_minimum_(cfg["has_minimum"].to_bool(true))
850  , has_maximum_(cfg["has_maximum"].to_bool(true))
851 {
852  auto l = cfg.optional_child("list_definition");
853 
854  VALIDATE(l, _("No list defined."));
855 
856  list_builder = std::make_shared<builder_grid>(*l);
857  assert(list_builder);
858 
859  VALIDATE(list_builder->rows == 1, _("A 'list_definition' should contain one row."));
860 
861  if(cfg.has_child("list_data")) {
862  list_data = parse_list_data(cfg.mandatory_child("list_data"), list_builder->cols);
863  }
864 }
865 
866 std::unique_ptr<widget> builder_grid_listbox::build() const
867 {
868  auto widget = std::make_unique<listbox>(*this, generator_base::table, list_builder);
869 
870  widget->set_vertical_scrollbar_mode(vertical_scrollbar_mode);
871  widget->set_horizontal_scrollbar_mode(horizontal_scrollbar_mode);
872 
873  DBG_GUI_G << "Window builder: placed listbox '" << id << "' with definition '" << definition << "'.";
874 
875  const auto conf = widget->cast_config_to<listbox_definition>();
876  assert(conf);
877 
878  widget->init_grid(*conf->grid);
879 
881  widget->finalize(std::move(generator), nullptr, nullptr, list_data);
882 
883  return widget;
884 }
885 
886 } // namespace implementation
887 
888 // }------------ END --------------
889 
890 } // 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:674
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:654
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:663
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:716
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:866
std::vector< widget_data > list_data
Listbox data.
Definition: listbox.hpp:590
virtual std::unique_ptr< widget > build() const override
Definition: listbox.cpp:823
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:780
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:694
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