reapack

Package manager for REAPER
Log | Files | Refs | Submodules | README | LICENSE

commit 7a20e8ff2130a5c11f90e2942dcbed66a3dfe123
parent 06e61c2dfcb1b660ff24da0ff2a3f7ac0b0410dd
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Wed, 16 Mar 2016 15:19:53 -0400

create the Package Browser dialog

Diffstat:
Msrc/about.cpp | 2+-
Asrc/browser.cpp | 151++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/browser.hpp | 61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/main.cpp | 6++++++
Msrc/package.cpp | 14++++++++++++++
Msrc/package.hpp | 1+
Msrc/reapack.cpp | 33++++++++++++++++++++++++++++++---
Msrc/reapack.hpp | 8++++++--
Msrc/report.cpp | 2+-
Msrc/resource.hpp | 6++++++
Msrc/resource.rc | 19+++++++++++++++++++
Msrc/version.cpp | 10+++++++++-
Msrc/version.hpp | 3++-
Mtest/package.cpp | 14++++++++++++++
Mtest/version.cpp | 8++++----
15 files changed, 325 insertions(+), 13 deletions(-)

diff --git a/src/about.cpp b/src/about.cpp @@ -192,7 +192,7 @@ void About::updatePackages() const Version *lastVer = pkg->lastVersion(); const auto_string &name = make_autostring(pkg->name()); const auto_string &version = make_autostring(lastVer->name()); - const auto_string &author = make_autostring(lastVer->author()); + const auto_string &author = make_autostring(lastVer->displayAuthor()); m_packages->addRow({name, version, author.empty() ? AUTO_STR("Unknown") : author}); diff --git a/src/browser.cpp b/src/browser.cpp @@ -0,0 +1,151 @@ +/* ReaPack: Package manager for REAPER + * Copyright (C) 2015-2016 Christian Fillion + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "browser.hpp" + +#include "encoding.hpp" +#include "index.hpp" +#include "listview.hpp" +#include "reapack.hpp" +#include "resource.hpp" + +#include <boost/algorithm/string.hpp> + +using namespace std; + +Browser::Browser(const vector<IndexPtr> &indexes, ReaPack *reapack) + : Dialog(IDD_BROWSER_DIALOG), m_indexes(indexes), m_reapack(reapack), + m_checkFilter(false) +{ +} + +void Browser::onInit() +{ + m_filterHandle = getControl(IDC_FILTER); + + m_types = { + {Package::ScriptType, getControl(IDC_SCRIPTS)}, + {Package::EffectType, getControl(IDC_EFFECTS)}, + {Package::ExtensionType, getControl(IDC_EXTENSIONS)}, + }; + + for(const auto &pair : m_types) + SendMessage(pair.second, BM_SETCHECK, BST_CHECKED, 0); + + m_list = createControl<ListView>(IDC_PACKAGES, ListView::Columns{ + {AUTO_STR(""), 20}, + {AUTO_STR("Package Name"), 382}, + {AUTO_STR("Category"), 150}, + {AUTO_STR("Version"), 80}, + {AUTO_STR("Author"), 105}, + {AUTO_STR("Type"), 70}, + }); + + m_list->sortByColumn(1); + + reload(); + m_reloadTimer = startTimer(200); +} + +void Browser::onCommand(const int id) +{ + switch(id) { + case IDC_SCRIPTS: + case IDC_EFFECTS: + case IDC_EXTENSIONS: + reload(); + break; + case IDC_FILTER: + m_checkFilter = true; + break; + case IDC_CLEAR: + SetWindowText(m_filterHandle, AUTO_STR("")); + checkFilter(); + break; + case IDOK: + case IDCANCEL: + close(); + break; + } +} + +void Browser::onContextMenu(HWND, const int, const int) +{ + (void)m_reapack; +} + +void Browser::onTimer(const int id) +{ + if(id != m_reloadTimer || !m_checkFilter) + return; + + checkFilter(); +} + +void Browser::checkFilter() +{ + m_checkFilter = false; + + auto_string wideFilter(4096, 0); + GetWindowText(m_filterHandle, &wideFilter[0], (int)wideFilter.size()); + wideFilter.resize(wideFilter.find(AUTO_STR('\0'))); + + const string &filter = from_autostring(wideFilter); + + if(filter != m_filter) { + m_filter = filter; + reload(); + } +} + +void Browser::reload() +{ + InhibitControl freeze(m_list); + + m_list->clear(); + + for(IndexPtr index : m_indexes) { + for(const Package *pkg : index->packages()) { + const Version *ver = pkg->lastVersion(); + + if(!match(ver)) + continue; + + m_list->addRow({"??", pkg->name(), pkg->category()->name(), + ver->name(), ver->displayAuthor(), pkg->displayType()}); + } + } + + m_list->sort(); +} + +bool Browser::match(const Version *ver) +{ + using namespace boost; + + const Package *pkg = ver->package(); + + const auto typeIt = m_types.find(pkg->type()); + + if(typeIt == m_types.end() || + SendMessage(typeIt->second, BM_GETCHECK, 0, 0) == BST_UNCHECKED) + return false; + + return icontains(pkg->name(), m_filter) || + icontains(pkg->category()->name(), m_filter) || + icontains(ver->author(), m_filter); +} diff --git a/src/browser.hpp b/src/browser.hpp @@ -0,0 +1,61 @@ +/* ReaPack: Package manager for REAPER + * Copyright (C) 2015-2016 Christian Fillion + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef REAPACK_BROWSER_HPP +#define REAPACK_BROWSER_HPP + +#include "dialog.hpp" + +#include <map> +#include <memory> +#include <string> +#include <vector> + +class Index; +class ListView; +class ReaPack; +class Version; + +typedef std::shared_ptr<const Index> IndexPtr; + +class Browser : public Dialog { +public: + Browser(const std::vector<IndexPtr> &, ReaPack *); + void reload(); + +protected: + void onInit() override; + void onCommand(int) override; + void onContextMenu(HWND, int x, int y) override; + void onTimer(int) override; + +private: + bool match(const Version *); + void checkFilter(); + + std::vector<IndexPtr> m_indexes; + ReaPack *m_reapack; + bool m_checkFilter; + int m_reloadTimer; + std::string m_filter; + + HWND m_filterHandle; + std::map<int, HWND> m_types; + ListView *m_list; +}; + +#endif diff --git a/src/main.cpp b/src/main.cpp @@ -81,6 +81,9 @@ static void menuHook(const char *name, HMENU handle, int f) menu.addAction(AUTO_STR("&Synchronize packages"), NamedCommandLookup("_REAPACK_SYNC")); + menu.addAction(AUTO_STR("&Browse packages..."), + NamedCommandLookup("_REAPACK_BROWSE")); + menu.addAction(AUTO_STR("&Clean up packages..."), NamedCommandLookup("_REAPACK_CLEANUP")); @@ -159,6 +162,9 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT( reapack->setupAction("REAPACK_SYNC", "ReaPack: Synchronize packages", &reapack->syncAction, bind(&ReaPack::synchronizeAll, reapack)); + reapack->setupAction("REAPACK_BROWSE", "ReaPack: Browse packages...", + &reapack->browseAction, bind(&ReaPack::browsePackages, reapack)); + reapack->setupAction("REAPACK_CLEANUP", "ReaPack: Clean up packages...", &reapack->cleanupAction, bind(&ReaPack::cleanupPackages, reapack)); diff --git a/src/package.cpp b/src/package.cpp @@ -48,6 +48,20 @@ string Package::fullName() const return m_category ? m_category->fullName() + "/" + m_name : m_name; } +string Package::displayType() const +{ + switch(m_type) { + case UnknownType: + return "Unknown"; + case ScriptType: + return "Script"; + case ExtensionType: + return "Extension"; + case EffectType: + return "Effect"; + } +} + Package::~Package() { for(const Version *ver : m_versions) diff --git a/src/package.hpp b/src/package.hpp @@ -42,6 +42,7 @@ public: Category *category() const { return m_category; } Type type() const { return m_type; } + std::string displayType() const; const std::string &name() const { return m_name; } std::string fullName() const; diff --git a/src/reapack.cpp b/src/reapack.cpp @@ -18,6 +18,7 @@ #include "reapack.hpp" #include "about.hpp" +#include "browser.hpp" #include "cleanup.hpp" #include "config.hpp" #include "errors.hpp" @@ -62,9 +63,9 @@ static void CleanupTempFiles() #endif ReaPack::ReaPack(REAPER_PLUGIN_HINSTANCE instance) - : syncAction(), cleanupAction(), importAction(), configAction(), - m_transaction(nullptr), m_progress(nullptr), m_manager(nullptr), - m_import(nullptr), m_cleanup(nullptr), m_instance(instance) + : syncAction(), browseAction(), cleanupAction(), importAction(), configAction(), + m_transaction(nullptr), m_progress(nullptr), m_browser(nullptr), + m_cleanup(nullptr), m_import(nullptr), m_manager(nullptr), m_instance(instance) { m_mainWindow = GetMainHwnd(); m_useRootPath = new UseRootPath(GetResourcePath()); @@ -333,6 +334,32 @@ void ReaPack::cleanupPackages() }); } +void ReaPack::browsePackages() +{ + if(m_browser) { + m_browser->setFocus(); + return; + } + else if(m_transaction) { + ShowMessageBox( + "This feature cannot be used while packages are being installed. " + "Try again later.", "Browse packages", MB_OK + ); + return; + } + + const vector<Remote> &remotes = m_config->remotes()->getEnabled(); + + fetchIndexes(remotes, [=] (const vector<IndexPtr> &indexes) { + m_browser = Dialog::Create<Browser>(m_instance, m_mainWindow, indexes, this); + m_browser->show(); + m_browser->setCloseHandler([=] (INT_PTR) { + Dialog::Destroy(m_browser); + m_browser = nullptr; + }); + }); +} + void ReaPack::fetchIndex(const Remote &remote, const IndexCallback &callback, HWND parent) { diff --git a/src/reapack.hpp b/src/reapack.hpp @@ -28,6 +28,7 @@ #include <reaper_plugin.h> +class Browser; class Cleanup; class Config; class DownloadQueue; @@ -50,6 +51,7 @@ public: static const std::string BUILDTIME; gaccel_register_t syncAction; + gaccel_register_t browseAction; gaccel_register_t cleanupAction; gaccel_register_t importAction; gaccel_register_t configAction; @@ -74,6 +76,7 @@ public: void aboutSelf(); void about(const Remote &, HWND parent); void cleanupPackages(); + void browsePackages(); void fetchIndexes(const std::vector<Remote> &, const IndexesCallback &, HWND = nullptr); @@ -94,9 +97,10 @@ private: Config *m_config; Transaction *m_transaction; Progress *m_progress; - Manager *m_manager; - Import *m_import; + Browser *m_browser; Cleanup *m_cleanup; + Import *m_import; + Manager *m_manager; REAPER_PLUGIN_HINSTANCE m_instance; HWND m_mainWindow; diff --git a/src/report.cpp b/src/report.cpp @@ -60,7 +60,7 @@ void ReportDialog::printVersion(const Version *ver) if(!ver->author().empty()) stream() << " by " << ver->author(); - const string &date = ver->formattedDate(); + const string &date = ver->displayTime(); if(!date.empty()) stream() << " – " << date; diff --git a/src/resource.hpp b/src/resource.hpp @@ -40,6 +40,7 @@ #define IDD_ABOUT_DIALOG 103 #define IDD_IMPORT_DIALOG 104 #define IDD_CLEANUP_DIALOG 105 +#define IDD_BROWSER_DIALOG 106 #define IDC_LABEL 200 #define IDC_LABEL2 201 @@ -57,5 +58,10 @@ #define IDC_PACKAGES 220 #define IDC_GROUPBOX 221 #define IDC_URL 222 +#define IDC_SCRIPTS 223 +#define IDC_EFFECTS 224 +#define IDC_EXTENSIONS 225 +#define IDC_FILTER 226 +#define IDC_CLEAR 227 #endif diff --git a/src/resource.rc b/src/resource.rc @@ -90,3 +90,22 @@ BEGIN DEFPUSHBUTTON "&Uninstall selected packages", IDOK, 176, 180, 120, 14 PUSHBUTTON "&Cancel", IDCANCEL, 299, 180, 45, 14 END + +IDD_BROWSER_DIALOG DIALOGEX 0, 0, 500, 250 +STYLE DIALOG_STYLE +FONT DIALOG_FONT +CAPTION "ReaPack: Package Browser" +BEGIN + LTEXT "Filter:", IDC_LABEL, 5, 7, 20, 10 + EDITTEXT IDC_FILTER, 25, 4, 280, 14, ES_AUTOHSCROLL + PUSHBUTTON "Cl&ear", IDC_CLEAR, 307, 4, 32, 14 + LTEXT "Type:", IDC_LABEL, 360, 7, 20, 10 + CHECKBOX "&Scripts", IDC_SCRIPTS, 380, 4, 40, 14 + CHECKBOX "&Effects", IDC_EFFECTS, 415, 4, 40, 14 + CHECKBOX "&Extensions", IDC_EXTENSIONS, 450, 4, 50, 14 + CONTROL "", IDC_PACKAGES, WC_LISTVIEW, LVS_REPORT | LVS_SHOWSELALWAYS | + WS_BORDER | WS_TABSTOP, 5, 22, 490, 204 + PUSHBUTTON "&OK", IDCANCEL, 368, 230, 40, 14 + PUSHBUTTON "&Close", IDCANCEL, 411, 230, 40, 14 + DEFPUSHBUTTON "&Apply", IDAPPLY, 454, 230, 40, 14 +END diff --git a/src/version.cpp b/src/version.cpp @@ -76,6 +76,14 @@ string Version::fullName() const return m_package ? m_package->fullName() + " " + fName : fName; } +string Version::displayAuthor() const +{ + if(m_author.empty()) + return "Unknown"; + else + return m_author; +} + void Version::addSource(Source *source) { const Source::Platform p = source->platform(); @@ -135,7 +143,7 @@ void Version::setTime(const char *iso8601) m_time.tm_mday = day; } -string Version::formattedDate() const +string Version::displayTime() const { if(m_time.tm_year == 0) return {}; diff --git a/src/version.hpp b/src/version.hpp @@ -44,10 +44,11 @@ public: void setAuthor(const std::string &author) { m_author = author; } const std::string &author() const { return m_author; } + std::string displayAuthor() const; void setTime(const char *iso8601); const std::tm &time() const { return m_time; } - std::string formattedDate() const; + std::string displayTime() const; void setChangelog(const std::string &); const std::string &changelog() const { return m_changelog; } diff --git a/test/package.cpp b/test/package.cpp @@ -26,6 +26,20 @@ TEST_CASE("package type from string", M) { REQUIRE(Package::typeFor("effect") == Package::EffectType); } +TEST_CASE("package type to string", M) { + SECTION("unknown") + REQUIRE("Unknown" == Package(Package::UnknownType, "test").displayType()); + + SECTION("script") + REQUIRE("Script" == Package(Package::ScriptType, "test").displayType()); + + SECTION("extension") + REQUIRE("Extension" == Package(Package::ExtensionType, "test").displayType()); + + SECTION("effect") + REQUIRE("Effect" == Package(Package::EffectType, "test").displayType()); +} + TEST_CASE("empty package name", M) { try { Package pack(Package::ScriptType, string()); diff --git a/test/version.cpp b/test/version.cpp @@ -219,25 +219,25 @@ TEST_CASE("version date", M) { CHECK(ver.time().tm_year == 0); CHECK(ver.time().tm_mon == 0); CHECK(ver.time().tm_mday == 0); - CHECK(ver.formattedDate() == ""); + CHECK(ver.displayTime() == ""); SECTION("valid") { ver.setTime("2016-02-12T01:16:40Z"); REQUIRE(ver.time().tm_year == 2016 - 1900); REQUIRE(ver.time().tm_mon == 2 - 1); REQUIRE(ver.time().tm_mday == 12); - REQUIRE(ver.formattedDate() != ""); + REQUIRE(ver.displayTime() != ""); } SECTION("garbage") { ver.setTime("hello world"); REQUIRE(ver.time().tm_year == 0); - REQUIRE(ver.formattedDate() == ""); + REQUIRE(ver.displayTime() == ""); } SECTION("out of range") { ver.setTime("2016-99-99T99:99:99Z"); - ver.formattedDate(); // no crash + ver.displayTime(); // no crash } }