reapack

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

commit 97495ed1e1db0d9c8de1458158f175600e9e84c5
parent 7738ba0fe5d3b6449f1df2b59919e4746c10f147
Author: cfillion <cfillion@users.noreply.github.com>
Date:   Sun, 29 Oct 2017 09:33:23 -0400

Merge branch 'listview-checkboxes'

Diffstat:
Asrc/iconlist.cpp | 53+++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/iconlist.hpp | 49+++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/listview.cpp | 83+++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------
Msrc/listview.hpp | 16++++++++++------
Msrc/manager.cpp | 86++++++++++++++++++++++++++++---------------------------------------------------
Msrc/manager.hpp | 7+++----
Msrc/resource.rc | 2+-
Mwin32.tup | 2+-
8 files changed, 205 insertions(+), 93 deletions(-)

diff --git a/src/iconlist.cpp b/src/iconlist.cpp @@ -0,0 +1,53 @@ +/* ReaPack: Package manager for REAPER + * Copyright (C) 2015-2017 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 "iconlist.hpp" + +#ifdef _WIN32 +# define DEFINE_ICON(icon, rc, _) IconList::Icon IconList::icon = MAKEINTRESOURCE(rc) +#else +# define DEFINE_ICON(icon, _, name) IconList::Icon IconList::icon = name +#endif + +DEFINE_ICON(CheckedIcon, 141, "checked"); +DEFINE_ICON(UncheckedIcon, 142, "unchecke"); + +IconList::IconList(const std::initializer_list<const Win32::char_type *> &icons) +{ + m_list = ImageList_Create(16, 16, 1, (int)icons.size(), (int)icons.size()); + + for(const auto *icon : icons) + loadIcon(icon); +} + +void IconList::loadIcon(const Win32::char_type *name) +{ +#ifdef _WIN32 + HINSTANCE reaper = GetModuleHandle(nullptr); + HICON icon = LoadIcon(reaper, name); + ImageList_AddIcon(m_list, icon); +#else + HICON icon = LoadNamedImage(name, true); + ImageList_Add(m_list, icon, 0); +#endif + DestroyIcon(icon); +} + +IconList::~IconList() +{ + ImageList_Destroy(m_list); +} diff --git a/src/iconlist.hpp b/src/iconlist.hpp @@ -0,0 +1,49 @@ +/* ReaPack: Package manager for REAPER + * Copyright (C) 2015-2017 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_ICONLIST_HPP +#define REAPACK_ICONLIST_HPP + +#include "win32.hpp" + +#include <initializer_list> + +#ifdef _WIN32 +# include <windows.h> +# include <commctrl.h> +#else +# include <swell.h> +#endif + +class IconList { +public: + typedef const Win32::char_type *Icon; + + static Icon CheckedIcon; + static Icon UncheckedIcon; + + IconList(const std::initializer_list<Icon> &icons); + ~IconList(); + + void loadIcon(Icon icon); + HIMAGELIST handle() const { return m_list; } + +private: + HIMAGELIST m_list; +}; + +#endif diff --git a/src/listview.cpp b/src/listview.cpp @@ -17,6 +17,7 @@ #include "listview.hpp" +#include "iconlist.hpp" #include "menu.hpp" #include "time.hpp" #include "version.hpp" @@ -24,14 +25,22 @@ #include <boost/algorithm/string/case_conv.hpp> +using namespace std; + +static int adjustWidth(const int points) +{ #ifdef _WIN32 -# include <commctrl.h> + if(points < 1) + return points; + else + return (int)ceil(points * 0.863); // magic number to make pretty sizes... +#else + return points; #endif - -using namespace std; +} ListView::ListView(HWND handle, const Columns &columns) - : Control(handle), m_customizable(false), m_sort(), m_defaultSort(), m_dirty(0) + : Control(handle), m_dirty(0), m_customizable(false), m_sort(), m_defaultSort() { for(const Column &col : columns) addColumn(col); @@ -116,6 +125,25 @@ void ListView::updateCell(int row, int cell) m_dirty |= NeedFilterFlag; } +void ListView::enableIcons() +{ + static IconList list({IconList::UncheckedIcon, IconList::CheckedIcon}); + + // NOTE: the list must have the LVS_SHAREIMAGELISTS style to prevent + // it from taking ownership of the image list + ListView_SetImageList(handle(), list.handle(), LVSIL_SMALL); +} + +void ListView::setRowIcon(const int row, const int image) +{ + LVITEM item{}; + item.iItem = translate(row); + item.iImage = image; + item.mask |= LVIF_IMAGE; + + ListView_SetItem(handle(), &item); +} + void ListView::removeRow(const int userIndex) { // translate to view index before fixing lParams @@ -353,12 +381,18 @@ int ListView::selectionSize() const return ListView_GetSelectedCount(handle()); } -int ListView::itemUnderMouse() const +int ListView::itemUnderMouse(bool *overIcon) const { LVHITTESTINFO info{}; GetCursorPos(&info.pt); ScreenToClient(handle(), &info.pt); - ListView_HitTest(handle(), &info); + ListView_SubItemHitTest(handle(), &info); + + if(overIcon) { + *overIcon = info.iSubItem == 0 && + (info.flags & (LVHT_ONITEMICON | LVHT_ONITEMSTATEICON)) != 0 && + (info.flags & LVHT_ONITEMLABEL) == 0; + } return translateBack(info.iItem); } @@ -396,8 +430,9 @@ void ListView::onNotify(LPNMHDR info, LPARAM lParam) case LVN_ITEMCHANGED: m_onSelect(); break; + case NM_CLICK: case NM_DBLCLK: - handleDoubleClick(); + handleClick(info->code == NM_DBLCLK); break; case LVN_COLUMNCLICK: handleColumnClick(lParam); @@ -458,16 +493,21 @@ bool ListView::onContextMenu(HWND dialog, int x, int y) return true; } -void ListView::handleDoubleClick() +void ListView::handleClick(const bool dbclick) { - // user double clicked on an item - if(itemUnderMouse() > -1 && currentIndex() > -1) - m_onActivate(); + bool overIcon; + + if(itemUnderMouse(&overIcon) > -1 && currentIndex() > -1) { + if(dbclick) + m_onActivate(); + else if(overIcon) + m_onIconClick(); + } } -void ListView::handleColumnClick(LPARAM lParam) +void ListView::handleColumnClick(const LPARAM lParam) { - auto info = (LPNMLISTVIEW)lParam; + const auto info = reinterpret_cast<LPNMLISTVIEW>(lParam); const int col = info->iSubItem; SortOrder order = AscendingOrder; @@ -509,18 +549,6 @@ int ListView::translateBack(const int internalIndex) const return -1; } -int ListView::adjustWidth(const int points) -{ -#ifdef _WIN32 - if(points < 1) - return points; - else - return (int)ceil(points * 0.863); // magic number to make pretty sizes... -#else - return points; -#endif -} - void ListView::headerMenu(const int x, const int y) { enum { ACTION_RESTORE = 800 }; @@ -668,6 +696,11 @@ void ListView::Row::setCell(const int i, const string &val, void *data) m_list->updateCell(m_userIndex, i); } +void ListView::Row::setChecked(bool checked) +{ + m_list->setRowIcon(m_userIndex, checked); +} + vector<string> ListView::Row::filterValues() const { vector<string> values; diff --git a/src/listview.hpp b/src/listview.hpp @@ -66,6 +66,7 @@ public: const Cell &cell(const int i) const { return m_cells[i]; } void setCell(const int i, const std::string &, void *data = nullptr); + void setChecked(bool check = true); std::vector<std::string> filterValues() const; @@ -121,9 +122,10 @@ public: void clear(); void reset(); void autoSizeHeader(); + void enableIcons(); int currentIndex() const; - int itemUnderMouse() const; + int itemUnderMouse(bool *overIcon = nullptr) const; int scroll() const; void setScroll(int); @@ -151,6 +153,7 @@ public: void resetColumns(); void onSelect(const VoidSignal::slot_type &slot) { m_onSelect.connect(slot); } + void onIconClick(const VoidSignal::slot_type &slot) { m_onIconClick.connect(slot); } void onActivate(const VoidSignal::slot_type &slot) { m_onActivate.connect(slot); } void onContextMenu(const MenuSignal::slot_type &slot) { m_onContextMenu.connect(slot); } @@ -158,6 +161,7 @@ protected: friend Row; friend BeginEdit; void updateCell(int row, int cell); + void setRowIcon(int row, int icon); void endEdit(); private: @@ -178,10 +182,9 @@ private: void onNotify(LPNMHDR, LPARAM) override; bool onContextMenu(HWND, int, int) override; - static int adjustWidth(int); void setExStyle(int style, bool enable = true); void setSortArrow(bool); - void handleDoubleClick(); + void handleClick(bool dbclick); void handleColumnClick(LPARAM lpnmlistview); int translate(int userIndex) const; int translateBack(int internalIndex) const; @@ -191,16 +194,17 @@ private: void reindexVisible(); void filter(); + int m_dirty; + Filter m_filter; + bool m_customizable; std::vector<Column> m_cols; std::vector<RowPtr> m_rows; boost::optional<Sort> m_sort; boost::optional<Sort> m_defaultSort; - Filter m_filter; - int m_dirty; - VoidSignal m_onSelect; + VoidSignal m_onIconClick; VoidSignal m_onActivate; MenuSignal m_onContextMenu; }; diff --git a/src/manager.cpp b/src/manager.cpp @@ -39,12 +39,12 @@ static const Win32::char_type *ARCHIVE_EXT = L("ReaPackArchive"); using namespace std; enum { - ACTION_ENABLE = 80, ACTION_DISABLE, ACTION_UNINSTALL, ACTION_ABOUT, - ACTION_REFRESH, ACTION_COPYURL, ACTION_SELECT, ACTION_UNSELECT, - ACTION_AUTOINSTALL_GLOBAL, ACTION_AUTOINSTALL_OFF, ACTION_AUTOINSTALL_ON, - ACTION_AUTOINSTALL, ACTION_BLEEDINGEDGE, ACTION_PROMPTOBSOLETE, - ACTION_NETCONFIG, ACTION_RESETCONFIG, ACTION_IMPORT_REPO, - ACTION_IMPORT_ARCHIVE, ACTION_EXPORT_ARCHIVE + ACTION_UNINSTALL = 80, ACTION_ABOUT, ACTION_REFRESH, ACTION_COPYURL, + ACTION_SELECT, ACTION_UNSELECT, ACTION_AUTOINSTALL_GLOBAL, + ACTION_AUTOINSTALL_OFF, ACTION_AUTOINSTALL_ON, ACTION_AUTOINSTALL, + ACTION_BLEEDINGEDGE, ACTION_PROMPTOBSOLETE, ACTION_NETCONFIG, + ACTION_RESETCONFIG, ACTION_IMPORT_REPO, ACTION_IMPORT_ARCHIVE, + ACTION_EXPORT_ARCHIVE, }; Manager::Manager() @@ -65,13 +65,14 @@ void Manager::onInit() disable(m_apply); m_list = createControl<ListView>(IDC_LIST, ListView::Columns{ - {"Name", 115}, - {"Index URL", 415}, - {"State", 60}, + {"Name", 155}, + {"Index URL", 435}, }); - m_list->onActivate(bind(&Manager::aboutRepo, this, true)); + m_list->enableIcons(); m_list->onSelect(bind(&Dialog::startTimer, this, 100, 0, true)); + m_list->onIconClick(bind(&Manager::toggleEnabled, this)); + m_list->onActivate(bind(&Manager::aboutRepo, this, true)); m_list->onContextMenu(bind(&Manager::fillContextMenu, this, _1, _2)); setAnchor(m_list->handle(), AnchorRight | AnchorBottom); @@ -82,7 +83,7 @@ void Manager::onInit() setAnchor(getControl(IDCANCEL), AnchorAll); setAnchor(m_apply, AnchorAll); - auto data = m_serializer.read(g_reapack->config()->windowState.manager, 1); + auto data = m_serializer.read(g_reapack->config()->windowState.manager, 2); restoreState(data); m_list->restoreState(data); @@ -121,12 +122,6 @@ void Manager::onCommand(const int id, int) case IDC_OPTIONS: options(); break; - case ACTION_ENABLE: - setRemoteEnabled(true); - break; - case ACTION_DISABLE: - setRemoteEnabled(false); - break; case ACTION_REFRESH: refreshIndex(); break; @@ -215,14 +210,8 @@ bool Manager::fillContextMenu(Menu &menu, const int index) const return true; } - const UINT enableAction = - menu.addAction("&Enable", ACTION_ENABLE); - const UINT disableAction = - menu.addAction("&Disable", ACTION_DISABLE); - - menu.addSeparator(); - menu.addAction("&Refresh", ACTION_REFRESH); + menu.addAction("&Copy URL", ACTION_COPYURL); Menu autoInstallMenu = menu.addMenu("&Install new packages"); const UINT autoInstallGlobal = autoInstallMenu.addAction( @@ -232,8 +221,6 @@ bool Manager::fillContextMenu(Menu &menu, const int index) const const UINT autoInstallOn = autoInstallMenu.addAction( "When synchronizing", ACTION_AUTOINSTALL_ON); - menu.addAction("&Copy URL", ACTION_COPYURL); - const UINT uninstallAction = menu.addAction("&Uninstall", ACTION_UNINSTALL); @@ -242,8 +229,6 @@ bool Manager::fillContextMenu(Menu &menu, const int index) const menu.addAction(String::format("&About %s", remote.name().c_str()), index | (ACTION_ABOUT << 8)); - bool allEnabled = true; - bool allDisabled = true; bool allProtected = true; bool allAutoInstallGlobal = true; bool allAutoInstallOff = true; @@ -251,11 +236,6 @@ bool Manager::fillContextMenu(Menu &menu, const int index) const for(const int i : m_list->selection()) { const Remote &r = getRemote(i); - if(isRemoteEnabled(r)) - allDisabled = false; - else - allEnabled = false; - if(!r.isProtected()) allProtected = false; @@ -273,10 +253,6 @@ bool Manager::fillContextMenu(Menu &menu, const int index) const } }; - if(allEnabled) - menu.disable(enableAction); - if(allDisabled) - menu.disable(disableAction); if(allProtected) menu.disable(uninstallAction); @@ -301,6 +277,8 @@ bool Manager::onKeyDown(const int key, const int mods) m_list->unselectAll(); else if(mods == CtrlModifier && key == 'C') copyUrl(); + else if(!mods && key == VK_SPACE) + toggleEnabled(); else return false; @@ -327,21 +305,16 @@ void Manager::refresh() int c = 0; auto row = m_list->createRow(); + row->setChecked(isRemoteEnabled(remote)); row->setCell(c++, remote.name()); row->setCell(c++, remote.url()); - updateEnabledCell(row->index(), remote); if(find(selected.begin(), selected.end(), remote.name()) != selected.end()) m_list->select(row->index()); } } -void Manager::updateEnabledCell(int index, const Remote &remote) -{ - m_list->row(index)->setCell(2, isRemoteEnabled(remote) ? "Enabled" : "Disabled"); -} - -void Manager::setMods(const ModsCallback &cb, const bool updateRow) +void Manager::setMods(const ModsCallback &cb) { ListView::BeginEdit edit(m_list); @@ -352,7 +325,7 @@ void Manager::setMods(const ModsCallback &cb, const bool updateRow) if(it == m_mods.end()) { RemoteMods mods; - cb(remote, &mods); + cb(remote, index, &mods); if(!mods) continue; @@ -362,27 +335,28 @@ void Manager::setMods(const ModsCallback &cb, const bool updateRow) } else { RemoteMods *mods = &it->second; - cb(remote, mods); + cb(remote, index, mods); if(!*mods) { m_mods.erase(it); setChange(-1); } } - - if(updateRow) - updateEnabledCell(index, remote); } } -void Manager::setRemoteEnabled(const bool enabled) +void Manager::toggleEnabled() { - setMods([=](const Remote &remote, RemoteMods *mods) { - if(remote.isEnabled() == enabled) + setMods([=](const Remote &remote, const int index, RemoteMods *mods) { + const bool enable = !mods->enable.value_or(remote.isEnabled()); + + if(remote.isEnabled() == enable) mods->enable = boost::none; else - mods->enable = enabled; - }, true); + mods->enable = enable; + + m_list->row(index)->setChecked(enable); + }); } bool Manager::isRemoteEnabled(const Remote &remote) const @@ -397,7 +371,7 @@ bool Manager::isRemoteEnabled(const Remote &remote) const void Manager::setRemoteAutoInstall(const tribool &enabled) { - setMods([=](const Remote &remote, RemoteMods *mods) { + setMods([=](const Remote &remote, int, RemoteMods *mods) { const bool same = remote.autoInstall() == enabled || (indeterminate(remote.autoInstall()) && indeterminate(enabled)); @@ -405,7 +379,7 @@ void Manager::setRemoteAutoInstall(const tribool &enabled) mods->autoInstall = boost::none; else mods->autoInstall = enabled; - }, false); + }); } tribool Manager::remoteAutoInstall(const Remote &remote) const diff --git a/src/manager.hpp b/src/manager.hpp @@ -52,13 +52,12 @@ private: operator bool() const { return enable || autoInstall; } }; - typedef std::function<void (const Remote &, RemoteMods *)> ModsCallback; + typedef std::function<void (const Remote &, int index, RemoteMods *)> ModsCallback; Remote getRemote(int index) const; bool fillContextMenu(Menu &, int index) const; - void updateEnabledCell(int index, const Remote &); - void setMods(const ModsCallback &, bool updateRow); - void setRemoteEnabled(bool); + void setMods(const ModsCallback &); + void toggleEnabled(); bool isRemoteEnabled(const Remote &) const; void setRemoteAutoInstall(const tribool &); tribool remoteAutoInstall(const Remote &) const; diff --git a/src/resource.rc b/src/resource.rc @@ -33,7 +33,7 @@ BEGIN LTEXT "The repositories enabled in this list are used to populate the package list:", IDC_LABEL, 5, 5, 360, 10 CONTROL "", IDC_LIST, WC_LISTVIEW, LVS_REPORT | LVS_SHOWSELALWAYS | - WS_BORDER | WS_TABSTOP, 5, 18, 360, 200 + LVS_SHAREIMAGELISTS | WS_BORDER | WS_TABSTOP, 5, 18, 360, 200 PUSHBUTTON "&Browse packages", IDC_BROWSE, 5, 221, 75, 14 PUSHBUTTON "&Import/export...", IDC_IMPORT, 83, 221, 65, 14 PUSHBUTTON "&Options...", IDC_OPTIONS, 151, 221, 45, 14 diff --git a/win32.tup b/win32.tup @@ -25,7 +25,7 @@ SQLFLAGS += /DSQLITE_OMIT_COMPILEOPTION_DIAGS /DSQLITE_OMIT_CAST SQLFLAGS += /DSQLITE_OMIT_CHECK /DSQLITE_OMIT_DECLTYPE /DSQLITE_OMIT_DEPRECATED LD := $(WRAP) link -LDFLAGS := /nologo User32.lib Shell32.lib Gdi32.lib Comdlg32.lib +LDFLAGS := /nologo User32.lib Shell32.lib Gdi32.lib Comdlg32.lib Comctl32.lib LDFLAGS += vendor/libcurl@(SUFFIX)/lib/libcurl_a.lib LDFLAGS += $(TUP_VARIANTDIR)/src/resource.res LDFLAGS += $(TUP_VARIANTDIR)/build/vendor/sqlite3.o