summaryrefslogtreecommitdiff
path: root/RealtimeSync/tray_menu.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'RealtimeSync/tray_menu.cpp')
-rw-r--r--RealtimeSync/tray_menu.cpp479
1 files changed, 262 insertions, 217 deletions
diff --git a/RealtimeSync/tray_menu.cpp b/RealtimeSync/tray_menu.cpp
index e18101e0..4afef5e4 100644
--- a/RealtimeSync/tray_menu.cpp
+++ b/RealtimeSync/tray_menu.cpp
@@ -16,38 +16,42 @@
#include <wx/menu.h>
#include "watcher.h"
#include <wx/utils.h>
-#include <wx/log.h>
#include <wx/icon.h> //Linux needs this
#include <wx/timer.h>
+#include <wx+/mouse_move_dlg.h>
#include "resources.h"
#include <wx+/string_conv.h>
#include <zen/assert_static.h>
#include <zen/build_info.h>
#include <wx+/shell_execute.h>
+#include "gui_generated.h"
using namespace rts;
using namespace zen;
-class TrayIconHolder : private wxEvtHandler
+
+namespace
{
-public:
- TrayIconHolder(const wxString& jobname);
- ~TrayIconHolder();
+struct AbortCallback //never throw exceptions through a C-Layer (GUI)!
+{
+ virtual ~AbortCallback() {}
+ virtual void requestResume() = 0;
+ virtual void requestAbort() = 0;
+};
- void doUiRefreshNow();
- void showIconActive();
- void showIconWaiting();
- void requestAbort()
+//RtsTrayIcon is a dumb class whose sole purpose is to enable wxWidgets deferred deletion
+class RtsTrayIconRaw : public wxTaskBarIcon
+{
+public:
+ RtsTrayIconRaw(AbortCallback& abortCb) : abortCb_(&abortCb)
{
- m_abortRequested = true;
+ Connect(wxEVT_TASKBAR_LEFT_DCLICK, wxCommandEventHandler(RtsTrayIconRaw::OnDoubleClick), nullptr, this);
}
- void OnRequestResume(wxCommandEvent& event)
- {
- m_resumeRequested = true;
- }
+ void dontCallBackAnymore() { abortCb_ = nullptr; } //call before tray icon is marked for deferred deletion
+private:
enum Selection
{
CONTEXT_ABORT,
@@ -55,34 +59,9 @@ public:
CONTEXT_ABOUT
};
- void OnContextMenuSelection(wxCommandEvent& event);
-
-private:
- class RtsTrayIcon;
- RtsTrayIcon* trayMenu;
-
- bool m_abortRequested;
- bool m_resumeRequested;
-
- const wxString jobName_; //RTS job name, may be empty
-};
-
-
-//RtsTrayIcon shall be a dumb class whose sole purpose is to enable wxWidgets deferred deletion
-class TrayIconHolder::RtsTrayIcon : public wxTaskBarIcon
-{
-public:
- RtsTrayIcon(TrayIconHolder* parent) : parent_(parent) {}
-
- void parentHasDied() //call before tray icon is marked for deferred deletion
- {
- parent_ = nullptr;
- }
-
-private:
virtual wxMenu* CreatePopupMenu()
{
- if (!parent_)
+ if (!abortCb_)
return nullptr;
wxMenu* contextMenu = new wxMenu;
@@ -91,169 +70,150 @@ private:
contextMenu->AppendSeparator();
contextMenu->Append(CONTEXT_ABORT, _("&Exit"));
//event handling
- contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(TrayIconHolder::OnContextMenuSelection), nullptr, parent_);
+ contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(RtsTrayIconRaw::OnContextMenuSelection), nullptr, this);
return contextMenu; //ownership transferred to caller
}
- TrayIconHolder* parent_;
-};
-//##############################################################################################################
+ void OnContextMenuSelection(wxCommandEvent& event)
+ {
+ if (!abortCb_)
+ return;
+ switch (static_cast<Selection>(event.GetId()))
+ {
+ case CONTEXT_ABORT:
+ abortCb_->requestAbort();
+ break;
-class AbortThisProcess //exception class
-{
-public:
- AbortThisProcess(MonitorResponse command) : command_(command) {}
+ case CONTEXT_RESTORE:
+ abortCb_->requestResume();
+ break;
+
+ case CONTEXT_ABOUT:
+ {
+ //build information
+ wxString build = __TDATE__;
+#if wxUSE_UNICODE
+ build += L" - Unicode";
+#else
+ build += L" - ANSI";
+#endif //wxUSE_UNICODE
+
+ //compile time info about 32/64-bit build
+ if (zen::is64BitBuild)
+ build += L" x64";
+ else
+ build += L" x86";
+ assert_static(zen::is32BitBuild || zen::is64BitBuild);
+
+ wxMessageBox(L"RealtimeSync" L"\n\n" + replaceCpy(_("(Build: %x)"), L"%x", build), _("About"), wxOK);
+ }
+ break;
+ }
+ }
- MonitorResponse getCommand() const
+ void OnDoubleClick(wxCommandEvent& event)
{
- return command_;
+ if (abortCb_)
+ abortCb_->requestResume();
}
-private:
- MonitorResponse command_;
+ AbortCallback* abortCb_;
};
-//##############################################################################################################
-TrayIconHolder::TrayIconHolder(const wxString& jobname) :
- m_abortRequested(false),
- m_resumeRequested(false),
- jobName_(jobname)
+class TrayIconHolder
{
- trayMenu = new RtsTrayIcon(this); //not in initialization list: give it a valid parent object!
-
- showIconActive();
-
- //register double-click
- trayMenu->Connect(wxEVT_TASKBAR_LEFT_DCLICK, wxCommandEventHandler(TrayIconHolder::OnRequestResume), nullptr, this);
-}
-
+public:
+ TrayIconHolder(const wxString& jobname, AbortCallback& abortCb) :
+ jobName_(jobname)
+ {
+ trayMenu = new RtsTrayIconRaw(abortCb); //not in initialization list: give it a valid parent object!
+ showIconActive();
+ }
-TrayIconHolder::~TrayIconHolder()
-{
- trayMenu->Disconnect(wxEVT_TASKBAR_LEFT_DCLICK, wxCommandEventHandler(TrayIconHolder::OnRequestResume), nullptr, this);
- trayMenu->RemoveIcon(); //(try to) hide icon until final deletion takes place
- trayMenu->parentHasDied();
+ ~TrayIconHolder()
+ {
+ trayMenu->RemoveIcon(); //(try to) hide icon until final deletion takes place
+ trayMenu->dontCallBackAnymore();
- //use wxWidgets delayed destruction: delete during next idle loop iteration (handle late window messages, e.g. when double-clicking)
- if (!wxPendingDelete.Member(trayMenu))
- wxPendingDelete.Append(trayMenu);
-}
+ //use wxWidgets delayed destruction: delete during next idle loop iteration (handle late window messages, e.g. when double-clicking)
+ if (!wxPendingDelete.Member(trayMenu))
+ wxPendingDelete.Append(trayMenu);
+ }
+ void doUiRefreshNow()
+ {
+ wxTheApp->Yield();
+ } //yield is UI-layer which is represented by this tray icon
-void TrayIconHolder::showIconActive()
-{
- wxIcon realtimeIcon;
+ void showIconActive()
+ {
+ wxIcon realtimeIcon;
#ifdef FFS_WIN
- realtimeIcon.CopyFromBitmap(GlobalResources::getImage(wxT("RTS_tray_win.png"))); //use a 16x16 bitmap
+ realtimeIcon.CopyFromBitmap(GlobalResources::getImage(L"RTS_tray_win.png")); //use a 16x16 bitmap
#elif defined FFS_LINUX
- realtimeIcon.CopyFromBitmap(GlobalResources::getImage(wxT("RTS_tray_linux.png"))); //use a 22x22 bitmap for perfect fit
+ realtimeIcon.CopyFromBitmap(GlobalResources::getImage(L"RTS_tray_linux.png")); //use a 22x22 bitmap for perfect fit
#endif
- const wxString postFix = jobName_.empty() ? wxString() : (wxT("\n\"") + jobName_ + wxT("\""));
- trayMenu->SetIcon(realtimeIcon, _("Monitoring active...") + postFix);
-}
-
+ const wxString postFix = jobName_.empty() ? wxString() : (L"\n\"" + jobName_ + L"\"");
+ trayMenu->SetIcon(realtimeIcon, _("Monitoring active...") + postFix);
+ }
-void TrayIconHolder::showIconWaiting()
-{
- wxIcon realtimeIcon;
+ void showIconWaiting()
+ {
+ wxIcon realtimeIcon;
#ifdef FFS_WIN
- realtimeIcon.CopyFromBitmap(GlobalResources::getImage(wxT("RTS_tray_waiting_win.png"))); //use a 16x16 bitmap
+ realtimeIcon.CopyFromBitmap(GlobalResources::getImage(L"RTS_tray_waiting_win.png")); //use a 16x16 bitmap
#elif defined FFS_LINUX
- realtimeIcon.CopyFromBitmap(GlobalResources::getImage(wxT("RTS_tray_waiting_linux.png"))); //use a 22x22 bitmap for perfect fit
+ realtimeIcon.CopyFromBitmap(GlobalResources::getImage(L"RTS_tray_waiting_linux.png")); //use a 22x22 bitmap for perfect fit
#endif
- const wxString postFix = jobName_.empty() ? wxString() : (wxT("\n\"") + jobName_ + wxT("\""));
- trayMenu->SetIcon(realtimeIcon, _("Waiting for missing directories...") + postFix);
-}
-
-
-void TrayIconHolder::OnContextMenuSelection(wxCommandEvent& event)
-{
- const int eventId = event.GetId();
- switch (static_cast<Selection>(eventId))
- {
- case CONTEXT_ABORT:
- requestAbort();
- break;
- case CONTEXT_RESTORE:
- OnRequestResume(event); //just remember: never throw exceptions through a C-Layer (GUI) ;)
- break;
- case CONTEXT_ABOUT:
- {
- //build information
- wxString build = __TDATE__;
-#if wxUSE_UNICODE
- build += wxT(" - Unicode");
-#else
- build += wxT(" - ANSI");
-#endif //wxUSE_UNICODE
-
- //compile time info about 32/64-bit build
- if (zen::is64BitBuild)
- build += wxT(" x64");
- else
- build += wxT(" x86");
- assert_static(zen::is32BitBuild || zen::is64BitBuild);
-
- wxString buildFormatted = _("(Build: %x)");
- buildFormatted.Replace(wxT("%x"), build);
-
- wxMessageDialog aboutDlg(nullptr, wxString(wxT("RealtimeSync")) + wxT("\n\n") + buildFormatted, _("About"), wxOK);
- aboutDlg.ShowModal();
- }
- break;
+ const wxString postFix = jobName_.empty() ? wxString() : (L"\n\"" + jobName_ + L"\"");
+ trayMenu->SetIcon(realtimeIcon, _("Waiting for missing directories...") + postFix);
}
-}
-
-void TrayIconHolder::doUiRefreshNow()
-{
- wxTheApp->Yield();
+private:
+ RtsTrayIconRaw* trayMenu;
+ const wxString jobName_; //RTS job name, may be empty
+};
- if (m_abortRequested)
- throw ::AbortThisProcess(QUIT);
- if (m_resumeRequested)
- throw ::AbortThisProcess(RESUME);
-}
//##############################################################################################################
-namespace
-{
-std::vector<Zstring> convert(const std::vector<wxString>& dirList)
+struct AbortMonitoring//exception class
{
- std::set<Zstring, LessFilename> output;
- std::transform(dirList.begin(), dirList.end(),
- std::inserter(output, output.end()), [](const wxString & str) { return zen::toZ(str); });
- return std::vector<Zstring>(output.begin(), output.end());
-}
-}
-
+ AbortMonitoring(AbortReason reasonCode) : reasonCode_(reasonCode) {}
+ AbortReason reasonCode_;
+};
class StartSyncNowException {};
+//##############################################################################################################
-class WaitCallbackImpl : public rts::WaitCallback
+class WaitCallbackImpl : public rts::WaitCallback, private AbortCallback
{
public:
WaitCallbackImpl(const wxString& jobname) :
- trayIcon(jobname),
- nextSyncStart_(std::numeric_limits<long>::max()) {}
+ trayIcon(jobname, *this),
+ nextSyncStart_(std::numeric_limits<long>::max()),
+ resumeRequested(false),
+ abortRequested(false) {}
- void notifyAllDirectoriesExist()
- {
- trayIcon.showIconActive();
- }
+ void notifyAllDirectoriesExist() { trayIcon.showIconActive(); }
+ void notifyDirectoryMissing () { trayIcon.showIconWaiting(); }
- void notifyDirectoryMissing()
- {
- trayIcon.showIconWaiting();
- }
+ void scheduleNextSync(long nextSyncStart) { nextSyncStart_ = nextSyncStart; }
+ void clearSchedule() { nextSyncStart_ = std::numeric_limits<long>::max(); }
- virtual void requestUiRefresh() //throw StartSyncNowException()
+ //implement WaitCallback
+ virtual void requestUiRefresh() //throw StartSyncNowException, AbortMonitoring
{
+ if (resumeRequested)
+ throw AbortMonitoring(SHOW_GUI);
+
+ if (abortRequested)
+ throw AbortMonitoring(EXIT_APP);
+
if (nextSyncStart_ <= wxGetLocalTime())
throw StartSyncNowException(); //abort wait and start sync
@@ -261,16 +221,87 @@ public:
trayIcon.doUiRefreshNow();
}
- void scheduleNextSync(long nextSyncStart)
+private:
+ //implement AbortCallback: used from C-GUI call stack
+ virtual void requestResume() { resumeRequested = true; }
+ virtual void requestAbort () { abortRequested = true; }
+
+ TrayIconHolder trayIcon;
+ long nextSyncStart_;
+ bool resumeRequested;
+ bool abortRequested;
+};
+
+
+
+class ErrorDlgWithTimeout : public ErrorDlgGenerated
+{
+public:
+ ErrorDlgWithTimeout(wxWindow* parent, const wxString& messageText) :
+ ErrorDlgGenerated(parent),
+ secondsLeft(15) //give user some time to read msg!?
{
- nextSyncStart_ = nextSyncStart;
+#ifdef FFS_WIN
+ new zen::MouseMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere...; ownership passed to "this"
+#endif
+ m_bitmap10->SetBitmap(GlobalResources::getImage(L"error"));
+ m_textCtrl8->SetValue(messageText);
+ m_buttonRetry->SetFocus();
+
+ //count down X seconds then automatically press "retry"
+ timer.Connect(wxEVT_TIMER, wxEventHandler(ErrorDlgWithTimeout::OnTimerEvent), nullptr, this);
+ timer.Start(1000); //timer interval in ms
+ updateButtonLabel();
}
+ enum ButtonPressed
+ {
+ BUTTON_RETRY,
+ BUTTON_ABORT
+ };
+
private:
- TrayIconHolder trayIcon;
- long nextSyncStart_;
+ void OnTimerEvent(wxEvent& event)
+ {
+ --secondsLeft;
+ if (secondsLeft < 0)
+ {
+ EndModal(BUTTON_RETRY);
+ return;
+ }
+ updateButtonLabel();
+ }
+
+ void updateButtonLabel()
+ {
+ m_buttonRetry->SetLabel(_("&Retry") + L" (" + replaceCpy(_P("1 sec", "%x sec", secondsLeft), L"%x", numberTo<std::wstring>(secondsLeft)) + L")");
+ Layout();
+ }
+
+ void OnClose(wxCloseEvent& event) { EndModal(BUTTON_ABORT); }
+ void OnRetry(wxCommandEvent& event) { EndModal(BUTTON_RETRY); }
+ void OnAbort(wxCommandEvent& event) { EndModal(BUTTON_ABORT); }
+
+ int secondsLeft;
+ wxTimer timer;
};
+
+bool reportErrorTimeout(const std::wstring& msg) //return true if timeout or user selected "retry", else abort
+{
+ ErrorDlgWithTimeout errorDlg(nullptr, msg);
+ //errorDlg.Raise(); -> don't steal focus every X seconds
+ switch (static_cast<ErrorDlgWithTimeout::ButtonPressed>(errorDlg.ShowModal()))
+ {
+ case ErrorDlgWithTimeout::BUTTON_RETRY:
+ return true;
+ case ErrorDlgWithTimeout::BUTTON_ABORT:
+ return false;
+ }
+ return false;
+}
+}
+
/*
Data Flow:
----------
@@ -278,82 +309,96 @@ Data Flow:
TrayIconHolder (GUI output)
/|\
|
-WaitCallbackImpl (higher level "interface")
+WaitCallbackImpl
/|\
|
startDirectoryMonitor() (wire dir-changes and execution of commandline)
- /|\
- |
-watcher.h (low level wait for directory changes)
*/
-rts::MonitorResponse rts::startDirectoryMonitor(const xmlAccess::XmlRealConfig& config, const wxString& jobname)
+rts::AbortReason rts::startDirectoryMonitor(const xmlAccess::XmlRealConfig& config, const wxString& jobname)
{
- Zstring lastFileChanged;
+ const std::vector<Zstring> dirList = toZ(config.directories);
+
+ auto cmdLine = config.commandline;
+ trim(cmdLine);
+
+ if (cmdLine.empty())
+ {
+ wxMessageBox(replaceCpy(_("Invalid command line: %x"), L"%x", L"\"\""), _("Error"), wxOK | wxICON_ERROR);
+ return SHOW_GUI;
+ }
+ if (dirList.empty() || std::any_of(dirList.begin(), dirList.end(), [](Zstring str) -> bool { trim(str); return str.empty(); }))
+ {
+ wxMessageBox(_("A directory input field is empty."), _("Error"), wxOK | wxICON_ERROR);
+ return SHOW_GUI;
+ }
- const std::vector<Zstring> dirList = convert(config.directories);
try
{
+ Zstring lastFileChanged;
WaitCallbackImpl callback(jobname);
- if (config.commandline.empty())
- {
- std::wstring errorMsg = _("Invalid command line: %x");
- replace(errorMsg, L"%x", L"\"" + config.commandline + L"\"");
- throw FileError(errorMsg);
- }
-
- callback.notifyDirectoryMissing();
- waitForMissingDirs(dirList, &callback);
- callback.notifyAllDirectoriesExist();
-
- while (true)
+ auto execMonitoring = [&] //throw FileError, AbortMonitoring
{
- ::wxSetEnv(L"changed_file", utf8CvrtTo<wxString>(lastFileChanged)); //some way to output what file changed to the user
- lastFileChanged.clear(); //make sure old name is not shown again after a directory reappears
-
- //execute command
- zen::shellExecute(config.commandline, zen::EXEC_TYPE_SYNC);
+ callback.clearSchedule();
- wxLog::FlushActive(); //show wxWidgets error messages (if any)
+ callback.notifyDirectoryMissing();
+ waitForMissingDirs(dirList, callback); //throw FileError, StartSyncNowException(not scheduled yet), AbortMonitoring
+ callback.notifyAllDirectoriesExist();
- callback.scheduleNextSync(std::numeric_limits<long>::max()); //next sync not scheduled (yet)
+ //schedule initial execution only AFTER waitForMissingDirs(), else StartSyncNowException might be thrown while directory checking hangs
+ callback.scheduleNextSync(wxGetLocalTime() + static_cast<long>(config.delay));
- try
+ while (true)
{
- while (true)
+ try
{
- //wait for changes (and for all directories to become available)
- WaitResult res = waitForChanges(dirList, &callback);
- switch (res.type)
+ while (true)
{
- case CHANGE_DIR_MISSING: //don't execute the commandline before all directories are available!
- callback.scheduleNextSync(std::numeric_limits<long>::max()); //next sync not scheduled (yet)
- callback.notifyDirectoryMissing();
- waitForMissingDirs(dirList, &callback);
- callback.notifyAllDirectoriesExist();
- break;
- case CHANGE_DETECTED:
- lastFileChanged = res.filename;
- break;
+ //wait for changes (and for all directories to become available)
+ WaitResult res = waitForChanges(dirList, callback); //throw FileError, StartSyncNowException, AbortMonitoring
+ switch (res.type)
+ {
+ case CHANGE_DIR_MISSING: //don't execute the commandline before all directories are available!
+ callback.clearSchedule();
+
+ callback.notifyDirectoryMissing();
+ waitForMissingDirs(dirList, callback); //throw FileError, StartSyncNowException(not scheduled yet), AbortMonitoring
+ callback.notifyAllDirectoriesExist();
+ break;
+
+ case CHANGE_DETECTED:
+ lastFileChanged = res.filename;
+ break;
+ }
+ callback.scheduleNextSync(wxGetLocalTime() + static_cast<long>(config.delay));
}
-
- callback.scheduleNextSync(wxGetLocalTime() + static_cast<long>(config.delay));
}
+ catch (StartSyncNowException&) {}
+
+ ::wxSetEnv(L"changed_file", utf8CvrtTo<wxString>(lastFileChanged)); //some way to output what file changed to the user
+ lastFileChanged.clear(); //make sure old name is not shown again after a directory reappears
+
+ //execute command
+ zen::shellExecute(cmdLine, zen::EXEC_TYPE_SYNC);
+ callback.clearSchedule();
+ }
+ };
+
+ while (true)
+ try
+ {
+ execMonitoring(); //throw FileError, AbortMonitoring
+ }
+ catch (const zen::FileError& e)
+ {
+ if (!reportErrorTimeout(e.toString())) //return true if timeout or user selected "retry", else abort
+ return SHOW_GUI;
}
- catch (StartSyncNowException&) {}
- }
- }
- catch (const ::AbortThisProcess& ab)
- {
- return ab.getCommand();
}
- catch (const zen::FileError& error)
+ catch (const AbortMonitoring& ab)
{
- wxMessageBox(error.toString(), _("Error"), wxOK | wxICON_ERROR);
- return RESUME;
+ return ab.reasonCode_;
}
-
- return RESUME;
}
bgstack15