The Battle for Wesnoth  1.19.1+dev
context_manager.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2024
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 "terrain/translation.hpp"
52 
53 #include <memory>
54 #include <boost/algorithm/string.hpp>
55 #include <boost/filesystem.hpp>
56 
57 namespace {
58 
59 std::vector<std::unique_ptr<editor::map_context>> saved_contexts_;
60 int last_context_ = 0;
61 
62 const std::string get_menu_marker(const bool changed)
63 {
64  std::ostringstream ss;
65  ss << "[<span ";
66 
67  if(changed) {
68  ss << "color='#f00' ";
69  }
70 
71  ss << ">" << font::unicode_bullet << "</span>]";
72  return ss.str();
73 }
74 
75 }
76 
77 namespace editor {
78 
80  : locs_(nullptr)
81  , gui_(gui)
82  , game_config_(game_config)
84  , current_addon_(addon_id)
85  , map_generators_()
86  , last_map_generator_(nullptr)
87  , current_context_index_(0)
88  , auto_update_transitions_(prefs::get().editor_auto_update_transitions())
89  , map_contexts_()
90  , clipboard_()
91 {
92  resources::filter_con = this;
95 }
96 
98 {
99  // Restore default window title
101 
102  resources::filter_con = nullptr;
103 }
104 
106 {
108 
109  // TODO register the tod_manager with the gui?
112 
113  // Reset side when switching to an existing scenario
114  if (gui().get_teams().size() > 0) {
115  gui().set_team(0, true);
116  gui().set_playing_team(0);
117  }
118  gui().init_flags();
119 
120  reload_map();
121 
122  // Enable the labels of the current context;
124 
126 }
127 
129 {
130  gui_.rebuild_all();
136  if(locs_) {
137  for(const auto& loc : get_map_context().map().special_locations().left) {
138  locs_->add_item(loc.first);
139  }
140  if(!get_map_context().is_pure_map()) {
141  // If the scenario has more than 9 teams, add locations for them
142  // (First 9 teams are always in the list)
143  size_t n_teams = get_map_context().teams().size();
144  for(size_t i = 10; i <= n_teams; i++) {
145  locs_->add_item(std::to_string(i));
146  }
147  }
148  }
149 }
150 
152 {
153  gui_.reload_map();
156  refresh_all();
157 }
158 
160 {
161  switch (auto_update_transitions_) {
163  return (item == "editor-auto-update-transitions");
165  return (item == "editor-partial-update-transitions");
167  return (item == "editor-no-update-transitions");
168  }
169 
170  return true; //should not be reached
171 }
172 
174 {
177 
179  return true;
180  }
181 
182  return false;
183 }
184 
185 std::size_t context_manager::modified_maps(std::string& message)
186 {
187  std::vector<std::string> modified;
188  for(auto& mc : map_contexts_) {
189  if(mc->modified()) {
190  if(!mc->get_name().empty()) {
191  modified.push_back(mc->get_name());
192  } else if(!mc->get_filename().empty()) {
193  modified.push_back(mc->get_filename());
194  } else {
195  modified.push_back(mc->get_default_context_name());
196  }
197  }
198  }
199 
200  for(std::string& str : modified) {
201  message += "\n" + font::unicode_bullet + " " + str;
202  }
203 
204  return modified.size();
205 }
206 
207 void context_manager::load_map_dialog(bool force_same_context /* = false */)
208 {
209  std::string fn = get_map_context().get_filename();
210  if(fn.empty()) {
212  }
213 
215 
216  dlg.set_title(_("Load Map"))
217  .set_path(fn);
218 
219  if(dlg.show()) {
220  load_map(dlg.path(), !force_same_context);
221  }
222 }
223 
224 void context_manager::load_mru_item(unsigned index, bool force_same_context /* = false */)
225 {
226  const std::vector<std::string>& mru = prefs::get().recent_files();
227  if(mru.empty() || index >= mru.size()) {
228  return;
229  }
230 
231  load_map(mru[index], !force_same_context);
232 }
233 
235 {
236  team& t = get_map_context().teams()[side_index];
237 
238  editor_team_info team_info(t);
239 
240  if(gui2::dialogs::editor_edit_side::execute(team_info)) {
241  get_map_context().set_side_setup(team_info);
242  }
243 }
244 
246 {
247  if(!current_addon_.empty()) {
248  std::string pbl = filesystem::get_current_editor_dir(current_addon_) + "/_server.pbl";
249  gui2::dialogs::editor_edit_pbl::execute(pbl, current_addon_);
250  }
251 }
252 
254 {
255  std::string new_addon_id = current_addon_;
256  gui2::dialogs::prompt::execute(new_addon_id);
257 
258  std::string old_dir = filesystem::get_current_editor_dir(current_addon_);
259  std::string new_dir = filesystem::get_current_editor_dir(new_addon_id);
260  if(addon_filename_legal(new_addon_id) && filesystem::rename_dir(old_dir, new_dir)) {
261  std::string main_cfg = new_dir + "/_main.cfg";
262  std::string main = filesystem::read_file(main_cfg);
263 
264  // update paths
265  boost::replace_all(main, "/"+current_addon_, "/"+new_addon_id);
266  // update textdomain
267  boost::replace_all(main, "wesnoth-"+current_addon_, "wesnoth-"+new_addon_id);
268  filesystem::write_file(main_cfg, main);
269 
270  current_addon_ = new_addon_id;
271 
272  for(std::unique_ptr<map_context>& context : map_contexts_) {
273  context->set_addon_id(current_addon_);
274  }
275  }
276 }
277 
279 {
280  map_context& context = get_map_context();
281 
282  std::string id = context.get_id();
283  std::string name = context.get_name();
284  std::string description = context.get_description();
285 
286  int turns = context.get_time_manager()->number_of_turns();
287  int xp_mod = context.get_xp_mod() ? *context.get_xp_mod() : 70;
288 
289  bool victory = context.victory_defeated();
290  bool random = context.random_start_time();
291 
292  const bool ok = gui2::dialogs::editor_edit_scenario::execute(
293  id, name, description, turns, xp_mod, victory, random
294  );
295 
296  if(!ok) {
297  return;
298  }
299 
300  context.set_scenario_setup(id, name, description, turns, xp_mod, victory, random);
301 
302  if(!name.empty()) {
304  }
305 }
306 
308 {
309  const editor_map& map = get_map_context().map();
310 
311  int w = map.w();
312  int h = map.h();
313 
314  if(gui2::dialogs::editor_new_map::execute(_("New Map"), w, h)) {
316  new_map(w, h, fill, true);
317  }
318 }
319 
321 {
322  const editor_map& map = get_map_context().map();
323 
324  int w = map.w();
325  int h = map.h();
326 
327  if(gui2::dialogs::editor_new_map::execute(_("New Scenario"), w, h)) {
329  new_scenario(w, h, fill, true);
330  }
331 }
332 
333 void context_manager::expand_open_maps_menu(std::vector<config>& items, int i)
334 {
335  auto pos = items.erase(items.begin() + i);
336  std::vector<config> contexts;
337 
338  for(std::size_t mci = 0; mci < map_contexts_.size(); ++mci) {
339  map_context& mc = *map_contexts_[mci];
340 
341  std::string filename;
342  if(mc.is_pure_map()) {
343  filename = filesystem::base_name(mc.get_filename());
344  } else {
345  filename = mc.get_name();
346  }
347 
348  if(filename.empty()) {
349  filename = mc.get_default_context_name();
350  }
351 
352  std::ostringstream ss;
353  ss << "[" << mci + 1 << "] ";
354 
355  const bool changed = mc.modified();
356 
357  if(changed) {
358  ss << "<i>" << filename << "</i>";
359  } else {
360  ss << filename;
361  }
362 
363  if(mc.is_embedded()) {
364  ss << " (E)";
365  }
366 
367  const std::string label = ss.str();
368  const std::string details = get_menu_marker(changed);
369 
370  contexts.emplace_back("label", label, "details", details);
371  }
372 
373  items.insert(pos, contexts.begin(), contexts.end());
374 }
375 
376 void context_manager::expand_load_mru_menu(std::vector<config>& items, int i)
377 {
378  std::vector<std::string> mru = prefs::get().recent_files();
379 
380  auto pos = items.erase(items.begin() + i);
381 
382  if(mru.empty()) {
383  items.insert(pos, config {"label", _("No Recent Files")});
384  return;
385  }
386 
387  for(std::string& path : mru) {
388  // TODO: add proper leading ellipsization instead, since otherwise
389  // it'll be impossible to tell apart files with identical names and
390  // different parent paths.
392  }
393 
394  std::vector<config> temp;
395  std::transform(mru.begin(), mru.end(), std::back_inserter(temp), [](const std::string& str) {
396  return config {"label", str};
397  });
398 
399  items.insert(pos, temp.begin(), temp.end());
400 }
401 
402 void context_manager::expand_areas_menu(std::vector<config>& items, int i)
403 {
404  tod_manager* tod = get_map_context().get_time_manager();
405  if(!tod) {
406  return;
407  }
408 
409  auto pos = items.erase(items.begin() + i);
410  std::vector<config> area_entries;
411 
412  std::vector<std::string> area_ids = tod->get_area_ids();
413 
414  for(std::size_t mci = 0; mci < area_ids.size(); ++mci) {
415  const std::string& area = area_ids[mci];
416 
417  std::stringstream ss;
418  ss << "[" << mci + 1 << "] ";\
419 
420  if(area.empty()) {
421  ss << "<i>" << _("Unnamed Area") << "</i>";
422  } else {
423  ss << area;
424  }
425 
426  const bool changed =
427  mci == static_cast<std::size_t>(get_map_context().get_active_area())
428  && tod->get_area_by_index(mci) != get_map_context().map().selection();
429 
430  const std::string label = ss.str();
431  const std::string details = get_menu_marker(changed);
432 
433  area_entries.emplace_back("label", label, "details", details);
434  }
435 
436  items.insert(pos, area_entries.begin(), area_entries.end());
437 }
438 
439 void context_manager::expand_sides_menu(std::vector<config>& items, int i)
440 {
441  auto pos = items.erase(items.begin() + i);
442  std::vector<config> contexts;
443 
444  for(std::size_t mci = 0; mci < get_map_context().teams().size(); ++mci) {
445 
446  const team& t = get_map_context().teams()[mci];
447  const std::string& teamname = t.user_team_name();
448  std::stringstream label;
449  label << "[" << mci+1 << "] ";
450 
451  if(teamname.empty()) {
452  label << "<i>" << _("New Side") << "</i>";
453  } else {
454  label << teamname;
455  }
456 
457  contexts.emplace_back("label", label.str());
458  }
459 
460  items.insert(pos, contexts.begin(), contexts.end());
461 }
462 
463 void context_manager::expand_time_menu(std::vector<config>& items, int i)
464 {
465  auto pos = items.erase(items.begin() + i);
466  std::vector<config> times;
467 
468  tod_manager* tod_m = get_map_context().get_time_manager();
469 
470  assert(tod_m != nullptr);
471 
472  for(const time_of_day& time : tod_m->times()) {
473  times.emplace_back(
474  "details", time.name, // Use 'details' field here since the image will take the first column
475  "image", time.image
476  );
477  }
478 
479  items.insert(pos, times.begin(), times.end());
480 }
481 
482 void context_manager::expand_local_time_menu(std::vector<config>& items, int i)
483 {
484  auto pos = items.erase(items.begin() + i);
485  std::vector<config> times;
486 
487  tod_manager* tod_m = get_map_context().get_time_manager();
488 
489  for(const time_of_day& time : tod_m->times(get_map_context().get_active_area())) {
490  times.emplace_back(
491  "details", time.name, // Use 'details' field here since the image will take the first column
492  "image", time.image
493  );
494  }
495 
496  items.insert(pos, times.begin(), times.end());
497 }
498 
499 void context_manager::apply_mask_dialog()
500 {
501  std::string fn = get_map_context().get_filename();
502  if(fn.empty()) {
503  fn = filesystem::get_dir(filesystem::get_current_editor_dir(current_addon_) + "/masks");
504  }
505 
507 
508  dlg.set_title(_("Apply Mask"))
509  .set_path(fn);
510 
511  if(dlg.show()) {
512  try {
513  map_context mask(game_config_, dlg.path(), current_addon_);
515  perform_refresh(a);
516  } catch (const editor_map_load_exception& e) {
517  gui2::show_transient_message(_("Error loading mask"), e.what());
518  return;
519  } catch (const editor_action_exception& e) {
520  gui2::show_error_message(e.what());
521  return;
522  }
523  }
524 }
525 
526 void context_manager::perform_refresh(const editor_action& action, bool drag_part /* =false */)
527 {
528  get_map_context().perform_action(action);
529  refresh_after_action(drag_part);
530 }
531 
532 void context_manager::rename_area_dialog()
533 {
534  int active_area = get_map_context().get_active_area();
535  std::string name = get_map_context().get_time_manager()->get_area_ids()[active_area];
536 
537  if(gui2::dialogs::edit_text::execute(N_("Rename Area"), N_("Identifier:"), name)) {
538  get_map_context().get_time_manager()->set_area_id(active_area, name);
539  }
540 }
541 
542 void context_manager::create_mask_to_dialog()
543 {
544  std::string fn = get_map_context().get_filename();
545  if(fn.empty()) {
546  fn = filesystem::get_dir(filesystem::get_current_editor_dir(current_addon_) + "/masks");
547  }
548 
550 
551  dlg.set_title(_("Choose Target Map"))
552  .set_path(fn);
553 
554  if(dlg.show()) {
555  try {
556  map_context map(game_config_, dlg.path(), current_addon_);
558  perform_refresh(a);
559  } catch (const editor_map_load_exception& e) {
560  gui2::show_transient_message(_("Error loading map"), e.what());
561  return;
562  } catch (const editor_action_exception& e) {
563  gui2::show_error_message(e.what());
564  return;
565  }
566  }
567 }
568 
569 void context_manager::refresh_after_action(bool drag_part)
570 {
571  if(get_map_context().needs_reload()) {
572  reload_map();
573  return;
574  }
575 
576  const std::set<map_location>& changed_locs = get_map_context().changed_locations();
577 
578  if(get_map_context().needs_terrain_rebuild()) {
579  if((auto_update_transitions_ == pref_constants::TRANSITION_UPDATE_ON)
580  || ((auto_update_transitions_ == pref_constants::TRANSITION_UPDATE_PARTIAL)
581  && (!drag_part || get_map_context().everything_changed())))
582  {
583  gui_.rebuild_all();
584  get_map_context().set_needs_terrain_rebuild(false);
585  gui_.invalidate_all();
586  } else {
587  for(const map_location& loc : changed_locs) {
588  gui_.rebuild_terrain(loc);
589  }
590  gui_.invalidate(changed_locs);
591  }
592  } else {
593  if(get_map_context().everything_changed()) {
594  gui_.invalidate_all();
595  } else {
596  gui_.invalidate(changed_locs);
597  }
598  }
599 
600  if(get_map_context().needs_labels_reset()) {
601  get_map_context().reset_starting_position_labels(gui_);
602  }
603 
604  get_map_context().clear_changed_locations();
605  gui_.recalculate_minimap();
606 }
607 
608 void context_manager::resize_map_dialog()
609 {
610  const editor_map& map = get_map_context().map();
611 
612  int w = map.w();
613  int h = map.h();
614 
616  bool copy = false;
617 
618  if(!gui2::dialogs::editor_resize_map::execute(w, h, dir, copy)) {
619  return;
620  }
621 
622  if(w != map.w() || h != map.h()) {
624  if(copy) {
626  }
627 
628  int x_offset = map.w() - w;
629  int y_offset = map.h() - h;
630 
631  switch (dir) {
635  y_offset = 0;
636  break;
640  y_offset /= 2;
641  break;
645  break;
646  default:
647  y_offset = 0;
648  WRN_ED << "Unknown resize expand direction";
649  break;
650  }
651 
652  switch (dir) {
656  x_offset = 0;
657  break;
661  x_offset /= 2;
662  break;
666  break;
667  default:
668  x_offset = 0;
669  break;
670  }
671 
672  editor_action_resize_map a(w, h, x_offset, y_offset, fill);
673  perform_refresh(a);
674  }
675 }
676 
677 void context_manager::save_map_as_dialog()
678 {
679  bool first_pick = false;
680  std::string input_name = get_map_context().get_filename();
681  if(input_name.empty()) {
682  first_pick = true;
683  input_name = filesystem::get_current_editor_dir(editor_controller::current_addon_id_)+"/maps";
684  }
685 
687 
688  dlg.set_title(_("Save Map As"))
689  .set_save_mode(true)
690  .set_path(input_name)
693 
694  if(!dlg.show()) {
695  return;
696  }
697 
698  boost::filesystem::path save_path(dlg.path());
699 
700  // Show warning the first time user tries to save in a wrong folder
701  std::string last_folder = save_path.parent_path().filename().string();
702  if ((last_folder == "scenarios")
703  && first_pick
704  && (gui2::show_message(
705  _("Error"),
706  VGETTEXT("Do you really want to save $type1 in $type2 folder?", {{"type1", "map"}, {"type2", "scenarios"}}),
708  {
709  return;
710  }
711 
712  std::size_t is_open = check_open_map(save_path.string());
713  if(is_open < map_contexts_.size() && is_open != static_cast<unsigned>(current_context_index_)) {
714  gui2::show_transient_message(_("This map is already open."), save_path.string());
715  }
716 
717  std::string old_filename = get_map_context().get_filename();
718 
719  get_map_context().set_filename(save_path.string());
720 
721  if(!write_map(true)) {
722  get_map_context().set_filename(old_filename);
723  }
724 }
725 
726 void context_manager::save_scenario_as_dialog()
727 {
728  bool first_pick = false;
729  std::string input_name = get_map_context().get_filename();
730  if(input_name.empty()) {
731  first_pick = true;
732  input_name = filesystem::get_current_editor_dir(editor_controller::current_addon_id_) + "/scenarios";
733  }
734 
736 
737  dlg.set_title(_("Save Scenario As"))
738  .set_save_mode(true)
739  .set_path(input_name)
742 
743  if(!dlg.show()) {
744  return;
745  }
746 
747  boost::filesystem::path save_path(dlg.path());
748 
749  // Show warning the first time user tries to save in a wrong folder
750  std::string last_folder = save_path.parent_path().filename().string();
751  if ((last_folder == "maps")
752  && first_pick
753  && (gui2::show_message(
754  _("Error"),
755  VGETTEXT("Do you really want to save $type1 in $type2 folder?", {{"type1", "scenario"}, {"type2", "maps"}}),
757  {
758  return;
759  }
760 
761  std::size_t is_open = check_open_map(save_path.string());
762  if(is_open < map_contexts_.size() && is_open != static_cast<unsigned>(current_context_index_)) {
763  gui2::show_transient_message(_("This scenario is already open."), save_path.string());
764  return;
765  }
766 
767  std::string old_filename = get_map_context().get_filename();
768 
769  get_map_context().set_filename(save_path.string());
770 
771  if(!write_scenario(true)) {
772  get_map_context().set_filename(old_filename);
773  return;
774  }
775 }
776 
777 void context_manager::init_map_generators(const game_config_view& game_config)
778 {
779  for(const config& i : game_config.child_range("multiplayer")) {
780  if(i["map_generation"].empty() && i["scenario_generation"].empty()) {
781  continue;
782  }
783 
784  // TODO: we should probably use `child` with a try/catch block once that function throws
785  if(const auto generator_cfg = i.optional_child("generator")) {
786  map_generators_.emplace_back(create_map_generator(i["map_generation"].empty() ? i["scenario_generation"] : i["map_generation"], generator_cfg.value()));
787  } else {
788  ERR_ED << "Scenario \"" << i["name"] << "\" with id " << i["id"]
789  << " has map_generation= but no [generator] tag";
790  }
791  }
792 }
793 
794 void context_manager::generate_map_dialog()
795 {
796  if(map_generators_.empty()) {
797  gui2::show_error_message(_("No random map generators found."));
798  return;
799  }
800 
801  gui2::dialogs::editor_generate_map dialog(map_generators_);
802  dialog.select_map_generator(last_map_generator_);
803 
804  if(dialog.show()) {
805  std::string map_string;
807  try {
808  map_string = map_generator->create_map(dialog.get_seed());
809  } catch (const mapgen_exception& e) {
810  gui2::show_transient_message(_("Map creation failed."), e.what());
811  return;
812  }
813 
814  if(map_string.empty()) {
815  gui2::show_transient_message("", _("Map creation failed."));
816  } else {
817  editor_map new_map(map_string);
818  editor_action_whole_map a(new_map);
819  get_map_context().set_needs_labels_reset(); // Ensure Player Start labels are updated together with newly generated map
820  perform_refresh(a);
821  }
822 
823  last_map_generator_ = map_generator;
824  }
825 }
826 
827 bool context_manager::confirm_discard()
828 {
829  if(get_map_context().modified()) {
830  const int res = gui2::show_message(_("Unsaved Changes"),
831  _("Do you want to discard all changes made to the map since the last save?"), gui2::dialogs::message::yes_no_buttons);
832  return gui2::retval::CANCEL != res;
833  }
834 
835  return true;
836 }
837 
838 void context_manager::fill_selection()
839 {
840  perform_refresh(editor_action_paint_area(get_map_context().map().selection(), get_selected_bg_terrain()));
841 }
842 
843 void context_manager::save_all_maps()
844 {
845  int current = current_context_index_;
846  for(std::size_t i = 0; i < map_contexts_.size(); ++i) {
847  switch_context(i);
848  save_map();
849  }
850  switch_context(current);
851 }
852 
853 void context_manager::save_contexts()
854 {
855  saved_contexts_.swap(map_contexts_);
856  std::swap(last_context_, current_context_index_);
857  create_blank_context();
858  switch_context(0, true);
859 }
860 
861 void context_manager::save_map(bool show_confirmation)
862 {
863  const std::string& name = get_map_context().get_filename();
864  if(name.empty() || filesystem::is_directory(name)) {
865  if(get_map_context().is_pure_map()) {
866  save_map_as_dialog();
867  } else {
868  save_scenario_as_dialog();
869  }
870  } else {
871  if(get_map_context().is_pure_map()) {
872  write_map(show_confirmation);
873  } else {
874  write_scenario(show_confirmation);
875  }
876  }
877 }
878 
879 bool context_manager::write_scenario(bool display_confirmation)
880 {
881  try {
882  get_map_context().save_scenario();
883  if(display_confirmation) {
884  gui2::show_transient_message("", _("Scenario saved."));
885  }
886  } catch (const editor_map_save_exception& e) {
887  gui2::show_transient_message("", e.what());
888  return false;
889  }
890 
891  return true;
892 }
893 
894 bool context_manager::write_map(bool display_confirmation)
895 {
896  try {
897  get_map_context().save_map();
898  if(display_confirmation) {
899  gui2::show_transient_message("", _("Map saved."));
900  }
901  } catch (const editor_map_save_exception& e) {
902  gui2::show_transient_message("", e.what());
903  return false;
904  }
905 
906  return true;
907 }
908 
909 std::size_t context_manager::check_open_map(const std::string& fn) const
910 {
911  std::size_t i = 0;
912  while(i < map_contexts_.size() && map_contexts_[i]->get_filename() != fn) {
913  ++i;
914  }
915 
916  return i;
917 }
918 
919 bool context_manager::check_switch_open_map(const std::string& fn)
920 {
921  std::size_t i = check_open_map(fn);
922  if(i < map_contexts_.size()) {
923  gui2::show_transient_message(_("This map is already open."), fn);
924  switch_context(i);
925  return true;
926  }
927 
928  return false;
929 }
930 
931 void context_manager::load_map(const std::string& filename, bool new_context)
932 {
933  if(new_context && check_switch_open_map(filename)) {
934  return;
935  }
936 
937  if(filesystem::is_cfg(filename)) {
938  if(editor_controller::current_addon_id_.empty()) {
939  // if no addon id has been set and the file being loaded is from an addon
940  // then use the file path to determine the addon rather than showing a dialog
941  if(auto addon_at_path = filesystem::get_addon_id_from_path(filename)) {
942  editor_controller::current_addon_id_ = addon_at_path.value();
943  } else {
944  editor_controller::current_addon_id_ = editor::initialize_addon();
945  }
946 
947  set_addon_id(editor_controller::current_addon_id_);
948  }
949 
950  if(editor_controller::current_addon_id_.empty()) {
951  return;
952  }
953  }
954 
955  LOG_ED << "Load map: " << filename << (new_context ? " (new)" : " (same)");
956  try {
957  {
958  auto mc = std::make_unique<map_context>(game_config_, filename, current_addon_);
959  if(mc->get_filename() != filename) {
960  if(new_context && check_switch_open_map(mc->get_filename())) {
961  return;
962  }
963  }
964 
965  if(new_context) {
966  int new_id = add_map_context_of(std::move(mc));
967  switch_context(new_id);
968  } else {
969  replace_map_context_with(std::move(mc));
970  }
971  }
972 
973  if(get_map_context().is_embedded()) {
974  const std::string& msg = _("Loaded embedded map data");
975  gui2::show_transient_message(_("Map loaded from scenario"), msg);
976  } else {
977  if(get_map_context().get_filename() != filename) {
978  gui2::show_transient_message(_("Map loaded from scenario"), _("Loaded referenced map file:")+"\n"+get_map_context().get_filename());
979  }
980  }
981  } catch(const editor_map_load_exception& e) {
982  gui2::show_transient_message(_("Error loading map"), e.what());
983  return;
984  }
985 }
986 
987 void context_manager::revert_map()
988 {
989  if(!confirm_discard()) {
990  return;
991  }
992 
993  std::string filename = get_map_context().get_filename();
994  if(filename.empty()) {
995  ERR_ED << "Empty filename in map revert";
996  return;
997  }
998 
999  load_map(filename, false);
1000 }
1001 
1002 void context_manager::new_map(int width, int height, const t_translation::terrain_code& fill, bool new_context)
1003 {
1004  const config& default_schedule = game_config_.find_mandatory_child("editor_times", "id", "empty");
1005  editor_map m(width, height, fill);
1006 
1007  if(new_context) {
1008  int new_id = add_map_context(m, true, default_schedule, current_addon_);
1009  switch_context(new_id);
1010  } else {
1011  replace_map_context(m, true, default_schedule, current_addon_);
1012  }
1013 }
1014 
1015 void context_manager::new_scenario(int width, int height, const t_translation::terrain_code& fill, bool new_context)
1016 {
1017  auto default_schedule = game_config_.find_child("editor_times", "id", "empty");
1018  editor_map m(width, height, fill);
1019 
1020  if(new_context) {
1021  int new_id = add_map_context(m, false, *default_schedule, current_addon_);
1022  switch_context(new_id);
1023  } else {
1024  replace_map_context(m, false, *default_schedule, current_addon_);
1025  }
1026 
1027  // Give the new scenario an initial side.
1028  get_map_context().new_side();
1029  gui().set_team(0, true);
1030  gui().set_playing_team(0);
1031  gui_.init_flags();
1032 }
1033 
1034 //
1035 // Context manipulation
1036 //
1037 
1038 template<typename... T>
1039 int context_manager::add_map_context(const T&... args)
1040 {
1041  map_contexts_.emplace_back(std::make_unique<map_context>(args...));
1042  return map_contexts_.size() - 1;
1043 }
1044 
1045 int context_manager::add_map_context_of(std::unique_ptr<map_context>&& mc)
1046 {
1047  map_contexts_.emplace_back(std::move(mc));
1048  return map_contexts_.size() - 1;
1049 }
1050 
1051 template<typename... T>
1052 void context_manager::replace_map_context(const T&... args)
1053 {
1054  replace_map_context_with(std::move(std::make_unique<map_context>(args...)));
1055 }
1056 
1057 void context_manager::replace_map_context_with(std::unique_ptr<map_context>&& mc)
1058 {
1059  map_contexts_[current_context_index_].swap(mc);
1060  refresh_on_context_change();
1061 }
1062 
1063 void context_manager::create_default_context()
1064 {
1065  if(saved_contexts_.empty()) {
1066  create_blank_context();
1067  switch_context(0, true);
1068  } else {
1069  saved_contexts_.swap(map_contexts_);
1070  switch_context(last_context_, true);
1071  last_context_ = 0;
1072  }
1073 }
1074 
1075 void context_manager::create_blank_context()
1076 {
1079 
1080  const config& default_schedule = game_config_.find_mandatory_child("editor_times", "id", "empty");
1081  add_map_context(editor_map(44, 33, default_terrain), true, default_schedule, current_addon_);
1082 }
1083 
1084 void context_manager::close_current_context()
1085 {
1086  if(!confirm_discard()) return;
1087 
1088  if(map_contexts_.size() == 1) {
1089  create_default_context();
1090  map_contexts_.erase(map_contexts_.begin());
1091  } else if(current_context_index_ == static_cast<int>(map_contexts_.size()) - 1) {
1092  map_contexts_.pop_back();
1093  current_context_index_--;
1094  } else {
1095  map_contexts_.erase(map_contexts_.begin() + current_context_index_);
1096  }
1097 
1098  refresh_on_context_change();
1099 }
1100 
1101 void context_manager::switch_context(const int index, const bool force)
1102 {
1103  if(index < 0 || static_cast<std::size_t>(index) >= map_contexts_.size()) {
1104  WRN_ED << "Invalid index in switch map context: " << index;
1105  return;
1106  }
1107 
1108  if(index == current_context_index_ && !force) {
1109  return;
1110  }
1111 
1112  // Disable the labels of the current context before switching.
1113  // The refresher handles enabling the new ones.
1114  get_map_context().get_labels().enable(false);
1115 
1116  current_context_index_ = index;
1117 
1118  refresh_on_context_change();
1119 }
1120 
1122 {
1123  std::string name = get_map_context().get_name();
1124 
1125  if(name.empty()) {
1126  name = filesystem::base_name(get_map_context().get_filename());
1127  }
1128 
1129  if(name.empty()){
1130  name = get_map_context().get_default_context_name();
1131  }
1132 
1133  const std::string& wm_title_string = name + " - " + game_config::get_default_title_string();
1134  video::set_window_title(wm_title_string);
1135 }
1136 
1137 } //Namespace editor
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:159
config & find_mandatory_child(config_key_type key, const std::string &name, const std::string &value)
Definition: config.cpp:813
void recalculate_minimap()
Schedule the minimap for recalculation.
Definition: display.cpp:1610
void rebuild_all()
Rebuild all dynamic terrain.
Definition: display.cpp:452
void change_display_context(const display_context *dc)
Definition: display.cpp:463
void set_team(std::size_t team, bool observe=false)
Sets the team controlled by the player using the computer.
Definition: display.cpp:353
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:3138
void init_flags()
Init the flag list and the team colors used by ~TC.
Definition: display.cpp:259
void reload_map()
Updates internals that cache map size.
Definition: display.cpp:457
void create_buttons()
Definition: display.cpp:875
void set_playing_team(std::size_t team)
set_playing_team sets the team whose turn it currently is
Definition: display.cpp:370
std::vector< std::unique_ptr< map_context > > map_contexts_
The currently opened map context object.
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 new_map(int width, int height, const t_translation::terrain_code &fill, bool new_context)
Create a new map.
void new_map_dialog()
Display a new map dialog and process user input.
int auto_update_transitions_
Flag to rebuild terrain on every terrain change.
void load_map_dialog(bool force_same_context=false)
Display a load map dialog and process user input.
void set_window_title()
Displays the specified map name in the window titlebar.
void refresh_all()
Refresh everything, i.e.
void init_map_generators(const game_config_view &game_config)
init available random map generators
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 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.
editor_display & gui()
void edit_scenario_dialog()
Display a scenario edit dialog and process user input.
std::string current_addon_
The currently selected add-on.
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.
class location_palette * locs_
void expand_load_mru_menu(std::vector< config > &items, int i)
Menu expanding for most recent loaded list.
void expand_open_maps_menu(std::vector< config > &items, int i)
Menu expanding for open maps list.
void edit_side_dialog(int side_index)
Display a side edit dialog and process user input.
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
This class adds extra editor-specific functionality to a normal gamemap.
Definition: editor_map.hpp:70
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
void set_needs_reload(bool value=true)
Setter for the reload flag.
const std::string & get_id() const
bool random_start_time() const
void set_side_setup(editor_team_info &info)
const std::string & get_description() const
bool is_embedded() const
void clear_changed_locations()
const tod_manager * get_time_manager() const
std::optional< int > get_xp_mod() const
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_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 ...
int w() const
Effective map width.
Definition: map.hpp:50
int h() const
Effective map height.
Definition: map.hpp:53
std::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(std::optional< uint32_t > randomseed={})=0
Creates a new map and returns it.
void enable(bool is_enabled)
Definition: label.cpp:254
static prefs & get()
void set_editor_auto_update_transitions(int value)
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
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 swap(config &lhs, config &rhs)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1347
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:968
int w
#define N_(String)
Definition: gettext.hpp:101
static std::string _(const char *str)
Definition: gettext.hpp:93
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:207
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:216
@ 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:50
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:31
std::string get_legacy_editor_dir()
const std::string wml_extension
Definition: filesystem.hpp:80
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:335
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:762
std::optional< std::string > get_addon_id_from_path(const std::string &location)
Returns the add-on ID from a path.
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:78
const std::string mask_extension
Definition: filesystem.hpp:79
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:89
std::string get_default_title_string()
std::string default_terrain
Definition: game_config.cpp:54
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:203
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:150
@ 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::pair< std::string, unsigned > item
Definition: help_impl.hpp:410
const int TRANSITION_UPDATE_ON
Definition: preferences.hpp:51
const int TRANSITION_UPDATE_COUNT
Definition: preferences.hpp:53
const int TRANSITION_UPDATE_OFF
Definition: preferences.hpp:50
const int TRANSITION_UPDATE_PARTIAL
Definition: preferences.hpp:52
::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(const std::string &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:634
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
Encapsulates the map of the game.
Definition: location.hpp:38
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:67
#define e
#define h
#define a