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