From 744dd6cd39e8b530bc31ba0b8e65ed368ebd42b7 Mon Sep 17 00:00:00 2001 From: "Ignacio R. Morelle" Date: Fri, 9 Oct 2015 19:23:55 -0300 Subject: [PATCH] editor: Add Recent Files menu with recently loaded/saved files File paths are recorded into the game preferences up to a (currently hardcoded) limit of 6 and opening or saving maps adds or bumps existing entries to the top. We may allow users to change the MRU limit in Advanced Preferences in a later commit. Adds two translatable strings. In order to fit file paths in the menu without filling up the whole screen sideways, we use only the file names for now. Because identical file names could prove to be an actual issue later, I intend to look into ellipsizing paths correctly in a later step. (Note that I'm piggybacking on the crummy submenu support we already had in the themable UI so that this can be safely backported to 1.12. It's decidedly not optimal usability-wise, but it'll have to do for now.) --- data/themes/editor.cfg | 10 +++- src/editor/editor_controller.cpp | 12 ++++ src/editor/editor_controller.hpp | 1 + src/editor/editor_preferences.cpp | 94 ++++++++++++++++++++++++++++++ src/editor/editor_preferences.hpp | 8 +++ src/editor/map/context_manager.cpp | 41 +++++++++++++ src/editor/map/context_manager.hpp | 6 ++ src/editor/map/map_context.cpp | 16 ++++- src/editor/map/map_context.hpp | 3 + 9 files changed, 188 insertions(+), 3 deletions(-) diff --git a/data/themes/editor.cfg b/data/themes/editor.cfg index 5142d712f2fd..40c47b5ec42d 100644 --- a/data/themes/editor.cfg +++ b/data/themes/editor.cfg @@ -61,12 +61,20 @@ title= _ "File" type=turbo font_size=9 - items=editor-scenario-edit,statustable,unitlist,editor-map-new,editor-scenario-new,editor-map-load,editor-map-revert,editor-map-save,editor-map-save-as,editor-scenario-save-as,editor-map-save-all,preferences,help,editor-close-map,quit-editor,editor-quit-to-desktop + items=editor-scenario-edit,statustable,unitlist,editor-map-new,editor-scenario-new,editor-map-load,menu-editor-recent,editor-map-revert,editor-map-save,editor-map-save-as,editor-scenario-save-as,editor-map-save-all,preferences,help,editor-close-map,quit-editor,editor-quit-to-desktop ref=top-panel rect="=+1,=+1,+100,+20" xanchor=fixed yanchor=fixed [/menu] + + [menu] + title= _ "Load Recent" + id=menu-editor-recent + button=false + items=EDITOR-LOAD-MRU-PLACEHOLDER + [/menu] + # [menu] # id=menu-editor-edit # title= _ "Edit" diff --git a/src/editor/editor_controller.cpp b/src/editor/editor_controller.cpp index 3fcb2e32b1d8..b0038ff33866 100644 --- a/src/editor/editor_controller.cpp +++ b/src/editor/editor_controller.cpp @@ -246,6 +246,7 @@ bool editor_controller::can_execute_command(const hotkey::hotkey_command& cmd, i return true; } return false; + case editor::LOAD_MRU: case editor::PALETTE: case editor::AREA: case editor::SIDE: @@ -526,6 +527,8 @@ hotkey::ACTION_STATE editor_controller::get_action_state(hotkey::HOTKEY_COMMAND case editor::MAP: return index == context_manager_->current_context_index() ? ACTION_SELECTED : ACTION_DESELECTED; + case editor::LOAD_MRU: + return ACTION_STATELESS; case editor::PALETTE: return ACTION_STATELESS; case editor::AREA: @@ -587,6 +590,11 @@ bool editor_controller::execute_command(const hotkey::hotkey_command& cmd, int i } } return false; + case LOAD_MRU: + if (index >= 0) { + context_manager_->load_mru_item(static_cast(index)); + } + return true; case PALETTE: toolkit_->get_palette_manager()->set_group(index); return true; @@ -970,6 +978,10 @@ void editor_controller::show_menu(const std::vector& items_arg, int } ++i; } + if (!items.empty() && items.front() == "EDITOR-LOAD-MRU-PLACEHOLDER") { + active_menu_ = editor::LOAD_MRU; + context_manager_->expand_load_mru_menu(items); + } if (!items.empty() && items.front() == "editor-switch-map") { active_menu_ = editor::MAP; context_manager_->expand_open_maps_menu(items); diff --git a/src/editor/editor_controller.hpp b/src/editor/editor_controller.hpp index 6a1a896165ae..d1c4b94b3ab5 100644 --- a/src/editor/editor_controller.hpp +++ b/src/editor/editor_controller.hpp @@ -55,6 +55,7 @@ std::string get_left_button_function(); enum menu_type { MAP, + LOAD_MRU, PALETTE, AREA, SIDE, diff --git a/src/editor/editor_preferences.cpp b/src/editor/editor_preferences.cpp index 558e5b040f6e..dbe2e9bd041e 100644 --- a/src/editor/editor_preferences.cpp +++ b/src/editor/editor_preferences.cpp @@ -13,10 +13,13 @@ */ #include "editor/editor_preferences.hpp" +#include "config.hpp" #include "game_preferences.hpp" #include "serialization/string_utils.hpp" #include "util.hpp" +#include + namespace preferences { namespace editor { @@ -94,6 +97,97 @@ namespace editor { return lexical_cast_in_range(preferences::get("editor_b"), 0, -255, 255); } + namespace { + // TODO: make user-customizable in Advanced Preferences. + const size_t MAX_RECENT_FILES = 6; + + // + // NOTE: The MRU read/save functions enforce the entry count limit in + // order to ensure the list on disk doesn't grow forever. Otherwise, + // normally this would be the UI's responsibility instead. + // + + std::vector do_read_editor_mru() + { + const config& cfg = preferences::get_child("editor_recent_files"); + + std::vector mru; + if(!cfg) { + return mru; + } + + BOOST_FOREACH(const config& child, cfg.child_range("entry")) + { + const std::string& entry = child["path"].str(); + if(!entry.empty()) { + mru.push_back(entry); + } + } + + mru.resize(std::min(MAX_RECENT_FILES, mru.size())); + + return mru; + } + + void do_commit_editor_mru(const std::vector& mru) + { + config cfg; + unsigned n = 0; + + BOOST_FOREACH(const std::string& entry, mru) + { + if(entry.empty()) { + continue; + } + + config& child = cfg.add_child("entry"); + child["path"] = entry; + + if(++n >= MAX_RECENT_FILES) { + break; + } + } + + preferences::set_child("editor_recent_files", cfg); + } + } + + std::vector recent_files() + { + return do_read_editor_mru(); + } + + void add_recent_files_entry(const std::string& path) + { + if(path.empty()) { + return; + } + + std::vector mru = do_read_editor_mru(); + + // Enforce uniqueness. Normally shouldn't do a thing unless somebody + // has been tampering with the preferences file. + mru.erase(std::remove(mru.begin(), mru.end(), path), mru.end()); + + mru.insert(mru.begin(), path); + mru.resize(std::min(MAX_RECENT_FILES, mru.size())); + + do_commit_editor_mru(mru); + } + + void remove_recent_files_entry(const std::string& path) + { + if(path.empty()) { + return; + } + + std::vector mru = do_read_editor_mru(); + + mru.erase(std::remove(mru.begin(), mru.end(), path), mru.end()); + + do_commit_editor_mru(mru); + } + } //end namespace editor } //end namespace preferences diff --git a/src/editor/editor_preferences.hpp b/src/editor/editor_preferences.hpp index a05384ec883f..2434e686b67d 100644 --- a/src/editor/editor_preferences.hpp +++ b/src/editor/editor_preferences.hpp @@ -16,6 +16,7 @@ #define EDITOR_PREFERENCES_HPP_INCLUDED #include +#include namespace preferences { @@ -56,6 +57,13 @@ namespace editor { /** Get editor blue tint level. */ int tod_b(); + /** Retrieves the list of recently opened files. */ + std::vector recent_files(); + /** Adds an entry to the recent files list. */ + void add_recent_files_entry(const std::string& path); + /** Removes a single entry from the recent files list. */ + void remove_recent_files_entry(const std::string& path); + } //end namespace editor } //end namespace preferences diff --git a/src/editor/map/context_manager.cpp b/src/editor/map/context_manager.cpp index 490c9cab0164..bf9eed23d66b 100644 --- a/src/editor/map/context_manager.cpp +++ b/src/editor/map/context_manager.cpp @@ -187,6 +187,17 @@ void context_manager::load_map_dialog(bool force_same_context /* = false */) } } +void context_manager::load_mru_item(unsigned int index, bool force_same_context /* = false */) +{ + const std::vector& mru = preferences::editor::recent_files(); + + if(mru.empty() || index >= mru.size()) { + return; + } + + load_map(mru[index], !force_same_context); +} + void context_manager::edit_side_dialog(int side) { team& t = get_map_context().get_teams()[side]; @@ -302,6 +313,36 @@ void context_manager::expand_open_maps_menu(std::vector& items) } } +void context_manager::expand_load_mru_menu(std::vector& items) +{ + std::vector mru = preferences::editor::recent_files(); + + for (unsigned int i = 0; i < items.size(); ++i) { + if (items[i] != "EDITOR-LOAD-MRU-PLACEHOLDER") { + continue; + } + + items.erase(items.begin() + i); + + if(mru.empty()) { + items.insert(items.begin() + i, _("No Recent Files")); + continue; + } + + BOOST_FOREACH(std::string& path, mru) + { + // TODO: add proper leading ellipsization instead, since otherwise + // it'll be impossible to tell apart files with identical names and + // different parent paths. + path = filesystem::base_name(path); + } + + items.insert(items.begin() + i, mru.begin(), mru.end()); + break; + } + +} + void context_manager::expand_areas_menu(std::vector& items) { tod_manager* tod = get_map_context().get_time_manager(); diff --git a/src/editor/map/context_manager.hpp b/src/editor/map/context_manager.hpp index 30bf29052621..af88e6db3cc1 100644 --- a/src/editor/map/context_manager.hpp +++ b/src/editor/map/context_manager.hpp @@ -111,6 +111,9 @@ class context_manager { /** Menu expanding for open maps list */ void expand_open_maps_menu(std::vector& items); + /** Menu expanding for most recent loaded list */ + void expand_load_mru_menu(std::vector& items); + /** Menu expanding for the map's player sides */ void expand_sides_menu(std::vector& items); @@ -126,6 +129,9 @@ class context_manager { /** Display a load map dialog and process user input. */ void load_map_dialog(bool force_same_context = false); + /** Open the specified entry from the recent files list. */ + void load_mru_item(unsigned index, bool force_same_context = false); + /** Display a scenario edit dialog and process user input. */ void edit_scenario_dialog(); diff --git a/src/editor/map/map_context.cpp b/src/editor/map/map_context.cpp index 9a1ed0274eba..40db99b9fe7b 100644 --- a/src/editor/map/map_context.cpp +++ b/src/editor/map/map_context.cpp @@ -14,6 +14,7 @@ #define GETTEXT_DOMAIN "wesnoth-editor" #include "editor/action/action.hpp" +#include "editor/editor_preferences.hpp" #include "map_context.hpp" #include "display.hpp" @@ -141,6 +142,8 @@ map_context::map_context(const config& game_config, const std::string& filename, boost::regex_constants::match_not_dot_null)) { map_ = editor_map::from_string(game_config, file_string); //throws on error pure_map_ = true; + + add_to_recent_files(); return; } @@ -160,14 +163,15 @@ map_context::map_context(const config& game_config, const std::string& filename, } catch (config::error & e) { throw editor_map_load_exception("load_scenario", e.message); //we already caught and rethrew this exception in load_scenario } - return; } else { LOG_ED << "Loading embedded map file" << std::endl; embedded_ = true; pure_map_ = true; map_ = editor_map::from_string(game_config, map_data); - return; } + + add_to_recent_files(); + return; } // 3.0 Macro referenced pure map @@ -186,6 +190,8 @@ map_context::map_context(const config& game_config, const std::string& filename, file_string = filesystem::read_file(filename_); map_ = editor_map::from_string(game_config, file_string); pure_map_ = true; + + add_to_recent_files(); } void map_context::set_side_setup(int side, const std::string& team_name, const std::string& user_team_name, @@ -545,6 +551,7 @@ bool map_context::save_map() throw editor_map_save_exception(_("Could not save into scenario")); } } + add_to_recent_files(); clear_modified(); } catch (filesystem::io_exception& e) { utils::string_map symbols; @@ -610,6 +617,11 @@ void map_context::clear_modified() actions_since_save_ = 0; } +void map_context::add_to_recent_files() +{ + preferences::editor::add_recent_files_entry(get_filename()); +} + bool map_context::can_undo() const { return !undo_stack_.empty(); diff --git a/src/editor/map/map_context.hpp b/src/editor/map/map_context.hpp index 611281005042..cda07ae68176 100644 --- a/src/editor/map/map_context.hpp +++ b/src/editor/map/map_context.hpp @@ -311,6 +311,9 @@ class map_context : private boost::noncopyable /** Clear the modified state */ void clear_modified(); + /** Adds the map to the editor's recent files list. */ + void add_to_recent_files(); + /** @return true when undo can be performed, false otherwise */ bool can_undo() const;