The Battle for Wesnoth  1.19.5+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"
21 #include "gui/core/log.hpp"
29 #include "gui/widgets/window.hpp"
30 #include "sdl/rect.hpp"
31 #include "wml_exception.hpp"
32 #include <functional>
33 #include "utils/optional_fwd.hpp"
34 
35 #define LOG_SCOPE_HEADER get_control_type() + " [" + id() + "] " + __func__
36 #define LOG_HEADER LOG_SCOPE_HEADER + ':'
37 
38 namespace gui2
39 {
40 // ------------ WIDGET -----------{
41 
42 REGISTER_WIDGET(listbox)
43 REGISTER_WIDGET3(listbox_definition, horizontal_listbox, nullptr)
44 REGISTER_WIDGET3(listbox_definition, grid_listbox, nullptr)
45 
46 listbox::listbox(const implementation::builder_styled_widget& builder,
47  const generator_base::placement placement,
48  builder_grid_ptr list_builder)
49  : scrollbar_container(builder, type())
50  , generator_(nullptr)
51  , is_horizontal_(placement == generator_base::horizontal_list)
52  , list_builder_(list_builder)
53  , orders_()
54  , callback_order_change_()
55 {
56 }
57 
58 grid& listbox::add_row(const widget_item& item, const int index)
59 {
60  assert(generator_);
61  grid& row = generator_->create_item(index, *list_builder_, item, std::bind(&listbox::list_item_clicked, this, std::placeholders::_1));
62 
63  resize_content(row);
64 
65  return row;
66 }
67 
69 {
70  assert(generator_);
71  grid& row = generator_->create_item(index, *list_builder_, data, std::bind(&listbox::list_item_clicked, this, std::placeholders::_1));
72 
73  resize_content(row);
74 
75  return row;
76 }
77 
78 void listbox::remove_row(const unsigned row, unsigned count)
79 {
80  assert(generator_);
81 
82  if(row >= get_item_count()) {
83  return;
84  }
85 
86  if(!count || count + row > get_item_count()) {
87  count = get_item_count() - row;
88  }
89 
90  int height_reduced = 0;
91  int width_reduced = 0;
92 
93  // TODO: Fix this for horizontal listboxes
94  // Note the we have to use content_grid_ and cannot use "_list_grid" which is what generator_ uses.
95  int row_pos_y = is_horizontal_ ? -1 : generator_->item(row).get_y() - content_grid_->get_y();
96  int row_pos_x = is_horizontal_ ? -1 : 0;
97 
98  for(; count; --count) {
100  if(is_horizontal_) {
101  width_reduced += generator_->item(row).get_width();
102  } else {
103  height_reduced += generator_->item(row).get_height();
104  }
105  }
106 
107  generator_->delete_item(row);
108  }
109 
110  if((height_reduced != 0 || width_reduced != 0) && get_item_count() != 0) {
111  resize_content(-width_reduced, -height_reduced, row_pos_x, row_pos_y);
112  } else {
114  }
115 }
116 
118 {
119  generator_->clear();
121 }
122 
123 unsigned listbox::get_item_count() const
124 {
125  assert(generator_);
126  return generator_->get_item_count();
127 }
128 
129 void listbox::set_row_active(const unsigned row, const bool active)
130 {
131  assert(generator_);
132  generator_->item(row).set_active(active);
133 }
134 
135 void listbox::set_row_shown(const unsigned row, const bool shown)
136 {
137  assert(generator_);
138 
139  window* window = get_window();
140  assert(window);
141 
142  const int selected_row = get_selected_row();
143 
144  bool resize_needed = false;
145 
146  // Local scope for invalidate_layout_blocker
147  {
148  window::invalidate_layout_blocker invalidate_layout_blocker(*window);
149 
150  generator_->set_item_shown(row, shown);
151 
152  point best_size = generator_->calculate_best_size();
153  generator_->place(generator_->get_origin(), {std::max(best_size.x, content_visible_area().w), best_size.y});
154 
155  resize_needed = !content_resize_request();
156  }
157 
158  if(resize_needed) {
160  } else {
161  content_grid_->set_visible_rectangle(content_visible_area());
162  queue_redraw(); // TODO: draw_manager - does this get the right area?
163  }
164 
165  if(selected_row != get_selected_row()) {
166  fire(event::NOTIFY_MODIFIED, *this, nullptr);
167  }
168 }
169 
170 void listbox::set_row_shown(const boost::dynamic_bitset<>& shown)
171 {
172  assert(generator_);
173  assert(shown.size() == get_item_count());
174 
175  if(generator_->get_items_shown() == shown) {
176  LOG_GUI_G << LOG_HEADER << " returning early";
177  return;
178  }
179 
180  window* window = get_window();
181  assert(window);
182 
183  const int selected_row = get_selected_row();
184 
185  bool resize_needed = false;
186 
187  // Local scope for invalidate_layout_blocker
188  {
189  window::invalidate_layout_blocker invalidate_layout_blocker(*window);
190 
191  for(std::size_t i = 0; i < shown.size(); ++i) {
192  generator_->set_item_shown(i, shown[i]);
193  }
194 
195  point best_size = generator_->calculate_best_size();
196  generator_->place(generator_->get_origin(), {std::max(best_size.x, content_visible_area().w), best_size.y});
197 
198  resize_needed = !content_resize_request(true);
199  }
200 
201  if(resize_needed) {
203  } else {
204  content_grid_->set_visible_rectangle(content_visible_area());
205  queue_redraw(); // TODO: draw_manager - does this get the right area?
206  }
207 
208  if(selected_row != get_selected_row()) {
209  fire(event::NOTIFY_MODIFIED, *this, nullptr);
210  }
211 }
212 
213 boost::dynamic_bitset<> listbox::get_rows_shown() const
214 {
215  return generator_->get_items_shown();
216 }
217 
219 {
220  for(std::size_t i = 0; i < get_item_count(); i++) {
221  if(generator_->get_item_shown(i)) {
222  return true;
223  }
224  }
225 
226  return false;
227 }
228 
229 const grid* listbox::get_row_grid(const unsigned row) const
230 {
231  assert(generator_);
232  // rename this function and can we return a reference??
233  return &generator_->item(row);
234 }
235 
236 grid* listbox::get_row_grid(const unsigned row)
237 {
238  assert(generator_);
239  return &generator_->item(row);
240 }
241 
242 bool listbox::select_row(const unsigned row, const bool select)
243 {
244  if(row >= get_item_count()) {
245  throw std::invalid_argument("invalid listbox index");
246  }
247  assert(generator_);
248 
249  unsigned int before = generator_->get_selected_item_count();
250  generator_->select_item(row, select);
251 
252  return before != generator_->get_selected_item_count();
253 }
254 
255 bool listbox::select_row_at(const unsigned row, const bool select)
256 {
257  assert(generator_);
258  return select_row(generator_->get_item_at_ordered(row), select);
259 }
260 
261 bool listbox::row_selected(const unsigned row)
262 {
263  assert(generator_);
264  return generator_->is_selected(row);
265 }
266 
268 {
269  assert(generator_);
270  return generator_->get_selected_item();
271 }
272 
274 {
275  assert(generator_);
276 
277  /** @todo Hack to capture the keyboard focus. */
278  get_window()->keyboard_capture(this);
279 
280  for(std::size_t i = 0; i < generator_->get_item_count(); ++i) {
281  if(generator_->item(i).has_widget(caller)) {
282  toggle_button* checkbox = dynamic_cast<toggle_button*>(&caller);
283 
284  if(checkbox != nullptr) {
285  generator_->select_item(i, checkbox->get_value_bool());
286  } else {
288  }
289 
290  // TODO: enable this code once toggle_panel::set_value dispatches
291  // NOTIFY_MODIFED events. See comment in said function for more details.
292 #if 0
293  selectable_item& selectable = dynamic_cast<selectable_item&>(caller);
294 
295  generator_->select_item(i, selectable.get_value_bool());
296 #endif
297 
298  fire(event::NOTIFY_MODIFIED, *this, nullptr);
299  break;
300  }
301  }
302 
303  const int selected_item = generator_->get_selected_item();
304  if(selected_item == -1) {
305  return;
306  }
307 
308  const rect& visible = content_visible_area();
309  rect r = generator_->item(selected_item).get_rectangle();
310 
311  if(visible.overlaps(r)) {
312  r.x = visible.x;
313  r.w = visible.w;
314 
316  }
317 }
318 
319 void listbox::set_self_active(const bool /*active*/)
320 {
321  /* DO NOTHING */
322 }
323 
325 {
327  return true;
328  }
329 
330  if(get_size() == point()) {
331  return false;
332  }
333 
334  if(content_resize_request(true)) {
335  content_grid_->set_visible_rectangle(content_visible_area());
336  queue_redraw(); // TODO: draw_manager - does this get the right area?
337  return true;
338  }
339 
340  return false;
341 }
342 
343 void listbox::place(const point& origin, const point& size)
344 {
345  utils::optional<unsigned> vertical_scrollbar_position, horizontal_scrollbar_position;
346 
347  // Check if this is the first time placing the list box
348  if(get_origin() != point {-1, -1}) {
349  vertical_scrollbar_position = get_vertical_scrollbar_item_position();
350  horizontal_scrollbar_position = get_horizontal_scrollbar_item_position();
351  }
352 
353  // Inherited.
355 
356  const int selected_item = generator_->get_selected_item();
357  if(vertical_scrollbar_position && horizontal_scrollbar_position) {
358  LOG_GUI_L << LOG_HEADER << " restoring scroll position";
359 
360  set_vertical_scrollbar_item_position(*vertical_scrollbar_position);
361  set_horizontal_scrollbar_item_position(*horizontal_scrollbar_position);
362  } else if(selected_item != -1) {
363  LOG_GUI_L << LOG_HEADER << " making the initially selected item visible";
364 
365  const SDL_Rect& visible = content_visible_area();
366  SDL_Rect rect = generator_->item(selected_item).get_rectangle();
367 
368  rect.x = visible.x;
369  rect.w = visible.w;
370 
372  }
373 }
374 
375 void listbox::resize_content(const int width_modification,
376  const int height_modification,
377  const int width_modification_pos,
378  const int height_modification_pos)
379 {
380  DBG_GUI_L << LOG_HEADER << " current size " << content_grid()->get_size() << " width_modification "
381  << width_modification << " height_modification " << height_modification << ".";
382 
384  width_modification, height_modification, width_modification_pos, height_modification_pos))
385  {
386  // Calculate new size.
388  size.x += width_modification;
389  size.y += height_modification;
390 
391  // Set new size.
393  update_layout();
394 
395  // If the content grows assume it "overwrites" the old content.
396  if(width_modification < 0 || height_modification < 0) {
397  queue_redraw();
398  }
399 
400  DBG_GUI_L << LOG_HEADER << " succeeded.";
401  } else {
402  DBG_GUI_L << LOG_HEADER << " failed.";
403  }
404 }
405 
407 {
408  if(row.get_visible() == visibility::invisible) {
409  return;
410  }
411 
412  DBG_GUI_L << LOG_HEADER << " current size " << content_grid()->get_size() << " row size " << row.get_best_size()
413  << ".";
414 
415  const point content = content_grid()->get_size();
416  point size = row.get_best_size();
417 
418  if(size.x < content.x) {
419  size.x = 0;
420  } else {
421  size.x -= content.x;
422  }
423 
424  resize_content(size.x, size.y);
425 }
426 
428 {
429  // Get the size from the base class, then add any extra space for the header and footer.
431 
432  if(const grid* header = get_grid().find_widget<const grid>("_header_grid", false, false)) {
433  result.y += header->get_best_size().y;
434  }
435 
436  if(const grid* footer = get_grid().find_widget<const grid>("_footer_grid", false, false)) {
437  result.y += footer->get_best_size().y;
438  }
439 
440  return result;
441 }
442 
444 {
445  const SDL_Rect& visible = content_visible_area();
447 
448  // When scrolling make sure the new items are visible...
449  if(direction == KEY_VERTICAL) {
450  // ...but leave the horizontal scrollbar position.
451  rect.x = visible.x;
452  rect.w = visible.w;
453  } else {
454  // ...but leave the vertical scrollbar position.
455  rect.y = visible.y;
456  rect.h = visible.h;
457  }
458 
460 
461  fire(event::NOTIFY_MODIFIED, *this, nullptr);
462 }
463 
464 void listbox::handle_key_up_arrow(SDL_Keymod modifier, bool& handled)
465 {
466  assert(generator_);
467 
468  generator_->handle_key_up_arrow(modifier, handled);
469 
470  if(handled) {
472  } else {
473  // Inherited.
474  scrollbar_container::handle_key_up_arrow(modifier, handled);
475  }
476 }
477 
478 void listbox::handle_key_down_arrow(SDL_Keymod modifier, bool& handled)
479 {
480  assert(generator_);
481 
482  generator_->handle_key_down_arrow(modifier, handled);
483 
484  if(handled) {
486  } else {
487  // Inherited.
488  scrollbar_container::handle_key_up_arrow(modifier, handled);
489  }
490 }
491 
492 void listbox::handle_key_left_arrow(SDL_Keymod modifier, bool& handled)
493 {
494  assert(generator_);
495 
496  generator_->handle_key_left_arrow(modifier, handled);
497 
498  // Inherited.
499  if(handled) {
501  } else {
503  }
504 }
505 
506 void listbox::handle_key_right_arrow(SDL_Keymod modifier, bool& handled)
507 {
508  assert(generator_);
509 
510  generator_->handle_key_right_arrow(modifier, handled);
511 
512  // Inherited.
513  if(handled) {
515  } else {
517  }
518 }
519 
520 void listbox::finalize(std::unique_ptr<generator_base> generator,
521  builder_grid_const_ptr header,
522  builder_grid_const_ptr footer,
523  const std::vector<widget_data>& list_data)
524 {
525  // "Inherited."
527 
528  if(header) {
529  swap_grid(&get_grid(), content_grid(), header->build(), "_header_grid");
530  }
531 
532  grid& p = find_widget<grid>("_header_grid");
533 
534  for(unsigned i = 0, max = std::max(p.get_cols(), p.get_rows()); i < max; ++i) {
535  //
536  // TODO: I had to change this to case to a toggle_button in order to use a signal handler.
537  // Should probably look into a way to make it more general like it was before (used to be
538  // cast to selectable_item).
539  //
540  // - vultraz, 2017-08-23
541  //
542  if(toggle_button* selectable = p.find_widget<toggle_button>("sort_" + std::to_string(i), false, false)) {
543  // Register callback to sort the list.
544  connect_signal_notify_modified(*selectable, std::bind(&listbox::order_by_column, this, i, std::placeholders::_1));
545 
546  if(orders_.size() < max) {
547  orders_.resize(max);
548  }
549 
550  orders_[i].first = selectable;
551  }
552  }
553 
554  if(footer) {
555  swap_grid(&get_grid(), content_grid(), footer->build(), "_footer_grid");
556  }
557 
558  // Save our *non-owning* pointer before this gets moved into the grid.
559  generator_ = generator.get();
560  assert(generator_);
561 
562  generator->create_items(-1, *list_builder_, list_data, std::bind(&listbox::list_item_clicked, this, std::placeholders::_1));
563  swap_grid(nullptr, content_grid(), std::move(generator), "_list_grid");
564 }
565 
566 void listbox::order_by_column(unsigned column, widget& widget)
567 {
568  selectable_item& selectable = dynamic_cast<selectable_item&>(widget);
569  if(column >= orders_.size()) {
570  return;
571  }
572 
573  for(auto& pair : orders_) {
574  if(pair.first != nullptr && pair.first != &selectable) {
575  pair.first->set_value(static_cast<unsigned int>(sort_order::type::none));
576  }
577  }
578 
579  sort_order::type order = sort_order::get_enum(selectable.get_value()).value_or(sort_order::type::none);
580 
581  if(static_cast<unsigned int>(order) > orders_[column].second.size()) {
582  return;
583  }
584 
585  if(order == sort_order::type::none) {
586  order_by(std::less<unsigned>());
587  } else {
588  order_by(orders_[column].second[static_cast<unsigned int>(order) - 1]);
589  }
590 
591  if(callback_order_change_ != nullptr) {
592  callback_order_change_(column, order);
593  }
594 }
595 
597 {
598  generator_->set_order(func);
599 
600  update_layout();
601 }
602 
603 void listbox::set_column_order(unsigned col, const generator_sort_array& func)
604 {
605  if(col >= orders_.size()) {
606  orders_.resize(col + 1);
607  }
608 
609  orders_[col].second = func;
610 }
611 
613 {
614  set_column_order(col, {{
615  [f](int lhs, int rhs) { return translation::icompare(f(lhs), f(rhs)) < 0; },
616  [f](int lhs, int rhs) { return translation::icompare(f(lhs), f(rhs)) > 0; }
617  }});
618 }
619 
620 void listbox::set_active_sorting_option(const order_pair& sort_by, const bool select_first)
621 {
622  // TODO: should this be moved to a public header_grid() getter function?
623  grid& header_grid = find_widget<grid>("_header_grid");
624 
625  selectable_item& w = header_grid.find_widget<selectable_item>("sort_" + std::to_string(sort_by.first));
626 
627  // Set the sorting toggle widgets' value (in this case, its state) to the given sorting
628  // order. This is necessary since the widget's value is used to determine the order in
629  // @ref order_by_column in lieu of a direction being passed directly.
630  w.set_value(static_cast<int>(sort_by.second));
631 
632  order_by_column(sort_by.first, dynamic_cast<widget&>(w));
633 
634  if(select_first && generator_->get_item_count() > 0) {
635  select_row_at(0);
636  }
637 }
638 
640 {
641  for(unsigned int column = 0; column < orders_.size(); ++column) {
642  selectable_item* w = orders_[column].first;
643  if(!w) continue;
644 
645  sort_order::type sort = sort_order::get_enum(w->get_value()).value_or(sort_order::type::none);
646  if(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& [key, value] : c.attribute_range()) {
731  list_data.back()[""][key] = value;
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& [key, value] : w.attribute_range()) {
738  list_data.back()[w["id"]][key] = value;
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  , allow_selection_(cfg["allow_selection"].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:172
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:366
bool has_child(config_key_type key) const
Determine whether a config has a child or not.
Definition: config.cpp:316
optional_config_impl< config > optional_child(config_key_type key, int n=0)
Equivalent to mandatory_child, but returns an empty optional if the nth child was not found.
Definition: config.cpp:384
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:1160
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:273
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:427
void handle_key_right_arrow(SDL_Keymod modifier, bool &handled) override
Inherited from scrollbar_container.
Definition: listbox.cpp:506
void set_row_active(const unsigned row, const bool active)
Makes a row active or inactive.
Definition: listbox.cpp:129
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:135
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:620
void handle_key_left_arrow(SDL_Keymod modifier, bool &handled) override
Inherited from scrollbar_container.
Definition: listbox.cpp:492
void order_by_column(unsigned column, widget &widget)
Definition: listbox.cpp:566
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:375
void order_by(const generator_base::order_func &func)
Definition: listbox.cpp:596
virtual void set_self_active(const bool active) override
See container_base::set_self_active.
Definition: listbox.cpp:319
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:58
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:229
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:612
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:343
void update_visible_area_on_key_event(const KEY_SCROLL_DIRECTION direction)
Helper to update visible area after a key event.
Definition: listbox.cpp:443
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:242
bool update_content_size()
Request to update the size of the content after changing the content.
Definition: listbox.cpp:324
boost::dynamic_bitset get_rows_shown() const
Returns a list of visible rows.
Definition: listbox.cpp:213
bool any_rows_shown() const
Definition: listbox.cpp:218
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:520
void handle_key_down_arrow(SDL_Keymod modifier, bool &handled) override
Inherited from scrollbar_container.
Definition: listbox.cpp:478
void remove_row(const unsigned row, unsigned count=1)
Removes a row in the listbox.
Definition: listbox.cpp:78
void clear()
Removes all the rows in the listbox, clearing it.
Definition: listbox.cpp:117
void set_column_order(unsigned col, const generator_sort_array &func)
Definition: listbox.cpp:603
int get_selected_row() const
Returns the first selected row.
Definition: listbox.cpp:267
unsigned get_item_count() const
Returns the number of items in the listbox.
Definition: listbox.cpp:123
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:255
const order_pair get_active_sorting_option()
Definition: listbox.cpp:639
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:261
void handle_key_up_arrow(SDL_Keymod modifier, bool &handled) override
Inherited from scrollbar_container.
Definition: listbox.cpp:464
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?
Base class for all widgets.
Definition: widget.hpp:55
point get_best_size() const
Gets the best size for the widget.
Definition: widget.cpp:203
NOT_DANGLING T * find_widget(const std::string &id, const bool must_be_active, const bool must_exist)
Gets a widget with the wanted id.
Definition: widget.hpp:742
void queue_redraw()
Indicates that this widget should be redrawn.
Definition: widget.cpp:464
visibility get_visible() const
Definition: widget.cpp:506
point get_origin() const
Returns the screen origin of the widget.
Definition: widget.cpp:311
unsigned get_width() const
Definition: widget.cpp:336
int get_y() const
Definition: widget.cpp:331
point get_size() const
Returns the size of the widget.
Definition: widget.cpp:316
unsigned get_height() const
Definition: widget.cpp:341
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:321
virtual void set_size(const point &size)
Sets the size of the widget.
Definition: widget.cpp:236
Helper class to block invalidate_layout.
Definition: window.hpp:235
base class of top level items, the only item which needs to store the final canvases to draw on.
Definition: window.hpp:61
void keyboard_capture(widget *widget)
Definition: window.cpp:1207
void invalidate_layout()
Updates the size of the window.
Definition: window.cpp:761
std::size_t i
Definition: function.cpp:1028
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:36
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:36
std::map< std::string, t_string > widget_item
Definition: widget.hpp:33
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
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:178
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:495
scrollbar_container::scrollbar_mode vertical_scrollbar_mode
Definition: listbox.hpp:494
virtual std::unique_ptr< widget > build() const override
Definition: listbox.cpp:867
std::vector< widget_data > list_data
Listbox data.
Definition: listbox.hpp:505
virtual std::unique_ptr< widget > build() const override
Definition: listbox.cpp:824
std::vector< widget_data > list_data
Listbox data.
Definition: listbox.hpp:481
scrollbar_container::scrollbar_mode vertical_scrollbar_mode
Definition: listbox.hpp:470
scrollbar_container::scrollbar_mode horizontal_scrollbar_mode
Definition: listbox.hpp:471
scrollbar_container::scrollbar_mode vertical_scrollbar_mode
Definition: listbox.hpp:443
std::vector< widget_data > list_data
Listbox data.
Definition: listbox.hpp:457
virtual std::unique_ptr< widget > build() const override
Definition: listbox.cpp:781
scrollbar_container::scrollbar_mode horizontal_scrollbar_mode
Definition: listbox.hpp:444
std::string definition
Parameters for the styled_widget.
listbox_definition(const config &cfg)
Definition: listbox.cpp:694
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:73
static constexpr utils::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