editor.cpp

Go to the documentation of this file.
00001 /* $Id: editor.cpp 26769 2008-05-22 13:21:04Z mog $ */
00002 /*
00003   Copyright (C) 2003 - 2008 by David White <dave@whitevine.net>
00004   Part of the Battle for Wesnoth Project http://www.wesnoth.org/
00005 
00006   This program is free software; you can redistribute it and/or modify
00007   it under the terms of the GNU General Public License version 2
00008   or at your option any later version.
00009   This program is distributed in the hope that it will be useful,
00010   but WITHOUT ANY WARRANTY.
00011 
00012   See the COPYING file for more details.
00013 */
00014 
00015 //! @file editor/editor.cpp
00016 //! Map-editor.
00017 
00018 #include "SDL.h"
00019 #include "SDL_keysym.h"
00020 
00021 #include "../config.hpp"
00022 #include "../construct_dialog.hpp"
00023 #include "../cursor.hpp"
00024 #include "../file_chooser.hpp"
00025 #include "../filesystem.hpp"
00026 #include "../font.hpp"
00027 #include "../game_config.hpp"
00028 #include "../gettext.hpp"
00029 #include "../key.hpp"
00030 #include "../language.hpp"
00031 #include "../widgets/menu.hpp"
00032 #include "../pathfind.hpp"
00033 
00034 #include "../preferences.hpp"
00035 #include "../sdl_utils.hpp"
00036 #include "../tooltips.hpp"
00037 #include "../team.hpp"
00038 #include "../util.hpp"
00039 #include "../video.hpp"
00040 #include "../wml_separators.hpp"
00041 #include "../wml_exception.hpp"
00042 #include "serialization/parser.hpp"
00043 #include "serialization/string_utils.hpp"
00044 
00045 #include "editor.hpp"
00046 #include "map_manip.hpp"
00047 #include "editor_dialogs.hpp"
00048 #include "editor_palettes.hpp"
00049 
00050 #include <cctype>
00051 #include <iostream>
00052 #include <map>
00053 #include <vector>
00054 #include <string>
00055 #include <cmath>
00056 
00057 namespace {
00058     static std::string wm_title_string;
00059 
00060     // Milliseconds to sleep in every iteration of the main loop.
00061     const unsigned int sdl_delay = 20;
00062     const std::string prefs_filename = get_dir(get_user_data_dir() + "/editor")
00063         + "/preferences";
00064 
00065     // Return the side that has it's starting position at hex,
00066     // or -1 if none has.
00067     int starting_side_at(const gamemap& map, const gamemap::location hex) {
00068         int start_side = -1;
00069         for (int i = 0; i < gamemap::MAX_PLAYERS+1; i++) {
00070             if (map.starting_position(i) == hex) {
00071                 start_side = i;
00072             }
00073         }
00074         return start_side;
00075     }
00076 }
00077 
00078 namespace map_editor {
00079 
00080 bool check_data(std::string &data, std::string &filename, bool &from_scenario, config &game_cfg)
00081 {
00082     std::string file_content = data;
00083 
00084     if (valid_mapdata(file_content, game_cfg)) {
00085         from_scenario = false;
00086         return true;
00087     }
00088 
00089     std::string::size_type start, stop;
00090     start = file_content.find("map_data=");
00091     if (start==std::string::npos)
00092         return false;
00093     start += 10;
00094     stop = file_content.find('\"', start);
00095     if (stop==std::string::npos)
00096         return false;
00097     std::string nfname;
00098     std::string newfilename = std::string(file_content, start, stop-start);
00099     if (newfilename[0]=='{' && newfilename[newfilename.size()-1]=='}') {
00100         newfilename.erase(newfilename.begin());
00101         newfilename.erase(newfilename.end() - 1);
00102 
00103         // If the filename begins with a '~', then look in the user's data directory.
00104         // If the filename begins with a '@' then we look in the user's data directory,
00105         // but default to the standard data directory if it's not found there.
00106         if(newfilename != "" && (newfilename[0] == '~' || newfilename[0] == '@')) {
00107             nfname = newfilename;
00108             nfname.erase(nfname.begin(),nfname.begin()+1);
00109             nfname = get_user_data_dir() + "/data/" + nfname;
00110 
00111             std::cout << "got relative name '" << newfilename << "' -> '" << nfname << "'\n";
00112 
00113             if(newfilename[0] == '@' && file_exists(nfname) == false && is_directory(nfname) == false) {
00114                 nfname = "data/" + newfilename.substr(1);
00115             }
00116         } else
00117         if(newfilename.size() >= 2 && newfilename[0] == '.' &&
00118             newfilename[1] == '/' ) {
00119             // If the filename begins with a "./",
00120             // then look in the same directory
00121             // as the file currrently being preprocessed
00122             nfname = newfilename;
00123             nfname.erase(nfname.begin(),nfname.begin()+2);
00124             nfname = directory_name(filename) + nfname;
00125 
00126         } else {
00127                 nfname = "data/" + newfilename;
00128         }
00129 
00130         data = read_file(nfname);
00131         filename = nfname;
00132         from_scenario = false;
00133     } else {
00134         data = newfilename;
00135         from_scenario = true;
00136     }
00137     return true;
00138 }
00139 
00140 // The map_editor object will be recreated when operations that affect
00141 // the whole map takes place. It may not be the most beautiful solution,
00142 // but it is the way the least interference with the game system is
00143 // needed. That is the reason we need some static variables to handle
00144 // things that should be permanent through the program's life time.
00145 // Of course, the functionality of this assumes that no more than one
00146 // map_editor object will exist, but that is a reasonable restriction imho.
00147 bool map_editor::first_time_created_ = true;
00148 int map_editor::num_operations_since_save_ = 0;
00149 config map_editor::prefs_;
00150 config map_editor::hotkeys_;
00151 // Do not init the l_button_func_ to DRAW, since it should be changed
00152 // in the constructor to update the report the first time.
00153 map_editor::LEFT_BUTTON_FUNC map_editor::l_button_func_ = PASTE;
00154 t_translation::t_terrain map_editor::old_fg_terrain_;
00155 t_translation::t_terrain map_editor::old_bg_terrain_;
00156 int map_editor::old_brush_size_;
00157 
00158 map_editor::map_editor(editor_display &gui, editormap &map, config &theme, config &game_config)
00159     : gui_(gui), map_(map), abort_(DONT_ABORT),
00160       theme_(theme), game_config_(game_config), map_dirty_(false),  auto_update_(true), l_button_palette_dirty_(true),
00161       everything_dirty_(false), palette_(gui, size_specs_, map, game_config), brush_(gui, size_specs_),
00162       l_button_held_func_(NONE), tooltip_manager_(gui_.video()), floating_label_manager_(),
00163       mouse_moved_(false),
00164       highlighted_locs_cleared_(false), prefs_disp_manager_(&gui_), all_hexes_selected_(false) {
00165 
00166     // Set size specs.
00167     adjust_sizes(gui_, size_specs_);
00168     if (first_time_created_) {
00169         // Perform some initializations that should only be performed
00170         // the first time the editor object is created.
00171         try {
00172             scoped_istream stream = istream_file(prefs_filename);
00173             read(prefs_, *stream);
00174         }
00175         catch (config::error e) {
00176             std::cerr << "Error when reading " << prefs_filename << ": "
00177                       << e.message << std::endl;
00178         }
00179         hotkey::load_hotkeys(theme_);
00180         hotkey::load_hotkeys(prefs_);
00181         left_button_func_changed(DRAW);
00182         first_time_created_ = false;
00183     }
00184     else {
00185         hotkey::load_hotkeys(hotkeys_);
00186 
00187         //! @todo FIXME: saved hotkeys don't reload correctly,
00188         //! so we just reread the theme hotkeys every time
00189         hotkey::load_hotkeys(theme_);
00190 
00191         palette_.select_fg_terrain(old_fg_terrain_);
00192         palette_.select_bg_terrain(old_bg_terrain_);
00193         brush_.select_brush_size(old_brush_size_);
00194     }
00195 
00196     hotkey::load_descriptions();
00197     recalculate_starting_pos_labels();
00198     gui_.invalidate_game_status();
00199     //gui_.begin_game();
00200     gui_.invalidate_all();
00201     gui_.draw();
00202     palette_.adjust_size();
00203     brush_.adjust_size();
00204     events::raise_draw_event();
00205     redraw_everything();
00206     load_tooltips();
00207 }
00208 
00209 
00210 //! @todo
00211 //! This should be replaced by a WML tag called 'tooltip='
00212 //! in the data/themes/editor.cfg file.
00213 //! The theme and display classes should then load
00214 //! the given tooltip in the button.
00215 void map_editor::load_tooltips()
00216 {
00217     // Clear all previous tooltips
00218     tooltips::clear_tooltips();
00219 
00220     // Add tooltips to all buttons
00221     const theme &t = gui_.get_theme();
00222     const std::vector<theme::menu> &menus = t.menus();
00223     std::vector<theme::menu>::const_iterator it;
00224     for (it = menus.begin(); it != menus.end(); it++) {
00225 
00226         // Get the button's screen location
00227         SDL_Rect screen = gui_.screen_area();
00228 
00229         const SDL_Rect tooltip_rect = (*it).location(screen);
00230         std::string text = "";
00231 
00232         const std::vector<std::string> &menu_items = (*it).items();
00233         if (menu_items.size() == 1) {
00234             if(menu_items.back() == "editdraw")
00235                 text = _("Draw tiles");
00236             else if(menu_items.back() == "editfloodfill")
00237                 text = _("Fill");
00238             else if(menu_items.back() == "editsetstartpos")
00239                 text = _("Set player's starting position");
00240             else if(menu_items.back() == "zoomin")
00241                 text = _("Zoom in");
00242             else if(menu_items.back() == "zoomout")
00243                 text = _("Zoom out");
00244             else if(menu_items.back() == "undo")
00245                 text = _("Undo");
00246             else if(menu_items.back() == "redo")
00247                 text = _("Redo");
00248             else if(menu_items.back() == "zoomdefault")
00249                 text = _("Zoom to default view");
00250             else if(menu_items.back() == "togglegrid")
00251                 text = _("Toggle grid");
00252             else if(menu_items.back() == "editresize")
00253                 text = _("Resize the map");
00254             else if(menu_items.back() == "editflip")
00255                 text = _("Flip map");
00256             else if(menu_items.back() == "editupdate")
00257                 text = _("Update transitions");
00258             else if(menu_items.back() == "editautoupdate")
00259                 text = _("Delay transition updates");
00260         }
00261 
00262         if(text != "")
00263             tooltips::add_tooltip(tooltip_rect, text);
00264     }
00265 
00266     // Tooltips for the groups
00267     palette_.load_tooltips();
00268 }
00269 
00270 map_editor::~map_editor() {
00271     // Save the hotkeys so that they are remembered if the editor is recreated.
00272     hotkey::save_hotkeys(hotkeys_);
00273     old_fg_terrain_ = palette_.selected_fg_terrain();
00274     old_bg_terrain_ = palette_.selected_bg_terrain();
00275     old_brush_size_ = brush_.selected_brush_size();
00276     try {
00277         scoped_ostream prefs_file = ostream_file(prefs_filename);
00278         write(*prefs_file, prefs_);
00279     }
00280     catch (io_exception& e) {
00281         std::cerr << "Error when writing to " << prefs_filename << ": "
00282                   << e.what() << std::endl;
00283     }
00284 }
00285 
00286 void map_editor::handle_event(const SDL_Event &event) {
00287     if (event.type == SDL_VIDEORESIZE) {
00288         everything_dirty_ = true;
00289     }
00290     int mousex, mousey;
00291 
00292     SDL_GetMouseState(&mousex,&mousey);
00293     const SDL_KeyboardEvent keyboard_event = event.key;
00294     handle_keyboard_event(keyboard_event, mousex, mousey);
00295 
00296     const SDL_MouseButtonEvent mouse_button_event = event.button;
00297     handle_mouse_button_event(mouse_button_event, mousex, mousey);
00298 
00299     tooltips::process(mousex, mousey);
00300 }
00301 
00302 void map_editor::handle_keyboard_event(const SDL_KeyboardEvent &event,
00303                                        const int /*mousex*/, const int /*mousey*/) {
00304     if (event.type == SDL_KEYDOWN) {
00305         const SDLKey sym = event.keysym.sym;
00306         // We must intercept escape-presses here,
00307         // because we don't want the default shutdown behavior,
00308         // we want to ask for saving.
00309         if (sym == SDLK_ESCAPE) {
00310             set_abort();
00311         }
00312         else {
00313             const bool old_fullscreen = preferences::fullscreen();
00314             const std::pair<int, int> old_resolution = preferences::resolution();
00315             hotkey::key_event(gui_, event, this);
00316             // A key event may change the video mode.
00317             // The redraw functionality inside the preferences module
00318             // does not redraw our palettes, so we need to check
00319             // if the mode has changed and if so redraw everything.
00320             if (preferences::fullscreen() != old_fullscreen
00321                 || old_resolution != preferences::resolution()) {
00322                 everything_dirty_ = true;
00323             }
00324         }
00325     }
00326 }
00327 
00328 void map_editor::handle_mouse_button_event(const SDL_MouseButtonEvent &event,
00329                                            const int mousex, const int mousey) {
00330     if (event.type == SDL_MOUSEBUTTONDOWN) {
00331         const Uint8 button = event.button;
00332         int scrollx = 0;
00333         int scrolly = 0;
00334         if (button == SDL_BUTTON_RIGHT) {
00335             selected_hex_ = gui_.hex_clicked_on(mousex, mousey);
00336             const theme::menu* const m = gui_.get_theme().context_menu();
00337             if (m != NULL) {
00338                 show_menu(m->items(), mousex, mousey + 1, true);
00339             }
00340         }
00341         if (button == SDL_BUTTON_LEFT) {
00342             gamemap::location hex_clicked = gui_.hex_clicked_on(mousex, mousey);
00343             if (map_.on_board(hex_clicked, true)) {
00344                 left_click(hex_clicked);
00345             }
00346         }
00347         if (button == SDL_BUTTON_RIGHT) {
00348             gamemap::location hex_clicked = gui_.hex_clicked_on(mousex, mousey);
00349             if (map_.on_board(hex_clicked, true)) {
00350                 right_click(hex_clicked);
00351             }
00352         }
00353         // Mimic the game's behavior on middle click and mouse wheel movement.
00354         // It would be nice to have had these in functions provided from the game,
00355         // but I don't want to interfer too much with the game code
00356         // and it's fairly simple stuff to rip.
00357         if (button == SDL_BUTTON_MIDDLE) {
00358             const SDL_Rect& rect = gui_.map_area();
00359             const int centerx = (rect.x + rect.w) / 2;
00360             const int centery = (rect.y + rect.h) / 2;
00361 
00362             const int xdisp = mousex - centerx;
00363             const int ydisp = mousey - centery;
00364             gui_.scroll(xdisp, ydisp);
00365         }
00366         if (point_in_rect(mousex, mousey, gui_.map_outside_area())) {
00367             if (event.button == SDL_BUTTON_WHEELUP) {
00368                 scrolly = - preferences::scroll_speed();
00369             } else if (event.button == SDL_BUTTON_WHEELDOWN) {
00370                 scrolly = preferences::scroll_speed();
00371             } else if (event.button == SDL_BUTTON_WHEELLEFT) {
00372                 scrollx = - preferences::scroll_speed();
00373             } else if (event.button == SDL_BUTTON_WHEELRIGHT) {
00374                 scrollx = preferences::scroll_speed();
00375             }
00376         }
00377 
00378         if (scrollx != 0 || scrolly != 0) {
00379         CKey pressed;
00380         // Alt + mousewheel do an 90° rotation on the scroll direction
00381         if (pressed[SDLK_LALT] || pressed[SDLK_RALT])
00382             gui_.scroll(scrolly,scrollx);
00383         else
00384             gui_.scroll(scrollx,scrolly);
00385         }
00386 
00387     }
00388     if (event.type == SDL_MOUSEBUTTONUP) {
00389         // If we miss the mouse up event, we need to perform the actual
00390         // movement if we are in the progress of moving a selection.
00391         if (l_button_held_func_ == MOVE_SELECTION) {
00392             perform_selection_move();
00393         }
00394         l_button_held_func_ = NONE;
00395     }
00396 }
00397 
00398 void map_editor::left_click(const gamemap::location hex_clicked) {
00399     if (key_[SDLK_LSHIFT] || key_[SDLK_RSHIFT]) {
00400         if (selected_hexes_.find(hex_clicked)
00401             == selected_hexes_.end()) {
00402             l_button_held_func_ = ADD_SELECTION;
00403         }
00404         else {
00405             l_button_held_func_ = REMOVE_SELECTION;
00406         }
00407     }
00408     else if (key_[SDLK_RCTRL] || key_[SDLK_LCTRL]) {
00409         const t_translation::t_terrain terrain = map_.get_terrain(selected_hex_);
00410         if(palette_.selected_fg_terrain() != terrain) {
00411             palette_.select_fg_terrain(terrain);
00412         }
00413         l_button_held_func_ = NONE;
00414     }
00415     else if ( (key_[SDLK_RALT] || key_[SDLK_LALT]) && l_button_func_ == DRAW) {
00416         reset_mouseover_overlay();
00417         draw_on_mouseover_hexes(palette_.selected_fg_terrain(), true);
00418         l_button_held_func_ = DRAW_TERRAIN_LAYER;
00419     }
00420     else if (selected_hexes_.find(hex_clicked) != selected_hexes_.end()) {
00421         l_button_held_func_ = MOVE_SELECTION;
00422         selection_move_start_ = hex_clicked;
00423     }
00424     else if (!selected_hexes_.empty()) {
00425         // If hexes are selected, clear them
00426         // and do not draw anything.
00427         selected_hexes_.clear();
00428         clear_highlighted_hexes_in_gui();
00429     }
00430     else if (l_button_func_ == DRAW) {
00431         reset_mouseover_overlay();
00432         draw_on_mouseover_hexes(palette_.selected_fg_terrain());
00433         l_button_held_func_ = DRAW_TERRAIN;
00434     }
00435     else if (l_button_func_ == FLOOD_FILL) {
00436         perform_flood_fill(palette_.selected_fg_terrain());
00437     }
00438     else if (l_button_func_ == SET_STARTING_POSITION) {
00439         perform_set_starting_pos();
00440     }
00441     else if (l_button_func_ == PASTE) {
00442         perform_paste();
00443     }
00444 }
00445 
00446 void map_editor::right_click(const gamemap::location hex_clicked ) {
00447     if (key_[SDLK_RCTRL] || key_[SDLK_LCTRL]) {
00448         const t_translation::t_terrain terrain = map_.get_terrain(hex_clicked);
00449         if(palette_.selected_fg_terrain() != terrain) {
00450             palette_.select_bg_terrain(terrain);
00451         }
00452     }
00453     else if (l_button_func_ == FLOOD_FILL) {
00454         perform_flood_fill(palette_.selected_bg_terrain());
00455     }
00456 }
00457 
00458 
00459 //! Change the language (effectively reload the editor).
00460 void map_editor::change_language() {
00461     std::vector<language_def> langdefs = get_languages();
00462 
00463     // This only works because get_languages() returns a fresh vector
00464     // at each calls unless show_gui cleans the "*" flag.
00465     const std::vector<language_def>::iterator current = std::find(langdefs.begin(),langdefs.end(),get_language());
00466     if(current != langdefs.end()) {
00467         (*current).language = "*" + (*current).language;
00468     }
00469 
00470     // Prepare a copy with just the labels for the list to be displayed
00471     std::vector<std::string> langs;
00472     langs.reserve(langdefs.size());
00473     std::transform(langdefs.begin(),langdefs.end(),std::back_inserter(langs),languagedef_name);
00474 
00475     const std::string language = _("Language");
00476     const std::string preferred = _("Choose your preferred language:");
00477     gui::dialog lmenu = gui::dialog(gui_, language, preferred,
00478                       gui::OK_CANCEL);
00479     lmenu.set_menu(langs);
00480     int res = lmenu.show();
00481     const std::vector<language_def>& languages = get_languages();
00482     if(size_t(res) < languages.size()) {
00483 		::set_language(languages[res]);
00484         preferences::set_language(languages[res].localename);
00485 
00486         game_config_.reset_translation();
00487 
00488         // Reload tooltips and menu items
00489         load_tooltips();
00490     }
00491 
00492     // Update the frame title
00493     wm_title_string = _("Battle for Wesnoth Map Editor");
00494     wm_title_string += " - " + game_config::revision;
00495     SDL_WM_SetCaption(wm_title_string.c_str(), NULL);
00496 
00497     font::load_font_config();
00498     hotkey::load_descriptions();
00499 
00500     // To reload the terrain names, we need to reload the configuration file
00501     editormap new_map(game_config_, map_.write());
00502     map_ = new_map;
00503 
00504     // Update the selected terrain strings
00505     palette_.update_selected_terrains();
00506 }
00507 
00508 void map_editor::edit_save_as() {
00509     const std::string default_dir =
00510         get_dir(get_dir(get_user_data_dir() + "/editor") + "/maps");
00511     std::string input_name = filename_.empty() ? default_dir : filename_;
00512     const std::string old_input_name = input_name;
00513 
00514     int res = 0;
00515     int overwrite = 1;
00516     do {
00517         input_name = old_input_name;
00518         res = dialogs::show_file_chooser_dialog(gui_, input_name, _("Save the Map As"));
00519         if (res == 0) {
00520 
00521             // Check whether the filename contains illegal characters
00522             if(!verify_filename(input_name, true))
00523             {
00524                 input_name = old_input_name;
00525                 continue;
00526             }
00527             else if (file_exists(input_name)) {
00528                 overwrite = gui::dialog(gui_, "",
00529                     _("The map already exists. Do you want to overwrite it?"),
00530                     gui::YES_NO).show();
00531             }
00532             else
00533                 overwrite = 0;
00534         }
00535     } while (res == 0 && overwrite != 0);
00536 
00537     // Try to save the map, if it fails we reset the filename
00538     if (res == 0) {
00539         std::string old_file_name = filename_;
00540         set_file_to_save_as(input_name, from_scenario_);
00541         if(!save_map("", true))
00542         {
00543             filename_ = old_file_name;
00544         }
00545     }
00546 }
00547 
00548 void map_editor::perform_set_starting_pos() {
00549     std::vector<std::string> players;
00550 
00551     std::stringstream none_str;
00552     none_str << _("None");
00553     players.push_back(none_str.str());
00554 
00555     for (int i = 0; i < gamemap::MAX_PLAYERS; i++) {
00556         std::stringstream str;
00557         str << _("Player") << " " << i + 1;
00558         players.push_back(str.str());
00559     }
00560     gui::dialog pmenu = gui::dialog(gui_,
00561                        _("Which Player?"),
00562                        _("Which player should start here?"),
00563                        gui::OK_CANCEL);
00564     pmenu.set_menu(players);
00565     int res = pmenu.show();
00566     if (res >= 0) {
00567         // We erase previous starting position on this hex.
00568         // This will prevent to cause a "stack" of these.
00569         //! @todo TODO: only use 1 undo to revert it (instead of 2)
00570         const int current_side = starting_side_at(map_, selected_hex_);
00571         if (current_side != -1) {
00572             set_starting_position(current_side, gamemap::location());
00573         }
00574         if (res > 0) {
00575             set_starting_position(res, selected_hex_);
00576         }
00577     }
00578 }
00579 
00580 void map_editor::edit_set_start_pos() {
00581     left_button_func_changed(SET_STARTING_POSITION);
00582 }
00583 
00584 void map_editor::perform_flood_fill(const t_translation::t_terrain fill_with) {
00585 
00586     t_translation::t_terrain fill_with_final = map_.get_terrain_info(fill_with).terrain_with_default_base();
00587 
00588     terrain_log log;
00589     flood_fill(map_, selected_hex_, fill_with_final, &log);
00590     map_undo_action action;
00591     for (terrain_log::iterator it = log.begin(); it != log.end(); it++) {
00592         action.add_terrain((*it).second, palette_.selected_fg_terrain(),
00593                (*it).first);
00594         terrain_changed((*it).first);
00595     }
00596 
00597     save_undo_action(action);
00598 }
00599 
00600 void map_editor::edit_flood_fill() {
00601     left_button_func_changed(FLOOD_FILL);
00602 }
00603 
00604 void map_editor::edit_save_map() {
00605     save_map("", true);
00606 }
00607 
00608 void map_editor::edit_quit() {
00609     set_abort();
00610 }
00611 
00612 void map_editor::edit_new_map() {
00613     t_translation::t_terrain bg_fill = map_.get_terrain_info(palette_.selected_bg_terrain()).terrain_with_default_base();
00614 
00615     const std::string map = new_map_dialog(gui_, bg_fill,
00616                                            changed_since_save(), game_config_);
00617     if (map != "") {
00618         num_operations_since_save_ = 0;
00619         clear_undo_actions();
00620         throw new_map_exception(map);
00621     }
00622 }
00623 
00624 void map_editor::edit_load_map() {
00625     std::string fn = filename_.empty() ?
00626         get_dir(get_dir(get_user_data_dir() + "/editor") + "/maps") : filename_;
00627     int res = dialogs::show_file_chooser_dialog(gui_, fn, _("Choose a Map to Load"));
00628     if (res == 0) {
00629         // Check if the mapname contains any illegal characters
00630         if(!verify_filename(fn, false)) {
00631             gui::message_dialog(gui_, "", _("Warning: Illegal characters found in the map name. Please save under a different name.")).show();
00632         }
00633 
00634         std::string new_map = read_file(fn);
00635         bool scenario;
00636         if (check_data(new_map, fn, scenario, game_config_)) {
00637             if (!changed_since_save() || confirm_modification_disposal(gui_)) {
00638                 num_operations_since_save_ = 0;
00639                 clear_undo_actions();
00640                 throw new_map_exception(new_map, fn, scenario);
00641             }
00642         }
00643         else {
00644             gui::message_dialog(gui_, "", _("The file does not contain a valid map.")).show();
00645         }
00646     }
00647 }
00648 
00649 void map_editor::edit_fill_selection() {
00650     map_undo_action undo_action;
00651     perform_fill_hexes(selected_hexes_, palette_.selected_bg_terrain(), undo_action);
00652     save_undo_action(undo_action);
00653 }
00654 
00655 void map_editor::edit_cut() {
00656     edit_copy();
00657     edit_fill_selection();
00658 }
00659 
00660 void map_editor::edit_copy() {
00661     clear_buffer(clipboard_);
00662     insert_selection_in_clipboard();
00663 }
00664 
00665 void map_editor::perform_paste() {
00666     map_undo_action undo_action;
00667     paste_buffer(clipboard_, selected_hex_, undo_action);
00668     save_undo_action(undo_action);
00669 }
00670 
00671 void map_editor::edit_paste() {
00672     left_button_func_changed(PASTE);
00673 }
00674 
00675 void map_editor::edit_rotate_selection()
00676 {
00677     if (selected_hexes_.empty()) {return;}
00678 
00679     // we use the selected hex as center
00680     gamemap::location center = selected_hex_;
00681     if (!center.valid()) {
00682         // except if invalid (e.g the mouse is in menu)
00683         // then we search the "center of mass" 
00684         center = gamemap::location(0,0);
00685         std::set<gamemap::location>::const_iterator it;
00686         for(it = selected_hexes_.begin(); it != selected_hexes_.end(); it++) {
00687             center = center + *it;
00688         }
00689         center.x = center.x / selected_hexes_.size();
00690         center.y = center.y / selected_hexes_.size();
00691     }
00692 
00693     map_buffer buf;
00694     copy_buffer(buf, selected_hexes_, center);
00695 
00696     std::vector<buffer_item>::iterator it;
00697     for(it = buf.begin(); it != buf.end(); it++) {
00698         gamemap::location l(0,0);
00699         int x = it->offset.x;
00700         int y = it->offset.y;
00701         // rotate the X-Y axes to SOUTH/SOUTH_EAST - SOUTH_WEST axes
00702         // but if x is odd, simply using x/2 + x/2 will lack a step
00703         l = l.get_direction(gamemap::location::SOUTH, (x+is_odd(x))/2);
00704         l = l.get_direction(gamemap::location::SOUTH_EAST, (x-is_odd(x))/2 );
00705         l = l.get_direction(gamemap::location::SOUTH_WEST, y);
00706         it->offset = l;
00707     }
00708 
00709     map_undo_action undo_action;
00710     paste_buffer(buf, center, undo_action);
00711     save_undo_action(undo_action);
00712 }
00713 
00714 void map_editor::edit_revert() {
00715     std::string new_map = read_file(filename_);
00716     bool scenario;
00717     if (check_data(new_map, filename_, scenario, game_config_)) {
00718         map_undo_action action;
00719         action.set_map_data(map_.write(), new_map);
00720         save_undo_action(action);
00721         throw new_map_exception(new_map, filename_, scenario);
00722     }
00723     else {
00724         gui::message_dialog(gui_, "", _("The file does not contain a valid map.")).show();
00725     }
00726 }
00727 
00728 void map_editor::edit_resize() {
00729 
00730     unsigned width = map_.w(), height = map_.h();
00731     int x_offset = 0, y_offset = 0;
00732     bool do_expand = true;
00733     if(resize_dialog(gui_, width, height, x_offset, y_offset, do_expand)) {
00734 
00735         try {
00736             // we need the old map data for the undo so store it
00737             // before the map gets modified.
00738             const std::string old_data = map_.write();
00739 
00740             t_translation::t_terrain bg_fill = map_.get_terrain_info(palette_.selected_bg_terrain()).terrain_with_default_base();
00741             const std::string resized_map =
00742                 resize_map(map_, width, height, x_offset, y_offset,
00743                 do_expand, bg_fill);
00744 
00745             if (resized_map != "") {
00746                 map_undo_action action;
00747                 action.set_map_data(old_data, resized_map);
00748                 save_undo_action(action);
00749                 throw new_map_exception(resized_map, filename_, from_scenario_);
00750             }
00751         } catch (gamemap::incorrect_format_exception& e) {
00752             std::cerr << "ERROR: " << e.msg_ << '\n';
00753             gui::message_dialog(gui_, "", e.msg_).show();
00754         } catch(twml_exception& e) {
00755             e.show(gui_);
00756         }
00757     }
00758 }
00759 
00760 void map_editor::edit_flip() {
00761     const FLIP_AXIS flip_axis = flip_dialog(gui_);
00762     if (flip_axis != NO_FLIP) {
00763         const std::string flipped_map = flip_map(map_, flip_axis);
00764         map_undo_action action;
00765         action.set_map_data(map_.write(), flipped_map);
00766         save_undo_action(action);
00767         throw new_map_exception(flipped_map, filename_, from_scenario_);
00768     }
00769 }
00770 
00771 void map_editor::edit_select_all() {
00772     if (!all_hexes_selected_) {
00773         for (int i = 0; i < map_.w(); i++) {
00774             for (int j = 0; j < map_.h(); j++) {
00775                 selected_hexes_.insert(gamemap::location(i, j));
00776             }
00777         }
00778         all_hexes_selected_ = true;
00779     }
00780     else {
00781         selected_hexes_.clear();
00782         all_hexes_selected_ = false;
00783     }
00784     highlight_selected_hexes();
00785 }
00786 
00787 void map_editor::edit_draw() {
00788     left_button_func_changed(DRAW);
00789 }
00790 
00791 void map_editor::edit_refresh() {
00792     image::flush_cache();
00793     redraw_everything();
00794 }
00795 
00796 void map_editor::edit_update() {
00797     if (map_dirty_) {
00798         map_dirty_ = false;
00799         gui_.rebuild_all();
00800         gui_.invalidate_all();
00801         recalculate_starting_pos_labels();
00802         gui_.recalculate_minimap();
00803     }
00804 }
00805 
00806 void map_editor::edit_auto_update() {
00807     auto_update_ = !auto_update_;
00808 }
00809 
00810 hotkey::ACTION_STATE map_editor::get_action_state(hotkey::HOTKEY_COMMAND command) const {
00811     if(command == hotkey::HOTKEY_EDIT_AUTO_UPDATE) {
00812         return (auto_update_) ? hotkey::ACTION_OFF : hotkey::ACTION_ON;
00813     }
00814     return command_executor::get_action_state(command);
00815 }
00816 
00817 void map_editor::copy_buffer(map_buffer& buffer, const std::set<gamemap::location> &locs, const gamemap::location &origin)
00818 {
00819     std::set<gamemap::location>::const_iterator it;
00820     for (it = locs.begin(); it != locs.end(); it++) {
00821         t_translation::t_terrain terrain = map_.get_terrain(*it);
00822         buffer.push_back(buffer_item(*it-origin, terrain, starting_side_at(map_, *it)));
00823     }
00824 }
00825 
00826 void map_editor::paste_buffer(const map_buffer &buffer, const gamemap::location &loc, map_undo_action &undo_action)
00827 {
00828     std::set<gamemap::location> filled;
00829     std::vector<buffer_item>::const_iterator it;
00830     for (it = buffer.begin(); it != buffer.end(); it++) {
00831         //the addition of locations is not commutative !
00832         gamemap::location target = it->offset + loc;
00833 
00834         if (map_.on_board(target, true)) {
00835             undo_action.add_terrain(map_.get_terrain(target), it->terrain, target);
00836             map_.set_terrain(target, it->terrain);
00837             terrain_changed(target);
00838 
00839             const int start_side = it->starting_side;
00840             if (start_side != -1) {
00841                 undo_action.add_starting_location(start_side, start_side,
00842                     map_.starting_position(start_side), target);
00843                 map_.set_starting_position(start_side, target);
00844             }
00845             filled.insert(target);
00846         }
00847     }
00848 
00849     undo_action.set_selection(selected_hexes_, filled);
00850     selected_hexes_ = filled;
00851     highlight_selected_hexes(true);
00852 }
00853 
00854 void map_editor::insert_selection_in_clipboard() {
00855     // Find the hex that is closest to the selected one,
00856     // use this as origin
00857     gamemap::location origin(1000,1000);
00858     std::set<gamemap::location>::const_iterator it;
00859     for (it = selected_hexes_.begin(); it != selected_hexes_.end(); it++) {
00860         if (distance_between(selected_hex_, *it) <
00861             distance_between(selected_hex_, origin)) {
00862             origin = *it;
00863         }
00864     }
00865 
00866     copy_buffer(clipboard_, selected_hexes_, origin);
00867 }
00868 
00869 void map_editor::perform_fill_hexes(std::set<gamemap::location> &fill_hexes,
00870     const t_translation::t_terrain terrain, map_undo_action &undo_action) {
00871     std::set<gamemap::location>::const_iterator it;
00872     for (it = fill_hexes.begin(); it != fill_hexes.end(); it++) {
00873         if (map_.on_board(*it, true)) {
00874             undo_action.add_terrain(map_.get_terrain(*it), terrain, *it);
00875             if (terrain.base == t_translation::NO_LAYER) {
00876                 map_.set_terrain(*it, terrain, gamemap::OVERLAY);
00877             }
00878             else {
00879                 map_.set_terrain(*it, terrain);
00880             }
00881             terrain_changed(*it);
00882         }
00883     }
00884 }
00885 
00886 void map_editor::perform_selection_move() {
00887     map_undo_action undo_action;
00888     std::set<gamemap::location> old_selection = selected_hexes_;
00889 
00890     map_buffer buf;
00891     copy_buffer(buf, selected_hexes_, selection_move_start_);
00892     paste_buffer(buf,selected_hex_, undo_action);
00893 
00894     std::set<gamemap::location>::const_iterator it;
00895     for (it = old_selection.begin(); it != old_selection.end(); it++) {
00896         if (selected_hexes_.find(*it) != selected_hexes_.end())
00897             old_selection.erase(*it);
00898     }
00899     perform_fill_hexes(old_selection, palette_.selected_bg_terrain(),undo_action);
00900 
00901     save_undo_action(undo_action);
00902 }
00903 
00904 
00905 bool map_editor::can_execute_command(hotkey::HOTKEY_COMMAND command, int) const {
00906     switch (command) {
00907     case hotkey::HOTKEY_UNDO:
00908     case hotkey::HOTKEY_REDO:
00909     case hotkey::HOTKEY_ZOOM_IN:
00910     case hotkey::HOTKEY_ZOOM_OUT:
00911     case hotkey::HOTKEY_ZOOM_DEFAULT:
00912     case hotkey::HOTKEY_FULLSCREEN:
00913     case hotkey::HOTKEY_SCREENSHOT:
00914     case hotkey::HOTKEY_MAP_SCREENSHOT:
00915     case hotkey::HOTKEY_TOGGLE_GRID:
00916     case hotkey::HOTKEY_MOUSE_SCROLL:
00917     case hotkey::HOTKEY_PREFERENCES:
00918 
00919     case hotkey::HOTKEY_EDIT_SAVE_MAP:
00920     case hotkey::HOTKEY_EDIT_SAVE_AS:
00921     case hotkey::HOTKEY_EDIT_QUIT:
00922     case hotkey::HOTKEY_EDIT_SET_START_POS:
00923     case hotkey::HOTKEY_EDIT_NEW_MAP:
00924     case hotkey::HOTKEY_EDIT_LOAD_MAP:
00925     case hotkey::HOTKEY_EDIT_FLOOD_FILL:
00926     case hotkey::HOTKEY_EDIT_FILL_SELECTION:
00927     case hotkey::HOTKEY_EDIT_ROTATE_SELECTION:
00928     case hotkey::HOTKEY_EDIT_COPY:
00929     case hotkey::HOTKEY_EDIT_CUT:
00930     case hotkey::HOTKEY_EDIT_PASTE:
00931     case hotkey::HOTKEY_EDIT_REVERT:
00932     case hotkey::HOTKEY_EDIT_RESIZE:
00933     case hotkey::HOTKEY_EDIT_FLIP:
00934     case hotkey::HOTKEY_EDIT_SELECT_ALL:
00935     case hotkey::HOTKEY_EDIT_DRAW:
00936     case hotkey::HOTKEY_EDIT_REFRESH:
00937     case hotkey::HOTKEY_EDIT_UPDATE:
00938     case hotkey::HOTKEY_EDIT_AUTO_UPDATE:
00939     case hotkey::HOTKEY_LANGUAGE:
00940         return true;
00941     default:
00942         return false;
00943     }
00944 }
00945 
00946 void map_editor::toggle_grid() {
00947     preferences::set_grid(!preferences::grid());
00948     gui_.set_grid(preferences::grid());
00949     gui_.invalidate_all();
00950 }
00951 
00952 void map_editor::save_undo_action(const map_undo_action &action) {
00953     // we skip empty action
00954     // NOTE: if changing this, improve drawing functions to discard those
00955     if (action.something_set()) {
00956         add_undo_action(action);
00957         num_operations_since_save_++;
00958     }
00959 }
00960 
00961 void map_editor::undo() {
00962     if(exist_undo_actions()) {
00963         --num_operations_since_save_;
00964         map_undo_action action = pop_undo_action();
00965         if (action.selection_set()) {
00966             selected_hexes_ = action.undo_selection();
00967             highlight_selected_hexes(true);
00968         }
00969         if (action.terrain_set()) {
00970             for(std::map<gamemap::location, t_translation::t_terrain>::const_iterator it =
00971                     action.undo_terrains().begin();
00972                 it != action.undo_terrains().end(); ++it) {
00973                 map_.set_terrain(it->first, it->second);
00974                 terrain_changed(it->first);
00975             }
00976         }
00977         if (action.starting_location_set()) {
00978             for (std::map<gamemap::location, int>::const_iterator it =
00979                      action.undo_starting_locations().begin();
00980                  it != action.undo_starting_locations().end(); it++) {
00981                 map_.set_starting_position((*it).second, (*it).first);
00982             }
00983             recalculate_starting_pos_labels();
00984         }
00985         
00986         if (action.map_data_set()) {
00987             throw new_map_exception(action.old_map_data(), filename_, from_scenario_);
00988         }
00989     }
00990 }
00991 
00992 void map_editor::redo() {
00993     if(exist_redo_actions()) {
00994         ++num_operations_since_save_;
00995         map_undo_action action = pop_redo_action();
00996         if (action.selection_set()) {
00997             selected_hexes_ = action.redo_selection();
00998             highlight_selected_hexes(true);
00999         }
01000         if (action.terrain_set()) {
01001             for(std::map<gamemap::location, t_translation::t_terrain>::const_iterator it =
01002                     action.redo_terrains().begin();
01003                 it != action.redo_terrains().end(); ++it) {
01004                 if (it->second.base == t_translation::NO_LAYER) {
01005                     map_.set_terrain(it->first, it->second, gamemap::OVERLAY);
01006                 }
01007                 else {
01008                     map_.set_terrain(it->first, it->second);
01009                 }
01010                 terrain_changed(it->first);
01011             }
01012         }
01013         if (action.starting_location_set()) {
01014             for (std::map<gamemap::location, int>::const_iterator it =
01015                      action.redo_starting_locations().begin();
01016                  it != action.redo_starting_locations().end(); it++) {
01017                 map_.set_starting_position((*it).second, (*it).first);
01018             }
01019             recalculate_starting_pos_labels();
01020         }
01021         if (action.map_data_set()) {
01022             throw new_map_exception(action.new_map_data(), filename_, from_scenario_);
01023         }
01024     }
01025 }
01026 
01027 void map_editor::preferences() {
01028     preferences_dialog(gui_, prefs_);
01029     everything_dirty_ = true;
01030 }
01031 
01032 void map_editor::redraw_everything() {
01033     adjust_sizes(gui_, size_specs_);
01034     palette_.adjust_size();
01035     brush_.adjust_size();
01036     gui_.redraw_everything();
01037     update_l_button_palette();
01038     palette_.draw(true);
01039     brush_.draw(true);
01040     load_tooltips();
01041 }
01042 
01043 void map_editor::highlight_selected_hexes(const bool clear_old) {
01044     if (clear_old) {
01045         clear_highlighted_hexes_in_gui();
01046     }
01047     for (std::set<gamemap::location>::const_iterator it = selected_hexes_.begin();
01048          it != selected_hexes_.end(); it++) {
01049         gui_.add_highlighted_loc(*it);
01050     }
01051 }
01052 
01053 void map_editor::clear_highlighted_hexes_in_gui() {
01054     gui_.clear_highlighted_locs();
01055     highlighted_locs_cleared_ = true;
01056 }
01057 
01058 void map_editor::set_mouseover_overlay()
01059 {
01060     surface image_fg(image::get_image("terrain/" + map_.get_terrain_info(
01061                 palette_.selected_fg_terrain()).editor_image() +
01062                 ".png"));
01063     surface image_bg(image::get_image("terrain/" + map_.get_terrain_info(
01064                 palette_.selected_bg_terrain()).editor_image() +
01065                 ".png"));
01066 
01067     if (image_fg == NULL || image_bg == NULL) {
01068         std::cerr << "Missing terrain icon\n";
01069         gui_.set_mouseover_hex_overlay(NULL);
01070         return; 
01071     }
01072 
01073     // Create a transparent surface of the right size.
01074     surface image = create_compatible_surface(image_fg, image_fg->w, image_fg->h);
01075     SDL_FillRect(image,NULL,SDL_MapRGBA(image->format,0,0,0, 0));
01076 
01077     // For efficiency the size of the tile is cached.
01078     // We assume all tiles are of the same size.
01079     // The zoom factor can change, so it's not cached.
01080     // NOTE: when zooming and not moving the mouse, there are glitches.
01081     // Since the optimal alpha factor is unknown, it has to be calculated
01082     // on the fly, and caching the surfaces makes no sense yet.
01083     static const Uint8 alpha = 196;
01084     static const int size = image_fg->w;
01085     static const int half_size = size / 2;
01086     static const int quarter_size = size / 4;
01087     static const int offset = 2;
01088     static const int new_size = half_size - 2;
01089     const int zoom = static_cast<int>(size * gui_.get_zoom_factor());
01090 
01091     // Blit left side
01092     image_fg = scale_surface(image_fg, new_size, new_size);
01093     SDL_Rect rcDestLeft = { offset, quarter_size, 0, 0 };
01094     SDL_BlitSurface ( image_fg, NULL, image, &rcDestLeft );
01095 
01096     // Blit left side
01097     image_bg = scale_surface(image_bg, new_size, new_size);
01098     SDL_Rect rcDestRight = { half_size, quarter_size, 0, 0 };
01099     SDL_BlitSurface ( image_bg, NULL, image, &rcDestRight );
01100 
01101     // Add the alpha factor and scale the image
01102     image = scale_surface(adjust_surface_alpha(image, alpha), zoom, zoom);
01103 
01104     // Set as mouseover
01105     gui_.set_mouseover_hex_overlay(image);
01106 }
01107 
01108 bool map_editor::changed_since_save() const {
01109     return num_operations_since_save_ != 0;
01110 }
01111 
01112 void map_editor::set_starting_position(const int player, const gamemap::location loc) {
01113     if(map_.on_board(loc)) {
01114         map_undo_action action;
01115 
01116         action.add_starting_location(player, player, map_.starting_position(player), loc);
01117         map_.set_starting_position(player, loc);
01118         save_undo_action(action);
01119         recalculate_starting_pos_labels();
01120     }
01121     else {
01122         // If you selected an off-board hex,
01123         // we just use the standard invalid location
01124         map_undo_action action;
01125         action.add_starting_location(player, player, map_.starting_position(player), gamemap::location());
01126         map_.set_starting_position(player, gamemap::location());
01127         save_undo_action(action);
01128         recalculate_starting_pos_labels();
01129         //gui::message_dialog(gui_, "",
01130         //                       _("You must have a hex selected on the board.")).show();
01131     }
01132 }
01133 
01134 void map_editor::set_abort(const ABORT_MODE abort) {
01135     abort_ = abort;
01136 }
01137 
01138 void map_editor::set_file_to_save_as(const std::string filename, bool from_scenario) {
01139     if (original_filename_.empty())
01140         original_filename_ = filename;
01141     filename_ = filename;
01142     from_scenario_ = from_scenario;
01143 }
01144 
01145 void map_editor::left_button_down(const int mousex, const int mousey) {
01146     const gamemap::location& minimap_loc = gui_.minimap_location_on(mousex,mousey);
01147     const gamemap::location hex = gui_.hex_clicked_on(mousex, mousey);
01148     if (minimap_loc.valid()) {
01149         gui_.scroll_to_tile(minimap_loc,display::WARP,false);
01150     }
01151     else if (key_[SDLK_RSHIFT] || key_[SDLK_LSHIFT]) {
01152         if (key_[SDLK_RALT] || key_[SDLK_LALT]) {
01153             // Select/deselect a component.
01154             std::set<gamemap::location> selected;
01155             selected = get_component(map_, selected_hex_);
01156             for (std::set<gamemap::location>::const_iterator it = selected.begin();
01157                  it != selected.end(); it++) {
01158                 if (l_button_held_func_ == ADD_SELECTION) {
01159                     gui_.add_highlighted_loc(*it);
01160                     selected_hexes_.insert(*it);
01161                 }
01162                 else {
01163                     gui_.remove_highlighted_loc(*it);
01164                     selected_hexes_.erase(*it);
01165                 }
01166             }
01167             update_mouse_over_hexes(hex);
01168         }
01169         else {
01170             // Select what the brush is over.
01171             std::vector<gamemap::location> selected;
01172             selected = get_tiles(map_, hex, brush_.selected_brush_size());
01173             for (std::vector<gamemap::location>::const_iterator it = selected.begin();
01174                  it != selected.end(); it++) {
01175                 if (l_button_held_func_ == ADD_SELECTION) {
01176                     gui_.add_highlighted_loc(*it);
01177                     selected_hexes_.insert(*it);
01178                 }
01179                 else {
01180                     gui_.remove_highlighted_loc(*it);
01181                     selected_hexes_.erase(*it);
01182                 }
01183             }
01184             update_mouse_over_hexes(hex);
01185         }
01186     }
01187     // If the left mouse button is down and we beforhand have registered
01188     // a mouse down event, draw terrain at the current location.
01189     else if (l_button_held_func_ == DRAW_TERRAIN && mouse_moved_) {
01190         reset_mouseover_overlay();
01191         draw_on_mouseover_hexes(palette_.selected_fg_terrain());
01192     }
01193     else if (l_button_held_func_ == DRAW_TERRAIN_LAYER && mouse_moved_) {
01194         reset_mouseover_overlay();
01195         draw_on_mouseover_hexes(palette_.selected_fg_terrain(), true);
01196     }
01197     else if (l_button_held_func_ == MOVE_SELECTION && mouse_moved_) {
01198         reset_mouseover_overlay();
01199         //(*it-selection_move_start_) + hex
01200         // No other selections should be active when doing this.
01201         gui_.clear_highlighted_locs();
01202         std::set<gamemap::location>::const_iterator it;
01203         for (it = selected_hexes_.begin(); it != selected_hexes_.end(); it++) {
01204             const gamemap::location hl_loc = (*it-selection_move_start_) + hex;
01205             if (map_.on_board(hl_loc, true)) {
01206                 gui_.add_highlighted_loc(hl_loc);
01207             }
01208         }
01209     }
01210 }
01211 
01212 void map_editor::draw_on_mouseover_hexes(const t_translation::t_terrain terrain, const bool one_layer_only) {
01213     if(map_.on_board(selected_hex_, true)) {
01214         std::vector<gamemap::location> hexes =
01215             get_tiles(map_, selected_hex_, brush_.selected_brush_size());
01216         draw_terrain(terrain, hexes, one_layer_only);
01217     }
01218 }
01219 
01220 void map_editor::draw_terrain(const t_translation::t_terrain terrain,
01221                               const std::vector<gamemap::location> &hexes, const bool one_layer_only)
01222 {
01223     map_undo_action undo_action;
01224     t_translation::t_terrain final_terrain = terrain;
01225     if(!one_layer_only) {
01226         final_terrain = map_.get_terrain_info(terrain).terrain_with_default_base();
01227     }
01228 
01229     for(std::vector<gamemap::location>::const_iterator it = hexes.begin();
01230             it != hexes.end(); ++it) {
01231         const t_translation::t_terrain old_terrain = map_.get_terrain(*it);
01232         if(final_terrain != old_terrain) {
01233             undo_action.add_terrain(old_terrain, final_terrain, *it);
01234             if (final_terrain.base == t_translation::NO_LAYER) {
01235                 map_.set_terrain(*it, final_terrain, gamemap::OVERLAY);
01236             }
01237             else if (one_layer_only) {
01238                 map_.set_terrain(*it, final_terrain, gamemap::BASE);
01239             }
01240             else {
01241                 map_.set_terrain(*it, final_terrain);
01242             }
01243 
01244             // always rebuild localy to show the drawing progress
01245             gui_.rebuild_terrain(*it);
01246             gui_.invalidate(*it);
01247             map_dirty_ = true;
01248         }
01249     }
01250 
01251     save_undo_action(undo_action);
01252 }
01253 
01254 void map_editor::terrain_changed(const gamemap::location &hex)
01255 {
01256     if (!auto_update_) {
01257         gui_.rebuild_terrain(hex);
01258         gui_.invalidate(hex);
01259     }
01260     map_dirty_ = true;
01261 }
01262 
01263 // These 2 functions are useless now, maybe later?
01264 /*
01265 void map_editor::terrain_changed(const std::vector<gamemap::location> &hexes)
01266 {
01267     std::vector<gamemap::location>::const_iterator it;
01268     for (it = hexes.begin(); it != hexes.end(); it++) {
01269         terrain_changed(*it);
01270     }
01271 }
01272 
01273 void map_editor::terrain_changed(const std::set<gamemap::location> &hexes) {
01274     std::set<gamemap::location>::const_iterator it;
01275     for (it = hexes.begin(); it != hexes.end(); it++) {
01276         terrain_changed(*it);
01277     }   
01278 }
01279 */
01280 
01281 
01282 void map_editor::right_button_down(const int /*mousex*/, const int /*mousey*/) {
01283     // Draw with the background terrain on rightclick,
01284     // no matter what operations are wanted with the left button.
01285     //! @todo TODO evaluate if this is what is the smartest thing to do.
01286     draw_on_mouseover_hexes(palette_.selected_bg_terrain());
01287 }
01288 
01289 void map_editor::middle_button_down(const int mousex, const int mousey) {
01290     const gamemap::location& minimap_loc = gui_.minimap_location_on(mousex,mousey);
01291     const gamemap::location hex = gui_.hex_clicked_on(mousex, mousey);
01292     if (minimap_loc.valid()) {
01293         gui_.scroll_to_tile(minimap_loc,display::WARP,false);
01294     }
01295 }
01296 
01297 bool map_editor::confirm_exit_and_save() {
01298     if (!changed_since_save())
01299         return true;
01300     if (gui::dialog(gui_, "",
01301                          _("Quit Editor"), gui::YES_NO).show() != 0) {
01302         return false;
01303     }
01304     if (gui::dialog(gui_, "",
01305                      _("Do you want to save the map before quitting?"), gui::YES_NO).show() == 0) {
01306         if (!save_map("", false)) {
01307             return false;
01308         }
01309     }
01310     return true;
01311 }
01312 
01313 bool map_editor::save_map(const std::string fn, const bool display_confirmation) {
01314     std::string filename = fn;
01315     if (filename == "") {
01316         filename = filename_;
01317         if(filename == "") {
01318             edit_save_as();
01319             return (filename_ != "");
01320         }
01321     }
01322     else {
01323         filename_ = filename;
01324     }
01325 
01326     // Check if the filename is correct before saving.
01327     // We do this twice (also in the 'save as' routine),
01328     // because a file might already contain illegal characters if loaded.
01329     if(!verify_filename(filename, display_confirmation)) {
01330         return false;
01331     }
01332 
01333     try {
01334         std::string data;
01335         if (from_scenario_) {
01336             data = read_file(original_filename_);
01337             std::string::size_type start, stop;
01338             start = data.find("map_data=");
01339             if (start==std::string::npos)
01340                 return false;
01341             start += 10;
01342             stop = data.find('\"', start);
01343             if (stop==std::string::npos)
01344                 return false;
01345             data.replace(start, stop-start, map_.write());
01346         } else {
01347             data = map_.write();
01348         }
01349 
01350         write_file(filename, data);
01351         num_operations_since_save_ = 0;
01352         if (display_confirmation) {
01353             gui::message_dialog(gui_, "", _("Map saved.")).show();
01354         }
01355         if (from_scenario_)
01356             original_filename_ = filename;
01357     }
01358     catch (io_exception& e) {
01359         utils::string_map symbols;
01360         symbols["msg"] = e.what();
01361         const std::string msg = vgettext("Could not save the map: $msg",symbols);
01362         gui::message_dialog(gui_, "", msg).show();
01363         return false;
01364     }
01365     return true;
01366 }
01367 
01368 bool map_editor::verify_filename(const std::string& filename, bool show_error) const
01369 {
01370     static const std::string allowed_characters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_/\\.";
01371     static const std::string prefix = "\\/";
01372     size_t start_pos = filename.find_last_of(prefix);
01373 
01374     if(filename.find_first_not_of(allowed_characters, start_pos) != std::string::npos) {
01375         if(show_error) {
01376             gui::message_dialog(gui_, "", _("Error: Illegal character in filename.")).show();
01377         }
01378         return false;
01379     }
01380 
01381     return true;
01382 }
01383 void map_editor::show_menu(const std::vector<std::string>& items, const int xloc,
01384                            const int yloc, const bool /*context_menu*/) {
01385     // menu is what to display in the menu.
01386     if(items.size() == 1) {
01387         execute_command(hotkey::get_hotkey(items.front()).get_id());
01388         return;
01389     }
01390     static const std::string style = "menu2";
01391     gui::dialog kmenu = gui::dialog(gui_, "", "", gui::MESSAGE,
01392                             gui::dialog::hotkeys_style);
01393     kmenu.set_menu(get_menu_images(items));
01394     const int res = kmenu.show(xloc, yloc);
01395     if(res < 0 || static_cast<unsigned>(res) >= items.size())
01396         return;
01397     const hotkey::HOTKEY_COMMAND cmd = hotkey::get_hotkey(items[res]).get_id();
01398     execute_command(cmd);
01399 }
01400 
01401 void map_editor::execute_command(const hotkey::HOTKEY_COMMAND command) {
01402     if (command == hotkey::HOTKEY_QUIT_GAME) {
01403         set_abort();
01404     }
01405     else {
01406         hotkey::execute_command(gui_, command, this);
01407     }
01408 }
01409 
01410 void map_editor::recalculate_starting_pos_labels() {
01411     // Remove the current labels.
01412     for (std::vector<gamemap::location>::const_iterator it = starting_positions_.begin();
01413          it != starting_positions_.end(); it++) {
01414         gui_.labels().set_label(*it, "");
01415     }
01416     starting_positions_.clear();
01417     // Set new labels.
01418     for (int i = 1; i < gamemap::MAX_PLAYERS+1; i++) {
01419         gamemap::location loc = map_.starting_position(i);
01420         if (loc.valid()) {
01421             std::stringstream ss;
01422             ss << _("Player") << " " << i;
01423             gui_.labels().set_label(loc, ss.str());
01424             starting_positions_.push_back(loc);
01425         }
01426     }
01427 }
01428 
01429 void map_editor::update_mouse_over_hexes(const gamemap::location mouse_over_hex)
01430 {
01431     const int size = (l_button_func_ == DRAW) ? brush_.selected_brush_size() : 1;
01432     std::vector<gamemap::location> curr_locs = get_tiles(map_, mouse_over_hex, size);
01433 
01434     std::set<gamemap::location>::iterator it;
01435     for (it = mouse_over_hexes_.begin(); it != mouse_over_hexes_.end(); it++) {
01436         if (selected_hexes_.find(*it) == selected_hexes_.end()) {
01437             // Only remove highlightning if the hex is not selected
01438             // in an other way.
01439             gui_.remove_highlighted_loc(*it);
01440         }
01441     }
01442     mouse_over_hexes_.clear();
01443     if (mouse_over_hex != gamemap::location()) {
01444         // Only highlight if the mouse is on the map.
01445         for (std::vector<gamemap::location>::iterator itv = curr_locs.begin();
01446              itv != curr_locs.end(); itv++) {
01447             mouse_over_hexes_.insert(*itv);
01448             gui_.add_highlighted_loc(*itv);
01449         }
01450     }
01451 
01452     gui_.highlight_hex(mouse_over_hex);
01453 
01454     if(l_button_func_ == DRAW || l_button_func_ == FLOOD_FILL) {
01455         set_mouseover_overlay();
01456     }
01457     selected_hex_ = mouse_over_hex;
01458 }
01459 
01460 void map_editor::left_button_func_changed(const LEFT_BUTTON_FUNC func) {
01461     if (func != l_button_func_) {
01462         reset_mouseover_overlay();
01463         l_button_func_ = func;
01464         gui_.set_report_content(reports::EDIT_LEFT_BUTTON_FUNCTION,
01465                 hotkey::get_hotkey(get_action_name(func)).get_description());
01466         gui_.invalidate_game_status();
01467         l_button_palette_dirty_ = true;
01468     }
01469 }
01470 
01471 void map_editor::update_l_button_palette() {
01472     const theme &t = gui_.get_theme();
01473     const std::vector<theme::menu> &menus = t.menus();
01474     std::vector<theme::menu>::const_iterator it;
01475     for (it = menus.begin(); it != menus.end(); it++) {
01476         if (is_left_button_func_menu(*it)) {
01477             const SDL_Rect &r = (*it).location(gui_.screen_area());
01478             const int draw_x = maximum<int>(r.x - 1, gui_.screen_area().x);
01479             const int draw_y = maximum<int>(r.y - 1, gui_.screen_area().y);
01480             const int draw_w = minimum<int>(r.w + 2, gui_.screen_area().w);
01481             const int draw_h = minimum<int>(r.h + 2, gui_.screen_area().h);
01482             const SDL_Rect draw_rect = {draw_x, draw_y, draw_w, draw_h};
01483             SDL_Surface* const screen = gui_.video().getSurface();
01484             Uint32 color;
01485             if ((*it).items().back() == get_action_name(l_button_func_)) {
01486                 color = SDL_MapRGB(screen->format,0xFF,0x00,0x00);
01487             }
01488             else {
01489                 color = SDL_MapRGB(screen->format,0x00,0x00,0x00);
01490             }
01491             draw_rectangle(draw_rect.x, draw_rect.y, draw_rect.w, draw_rect.h,
01492                        color, gui_.video().getSurface());
01493             update_rect(draw_rect);
01494         }
01495     }
01496 }
01497 
01498 std::string map_editor::get_action_name(const LEFT_BUTTON_FUNC func) const {
01499     switch (func) {
01500     case DRAW:
01501         return "editdraw";
01502     case SELECT_HEXES:
01503         return ""; // Not implemented yet.
01504     case FLOOD_FILL:
01505         return "editfloodfill";
01506     case SET_STARTING_POSITION:
01507         return "editsetstartpos";
01508     case PASTE:
01509         return "editpaste";
01510     default:
01511         return "";
01512     }
01513 }
01514 
01515 bool map_editor::is_left_button_func_menu(const theme::menu &menu) const {
01516     const std::vector<std::string> &menu_items = menu.items();
01517     if (menu_items.size() == 1) {
01518         const std::string item_name = menu_items.back();
01519         for (size_t i = 0; i < NUM_L_BUTTON_FUNC; i++) {
01520             if (get_action_name(LEFT_BUTTON_FUNC(i)) == item_name) {
01521                 return true;
01522             }
01523         }
01524     }
01525     return false;
01526 }
01527 
01528 void map_editor::main_loop() {
01529     unsigned int last_brush_size = brush_.selected_brush_size();
01530     while (abort_ == DONT_ABORT) {
01531         int mousex, mousey;
01532         const int scroll_speed = preferences::scroll_speed();
01533         Uint8 mouse_flags = SDL_GetMouseState(&mousex,&mousey);
01534         const bool l_button_down = (0 != (mouse_flags & SDL_BUTTON_LMASK));
01535         const bool r_button_down = (0 != (mouse_flags & SDL_BUTTON_RMASK));
01536         const bool m_button_down = (0 != (mouse_flags & SDL_BUTTON_MMASK));
01537 
01538         const gamemap::location cur_hex = gui_.hex_clicked_on(mousex,mousey);
01539         if (cur_hex != selected_hex_) {
01540             mouse_moved_ = true;
01541         }
01542         if (mouse_moved_ || last_brush_size != brush_.selected_brush_size()
01543             || highlighted_locs_cleared_) {
01544             // The mouse has moved or the brush size has changed,
01545             // adjust the hexes the mouse is over.
01546             highlighted_locs_cleared_ = false;
01547             update_mouse_over_hexes(cur_hex);
01548             last_brush_size = brush_.selected_brush_size();
01549         }
01550         const theme::menu* const m = gui_.menu_pressed();
01551         if (m != NULL) {
01552             const SDL_Rect& menu_loc = m->location(gui_.screen_area());
01553             const int x = menu_loc.x + 1;
01554             const int y = menu_loc.y + menu_loc.h + 1;
01555             show_menu(m->items(), x, y, false);
01556         }
01557 
01558         if(key_[SDLK_UP] || mousey == 0) {
01559             gui_.scroll(0,-scroll_speed);
01560         }
01561         if(key_[SDLK_DOWN] || mousey == gui_.h()-1) {
01562             gui_.scroll(0,scroll_speed);
01563         }
01564         if(key_[SDLK_LEFT] || mousex == 0) {
01565             gui_.scroll(-scroll_speed,0);
01566         }
01567         if(key_[SDLK_RIGHT] || mousex == gui_.w()-1) {
01568             gui_.scroll(scroll_speed,0);
01569         }
01570 
01571         if (l_button_down) {
01572             left_button_down(mousex, mousey);
01573         }
01574         else {
01575             if (l_button_held_func_ == MOVE_SELECTION) {
01576                 // When it is detected that the mouse is no longer down
01577                 // and we are in the progress of moving a selection,
01578                 // perform the movement.
01579                 perform_selection_move();
01580             }
01581         }
01582         if (r_button_down) {
01583             right_button_down(mousex, mousey);
01584         }
01585         if (m_button_down) {
01586             middle_button_down(mousex, mousey);
01587         }
01588 
01589         gui_.draw(false);
01590         events::raise_draw_event();
01591 
01592         // When the map has changed, wait until the left mouse button
01593         // is not held down, and then update the minimap and
01594         // the starting position labels.
01595         if (map_dirty_) {
01596             if (!l_button_down && !r_button_down) {
01597                 if (auto_update_) {
01598                     gui_.rebuild_all();
01599                     gui_.invalidate_all();
01600                     map_dirty_ = false;
01601                 }
01602                 gui_.recalculate_minimap();
01603                 recalculate_starting_pos_labels();
01604             }
01605         }
01606         if (l_button_palette_dirty_) {
01607             update_l_button_palette();
01608             l_button_palette_dirty_ = false;
01609         }
01610         gui_.update_display();
01611         SDL_Delay(sdl_delay);
01612         events::pump();
01613         if (everything_dirty_) {
01614             redraw_everything();
01615             everything_dirty_ = false;
01616         }
01617         if (abort_ == ABORT_NORMALLY) {
01618             if (!confirm_exit_and_save()) {
01619                 set_abort(DONT_ABORT);
01620             }
01621         }
01622         mouse_moved_ = false;
01623     }
01624 }
01625 } // end namespace map_editor
01626 
01627 

Generated by doxygen 1.5.5 on 23 May 2008 for The Battle for Wesnoth
Gna! | Forum | Wiki | CIA | devdocs