commit 4dd103cbb07beacfa14394b979e0f4ff2a10c891
parent 9a8ef6677e6d3541f0138598f717043e81dab8b6
Author: cfillion <cfillion@users.noreply.github.com>
Date: Sat, 5 Dec 2015 21:07:40 -0500
show a progress dialog when running a transaction
Diffstat:
17 files changed, 411 insertions(+), 62 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -2,5 +2,6 @@
.tup/
bin/
build/
+src/resource.rc_mac*
vendor/
!vendor/.gitkeep
diff --git a/Tupfile b/Tupfile
@@ -14,7 +14,7 @@ else
endif
endif
-: foreach src/*.cpp |> !build |> build/%B.o
+: foreach src/*.cpp | $(PREDEPS) |> !build |> build/%B.o
: foreach $(WDLSOURCE) |> !build $(WDLFLAGS) |> build/wdl_%B.o
: build/*.o |> !link $(SOFLAGS) |> $(SOTARGET)
diff --git a/Tupfile.osx b/Tupfile.osx
@@ -1,8 +1,10 @@
CXX := c++
-CXXFLAGS := -Wall -Wextra -Werror -Wno-unused-parameter -pipe -fPIC
+CXXFLAGS := -Wall -Wextra -Werror
+CXXFLAGS += -Wno-unused-parameter -Wno-missing-field-initializers
+CXXFLAGS += -Wno-unused-function
CXXFLAGS += -fdiagnostics-color -fstack-protector-strong
-CXXFLAGS += -O2 -std=c++14
+CXXFLAGS += -pipe -fPIC -O2 -std=c++14
CXXFLAGS += -Ivendor -Ivendor/WDL -Ivendor/WDL/WDL
CXXFLAGS += -DWDL_NO_DEFINE_MINMAX
@@ -21,3 +23,6 @@ TSTARGET := bin/test
!build = |> $(CXX) $(CXXFLAGS) -c %f -o %o |>
!link = |> $(CXX) $(CXXFLAGS) %f $(LDFLAGS) -o %o |>
+
+PREDEPS := src/resource.rc_mac_menu src/resource.rc_mac_dlg
+: src/resource.rc |> php $(SWELL)/mac_resgen.php %f |> $(PREDEPS)
diff --git a/src/dialog.cpp b/src/dialog.cpp
@@ -0,0 +1,121 @@
+#include "dialog.hpp"
+
+DialogMap Dialog::s_instances;
+
+WDL_DLGRET Dialog::Proc(HWND handle, UINT msg, WPARAM wParam, LPARAM lParam)
+{
+ Dialog *dlg = 0;
+
+ const auto it = Dialog::s_instances.find(handle);
+ if(it != Dialog::s_instances.end())
+ dlg = it->second;
+
+ switch(msg) {
+ case WM_INITDIALOG:
+ dlg = (Dialog *)lParam;
+ dlg->m_handle = handle;
+ Dialog::s_instances[handle] = dlg;
+
+ dlg->onInit();
+ break;
+ case WM_SHOWWINDOW:
+ if(wParam) {
+ dlg->m_isVisible = true;
+ dlg->onShow();
+ }
+ else {
+ dlg->m_isVisible = false;
+ dlg->onHide();
+ }
+ break;
+ case WM_TIMER:
+ dlg->onTimer();
+ break;
+ case WM_COMMAND:
+ dlg->onCommand(wParam, lParam);
+ break;
+ case WM_DESTROY:
+ dlg->onDestroy();
+ break;
+ };
+
+ return false;
+}
+
+Dialog::Dialog(const int templateId)
+ : m_template(templateId), m_parent(0), m_handle(0), m_isVisible(false)
+{
+}
+
+void Dialog::init(REAPER_PLUGIN_HINSTANCE instance, HWND parent)
+{
+ m_parent = parent;
+
+ CreateDialogParam(instance, MAKEINTRESOURCE(m_template),
+ m_parent, Proc, (LPARAM)this);
+}
+
+void Dialog::Destroy(Dialog *dlg)
+{
+ if(dlg->isVisible())
+ dlg->onHide();
+
+ delete dlg;
+}
+
+Dialog::~Dialog()
+{
+ DestroyWindow(m_handle);
+ s_instances.erase(m_handle);
+}
+
+void Dialog::show()
+{
+ center();
+
+ ShowWindow(m_handle, SW_SHOW);
+}
+
+void Dialog::hide()
+{
+ ShowWindow(m_handle, SW_HIDE);
+}
+
+void Dialog::center()
+{
+ RECT dialogRect, rect;
+
+ GetWindowRect(m_handle, &dialogRect);
+ GetWindowRect(m_parent, &rect);
+
+ OffsetRect(&dialogRect, -dialogRect.left, -dialogRect.top);
+ OffsetRect(&rect, -(dialogRect.right / 2), 0);
+ OffsetRect(&rect, 0, -(dialogRect.bottom / 2));
+
+ SetWindowPos(m_handle, HWND_TOP, rect.right / 2, rect.bottom / 1.4,
+ 0, 0, SWP_NOSIZE);
+}
+
+void Dialog::onInit()
+{
+}
+
+void Dialog::onShow()
+{
+}
+
+void Dialog::onHide()
+{
+}
+
+void Dialog::onTimer()
+{
+}
+
+void Dialog::onCommand(WPARAM, LPARAM)
+{
+}
+
+void Dialog::onDestroy()
+{
+}
diff --git a/src/dialog.hpp b/src/dialog.hpp
@@ -0,0 +1,55 @@
+#ifndef REAPACK_DIALOG_HPP
+#define REAPACK_DIALOG_HPP
+
+#include <map>
+
+#include <wdltypes.h>
+#include <reaper_plugin.h>
+
+class Dialog;
+typedef std::map<HWND, Dialog *> DialogMap;
+
+class Dialog {
+public:
+ template<typename T>
+ static T *Create(REAPER_PLUGIN_HINSTANCE instance, HWND parent)
+ {
+ Dialog *dlg = new T();
+ dlg->init(instance, parent);
+
+ return dynamic_cast<T *>(dlg);
+ }
+
+ static void Destroy(Dialog *);
+
+ void init(REAPER_PLUGIN_HINSTANCE, HWND);
+
+ HWND handle() const { return m_handle; }
+ bool isVisible() const { return m_isVisible; }
+
+ void show();
+ void hide();
+ void center();
+
+protected:
+ Dialog(const int templateId);
+ virtual ~Dialog();
+
+ virtual void onInit();
+ virtual void onShow();
+ virtual void onHide();
+ virtual void onTimer();
+ virtual void onCommand(WPARAM, LPARAM);
+ virtual void onDestroy();
+
+private:
+ static WDL_DLGRET Proc(HWND, UINT, WPARAM, LPARAM);
+ static DialogMap s_instances;
+
+ const int m_template;
+ HWND m_parent;
+ HWND m_handle;
+
+ bool m_isVisible;
+};
+#endif
diff --git a/src/download.cpp b/src/download.cpp
@@ -22,7 +22,7 @@ Download::~Download()
s_active.erase(remove(s_active.begin(), s_active.end(), this));
// call stop after removing from the active list to prevent
- // bad access from timeTick -> execCallbacks
+ // bad access from timeTick -> finishInMainThread
stop();
// free the content buffer
@@ -37,11 +37,6 @@ void Download::reset()
m_contents.clear();
}
-void Download::addCallback(const Download::Callback &callback)
-{
- m_onFinish.connect(callback);
-}
-
void Download::TimerTick()
{
vector<Download *> &activeDownloads = Download::s_active;
@@ -54,11 +49,11 @@ void Download::TimerTick()
if(!download->isFinished())
continue;
- // this need to be done before execCallback in case one of the callback
- // deletes the download object
+ // this need to be done before finishInMainThread in case one
+ // of the callbacks deletes the download object
activeDownloads.erase(begin + i);
- download->execCallbacks();
+ download->finishInMainThread();
}
if(Download::s_active.empty())
@@ -73,6 +68,8 @@ void Download::start()
reset();
s_active.push_back(this);
+ m_onStart();
+
plugin_register("timer", (void*)TimerTick);
m_threadHandle = CreateThread(NULL, 0, Worker, (void *)this, 0, 0);
@@ -168,7 +165,7 @@ void Download::finish(const int status, const string &contents)
m_contents = contents;
}
-void Download::execCallbacks()
+void Download::finishInMainThread()
{
WDL_MutexLock lock(&m_mutex);
@@ -216,7 +213,9 @@ DownloadQueue::~DownloadQueue()
void DownloadQueue::push(Download *dl)
{
- dl->addCallback([=]() {
+ m_onPush(dl);
+
+ dl->onFinish([=]() {
m_queue.pop();
delete dl;
diff --git a/src/download.hpp b/src/download.hpp
@@ -26,7 +26,8 @@ public:
bool isFinished();
bool isAborted();
- void addCallback(const Callback &);
+ void onStart(const Callback &callback) { m_onStart.connect(callback); }
+ void onFinish(const Callback &callback) { m_onFinish.connect(callback); }
void start();
void stop();
@@ -39,7 +40,7 @@ private:
static DWORD WINAPI Worker(void *ptr);
void finish(const int status, const std::string &contents);
- void execCallbacks();
+ void finishInMainThread();
void abort();
void reset();
@@ -52,6 +53,7 @@ private:
std::string m_name;
std::string m_url;
+ Signal m_onStart;
Signal m_onFinish;
WDL_Mutex m_mutex;
@@ -59,14 +61,24 @@ private:
class DownloadQueue {
public:
+ typedef boost::signals2::signal<void (Download *)> Signal;
+ typedef Signal::slot_type Callback;
+
+ DownloadQueue() {}
+ DownloadQueue(const DownloadQueue &) = delete;
~DownloadQueue();
void push(Download *);
+
size_t size() const { return m_queue.size(); }
bool empty() const { return m_queue.empty(); }
+ void onPush(const Callback &callback) { m_onPush.connect(callback); }
+
private:
std::queue<Download *> m_queue;
+
+ Signal m_onPush;
};
#endif
diff --git a/src/main.cpp b/src/main.cpp
@@ -4,6 +4,8 @@
#define REAPERAPI_IMPLEMENT
#include <reaper_plugin_functions.h>
+using namespace std;
+
static ReaPack reapack;
static bool commandHook(const int id, const int flag)
@@ -42,10 +44,10 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
reapack.init(instance, rec);
reapack.setupAction("REAPACK_SYNC", "ReaPack: Synchronize Packages",
- &reapack.syncAction, std::bind(&ReaPack::synchronize, reapack));
+ &reapack.syncAction, bind(&ReaPack::synchronize, reapack));
reapack.setupAction("REAPACK_IMPORT",
- std::bind(&ReaPack::importRemote, reapack));
+ bind(&ReaPack::importRemote, reapack));
rec->Register("hookcommand", (void *)commandHook);
rec->Register("hookcustommenu", (void *)menuHook);
@@ -54,3 +56,13 @@ extern "C" REAPER_PLUGIN_DLL_EXPORT int REAPER_PLUGIN_ENTRYPOINT(
return 1;
}
+
+#ifdef __APPLE__
+#include "resource.hpp"
+
+#include <swell/swell-dlggen.h>
+#include "resource.rc_mac_dlg"
+
+#include <swell/swell-menugen.h>
+#include "resource.rc_mac_menu"
+#endif
diff --git a/src/package.hpp b/src/package.hpp
@@ -4,7 +4,6 @@
#include "path.hpp"
#include "version.hpp"
-class Database;
class Category;
class Package;
diff --git a/src/progress.cpp b/src/progress.cpp
@@ -0,0 +1,69 @@
+#include "progress.hpp"
+
+#include "download.hpp"
+#include "resource.hpp"
+#include "transaction.hpp"
+
+using namespace std;
+
+Progress::Progress()
+ : Dialog(IDD_PROGRESS_DIALOG),
+ m_transaction(0), m_done(0), m_total(0), m_label(0), m_progress(0)
+{
+}
+
+void Progress::setTransaction(Transaction *t)
+{
+ m_transaction = t;
+
+ SetWindowText(m_label, "");
+
+ m_done = 0;
+ m_total = 0;
+ SendMessage(m_progress, PBM_SETPOS, m_done, 0);
+
+ if(!m_transaction)
+ return;
+
+ m_transaction->downloadQueue()->onPush(
+ bind(&Progress::addDownload, this, placeholders::_1));
+}
+
+void Progress::onInit()
+{
+ m_label = GetDlgItem(handle(), IDC_LABEL);
+ m_progress = GetDlgItem(handle(), IDC_PROGRESS);
+}
+
+void Progress::onCommand(WPARAM wParam, LPARAM)
+{
+ const int commandId = LOWORD(wParam);
+
+ switch(commandId) {
+ case IDCANCEL:
+ m_transaction->cancel();
+ break;
+ }
+}
+
+void Progress::addDownload(Download *dl)
+{
+ m_total++;
+ updateProgress();
+
+ dl->onStart([=] {
+ const string text = "Downloading: " + dl->name() + "\n" + dl->url();
+ SetWindowText(m_label, text.c_str());
+ });
+
+ dl->onFinish([=] {
+ m_done++;
+ updateProgress();
+ });
+}
+
+void Progress::updateProgress()
+{
+ const double pos = m_done / m_total;
+ SendMessage(m_progress, PBM_SETPOS, pos * 100, 0);
+}
diff --git a/src/progress.hpp b/src/progress.hpp
@@ -0,0 +1,32 @@
+#ifndef REAPACK_PROGRESS_HPP
+#define REAPACK_PROGRESS_HPP
+
+#include "dialog.hpp"
+
+class Download;
+class Transaction;
+
+class Progress : public Dialog {
+public:
+ Progress();
+
+ void setTransaction(Transaction *t);
+
+protected:
+ void onInit() override;
+ void onCommand(WPARAM, LPARAM) override;
+
+private:
+ void addDownload(Download *);
+ void updateProgress();
+
+ Transaction *m_transaction;
+
+ int m_done;
+ double m_total;
+
+ HWND m_label;
+ HWND m_progress;
+};
+
+#endif
diff --git a/src/reapack.cpp b/src/reapack.cpp
@@ -1,5 +1,7 @@
#include "reapack.hpp"
+#include "config.hpp"
+#include "progress.hpp"
#include "transaction.hpp"
#include <reaper_plugin_functions.h>
@@ -9,7 +11,7 @@
using namespace std;
ReaPack::ReaPack()
- : m_transaction(0)
+ : m_config(0), m_transaction(0), m_progress(0)
{
}
@@ -17,13 +19,13 @@ void ReaPack::init(REAPER_PLUGIN_HINSTANCE instance, reaper_plugin_info_t *rec)
{
m_instance = instance;
m_rec = rec;
- m_mainHandle = GetMainHwnd();
+ m_mainWindow = GetMainHwnd();
m_resourcePath.append(GetResourcePath());
- // wtf? If m_config is a member object different instances will be used
- // in importRemote(), synchronize() and cleanup()
m_config = new Config;
m_config->read(m_resourcePath + "reapack.ini");
+
+ m_progress = Dialog::Create<Progress>(m_instance, m_mainWindow);
}
void ReaPack::cleanup()
@@ -32,6 +34,8 @@ void ReaPack::cleanup()
// and two times during shutdown on osx... cleanup() is called only once
m_config->write();
delete m_config;
+
+ Dialog::Destroy(m_progress);
}
int ReaPack::setupAction(const char *name, const ActionCallback &callback)
@@ -65,33 +69,8 @@ bool ReaPack::execActions(const int id, const int)
return true;
}
-Transaction *ReaPack::createTransaction()
-{
- if(m_transaction)
- return 0;
-
- m_transaction = new Transaction(m_config->registry(), m_resourcePath);
-
- m_transaction->onReady([=] {
- // TODO: display the package list with the changelogs
- m_transaction->run();
- });
-
- m_transaction->onFinish([=] {
- delete m_transaction;
- m_transaction = 0;
-
- m_config->write();
- });
-
- return m_transaction;
-}
-
void ReaPack::synchronize()
{
- if(m_transaction)
- return;
-
RemoteMap remotes = m_config->remotes();
if(remotes.empty()) {
@@ -141,3 +120,31 @@ void ReaPack::importRemote()
if(t)
t->fetch(remote);
}
+
+Transaction *ReaPack::createTransaction()
+{
+ if(m_transaction)
+ return 0;
+
+ m_transaction = new Transaction(m_config->registry(), m_resourcePath);
+
+ m_transaction->onReady([=] {
+ // TODO: display the package list with the changelogs
+ m_transaction->run();
+ });
+
+ m_transaction->onFinish([=] {
+ m_progress->setTransaction(0);
+ m_progress->hide();
+
+ delete m_transaction;
+ m_transaction = 0;
+
+ m_config->write();
+ });
+
+ m_progress->setTransaction(m_transaction);
+ m_progress->show();
+
+ return m_transaction;
+}
diff --git a/src/reapack.hpp b/src/reapack.hpp
@@ -4,14 +4,15 @@
#include <functional>
#include <map>
-#include "config.hpp"
#include "path.hpp"
#include <reaper_plugin.h>
typedef std::function<void()> ActionCallback;
+class Config;
class Transaction;
+class Progress;
class ReaPack {
public:
@@ -37,10 +38,11 @@ private:
Config *m_config;
Transaction *m_transaction;
+ Progress *m_progress;
REAPER_PLUGIN_HINSTANCE m_instance;
reaper_plugin_info_t *m_rec;
- HWND m_mainHandle;
+ HWND m_mainWindow;
Path m_resourcePath;
};
diff --git a/src/resource.hpp b/src/resource.hpp
@@ -0,0 +1,11 @@
+#ifndef REAPACK_RESOURCE_HPP
+#define REAPACK_RESOURCE_HPP
+
+#define PROGRESS_CLASS "msctls_progress32"
+
+#define IDD_PROGRESS_DIALOG 100
+
+#define IDC_LABEL 200
+#define IDC_PROGRESS 201
+
+#endif
diff --git a/src/resource.rc b/src/resource.rc
@@ -0,0 +1,10 @@
+#include "resource.hpp"
+
+IDD_PROGRESS_DIALOG DIALOGEX 0, 0, 260, 85
+STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_SYSMENU | WS_CAPTION
+CAPTION "ReaPack: Download Progress"
+BEGIN
+ LTEXT "File Name", IDC_LABEL, 5, 5, 250, 30
+ CONTROL "", IDC_PROGRESS, PROGRESS_CLASS, 0x0, 5, 40, 250, 11
+ PUSHBUTTON "Cancel", IDCANCEL, 105, 60, 50, 14
+END
diff --git a/src/transaction.cpp b/src/transaction.cpp
@@ -30,7 +30,7 @@ void Transaction::fetch(const RemoteMap &remotes)
void Transaction::fetch(const Remote &remote)
{
Download *dl = new Download(remote.first, remote.second);
- dl->addCallback([=]() {
+ dl->onFinish([=]() {
if(dl->status() != 200) {
addError(dl->contents(), dl->name());
return;
@@ -47,17 +47,22 @@ void Transaction::fetch(const Remote &remote)
file << dl->contents();
file.close();
- Database *db = Database::load(path.join().c_str());
- db->setName(dl->name());
+ try {
+ Database *db = Database::load(path.join().c_str());
+ db->setName(dl->name());
- m_databases.push_back(db);
+ m_databases.push_back(db);
+ }
+ catch(const reapack_error &e) {
+ addError(e.what(), dl->name());
+ }
});
m_queue.push(dl);
// execute prepare after the download is deleted, in case finish is called
// the queue will also not contain the download anymore
- dl->addCallback(bind(&Transaction::prepare, this));
+ dl->onFinish(bind(&Transaction::prepare, this));
}
void Transaction::prepare()
@@ -93,13 +98,19 @@ void Transaction::run()
}
}
+void Transaction::cancel()
+{
+ ShowMessageBox("Not Implemented", "Cancel transaction", 0);
+}
+
void Transaction::install(Package *pkg)
{
const string &url = pkg->lastVersion()->source(0)->url();
const Path path = m_root + pkg->targetPath();
+ const string dbName = pkg->category()->database()->name();
- Download *dl = new Download(pkg->name(), url);
- dl->addCallback([=] {
+ Download *dl = new Download(dbName + "/" + pkg->name(), url);
+ dl->onFinish([=] {
if(dl->status() != 200) {
addError(dl->contents(), dl->name());
return;
@@ -123,7 +134,7 @@ void Transaction::install(Package *pkg)
// execute finish after the download is deleted
// this prevents the download queue from being deleted before the download
- dl->addCallback(bind(&Transaction::finish, this));
+ dl->onFinish(bind(&Transaction::finish, this));
}
void Transaction::finish()
@@ -136,5 +147,5 @@ void Transaction::finish()
void Transaction::addError(const string &message, const string &title)
{
- ShowMessageBox(message.c_str(), title.c_str(), 0);
+ // ShowMessageBox(message.c_str(), title.c_str(), 0);
}
diff --git a/src/transaction.hpp b/src/transaction.hpp
@@ -23,15 +23,18 @@ public:
void fetch(const RemoteMap &);
void fetch(const Remote &);
- void prepare();
void run();
- void finish();
+ void cancel();
const PackageList &packages() const { return m_packages; }
+ DownloadQueue *downloadQueue() { return &m_queue; }
private:
+ void prepare();
+ void finish();
+
void install(Package *);
- void addError(const std::string &, const std::string &);
+ void addError(const std::string &msg, const std::string &title);
Registry *m_registry;