The Battle for Wesnoth  1.19.4+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_viewing_team_index(0, true);
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 {
176  prefs::get().set_editor_auto_update_transitions(auto_update_transitions_);
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  fn = filesystem::get_legacy_editor_dir() + "/maps";
213  } else {
215  }
216  }
217 
219 
220  dlg.set_title(_("Load Map"))
221  .set_path(fn);
222 
223  if(dlg.show()) {
224  load_map(dlg.path(), !force_same_context);
225  }
226 }
227 
228 void context_manager::load_mru_item(unsigned index, bool force_same_context /* = false */)
229 {
230  const std::vector<std::string>& mru = prefs::get().recent_files();
231  if(mru.empty() || index >= mru.size()) {
232  return;
233  }
234 
235  load_map(mru[index], !force_same_context);
236 }
237 
239 {
240  editor_team_info team_info(t);
241 
242  if(gui2::dialogs::editor_edit_side::execute(team_info)) {
243  get_map_context().set_side_setup(team_info);
244  }
245 }
246 
248 {
249  if(!current_addon_.empty()) {
250  std::string pbl = filesystem::get_current_editor_dir(current_addon_) + "/_server.pbl";
251  gui2::dialogs::editor_edit_pbl::execute(pbl, current_addon_);
252  }
253 }
254 
256 {
257  std::string new_addon_id = current_addon_;
258  gui2::dialogs::prompt::execute(new_addon_id);
259 
260  std::string old_dir = filesystem::get_current_editor_dir(current_addon_);
261  std::string new_dir = filesystem::get_current_editor_dir(new_addon_id);
262  if(addon_filename_legal(new_addon_id) && filesystem::rename_dir(old_dir, new_dir)) {
263  std::string main_cfg = new_dir + "/_main.cfg";
264  std::string main = filesystem::read_file(main_cfg);
265 
266  // update paths
267  boost::replace_all(main, "/"+current_addon_, "/"+new_addon_id);
268  // update textdomain
269  boost::replace_all(main, "wesnoth-"+current_addon_, "wesnoth-"+new_addon_id);
270  filesystem::write_file(main_cfg, main);
271 
272  current_addon_ = new_addon_id;
273 
274  for(std::unique_ptr<map_context>& context : map_contexts_) {
275  context->set_addon_id(current_addon_);
276  }
277  }
278 }
279 
281 {
282  map_context& context = get_map_context();
283 
284  std::string id = context.get_id();
285  std::string name = context.get_name();
286  std::string description = context.get_description();
287 
288  int turns = context.get_time_manager()->number_of_turns();
289  int xp_mod = context.get_xp_mod() ? *context.get_xp_mod() : 70;
290 
291  bool victory = context.victory_defeated();
292  bool random = context.random_start_time();
293 
294  const bool ok = gui2::dialogs::editor_edit_scenario::execute(
295  id, name, description, turns, xp_mod, victory, random
296  );
297 
298  if(!ok) {
299  return;
300  }
301 
302  context.set_scenario_setup(id, name, description, turns, xp_mod, victory, random);
303 
304  if(!name.empty()) {
306  }
307 }
308 
310 {
311  const editor_map& map = get_map_context().map();
312 
313  int w = map.w();
314  int h = map.h();
315 
316  if(gui2::dialogs::editor_new_map::execute(_("New Map"), w, h)) {
318  new_map(w, h, fill, true);
319  }
320 }
321 
323 {
324  const editor_map& map = get_map_context().map();
325 
326  int w = map.w();
327  int h = map.h();
328 
329  if(gui2::dialogs::editor_new_map::execute(_("New Scenario"), w, h)) {
331  new_scenario(w, h, fill, true);
332  }
333 }
334 
335 void context_manager::expand_open_maps_menu(std::vector<config>& items, int i)
336 {
337  auto pos = items.erase(items.begin() + i);
338  std::vector<config> contexts;
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 << "<i>" << filename << "</i>";
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  contexts.emplace_back("label", label, "details", details);
373  }
374 
375  items.insert(pos, contexts.begin(), contexts.end());
376 }
377 
378 void context_manager::expand_load_mru_menu(std::vector<config>& items, int i)
379 {
380  std::vector<std::string> mru = prefs::get().recent_files();
381 
382  auto pos = items.erase(items.begin() + i);
383 
384  if(mru.empty()) {
385  items.insert(pos, config {"label", _("No Recent Files")});
386  return;
387  }
388 
389  for(std::string& path : mru) {
390  // TODO: add proper leading ellipsization instead, since otherwise
391  // it'll be impossible to tell apart files with identical names and
392  // different parent paths.
394  }
395 
396  std::vector<config> temp;
397  std::transform(mru.begin(), mru.end(), std::back_inserter(temp), [](const std::string& str) {
398  return config {"label", str};
399  });
400 
401  items.insert(pos, temp.begin(), temp.end());
402 }
403 
404 void context_manager::expand_areas_menu(std::vector<config>& items, int i)
405 {
406  tod_manager* tod = get_map_context().get_time_manager();
407  if(!tod) {
408  return;
409  }
410 
411  auto pos = items.erase(items.begin() + i);
412  std::vector<config> area_entries;
413 
414  std::vector<std::string> area_ids = tod->get_area_ids();
415 
416  for(std::size_t mci = 0; mci < area_ids.size(); ++mci) {
417  const std::string& area = area_ids[mci];
418 
419  std::stringstream ss;
420  ss << "[" << mci + 1 << "] ";\
421 
422  if(area.empty()) {
423  ss << "<i>" << _("Unnamed Area") << "</i>";
424  } else {
425  ss << area;
426  }
427 
428  const bool changed =
429  mci == static_cast<std::size_t>(get_map_context().get_active_area())
430  && tod->get_area_by_index(mci) != get_map_context().map().selection();
431 
432  const std::string label = ss.str();
433  const std::string details = get_menu_marker(changed);
434 
435  area_entries.emplace_back("label", label, "details", details);
436  }
437 
438  items.insert(pos, area_entries.begin(), area_entries.end());
439 }
440 
441 void context_manager::expand_sides_menu(std::vector<config>& items, int i)
442 {
443  auto pos = items.erase(items.begin() + i);
444  std::vector<config> contexts;
445 
446  for(std::size_t mci = 0; mci < get_map_context().teams().size(); ++mci) {
447 
448  const team& t = get_map_context().teams()[mci];
449  const std::string& teamname = t.user_team_name();
450  std::stringstream label;
451  label << "[" << mci+1 << "] ";
452 
453  if(teamname.empty()) {
454  label << "<i>" << _("New Side") << "</i>";
455  } else {
456  label << teamname;
457  }
458 
459  contexts.emplace_back("label", label.str());
460  }
461 
462  items.insert(pos, contexts.begin(), contexts.end());
463 }
464 
465 void context_manager::expand_time_menu(std::vector<config>& items, int i)
466 {
467  auto pos = items.erase(items.begin() + i);
468  std::vector<config> times;
469 
470  tod_manager* tod_m = get_map_context().get_time_manager();
471 
472  assert(tod_m != nullptr);
473 
474  for(const time_of_day& time : tod_m->times()) {
475  times.emplace_back(
476  "details", time.name, // Use 'details' field here since the image will take the first column
477  "image", time.image
478  );
479  }
480 
481  items.insert(pos, times.begin(), times.end());
482 }
483 
484 void context_manager::expand_local_time_menu(std::vector<config>& items, int i)
485 {
486  auto pos = items.erase(items.begin() + i);
487  std::vector<config> times;
488 
489  tod_manager* tod_m = get_map_context().get_time_manager();
490 
491  for(const time_of_day& time : tod_m->times(get_map_context().get_active_area())) {
492  times.emplace_back(
493  "details", time.name, // Use 'details' field here since the image will take the first column
494  "image", time.image
495  );
496  }
497 
498  items.insert(pos, times.begin(), times.end());
499 }
500 
501 void context_manager::apply_mask_dialog()
502 {
503  std::string fn = get_map_context().get_filename();
504  if(fn.empty()) {
505  fn = filesystem::get_dir(filesystem::get_current_editor_dir(current_addon_) + "/masks");
506  }
507 
509 
510  dlg.set_title(_("Apply Mask"))
511  .set_path(fn);
512 
513  if(dlg.show()) {
514  try {
515  map_context mask(game_config_, dlg.path(), current_addon_);
516  editor_action_apply_mask a(mask.map());
517  perform_refresh(a);
518  } catch (const editor_map_load_exception& e) {
519  gui2::show_transient_message(_("Error loading mask"), e.what());
520  return;
521  } catch (const editor_action_exception& e) {
522  gui2::show_error_message(e.what());
523  return;
524  }
525  }
526 }
527 
528 void context_manager::perform_refresh(const editor_action& action, bool drag_part /* =false */)
529 {
530  get_map_context().perform_action(action);
531  refresh_after_action(drag_part);
532 }
533 
534 void context_manager::rename_area_dialog()
535 {
536  int active_area = get_map_context().get_active_area();
537  std::string name = get_map_context().get_time_manager()->get_area_ids()[active_area];
538 
539  if(gui2::dialogs::edit_text::execute(N_("Rename Area"), N_("Identifier:"), name)) {
540  get_map_context().get_time_manager()->set_area_id(active_area, name);
541  }
542 }
543 
544 void context_manager::create_mask_to_dialog()
545 {
546  std::string fn = get_map_context().get_filename();
547  if(fn.empty()) {
548  fn = filesystem::get_dir(filesystem::get_current_editor_dir(current_addon_) + "/masks");
549  }
550 
552 
553  dlg.set_title(_("Choose Target Map"))
554  .set_path(fn);
555 
556  if(dlg.show()) {
557  try {
558  map_context map(game_config_, dlg.path(), current_addon_);
560  perform_refresh(a);
561  } catch (const editor_map_load_exception& e) {
562  gui2::show_transient_message(_("Error loading map"), e.what());
563  return;
564  } catch (const editor_action_exception& e) {
565  gui2::show_error_message(e.what());
566  return;
567  }
568  }
569 }
570 
571 void context_manager::refresh_after_action(bool drag_part)
572 {
573  if(get_map_context().needs_reload()) {
574  reload_map();
575  return;
576  }
577 
578  const std::set<map_location>& changed_locs = get_map_context().changed_locations();
579 
580  if(get_map_context().needs_terrain_rebuild()) {
581  if((auto_update_transitions_ == pref_constants::TRANSITION_UPDATE_ON)
582  || ((auto_update_transitions_ == pref_constants::TRANSITION_UPDATE_PARTIAL)
583  && (!drag_part || get_map_context().everything_changed())))
584  {
585  gui_.rebuild_all();
586  get_map_context().set_needs_terrain_rebuild(false);
587  gui_.invalidate_all();
588  } else {
589  for(const map_location& loc : changed_locs) {
590  gui_.rebuild_terrain(loc);
591  }
592  gui_.invalidate(changed_locs);
593  }
594  } else {
595  if(get_map_context().everything_changed()) {
596  gui_.invalidate_all();
597  } else {
598  gui_.invalidate(changed_locs);
599  }
600  }
601 
602  if(get_map_context().needs_labels_reset()) {
603  get_map_context().reset_starting_position_labels(gui_);
604  }
605 
606  get_map_context().clear_changed_locations();
607  gui_.recalculate_minimap();
608 }
609 
610 void context_manager::resize_map_dialog()
611 {
612  const editor_map& map = get_map_context().map();
613 
614  int w = map.w();
615  int h = map.h();
616 
618  bool copy = false;
619 
620  if(!gui2::dialogs::editor_resize_map::execute(w, h, dir, copy)) {
621  return;
622  }
623 
624  if(w != map.w() || h != map.h()) {
626  if(copy) {
628  }
629 
630  int x_offset = map.w() - w;
631  int y_offset = map.h() - h;
632 
633  switch (dir) {
637  y_offset = 0;
638  break;
642  y_offset /= 2;
643  break;
647  break;
648  default:
649  y_offset = 0;
650  WRN_ED << "Unknown resize expand direction";
651  break;
652  }
653 
654  switch (dir) {
658  x_offset = 0;
659  break;
663  x_offset /= 2;
664  break;
668  break;
669  default:
670  x_offset = 0;
671  break;
672  }
673 
674  editor_action_resize_map a(w, h, x_offset, y_offset, fill);
675  perform_refresh(a);
676  }
677 }
678 
679 void context_manager::save_map_as_dialog()
680 {
681  bool first_pick = false;
682  std::string input_name = get_map_context().get_filename();
683  if(input_name.empty()) {
684  first_pick = true;
685  if (editor_controller::current_addon_id_.empty()) {
686  input_name = filesystem::get_legacy_editor_dir() + "/maps";
687  } else {
688  input_name = filesystem::get_current_editor_dir(editor_controller::current_addon_id_) + "/maps";
689  }
690  }
691 
693 
694  dlg.set_title(_("Save Map As"))
695  .set_save_mode(true)
696  .set_path(input_name)
699 
700  if(!dlg.show()) {
701  return;
702  }
703 
704  boost::filesystem::path save_path(dlg.path());
705 
706  // Show warning the first time user tries to save in a wrong folder
707  std::string last_folder = save_path.parent_path().filename().string();
708  if ((last_folder == "scenarios")
709  && first_pick
710  && (gui2::show_message(
711  _("Error"),
712  VGETTEXT("Do you really want to save $type1 in $type2 folder?", {{"type1", "map"}, {"type2", "scenarios"}}),
714  {
715  return;
716  }
717 
718  std::size_t is_open = check_open_map(save_path.string());
719  if(is_open < map_contexts_.size() && is_open != static_cast<unsigned>(current_context_index_)) {
720  gui2::show_transient_message(_("This map is already open."), save_path.string());
721  }
722 
723  std::string old_filename = get_map_context().get_filename();
724 
725  get_map_context().set_filename(save_path.string());
726 
727  if(!write_map(true)) {
728  get_map_context().set_filename(old_filename);
729  }
730 }
731 
732 void context_manager::save_scenario_as_dialog()
733 {
734  bool first_pick = false;
735  std::string input_name = get_map_context().get_filename();
736  if(input_name.empty()) {
737  first_pick = true;
738  input_name = filesystem::get_current_editor_dir(editor_controller::current_addon_id_) + "/scenarios";
739  }
740 
742 
743  dlg.set_title(_("Save Scenario As"))
744  .set_save_mode(true)
745  .set_path(input_name)
748 
749  if(!dlg.show()) {
750  return;
751  }
752 
753  boost::filesystem::path save_path(dlg.path());
754 
755  // Show warning the first time user tries to save in a wrong folder
756  std::string last_folder = save_path.parent_path().filename().string();
757  if ((last_folder == "maps")
758  && first_pick
759  && (gui2::show_message(
760  _("Error"),
761  VGETTEXT("Do you really want to save $type1 in $type2 folder?", {{"type1", "scenario"}, {"type2", "maps"}}),
763  {
764  return;
765  }
766 
767  std::size_t is_open = check_open_map(save_path.string());
768  if(is_open < map_contexts_.size() && is_open != static_cast<unsigned>(current_context_index_)) {
769  gui2::show_transient_message(_("This scenario is already open."), save_path.string());
770  return;
771  }
772 
773  std::string old_filename = get_map_context().get_filename();
774 
775  get_map_context().set_filename(save_path.string());
776 
777  if(!write_scenario(true)) {
778  get_map_context().set_filename(old_filename);
779  return;
780  }
781 }
782 
783 void context_manager::init_map_generators(const game_config_view& game_config)
784 {
785  for(const config& i : game_config.child_range("multiplayer")) {
786  if(i["map_generation"].empty() && i["scenario_generation"].empty()) {
787  continue;
788  }
789 
790  // TODO: we should probably use `child` with a try/catch block once that function throws
791  if(const auto generator_cfg = i.optional_child("generator")) {
792  map_generators_.emplace_back(create_map_generator(i["map_generation"].empty() ? i["scenario_generation"] : i["map_generation"], generator_cfg.value()));
793  } else {
794  ERR_ED << "Scenario \"" << i["name"] << "\" with id " << i["id"]
795  << " has map_generation= but no [generator] tag";
796  }
797  }
798 }
799 
800 void context_manager::generate_map_dialog()
801 {
802  if(map_generators_.empty()) {
803  gui2::show_error_message(_("No random map generators found."));
804  return;
805  }
806 
807  gui2::dialogs::editor_generate_map dialog(map_generators_);
808  dialog.select_map_generator(last_map_generator_);
809 
810  if(dialog.show()) {
811  std::string map_string;
813  try {
814  map_string = map_generator->create_map(dialog.get_seed());
815  } catch (const mapgen_exception& e) {
816  gui2::show_transient_message(_("Map creation failed."), e.what());
817  return;
818  }
819 
820  if(map_string.empty()) {
821  gui2::show_transient_message("", _("Map creation failed."));
822  } else {
823  editor_map new_map(map_string);
824  editor_action_whole_map a(new_map);
825  get_map_context().set_needs_labels_reset(); // Ensure Player Start labels are updated together with newly generated map
826  perform_refresh(a);
827  }
828 
829  last_map_generator_ = map_generator;
830  }
831 }
832 
833 bool context_manager::confirm_discard()
834 {
835  if(get_map_context().modified()) {
836  const int res = gui2::show_message(_("Unsaved Changes"),
837  _("Do you want to discard all changes made to the map since the last save?"), gui2::dialogs::message::yes_no_buttons);
838  return gui2::retval::CANCEL != res;
839  }
840 
841  return true;
842 }
843 
844 void context_manager::fill_selection()
845 {
846  perform_refresh(editor_action_paint_area(get_map_context().map().selection(), get_selected_bg_terrain()));
847 }
848 
849 void context_manager::save_all_maps()
850 {
851  int current = current_context_index_;
852  for(std::size_t i = 0; i < map_contexts_.size(); ++i) {
853  switch_context(i);
854  save_map();
855  }
856  switch_context(current);
857 }
858 
859 void context_manager::save_contexts()
860 {
861  saved_contexts_.swap(map_contexts_);
862  std::swap(last_context_, current_context_index_);
863  create_blank_context();
864  switch_context(0, true);
865 }
866 
867 void context_manager::save_map(bool show_confirmation)
868 {
869  const std::string& name = get_map_context().get_filename();
870  if(name.empty() || filesystem::is_directory(name)) {
871  if(get_map_context().is_pure_map()) {
872  save_map_as_dialog();
873  } else {
874  save_scenario_as_dialog();
875  }
876  } else {
877  if(get_map_context().is_pure_map()) {
878  write_map(show_confirmation);
879  } else {
880  write_scenario(show_confirmation);
881  }
882  }
883 }
884 
885 bool context_manager::write_scenario(bool display_confirmation)
886 {
887  try {
888  get_map_context().save_scenario();
889  if(display_confirmation) {
890  gui_.set_status(_("Scenario saved."), true);
891  }
892  } catch (const editor_map_save_exception& e) {
893  gui_.set_status(e.what(), false);
894  return false;
895  }
896 
897  return true;
898 }
899 
900 bool context_manager::write_map(bool display_confirmation)
901 {
902  try {
903  get_map_context().save_map();
904  if(display_confirmation) {
905  gui_.set_status(_("Map saved"), true);
906  }
907  } catch (const editor_map_save_exception& e) {
908  gui_.set_status(e.what(), false);
909  return false;
910  }
911 
912  return true;
913 }
914 
915 std::size_t context_manager::check_open_map(const std::string& fn) const
916 {
917  std::size_t i = 0;
918  while(i < map_contexts_.size() && map_contexts_[i]->get_filename() != fn) {
919  ++i;
920  }
921 
922  return i;
923 }
924 
925 bool context_manager::check_switch_open_map(const std::string& fn)
926 {
927  std::size_t i = check_open_map(fn);
928  if(i < map_contexts_.size()) {
929  gui2::show_transient_message(_("This map is already open."), fn);
930  switch_context(i);
931  return true;
932  }
933 
934  return false;
935 }
936 
937 void context_manager::load_map(const std::string& filename, bool new_context)
938 {
939  if(new_context && check_switch_open_map(filename)) {
940  return;
941  }
942 
944  if(editor_controller::current_addon_id_.empty()) {
945  // if no addon id has been set and the file being loaded is from an addon
946  // then use the file path to determine the addon rather than showing a dialog
947  if(auto addon_at_path = filesystem::get_addon_id_from_path(filename)) {
948  editor_controller::current_addon_id_ = addon_at_path.value();
949  } else {
950  editor_controller::current_addon_id_ = editor::initialize_addon();
951  }
952 
953  set_addon_id(editor_controller::current_addon_id_);
954  }
955 
956  if(editor_controller::current_addon_id_.empty()) {
957  return;
958  }
959  }
960 
961  LOG_ED << "Load map: " << filename << (new_context ? " (new)" : " (same)");
962  try {
963  {
964  auto mc = std::make_unique<map_context>(game_config_, filename, current_addon_);
965  if(mc->get_filename() != filename) {
966  if(new_context && check_switch_open_map(mc->get_filename())) {
967  return;
968  }
969  }
970 
971  if(new_context) {
972  int new_id = add_map_context_of(std::move(mc));
973  switch_context(new_id);
974  } else {
975  replace_map_context_with(std::move(mc));
976  }
977  }
978 
979  if(get_map_context().is_embedded()) {
980  const std::string& msg = _("Loaded embedded map data");
981  gui2::show_transient_message(_("Map loaded from scenario"), msg);
982  } else {
983  if(get_map_context().get_filename() != filename) {
984  gui2::show_transient_message(_("Map loaded from scenario"), _("Loaded referenced map file:")+"\n"+get_map_context().get_filename());
985  }
986  }
987  } catch(const editor_map_load_exception& e) {
988  gui2::show_transient_message(_("Error loading map"), e.what());
989  return;
990  }
991 }
992 
993 void context_manager::revert_map()
994 {
995  if(!confirm_discard()) {
996  return;
997  }
998 
999  std::string filename = get_map_context().get_filename();
1000  if(filename.empty()) {
1001  ERR_ED << "Empty filename in map revert";
1002  return;
1003  }
1004 
1005  load_map(filename, false);
1006 }
1007 
1008 void context_manager::new_map(int width, int height, const t_translation::terrain_code& fill, bool new_context)
1009 {
1010  const config& default_schedule = game_config_.find_mandatory_child("editor_times", "id", "empty");
1011  editor_map m(width, height, fill);
1012 
1013  if(new_context) {
1014  int new_id = add_map_context(m, true, default_schedule, current_addon_);
1015  switch_context(new_id);
1016  } else {
1017  replace_map_context(m, true, default_schedule, current_addon_);
1018  }
1019 }
1020 
1021 void context_manager::new_scenario(int width, int height, const t_translation::terrain_code& fill, bool new_context)
1022 {
1023  auto default_schedule = game_config_.find_child("editor_times", "id", "empty");
1024  editor_map m(width, height, fill);
1025 
1026  if(new_context) {
1027  int new_id = add_map_context(m, false, *default_schedule, current_addon_);
1028  switch_context(new_id);
1029  } else {
1030  replace_map_context(m, false, *default_schedule, current_addon_);
1031  }
1032 
1033  // Give the new scenario an initial side.
1034  get_map_context().new_side();
1035  gui().set_viewing_team_index(0, true);
1036  gui().set_playing_team_index(0);
1037  gui_.init_flags();
1038 }
1039 
1040 //
1041 // Context manipulation
1042 //
1043 
1044 template<typename... T>
1045 int context_manager::add_map_context(const T&... args)
1046 {
1047  map_contexts_.emplace_back(std::make_unique<map_context>(args...));
1048  return map_contexts_.size() - 1;
1049 }
1050 
1051 int context_manager::add_map_context_of(std::unique_ptr<map_context>&& mc)
1052 {
1053  map_contexts_.emplace_back(std::move(mc));
1054  return map_contexts_.size() - 1;
1055 }
1056 
1057 template<typename... T>
1058 void context_manager::replace_map_context(const T&... args)
1059 {
1060  replace_map_context_with(std::move(std::make_unique<map_context>(args...)));
1061 }
1062 
1063 void context_manager::replace_map_context_with(std::unique_ptr<map_context>&& mc)
1064 {
1065  map_contexts_[current_context_index_].swap(mc);
1066  refresh_on_context_change();
1067 }
1068 
1069 void context_manager::create_default_context()
1070 {
1071  if(saved_contexts_.empty()) {
1072  create_blank_context();
1073  switch_context(0, true);
1074  } else {
1075  saved_contexts_.swap(map_contexts_);
1076  switch_context(last_context_, true);
1077  last_context_ = 0;
1078  }
1079 }
1080 
1081 void context_manager::create_blank_context()
1082 {
1085 
1086  const config& default_schedule = game_config_.find_mandatory_child("editor_times", "id", "empty");
1087  add_map_context(editor_map(44, 33, default_terrain), true, default_schedule, current_addon_);
1088 }
1089 
1090 void context_manager::close_current_context()
1091 {
1092  if(!confirm_discard()) return;
1093 
1094  if(map_contexts_.size() == 1) {
1095  create_default_context();
1096  map_contexts_.erase(map_contexts_.begin());
1097  } else if(current_context_index_ == static_cast<int>(map_contexts_.size()) - 1) {
1098  map_contexts_.pop_back();
1099  current_context_index_--;
1100  } else {
1101  map_contexts_.erase(map_contexts_.begin() + current_context_index_);
1102  }
1103 
1104  refresh_on_context_change();
1105 }
1106 
1107 void context_manager::switch_context(const int index, const bool force)
1108 {
1109  if(index < 0 || static_cast<std::size_t>(index) >= map_contexts_.size()) {
1110  WRN_ED << "Invalid index in switch map context: " << index;
1111  return;
1112  }
1113 
1114  if(index == current_context_index_ && !force) {
1115  return;
1116  }
1117 
1118  // Disable the labels of the current context before switching.
1119  // The refresher handles enabling the new ones.
1120  get_map_context().get_labels().enable(false);
1121 
1122  current_context_index_ = index;
1123 
1124  refresh_on_context_change();
1125 }
1126 
1128 {
1129  std::string name = get_map_context().get_name();
1130 
1131  if(name.empty()) {
1132  name = filesystem::base_name(get_map_context().get_filename());
1133  }
1134 
1135  if(name.empty()){
1136  name = get_map_context().get_default_context_name();
1137  }
1138 
1139  const std::string& wm_title_string = name + " - " + game_config::get_default_title_string();
1140  video::set_window_title(wm_title_string);
1141 }
1142 
1143 } //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:814
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:351
void recalculate_minimap()
Schedule the minimap for recalculation.
Definition: display.cpp:1604
void set_playing_team_index(std::size_t team)
sets the team whose turn it currently is
Definition: display.cpp:368
void rebuild_all()
Rebuild all dynamic terrain.
Definition: display.cpp:438
void change_display_context(const display_context *dc)
Definition: display.cpp:449
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:3137
void init_flags()
Init the flag list and the team colors used by ~TC.
Definition: display.cpp:264
void reload_map()
Updates internals that cache map size.
Definition: display.cpp:443
void create_buttons()
Definition: display.cpp:862
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: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:1023
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:199
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: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: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: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::pair< std::string, unsigned > item
Definition: help_impl.hpp:387
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:632
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:44
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