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