The Battle for Wesnoth 1.21.0-dev
 
Loading...
Searching...
No Matches
editor_controller.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2008 - 2025
3 by Tomasz Sniatowski <kailoran@gmail.com>
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 "desktop/open.hpp"
21
29
30#include "help/help.hpp"
31
41
43#include "resources.hpp"
44#include "reports.hpp"
45#include "wml_exception.hpp"
46
47#include "cursor.hpp"
48#include "desktop/clipboard.hpp"
49#include "floating_label.hpp"
51#include "gettext.hpp"
52#include "picture.hpp"
53#include "quit_confirmation.hpp"
54#include "sdl/input.hpp" // get_mouse_button_mask
56#include "sound.hpp"
58#include "units/unit.hpp"
59#include "utils/scope_exit.hpp"
60
61#include <functional>
62
63namespace editor
64{
65namespace
66{
67auto parse_editor_music(const config_array_view& editor_music_range)
68{
69 std::vector<std::shared_ptr<sound::music_track>> tracks;
70
71 for(const config& editor_music : editor_music_range) {
72 for(const config& music : editor_music.child_range("music")) {
73 if(auto track = sound::music_track::create(music)) {
74 tracks.push_back(std::move(track));
75 }
76 }
77 }
78
79 if(tracks.empty()) {
80 ERR_ED << "No editor music defined";
81 }
82
83 return tracks;
84}
85
86auto parse_map_generators(const config_array_view& multiplayer_tag_range)
87{
88 std::vector<std::unique_ptr<map_generator>> generators;
89
90 for(const config& i : multiplayer_tag_range) {
91 if(i["map_generation"].empty() && i["scenario_generation"].empty()) {
92 continue;
93 }
94
95 if(const auto generator_cfg = i.optional_child("generator")) {
96 generators.emplace_back(create_map_generator(i["map_generation"].str(i["scenario_generation"]), generator_cfg.value()));
97 }
98 }
99
100 return generators;
101}
102
103} // namespace
104
106
109 , mouse_handler_base()
110 , quit_confirmation(std::bind(&editor_controller::quit_confirm, this))
111 , active_menu_(menu_type::none)
112 , reports_(new reports())
113 , gui_(new editor_display(*this, *reports_))
114 , tods_()
115 , context_manager_(new context_manager(*gui_.get(), clear_id ? "" : editor_controller::current_addon_id_))
116 , toolkit_(nullptr)
117 , tooltip_manager_()
118 , floating_label_manager_(nullptr)
119 , help_manager_(help::help_manager::get_instance())
120 , do_quit_(false)
121 , quit_mode_(EXIT_ERROR)
122 , music_tracks_(parse_editor_music(game_config_.child_range("editor_music")))
123 , map_generators_(parse_map_generators(game_config_.child_range("multiplayer")))
124{
125 if(clear_id) {
127 }
128
129 init_gui();
130 toolkit_.reset(new editor_toolkit(*gui_.get(), game_config_, *context_manager_.get()));
131 context_manager_->locs_ = toolkit_->get_palette_manager()->location_palette_.get();
135
137}
138
140{
142 gui().add_redraw_observer(std::bind(&editor_controller::display_redraw_callback, this, std::placeholders::_1));
148// halo_manager_.reset(new halo::manager(*gui_));
149// resources::halo = halo_manager_.get();
150// ^ These lines no longer necessary, the gui owns its halo manager.
151// TODO: Should the editor map contexts actually own the halo manager and swap them in and out from the gui?
152// Note that if that is what happens it might not actually be a good idea for the gui to own the halo manager, so that it can be swapped out
153// without deleting it.
154}
155
157{
158 for (const config &schedule : game_config.child_range("editor_times")) {
159
160 const std::string& schedule_id = schedule["id"];
161 /* Use schedule id as the name if schedule name is empty */
162 const std::string& schedule_name = schedule["name"].empty() ? schedule["id"] : schedule["name"];
163 if (schedule_id.empty()) {
164 ERR_ED << "Missing ID attribute in a TOD Schedule.";
165 continue;
166 }
167
168 tods_map::iterator times = tods_.find(schedule_id);
169 if (times == tods_.end()) {
170 std::pair<tods_map::iterator, bool> new_times =
171 tods_.emplace(schedule_id, std::pair(schedule_name, std::vector<time_of_day>()));
172 times = new_times.first;
173 } else {
174 ERR_ED << "Duplicate TOD Schedule identifiers.";
175 continue;
176 }
177
178 for (const config &time : schedule.child_range("time")) {
179 times->second.second.emplace_back(time);
180 }
181
182 }
183
184 if (tods_.empty()) {
185 ERR_ED << "No editor time-of-day defined";
186 }
187}
188
196
198{
199 try {
200 while (!do_quit_) {
201 play_slice();
202 }
203 } catch (const editor_exception& e) {
204 gui2::show_transient_message(_("Fatal error"), e.what());
205 return EXIT_ERROR;
206 } catch (const wml_exception& e) {
207 e.show();
208 }
209 return quit_mode_;
210}
211
214
215void editor_controller::do_screenshot(const std::string& screenshot_filename /* = "map_screenshot.png" */)
216{
217 try {
218 surface screenshot = gui().screenshot(true);
219 if(!screenshot || image::save_image(screenshot, screenshot_filename) != image::save_result::success) {
220 ERR_ED << "Screenshot creation failed!";
221 }
222 } catch (const wml_exception& e) {
223 e.show();
224 }
225}
226
228{
229 std::string modified;
230 std::size_t amount = context_manager_->modified_maps(modified);
231
232 std::string message;
233 if (amount == 0) {
234 message = _("Do you really want to quit?");
235 } else if (amount == 1 && get_current_map_context().modified()) {
236 message = _("Do you really want to quit? Changes to this map since the last save will be lost.");
237 } else {
238 message = _("Do you really want to quit? The following maps were modified and all changes since the last save will be lost:");
239 message += "\n" + modified;
240 }
241 return quit_confirmation::show_prompt(message);
242}
243
251
253{
254 if (tods_.empty()) {
255 gui2::show_error_message(_("No editor time-of-day found."));
256 return;
257 }
258
260 std::vector<time_of_day> prev_schedule = manager.times();
261
263
264 /* Register callback to the dialog so that the map changes can be
265 * previewed in real time.
266 */
267 std::function<void(std::vector<time_of_day>)> update_func(
268 std::bind(
270 this,
271 std::placeholders::_1));
272 tod_dlg.register_callback(update_func);
273
274 /* Autogenerate schedule id */
275 static constexpr std::string_view ts_format = "%Y-%m-%d_%H-%M-%S";
276 std::string timestamp = chrono::format_local_timestamp(std::chrono::system_clock::now(), ts_format);
277 std::string sch_id = current_addon_id_+"-schedule";
278 /* Set correct textdomain */
279 t_string sch_name("", "wesnoth-"+current_addon_id_);
280
281 // TODO : Needs better error handling messages
282 /* Show dialog and update current schedule */
283 if(tod_dlg.show()) {
284 /* Save the new schedule */
285 std::vector<time_of_day> schedule = tod_dlg.get_schedule();
286 if(!gui2::dialogs::tod_new_schedule::execute(sch_id, sch_name)) {
287 /* User pressed Cancel. Restore old schedule */
289 return;
290 }
291
292 /* In case the ID or Name field is blank and user presses OK */
293 if (sch_id.empty()) {
294 sch_id = current_addon_id_ + "-schedule-" + timestamp;
295 } else {
296 /* Check if the id entered is same as any of the existing ids
297 * If so, replace */
298 // TODO : Notify the user if they enter an already existing schedule ID
299 for (auto map_elem : tods_) {
300 if (sch_id == map_elem.first) {
301 sch_id = current_addon_id_ + "-schedule-" + timestamp;
302 }
303 }
304 }
305
306 tods_.emplace(sch_id, std::pair(sch_name, schedule));
309 gui_->update_tod();
310 context_manager_->refresh_all();
311 } else {
312 /* Restore old schedule */
314 }
315}
316
317void editor_controller::update_map_schedule(const std::vector<time_of_day>& schedule)
318{
320 gui_->update_tod();
321 context_manager_->refresh_all();
322}
323
325{
326 using namespace hotkey; //reduce hotkey:: clutter
327 int index = cmd.index;
328 switch(cmd.hotkey_command) {
329 case HOTKEY_NULL:
330 if (index >= 0) {
331 unsigned i = static_cast<unsigned>(index);
332
333 switch (active_menu_) {
334 case menu_type::map:
335 if (i < context_manager_->open_maps()) {
336 return true;
337 }
338 return false;
341 case menu_type::area:
342 case menu_type::side:
343 case menu_type::time:
346 case menu_type::music:
349 case menu_type::none:
350 return true;
351 }
352 }
353 return false;
354 case HOTKEY_EDITOR_PALETTE_GROUPS:
355 return true;
356 case HOTKEY_EDITOR_PALETTE_UPSCROLL:
357 return toolkit_->get_palette_manager()->can_scroll_up();
358 case HOTKEY_EDITOR_PALETTE_DOWNSCROLL:
359 return toolkit_->get_palette_manager()->can_scroll_down();
360 case HOTKEY_ZOOM_IN:
361 return !gui_->zoom_at_max();
362 case HOTKEY_ZOOM_OUT:
363 return !gui_->zoom_at_min();
364 case HOTKEY_ZOOM_DEFAULT:
365 case HOTKEY_FULLSCREEN:
366 case HOTKEY_SCREENSHOT:
367 case HOTKEY_MAP_SCREENSHOT:
368 case HOTKEY_TOGGLE_GRID:
369 case HOTKEY_MOUSE_SCROLL:
370 case HOTKEY_ANIMATE_MAP:
371 case HOTKEY_MUTE:
372 case HOTKEY_PREFERENCES:
373 case HOTKEY_HELP:
374 case HOTKEY_QUIT_GAME:
375 case HOTKEY_SCROLL_UP:
376 case HOTKEY_SCROLL_DOWN:
377 case HOTKEY_SCROLL_LEFT:
378 case HOTKEY_SCROLL_RIGHT:
379 return true; //general hotkeys we can always do
380
381 case HOTKEY_UNIT_LIST:
382 return !get_current_map_context().units().empty();
383
384 // TODO Disabling this for now until the functionality can be implemnted.
385 // See the status_table() method
386 case HOTKEY_STATUS_TABLE:
387 //return !get_current_map_context().teams().empty();
388 return false;
389 /////////////////////////////
390
391 case HOTKEY_TERRAIN_DESCRIPTION:
392 return gui().mouseover_hex().valid();
393
394 // unit tool related
395 case HOTKEY_DELETE_UNIT:
396 case HOTKEY_RENAME_UNIT:
397 case HOTKEY_EDITOR_UNIT_CHANGE_ID:
398 case HOTKEY_EDITOR_UNIT_TOGGLE_CANRECRUIT:
399 case HOTKEY_EDITOR_UNIT_TOGGLE_RENAMEABLE:
400 case HOTKEY_EDITOR_UNIT_TOGGLE_LOYAL:
401 case HOTKEY_EDITOR_UNIT_FACING:
402 case HOTKEY_UNIT_DESCRIPTION:
403 {
404 map_location loc = gui_->mouseover_hex();
405 const unit_map& units = get_current_map_context().units();
406 return (toolkit_->is_mouse_action_set(HOTKEY_EDITOR_TOOL_UNIT) &&
407 units.find(loc) != units.end());
408 }
409
410 case HOTKEY_UNDO:
411 case HOTKEY_EDITOR_PARTIAL_UNDO:
413 case HOTKEY_REDO:
415
416 case TITLE_SCREEN__RELOAD_WML:
417 case HOTKEY_QUIT_TO_DESKTOP:
418 case HOTKEY_EDITOR_MAP_NEW:
419 case HOTKEY_EDITOR_SCENARIO_NEW:
420 case HOTKEY_EDITOR_MAP_LOAD:
421 case HOTKEY_EDITOR_MAP_SAVE_AS:
422 return true;
423
424 // Can be enabled as long as a valid addon_id is set
425 case HOTKEY_EDITOR_EDIT_UNIT:
426 return !current_addon_id_.empty();
427
428 // Only enable when editing a scenario
429 case HOTKEY_EDITOR_MAP_TO_SCENARIO:
431
432 // Only enable when editing a scenario
433 case HOTKEY_EDITOR_CUSTOM_TODS:
434 case HOTKEY_EDITOR_SCENARIO_SAVE_AS:
436
437 case HOTKEY_EDITOR_PBL:
438 case HOTKEY_EDITOR_CHANGE_ADDON_ID:
439 case HOTKEY_EDITOR_SELECT_ADDON:
440 case HOTKEY_EDITOR_OPEN_ADDON:
441 return true;
442
443 case HOTKEY_EDITOR_AREA_ADD:
444 case HOTKEY_EDITOR_SIDE_NEW:
446
447 case HOTKEY_EDITOR_SIDE_EDIT:
448 case HOTKEY_EDITOR_SIDE_REMOVE:
449 return !get_current_map_context().teams().empty();
450
451 // brushes
452 case HOTKEY_EDITOR_BRUSH_NEXT:
453 case HOTKEY_EDITOR_BRUSH_1:
454 case HOTKEY_EDITOR_BRUSH_2:
455 case HOTKEY_EDITOR_BRUSH_3:
456 case HOTKEY_EDITOR_BRUSH_NW_SE:
457 case HOTKEY_EDITOR_BRUSH_SW_NE:
459
460 case HOTKEY_EDITOR_TOOL_NEXT:
461 return true;
462 case HOTKEY_EDITOR_PALETTE_ITEM_SWAP:
463 return toolkit_->get_palette_manager()->active_palette().supports_swap();
464 case HOTKEY_EDITOR_MAP_SAVE:
466 case HOTKEY_EDITOR_MAP_SAVE_ALL:
467 {
468 std::string dummy;
469 return context_manager_->modified_maps(dummy) > 1;
470 }
471 case HOTKEY_EDITOR_PLAYLIST:
472 case HOTKEY_EDITOR_SCHEDULE:
474 case HOTKEY_EDITOR_MAP_SWITCH:
475 case HOTKEY_EDITOR_MAP_CLOSE:
476 return true;
477 case HOTKEY_EDITOR_MAP_REVERT:
478 return !get_current_map_context().get_filename().empty()
480
481 // Tools
482 // Pure map editing tools this can be used all the time.
483 case HOTKEY_EDITOR_TOOL_PAINT:
484 case HOTKEY_EDITOR_TOOL_FILL:
485 case HOTKEY_EDITOR_TOOL_SELECT:
486 case HOTKEY_EDITOR_TOOL_STARTING_POSITION:
487 return true;
488 // WWL dependent tools which don't rely on defined sides.
489 case HOTKEY_EDITOR_SCENARIO_EDIT:
490 case HOTKEY_EDITOR_TOOL_LABEL:
491 case HOTKEY_EDITOR_TOOL_ITEM:
493 case HOTKEY_EDITOR_TOOL_UNIT:
494 case HOTKEY_EDITOR_TOOL_VILLAGE:
495 return !get_current_map_context().teams().empty();
496
497 case HOTKEY_EDITOR_AREA_REMOVE:
498 case HOTKEY_EDITOR_AREA_RENAME:
499 case HOTKEY_EDITOR_LOCAL_TIME:
502
503 case HOTKEY_EDITOR_AREA_SAVE:
507
508 case HOTKEY_EDITOR_SELECTION_EXPORT:
509 case HOTKEY_EDITOR_SELECTION_CUT:
510 case HOTKEY_EDITOR_SELECTION_COPY:
511 case HOTKEY_EDITOR_SELECTION_FILL:
513 && !toolkit_->is_mouse_action_set(HOTKEY_EDITOR_CLIPBOARD_PASTE);
514 case HOTKEY_EDITOR_SELECTION_RANDOMIZE:
516 && !toolkit_->is_mouse_action_set(HOTKEY_EDITOR_CLIPBOARD_PASTE);
517 case HOTKEY_EDITOR_SELECTION_ROTATE:
518 case HOTKEY_EDITOR_SELECTION_FLIP:
519 case HOTKEY_EDITOR_CLIPBOARD_PASTE:
520 return !context_manager_->clipboard_empty();
521 case HOTKEY_EDITOR_CLIPBOARD_ROTATE_CW:
522 case HOTKEY_EDITOR_CLIPBOARD_ROTATE_CCW:
523 case HOTKEY_EDITOR_CLIPBOARD_FLIP_HORIZONTAL:
524 case HOTKEY_EDITOR_CLIPBOARD_FLIP_VERTICAL:
525 return !context_manager_->clipboard_empty()
526 && toolkit_->is_mouse_action_set(HOTKEY_EDITOR_CLIPBOARD_PASTE);
527 case HOTKEY_EDITOR_SELECT_ALL:
528 case HOTKEY_EDITOR_SELECT_NONE:
529 return !toolkit_->is_mouse_action_set(HOTKEY_EDITOR_CLIPBOARD_PASTE);
530 case HOTKEY_EDITOR_SELECT_INVERSE:
533 && !toolkit_->is_mouse_action_set(HOTKEY_EDITOR_CLIPBOARD_PASTE);
534 case HOTKEY_EDITOR_MAP_RESIZE:
535 case HOTKEY_EDITOR_MAP_GENERATE:
536 case HOTKEY_EDITOR_MAP_APPLY_MASK:
537 case HOTKEY_EDITOR_MAP_CREATE_MASK_TO:
538 case HOTKEY_EDITOR_REFRESH:
539 case HOTKEY_EDITOR_UPDATE_TRANSITIONS:
540 case HOTKEY_EDITOR_AUTO_UPDATE_TRANSITIONS:
541 case HOTKEY_EDITOR_PARTIAL_UPDATE_TRANSITIONS:
542 case HOTKEY_EDITOR_NO_UPDATE_TRANSITIONS:
543 case HOTKEY_EDITOR_REFRESH_IMAGE_CACHE:
544 case HOTKEY_EDITOR_HELP_TEXT_SHOWN:
545 case HOTKEY_MINIMAP_CODING_TERRAIN:
546 case HOTKEY_MINIMAP_CODING_UNIT:
547 case HOTKEY_MINIMAP_DRAW_UNITS:
548 case HOTKEY_MINIMAP_DRAW_TERRAIN:
549 case HOTKEY_MINIMAP_DRAW_VILLAGES:
550 case HOTKEY_EDITOR_REMOVE_LOCATION:
551 return true;
552 case HOTKEY_EDITOR_DRAW_COORDINATES:
553 case HOTKEY_EDITOR_DRAW_TERRAIN_CODES:
554 case HOTKEY_EDITOR_DRAW_NUM_OF_BITMAPS:
555 return true;
556 default:
557 return false;
558 }
559}
560
562{
563 using namespace hotkey;
564 int index = cmd.index;
565 switch (cmd.hotkey_command) {
566
567 case HOTKEY_EDITOR_UNIT_TOGGLE_LOYAL:
568 {
570 get_current_map_context().units().find(gui_->mouseover_hex());
571 return hotkey::on_if(un->loyal());
572 }
573 case HOTKEY_EDITOR_UNIT_TOGGLE_CANRECRUIT:
574 {
576 get_current_map_context().units().find(gui_->mouseover_hex());
577 return hotkey::on_if(un->can_recruit());
578 }
579 case HOTKEY_EDITOR_UNIT_TOGGLE_RENAMEABLE:
580 {
582 get_current_map_context().units().find(gui_->mouseover_hex());
583 return hotkey::on_if(!un->unrenamable());
584 }
585 //TODO remove hardcoded hotkey names
586 case HOTKEY_EDITOR_AUTO_UPDATE_TRANSITIONS:
587 return hotkey::selected_if(context_manager_->is_active_transitions_hotkey("editor-auto-update-transitions"));
588 case HOTKEY_EDITOR_PARTIAL_UPDATE_TRANSITIONS:
589 return hotkey::selected_if(context_manager_->is_active_transitions_hotkey("editor-partial-update-transitions"));
590 case HOTKEY_EDITOR_NO_UPDATE_TRANSITIONS:
591 return hotkey::selected_if(context_manager_->is_active_transitions_hotkey("editor-no-update-transitions"));
592 case HOTKEY_EDITOR_BRUSH_1:
593 return hotkey::on_if(toolkit_->is_active_brush("brush-1"));
594 case HOTKEY_EDITOR_BRUSH_2:
595 return hotkey::on_if(toolkit_->is_active_brush("brush-2"));
596 case HOTKEY_EDITOR_BRUSH_3:
597 return hotkey::on_if(toolkit_->is_active_brush("brush-3"));
598 case HOTKEY_EDITOR_BRUSH_NW_SE:
599 return hotkey::on_if(toolkit_->is_active_brush("brush-nw-se"));
600 case HOTKEY_EDITOR_BRUSH_SW_NE:
601 return hotkey::on_if(toolkit_->is_active_brush("brush-sw-ne"));
602
603 case HOTKEY_TOGGLE_GRID:
604 return hotkey::on_if(prefs::get().grid());
605 case HOTKEY_EDITOR_SELECT_ALL:
606 return hotkey::selected_if(get_current_map_context().map().everything_selected());
607 case HOTKEY_EDITOR_SELECT_NONE:
608 return hotkey::selected_if(get_current_map_context().map().nothing_selected());
609 case HOTKEY_EDITOR_TOOL_FILL:
610 case HOTKEY_EDITOR_TOOL_LABEL:
611 case HOTKEY_EDITOR_TOOL_PAINT:
612 case HOTKEY_EDITOR_TOOL_SELECT:
613 case HOTKEY_EDITOR_CLIPBOARD_PASTE:
614 case HOTKEY_EDITOR_TOOL_STARTING_POSITION:
615 case HOTKEY_EDITOR_TOOL_UNIT:
616 case HOTKEY_EDITOR_TOOL_VILLAGE:
617 case HOTKEY_EDITOR_TOOL_ITEM:
618 return hotkey::on_if(toolkit_->is_mouse_action_set(cmd.hotkey_command));
619 case HOTKEY_EDITOR_DRAW_COORDINATES:
620 return hotkey::on_if(gui_->debug_flag_set(display::DEBUG_COORDINATES));
621 case HOTKEY_EDITOR_DRAW_TERRAIN_CODES:
622 return hotkey::on_if(gui_->debug_flag_set(display::DEBUG_TERRAIN_CODES));
623 case HOTKEY_EDITOR_DRAW_NUM_OF_BITMAPS:
624 return hotkey::on_if(gui_->debug_flag_set(display::DEBUG_NUM_BITMAPS));
625 case HOTKEY_EDITOR_HELP_TEXT_SHOWN:
626 return hotkey::on_if(gui_->help_string_enabled());
627 case HOTKEY_MINIMAP_DRAW_VILLAGES:
629 case HOTKEY_MINIMAP_CODING_UNIT:
631 case HOTKEY_MINIMAP_CODING_TERRAIN:
633 case HOTKEY_MINIMAP_DRAW_UNITS:
635 case HOTKEY_MINIMAP_DRAW_TERRAIN:
637 case HOTKEY_ZOOM_DEFAULT:
638 return hotkey::on_if(gui_->get_zoom_factor() == 1.0);
639
640 case HOTKEY_NULL:
641 switch (active_menu_) {
642 case menu_type::map:
643 return hotkey::selected_if(index == context_manager_->current_context_index());
648 case menu_type::area:
649 return hotkey::selected_if(index == get_current_map_context().get_active_area());
650 case menu_type::side:
651 return hotkey::selected_if(static_cast<std::size_t>(index) == gui_->playing_team_index());
652 case menu_type::time:
653 return hotkey::selected_if(index == get_current_map_context().get_time_manager()->get_current_time());
655 return hotkey::selected_if(index == get_current_map_context().get_time_manager()->get_current_area_time(
656 get_current_map_context().get_active_area()));
657 case menu_type::music:
658 return hotkey::on_if(get_current_map_context().playlist_contains(music_tracks_[index]));
660 {
661 tods_map::const_iterator it = tods_.begin();
662 std::advance(it, index);
663 const std::vector<time_of_day>& times1 = it->second.second;
664 const std::vector<time_of_day>& times2 = get_current_map_context().get_time_manager()->times();
666 }
668 {
669 tods_map::const_iterator it = tods_.begin();
670 std::advance(it, index);
671 const std::vector<time_of_day>& times1 = it->second.second;
673 const std::vector<time_of_day>& times2 = get_current_map_context().get_time_manager()->times(active_area);
675 }
677 {
679 assert(un != get_current_map_context().units().end());
680 return hotkey::selected_if(un->facing() == map_location::direction{index});
681 }
682 case menu_type::none:
684 }
686 default:
687 return command_executor::get_action_state(cmd);
688 }
689}
690
691bool editor_controller::do_execute_command(const hotkey::ui_command& cmd, bool press, bool release)
692{
693 using namespace hotkey;
694 HOTKEY_COMMAND command = cmd.hotkey_command;
695 SCOPE_ED;
696 int index = cmd.index;
697
698 // nothing here handles release; fall through to base implementation
699 if (!press) {
700 return command_executor::do_execute_command(cmd, press, release);
701 }
702
703 switch (command) {
704 case HOTKEY_NULL:
705 switch (active_menu_) {
706 case menu_type::map:
707 if (index >= 0) {
708 unsigned i = static_cast<unsigned>(index);
709 if (i < context_manager_->size()) {
710 context_manager_->switch_context(index);
711 toolkit_->hotkey_set_mouse_action(HOTKEY_EDITOR_TOOL_PAINT);
712 return true;
713 }
714 }
715 return false;
717 if (index >= 0) {
718 context_manager_->load_mru_item(static_cast<unsigned>(index));
719 }
720 return true;
722 toolkit_->get_palette_manager()->set_group(index);
723 return true;
724 case menu_type::side:
725 gui_->set_viewing_team_index(index, true);
726 gui_->set_playing_team_index(index);
727 toolkit_->get_palette_manager()->draw_contents();
728 return true;
729 case menu_type::area:
730 {
732 const std::set<map_location>& area =
735 gui_->scroll_to_tiles({ area.begin(), area.end() });
736 return true;
737 }
738 case menu_type::time:
739 {
741 gui_->update_tod();
742 return true;
743 }
745 {
747 return true;
748 }
749 case menu_type::music:
750 {
751 //TODO mark the map as changed
754 return true;
755 }
757 {
758 tods_map::iterator iter = tods_.begin();
759 std::advance(iter, index);
761 // TODO: test again after the assign-schedule menu is fixed. Should work, though.
762 gui_->update_tod();
763 return true;
764 }
766 {
767 tods_map::iterator iter = tods_.begin();
768 std::advance(iter, index);
770 return true;
771 }
773 {
775 assert(un != get_current_map_context().units().end());
776 un->set_facing(map_location::direction(index));
777 un->anim_comp().set_standing();
778 return true;
779 }
780 case menu_type::none:
781 return true;
782 }
783 return true;
784
785 //Zoom
786 case HOTKEY_ZOOM_IN:
787 gui_->set_zoom(true);
789 toolkit_->set_mouseover_overlay(*gui_);
790 return true;
791 case HOTKEY_ZOOM_OUT:
792 gui_->set_zoom(false);
794 toolkit_->set_mouseover_overlay(*gui_);
795 return true;
796 case HOTKEY_ZOOM_DEFAULT:
797 gui_->toggle_default_zoom();
799 toolkit_->set_mouseover_overlay(*gui_);
800 return true;
801
802 //Palette
803 case HOTKEY_EDITOR_PALETTE_GROUPS:
804 //TODO this code waits for the gui2 dialog to get ready
805// std::vector< std::pair< std::string, std::string >> blah_items;
806// toolkit_->get_palette_manager()->active_palette().expand_palette_groups_menu(blah_items);
807// int selected = 1; //toolkit_->get_palette_manager()->active_palette().get_selected;
808// gui2::teditor_select_palette_group::execute(selected, blah_items);
809 return true;
810 case HOTKEY_EDITOR_PALETTE_UPSCROLL:
811 toolkit_->get_palette_manager()->scroll_up();
812 return true;
813 case HOTKEY_EDITOR_PALETTE_DOWNSCROLL:
814 toolkit_->get_palette_manager()->scroll_down();
815 return true;
816
817 case HOTKEY_QUIT_GAME:
819 do_quit_ = true;
821 }
822 return true;
823 case HOTKEY_QUIT_TO_DESKTOP:
825 return true;
826 case TITLE_SCREEN__RELOAD_WML:
827 context_manager_->save_contexts();
828 do_quit_ = true;
830 return true;
831 case HOTKEY_EDITOR_EDIT_UNIT:
833 return true;
834 case HOTKEY_EDITOR_CUSTOM_TODS:
836 return true;
837 case HOTKEY_EDITOR_PALETTE_ITEM_SWAP:
838 toolkit_->get_palette_manager()->active_palette().swap();
839 return true;
840 case HOTKEY_EDITOR_PARTIAL_UNDO:
841 if (dynamic_cast<const editor_action_chain*>(get_current_map_context().last_undo_action()) != nullptr) {
843 context_manager_->refresh_after_action();
844 } else {
845 undo();
846 }
847 return true;
848
849 //Tool Selection
850 case HOTKEY_EDITOR_TOOL_PAINT:
851 case HOTKEY_EDITOR_TOOL_FILL:
852 case HOTKEY_EDITOR_TOOL_SELECT:
853 case HOTKEY_EDITOR_TOOL_STARTING_POSITION:
854 case HOTKEY_EDITOR_TOOL_LABEL:
855 case HOTKEY_EDITOR_TOOL_UNIT:
856 case HOTKEY_EDITOR_TOOL_VILLAGE:
857 case HOTKEY_EDITOR_TOOL_ITEM:
858 toolkit_->hotkey_set_mouse_action(command);
859 return true;
860
861 case HOTKEY_EDITOR_PBL:
862 if(initialize_addon()) {
863 context_manager_->edit_pbl();
864 }
865 return true;
866
867 case HOTKEY_EDITOR_CHANGE_ADDON_ID:
868 if(initialize_addon()) {
869 context_manager_->change_addon_id();
870 }
871 return true;
872
873 case HOTKEY_EDITOR_SELECT_ADDON:
874 select_addon();
875 return true;
876
877 case HOTKEY_EDITOR_OPEN_ADDON:
878 {
879 if (!initialize_addon()) {
880 gui2::show_error_message("Could not initialize add-on!");
881 return true;
882 }
883
885
886 dlg.set_title(_("Add-on Files"))
888
889 if (dlg.show()) {
890 std::string filepath = dlg.path();
892 // Open map or scenario
893 context_manager_->load_map(filepath, true);
894 } else {
895 // Open file using OS application for that format
898 } else {
899 gui2::show_message("", _("Opening files is not supported, contact your packager"), gui2::dialogs::message::auto_close);
900 }
901 }
902 }
903
904 return true;
905 }
906
907 case HOTKEY_EDITOR_AREA_ADD:
908 add_area();
909 return true;
910
911 case HOTKEY_EDITOR_UNIT_CHANGE_ID:
913 return true;
914
915 case HOTKEY_EDITOR_UNIT_TOGGLE_RENAMEABLE:
916 {
917 map_location loc = gui_->mouseover_hex();
919 bool unrenamable = un->unrenamable();
920 un->set_unrenamable(!unrenamable);
921 return true;
922 }
923 case HOTKEY_EDITOR_UNIT_TOGGLE_CANRECRUIT:
924 {
925 map_location loc = gui_->mouseover_hex();
927 bool canrecruit = un->can_recruit();
928 un->set_can_recruit(!canrecruit);
929 un->anim_comp().set_standing();
930 return true;
931 }
932 case HOTKEY_EDITOR_UNIT_TOGGLE_LOYAL:
933 {
934 map_location loc = gui_->mouseover_hex();
936 bool loyal = un->loyal();
937 un->set_loyal(!loyal);
938 return true;
939 }
940 case HOTKEY_DELETE_UNIT:
941 {
942 map_location loc = gui_->mouseover_hex();
943 perform_delete(std::make_unique<editor_action_unit_delete>(loc));
944 return true;
945 }
946 case HOTKEY_EDITOR_CLIPBOARD_PASTE: //paste is somewhat different as it might be "one action then revert to previous mode"
947 toolkit_->hotkey_set_mouse_action(command);
948 return true;
949
950 //Clipboard
951 case HOTKEY_EDITOR_CLIPBOARD_ROTATE_CW:
952 context_manager_->get_clipboard().rotate_60_cw();
953 toolkit_->update_mouse_action_highlights();
954 return true;
955 case HOTKEY_EDITOR_CLIPBOARD_ROTATE_CCW:
956 context_manager_->get_clipboard().rotate_60_ccw();
957 toolkit_->update_mouse_action_highlights();
958 return true;
959 case HOTKEY_EDITOR_CLIPBOARD_FLIP_HORIZONTAL:
960 context_manager_->get_clipboard().flip_horizontal();
961 toolkit_->update_mouse_action_highlights();
962 return true;
963 case HOTKEY_EDITOR_CLIPBOARD_FLIP_VERTICAL:
964 context_manager_->get_clipboard().flip_vertical();
965 toolkit_->update_mouse_action_highlights();
966 return true;
967
968 //Brushes
969 case HOTKEY_EDITOR_BRUSH_NEXT:
970 toolkit_->cycle_brush();
971 return true;
972 case HOTKEY_EDITOR_BRUSH_1:
973 toolkit_->set_brush("brush-1");
974 return true;
975 case HOTKEY_EDITOR_BRUSH_2:
976 toolkit_->set_brush("brush-2");
977 return true;
978 case HOTKEY_EDITOR_BRUSH_3:
979 toolkit_->set_brush("brush-3");
980 return true;
981 case HOTKEY_EDITOR_BRUSH_NW_SE:
982 toolkit_->set_brush("brush-nw-se");
983 return true;
984 case HOTKEY_EDITOR_BRUSH_SW_NE:
985 toolkit_->set_brush("brush-sw-ne");
986 return true;
987
988 case HOTKEY_EDITOR_SELECTION_COPY:
990 return true;
991 case HOTKEY_EDITOR_SELECTION_CUT:
993 return true;
994 case HOTKEY_EDITOR_AREA_RENAME:
995 context_manager_->rename_area_dialog();
996 return true;
997 case HOTKEY_EDITOR_AREA_SAVE:
998 save_area();
999 return true;
1000 case HOTKEY_EDITOR_SELECTION_EXPORT:
1002 return true;
1003 case HOTKEY_EDITOR_SELECT_ALL:
1004 if(!get_current_map_context().map().everything_selected()) {
1005 context_manager_->perform_refresh(editor_action_select_all());
1006 return true;
1007 }
1008 [[fallthrough]];
1009 case HOTKEY_EDITOR_SELECT_INVERSE:
1011 return true;
1012 case HOTKEY_EDITOR_SELECT_NONE:
1013 context_manager_->perform_refresh(editor_action_select_none());
1014 return true;
1015 case HOTKEY_EDITOR_SELECTION_FILL:
1016 context_manager_->fill_selection();
1017 return true;
1018 case HOTKEY_EDITOR_SELECTION_RANDOMIZE:
1020 get_current_map_context().map().selection()));
1021 return true;
1022
1023 case HOTKEY_EDITOR_SCENARIO_EDIT:
1024 context_manager_->edit_scenario_dialog();
1025 return true;
1026
1027 case HOTKEY_EDITOR_AREA_REMOVE:
1029 get_current_map_context().get_active_area());
1030 return true;
1031
1032 // map specific
1033 case HOTKEY_EDITOR_MAP_CLOSE:
1034 context_manager_->close_current_context();
1035 // Copy behaviour from when switching windows to always reset the active tool to the Paint Tool
1036 // This avoids the situation of having a scenario-specific tool active in a map context which can cause a crash if used
1037 // Not elegant but at least avoids a potential crash and is consistent with existing behaviour
1038 toolkit_->hotkey_set_mouse_action(HOTKEY_EDITOR_TOOL_PAINT);
1039 return true;
1040 case HOTKEY_EDITOR_MAP_LOAD:
1041 context_manager_->load_map_dialog();
1042 return true;
1043 case HOTKEY_EDITOR_MAP_REVERT:
1044 context_manager_->revert_map();
1045 return true;
1046 case HOTKEY_EDITOR_MAP_NEW:
1047 context_manager_->new_map_dialog();
1048 return true;
1049 case HOTKEY_EDITOR_SCENARIO_NEW:
1050 if(initialize_addon()) {
1051 context_manager_->new_scenario_dialog();
1052 }
1053 return true;
1054 case HOTKEY_EDITOR_MAP_SAVE:
1055 save_map();
1056 return true;
1057 case HOTKEY_EDITOR_MAP_SAVE_ALL:
1058 context_manager_->save_all_maps();
1059 return true;
1060 case HOTKEY_EDITOR_MAP_SAVE_AS:
1061 context_manager_->save_map_as_dialog();
1062 return true;
1063 case HOTKEY_EDITOR_MAP_TO_SCENARIO:
1064 if(initialize_addon()) {
1065 context_manager_->map_to_scenario();
1066 }
1067 return true;
1068 case HOTKEY_EDITOR_SCENARIO_SAVE_AS:
1069 if(initialize_addon()) {
1070 context_manager_->save_scenario_as_dialog();
1071 }
1072 return true;
1073 case HOTKEY_EDITOR_MAP_GENERATE:
1074 context_manager_->generate_map_dialog(map_generators_);
1075 return true;
1076 case HOTKEY_EDITOR_MAP_APPLY_MASK:
1077 context_manager_->apply_mask_dialog();
1078 return true;
1079 case HOTKEY_EDITOR_MAP_CREATE_MASK_TO:
1080 context_manager_->create_mask_to_dialog();
1081 return true;
1082 case HOTKEY_EDITOR_MAP_RESIZE:
1083 context_manager_->resize_map_dialog();
1084 return true;
1085
1086 // Side specific ones
1087 case HOTKEY_EDITOR_SIDE_NEW:
1088 if(get_current_map_context().teams().size() >= 9) {
1089 std::size_t new_side_num = get_current_map_context().teams().size() + 1;
1090 toolkit_->get_palette_manager()->location_palette_->add_item(std::to_string(new_side_num));
1091 }
1093 gui_->init_flags();
1094 return true;
1095 case HOTKEY_EDITOR_SIDE_REMOVE:
1096 gui_->set_viewing_team_index(0, true);
1097 gui_->set_playing_team_index(0);
1099 return true;
1100 case HOTKEY_EDITOR_SIDE_EDIT:
1101 context_manager_->edit_side_dialog(gui_->viewing_team());
1102 return true;
1103
1104 // Transitions
1105 case HOTKEY_EDITOR_PARTIAL_UPDATE_TRANSITIONS:
1106 context_manager_->set_update_transitions_mode(2);
1107 return true;
1108 case HOTKEY_EDITOR_AUTO_UPDATE_TRANSITIONS:
1109 context_manager_->set_update_transitions_mode(1);
1110 return true;
1111 case HOTKEY_EDITOR_NO_UPDATE_TRANSITIONS:
1112 context_manager_->set_update_transitions_mode(0);
1113 return true;
1114 case HOTKEY_EDITOR_TOGGLE_TRANSITIONS:
1115 if(context_manager_->toggle_update_transitions()) {
1116 return true;
1117 }
1118 [[fallthrough]];
1119 case HOTKEY_EDITOR_UPDATE_TRANSITIONS:
1120 context_manager_->refresh_all();
1121 return true;
1122 // Refresh
1123 case HOTKEY_EDITOR_REFRESH:
1124 context_manager_->reload_map();
1125 return true;
1126 case HOTKEY_EDITOR_REFRESH_IMAGE_CACHE:
1128 return true;
1129
1130 case HOTKEY_EDITOR_DRAW_COORDINATES:
1132 prefs::get().set_editor_draw_hex_coordinates(gui().debug_flag_set(display::DEBUG_COORDINATES));
1133 gui().invalidate_all();
1134 return true;
1135 case HOTKEY_EDITOR_DRAW_TERRAIN_CODES:
1137 prefs::get().set_editor_draw_terrain_codes(gui().debug_flag_set(display::DEBUG_TERRAIN_CODES));
1138 gui().invalidate_all();
1139 return true;
1140 case HOTKEY_EDITOR_DRAW_NUM_OF_BITMAPS:
1142 prefs::get().set_editor_draw_num_of_bitmaps(gui().debug_flag_set(display::DEBUG_NUM_BITMAPS));
1143 gui().invalidate_all();
1144 return true;
1145 case HOTKEY_EDITOR_HELP_TEXT_SHOWN:
1146 gui().set_help_string_enabled(!gui().help_string_enabled());
1147 prefs::get().set_editor_help_text_shown(gui().help_string_enabled());
1148 return true;
1149 case HOTKEY_EDITOR_REMOVE_LOCATION: {
1150 location_palette* lp = dynamic_cast<location_palette*>(&toolkit_->get_palette_manager()->active_palette());
1151 if (lp) {
1152 perform_delete(std::make_unique<editor_action_starting_position>(map_location(), lp->selected_item()));
1153 // No idea if this is the right thing to call, but it ensures starting
1154 // position labels get removed on delete.
1155 context_manager_->refresh_after_action();
1156 }
1157 return true;
1158 }
1159 default:
1160 return hotkey::command_executor::do_execute_command(cmd, press, release);
1161 }
1162}
1163
1169
1171{
1172 if(current_addon_id_.empty()) {
1173 select_addon();
1174 }
1175
1176 // current_addon_id_ could be empty if editor::initialize_addon failed
1177 return !current_addon_id_.empty();
1178}
1179
1181{
1182 help::show_help("..editor");
1183}
1184
1186{
1187 // Keep the music menu open to allow multiple selections easily
1189}
1190
1191void editor_controller::show_menu(const std::vector<config>& items_arg, const point& menu_loc, bool context_menu)
1192{
1193 // Ensure active_menu_ is only valid within the scope of this function.
1195
1196 if(context_menu
1197 && !get_current_map_context().map().on_board_with_border(gui().hex_clicked_on(menu_loc.x, menu_loc.y)))
1198 {
1199 return;
1200 }
1201
1202 std::vector<config> items;
1203 for(const auto& c : items_arg) {
1204 const std::string& id = c["id"];
1205 const auto cmd = hotkey::ui_command(id);
1206
1207 if((can_execute_command(cmd) && (!context_menu || in_context_menu(cmd)))
1208 || cmd.hotkey_command == hotkey::HOTKEY_NULL)
1209 {
1210 items.emplace_back("id", id);
1211 }
1212 }
1213
1214 // No point in showing an empty menu.
1215 if(items.empty()) {
1216 return;
1217 }
1218
1219 // Based on the ID of the first entry, we fill the menu contextually.
1220 const std::string& first_id = items.front()["id"];
1221
1222 // All generated items (might be empty).
1223 std::vector<config> generated;
1224
1225 if(first_id == "EDITOR-LOAD-MRU-PLACEHOLDER") {
1227 context_manager_->expand_load_mru_menu(generated);
1228 }
1229
1230 else if(first_id == "editor-switch-map") {
1232 context_manager_->expand_open_maps_menu(generated);
1233 }
1234
1235 else if(first_id == "editor-palette-groups") {
1237 toolkit_->get_palette_manager()->active_palette().expand_palette_groups_menu(generated);
1238 }
1239
1240 else if(first_id == "editor-switch-side") {
1242 context_manager_->expand_sides_menu(generated);
1243 }
1244
1245 else if(first_id == "editor-switch-area") {
1247 context_manager_->expand_areas_menu(generated);
1248 }
1249
1250 else if(first_id == "editor-switch-time") {
1252 context_manager_->expand_time_menu(generated);
1253 }
1254
1255 else if(first_id == "editor-assign-local-time") {
1257 context_manager_->expand_local_time_menu(generated);
1258 }
1259
1260 else if(first_id == "menu-unit-facings") {
1262 auto count = static_cast<int>(map_location::direction::indeterminate);
1263 std::generate_n(std::back_inserter(generated), count, [dir = 0]() mutable {
1265 });
1266 }
1267
1268 else if(first_id == "editor-playlist") {
1270 std::transform(music_tracks_.begin(), music_tracks_.end(), std::back_inserter(generated),
1271 [](const std::shared_ptr<sound::music_track>& track) {
1272 return config{"label", track->title().empty() ? track->id() : track->title()};
1273 });
1274 }
1275
1276 else if(first_id == "editor-assign-schedule") {
1277 active_menu_ = menu_type::schedule;
1278 std::transform(tods_.begin(), tods_.end(), std::back_inserter(generated),
1279 [](const tods_map::value_type& tod) { return config{"label", tod.second.first}; });
1280 }
1281
1282 else if(first_id == "editor-assign-local-schedule") {
1283 active_menu_ = menu_type::local_schedule;
1284 std::transform(tods_.begin(), tods_.end(), std::back_inserter(generated),
1285 [](const tods_map::value_type& tod) { return config{"label", tod.second.first}; });
1286 }
1287
1288 else {
1289 // No placeholders, show everything
1290 command_executor::show_menu(items, menu_loc, context_menu);
1291 return;
1292 }
1293
1294 // Splice the lists, excluding placeholder entry
1295 if(items.size() > 1) {
1296 std::move(items.begin() + 1, items.end(), std::back_inserter(generated));
1297 }
1298
1299 command_executor::show_menu(generated, menu_loc, context_menu);
1300}
1301
1302void editor_controller::preferences()
1303{
1304 gui_->clear_help_string();
1305 gui2::dialogs::preferences_dialog::display();
1306
1307 gui_->queue_rerender();
1308}
1309
1310void editor_controller::toggle_grid()
1311{
1312 prefs::get().set_grid(!prefs::get().grid());
1313 gui_->invalidate_all();
1314}
1315
1316void editor_controller::unit_description()
1317{
1318 map_location loc = gui_->mouseover_hex();
1319 const unit_map& units = get_current_map_context().units();
1320 const unit_map::const_unit_iterator un = units.find(loc);
1321 if(un != units.end()) {
1322 help::show_unit_description(un->type());
1323 } else {
1324 help::show_help("..units");
1325 }
1326}
1327
1328
1329void editor_controller::copy_selection()
1330{
1331 if(get_current_map_context().map().anything_selected()) {
1332 context_manager_->get_clipboard() = map_fragment(get_current_map_context().map(), get_current_map_context().map().selection());
1333 context_manager_->get_clipboard().center_by_mass();
1334 }
1335}
1336
1337void editor_controller::change_unit_id()
1338{
1339 map_location loc = gui_->mouseover_hex();
1340 unit_map& units = get_current_map_context().units();
1341 const unit_map::unit_iterator& un = units.find(loc);
1342
1343 const std::string title(N_("Change Unit ID"));
1344 const std::string label(N_("ID:"));
1345
1346 if(un != units.end()) {
1347 std::string id = un->id();
1348 if (gui2::dialogs::edit_text::execute(title, label, id)) {
1349 un->set_id(id);
1350 }
1351 }
1352}
1353
1354void editor_controller::rename_unit()
1355{
1356 map_location loc = gui_->mouseover_hex();
1357 unit_map& units = get_current_map_context().units();
1358 const unit_map::unit_iterator& un = units.find(loc);
1359
1360 const std::string title(N_("Rename Unit"));
1361 const std::string label(N_("Name:"));
1362
1363 if(un != units.end()) {
1364 std::string name = un->name();
1365 if(gui2::dialogs::edit_text::execute(title, label, name)) {
1366 //TODO we may not want a translated name here.
1367 un->set_name(name);
1368 }
1369 }
1370}
1371
1372void editor_controller::unit_list()
1373{
1374 std::vector<unit_const_ptr> unit_list;
1375
1376 const unit_map& units = gui().context().units();
1377 for(unit_map::const_iterator i = units.begin(); i != units.end(); ++i) {
1378 if(i->side() != gui().viewing_team().side()) {
1379 continue;
1380 }
1381 unit_list.push_back(i.get_shared_ptr());
1382 }
1383
1384 const auto& unit_dlg = gui2::dialogs::units_dialog::build_unit_list_dialog(unit_list);
1385
1386 if (unit_dlg->show() && unit_dlg->is_selected()) {
1387 const map_location& loc = unit_list[unit_dlg->get_selected_index()]->get_location();
1388 gui().scroll_to_tile(loc, display::WARP);
1389 gui().select_hex(loc);
1390 }
1391}
1392
1393void editor_controller::cut_selection()
1394{
1395 copy_selection();
1396 context_manager_->perform_refresh(editor_action_paint_area(get_current_map_context().map().selection(), get_selected_bg_terrain()));
1397}
1398
1399void editor_controller::save_area()
1400{
1401 const std::set<map_location>& area = get_current_map_context().map().selection();
1402 get_current_map_context().save_area(area);
1403}
1404
1405void editor_controller::add_area()
1406{
1407 const std::set<map_location>& area = get_current_map_context().map().selection();
1408 get_current_map_context().new_area(area);
1409}
1410
1411void editor_controller::export_selection_coords()
1412{
1413 std::stringstream ssx, ssy;
1414 auto selection = get_current_map_context().map().selection();
1415 std::set<map_location>::const_iterator i = selection.begin();
1416 if (i != selection.end()) {
1417 ssx << "x = " << i->wml_x();
1418 ssy << "y = " << i->wml_y();
1419 ++i;
1420 while (i != selection.end()) {
1421 ssx << ", " << i->wml_x();
1422 ssy << ", " << i->wml_y();
1423 ++i;
1424 }
1425 ssx << "\n" << ssy.str() << "\n";
1427 }
1428}
1429
1430void editor_controller::perform_delete(std::unique_ptr<editor_action> action)
1431{
1432 if (action) {
1433 get_current_map_context().perform_action(*action);
1434 }
1435}
1436
1437void editor_controller::perform_refresh_delete(std::unique_ptr<editor_action> action, bool drag_part /* =false */)
1438{
1439 if (action) {
1440 context_manager_->perform_refresh(*action, drag_part);
1441 }
1442}
1443
1444void editor_controller::refresh_image_cache()
1445{
1447 context_manager_->refresh_all();
1448}
1449
1450void editor_controller::display_redraw_callback(display&)
1451{
1452 set_button_state();
1453 toolkit_->adjust_size();
1454 get_current_map_context().get_labels().recalculate_labels();
1455}
1456
1457void editor_controller::undo()
1458{
1459 get_current_map_context().undo();
1460 context_manager_->refresh_after_action();
1461}
1462
1463void editor_controller::redo()
1464{
1465 get_current_map_context().redo();
1466 context_manager_->refresh_after_action();
1467}
1468
1469void editor_controller::mouse_motion(int x, int y, const bool /*browse*/,
1470 bool update, map_location /*new_loc*/)
1471{
1472 if (mouse_handler_base::mouse_motion_default(x, y, update)) return;
1473 map_location hex_clicked = gui().hex_clicked_on(x, y);
1474 if (get_current_map_context().map().on_board_with_border(drag_from_hex_) && is_dragging()) {
1475 std::unique_ptr<editor_action> a;
1476 bool partial = false;
1477 // last_undo is a non-owning pointer. Although it could have other uses, it seems to be
1478 // mainly (only?) used for printing debugging information.
1479 auto last_undo = get_current_map_context().last_undo_action();
1480 if (dragging_left_ && (sdl::get_mouse_button_mask() & SDL_BUTTON_MASK(1)) != 0) {
1481 if (!get_current_map_context().map().on_board_with_border(hex_clicked)) return;
1482 a = get_mouse_action().drag_left(*gui_, x, y, partial, last_undo);
1483 } else if (dragging_right_ && (sdl::get_mouse_button_mask() & SDL_BUTTON_MASK(3)) != 0) {
1484 if (!get_current_map_context().map().on_board_with_border(hex_clicked)) return;
1485 a = get_mouse_action().drag_right(*gui_, x, y, partial, last_undo);
1486 }
1487 //Partial means that the mouse action has modified the
1488 //last undo action and the controller shouldn't add
1489 //anything to the undo stack (hence a different perform_ call)
1490 if (a != nullptr) {
1491 if (partial) {
1492 get_current_map_context().perform_partial_action(*a);
1493 } else {
1494 get_current_map_context().perform_action(*a);
1495 }
1496 context_manager_->refresh_after_action(true);
1497 }
1498 } else {
1499 get_mouse_action().move(*gui_, hex_clicked);
1500 }
1501 gui().highlight_hex(hex_clicked);
1502}
1503
1504void editor_controller::touch_motion(int /* x */, int /* y */, const bool /* browse */, bool /* update */, map_location /* new_loc */)
1505{
1506 // Not implemented at all. Sorry, it's a very low priority for iOS port.
1507}
1508
1509bool editor_controller::allow_mouse_wheel_scroll(int x, int y)
1510{
1511 return get_current_map_context().map().on_board_with_border(gui().hex_clicked_on(x,y));
1512}
1513
1514bool editor_controller::right_click_show_menu(int /*x*/, int /*y*/, const bool /*browse*/)
1515{
1516 return get_mouse_action().has_context_menu();
1517}
1518
1519bool editor_controller::left_click(int x, int y, const bool browse)
1520{
1521 toolkit_->clear_mouseover_overlay();
1522 if (mouse_handler_base::left_click(x, y, browse))
1523 return true;
1524
1525 LOG_ED << "Left click, after generic handling";
1526 map_location hex_clicked = gui().hex_clicked_on(x, y);
1527 if (!get_current_map_context().map().on_board_with_border(hex_clicked))
1528 return true;
1529
1530 LOG_ED << "Left click action " << hex_clicked;
1531 auto a = get_mouse_action().click_left(*gui_, x, y);
1532 if(a) {
1533 perform_refresh_delete(std::move(a), true);
1534 set_button_state();
1535 }
1536
1537 return false;
1538}
1539
1540void editor_controller::left_drag_end(int x, int y, const bool /*browse*/)
1541{
1542 auto a = get_mouse_action().drag_end_left(*gui_, x, y);
1543 perform_delete(std::move(a));
1544}
1545
1546void editor_controller::left_mouse_up(int x, int y, const bool /*browse*/)
1547{
1548 auto a = get_mouse_action().up_left(*gui_, x, y);
1549 if(a) {
1550 perform_delete(std::move(a));
1551 set_button_state();
1552 }
1553 toolkit_->set_mouseover_overlay();
1554 context_manager_->refresh_after_action();
1555}
1556
1557bool editor_controller::right_click(int x, int y, const bool browse)
1558{
1559 toolkit_->clear_mouseover_overlay();
1560 if (mouse_handler_base::right_click(x, y, browse)) return true;
1561 LOG_ED << "Right click, after generic handling";
1562 map_location hex_clicked = gui().hex_clicked_on(x, y);
1563 if (!get_current_map_context().map().on_board_with_border(hex_clicked)) return true;
1564 LOG_ED << "Right click action " << hex_clicked;
1565 auto a = get_mouse_action().click_right(*gui_, x, y);
1566 if(a) {
1567 perform_refresh_delete(std::move(a), true);
1568 set_button_state();
1569 }
1570 return false;
1571}
1572
1573void editor_controller::right_drag_end(int x, int y, const bool /*browse*/)
1574{
1575 auto a = get_mouse_action().drag_end_right(*gui_, x, y);
1576 perform_delete(std::move(a));
1577}
1578
1579void editor_controller::right_mouse_up(int x, int y, const bool browse)
1580{
1581 // Call base method to handle context menus.
1582 mouse_handler_base::right_mouse_up(x, y, browse);
1583
1584 auto a = get_mouse_action().up_right(*gui_, x, y);
1585 if(a) {
1586 perform_delete(std::move(a));
1587 set_button_state();
1588 }
1589 toolkit_->set_mouseover_overlay();
1590 context_manager_->refresh_after_action();
1591}
1592
1593void editor_controller::terrain_description()
1594{
1595 const map_location& loc = gui().mouseover_hex();
1596 if (get_current_map_context().map().on_board(loc) == false)
1597 return;
1598
1599 const terrain_type& type = get_current_map_context().map().get_terrain_info(loc);
1601}
1602
1603void editor_controller::process_keyup_event(const SDL_Event& event)
1604{
1605 auto a = get_mouse_action().key_event(gui(), event);
1606 perform_refresh_delete(std::move(a));
1607 toolkit_->set_mouseover_overlay();
1608}
1609
1610hotkey::command_executor * editor_controller::get_hotkey_command_executor() {
1611 return this;
1612}
1613
1614void editor_controller::scroll_up(bool on)
1615{
1617}
1618
1619void editor_controller::scroll_down(bool on)
1620{
1622}
1623
1624void editor_controller::scroll_left(bool on)
1625{
1627}
1628
1629void editor_controller::scroll_right(bool on)
1630{
1632}
1633
1634std::vector<std::string> editor_controller::additional_actions_pressed()
1635{
1636 return toolkit_->get_palette_manager()->active_palette().action_pressed();
1637}
1638
1639} //end namespace editor
Editor action classes.
Editor action classes.
map_location loc
Definition move.cpp:172
static auto & dummy
A config object defines a single node in a WML file, with access to child nodes.
Definition config.hpp:157
void set_scroll_up(bool on)
void set_scroll_left(bool on)
void set_scroll_right(bool on)
const game_config_view & game_config_
void set_scroll_down(bool on)
virtual void play_slice()
Sort-of-Singleton that many classes, both GUI and non-GUI, use to access the game data.
Definition display.hpp:88
void toggle_debug_flag(DEBUG_FLAG flag)
Definition display.hpp:926
void change_display_context(const display_context *dc)
Definition display.cpp:439
void add_redraw_observer(const std::function< void(display &)> &f)
Adds a redraw observer, a function object to be called when a full rerender is queued.
Definition display.cpp:2164
@ DEBUG_COORDINATES
Overlays x,y coords on tiles.
Definition display.hpp:898
@ DEBUG_NUM_BITMAPS
Overlays number of bitmaps on tiles.
Definition display.hpp:904
@ DEBUG_TERRAIN_CODES
Overlays terrain codes on tiles.
Definition display.hpp:901
void invalidate_all()
Function to invalidate all tiles.
Definition display.cpp:2920
surface screenshot(bool map_screenshot=false)
Capture a (map-)screenshot into a surface.
Definition display.cpp:723
void queue_rerender()
Marks everything for rendering including all tiles and sidebar.
Definition display.cpp:2105
void set_debug_flag(DEBUG_FLAG flag, bool value)
Definition display.hpp:921
const map_location & mouseover_hex() const
Definition display.hpp:302
Container action wrapping several actions into one.
Definition action.hpp:88
Paint the same terrain on a number of locations on the map.
Definition action.hpp:266
Randomize terrain in an area.
Definition action.hpp:399
The editor_controller class contains the mouse and keyboard event handling routines for the editor.
const std::unique_ptr< context_manager > context_manager_
void refresh_image_cache()
Reload images.
editor_display & gui() override
Reference to the used display objects.
void unit_editor_dialog()
Show Unit Editor dialog.
bool can_execute_command(const hotkey::ui_command &command) const override
command_executor override
bool quit_confirm()
Show a quit confirmation dialog and returns true if the user pressed 'yes'.
void update_map_schedule(const std::vector< time_of_day > &schedule)
Updates schedule and the map display.
void cut_selection()
Cut the selection from the current map to the clipboard.
bool keep_menu_open() const override
command_executor override
void display_redraw_callback(display &)
Callback function passed to display to be called on queue_rerender.
std::unique_ptr< font::floating_label_context > floating_label_manager_
menu_type active_menu_
The currently invoked dropdown menu.
void undo() override
Undos an action in the current map context.
void select_addon()
Show dialog to select active addon or create a new one.
map_context & get_current_map_context() const
void init_tods(const game_config_view &game_config)
init the available time-of-day settings
bool initialize_addon()
Show dialog to select active addon or create a new one if one is not yet initialized.
std::unique_ptr< editor_toolkit > toolkit_
std::vector< std::shared_ptr< sound::music_track > > music_tracks_
const std::unique_ptr< editor_display > gui_
The display object used and owned by the editor.
bool do_execute_command(const hotkey::ui_command &command, bool press=true, bool release=false) override
command_executor override
void export_selection_coords()
Export the WML-compatible list of selected tiles to the system clipboard.
void perform_delete(std::unique_ptr< editor_action > action)
Perform an action, then delete the action object.
std::vector< std::unique_ptr< map_generator > > map_generators_
Available random map generators.
EXIT_STATUS main_loop()
Editor main loop.
editor_controller(const editor_controller &)=delete
void custom_tods_dialog()
Display the settings dialog, used to control e.g.
void do_screenshot(const std::string &screenshot_filename="map_screenshot.png")
Takes a screenshot.
void save_area()
Save the current selection to the active area.
void copy_selection()
Copy the selection on the current map to the clipboard.
void init_gui()
init the display object and general set-up
hotkey::action_state get_action_state(const hotkey::ui_command &command) const override
command_executor override
void show_menu(const std::vector< config > &items_arg, const point &menu_loc, bool context_menu) override
command_executor override
static std::string current_addon_id_
void add_area()
Add a new area to the current context, filled with the selection if any.
const mouse_action & get_mouse_action() const
Get the current mouse action.
bool do_quit_
Quit main loop flag.
void save_map() override
Save the map, open dialog if not named yet.
void set_help_string_enabled(bool value)
Sets whether the help text should be shown.
bool everything_selected() const
bool anything_selected() const
std::size_t num_selected() const
List of starting locations and location ids.
void remove_side()
removes the last side from the scenario
void set_active_area(int index)
void set_local_starting_time(int time)
int get_active_area() const
map_labels & get_labels()
void toggle_track(const std::shared_ptr< sound::music_track > &track)
Remove the given track from the current playlist if present, else appends it.
void new_side()
Adds a new side to the map.
const std::string & get_filename() const
void set_starting_time(int time)
virtual const editor_map & map() const override
Const map accessor.
virtual const unit_map & units() const override
Const units accessor.
void replace_schedule(const std::vector< time_of_day > &schedule)
void remove_area(int index)
const tod_manager * get_time_manager() const
void select_area(int index)
Select the nth tod area.
void replace_local_schedule(const std::vector< time_of_day > &schedule)
Replace the [time]s of the currently active area.
void partial_undo()
Un-does a single step from a undo action chain.
virtual const std::vector< team > & teams() const override
Const teams accessor.
void set_starting_position_labels(display &disp)
bool is_pure_map() const
void save_schedule(const std::string &schedule_id, const std::string &schedule_name)
Save custom time of day schedule in the utils directory.
A map fragment – a collection of locations and information abut them.
virtual bool supports_brushes() const
Whether we need the brush bar, is used to grey it out.
A class grating read only view to a vector of config objects, viewed as one config with all children ...
Dialog that allows user to create custom unit types.
Definition edit_unit.hpp:33
file_dialog & set_path(const std::string &value)
Sets the initial file selection.
std::string path() const
Gets the current file selection.
file_dialog & set_title(const std::string &value)
Sets the current dialog title text.
@ auto_close
Enables auto close.
Definition message.hpp:71
static std::unique_ptr< units_dialog > build_unit_list_dialog(std::vector< unit_const_ptr > &units_list)
virtual bool do_execute_command(const hotkey::ui_command &command, bool press=true, bool release=false)
virtual bool in_context_menu(const hotkey::ui_command &) const
Determines whether the command should be in the context menu or not.
void recalculate_labels()
Definition label.cpp:246
static prefs & get()
Implements a quit confirmation dialog.
static bool show_prompt(const std::string &message)
static bool quit()
Shows the quit confirmation if needed.
static void quit_to_desktop()
static std::shared_ptr< music_track > create(const config &cfg)
const std::vector< time_of_day > & times() const
std::vector< std::string > get_area_ids() const
const std::set< map_location > & get_area_by_index(int index) const
int get_current_time() const
Container associating units to locations.
Definition map.hpp:98
unit_iterator end()
Definition map.hpp:428
bool empty() const
Definition map.hpp:445
unit_iterator find(std::size_t id)
Definition map.cpp:302
unit_iterator begin()
Definition map.hpp:418
Editor action classes.
#define LOG_ED
#define ERR_ED
#define SCOPE_ED
std::vector< std::reference_wrapper< const config > > config_array_view
#define N_(String)
Definition gettext.hpp:108
static std::string _(const char *str)
Definition gettext.hpp:100
std::string label
What to show in the filter's drop-down list.
Definition manager.cpp:201
Contains functions for cleanly handling SDL input.
static bool timestamp
Definition log.cpp:89
map_generator * create_map_generator(const std::string &name, const config &cfg, const config *vars)
auto format_local_timestamp(const std::chrono::system_clock::time_point &time, std::string_view format="%F %T")
Definition chrono.hpp:72
@ NORMAL
Definition cursor.hpp:28
void set(CURSOR_TYPE type)
Use the default parameter to reset cursors.
Definition cursor.cpp:151
void copy_to_clipboard(const std::string &text)
Copies text to the clipboard.
Definition clipboard.cpp:24
constexpr bool open_object_is_supported()
Returns whether open_object() is supported/implemented for the current platform.
Definition open.hpp:54
bool open_object(const std::string &path_or_url)
Opens the specified object with the default application configured for its type.
Definition open.cpp:50
Manage the empty-palette in the editor.
Definition action.cpp:31
@ EXIT_RELOAD_DATA
const t_translation::terrain_code & get_selected_bg_terrain()
std::string initialize_addon()
bool is_cfg(const std::string &filename)
Returns true if the file ends with the wmlfile extension.
bool is_map(const std::string &filename)
Returns true if the file ends with the mapfile extension.
std::string get_current_editor_dir(const std::string &addon_id)
Game configuration data as global variables.
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
General purpose widgets.
void show_terrain_description(const terrain_type &t)
Definition help.cpp:74
void show_unit_description(const unit &u)
Definition help.cpp:64
void show_help(const std::string &show_topic)
Open the help browser.
Definition help.cpp:84
Keyboard shortcuts for game actions.
action_state on_if(bool condition)
Returns action_state::on if condition is true, else action_state::off.
action_state selected_if(bool condition)
Returns action_state::selected if condition is true, else action_state::deselected.
void flush_cache()
Purges all image caches.
Definition picture.cpp:210
save_result save_image(const locator &i_locator, const std::string &filename)
Definition picture.cpp:921
Unit and team statistics.
::tod_manager * tod_manager
Definition resources.cpp:29
game_classification * classification
Definition resources.cpp:34
filter_context * filter_con
Definition resources.cpp:23
uint32_t get_mouse_button_mask()
Returns the current mouse button mask.
Definition input.cpp:44
void play_music_once(const std::string &file)
Definition sound.cpp:596
STL namespace.
Desktop environment interaction functions.
@ partial
There are still moves and/or attacks possible, but the unit doesn't fit in the "unmoved" status.
#define ON_SCOPE_EXIT(...)
Run some arbitrary code (a lambda) when the current scope exits The lambda body follows this header,...
structure which will hide all current floating labels, and cause floating labels instantiated after i...
Used as the main parameter for can_execute_command/do_execute_command These functions are used to exe...
hotkey::HOTKEY_COMMAND hotkey_command
The hotkey::HOTKEY_COMMAND associated with this action, HOTKEY_NULL for actions that don't allow hotk...
int index
When this action was the result of a menu click, this is the index of the clicked item in the menu.
Encapsulates the map of the game.
Definition location.hpp:46
bool valid() const
Definition location.hpp:111
direction
Valid directions which can be moved in our hexagonal world.
Definition location.hpp:48
static std::string write_translated_direction(direction dir)
Definition location.cpp:174
Holds a 2D point.
Definition point.hpp:25
Helper class, don't construct this directly.
Add a special kind of assert to validate whether the input from WML doesn't contain any problems that...
#define i
#define e
#define c