The Battle for Wesnoth  1.19.18+dev
context_manager.cpp
Go to the documentation of this file.
1 /*
2  Copyright (C) 2003 - 2025
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 
19 
20 #include "addon/validation.hpp"
21 #include "editor/action/action.hpp"
28 
29 #include "filesystem.hpp"
30 #include "formula/string_utils.hpp"
31 #include "gettext.hpp"
32 #include "resources.hpp"
33 #include "team.hpp"
34 
36 
38 #include "gui/dialogs/prompt.hpp"
43 #include "gui/dialogs/message.hpp"
45 #include "gui/widgets/retval.hpp"
46 
50 #include "game_config_view.hpp"
51 
52 #include "serialization/markup.hpp"
53 #include "terrain/translation.hpp"
54 
55 #include "units/unit.hpp"
56 #include "video.hpp"
57 
58 #include <memory>
59 #include <boost/algorithm/string.hpp>
60 #include <boost/filesystem.hpp>
61 
62 namespace {
63 
64 std::vector<std::unique_ptr<editor::map_context>> saved_contexts_;
65 int last_context_ = 0;
66 
67 std::string get_menu_marker(const bool changed)
68 {
69  if (changed) {
70  return "[" + markup::span_color("#f00", font::unicode_bullet) + "]";
71  } else {
72  return font::unicode_bullet;
73  }
74 }
75 
76 } // namespace
77 
78 namespace editor {
79 
80 context_manager::context_manager(editor_display& gui, const std::string& addon_id)
81  : locs_(nullptr)
82  , gui_(gui)
84  , current_addon_(addon_id)
85  , current_context_index_(0)
86  , auto_update_transitions_(prefs::get().editor_auto_update_transitions())
87  , map_contexts_()
88  , clipboard_()
89 {
90  resources::filter_con = this;
92 }
93 
95 {
96  // Restore default window title
98 
99  resources::filter_con = nullptr;
100 }
101 
103 {
105 
106  // TODO register the tod_manager with the gui?
109 
110  // Reset side when switching to an existing scenario
111  if(!get_map_context().teams().empty()) {
112  gui().set_viewing_team_index(0, true);
114  }
115  gui().init_flags();
116 
117  reload_map();
118 
119  // Enable the labels of the current context;
121 
123 }
124 
126 {
127  gui_.rebuild_all();
133  if(locs_) {
134  for(const auto& loc : get_map_context().map().special_locations().left) {
135  locs_->add_item(loc.first);
136  }
137  if(!get_map_context().is_pure_map()) {
138  // If the scenario has more than 9 teams, add locations for them
139  // (First 9 teams are always in the list)
140  std::size_t n_teams = get_map_context().teams().size();
141  for(std::size_t i = 10; i <= n_teams; i++) {
142  locs_->add_item(std::to_string(i));
143  }
144  }
145  }
146 }
147 
149 {
150  gui_.reload_map();
153  refresh_all();
154 }
155 
157 {
158  switch (auto_update_transitions_) {
160  return (item == "editor-auto-update-transitions");
162  return (item == "editor-partial-update-transitions");
164  return (item == "editor-no-update-transitions");
165  }
166 
167  return true; //should not be reached
168 }
169 
171 {
173  prefs::get().set_editor_auto_update_transitions(auto_update_transitions_);
174 
176  return true;
177  }
178 
179  return false;
180 }
181 
182 std::size_t context_manager::modified_maps(std::string& message)
183 {
184  std::vector<std::string> modified;
185  for(auto& mc : map_contexts_) {
186  if(mc->modified()) {
187  if(!mc->get_name().empty()) {
188  modified.push_back(mc->get_name());
189  } else if(!mc->get_filename().empty()) {
190  modified.push_back(mc->get_filename());
191  } else {
192  modified.push_back(mc->get_default_context_name());
193  }
194  }
195  }
196 
197  for(std::string& str : modified) {
198  message += "\n" + font::unicode_bullet + " " + str;
199  }
200 
201  return modified.size();
202 }
203 
204 void context_manager::load_map_dialog(bool force_same_context /* = false */)
205 {
206  std::string fn = get_map_context().get_filename();
207  if(fn.empty()) {
209  fn = filesystem::get_legacy_editor_dir() + "/maps";
210  } else {
212  }
213  }
214 
216 
217  dlg.set_title(_("Load Map or Scenario"))
218  .set_path(fn);
219 
220  if(dlg.show()) {
221  load_map(dlg.path(), !force_same_context);
222  }
223 }
224 
225 void context_manager::load_mru_item(unsigned index, bool force_same_context /* = false */)
226 {
227  const std::vector<std::string>& mru = prefs::get().recent_files();
228  if(mru.empty() || index >= mru.size()) {
229  return;
230  }
231 
232  load_map(mru[index], !force_same_context);
233 }
234 
236 {
237  editor_team_info team_info(t);
238 
239  if(gui2::dialogs::editor_edit_side::execute(team_info)) {
240  get_map_context().set_side_setup(team_info);
241  }
242 }
243 
245 {
246  if(!current_addon_.empty()) {
247  std::string pbl = filesystem::get_current_editor_dir(current_addon_) + "/_server.pbl";
248  gui2::dialogs::editor_edit_pbl::execute(pbl, current_addon_);
249  }
250 }
251 
253 {
254  std::string new_addon_id = current_addon_;
255  gui2::dialogs::prompt::execute(new_addon_id);
256 
257  std::string old_dir = filesystem::get_current_editor_dir(current_addon_);
258  std::string new_dir = filesystem::get_current_editor_dir(new_addon_id);
259  if(addon_filename_legal(new_addon_id) && filesystem::rename_dir(old_dir, new_dir)) {
260  std::string main_cfg = new_dir + "/_main.cfg";
261  std::string main = filesystem::read_file(main_cfg);
262 
263  // update paths
264  boost::replace_all(main, "/"+current_addon_, "/"+new_addon_id);
265  // update textdomain
266  boost::replace_all(main, "wesnoth-"+current_addon_, "wesnoth-"+new_addon_id);
267  filesystem::write_file(main_cfg, main);
268 
269  current_addon_ = new_addon_id;
270 
271  for(std::unique_ptr<map_context>& context : map_contexts_) {
272  context->set_addon_id(current_addon_);
273  }
274  }
275 }
276 
278 {
279  map_context& context = get_map_context();
280 
281  std::string id = context.get_id();
282  std::string name = context.get_name();
283  std::string description = context.get_description();
284 
285  int turns = context.get_time_manager()->number_of_turns();
286  int xp_mod = context.get_xp_mod() ? *context.get_xp_mod() : 70;
287 
288  bool victory = context.victory_defeated();
289  bool random = context.random_start_time();
290 
291  const bool ok = gui2::dialogs::editor_edit_scenario::execute(
292  id, name, description, turns, xp_mod, victory, random
293  );
294 
295  if(!ok) {
296  return;
297  }
298 
299  context.set_scenario_setup(id, name, description, turns, xp_mod, victory, random);
300 
301  if(!name.empty()) {
303  }
304 }
305 
307 {
308  const editor_map& map = get_map_context().map();
309 
310  int w = map.w();
311  int h = map.h();
312 
313  if(gui2::dialogs::editor_new_map::execute(_("New Map"), w, h)) {
315  new_map(w, h, fill, true);
316  }
317 }
318 
320 {
321  const editor_map& map = get_map_context().map();
322 
323  int w = map.w();
324  int h = map.h();
325 
326  if(gui2::dialogs::editor_new_map::execute(_("New Scenario"), w, h)) {
328  new_scenario(w, h, fill, true);
329  }
330 }
331 
332 void context_manager::expand_open_maps_menu(std::vector<config>& items) const
333 {
334  for(std::size_t mci = 0; mci < map_contexts_.size(); ++mci) {
335  map_context& mc = *map_contexts_[mci];
336 
337  std::string filename;
338  if(mc.is_pure_map()) {
340  } else {
341  filename = mc.get_name();
342  }
343 
344  if(filename.empty()) {
346  }
347 
348  std::ostringstream ss;
349  ss << "[" << mci + 1 << "] ";
350 
351  const bool changed = mc.modified();
352 
353  if(changed) {
354  ss << markup::italic(filename);
355  } else {
356  ss << filename;
357  }
358 
359  if(mc.is_embedded()) {
360  ss << " (E)";
361  }
362 
363  items.emplace_back("label", ss.str(), "details", get_menu_marker(changed));
364  }
365 }
366 
367 void context_manager::expand_load_mru_menu(std::vector<config>& items) const
368 {
369  std::vector<std::string> mru = prefs::get().recent_files();
370  if(mru.empty()) {
371  items.emplace_back("label", _("No Recent Files"));
372  return;
373  }
374 
375  for(const std::string& path : mru) {
376  // TODO: add proper leading ellipsization instead, since otherwise
377  // it'll be impossible to tell apart files with identical names and
378  // different parent paths.
379  items.emplace_back("label", filesystem::base_name(path));
380  }
381 }
382 
383 void context_manager::expand_areas_menu(std::vector<config>& items) const
384 {
386  if(!tod) {
387  return;
388  }
389 
390  std::vector<std::string> area_ids = tod->get_area_ids();
391 
392  for(std::size_t mci = 0; mci < area_ids.size(); ++mci) {
393  const std::string& area = area_ids[mci];
394 
395  std::stringstream ss;
396  ss << "[" << mci + 1 << "] ";
397 
398  if(area.empty()) {
399  ss << markup::italic(_("Unnamed Area"));
400  } else {
401  ss << area;
402  }
403 
404  const bool changed =
405  mci == static_cast<std::size_t>(get_map_context().get_active_area())
406  && tod->get_area_by_index(mci) != get_map_context().map().selection();
407 
408  items.emplace_back("label", ss.str(), "details", get_menu_marker(changed));
409  }
410 }
411 
412 void context_manager::expand_sides_menu(std::vector<config>& items) const
413 {
414  for(std::size_t mci = 0; mci < get_map_context().teams().size(); ++mci) {
415 
416  const team& t = get_map_context().teams()[mci];
417  const std::string& teamname = t.user_team_name();
418  std::stringstream label;
419  label << "[" << mci+1 << "] ";
420 
421  if(teamname.empty()) {
422  label << markup::italic(_("New Side"));
423  } else {
424  label << teamname;
425  }
426 
427  items.emplace_back("label", label.str());
428  }
429 }
430 
431 void context_manager::expand_time_menu(std::vector<config>& items) const
432 {
433  const tod_manager* tod_m = get_map_context().get_time_manager();
434  assert(tod_m != nullptr);
435 
436  for(const time_of_day& time : tod_m->times()) {
437  // Use 'details' field here since the image will take the first column
438  items.emplace_back("details", time.name, "image", time.image);
439  }
440 }
441 
442 void context_manager::expand_local_time_menu(std::vector<config>& items) const
443 {
444  const tod_manager* tod_m = get_map_context().get_time_manager();
445 
446  for(const time_of_day& time : tod_m->times(get_map_context().get_active_area())) {
447  // Use 'details' field here since the image will take the first column
448  items.emplace_back("details", time.name, "image", time.image);
449  }
450 }
451 
453 {
454  std::string fn = get_map_context().get_filename();
455  if(fn.empty()) {
457  }
458 
460 
461  dlg.set_title(_("Apply Mask"))
462  .set_path(fn);
463 
464  if(dlg.show()) {
465  try {
466  map_context mask(dlg.path(), current_addon_);
467  editor_action_apply_mask a(mask.map());
468  perform_refresh(a);
469  } catch (const editor_map_load_exception& e) {
470  gui2::show_transient_message(_("Error loading mask"), e.what());
471  return;
472  } catch (const editor_action_exception& e) {
473  gui2::show_error_message(e.what());
474  return;
475  }
476  }
477 }
478 
479 void context_manager::perform_refresh(const editor_action& action, bool drag_part /* =false */)
480 {
482  refresh_after_action(drag_part);
483 }
484 
486 {
487  int active_area = get_map_context().get_active_area();
488  std::string name = get_map_context().get_time_manager()->get_area_ids()[active_area];
489 
490  if(gui2::dialogs::edit_text::execute(N_("Rename Area"), N_("Identifier:"), name)) {
491  get_map_context().get_time_manager()->set_area_id(active_area, name);
492  }
493 }
494 
496 {
497  std::string fn = get_map_context().get_filename();
498  if(fn.empty()) {
500  }
501 
503 
504  dlg.set_title(_("Choose Target Map"))
505  .set_path(fn);
506 
507  if(dlg.show()) {
508  try {
509  map_context map(dlg.path(), current_addon_);
511  perform_refresh(a);
512  } catch (const editor_map_load_exception& e) {
513  gui2::show_transient_message(_("Error loading map"), e.what());
514  return;
515  } catch (const editor_action_exception& e) {
516  gui2::show_error_message(e.what());
517  return;
518  }
519  }
520 }
521 
523 {
524  if(get_map_context().needs_reload()) {
525  reload_map();
526  return;
527  }
528 
529  const std::set<map_location>& changed_locs = get_map_context().changed_locations();
530 
531  if(get_map_context().needs_terrain_rebuild()) {
534  && (!drag_part || get_map_context().everything_changed())))
535  {
536  gui_.rebuild_all();
539  } else {
540  for(const map_location& loc : changed_locs) {
542  }
543  gui_.invalidate(changed_locs);
544  }
545  } else {
546  if(get_map_context().everything_changed()) {
548  } else {
549  gui_.invalidate(changed_locs);
550  }
551  }
552 
553  if(get_map_context().needs_labels_reset()) {
555  }
556 
559 }
560 
562 {
563  const editor_map& map = get_map_context().map();
564 
565  int w = map.w();
566  int h = map.h();
567 
569  bool copy = false;
570 
571  if(!gui2::dialogs::editor_resize_map::execute(w, h, dir, copy)) {
572  return;
573  }
574 
575  if(w != map.w() || h != map.h()) {
577  if(copy) {
579  }
580 
581  int x_offset = map.w() - w;
582  int y_offset = map.h() - h;
583 
584  switch (dir) {
588  y_offset = 0;
589  break;
593  y_offset /= 2;
594  break;
598  break;
599  default:
600  y_offset = 0;
601  WRN_ED << "Unknown resize expand direction";
602  break;
603  }
604 
605  switch (dir) {
609  x_offset = 0;
610  break;
614  x_offset /= 2;
615  break;
619  break;
620  default:
621  x_offset = 0;
622  break;
623  }
624 
625  auto chain = std::make_unique<editor_action_chain>();
626  auto units = get_map_context().units();
627  for (const auto& u : units) {
628  map_location l{u.get_location().x - x_offset, u.get_location().y - y_offset};
629  chain->append_action(std::make_unique<editor_action_unit_replace>(u.get_location(), l));
630  }
631 
632  chain->append_action(std::make_unique<editor_action_item_move_all>(x_offset, y_offset));
633  chain->append_action(std::make_unique<editor_action_time_area_move_all>(x_offset, y_offset));
634 
635  // TODO Reduces the number of editor actions in the following loop.
636  // It can be a lot if the map has a lot of labels (say hundreds).
637  map_labels& labels = get_map_context().get_labels();
639  map_location new_loc{loc.x - x_offset, loc.y - y_offset};
640  const terrain_label* old_label = labels.get_label(loc);
641 
642  if (old_label == nullptr) {
643  return;
644  }
645 
646  // Create action chain to:
647  // 1. Delete label in initial hex
648  // 2. Delete label in target hex if it exists, otherwise will no-op
649  // 3. Create label in target hex
650  chain->append_action(std::make_unique<editor_action_label_delete>(loc));
651  chain->append_action(std::make_unique<editor_action_label_delete>(new_loc));
652  chain->append_action(
653  std::make_unique<editor_action_label>(
654  new_loc,
655  old_label->text(),
656  old_label->team_name(),
657  old_label->color(),
658  old_label->visible_in_fog(),
659  old_label->visible_in_shroud(),
660  old_label->immutable(),
661  old_label->category()
662  )
663  );
664  });
665 
666  chain->append_action(
667  std::make_unique<editor_action_resize_map>(w, h, x_offset, y_offset, fill));
668  perform_refresh(*chain);
669  }
670 }
671 
673 {
674  bool first_pick = false;
675  std::string input_name = get_map_context().get_filename();
676  if(input_name.empty()) {
677  first_pick = true;
679  input_name = filesystem::get_legacy_editor_dir() + "/maps";
680  } else {
682  }
683  }
684 
686 
687  dlg.set_title(_("Save Map As"))
688  .set_save_mode(true)
689  .set_path(input_name)
692 
693  if(!dlg.show()) {
694  return;
695  }
696 
697  boost::filesystem::path save_path(dlg.path());
698 
699  // Show warning the first time user tries to save in a wrong folder
700  std::string last_folder = save_path.parent_path().filename().string();
701  if ((last_folder == "scenarios")
702  && first_pick
703  && (gui2::show_message(
704  _("Error"),
705  _("Do you really want to save a map in the scenarios folder?"),
707  {
708  return;
709  }
710 
711  std::size_t is_open = check_open_map(save_path.string());
712  if(is_open < map_contexts_.size() && is_open != static_cast<unsigned>(current_context_index_)) {
713  gui2::show_transient_message(_("This map is already open."), save_path.string());
714  }
715 
716  std::string old_filename = get_map_context().get_filename();
717 
718  get_map_context().set_filename(save_path.string());
719 
720  if(!write_map(true)) {
721  get_map_context().set_filename(old_filename);
722  }
723 }
724 
726 {
727  bool first_pick = false;
728  std::string input_name = get_map_context().get_filename();
729  if(input_name.empty()) {
730  first_pick = true;
732  }
733 
735 
736  dlg.set_title(_("Save Scenario As"))
737  .set_save_mode(true)
738  .set_path(input_name)
741 
742  if(!dlg.show()) {
743  return;
744  }
745 
746  boost::filesystem::path save_path(dlg.path());
747 
748  // Show warning the first time user tries to save in a wrong folder
749  std::string last_folder = save_path.parent_path().filename().string();
750  if ((last_folder == "maps")
751  && first_pick
752  && (gui2::show_message(
753  _("Error"),
754  _("Do you really want to save a scenario in the maps folder?"),
756  {
757  return;
758  }
759 
760  std::size_t is_open = check_open_map(save_path.string());
761  if(is_open < map_contexts_.size() && is_open != static_cast<unsigned>(current_context_index_)) {
762  gui2::show_transient_message(_("This scenario is already open."), save_path.string());
763  return;
764  }
765 
766  std::string old_filename = get_map_context().get_filename();
767 
768  get_map_context().set_filename(save_path.string());
769 
770  if(!write_scenario(true)) {
771  get_map_context().set_filename(old_filename);
772  return;
773  }
774 }
775 
776 void context_manager::generate_map_dialog(const std::vector<std::unique_ptr<map_generator>>& map_generators)
777 {
778  if(map_generators.empty()) {
779  gui2::show_error_message(_("No random map generators found."));
780  return;
781  }
782 
783  gui2::dialogs::editor_generate_map dialog(map_generators);
784  dialog.select_map_generator(get_map_context().last_used_generator());
785 
786  if(dialog.show()) {
787  std::string map_string;
789  try {
790  map_string = map_generator->create_map(dialog.get_seed());
791  } catch (const mapgen_exception& e) {
792  gui2::show_transient_message(_("Map creation failed."), e.what());
793  return;
794  }
795 
796  if(map_string.empty()) {
797  gui2::show_transient_message("", _("Map creation failed."));
798  } else {
799  editor_map new_map(map_string);
801  get_map_context().set_needs_labels_reset(); // Ensure Player Start labels are updated together with newly generated map
802  perform_refresh(a);
803  }
804 
806  }
807 }
808 
810 {
811  if(get_map_context().modified()) {
812  const int res = gui2::show_message(_("Unsaved Changes"),
813  _("Do you want to discard all changes made to the map since the last save?"), gui2::dialogs::message::yes_no_buttons);
814  return gui2::retval::CANCEL != res;
815  }
816 
817  return true;
818 }
819 
821 {
823 }
824 
826 {
827  int current = current_context_index_;
828  for(std::size_t i = 0; i < map_contexts_.size(); ++i) {
829  switch_context(i);
830  save_map();
831  }
832  switch_context(current);
833 }
834 
836 {
837  saved_contexts_.swap(map_contexts_);
838  std::swap(last_context_, current_context_index_);
840  switch_context(0, true);
841 }
842 
843 void context_manager::save_map(bool show_confirmation)
844 {
845  const std::string& name = get_map_context().get_filename();
846  if(name.empty() || filesystem::is_directory(name)) {
847  if(get_map_context().is_pure_map()) {
849  } else {
851  }
852  } else {
853  if(get_map_context().is_pure_map()) {
854  write_map(show_confirmation);
855  } else {
856  write_scenario(show_confirmation);
857  }
858  }
859 }
860 
861 bool context_manager::write_scenario(bool display_confirmation)
862 {
863  try {
865  if(display_confirmation) {
866  gui_.set_status(_("Scenario saved."), true);
867  }
868  } catch (const editor_map_save_exception& e) {
869  gui_.set_status(e.what(), false);
870  return false;
871  }
872 
873  return true;
874 }
875 
876 bool context_manager::write_map(bool display_confirmation)
877 {
878  try {
880  if(display_confirmation) {
881  gui_.set_status(_("Map saved"), true);
882  }
883  } catch (const editor_map_save_exception& e) {
884  gui_.set_status(e.what(), false);
885  return false;
886  }
887 
888  return true;
889 }
890 
891 std::size_t context_manager::check_open_map(const std::string& fn) const
892 {
893  std::size_t i = 0;
894  while(i < map_contexts_.size() && map_contexts_[i]->get_filename() != fn) {
895  ++i;
896  }
897 
898  return i;
899 }
900 
901 bool context_manager::check_switch_open_map(const std::string& fn)
902 {
903  std::size_t i = check_open_map(fn);
904  if(i < map_contexts_.size()) {
905  gui2::show_transient_message(_("This map is already open."), fn);
906  switch_context(i);
907  return true;
908  }
909 
910  return false;
911 }
912 
913 void context_manager::load_map(const std::string& filename, bool new_context)
914 {
915  if(new_context && check_switch_open_map(filename)) {
916  return;
917  }
918 
921  // if no addon id has been set and the file being loaded is from an addon
922  // then use the file path to determine the addon rather than showing a dialog
923  if(auto addon_at_path = filesystem::get_addon_id_from_path(filename)) {
924  editor_controller::current_addon_id_ = addon_at_path.value();
925  } else {
927  }
928 
930  }
931 
933  return;
934  }
935  }
936 
937  LOG_ED << "Load map: " << filename << (new_context ? " (new)" : " (same)");
938  try {
939  {
940  auto mc = std::make_unique<map_context>(filename, current_addon_);
941  if(mc->get_filename() != filename) {
942  if(new_context && check_switch_open_map(mc->get_filename())) {
943  return;
944  }
945  }
946 
947  if(new_context) {
948  int new_id = add_map_context_of(std::move(mc));
949  switch_context(new_id);
950  } else {
951  replace_map_context_with(std::move(mc));
952  }
953  }
954 
955  if(get_map_context().is_embedded()) {
956  const std::string& msg = _("Loaded embedded map data");
957  gui2::show_transient_message(_("Map loaded from scenario"), msg);
958  } else {
960  gui2::show_transient_message(_("Map loaded from scenario"), _("Loaded referenced map file:")+"\n"+get_map_context().get_filename());
961  }
962  }
963  } catch(const editor_map_load_exception& e) {
964  gui2::show_transient_message(_("Error loading map"), e.what());
965  return;
966  }
967 }
968 
970 {
971  if(!confirm_discard()) {
972  return;
973  }
974 
975  std::string filename = get_map_context().get_filename();
976  if(filename.empty()) {
977  ERR_ED << "Empty filename in map revert";
978  return;
979  }
980 
981  load_map(filename, false);
982 }
983 
984 void context_manager::init_context(int width, int height, const t_translation::terrain_code& fill, bool new_context, bool is_pure_map) {
985  editor_map m(width, height, fill);
986 
987  if(new_context) {
988  int new_id = add_map_context(m, is_pure_map, current_addon_);
989  switch_context(new_id);
990  } else {
991  replace_map_context(m, is_pure_map, current_addon_);
992  }
993 }
994 
995 void context_manager::new_map(int width, int height, const t_translation::terrain_code& fill, bool new_context)
996 {
997  init_context(width, height, fill, new_context, true);
998 }
999 
1000 void context_manager::new_scenario(int width, int height, const t_translation::terrain_code& fill, bool new_context)
1001 {
1002  init_context(width, height, fill, new_context, false);
1003 
1004  // Give the new scenario an initial side.
1006  gui().set_viewing_team_index(0, true);
1008  gui_.init_flags();
1009 }
1010 
1012 {
1014 
1015  // Give the converted scenario a number of sides
1016  // equal to the number of valid starting positions.
1017  int start_pos_count = get_map_context().map().num_valid_starting_positions();
1018  if(start_pos_count == 0) {
1019  start_pos_count = 1;
1020  }
1021  for(int i = 0; i < start_pos_count; i++) {
1023  }
1024  gui().set_viewing_team_index(0, true);
1026  gui_.init_flags();
1027 }
1028 
1029 //
1030 // Context manipulation
1031 //
1032 
1033 template<typename... T>
1035 {
1036  map_contexts_.emplace_back(new map_context(std::forward<T>(args)...));
1037  return map_contexts_.size() - 1;
1038 }
1039 
1040 int context_manager::add_map_context_of(std::unique_ptr<map_context>&& mc)
1041 {
1042  map_contexts_.emplace_back(std::move(mc));
1043  return map_contexts_.size() - 1;
1044 }
1045 
1046 template<typename... T>
1048 {
1049  map_contexts_[current_context_index_].reset(new map_context(std::forward<T>(args)...));
1051 }
1052 
1053 void context_manager::replace_map_context_with(std::unique_ptr<map_context>&& mc)
1054 {
1055  map_contexts_[current_context_index_] = std::move(mc);
1057 }
1058 
1060 {
1061  if(saved_contexts_.empty()) {
1063  switch_context(0, true);
1064  } else {
1065  saved_contexts_.swap(map_contexts_);
1066  switch_context(last_context_, true);
1067  last_context_ = 0;
1068  }
1069 }
1070 
1072 {
1075 
1077 }
1078 
1080 {
1081  if(!confirm_discard()) return;
1082 
1083  if(map_contexts_.size() == 1) {
1085  map_contexts_.erase(map_contexts_.begin());
1086  } else if(current_context_index_ == static_cast<int>(map_contexts_.size()) - 1) {
1087  map_contexts_.pop_back();
1089  } else {
1091  }
1092 
1094 }
1095 
1096 void context_manager::switch_context(const int index, const bool force)
1097 {
1098  if(index < 0 || static_cast<std::size_t>(index) >= map_contexts_.size()) {
1099  WRN_ED << "Invalid index in switch map context: " << index;
1100  return;
1101  }
1102 
1103  if(index == current_context_index_ && !force) {
1104  return;
1105  }
1106 
1107  // Disable the labels of the current context before switching.
1108  // The refresher handles enabling the new ones.
1109  get_map_context().get_labels().enable(false);
1110 
1112 
1114 }
1115 
1117 {
1118  std::string name = get_map_context().get_name();
1119 
1120  if(name.empty()) {
1122  }
1123 
1124  if(name.empty()){
1126  }
1127 
1128  const std::string& wm_title_string = name + " - " + game_config::get_default_title_string();
1129  video::set_window_title(wm_title_string);
1130 }
1131 
1132 } //Namespace editor
Editor action classes.
Editor action classes.
Editor action classes.
map_location loc
Definition: move.cpp:172
double t
Definition: astarsearch.cpp:63
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:336
void recalculate_minimap()
Schedule the minimap for recalculation.
Definition: display.cpp:1456
bool invalidate(const map_location &loc)
Function to invalidate a specific tile for redrawing.
Definition: display.cpp:2961
void set_playing_team_index(std::size_t team)
sets the team whose turn it currently is
Definition: display.cpp:353
void rebuild_all()
Rebuild all dynamic terrain.
Definition: display.cpp:422
void change_display_context(const display_context *dc)
Definition: display.cpp:433
void invalidate_all()
Function to invalidate all tiles.
Definition: display.cpp:2954
void init_flags()
Init the flag list and the team colors used by ~TC.
Definition: display.cpp:252
void reload_map()
Updates internals that cache map size.
Definition: display.cpp:427
void create_buttons()
Definition: display.cpp:823
std::vector< std::unique_ptr< map_context > > map_contexts_
The currently opened map context object.
bool write_scenario(bool display_confirmation=true)
void replace_map_context(T &&... args)
Replace the current map context and refresh accordingly.
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.
int add_map_context(T &&... args)
Add a map context.
void expand_local_time_menu(std::vector< config > &items) const
Menu expanding for the map's defined areas.
void new_map(int width, int height, const t_translation::terrain_code &fill, bool new_context)
Create a new map.
void fill_selection()
Fill the selection with the foreground terrain.
void new_map_dialog()
Display a new map dialog and process user input.
bool confirm_discard()
Shows an are-you-sure dialog if the map was modified.
int auto_update_transitions_
Flag to rebuild terrain on every terrain change.
void rename_area_dialog()
Display an dialog to querry a new id for an [time_area].
void revert_map()
Revert the map by reloading it from disk.
void load_map_dialog(bool force_same_context=false)
Display a load map dialog and process user input.
bool check_switch_open_map(const std::string &fn)
Check if a map is already open.
void set_window_title()
Displays the specified map name in the window titlebar.
void switch_context(const int index, const bool force=false)
Switches the context to the one under the specified index.
void expand_sides_menu(std::vector< config > &items) const
Menu expanding for the map's player sides.
std::size_t check_open_map(const std::string &fn) const
Check if a map is already open.
void save_scenario_as_dialog()
Display a save map as dialog and process user input.
context_manager(editor_display &gui, const std::string &addon_id)
void refresh_all()
Refresh everything, i.e.
void generate_map_dialog(const std::vector< std::unique_ptr< map_generator >> &map_generators)
Display a generate random map dialog and process user input.
void save_all_maps()
Save all maps, show save dialogs for unsaved ones.
int add_map_context_of(std::unique_ptr< map_context > &&mc)
void expand_time_menu(std::vector< config > &items) const
Menu expanding for the map's defined areas.
void map_to_scenario()
Convert existing map to scenario.
void save_map_as_dialog()
Display a save map as dialog and process user input.
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 apply_mask_dialog()
Display an apply mask 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.
void save_contexts()
Save all open map_contexts to memory.
void set_addon_id(const std::string &id)
void create_mask_to_dialog()
Display an apply mask dialog and process user input.
void close_current_context()
Closes the active map context.
editor_display & gui()
void resize_map_dialog()
Display a load map dialog and process user input.
void edit_scenario_dialog()
Display a scenario edit dialog and process user input.
std::string current_addon_
The currently selected add-on.
void replace_map_context_with(std::unique_ptr< map_context > &&mc)
void perform_refresh(const editor_action &action, bool drag_part=false)
Perform an action on the current map_context, then refresh the display.
void init_context(int width, int height, const t_translation::terrain_code &fill, bool new_context, bool is_pure_map=true)
void expand_load_mru_menu(std::vector< config > &items) const
Menu expanding for most recent loaded list.
void save_map(bool show_confirmation=true)
Save the map, open dialog if not named yet.
void edit_side_dialog(const team &t)
Display a side edit dialog and process user input.
bool write_map(bool display_confirmation=true)
Save the map under a given filename.
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.
void expand_areas_menu(std::vector< config > &items) const
Menu expanding for the map's defined areas.
void expand_open_maps_menu(std::vector< config > &items) const
Menu expanding for open maps list.
class location_palette * locs_
void refresh_after_action(bool drag_part=false)
Refresh the display after an action has been performed.
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_
void rebuild_terrain(const map_location &loc)
void set_status(const std::string &str, const bool is_success)
Set a status text at the bottom left of the map area.
This class adds extra editor-specific functionality to a normal gamemap.
Definition: editor_map.hpp:70
std::set< map_location > selection() const
Return the selection set.
Definition: editor_map.cpp:140
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:64
bool modified() const
void set_last_used_generator(map_generator *generator)
virtual const unit_map & units() const override
Const units accessor.
int get_active_area() const
void set_needs_labels_reset(bool value=true)
Setter for the labels reset flag.
void set_needs_reload(bool value=true)
Setter for the reload flag.
void new_side()
Adds a new side to the map.
const std::string & get_id() const
bool random_start_time() const
void set_side_setup(editor_team_info &info)
void save_scenario()
Saves the scenario under the current filename.
void perform_action(const editor_action &action)
Performs an action (thus modifying the map).
const std::string & get_description() const
bool is_embedded() const
void clear_changed_locations()
const tod_manager * get_time_manager() const
void save_map()
Saves the map under the current filename.
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_filename(const std::string &fn)
const std::set< map_location > changed_locations() 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
int w() const
Effective map width.
Definition: map.hpp:50
int h() const
Effective map height.
Definition: map.hpp:53
void for_each_loc(const F &f) const
Definition: map.hpp:140
int num_valid_starting_positions() const
Counts the number of sides that have valid starting positions on this map.
Definition: map.cpp:303
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.
const terrain_label * get_label(const map_location &loc, const std::string &team_name) const
Definition: label.hpp:48
void enable(bool is_enabled)
Definition: label.cpp:255
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
To store label data Class implements logic for rendering.
Definition: label.hpp:111
bool visible_in_shroud() const
Definition: label.hpp:169
bool immutable() const
Definition: label.hpp:174
bool visible_in_fog() const
Definition: label.hpp:164
const color_t & color() const
Definition: label.hpp:184
const std::string & category() const
Definition: label.hpp:159
const t_string & text() const
Definition: label.hpp:139
const std::string & team_name() const
Definition: label.hpp:154
int number_of_turns() const
std::vector< std::string > get_area_ids() const
const std::set< map_location > & get_area_by_index(int index) const
void set_area_id(int area_index, const std::string &id)
const std::vector< time_of_day > & times() const
void swap(config &lhs, config &rhs) noexcept
Implement non-member swap function for std::swap (calls config::swap).
Definition: config.cpp:1287
Editor action classes.
#define LOG_ED
#define ERR_ED
#define WRN_ED
Declarations for File-IO.
std::size_t i
Definition: function.cpp:1032
#define N_(String)
Definition: gettext.hpp:105
static std::string _(const char *str)
Definition: gettext.hpp:97
std::string label
What to show in the filter's drop-down list.
Definition: manager.cpp:201
CURSOR_TYPE get()
Definition: cursor.cpp:218
@ GAME_EDITOR_MAP_DIR
Editor map dir.
Definition: paths.hpp:61
void fill(const ::rect &rect, uint8_t r, uint8_t g, uint8_t b, uint8_t a)
Fill an area with the given colour.
Definition: draw.cpp:52
Manage the empty-palette in the editor.
Definition: action.cpp:31
const t_translation::terrain_code & get_selected_bg_terrain()
std::string initialize_addon()
Definition: editor_main.cpp:32
std::string get_legacy_editor_dir()
const std::string wml_extension
Definition: filesystem.cpp:281
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:355
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:818
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.cpp:279
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.cpp:280
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
std::string path
Definition: filesystem.cpp:106
std::string get_default_title_string()
std::string default_terrain
Definition: game_config.cpp:57
void show_transient_message(const std::string &title, const std::string &message, const std::string &image, const bool message_use_markup, const bool title_use_markup)
Shows a transient message to the user.
void show_error_message(const std::string &msg, bool message_use_markup)
Shows an error message to the user.
Definition: message.cpp:201
void show_message(const std::string &title, const std::string &msg, const std::string &button_caption, const bool auto_close, const bool message_use_markup, const bool title_use_markup)
Shows a message to the user.
Definition: message.cpp:148
@ OK
Dialog was closed with the OK button.
Definition: retval.hpp:35
@ CANCEL
Dialog was closed with the CANCEL button.
Definition: retval.hpp:38
General purpose widgets.
std::string italic(Args &&... data)
Applies italic Pango markup to the input.
Definition: markup.hpp:176
std::string span_color(const color_t &color, Args &&... data)
Applies Pango markup to the input specifying its display color.
Definition: markup.hpp:110
const int TRANSITION_UPDATE_ON
Definition: preferences.hpp:53
const int TRANSITION_UPDATE_COUNT
Definition: preferences.hpp:55
const int TRANSITION_UPDATE_OFF
Definition: preferences.hpp:52
const int TRANSITION_UPDATE_PARTIAL
Definition: preferences.hpp:54
::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.
constexpr terrain_code NONE_TERRAIN
Definition: translation.hpp:58
std::size_t index(std::string_view 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:671
static void msg(const char *act, debug_info &i, const char *to="", const char *result="")
Definition: debugger.cpp:109
int w
Definition: pathfind.cpp:188
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:46
A terrain string which is converted to a terrain is a string with 1 or 2 layers the layers are separa...
Definition: translation.hpp:49
Object which defines a time of day with associated bonuses, image, sounds etc.
Definition: time_of_day.hpp:57
bool addon_filename_legal(const std::string &name)
Checks whether an add-on file name is legal or not.
Definition: validation.cpp:66
#define e
#define h