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