diff options
Diffstat (limited to 'FreeFileSync/Source/RealtimeSync/main_dlg.cpp')
-rw-r--r-- | FreeFileSync/Source/RealtimeSync/main_dlg.cpp | 532 |
1 files changed, 532 insertions, 0 deletions
diff --git a/FreeFileSync/Source/RealtimeSync/main_dlg.cpp b/FreeFileSync/Source/RealtimeSync/main_dlg.cpp new file mode 100644 index 00000000..ee3e7dda --- /dev/null +++ b/FreeFileSync/Source/RealtimeSync/main_dlg.cpp @@ -0,0 +1,532 @@ +// ************************************************************************** +// * 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) Zenju (zenju AT gmx DOT de) - All Rights Reserved * +// ************************************************************************** + +#include "main_dlg.h" +#include <wx/wupdlock.h> +#include <wx/filedlg.h> +#include <wx+/bitmap_button.h> +#include <wx+/string_conv.h> +#include <wx+/mouse_move_dlg.h> +#include <wx+/font_size.h> +#include <wx+/popup_dlg.h> +#include <wx+/image_resources.h> +#include <zen/assert_static.h> +#include <zen/file_handling.h> +#include <zen/build_info.h> +#include "xml_proc.h" +#include "tray_menu.h" +#include "xml_ffs.h" +#include "app_icon.h" +#include "../lib/help_provider.h" +#include "../lib/process_xml.h" +#include "../lib/ffs_paths.h" +#ifdef ZEN_LINUX +#include <gtk/gtk.h> +#elif defined ZEN_MAC +#include <ApplicationServices/ApplicationServices.h> +#endif + +using namespace zen; + + +class DirectoryPanel : public FolderGenerated +{ +public: + DirectoryPanel(wxWindow* parent) : + FolderGenerated(parent), + dirName(*this, *m_buttonSelectDir, *m_txtCtrlDirectory) + { +#ifdef ZEN_LINUX + //file drag and drop directly into the text control unhelpfully inserts in format "file://..<cr><nl>"; see folder_history_box.cpp + if (GtkWidget* widget = m_txtCtrlDirectory->GetConnectWidget()) + ::gtk_drag_dest_unset(widget); +#endif + } + + void setName(const wxString& dirname) { dirName.setName(dirname); } + wxString getName() const { return dirName.getName(); } + +private: + zen::DirectoryName<wxTextCtrl> dirName; +}; + + +void MainDialog::create(const Zstring& cfgFile) +{ + /*MainDialog* frame = */ new MainDialog(nullptr, cfgFile); +} + + +MainDialog::MainDialog(wxDialog* dlg, const Zstring& cfgFileName) + : MainDlgGenerated(dlg) +{ +#ifdef ZEN_WIN + new MouseMoveWindow(*this); //ownership passed to "this" + wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X! +#endif + +#ifdef ZEN_LINUX + //file drag and drop directly into the text control unhelpfully inserts in format "file://..<cr><nl>"; see folder_history_box.cpp + if (GtkWidget* widget = m_txtCtrlDirectoryMain->GetConnectWidget()) + ::gtk_drag_dest_unset(widget); +#endif + + SetIcon(getRtsIcon()); //set application icon + + setRelativeFontSize(*m_buttonStart, 1.5); + + m_bpButtonRemoveTopFolder->Hide(); + m_panelMainFolder->Layout(); + + m_bpButtonAddFolder ->SetBitmapLabel(getResourceImage(L"item_add")); + m_bpButtonRemoveTopFolder->SetBitmapLabel(getResourceImage(L"item_remove")); + setBitmapTextLabel(*m_buttonStart, getResourceImage(L"startRts").ConvertToImage(), m_buttonStart->GetLabel(), 5, 8); + + + //register key event + Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::OnKeyPressed), nullptr, this); + + //prepare drag & drop + dirNameFirst.reset(new DirectoryName<wxTextCtrl>(*m_panelMainFolder, *m_buttonSelectDirMain, *m_txtCtrlDirectoryMain, m_staticTextFinalPath)); + + //--------------------------- load config values ------------------------------------ + xmlAccess::XmlRealConfig newConfig; + + const Zstring currentConfigFile = cfgFileName.empty() ? lastConfigFileName() : cfgFileName; + bool loadCfgSuccess = false; + if (!cfgFileName.empty() || fileExists(lastConfigFileName())) + try + { + rts::readRealOrBatchConfig(currentConfigFile, newConfig); //throw FfsXmlError + loadCfgSuccess = true; + } + catch (const xmlAccess::FfsXmlError& e) + { + if (e.getSeverity() == xmlAccess::FfsXmlError::WARNING) + showNotificationDialog(this, DialogInfoType::WARNING, PopupDialogCfg().setDetailInstructions(e.toString())); + else + showNotificationDialog(this, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString())); + } + + const bool startWatchingImmediately = loadCfgSuccess && !cfgFileName.empty(); + + setConfiguration(newConfig); + setLastUsedConfig(currentConfigFile); + //----------------------------------------------------------------------------------------- + + if (startWatchingImmediately) //start watch mode directly + { + wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED); + this->OnStart(dummy2); + //don't Show()! + } + else + { + m_buttonStart->SetFocus(); //don't "steal" focus if program is running from sys-tray" + Show(); +#ifdef ZEN_MAC + ProcessSerialNumber psn = { 0, kCurrentProcess }; + ::TransformProcessType(&psn, kProcessTransformToForegroundApplication); //show dock icon, even if we're not an application bundle + ::SetFrontProcess(&psn); //call before TransformProcessType() so that OSX menu is updated correctly + //if the executable is not yet in a bundle or if it is called through a launcher, we need to set focus manually: +#endif + } + + //drag and drop .ffs_real and .ffs_batch on main dialog + setupFileDrop(*m_panelMain); + m_panelMain->Connect(EVENT_DROP_FILE, FileDropEventHandler(MainDialog::onFilesDropped), nullptr, this); + + timerForAsyncTasks.Connect(wxEVT_TIMER, wxEventHandler(MainDialog::onProcessAsyncTasks), nullptr, this); +} + + +MainDialog::~MainDialog() +{ + //save current configuration + const xmlAccess::XmlRealConfig currentCfg = getConfiguration(); + + try //write config to XML + { + writeRealConfig(currentCfg, lastConfigFileName()); //throw FfsXmlError + } + catch (const xmlAccess::FfsXmlError& e) + { + showNotificationDialog(this, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString())); + } +} + + +void MainDialog::onQueryEndSession() +{ + try { writeRealConfig(getConfiguration(), lastConfigFileName()); } //throw FfsXmlError + catch (const xmlAccess::FfsXmlError&) {} //we try our best do to something useful in this extreme situation - no reason to notify or even log errors here! +} + + +void MainDialog::onProcessAsyncTasks(wxEvent& event) +{ + //schedule and run long-running tasks asynchronously + asyncTasks.evalResults(); //process results on GUI queue + if (asyncTasks.empty()) + timerForAsyncTasks.Stop(); +} + + +const Zstring& MainDialog::lastConfigFileName() +{ + static Zstring instance = zen::getConfigDir() + Zstr("LastRun.ffs_real"); + return instance; +} + + +void MainDialog::OnShowHelp(wxCommandEvent& event) +{ + zen::displayHelpEntry(L"html/RealtimeSync.html", this); +} + + +void MainDialog::OnMenuAbout(wxCommandEvent& event) +{ + wxString build = __TDATE__; + build += L" - Unicode"; +#ifndef wxUSE_UNICODE +#error what is going on? +#endif + + build += zen::is64BitBuild ? L" x64" : L" x86"; + assert_static(zen::is32BitBuild || zen::is64BitBuild); + + showNotificationDialog(this, DialogInfoType::INFO, PopupDialogCfg(). + setTitle(_("About")). + setMainInstructions(L"RealtimeSync" L"\n\n" + replaceCpy(_("Build: %x"), L"%x", build))); +} + + +void MainDialog::OnKeyPressed(wxKeyEvent& event) +{ + const int keyCode = event.GetKeyCode(); + if (keyCode == WXK_ESCAPE) + { + Close(); + return; + } + event.Skip(); +} + + +void MainDialog::OnStart(wxCommandEvent& event) +{ + xmlAccess::XmlRealConfig currentCfg = getConfiguration(); + + Hide(); +#ifdef ZEN_MAC + //hide dock icon: else user is able to forcefully show the hidden main dialog by clicking on the icon!! + ProcessSerialNumber psn = { 0, kCurrentProcess }; + ::TransformProcessType(&psn, kProcessTransformToUIElementApplication); +#endif + + switch (rts::startDirectoryMonitor(currentCfg, xmlAccess::extractJobName(utfCvrtTo<Zstring>(currentConfigFileName)))) + { + case rts::EXIT_APP: + Close(); + return; + + case rts::SHOW_GUI: + break; + } + Show(); //don't show for EXIT_APP +#ifdef ZEN_MAC + //why isn't this covered by wxWindows::Raise()?? + ::TransformProcessType(&psn, kProcessTransformToForegroundApplication); //show dock icon again + ::SetFrontProcess(&psn); //call before TransformProcessType() so that OSX menu is updated correctly +#endif + Raise(); +} + + +void MainDialog::OnConfigSave(wxCommandEvent& event) +{ + Zstring defaultFileName = currentConfigFileName.empty() ? Zstr("Realtime.ffs_real") : currentConfigFileName; + //attention: currentConfigFileName may be an imported *.ffs_batch file! We don't want to overwrite it with a GUI config! + if (endsWith(defaultFileName, Zstr(".ffs_batch"))) + replace(defaultFileName, Zstr(".ffs_batch"), Zstr(".ffs_real"), false); + + + wxFileDialog filePicker(this, + wxEmptyString, + //OS X really needs dir/file separated like this: + utfCvrtTo<wxString>(beforeLast(defaultFileName, FILE_NAME_SEPARATOR)), //default dir; empty string if / not found + utfCvrtTo<wxString>(afterLast (defaultFileName, FILE_NAME_SEPARATOR)), //default file; whole string if / not found + wxString(L"RealtimeSync (*.ffs_real)|*.ffs_real") + L"|" +_("All files") + L" (*.*)|*", + wxFD_SAVE | wxFD_OVERWRITE_PROMPT); + if (filePicker.ShowModal() != wxID_OK) + return; + + const Zstring newFileName = utfCvrtTo<Zstring>(filePicker.GetPath()); + + //write config to XML + const xmlAccess::XmlRealConfig currentCfg = getConfiguration(); + try + { + writeRealConfig(currentCfg, newFileName); //throw FfsXmlError + setLastUsedConfig(newFileName); + } + catch (const xmlAccess::FfsXmlError& e) + { + showNotificationDialog(this, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString())); + } +} + + +void MainDialog::loadConfig(const Zstring& filename) +{ + xmlAccess::XmlRealConfig newConfig; + + try + { + rts::readRealOrBatchConfig(filename, newConfig); + } + catch (const xmlAccess::FfsXmlError& e) + { + if (e.getSeverity() == xmlAccess::FfsXmlError::WARNING) + showNotificationDialog(this, DialogInfoType::WARNING, PopupDialogCfg().setDetailInstructions(e.toString())); + else + { + showNotificationDialog(this, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString())); + return; + } + } + + setConfiguration(newConfig); + setLastUsedConfig(filename); +} + + +void MainDialog::setLastUsedConfig(const Zstring& filename) +{ + //set title + if (filename == lastConfigFileName()) + { + SetTitle(L"RealtimeSync - " + _("Automated Synchronization")); + currentConfigFileName.clear(); + } + else + { + SetTitle(utfCvrtTo<wxString>(filename)); + currentConfigFileName = filename; + } +} + + +void MainDialog::OnConfigLoad(wxCommandEvent& event) +{ + wxFileDialog filePicker(this, + wxEmptyString, + utfCvrtTo<wxString>(beforeLast(currentConfigFileName, FILE_NAME_SEPARATOR)), //default dir; empty string if / not found + wxEmptyString, + wxString(L"RealtimeSync (*.ffs_real; *.ffs_batch)|*.ffs_real;*.ffs_batch") + L"|" +_("All files") + L" (*.*)|*", + wxFD_OPEN); + if (filePicker.ShowModal() == wxID_OK) + loadConfig(utfCvrtTo<Zstring>(filePicker.GetPath())); +} + + +void MainDialog::onFilesDropped(FileDropEvent& event) +{ + const auto& files = event.getFiles(); + if (!files.empty()) + loadConfig(utfCvrtTo<Zstring>(files[0])); +} + + +void MainDialog::setConfiguration(const xmlAccess::XmlRealConfig& cfg) +{ + //clear existing folders + dirNameFirst->setName(wxString()); + clearAddFolders(); + + if (!cfg.directories.empty()) + { + //fill top folder + dirNameFirst->setName(utfCvrtTo<wxString>(*cfg.directories.begin())); + + //fill additional folders + addFolder(std::vector<Zstring>(cfg.directories.begin() + 1, cfg.directories.end())); + } + + //fill commandline + m_textCtrlCommand->SetValue(utfCvrtTo<wxString>(cfg.commandline)); + + //set delay + m_spinCtrlDelay->SetValue(static_cast<int>(cfg.delay)); +} + + +xmlAccess::XmlRealConfig MainDialog::getConfiguration() +{ + xmlAccess::XmlRealConfig output; + + output.directories.push_back(utfCvrtTo<Zstring>(dirNameFirst->getName())); + for (const DirectoryPanel* dne : dirNamesExtra) + output.directories.push_back(utfCvrtTo<Zstring>(dne->getName())); + + output.commandline = utfCvrtTo<Zstring>(m_textCtrlCommand->GetValue()); + output.delay = m_spinCtrlDelay->GetValue(); + + return output; +} + + +void MainDialog::OnAddFolder(wxCommandEvent& event) +{ + const Zstring topFolder = utfCvrtTo<Zstring>(dirNameFirst->getName()); + + //clear existing top folder first + dirNameFirst->setName(wxString()); + + std::vector<Zstring> newFolders; + newFolders.push_back(topFolder); + + addFolder(newFolders, true); //add pair in front of additonal pairs +} + + +void MainDialog::OnRemoveFolder(wxCommandEvent& event) +{ + //find folder pair originating the event + const wxObject* const eventObj = event.GetEventObject(); + for (auto it = dirNamesExtra.begin(); it != dirNamesExtra.end(); ++it) + if (eventObj == static_cast<wxObject*>((*it)->m_bpButtonRemoveFolder)) + { + removeAddFolder(it - dirNamesExtra.begin()); + return; + } +} + + +void MainDialog::OnRemoveTopFolder(wxCommandEvent& event) +{ + if (dirNamesExtra.size() > 0) + { + dirNameFirst->setName(dirNamesExtra[0]->getName()); + removeAddFolder(0); //remove first of additional folders + } +} + + +#ifdef ZEN_WIN +static const size_t MAX_ADD_FOLDERS = 8; +#elif defined ZEN_LINUX || defined ZEN_MAC +static const size_t MAX_ADD_FOLDERS = 6; +#endif + + +void MainDialog::addFolder(const std::vector<Zstring>& newFolders, bool addFront) +{ + if (newFolders.size() == 0) + return; + +#ifdef ZEN_WIN + wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X! +#endif + + int folderHeight = 0; + for (const Zstring& dirname : newFolders) + { + //add new folder pair + DirectoryPanel* newFolder = new DirectoryPanel(m_scrolledWinFolders); + newFolder->m_bpButtonRemoveFolder->SetBitmapLabel(getResourceImage(L"item_remove")); + + //get size of scrolled window + folderHeight = newFolder->GetSize().GetHeight(); + + if (addFront) + { + bSizerFolders->Insert(0, newFolder, 0, wxEXPAND, 5); + dirNamesExtra.insert(dirNamesExtra.begin(), newFolder); + } + else + { + bSizerFolders->Add(newFolder, 0, wxEXPAND, 5); + dirNamesExtra.push_back(newFolder); + } + + //register events + newFolder->m_bpButtonRemoveFolder->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainDialog::OnRemoveFolder), nullptr, this ); + + //insert directory name + newFolder->setName(utfCvrtTo<wxString>(dirname)); + } + + //set size of scrolled window + const size_t additionalRows = std::min(dirNamesExtra.size(), MAX_ADD_FOLDERS); //up to MAX_ADD_FOLDERS additional folders shall be shown + m_scrolledWinFolders->SetMinSize(wxSize( -1, folderHeight * static_cast<int>(additionalRows))); + + //adapt delete top folder pair button + m_bpButtonRemoveTopFolder->Show(); + + GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() + Layout(); + Refresh(); //remove a little flicker near the start button +} + + +void MainDialog::removeAddFolder(size_t pos) +{ +#ifdef ZEN_WIN + wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X! +#endif + + if (pos < dirNamesExtra.size()) + { + //remove folder pairs from window + DirectoryPanel* pairToDelete = dirNamesExtra[pos]; + const int folderHeight = pairToDelete->GetSize().GetHeight(); + + bSizerFolders->Detach(pairToDelete); //Remove() does not work on Window*, so do it manually + dirNamesExtra.erase(dirNamesExtra.begin() + pos); //remove last element in vector + //more (non-portable) wxWidgets bullshit: on OS X wxWindow::Destroy() screws up and calls "operator delete" directly rather than + //the deferred deletion it is expected to do (and which is implemented correctly on Windows and Linux) + //http://bb10.com/python-wxpython-devel/2012-09/msg00004.html + //=> since we're in a mouse button callback of a sub-component of "pairToDelete" we need to delay deletion ourselves: + processAsync2([] {}, [pairToDelete] { pairToDelete->Destroy(); }); + + //set size of scrolled window + const size_t additionalRows = std::min(dirNamesExtra.size(), MAX_ADD_FOLDERS); //up to MAX_ADD_FOLDERS additional folders shall be shown + m_scrolledWinFolders->SetMinSize(wxSize( -1, folderHeight * static_cast<int>(additionalRows))); + + //adapt delete top folder pair button + if (dirNamesExtra.size() == 0) + { + m_bpButtonRemoveTopFolder->Hide(); + m_panelMainFolder->Layout(); + } + + GetSizer()->SetSizeHints(this); //~=Fit() + SetMinSize() + Layout(); + Refresh(); //remove a little flicker near the start button + } +} + + +void MainDialog::clearAddFolders() +{ +#ifdef ZEN_WIN + wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X! +#endif + + bSizerFolders->Clear(true); + dirNamesExtra.clear(); + + m_scrolledWinFolders->SetMinSize(wxSize(-1, 0)); + + m_bpButtonRemoveTopFolder->Hide(); + m_panelMainFolder->Layout(); + + GetSizer()->SetSizeHints(this); //~=Fit() + Layout(); + Refresh(); //remove a little flicker near the start button +} |