diff --git a/data/gui.xml b/data/gui.xml index 59f71b2fe..3aaaae3b3 100644 --- a/data/gui.xml +++ b/data/gui.xml @@ -591,8 +591,12 @@ - - + + + + + + diff --git a/data/strings/en.ini b/data/strings/en.ini index 7deddd890..ced97c029 100644 --- a/data/strings/en.ini +++ b/data/strings/en.ini @@ -238,6 +238,7 @@ ChangePixelFormat_Indexed = Indexed ChangePixelFormat_MoreOptions = More Options Clear = Clear ClearCel = Clear Cel +ClearRecentFiles = Clear Recent Files CloseAllFiles = Close All Files CloseFile = Close File ColorCurve = Color Curve @@ -692,7 +693,9 @@ file = &File file_new = &New... file_open = &Open... file_open_recent = Open &Recent -file_reopen_closed = Reopen Close&d File +file_reopen_closed = &Reopen Closed File +file_no_recent_file = No Recent File +file_clear_recent_files = &Clear Recent Files file_save = &Save file_save_as = Save &As... file_export = Expor&t... diff --git a/src/app/CMakeLists.txt b/src/app/CMakeLists.txt index 227b780b9..6d43d5b64 100644 --- a/src/app/CMakeLists.txt +++ b/src/app/CMakeLists.txt @@ -204,6 +204,7 @@ if(ENABLE_UI) commands/cmd_change_color.cpp commands/cmd_clear.cpp commands/cmd_clear_cel.cpp + commands/cmd_clear_recent_files.cpp commands/cmd_close_file.cpp commands/cmd_color_quantization.cpp commands/cmd_contiguous_fill.cpp diff --git a/src/app/app_menus.cpp b/src/app/app_menus.cpp index 608047d92..15214f478 100644 --- a/src/app/app_menus.cpp +++ b/src/app/app_menus.cpp @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -301,7 +302,7 @@ AppMenus* AppMenus::instance() } AppMenus::AppMenus() - : m_recentListMenuitem(nullptr) + : m_recentFilesPlaceholder(nullptr) , m_osMenu(nullptr) { m_recentFilesConn = @@ -453,61 +454,62 @@ void AppMenus::initTheme() bool AppMenus::rebuildRecentList() { - AppMenuItem* list_menuitem = dynamic_cast(m_recentListMenuitem); - MenuItem* menuitem; + if (!m_recentFilesPlaceholder) + return true; - // Update the recent file list menu item - if (list_menuitem) { - if (list_menuitem->hasSubmenuOpened()) - return false; + Menu* menu = dynamic_cast(m_recentFilesPlaceholder->parent()); + if (!menu) + return false; - Command* cmd_open_file = - Commands::instance()->byId(CommandId::OpenFile()); + AppMenuItem* owner = dynamic_cast(menu->getOwnerMenuItem()); + if (!owner || owner->hasSubmenuOpened()) + return false; - Menu* submenu = list_menuitem->getSubmenu(); - if (submenu) { - list_menuitem->setSubmenu(NULL); - submenu->deferDelete(); + int insertIndex = menu->getChildIndex(m_recentFilesPlaceholder)+1; + + // Remove active items + while (auto appItem = dynamic_cast(menu->at(insertIndex))) { + menu->removeChild(appItem); + appItem->deferDelete(); + } + + Command* openFile = Commands::instance()->byId(CommandId::OpenFile()); + + auto recent = App::instance()->recentFiles(); + base::paths files; + files.insert(files.end(), + recent->pinnedFiles().begin(), + recent->pinnedFiles().end()); + files.insert(files.end(), + recent->recentFiles().begin(), + recent->recentFiles().end()); + if (!files.empty()) { + Params params; + for (const auto& fn : files) { + params.set("filename", fn.c_str()); + + auto menuitem = new AppMenuItem(base::get_file_name(fn).c_str(), + openFile, params); + menuitem->setIsRecentFileItem(true); + menu->insertChild(insertIndex++, menuitem); } + } + else { + auto menuitem = new AppMenuItem( + Strings::main_menu_file_no_recent_file(), nullptr); + menuitem->setIsRecentFileItem(true); + menuitem->setEnabled(false); + menu->insertChild(insertIndex++, menuitem); + } - // Build the menu of recent files - submenu = new Menu(); - list_menuitem->setSubmenu(submenu); - - auto recent = App::instance()->recentFiles(); - base::paths files; - files.insert(files.end(), - recent->pinnedFiles().begin(), - recent->pinnedFiles().end()); - files.insert(files.end(), - recent->recentFiles().begin(), - recent->recentFiles().end()); - if (!files.empty()) { - Params params; - for (const auto& fn : files) { - params.set("filename", fn.c_str()); - menuitem = new AppMenuItem( - base::get_file_name(fn).c_str(), - cmd_open_file, - params); - submenu->addChild(menuitem); - } - } - else { - menuitem = new AppMenuItem("Nothing", NULL, Params()); - menuitem->setEnabled(false); - submenu->addChild(menuitem); - } - - // Sync native menus - if (list_menuitem->native() && - list_menuitem->native()->menuItem) { - os::Menus* menus = os::instance()->menus(); - os::Menu* osMenu = (menus ? menus->createMenu(): nullptr); - if (osMenu) { - createNativeSubmenus(osMenu, submenu); - list_menuitem->native()->menuItem->setSubmenu(osMenu); - } + // Sync native menus + if (owner->native() && + owner->native()->menuItem) { + os::Menus* menus = os::instance()->menus(); + os::Menu* osMenu = (menus ? menus->createMenu(): nullptr); + if (osMenu) { + createNativeSubmenus(osMenu, menu); + owner->native()->menuItem->setSubmenu(osMenu); } } @@ -563,7 +565,14 @@ Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem) // is it a ? if (strcmp(elem->Value(), "separator") == 0) { auto item = new MenuSeparator; - if (id) item->setId(id); + if (id) { + item->setId(id); + + // Recent list menu + if (std::strcmp(id, "recent_files_placeholder") == 0) { + m_recentFilesPlaceholder = item; + } + } return item; } @@ -598,11 +607,7 @@ Widget* AppMenus::convertXmlelemToMenuitem(TiXmlElement* elem) // Has it a ID? if (id) { - // Recent list menu - if (std::strcmp(id, "recent_list") == 0) { - m_recentListMenuitem = menuitem; - } - else if (std::strcmp(id, "help_menu") == 0) { + if (std::strcmp(id, "help_menu") == 0) { m_helpMenuitem = menuitem; } } diff --git a/src/app/app_menus.h b/src/app/app_menus.h index 91e2869b4..3dbb4e09a 100644 --- a/src/app/app_menus.h +++ b/src/app/app_menus.h @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -49,7 +50,6 @@ namespace app { bool rebuildRecentList(); Menu* getRootMenu() { return m_rootMenu.get(); } - MenuItem* getRecentListMenuitem() { return m_recentListMenuitem; } Menu* getTabPopupMenu() { return m_tabPopupMenu.get(); } Menu* getDocumentTabPopupMenu() { return m_documentTabPopupMenu.get(); } Menu* getLayerPopupMenu() { return m_layerPopupMenu.get(); } @@ -84,7 +84,7 @@ namespace app { #endif std::unique_ptr m_rootMenu; - MenuItem* m_recentListMenuitem; + Widget* m_recentFilesPlaceholder; MenuItem* m_helpMenuitem; std::unique_ptr m_tabPopupMenu; std::unique_ptr m_documentTabPopupMenu; diff --git a/src/app/commands/cmd_clear_recent_files.cpp b/src/app/commands/cmd_clear_recent_files.cpp new file mode 100644 index 000000000..a13dab0ad --- /dev/null +++ b/src/app/commands/cmd_clear_recent_files.cpp @@ -0,0 +1,49 @@ +// Aseprite +// Copyright (C) 2019 Igara Studio S.A. +// +// This program is distributed under the terms of +// the End-User License Agreement for Aseprite. + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "app/app.h" +#include "app/commands/command.h" +#include "app/recent_files.h" + +namespace app { + +class ClearRecentFilesCommand : public Command { +public: + ClearRecentFilesCommand(); + +protected: + bool onEnabled(Context* ctx) override; + void onExecute(Context* ctx) override; +}; + +ClearRecentFilesCommand::ClearRecentFilesCommand() + : Command(CommandId::ClearRecentFiles(), CmdUIOnlyFlag) +{ +} + +bool ClearRecentFilesCommand::onEnabled(Context* ctx) +{ + auto recent = App::instance()->recentFiles(); + return (recent && + (!recent->recentFiles().empty() || + !recent->recentFolders().empty())); +} + +void ClearRecentFilesCommand::onExecute(Context* ctx) +{ + App::instance()->recentFiles()->clear(); +} + +Command* CommandFactory::createClearRecentFilesCommand() +{ + return new ClearRecentFilesCommand; +} + +} // namespace app diff --git a/src/app/commands/cmd_keyboard_shortcuts.cpp b/src/app/commands/cmd_keyboard_shortcuts.cpp index d00efdd87..547bfe0c4 100644 --- a/src/app/commands/cmd_keyboard_shortcuts.cpp +++ b/src/app/commands/cmd_keyboard_shortcuts.cpp @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018 Igara Studio S.A. +// Copyright (C) 2018-2019 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -780,7 +780,7 @@ private: void fillMenusList(ListBox* listbox, Menu* menu, int level) { for (auto child : menu->children()) { if (AppMenuItem* menuItem = dynamic_cast(child)) { - if (menuItem == AppMenus::instance()->getRecentListMenuitem()) + if (menuItem->isRecentFileItem()) continue; KeyItem* keyItem = new KeyItem( @@ -917,7 +917,7 @@ void KeyboardShortcutsCommand::fillMenusKeys(app::KeyboardShortcuts& keys, { for (auto child : menu->children()) { if (AppMenuItem* menuItem = dynamic_cast(child)) { - if (menuItem == AppMenus::instance()->getRecentListMenuitem()) + if (menuItem->isRecentFileItem()) continue; if (menuItem->getCommand()) { diff --git a/src/app/commands/commands_list.h b/src/app/commands/commands_list.h index 40e3117ff..8fca57e56 100644 --- a/src/app/commands/commands_list.h +++ b/src/app/commands/commands_list.h @@ -39,6 +39,7 @@ FOR_EACH_COMMAND(ChangeBrush) FOR_EACH_COMMAND(ChangeColor) FOR_EACH_COMMAND(Clear) FOR_EACH_COMMAND(ClearCel) +FOR_EACH_COMMAND(ClearRecentFiles) FOR_EACH_COMMAND(CloseAllFiles) FOR_EACH_COMMAND(CloseFile) FOR_EACH_COMMAND(ColorCurve) diff --git a/src/app/recent_files.h b/src/app/recent_files.h index 86875ef8e..9f52bf3d8 100644 --- a/src/app/recent_files.h +++ b/src/app/recent_files.h @@ -1,5 +1,5 @@ // Aseprite -// Copyright (C) 2018 Igara Studio S.A. +// Copyright (C) 2018-2019 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This program is distributed under the terms of @@ -23,10 +23,10 @@ namespace app { kRecentFolders, kCollections }; public: - const base::paths& pinnedFiles() { return m_paths[kPinnedFiles]; } - const base::paths& recentFiles() { return m_paths[kRecentFiles]; } - const base::paths& pinnedFolders() { return m_paths[kPinnedFolders]; } - const base::paths& recentFolders() { return m_paths[kRecentFolders]; } + const base::paths& pinnedFiles() const { return m_paths[kPinnedFiles]; } + const base::paths& recentFiles() const { return m_paths[kRecentFiles]; } + const base::paths& pinnedFolders() const { return m_paths[kPinnedFolders]; } + const base::paths& recentFolders() const { return m_paths[kRecentFolders]; } RecentFiles(const int limit); ~RecentFiles(); diff --git a/src/app/ui/app_menuitem.cpp b/src/app/ui/app_menuitem.cpp index b0b4a3642..a220b81cd 100644 --- a/src/app/ui/app_menuitem.cpp +++ b/src/app/ui/app_menuitem.cpp @@ -1,4 +1,5 @@ // Aseprite +// Copyright (C) 2019 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This program is distributed under the terms of @@ -35,18 +36,24 @@ using namespace ui; Params AppMenuItem::s_contextParams; AppMenuItem::AppMenuItem(const std::string& text, - Command* command, const Params& params) + Command* command, + const Params& params) : MenuItem(text) , m_key(nullptr) , m_command(command) , m_params(params) + , m_isRecentFileItem(false) , m_native(nullptr) { } AppMenuItem::~AppMenuItem() { - delete m_native; + if (m_native) { + if (m_native->menuItem) + m_native->menuItem->dispose(); + delete m_native; + } } void AppMenuItem::setKey(const KeyPtr& key) @@ -59,8 +66,11 @@ void AppMenuItem::setNative(const Native& native) { if (!m_native) m_native = new Native(native); - else + else { + if (m_native->menuItem) + m_native->menuItem->dispose(); *m_native = native; + } } void AppMenuItem::syncNativeMenuItemKeyShortcut() diff --git a/src/app/ui/app_menuitem.h b/src/app/ui/app_menuitem.h index 7c3ae7d38..bfcb7bdec 100644 --- a/src/app/ui/app_menuitem.h +++ b/src/app/ui/app_menuitem.h @@ -41,6 +41,9 @@ namespace app { KeyPtr key() { return m_key; } void setKey(const KeyPtr& key); + void setIsRecentFileItem(bool state) { m_isRecentFileItem = state; } + bool isRecentFileItem() const { return m_isRecentFileItem; } + Command* getCommand() { return m_command; } const Params& getParams() const { return m_params; } @@ -60,6 +63,7 @@ namespace app { KeyPtr m_key; Command* m_command; Params m_params; + bool m_isRecentFileItem; Native* m_native; static Params s_contextParams; diff --git a/src/ui/widget.cpp b/src/ui/widget.cpp index e5475bb38..1a8710d53 100644 --- a/src/ui/widget.cpp +++ b/src/ui/widget.cpp @@ -1,5 +1,5 @@ // Aseprite UI Library -// Copyright (C) 2018 Igara Studio S.A. +// Copyright (C) 2018-2019 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This file is released under the terms of the MIT license. @@ -409,6 +409,15 @@ Manager* Widget::manager() const return Manager::getDefault(); } +int Widget::getChildIndex(Widget* child) +{ + auto it = std::find(m_children.begin(), m_children.end(), child); + if (it != m_children.end()) + return it - m_children.begin(); + else + return -1; +} + Widget* Widget::nextSibling() { assert_ui_thread(); diff --git a/src/ui/widget.h b/src/ui/widget.h index 04ed00d1c..ec0368eff 100644 --- a/src/ui/widget.h +++ b/src/ui/widget.h @@ -1,5 +1,5 @@ // Aseprite UI Library -// Copyright (C) 2018 Igara Studio S.A. +// Copyright (C) 2018-2019 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This file is released under the terms of the MIT license. @@ -169,6 +169,7 @@ namespace ui { const WidgetsList& children() const { return m_children; } Widget* at(int index) { return m_children[index]; } + int getChildIndex(Widget* child); // Returns the first/last child or NULL if it doesn't exist. Widget* firstChild() {