The Battle for Wesnoth  1.19.16+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 
19 
20 #include "addon/validation.hpp"
21 #include "editor/action/action.hpp"
28 
29 #include "filesystem.hpp"
30 #include "formula/string_utils.hpp"
33 #include "gettext.hpp"
34 #include "resources.hpp"
35 #include "team.hpp"
36 
38 
40 #include "gui/dialogs/prompt.hpp"
45 #include "gui/dialogs/message.hpp"
47 #include "gui/widgets/retval.hpp"
48 
52 #include "game_config_view.hpp"
53 
54 #include "serialization/markup.hpp"
55 #include "terrain/translation.hpp"
56 
57 #include "units/unit.hpp"
58 #include "video.hpp"
59 
60 #include <memory>
61 #include <boost/algorithm/string.hpp>
62 #include <boost/filesystem.hpp>
63 
64 namespace {
65 
66 std::vector<std::unique_ptr<editor::map_context>> saved_contexts_;
67 int last_context_ = 0;
68 
69 const std::string get_menu_marker(const bool changed)
70 {
71  if (changed) {
72  return "[" + markup::span_color("#f00", font::unicode_bullet) + "]";
73  } else {
74  return font::unicode_bullet;
75  }
76 }
77 
78 }
79 
80 namespace editor {
81 
83  : locs_(nullptr)
84  , gui_(gui)
85  , game_config_(game_config)
87  , current_addon_(addon_id)
88  , map_generators_()
89  , last_map_generator_(nullptr)
90  , current_context_index_(0)
91  , auto_update_transitions_(prefs::get().editor_auto_update_transitions())
92  , map_contexts_()
93  , clipboard_()
94 {
95  resources::filter_con = this;
98 }
99 
101 {
102  // Restore default window title
104 
105  resources::filter_con = nullptr;
106 }
107 
109 {
111 
112  // TODO register the tod_manager with the gui?
115 
116  // Reset side when switching to an existing scenario
117  if(!get_map_context().teams().empty()) {
118  gui().set_viewing_team_index(0, true);
120  }
121  gui().init_flags();
122 
123  reload_map();
124 
125  // Enable the labels of the current context;
127 
129 }
130 
132 {
133  gui_.rebuild_all();
139  if(locs_) {
140  for(const auto& loc : get_map_context().map().special_locations().left) {
141  locs_->add_item(loc.first);
142  }
143  if(!get_map_context().is_pure_map()) {
144  // If the scenario has more than 9 teams, add locations for them
145  // (First 9 teams are always in the list)
146  std::size_t n_teams = get_map_context().teams().size();
147  for(std::size_t i = 10; i <= n_teams; i++) {
148  locs_->add_item(std::to_string(i));
149  }
150  }
151  }
152 }
153 
155 {
156  gui_.reload_map();
159  refresh_all();
160 }
161 
163 {
164  switch (auto_update_transitions_) {
166  return (item == "editor-auto-update-transitions");
168  return (item == "editor-partial-update-transitions");
170  return (item == "editor-no-update-transitions");
171  }
172 
173  return true; //should not be reached
174 }
175 
177 {
179  prefs::get().set_editor_auto_update_transitions(auto_update_transitions_);
180 
182  return true;
183  }
184 
185  return false;
186 }
187 
188 std::size_t context_manager::modified_maps(std::string& message)
189 {
190  std::vector<std::string> modified;
191  for(auto& mc : map_contexts_) {
192  if(mc->modified()) {
193  if(!mc->get_name().empty()) {
194  modified.push_back(mc->get_name());
195  } else if(!mc->get_filename().empty()) {
196  modified.push_back(mc->get_filename());
197  } else {
198  modified.push_back(mc->get_default_context_name());
199  }
200  }
201  }
202 
203  for(std::string& str : modified) {
204  message += "\n" + font::unicode_bullet + " " + str;
205  }
206 
207  return modified.size();
208 }
209 
210 void context_manager::load_map_dialog(bool force_same_context /* = false */)
211 {
212  std::string fn = get_map_context().get_filename();
213  if(fn.empty()) {
215  fn = filesystem::get_legacy_editor_dir() + "/maps";
216  } else {
218  }
219  }
220 
222 
223  dlg.set_title(_("Load Map or Scenario"))
224  .set_path(fn);
225 
226  if(dlg.show()) {
227  load_map(dlg.path(), !force_same_context);
228  }
229 }
230 
231 void context_manager::load_mru_item(unsigned index, bool force_same_context /* = false */)
232 {
233  const std::vector<std::string>& mru = prefs::get().recent_files();
234  if(mru.empty() || index >= mru.size()) {
235  return;
236  }
237 
238  load_map(mru[index], !force_same_context);
239 }
240 
242 {
243  editor_team_info team_info(t);
244 
245  if(gui2::dialogs::editor_edit_side::execute(team_info)) {
246  get_map_context().set_side_setup(team_info);
247  }
248 }
249 
251 {
252  if(!current_addon_.empty()) {
253  std::string pbl = filesystem::get_current_editor_dir(current_addon_) + "/_server.pbl";
254  gui2::dialogs::editor_edit_pbl::execute(pbl, current_addon_);
255  }
256 }
257 
259 {
260  std::string new_addon_id = current_addon_;
261  gui2::dialogs::prompt::execute(new_addon_id);
262 
263  std::string old_dir = filesystem::get_current_editor_dir(current_addon_);
264  std::string new_dir = filesystem::get_current_editor_dir(new_addon_id);
265  if(addon_filename_legal(new_addon_id) && filesystem::rename_dir(old_dir, new_dir)) {
266  std::string main_cfg = new_dir + "/_main.cfg";
267  std::string main = filesystem::read_file(main_cfg);
268 
269  // update paths
270  boost::replace_all(main, "/"+current_addon_, "/"+new_addon_id);
271  // update textdomain
272  boost::replace_all(main, "wesnoth-"+current_addon_, "wesnoth-"+new_addon_id);
273  filesystem::write_file(main_cfg, main);
274 
275  current_addon_ = new_addon_id;
276 
277  for(std::unique_ptr<map_context>& context : map_contexts_) {
278  context->set_addon_id(current_addon_);
279  }
280  }
281 }
282 
284 {
285  map_context& context = get_map_context();
286 
287  std::string id = context.get_id();
288  std::string name = context.get_name();
289  std::string description = context.get_description();
290 
291  int turns = context.get_time_manager()->number_of_turns();
292  int xp_mod = context.get_xp_mod() ? *context.get_xp_mod() : 70;
293 
294  bool victory = context.victory_defeated();
295  bool random = context.random_start_time();
296 
297  const bool ok = gui2::dialogs::editor_edit_scenario::execute(
298  id, name, description, turns, xp_mod, victory, random
299  );
300 
301  if(!ok) {
302  return;
303  }
304 
305  context.set_scenario_setup(id, name, description, turns, xp_mod, victory, random);
306 
307  if(!name.empty()) {
309  }
310 }
311 
313 {
314  const editor_map& map = get_map_context().map();
315 
316  int w = map.w();
317  int h = map.h();
318 
319  if(gui2::dialogs::editor_new_map::execute(_("New Map"), w, h)) {
321  new_map(w, h, fill, true);
322  }
323 }
324 
326 {
327  const editor_map& map = get_map_context().map();
328 
329  int w = map.w();
330  int h = map.h();
331 
332  if(gui2::dialogs::editor_new_map::execute(_("New Scenario"), w, h)) {
334  new_scenario(w, h, fill, true);
335  }
336 }
337 
338 void context_manager::expand_open_maps_menu(std::vector<config>& items) const
339 {
340  for(std::size_t mci = 0; mci < map_contexts_.size(); ++mci) {
341  map_context& mc = *map_contexts_[mci];
342 
343  std::string filename;
344  if(mc.is_pure_map()) {
346  } else {
347  filename = mc.get_name();
348  }
349 
350  if(filename.empty()) {
352  }
353 
354  std::ostringstream ss;
355  ss << "[" << mci + 1 << "] ";
356 
357  const bool changed = mc.modified();
358 
359  if(changed) {
360  ss << markup::italic(filename);
361  } else {
362  ss << filename;
363  }
364 
365  if(mc.is_embedded()) {
366  ss << " (E)";
367  }
368 
369  const std::string label = ss.str();
370  const std::string details = get_menu_marker(changed);
371 
372  items.emplace_back("label", label, "details", details);
373  }
374 }
375 
376 void context_manager::expand_load_mru_menu(std::vector<config>& items) const
377 {
378  std::vector<std::string> mru = prefs::get().recent_files();
379  if(mru.empty()) {
380  items.emplace_back("label", _("No Recent Files"));
381  return;
382  }
383 
384  for(const std::string& path : mru) {
385  // TODO: add proper leading ellipsization instead, since otherwise
386  // it'll be impossible to tell apart files with identical names and
387  // different parent paths.
388  items.emplace_back("label", filesystem::base_name(path));
389  }
390 }
391 
392 void context_manager::expand_areas_menu(std::vector<config>& items) const
393 {
395  if(!tod) {
396  return;
397  }
398 
399  std::vector<std::string> area_ids = tod->get_area_ids();
400 
401  for(std::size_t mci = 0; mci < area_ids.size(); ++mci) {
402  const std::string& area = area_ids[mci];
403 
404  std::stringstream ss;
405  ss << "[" << mci + 1 << "] ";\
406 
407  if(area.empty()) {
408  ss << markup::italic(_("Unnamed Area"));
409  } else {
410  ss << area;
411  }
412 
413  const bool changed =
414  mci == static_cast<std::size_t>(get_map_context().get_active_area())
415  && tod->get_area_by_index(mci) != get_map_context().map().selection();
416 
417  const std::string label = ss.str();
418  const std::string details = get_menu_marker(changed);
419 
420  items.emplace_back("label", label, "details", details);
421  }
422 }
423 
424 void context_manager::expand_sides_menu(std::vector<config>& items) const
425 {
426  for(std::size_t mci = 0; mci < get_map_context().teams().size(); ++mci) {
427 
428  const team& t = get_map_context().teams()[mci];
429  const std::string& teamname = t.user_team_name();
430  std::stringstream label;
431  label << "[" << mci+1 << "] ";
432 
433  if(teamname.empty()) {
434  label << markup::italic(_("New Side"));
435  } else {
436  label << teamname;
437  }
438 
439  items.emplace_back("label", label.str());
440  }
441 }
442 
443 void context_manager::expand_time_menu(std::vector<config>& items) const
444 {
445  const tod_manager* tod_m = get_map_context().get_time_manager();
446  assert(tod_m != nullptr);
447 
448  for(const time_of_day& time : tod_m->times()) {
449  // Use 'details' field here since the image will take the first column
450  items.emplace_back("details", time.name, "image", time.image);
451  }
452 }
453 
454 void context_manager::expand_local_time_menu(std::vector<config>& items) const
455 {
456  const tod_manager* tod_m = get_map_context().get_time_manager();
457 
458  for(const time_of_day& time : tod_m->times(get_map_context().get_active_area())) {
459  // Use 'details' field here since the image will take the first column
460  items.emplace_back("details", time.name, "image", time.image);
461  }
462 }
463 
465 {
466  std::string fn = get_map_context().get_filename();
467  if(fn.empty()) {
469  }
470 
472 
473  dlg.set_title(_("Apply Mask"))
474  .set_path(fn);
475 
476  if(dlg.show()) {
477  try {
479  editor_action_apply_mask a(mask.map());
480  perform_refresh(a);
481  } catch (const editor_map_load_exception& e) {
482  gui2::show_transient_message(_("Error loading mask"), e.what());
483  return;
484  } catch (const editor_action_exception& e) {
485  gui2::show_error_message(e.what());
486  return;
487  }
488  }
489 }
490 
491 void context_manager::perform_refresh(const editor_action& action, bool drag_part /* =false */)
492 {
494  refresh_after_action(drag_part);
495 }
496 
498 {
499  int active_area = get_map_context().get_active_area();
500  std::string name = get_map_context().get_time_manager()->get_area_ids()[active_area];
501 
502  if(gui2::dialogs::edit_text::execute(N_("Rename Area"), N_("Identifier:"), name)) {
503  get_map_context().get_time_manager()->set_area_id(active_area, name);
504  }
505 }
506 
508 {
509  std::string fn = get_map_context().get_filename();
510  if(fn.empty()) {
512  }
513 
515 
516  dlg.set_title(_("Choose Target Map"))
517  .set_path(fn);
518 
519  if(dlg.show()) {
520  try {
523  perform_refresh(a);
524  } catch (const editor_map_load_exception& e) {
525  gui2::show_transient_message(_("Error loading map"), e.what());
526  return;
527  } catch (const editor_action_exception& e) {
528  gui2::show_error_message(e.what());
529  return;
530  }
531  }
532 }
533 
535 {
536  if(get_map_context().needs_reload()) {
537  reload_map();
538  return;
539  }
540 
541  const std::set<map_location>& changed_locs = get_map_context().changed_locations();
542 
543  if(get_map_context().needs_terrain_rebuild()) {
546  && (!drag_part || get_map_context().everything_changed())))
547  {
548  gui_.rebuild_all();
551  } else {
552  for(const map_location& loc : changed_locs) {
554  }
555  gui_.invalidate(changed_locs);
556  }
557  } else {
558  if(get_map_context().everything_changed()) {
560  } else {
561  gui_.invalidate(changed_locs);
562  }
563  }
564 
565  if(get_map_context().needs_labels_reset()) {
567  }
568 
571 }
572 
574 {
575  const editor_map& map = get_map_context().map();
576 
577  int w = map.w();
578  int h = map.h();
579 
581  bool copy = false;
582 
583  if(!gui2::dialogs::editor_resize_map::execute(w, h, dir, copy)) {
584  return;
585  }
586 
587  if(w != map.w() || h != map.h()) {
589  if(copy) {
591  }
592 
593  int x_offset = map.w() - w;
594  int y_offset = map.h() - h;
595 
596  switch (dir) {
600  y_offset = 0;
601  break;
605  y_offset /= 2;
606  break;
610  break;
611  default:
612  y_offset = 0;
613  WRN_ED << "Unknown resize expand direction";
614  break;
615  }
616 
617  switch (dir) {
621  x_offset = 0;
622  break;
626  x_offset /= 2;
627  break;
631  break;
632  default:
633  x_offset = 0;
634  break;
635  }
636 
637  auto chain = std::make_unique<editor_action_chain>();
638  auto units = get_map_context().units();
639  for (const auto& u : units) {
640  map_location l{u.get_location().x - x_offset, u.get_location().y - y_offset};
641  chain->append_action(std::make_unique<editor_action_unit_replace>(u.get_location(), l));
642  }
643 
644  chain->append_action(std::make_unique<editor_action_item_move_all>(x_offset, y_offset));
645  chain->append_action(std::make_unique<editor_action_time_area_move_all>(x_offset, y_offset));
646 
647  // TODO Reduces the number of editor actions in the following loop.
648  // It can be a lot if the map has a lot of labels (say hundreds).
649  map_labels& labels = get_map_context().get_labels();
651  map_location new_loc{loc.x - x_offset, loc.y - y_offset};
652  const terrain_label* old_label = labels.get_label(loc);
653 
654  if (old_label == nullptr) {
655  return;
656  }
657 
658  // Create action chain to:
659  // 1. Delete label in initial hex
660  // 2. Delete label in target hex if it exists, otherwise will no-op
661  // 3. Create label in target hex
662  chain->append_action(std::make_unique<editor_action_label_delete>(loc));
663  chain->append_action(std::make_unique<editor_action_label_delete>(new_loc));
664  chain->append_action(
665  std::make_unique<editor_action_label>(
666  new_loc,
667  old_label->text(),
668  old_label->team_name(),
669  old_label->color(),
670  old_label->visible_in_fog(),
671  old_label->visible_in_shroud(),
672  old_label->immutable(),
673  old_label->category()
674  )
675  );
676  });
677 
678  chain->append_action(
679  std::make_unique<editor_action_resize_map>(w, h, x_offset, y_offset, fill));
680  perform_refresh(*chain);
681  }
682 }
683 
685 {
686  bool first_pick = false;
687  std::string input_name = get_map_context().get_filename();
688  if(input_name.empty()) {
689  first_pick = true;
691  input_name = filesystem::get_legacy_editor_dir() + "/maps";
692  } else {
694  }
695  }
696 
698 
699  dlg.set_title(_("Save Map As"))
700  .set_save_mode(true)
701  .set_path(input_name)
704 
705  if(!dlg.show()) {
706  return;
707  }
708 
709  boost::filesystem::path save_path(dlg.path());
710 
711  // Show warning the first time user tries to save in a wrong folder
712  std::string last_folder = save_path.parent_path().filename().string();
713  if ((last_folder == "scenarios")
714  && first_pick
715  && (gui2::show_message(
716  _("Error"),
717  VGETTEXT("Do you really want to save $type1 in $type2 folder?", {{"type1", "map"}, {"type2", "scenarios"}}),
719  {
720  return;
721  }
722 
723  std::size_t is_open = check_open_map(save_path.string());
724  if(is_open < map_contexts_.size() && is_open != static_cast<unsigned>(current_context_index_)) {
725  gui2::show_transient_message(_("This map is already open."), save_path.string());
726  }
727 
728  std::string old_filename = get_map_context().get_filename();
729 
730  get_map_context().set_filename(save_path.string());
731 
732  if(!write_map(true)) {
733  get_map_context().set_filename(old_filename);
734  }
735 }
736 
738 {
739  bool first_pick = false;
740  std::string input_name = get_map_context().get_filename();
741  if(input_name.empty()) {
742  first_pick = true;
744  }
745 
747 
748  dlg.set_title(_("Save Scenario As"))
749  .set_save_mode(true)
750  .set_path(input_name)
753 
754  if(!dlg.show()) {
755  return;
756  }
757 
758  boost::filesystem::path save_path(dlg.path());
759 
760  // Show warning the first time user tries to save in a wrong folder
761  std::string last_folder = save_path.parent_path().filename().string();
762  if ((last_folder == "maps")
763  && first_pick
764  && (gui2::show_message(
765  _("Error"),
766  VGETTEXT("Do you really want to save $type1 in $type2 folder?", {{"type1", "scenario"}, {"type2", "maps"}}),
768  {
769  return;
770  }
771 
772  std::size_t is_open = check_open_map(save_path.string());
773  if(is_open < map_contexts_.size() && is_open != static_cast<unsigned>(current_context_index_)) {
774  gui2::show_transient_message(_("This scenario is already open."), save_path.string());
775  return;
776  }
777 
778  std::string old_filename = get_map_context().get_filename();
779 
780  get_map_context().set_filename(save_path.string());
781 
782  if(!write_scenario(true)) {
783  get_map_context().set_filename(old_filename);
784  return;
785  }
786 }
787 
789 {
790  for(const config& i : game_config.child_range("multiplayer")) {
791  if(i["map_generation"].empty() && i["scenario_generation"].empty()) {
792  continue;
793  }
794 
795  if(const auto generator_cfg = i.optional_child("generator")) {
796  map_generators_.emplace_back(create_map_generator(i["map_generation"].empty() ? i["scenario_generation"] : i["map_generation"], generator_cfg.value()));
797  } else {
798  ERR_ED << "Scenario \"" << i["name"] << "\" with id " << i["id"]
799  << " has map_generation= but no [generator] tag";
800  }
801  }
802 }
803 
805 {
806  if(map_generators_.empty()) {
807  gui2::show_error_message(_("No random map generators found."));
808  return;
809  }
810 
813 
814  if(dialog.show()) {
815  std::string map_string;
817  try {
818  map_string = map_generator->create_map(dialog.get_seed());
819  } catch (const mapgen_exception& e) {
820  gui2::show_transient_message(_("Map creation failed."), e.what());
821  return;
822  }
823 
824  if(map_string.empty()) {
825  gui2::show_transient_message("", _("Map creation failed."));
826  } else {
827  editor_map new_map(map_string);
829  get_map_context().set_needs_labels_reset(); // Ensure Player Start labels are updated together with newly generated map
830  perform_refresh(a);
831  }
832 
834  }
835 }
836 
838 {
839  if(get_map_context().modified()) {
840  const int res = gui2::show_message(_("Unsaved Changes"),
841  _("Do you want to discard all changes made to the map since the last save?"), gui2::dialogs::message::yes_no_buttons);
842  return gui2::retval::CANCEL != res;
843  }
844 
845  return true;
846 }
847 
849 {
851 }
852 
854 {
855  int current = current_context_index_;
856  for(std::size_t i = 0; i < map_contexts_.size(); ++i) {
857  switch_context(i);
858  save_map();
859  }
860  switch_context(current);
861 }
862 
864 {
865  saved_contexts_.swap(map_contexts_);
866  std::swap(last_context_, current_context_index_);
868  switch_context(0, true);
869 }
870 
871 void context_manager::save_map(bool show_confirmation)
872 {
873  const std::string& name = get_map_context().get_filename();
874  if(name.empty() || filesystem::is_directory(name)) {
875  if(get_map_context().is_pure_map()) {
877  } else {
879  }
880  } else {
881  if(get_map_context().is_pure_map()) {
882  write_map(show_confirmation);
883  } else {
884  write_scenario(show_confirmation);
885  }
886  }
887 }
888 
889 bool context_manager::write_scenario(bool display_confirmation)
890 {
891  try {
893  if(display_confirmation) {
894  gui_.set_status(_("Scenario saved."), true);
895  }
896  } catch (const editor_map_save_exception& e) {
897  gui_.set_status(e.what(), false);
898  return false;
899  }
900 
901  return true;
902 }
903 
904 bool context_manager::write_map(bool display_confirmation)
905 {
906  try {
908  if(display_confirmation) {
909  gui_.set_status(_("Map saved"), true);
910  }
911  } catch (const editor_map_save_exception& e) {
912  gui_.set_status(e.what(), false);
913  return false;
914  }
915 
916  return true;
917 }
918 
919 std::size_t context_manager::check_open_map(const std::string& fn) const
920 {
921  std::size_t i = 0;
922  while(i < map_contexts_.size() && map_contexts_[i]->get_filename() != fn) {
923  ++i;
924  }
925 
926  return i;
927 }
928 
929 bool context_manager::check_switch_open_map(const std::string& fn)
930 {
931  std::size_t i = check_open_map(fn);
932  if(i < map_contexts_.size()) {
933  gui2::show_transient_message(_("This map is already open."), fn);
934  switch_context(i);
935  return true;
936  }
937 
938  return false;
939 }
940 
941 void context_manager::load_map(const std::string& filename, bool new_context)
942 {
943  if(new_context && check_switch_open_map(filename)) {
944  return;
945  }
946 
949  // if no addon id has been set and the file being loaded is from an addon
950  // then use the file path to determine the addon rather than showing a dialog
951  if(auto addon_at_path = filesystem::get_addon_id_from_path(filename)) {
952  editor_controller::current_addon_id_ = addon_at_path.value();
953  } else {
955  }
956 
958  }
959 
961  return;
962  }
963  }
964 
965  LOG_ED << "Load map: " << filename << (new_context ? " (new)" : " (same)");
966  try {
967  {
968  auto mc = std::make_unique<map_context>(game_config_, filename, current_addon_);
969  if(mc->get_filename() != filename) {
970  if(new_context && check_switch_open_map(mc->get_filename())) {
971  return;
972  }
973  }
974 
975  if(new_context) {
976  int new_id = add_map_context_of(std::move(mc));
977  switch_context(new_id);
978  } else {
979  replace_map_context_with(std::move(mc));
980  }
981  }
982 
983  if(get_map_context().is_embedded()) {
984  const std::string& msg = _("Loaded embedded map data");
985  gui2::show_transient_message(_("Map loaded from scenario"), msg);
986  } else {
988  gui2::show_transient_message(_("Map loaded from scenario"), _("Loaded referenced map file:")+"\n"+get_map_context().get_filename());
989  }
990  }
991  } catch(const editor_map_load_exception& e) {
992  gui2::show_transient_message(_("Error loading map"), e.what());
993  return;
994  }
995 }
996 
998 {
999  if(!confirm_discard()) {
1000  return;
1001  }
1002 
1003  std::string filename = get_map_context().get_filename();
1004  if(filename.empty()) {
1005  ERR_ED << "Empty filename in map revert";
1006  return;
1007  }
1008 
1009  load_map(filename, false);
1010 }
1011 
1012 void context_manager::init_context(int width, int height, const t_translation::terrain_code& fill, bool new_context, bool is_pure_map) {
1013  const config& default_schedule = game_config_.find_mandatory_child("editor_times", "id", "empty");
1014  editor_map m(width, height, fill);
1015 
1016  if(new_context) {
1017  int new_id = add_map_context(m, is_pure_map, default_schedule, current_addon_);
1018  switch_context(new_id);
1019  } else {
1020  replace_map_context(m, is_pure_map, default_schedule, current_addon_);
1021  }
1022 }
1023 
1024 void context_manager::new_map(int width, int height, const t_translation::terrain_code& fill, bool new_context)
1025 {
1026  init_context(width, height, fill, new_context, true);
1027 }
1028 
1029 void context_manager::new_scenario(int width, int height, const t_translation::terrain_code& fill, bool new_context)
1030 {
1031  init_context(width, height, fill, new_context, false);
1032 
1033  // Give the new scenario an initial side.
1035  gui().set_viewing_team_index(0, true);
1037  gui_.init_flags();
1038 }
1039 
1041 {
1042  const config& default_schedule = game_config_.find_mandatory_child("editor_times", "id", "empty");
1043  replace_map_context(get_map_context().map(), false, default_schedule, current_addon_);
1044 
1045  // Give the converted scenario a number of sides
1046  // equal to the number of valid starting positions.
1047  int start_pos_count = get_map_context().map().num_valid_starting_positions();
1048  if(start_pos_count == 0) {
1049  start_pos_count = 1;
1050  }
1051  for(int i = 0; i < start_pos_count; i++) {
1053  }
1054  gui().set_viewing_team_index(0, true);
1056  gui_.init_flags();
1057 }
1058 
1059 //
1060 // Context manipulation
1061 //
1062 
1063 template<typename... T>
1065 {
1066  map_contexts_.emplace_back(std::make_unique<map_context>(args...));
1067  return map_contexts_.size() - 1;
1068 }
1069 
1070 int context_manager::add_map_context_of(std::unique_ptr<map_context>&& mc)
1071 {
1072  map_contexts_.emplace_back(std::move(mc));
1073  return map_contexts_.size() - 1;
1074 }
1075 
1076 template<typename... T>
1078 {
1079  replace_map_context_with(std::move(std::make_unique<map_context>(args...)));
1080 }
1081 
1082 void context_manager::replace_map_context_with(std::unique_ptr<map_context>&& mc)
1083 {
1084  map_contexts_[current_context_index_] = std::move(mc);
1086 }
1087 
1089 {
1090  if(saved_contexts_.empty()) {
1092  switch_context(0, true);
1093  } else {
1094  saved_contexts_.swap(map_contexts_);
1095  switch_context(last_context_, true);
1096  last_context_ = 0;
1097  }
1098 }
1099 
1101 {
1104 
1105  const config& default_schedule = game_config_.find_mandatory_child("editor_times", "id", "empty");
1106  add_map_context(editor_map(44, 33, default_terrain), true, default_schedule, current_addon_);
1107 }
1108 
1110 {
1111  if(!confirm_discard()) return;
1112 
1113  if(map_contexts_.size() == 1) {
1115  map_contexts_.erase(map_contexts_.begin());
1116  } else if(current_context_index_ == static_cast<int>(map_contexts_.size()) - 1) {
1117  map_contexts_.pop_back();
1119  } else {
1121  }
1122 
1124 }
1125 
1126 void context_manager::switch_context(const int index, const bool force)
1127 {
1128  if(index < 0 || static_cast<std::size_t>(index) >= map_contexts_.size()) {
1129  WRN_ED << "Invalid index in switch map context: " << index;
1130  return;
1131  }
1132 
1133  if(index == current_context_index_ && !force) {
1134  return;
1135  }
1136 
1137  // Disable the labels of the current context before switching.
1138  // The refresher handles enabling the new ones.
1139  get_map_context().get_labels().enable(false);
1140 
1142 
1144 }
1145 
1147 {
1148  std::string name = get_map_context().get_name();
1149 
1150  if(name.empty()) {
1152  }
1153 
1154  if(name.empty()){
1156  }
1157 
1158  const std::string& wm_title_string = name + " - " + game_config::get_default_title_string();
1159  video::set_window_title(wm_title_string);
1160 }
1161 
1162 } //Namespace editor
Editor action classes.
Editor action classes.
Editor action classes.
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:157
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
virtual const unit_map & units() const override
Const units accessor.
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(std::string_view 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
void for_each_loc(const F &f) const
Definition: map.hpp:136
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.
const terrain_label * get_label(const map_location &loc, const std::string &team_name) const
Definition: label.hpp:48
void enable(bool is_enabled)
Definition: label.cpp:255
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:74
To store label data Class implements logic for rendering.
Definition: label.hpp:111
bool visible_in_shroud() const
Definition: label.hpp:169
bool immutable() const
Definition: label.hpp:174
bool visible_in_fog() const
Definition: label.hpp:164
const color_t & color() const
Definition: label.hpp:184
const std::string & category() const
Definition: label.hpp:159
const t_string & text() const
Definition: label.hpp:139
const std::string & team_name() const
Definition: label.hpp:154
int number_of_turns() 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)
const std::vector< time_of_day > & times() const
void swap(config &lhs, config &rhs) noexcept
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1316
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
#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 ::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:671
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
int w
Definition: pathfind.cpp:188
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:46
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