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:
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