The Battle for Wesnoth  1.15.4+dev
theme.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 /** @file */
16 
17 #include "theme.hpp"
18 
19 #include "desktop/battery_info.hpp"
20 #include "display.hpp"
21 #include "gettext.hpp"
23 #include "hotkey/hotkey_item.hpp"
24 #include "log.hpp"
25 #include "sdl/rect.hpp"
27 #include "wml_exception.hpp"
28 #include "game_config_view.hpp"
29 #include <sstream>
30 #include <utility>
31 
32 static lg::log_domain log_display("display");
33 #define DBG_DP LOG_STREAM(debug, log_display)
34 #define LOG_DP LOG_STREAM(info, log_display)
35 #define ERR_DP LOG_STREAM(err, log_display)
36 
37 namespace
38 {
39 
40 const std::size_t DefaultFontSize = font::SIZE_NORMAL;
41 const color_t DefaultFontRGB {200, 200, 200};
42 
43 _rect ref_rect {0, 0, 0, 0};
44 }
45 
46 static std::size_t compute(std::string expr, std::size_t ref1, std::size_t ref2 = 0)
47 {
48  std::size_t ref = 0;
49  if(expr[0] == '=') {
50  ref = ref1;
51  expr = expr.substr(1);
52  } else if((expr[0] == '+') || (expr[0] == '-')) {
53  ref = ref2;
54  }
55 
56  return ref + atoi(expr.c_str());
57 }
58 
59 // If x2 or y2 are not specified, use x1 and y1 values
60 static _rect read_rect(const config& cfg)
61 {
62  _rect rect {0, 0, 0, 0};
63  std::vector<std::string> items = utils::split(cfg["rect"].str());
64  if(items.size() >= 1)
65  rect.x1 = atoi(items[0].c_str());
66 
67  if(items.size() >= 2)
68  rect.y1 = atoi(items[1].c_str());
69 
70  if(items.size() >= 3)
71  rect.x2 = atoi(items[2].c_str());
72  else
73  rect.x2 = rect.x1;
74 
75  if(items.size() >= 4)
76  rect.y2 = atoi(items[3].c_str());
77  else
78  rect.y2 = rect.y1;
79 
80  return rect;
81 }
82 
83 static SDL_Rect read_sdl_rect(const config& cfg)
84 {
85  SDL_Rect sdlrect;
86  const _rect rect = read_rect(cfg);
87  sdlrect.x = rect.x1;
88  sdlrect.y = rect.y1;
89  sdlrect.w = (rect.x2 > rect.x1) ? (rect.x2 - rect.x1) : 0;
90  sdlrect.h = (rect.y2 > rect.y1) ? (rect.y2 - rect.y1) : 0;
91 
92  return sdlrect;
93 }
94 
95 static std::string resolve_rect(const std::string& rect_str)
96 {
97  _rect rect {0, 0, 0, 0};
98  std::stringstream resolved;
99  const std::vector<std::string> items = utils::split(rect_str.c_str());
100  if(items.size() >= 1) {
101  rect.x1 = compute(items[0], ref_rect.x1, ref_rect.x2);
102  resolved << rect.x1;
103  }
104  if(items.size() >= 2) {
105  rect.y1 = compute(items[1], ref_rect.y1, ref_rect.y2);
106  resolved << "," << rect.y1;
107  }
108  if(items.size() >= 3) {
109  rect.x2 = compute(items[2], ref_rect.x2, rect.x1);
110  resolved << "," << rect.x2;
111  }
112  if(items.size() >= 4) {
113  rect.y2 = compute(items[3], ref_rect.y2, rect.y1);
114  resolved << "," << rect.y2;
115  }
116 
117  // DBG_DP << "Rect " << rect_str << "\t: " << resolved.str() << "\n";
118 
119  ref_rect = rect;
120  return resolved.str();
121 }
122 
123 static config& find_ref(const std::string& id, config& cfg, bool remove = false)
124 {
125  static config empty_config;
126 
128  for(config::all_children_iterator i = itors.begin(); i != itors.end(); ++i) {
129  config& icfg = i->cfg;
130  if(i->cfg["id"] == id) {
131  if(remove) {
132  cfg.erase(i);
133  return empty_config;
134  } else {
135  return icfg;
136  }
137  }
138 
139  // Recursively look in children.
140  config& c = find_ref(id, icfg, remove);
141  if(&c != &empty_config) {
142  return c;
143  }
144  }
145 
146  // Not found.
147  return empty_config;
148 }
149 
150 #ifdef DEBUG
151 
152 // to be called from gdb
153 static config& find_ref(const char* id, config& cfg)
154 {
155  return find_ref(std::string(id), cfg);
156 }
157 
158 namespace
159 {
160 // avoid some compiler warnings in stricter mode.
161 static config cfg;
162 static config& result = find_ref("", cfg);
163 } // namespace
164 
165 #endif
166 
167 /**
168  * Returns a copy of the wanted resolution.
169  *
170  * The function returns a copy since our caller uses a copy of this resolution
171  * as base to expand a partial resolution.
172  *
173  * @param resolutions A config object containing the expanded
174  * resolutions.
175  * @param id The id of the resolution to return.
176  *
177  * @throw config::error If the @p id is not found.
178  *
179  * @returns A copy of the resolution config.
180  */
181 static config get_resolution(const config& resolutions, const std::string& id)
182 {
183  for(const auto& resolution : resolutions.child_range("resolution")) {
184  if(resolution["id"] == id) {
185  return resolution;
186  }
187  }
188 
189  throw config::error("[partialresolution] refers to non-existent [resolution] " + id);
190 }
191 
192 /**
193  * Returns a config with all partial resolutions of a theme expanded.
194  *
195  * @param theme The original object, whose objects need to be
196  * expanded.
197  *
198  * @returns A new object with the expanded resolutions in
199  * a theme. This object no longer contains
200  * partial resolutions.
201  */
203 {
204  config result;
205 
206  // Add all the resolutions
207  for(const auto& resolution : theme.child_range("resolution")) {
208  result.add_child("resolution", resolution);
209  }
210 
211  // Resolve all the partialresolutions
212  for(const auto& part : theme.child_range("partialresolution")) {
213  config resolution = get_resolution(result, part["inherits"]);
214  resolution.merge_attributes(part);
215 
216  for(const auto& remove : part.child_range("remove")) {
217  VALIDATE(!remove["id"].empty(), missing_mandatory_wml_key("[theme][partialresolution][remove]", "id"));
218 
219  find_ref(remove["id"], resolution, true);
220  }
221 
222  for(const auto& change : part.child_range("change")) {
223  VALIDATE(!change["id"].empty(), missing_mandatory_wml_key("[theme][partialresolution][change]", "id"));
224 
225  config& target = find_ref(change["id"], resolution, false);
226  target.merge_attributes(change);
227  }
228 
229  // cannot add [status] sub-elements, but who cares
230  for(const auto& add : part.child_range("add")) {
231  for(const auto& child : add.all_children_range()) {
232  resolution.add_child(child.key, child.cfg);
233  }
234  }
235 
236  result.add_child("resolution", resolution);
237  }
238 
239  return result;
240 }
241 
242 static void do_resolve_rects(const config& cfg, config& resolved_config, config* resol_cfg = nullptr)
243 {
244  // recursively resolve children
245  for(const config::any_child& value : cfg.all_children_range()) {
246  config& childcfg = resolved_config.add_child(value.key);
247  do_resolve_rects(value.cfg, childcfg, value.key == "resolution" ? &childcfg : resol_cfg);
248  }
249 
250  // copy all key/values
251  resolved_config.merge_attributes(cfg);
252 
253  // override default reference rect with "ref" parameter if any
254  if(!cfg["ref"].empty()) {
255  if(resol_cfg == nullptr) {
256  ERR_DP << "Use of ref= outside a [resolution] block" << std::endl;
257  } else {
258  // DBG_DP << ">> Looking for " << cfg["ref"] << "\n";
259  const config& ref = find_ref(cfg["ref"], *resol_cfg);
260 
261  if(ref["id"].empty()) {
262  ERR_DP << "Reference to non-existent rect id \"" << cfg["ref"] << "\"" << std::endl;
263  } else if(ref["rect"].empty()) {
264  ERR_DP << "Reference to id \"" << cfg["ref"] << "\" which does not have a \"rect\"\n";
265  } else {
266  ref_rect = read_rect(ref);
267  }
268  }
269  }
270  // resolve the rect value to absolute coordinates
271  if(!cfg["rect"].empty()) {
272  resolved_config["rect"] = resolve_rect(cfg["rect"]);
273  }
274 }
275 
277  : location_modified_(false)
278  , id_()
279  , loc_(sdl::empty_rect)
280  , relative_loc_(sdl::empty_rect)
281  , last_screen_(sdl::empty_rect)
282  , xanchor_(object::FIXED)
283  , yanchor_(object::FIXED)
284  , spec_width_(0)
285  , spec_height_(0)
286 {
287 }
288 
289 theme::object::object(std::size_t sw, std::size_t sh, const config& cfg)
290  : location_modified_(false)
291  , id_(cfg["id"])
292  , loc_(read_sdl_rect(cfg))
295  , xanchor_(read_anchor(cfg["xanchor"]))
296  , yanchor_(read_anchor(cfg["yanchor"]))
297  , spec_width_(sw)
298  , spec_height_(sh)
299 {
300 }
301 
303  : size(0.0)
304  , background_image()
305  , tile_image()
306  , show_border(true)
307 {
308 }
309 
311  : size(cfg["border_size"].to_double())
312  , background_image(cfg["background_image"])
313  , tile_image(cfg["tile_image"])
314  , show_border(cfg["show_border"].to_bool(true))
315 {
316  VALIDATE(size >= 0.0 && size <= 0.5, _("border_size should be between 0.0 and 0.5."));
317 }
318 
319 SDL_Rect& theme::object::location(const SDL_Rect& screen) const
320 {
321  if(last_screen_ == screen && !location_modified_)
322  return relative_loc_;
323 
324  last_screen_ = screen;
325 
326  switch(xanchor_) {
327  case FIXED:
328  relative_loc_.x = loc_.x;
329  relative_loc_.w = loc_.w;
330  break;
331  case TOP_ANCHORED:
332  relative_loc_.x = loc_.x;
333  relative_loc_.w = loc_.w + screen.w - std::min<std::size_t>(spec_width_, loc_.w + screen.w);
334  break;
335  case BOTTOM_ANCHORED:
336  relative_loc_.x = loc_.x + screen.w - std::min<std::size_t>(spec_width_, loc_.x + screen.w);
337  relative_loc_.w = loc_.w;
338  break;
339  case PROPORTIONAL:
340  relative_loc_.x = (loc_.x * screen.w) / spec_width_;
341  relative_loc_.w = (loc_.w * screen.w) / spec_width_;
342  break;
343  default:
344  assert(false);
345  }
346 
347  switch(yanchor_) {
348  case FIXED:
349  relative_loc_.y = loc_.y;
350  relative_loc_.h = loc_.h;
351  break;
352  case TOP_ANCHORED:
353  relative_loc_.y = loc_.y;
354  relative_loc_.h = loc_.h + screen.h - std::min<std::size_t>(spec_height_, loc_.h + screen.h);
355  break;
356  case BOTTOM_ANCHORED:
357  relative_loc_.y = loc_.y + screen.h - std::min<std::size_t>(spec_height_, loc_.y + screen.h);
358  relative_loc_.h = loc_.h;
359  break;
360  case PROPORTIONAL:
361  relative_loc_.y = (loc_.y * screen.h) / spec_height_;
362  relative_loc_.h = (loc_.h * screen.h) / spec_height_;
363  break;
364  default:
365  assert(false);
366  }
367 
368  relative_loc_.w = std::min<int>(relative_loc_.w, screen.w);
369  if(relative_loc_.x > screen.w) {
370  relative_loc_.x = std::min<int>(relative_loc_.x, screen.w - relative_loc_.w);
371  }
372  relative_loc_.w = std::min<int>(relative_loc_.w, screen.w - relative_loc_.x);
373 
374  relative_loc_.h = std::min<int>(relative_loc_.h, screen.h);
375  if(relative_loc_.y > screen.h) {
376  relative_loc_.y = std::min<int>(relative_loc_.y, screen.h - relative_loc_.h);
377  }
378  relative_loc_.h = std::min<int>(relative_loc_.h, screen.h - relative_loc_.y);
379 
380  return relative_loc_;
381 }
382 
384 {
385  static const std::string top_anchor = "top", left_anchor = "left", bot_anchor = "bottom", right_anchor = "right",
386  proportional_anchor = "proportional";
387  if(str == top_anchor || str == left_anchor)
388  return TOP_ANCHORED;
389  else if(str == bot_anchor || str == right_anchor)
390  return BOTTOM_ANCHORED;
391  else if(str == proportional_anchor)
392  return PROPORTIONAL;
393  else
394  return FIXED;
395 }
396 
398 {
399  loc_.x = rect.x1;
400  loc_.y = rect.y1;
401  loc_.w = rect.x2 - rect.x1;
402  loc_.h = rect.y2 - rect.y1;
403  location_modified_ = true;
404 }
405 
406 void theme::object::modify_location(std::string rect_str, SDL_Rect location_ref_rect)
407 {
408  _rect rect {0, 0, 0, 0};
409  const std::vector<std::string> items = utils::split(rect_str.c_str());
410  if(items.size() >= 1) {
411  rect.x1 = compute(items[0], location_ref_rect.x, location_ref_rect.x + location_ref_rect.w);
412  }
413  if(items.size() >= 2) {
414  rect.y1 = compute(items[1], location_ref_rect.y, location_ref_rect.y + location_ref_rect.h);
415  }
416  if(items.size() >= 3) {
417  rect.x2 = compute(items[2], location_ref_rect.x + location_ref_rect.w, rect.x1);
418  }
419  if(items.size() >= 4) {
420  rect.y2 = compute(items[3], location_ref_rect.y + location_ref_rect.h, rect.y1);
421  }
422  modify_location(rect);
423 }
424 
426  : text_()
427  , icon_()
428  , font_()
429  , font_rgb_set_(false)
430  , font_rgb_(DefaultFontRGB)
431 {
432 }
433 
434 theme::label::label(std::size_t sw, std::size_t sh, const config& cfg)
435  : object(sw, sh, cfg)
436  , text_(cfg["prefix"].str() + cfg["text"].str() + cfg["postfix"].str())
437  , icon_(cfg["icon"])
438  , font_(cfg["font_size"])
439  , font_rgb_set_(false)
440  , font_rgb_(DefaultFontRGB)
441 {
442  if(font_ == 0)
443  font_ = DefaultFontSize;
444 
445  if(cfg.has_attribute("font_rgb")) {
446  font_rgb_ = color_t::from_rgb_string(cfg["font_rgb"]);
447  font_rgb_set_ = true;
448  }
449 }
450 
451 theme::status_item::status_item(std::size_t sw, std::size_t sh, const config& cfg)
452  : object(sw, sh, cfg)
453  , prefix_(cfg["prefix"].str() + cfg["prefix_literal"].str())
454  , postfix_(cfg["postfix_literal"].str() + cfg["postfix"].str())
455  , label_()
456  , font_(cfg["font_size"])
457  , font_rgb_set_(false)
458  , font_rgb_(DefaultFontRGB)
459 {
460  if(font_ == 0)
461  font_ = DefaultFontSize;
462 
463  if(const config& label_child = cfg.child("label")) {
464  label_ = label(sw, sh, label_child);
465  }
466 
467  if(cfg.has_attribute("font_rgb")) {
468  font_rgb_ = color_t::from_rgb_string(cfg["font_rgb"]);
469  font_rgb_set_ = true;
470  }
471 }
472 
473 theme::panel::panel(std::size_t sw, std::size_t sh, const config& cfg)
474  : object(sw, sh, cfg)
475  , image_(cfg["image"])
476 {
477 }
478 
480  : object()
481  , title_()
482  , tooltip_()
483  , image_()
484  , overlay_()
485  , black_line_(false)
486 {
487 }
488 theme::slider::slider(std::size_t sw, std::size_t sh, const config& cfg)
489  : object(sw, sh, cfg)
490  , title_(cfg["title"].str() + cfg["title_literal"].str())
491  , tooltip_(cfg["tooltip"])
492  , image_(cfg["image"])
493  , overlay_(cfg["overlay"])
494  , black_line_(cfg["black_line"].to_bool(false))
495 {
496 }
497 
499  : object()
500  , button_(true)
501  , context_(false)
502  , title_()
503  , tooltip_()
504  , image_()
505  , overlay_()
506  , items_()
507 {
508 }
509 
510 theme::menu::menu(std::size_t sw, std::size_t sh, const config& cfg)
511  : object(sw, sh, cfg)
512  , button_(cfg["button"].to_bool(true))
513  , context_(cfg["is_context_menu"].to_bool(false))
514  , title_(cfg["title"].str() + cfg["title_literal"].str())
515  , tooltip_(cfg["tooltip"])
516  , image_(cfg["image"])
517  , overlay_(cfg["overlay"])
518  , items_()
519 {
520  for(const auto& item : utils::split(cfg["items"])) {
521  items_.emplace_back("id", item);
522  }
523 
524  if(cfg["auto_tooltip"].to_bool() && tooltip_.empty() && items_.size() == 1) {
525  tooltip_ = hotkey::get_description(items_[0]["id"]) + hotkey::get_names(items_[0]["id"]) + "\n"
526  + hotkey::get_tooltip(items_[0]["id"]);
527  } else if(cfg["tooltip_name_prepend"].to_bool() && items_.size() == 1) {
528  tooltip_ = hotkey::get_description(items_[0]["id"]) + hotkey::get_names(items_[0]["id"]) + "\n" + tooltip_;
529  }
530 }
531 
533  : object()
534  , context_(false)
535  , auto_tooltip_(false)
536  , tooltip_name_prepend_(false)
537  , title_()
538  , tooltip_()
539  , image_()
540  , overlay_()
541  , type_()
542  , items_()
543 {
544 }
545 
546 theme::action::action(std::size_t sw, std::size_t sh, const config& cfg)
547  : object(sw, sh, cfg)
548  , context_(cfg["is_context_menu"].to_bool())
549  , auto_tooltip_(cfg["auto_tooltip"].to_bool(false))
550  , tooltip_name_prepend_(cfg["tooltip_name_prepend"].to_bool(false))
551  , title_(cfg["title"].str() + cfg["title_literal"].str())
552  , tooltip_(cfg["tooltip"])
553  , image_(cfg["image"])
554  , overlay_(cfg["overlay"])
555  , type_(cfg["type"])
556  , items_(utils::split(cfg["items"]))
557 {
558 }
559 
560 const std::string theme::action::tooltip(std::size_t index) const
561 {
562  std::stringstream result;
563  if(auto_tooltip_ && tooltip_.empty() && items_.size() > index) {
564  result << hotkey::get_description(items_[index]);
565  if(!hotkey::get_names(items_[index]).empty())
566  result << "\n" << _("Hotkey(s): ") << hotkey::get_names(items_[index]);
567  result << "\n" << hotkey::get_tooltip(items_[index]);
568  } else if(tooltip_name_prepend_ && items_.size() == 1) {
569  result << hotkey::get_description(items_[index]);
570  if(!hotkey::get_names(items_[index]).empty())
571  result << "\n" << _("Hotkey(s): ") << hotkey::get_names(items_[index]);
572  result << "\n" << tooltip_;
573  } else {
574  result << tooltip_;
575  }
576 
577  return result.str();
578 }
579 
580 theme::theme(const config& cfg, const SDL_Rect& screen)
581  : theme_reset_event_("theme_reset")
582  , cur_theme()
583  , cfg_()
584  , panels_()
585  , labels_()
586  , menus_()
587  , actions_()
588  , context_()
589  , status_()
590  , main_map_()
591  , mini_map_()
592  , unit_image_()
593  , palette_()
594  , border_()
595  , screen_dimensions_(screen)
596  , cur_spec_width_(0)
597  , cur_spec_height_(0)
598 {
600  set_resolution(screen);
601 }
602 
604 {
605  theme_reset_event_ = other.theme_reset_event_;
606  cur_theme = std::move(other.cur_theme);
607  cfg_ = std::move(other.cfg_);
608  panels_ = std::move(other.panels_);
609  labels_ = std::move(other.labels_);
610  menus_ = std::move(other.menus_);
611  actions_ = std::move(other.actions_);
612  sliders_ = std::move(other.sliders_);
613  context_ = other.context_;
614  action_context_ = other.action_context_;
615  status_ = std::move(other.status_);
616  main_map_ = other.main_map_;
617  mini_map_ = other.mini_map_;
618  unit_image_ = other.unit_image_;
619  palette_ = other.palette_;
620  border_ = other.border_;
621 
622  return *this;
623 }
624 
625 bool theme::set_resolution(const SDL_Rect& screen)
626 {
627  screen_dimensions_ = screen;
628 
629  bool result = false;
630 
631  int current_rating = 1000000;
632  const config* current = nullptr;
633  for(const config& i : cfg_.child_range("resolution")) {
634  int width = i["width"];
635  int height = i["height"];
636  LOG_DP << "comparing resolution " << screen.w << "," << screen.h << " to " << width << "," << height << "\n";
637  if(screen.w >= width && screen.h >= height) {
638  LOG_DP << "loading theme: " << width << "," << height << "\n";
639  current = &i;
640  result = true;
641  break;
642  }
643 
644  const int rating = width * height;
645  if(rating < current_rating) {
646  current = &i;
647  current_rating = rating;
648  }
649  }
650 
651  if(!current) {
652  if(cfg_.child_count("resolution")) {
653  ERR_DP << "No valid resolution found" << std::endl;
654  }
655  return false;
656  }
657  cur_spec_width_ = (*current)["width"];
658  cur_spec_height_ = (*current)["height"];
659 
660  std::map<std::string, std::string> title_stash_menus;
662  for(m = menus_.begin(); m != menus_.end(); ++m) {
663  if(!m->title().empty() && !m->get_id().empty())
664  title_stash_menus[m->get_id()] = m->title();
665  }
666 
667  std::map<std::string, std::string> title_stash_actions;
669  for(a = actions_.begin(); a != actions_.end(); ++a) {
670  if(!a->title().empty() && !a->get_id().empty())
671  title_stash_actions[a->get_id()] = a->title();
672  }
673 
674  panels_.clear();
675  labels_.clear();
676  status_.clear();
677  menus_.clear();
678  actions_.clear();
679  sliders_.clear();
680 
682 
683  for(m = menus_.begin(); m != menus_.end(); ++m) {
684  if(title_stash_menus.find(m->get_id()) != title_stash_menus.end())
685  m->set_title(title_stash_menus[m->get_id()]);
686  }
687 
688  for(a = actions_.begin(); a != actions_.end(); ++a) {
689  if(title_stash_actions.find(a->get_id()) != title_stash_actions.end())
690  a->set_title(title_stash_actions[a->get_id()]);
691  }
692 
694 
695  return result;
696 }
697 
698 void theme::add_object(std::size_t sw, std::size_t sh, const config& cfg)
699 {
700  if(const config& c = cfg.child("main_map")) {
701  main_map_ = object(sw, sh, c);
702  }
703 
704  if(const config& c = cfg.child("mini_map")) {
705  mini_map_ = object(sw, sh, c);
706  }
707 
708  if(const config& c = cfg.child("palette")) {
709  palette_ = object(sw, sh, c);
710  }
711 
712  if(const config& status_cfg = cfg.child("status")) {
713  for(const config::any_child& i : status_cfg.all_children_range()) {
714  status_[i.key].reset(new status_item(sw, sh, i.cfg));
715  }
716  if(const config& unit_image_cfg = status_cfg.child("unit_image")) {
717  unit_image_ = object(sw, sh, unit_image_cfg);
718  } else {
719  unit_image_ = object();
720  }
721  }
722 
723  for(const config& p : cfg.child_range("panel")) {
724  panel new_panel(sw, sh, p);
725  set_object_location(new_panel, p["rect"], p["ref"]);
726  panels_.push_back(new_panel);
727  }
728 
729  for(const config& lb : cfg.child_range("label")) {
730  label new_label(sw, sh, lb);
731  set_object_location(new_label, lb["rect"], lb["ref"]);
732  labels_.push_back(new_label);
733  }
734 
735  for(const config& m : cfg.child_range("menu")) {
736  menu new_menu(sw, sh, m);
737  DBG_DP << "adding menu: " << (new_menu.is_context() ? "is context" : "not context") << "\n";
738  if(new_menu.is_context())
739  context_ = new_menu;
740  else {
741  set_object_location(new_menu, m["rect"], m["ref"]);
742  menus_.push_back(new_menu);
743  }
744 
745  DBG_DP << "done adding menu...\n";
746  }
747 
748  for(const config& a : cfg.child_range("action")) {
749  action new_action(sw, sh, a);
750  DBG_DP << "adding action: " << (new_action.is_context() ? "is context" : "not context") << "\n";
751  if(new_action.is_context())
752  action_context_ = new_action;
753  else {
754  set_object_location(new_action, a["rect"], a["ref"]);
755  actions_.push_back(new_action);
756  }
757 
758  DBG_DP << "done adding action...\n";
759  }
760 
761  for(const config& s : cfg.child_range("slider")) {
762  slider new_slider(sw, sh, s);
763  DBG_DP << "adding slider\n";
764  set_object_location(new_slider, s["rect"], s["ref"]);
765  sliders_.push_back(new_slider);
766 
767  DBG_DP << "done adding slider...\n";
768  }
769 
770  if(const config& c = cfg.child("main_map_border")) {
771  border_ = border_t(c);
772  }
773 
774  // Battery charge indicator is always hidden if there isn't enough horizontal space
775  // (GitHub issue #3714)
776  static const int BATTERY_ICON_MIN_WIDTH = 1152;
777  if(!desktop::battery_info::does_device_have_battery() || screen_dimensions_.w < BATTERY_ICON_MIN_WIDTH) {
778  if(const config& c = cfg.child("no_battery")) {
779  modify(c);
780  }
781  }
782 }
783 
785 {
786  if(status_.erase(id) > 0u) {
787  return;
788  }
789 
790  for(auto p = panels_.begin(); p != panels_.end(); ++p) {
791  if(p->get_id() == id) {
792  panels_.erase(p);
793  return;
794  }
795  }
796  for(auto l = labels_.begin(); l != labels_.end(); ++l) {
797  if(l->get_id() == id) {
798  labels_.erase(l);
799  return;
800  }
801  }
802  for(auto m = menus_.begin(); m != menus_.end(); ++m) {
803  if(m->get_id() == id) {
804  menus_.erase(m);
805  return;
806  }
807  }
808  for(auto a = actions_.begin(); a != actions_.end(); ++a) {
809  if(a->get_id() == id) {
810  actions_.erase(a);
811  return;
812  }
813  }
814  for(auto s = sliders_.begin(); s != sliders_.end(); ++s) {
815  if(s->get_id() == id) {
816  sliders_.erase(s);
817  return;
818  }
819  }
820 
821  std::stringstream stream;
822  stream << "theme object " << id << " not found";
823  throw config::error(stream.str());
824 }
825 
827 {
828  theme::object ref_element = element;
829  if(ref_id.empty()) {
830  ref_id = element.get_id();
831  } else {
832  ref_element = find_element(ref_id);
833  }
834  if(ref_element.get_id() == ref_id) {
835  SDL_Rect location_ref_rect = ref_element.get_location();
836  element.modify_location(rect_str, location_ref_rect);
837  }
838 }
839 
840 void theme::modify(const config& cfg)
841 {
842  std::map<std::string, std::string> title_stash;
844  for(m = menus_.begin(); m != menus_.end(); ++m) {
845  if(!m->title().empty() && !m->get_id().empty())
846  title_stash[m->get_id()] = m->title();
847  }
848 
850  for(a = actions_.begin(); a != actions_.end(); ++a) {
851  if(!a->title().empty() && !a->get_id().empty())
852  title_stash[a->get_id()] = a->title();
853  }
854 
855  // Change existing theme objects.
856  for(const config& c : cfg.child_range("change")) {
857  std::string id = c["id"];
858  std::string ref_id = c["ref"];
859  theme::object& element = find_element(id);
860  if(element.get_id() == id)
861  set_object_location(element, c["rect"], ref_id);
862  }
863 
864  // Add new theme objects.
865  for(const config& c : cfg.child_range("add")) {
867  }
868 
869  // Remove existent theme objects.
870  for(const config& c : cfg.child_range("remove")) {
871  remove_object(c["id"]);
872  }
873 
874  for(m = menus_.begin(); m != menus_.end(); ++m) {
875  if(title_stash.find(m->get_id()) != title_stash.end())
876  m->set_title(title_stash[m->get_id()]);
877  }
878  for(a = actions_.begin(); a != actions_.end(); ++a) {
879  if(title_stash.find(a->get_id()) != title_stash.end())
880  a->set_title(title_stash[a->get_id()]);
881  }
882 }
883 
885 {
886  static theme::object empty_object;
887  theme::object* res = &empty_object;
888 
889  auto status_item_it = status_.find(id);
890  if(status_item_it != status_.end()) {
891  res = status_item_it->second.get();
892  }
893 
894  for(std::vector<theme::panel>::iterator p = panels_.begin(); p != panels_.end(); ++p) {
895  if(p->get_id() == id) {
896  res = &(*p);
897  }
898  }
899  for(std::vector<theme::label>::iterator l = labels_.begin(); l != labels_.end(); ++l) {
900  if(l->get_id() == id) {
901  res = &(*l);
902  }
903  }
904  for(std::vector<theme::menu>::iterator m = menus_.begin(); m != menus_.end(); ++m) {
905  if(m->get_id() == id) {
906  res = &(*m);
907  }
908  }
909  for(std::vector<theme::action>::iterator a = actions_.begin(); a != actions_.end(); ++a) {
910  if(a->get_id() == id) {
911  res = &(*a);
912  }
913  }
914  if(id == "main-map") {
915  res = &main_map_;
916  }
917  if(id == "mini-map") {
918  res = &mini_map_;
919  }
920  if(id == "palette") {
921  res = &palette_;
922  }
923  if(id == "unit-image") {
924  res = &unit_image_;
925  }
926  return *res;
927 }
928 
930 {
931  const auto& i = status_.find(key);
932  if(i != status_.end())
933  return i->second.get();
934  else
935  return nullptr;
936 }
937 
938 typedef std::map<std::string, config> known_themes_map;
940 
942 {
943  known_themes.clear();
944  if(!cfg)
945  return;
946 
947  for(const config& thm : cfg->child_range("theme")) {
948  std::string thm_id = thm["id"];
949 
950  if(!thm["hidden"].to_bool(false)) {
951  known_themes[thm_id] = thm;
952  }
953  }
954 }
955 
956 std::vector<theme_info> theme::get_known_themes()
957 {
958  std::vector<theme_info> res;
959 
960  for(known_themes_map::const_iterator i = known_themes.begin(); i != known_themes.end(); ++i) {
961  res.push_back(theme_info());
962  res.back().id = i->first;
963  res.back().name = i->second["name"].t_str();
964  res.back().description = i->second["description"].t_str();
965  }
966 
967  return res;
968 }
969 
971 {
972  for(const theme::menu& m : menus_) {
973  if(m.get_id() == key)
974  return &m;
975  }
976  return nullptr;
977 }
978 
980 {
981  for(const theme::action& a : actions_) {
982  if(a.get_id() == key)
983  return &a;
984  }
985  return nullptr;
986 }
987 
989 {
990  theme::object* res = nullptr;
991 
992  for(std::vector<theme::action>::iterator a = actions_.begin(); a != actions_.end(); ++a) {
993  if(a->get_id() == id) {
994  res = &(*a);
995  a->set_title(new_title);
996  }
997  }
998 
999  for(std::vector<theme::menu>::iterator m = menus_.begin(); m != menus_.end(); ++m) {
1000  if(m->get_id() == id) {
1001  res = &(*m);
1002  m->set_title(new_title);
1003  }
1004  }
1005 
1006  return res;
1007 }
1008 
1010 {
1011  std::string new_title;
1012 
1013  const config& cfg = find_ref(id, cfg_, false);
1014  if(!cfg[title_tag].empty())
1015  new_title = cfg[title_tag].str();
1016 
1017  return refresh_title(id, new_title);
1018 }
1019 
1020 void theme::modify_label(const std::string& id, const std::string& text)
1021 {
1022  theme::label* label = dynamic_cast<theme::label*>(&find_element(id));
1023  if(!label) {
1024  LOG_DP << "Theme contains no label called '" << id << "'.\n";
1025  return;
1026  }
1027  label->set_text(text);
1028 }
std::vector< label > labels_
Definition: theme.hpp:301
std::map< std::string, std::unique_ptr< status_item > > status_
Definition: theme.hpp:309
bool context_
Definition: theme.hpp:239
std::string title_
Definition: theme.hpp:240
virtual SDL_Rect & location(const SDL_Rect &screen) const
Definition: theme.cpp:319
std::string image_
Definition: theme.hpp:240
events::generic_event theme_reset_event_
Definition: theme.hpp:295
config cfg_
Definition: theme.hpp:299
config & child(config_key_type key, int n=0)
Returns the nth child with the given key, or a reference to an invalid config if there is none...
Definition: config.cpp:420
std::string cur_theme
Definition: theme.hpp:298
static lg::log_domain log_display("display")
std::string overlay_
Definition: theme.hpp:240
std::size_t x2
Definition: theme.hpp:31
const_all_children_itors all_children_range() const
In-order iteration over all children.
Definition: config.cpp:921
std::size_t x1
Definition: theme.hpp:31
std::string title_
Definition: theme.hpp:186
status_item(std::size_t sw, std::size_t sh, const config &cfg)
Definition: theme.cpp:451
virtual void notify_observers()
Definition: theme.hpp:31
bool is_context() const
Definition: theme.hpp:169
const SDL_Rect & get_location() const
Definition: theme.hpp:51
std::vector< action > actions_
Definition: theme.hpp:303
Add a special kind of assert to validate whether the input from WML doesn&#39;t contain any problems that...
const action * get_action_item(const std::string &key) const
Definition: theme.cpp:979
static l_noret error(LoadState *S, const char *why)
Definition: lundump.cpp:39
std::string title_
Definition: theme.hpp:210
bool has_attribute(config_key_type key) const
Definition: config.cpp:213
#define a
bool black_line_
Definition: theme.hpp:211
config_array_view child_range(config_key_type key) const
unsigned child_count(config_key_type key) const
Definition: config.cpp:390
child_itors child_range(config_key_type key)
Definition: config.cpp:362
std::vector< config > items_
Definition: theme.hpp:241
static void set_known_themes(const game_config_view *cfg)
Definition: theme.cpp:941
color_t font_rgb_
Definition: theme.hpp:145
std::vector< panel > panels_
Definition: theme.hpp:300
SDL_Rect last_screen_
Definition: theme.hpp:74
std::string image_
Definition: theme.hpp:210
static config expand_partialresolution(const config &theme)
Returns a config with all partial resolutions of a theme expanded.
Definition: theme.cpp:202
std::vector< slider > sliders_
Definition: theme.hpp:304
const std::vector< std::string > items
object mini_map_
Definition: theme.hpp:311
object palette_
Definition: theme.hpp:311
static void do_resolve_rects(const config &cfg, config &resolved_config, config *resol_cfg=nullptr)
Definition: theme.cpp:242
std::string text_
Definition: theme.hpp:116
std::string str
Definition: statement.cpp:110
bool auto_tooltip_
Definition: theme.hpp:185
theme::object & find_element(const std::string &id)
Definition: theme.cpp:884
void remove_object(const std::string &id)
Definition: theme.cpp:784
std::string type_
Definition: theme.hpp:186
bool context_
Definition: theme.hpp:185
std::string overlay_
Definition: theme.hpp:210
std::string tooltip_
Definition: theme.hpp:186
std::size_t font_
Definition: theme.hpp:117
static std::map< std::string, config > known_themes
Definition: theme.hpp:297
std::string missing_mandatory_wml_key(const std::string &section, const std::string &key, const std::string &primary_key, const std::string &primary_value)
Returns a standard message for a missing wml key.
const int SIZE_NORMAL
Definition: constants.cpp:19
map_location loc_
std::string image_
Definition: theme.hpp:186
panel(std::size_t sw, std::size_t sh, const config &cfg)
Definition: theme.cpp:473
const std::string & get_tooltip(const std::string &command)
std::string get_names(const std::string &id)
Returns a comma-separated string of hotkey names.
static UNUSEDNOWARN std::string _(const char *str)
Definition: gettext.hpp:100
std::size_t y1
Definition: theme.hpp:31
static config get_resolution(const config &resolutions, const std::string &id)
Returns a copy of the wanted resolution.
Definition: theme.cpp:181
#define VALIDATE(cond, message)
The macro to use for the validation of WML.
std::vector< menu > menus_
Definition: theme.hpp:302
std::size_t spec_width_
Definition: theme.hpp:77
menu context_
Definition: theme.hpp:306
bool button_
Definition: theme.hpp:238
std::string background_image
Definition: theme.hpp:90
theme(const config &cfg, const SDL_Rect &screen)
Definition: theme.cpp:580
double size
Definition: theme.hpp:88
void modify(const config &cfg)
Definition: theme.cpp:840
map_display and display: classes which take care of displaying the map and game-data on the screen...
const std::string & get_id() const
Definition: theme.hpp:52
std::string tooltip_
Definition: theme.hpp:210
all_children_iterator erase(const all_children_iterator &i)
Definition: config.cpp:689
std::string image_
Definition: theme.hpp:158
#define ERR_DP
Definition: theme.cpp:35
std::string icon_
Definition: theme.hpp:116
#define LOG_DP
Definition: theme.cpp:34
std::size_t i
Definition: function.cpp:933
bool show_border
Definition: theme.hpp:93
std::size_t cur_spec_height_
Definition: theme.hpp:316
static _rect read_rect(const config &cfg)
Definition: theme.cpp:60
static std::vector< theme_info > get_known_themes()
Definition: theme.cpp:956
u64 size
Definition: statement.cpp:80
mock_party p
object * refresh_title(const std::string &id, const std::string &new_title)
Definition: theme.cpp:988
static void expr(LexState *ls, expdesc *v)
Definition: lparser.cpp:1078
std::string tooltip_
Definition: theme.hpp:240
static map_location::DIRECTION s
SDL_Rect screen_dimensions_
Definition: theme.hpp:315
boost::iterator_range< all_children_iterator > all_children_itors
Definition: config.hpp:637
Definition: theme.hpp:40
void modify_label(const std::string &id, const std::string &text)
Definition: theme.cpp:1020
Definitions related to theme-support.
theme & operator=(const theme &)=delete
ANCHORING xanchor_
Definition: theme.hpp:76
bool is_context() const
Definition: theme.hpp:224
const menu * get_menu_item(const std::string &key) const
Definition: theme.cpp:970
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 map_location::DIRECTION sw
const std::string & get_description(const std::string &command)
config & add_child(config_key_type key)
Definition: config.cpp:476
void merge_attributes(const config &)
Definition: config.cpp:788
std::size_t cur_spec_width_
Definition: theme.hpp:316
const status_item * get_status_item(const std::string &item) const
Definition: theme.cpp:929
std::size_t font_
Definition: theme.hpp:143
bool location_modified_
Definition: theme.hpp:70
bool tooltip_name_prepend_
Definition: theme.hpp:185
void add_object(std::size_t sw, std::size_t sh, const config &cfg)
Definition: theme.cpp:698
bool set_resolution(const SDL_Rect &screen)
Definition: theme.cpp:625
#define DBG_DP
Definition: theme.cpp:33
void set_text(const std::string &text)
Definition: theme.hpp:107
static config & find_ref(const std::string &id, config &cfg, bool remove=false)
Definition: theme.cpp:123
border_t border_
Definition: theme.hpp:313
Contains the SDL_Rect helper code.
std::string tile_image
Definition: theme.hpp:91
static SDL_Rect read_sdl_rect(const config &cfg)
Definition: theme.cpp:83
constexpr const SDL_Rect empty_rect
Definition: rect.hpp:31
std::vector< std::string > split(const config_attribute_value &val)
std::map< std::string, config > known_themes_map
Definition: theme.cpp:938
static std::size_t compute(std::string expr, std::size_t ref1, std::size_t ref2=0)
Definition: theme.cpp:46
std::string overlay_
Definition: theme.hpp:186
std::size_t y2
Definition: theme.hpp:31
std::vector< std::string > items_
Definition: theme.hpp:187
object * refresh_title2(const std::string &id, const std::string &title_tag)
Definition: theme.cpp:1009
Standard logging facilities (interface).
void modify_location(const _rect &rect)
Definition: theme.cpp:397
std::string id_
Definition: theme.hpp:71
std::size_t spec_height_
Definition: theme.hpp:77
void set_object_location(theme::object &element, std::string rect_str, std::string ref_id)
Definition: theme.cpp:826
object unit_image_
Definition: theme.hpp:311
point resolution()
Definition: general.cpp:374
color_t font_rgb_
Definition: theme.hpp:119
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:68
static ANCHORING read_anchor(const std::string &str)
Definition: theme.cpp:383
mock_char c
bool font_rgb_set_
Definition: theme.hpp:118
ANCHORING yanchor_
Definition: theme.hpp:76
SDL_Rect relative_loc_
Definition: theme.hpp:73
static color_t from_rgb_string(const std::string &c)
Creates a new opaque color_t object from a string variable in "R,G,B" format.
Definition: color.cpp:41
std::string::const_iterator iterator
Definition: tokenizer.hpp:24
const std::string tooltip(std::size_t index) const
Definition: theme.cpp:560
static std::string resolve_rect(const std::string &rect_str)
Definition: theme.cpp:95
std::pair< std::string, unsigned > item
Definition: help_impl.hpp:384
object main_map_
Definition: theme.hpp:311
action action_context_
Definition: theme.hpp:307
SDL_Rect loc_
Definition: theme.hpp:72