The Battle for Wesnoth  1.19.13+dev
context_manager.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
3  by David White <dave@whitevine.net>
4  Part of the Battle for Wesnoth Project https://www.wesnoth.org/
5 
6  This program is free software; you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation; either version 2 of the License, or
9  (at your option) any later version.
10  This program is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY.
12 
13  See the COPYING file for more details.
14 */
15 
16 #define GETTEXT_DOMAIN "wesnoth-editor"
17 
18 #include "resources.hpp"
19 #include "team.hpp"
20 
21 #include "addon/validation.hpp"
22 
25 #include "filesystem.hpp"
26 #include "formula/string_utils.hpp"
29 #include "gettext.hpp"
30 #include "video.hpp"
31 
32 #include "editor/action/action.hpp"
35 
37 #include "gui/dialogs/prompt.hpp"
42 #include "gui/dialogs/message.hpp"
44 #include "gui/widgets/retval.hpp"
45 
49 #include "game_config_view.hpp"
50 
51 #include "serialization/markup.hpp"
52 #include "terrain/translation.hpp"
53 
54 #include <memory>
55 #include <boost/algorithm/string.hpp>
56 #include <boost/filesystem.hpp>
57 
58 namespace {
59 
60 std::vector<std::unique_ptr<editor::map_context>> saved_contexts_;
61 int last_context_ = 0;
62 
63 const std::string get_menu_marker(const bool changed)
64 {
65  if (changed) {
66  return "[" + markup::span_color("#f00", font::unicode_bullet) + "]";
67  } else {
68  return font::unicode_bullet;
69  }
70 }
71 
72 }
73 
74 namespace editor {
75 
77  : locs_(nullptr)
78  , gui_(gui)
79  , game_config_(game_config)
81  , current_addon_(addon_id)
82  , map_generators_()
83  , last_map_generator_(nullptr)
84  , current_context_index_(0)
85  , auto_update_transitions_(prefs::get().editor_auto_update_transitions())
86  , map_contexts_()
87  , clipboard_()
88 {
89  resources::filter_con = this;
92 }
93 
95 {
96  // Restore default window title
98 
99  resources::filter_con = nullptr;
100 }
101 
103 {
105 
106  // TODO register the tod_manager with the gui?
109 
110  // Reset side when switching to an existing scenario
111  if(!get_map_context().teams().empty()) {
112  gui().set_viewing_team_index(0, true);
114  }
115  gui().init_flags();
116 
117  reload_map();
118 
119  // Enable the labels of the current context;
121 
123 }
124 
126 {
127  gui_.rebuild_all();
133  if(locs_) {
134  for(const auto& loc : get_map_context().map().special_locations().left) {
135  locs_->add_item(loc.first);
136  }
137  if(!get_map_context().is_pure_map()) {
138  // If the scenario has more than 9 teams, add locations for them
139  // (First 9 teams are always in the list)
140  size_t n_teams = get_map_context().teams().size();
141  for(size_t i = 10; i <= n_teams; i++) {
142  locs_->add_item(std::to_string(i));
143  }
144  }
145  }
146 }
147 
149 {
150  gui_.reload_map();
153  refresh_all();
154 }
155 
157 {
158  switch (auto_update_transitions_) {
160  return (item == "editor-auto-update-transitions");
162  return (item == "editor-partial-update-transitions");
164  return (item == "editor-no-update-transitions");
165  }
166 
167  return true; //should not be reached
168 }
169 
171 {
173  prefs::get().set_editor_auto_update_transitions(auto_update_transitions_);
174 
176  return true;
177  }
178 
179  return false;
180 }
181 
182 std::size_t context_manager::modified_maps(std::string& message)
183 {
184  std::vector<std::string> modified;
185  for(auto& mc : map_contexts_) {
186  if(mc->modified()) {
187  if(!mc->get_name().empty()) {
188  modified.push_back(mc->get_name());
189  } else if(!mc->get_filename().empty()) {
190  modified.push_back(mc->get_filename());
191  } else {
192  modified.push_back(mc->get_default_context_name());
193  }
194  }
195  }
196 
197  for(std::string& str : modified) {
198  message += "\n" + font::unicode_bullet + " " + str;
199  }
200 
201  return modified.size();
202 }
203 
204 void context_manager::load_map_dialog(bool force_same_context /* = false */)
205 {
206  std::string fn = get_map_context().get_filename();
207  if(fn.empty()) {
209  fn = filesystem::get_legacy_editor_dir() + "/maps";
210  } else {
212  }
213  }
214 
216 
217  dlg.set_title(_("Load Map or Scenario"))
218  .set_path(fn);
219 
220  if(dlg.show()) {
221  load_map(dlg.path(), !force_same_context);
222  }
223 }
224 
225 void context_manager::load_mru_item(unsigned index, bool force_same_context /* = false */)
226 {
227  const std::vector<std::string>& mru = prefs::get().recent_files();
228  if(mru.empty() || index >= mru.size()) {
229  return;
230  }
231 
232  load_map(mru[index], !force_same_context);
233 }
234 
236 {
237  editor_team_info team_info(t);
238 
239  if(gui2::dialogs::editor_edit_side::execute(team_info)) {
240  get_map_context().set_side_setup(team_info);
241  }
242 }
243 
245 {
246  if(!current_addon_.empty()) {
247  std::string pbl = filesystem::get_current_editor_dir(current_addon_) + "/_server.pbl";
248  gui2::dialogs::editor_edit_pbl::execute(pbl, current_addon_);
249  }
250 }
251 
253 {
254  std::string new_addon_id = current_addon_;
255  gui2::dialogs::prompt::execute(new_addon_id);
256 
257  std::string old_dir = filesystem::get_current_editor_dir(current_addon_);
258  std::string new_dir = filesystem::get_current_editor_dir(new_addon_id);
259  if(addon_filename_legal(new_addon_id) && filesystem::rename_dir(old_dir, new_dir)) {
260  std::string main_cfg = new_dir + "/_main.cfg";
261  std::string main = filesystem::read_file(main_cfg);
262 
263  // update paths
264  boost::replace_all(main, "/"+current_addon_, "/"+new_addon_id);
265  // update textdomain
266  boost::replace_all(main, "wesnoth-"+current_addon_, "wesnoth-"+new_addon_id);
267  filesystem::write_file(main_cfg, main);
268 
269  current_addon_ = new_addon_id;
270 
271  for(std::unique_ptr<map_context>& context : map_contexts_) {
272  context->set_addon_id(current_addon_);
273  }
274  }
275 }
276 
278 {
279  map_context& context = get_map_context();
280 
281  std::string id = context.get_id();
282  std::string name = context.get_name();
283  std::string description = context.get_description();
284 
285  int turns = context.get_time_manager()->number_of_turns();
286  int xp_mod = context.get_xp_mod() ? *context.get_xp_mod() : 70;
287 
288  bool victory = context.victory_defeated();
289  bool random = context.random_start_time();
290 
291  const bool ok = gui2::dialogs::editor_edit_scenario::execute(
292  id, name, description, turns, xp_mod, victory, random
293  );
294 
295  if(!ok) {
296  return;
297  }
298 
299  context.set_scenario_setup(id, name, description, turns, xp_mod, victory, random);
300 
301  if(!name.empty()) {
303  }
304 }
305 
307 {
308  const editor_map& map = get_map_context().map();
309 
310  int w = map.w();
311  int h = map.h();
312 
313  if(gui2::dialogs::editor_new_map::execute(_("New Map"), w, h)) {
315  new_map(w, h, fill, true);
316  }
317 }
318 
320 {
321  const editor_map& map = get_map_context().map();
322 
323  int w = map.w();
324  int h = map.h();
325 
326  if(gui2::dialogs::editor_new_map::execute(_("New Scenario"), w, h)) {
328  new_scenario(w, h, fill, true);
329  }
330 }
331 
332 void context_manager::expand_open_maps_menu(std::vector<config>& items) const
333 {
334  for(std::size_t mci = 0; mci < map_contexts_.size(); ++mci) {
335  map_context& mc = *map_contexts_[mci];
336 
337  std::string filename;
338  if(mc.is_pure_map()) {
340  } else {
341  filename = mc.get_name();
342  }
343 
344  if(filename.empty()) {
346  }
347 
348  std::ostringstream ss;
349  ss << "[" << mci + 1 << "] ";
350 
351  const bool changed = mc.modified();
352 
353  if(changed) {
354  ss << markup::italic(filename);
355  } else {
356  ss << filename;
357  }
358 
359  if(mc.is_embedded()) {
360  ss << " (E)";
361  }
362 
363  const std::string label = ss.str();
364  const std::string details = get_menu_marker(changed);
365 
366  items.emplace_back("label", label, "details", details);
367  }
368 }
369 
370 void context_manager::expand_load_mru_menu(std::vector<config>& items) const
371 {
372  std::vector<std::string> mru = prefs::get().recent_files();
373  if(mru.empty()) {
374  items.emplace_back("label", _("No Recent Files"));
375  return;
376  }
377 
378  for(const std::string& path : mru) {
379  // TODO: add proper leading ellipsization instead, since otherwise
380  // it'll be impossible to tell apart files with identical names and
381  // different parent paths.
382  items.emplace_back("label", filesystem::base_name(path));
383  }
384 }
385 
386 void context_manager::expand_areas_menu(std::vector<config>& items) const
387 {
389  if(!tod) {
390  return;
391  }
392 
393  std::vector<std::string> area_ids = tod->get_area_ids();
394 
395  for(std::size_t mci = 0; mci < area_ids.size(); ++mci) {
396  const std::string& area = area_ids[mci];
397 
398  std::stringstream ss;
399  ss << "[" << mci + 1 << "] ";\
400 
401  if(area.empty()) {
402  ss << markup::italic(_("Unnamed Area"));
403  } else {
404  ss << area;
405  }
406 
407  const bool changed =
408  mci == static_cast<std::size_t>(get_map_context().get_active_area())
409  && tod->get_area_by_index(mci) != get_map_context().map().selection();
410 
411  const std::string label = ss.str();
412  const std::string details = get_menu_marker(changed);
413 
414  items.emplace_back("label", label, "details", details);
415  }
416 }
417 
418 void context_manager::expand_sides_menu(std::vector<config>& items) const
419 {
420  for(std::size_t mci = 0; mci < get_map_context().teams().size(); ++mci) {
421 
422  const team& t = get_map_context().teams()[mci];
423  const std::string& teamname = t.user_team_name();
424  std::stringstream label;
425  label << "[" << mci+1 << "] ";
426 
427  if(teamname.empty()) {
428  label << markup::italic(_("New Side"));
429  } else {
430  label << teamname;
431  }
432 
433  items.emplace_back("label", label.str());
434  }
435 }
436 
437 void context_manager::expand_time_menu(std::vector<config>& items) const
438 {
439  const tod_manager* tod_m = get_map_context().get_time_manager();
440  assert(tod_m != nullptr);
441 
442  for(const time_of_day& time : tod_m->times()) {
443  // Use 'details' field here since the image will take the first column
444  items.emplace_back("details", time.name, "image", time.image);
445  }
446 }
447 
448 void context_manager::expand_local_time_menu(std::vector<config>& items) const
449 {
450  const tod_manager* tod_m = get_map_context().get_time_manager();
451 
452  for(const time_of_day& time : tod_m->times(get_map_context().get_active_area())) {
453  // Use 'details' field here since the image will take the first column
454  items.emplace_back("details", time.name, "image", time.image);
455  }
456 }
457 
459 {
460  std::string fn = get_map_context().get_filename();
461  if(fn.empty()) {
463  }
464 
466 
467  dlg.set_title(_("Apply Mask"))
468  .set_path(fn);
469 
470  if(dlg.show()) {
471  try {
473  editor_action_apply_mask a(mask.map());
474  perform_refresh(a);
475  } catch (const editor_map_load_exception& e) {
476  gui2::show_transient_message(_("Error loading mask"), e.what());
477  return;
478  } catch (const editor_action_exception& e) {
479  gui2::show_error_message(e.what());
480  return;
481  }
482  }
483 }
484 
485 void context_manager::perform_refresh(const editor_action& action, bool drag_part /* =false */)
486 {
488  refresh_after_action(drag_part);
489 }
490 
492 {
493  int active_area = get_map_context().get_active_area();
494  std::string name = get_map_context().get_time_manager()->get_area_ids()[active_area];
495 
496  if(gui2::dialogs::edit_text::execute(N_("Rename Area"), N_("Identifier:"), name)) {
497  get_map_context().get_time_manager()->set_area_id(active_area, name);
498  }
499 }
500 
502 {
503  std::string fn = get_map_context().get_filename();
504  if(fn.empty()) {
506  }
507 
509 
510  dlg.set_title(_("Choose Target Map"))
511  .set_path(fn);
512 
513  if(dlg.show()) {
514  try {
517  perform_refresh(a);
518  } catch (const editor_map_load_exception& e) {
519  gui2::show_transient_message(_("Error loading map"), e.what());
520  return;
521  } catch (const editor_action_exception& e) {
522  gui2::show_error_message(e.what());
523  return;
524  }
525  }
526 }
527 
529 {
530  if(get_map_context().needs_reload()) {
531  reload_map();
532  return;
533  }
534 
535  const std::set<map_location>& changed_locs = get_map_context().changed_locations();
536 
537  if(get_map_context().needs_terrain_rebuild()) {
540  && (!drag_part || get_map_context().everything_changed())))
541  {
542  gui_.rebuild_all();
545  } else {
546  for(const map_location& loc : changed_locs) {
548  }
549  gui_.invalidate(changed_locs);
550  }
551  } else {
552  if(get_map_context().everything_changed()) {
554  } else {
555  gui_.invalidate(changed_locs);
556  }
557  }
558 
559  if(get_map_context().needs_labels_reset()) {
561  }
562 
565 }
566 
568 {
569  const editor_map& map = get_map_context().map();
570 
571  int w = map.w();
572  int h = map.h();
573 
575  bool copy = false;
576 
577  if(!gui2::dialogs::editor_resize_map::execute(w, h, dir, copy)) {
578  return;
579  }
580 
581  if(w != map.w() || h != map.h()) {
583  if(copy) {
585  }
586 
587  int x_offset = map.w() - w;
588  int y_offset = map.h() - h;
589 
590  switch (dir) {
594  y_offset = 0;
595  break;
599  y_offset /= 2;
600  break;
604  break;
605  default:
606  y_offset = 0;
607  WRN_ED << "Unknown resize expand direction";
608  break;
609  }
610 
611  switch (dir) {
615  x_offset = 0;
616  break;
620  x_offset /= 2;
621  break;
625  break;
626  default:
627  x_offset = 0;
628  break;
629  }
630 
631  editor_action_resize_map a(w, h, x_offset, y_offset, fill);
632  perform_refresh(a);
633  }
634 }
635 
637 {
638  bool first_pick = false;
639  std::string input_name = get_map_context().get_filename();
640  if(input_name.empty()) {
641  first_pick = true;
643  input_name = filesystem::get_legacy_editor_dir() + "/maps";
644  } else {
646  }
647  }
648 
650 
651  dlg.set_title(_("Save Map As"))
652  .set_save_mode(true)
653  .set_path(input_name)
656 
657  if(!dlg.show()) {
658  return;
659  }
660 
661  boost::filesystem::path save_path(dlg.path());
662 
663  // Show warning the first time user tries to save in a wrong folder
664  std::string last_folder = save_path.parent_path().filename().string();
665  if ((last_folder == "scenarios")
666  && first_pick
667  && (gui2::show_message(
668  _("Error"),
669  VGETTEXT("Do you really want to save $type1 in $type2 folder?", {{"type1", "map"}, {"type2", "scenarios"}}),
671  {
672  return;
673  }
674 
675  std::size_t is_open = check_open_map(save_path.string());
676  if(is_open < map_contexts_.size() && is_open != static_cast<unsigned>(current_context_index_)) {
677  gui2::show_transient_message(_("This map is already open."), save_path.string());
678  }
679 
680  std::string old_filename = get_map_context().get_filename();
681 
682  get_map_context().set_filename(save_path.string());
683 
684  if(!write_map(true)) {
685  get_map_context().set_filename(old_filename);
686  }
687 }
688 
690 {
691  bool first_pick = false;
692  std::string input_name = get_map_context().get_filename();
693  if(input_name.empty()) {
694  first_pick = true;
696  }
697 
699 
700  dlg.set_title(_("Save Scenario As"))
701  .set_save_mode(true)
702  .set_path(input_name)
705 
706  if(!dlg.show()) {
707  return;
708  }
709 
710  boost::filesystem::path save_path(dlg.path());
711 
712  // Show warning the first time user tries to save in a wrong folder
713  std::string last_folder = save_path.parent_path().filename().string();
714  if ((last_folder == "maps")
715  && first_pick
716  && (gui2::show_message(
717  _("Error"),
718  VGETTEXT("Do you really want to save $type1 in $type2 folder?", {{"type1", "scenario"}, {"type2", "maps"}}),
720  {
721  return;
722  }
723 
724  std::size_t is_open = check_open_map(save_path.string());
725  if(is_open < map_contexts_.size() && is_open != static_cast<unsigned>(current_context_index_)) {
726  gui2::show_transient_message(_("This scenario is already open."), save_path.string());
727  return;
728  }
729 
730  std::string old_filename = get_map_context().get_filename();
731 
732  get_map_context().set_filename(save_path.string());
733 
734  if(!write_scenario(true)) {
735  get_map_context().set_filename(old_filename);
736  return;
737  }
738 }
739 
741 {
742  for(const config& i : game_config.child_range("multiplayer")) {
743  if(i["map_generation"].empty() && i["scenario_generation"].empty()) {
744  continue;
745  }
746 
747  if(const auto generator_cfg = i.optional_child("generator")) {
748  map_generators_.emplace_back(create_map_generator(i["map_generation"].empty() ? i["scenario_generation"] : i["map_generation"], generator_cfg.value()));
749  } else {
750  ERR_ED << "Scenario \"" << i["name"] << "\" with id " << i["id"]
751  << " has map_generation= but no [generator] tag";
752  }
753  }
754 }
755 
757 {
758  if(map_generators_.empty()) {
759  gui2::show_error_message(_("No random map generators found."));
760  return;
761  }
762 
765 
766  if(dialog.show()) {
767  std::string map_string;
769  try {
770  map_string = map_generator->create_map(dialog.get_seed());
771  } catch (const mapgen_exception& e) {
772  gui2::show_transient_message(_("Map creation failed."), e.what());
773  return;
774  }
775 
776  if(map_string.empty()) {
777  gui2::show_transient_message("", _("Map creation failed."));
778  } else {
779  editor_map new_map(map_string);
781  get_map_context().set_needs_labels_reset(); // Ensure Player Start labels are updated together with newly generated map
782  perform_refresh(a);
783  }
784 
786  }
787 }
788 
790 {
791  if(get_map_context().modified()) {
792  const int res = gui2::show_message(_("Unsaved Changes"),
793  _("Do you want to discard all changes made to the map since the last save?"), gui2::dialogs::message::yes_no_buttons);
794  return gui2::retval::CANCEL != res;
795  }
796 
797  return true;
798 }
799 
801 {
803 }
804 
806 {
807  int current = current_context_index_;
808  for(std::size_t i = 0; i < map_contexts_.size(); ++i) {
809  switch_context(i);
810  save_map();
811  }
812  switch_context(current);
813 }
814 
816 {
817  saved_contexts_.swap(map_contexts_);
818  std::swap(last_context_, current_context_index_);
820  switch_context(0, true);
821 }
822 
823 void context_manager::save_map(bool show_confirmation)
824 {
825  const std::string& name = get_map_context().get_filename();
826  if(name.empty() || filesystem::is_directory(name)) {
827  if(get_map_context().is_pure_map()) {
829  } else {
831  }
832  } else {
833  if(get_map_context().is_pure_map()) {
834  write_map(show_confirmation);
835  } else {
836  write_scenario(show_confirmation);
837  }
838  }
839 }
840 
841 bool context_manager::write_scenario(bool display_confirmation)
842 {
843  try {
845  if(display_confirmation) {
846  gui_.set_status(_("Scenario saved."), true);
847  }
848  } catch (const editor_map_save_exception& e) {
849  gui_.set_status(e.what(), false);
850  return false;
851  }
852 
853  return true;
854 }
855 
856 bool context_manager::write_map(bool display_confirmation)
857 {
858  try {
860  if(display_confirmation) {
861  gui_.set_status(_("Map saved"), true);
862  }
863  } catch (const editor_map_save_exception& e) {
864  gui_.set_status(e.what(), false);
865  return false;
866  }
867 
868  return true;
869 }
870 
871 std::size_t context_manager::check_open_map(const std::string& fn) const
872 {
873  std::size_t i = 0;
874  while(i < map_contexts_.size() && map_contexts_[i]->get_filename() != fn) {
875  ++i;
876  }
877 
878  return i;
879 }
880 
881 bool context_manager::check_switch_open_map(const std::string& fn)
882 {
883  std::size_t i = check_open_map(fn);
884  if(i < map_contexts_.size()) {
885  gui2::show_transient_message(_("This map is already open."), fn);
886  switch_context(i);
887  return true;
888  }
889 
890  return false;
891 }
892 
893 void context_manager::load_map(const std::string& filename, bool new_context)
894 {
895  if(new_context && check_switch_open_map(filename)) {
896  return;
897  }
898 
901  // if no addon id has been set and the file being loaded is from an addon
902  // then use the file path to determine the addon rather than showing a dialog
903  if(auto addon_at_path = filesystem::get_addon_id_from_path(filename)) {
904  editor_controller::current_addon_id_ = addon_at_path.value();
905  } else {
907  }
908 
910  }
911 
913  return;
914  }
915  }
916 
917  LOG_ED << "Load map: " << filename << (new_context ? " (new)" : " (same)");
918  try {
919  {
920  auto mc = std::make_unique<map_context>(game_config_, filename, current_addon_);
921  if(mc->get_filename() != filename) {
922  if(new_context && check_switch_open_map(mc->get_filename())) {
923  return;
924  }
925  }
926 
927  if(new_context) {
928  int new_id = add_map_context_of(std::move(mc));
929  switch_context(new_id);
930  } else {
931  replace_map_context_with(std::move(mc));
932  }
933  }
934 
935  if(get_map_context().is_embedded()) {
936  const std::string& msg = _("Loaded embedded map data");
937  gui2::show_transient_message(_("Map loaded from scenario"), msg);
938  } else {
940  gui2::show_transient_message(_("Map loaded from scenario"), _("Loaded referenced map file:")+"\n"+get_map_context().get_filename());
941  }
942  }
943  } catch(const editor_map_load_exception& e) {
944  gui2::show_transient_message(_("Error loading map"), e.what());
945  return;
946  }
947 }
948 
950 {
951  if(!confirm_discard()) {
952  return;
953  }
954 
955  std::string filename = get_map_context().get_filename();
956  if(filename.empty()) {
957  ERR_ED << "Empty filename in map revert";
958  return;
959  }
960 
961  load_map(filename, false);
962 }
963 
964 void context_manager::init_context(int width, int height, const t_translation::terrain_code& fill, bool new_context, bool is_pure_map) {
965  const config& default_schedule = game_config_.find_mandatory_child("editor_times", "id", "empty");
966  editor_map m(width, height, fill);
967 
968  if(new_context) {
969  int new_id = add_map_context(m, is_pure_map, default_schedule, current_addon_);
970  switch_context(new_id);
971  } else {
972  replace_map_context(m, is_pure_map, default_schedule, current_addon_);
973  }
974 }
975 
976 void context_manager::new_map(int width, int height, const t_translation::terrain_code& fill, bool new_context)
977 {
978  init_context(width, height, fill, new_context, true);
979 }
980 
981 void context_manager::new_scenario(int width, int height, const t_translation::terrain_code& fill, bool new_context)
982 {
983  init_context(width, height, fill, new_context, false);
984 
985  // Give the new scenario an initial side.
987  gui().set_viewing_team_index(0, true);
989  gui_.init_flags();
990 }
991 
993 {
994  const config& default_schedule = game_config_.find_mandatory_child("editor_times", "id", "empty");
995  replace_map_context(get_map_context().map(), false, default_schedule, current_addon_);
996 
997  // Give the converted scenario a number of sides
998  // equal to the number of valid starting positions.
999  int start_pos_count = get_map_context().map().num_valid_starting_positions();
1000  if(start_pos_count == 0) {
1001  start_pos_count = 1;
1002  }
1003  for(int i = 0; i < start_pos_count; i++) {
1005  }
1006  gui().set_viewing_team_index(0, true);
1008  gui_.init_flags();
1009 }
1010 
1011 //
1012 // Context manipulation
1013 //
1014 
1015 template<typename... T>
1017 {
1018  map_contexts_.emplace_back(std::make_unique<map_context>(args...));
1019  return map_contexts_.size() - 1;
1020 }
1021 
1022 int context_manager::add_map_context_of(std::unique_ptr<map_context>&& mc)
1023 {
1024  map_contexts_.emplace_back(std::move(mc));
1025  return map_contexts_.size() - 1;
1026 }
1027 
1028 template<typename... T>
1030 {
1031  replace_map_context_with(std::move(std::make_unique<map_context>(args...)));
1032 }
1033 
1034 void context_manager::replace_map_context_with(std::unique_ptr<map_context>&& mc)
1035 {
1036  map_contexts_[current_context_index_] = std::move(mc);
1038 }
1039 
1041 {
1042  if(saved_contexts_.empty()) {
1044  switch_context(0, true);
1045  } else {
1046  saved_contexts_.swap(map_contexts_);
1047  switch_context(last_context_, true);
1048  last_context_ = 0;
1049  }
1050 }
1051 
1053 {
1056 
1057  const config& default_schedule = game_config_.find_mandatory_child("editor_times", "id", "empty");
1058  add_map_context(editor_map(44, 33, default_terrain), true, default_schedule, current_addon_);
1059 }
1060 
1062 {
1063  if(!confirm_discard()) return;
1064 
1065  if(map_contexts_.size() == 1) {
1067  map_contexts_.erase(map_contexts_.begin());
1068  } else if(current_context_index_ == static_cast<int>(map_contexts_.size()) - 1) {
1069  map_contexts_.pop_back();
1071  } else {
1073  }
1074 
1076 }
1077 
1078 void context_manager::switch_context(const int index, const bool force)
1079 {
1080  if(index < 0 || static_cast<std::size_t>(index) >= map_contexts_.size()) {
1081  WRN_ED << "Invalid index in switch map context: " << index;
1082  return;
1083  }
1084 
1085  if(index == current_context_index_ && !force) {
1086  return;
1087  }
1088 
1089  // Disable the labels of the current context before switching.
1090  // The refresher handles enabling the new ones.
1091  get_map_context().get_labels().enable(false);
1092 
1094 
1096 }
1097 
1099 {
1100  std::string name = get_map_context().get_name();
1101 
1102  if(name.empty()) {
1104  }
1105 
1106  if(name.empty()){
1108  }
1109 
1110  const std::string& wm_title_string = name + " - " + game_config::get_default_title_string();
1111  video::set_window_title(wm_title_string);
1112 }
1113 
1114 } //Namespace editor
map_location loc
Definition: move.cpp:172
double t
Definition: astarsearch.cpp:63
A config object defines a single node in a WML file, with access to child nodes.
Definition: config.hpp:158
void set_viewing_team_index(std::size_t team, bool observe=false)
Sets the team controlled by the player using the computer.
Definition: display.cpp:336
void recalculate_minimap()
Schedule the minimap for recalculation.
Definition: display.cpp:1455
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:2960
void set_playing_team_index(std::size_t team)
sets the team whose turn it currently is
Definition: display.cpp:353
void rebuild_all()
Rebuild all dynamic terrain.
Definition: display.cpp:422
void change_display_context(const display_context *dc)
Definition: display.cpp:433
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:2953
void init_flags()
Init the flag list and the team colors used by ~TC.
Definition: display.cpp:252
void reload_map()
Updates internals that cache map size.
Definition: display.cpp:427
void create_buttons()
Definition: display.cpp:822
const game_config_view & game_config_
std::vector< std::unique_ptr< map_context > > map_contexts_
The currently opened map context object.
bool write_scenario(bool display_confirmation=true)
context_manager(editor_display &gui, const game_config_view &game_config, const std::string &addon_id)
bool is_active_transitions_hotkey(const std::string &item)
void new_scenario(int width, int height, const t_translation::terrain_code &fill, bool new_context)
Create a new scenario.
void expand_local_time_menu(std::vector< config > &items) const
Menu expanding for the map's defined areas.
void new_map(int width, int height, const t_translation::terrain_code &fill, bool new_context)
Create a new map.
void fill_selection()
Fill the selection with the foreground terrain.
void new_map_dialog()
Display a new map dialog and process user input.
bool confirm_discard()
Shows an are-you-sure dialog if the map was modified.
void generate_map_dialog()
Display a generate random map dialog and process user input.
int auto_update_transitions_
Flag to rebuild terrain on every terrain change.
void rename_area_dialog()
Display an dialog to querry a new id for an [time_area].
void revert_map()
Revert the map by reloading it from disk.
void load_map_dialog(bool force_same_context=false)
Display a load map dialog and process user input.
bool check_switch_open_map(const std::string &fn)
Check if a map is already open.
std::vector< std::unique_ptr< map_generator > > map_generators_
Available random map generators.
void set_window_title()
Displays the specified map name in the window titlebar.
void switch_context(const int index, const bool force=false)
Switches the context to the one under the specified index.
map_generator * last_map_generator_
void expand_sides_menu(std::vector< config > &items) const
Menu expanding for the map's player sides.
std::size_t check_open_map(const std::string &fn) const
Check if a map is already open.
void save_scenario_as_dialog()
Display a save map as dialog and process user input.
void refresh_all()
Refresh everything, i.e.
void init_map_generators(const game_config_view &game_config)
init available random map generators
void save_all_maps()
Save all maps, show save dialogs for unsaved ones.
int add_map_context_of(std::unique_ptr< map_context > &&mc)
void expand_time_menu(std::vector< config > &items) const
Menu expanding for the map's defined areas.
void map_to_scenario()
Convert existing map to scenario.
void save_map_as_dialog()
Display a save map as dialog and process user input.
void create_default_context()
Creates a default map context object, used to ensure there is always at least one.
void new_scenario_dialog()
Display a new map dialog and process user input.
void replace_map_context(const T &... args)
Replace the current map context and refresh accordingly.
void apply_mask_dialog()
Display an apply mask dialog and process user input.
void load_map(const std::string &filename, bool new_context)
Load a map given the filename.
void reload_map()
Reload the map after it has significantly changed (when e.g.
void save_contexts()
Save all open map_contexts to memory.
void set_addon_id(const std::string &id)
void create_mask_to_dialog()
Display an apply mask dialog and process user input.
void close_current_context()
Closes the active map context.
editor_display & gui()
void resize_map_dialog()
Display a load map dialog and process user input.
void edit_scenario_dialog()
Display a scenario edit dialog and process user input.
std::string current_addon_
The currently selected add-on.
void replace_map_context_with(std::unique_ptr< map_context > &&mc)
void perform_refresh(const editor_action &action, bool drag_part=false)
Perform an action on the current map_context, then refresh the display.
int add_map_context(const T &... args)
Add a map context.
void init_context(int width, int height, const t_translation::terrain_code &fill, bool new_context, bool is_pure_map=true)
void expand_load_mru_menu(std::vector< config > &items) const
Menu expanding for most recent loaded list.
void save_map(bool show_confirmation=true)
Save the map, open dialog if not named yet.
void edit_side_dialog(const team &t)
Display a side edit dialog and process user input.
bool write_map(bool display_confirmation=true)
Save the map under a given filename.
void load_mru_item(unsigned index, bool force_same_context=false)
Open the specified entry from the recent files list.
map_context & get_map_context()
Get the current map context object.
void expand_areas_menu(std::vector< config > &items) const
Menu expanding for the map's defined areas.
void expand_open_maps_menu(std::vector< config > &items) const
Menu expanding for open maps list.
class location_palette * locs_
void refresh_after_action(bool drag_part=false)
Refresh the display after an action has been performed.
void refresh_on_context_change()
Performs the necessary housekeeping necessary when switching contexts.
std::size_t modified_maps(std::string &modified)
Paint the same terrain on a number of locations on the map.
Definition: action.hpp:266
Replace contents of the entire map, Useful as a fallback undo method when something else would be imp...
Definition: action.hpp:39
Base class for all editor actions.
Definition: action_base.hpp:42
static std::string current_addon_id_
void rebuild_terrain(const map_location &loc)
void set_status(const std::string &str, const bool is_success)
Set a status text at the bottom left of the map area.
This class adds extra editor-specific functionality to a normal gamemap.
Definition: editor_map.hpp:70
const std::set< map_location > & selection() const
Return the selection set.
Definition: editor_map.hpp:148
void add_item(const std::string &id)
This class wraps around a map to provide a concise interface for the editor to work with.
Definition: map_context.hpp:63
bool modified() const
int get_active_area() const
void set_needs_labels_reset(bool value=true)
Setter for the labels reset flag.
void set_needs_reload(bool value=true)
Setter for the reload flag.
void new_side()
Adds a new side to the map.
const std::string & get_id() const
bool random_start_time() const
void set_side_setup(editor_team_info &info)
void save_scenario()
Saves the scenario under the current filename.
void perform_action(const editor_action &action)
Performs an action (thus modifying the map).
const std::string & get_description() const
bool is_embedded() const
void clear_changed_locations()
const tod_manager * get_time_manager() const
void save_map()
Saves the map under the current filename.
void reset_starting_position_labels(display &disp)
virtual const editor_map & map() const override
Const map accessor.
void set_needs_terrain_rebuild(bool value=true)
Setter for the terrain rebuild flag.
const t_string get_default_context_name() const
const std::string & get_filename() const
map_labels & get_labels()
virtual const std::vector< team > & teams() const override
Const teams accessor.
game_classification & get_classification()
bool is_pure_map() const
bool victory_defeated() const
void set_filename(const std::string &fn)
const std::set< map_location > changed_locations() const
utils::optional< int > get_xp_mod() const
void set_scenario_setup(const std::string &id, const std::string &name, const std::string &description, int turns, int xp_mod, bool victory_defeated, bool random_time)
const std::string & get_name() const
A class grating read only view to a vector of config objects, viewed as one config with all children ...
const config & find_mandatory_child(config_key_type key, const std::string &name, const std::string &value) const
int w() const
Effective map width.
Definition: map.hpp:50
int h() const
Effective map height.
Definition: map.hpp:53
int num_valid_starting_positions() const
Counts the number of sides that have valid starting positions on this map.
Definition: map.cpp:334
utils::optional< uint32_t > get_seed()
void select_map_generator(map_generator *mg)
map_generator * get_selected_map_generator()
file_dialog & set_extension(const std::string &value)
Sets allowed file extensions for file names in save mode.
file_dialog & set_path(const std::string &value)
Sets the initial file selection.
file_dialog & set_title(const std::string &value)
Sets the current dialog title text.
Definition: file_dialog.hpp:59
file_dialog & set_save_mode(bool value)
Sets the dialog's behavior on non-existent file name inputs.
std::string path() const
Gets the current file selection.
file_dialog & add_extra_path(desktop::GAME_PATH_TYPES path)
@ yes_no_buttons
Shows a yes and no button.
Definition: message.hpp:81
bool show(const unsigned auto_close_time=0)
Shows the window.
virtual std::string create_map(utils::optional< uint32_t > randomseed={})=0
Creates a new map and returns it.
void enable(bool is_enabled)
Definition: label.cpp:254
static prefs & get()
std::vector< std::string > recent_files()
Retrieves the list of recently opened files.
This class stores all the data for a single 'side' (in game nomenclature).
Definition: team.hpp:75
int number_of_turns() const
const std::vector< time_of_day > & times(const map_location &loc=map_location::null_location()) const
std::vector< std::string > get_area_ids() const
const std::set< map_location > & get_area_by_index(int index) const
void set_area_id(int area_index, const std::string &id)
void swap(config &lhs, config &rhs)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1339
Editor action classes.
#define LOG_ED
#define ERR_ED
#define WRN_ED
Declarations for File-IO.
#define VGETTEXT(msgid,...)
Handy wrappers around interpolate_variables_into_string and gettext.
std::size_t i
Definition: function.cpp:1032
int w
#define N_(String)
Definition: gettext.hpp:105
static std::string _(const char *str)
Definition: gettext.hpp:97
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:201
map_generator * create_map_generator(const std::string &name, const config &cfg, const config *vars)
Definition: map_create.cpp:28
CURSOR_TYPE get()
Definition: cursor.cpp:218
@ GAME_EDITOR_MAP_DIR
Editor map dir.
Definition: paths.hpp:61
void fill(const SDL_Rect &rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Fill an area with the given colour.
Definition: draw.cpp:52
Manage the empty-palette in the editor.
Definition: action.cpp:31
const t_translation::terrain_code & get_selected_bg_terrain()
std::string initialize_addon()
Definition: editor_main.cpp:32
std::string get_legacy_editor_dir()
const std::string wml_extension
Definition: filesystem.hpp:81
bool is_cfg(const std::string &filename)
Returns true if the file ends with the wmlfile extension.
static bfs::path get_dir(const bfs::path &dirpath)
Definition: filesystem.cpp:352
std::string base_name(const std::string &file, const bool remove_extension)
Returns the base filename of a file, with directory name stripped.
bool rename_dir(const std::string &old_dir, const std::string &new_dir)
Definition: filesystem.cpp:815
bool is_directory(const std::string &fname)
Returns true if the given file is a directory.
std::string read_file(const std::string &fname)
Basic disk I/O - read file.
const std::string map_extension
Definition: filesystem.hpp:79
utils::optional< std::string > get_addon_id_from_path(const std::string &location)
Returns the add-on ID from a path.
const std::string mask_extension
Definition: filesystem.hpp:80
void write_file(const std::string &fname, const std::string &data, std::ios_base::openmode mode)
Throws io_exception if an error occurs.
std::string get_current_editor_dir(const std::string &addon_id)
const std::string unicode_bullet
Definition: constants.cpp:47
Game configuration data as global variables.
Definition: build_info.cpp:61
std::string path
Definition: filesystem.cpp:106
std::string get_default_title_string()
std::string default_terrain
Definition: game_config.cpp:57
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:201
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition: message.cpp:148
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
@ CANCEL
Dialog was closed with the CANCEL button.
Definition: retval.hpp:38
General purpose widgets.
std::string italic(Args &&... data)
Applies italic Pango markup to the input.
Definition: markup.hpp:176
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition: markup.hpp:110
const int TRANSITION_UPDATE_ON
Definition: preferences.hpp:52
const int TRANSITION_UPDATE_COUNT
Definition: preferences.hpp:54
const int TRANSITION_UPDATE_OFF
Definition: preferences.hpp:51
const int TRANSITION_UPDATE_PARTIAL
Definition: preferences.hpp:53
::tod_manager * tod_manager
Definition: resources.cpp:29
game_classification * classification
Definition: resources.cpp:34
filter_context * filter_con
Definition: resources.cpp:23
terrain_code read_terrain_code(std::string_view str, const ter_layer filler)
Reads a single terrain from a string.
const terrain_code NONE_TERRAIN
Definition: translation.hpp:58
std::size_t index(std::string_view str, const std::size_t index)
Codepoint index corresponding to the nth character in a UTF-8 string.
Definition: unicode.cpp:70
void set_window_title(const std::string &title)
Sets the title of the main window.
Definition: video.cpp:663
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
static std::string get_filename(const std::string &file_code)
int main(int, char **)
Definition: sdl2.cpp:25
std::string filename
Filename.
Encapsulates the map of the game.
Definition: location.hpp:45
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
Object which defines a time of day with associated bonuses, image, sounds etc.
Definition: time_of_day.hpp:57
bool addon_filename_legal(const std::string &name)
Checks whether an add-on file name is legal or not.
Definition: validation.cpp:66
#define e
#define h