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