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