From 544366ff2a1d8a0f91965cf67009b3ebc43215d6 Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Mon, 6 Feb 2023 18:39:59 +0100 Subject: LibGUI: Add a simple "recently open files" feature This feature allows any application to easily install an automatically updating list of recently open files in a GUI::Menu. There are three main pieces to this mechanism: - GUI::Application::set_config_domain(domain): This must be called before using the recent files feature. It informs the Application object about which config domain to find the relevant RecentFiles list under. - GUI::Menu::add_recently_open_files(callback): This inserts the list in a menu. A callback must be provided to handle actually opening the recent file in some application-specific way. - GUI::Application::set_most_recently_open_file(path): This updates the list of recently open files, both in the configuration files, and in the GUI menu. --- Userland/Libraries/LibGUI/Application.cpp | 71 +++++++++++++++++++++++++++++++ Userland/Libraries/LibGUI/Application.h | 12 ++++++ Userland/Libraries/LibGUI/Menu.cpp | 25 +++++++++++ Userland/Libraries/LibGUI/Menu.h | 4 ++ 4 files changed, 112 insertions(+) diff --git a/Userland/Libraries/LibGUI/Application.cpp b/Userland/Libraries/LibGUI/Application.cpp index 7fc73b965b..286e229355 100644 --- a/Userland/Libraries/LibGUI/Application.cpp +++ b/Userland/Libraries/LibGUI/Application.cpp @@ -5,6 +5,7 @@ */ #include +#include #include #include #include @@ -324,4 +325,74 @@ void Application::event(Core::Event& event) Object::event(event); } +void Application::set_config_domain(String config_domain) +{ + m_config_domain = move(config_domain); +} + +void Application::register_recent_file_actions(Badge, Vector> actions) +{ + m_recent_file_actions = move(actions); + update_recent_file_actions(); +} + +void Application::update_recent_file_actions() +{ + VERIFY(!m_config_domain.is_empty()); + + size_t number_of_recently_open_files = 0; + auto update_action = [&](size_t index) { + auto& action = m_recent_file_actions[index]; + char buffer = static_cast('0' + index); + auto key = StringView(&buffer, 1); + auto path = Config::read_string(m_config_domain, "RecentFiles"sv, key); + + if (path.is_empty()) { + action->set_visible(false); + action->set_enabled(false); + } else { + action->set_visible(true); + action->set_enabled(true); + action->set_text(path); + ++number_of_recently_open_files; + } + }; + for (size_t i = 0; i < max_recently_open_files(); ++i) + update_action(i); + + // Hide or show the "(No recently open files)" placeholder. + m_recent_file_actions.last()->set_visible(number_of_recently_open_files == 0); +} + +void Application::set_most_recently_open_file(String new_path) +{ + Vector new_recent_files_list; + + for (size_t i = 0; i < max_recently_open_files(); ++i) { + static_assert(max_recently_open_files() < 10); + char buffer = static_cast('0' + i); + auto key = StringView(&buffer, 1); + new_recent_files_list.append(Config::read_string(m_config_domain, "RecentFiles"sv, key)); + } + + new_recent_files_list.remove_all_matching([&](auto& existing_path) { + return existing_path.view() == new_path; + }); + + new_recent_files_list.prepend(new_path.to_deprecated_string()); + + for (size_t i = 0; i < max_recently_open_files(); ++i) { + auto& path = new_recent_files_list[i]; + char buffer = static_cast('0' + i); + auto key = StringView(&buffer, 1); + Config::write_string( + m_config_domain, + "RecentFiles"sv, + key, + path); + } + + update_recent_file_actions(); +} + } diff --git a/Userland/Libraries/LibGUI/Application.h b/Userland/Libraries/LibGUI/Application.h index 5f03f68cf4..470953227e 100644 --- a/Userland/Libraries/LibGUI/Application.h +++ b/Userland/Libraries/LibGUI/Application.h @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -87,6 +88,14 @@ public: auto const& global_shortcut_actions(Badge) const { return m_global_shortcut_actions; } + static constexpr size_t max_recently_open_files() { return 4; } + + void set_config_domain(String); + void update_recent_file_actions(); + void set_most_recently_open_file(String path); + + void register_recent_file_actions(Badge, Vector>); + private: Application(int argc, char** argv, Core::EventLoop::MakeInspectable = Core::EventLoop::MakeInspectable::No); Application(Main::Arguments const& arguments, Core::EventLoop::MakeInspectable inspectable = Core::EventLoop::MakeInspectable::No) @@ -120,6 +129,9 @@ private: Vector m_args; WeakPtr m_drag_hovered_widget; WeakPtr m_pending_drop_widget; + + String m_config_domain; + Vector> m_recent_file_actions; }; } diff --git a/Userland/Libraries/LibGUI/Menu.cpp b/Userland/Libraries/LibGUI/Menu.cpp index d57141a68c..3a5869264e 100644 --- a/Userland/Libraries/LibGUI/Menu.cpp +++ b/Userland/Libraries/LibGUI/Menu.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -217,4 +218,28 @@ void Menu::realize_menu_item(MenuItem& item, int item_id) } } +ErrorOr Menu::add_recent_files_list(Function callback) +{ + m_recent_files_callback = move(callback); + + Vector> recent_file_actions; + + for (size_t i = 0; i < GUI::Application::max_recently_open_files(); ++i) { + recent_file_actions.append(GUI::Action::create("", [&](auto& action) { m_recent_files_callback(action); })); + } + + recent_file_actions.append(GUI::Action::create("(No recently open files)", nullptr)); + recent_file_actions.last()->set_enabled(false); + + auto* app = GUI::Application::the(); + app->register_recent_file_actions({}, recent_file_actions); + + for (auto& action : recent_file_actions) { + TRY(try_add_action(action)); + } + + TRY(try_add_separator()); + return {}; +} + } diff --git a/Userland/Libraries/LibGUI/Menu.h b/Userland/Libraries/LibGUI/Menu.h index f95b7db989..c3abdb0003 100644 --- a/Userland/Libraries/LibGUI/Menu.h +++ b/Userland/Libraries/LibGUI/Menu.h @@ -48,6 +48,8 @@ public: Menu& add_submenu(DeprecatedString name); void remove_all_actions(); + ErrorOr add_recent_files_list(Function); + void popup(Gfx::IntPoint screen_position, RefPtr const& default_action = nullptr, Gfx::IntRect const& button_rect = {}); void dismiss(); @@ -78,6 +80,8 @@ private: NonnullOwnPtrVector m_items; WeakPtr m_current_default_action; bool m_visible { false }; + + Function m_recent_files_callback; }; } -- cgit v1.2.3