00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015 #define GETTEXT_DOMAIN "wesnoth-editor"
00016
00017 #include "editor/action/action.hpp"
00018 #include "map_context.hpp"
00019
00020 #include "display.hpp"
00021 #include "filesystem.hpp"
00022 #include "foreach.hpp"
00023 #include "gettext.hpp"
00024 #include "map_exception.hpp"
00025 #include "map_label.hpp"
00026 #include "serialization/binary_or_text.hpp"
00027 #include "serialization/parser.hpp"
00028 #include "wml_exception.hpp"
00029
00030
00031 #include "formula_string_utils.hpp"
00032
00033 #include <boost/regex.hpp>
00034
00035
00036 namespace editor {
00037
00038 const size_t map_context::max_action_stack_size_ = 100;
00039
00040 map_context::map_context(const editor_map& map)
00041 : filename_()
00042 , map_data_key_()
00043 , embedded_(false)
00044 , map_(map)
00045 , undo_stack_()
00046 , redo_stack_()
00047 , actions_since_save_(0)
00048 , starting_position_label_locs_()
00049 , needs_reload_(false)
00050 , needs_terrain_rebuild_(false)
00051 , needs_labels_reset_(false)
00052 , changed_locations_()
00053 , everything_changed_(false)
00054 {
00055 }
00056
00057 map_context::map_context(const config& game_config, const std::string& filename, const display& disp)
00058 : filename_(filename)
00059 , map_data_key_()
00060 , embedded_(false)
00061 , map_(game_config, disp)
00062 , undo_stack_()
00063 , redo_stack_()
00064 , actions_since_save_(0)
00065 , starting_position_label_locs_()
00066 , needs_reload_(false)
00067 , needs_terrain_rebuild_(false)
00068 , needs_labels_reset_(false)
00069 , changed_locations_()
00070 , everything_changed_(false)
00071 {
00072 log_scope2(log_editor, "Loading map " + filename);
00073 if (!file_exists(filename) || is_directory(filename)) {
00074 throw editor_map_load_exception(filename, _("File not found"));
00075 }
00076
00077 std::string map_string = read_file(filename);
00078
00079 if (map_string.empty()) {
00080 std::string message = _("Empty file");
00081 throw editor_map_load_exception(filename, message);
00082 }
00083
00084 try {
00085 config file;
00086 ::read(file, map_string);
00087
00088 map_ = editor_map(game_config, file, disp);
00089 return;
00090 } catch (config::error&) {
00091 }
00092
00093 boost::regex re("data\\s*=\\s*\"(.+?)\"");
00094 boost::smatch m;
00095 if (boost::regex_search(map_string, m, re, boost::regex_constants::match_not_dot_null)) {
00096 boost::regex re2("\\{(.+?)\\}");
00097 boost::smatch m2;
00098 std::string m1 = m[1];
00099 if (boost::regex_search(m1, m2, re2)) {
00100 map_data_key_ = m1;
00101 LOG_ED << "Map looks like a scenario, trying {" << m2[1] << "}\n";
00102 std::string new_filename = get_wml_location(m2[1], directory_name(m2[1]));
00103 if (new_filename.empty()) {
00104 std::string message = _("The map file looks like a scenario, "
00105 "but the map_data value does not point to an existing file")
00106 + std::string("\n") + m2[1];
00107 throw editor_map_load_exception(filename, message);
00108 }
00109 LOG_ED << "New filename is: " << new_filename << "\n";
00110 filename_ = new_filename;
00111 map_string = read_file(filename_);
00112 } else {
00113 LOG_ED << "Loading embedded map file\n";
00114 embedded_ = true;
00115 map_string = m[1];
00116 }
00117 }
00118 if (map_string.empty()) {
00119 std::string message = _("Empty map file");
00120 throw editor_map_load_exception(filename, message);
00121 }
00122
00123 map_ = editor_map::from_string(game_config, map_string, disp);
00124 }
00125
00126 map_context::~map_context()
00127 {
00128 clear_stack(undo_stack_);
00129 clear_stack(redo_stack_);
00130 }
00131
00132 void map_context::draw_terrain(t_translation::t_terrain terrain,
00133 const map_location& loc, bool one_layer_only)
00134 {
00135 if (!one_layer_only) {
00136 terrain = map_.get_terrain_info(terrain).terrain_with_default_base();
00137 }
00138 draw_terrain_actual(terrain, loc, one_layer_only);
00139 }
00140
00141 void map_context::draw_terrain_actual(t_translation::t_terrain terrain,
00142 const map_location& loc, bool one_layer_only)
00143 {
00144 if (!map_.on_board_with_border(loc)) {
00145
00146
00147 LOG_ED << "Attempted to draw terrain off the map (" << loc << ")\n";
00148 return;
00149 }
00150 t_translation::t_terrain old_terrain = map_.get_terrain(loc);
00151 if (terrain != old_terrain) {
00152 if (terrain.base == t_translation::NO_LAYER) {
00153 map_.set_terrain(loc, terrain, gamemap::OVERLAY);
00154 } else if (one_layer_only) {
00155 map_.set_terrain(loc, terrain, gamemap::BASE);
00156 } else {
00157 map_.set_terrain(loc, terrain);
00158 }
00159 add_changed_location(loc);
00160 }
00161 }
00162
00163 void map_context::draw_terrain(t_translation::t_terrain terrain,
00164 const std::set<map_location>& locs, bool one_layer_only)
00165 {
00166 if (!one_layer_only) {
00167 terrain = map_.get_terrain_info(terrain).terrain_with_default_base();
00168 }
00169 foreach (const map_location& loc, locs) {
00170 draw_terrain_actual(terrain, loc, one_layer_only);
00171 }
00172 }
00173
00174 void map_context::clear_changed_locations()
00175 {
00176 everything_changed_ = false;
00177 changed_locations_.clear();
00178 }
00179
00180 void map_context::add_changed_location(const map_location& loc)
00181 {
00182 if (!everything_changed()) {
00183 changed_locations_.insert(loc);
00184 }
00185 }
00186
00187 void map_context::add_changed_location(const std::set<map_location>& locs)
00188 {
00189 if (!everything_changed()) {
00190 changed_locations_.insert(locs.begin(), locs.end());
00191 }
00192 }
00193
00194 void map_context::set_everything_changed()
00195 {
00196 everything_changed_ = true;
00197 }
00198
00199 bool map_context::everything_changed() const
00200 {
00201 return everything_changed_;
00202 }
00203
00204 void map_context::clear_starting_position_labels(display& disp)
00205 {
00206 disp.labels().clear_all();
00207 starting_position_label_locs_.clear();
00208 }
00209
00210 void map_context::set_starting_position_labels(display& disp)
00211 {
00212 std::set<map_location> new_label_locs = map_.set_starting_position_labels(disp);
00213 starting_position_label_locs_.insert(new_label_locs.begin(), new_label_locs.end());
00214 }
00215
00216 void map_context::reset_starting_position_labels(display& disp)
00217 {
00218 clear_starting_position_labels(disp);
00219 set_starting_position_labels(disp);
00220 set_needs_labels_reset(false);
00221 }
00222
00223 bool map_context::save()
00224 {
00225
00226
00227
00228 config data;
00229 map_.write(data);
00230
00231 std::stringstream ss;
00232 {
00233 config_writer out(ss, false);
00234 out.write(data);
00235 }
00236
00237 try {
00238 if (!is_embedded()) {
00239 std::stringstream ss;
00240 config_writer writer(ss, false);
00241 writer.write(data);
00242 write_file(get_filename(), ss.str());
00243 } else {
00244 std::string map_string = read_file(get_filename());
00245 boost::regex re("(.*)(map_data\\s*=\\s*\".+?\")(.*)");
00246 boost::smatch m;
00247 if (boost::regex_search(map_string, m, re, boost::regex_constants::match_not_dot_null)) {
00248 std::stringstream ss2;
00249 ss2 << m[1];
00250 ss2 << ss.str();
00251 ss2 << m[3];
00252 write_file(get_filename(), ss2.str());
00253 } else {
00254
00255 boost::regex re_maptag("(.*)([map].+?)([/map].*)");
00256 boost::smatch match_maptag;
00257 if (boost::regex_search(map_string, match_maptag, re_maptag, boost::regex_constants::match_not_dot_null)) {
00258 std::stringstream ss3;
00259 ss3 << match_maptag[1];
00260 ss3 << ss.str();
00261 ss3 << match_maptag[3];
00262 write_file(get_filename(), ss3.str());
00263 } else {
00264 throw editor_map_save_exception(_("Could not save into scenario"));
00265 }
00266 }
00267 }
00268 clear_modified();
00269 } catch (io_exception& e) {
00270 utils::string_map symbols;
00271 symbols["msg"] = e.what();
00272 const std::string msg = vgettext("Could not save the map: $msg", symbols);
00273 throw editor_map_save_exception(msg);
00274 }
00275 return true;
00276 }
00277
00278 void map_context::set_map(const editor_map& map)
00279 {
00280 if (map_.h() != map.h() || map_.w() != map.w()) {
00281 set_needs_reload();
00282 } else {
00283 set_needs_terrain_rebuild();
00284 }
00285 map_ = map;
00286 }
00287
00288 void map_context::perform_action(const editor_action& action)
00289 {
00290 LOG_ED << "Performing action " << action.get_id() << ": " << action.get_name()
00291 << ", actions count is " << action.get_instance_count() << "\n";
00292 editor_action* undo = action.perform(*this);
00293 if (actions_since_save_ < 0) {
00294
00295
00296 actions_since_save_ = 1 + undo_stack_.size();
00297 }
00298 actions_since_save_++;
00299 undo_stack_.push_back(undo);
00300 trim_stack(undo_stack_);
00301 clear_stack(redo_stack_);
00302 }
00303
00304 void map_context::perform_partial_action(const editor_action& action)
00305 {
00306 LOG_ED << "Performing (partial) action " << action.get_id() << ": " << action.get_name()
00307 << ", actions count is " << action.get_instance_count() << "\n";
00308 if (!can_undo()) {
00309 throw editor_logic_exception("Empty undo stack in perform_partial_action()");
00310 }
00311 editor_action_chain* undo_chain = dynamic_cast<editor_action_chain*>(last_undo_action());
00312 if (undo_chain == NULL) {
00313 throw editor_logic_exception("Last undo action not a chain in perform_partial_action()");
00314 }
00315 editor_action* undo = action.perform(*this);
00316
00317 undo_chain->prepend_action(undo);
00318 clear_stack(redo_stack_);
00319 }
00320 bool map_context::modified() const
00321 {
00322 return actions_since_save_ != 0;
00323 }
00324
00325 void map_context::clear_modified()
00326 {
00327 actions_since_save_ = 0;
00328 }
00329
00330 bool map_context::can_undo() const
00331 {
00332 return !undo_stack_.empty();
00333 }
00334
00335 bool map_context::can_redo() const
00336 {
00337 return !redo_stack_.empty();
00338 }
00339
00340 editor_action* map_context::last_undo_action()
00341 {
00342 return undo_stack_.empty() ? NULL : undo_stack_.back();
00343 }
00344
00345 editor_action* map_context::last_redo_action()
00346 {
00347 return redo_stack_.empty() ? NULL : redo_stack_.back();
00348 }
00349
00350 const editor_action* map_context::last_undo_action() const
00351 {
00352 return undo_stack_.empty() ? NULL : undo_stack_.back();
00353 }
00354
00355 const editor_action* map_context::last_redo_action() const
00356 {
00357 return redo_stack_.empty() ? NULL : redo_stack_.back();
00358 }
00359
00360 void map_context::undo()
00361 {
00362 LOG_ED << "undo() beg, undo stack is " << undo_stack_.size() << ", redo stack " << redo_stack_.size() << "\n";
00363 if (can_undo()) {
00364 perform_action_between_stacks(undo_stack_, redo_stack_);
00365 actions_since_save_--;
00366 } else {
00367 WRN_ED << "undo() called with an empty undo stack\n";
00368 }
00369 LOG_ED << "undo() end, undo stack is " << undo_stack_.size() << ", redo stack " << redo_stack_.size() << "\n";
00370 }
00371
00372 void map_context::redo()
00373 {
00374 LOG_ED << "redo() beg, undo stack is " << undo_stack_.size() << ", redo stack " << redo_stack_.size() << "\n";
00375 if (can_redo()) {
00376 perform_action_between_stacks(redo_stack_, undo_stack_);
00377 actions_since_save_++;
00378 } else {
00379 WRN_ED << "redo() called with an empty redo stack\n";
00380 }
00381 LOG_ED << "redo() end, undo stack is " << undo_stack_.size() << ", redo stack " << redo_stack_.size() << "\n";
00382 }
00383
00384 void map_context::partial_undo()
00385 {
00386
00387 if (!can_undo()) {
00388 throw editor_logic_exception("Empty undo stack in partial_undo()");
00389 }
00390 editor_action_chain* undo_chain = dynamic_cast<editor_action_chain*>(last_undo_action());
00391 if (undo_chain == NULL) {
00392 throw editor_logic_exception("Last undo action not a chain in partial undo");
00393 }
00394
00395
00396 boost::scoped_ptr<editor_action> first_action_in_chain(undo_chain->pop_first_action());
00397 if (undo_chain->empty()) {
00398 actions_since_save_--;
00399 delete undo_chain;
00400 undo_stack_.pop_back();
00401 }
00402 redo_stack_.push_back(first_action_in_chain.get()->perform(*this));
00403
00404 }
00405
00406 void map_context::clear_undo_redo()
00407 {
00408 clear_stack(undo_stack_);
00409 clear_stack(redo_stack_);
00410 }
00411
00412 void map_context::trim_stack(action_stack& stack)
00413 {
00414 if (stack.size() > max_action_stack_size_) {
00415 delete stack.front();
00416 stack.pop_front();
00417 }
00418 }
00419
00420 void map_context::clear_stack(action_stack& stack)
00421 {
00422 foreach (editor_action* a, stack) {
00423 delete a;
00424 }
00425 stack.clear();
00426 }
00427
00428 void map_context::perform_action_between_stacks(action_stack& from, action_stack& to)
00429 {
00430 assert(!from.empty());
00431 boost::scoped_ptr<editor_action> action(from.back());
00432 from.pop_back();
00433 editor_action* reverse_action = action->perform(*this);
00434 to.push_back(reverse_action);
00435 trim_stack(to);
00436 }
00437
00438 }