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