The Battle for Wesnoth  1.19.5+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 "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"))
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, int i)
333 {
334  auto pos = items.erase(items.begin() + i);
335  std::vector<config> contexts;
336 
337  for(std::size_t mci = 0; mci < map_contexts_.size(); ++mci) {
338  map_context& mc = *map_contexts_[mci];
339 
340  std::string filename;
341  if(mc.is_pure_map()) {
343  } else {
344  filename = mc.get_name();
345  }
346 
347  if(filename.empty()) {
349  }
350 
351  std::ostringstream ss;
352  ss << "[" << mci + 1 << "] ";
353 
354  const bool changed = mc.modified();
355 
356  if(changed) {
357  ss << markup::italic(filename);
358  } else {
359  ss << filename;
360  }
361 
362  if(mc.is_embedded()) {
363  ss << " (E)";
364  }
365 
366  const std::string label = ss.str();
367  const std::string details = get_menu_marker(changed);
368 
369  contexts.emplace_back("label", label, "details", details);
370  }
371 
372  items.insert(pos, contexts.begin(), contexts.end());
373 }
374 
375 void context_manager::expand_load_mru_menu(std::vector<config>& items, int i)
376 {
377  std::vector<std::string> mru = prefs::get().recent_files();
378 
379  auto pos = items.erase(items.begin() + i);
380 
381  if(mru.empty()) {
382  items.insert(pos, config {"label", _("No Recent Files")});
383  return;
384  }
385 
386  for(std::string& path : mru) {
387  // TODO: add proper leading ellipsization instead, since otherwise
388  // it'll be impossible to tell apart files with identical names and
389  // different parent paths.
391  }
392 
393  std::vector<config> temp;
394  std::transform(mru.begin(), mru.end(), std::back_inserter(temp), [](const std::string& str) {
395  return config {"label", str};
396  });
397 
398  items.insert(pos, temp.begin(), temp.end());
399 }
400 
401 void context_manager::expand_areas_menu(std::vector<config>& items, int i)
402 {
403  tod_manager* tod = get_map_context().get_time_manager();
404  if(!tod) {
405  return;
406  }
407 
408  auto pos = items.erase(items.begin() + i);
409  std::vector<config> area_entries;
410 
411  std::vector<std::string> area_ids = tod->get_area_ids();
412 
413  for(std::size_t mci = 0; mci < area_ids.size(); ++mci) {
414  const std::string& area = area_ids[mci];
415 
416  std::stringstream ss;
417  ss << "[" << mci + 1 << "] ";\
418 
419  if(area.empty()) {
420  ss << markup::italic(_("Unnamed Area"));
421  } else {
422  ss << area;
423  }
424 
425  const bool changed =
426  mci == static_cast<std::size_t>(get_map_context().get_active_area())
427  && tod->get_area_by_index(mci) != get_map_context().map().selection();
428 
429  const std::string label = ss.str();
430  const std::string details = get_menu_marker(changed);
431 
432  area_entries.emplace_back("label", label, "details", details);
433  }
434 
435  items.insert(pos, area_entries.begin(), area_entries.end());
436 }
437 
438 void context_manager::expand_sides_menu(std::vector<config>& items, int i)
439 {
440  auto pos = items.erase(items.begin() + i);
441  std::vector<config> contexts;
442 
443  for(std::size_t mci = 0; mci < get_map_context().teams().size(); ++mci) {
444 
445  const team& t = get_map_context().teams()[mci];
446  const std::string& teamname = t.user_team_name();
447  std::stringstream label;
448  label << "[" << mci+1 << "] ";
449 
450  if(teamname.empty()) {
451  label << markup::italic(_("New Side"));
452  } else {
453  label << teamname;
454  }
455 
456  contexts.emplace_back("label", label.str());
457  }
458 
459  items.insert(pos, contexts.begin(), contexts.end());
460 }
461 
462 void context_manager::expand_time_menu(std::vector<config>& items, int i)
463 {
464  auto pos = items.erase(items.begin() + i);
465  std::vector<config> times;
466 
467  tod_manager* tod_m = get_map_context().get_time_manager();
468 
469  assert(tod_m != nullptr);
470 
471  for(const time_of_day& time : tod_m->times()) {
472  times.emplace_back(
473  "details", time.name, // Use 'details' field here since the image will take the first column
474  "image", time.image
475  );
476  }
477 
478  items.insert(pos, times.begin(), times.end());
479 }
480 
481 void context_manager::expand_local_time_menu(std::vector<config>& items, int i)
482 {
483  auto pos = items.erase(items.begin() + i);
484  std::vector<config> times;
485 
486  tod_manager* tod_m = get_map_context().get_time_manager();
487 
488  for(const time_of_day& time : tod_m->times(get_map_context().get_active_area())) {
489  times.emplace_back(
490  "details", time.name, // Use 'details' field here since the image will take the first column
491  "image", time.image
492  );
493  }
494 
495  items.insert(pos, times.begin(), times.end());
496 }
497 
498 void context_manager::apply_mask_dialog()
499 {
500  std::string fn = get_map_context().get_filename();
501  if(fn.empty()) {
502  fn = filesystem::get_dir(filesystem::get_current_editor_dir(current_addon_) + "/masks");
503  }
504 
506 
507  dlg.set_title(_("Apply Mask"))
508  .set_path(fn);
509 
510  if(dlg.show()) {
511  try {
512  map_context mask(game_config_, dlg.path(), current_addon_);
513  editor_action_apply_mask a(mask.map());
514  perform_refresh(a);
515  } catch (const editor_map_load_exception& e) {
516  gui2::show_transient_message(_("Error loading mask"), e.what());
517  return;
518  } catch (const editor_action_exception& e) {
519  gui2::show_error_message(e.what());
520  return;
521  }
522  }
523 }
524 
525 void context_manager::perform_refresh(const editor_action& action, bool drag_part /* =false */)
526 {
527  get_map_context().perform_action(action);
528  refresh_after_action(drag_part);
529 }
530 
531 void context_manager::rename_area_dialog()
532 {
533  int active_area = get_map_context().get_active_area();
534  std::string name = get_map_context().get_time_manager()->get_area_ids()[active_area];
535 
536  if(gui2::dialogs::edit_text::execute(N_("Rename Area"), N_("Identifier:"), name)) {
537  get_map_context().get_time_manager()->set_area_id(active_area, name);
538  }
539 }
540 
541 void context_manager::create_mask_to_dialog()
542 {
543  std::string fn = get_map_context().get_filename();
544  if(fn.empty()) {
545  fn = filesystem::get_dir(filesystem::get_current_editor_dir(current_addon_) + "/masks");
546  }
547 
549 
550  dlg.set_title(_("Choose Target Map"))
551  .set_path(fn);
552 
553  if(dlg.show()) {
554  try {
555  map_context map(game_config_, dlg.path(), current_addon_);
557  perform_refresh(a);
558  } catch (const editor_map_load_exception& e) {
559  gui2::show_transient_message(_("Error loading map"), e.what());
560  return;
561  } catch (const editor_action_exception& e) {
562  gui2::show_error_message(e.what());
563  return;
564  }
565  }
566 }
567 
568 void context_manager::refresh_after_action(bool drag_part)
569 {
570  if(get_map_context().needs_reload()) {
571  reload_map();
572  return;
573  }
574 
575  const std::set<map_location>& changed_locs = get_map_context().changed_locations();
576 
577  if(get_map_context().needs_terrain_rebuild()) {
578  if((auto_update_transitions_ == pref_constants::TRANSITION_UPDATE_ON)
579  || ((auto_update_transitions_ == pref_constants::TRANSITION_UPDATE_PARTIAL)
580  && (!drag_part || get_map_context().everything_changed())))
581  {
582  gui_.rebuild_all();
583  get_map_context().set_needs_terrain_rebuild(false);
584  gui_.invalidate_all();
585  } else {
586  for(const map_location& loc : changed_locs) {
587  gui_.rebuild_terrain(loc);
588  }
589  gui_.invalidate(changed_locs);
590  }
591  } else {
592  if(get_map_context().everything_changed()) {
593  gui_.invalidate_all();
594  } else {
595  gui_.invalidate(changed_locs);
596  }
597  }
598 
599  if(get_map_context().needs_labels_reset()) {
600  get_map_context().reset_starting_position_labels(gui_);
601  }
602 
603  get_map_context().clear_changed_locations();
604  gui_.recalculate_minimap();
605 }
606 
607 void context_manager::resize_map_dialog()
608 {
609  const editor_map& map = get_map_context().map();
610 
611  int w = map.w();
612  int h = map.h();
613 
615  bool copy = false;
616 
617  if(!gui2::dialogs::editor_resize_map::execute(w, h, dir, copy)) {
618  return;
619  }
620 
621  if(w != map.w() || h != map.h()) {
623  if(copy) {
625  }
626 
627  int x_offset = map.w() - w;
628  int y_offset = map.h() - h;
629 
630  switch (dir) {
634  y_offset = 0;
635  break;
639  y_offset /= 2;
640  break;
644  break;
645  default:
646  y_offset = 0;
647  WRN_ED << "Unknown resize expand direction";
648  break;
649  }
650 
651  switch (dir) {
655  x_offset = 0;
656  break;
660  x_offset /= 2;
661  break;
665  break;
666  default:
667  x_offset = 0;
668  break;
669  }
670 
671  editor_action_resize_map a(w, h, x_offset, y_offset, fill);
672  perform_refresh(a);
673  }
674 }
675 
676 void context_manager::save_map_as_dialog()
677 {
678  bool first_pick = false;
679  std::string input_name = get_map_context().get_filename();
680  if(input_name.empty()) {
681  first_pick = true;
682  if (editor_controller::current_addon_id_.empty()) {
683  input_name = filesystem::get_legacy_editor_dir() + "/maps";
684  } else {
685  input_name = filesystem::get_current_editor_dir(editor_controller::current_addon_id_) + "/maps";
686  }
687  }
688 
690 
691  dlg.set_title(_("Save Map As"))
692  .set_save_mode(true)
693  .set_path(input_name)
696 
697  if(!dlg.show()) {
698  return;
699  }
700 
701  boost::filesystem::path save_path(dlg.path());
702 
703  // Show warning the first time user tries to save in a wrong folder
704  std::string last_folder = save_path.parent_path().filename().string();
705  if ((last_folder == "scenarios")
706  && first_pick
707  && (gui2::show_message(
708  _("Error"),
709  VGETTEXT("Do you really want to save $type1 in $type2 folder?", {{"type1", "map"}, {"type2", "scenarios"}}),
711  {
712  return;
713  }
714 
715  std::size_t is_open = check_open_map(save_path.string());
716  if(is_open < map_contexts_.size() && is_open != static_cast<unsigned>(current_context_index_)) {
717  gui2::show_transient_message(_("This map is already open."), save_path.string());
718  }
719 
720  std::string old_filename = get_map_context().get_filename();
721 
722  get_map_context().set_filename(save_path.string());
723 
724  if(!write_map(true)) {
725  get_map_context().set_filename(old_filename);
726  }
727 }
728 
729 void context_manager::save_scenario_as_dialog()
730 {
731  bool first_pick = false;
732  std::string input_name = get_map_context().get_filename();
733  if(input_name.empty()) {
734  first_pick = true;
735  input_name = filesystem::get_current_editor_dir(editor_controller::current_addon_id_) + "/scenarios";
736  }
737 
739 
740  dlg.set_title(_("Save Scenario As"))
741  .set_save_mode(true)
742  .set_path(input_name)
745 
746  if(!dlg.show()) {
747  return;
748  }
749 
750  boost::filesystem::path save_path(dlg.path());
751 
752  // Show warning the first time user tries to save in a wrong folder
753  std::string last_folder = save_path.parent_path().filename().string();
754  if ((last_folder == "maps")
755  && first_pick
756  && (gui2::show_message(
757  _("Error"),
758  VGETTEXT("Do you really want to save $type1 in $type2 folder?", {{"type1", "scenario"}, {"type2", "maps"}}),
760  {
761  return;
762  }
763 
764  std::size_t is_open = check_open_map(save_path.string());
765  if(is_open < map_contexts_.size() && is_open != static_cast<unsigned>(current_context_index_)) {
766  gui2::show_transient_message(_("This scenario is already open."), save_path.string());
767  return;
768  }
769 
770  std::string old_filename = get_map_context().get_filename();
771 
772  get_map_context().set_filename(save_path.string());
773 
774  if(!write_scenario(true)) {
775  get_map_context().set_filename(old_filename);
776  return;
777  }
778 }
779 
780 void context_manager::init_map_generators(const game_config_view& game_config)
781 {
782  for(const config& i : game_config.child_range("multiplayer")) {
783  if(i["map_generation"].empty() && i["scenario_generation"].empty()) {
784  continue;
785  }
786 
787  // TODO: we should probably use `child` with a try/catch block once that function throws
788  if(const auto generator_cfg = i.optional_child("generator")) {
789  map_generators_.emplace_back(create_map_generator(i["map_generation"].empty() ? i["scenario_generation"] : i["map_generation"], generator_cfg.value()));
790  } else {
791  ERR_ED << "Scenario \"" << i["name"] << "\" with id " << i["id"]
792  << " has map_generation= but no [generator] tag";
793  }
794  }
795 }
796 
797 void context_manager::generate_map_dialog()
798 {
799  if(map_generators_.empty()) {
800  gui2::show_error_message(_("No random map generators found."));
801  return;
802  }
803 
804  gui2::dialogs::editor_generate_map dialog(map_generators_);
805  dialog.select_map_generator(last_map_generator_);
806 
807  if(dialog.show()) {
808  std::string map_string;
810  try {
811  map_string = map_generator->create_map(dialog.get_seed());
812  } catch (const mapgen_exception& e) {
813  gui2::show_transient_message(_("Map creation failed."), e.what());
814  return;
815  }
816 
817  if(map_string.empty()) {
818  gui2::show_transient_message("", _("Map creation failed."));
819  } else {
820  editor_map new_map(map_string);
821  editor_action_whole_map a(new_map);
822  get_map_context().set_needs_labels_reset(); // Ensure Player Start labels are updated together with newly generated map
823  perform_refresh(a);
824  }
825 
826  last_map_generator_ = map_generator;
827  }
828 }
829 
830 bool context_manager::confirm_discard()
831 {
832  if(get_map_context().modified()) {
833  const int res = gui2::show_message(_("Unsaved Changes"),
834  _("Do you want to discard all changes made to the map since the last save?"), gui2::dialogs::message::yes_no_buttons);
835  return gui2::retval::CANCEL != res;
836  }
837 
838  return true;
839 }
840 
841 void context_manager::fill_selection()
842 {
843  perform_refresh(editor_action_paint_area(get_map_context().map().selection(), get_selected_bg_terrain()));
844 }
845 
846 void context_manager::save_all_maps()
847 {
848  int current = current_context_index_;
849  for(std::size_t i = 0; i < map_contexts_.size(); ++i) {
850  switch_context(i);
851  save_map();
852  }
853  switch_context(current);
854 }
855 
856 void context_manager::save_contexts()
857 {
858  saved_contexts_.swap(map_contexts_);
859  std::swap(last_context_, current_context_index_);
860  create_blank_context();
861  switch_context(0, true);
862 }
863 
864 void context_manager::save_map(bool show_confirmation)
865 {
866  const std::string& name = get_map_context().get_filename();
867  if(name.empty() || filesystem::is_directory(name)) {
868  if(get_map_context().is_pure_map()) {
869  save_map_as_dialog();
870  } else {
871  save_scenario_as_dialog();
872  }
873  } else {
874  if(get_map_context().is_pure_map()) {
875  write_map(show_confirmation);
876  } else {
877  write_scenario(show_confirmation);
878  }
879  }
880 }
881 
882 bool context_manager::write_scenario(bool display_confirmation)
883 {
884  try {
885  get_map_context().save_scenario();
886  if(display_confirmation) {
887  gui_.set_status(_("Scenario saved."), true);
888  }
889  } catch (const editor_map_save_exception& e) {
890  gui_.set_status(e.what(), false);
891  return false;
892  }
893 
894  return true;
895 }
896 
897 bool context_manager::write_map(bool display_confirmation)
898 {
899  try {
900  get_map_context().save_map();
901  if(display_confirmation) {
902  gui_.set_status(_("Map saved"), true);
903  }
904  } catch (const editor_map_save_exception& e) {
905  gui_.set_status(e.what(), false);
906  return false;
907  }
908 
909  return true;
910 }
911 
912 std::size_t context_manager::check_open_map(const std::string& fn) const
913 {
914  std::size_t i = 0;
915  while(i < map_contexts_.size() && map_contexts_[i]->get_filename() != fn) {
916  ++i;
917  }
918 
919  return i;
920 }
921 
922 bool context_manager::check_switch_open_map(const std::string& fn)
923 {
924  std::size_t i = check_open_map(fn);
925  if(i < map_contexts_.size()) {
926  gui2::show_transient_message(_("This map is already open."), fn);
927  switch_context(i);
928  return true;
929  }
930 
931  return false;
932 }
933 
934 void context_manager::load_map(const std::string& filename, bool new_context)
935 {
936  if(new_context && check_switch_open_map(filename)) {
937  return;
938  }
939 
941  if(editor_controller::current_addon_id_.empty()) {
942  // if no addon id has been set and the file being loaded is from an addon
943  // then use the file path to determine the addon rather than showing a dialog
944  if(auto addon_at_path = filesystem::get_addon_id_from_path(filename)) {
945  editor_controller::current_addon_id_ = addon_at_path.value();
946  } else {
947  editor_controller::current_addon_id_ = editor::initialize_addon();
948  }
949 
950  set_addon_id(editor_controller::current_addon_id_);
951  }
952 
953  if(editor_controller::current_addon_id_.empty()) {
954  return;
955  }
956  }
957 
958  LOG_ED << "Load map: " << filename << (new_context ? " (new)" : " (same)");
959  try {
960  {
961  auto mc = std::make_unique<map_context>(game_config_, filename, current_addon_);
962  if(mc->get_filename() != filename) {
963  if(new_context && check_switch_open_map(mc->get_filename())) {
964  return;
965  }
966  }
967 
968  if(new_context) {
969  int new_id = add_map_context_of(std::move(mc));
970  switch_context(new_id);
971  } else {
972  replace_map_context_with(std::move(mc));
973  }
974  }
975 
976  if(get_map_context().is_embedded()) {
977  const std::string& msg = _("Loaded embedded map data");
978  gui2::show_transient_message(_("Map loaded from scenario"), msg);
979  } else {
980  if(get_map_context().get_filename() != filename) {
981  gui2::show_transient_message(_("Map loaded from scenario"), _("Loaded referenced map file:")+"\n"+get_map_context().get_filename());
982  }
983  }
984  } catch(const editor_map_load_exception& e) {
985  gui2::show_transient_message(_("Error loading map"), e.what());
986  return;
987  }
988 }
989 
990 void context_manager::revert_map()
991 {
992  if(!confirm_discard()) {
993  return;
994  }
995 
996  std::string filename = get_map_context().get_filename();
997  if(filename.empty()) {
998  ERR_ED << "Empty filename in map revert";
999  return;
1000  }
1001 
1002  load_map(filename, false);
1003 }
1004 
1005 void context_manager::new_map(int width, int height, const t_translation::terrain_code& fill, bool new_context)
1006 {
1007  const config& default_schedule = game_config_.find_mandatory_child("editor_times", "id", "empty");
1008  editor_map m(width, height, fill);
1009 
1010  if(new_context) {
1011  int new_id = add_map_context(m, true, default_schedule, current_addon_);
1012  switch_context(new_id);
1013  } else {
1014  replace_map_context(m, true, default_schedule, current_addon_);
1015  }
1016 }
1017 
1018 void context_manager::new_scenario(int width, int height, const t_translation::terrain_code& fill, bool new_context)
1019 {
1020  auto default_schedule = game_config_.find_child("editor_times", "id", "empty");
1021  editor_map m(width, height, fill);
1022 
1023  if(new_context) {
1024  int new_id = add_map_context(m, false, *default_schedule, current_addon_);
1025  switch_context(new_id);
1026  } else {
1027  replace_map_context(m, false, *default_schedule, current_addon_);
1028  }
1029 
1030  // Give the new scenario an initial side.
1031  get_map_context().new_side();
1032  gui().set_viewing_team_index(0, true);
1033  gui().set_playing_team_index(0);
1034  gui_.init_flags();
1035 }
1036 
1037 //
1038 // Context manipulation
1039 //
1040 
1041 template<typename... T>
1042 int context_manager::add_map_context(const T&... args)
1043 {
1044  map_contexts_.emplace_back(std::make_unique<map_context>(args...));
1045  return map_contexts_.size() - 1;
1046 }
1047 
1048 int context_manager::add_map_context_of(std::unique_ptr<map_context>&& mc)
1049 {
1050  map_contexts_.emplace_back(std::move(mc));
1051  return map_contexts_.size() - 1;
1052 }
1053 
1054 template<typename... T>
1055 void context_manager::replace_map_context(const T&... args)
1056 {
1057  replace_map_context_with(std::move(std::make_unique<map_context>(args...)));
1058 }
1059 
1060 void context_manager::replace_map_context_with(std::unique_ptr<map_context>&& mc)
1061 {
1062  map_contexts_[current_context_index_].swap(mc);
1063  refresh_on_context_change();
1064 }
1065 
1066 void context_manager::create_default_context()
1067 {
1068  if(saved_contexts_.empty()) {
1069  create_blank_context();
1070  switch_context(0, true);
1071  } else {
1072  saved_contexts_.swap(map_contexts_);
1073  switch_context(last_context_, true);
1074  last_context_ = 0;
1075  }
1076 }
1077 
1078 void context_manager::create_blank_context()
1079 {
1082 
1083  const config& default_schedule = game_config_.find_mandatory_child("editor_times", "id", "empty");
1084  add_map_context(editor_map(44, 33, default_terrain), true, default_schedule, current_addon_);
1085 }
1086 
1087 void context_manager::close_current_context()
1088 {
1089  if(!confirm_discard()) return;
1090 
1091  if(map_contexts_.size() == 1) {
1092  create_default_context();
1093  map_contexts_.erase(map_contexts_.begin());
1094  } else if(current_context_index_ == static_cast<int>(map_contexts_.size()) - 1) {
1095  map_contexts_.pop_back();
1096  current_context_index_--;
1097  } else {
1098  map_contexts_.erase(map_contexts_.begin() + current_context_index_);
1099  }
1100 
1101  refresh_on_context_change();
1102 }
1103 
1104 void context_manager::switch_context(const int index, const bool force)
1105 {
1106  if(index < 0 || static_cast<std::size_t>(index) >= map_contexts_.size()) {
1107  WRN_ED << "Invalid index in switch map context: " << index;
1108  return;
1109  }
1110 
1111  if(index == current_context_index_ && !force) {
1112  return;
1113  }
1114 
1115  // Disable the labels of the current context before switching.
1116  // The refresher handles enabling the new ones.
1117  get_map_context().get_labels().enable(false);
1118 
1119  current_context_index_ = index;
1120 
1121  refresh_on_context_change();
1122 }
1123 
1125 {
1126  std::string name = get_map_context().get_name();
1127 
1128  if(name.empty()) {
1129  name = filesystem::base_name(get_map_context().get_filename());
1130  }
1131 
1132  if(name.empty()){
1133  name = get_map_context().get_default_context_name();
1134  }
1135 
1136  const std::string& wm_title_string = name + " - " + game_config::get_default_title_string();
1137  video::set_window_title(wm_title_string);
1138 }
1139 
1140 } //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:172
config & find_mandatory_child(config_key_type key, const std::string &name, const std::string &value)
Definition: config.cpp:810
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:347
void recalculate_minimap()
Schedule the minimap for recalculation.
Definition: display.cpp:1576
void set_playing_team_index(std::size_t team)
sets the team whose turn it currently is
Definition: display.cpp:364
void rebuild_all()
Rebuild all dynamic terrain.
Definition: display.cpp:433
void change_display_context(const display_context *dc)
Definition: display.cpp:444
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:3080
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:438
void create_buttons()
Definition: display.cpp:845
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 edit_side_dialog(const team &t)
Display a side edit dialog and process user input.
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 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_
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
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
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 ...
int w() const
Effective map width.
Definition: map.hpp:50
int h() const
Effective map height.
Definition: map.hpp:53
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 swap(config &lhs, config &rhs)
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1343
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:1028
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:200
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: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:336
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:796
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:90
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)
Definition: markup.hpp:140
std::string span_color(const color_t &color, Args &&... data)
Definition: markup.hpp:68
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(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:636
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