From 5affdbbae1d30f305e18ffcc018a0b2cd3b75117 Mon Sep 17 00:00:00 2001 From: David Capello Date: Mon, 20 Apr 2020 14:20:07 -0300 Subject: [PATCH] Shox extensions in different categories in the Preferences dialog --- data/strings/en.ini | 6 ++ src/app/commands/cmd_options.cpp | 113 +++++++++++++++++++++++++++++-- src/app/extensions.cpp | 14 ++++ src/app/extensions.h | 14 ++++ src/ui/listbox.cpp | 17 +++-- src/ui/listbox.h | 2 + 6 files changed, 154 insertions(+), 12 deletions(-) diff --git a/data/strings/en.ini b/data/strings/en.ini index e750a96a5..d725ad3f4 100644 --- a/data/strings/en.ini +++ b/data/strings/en.ini @@ -1209,6 +1209,12 @@ available_themes = Available Themes select_theme = &Select download_themes = Download Themes open_theme_folder = Open &Folder +language_extensions = Languages +theme_extensions = Themes +script_extensions = Scripts +palette_extensions = Palettes +dithering_matrix_extensions = Dithering Matrices +multiple_extensions = Multiple Categories add_extension = &Add Extension add_extension_title = Add Extension enable_extension = &Enable diff --git a/src/app/commands/cmd_options.cpp b/src/app/commands/cmd_options.cpp index 0a257f80e..05894e2f3 100644 --- a/src/app/commands/cmd_options.cpp +++ b/src/app/commands/cmd_options.cpp @@ -71,6 +71,25 @@ app::gen::ColorProfileBehavior missingCsMap[] = { app::gen::ColorProfileBehavior::ASK, }; +class ExtensionCategorySeparator : public SeparatorInView { +public: + ExtensionCategorySeparator(const Extension::Category category, + const std::string& text) + : SeparatorInView(text, ui::HORIZONTAL) + , m_category(category) { + InitTheme.connect( + [this]{ + auto b = this->border(); + b.top(2*b.top()); + b.bottom(2*b.bottom()); + this->setBorder(b); + }); + } + Extension::Category category() const { return m_category; } +private: + Extension::Category m_category; +}; + } // anonymous namespace using namespace ui; @@ -123,6 +142,11 @@ class OptionsWindow : public app::gen::Options { Extension* extension() { return m_extension; } + Extension::Category category() const { + ASSERT(m_extension); + return m_extension->category(); + } + bool isEnabled() const { ASSERT(m_extension); return m_extension->isEnabled(); @@ -1133,16 +1157,51 @@ private: themeList()->layout(); } + void loadExtensionsByCategory(const Extension::Category category, + const std::string& label) { + bool hasItems = false; + auto sep = new ExtensionCategorySeparator(category, label); + extensionsList()->addChild(sep); + for (auto e : App::instance()->extensions()) { + if (e->category() == category) { + ExtensionItem* item = new ExtensionItem(e); + extensionsList()->addChild(item); + hasItems = true; + } + } + sep->setVisible(hasItems); + } + void loadExtensions() { // Extensions already loaded if (extensionsList()->getItemsCount() > 0) return; - for (auto extension : App::instance()->extensions()) { - ExtensionItem* item = new ExtensionItem(extension); - extensionsList()->addChild(item); - } - extensionsList()->sortItems(); + loadExtensionsByCategory( + Extension::Category::Languages, + Strings::options_language_extensions()); + + loadExtensionsByCategory( + Extension::Category::Themes, + Strings::options_theme_extensions()); + +#ifdef ENABLE_SCRIPTING + loadExtensionsByCategory( + Extension::Category::Scripts, + Strings::options_script_extensions()); +#endif + + loadExtensionsByCategory( + Extension::Category::Palettes, + Strings::options_palette_extensions()); + + loadExtensionsByCategory( + Extension::Category::DitheringMatrices, + Strings::options_dithering_matrix_extensions()); + + loadExtensionsByCategory( + Extension::Category::Multiple, + Strings::options_multiple_extensions()); onExtensionChange(); extensionsList()->layout(); @@ -1299,7 +1358,33 @@ private: // Add the new extension in the listbox ExtensionItem* item = new ExtensionItem(ext); extensionsList()->addChild(item); - extensionsList()->sortItems(); + updateCategoryVisibility(); + extensionsList()->sortItems( + [](Widget* a, Widget* b){ + auto aSep = dynamic_cast(a); + auto bSep = dynamic_cast(b); + auto aItem = dynamic_cast(a); + auto bItem = dynamic_cast(b); + auto aCat = (aSep ? aSep->category(): + aItem ? aItem->category(): Extension::Category::None); + auto bCat = (bSep ? bSep->category(): + bItem ? bItem->category(): Extension::Category::None); + if (aCat < bCat) + return true; + else if (aCat == bCat) { + // There are no two separators with same category. + ASSERT(!(aSep && bSep)); + + if (aSep && !bSep) + return true; + else if (!aSep && bSep) + return false; + else + return (base::compare_filenames(a->text(), b->text()) < 0); + } + else + return false; + }); extensionsList()->layout(); extensionsList()->selectChild(item); } @@ -1339,6 +1424,7 @@ private: void deleteExtensionItem(ExtensionItem* item) { // Remove the item from the list extensionsList()->removeChild(item); + updateCategoryVisibility(); extensionsList()->layout(); item->deferDelete(); } @@ -1404,6 +1490,21 @@ private: return paths; } + void updateCategoryVisibility() { + bool visibleCategories[int(Extension::Category::Max)]; + for (auto& v : visibleCategories) + v = false; + for (auto w : extensionsList()->children()) { + if (auto item = dynamic_cast(w)) { + visibleCategories[int(item->category())] = true; + } + } + for (auto w : extensionsList()->children()) { + if (auto sep = dynamic_cast(w)) + sep->setVisible(visibleCategories[int(sep->category())]); + } + } + Context* m_context; Preferences& m_pref; DocumentPreferences& m_globPref; diff --git a/src/app/extensions.cpp b/src/app/extensions.cpp index 69a6e1c7e..a19092514 100644 --- a/src/app/extensions.cpp +++ b/src/app/extensions.cpp @@ -222,6 +222,7 @@ Extension::Extension(const std::string& path, , m_name(name) , m_version(version) , m_displayName(displayName) + , m_category(Category::None) , m_isEnabled(isEnabled) , m_isInstalled(true) , m_isBuiltinExtension(isBuiltinExtension) @@ -257,16 +258,19 @@ void Extension::executeExitActions() void Extension::addLanguage(const std::string& id, const std::string& path) { m_languages[id] = path; + updateCategory(Category::Languages); } void Extension::addTheme(const std::string& id, const std::string& path) { m_themes[id] = path; + updateCategory(Category::Themes); } void Extension::addPalette(const std::string& id, const std::string& path) { m_palettes[id] = path; + updateCategory(Category::Palettes); } void Extension::addDitheringMatrix(const std::string& id, @@ -275,6 +279,7 @@ void Extension::addDitheringMatrix(const std::string& id, { DitheringMatrixInfo info(path, name); m_ditheringMatrices[id] = info; + updateCategory(Category::DitheringMatrices); } #ifdef ENABLE_SCRIPTING @@ -447,6 +452,14 @@ bool Extension::isDefaultTheme() const return (name() == kAsepriteDefaultThemeExtensionName); } +void Extension::updateCategory(const Category newCategory) +{ + if (m_category == Category::None) + m_category = newCategory; + else if (m_category != newCategory) + m_category = Category::Multiple; +} + #ifdef ENABLE_SCRIPTING // TODO move this to app/script/tableutils.h @@ -669,6 +682,7 @@ void Extension::exitScripts() void Extension::addScript(const std::string& fn) { m_plugin.scripts.push_back(ScriptItem(fn)); + updateCategory(Category::Scripts); } #endif // ENABLE_SCRIPTING diff --git a/src/app/extensions.h b/src/app/extensions.h index fa618ffdb..6c96dee30 100644 --- a/src/app/extensions.h +++ b/src/app/extensions.h @@ -37,6 +37,17 @@ namespace app { class Extension { friend class Extensions; public: + enum class Category { + None, + Languages, + Themes, + Scripts, + Palettes, + DitheringMatrices, + Multiple, + Max + }; + class DitheringMatrixInfo { public: DitheringMatrixInfo() : m_matrix(nullptr) { } @@ -69,6 +80,7 @@ namespace app { const std::string& name() const { return m_name; } const std::string& version() const { return m_version; } const std::string& displayName() const { return m_displayName; } + const Category category() const { return m_category; } const ExtensionItems& languages() const { return m_languages; } const ExtensionItems& themes() const { return m_themes; } @@ -105,6 +117,7 @@ namespace app { void uninstallFiles(const std::string& path); bool isCurrentTheme() const; bool isDefaultTheme() const; + void updateCategory(const Category newCategory); #ifdef ENABLE_SCRIPTING void initScripts(); void exitScripts(); @@ -137,6 +150,7 @@ namespace app { std::string m_name; std::string m_version; std::string m_displayName; + Category m_category; bool m_isEnabled; bool m_isInstalled; bool m_isBuiltinExtension; diff --git a/src/ui/listbox.cpp b/src/ui/listbox.cpp index 076af0a4e..88962d187 100644 --- a/src/ui/listbox.cpp +++ b/src/ui/listbox.cpp @@ -1,5 +1,5 @@ // Aseprite UI Library -// Copyright (C) 2019 Igara Studio S.A. +// Copyright (C) 2019-2020 Igara Studio S.A. // Copyright (C) 2001-2018 David Capello // // This file is released under the terms of the MIT license. @@ -26,6 +26,10 @@ namespace ui { +static inline bool sort_by_text(Widget* a, Widget* b) { + return (base::compare_filenames(a->text(), b->text()) < 0); +} + using namespace gfx; ListBox::ListBox() @@ -185,14 +189,15 @@ void ListBox::centerScroll() } } -inline bool sort_by_text(Widget* a, Widget* b) { - return (base::compare_filenames(a->text(), b->text()) < 0); -} - void ListBox::sortItems() +{ + sortItems(&sort_by_text); +} + +void ListBox::sortItems(bool (*cmp)(Widget* a, Widget* b)) { WidgetsList widgets = children(); - std::sort(widgets.begin(), widgets.end(), &sort_by_text); + std::sort(widgets.begin(), widgets.end(), cmp); // Remove all children and add then again. removeAllChildren(); diff --git a/src/ui/listbox.h b/src/ui/listbox.h index f86f41d07..60299ea09 100644 --- a/src/ui/listbox.h +++ b/src/ui/listbox.h @@ -1,4 +1,5 @@ // Aseprite UI Library +// Copyright (C) 2020 Igara Studio S.A. // Copyright (C) 2001-2017 David Capello // // This file is released under the terms of the MIT license. @@ -35,6 +36,7 @@ namespace ui { void makeChildVisible(Widget* item); void centerScroll(); void sortItems(); + void sortItems(bool (*cmp)(Widget* a, Widget* b)); obs::signal Change; obs::signal DoubleClickItem;