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