The Battle for Wesnoth  1.17.10+dev
menu.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2022
3  by David White <dave@whitevine.net>
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 "widgets/menu.hpp"
19 
20 #include "draw.hpp"
21 #include "font/sdl_ttf_compat.hpp"
22 #include "font/standard_colors.hpp"
23 #include "floating_label.hpp"
24 #include "game_config.hpp"
25 #include "language.hpp"
26 #include "lexical_cast.hpp"
27 #include "picture.hpp"
28 #include "sdl/rect.hpp"
29 #include "sdl/texture.hpp"
30 #include "sound.hpp"
31 #include "utils/general.hpp"
32 #include "video.hpp"
33 #include "wml_separators.hpp"
34 
35 #include <numeric>
36 
37 namespace gui {
38 
40  : alpha_sort_()
41  , numeric_sort_()
42  , id_sort_()
43  , redirect_sort_()
44  , pos_sort_()
45 {
46  set_id_sort(-1);
47 }
48 
50 {
51  alpha_sort_.insert(column);
52  return *this;
53 }
54 
56 {
57  numeric_sort_.insert(column);
58  return *this;
59 }
60 
62 {
63  id_sort_.insert(column);
64  return *this;
65 }
66 
68 {
69  if(column != to) {
70  redirect_sort_.emplace(column, to);
71  }
72 
73  return *this;
74 }
75 
76 menu::basic_sorter& menu::basic_sorter::set_position_sort(int column, const std::vector<int>& pos)
77 {
78  pos_sort_[column] = pos;
79  return *this;
80 }
81 
83 {
84  const std::map<int,int>::const_iterator redirect = redirect_sort_.find(column);
85  if(redirect != redirect_sort_.end()) {
86  return column_sortable(redirect->second);
87  }
88 
89  return alpha_sort_.count(column) == 1 || numeric_sort_.count(column) == 1 ||
90  pos_sort_.count(column) == 1 || id_sort_.count(column) == 1;
91 }
92 
93 bool menu::basic_sorter::less(int column, const item& row1, const item& row2) const
94 {
95  const std::map<int,int>::const_iterator redirect = redirect_sort_.find(column);
96  if(redirect != redirect_sort_.end()) {
97  return less(redirect->second,row1,row2);
98  }
99 
100  if(id_sort_.count(column) == 1) {
101  return row1.id < row2.id;
102  }
103 
104  if(column < 0 || column >= int(row2.fields.size())) {
105  return false;
106  }
107 
108  if(column >= int(row1.fields.size())) {
109  return true;
110  }
111 
112  const std::string& item1 = row1.fields[column];
113  const std::string& item2 = row2.fields[column];
114 
115  if(alpha_sort_.count(column) == 1) {
116  std::string::const_iterator begin1 = item1.begin(), end1 = item1.end(),
117  begin2 = item2.begin(), end2 = item2.end();
118  while(begin1 != end1 && is_wml_separator(*begin1)) {
119  ++begin1;
120  }
121 
122  while(begin2 != end2 && is_wml_separator(*begin2)) {
123  ++begin2;
124  }
125 
126  return std::lexicographical_compare(begin1,end1,begin2,end2,utils::chars_less_insensitive);
127  } else if(numeric_sort_.count(column) == 1) {
128  int val_1 = lexical_cast_default<int>(item1, 0);
129  int val_2 = lexical_cast_default<int>(item2, 0);
130 
131  return val_1 > val_2;
132  }
133 
134  const std::map<int,std::vector<int>>::const_iterator itor = pos_sort_.find(column);
135  if(itor != pos_sort_.end()) {
136  const std::vector<int>& pos = itor->second;
137  if(row1.id >= pos.size()) {
138  return false;
139  }
140 
141  if(row2.id >= pos.size()) {
142  return true;
143  }
144 
145  return pos[row1.id] < pos[row2.id];
146  }
147 
148  return false;
149 }
150 
151 menu::menu(const std::vector<std::string>& items,
152  bool click_selects, int max_height, int max_width,
153  const sorter* sorter_obj, style *menu_style, const bool auto_join)
154 : scrollarea(auto_join), silent_(false),
155  max_height_(max_height), max_width_(max_width),
156  max_items_(-1), item_height_(-1),
157  heading_height_(-1),
158  cur_help_(-1,-1),
159  selected_(0), click_selects_(click_selects), out_(false),
160  previous_button_(true), show_result_(false),
161  double_clicked_(false),
162  num_selects_(true),
164  last_was_doubleclick_(false), use_ellipsis_(false),
165  sorter_(sorter_obj), sortby_(-1), sortreversed_(false), highlight_heading_(-1)
166 {
167  style_ = (menu_style) ? menu_style : &default_style;
168  style_->init();
169  fill_items(items, true);
170 }
171 
173 {
174 }
175 
176 void menu::fill_items(const std::vector<std::string>& items, bool strip_spaces)
177 {
178  for(std::vector<std::string>::const_iterator itor = items.begin();
179  itor != items.end(); ++itor) {
180 
181  if(itor->empty() == false && (*itor)[0] == HEADING_PREFIX) {
182  heading_ = utils::quoted_split(itor->substr(1),COLUMN_SEPARATOR, !strip_spaces);
183  continue;
184  }
185 
186  const std::size_t id = items_.size();
187  item_pos_.push_back(id);
188  const item new_item(utils::quoted_split(*itor, COLUMN_SEPARATOR, !strip_spaces),id);
189  items_.push_back(new_item);
190 
191  //make sure there is always at least one item
192  if(items_.back().fields.empty()) {
193  items_.back().fields.push_back(" ");
194  }
195 
196  //if the first character in an item is an asterisk,
197  //it means this item should be selected by default
198  std::string& first_item = items_.back().fields.front();
199  if(first_item.empty() == false && first_item[0] == DEFAULT_ITEM) {
200  selected_ = id;
201  first_item.erase(first_item.begin());
202  }
203  }
204 
206 
207  if(sortby_ >= 0) {
208  do_sort();
209  }
210  update_size();
211 }
212 
213 namespace {
214 
215 class sort_func
216 {
217 public:
218  sort_func(const menu::sorter& pred, int column) : pred_(&pred), column_(column)
219  {}
220 
221  bool operator()(const menu::item& a, const menu::item& b) const
222  {
223  return pred_->less(column_,a,b);
224  }
225 
226 private:
227  const menu::sorter* pred_;
228  int column_;
229 };
230 
231 }
232 
234 {
235  if(sorter_ == nullptr || sorter_->column_sortable(sortby_) == false) {
236  return;
237  }
238 
239  const int selectid = selection();
240 
241  std::stable_sort(items_.begin(), items_.end(), sort_func(*sorter_, sortby_));
242  if (sortreversed_)
243  std::reverse(items_.begin(), items_.end());
244 
245  recalculate_pos();
246 
247  if(selectid >= 0 && selectid < int(item_pos_.size())) {
248  move_selection_to(selectid, true, NO_MOVE_VIEWPORT);
249  }
250 
251  queue_redraw();
252 }
253 
255 {
256  std::size_t sz = items_.size();
257  item_pos_.resize(sz);
258  for(std::size_t i = 0; i != sz; ++i)
259  item_pos_[items_[i].id] = i;
260  assert_pos();
261 }
262 
264 {
265  std::size_t sz = items_.size();
266  assert(item_pos_.size() == sz);
267  for(std::size_t n = 0; n != sz; ++n) {
268  assert(item_pos_[n] < sz && n == items_[item_pos_[n]].id);
269  }
270 }
271 
273 {
274  for(std::vector<item>::iterator i = items_.begin(); i != items_.end(); ++i) {
275  i->help.clear();
276  for(std::vector<std::string>::iterator j = i->fields.begin(); j != i->fields.end(); ++j) {
277  if(std::find(j->begin(),j->end(),static_cast<char>(HELP_STRING_SEPARATOR)) == j->end()) {
278  i->help.emplace_back();
279  } else {
280  const std::vector<std::string>& items = utils::split(*j, HELP_STRING_SEPARATOR, 0);
281  if(items.size() >= 2) {
282  *j = items.front();
283  i->help.push_back(items.back());
284  } else {
285  i->help.emplace_back();
286  }
287  }
288  }
289  }
290 }
291 
293 {
294  set_full_size(items_.size());
296 }
297 
299 {
300  int h = heading_height();
301  for(std::size_t i = get_position(),
302  i_end = std::min(items_.size(), i + max_items_onscreen());
303  i < i_end; ++i)
304  h += get_item_rect(i).h;
305  h = std::max(h, height());
306  if (max_height_ > 0 && h > (max_height_)) {
307  h = max_height_;
308  }
309 
310  use_ellipsis_ = false;
311  const std::vector<int>& widths = column_widths();
312  int w = std::accumulate(widths.begin(), widths.end(), 0);
313  if (items_.size() > max_items_onscreen())
314  w += scrollbar_width();
315  w = std::max(w, width());
316  if (max_width_ > 0 && w > (max_width_)) {
317  use_ellipsis_ = true;
318  w = max_width_;
319  }
320 
322  set_measurements(w, h);
323 }
324 
325 int menu::selection() const
326 {
327  if (selected_ >= items_.size()) {
328  return -1;
329  }
330 
331  return items_[selected_].id;
332 }
333 
334 void menu::set_inner_location(const SDL_Rect& /*rect*/)
335 {
336  itemRects_.clear();
338 }
339 
340 void menu::change_item(int pos1, int pos2,const std::string& str)
341 {
342  if(pos1 < 0 || pos1 >= int(item_pos_.size()) ||
343  pos2 < 0 || pos2 >= int(items_[item_pos_[pos1]].fields.size())) {
344  return;
345  }
346 
347  items_[item_pos_[pos1]].fields[pos2] = str;
348  queue_redraw();
349 }
350 
351 void menu::erase_item(std::size_t index)
352 {
353  std::size_t nb_items = items_.size();
354  if (index >= nb_items)
355  return;
356  --nb_items;
357 
358  clear_item(nb_items);
359 
360  // fix ordered positions of items
361  std::size_t pos = item_pos_[index];
362  item_pos_.erase(item_pos_.begin() + index);
363  items_.erase(items_.begin() + pos);
364  for(std::size_t i = 0; i != nb_items; ++i) {
365  std::size_t &n1 = item_pos_[i], &n2 = items_[i].id;
366  if (n1 > pos) --n1;
367  if (n2 > index) --n2;
368  }
369  assert_pos();
370 
371  if (selected_ >= nb_items)
372  selected_ = nb_items - 1;
373 
376  itemRects_.clear();
377  queue_redraw();
378 }
379 
380 void menu::set_heading(const std::vector<std::string>& heading)
381 {
382  itemRects_.clear();
383  column_widths_.clear();
384 
385  heading_ = heading;
386  max_items_ = -1;
387 
388  queue_redraw();
389 }
390 
391 void menu::set_items(const std::vector<std::string>& items, bool strip_spaces, bool keep_viewport)
392 {
393 
394  const bool scrolled_to_max = (has_scrollbar() && get_position() == get_max_position());
395  items_.clear();
396  item_pos_.clear();
397  itemRects_.clear();
398  column_widths_.clear();
399  //undrawn_items_.clear();
400  max_items_ = -1; // Force recalculation of the max items.
401  item_height_ = -1; // Force recalculation of the item height.
402 
403  if (!keep_viewport || selected_ >= items.size()) {
404  selected_ = 0;
405  }
406 
407  fill_items(items, strip_spaces);
408  if(!keep_viewport) {
409  set_position(0);
410  } else if(scrolled_to_max) {
412  }
413 
415 
416  if(!keep_viewport) {
418  }
419  queue_redraw();
420 }
421 
422 void menu::set_max_height(const int new_max_height)
423 {
424  max_height_ = new_max_height;
425  itemRects_.clear();
426  max_items_ = -1;
427  update_size();
428 }
429 
430 void menu::set_max_width(const int new_max_width)
431 {
432  max_width_ = new_max_width;
433  itemRects_.clear();
434  column_widths_.clear();
435  update_size();
436 }
437 
438 std::size_t menu::max_items_onscreen() const
439 {
440  if(max_items_ != -1) {
441  return std::size_t(max_items_);
442  }
443 
444  const std::size_t max_height = (
445  max_height_ == -1
446  ? (video::game_canvas_size().y * 66) / 100
447  : max_height_
448  ) - heading_height();
449 
450  std::vector<int> heights;
451  std::size_t n;
452  for(n = 0; n != items_.size(); ++n) {
453  heights.push_back(get_item_height(n));
454  }
455 
456  std::sort(heights.begin(),heights.end(),std::greater<int>());
457  std::size_t sum = 0;
458  for(n = 0; n != items_.size() && sum < max_height; ++n) {
459  sum += heights[n];
460  }
461 
462  if(sum > max_height && n > 1)
463  --n;
464 
465  return max_items_ = n;
466 }
467 
469 {
470  if(click_selects_)
471  return;
473 }
474 
475 void menu::set_selection_pos(std::size_t new_selected, bool silent, SELECTION_MOVE_VIEWPORT move_viewport)
476 {
477  if (new_selected >= items_.size())
478  return;
479 
480  bool changed = false;
481  if (new_selected != selected_) {
483  invalidate_row_pos(new_selected);
484  selected_ = new_selected;
485  changed = true;
486  }
487 
488  if(move_viewport == MOVE_VIEWPORT) {
490  if(!silent_ && !silent && changed) {
492  }
493  }
494 }
495 
496 void menu::move_selection_up(std::size_t dep)
497 {
498  set_selection_pos(selected_ > dep ? selected_ - dep : 0);
499 }
500 
501 void menu::move_selection_down(std::size_t dep)
502 {
503  std::size_t nb_items = items_.size();
504  set_selection_pos(selected_ + dep >= nb_items ? nb_items - 1 : selected_ + dep);
505 }
506 
507 // private function with control over sound and viewport
508 void menu::move_selection_to(std::size_t id, bool silent, SELECTION_MOVE_VIEWPORT move_viewport)
509 {
510  if(id < item_pos_.size()) {
511  set_selection_pos(item_pos_[id], silent, move_viewport);
512  }
513 }
514 
515 // public function
516 void menu::move_selection(std::size_t id)
517 {
518  if(id < item_pos_.size()) {
520  }
521 }
522 
523 // public function
525 {
526  if(id < item_pos_.size()) {
528  }
529 }
530 
532 {
533  set_selection_pos(0, true);
534 }
535 
536 void menu::key_press(SDL_Keycode key)
537 {
538  if (!click_selects_) {
539  switch(key) {
540  case SDLK_UP:
542  break;
543  case SDLK_DOWN:
545  break;
546  case SDLK_PAGEUP:
548  break;
549  case SDLK_PAGEDOWN:
551  break;
552  case SDLK_HOME:
554  break;
555  case SDLK_END:
556  set_selection_pos(items_.size() - 1);
557  break;
558  //case SDLK_RETURN:
559  // double_clicked_ = true;
560  // break;
561  default:
562  break;
563  }
564  }
565 
566  if (num_selects_ && key >= SDLK_1 && key <= SDLK_9)
567  set_selection_pos(key - SDLK_1);
568 }
569 
570 bool menu::requires_event_focus(const SDL_Event* event) const
571 {
572  if(!focus_ || height() == 0 || hidden()) {
573  return false;
574  }
575  if(event == nullptr) {
576  //when event is not specified, signal that focus may be desired later
577  return true;
578  }
579 
580  if(event->type == SDL_KEYDOWN) {
581  SDL_Keycode key = event->key.keysym.sym;
582  if (!click_selects_) {
583  switch(key) {
584  case SDLK_UP:
585  case SDLK_DOWN:
586  case SDLK_PAGEUP:
587  case SDLK_PAGEDOWN:
588  case SDLK_HOME:
589  case SDLK_END:
590  return true;
591  default:
592  break;
593  }
594  }
595  if (num_selects_ && key >= SDLK_1 && key <= SDLK_9) {
596  return true;
597  }
598  }
599  //mouse events are processed regardless of focus
600  return false;
601 }
602 
603 void menu::handle_event(const SDL_Event& event)
604 {
606  if (height()==0 || hidden())
607  return;
608 
609  if(event.type == SDL_KEYDOWN) {
610  // Only pass key events if we have the focus
611  if (focus(&event))
612  key_press(event.key.keysym.sym);
613  } else if(!mouse_locked() && ((event.type == SDL_MOUSEBUTTONDOWN &&
614  (event.button.button == SDL_BUTTON_LEFT || event.button.button == SDL_BUTTON_RIGHT)) ||
615  event.type == DOUBLE_CLICK_EVENT)) {
616 
617  int x = 0;
618  int y = 0;
619  if(event.type == SDL_MOUSEBUTTONDOWN) {
620  x = event.button.x;
621  y = event.button.y;
622  } else {
623  x = reinterpret_cast<std::size_t>(event.user.data1);
624  y = reinterpret_cast<std::size_t>(event.user.data2);
625  }
626 
627  const int item = hit(x,y);
628  if(item != -1) {
629  set_focus(true);
630  move_selection_to(item);
631 
632  if(click_selects_) {
633  show_result_ = true;
634  }
635 
636  if(event.type == DOUBLE_CLICK_EVENT) {
638  ignore_next_doubleclick_ = false;
639  } else {
640  double_clicked_ = true;
641  last_was_doubleclick_ = true;
642  if(!silent_) {
644  }
645  }
646  } else if (last_was_doubleclick_) {
647  // If we have a double click as the next event, it means
648  // this double click was generated from a click that
649  // already has helped in generating a double click.
650  SDL_Event ev;
651  SDL_PeepEvents(&ev, 1, SDL_PEEKEVENT, DOUBLE_CLICK_EVENT, DOUBLE_CLICK_EVENT);
652  if (ev.type == DOUBLE_CLICK_EVENT) {
654  }
655  last_was_doubleclick_ = false;
656  }
657  }
658 
659 
660  if(sorter_ != nullptr) {
661  const int heading = hit_heading(x,y);
662  if(heading >= 0 && sorter_->column_sortable(heading)) {
663  sort_by(heading);
664  }
665  }
666  } else if(!mouse_locked() && event.type == SDL_MOUSEMOTION) {
667  if(click_selects_) {
668  const int item = hit(event.motion.x,event.motion.y);
669  const bool out = (item == -1);
670  if (out_ != out) {
671  out_ = out;
673  }
674  if (item != -1) {
675  move_selection_to(item);
676  }
677  }
678 
679  const int heading_item = hit_heading(event.motion.x,event.motion.y);
680  if(heading_item != highlight_heading_) {
681  highlight_heading_ = heading_item;
683  }
684  }
685 }
686 
688 {
689  if(show_result_) {
690  show_result_ = false;
691  return selected_;
692  } else {
693  return -1;
694  }
695 }
696 
698 {
699  bool old = double_clicked_;
700  double_clicked_ = false;
701  return old;
702 }
703 
704 void menu::set_click_selects(bool value)
705 {
706  click_selects_ = value;
707 }
708 
710 {
711  num_selects_ = value;
712 }
713 
714 void menu::scroll(unsigned int)
715 {
716  itemRects_.clear();
717  queue_redraw();
718 }
719 
721 {
722  if(sortby_ >= 0) {
723  //clear an existing sort
724  sort_by(-1);
725  }
726  sorter_ = s;
727  sortreversed_ = false;
728  sortby_ = -1;
729 }
730 
731 void menu::sort_by(int column)
732 {
733  const bool already_sorted = (column == sortby_);
734 
735  if(already_sorted) {
736  if(sortreversed_ == false) {
737  sortreversed_ = true;
738  } else {
739  sortreversed_ = false;
740  sortby_ = -1;
741  }
742  } else {
743  sortby_ = column;
744  sortreversed_ = false;
745  }
746 
747  do_sort();
748  itemRects_.clear();
749  queue_redraw();
750 }
751 
752 SDL_Rect menu::style::item_size(const std::string& item) const {
753  SDL_Rect res {0,0,0,0};
754  std::vector<std::string> img_text_items = utils::split(item, IMG_TEXT_SEPARATOR, utils::REMOVE_EMPTY);
755  for (std::vector<std::string>::const_iterator it = img_text_items.begin();
756  it != img_text_items.end(); ++it) {
757  if (res.w > 0 || res.h > 0) {
758  // Not the first item, add the spacing.
759  res.w += 5;
760  }
761  const std::string str = *it;
762  if (!str.empty() && str[0] == IMAGE_PREFIX) {
763  const std::string image_name(str.begin()+1,str.end());
764  const point image_size = image::get_size(image_name);
765  if (image_size.x && image_size.y) {
766  int w = image_size.x;
767  int h = image_size.y;
768  adjust_image_bounds(w, h);
769  res.w += w;
770  res.h = std::max<int>(h, res.h);
771  }
772  }
773  else {
774  const SDL_Rect area {0,0,10000,10000};
775  const SDL_Rect font_size =
776  font::pango_draw_text(false, area, get_font_size(),
777  font::NORMAL_COLOR, str, 0, 0);
778  res.w += font_size.w;
779  res.h = std::max<int>(font_size.h, res.h);
780  }
781  }
782  return res;
783 }
784 
785 void menu::style::draw_row_bg(menu& /*menu_ref*/, const std::size_t /*row_index*/, const SDL_Rect& rect, ROW_TYPE type)
786 {
787  int rgb = 0;
788  double alpha = 0.0;
789 
790  switch(type) {
791  case NORMAL_ROW:
792  rgb = normal_rgb_;
793  alpha = normal_alpha_;
794  break;
795  case SELECTED_ROW:
796  rgb = selected_rgb_;
797  alpha = selected_alpha_;
798  break;
799  case HEADING_ROW:
800  rgb = heading_rgb_;
801  alpha = heading_alpha_;
802  break;
803  }
804 
805  // FIXME: make this clearer
806  color_t c((rgb & 0xff0000) >> 16, (rgb & 0xff00) >> 8, rgb & 0xff);
807  c.a = 255 * alpha;
808 
809  draw::fill(rect, c);
810 }
811 
812 void menu::style::draw_row(menu& menu_ref, const std::size_t row_index, const SDL_Rect& rect, ROW_TYPE type)
813 {
814  if(rect.w == 0 || rect.h == 0) {
815  return;
816  }
817  draw_row_bg(menu_ref, row_index, rect, type);
818 
819  SDL_Rect minirect = rect;
820  if(type != HEADING_ROW) {
821  minirect.x += thickness_;
822  minirect.y += thickness_;
823  minirect.w -= 2*thickness_;
824  minirect.h -= 2*thickness_;
825  }
826  menu_ref.draw_row(row_index, minirect, type);
827 }
828 
829 
830 
831 void menu::column_widths_item(const std::vector<std::string>& row, std::vector<int>& widths) const
832 {
833  for(std::size_t col = 0; col != row.size(); ++col) {
834  const SDL_Rect res = style_->item_size(row[col]);
835  std::size_t text_trailing_space = (item_ends_with_image(row[col])) ? 0 : style_->get_cell_padding();
836 
837  if(col == widths.size()) {
838  widths.push_back(res.w + text_trailing_space);
839  } else if(static_cast<std::size_t>(res.w) > widths[col] - text_trailing_space) {
840  widths[col] = res.w + text_trailing_space;
841  }
842  }
843 }
844 
845 bool menu::item_ends_with_image(const std::string& item) const
846 {
847  std::string::size_type pos = item.find_last_of(IMG_TEXT_SEPARATOR);
848  pos = (pos == std::string::npos) ? 0 : pos+1;
849  return(item.size() > pos && item.at(pos) == IMAGE_PREFIX);
850 }
851 
852 const std::vector<int>& menu::column_widths() const
853 {
854  if(column_widths_.empty()) {
856  for(std::size_t row = 0; row != items_.size(); ++row) {
858  }
859  }
860 
861  return column_widths_;
862 }
863 
865 {
866  SDL_Rect rect = get_item_rect(item);
867  if (rect.w == 0)
868  return;
869  queue_redraw(rect);
870 }
871 
872 void menu::draw_row(const std::size_t row_index, const SDL_Rect& loc, ROW_TYPE type)
873 {
874  //called from style, draws one row's contents in a generic and adaptable way
875  const std::vector<std::string>& row = (type == HEADING_ROW) ? heading_ : items_[row_index].fields;
876  rect area = video::game_canvas();
877  rect column = inner_location();
878  const std::vector<int>& widths = column_widths();
879  bool lang_rtl = current_language_rtl();
880  int dir = (lang_rtl) ? -1 : 1;
881 
882  int xpos = loc.x;
883  if(lang_rtl) {
884  xpos += loc.w;
885  }
886 
887  for(std::size_t i = 0; i != row.size(); ++i) {
888 
889  if(lang_rtl) {
890  xpos -= widths[i];
891  }
892  if(type == HEADING_ROW) {
893  rect draw_rect {xpos, loc.y, widths[i], loc.h };
894 
895  if(highlight_heading_ == int(i)) {
896  draw::fill(draw_rect, {255,255,255,77});
897  } else if(sortby_ == int(i)) {
898  draw::fill(draw_rect, {255,255,255,26});
899  }
900  }
901 
902  const int last_x = xpos;
903  column.w = widths[i];
904  std::string str = row[i];
905  std::vector<std::string> img_text_items = utils::split(str, IMG_TEXT_SEPARATOR, utils::REMOVE_EMPTY);
906  for (std::vector<std::string>::const_iterator it = img_text_items.begin();
907  it != img_text_items.end(); ++it) {
908  str = *it;
909  if (!str.empty() && str[0] == IMAGE_PREFIX) {
910  const std::string image_name(str.begin()+1,str.end());
911  const texture img = image::get_texture(image_name);
912  int img_w = img.w();
913  int img_h = img.h();
914  style_->adjust_image_bounds(img_w, img_h);
915  const int remaining_width = max_width_ < 0 ? area.w :
916  std::min<int>(max_width_, ((lang_rtl)? xpos - loc.x : loc.x + loc.w - xpos));
917  if(img && img_w <= remaining_width
918  && loc.y + img_h < area.h) {
919  const std::size_t y = loc.y + (loc.h - img_h)/2;
920  const std::size_t w = img_w + 5;
921  const std::size_t x = xpos + ((lang_rtl) ? widths[i] - w : 0);
922  draw::blit(img, {int(x), int(y), img_w, img_h});
923  if(!lang_rtl)
924  xpos += w;
925  column.w -= w;
926  }
927  } else {
928  column.x = xpos;
929 
930  const auto text_size = font::pango_line_size(str, style_->get_font_size());
931  const std::size_t y = loc.y + (loc.h - text_size.second)/2;
932  const std::size_t padding = 2;
933  rect text_loc = column;
934  text_loc.w = loc.w - (xpos - loc.x) - 2 * style_->get_thickness();
935  text_loc.h = text_size.second;
937  (type == HEADING_ROW ? xpos+padding : xpos), y);
938 
939  if(type == HEADING_ROW && sortby_ == int(i)) {
940  const texture sort_tex(image::get_texture(sortreversed_ ? "buttons/sliders/slider_arrow_blue.png" :
941  "buttons/sliders/slider_arrow_blue.png~ROTATE(180)"));
942  if(sort_tex && sort_tex.w() <= widths[i] && sort_tex.h() <= loc.h) {
943  const int sort_x = xpos + widths[i] - sort_tex.w() - padding;
944  const int sort_y = loc.y + loc.h/2 - sort_tex.h()/2;
945  SDL_Rect dest = {sort_x, sort_y, sort_tex.w(), sort_tex.h()};
946  draw::blit(sort_tex, dest);
947  }
948  }
949 
950  xpos += dir * (text_size.first + 5);
951  }
952  }
953  if(lang_rtl)
954  xpos = last_x;
955  else
956  xpos = last_x + widths[i];
957  }
958 }
959 
961 {
962  SDL_Rect heading_rect = inner_location();
963  heading_rect.h = heading_height();
964  style_->draw_row(*this,0,heading_rect,HEADING_ROW);
965 
966  for(std::size_t i = 0; i != item_pos_.size(); ++i) {
969  }
970 }
971 
972 int menu::hit(int x, int y) const
973 {
974  const SDL_Rect& loc = inner_location();
975  if (x >= loc.x && x < loc.x + loc.w && y >= loc.y && y < loc.y + loc.h) {
976  for(std::size_t i = 0; i != items_.size(); ++i) {
977  const SDL_Rect& rect = get_item_rect(i);
978  if (y >= rect.y && y < rect.y + rect.h)
979  return i;
980  }
981  }
982 
983  return -1;
984 }
985 
986 int menu::hit_column(int x) const
987 {
988  const std::vector<int>& widths = column_widths();
989  int j = -1, j_end = widths.size();
990  for(x -= location().x; x >= 0; x -= widths[j]) {
991  if(++j == j_end) {
992  return -1;
993  }
994  }
995  return j;
996 }
997 
998 std::pair<int,int> menu::hit_cell(int x, int y) const
999 {
1000  const int row = hit(x, y);
1001  if(row < 0) {
1002  return std::pair<int,int>(-1, -1);
1003  }
1004 
1005  const int col = hit_column(x);
1006  if(col < 0) {
1007  return std::pair<int,int>(-1, -1);
1008  }
1009 
1010  return std::pair<int,int>(x,y);
1011 }
1012 
1013 int menu::hit_heading(int x, int y) const
1014 {
1015  const std::size_t height = heading_height();
1016  const SDL_Rect& loc = inner_location();
1017  if(y >= loc.y && static_cast<std::size_t>(y) < loc.y + height) {
1018  return hit_column(x);
1019  } else {
1020  return -1;
1021  }
1022 }
1023 
1024 SDL_Rect menu::get_item_rect(int item) const
1025 {
1026  return get_item_rect_internal(item_pos_[item]);
1027 }
1028 
1029 SDL_Rect menu::get_item_rect_internal(std::size_t item) const
1030 {
1031  unsigned int first_item_on_screen = get_position();
1032  if (item < first_item_on_screen ||
1033  item >= first_item_on_screen + max_items_onscreen()) {
1034  return sdl::empty_rect;
1035  }
1036 
1037  const std::map<int,SDL_Rect>::const_iterator i = itemRects_.find(item);
1038  if(i != itemRects_.end())
1039  return i->second;
1040 
1041  const SDL_Rect& loc = inner_location();
1042 
1043  int y = loc.y + heading_height();
1044  if (item != first_item_on_screen) {
1045  const SDL_Rect& prev = get_item_rect_internal(item-1);
1046  y = prev.y + prev.h;
1047  }
1048 
1049  rect res(loc.x, y, loc.w, get_item_height(item));
1050 
1051  const point canvas_size = video::game_canvas_size();
1052 
1053  if(res.x > canvas_size.x) {
1054  return sdl::empty_rect;
1055  } else if(res.x + res.w > canvas_size.x) {
1056  res.w = canvas_size.x - res.x;
1057  }
1058 
1059  if(res.y > canvas_size.y) {
1060  return sdl::empty_rect;
1061  } else if(res.y + res.h > canvas_size.y) {
1062  res.h = canvas_size.y - res.y;
1063  }
1064 
1065  //only insert into the cache if the menu's co-ordinates have
1066  //been initialized
1067  if (loc.x > 0 && loc.y > 0)
1068  itemRects_.emplace(item, res);
1069 
1070  return res;
1071 }
1072 
1073 std::size_t menu::get_item_height_internal(const std::vector<std::string>& item) const
1074 {
1075  std::size_t res = 0;
1076  for(std::vector<std::string>::const_iterator i = item.begin(); i != item.end(); ++i) {
1077  SDL_Rect rect = style_->item_size(*i);
1078  res = std::max<int>(rect.h,res);
1079  }
1080 
1081  return res;
1082 }
1083 
1084 std::size_t menu::heading_height() const
1085 {
1086  if(heading_height_ == -1) {
1088  }
1089 
1090  return std::min<unsigned int>(heading_height_,max_height_);
1091 }
1092 
1093 std::size_t menu::get_item_height(int) const
1094 {
1095  if(item_height_ != -1)
1096  return std::size_t(item_height_);
1097 
1098  std::size_t max_height = 0;
1099  for(std::size_t n = 0; n != items_.size(); ++n) {
1100  max_height = std::max<int>(max_height,get_item_height_internal(items_[n].fields));
1101  }
1102 
1103  return item_height_ = max_height;
1104 }
1105 
1106 void menu::process_help_string(int mousex, int mousey)
1107 {
1108  if (hidden()) return;
1109 
1110  const std::pair<int,int> loc(hit(mousex,mousey), hit_column(mousex));
1111  if(loc == cur_help_) {
1112  return;
1113  } else if(loc.first == -1) {
1115  } else {
1117  if(std::size_t(loc.first) < items_.size()) {
1118  const std::vector<std::string>& row = items_[item_pos_[loc.first]].help;
1119  if(std::size_t(loc.second) < row.size()) {
1120  const std::string& help = row[loc.second];
1121  if(help.empty() == false) {
1122  //PLAIN_LOG << "setting help string from menu to '" << help << "'";
1123  font::set_help_string(help);
1124  }
1125  }
1126  }
1127  }
1128 
1129  cur_help_ = loc;
1130 }
1131 
1132 void menu::invalidate_row(std::size_t id)
1133 {
1134  if(id >= items_.size()) {
1135  return;
1136  }
1137 
1139 }
1140 
1141 void menu::invalidate_row_pos(std::size_t pos)
1142 {
1143  if(pos >= items_.size()) {
1144  return;
1145  }
1146 
1147  invalidate_row(items_[pos].id);
1148 }
1149 
1151 {
1152  rect heading_rect = inner_location();
1153  heading_rect.h = heading_height();
1154  queue_redraw(heading_rect);
1155 }
1156 
1157 }
Drawing functions, for drawing things on the screen.
basic_sorter & set_alpha_sort(int column)
Definition: menu.cpp:49
std::size_t id
Definition: menu.hpp:109
void sort_by(int column)
Definition: menu.cpp:731
bool last_was_doubleclick_
Definition: menu.hpp:284
char const IMG_TEXT_SEPARATOR
void set_shown_size(unsigned h)
Definition: scrollarea.cpp:106
std::size_t get_font_size() const
Definition: menu_style.cpp:56
void column_widths_item(const std::vector< std::string > &row, std::vector< int > &widths) const
Definition: menu.cpp:831
void draw_contents() override
Definition: menu.cpp:960
void set_numeric_keypress_selection(bool value)
Definition: menu.cpp:709
void invalidate_row_pos(std::size_t pos)
Definition: menu.cpp:1141
void adjust_position(unsigned pos)
Definition: scrollarea.cpp:96
int w() const
The draw-space width of the texture, in pixels.
Definition: texture.hpp:105
void set_selection_pos(std::size_t pos, bool silent=false, SELECTION_MOVE_VIEWPORT move_viewport=MOVE_VIEWPORT)
Definition: menu.cpp:475
virtual void init()
Definition: menu.hpp:42
SDL_Rect get_item_rect(int item) const
Definition: menu.cpp:1024
bool hidden() const
Definition: widget.cpp:162
int max_height_
Definition: menu.hpp:228
void move_selection(std::size_t id)
Definition: menu.cpp:516
unsigned scrollbar_width() const
Definition: scrollarea.cpp:142
bool num_selects_
variable which determines whether a numeric keypress should select an item on the dialog ...
Definition: menu.hpp:279
void queue_redraw()
Indicate that the widget should be redrawn.
Definition: widget.cpp:216
std::map< int, int > redirect_sort_
Definition: menu.hpp:137
int selection() const
Definition: menu.cpp:325
const std::vector< int > & column_widths() const
Definition: menu.cpp:852
New lexcical_cast header.
#define a
std::size_t get_item_height(int item) const
Definition: menu.cpp:1093
int h() const
The draw-space height of the texture, in pixels.
Definition: texture.hpp:114
int sortby_
Definition: menu.hpp:290
ROW_TYPE
Definition: menu.hpp:35
basic_sorter & set_position_sort(int column, const std::vector< int > &pos)
Definition: menu.cpp:76
std::vector< std::string > heading_
Definition: menu.hpp:237
std::pair< int, int > pango_line_size(const std::string &line, int font_size, font::pango_text::FONT_STYLE font_style)
Determine the width and height of a line of text given a certain font size.
General purpose widgets.
std::size_t max_items_onscreen() const
Definition: menu.cpp:438
void do_sort()
Definition: menu.cpp:233
virtual SDL_Rect item_size(const std::string &item) const
Definition: menu.cpp:752
void move_selection_keeping_viewport(std::size_t id)
Definition: menu.cpp:524
menu(const std::vector< std::string > &items, bool click_selects=false, int max_height=-1, int max_width=-1, const sorter *sorter_obj=nullptr, style *menu_style=nullptr, const bool auto_join=true)
Definition: menu.cpp:151
const std::vector< std::string > items
basic_sorter & set_redirect_sort(int column, int to)
Definition: menu.cpp:67
void set_focus(bool focus)
Definition: widget.cpp:129
SELECTION_MOVE_VIEWPORT
Definition: menu.hpp:305
const std::string menu_select
bool show_result_
Definition: menu.hpp:253
#define h
virtual bool column_sortable(int column) const =0
std::vector< int > column_widths_
Definition: menu.hpp:245
void adjust_viewport_to_selection()
Definition: menu.cpp:468
void process_help_string(int mousex, int mousey) override
Definition: menu.cpp:1106
point get_size(const locator &i_locator, bool skip_cache)
Returns the width and height of an image.
Definition: picture.cpp:970
int hit(int x, int y) const
Definition: menu.cpp:972
void set_sorter(sorter *s)
Definition: menu.cpp:720
void set_help_string(const std::string &str)
Displays a help string with the given text.
int item_height_
Definition: menu.hpp:229
Wrapper class to encapsulate creation and management of an SDL_Texture.
Definition: texture.hpp:32
void rect(const SDL_Rect &rect)
Draw a rectangle.
Definition: draw.cpp:141
char const IMAGE_PREFIX
void update_size()
Definition: menu.cpp:298
void set_click_selects(bool value)
Definition: menu.cpp:704
void set_measurements(int w, int h)
Definition: widget.cpp:109
bool ignore_next_doubleclick_
Definition: menu.hpp:283
#define b
void reset_selection()
Definition: menu.cpp:531
bool sortreversed_
Definition: menu.hpp:291
void move_selection_down(std::size_t dep)
Definition: menu.cpp:501
rect game_canvas()
The game canvas area, in drawing coordinates.
Definition: video.cpp:417
void set_full_size(unsigned h)
Definition: scrollarea.cpp:113
void clear_help_string()
Removes the help string.
void adjust_image_bounds(int &w, int &h) const
Definition: menu_style.cpp:66
bool has_scrollbar() const
Definition: scrollarea.cpp:33
char const DEFAULT_ITEM
void create_help_strings()
Definition: menu.cpp:272
void scroll(unsigned int pos) override
Definition: menu.cpp:714
void set_position(unsigned pos)
Definition: scrollarea.cpp:91
char const HELP_STRING_SEPARATOR
bool current_language_rtl()
Definition: language.cpp:55
void fill_items(const std::vector< std::string > &items, bool strip_spaces)
Set new items to show.
Definition: menu.cpp:176
bool focus(const SDL_Event *event)
Definition: widget.cpp:137
bool out_
Definition: menu.hpp:249
void update_scrollbar_grip_height()
Definition: menu.cpp:292
std::pair< int, int > cur_help_
Definition: menu.hpp:243
basic_sorter & set_id_sort(int column)
Definition: menu.cpp:61
virtual void draw_row(menu &menu_ref, const std::size_t row_index, const SDL_Rect &rect, ROW_TYPE type)
Definition: menu.cpp:812
std::set< int > id_sort_
Definition: menu.hpp:136
std::vector< std::string > fields
Definition: menu.hpp:107
std::size_t heading_height() const
Definition: menu.cpp:1084
int process()
Definition: menu.cpp:687
The basic class for representing 8-bit RGB or RGBA colour values.
Definition: color.hpp:58
int max_width_
Definition: menu.hpp:228
std::vector< std::size_t > item_pos_
Definition: menu.hpp:235
const rect & location() const
Definition: widget.cpp:124
void set_heading(const std::vector< std::string > &heading)
Definition: menu.cpp:380
static style & default_style
Definition: menu.hpp:95
void set_max_width(const int new_max_width)
Definition: menu.cpp:430
int highlight_heading_
Definition: menu.hpp:292
std::pair< int, int > hit_cell(int x, int y) const
Definition: menu.cpp:998
virtual void erase_item(std::size_t index)
Definition: menu.cpp:351
void set_inner_location(const SDL_Rect &rect) override
Definition: menu.cpp:334
const color_t NORMAL_COLOR
std::set< int > numeric_sort_
Definition: menu.hpp:136
bool item_ends_with_image(const std::string &item) const
Definition: menu.cpp:845
virtual void set_items(const std::vector< std::string > &items, bool strip_spaces=true, bool keep_viewport=false)
Set new items to show and redraw/recalculate everything.
Definition: menu.cpp:391
void change_item(int pos1, int pos2, const std::string &str)
Definition: menu.cpp:340
std::size_t get_cell_padding() const
Definition: menu_style.cpp:57
#define DOUBLE_CLICK_EVENT
Definition: events.hpp:24
void invalidate_row(std::size_t id)
Definition: menu.cpp:1132
virtual void draw_row(const std::size_t row_index, const SDL_Rect &rect, ROW_TYPE type)
Definition: menu.cpp:872
int hit_heading(int x, int y) const
Definition: menu.cpp:1013
std::size_t i
Definition: function.cpp:967
bool is_wml_separator(char c)
virtual void handle_event(const SDL_Event &event) override
Definition: menu.cpp:603
bool previous_button_
Definition: menu.hpp:250
const std::string & id() const
Definition: widget.cpp:199
void assert_pos()
Definition: menu.cpp:263
virtual bool less(int column, const item &row1, const item &row2) const
Definition: menu.cpp:93
void invalidate_heading()
Definition: menu.cpp:1150
static map_location::DIRECTION s
std::vector< std::string > quoted_split(const std::string &val, char c, int flags, char quote)
This function is identical to split(), except it does not split when it otherwise would if the previo...
point game_canvas_size()
The size of the game canvas, in drawing coordinates / game pixels.
Definition: video.cpp:422
virtual void handle_event(const SDL_Event &event)
Definition: scrollarea.cpp:147
bool focus_
Definition: widget.hpp:93
rect pango_draw_text(bool actually_draw, const rect &area, int size, const color_t &color, const std::string &text, int x, int y, bool use_tooltips, pango_text::FONT_STYLE style)
Draws text on the screen.
unsigned get_max_position() const
Definition: scrollarea.cpp:86
bool use_ellipsis_
Definition: menu.hpp:287
An abstract description of a rectangle with integer coordinates.
Definition: rect.hpp:46
Holds a 2D point.
Definition: point.hpp:24
void move_selection_to(std::size_t id, bool silent=false, SELECTION_MOVE_VIEWPORT move_viewport=MOVE_VIEWPORT)
Definition: menu.cpp:508
int width() const
Definition: widget.cpp:114
int w
int height() const
Definition: widget.cpp:119
std::size_t index(const std::string &str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:72
bool mouse_locked() const
Definition: widget.cpp:65
bool double_clicked()
Definition: menu.cpp:697
basic_sorter & set_numeric_sort(int column)
Definition: menu.cpp:55
virtual void draw_row_bg(menu &menu_ref, const std::size_t row_index, const SDL_Rect &rect, ROW_TYPE type)
Definition: menu.cpp:785
void blit(const texture &tex, const SDL_Rect &dst)
Draws a texture, or part of a texture, at the given location.
Definition: draw.cpp:301
bool chars_less_insensitive(char a, char b)
Definition: general.hpp:25
std::set< int > alpha_sort_
Definition: menu.hpp:136
const std::string button_press
void key_press(SDL_Keycode key)
Definition: menu.cpp:536
std::size_t get_item_height_internal(const std::vector< std::string > &item) const
Definition: menu.cpp:1073
std::map< int, std::vector< int > > pos_sort_
Definition: menu.hpp:138
char const HEADING_PREFIX
Contains the SDL_Rect helper code.
virtual bool column_sortable(int column) const
Definition: menu.cpp:82
char const COLUMN_SEPARATOR
bool click_selects_
Definition: menu.hpp:248
~menu()
Default implementation, but defined out-of-line for efficiency reasons.
Definition: menu.cpp:172
constexpr const SDL_Rect empty_rect
Definition: rect.hpp:30
std::vector< std::string > split(const config_attribute_value &val)
map_location prev
Definition: astarsearch.cpp:66
SDL_Rect get_item_rect_internal(std::size_t pos) const
Definition: menu.cpp:1029
std::vector< item > items_
Definition: menu.hpp:234
void play_UI_sound(const std::string &files)
Definition: sound.cpp:1066
int hit_column(int x) const
Definition: menu.cpp:986
const sorter * sorter_
Definition: menu.hpp:289
bool requires_event_focus(const SDL_Event *event=nullptr) const override
Definition: menu.cpp:570
bool double_clicked_
Definition: menu.hpp:255
std::size_t selected_
Definition: menu.hpp:247
void clear_item(int item)
Definition: menu.cpp:864
rect inner_location() const
Definition: scrollarea.cpp:134
bool silent_
Definition: menu.hpp:210
Definition: help.cpp:57
int heading_height_
Definition: menu.hpp:238
texture get_texture(const image::locator &i_locator, TYPE type, bool skip_cache)
Returns an image texture suitable for hardware-accelerated rendering.
Definition: picture.cpp:1128
mock_char c
void recalculate_pos()
Definition: menu.cpp:254
static map_location::DIRECTION n
Transitional API for porting SDL_ttf-based code to Pango.
void fill(const SDL_Rect &rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Fill an area with the given colour.
Definition: draw.cpp:41
int max_items_
Definition: menu.hpp:229
void set_max_height(const int new_max_height)
Set a new max height for this menu.
Definition: menu.cpp:422
std::string::const_iterator iterator
Definition: tokenizer.hpp:25
std::map< int, SDL_Rect > itemRects_
Definition: menu.hpp:262
unsigned get_position() const
Definition: scrollarea.cpp:81
style * style_
Definition: menu.hpp:209
void move_selection_up(std::size_t dep)
Definition: menu.cpp:496
std::size_t get_thickness() const
Definition: menu_style.cpp:58