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