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