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.cpp282
1 files changed, 282 insertions, 0 deletions
diff --git a/RealtimeSync/tray_menu.cpp b/RealtimeSync/tray_menu.cpp
new file mode 100644
index 00000000..1ea7416c
--- /dev/null
+++ b/RealtimeSync/tray_menu.cpp
@@ -0,0 +1,282 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2010 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+//
+#include "tray_menu.h"
+#include <wx/msgdlg.h>
+#include <wx/taskbar.h>
+#include <wx/app.h>
+#include "resources.h"
+#include <algorithm>
+#include <iterator>
+#include "../shared/string_conv.h"
+#include <wx/utils.h>
+#include <wx/menu.h>
+#include "watcher.h"
+#include <wx/utils.h>
+#include <wx/log.h>
+#include "../shared/assert_static.h"
+#include "../shared/build_info.h"
+#include <wx/icon.h> //Linux needs this
+#include <wx/timer.h>
+
+using namespace rts;
+
+
+class WaitCallbackImpl : private wxEvtHandler, public rts::WaitCallback //keep this order: else VC++ generates wrong code
+{
+public:
+ WaitCallbackImpl();
+ ~WaitCallbackImpl();
+
+ virtual void requestUiRefresh();
+
+ void showIconActive();
+ void showIconWaiting();
+
+ void requestAbort()
+ {
+ m_abortRequested = true;
+ }
+
+ void OnRequestResume(wxCommandEvent& event)
+ {
+ m_resumeRequested = true;
+ }
+
+ enum Selection
+ {
+ CONTEXT_ABORT,
+ CONTEXT_RESTORE,
+ CONTEXT_ABOUT
+ };
+
+ void OnContextMenuSelection(wxCommandEvent& event);
+
+private:
+ class RtsTrayIcon;
+ RtsTrayIcon* trayMenu;
+
+ bool m_abortRequested;
+ bool m_resumeRequested;
+};
+
+
+//RtsTrayIcon shall be a dumb class whose sole purpose is to enable wxWidgets deferred deletion
+class WaitCallbackImpl::RtsTrayIcon : public wxTaskBarIcon
+{
+public:
+ RtsTrayIcon(WaitCallbackImpl* parent) : parent_(parent) {}
+
+ void parentHasDied() //call before tray icon is marked for deferred deletion
+ {
+ parent_ = NULL;
+ }
+
+private:
+ virtual wxMenu* CreatePopupMenu()
+ {
+ if (!parent_)
+ return NULL;
+
+ wxMenu* contextMenu = new wxMenu;
+ contextMenu->Append(CONTEXT_RESTORE, _("&Restore"));
+ contextMenu->Append(CONTEXT_ABOUT, _("&About..."));
+ contextMenu->AppendSeparator();
+ contextMenu->Append(CONTEXT_ABORT, _("&Exit"));
+ //event handling
+ contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(WaitCallbackImpl::OnContextMenuSelection), NULL, parent_);
+
+ return contextMenu; //ownership transferred to caller
+ }
+
+ WaitCallbackImpl* parent_;
+};
+//##############################################################################################################
+
+
+class AbortThisProcess //exception class
+{
+public:
+ AbortThisProcess(MonitorResponse command) : command_(command) {}
+
+ MonitorResponse getCommand() const
+ {
+ return command_;
+ }
+
+private:
+ MonitorResponse command_;
+};
+//##############################################################################################################
+
+
+WaitCallbackImpl::WaitCallbackImpl() :
+ m_abortRequested(false),
+ m_resumeRequested(false)
+{
+ 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(WaitCallbackImpl::OnRequestResume), NULL, this);
+}
+
+
+WaitCallbackImpl::~WaitCallbackImpl()
+{
+ trayMenu->Disconnect(wxEVT_TASKBAR_LEFT_DCLICK, wxCommandEventHandler(WaitCallbackImpl::OnRequestResume), NULL, this);
+ trayMenu->RemoveIcon(); //(try to) hide icon until final deletion takes place
+ trayMenu->parentHasDied();
+
+ //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 WaitCallbackImpl::showIconActive()
+{
+ wxIcon realtimeIcon;
+#ifdef FFS_WIN
+ realtimeIcon.CopyFromBitmap(GlobalResources::getInstance().getImageByName(wxT("RTS_tray_win.png"))); //use a 16x16 bitmap
+#elif defined FFS_LINUX
+ realtimeIcon.CopyFromBitmap(GlobalResources::getInstance().getImageByName(wxT("RTS_tray_linux.png"))); //use a 22x22 bitmap for perfect fit
+#endif
+ trayMenu->SetIcon(realtimeIcon, wxString(wxT("RealtimeSync")) + wxT(" - ") + _("Monitoring active..."));
+}
+
+
+void WaitCallbackImpl::showIconWaiting()
+{
+ wxIcon realtimeIcon;
+#ifdef FFS_WIN
+ realtimeIcon.CopyFromBitmap(GlobalResources::getInstance().getImageByName(wxT("RTS_tray_waiting_win.png"))); //use a 16x16 bitmap
+#elif defined FFS_LINUX
+ realtimeIcon.CopyFromBitmap(GlobalResources::getInstance().getImageByName(wxT("RTS_tray_waiting_linux.png"))); //use a 22x22 bitmap for perfect fit
+#endif
+ trayMenu->SetIcon(realtimeIcon, wxString(wxT("RealtimeSync")) + wxT(" - ") + _("Waiting for all directories to become available..."));
+}
+
+
+void WaitCallbackImpl::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 (util::is64BitBuild)
+ build += wxT(" x64");
+ else
+ build += wxT(" x86");
+ assert_static(util::is32BitBuild || util::is64BitBuild);
+
+ wxString buildFormatted = _("(Build: %x)");
+ buildFormatted.Replace(wxT("%x"), build);
+
+ wxMessageDialog aboutDlg(NULL, wxString(wxT("RealtimeSync")) + wxT("\n\n") + buildFormatted, _("About"), wxOK);
+ aboutDlg.ShowModal();
+ }
+ break;
+ }
+}
+
+
+void WaitCallbackImpl::requestUiRefresh()
+{
+ if (updateUiIsAllowed())
+ wxTheApp->Yield();
+
+ if (m_abortRequested)
+ throw ::AbortThisProcess(QUIT);
+
+ if (m_resumeRequested)
+ throw ::AbortThisProcess(RESUME);
+}
+//##############################################################################################################
+
+namespace
+{
+std::vector<Zstring> convert(const std::vector<wxString>& dirList)
+{
+ std::set<Zstring, LessFilename> output;
+ std::transform(dirList.begin(), dirList.end(),
+ std::inserter(output, output.end()), static_cast<Zstring (*)(const wxString&)>(ffs3::wxToZ));
+ return std::vector<Zstring>(output.begin(), output.end());
+}
+}
+
+
+rts::MonitorResponse rts::startDirectoryMonitor(const xmlAccess::XmlRealConfig& config)
+{
+ const std::vector<Zstring> dirList = convert(config.directories);
+
+ try
+ {
+ WaitCallbackImpl callback;
+
+ if (config.commandline.empty())
+ throw ffs3::FileError(_("Command line is empty!"));
+
+ while (true)
+ {
+ //execute commandline
+ callback.showIconWaiting();
+ waitForMissingDirs(dirList, &callback);
+ callback.showIconActive();
+
+ wxExecute(config.commandline, wxEXEC_SYNC); //execute command
+ wxLog::FlushActive(); //show wxWidgets error messages (if any)
+
+ //wait for changes (and for all directories to become available)
+ switch (waitForChanges(dirList, &callback))
+ {
+ case CHANGE_DIR_MISSING: //don't execute the commandline before all directories are available!
+ callback.showIconWaiting();
+ waitForMissingDirs(dirList, &callback);
+ callback.showIconActive();
+ break;
+ case CHANGE_DETECTED:
+ break;
+ }
+
+ //some delay
+ const long nextExec = wxGetLocalTime() + static_cast<long>(config.delay);
+ while (wxGetLocalTime() < nextExec)
+ {
+ callback.requestUiRefresh();
+ wxMilliSleep(rts::UI_UPDATE_INTERVAL);
+ }
+ }
+ }
+ catch (const ::AbortThisProcess& ab)
+ {
+ return ab.getCommand();
+ }
+ catch (const ffs3::FileError& error)
+ {
+ wxMessageBox(error.msg(), _("Error"), wxOK | wxICON_ERROR);
+ return RESUME;
+ }
+
+ return RESUME;
+}
bgstack15