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