summaryrefslogtreecommitdiff
path: root/ui/main_dlg.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 17:29:28 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 17:29:28 +0200
commit75c07011b7c4d06acd7b45dabdcd60ab9d80f385 (patch)
tree8853c3978dd152ef377e652239448b1352320206 /ui/main_dlg.cpp
parent5.22 (diff)
downloadFreeFileSync-75c07011b7c4d06acd7b45dabdcd60ab9d80f385.tar.gz
FreeFileSync-75c07011b7c4d06acd7b45dabdcd60ab9d80f385.tar.bz2
FreeFileSync-75c07011b7c4d06acd7b45dabdcd60ab9d80f385.zip
5.23
Diffstat (limited to 'ui/main_dlg.cpp')
-rw-r--r--ui/main_dlg.cpp4547
1 files changed, 0 insertions, 4547 deletions
diff --git a/ui/main_dlg.cpp b/ui/main_dlg.cpp
deleted file mode 100644
index 8ca9cc0b..00000000
--- a/ui/main_dlg.cpp
+++ /dev/null
@@ -1,4547 +0,0 @@
-// **************************************************************************
-// * 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 <zen/format_unit.h>
-#include <zen/file_handling.h>
-#include <zen/serialize.h>
-//#include <zen/perf.h>
-#include <zen/thread.h>
-#include <zen/shell_execute.h>
-#include <wx/clipbrd.h>
-#include <wx/wupdlock.h>
-#include <wx/sound.h>
-#include <wx/filedlg.h>
-#include <wx/display.h>
-#include <wx+/context_menu.h>
-#include <wx+/string_conv.h>
-#include <wx+/bitmap_button.h>
-#include <wx+/app_main.h>
-#include <wx+/toggle_button.h>
-#include <wx+/mouse_move_dlg.h>
-#include <wx+/no_flicker.h>
-#include <wx+/rtl.h>
-#include <wx+/font_size.h>
-#include <wx+/popup_dlg.h>
-#include <wx+/image_resources.h>
-#include "check_version.h"
-#include "gui_status_handler.h"
-#include "sync_cfg.h"
-#include "small_dlgs.h"
-#include "progress_indicator.h"
-#include "folder_pair.h"
-#include "search.h"
-#include "batch_config.h"
-#include "triple_splitter.h"
-#include "app_icon.h"
-//#include "config_history.h"
-#include "../comparison.h"
-#include "../synchronization.h"
-#include "../algorithm.h"
-#include "../lib/resolve_path.h"
-#include "../lib/ffs_paths.h"
-#include "../lib/help_provider.h"
-#include "../lib/lock_holder.h"
-#include "../lib/localization.h"
-
-#ifdef ZEN_MAC
-#include <ApplicationServices/ApplicationServices.h>
-#endif
-
-using namespace zen;
-using namespace std::rel_ops;
-
-
-namespace
-{
-struct wxClientHistoryData : public wxClientData //we need a wxClientData derived class to tell wxWidgets to take object ownership!
-{
- wxClientHistoryData(const Zstring& cfgFile, int lastUseIndex) : cfgFile_(cfgFile), lastUseIndex_(lastUseIndex) {}
-
- Zstring cfgFile_;
- int lastUseIndex_; //support sorting history by last usage, the higher the index the more recent the usage
-};
-
-IconBuffer::IconSize convert(xmlAccess::FileIconSize isize)
-{
- using namespace xmlAccess;
- switch (isize)
- {
- case ICON_SIZE_SMALL:
- return IconBuffer::SIZE_SMALL;
- case ICON_SIZE_MEDIUM:
- return IconBuffer::SIZE_MEDIUM;
- case ICON_SIZE_LARGE:
- return IconBuffer::SIZE_LARGE;
- }
- return IconBuffer::SIZE_SMALL;
-}
-}
-
-
-class DirectoryNameMainImpl : public DirectoryName<FolderHistoryBox>
-{
-public:
- DirectoryNameMainImpl(MainDialog& mainDlg,
- wxWindow& dropWindow1,
- Grid& dropGrid,
- wxButton& dirSelectButton,
- FolderHistoryBox& dirName,
- wxStaticText& staticText) :
- DirectoryName(dropWindow1, dirSelectButton, dirName, &staticText, &dropGrid.getMainWin()),
- mainDlg_(mainDlg) {}
-
- virtual bool acceptDrop(const std::vector<wxString>& droppedFiles, const wxPoint& clientPos, const wxWindow& wnd)
- {
- if (std::any_of(droppedFiles.begin(), droppedFiles.end(), [](const wxString& filename)
- {
- using namespace xmlAccess;
- switch (getXmlType(utfCvrtTo<Zstring>(filename))) //throw()
- {
- case XML_TYPE_GUI:
- case XML_TYPE_BATCH:
- return true;
- case XML_TYPE_GLOBAL:
- case XML_TYPE_OTHER:
- break;
- }
- return false;
- }))
- {
- mainDlg_.loadConfiguration(toZ(droppedFiles));
- return false;
- }
-
- //=> return true: change directory selection via drag and drop
- return true;
- }
-
-private:
- DirectoryNameMainImpl(const DirectoryNameMainImpl&);
- DirectoryNameMainImpl& operator=(const DirectoryNameMainImpl&);
-
- MainDialog& mainDlg_;
-};
-
-//------------------------------------------------------------------
-/* class hierarchy:
-
- template<>
- FolderPairPanelBasic
- /|\
- |
- template<>
- FolderPairCallback FolderPairPanelGenerated
- /|\ /|\
- _________|________ ________|
- | | |
- FolderPairFirst FolderPairPanel
-*/
-
-template <class GuiPanel>
-class FolderPairCallback : public FolderPairPanelBasic<GuiPanel> //implements callback functionality to MainDialog as imposed by FolderPairPanelBasic
-{
-public:
- FolderPairCallback(GuiPanel& basicPanel, MainDialog& mainDialog) :
- FolderPairPanelBasic<GuiPanel>(basicPanel), //pass FolderPairPanelGenerated part...
- mainDlg(mainDialog) {}
-
-private:
- virtual MainConfiguration getMainConfig() const { return mainDlg.getConfig().mainCfg; }
- virtual wxWindow* getParentWindow() { return &mainDlg; }
- virtual std::unique_ptr<FilterConfig>& getFilterCfgOnClipboardRef() { return mainDlg.filterCfgOnClipboard; }
-
- virtual void onAltCompCfgChange () { mainDlg.applyCompareConfig(); }
- virtual void onAltSyncCfgChange () { mainDlg.applySyncConfig(); }
- virtual void onLocalFilterCfgChange() { mainDlg.applyFilterConfig(); } //re-apply filter
-
- MainDialog& mainDlg;
-};
-
-
-class FolderPairPanel :
- public FolderPairPanelGenerated, //FolderPairPanel "owns" FolderPairPanelGenerated!
- public FolderPairCallback<FolderPairPanelGenerated>
-{
-public:
- FolderPairPanel(wxWindow* parent, MainDialog& mainDialog) :
- FolderPairPanelGenerated(parent),
- FolderPairCallback<FolderPairPanelGenerated>(static_cast<FolderPairPanelGenerated&>(*this), mainDialog), //pass FolderPairPanelGenerated part...
- dirNameLeft (*m_panelLeft, *m_buttonSelectDirLeft, *m_directoryLeft),
- dirNameRight(*m_panelRight, *m_buttonSelectDirRight, *m_directoryRight)
- {
- dirNameLeft .Connect(EVENT_ON_DIR_SELECTED, wxCommandEventHandler(MainDialog::onDirSelected), nullptr, &mainDialog);
- dirNameRight.Connect(EVENT_ON_DIR_SELECTED, wxCommandEventHandler(MainDialog::onDirSelected), nullptr, &mainDialog);
-
- dirNameLeft .Connect(EVENT_ON_DIR_MANUAL_CORRECTION, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog);
- dirNameRight.Connect(EVENT_ON_DIR_MANUAL_CORRECTION, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog);
- }
-
- void setValues(const Zstring& leftDir,
- const Zstring& rightDir,
- AltCompCfgPtr cmpCfg,
- AltSyncCfgPtr syncCfg,
- const FilterConfig& filter)
- {
- setConfig(cmpCfg, syncCfg, filter);
- dirNameLeft .setName(toWx(leftDir));
- dirNameRight.setName(toWx(rightDir));
- }
- Zstring getLeftDir () const { return toZ(dirNameLeft .getName()); }
- Zstring getRightDir() const { return toZ(dirNameRight.getName()); }
-
-private:
- //support for drag and drop
- DirectoryName<FolderHistoryBox> dirNameLeft;
- DirectoryName<FolderHistoryBox> dirNameRight;
-};
-
-
-class FolderPairFirst : public FolderPairCallback<MainDialogGenerated>
-{
-public:
- FolderPairFirst(MainDialog& mainDialog) :
- FolderPairCallback<MainDialogGenerated>(mainDialog, mainDialog),
-
- //prepare drag & drop
- dirNameLeft(mainDialog,
- *mainDialog.m_panelTopLeft,
- *mainDialog.m_gridMainL,
- *mainDialog.m_buttonSelectDirLeft,
- *mainDialog.m_directoryLeft,
- *mainDialog.m_staticTextResolvedPathL),
- dirNameRight(mainDialog,
- *mainDialog.m_panelTopRight,
- *mainDialog.m_gridMainR,
- *mainDialog.m_buttonSelectDirRight,
- *mainDialog.m_directoryRight,
- *mainDialog.m_staticTextResolvedPathR)
- {
- dirNameLeft .Connect(EVENT_ON_DIR_SELECTED, wxCommandEventHandler(MainDialog::onDirSelected), nullptr, &mainDialog);
- dirNameRight.Connect(EVENT_ON_DIR_SELECTED, wxCommandEventHandler(MainDialog::onDirSelected), nullptr, &mainDialog);
-
- dirNameLeft .Connect(EVENT_ON_DIR_MANUAL_CORRECTION, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog);
- dirNameRight.Connect(EVENT_ON_DIR_MANUAL_CORRECTION, wxCommandEventHandler(MainDialog::onDirManualCorrection), nullptr, &mainDialog);
- }
-
- void setValues(const Zstring& leftDir,
- const Zstring& rightDir,
- AltCompCfgPtr cmpCfg,
- AltSyncCfgPtr syncCfg,
- const FilterConfig& filter)
- {
- setConfig(cmpCfg, syncCfg, filter);
- dirNameLeft .setName(toWx(leftDir));
- dirNameRight.setName(toWx(rightDir));
- }
- Zstring getLeftDir () const { return toZ(dirNameLeft .getName()); }
- Zstring getRightDir() const { return toZ(dirNameRight.getName()); }
-
-private:
- //support for drag and drop
- DirectoryNameMainImpl dirNameLeft;
- DirectoryNameMainImpl dirNameRight;
-};
-
-
-#ifdef ZEN_WIN
-class PanelMoveWindow : public MouseMoveWindow
-{
-public:
- PanelMoveWindow(MainDialog& mainDlg) :
- MouseMoveWindow(mainDlg, false), //don't include main dialog itself, thereby prevent various mouse capture lost issues
- mainDlg_(mainDlg) {}
-
- virtual bool allowMove(const wxMouseEvent& event)
- {
- if (wxPanel* panel = dynamic_cast<wxPanel*>(event.GetEventObject()))
- {
- const wxAuiPaneInfo& paneInfo = mainDlg_.auiMgr.GetPane(panel);
- if (paneInfo.IsOk() &&
- paneInfo.IsFloating())
- return false; //prevent main dialog move
- }
-
- return true; //allow dialog move
- }
-
-private:
- MainDialog& mainDlg_;
-};
-#endif
-
-
-namespace
-{
-//workaround for wxWidgets bug failing to update menu item bitmaps (affects Windows- and Linux-build)
-void setMenuItemImage(wxMenuItem*& menuItem, const wxBitmap& bmp)
-{
- assert(menuItem->GetKind() == wxITEM_NORMAL);
-
- //support polling
- if (isEqual(bmp, menuItem->GetBitmap()))
- return;
-
- if (wxMenu* menu = menuItem->GetMenu())
- {
- int pos = menu->GetMenuItems().IndexOf(menuItem);
- if (pos != wxNOT_FOUND)
- {
- /*
- menu->Remove(menuItem); ->this simple sequence crashes on Kubuntu x64, wxWidgets 2.9.2
- menu->Insert(pos, menuItem);
- */
- const bool enabled = menuItem->IsEnabled();
- wxMenuItem* newItem = new wxMenuItem(menu, menuItem->GetId(), menuItem->GetItemLabel());
-
- newItem->SetBitmap(bmp);
-#ifdef __WXMSW__ //not availabe on wxGTK or wxOSX
- //for some inconceivable reason wxWidgets is not consistent with itself and renders disabled icons
- //just greyscale instead of brightened like bitmap buttons; much better:
- newItem->SetDisabledBitmap(bmp.ConvertToDisabled());
-#endif
- bool isDestroyed = menu->Destroy(menuItem); //actual workaround
- assert(isDestroyed);
- (void)isDestroyed;
- menuItem = menu->Insert(pos, newItem); //don't forget to update input item pointer!
-
- if (!enabled)
- menuItem->Enable(false); //do not enable BEFORE appending item! wxWidgets screws up for yet another crappy reason
- }
- }
-}
-
-//##################################################################################################################################
-
-xmlAccess::XmlGlobalSettings retrieveGlobalCfgFromDisk() //blocks on GUI on errors!
-{
- using namespace xmlAccess;
- XmlGlobalSettings globalCfg;
- try
- {
- if (fileExists(getGlobalConfigFile()))
- readConfig(globalCfg); //throw FfsXmlError
- //else: globalCfg already has default values
- }
- catch (const FfsXmlError& e)
- {
- assert(false);
- if (e.getSeverity() != FfsXmlError::WARNING) //ignore parsing errors: should be migration problems only *cross-fingers*
- showNotificationDialog(nullptr, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString())); //no parent window: main dialog not yet created!
- }
- return globalCfg;
-}
-}
-
-
-void MainDialog::create()
-{
- using namespace xmlAccess;
- const XmlGlobalSettings globalSettings = retrieveGlobalCfgFromDisk();
-
- std::vector<Zstring> filenames = globalSettings.gui.lastUsedConfigFiles; //2. now try last used files
-
- //------------------------------------------------------------------------------------------
- //check existence of all files in parallel:
- RunUntilFirstHit<NullType> findFirstMissing;
-
- std::for_each(filenames.begin(), filenames.end(), [&](const Zstring& filename)
- {
- findFirstMissing.addJob([=] { return filename.empty() /*ever empty??*/ || !fileExists(filename) ? zen::make_unique<NullType>() : nullptr; });
- });
- //potentially slow network access: give all checks 500ms to finish
- const bool allFilesExist = findFirstMissing.timedWait(boost::posix_time::milliseconds(500)) && //false: time elapsed
- !findFirstMissing.get(); //no missing
- if (!allFilesExist)
- filenames.clear(); //we do NOT want to show an error due to last config file missing on application start!
- //------------------------------------------------------------------------------------------
-
- if (filenames.empty())
- {
- if (zen::fileExists(lastRunConfigName())) //3. try to load auto-save config
- filenames.push_back(lastRunConfigName());
- }
-
- XmlGuiConfig guiCfg; //structure to receive gui settings with default values
-
- if (filenames.empty())
- {
- //add default exclusion filter: this is only ever relevant when creating new configurations!
- //a default XmlGuiConfig does not need these user-specific exclusions!
- Zstring& excludeFilter = guiCfg.mainCfg.globalFilter.excludeFilter;
- if (!excludeFilter.empty() && !endsWith(excludeFilter, Zstr("\n")))
- excludeFilter += Zstr("\n");
- excludeFilter += globalSettings.gui.defaultExclusionFilter;
- }
- else
- try
- {
- readAnyConfig(filenames, guiCfg); //throw FfsXmlError
- }
- catch (const FfsXmlError& e)
- {
- if (e.getSeverity() == FfsXmlError::WARNING)
- showNotificationDialog(nullptr, DialogInfoType::WARNING, PopupDialogCfg().setDetailInstructions(e.toString()));
- //what about simulating changed config on parsing errors????
- else
- showNotificationDialog(nullptr, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString()));
- }
-
- //------------------------------------------------------------------------------------------
-
- create(guiCfg, filenames, &globalSettings, false);
-}
-
-
-void MainDialog::create(const xmlAccess::XmlGuiConfig& guiCfg,
- const std::vector<Zstring>& referenceFiles,
- const xmlAccess::XmlGlobalSettings* globalSettings,
- bool startComparison)
-{
- const xmlAccess::XmlGlobalSettings& globSett = globalSettings ? *globalSettings : retrieveGlobalCfgFromDisk();
- try
- {
- //we need to set language *before* creating MainDialog!
- setLanguage(globSett.programLanguage); //throw FileError
- }
- catch (const FileError& e)
- {
- showNotificationDialog(nullptr, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString()));
- //continue!
- }
-
- MainDialog* frame = new MainDialog(guiCfg, referenceFiles, globSett, startComparison);
- frame->Show();
-#ifdef ZEN_MAC
- ProcessSerialNumber psn = { 0, kCurrentProcess };
- ::SetFrontProcess(&psn); //call before TransformProcessType() so that OSX menu is updated correctly
- ::TransformProcessType(&psn, kProcessTransformToForegroundApplication); //show dock icon, even if we're not an application bundle
- //if the executable is not yet in a bundle or if it is called through a launcher, we need to set focus manually:
-#endif
-}
-
-
-MainDialog::MainDialog(const xmlAccess::XmlGuiConfig& guiCfg,
- const std::vector<Zstring>& referenceFiles,
- const xmlAccess::XmlGlobalSettings& globalSettings,
- bool startComparison) :
- MainDialogGenerated(nullptr),
- folderHistoryLeft (std::make_shared<FolderHistory>()), //make sure it is always bound
- folderHistoryRight(std::make_shared<FolderHistory>()), //
- focusWindowAfterSearch(nullptr)
-{
- m_directoryLeft ->init(folderHistoryLeft);
- m_directoryRight->init(folderHistoryRight);
-
- //setup sash: detach + reparent:
- m_splitterMain->SetSizer(nullptr); //alas wxFormbuilder doesn't allow us to have child windows without a sizer, so we have to remove it here
- m_splitterMain->setupWindows(m_gridMainL, m_gridMainC, m_gridMainR);
-
-#ifdef ZEN_WIN
- wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X!
-#endif
-
- setRelativeFontSize(*m_buttonCompare, 1.4);
- setRelativeFontSize(*m_buttonSync, 1.4);
- setRelativeFontSize(*m_buttonCancel, 1.4);
-
- //---------------- support for dockable gui style --------------------------------
- bSizerPanelHolder->Detach(m_panelTopButtons);
- bSizerPanelHolder->Detach(m_panelDirectoryPairs);
- bSizerPanelHolder->Detach(m_gridNavi);
- bSizerPanelHolder->Detach(m_panelCenter);
- bSizerPanelHolder->Detach(m_panelConfig);
- bSizerPanelHolder->Detach(m_panelFilter);
- bSizerPanelHolder->Detach(m_panelViewFilter);
- bSizerPanelHolder->Detach(m_panelStatistics);
-
- auiMgr.SetManagedWindow(this);
- auiMgr.SetFlags(wxAUI_MGR_DEFAULT | wxAUI_MGR_LIVE_RESIZE);
-
- compareStatus = make_unique<CompareProgressDialog>(*this); //integrate the compare status panel (in hidden state)
-
- //caption required for all panes that can be manipulated by the users => used by context menu
- auiMgr.AddPane(m_panelCenter,
- wxAuiPaneInfo().Name(L"Panel3").CenterPane().PaneBorder(false));
-
- auiMgr.AddPane(m_panelDirectoryPairs,
- wxAuiPaneInfo().Name(L"Panel2").Layer(2).Top().Caption(_("Folder Pairs")).CaptionVisible(false).PaneBorder(false).Gripper());
-
- auiMgr.AddPane(m_panelSearch,
- wxAuiPaneInfo().Name(L"PanelFind").Layer(2).Bottom().Caption(_("Find")).CaptionVisible(false).PaneBorder(false).Gripper().MinSize(200, m_bpButtonHideSearch->GetSize().GetHeight()).Hide());
-
- auiMgr.AddPane(m_gridNavi,
- wxAuiPaneInfo().Name(L"Panel10").Layer(3).Left().Position(1).Caption(_("Overview")).MinSize(300, m_gridNavi->GetSize().GetHeight())); //MinSize(): just default size, see comment below
-
- auiMgr.AddPane(m_panelConfig,
- wxAuiPaneInfo().Name(L"Panel4").Layer(3).Left().Position(2).Caption(_("Configuration")).MinSize(m_listBoxHistory->GetSize().GetWidth(), m_panelConfig->GetSize().GetHeight()));
-
- auiMgr.AddPane(m_panelTopButtons,
- wxAuiPaneInfo().Name(L"Panel1").Layer(4).Top().Row(1).Caption(_("Main Bar")).CaptionVisible(false).PaneBorder(false).Gripper().MinSize(-1, m_panelTopButtons->GetSize().GetHeight()));
- //note: min height is calculated incorrectly by wxAuiManager if panes with and without caption are in the same row => use smaller min-size
-
- auiMgr.AddPane(compareStatus->getAsWindow(),
- wxAuiPaneInfo().Name(L"Panel9").Layer(4).Top().Row(2).CaptionVisible(false).PaneBorder(false).Hide());
-
- auiMgr.AddPane(m_panelFilter,
- wxAuiPaneInfo().Name(L"Panel5").Layer(4).Bottom().Position(1).Caption(_("Filter Files")).MinSize(m_bpButtonFilter->GetSize().GetWidth(), m_panelFilter->GetSize().GetHeight()));
-
- auiMgr.AddPane(m_panelViewFilter,
- wxAuiPaneInfo().Name(L"Panel6").Layer(4).Bottom().Position(2).Caption(_("Select View")).MinSize(m_bpButtonShowDoNothing->GetSize().GetWidth(), m_panelViewFilter->GetSize().GetHeight()));
-
- auiMgr.AddPane(m_panelStatistics,
- wxAuiPaneInfo().Name(L"Panel7").Layer(4).Bottom().Position(3).Caption(_("Statistics")).MinSize(m_bitmapData->GetSize().GetWidth() + m_staticTextData->GetSize().GetWidth(), m_panelStatistics->GetSize().GetHeight()));
-
- auiMgr.Update();
-
- //give panel captions bold typeface
- if (wxAuiDockArt* artProvider = auiMgr.GetArtProvider())
- {
- wxFont font = artProvider->GetFont(wxAUI_DOCKART_CAPTION_FONT);
- font.SetWeight(wxFONTWEIGHT_BOLD);
- font.SetPointSize(wxNORMAL_FONT->GetPointSize()); //= larger than the wxAuiDockArt default; looks better on OS X
- artProvider->SetFont(wxAUI_DOCKART_CAPTION_FONT, font);
-
- //accessibility: fix wxAUI drawing black text on black background on high-contrast color schemes:
- artProvider->SetColor(wxAUI_DOCKART_INACTIVE_CAPTION_TEXT_COLOUR, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT));
- }
-
- auiMgr.GetPane(m_gridNavi).MinSize(-1, -1); //we successfully tricked wxAuiManager into setting an initial Window size :> incomplete API anyone??
- auiMgr.Update(); //
-
- defaultPerspective = auiMgr.SavePerspective();
- //----------------------------------------------------------------------------------
- //register view layout context menu
- m_panelTopButtons->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(MainDialog::OnContextSetLayout), nullptr, this);
- m_panelConfig ->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(MainDialog::OnContextSetLayout), nullptr, this);
- m_panelFilter ->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(MainDialog::OnContextSetLayout), nullptr, this);
- m_panelViewFilter->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(MainDialog::OnContextSetLayout), nullptr, this);
- m_panelStatistics->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(MainDialog::OnContextSetLayout), nullptr, this);
- m_panelStatusBar ->Connect(wxEVT_RIGHT_DOWN, wxMouseEventHandler(MainDialog::OnContextSetLayout), nullptr, this);
- //----------------------------------------------------------------------------------
-
- //sort grids
- m_gridMainL->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridClickEventHandler(MainDialog::onGridLabelLeftClickL ), nullptr, this);
- m_gridMainC->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridClickEventHandler(MainDialog::onGridLabelLeftClickC ), nullptr, this);
- m_gridMainR->Connect(EVENT_GRID_COL_LABEL_MOUSE_LEFT, GridClickEventHandler(MainDialog::onGridLabelLeftClickR ), nullptr, this);
-
- m_gridMainL->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridClickEventHandler(MainDialog::onGridLabelContextL ), nullptr, this);
- m_gridMainC->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridClickEventHandler(MainDialog::onGridLabelContextC ), nullptr, this);
- m_gridMainR->Connect(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, GridClickEventHandler(MainDialog::onGridLabelContextR ), nullptr, this);
-
- //grid context menu
- m_gridMainL->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextL), nullptr, this);
- m_gridMainC->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextC), nullptr, this);
- m_gridMainR->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onMainGridContextR), nullptr, this);
- m_gridNavi ->Connect(EVENT_GRID_MOUSE_RIGHT_UP, GridClickEventHandler(MainDialog::onNaviGridContext ), nullptr, this);
-
- m_gridMainL->Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler(MainDialog::onGridDoubleClickL), nullptr, this );
- m_gridMainR->Connect(EVENT_GRID_MOUSE_LEFT_DOUBLE, GridClickEventHandler(MainDialog::onGridDoubleClickR), nullptr, this );
-
- m_gridNavi->Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(MainDialog::onNaviSelection), nullptr, this);
- //----------------------------------------------------------------------------------
-
- m_panelSearch->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::OnSearchPanelKeyPressed), nullptr, this);
-
- //set tool tips with (non-translated!) short cut hint
- m_bpButtonOpen ->SetToolTip(_("Open...") + L" (Ctrl+O)");
- m_bpButtonSave ->SetToolTip(_("Save") + L" (Ctrl+S)");
- m_buttonCompare ->SetToolTip(_("Compare both sides") + L" (F5)");
- m_bpButtonCmpConfig ->SetToolTip(_("Comparison settings") + L" (F6)");
- m_bpButtonSyncConfig->SetToolTip(_("Synchronization settings") + L" (F7)");
- m_buttonSync ->SetToolTip(_("Start synchronization") + L" (F8)");
-
- gridDataView = std::make_shared<GridView>();
- treeDataView = std::make_shared<TreeView>();
-
- cleanedUp = false;
- processingGlobalKeyEvent = false;
-
-#ifdef ZEN_WIN
- new PanelMoveWindow(*this); //allow moving main dialog by clicking (nearly) anywhere... //ownership passed to "this"
-#endif
-
- //set icons for this dialog
- SetIcon(getFfsIcon()); //set application icon
-
- m_bpButtonSyncConfig->SetBitmapLabel(getResourceImage(L"cfg_sync"));
- m_bpButtonCmpConfig ->SetBitmapLabel(getResourceImage(L"cfg_compare"));
- m_bpButtonOpen ->SetBitmapLabel(getResourceImage(L"load"));
- m_bpButtonBatchJob ->SetBitmapLabel(getResourceImage(L"batch"));
- m_bpButtonAddPair ->SetBitmapLabel(getResourceImage(L"item_add"));
- m_bpButtonHideSearch->SetBitmapLabel(getResourceImage(L"close_panel"));
-
- //we can't use a wxButton for cancel: it's rendered smaller on OS X than a wxBitmapButton!
- setBitmapTextLabel(*m_buttonCancel, wxImage(), m_buttonCancel->GetLabel());
-
- {
- IconBuffer tmp(IconBuffer::SIZE_SMALL);
- const wxBitmap& bmpFile = tmp.genericFileIcon();
- const wxBitmap& bmpDir = tmp.genericDirIcon();
-
- m_bitmapSmallDirectoryLeft ->SetBitmap(bmpDir);
- m_bitmapSmallFileLeft ->SetBitmap(bmpFile);
- m_bitmapSmallDirectoryRight->SetBitmap(bmpDir);
- m_bitmapSmallFileRight ->SetBitmap(bmpFile);
- }
-
- //menu icons: workaround for wxWidgets: small hack to update menu items: actually this is a wxWidgets bug (affects Windows- and Linux-build)
- setMenuItemImage(m_menuItem10, getResourceImage(L"compare_small"));
- setMenuItemImage(m_menuItem11, getResourceImage(L"sync_small"));
-
- setMenuItemImage(m_menuItemLoad, getResourceImage(L"load_small"));
- setMenuItemImage(m_menuItemSave, getResourceImage(L"save_small"));
-
- setMenuItemImage(m_menuItemGlobSett, getResourceImage(L"settings_small"));
- setMenuItemImage(m_menuItem7, getResourceImage(L"batch_small"));
-
- setMenuItemImage(m_menuItemManual, getResourceImage(L"help_small"));
- setMenuItemImage(m_menuItemAbout, getResourceImage(L"about_small"));
-
- if (!manualProgramUpdateRequired())
- {
- m_menuItemCheckVersionNow ->Enable(false);
- m_menuItemCheckVersionAuto->Enable(false);
-
- //wxFormbuilder doesn't give us a wxMenuItem for m_menuCheckVersion, so we need this abomination:
- wxMenuItemList& items = m_menuHelp->GetMenuItems();
- for (auto it = items.begin(); it != items.end(); ++it)
- if ((*it)->GetSubMenu() == m_menuCheckVersion)
- (*it)->Enable(false);
- }
-
- //create language selection menu
- std::for_each(zen::ExistingTranslations::get().begin(), ExistingTranslations::get().end(),
- [&](const ExistingTranslations::Entry& entry)
- {
- wxMenuItem* newItem = new wxMenuItem(m_menuLanguages, wxID_ANY, entry.languageName);
- newItem->SetBitmap(getResourceImage(entry.languageFlag));
-
- //map menu item IDs with language IDs: evaluated when processing event handler
- languageMenuItemMap.insert(std::make_pair(newItem->GetId(), entry.languageID));
-
- //connect event
- this->Connect(newItem->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnMenuLanguageSwitch));
- m_menuLanguages->Append(newItem);
- });
-
- //notify about (logical) application main window => program won't quit, but stay on this dialog
- zen::setMainWindow(this);
-
- //init handling of first folder pair
- firstFolderPair = make_unique<FolderPairFirst>(*this);
-
- initViewFilterButtons();
-
- //init grid settings
- gridview::init(*m_gridMainL, *m_gridMainC, *m_gridMainR, gridDataView);
- treeview::init(*m_gridNavi, treeDataView);
- //config_history::init(*m_gridConfigHistory, lastRunConfigName());
-
- //initialize and load configuration
- setGlobalCfgOnInit(globalSettings);
- setConfig(guiCfg, referenceFiles);
-
- //support for CTRL + C and DEL on grids
- m_gridMainL->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridButtonEventL), nullptr, this);
- m_gridMainC->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridButtonEventC), nullptr, this);
- m_gridMainR->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridButtonEventR), nullptr, this);
-
- m_gridNavi->getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onTreeButtonEvent), nullptr, this);
-
- //register global hotkeys (without explicit menu entry)
- wxTheApp->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::OnGlobalKeyEvent), nullptr, this);
- wxTheApp->Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::OnGlobalKeyEvent), nullptr, this); //capture direction keys
-
- //drag & drop on navi panel
- setupFileDrop(*m_gridNavi);
- m_gridNavi->Connect(EVENT_DROP_FILE, FileDropEventHandler(MainDialog::onNaviPanelFilesDropped), nullptr, this);
-
- timerForAsyncTasks.Connect(wxEVT_TIMER, wxEventHandler(MainDialog::onProcessAsyncTasks), nullptr, this);
-
- //Connect(wxEVT_SIZE, wxSizeEventHandler(MainDialog::OnResize), nullptr, this);
- //Connect(wxEVT_MOVE, wxSizeEventHandler(MainDialog::OnResize), nullptr, this);
-
- //calculate witdh of folder pair manually (if scrollbars are visible)
- m_panelTopLeft->Connect(wxEVT_SIZE, wxEventHandler(MainDialog::OnResizeLeftFolderWidth), nullptr, this);
-
- //dynamically change sizer direction depending on size
- m_panelConfig ->Connect(wxEVT_SIZE, wxEventHandler(MainDialog::OnResizeConfigPanel), nullptr, this);
- m_panelViewFilter->Connect(wxEVT_SIZE, wxEventHandler(MainDialog::OnResizeViewPanel), nullptr, this);
- m_panelStatistics->Connect(wxEVT_SIZE, wxEventHandler(MainDialog::OnResizeStatisticsPanel), nullptr, this);
- wxSizeEvent dummy3;
- OnResizeConfigPanel (dummy3); //call once on window creation
- OnResizeViewPanel (dummy3); //
- OnResizeStatisticsPanel(dummy3); //
-
- //event handler for manual (un-)checking of rows and setting of sync direction
- m_gridMainC->Connect(EVENT_GRID_CHECK_ROWS, CheckRowsEventHandler (MainDialog::onCheckRows), nullptr, this);
- m_gridMainC->Connect(EVENT_GRID_SYNC_DIRECTION, SyncDirectionEventHandler(MainDialog::onSetSyncDirection), nullptr, this);
-
- //mainly to update row label sizes...
- updateGui();
-
- //register regular check for update on next idle event
- Connect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnRegularUpdateCheck), nullptr, this);
-
- //asynchronous call to wxWindow::Layout(): fix superfluous frame on right and bottom when FFS is started in fullscreen mode
- Connect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnLayoutWindowAsync), nullptr, this);
- wxCommandEvent evtDummy; //call once before OnLayoutWindowAsync()
- OnResizeLeftFolderWidth(evtDummy); //
-
- //----------------------------------------------------------------------------------------------------------------------------------------------------------------
- //some convenience: if FFS is started with a *.ffs_gui file as commandline parameter AND all directories contained exist, comparison shall be started right away
- if (startComparison)
- {
- const zen::MainConfiguration currMainCfg = getConfig().mainCfg;
-
- //------------------------------------------------------------------------------------------
- //check existence of all directories in parallel!
- RunUntilFirstHit<NullType> findFirstMissing;
-
- //harmonize checks with comparison.cpp:: checkForIncompleteInput()
- //we're really doing two checks: 1. check directory existence 2. check config validity -> don't mix them!
- bool havePartialPair = false;
- bool haveFullPair = false;
-
- auto addDirCheck = [&](const FolderPairEnh& fp)
- {
- const Zstring dirLeft = getFormattedDirectoryName(fp.leftDirectory ); //should not block!?
- const Zstring dirRight = getFormattedDirectoryName(fp.rightDirectory); //
-
- if (dirLeft.empty() != dirRight.empty()) //only skip check if both sides are empty!
- havePartialPair = true;
- else if (!dirLeft.empty())
- haveFullPair = true;
-
- if (!dirLeft.empty())
- findFirstMissing.addJob([=] { return !dirExists(dirLeft ) ? zen::make_unique<NullType>() : nullptr; });
- if (!dirRight.empty())
- findFirstMissing.addJob([=] { return !dirExists(dirRight) ? zen::make_unique<NullType>() : nullptr; });
- };
-
- addDirCheck(currMainCfg.firstPair);
- std::for_each(currMainCfg.additionalPairs.begin(), currMainCfg.additionalPairs.end(), addDirCheck);
- //------------------------------------------------------------------------------------------
-
- if (havePartialPair != haveFullPair) //either all pairs full or all half-filled -> validity check!
- {
- //potentially slow network access: give all checks 500ms to finish
- const bool allFilesExist = findFirstMissing.timedWait(boost::posix_time::milliseconds(500)) && //true: have result
- !findFirstMissing.get(); //no missing
- if (allFilesExist)
- if (wxEvtHandler* evtHandler = m_buttonCompare->GetEventHandler())
- {
- wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED);
- evtHandler->AddPendingEvent(dummy2); //simulate button click on "compare"
- }
- }
- }
-}
-
-
-MainDialog::~MainDialog()
-{
- try //save "GlobalSettings.xml"
- {
- xmlAccess::writeConfig(getGlobalCfgBeforeExit()); //throw FfsXmlError
- }
- catch (const xmlAccess::FfsXmlError& e)
- {
- showNotificationDialog(this, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString()));
- }
-
- try //save "LastRun.ffs_gui"
- {
- xmlAccess::writeConfig(getConfig(), lastRunConfigName()); //throw FfsXmlError
- }
- //don't annoy users on read-only drives: it's enough to show a single error message when saving global config
- catch (const xmlAccess::FfsXmlError&) {}
-
- //important! event source wxTheApp is NOT dependent on this instance -> disconnect!
- wxTheApp->Disconnect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::OnGlobalKeyEvent), nullptr, this);
- wxTheApp->Disconnect(wxEVT_CHAR_HOOK, wxKeyEventHandler(MainDialog::OnGlobalKeyEvent), nullptr, this);
-
-#ifdef ZEN_MAC
- //more (non-portable) wxWidgets crap: wxListBox leaks wxClientData, both of the following functions fail to clean up:
- // src/common/ctrlsub.cpp:: wxItemContainer::~wxItemContainer() -> empty function body!!!
- // src/osx/listbox_osx.cpp: wxListBox::~wxListBox()
- //=> finally a manual wxItemContainer::Clear() will render itself useful:
- m_listBoxHistory->Clear();
-#endif
-
- auiMgr.UnInit();
-
- //no need for wxEventHandler::Disconnect() here; event sources are components of this window and are destroyed, too
-}
-
-//-------------------------------------------------------------------------------------------------------------------------------------
-
-void MainDialog::onQueryEndSession()
-{
- try { xmlAccess::writeConfig(getGlobalCfgBeforeExit()); }
- 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!
-
- try { xmlAccess::writeConfig(getConfig(), lastRunConfigName()); }
- catch (const xmlAccess::FfsXmlError&) {}
-}
-
-
-void MainDialog::setGlobalCfgOnInit(const xmlAccess::XmlGlobalSettings& globalSettings)
-{
- globalCfg = globalSettings;
-
- //caveat set/get language asymmmetry! setLanguage(globalSettings.programLanguage); //throw FileError
- //we need to set langugabe before creating this class!
-
- //set dialog size and position:
- // - width/height are invalid if the window is minimized (eg x,y == -32000; height = 28, width = 160)
- // - multi-monitor setups: dialog may be placed on second monitor which is currently turned off
- if (globalSettings.gui.dlgSize.GetWidth () > 0 &&
- globalSettings.gui.dlgSize.GetHeight() > 0)
- {
- //calculate how much of the dialog will be visible on screen
- const int dialogAreaTotal = globalSettings.gui.dlgSize.GetWidth() * globalSettings.gui.dlgSize.GetHeight();
- int dialogAreaVisible = 0;
-
- const int monitorCount = wxDisplay::GetCount();
- for (int i = 0; i < monitorCount; ++i)
- {
- wxRect intersection = wxDisplay(i).GetClientArea().Intersect(wxRect(globalSettings.gui.dlgPos, globalSettings.gui.dlgSize));
- dialogAreaVisible = std::max(dialogAreaVisible, intersection.GetWidth() * intersection.GetHeight());
- }
-
- if (dialogAreaVisible > 0.1 * dialogAreaTotal) //at least 10% of the dialog should be visible!
- SetSize(wxRect(globalSettings.gui.dlgPos, globalSettings.gui.dlgSize));
- else
- {
- SetSize(wxRect(globalSettings.gui.dlgSize));
- Centre();
- }
- }
- else
- Centre();
-
- Maximize(globalSettings.gui.isMaximized);
-
- //set column attributes
- m_gridMainL ->setColumnConfig(gridview::convertConfig(globalSettings.gui.columnAttribLeft));
- m_gridMainR ->setColumnConfig(gridview::convertConfig(globalSettings.gui.columnAttribRight));
- m_splitterMain->setSashOffset(globalSettings.gui.sashOffset);
-
- m_gridNavi->setColumnConfig(treeview::convertConfig(globalSettings.gui.columnAttribNavi));
- treeview::setShowPercentage(*m_gridNavi, globalSettings.gui.showPercentBar);
-
- treeDataView->setSortDirection(globalSettings.gui.naviLastSortColumn, globalSettings.gui.naviLastSortAscending);
-
- //--------------------------------------------------------------------------------
- //load list of last used configuration files
- //config_history::setItems(*m_gridConfigHistory, globalSettings.gui.lastUsedConfigFiles2);
-
- std::vector<Zstring> cfgFileNames;
- std::transform(globalSettings.gui.cfgFileHistory.rbegin(), globalSettings.gui.cfgFileHistory.rend(), std::back_inserter(cfgFileNames),
- [](const ConfigHistoryItem& item) { return item.configFile; });
- //list is stored with last used files first in xml, however addFileToCfgHistory() needs them last!!!
-
- cfgFileNames.push_back(lastRunConfigName()); //make sure <Last session> is always part of history list (if existing)
- addFileToCfgHistory(cfgFileNames);
-
- removeObsoleteCfgHistoryItems(cfgFileNames); //remove non-existent items (we need this only on startup)
- //--------------------------------------------------------------------------------
-
- //load list of last used folders
- *folderHistoryLeft = FolderHistory(globalSettings.gui.folderHistoryLeft, globalSettings.gui.folderHistMax);
- *folderHistoryRight = FolderHistory(globalSettings.gui.folderHistoryRight, globalSettings.gui.folderHistMax);
-
- //show/hide file icons
- gridview::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalSettings.gui.showIcons, convert(globalSettings.gui.iconSize));
-
- //------------------------------------------------------------------------------------------------
- m_checkBoxMatchCase->SetValue(globalCfg.gui.textSearchRespectCase);
-
- //wxAuiManager erroneously loads panel captions, we don't want that
- typedef std::vector<std::pair<wxString, wxString>> CaptionNameMapping;
- CaptionNameMapping captionNameMap;
- const wxAuiPaneInfoArray& paneArray = auiMgr.GetAllPanes();
- for (size_t i = 0; i < paneArray.size(); ++i)
- captionNameMap.push_back(std::make_pair(paneArray[i].caption, paneArray[i].name));
-
- auiMgr.LoadPerspective(globalSettings.gui.guiPerspectiveLast);
-
- //restore original captions
- for (auto it = captionNameMap.begin(); it != captionNameMap.end(); ++it)
- auiMgr.GetPane(it->second).Caption(it->first);
-
- //if MainDialog::onQueryEndSession() is called while comparison is active, this panel is saved and restored as "visible"
- auiMgr.GetPane(compareStatus->getAsWindow()).Hide();
-
- auiMgr.GetPane(m_panelSearch).Hide(); //no need to show it on startup
-
- m_menuItemCheckVersionAuto->Check(globalCfg.gui.lastUpdateCheck != -1);
-
- auiMgr.Update();
-}
-
-
-xmlAccess::XmlGlobalSettings MainDialog::getGlobalCfgBeforeExit()
-{
- Freeze(); //no need to Thaw() again!!
-
- xmlAccess::XmlGlobalSettings globalSettings = globalCfg;
-
- globalSettings.programLanguage = getLanguage();
-
- //retrieve column attributes
- globalSettings.gui.columnAttribLeft = gridview::convertConfig(m_gridMainL->getColumnConfig());
- globalSettings.gui.columnAttribRight = gridview::convertConfig(m_gridMainR->getColumnConfig());
- globalSettings.gui.sashOffset = m_splitterMain->getSashOffset();
-
- globalSettings.gui.columnAttribNavi = treeview::convertConfig(m_gridNavi->getColumnConfig());
- globalSettings.gui.showPercentBar = treeview::getShowPercentage(*m_gridNavi);
-
- const std::pair<ColumnTypeNavi, bool> sortInfo = treeDataView->getSortDirection();
- globalSettings.gui.naviLastSortColumn = sortInfo.first;
- globalSettings.gui.naviLastSortAscending = sortInfo.second;
-
- //--------------------------------------------------------------------------------
- //write list of last used configuration files
- std::map<int, Zstring> historyDetail; //(cfg-file/last use index)
- for (unsigned int i = 0; i < m_listBoxHistory->GetCount(); ++i)
- if (auto clientString = dynamic_cast<const wxClientHistoryData*>(m_listBoxHistory->GetClientObject(i)))
- historyDetail.insert(std::make_pair(clientString->lastUseIndex_, clientString->cfgFile_));
- else
- assert(false);
-
- //sort by last use; put most recent items *first* (looks better in xml than the reverse)
- std::vector<ConfigHistoryItem> history;
- std::transform(historyDetail.rbegin(), historyDetail.rend(), std::back_inserter(history), [](const std::pair<const int, Zstring>& item) { return ConfigHistoryItem(item.second); });
-
- if (history.size() > globalSettings.gui.cfgFileHistMax) //erase oldest elements
- history.resize(globalSettings.gui.cfgFileHistMax);
-
- globalSettings.gui.cfgFileHistory = history;
- //--------------------------------------------------------------------------------
- globalSettings.gui.lastUsedConfigFiles = activeConfigFiles;
- //globalSettings.gui.lastUsedConfigFiles2 = config_history::getItems(*m_gridConfigHistory);
-
- //write list of last used folders
- globalSettings.gui.folderHistoryLeft = folderHistoryLeft ->getList();
- globalSettings.gui.folderHistoryRight = folderHistoryRight->getList();
-
- globalSettings.gui.textSearchRespectCase = m_checkBoxMatchCase->GetValue();
-
- globalSettings.gui.guiPerspectiveLast = auiMgr.SavePerspective();
-
- //we need to portably retrieve non-iconized, non-maximized size and position (non-portable: GetWindowPlacement())
- //call *after* wxAuiManager::SavePerspective()!
- if (IsIconized())
- Iconize(false);
-
- globalSettings.gui.isMaximized = IsMaximized(); //evaluate AFTER uniconizing!
-
- if (IsMaximized())
- Maximize(false);
-
- globalSettings.gui.dlgSize = GetSize();
- globalSettings.gui.dlgPos = GetPosition();
-
- return globalSettings;
-}
-
-
-void MainDialog::setSyncDirManually(const std::vector<FileSystemObject*>& selection, SyncDirection direction)
-{
- if (!selection.empty())
- {
- std::for_each(selection.begin(), selection.end(),
- [&](FileSystemObject* fsObj)
- {
- setSyncDirectionRec(direction, *fsObj); //set new direction (recursively)
- zen::setActiveStatus(true, *fsObj); //works recursively for directories
- });
-
- updateGui();
- }
-}
-
-
-void MainDialog::setFilterManually(const std::vector<FileSystemObject*>& selection, bool setIncluded)
-{
- //if hidefiltered is active, there should be no filtered elements on screen => current element was filtered out
- assert(!currentCfg.hideExcludedItems || !setIncluded);
-
- if (!selection.empty())
- {
- std::for_each(selection.begin(), selection.end(),
- [&](FileSystemObject* fsObj) { zen::setActiveStatus(setIncluded, *fsObj); }); //works recursively for directories
-
- updateGuiDelayedIf(currentCfg.hideExcludedItems); //show update GUI before removing rows
- }
-}
-
-
-namespace
-{
-//perf: wxString doesn't model exponential growth and so is unusable for large data sets
-typedef Zbase<wchar_t> zxString; //guaranteed exponential growth
-}
-
-void MainDialog::copySelectionToClipboard(const std::vector<const Grid*>& gridRefs)
-{
- try
- {
- zxString clipboardString;
-
- auto addSelection = [&](const Grid& grid)
- {
- if (auto prov = grid.getDataProvider())
- {
- std::vector<Grid::ColumnAttribute> colAttr = grid.getColumnConfig();
- vector_remove_if(colAttr, [](const Grid::ColumnAttribute& ca) { return !ca.visible_; });
- if (!colAttr.empty())
- {
- const std::vector<size_t> selection = grid.getSelectedRows();
- std::for_each(selection.begin(), selection.end(),
- [&](size_t row)
- {
- std::for_each(colAttr.begin(), colAttr.end() - 1,
- [&](const Grid::ColumnAttribute& ca)
- {
- clipboardString += copyStringTo<zxString>(prov->getValue(row, ca.type_));
- clipboardString += L'\t';
- });
- clipboardString += copyStringTo<zxString>(prov->getValue(row, colAttr.back().type_));
- clipboardString += L'\n';
- });
- }
- }
- };
-
- for (auto it = gridRefs.begin(); it != gridRefs.end(); ++it)
- addSelection(**it);
-
- //finally write to clipboard
- if (!clipboardString.empty())
- if (wxClipboard::Get()->Open())
- {
- ZEN_ON_SCOPE_EXIT(wxClipboard::Get()->Close());
- wxClipboard::Get()->SetData(new wxTextDataObject(copyStringTo<wxString>(clipboardString))); //ownership passed
- }
- }
- catch (const std::bad_alloc& e)
- {
- showNotificationDialog(this, DialogInfoType::ERROR2, PopupDialogCfg().setMainInstructions(_("Out of memory.") + L" " + utfCvrtTo<std::wstring>(e.what())));
- }
-}
-
-
-std::vector<FileSystemObject*> MainDialog::getGridSelection(bool fromLeft, bool fromRight) const
-{
- std::set<size_t> selectedRows;
-
- auto addSelection = [&](const Grid& grid)
- {
- const std::vector<size_t>& sel = grid.getSelectedRows();
- selectedRows.insert(sel.begin(), sel.end());
- };
-
- if (fromLeft)
- addSelection(*m_gridMainL);
-
- if (fromRight)
- addSelection(*m_gridMainR);
-
- return gridDataView->getAllFileRef(selectedRows);
-}
-
-
-std::vector<FileSystemObject*> MainDialog::getTreeSelection() const
-{
- std::vector<FileSystemObject*> output;
-
- const std::vector<size_t>& sel = m_gridNavi->getSelectedRows();
- std::for_each(sel.begin(), sel.end(),
- [&](size_t row)
- {
- if (std::unique_ptr<TreeView::Node> node = treeDataView->getLine(row))
- {
- if (auto root = dynamic_cast<const TreeView::RootNode*>(node.get()))
- {
- //select first level of child elements
- for (auto& fsObj : root->baseDirObj_.refSubDirs ()) output.push_back(&fsObj);
- for (auto& fsObj : root->baseDirObj_.refSubFiles()) output.push_back(&fsObj);
- for (auto& fsObj : root->baseDirObj_.refSubLinks()) output.push_back(&fsObj);
- }
- else if (auto dir = dynamic_cast<const TreeView::DirNode*>(node.get()))
- output.push_back(&(dir->dirObj_));
- else if (auto file = dynamic_cast<const TreeView::FilesNode*>(node.get()))
- output.insert(output.end(), file->filesAndLinks_.begin(), file->filesAndLinks_.end());
- }
- });
- return output;
-}
-
-
-//Exception class used to abort the "compare" and "sync" process
-class AbortDeleteProcess {};
-
-class ManualDeletionHandler : private wxEvtHandler, public DeleteFilesHandler //throw AbortDeleteProcess
-{
-public:
- ManualDeletionHandler(MainDialog& main) :
- mainDlg(main),
- abortRequested(false),
- ignoreErrors(false)
- {
- mainDlg.disableAllElements(true); //disable everything except abort button
-
- //register abort button
- mainDlg.m_buttonCancel->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ManualDeletionHandler::OnAbortDeletion), nullptr, this );
- mainDlg.Connect(wxEVT_CHAR_HOOK, wxKeyEventHandler(ManualDeletionHandler::OnKeyPressed), nullptr, this);
- }
-
- ~ManualDeletionHandler()
- {
- //de-register abort button
- mainDlg.Disconnect(wxEVT_CHAR_HOOK, wxKeyEventHandler(ManualDeletionHandler::OnKeyPressed), nullptr, this);
- mainDlg.m_buttonCancel->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ManualDeletionHandler::OnAbortDeletion ), nullptr, this );
-
- mainDlg.enableAllElements();
- }
-
- virtual Response reportError(const std::wstring& msg)
- {
- if (ignoreErrors)
- return DeleteFilesHandler::IGNORE_ERROR;
-
- forceUiRefresh();
- bool ignoreNextErrors = false;
- switch (showConfirmationDialog3(&mainDlg, DialogInfoType::ERROR2, PopupDialogCfg3().
- setDetailInstructions(msg).
- setCheckBox(ignoreNextErrors, _("&Ignore subsequent errors"), ConfirmationButton3::DONT_DO_IT),
- _("&Ignore"), _("&Retry")))
- {
- case ConfirmationButton3::DO_IT: //ignore
- ignoreErrors = ignoreNextErrors;
- return DeleteFilesHandler::IGNORE_ERROR;
-
- case ConfirmationButton3::DONT_DO_IT: //retry
- return DeleteFilesHandler::RETRY;
-
- case ConfirmationButton3::CANCEL:
- throw AbortDeleteProcess();
- }
-
- assert (false);
- return DeleteFilesHandler::IGNORE_ERROR; //dummy return value
- }
-
- virtual void reportWarning(const std::wstring& msg, bool& warningActive)
- {
- if (!warningActive || ignoreErrors)
- return;
-
- forceUiRefresh();
- bool dontWarnAgain = false;
- switch (showConfirmationDialog(&mainDlg, DialogInfoType::WARNING, PopupDialogCfg().
- setDetailInstructions(msg).
- setCheckBox(dontWarnAgain, _("&Don't show this warning again")), _("&Ignore")))
- {
- case ConfirmationButton::DO_IT:
- warningActive = !dontWarnAgain;
- break;
- case ConfirmationButton::CANCEL:
- throw AbortDeleteProcess();
- }
- }
-
- virtual void reportStatus (const std::wstring& msg)
- {
- statusMsg = msg;
- requestUiRefresh();
- }
-
-private:
- virtual void requestUiRefresh()
- {
- if (updateUiIsAllowed()) //test if specific time span between ui updates is over
- forceUiRefresh();
-
- if (abortRequested) //test after (implicit) call to wxApp::Yield()
- throw AbortDeleteProcess();
- }
-
- void forceUiRefresh()
- {
- //std::wstring msg = toGuiString(deletionCount) +
- mainDlg.setStatusBarFullText(statusMsg);
- wxTheApp->Yield();
- }
-
- //context: C callstack message loop => throw()!
- void OnAbortDeletion(wxCommandEvent& event) //handle abort button click
- {
- abortRequested = true; //don't throw exceptions in a GUI-Callback!!! (throw zen::AbortThisProcess())
- }
-
- void OnKeyPressed(wxKeyEvent& event)
- {
- const int keyCode = event.GetKeyCode();
- if (keyCode == WXK_ESCAPE)
- {
- abortRequested = true; //don't throw exceptions in a GUI-Callback!!! (throw zen::AbortThisProcess())
- return;
- }
-
- event.Skip();
- }
-
- MainDialog& mainDlg;
-
- bool abortRequested;
- bool ignoreErrors;
- //size_t deletionCount; //
- std::wstring statusMsg; //status reporting
-};
-
-
-void MainDialog::deleteSelectedFiles(const std::vector<FileSystemObject*>& selectionLeft,
- const std::vector<FileSystemObject*>& selectionRight)
-{
- bool deleteOnBothSides = false; //let's keep this disabled by default -> don't save
- //=> clenup empty selection on either side:
- std::vector<FileSystemObject*> selectionLeftTmp;
- std::vector<FileSystemObject*> selectionRightTmp;
- std::copy_if(selectionLeft .begin(), selectionLeft .end(), std::back_inserter(selectionLeftTmp ), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<LEFT_SIDE >(); });
- std::copy_if(selectionRight.begin(), selectionRight.end(), std::back_inserter(selectionRightTmp), [](const FileSystemObject* fsObj) { return !fsObj->isEmpty<RIGHT_SIDE>(); });
-
- if (!selectionLeftTmp.empty() || !selectionRightTmp.empty())
- {
- wxWindow* oldFocus = wxWindow::FindFocus();
- ZEN_ON_SCOPE_EXIT(if (oldFocus) oldFocus->SetFocus();)
-
- if (zen::showDeleteDialog(this,
- selectionLeftTmp,
- selectionRightTmp,
- deleteOnBothSides,
- globalCfg.gui.useRecyclerForManualDeletion) == ReturnSmallDlg::BUTTON_OKAY)
- {
- //wxBusyCursor dummy; -> redundant: progress already shown in status bar!
- try
- {
- //handle errors when deleting files/folders
- ManualDeletionHandler statusHandler(*this);
-
- zen::deleteFromGridAndHD(selectionLeftTmp,
- selectionRightTmp,
- folderCmp,
- extractDirectionCfg(getConfig().mainCfg),
- deleteOnBothSides,
- globalCfg.gui.useRecyclerForManualDeletion,
- statusHandler,
- globalCfg.optDialogs.warningRecyclerMissing);
-
- gridview::clearSelection(*m_gridMainL, *m_gridMainC, *m_gridMainR); //do not clear, if aborted!
- }
- catch (AbortDeleteProcess&) {}
-
- //remove rows that are empty: just a beautification, invalid rows shouldn't cause issues
- gridDataView->removeInvalidRows();
-
- //redraw grid neccessary to update new dimensions and for UI-Backend data linkage
- updateGui(); //call immediately after deleteFromGridAndHD!!!
- }
- }
-}
-
-
-namespace
-{
-template <SelectedSide side>
-Zstring getExistingParentFolder(const FileSystemObject& fsObj)
-{
- const DirPair* dirObj = dynamic_cast<const DirPair*>(&fsObj);
- if (!dirObj)
- dirObj = dynamic_cast<const DirPair*>(&fsObj.parent());
-
- while (dirObj)
- {
- if (!dirObj->isEmpty<side>())
- return dirObj->getFullName<side>();
-
- dirObj = dynamic_cast<const DirPair*>(&dirObj->parent());
- }
- return fsObj.getBaseDirPf<side>();
-}
-}
-
-void MainDialog::openExternalApplication(const wxString& commandline, const std::vector<FileSystemObject*>& selection, bool leftSide)
-{
- if (commandline.empty())
- return;
-
- auto selectionTmp = selection;
-
- const bool openFileBrowserRequested = [&]() -> bool
- {
- xmlAccess::XmlGlobalSettings::Gui dummy;
- return !dummy.externelApplications.empty() && dummy.externelApplications[0].second == commandline;
- }();
-
- //support fallback instead of an error in this special case
- if (openFileBrowserRequested)
- {
- if (selectionTmp.size() > 1) //do not open more than one explorer instance!
- selectionTmp.resize(1); //
-
- if (selectionTmp.empty() ||
- (leftSide && selectionTmp[0]->isEmpty<LEFT_SIDE >()) ||
- (!leftSide && selectionTmp[0]->isEmpty<RIGHT_SIDE>()))
- {
- Zstring fallbackDir;
- if (selectionTmp.empty())
- fallbackDir = leftSide ?
- getFormattedDirectoryName(firstFolderPair->getLeftDir()) :
- getFormattedDirectoryName(firstFolderPair->getRightDir());
-
- else
- fallbackDir = leftSide ?
- getExistingParentFolder<LEFT_SIDE >(*selectionTmp[0]) :
- getExistingParentFolder<RIGHT_SIDE>(*selectionTmp[0]);
-
- try
- {
-#ifdef ZEN_WIN
- shellExecute2(L"\"" + fallbackDir + L"\"", EXEC_TYPE_ASYNC); //throw FileError
-#elif defined ZEN_LINUX
- shellExecute2("xdg-open \"" + fallbackDir + "\"", EXEC_TYPE_ASYNC); //
-#elif defined ZEN_MAC
- shellExecute2("open \"" + fallbackDir + "\"", EXEC_TYPE_ASYNC); //
-#endif
- }
- catch (const FileError& e) { showNotificationDialog(this, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString())); }
- return;
- }
- }
-
- const size_t massInvokeThreshold = 10; //more than this is likely a user mistake
-
- if (selectionTmp.size() > massInvokeThreshold)
- if (globalCfg.optDialogs.confirmExternalCommandMassInvoke)
- {
- bool dontAskAgain = false;
- switch (showConfirmationDialog(this, DialogInfoType::WARNING, PopupDialogCfg().
- setTitle(_("Confirm")).
- setMainInstructions(replaceCpy(_P("Do you really want to execute the command %y for one item?",
- "Do you really want to execute the command %y for %x items?", selectionTmp.size()),
- L"%y", L'\"' + commandline + L'\"')).
- setCheckBox(dontAskAgain, _("&Don't show this warning again")),
- _("&Execute")))
- {
- case ConfirmationButton::DO_IT:
- globalCfg.optDialogs.confirmExternalCommandMassInvoke = !dontAskAgain;
- break;
- case ConfirmationButton::CANCEL:
- return;
- }
- }
-
- //regular command evaluation
- for (const FileSystemObject* fsObj : selectionTmp) //context menu calls this function only if selection is not empty!
- {
- Zstring path1 = fsObj->getBaseDirPf<LEFT_SIDE>() + fsObj->getObjRelativeName(); //full path, even if item is not existing!
- Zstring dir1 = beforeLast(path1, FILE_NAME_SEPARATOR); //Win: wrong for root paths like "C:\file.txt"
-
- Zstring path2 = fsObj->getBaseDirPf<RIGHT_SIDE>() + fsObj->getObjRelativeName();
- Zstring dir2 = beforeLast(path2, FILE_NAME_SEPARATOR);
-
- if (!leftSide)
- {
- std::swap(path1, path2);
- std::swap(dir1, dir2);
- }
-
- Zstring command = utfCvrtTo<Zstring>(commandline);
- replace(command, Zstr("%item_path%"), path1);
- replace(command, Zstr("%item2_path%"), path2);
- replace(command, Zstr("%item_folder%"), dir1 );
- replace(command, Zstr("%item2_folder%"), dir2 );
-
- auto cmdExp = expandMacros(command);
- try
- {
- //caveat: spawning too many threads asynchronously can easily kill a user's desktop session on Ubuntu!
- shellExecute2(cmdExp, selectionTmp.size() > massInvokeThreshold ? EXEC_TYPE_SYNC : EXEC_TYPE_ASYNC); //throw FileError
- }
- catch (const FileError& e) { showNotificationDialog(this, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString())); }
- }
-}
-
-
-void MainDialog::setStatusBarFileStatistics(size_t filesOnLeftView,
- size_t foldersOnLeftView,
- size_t filesOnRightView,
- size_t foldersOnRightView,
- zen::UInt64 filesizeLeftView,
- zen::UInt64 filesizeRightView)
-{
-#ifdef ZEN_WIN
- wxWindowUpdateLocker dummy(m_panelStatusBar); //leads to GUI corruption problems on Linux/OS X!
-#endif
-
- //select state
- bSizerFileStatus->Show(true);
- m_staticTextFullStatus->Hide();
-
- //update status information
- bSizerStatusLeftDirectories->Show(foldersOnLeftView > 0);
- bSizerStatusLeftFiles ->Show(filesOnLeftView > 0);
-
- setText(*m_staticTextStatusLeftDirs, _P("1 directory", "%x directories", foldersOnLeftView));
- setText(*m_staticTextStatusLeftFiles, _P("1 file", "%x files", filesOnLeftView));
- setText(*m_staticTextStatusLeftBytes, L"(" + filesizeToShortString(to<Int64>(filesizeLeftView)) + L")");
- //------------------------------------------------------------------------------
- bSizerStatusRightDirectories->Show(foldersOnRightView > 0);
- bSizerStatusRightFiles ->Show(filesOnRightView > 0);
-
- setText(*m_staticTextStatusRightDirs, _P("1 directory", "%x directories", foldersOnRightView));
- setText(*m_staticTextStatusRightFiles, _P("1 file", "%x files", filesOnRightView));
- setText(*m_staticTextStatusRightBytes, L"(" + filesizeToShortString(to<Int64>(filesizeRightView)) + L")");
- //------------------------------------------------------------------------------
- wxString statusMiddleNew;
- if (gridDataView->rowsTotal() > 0)
- {
- statusMiddleNew = _P("%y of 1 row in view", "%y of %x rows in view", gridDataView->rowsTotal());
- replace(statusMiddleNew, L"%y", toGuiString(gridDataView->rowsOnView()), false); //%x is already used as plural form placeholder!
- }
-
- //fill middle text (considering flashStatusInformation())
- if (oldStatusMsgs.empty())
- setText(*m_staticTextStatusMiddle, statusMiddleNew);
- else
- oldStatusMsgs.front() = statusMiddleNew;
-
- m_panelStatusBar->Layout();
-}
-
-
-void MainDialog::setStatusBarFullText(const wxString& msg)
-{
- const bool needLayoutUpdate = !m_staticTextFullStatus->IsShown();
- //select state
- bSizerFileStatus->Show(false);
- m_staticTextFullStatus->Show();
-
- //update status information
- setText(*m_staticTextFullStatus, msg);
- m_panelStatusBar->Layout();
-
- if (needLayoutUpdate)
- auiMgr.Update(); //fix status bar height (needed on OS X)
-}
-
-
-void MainDialog::flashStatusInformation(const wxString& text)
-{
- oldStatusMsgs.push_back(m_staticTextStatusMiddle->GetLabel());
-
- m_staticTextStatusMiddle->SetLabel(text);
- m_staticTextStatusMiddle->SetForegroundColour(wxColour(31, 57, 226)); //highlight color: blue
- m_staticTextStatusMiddle->SetFont(m_staticTextStatusMiddle->GetFont().Bold());
-
- m_panelStatusBar->Layout();
- //if (needLayoutUpdate) auiMgr.Update(); -> not needed here, this is called anyway in updateGui()
-
- processAsync2([] { boost::this_thread::sleep(boost::posix_time::millisec(2500)); },
- [this] { this->restoreStatusInformation(); });
-}
-
-
-void MainDialog::restoreStatusInformation()
-{
- if (!oldStatusMsgs.empty())
- {
- wxString oldMsg = oldStatusMsgs.back();
- oldStatusMsgs.pop_back();
-
- if (oldStatusMsgs.empty()) //restore original status text
- {
- m_staticTextStatusMiddle->SetLabel(oldMsg);
- m_staticTextStatusMiddle->SetForegroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //reset color
-
- wxFont fnt = m_staticTextStatusMiddle->GetFont();
- fnt.SetWeight(wxFONTWEIGHT_NORMAL);
- m_staticTextStatusMiddle->SetFont(fnt);
-
- m_panelStatusBar->Layout();
- }
- }
-}
-
-
-void MainDialog::onProcessAsyncTasks(wxEvent& event)
-{
- //schedule and run long-running tasks asynchronously
- asyncTasks.evalResults(); //process results on GUI queue
- if (asyncTasks.empty())
- timerForAsyncTasks.Stop();
-}
-
-
-void MainDialog::disableAllElements(bool enableAbort)
-{
- //disables all elements (except abort button) that might receive user input during long-running processes:
- //when changing consider: comparison, synchronization, manual deletion
-
- EnableCloseButton(false); //not allowed for synchronization! progress indicator is top window! -> not honored on OS X!
-
- //OS X: wxWidgets portability promise is again a mess: http://wxwidgets.10942.n7.nabble.com/Disable-panel-and-appropriate-children-windows-linux-macos-td35357.html
-
- m_menubar1->EnableTop(0, false);
- m_menubar1->EnableTop(1, false);
- m_menubar1->EnableTop(2, false);
- m_bpButtonCmpConfig ->Disable();
- m_bpButtonSyncConfig ->Disable();
- m_buttonSync ->Disable();
- m_panelDirectoryPairs->Disable();
- m_splitterMain ->Disable(); //includes m_panelCenter, but not m_panelStatusBar!
- m_panelViewFilter ->Disable();
- m_panelFilter ->Disable();
- m_panelConfig ->Disable();
- m_panelStatistics ->Disable();
- m_gridNavi ->Disable();
-
- if (enableAbort)
- {
- //show abort button
- m_buttonCancel->Enable();
- m_buttonCancel->Show();
- if (m_buttonCancel->IsShownOnScreen())
- m_buttonCancel->SetFocus();
- m_buttonCompare->Disable();
- m_buttonCompare->Hide();
- m_panelTopButtons->Layout();
- }
- else
- m_panelTopButtons->Disable();
-}
-
-
-void MainDialog::enableAllElements()
-{
- //wxGTK, yet another QOI issue: some stupid bug, keeps moving main dialog to top!!
-
- EnableCloseButton(true);
-
- m_menubar1->EnableTop(0, true);
- m_menubar1->EnableTop(1, true);
- m_menubar1->EnableTop(2, true);
- m_bpButtonCmpConfig ->Enable();
- m_bpButtonSyncConfig ->Enable();
- m_buttonSync ->Enable();
- m_panelDirectoryPairs->Enable();
- m_splitterMain ->Enable();
- m_panelViewFilter ->Enable();
- m_panelFilter ->Enable();
- m_panelConfig ->Enable();
- m_panelStatistics ->Enable();
- m_gridNavi ->Enable();
-
- //show compare button
- m_buttonCancel->Disable();
- m_buttonCancel->Hide();
- m_buttonCompare->Enable();
- m_buttonCompare->Show();
-
- m_panelTopButtons->Enable();
- m_panelTopButtons->Layout();
-
- //at least wxWidgets on OS X fails to do this after enabling:
- Refresh();
-}
-
-
-namespace
-{
-void updateSizerOrientation(wxBoxSizer& sizer, wxWindow& window, double horizontalWeight)
-{
- const int newOrientation = window.GetSize().GetWidth() * horizontalWeight > window.GetSize().GetHeight() ? wxHORIZONTAL : wxVERTICAL; //check window NOT sizer width!
- if (sizer.GetOrientation() != newOrientation)
- {
- sizer.SetOrientation(newOrientation);
- window.Layout();
- }
-}
-}
-
-
-void MainDialog::OnResizeConfigPanel(wxEvent& event)
-{
- updateSizerOrientation(*bSizerConfig, *m_panelConfig, 0.5);
- event.Skip();
-}
-
-
-void MainDialog::OnResizeViewPanel(wxEvent& event)
-{
- updateSizerOrientation(*bSizerViewFilter, *m_panelViewFilter, 1.0);
- event.Skip();
-}
-
-
-void MainDialog::OnResizeStatisticsPanel(wxEvent& event)
-{
- //updateSizerOrientation(*bSizerStatistics, *m_panelStatistics);
-
- //we need something more fancy:
- const int parentOrient = m_panelStatistics->GetSize().GetWidth() > m_panelStatistics->GetSize().GetHeight() ? wxHORIZONTAL : wxVERTICAL; //check window NOT sizer width!
- if (bSizerStatistics->GetOrientation() != parentOrient)
- {
- bSizerStatistics->SetOrientation(parentOrient);
-
- //apply opposite orientation for child sizers
- const int childOrient = parentOrient == wxHORIZONTAL ? wxVERTICAL : wxHORIZONTAL;
- wxSizerItemList& sl = bSizerStatistics->GetChildren();
- for (auto it = sl.begin(); it != sl.end(); ++it) //yet another wxWidgets bug keeps us from using std::for_each
- {
- wxSizerItem& szItem = **it;
- if (auto sizerChild = dynamic_cast<wxBoxSizer*>(szItem.GetSizer()))
- if (sizerChild->GetOrientation() != childOrient)
- sizerChild->SetOrientation(childOrient);
- }
- m_panelStatistics->Layout();
- }
-
- event.Skip();
-}
-
-
-void MainDialog::OnResizeLeftFolderWidth(wxEvent& event)
-{
- //adapt left-shift display distortion caused by scrollbars for multiple folder pairs
- const int width = m_panelTopLeft->GetSize().GetWidth();
- std::for_each(additionalFolderPairs.begin(), additionalFolderPairs.end(),
- [&](FolderPairPanel* panel)
- {
- panel->m_panelLeft->SetMinSize(wxSize(width, -1));
- });
-
- event.Skip();
-}
-
-
-void MainDialog::onTreeButtonEvent(wxKeyEvent& event)
-{
- int keyCode = event.GetKeyCode();
- if (m_gridNavi->GetLayoutDirection() == wxLayout_RightToLeft)
- {
- if (keyCode == WXK_LEFT)
- keyCode = WXK_RIGHT;
- else if (keyCode == WXK_RIGHT)
- keyCode = WXK_LEFT;
- else if (keyCode == WXK_NUMPAD_LEFT)
- keyCode = WXK_NUMPAD_RIGHT;
- else if (keyCode == WXK_NUMPAD_RIGHT)
- keyCode = WXK_NUMPAD_LEFT;
- }
-
- if (event.ControlDown())
- switch (keyCode)
- {
- case 'C':
- case WXK_INSERT: //CTRL + C || CTRL + INS
- {
- std::vector<const Grid*> gridRefs;
- gridRefs.push_back(m_gridNavi);
- copySelectionToClipboard(gridRefs);
- }
- return;
- }
- else if (event.AltDown())
- switch (keyCode)
- {
- case WXK_NUMPAD_LEFT:
- case WXK_LEFT: //ALT + <-
- setSyncDirManually(getTreeSelection(), SyncDirection::LEFT);
- return;
-
- case WXK_NUMPAD_RIGHT:
- case WXK_RIGHT: //ALT + ->
- setSyncDirManually(getTreeSelection(), SyncDirection::RIGHT);
- return;
-
- case WXK_NUMPAD_UP:
- case WXK_NUMPAD_DOWN:
- case WXK_UP: /* ALT + /|\ */
- case WXK_DOWN: /* ALT + \|/ */
- setSyncDirManually(getTreeSelection(), SyncDirection::NONE);
- return;
- }
-
- else
- switch (keyCode)
- {
- case WXK_SPACE:
- case WXK_NUMPAD_SPACE:
- {
- const std::vector<FileSystemObject*>& selection = getTreeSelection();
- if (!selection.empty())
- setFilterManually(selection, !selection[0]->isActive());
- }
- return;
-
- case WXK_DELETE:
- case WXK_NUMPAD_DELETE:
- deleteSelectedFiles(getTreeSelection(), getTreeSelection());
- return;
- }
-
- event.Skip(); //unknown keypress: propagate
-}
-
-
-void MainDialog::onGridButtonEventL(wxKeyEvent& event)
-{
- onGridButtonEvent(event, *m_gridMainL, true);
-}
-void MainDialog::onGridButtonEventC(wxKeyEvent& event)
-{
- onGridButtonEvent(event, *m_gridMainC, true);
-}
-void MainDialog::onGridButtonEventR(wxKeyEvent& event)
-{
- onGridButtonEvent(event, *m_gridMainR, false);
-}
-
-void MainDialog::onGridButtonEvent(wxKeyEvent& event, Grid& grid, bool leftSide)
-{
- int keyCode = event.GetKeyCode();
- if (grid.GetLayoutDirection() == wxLayout_RightToLeft)
- {
- if (keyCode == WXK_LEFT)
- keyCode = WXK_RIGHT;
- else if (keyCode == WXK_RIGHT)
- keyCode = WXK_LEFT;
- else if (keyCode == WXK_NUMPAD_LEFT)
- keyCode = WXK_NUMPAD_RIGHT;
- else if (keyCode == WXK_NUMPAD_RIGHT)
- keyCode = WXK_NUMPAD_LEFT;
- }
-
- if (event.ControlDown())
- switch (keyCode)
- {
- case 'C':
- case WXK_INSERT: //CTRL + C || CTRL + INS
- {
- std::vector<const Grid*> gridRefs;
- gridRefs.push_back(m_gridMainL);
- gridRefs.push_back(m_gridMainR);
- copySelectionToClipboard(gridRefs);
- }
- return; // -> swallow event! don't allow default grid commands!
- }
-
- else if (event.AltDown())
- switch (keyCode)
- {
- case WXK_NUMPAD_LEFT:
- case WXK_LEFT: //ALT + <-
- setSyncDirManually(getGridSelection(), SyncDirection::LEFT);
- return;
-
- case WXK_NUMPAD_RIGHT:
- case WXK_RIGHT: //ALT + ->
- setSyncDirManually(getGridSelection(), SyncDirection::RIGHT);
- return;
-
- case WXK_NUMPAD_UP:
- case WXK_NUMPAD_DOWN:
- case WXK_UP: /* ALT + /|\ */
- case WXK_DOWN: /* ALT + \|/ */
- setSyncDirManually(getGridSelection(), SyncDirection::NONE);
- return;
- }
-
- else
- switch (keyCode)
- {
- case WXK_DELETE:
- case WXK_NUMPAD_DELETE:
- deleteSelectedFiles(getGridSelection(true, false),
- getGridSelection(false, true));
- return;
-
- case WXK_SPACE:
- case WXK_NUMPAD_SPACE:
- {
- const std::vector<FileSystemObject*>& selection = getGridSelection();
- if (!selection.empty())
- setFilterManually(selection, !selection[0]->isActive());
- }
- return;
-
- case WXK_RETURN:
- case WXK_NUMPAD_ENTER:
- if (!globalCfg.gui.externelApplications.empty())
- openExternalApplication(globalCfg.gui.externelApplications[0].second, //open with first external application
- getGridSelection(), leftSide);
- return;
- }
-
- event.Skip(); //unknown keypress: propagate
-}
-
-
-bool isComponentOf(const wxWindow* child, const wxWindow* top)
-{
- for (const wxWindow* wnd = child; wnd != nullptr; wnd = wnd->GetParent())
- if (wnd == top)
- return true;
- return false;
-}
-
-
-void MainDialog::OnGlobalKeyEvent(wxKeyEvent& event) //process key events without explicit menu entry :)
-{
- //avoid recursion!!! -> this ugly construct seems to be the only (portable) way to avoid re-entrancy
- //recursion may happen in multiple situations: e.g. modal dialogs, wxGrid::ProcessEvent()!
- if (processingGlobalKeyEvent ||
- !IsShown() ||
- !IsActive() ||
- !IsEnabled() ||
- !m_gridMainL->IsEnabled() || //
- !m_gridMainC->IsEnabled() || //only handle if main window is in use
- !m_gridMainR->IsEnabled()) //
- {
- event.Skip();
- return;
- }
-
- processingGlobalKeyEvent = true;
- ZEN_ON_SCOPE_EXIT(processingGlobalKeyEvent = false;)
- //----------------------------------------------------
-
- const int keyCode = event.GetKeyCode();
-
- //CTRL + X
- //if (event.ControlDown())
- // switch (keyCode)
- // {
- // case 'F': //CTRL + F
- // showFindPanel();
- // return; //-> swallow event!
- // }
-
- switch (keyCode)
- {
- case WXK_F3:
- case WXK_NUMPAD_F3:
- startFindNext();
- return; //-> swallow event!
-
- case WXK_F6:
- {
- wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED); //simulate button click
- if (wxEvtHandler* evtHandler = m_bpButtonCmpConfig->GetEventHandler())
- evtHandler->ProcessEvent(dummy2); //synchronous call
- }
- return; //-> swallow event!
-
- case WXK_F7:
- {
- wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED); //simulate button click
- if (wxEvtHandler* evtHandler = m_bpButtonSyncConfig->GetEventHandler())
- evtHandler->ProcessEvent(dummy2); //synchronous call
- }
- return; //-> swallow event!
-
- case WXK_F9:
- setViewTypeSyncAction(!m_bpButtonViewTypeSyncAction->isActive());
- return; //-> swallow event!
-
- case WXK_F10:
- {
- wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED); //simulate button click
- if (wxEvtHandler* evtHandler = m_bpButtonFilter->GetEventHandler())
- evtHandler->ProcessEvent(dummy2); //synchronous call
- }
- return; //-> swallow event!
-
- //redirect certain (unhandled) keys directly to grid!
- case WXK_UP:
- case WXK_DOWN:
- case WXK_LEFT:
- case WXK_RIGHT:
- case WXK_NUMPAD_UP:
- case WXK_NUMPAD_DOWN:
- case WXK_NUMPAD_LEFT:
- case WXK_NUMPAD_RIGHT:
-
- case WXK_PAGEUP:
- case WXK_PAGEDOWN:
- case WXK_HOME:
- case WXK_END:
- case WXK_NUMPAD_PAGEUP:
- case WXK_NUMPAD_PAGEDOWN:
- case WXK_NUMPAD_HOME:
- case WXK_NUMPAD_END:
- {
- const wxWindow* focus = wxWindow::FindFocus();
- if (!isComponentOf(focus, m_gridMainL ) && //
- !isComponentOf(focus, m_gridMainC ) && //don't propagate keyboard commands if grid is already in focus
- !isComponentOf(focus, m_gridMainR ) && //
- !isComponentOf(focus, m_gridNavi ) &&
- !isComponentOf(focus, m_listBoxHistory) && //don't propagate if selecting config
- !isComponentOf(focus, m_directoryLeft ) && //don't propagate if changing directory field
- !isComponentOf(focus, m_directoryRight) &&
- !isComponentOf(focus, m_panelSearch ) &&
- !isComponentOf(focus, m_scrolledWindowFolderPairs))
- if (wxEvtHandler* evtHandler = m_gridMainL->getMainWin().GetEventHandler())
- {
- m_gridMainL->SetFocus();
-
- event.SetEventType(wxEVT_KEY_DOWN); //the grid event handler doesn't expect wxEVT_CHAR_HOOK!
- evtHandler->ProcessEvent(event); //propagating event catched at wxTheApp to child leads to recursion, but we prevented it...
- event.Skip(false); //definitively handled now!
- return;
- }
- }
- break;
- }
-
- event.Skip();
-}
-
-
-void MainDialog::onNaviSelection(GridRangeSelectEvent& event)
-{
- //scroll m_gridMain to user's new selection on m_gridNavi
- ptrdiff_t leadRow = -1;
- if (event.rowFirst_ != event.rowLast_)
- if (std::unique_ptr<TreeView::Node> node = treeDataView->getLine(event.rowFirst_))
- {
- if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get()))
- leadRow = gridDataView->findRowFirstChild(&(root->baseDirObj_));
- else if (const TreeView::DirNode* dir = dynamic_cast<const TreeView::DirNode*>(node.get()))
- {
- leadRow = gridDataView->findRowDirect(&(dir->dirObj_));
- if (leadRow < 0) //directory was filtered out! still on tree view (but NOT on grid view)
- leadRow = gridDataView->findRowFirstChild(&(dir->dirObj_));
- }
- else if (const TreeView::FilesNode* files = dynamic_cast<const TreeView::FilesNode*>(node.get()))
- {
- assert(!files->filesAndLinks_.empty());
- if (!files->filesAndLinks_.empty())
- leadRow = gridDataView->findRowDirect(files->filesAndLinks_[0]->getId());
- }
- }
-
- if (leadRow >= 0)
- {
- leadRow = std::max<ptrdiff_t>(0, leadRow - 1); //scroll one more row
-
- m_gridMainL->scrollTo(leadRow); //scroll all of them (includes the "scroll master")
- m_gridMainC->scrollTo(leadRow); //
- m_gridMainR->scrollTo(leadRow); //
-
- m_gridNavi->getMainWin().Update(); //draw cursor immediately rather than on next idle event (required for slow CPUs, netbook)
- }
-
- //get selection on navigation tree and set corresponding markers on main grid
- hash_set<const FileSystemObject*> markedFilesAndLinks; //mark files/symlinks directly
- hash_set<const HierarchyObject*> markedContainer; //mark full container including child-objects
-
- const std::vector<size_t>& selection = m_gridNavi->getSelectedRows();
- std::for_each(selection.begin(), selection.end(),
- [&](size_t row)
- {
- if (std::unique_ptr<TreeView::Node> node = treeDataView->getLine(row))
- {
- if (const TreeView::RootNode* root = dynamic_cast<const TreeView::RootNode*>(node.get()))
- markedContainer.insert(&(root->baseDirObj_));
- else if (const TreeView::DirNode* dir = dynamic_cast<const TreeView::DirNode*>(node.get()))
- markedContainer.insert(&(dir->dirObj_));
- else if (const TreeView::FilesNode* files = dynamic_cast<const TreeView::FilesNode*>(node.get()))
- markedFilesAndLinks.insert(files->filesAndLinks_.begin(), files->filesAndLinks_.end());
- }
- });
-
- gridview::setNavigationMarker(*m_gridMainL, std::move(markedFilesAndLinks), std::move(markedContainer));
-
- event.Skip();
-}
-
-
-void MainDialog::onNaviGridContext(GridClickEvent& event)
-{
- const auto& selection = getTreeSelection(); //referenced by lambdas!
- ContextMenu menu;
-
- //----------------------------------------------------------------------------------------------------
- if (!selection.empty())
- //std::any_of(selection.begin(), selection.end(), [](const FileSystemObject* fsObj){ return fsObj->getSyncOperation() != SO_EQUAL; })) -> doesn't consider directories
- {
- auto getImage = [&](SyncDirection dir, SyncOperation soDefault)
- {
- return mirrorIfRtl(getSyncOpImage(selection[0]->getSyncOperation() != SO_EQUAL ?
- selection[0]->testSyncOperation(dir) : soDefault));
- };
- const wxBitmap opRight = getImage(SyncDirection::RIGHT, SO_OVERWRITE_RIGHT);
- const wxBitmap opNone = getImage(SyncDirection::NONE, SO_DO_NOTHING );
- const wxBitmap opLeft = getImage(SyncDirection::LEFT, SO_OVERWRITE_LEFT );
-
- wxString shortCutLeft = L"\tAlt+Left";
- wxString shortCutRight = L"\tAlt+Right";
- if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft)
- std::swap(shortCutLeft, shortCutRight);
-
- menu.addItem(_("Set direction:") + L" ->" + shortCutRight, [this, &selection] { setSyncDirManually(selection, SyncDirection::RIGHT); }, &opRight);
- menu.addItem(_("Set direction:") + L" -" L"\tAlt+Down", [this, &selection] { setSyncDirManually(selection, SyncDirection::NONE); }, &opNone);
- menu.addItem(_("Set direction:") + L" <-" + shortCutLeft, [this, &selection] { setSyncDirManually(selection, SyncDirection::LEFT); }, &opLeft);
- //Gtk needs a direction, "<-", because it has no context menu icons!
- //Gtk requires "no spaces" for shortcut identifiers!
- menu.addSeparator();
- }
-
- //----------------------------------------------------------------------------------------------------
- //FILE FILTER
- auto addFilterMenu = [&](const std::wstring& label, const wxString& iconName, bool include)
- {
- if (selection.size() == 1)
- {
- ContextMenu submenu;
-
- const bool isDir = dynamic_cast<const DirPair*>(selection[0]) != nullptr;
-
- //by short name
- Zstring labelShort = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + selection[0]->getObjShortName();
- if (isDir)
- labelShort += Zstring(FILE_NAME_SEPARATOR) + Zstr("*");
- submenu.addItem(utfCvrtTo<wxString>(labelShort), [this, &selection, include] { filterShortname(*selection[0], include); });
-
- //by relative path
- Zstring labelRel = FILE_NAME_SEPARATOR + selection[0]->getObjRelativeName();
- if (isDir)
- labelRel += Zstring(FILE_NAME_SEPARATOR) + Zstr("*");
- submenu.addItem(utfCvrtTo<wxString>(labelRel), [this, &selection, include] { filterItems(selection, include); });
-
- menu.addSubmenu(label, submenu, &getResourceImage(iconName));
- }
- else if (selection.size() > 1)
- {
- //by relative path
- menu.addItem(label + L" <" + _("multiple selection") + L">",
- [this, &selection, include] { filterItems(selection, include); }, &getResourceImage(iconName));
- }
- };
- addFilterMenu(_("Include via filter:"), L"filter_include_small", true);
- addFilterMenu(_("Exclude via filter:"), L"filter_exclude_small", false);
-
- //----------------------------------------------------------------------------------------------------
- if (!selection.empty())
- {
- if (selection[0]->isActive())
- menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, false); }, &getResourceImage(L"checkboxFalse"));
- else
- menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, true); }, &getResourceImage(L"checkboxTrue"));
- }
- else
- menu.addItem(_("Exclude temporarily") + L"\tSpace", [] {}, nullptr, false);
-
- //----------------------------------------------------------------------------------------------------
- //CONTEXT_DELETE_FILES
- menu.addSeparator();
-
- menu.addItem(_("Delete") + L"\tDel", [&] { deleteSelectedFiles(selection, selection); }, nullptr, !selection.empty());
-
- menu.popup(*this);
-}
-
-
-void MainDialog::onMainGridContextC(GridClickEvent& event)
-{
- ContextMenu menu;
-
- menu.addItem(_("Include all"), [&]
- {
- zen::setActiveStatus(true, folderCmp);
- updateGui();
- }, nullptr, gridDataView->rowsTotal() > 0);
-
- menu.addItem(_("Exclude all"), [&]
- {
- zen::setActiveStatus(false, folderCmp);
- updateGuiDelayedIf(currentCfg.hideExcludedItems); //show update GUI before removing rows
- }, nullptr, gridDataView->rowsTotal() > 0);
-
- menu.popup(*this);
-}
-
-void MainDialog::onMainGridContextL(GridClickEvent& event)
-{
- onMainGridContextRim(true);
-}
-
-void MainDialog::onMainGridContextR(GridClickEvent& event)
-{
- onMainGridContextRim(false);
-}
-
-void MainDialog::onMainGridContextRim(bool leftSide)
-{
- const auto& selection = getGridSelection(); //referenced by lambdas!
- ContextMenu menu;
-
- if (!selection.empty())
- {
- auto getImage = [&](SyncDirection dir, SyncOperation soDefault)
- {
- return mirrorIfRtl(getSyncOpImage(selection[0]->getSyncOperation() != SO_EQUAL ?
- selection[0]->testSyncOperation(dir) : soDefault));
- };
- const wxBitmap opRight = getImage(SyncDirection::RIGHT, SO_OVERWRITE_RIGHT);
- const wxBitmap opNone = getImage(SyncDirection::NONE, SO_DO_NOTHING );
- const wxBitmap opLeft = getImage(SyncDirection::LEFT, SO_OVERWRITE_LEFT );
-
- wxString shortCutLeft = L"\tAlt+Left";
- wxString shortCutRight = L"\tAlt+Right";
- if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft)
- std::swap(shortCutLeft, shortCutRight);
-
- menu.addItem(_("Set direction:") + L" ->" + shortCutRight, [this, &selection] { setSyncDirManually(selection, SyncDirection::RIGHT); }, &opRight);
- menu.addItem(_("Set direction:") + L" -" L"\tAlt+Down", [this, &selection] { setSyncDirManually(selection, SyncDirection::NONE); }, &opNone);
- menu.addItem(_("Set direction:") + L" <-" + shortCutLeft, [this, &selection] { setSyncDirManually(selection, SyncDirection::LEFT); }, &opLeft);
- //Gtk needs a direction, "<-", because it has no context menu icons!
- //Gtk requires "no spaces" for shortcut identifiers!
- menu.addSeparator();
- }
-
- //----------------------------------------------------------------------------------------------------
- //FILE FILTER
- auto addFilterMenu = [&](const wxString& label, const wxString& iconName, bool include)
- {
- if (selection.size() == 1)
- {
- ContextMenu submenu;
-
- const bool isDir = dynamic_cast<const DirPair*>(selection[0]) != nullptr;
-
- //by extension
- if (!isDir)
- {
- const Zstring filename = afterLast(selection[0]->getObjRelativeName(), FILE_NAME_SEPARATOR);
- if (contains(filename, Zchar('.'))) //be careful: afterLast returns the whole string if '.' is not found!
- {
- const Zstring extension = afterLast(filename, Zchar('.'));
- submenu.addItem(L"*." + utfCvrtTo<wxString>(extension),
- [this, extension, include] { filterExtension(extension, include); });
- }
- }
-
- //by short name
- Zstring labelShort = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + selection[0]->getObjShortName();
- if (isDir)
- labelShort += Zstring(FILE_NAME_SEPARATOR) + Zstr("*");
- submenu.addItem(utfCvrtTo<wxString>(labelShort), [this, &selection, include] { filterShortname(*selection[0], include); });
-
- //by relative path
- Zstring labelRel = FILE_NAME_SEPARATOR + selection[0]->getObjRelativeName();
- if (isDir)
- labelRel += Zstring(FILE_NAME_SEPARATOR) + Zstr("*");
- submenu.addItem(utfCvrtTo<wxString>(labelRel), [this, &selection, include] { filterItems(selection, include); });
-
- menu.addSubmenu(label, submenu, &getResourceImage(iconName));
- }
- else if (selection.size() > 1)
- {
- //by relative path
- menu.addItem(label + L" <" + _("multiple selection") + L">",
- [this, &selection, include] { filterItems(selection, include); }, &getResourceImage(iconName));
- }
- };
- addFilterMenu(_("Include via filter:"), L"filter_include_small", true);
- addFilterMenu(_("Exclude via filter:"), L"filter_exclude_small", false);
-
- //----------------------------------------------------------------------------------------------------
- if (!selection.empty())
- {
- if (selection[0]->isActive())
- menu.addItem(_("Exclude temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, false); }, &getResourceImage(L"checkboxFalse"));
- else
- menu.addItem(_("Include temporarily") + L"\tSpace", [this, &selection] { setFilterManually(selection, true); }, &getResourceImage(L"checkboxTrue"));
- }
- else
- menu.addItem(_("Exclude temporarily") + L"\tSpace", [] {}, nullptr, false);
-
- //----------------------------------------------------------------------------------------------------
- //CONTEXT_EXTERNAL_APP
- if (!globalCfg.gui.externelApplications.empty())
- {
- menu.addSeparator();
-
- for (auto it = globalCfg.gui.externelApplications.begin();
- it != globalCfg.gui.externelApplications.end();
- ++it)
- {
- //translate default external apps on the fly: 1. "open in explorer" 2. "start directly"
- wxString description = zen::implementation::translate(it->first);
- if (description.empty())
- description = L" "; //wxWidgets doesn't like empty items
-
- const wxString command = it->second; //COPY into lambda
-
- auto openApp = [this, &selection, leftSide, command] { openExternalApplication(command, selection, leftSide); };
-
- if (it == globalCfg.gui.externelApplications.begin())
- description += L"\tEnter";
-
- menu.addItem(description, openApp, nullptr, !selection.empty());
- }
- }
- //----------------------------------------------------------------------------------------------------
- //CONTEXT_DELETE_FILES
- menu.addSeparator();
-
- menu.addItem(_("Delete") + L"\tDel", [this]
- {
- deleteSelectedFiles(
- getGridSelection(true, false),
- getGridSelection(false, true));
- }, nullptr, !selection.empty());
-
- menu.popup(*this);
-}
-
-
-
-void MainDialog::filterPhrase(const Zstring& phrase, bool include, bool addNewLine)
-{
- Zstring& filterString = [&]() -> Zstring&
- {
- if (include)
- {
- Zstring& includeFilter = currentCfg.mainCfg.globalFilter.includeFilter;
- if (NameFilter::isNull(includeFilter, FilterConfig().excludeFilter)) //fancy way of checking for "*" include
- includeFilter.clear();
- return includeFilter;
- }
- else
- return currentCfg.mainCfg.globalFilter.excludeFilter;
- }();
-
- if (addNewLine)
- {
- if (!filterString.empty() && !endsWith(filterString, Zstr("\n")))
- filterString += Zstr("\n");
- filterString += phrase;
- }
- else
- {
- if (!filterString.empty() && !endsWith(filterString, Zstr("\n")) && !endsWith(filterString, Zstr(";")))
- filterString += Zstr("\n");
- filterString += phrase + Zstr(";"); //';' is appended to 'mark' that next exclude extension entry won't write to new line
- }
-
- updateGlobalFilterButton();
- if (include)
- applyFilterConfig(); //user's temporary exclusions lost!
- else //do not fully apply filter, just exclude new items: preserve user's temporary exclusions
- {
- std::for_each(begin(folderCmp), end(folderCmp), [&](BaseDirPair& baseDirObj) { addHardFiltering(baseDirObj, phrase); });
- updateGui();
- }
-}
-
-
-void MainDialog::filterExtension(const Zstring& extension, bool include)
-{
- assert(!extension.empty());
- filterPhrase(Zstr("*.") + extension, include, false);
-}
-
-
-void MainDialog::filterShortname(const FileSystemObject& fsObj, bool include)
-{
- Zstring phrase = Zstring(Zstr("*")) + FILE_NAME_SEPARATOR + fsObj.getObjShortName();
- const bool isDir = dynamic_cast<const DirPair*>(&fsObj) != nullptr;
- if (isDir)
- phrase += Zstring(FILE_NAME_SEPARATOR) + Zstr("*"); //include filter: * required; exclude filter: * optional, but let's still apply it!
-
- filterPhrase(phrase, include, true);
-}
-
-
-void MainDialog::filterItems(const std::vector<FileSystemObject*>& selection, bool include)
-{
- if (!selection.empty())
- {
- Zstring phrase;
- for (auto it = selection.begin(); it != selection.end(); ++it)
- {
- FileSystemObject* fsObj = *it;
-
- if (it != selection.begin())
- phrase += Zstr("\n");
-
- //#pragma warning(suppress: 6011) -> fsObj bound in this context!
- phrase += FILE_NAME_SEPARATOR + fsObj->getObjRelativeName();
-
- const bool isDir = dynamic_cast<const DirPair*>(fsObj) != nullptr;
- if (isDir)
- phrase += Zstring(FILE_NAME_SEPARATOR) + Zstr("*"); //include filter: * required; exclude filter: * optional, but let's still apply it!
- }
- filterPhrase(phrase, include, true);
- }
-}
-
-
-void MainDialog::onGridLabelContextC(GridClickEvent& event)
-{
- ContextMenu menu;
-
- const bool actionView = m_bpButtonViewTypeSyncAction->isActive();
- menu.addRadio(_("Category") + (actionView ? L"\tF9" : L""), [&] { setViewTypeSyncAction(false); }, !actionView);
- menu.addRadio(_("Action") + (!actionView ? L"\tF9" : L""), [&] { setViewTypeSyncAction(true ); }, actionView);
-
- //menu.addItem(_("Category") + L"\tF9", [&] { setViewTypeSyncAction(false); }, m_bpButtonViewTypeSyncAction->isActive() ? nullptr : &getResourceImage(L"compare_small"));
- //menu.addItem(_("Action"), [&] { setViewTypeSyncAction(true ); }, m_bpButtonViewTypeSyncAction->isActive() ? &getResourceImage(L"sync_small") : nullptr);
- menu.popup(*this);
-}
-
-
-void MainDialog::onGridLabelContextL(GridClickEvent& event)
-{
- onGridLabelContext(*m_gridMainL, static_cast<ColumnTypeRim>(event.colType_), getDefaultColumnAttributesLeft());
-}
-void MainDialog::onGridLabelContextR(GridClickEvent& event)
-{
- onGridLabelContext(*m_gridMainR, static_cast<ColumnTypeRim>(event.colType_), getDefaultColumnAttributesRight());
-}
-
-
-void MainDialog::onGridLabelContext(Grid& grid, ColumnTypeRim type, const std::vector<ColumnAttributeRim>& defaultColumnAttributes)
-{
- ContextMenu menu;
-
- auto toggleColumn = [&](ColumnType ct)
- {
- auto colAttr = grid.getColumnConfig();
-
- for (Grid::ColumnAttribute& ca : colAttr)
- if (ca.type_ == ct)
- {
- ca.visible_ = !ca.visible_;
- grid.setColumnConfig(colAttr);
- return;
- }
- };
-
- if (const GridData* prov = grid.getDataProvider())
- for (const Grid::ColumnAttribute& ca : grid.getColumnConfig())
- menu.addCheckBox(prov->getColumnLabel(ca.type_), [ca, toggleColumn] { toggleColumn(ca.type_); },
- ca.visible_, ca.type_ != static_cast<ColumnType>(COL_TYPE_FILENAME)); //do not allow user to hide file name column!
- //----------------------------------------------------------------------------------------------
- menu.addSeparator();
-
- auto setDefault = [&]
- {
- grid.setColumnConfig(gridview::convertConfig(defaultColumnAttributes));
- };
- menu.addItem(_("&Default"), setDefault); //'&' -> reuse text from "default" buttons elsewhere
- //----------------------------------------------------------------------------------------------
- menu.addSeparator();
- menu.addCheckBox(_("Show icons:"), [&]
- {
- globalCfg.gui.showIcons = !globalCfg.gui.showIcons;
- gridview::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalCfg.gui.showIcons, convert(globalCfg.gui.iconSize));
-
- }, globalCfg.gui.showIcons);
-
- auto setIconSize = [&](xmlAccess::FileIconSize sz)
- {
- globalCfg.gui.iconSize = sz;
- gridview::setupIcons(*m_gridMainL, *m_gridMainC, *m_gridMainR, globalCfg.gui.showIcons, convert(sz));
- };
- auto addSizeEntry = [&](const wxString& label, xmlAccess::FileIconSize sz)
- {
- auto setIconSize2 = setIconSize; //bring into scope
- menu.addRadio(label, [sz, setIconSize2] { setIconSize2(sz); }, globalCfg.gui.iconSize == sz, globalCfg.gui.showIcons);
- };
- addSizeEntry(L" " + _("Small" ), xmlAccess::ICON_SIZE_SMALL );
- addSizeEntry(L" " + _("Medium"), xmlAccess::ICON_SIZE_MEDIUM);
- addSizeEntry(L" " + _("Large" ), xmlAccess::ICON_SIZE_LARGE );
- //----------------------------------------------------------------------------------------------
- if (type == COL_TYPE_DATE)
- {
- menu.addSeparator();
-
- auto selectTimeSpan = [&]
- {
- if (showSelectTimespanDlg(this, manualTimeSpanFrom, manualTimeSpanTo) == ReturnSmallDlg::BUTTON_OKAY)
- {
- applyTimeSpanFilter(folderCmp, manualTimeSpanFrom, manualTimeSpanTo); //overwrite current active/inactive settings
- //updateGuiDelayedIf(currentCfg.hideExcludedItems); //show update GUI before removing rows
- updateGui();
- }
- };
- menu.addItem(_("Select time span..."), selectTimeSpan);
- }
-
- menu.popup(*this);
-}
-
-
-void MainDialog::OnContextSetLayout(wxMouseEvent& event)
-{
- ContextMenu menu;
-
- menu.addItem(_("Default view"), [&]
- {
- m_splitterMain->setSashOffset(0);
- auiMgr.LoadPerspective(defaultPerspective);
- updateGuiForFolderPair();
- });
- //----------------------------------------------------------------------------------------
- std::vector<std::pair<wxString, wxString>> captionNameMap; //(caption, identifier)
-
- const wxAuiPaneInfoArray& paneArray = auiMgr.GetAllPanes();
- for (size_t i = 0; i < paneArray.size(); ++i)
- if (!paneArray[i].IsShown() && !paneArray[i].name.empty() &&
- paneArray[i].window != compareStatus->getAsWindow() &&
- paneArray[i].window != m_panelSearch)
- captionNameMap.push_back(std::make_pair(paneArray[i].caption, paneArray[i].name));
-
- if (!captionNameMap.empty())
- menu.addSeparator();
-
- auto showPanel = [&](const wxString& identifier)
- {
- auiMgr.GetPane(identifier).Show();
- auiMgr.Update();
- };
-
- for (auto it = captionNameMap.begin(); it != captionNameMap.end(); ++it)
- {
- const wxString label = replaceCpy(_("Show \"%x\""), L"%x", it->first);
- const wxString identifier = it->second;
-
- menu.addItem(label, [showPanel, identifier] { showPanel(identifier); });
- }
-
- menu.popup(*this);
-}
-
-
-void MainDialog::OnCompSettingsContext(wxMouseEvent& event)
-{
- ContextMenu menu;
-
- auto setVariant = [&](CompareVariant var)
- {
- currentCfg.mainCfg.cmpConfig.compareVar = var;
- applyCompareConfig(true); //true: switchMiddleGrid
- };
-
- auto currentVar = getConfig().mainCfg.cmpConfig.compareVar;
-
- menu.addRadio(_("File time and size"), [&] { setVariant(CMP_BY_TIME_SIZE); }, currentVar == CMP_BY_TIME_SIZE);
- menu.addRadio(_("File content" ), [&] { setVariant(CMP_BY_CONTENT); }, currentVar == CMP_BY_CONTENT);
-
- menu.popup(*this);
-}
-
-
-void MainDialog::OnSyncSettingsContext(wxMouseEvent& event)
-{
- ContextMenu menu;
-
- auto setVariant = [&](DirectionConfig::Variant var)
- {
- currentCfg.mainCfg.syncCfg.directionCfg.var = var;
- applySyncConfig();
- };
-
- const auto currentVar = getConfig().mainCfg.syncCfg.directionCfg.var;
-
- menu.addRadio(L"<- " + _("Two way") + L" ->" , [&] { setVariant(DirectionConfig::TWOWAY); }, currentVar == DirectionConfig::TWOWAY);
- menu.addRadio( _("Mirror") + L" ->>", [&] { setVariant(DirectionConfig::MIRROR); }, currentVar == DirectionConfig::MIRROR);
- menu.addRadio( _("Update") + L" ->" , [&] { setVariant(DirectionConfig::UPDATE); }, currentVar == DirectionConfig::UPDATE);
- menu.addRadio( _("Custom") , [&] { setVariant(DirectionConfig::CUSTOM); }, currentVar == DirectionConfig::CUSTOM);
-
- menu.popup(*this);
-}
-
-
-void MainDialog::onNaviPanelFilesDropped(FileDropEvent& event)
-{
- loadConfiguration(toZ(event.getFiles()));
- event.Skip();
-}
-
-
-void MainDialog::onDirSelected(wxCommandEvent& event)
-{
- //left and right directory text-control and dirpicker are synchronized by MainFolderDragDrop automatically
- clearGrid(); //disable the sync button
- event.Skip();
-}
-
-
-void MainDialog::onDirManualCorrection(wxCommandEvent& event)
-{
- updateUnsavedCfgStatus();
- event.Skip();
-}
-
-
-wxString getFormattedHistoryElement(const Zstring& filename)
-{
- Zstring output = afterLast(filename, FILE_NAME_SEPARATOR);
- if (endsWith(output, Zstr(".ffs_gui")))
- output = beforeLast(output, Zstr('.'));
- return utfCvrtTo<wxString>(output);
-}
-
-
-void MainDialog::addFileToCfgHistory(const std::vector<Zstring>& filenames)
-{
- //determine highest "last use" index number of m_listBoxHistory
- int lastUseIndexMax = 0;
- for (unsigned int i = 0; i < m_listBoxHistory->GetCount(); ++i)
- if (auto histData = dynamic_cast<const wxClientHistoryData*>(m_listBoxHistory->GetClientObject(i)))
- if (histData->lastUseIndex_ > lastUseIndexMax)
- lastUseIndexMax = histData->lastUseIndex_;
-
- std::deque<bool> selections(m_listBoxHistory->GetCount()); //items to select after update of history list
-
- for (auto it = filenames.begin(); it != filenames.end(); ++it)
- {
- const Zstring& filename = *it;
-
- //Do we need to additionally check for aliases of the same physical files here? (and aliases for lastRunConfigName?)
-
- const auto itemPos = [&]() -> std::pair<wxClientHistoryData*, unsigned int>
- {
- for (unsigned int i = 0; i < m_listBoxHistory->GetCount(); ++i)
- if (auto histData = dynamic_cast<wxClientHistoryData*>(m_listBoxHistory->GetClientObject(i)))
- {
- if (EqualFilename()(filename, histData->cfgFile_))
- return std::make_pair(histData, i);
- }
- else
- assert(false);
- return std::make_pair(nullptr, 0);
- }();
-
- if (itemPos.first) //update
- {
- itemPos.first->lastUseIndex_ = ++lastUseIndexMax;
- selections[itemPos.second] = true;
- }
- else //insert
- {
- wxString label;
- unsigned int newPos = 0;
-
- if (EqualFilename()(filename, lastRunConfigName()))
- label = L"<" + _("Last session") + L">";
- else
- {
- //workaround wxWidgets 2.9 bug on GTK screwing up the client data if the list box is sorted:
- label = getFormattedHistoryElement(filename);
- //"linear insertion sort":
- for (; newPos < m_listBoxHistory->GetCount(); ++newPos)
- if (label.CmpNoCase(m_listBoxHistory->GetString(newPos)) < 0)
- break;
- }
-
- assert(!m_listBoxHistory->IsSorted());
- m_listBoxHistory->Insert(label, newPos, new wxClientHistoryData(filename, ++lastUseIndexMax));
-
- selections.insert(selections.begin() + newPos, true);
- }
- }
-
- assert(selections.size() == m_listBoxHistory->GetCount());
-
- //do not apply selections immediately but only when needed!
- //this prevents problems with m_listBoxHistory losing keyboard selection focus if identical selection is redundantly reapplied
- for (int pos = 0; pos < static_cast<int>(selections.size()); ++pos)
- if (m_listBoxHistory->IsSelected(pos) != selections[pos])
- m_listBoxHistory->SetSelection(pos, selections[pos]);
-}
-
-
-void MainDialog::removeObsoleteCfgHistoryItems(const std::vector<Zstring>& filenames)
-{
- //don't use wxString: NOT thread-safe! (e.g. non-atomic ref-count)
-
- auto getMissingFilesAsync = [filenames]() -> std::vector<Zstring>
- {
- //boost::this_thread::sleep(boost::posix_time::millisec(5000));
-
- //check existence of all config files in parallel!
- std::list<boost::unique_future<bool>> fileEx;
-
- for (auto it = filenames.begin(); it != filenames.end(); ++it) //avoid VC11 compiler issues with std::for_each
- {
- const Zstring filename = *it; //don't reference iterator in lambda!
- fileEx.push_back(zen::async2<bool>([=] { return fileExists(filename); }));
- }
-
- //potentially slow network access => limit maximum wait time!
- wait_for_all_timed(fileEx.begin(), fileEx.end(), boost::posix_time::milliseconds(1000));
-
- std::vector<Zstring> missingFiles;
-
- auto itFut = fileEx.begin();
- for (auto it = filenames.begin(); it != filenames.end(); ++it, (void)++itFut) //void: prevent ADL from dragging in boost's ,-overload: "MSVC warning C4913: user defined binary operator ',' exists but no overload could convert all operands"
- if (itFut->is_ready() && !itFut->get()) //remove only files that are confirmed to be non-existent
- missingFiles.push_back(*it);
-
- return missingFiles;
- };
-
- processAsync(getMissingFilesAsync, [this](const std::vector<Zstring>& files) { removeCfgHistoryItems(files); });
-}
-
-
-void MainDialog::removeCfgHistoryItems(const std::vector<Zstring>& filenames)
-{
- std::for_each(filenames.begin(), filenames.end(), [&](const Zstring& filename)
- {
- const int histSize = m_listBoxHistory->GetCount();
- for (int i = 0; i < histSize; ++i)
- if (auto histData = dynamic_cast<wxClientHistoryData*>(m_listBoxHistory->GetClientObject(i)))
- if (EqualFilename()(filename, histData->cfgFile_))
- {
- m_listBoxHistory->Delete(i);
- break;
- }
- });
-}
-
-
-void MainDialog::updateUnsavedCfgStatus()
-{
- const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : Zstring();
-
- const bool haveUnsavedCfg = lastConfigurationSaved != getConfig();
-
- //update save config button
- const bool allowSave = haveUnsavedCfg ||
- activeConfigFiles.size() > 1;
-
- auto makeBrightGrey = [](const wxBitmap& bmp) -> wxBitmap
- {
- wxImage img = bmp.ConvertToImage().ConvertToGreyscale(1.0/3, 1.0/3, 1.0/3); //treat all channels equally!
- brighten(img, 80);
- return img;
- };
- //setImage(*m_bpButtonSave, greyScale(getResourceImage(L"save")));
-
- setImage(*m_bpButtonSave, allowSave ? getResourceImage(L"save") : makeBrightGrey(getResourceImage(L"save")));
- m_bpButtonSave->Enable(allowSave);
- m_menuItemSave->Enable(allowSave); //bitmap is automatically greyscaled on Win7 (introducing a crappy looking shift), but not on XP
-
- //set main dialog title
- wxString title;
- if (haveUnsavedCfg)
- title += L'*';
-
- if (!activeCfgFilename.empty())
- title += toWx(activeCfgFilename);
- else if (activeConfigFiles.size() > 1)
- {
- const wchar_t* EM_DASH = L" \u2014 ";
- title += xmlAccess::extractJobName(activeConfigFiles[0]);
- std::for_each(activeConfigFiles.begin() + 1, activeConfigFiles.end(), [&](const Zstring& filename) { title += EM_DASH + xmlAccess::extractJobName(filename); });
- }
- else
- title += L"FreeFileSync - " + _("Folder Comparison and Synchronization");
-
- SetTitle(title);
-}
-
-
-void MainDialog::OnConfigSave(wxCommandEvent& event)
-{
- const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : Zstring();
-
- //if we work on a single named configuration document: save directly if changed
- //else: always show file dialog
- if (!activeCfgFilename.empty())
- {
- using namespace xmlAccess;
-
- switch (getXmlType(activeCfgFilename)) //throw()
- {
- case XML_TYPE_GUI:
- trySaveConfig(&activeCfgFilename);
- return;
- case XML_TYPE_BATCH:
- trySaveBatchConfig(&activeCfgFilename);
- return;
- case XML_TYPE_GLOBAL:
- case XML_TYPE_OTHER:
- assert(false);
- return;
- }
- }
-
- trySaveConfig(nullptr);
-}
-
-
-void MainDialog::OnConfigSaveAs(wxCommandEvent& event)
-{
- trySaveConfig(nullptr);
-}
-
-
-void MainDialog::OnSaveAsBatchJob(wxCommandEvent& event)
-{
- trySaveBatchConfig(nullptr);
-}
-
-
-bool MainDialog::trySaveConfig(const Zstring* fileNameGui) //return true if saved successfully
-{
- Zstring targetFilename;
-
- if (fileNameGui)
- {
- targetFilename = *fileNameGui;
- assert(endsWith(targetFilename, Zstr(".ffs_gui")));
- }
- else
- {
- Zstring defaultFileName = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : Zstr("SyncSettings.ffs_gui");
- //attention: activeConfigFiles 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_gui"), false);
-
- wxFileDialog filePicker(this, //put modal dialog on stack: creating this on freestore leads to memleak!
- 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"FreeFileSync (*.ffs_gui)|*.ffs_gui") + L"|" +_("All files") + L" (*.*)|*",
- wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
- if (filePicker.ShowModal() != wxID_OK)
- return false;
- targetFilename = toZ(filePicker.GetPath());
- }
-
- const xmlAccess::XmlGuiConfig guiCfg = getConfig();
-
- try
- {
- xmlAccess::writeConfig(guiCfg, targetFilename); //throw FfsXmlError
- setLastUsedConfig(targetFilename, guiCfg);
-
- flashStatusInformation(_("Configuration saved"));
- return true;
- }
- catch (const xmlAccess::FfsXmlError& e)
- {
- showNotificationDialog(this, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString()));
- return false;
- }
-}
-
-
-bool MainDialog::trySaveBatchConfig(const Zstring* fileNameBatch)
-{
- //essentially behave like trySaveConfig(): the collateral damage of not saving GUI-only settings "hideExcludedItems, m_bpButtonViewTypeSyncAction" is negliable
-
- const xmlAccess::XmlGuiConfig guiCfg = getConfig();
-
- Zstring targetFilename;
- xmlAccess::XmlBatchConfig batchCfg;
-
- if (fileNameBatch)
- {
- targetFilename = *fileNameBatch;
- batchCfg = convertGuiToBatchPreservingExistingBatch(guiCfg, *fileNameBatch);
- assert(endsWith(targetFilename, Zstr(".ffs_batch")));
- }
- else
- {
- const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : Zstring();
- batchCfg = convertGuiToBatchPreservingExistingBatch(guiCfg, activeCfgFilename);
-
- //let user change batch config: this should change batch-exclusive settings only, else the "setLastUsedConfig" below would be somewhat of a lie
- if (!customizeBatchConfig(this,
- batchCfg, //in/out
- globalCfg.gui.onCompletionHistory,
- globalCfg.gui.onCompletionHistoryMax))
- return false;
-
- Zstring defaultFileName = !activeCfgFilename.empty() ? activeCfgFilename : Zstr("BatchRun.ffs_batch");
- //attention: activeConfigFiles may be a *.ffs_gui file! We don't want to overwrite it with a BATCH config!
- if (endsWith(defaultFileName, Zstr(".ffs_gui")))
- replace(defaultFileName, Zstr(".ffs_gui"), Zstr(".ffs_batch"));
-
- wxFileDialog filePicker(this, //put modal dialog on stack: creating this on freestore leads to memleak!
- 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
- _("FreeFileSync batch") + L" (*.ffs_batch)|*.ffs_batch" + L"|" +_("All files") + L" (*.*)|*",
- wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
- if (filePicker.ShowModal() != wxID_OK)
- return false;
- targetFilename = toZ(filePicker.GetPath());
- }
-
- try
- {
- xmlAccess::writeConfig(batchCfg, targetFilename); //throw FfsXmlError
-
- setLastUsedConfig(targetFilename, guiCfg); //[!] behave as if we had saved guiCfg
- flashStatusInformation(_("Configuration saved"));
- return true;
- }
- catch (const xmlAccess::FfsXmlError& e)
- {
- showNotificationDialog(this, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString()));
- return false;
- }
-}
-
-
-bool MainDialog::saveOldConfig() //return false on user abort
-{
- if (lastConfigurationSaved != getConfig())
- {
- const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : Zstring();
-
- //notify user about changed settings
- if (globalCfg.optDialogs.popupOnConfigChange)
- if (!activeCfgFilename.empty())
- //only if check is active and non-default config file loaded
- {
- bool neverSaveChanges = false;
- switch (showConfirmationDialog3(this, DialogInfoType::INFO, PopupDialogCfg3().
- setTitle(toWx(activeCfgFilename)).
- setMainInstructions(replaceCpy(_("Do you want to save changes to %x?"), L"%x", fmtFileName(afterLast(activeCfgFilename, FILE_NAME_SEPARATOR)))).
- setCheckBox(neverSaveChanges, _("Never save &changes"), ConfirmationButton3::DO_IT),
- _("&Save"), _("Do&n't save")))
- {
- case ConfirmationButton3::DO_IT: //save
- using namespace xmlAccess;
- switch (getXmlType(activeCfgFilename)) //throw()
- {
- case XML_TYPE_GUI:
- return trySaveConfig(&activeCfgFilename);
- case XML_TYPE_BATCH:
- return trySaveBatchConfig(&activeCfgFilename);
- case XML_TYPE_GLOBAL:
- case XML_TYPE_OTHER:
- assert(false);
- return false;
- }
- break;
-
- case ConfirmationButton3::DONT_DO_IT: //don't save
- globalCfg.optDialogs.popupOnConfigChange = !neverSaveChanges;
- break;
-
- case ConfirmationButton3::CANCEL:
- return false;
- }
- }
-
- //discard current reference file(s), this ensures next app start will load <last session> instead of the original non-modified config selection
- setLastUsedConfig(std::vector<Zstring>(), lastConfigurationSaved);
- //this seems to make theoretical sense also: the job of this function is to make sure current (volatile) config and reference file name are in sync
- // => if user does not save cfg, it is not attached to a physical file names anymore!
- }
- return true;
-}
-
-
-void MainDialog::OnConfigLoad(wxCommandEvent& event)
-{
- const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : Zstring();
-
- wxFileDialog filePicker(this,
- wxEmptyString,
- utfCvrtTo<wxString>(beforeLast(activeCfgFilename, FILE_NAME_SEPARATOR)), //set default dir: empty string if "activeConfigFiles" is empty or has no path separator
- wxEmptyString,
- wxString(L"FreeFileSync (*.ffs_gui; *.ffs_batch)|*.ffs_gui;*.ffs_batch") + L"|" +_("All files") + L" (*.*)|*",
- wxFD_OPEN | wxFD_MULTIPLE);
-
- if (filePicker.ShowModal() == wxID_OK)
- {
- wxArrayString tmp;
- filePicker.GetPaths(tmp);
- std::vector<wxString> filenames(tmp.begin(), tmp.end());
-
- loadConfiguration(toZ(filenames));
- }
-}
-
-
-void MainDialog::OnConfigNew(wxCommandEvent& event)
-{
- if (!saveOldConfig()) //notify user about changed settings
- return;
-
- xmlAccess::XmlGuiConfig newConfig;
-
- //add default exclusion filter: this is only ever relevant when creating new configurations!
- //a default XmlGuiConfig does not need these user-specific exclusions!
- Zstring& excludeFilter = newConfig.mainCfg.globalFilter.excludeFilter;
- if (!excludeFilter.empty() && !endsWith(excludeFilter, Zstr("\n")))
- excludeFilter += Zstr("\n");
- excludeFilter += globalCfg.gui.defaultExclusionFilter;
-
- setConfig(newConfig, std::vector<Zstring>());
-}
-
-
-void MainDialog::OnLoadFromHistory(wxCommandEvent& event)
-{
- wxArrayInt selections;
- m_listBoxHistory->GetSelections(selections);
-
- std::vector<Zstring> filenames;
- std::for_each(selections.begin(), selections.end(),
- [&](int pos)
- {
- if (auto histData = dynamic_cast<const wxClientHistoryData*>(m_listBoxHistory->GetClientObject(pos)))
- filenames.push_back(histData->cfgFile_);
- else
- assert(false);
- });
-
- if (!filenames.empty())
- loadConfiguration(filenames);
-
- //user changed m_listBoxHistory selection so it's this method's responsibility to synchronize with activeConfigFiles:
- //- if user cancelled saving old config
- //- there's an error loading new config
- //- filenames is empty and user tried to unselect the current config
- addFileToCfgHistory(activeConfigFiles);
-}
-
-
-void MainDialog::OnLoadFromHistoryDoubleClick(wxCommandEvent& event)
-{
- wxArrayInt selections;
- m_listBoxHistory->GetSelections(selections);
-
- std::vector<Zstring> filenames;
- std::for_each(selections.begin(), selections.end(), [&](int pos)
- {
- if (auto histData = dynamic_cast<const wxClientHistoryData*>(m_listBoxHistory->GetClientObject(pos)))
- filenames.push_back(histData->cfgFile_);
- else
- assert(false);
- });
-
- if (!filenames.empty())
- if (loadConfiguration(filenames))
- {
- //simulate button click on "compare"
- wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED);
- if (wxEvtHandler* evtHandler = m_buttonCompare->GetEventHandler())
- evtHandler->ProcessEvent(dummy2); //synchronous call
- }
-
- //synchronize m_listBoxHistory and activeConfigFiles, see OnLoadFromHistory()
- addFileToCfgHistory(activeConfigFiles);
-}
-
-
-bool MainDialog::loadConfiguration(const std::vector<Zstring>& filenames)
-{
- if (filenames.empty())
- return true;
-
- if (!saveOldConfig())
- return false; //cancelled by user
-
- //load XML
- xmlAccess::XmlGuiConfig newGuiCfg; //structure to receive gui settings, already defaulted!!
- try
- {
- //allow reading batch configurations also
- xmlAccess::readAnyConfig(filenames, newGuiCfg); //throw FfsXmlError
-
- setConfig(newGuiCfg, filenames);
- //flashStatusInformation(("Configuration loaded")); -> irrelevant!?
- return true;
- }
- catch (const xmlAccess::FfsXmlError& e)
- {
- if (e.getSeverity() == xmlAccess::FfsXmlError::WARNING)
- {
- showNotificationDialog(this, DialogInfoType::WARNING, PopupDialogCfg().setDetailInstructions(e.toString()));
- setConfig(newGuiCfg, filenames);
- setLastUsedConfig(filenames, xmlAccess::XmlGuiConfig()); //simulate changed config due to parsing errors
- }
- else
- showNotificationDialog(this, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString()));
- return false;
- }
-}
-
-void MainDialog::deleteSelectedCfgHistoryItems()
-{
- wxArrayInt tmp;
- m_listBoxHistory->GetSelections(tmp);
-
- std::set<int> selections(tmp.begin(), tmp.end()); //sort ascending!
- //delete starting with high positions:
- std::for_each(selections.rbegin(), selections.rend(), [&](int pos) { m_listBoxHistory->Delete(pos); });
-
- //set active selection on next element to allow "batch-deletion" by holding down DEL key
- if (!selections.empty() && m_listBoxHistory->GetCount() > 0)
- {
- int newSelection = *selections.begin();
- if (newSelection >= static_cast<int>(m_listBoxHistory->GetCount()))
- newSelection = m_listBoxHistory->GetCount() - 1;
- m_listBoxHistory->SetSelection(newSelection);
- }
-}
-
-
-void MainDialog::OnCfgHistoryRightClick(wxMouseEvent& event)
-{
- ContextMenu menu;
- menu.addItem(_("Delete") + L"\tDel", [this] { deleteSelectedCfgHistoryItems(); });
- menu.popup(*this);
-}
-
-
-void MainDialog::OnCfgHistoryKeyEvent(wxKeyEvent& event)
-{
- const int keyCode = event.GetKeyCode();
- if (keyCode == WXK_DELETE || keyCode == WXK_NUMPAD_DELETE)
- {
- deleteSelectedCfgHistoryItems();
- return; //"swallow" event
- }
- event.Skip();
-}
-
-
-void MainDialog::OnClose(wxCloseEvent& event)
-{
- //attention: system shutdown: is handled in onQueryEndSession()!
-
- //regular destruction handling
- if (event.CanVeto())
- {
- const bool cancelled = !saveOldConfig(); //notify user about changed settings
- if (cancelled)
- {
- //attention: this Veto() will NOT cancel system shutdown since saveOldConfig() blocks on modal dialog
-
- event.Veto();
- return;
- }
- }
-
- Destroy();
-}
-
-
-void MainDialog::onCheckRows(CheckRowsEvent& event)
-{
- std::set<size_t> selectedRows;
-
- const size_t rowLast = std::min(event.rowLast_, gridDataView->rowsOnView()); //consider dummy rows
- for (size_t i = event.rowFirst_; i < rowLast; ++i)
- selectedRows.insert(i);
-
- if (!selectedRows.empty())
- {
- std::vector<FileSystemObject*> objects = gridDataView->getAllFileRef(selectedRows);
- setFilterManually(objects, event.setIncluded_);
- }
-}
-
-
-void MainDialog::onSetSyncDirection(SyncDirectionEvent& event)
-{
- std::set<size_t> selectedRows;
-
- const size_t rowLast = std::min(event.rowLast_, gridDataView->rowsOnView()); //consider dummy rows
- for (size_t i = event.rowFirst_; i < rowLast; ++i)
- selectedRows.insert(i);
-
- if (!selectedRows.empty())
- {
- std::vector<FileSystemObject*> objects = gridDataView->getAllFileRef(selectedRows);
- setSyncDirManually(objects, event.direction_);
- }
-}
-
-
-void MainDialog::setLastUsedConfig(const Zstring& filename, const xmlAccess::XmlGuiConfig& guiConfig)
-{
- std::vector<Zstring> filenames;
- filenames.push_back(filename);
- setLastUsedConfig(filenames, guiConfig);
-}
-
-
-void MainDialog::setLastUsedConfig(const std::vector<Zstring>& filenames,
- const xmlAccess::XmlGuiConfig& guiConfig)
-{
- activeConfigFiles = filenames;
- lastConfigurationSaved = guiConfig;
-
- addFileToCfgHistory(activeConfigFiles); //put filename on list of last used config files
-
- updateUnsavedCfgStatus();
-}
-
-
-void MainDialog::setConfig(const xmlAccess::XmlGuiConfig& newGuiCfg, const std::vector<Zstring>& referenceFiles)
-{
- currentCfg = newGuiCfg;
-
- //evaluate new settings...
-
- //(re-)set view filter buttons
- setViewFilterDefault();
-
- updateGlobalFilterButton();
-
- //set first folder pair
- firstFolderPair->setValues(currentCfg.mainCfg.firstPair.leftDirectory,
- currentCfg.mainCfg.firstPair.rightDirectory,
- currentCfg.mainCfg.firstPair.altCmpConfig,
- currentCfg.mainCfg.firstPair.altSyncConfig,
- currentCfg.mainCfg.firstPair.localFilter);
-
- //folderHistoryLeft->addItem(currentCfg.mainCfg.firstPair.leftDirectory);
- //folderHistoryRight->addItem(currentCfg.mainCfg.firstPair.rightDirectory);
-
- setAddFolderPairs(currentCfg.mainCfg.additionalPairs);
-
- //read GUI layout
- m_checkBoxHideExcluded->SetValue(currentCfg.hideExcludedItems);
-
- setViewTypeSyncAction(currentCfg.highlightSyncAction);
-
- clearGrid(); //+ update GUI!
-
- setLastUsedConfig(referenceFiles, newGuiCfg);
-}
-
-
-inline
-FolderPairEnh getEnhancedPair(const FolderPairPanel* panel)
-{
- return FolderPairEnh(panel->getLeftDir(),
- panel->getRightDir(),
- panel->getAltCompConfig(),
- panel->getAltSyncConfig(),
- panel->getAltFilterConfig());
-}
-
-
-xmlAccess::XmlGuiConfig MainDialog::getConfig() const
-{
- xmlAccess::XmlGuiConfig guiCfg = currentCfg;
-
- //load settings whose ownership lies not in currentCfg:
-
- //first folder pair
- guiCfg.mainCfg.firstPair = FolderPairEnh(firstFolderPair->getLeftDir(),
- firstFolderPair->getRightDir(),
- firstFolderPair->getAltCompConfig(),
- firstFolderPair->getAltSyncConfig(),
- firstFolderPair->getAltFilterConfig());
-
- //add additional pairs
- guiCfg.mainCfg.additionalPairs.clear();
- std::transform(additionalFolderPairs.begin(), additionalFolderPairs.end(),
- std::back_inserter(guiCfg.mainCfg.additionalPairs), getEnhancedPair);
-
- //sync preview
- guiCfg.highlightSyncAction = m_bpButtonViewTypeSyncAction->isActive();
-
- return guiCfg;
-}
-
-
-const Zstring& MainDialog::lastRunConfigName()
-{
- static Zstring instance = zen::getConfigDir() + Zstr("LastRun.ffs_gui");
- return instance;
-}
-
-
-void MainDialog::updateGuiDelayedIf(bool condition)
-{
- const int delay = 400;
-
- if (condition)
- {
- gridview::refresh(*m_gridMainL, *m_gridMainC, *m_gridMainR);
- m_gridMainL->Update();
- m_gridMainC->Update();
- m_gridMainR->Update();
-
- wxMilliSleep(delay); //some delay to show the changed GUI before removing rows from sight
- }
-
- updateGui();
-}
-
-
-void MainDialog::OnShowExcluded(wxCommandEvent& event)
-{
- //toggle showing filtered rows
- currentCfg.hideExcludedItems = !currentCfg.hideExcludedItems;
- //make sure, checkbox and value are in sync
- m_checkBoxHideExcluded->SetValue(currentCfg.hideExcludedItems);
-
- updateGui();
-}
-
-
-void MainDialog::OnConfigureFilter(wxCommandEvent& event)
-{
- if (showFilterDialog(this, currentCfg.mainCfg.globalFilter, _("Filter")) == ReturnSmallDlg::BUTTON_OKAY)
- {
- updateGlobalFilterButton(); //refresh global filter icon
- applyFilterConfig(); //re-apply filter
- }
-
- //event.Skip()
-}
-
-
-void MainDialog::OnGlobalFilterContext(wxMouseEvent& event)
-{
- auto clearFilter = [&]
- {
- currentCfg.mainCfg.globalFilter = FilterConfig();
- updateGlobalFilterButton(); //refresh global filter icon
- applyFilterConfig(); //re-apply filter
- };
- auto copyFilter = [&] { filterCfgOnClipboard = make_unique<FilterConfig>(currentCfg.mainCfg.globalFilter); };
- auto pasteFilter = [&]
- {
- if (filterCfgOnClipboard)
- {
- currentCfg.mainCfg.globalFilter = *filterCfgOnClipboard;
- updateGlobalFilterButton(); //refresh global filter icon
- applyFilterConfig(); //re-apply filter
- }
- };
-
- ContextMenu menu;
- menu.addItem( _("Clear filter settings"), clearFilter, nullptr, !isNullFilter(currentCfg.mainCfg.globalFilter));
- menu.addSeparator();
- menu.addItem( _("Copy"), copyFilter, nullptr, !isNullFilter(currentCfg.mainCfg.globalFilter));
- menu.addItem( _("Paste"), pasteFilter, nullptr, filterCfgOnClipboard.get() != nullptr);
- menu.popup(*this);
-}
-
-
-void MainDialog::OnToggleViewType(wxCommandEvent& event)
-{
- setViewTypeSyncAction(!m_bpButtonViewTypeSyncAction->isActive()); //toggle view
-}
-
-
-void MainDialog::OnToggleViewButton(wxCommandEvent& event)
-{
- if (auto button = dynamic_cast<ToggleButton*>(event.GetEventObject()))
- {
- button->toggle();
- updateGui();
- }
- else
- assert(false);
-}
-
-
-inline
-wxBitmap buttonPressed(const std::string& name)
-{
- wxBitmap background = getResourceImage(L"buttonPressed");
- return mirrorIfRtl(
- layOver(getResourceImage(utfCvrtTo<wxString>(name)), background));
-}
-
-
-inline
-wxBitmap buttonReleased(const std::string& name)
-{
- wxImage output = getResourceImage(utfCvrtTo<wxString>(name)).ConvertToImage().ConvertToGreyscale(1.0/3, 1.0/3, 1.0/3); //treat all channels equally!
- //zen::moveImage(output, 1, 0); //move image right one pixel
-
- brighten(output, 80);
- return mirrorIfRtl(output);
-}
-
-
-void MainDialog::initViewFilterButtons()
-{
- m_bpButtonViewTypeSyncAction->init(getResourceImage(L"viewtype_sync_action"), getResourceImage(L"viewtype_cmp_result"));
- //tooltip is updated dynamically in setViewTypeSyncAction()
-
- auto initButton = [](ToggleButton& btn, const char* imgName, const wxString& tooltip) { btn.init(buttonPressed(imgName), buttonReleased(imgName)); btn.SetToolTip(tooltip); };
-
- //compare result buttons
- initButton(*m_bpButtonShowLeftOnly, "cat_left_only", _("Show files that exist on left side only"));
- initButton(*m_bpButtonShowRightOnly, "cat_right_only", _("Show files that exist on right side only"));
- initButton(*m_bpButtonShowLeftNewer, "cat_left_newer", _("Show files that are newer on left"));
- initButton(*m_bpButtonShowRightNewer, "cat_right_newer", _("Show files that are newer on right"));
- initButton(*m_bpButtonShowEqual, "cat_equal", _("Show files that are equal"));
- initButton(*m_bpButtonShowDifferent, "cat_different", _("Show files that are different"));
- initButton(*m_bpButtonShowConflict, "cat_conflict", _("Show conflicts"));
-
- //sync preview buttons
- initButton(*m_bpButtonShowCreateLeft, "so_create_left", _("Show files that will be created on the left side"));
- initButton(*m_bpButtonShowCreateRight, "so_create_right", _("Show files that will be created on the right side"));
- initButton(*m_bpButtonShowDeleteLeft, "so_delete_left", _("Show files that will be deleted on the left side"));
- initButton(*m_bpButtonShowDeleteRight, "so_delete_right", _("Show files that will be deleted on the right side"));
- initButton(*m_bpButtonShowUpdateLeft, "so_update_left", _("Show files that will be overwritten on left side"));
- initButton(*m_bpButtonShowUpdateRight, "so_update_right", _("Show files that will be overwritten on right side"));
- initButton(*m_bpButtonShowDoNothing, "so_none", _("Show files that won't be copied"));
-}
-
-
-void MainDialog::setViewFilterDefault()
-{
- auto setButton = [](ToggleButton* tb, bool value) { tb->setActive(value); };
-
- const auto& def = globalCfg.gui.viewFilterDefault;
- setButton(m_bpButtonShowLeftOnly, def.leftOnly);
- setButton(m_bpButtonShowRightOnly, def.rightOnly);
- setButton(m_bpButtonShowLeftNewer, def.leftNewer);
- setButton(m_bpButtonShowRightNewer, def.rightNewer);
- setButton(m_bpButtonShowEqual, def.equal);
- setButton(m_bpButtonShowDifferent, def.different);
- setButton(m_bpButtonShowConflict, def.conflict);
-
- setButton(m_bpButtonShowCreateLeft, def.createLeft);
- setButton(m_bpButtonShowCreateRight,def.createRight);
- setButton(m_bpButtonShowUpdateLeft, def.updateLeft);
- setButton(m_bpButtonShowUpdateRight,def.updateRight);
- setButton(m_bpButtonShowDeleteLeft, def.deleteLeft);
- setButton(m_bpButtonShowDeleteRight,def.deleteRight);
- setButton(m_bpButtonShowDoNothing, def.doNothing);
-}
-
-
-void MainDialog::OnViewButtonRightClick(wxMouseEvent& event)
-{
- auto setButtonDefault = [](const ToggleButton* tb, bool& defaultValue)
- {
- if (tb->IsShown())
- defaultValue = tb->isActive();
- };
-
- auto setDefault = [&]
- {
- auto& def = globalCfg.gui.viewFilterDefault;
- setButtonDefault(m_bpButtonShowLeftOnly, def.leftOnly);
- setButtonDefault(m_bpButtonShowRightOnly, def.rightOnly);
- setButtonDefault(m_bpButtonShowLeftNewer, def.leftNewer);
- setButtonDefault(m_bpButtonShowRightNewer, def.rightNewer);
- setButtonDefault(m_bpButtonShowEqual, def.equal);
- setButtonDefault(m_bpButtonShowDifferent, def.different);
- setButtonDefault(m_bpButtonShowConflict, def.conflict);
-
- setButtonDefault(m_bpButtonShowCreateLeft, def.createLeft);
- setButtonDefault(m_bpButtonShowCreateRight, def.createRight);
- setButtonDefault(m_bpButtonShowUpdateLeft, def.updateLeft);
- setButtonDefault(m_bpButtonShowUpdateRight, def.updateRight);
- setButtonDefault(m_bpButtonShowDeleteLeft, def.deleteLeft);
- setButtonDefault(m_bpButtonShowDeleteRight, def.deleteRight);
- setButtonDefault(m_bpButtonShowDoNothing, def.doNothing);
- };
-
- ContextMenu menu;
- menu.addItem( _("Set as default"), setDefault);
- menu.popup(*this);
-}
-
-
-void MainDialog::updateGlobalFilterButton()
-{
- //global filter: test for Null-filter
- if (!isNullFilter(currentCfg.mainCfg.globalFilter))
- {
- setImage(*m_bpButtonFilter, getResourceImage(L"filter"));
- m_bpButtonFilter->SetToolTip(_("Filter") + L" (F10) (" + _("Active") + L")");
- }
- else
- {
- setImage(*m_bpButtonFilter, greyScale(getResourceImage(L"filter")));
- m_bpButtonFilter->SetToolTip(_("Filter") + L" (F10) (" + _("None") + L")");
- }
-}
-
-
-void MainDialog::OnCompare(wxCommandEvent& event)
-{
- //PERF_START;
-
- //wxBusyCursor dummy; -> redundant: progress already shown in progress dialog!
-
- wxWindow* oldFocus = wxWindow::FindFocus();
- ZEN_ON_SCOPE_EXIT(if (oldFocus) oldFocus->SetFocus();); //e.g. keep focus on main grid after pressing F5
-
- int scrollPosX = 0;
- int scrollPosY = 0;
- m_gridMainL->GetViewStart(&scrollPosX, &scrollPosY); //preserve current scroll position
- ZEN_ON_SCOPE_EXIT(
- m_gridMainL->Scroll(scrollPosX, scrollPosY); //
- m_gridMainR->Scroll(scrollPosX, scrollPosY); //restore
- m_gridMainC->Scroll(-1, scrollPosY); ) //
-
- clearGrid(); //avoid memory peak by clearing old data first
-
- disableAllElements(true); //CompareStatusHandler will internally process Window messages, so avoid unexpected callbacks!
- auto app = wxTheApp; //fix lambda/wxWigets/VC fuck up
- ZEN_ON_SCOPE_EXIT(app->Yield(); enableAllElements()); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks
-
- try
- {
- //class handling status display and error messages
- CompareStatusHandler statusHandler(*this);
-
- const std::vector<zen::FolderPairCfg> cmpConfig = zen::extractCompareCfg(getConfig().mainCfg);
-
- //GUI mode: place directory locks on directories isolated(!) during both comparison and synchronization
- std::unique_ptr<LockHolder> dirLocks;
-
- //COMPARE DIRECTORIES
- compare(globalCfg.fileTimeTolerance,
- globalCfg.optDialogs,
- true, //allow pw prompt
- globalCfg.runWithBackgroundPriority,
- globalCfg.createLockFile,
- dirLocks,
- cmpConfig,
- folderCmp,
- statusHandler); //throw GuiAbortProcess
- }
- catch (GuiAbortProcess&)
- {
- // if (m_buttonCompare->IsShownOnScreen()) m_buttonCompare->SetFocus();
- updateGui(); //refresh grid in ANY case! (also on abort)
- return;
- }
-
- gridDataView->setData(folderCmp); //update view on data
- treeDataView->setData(folderCmp); //
- updateGui();
-
- // if (m_buttonSync->IsShownOnScreen()) m_buttonSync->SetFocus();
-
- gridview::clearSelection(*m_gridMainL, *m_gridMainC, *m_gridMainR);
- m_gridNavi->clearSelection();
-
- //play (optional) sound notification after sync has completed (GUI and batch mode)
- //const Zstring soundFile = zen::getResourceDir() + Zstr("Compare_Complete.wav");
- //if (fileExists(soundFile))
- // wxSound::Play(toWx(soundFile), wxSOUND_ASYNC);
-
- //add to folder history after successful comparison only
- folderHistoryLeft ->addItem(toZ(m_directoryLeft ->GetValue()));
- folderHistoryRight->addItem(toZ(m_directoryRight->GetValue()));
-
- //prepare status information
- if (allElementsEqual(folderCmp))
- flashStatusInformation(_("All folders are in sync"));
-}
-
-
-void MainDialog::updateTopButtonImages()
-{
- auto updateButton = [&](wxBitmapButton& btn, const wxBitmap& bmp, const wxString& variantName, bool makeGrey)
- {
- wxImage labelImage = createImageFromText(btn.GetLabel(), btn.GetFont(), wxSystemSettings::GetColour(makeGrey ? wxSYS_COLOUR_GRAYTEXT : wxSYS_COLOUR_BTNTEXT));
- wxImage variantImage = createImageFromText(variantName, wxFont(wxNORMAL_FONT->GetPointSize(), wxFONTFAMILY_DEFAULT, wxFONTSTYLE_NORMAL, wxBOLD), wxSystemSettings::GetColour(wxSYS_COLOUR_GRAYTEXT));
-
- wxImage descrImage = stackImages(labelImage, variantImage, ImageStackLayout::VERTICAL, ImageStackAlignment::CENTER);
- const wxImage& iconImage = makeGrey ? greyScale(bmp.ConvertToImage()) : bmp.ConvertToImage();
-
- wxImage dynImage = btn.GetLayoutDirection() != wxLayout_RightToLeft ?
- stackImages(iconImage, descrImage, ImageStackLayout::HORIZONTAL, ImageStackAlignment::CENTER, 5) :
- stackImages(descrImage, iconImage, ImageStackLayout::HORIZONTAL, ImageStackAlignment::CENTER, 5);
-
- //SetMinSize() instead of SetSize() is needed here for wxWindows layout determination to work corretly
- wxSize minSize = dynImage.GetSize() + wxSize(10, 10); //add border space
- minSize.x = std::max(minSize.x, 180);
- btn.SetMinSize(minSize);
-
- btn.SetBitmapLabel(wxBitmap(dynImage));
- //SetLabel() calls confuse wxBitmapButton in the disabled state and it won't show the image! workaround:
- btn.SetBitmapDisabled(wxBitmap(dynImage.ConvertToDisabled()));
- };
-
- updateButton(*m_buttonCompare, getResourceImage(L"compare"), getConfig().mainCfg.getCompVariantName(), false);
- updateButton(*m_buttonSync, getResourceImage(L"sync"), getConfig().mainCfg.getSyncVariantName(), folderCmp.empty());
-
- m_panelTopButtons->Layout();
-}
-
-
-void MainDialog::updateGui()
-{
- updateGridViewData(); //update gridDataView and write status information
-
- updateStatistics();
-
- updateUnsavedCfgStatus();
-
- updateTopButtonImages();
-
- auiMgr.Update(); //fix small display distortion, if view filter panel is empty
-}
-
-
-void MainDialog::clearGrid()
-{
- folderCmp.clear();
- gridDataView->setData(folderCmp);
- treeDataView->setData(folderCmp);
-
- updateGui();
-}
-
-
-void MainDialog::updateStatistics()
-{
- //update preview of item count and bytes to be transferred:
- const SyncStatistics st(folderCmp);
-
- setText(*m_staticTextData, filesizeToShortString(st.getDataToProcess()));
- if (st.getDataToProcess() == 0)
- m_bitmapData->SetBitmap(greyScale(getResourceImage(L"data")));
- else
- m_bitmapData->SetBitmap(getResourceImage(L"data"));
-
- auto setValue = [](wxStaticText& txtControl, int value, wxStaticBitmap& bmpControl, const wchar_t* bmpName)
- {
- setText(txtControl, toGuiString(value));
-
- if (value == 0)
- bmpControl.SetBitmap(greyScale(mirrorIfRtl(getResourceImage(bmpName))));
- else
- bmpControl.SetBitmap(mirrorIfRtl(getResourceImage(bmpName)));
- };
-
- setValue(*m_staticTextCreateLeft, st.getCreate<LEFT_SIDE >(), *m_bitmapCreateLeft, L"so_create_left_small");
- setValue(*m_staticTextUpdateLeft, st.getUpdate<LEFT_SIDE >(), *m_bitmapUpdateLeft, L"so_update_left_small");
- setValue(*m_staticTextDeleteLeft, st.getDelete<LEFT_SIDE >(), *m_bitmapDeleteLeft, L"so_delete_left_small");
- setValue(*m_staticTextCreateRight, st.getCreate<RIGHT_SIDE>(), *m_bitmapCreateRight, L"so_create_right_small");
- setValue(*m_staticTextUpdateRight, st.getUpdate<RIGHT_SIDE>(), *m_bitmapUpdateRight, L"so_update_right_small");
- setValue(*m_staticTextDeleteRight, st.getDelete<RIGHT_SIDE>(), *m_bitmapDeleteRight, L"so_delete_right_small");
-
- m_panelStatistics->Layout();
- m_panelStatistics->Refresh(); //fix small mess up on RTL layout
-}
-
-
-void MainDialog::OnSyncSettings(wxCommandEvent& event)
-{
- ExecWhenFinishedCfg ewfCfg = { &currentCfg.mainCfg.onCompletion,
- &globalCfg.gui.onCompletionHistory,
- globalCfg.gui.onCompletionHistoryMax
- };
-
- if (showSyncConfigDlg(this,
- currentCfg.mainCfg.cmpConfig.compareVar,
- currentCfg.mainCfg.syncCfg,
- _("Synchronization Settings"),
- &currentCfg.handleError,
- &ewfCfg) == ReturnSyncConfig::BUTTON_OKAY) //optional input parameter
- {
- applySyncConfig();
- }
-}
-
-
-void MainDialog::applyCompareConfig(bool switchMiddleGrid)
-{
- clearGrid(); //+ GUI update
-
- //convenience: change sync view
- if (switchMiddleGrid)
- switch (currentCfg.mainCfg.cmpConfig.compareVar)
- {
- case CMP_BY_TIME_SIZE:
- setViewTypeSyncAction(true);
- break;
- case CMP_BY_CONTENT:
- setViewTypeSyncAction(false);
- break;
- }
-}
-
-
-void MainDialog::OnCmpSettings(wxCommandEvent& event)
-{
- //show window right next to the compare-config button
- //wxPoint windowPos = m_bpButtonCmpConfig->GetScreenPosition();
- //windowPos.x += m_bpButtonCmpConfig->GetSize().GetWidth() + 5;
-
- CompConfig cmpConfigNew = currentCfg.mainCfg.cmpConfig;
-
- if (zen::showCompareCfgDialog(this, cmpConfigNew, _("Comparison Settings")) == ReturnSmallDlg::BUTTON_OKAY &&
- //check if settings were changed at all
- cmpConfigNew != currentCfg.mainCfg.cmpConfig)
- {
- currentCfg.mainCfg.cmpConfig = cmpConfigNew;
-
- applyCompareConfig(true); //true: switchMiddleGrid
- }
-}
-
-
-void MainDialog::OnStartSync(wxCommandEvent& event)
-{
- if (folderCmp.empty())
- {
- //quick sync: simulate button click on "compare"
- wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED);
- if (wxEvtHandler* evtHandler = m_buttonCompare->GetEventHandler())
- evtHandler->ProcessEvent(dummy2); //synchronous call
-
- if (folderCmp.empty()) //check if user aborted or error occurred, ect...
- return;
- }
-
- //show sync preview/confirmation dialog
- if (globalCfg.optDialogs.confirmSyncStart)
- {
- bool dontShowAgain = false;
-
- if (zen::showSyncConfirmationDlg(this,
- getConfig().mainCfg.getSyncVariantName(),
- zen::SyncStatistics(folderCmp),
- dontShowAgain) != ReturnSmallDlg::BUTTON_OKAY)
- return;
-
- globalCfg.optDialogs.confirmSyncStart = !dontShowAgain;
- }
-
- try
- {
- //PERF_START;
- const Zstring activeCfgFilename = activeConfigFiles.size() == 1 && activeConfigFiles[0] != lastRunConfigName() ? activeConfigFiles[0] : Zstring();
-
- const auto& guiCfg = getConfig();
-
- disableAllElements(false); //SyncStatusHandler will internally process Window messages, so avoid unexpected callbacks!
- ZEN_ON_SCOPE_EXIT(enableAllElements());
-
- //class handling status updates and error messages
- SyncStatusHandler statusHandler(this, //throw GuiAbortProcess
- globalCfg.lastSyncsLogFileSizeMax,
- currentCfg.handleError,
- globalCfg.automaticRetryCount,
- globalCfg.automaticRetryDelay,
- xmlAccess::extractJobName(activeCfgFilename),
- guiCfg.mainCfg.onCompletion,
- globalCfg.gui.onCompletionHistory);
-
- //wxBusyCursor dummy; -> redundant: progress already shown in progress dialog!
-
- //GUI mode: place directory locks on directories isolated(!) during both comparison and synchronization
- std::unique_ptr<LockHolder> dirLocks;
- if (globalCfg.createLockFile)
- {
- std::set<Zstring, LessFilename> dirnamesExisting;
- for (auto it = begin(folderCmp); it != end(folderCmp); ++it)
- {
- if (it->isExisting<LEFT_SIDE>()) //do NOT check directory existence again!
- dirnamesExisting.insert(it->getBaseDirPf<LEFT_SIDE >());
- if (it->isExisting<RIGHT_SIDE>())
- dirnamesExisting.insert(it->getBaseDirPf<RIGHT_SIDE>());
- }
- dirLocks = make_unique<LockHolder>(dirnamesExisting, globalCfg.optDialogs.warningDirectoryLockFailed, statusHandler);
- }
-
- //START SYNCHRONIZATION
- const std::vector<zen::FolderPairSyncCfg> syncProcessCfg = zen::extractSyncCfg(guiCfg.mainCfg);
- if (syncProcessCfg.size() != folderCmp.size())
- throw std::logic_error("Programming Error: Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
- //should never happen: sync button is deactivated if they are not in sync
-
- synchronize(localTime(),
- globalCfg.optDialogs,
- globalCfg.verifyFileCopy,
- globalCfg.copyLockedFiles,
- globalCfg.copyFilePermissions,
- globalCfg.failsafeFileCopy,
- globalCfg.runWithBackgroundPriority,
- syncProcessCfg,
- folderCmp,
- statusHandler);
- }
- catch (GuiAbortProcess&)
- {
- //do NOT disable the sync button: user might want to try to sync the REMAINING rows
- } //enableSynchronization(false);
-
- //remove empty rows: just a beautification, invalid rows shouldn't cause issues
- gridDataView->removeInvalidRows();
-
- updateGui();
-}
-
-
-void MainDialog::onGridDoubleClickL(GridClickEvent& event)
-{
- onGridDoubleClickRim(event.row_, true);
-}
-void MainDialog::onGridDoubleClickR(GridClickEvent& event)
-{
- onGridDoubleClickRim(event.row_, false);
-}
-
-void MainDialog::onGridDoubleClickRim(size_t row, bool leftSide)
-{
- if (!globalCfg.gui.externelApplications.empty())
- {
- std::vector<FileSystemObject*> selection;
- if (FileSystemObject* fsObj = gridDataView->getObject(row)) //selection must be a list of BOUND pointers!
- selection.push_back(fsObj);
-
- openExternalApplication(globalCfg.gui.externelApplications[0].second, selection, leftSide);
- }
-}
-
-
-void MainDialog::onGridLabelLeftClick(bool onLeft, ColumnTypeRim type)
-{
- auto sortInfo = gridDataView->getSortInfo();
-
- bool sortAscending = GridView::getDefaultSortDirection(type);
- if (sortInfo && sortInfo->onLeft_ == onLeft && sortInfo->type_ == type)
- sortAscending = !sortInfo->ascending_;
-
- gridDataView->sortView(type, onLeft, sortAscending);
-
- gridview::clearSelection(*m_gridMainL, *m_gridMainC, *m_gridMainR);
- updateGui(); //refresh gridDataView
-}
-
-void MainDialog::onGridLabelLeftClickL(GridClickEvent& event)
-{
- onGridLabelLeftClick(true, static_cast<ColumnTypeRim>(event.colType_));
-}
-void MainDialog::onGridLabelLeftClickR(GridClickEvent& event)
-{
- onGridLabelLeftClick(false, static_cast<ColumnTypeRim>(event.colType_));
-}
-
-
-void MainDialog::onGridLabelLeftClickC(GridClickEvent& event)
-{
- //sorting middle grid is more or less useless: therefore let's toggle view instead!
- setViewTypeSyncAction(!m_bpButtonViewTypeSyncAction->isActive()); //toggle view
-}
-
-
-void MainDialog::OnSwapSides(wxCommandEvent& event)
-{
- //swap directory names: first pair
- firstFolderPair->setValues(firstFolderPair->getRightDir(), // swap directories
- firstFolderPair->getLeftDir(), //
- firstFolderPair->getAltCompConfig(),
- firstFolderPair->getAltSyncConfig(),
- firstFolderPair->getAltFilterConfig());
-
- //additional pairs
- for (auto it = additionalFolderPairs.begin(); it != additionalFolderPairs.end(); ++it)
- {
- FolderPairPanel* panel = *it;
- panel->setValues(panel->getRightDir(), // swap directories
- panel->getLeftDir(), //
- panel->getAltCompConfig(),
- panel->getAltSyncConfig(),
- panel->getAltFilterConfig());
- }
-
- //swap view filter
- bool tmp = m_bpButtonShowLeftOnly->isActive();
- m_bpButtonShowLeftOnly->setActive(m_bpButtonShowRightOnly->isActive());
- m_bpButtonShowRightOnly->setActive(tmp);
-
- tmp = m_bpButtonShowLeftNewer->isActive();
- m_bpButtonShowLeftNewer->setActive(m_bpButtonShowRightNewer->isActive());
- m_bpButtonShowRightNewer->setActive(tmp);
-
- /* for sync preview and "mirror" variant swapping may create strange effect:
- tmp = m_bpButtonShowCreateLeft->isActive();
- m_bpButtonShowCreateLeft->setActive(m_bpButtonShowCreateRight->isActive());
- m_bpButtonShowCreateRight->setActive(tmp);
-
- tmp = m_bpButtonShowDeleteLeft->isActive();
- m_bpButtonShowDeleteLeft->setActive(m_bpButtonShowDeleteRight->isActive());
- m_bpButtonShowDeleteRight->setActive(tmp);
-
- tmp = m_bpButtonShowUpdateLeft->isActive();
- m_bpButtonShowUpdateLeft->setActive(m_bpButtonShowUpdateRight->isActive());
- m_bpButtonShowUpdateRight->setActive(tmp);
- */
-
- //swap grid information
- zen::swapGrids(getConfig().mainCfg, folderCmp);
-
- updateGui();
-}
-
-
-void MainDialog::updateGridViewData()
-{
- size_t filesOnLeftView = 0;
- size_t foldersOnLeftView = 0;
- size_t filesOnRightView = 0;
- size_t foldersOnRightView = 0;
- zen::UInt64 filesizeLeftView;
- zen::UInt64 filesizeRightView;
-
- //disable all buttons per default
- m_bpButtonShowLeftOnly ->Show(false);
- m_bpButtonShowRightOnly ->Show(false);
- m_bpButtonShowLeftNewer ->Show(false);
- m_bpButtonShowRightNewer->Show(false);
- m_bpButtonShowDifferent ->Show(false);
- m_bpButtonShowEqual ->Show(false);
- m_bpButtonShowConflict ->Show(false);
-
- m_bpButtonShowCreateLeft ->Show(false);
- m_bpButtonShowCreateRight->Show(false);
- m_bpButtonShowDeleteLeft ->Show(false);
- m_bpButtonShowDeleteRight->Show(false);
- m_bpButtonShowUpdateLeft ->Show(false);
- m_bpButtonShowUpdateRight->Show(false);
- m_bpButtonShowDoNothing ->Show(false);
-
- if (m_bpButtonViewTypeSyncAction->isActive())
- {
- const GridView::StatusSyncPreview result = gridDataView->updateSyncPreview(currentCfg.hideExcludedItems,
- m_bpButtonShowCreateLeft ->isActive(),
- m_bpButtonShowCreateRight->isActive(),
- m_bpButtonShowDeleteLeft ->isActive(),
- m_bpButtonShowDeleteRight->isActive(),
- m_bpButtonShowUpdateLeft ->isActive(),
- m_bpButtonShowUpdateRight->isActive(),
- m_bpButtonShowDoNothing ->isActive(),
- m_bpButtonShowEqual ->isActive(),
- m_bpButtonShowConflict ->isActive());
- filesOnLeftView = result.filesOnLeftView;
- foldersOnLeftView = result.foldersOnLeftView;
- filesOnRightView = result.filesOnRightView;
- foldersOnRightView = result.foldersOnRightView;
- filesizeLeftView = result.filesizeLeftView;
- filesizeRightView = result.filesizeRightView;
-
-
- //sync preview buttons
- m_bpButtonShowCreateLeft ->Show(result.existsSyncCreateLeft);
- m_bpButtonShowCreateRight ->Show(result.existsSyncCreateRight);
- m_bpButtonShowDeleteLeft ->Show(result.existsSyncDeleteLeft);
- m_bpButtonShowDeleteRight ->Show(result.existsSyncDeleteRight);
- m_bpButtonShowUpdateLeft ->Show(result.existsSyncDirLeft);
- m_bpButtonShowUpdateRight ->Show(result.existsSyncDirRight);
- m_bpButtonShowDoNothing ->Show(result.existsSyncDirNone);
- m_bpButtonShowEqual ->Show(result.existsSyncEqual);
- m_bpButtonShowConflict ->Show(result.existsConflict);
-
- const bool anyViewFilterButtonShown = m_bpButtonShowCreateLeft ->IsShown() ||
- m_bpButtonShowCreateRight->IsShown() ||
- m_bpButtonShowDeleteLeft ->IsShown() ||
- m_bpButtonShowDeleteRight->IsShown() ||
- m_bpButtonShowUpdateLeft ->IsShown() ||
- m_bpButtonShowUpdateRight->IsShown() ||
- m_bpButtonShowDoNothing ->IsShown() ||
- m_bpButtonShowEqual ->IsShown() ||
- m_bpButtonShowConflict ->IsShown();
- m_bpButtonViewTypeSyncAction->Show(anyViewFilterButtonShown);
-
- if (anyViewFilterButtonShown)
- {
- m_panelViewFilter->Show();
- m_panelViewFilter->Layout();
- }
- else
- m_panelViewFilter->Hide();
- }
- else
- {
- const GridView::StatusCmpResult result = gridDataView->updateCmpResult(currentCfg.hideExcludedItems,
- m_bpButtonShowLeftOnly ->isActive(),
- m_bpButtonShowRightOnly ->isActive(),
- m_bpButtonShowLeftNewer ->isActive(),
- m_bpButtonShowRightNewer->isActive(),
- m_bpButtonShowDifferent ->isActive(),
- m_bpButtonShowEqual ->isActive(),
- m_bpButtonShowConflict ->isActive());
- filesOnLeftView = result.filesOnLeftView;
- foldersOnLeftView = result.foldersOnLeftView;
- filesOnRightView = result.filesOnRightView;
- foldersOnRightView = result.foldersOnRightView;
- filesizeLeftView = result.filesizeLeftView;
- filesizeRightView = result.filesizeRightView;
-
- //comparison result view buttons
- m_bpButtonShowLeftOnly ->Show(result.existsLeftOnly);
- m_bpButtonShowRightOnly ->Show(result.existsRightOnly);
- m_bpButtonShowLeftNewer ->Show(result.existsLeftNewer);
- m_bpButtonShowRightNewer->Show(result.existsRightNewer);
- m_bpButtonShowDifferent ->Show(result.existsDifferent);
- m_bpButtonShowEqual ->Show(result.existsEqual);
- m_bpButtonShowConflict ->Show(result.existsConflict);
-
- const bool anyViewFilterButtonShown = m_bpButtonShowLeftOnly ->IsShown() ||
- m_bpButtonShowRightOnly ->IsShown() ||
- m_bpButtonShowLeftNewer ->IsShown() ||
- m_bpButtonShowRightNewer->IsShown() ||
- m_bpButtonShowDifferent ->IsShown() ||
- m_bpButtonShowEqual ->IsShown() ||
- m_bpButtonShowConflict ->IsShown();
- m_bpButtonViewTypeSyncAction->Show(anyViewFilterButtonShown);
-
- if (anyViewFilterButtonShown)
- {
- m_panelViewFilter->Show();
- m_panelViewFilter->Layout();
- }
- else
- m_panelViewFilter->Hide();
- }
- //all three grids retrieve their data directly via gridDataView
- gridview::refresh(*m_gridMainL, *m_gridMainC, *m_gridMainR);
-
- //navigation tree
- if (m_bpButtonViewTypeSyncAction->isActive())
- treeDataView->updateSyncPreview(currentCfg.hideExcludedItems,
- m_bpButtonShowCreateLeft ->isActive(),
- m_bpButtonShowCreateRight->isActive(),
- m_bpButtonShowDeleteLeft ->isActive(),
- m_bpButtonShowDeleteRight->isActive(),
- m_bpButtonShowUpdateLeft ->isActive(),
- m_bpButtonShowUpdateRight->isActive(),
- m_bpButtonShowDoNothing ->isActive(),
- m_bpButtonShowEqual ->isActive(),
- m_bpButtonShowConflict ->isActive());
- else
- treeDataView->updateCmpResult(currentCfg.hideExcludedItems,
- m_bpButtonShowLeftOnly ->isActive(),
- m_bpButtonShowRightOnly ->isActive(),
- m_bpButtonShowLeftNewer ->isActive(),
- m_bpButtonShowRightNewer->isActive(),
- m_bpButtonShowDifferent ->isActive(),
- m_bpButtonShowEqual ->isActive(),
- m_bpButtonShowConflict ->isActive());
- m_gridNavi->Refresh();
-
- //update status bar information
- setStatusBarFileStatistics(filesOnLeftView,
- foldersOnLeftView,
- filesOnRightView,
- foldersOnRightView,
- filesizeLeftView,
- filesizeRightView);
-}
-
-
-void MainDialog::applyFilterConfig()
-{
- applyFiltering(folderCmp, getConfig().mainCfg);
- updateGui();
- //updateGuiDelayedIf(currentCfg.hideExcludedItems); //show update GUI before removing rows
-}
-
-
-void MainDialog::applySyncConfig()
-{
- zen::redetermineSyncDirection(getConfig().mainCfg, folderCmp,
- [&](const std::wstring& warning)
- {
- bool& warningActive = globalCfg.optDialogs.warningDatabaseError;
- if (warningActive)
- {
- bool dontWarnAgain = false;
-
- showNotificationDialog(this, DialogInfoType::WARNING, PopupDialogCfg().setDetailInstructions(warning).setCheckBox(dontWarnAgain, _("&Don't show this warning again")));
- warningActive = !dontWarnAgain;
- }
- });
-
- updateGui();
-}
-
-
-void MainDialog::OnMenuFindItem(wxCommandEvent& event) //CTRL + F
-{
- showFindPanel();
-}
-
-
-void MainDialog::OnSearchGridEnter(wxCommandEvent& event)
-{
- startFindNext();
-}
-
-
-void MainDialog::OnHideSearchPanel(wxCommandEvent& event)
-{
- hideFindPanel();
-}
-
-
-void MainDialog::OnSearchPanelKeyPressed(wxKeyEvent& event)
-{
- switch (event.GetKeyCode())
- {
- case WXK_RETURN:
- case WXK_NUMPAD_ENTER: //catches ENTER keys while focus is on *any* part of m_panelSearch! Seems to obsolete OnHideSearchPanel()!
- startFindNext();
- return;
- case WXK_ESCAPE:
- hideFindPanel();
- return;
- }
- event.Skip();
-}
-
-
-void MainDialog::showFindPanel() //CTRL + F or F3 with empty search phrase
-{
- auiMgr.GetPane(m_panelSearch).Show();
- auiMgr.Update();
-
- m_textCtrlSearchTxt->SelectAll();
-
- wxWindow* focus = wxWindow::FindFocus(); //restore when closing panel!
- if (!isComponentOf(focus, m_panelSearch))
- focusWindowAfterSearch = focus == &m_gridMainR->getMainWin() ? focus : &m_gridMainL->getMainWin();
- //don't save pointer to arbitrary window: it might not exist anymore when hideFindPanel() uses it!!! (e.g. some folder pair panel)
- m_textCtrlSearchTxt->SetFocus();
-}
-
-
-void MainDialog::hideFindPanel()
-{
- auiMgr.GetPane(m_panelSearch).Hide();
- auiMgr.Update();
-
- if (focusWindowAfterSearch)
- {
- focusWindowAfterSearch->SetFocus();
- focusWindowAfterSearch = nullptr;
- }
-}
-
-
-void MainDialog::startFindNext() //F3 or ENTER in m_textCtrlSearchTxt
-{
- const wxString& searchString = m_textCtrlSearchTxt->GetValue();
-
- if (searchString.empty())
- showFindPanel();
- else
- {
- Grid* grid1 = m_gridMainL;
- Grid* grid2 = m_gridMainR;
-
- wxWindow* focus = wxWindow::FindFocus();
- if ((isComponentOf(focus, m_panelSearch) ? focusWindowAfterSearch : focus) == &m_gridMainR->getMainWin())
- std::swap(grid1, grid2); //select side to start search at grid cursor position
-
- wxBeginBusyCursor(wxHOURGLASS_CURSOR);
- const std::pair<const Grid*, ptrdiff_t> result = findGridMatch(*grid1, *grid2, searchString,
- m_checkBoxMatchCase->GetValue()); //parameter owned by GUI, *not* globalCfg structure! => we should better implement a getGlocalCfg()!
- wxEndBusyCursor();
-
- if (Grid* grid = const_cast<Grid*>(result.first)) //grid wasn't const when passing to findAndSelectNext(), so this is safe
- {
- assert(result.second >= 0);
-
- gridview::setScrollMaster(*grid);
- grid->setGridCursor(result.second);
-
- focusWindowAfterSearch = &grid->getMainWin();
-
- if (!isComponentOf(wxWindow::FindFocus(), m_panelSearch))
- grid->getMainWin().SetFocus();
- }
- else
- {
- showFindPanel();
- showNotificationDialog(this, DialogInfoType::INFO, PopupDialogCfg().
- setTitle(_("Find")).
- setMainInstructions(replaceCpy(_("Cannot find %x"), L"%x", L"\"" + searchString + L"\"", false)));
- }
- }
-}
-
-
-void MainDialog::OnAddFolderPair(wxCommandEvent& event)
-{
-#ifdef ZEN_WIN
- wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X!
-#endif
-
- std::vector<FolderPairEnh> newPairs;
- newPairs.push_back(getConfig().mainCfg.firstPair);
-
- //clear first pair
- const FolderPairEnh cfgEmpty;
- firstFolderPair->setValues(cfgEmpty.leftDirectory,
- cfgEmpty.rightDirectory,
- cfgEmpty.altCmpConfig,
- cfgEmpty.altSyncConfig,
- cfgEmpty.localFilter);
-
- //keep sequence to update GUI as last step
- addAddFolderPair(newPairs, true); //add pair in front of additonal pairs
-}
-
-
-void MainDialog::OnRemoveTopFolderPair(wxCommandEvent& event)
-{
- if (!additionalFolderPairs.empty())
- {
-#ifdef ZEN_WIN
- wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X!
-#endif
-
- //get settings from second folder pair
- const FolderPairEnh cfgSecond = getEnhancedPair(additionalFolderPairs[0]);
-
- //reset first pair
- firstFolderPair->setValues(cfgSecond.leftDirectory,
- cfgSecond.rightDirectory,
- cfgSecond.altCmpConfig,
- cfgSecond.altSyncConfig,
- cfgSecond.localFilter);
-
- removeAddFolderPair(0); //remove second folder pair (first of additional folder pairs)
- }
-}
-
-
-void MainDialog::OnRemoveFolderPair(wxCommandEvent& event)
-{
-#ifdef ZEN_WIN
- wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X!
-#endif
-
- const wxObject* const eventObj = event.GetEventObject(); //find folder pair originating the event
- for (auto it = additionalFolderPairs.begin(); it != additionalFolderPairs.end(); ++it)
- if (eventObj == (*it)->m_bpButtonRemovePair)
- {
- removeAddFolderPair(it - additionalFolderPairs.begin());
- break;
- }
-}
-
-
-void MainDialog::updateGuiForFolderPair()
-{
-#ifdef ZEN_WIN
- wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X!
-#endif
-
- //adapt delete top folder pair button
- m_bpButtonRemovePair->Show(!additionalFolderPairs.empty());
- m_panelTopLeft->Layout();
-
- //adapt local filter and sync cfg for first folder pair
- const bool showLocalCfgFirstPair = !additionalFolderPairs.empty() ||
- firstFolderPair->getAltCompConfig().get() != nullptr ||
- firstFolderPair->getAltSyncConfig().get() != nullptr ||
- !isNullFilter(firstFolderPair->getAltFilterConfig());
-
- m_bpButtonAltCompCfg ->Show(showLocalCfgFirstPair);
- m_bpButtonAltSyncCfg ->Show(showLocalCfgFirstPair);
- m_bpButtonLocalFilter->Show(showLocalCfgFirstPair);
- setImage(*m_bpButtonSwapSides, getResourceImage(showLocalCfgFirstPair ? L"swap_slim" : L"swap"));
-
- //update sub-panel sizes for calculations below!!!
- m_panelTopMiddle->GetSizer()->SetSizeHints(m_panelTopMiddle); //~=Fit() + SetMinSize()
-
- int addPairMinimalHeight = 0;
- int addPairOptimalHeight = 0;
- if (!additionalFolderPairs.empty())
- {
- const int pairHeight = additionalFolderPairs[0]->GetSize().GetHeight();
- addPairMinimalHeight = std::min<double>(1.5, additionalFolderPairs.size()) * pairHeight; //have 1.5 * height indicate that more folders are there
- addPairOptimalHeight = std::min<double>(globalCfg.gui.maxFolderPairsVisible - 1 + 0.5, //subtract first/main folder pair and add 0.5 to indicate additional folders
- additionalFolderPairs.size()) * pairHeight;
-
- addPairOptimalHeight = std::max(addPairOptimalHeight, addPairMinimalHeight); //implicitly handle corrupted values for "maxFolderPairsVisible"
- }
-
- const int firstPairHeight = std::max(m_panelDirectoryPairs->ClientToWindowSize(m_panelTopLeft ->GetSize()).GetHeight(), //include m_panelDirectoryPairs window borders!
- m_panelDirectoryPairs->ClientToWindowSize(m_panelTopMiddle->GetSize()).GetHeight()); //
-
- //########################################################################################################################
- //wxAUI hack: set minimum height to desired value, then call wxAuiPaneInfo::Fixed() to apply it
- auiMgr.GetPane(m_panelDirectoryPairs).MinSize(-1, firstPairHeight + addPairOptimalHeight);
- auiMgr.GetPane(m_panelDirectoryPairs).Fixed();
- auiMgr.Update();
-
- //now make resizable again
- auiMgr.GetPane(m_panelDirectoryPairs).Resizable();
- auiMgr.Update();
- //########################################################################################################################
-
- //make sure user cannot fully shrink additional folder pairs
- auiMgr.GetPane(m_panelDirectoryPairs).MinSize(-1, firstPairHeight + addPairMinimalHeight);
- auiMgr.Update();
-
- //it seems there is no GetSizer()->SetSizeHints(this)/Fit() required due to wxAui "magic"
- //=> *massive* perf improvement on OS X!
-}
-
-
-void MainDialog::addAddFolderPair(const std::vector<FolderPairEnh>& newPairs, bool addFront)
-{
-#ifdef ZEN_WIN
- wxWindowUpdateLocker dummy(m_panelDirectoryPairs); //leads to GUI corruption problems on Linux/OS X!
-#endif
-
- std::vector<FolderPairPanel*> newEntries;
-
- std::for_each(newPairs.begin(), newPairs.end(),
- [&](const FolderPairEnh& enhPair)
- {
- //add new folder pair
- FolderPairPanel* newPair = new FolderPairPanel(m_scrolledWindowFolderPairs, *this);
-
- //init dropdown history
- newPair->m_directoryLeft ->init(folderHistoryLeft);
- newPair->m_directoryRight->init(folderHistoryRight);
-
- //set width of left folder panel
- const int width = m_panelTopLeft->GetSize().GetWidth();
- newPair->m_panelLeft->SetMinSize(wxSize(width, -1));
-
- if (addFront)
- {
- bSizerAddFolderPairs->Insert(0, newPair, 0, wxEXPAND);
- additionalFolderPairs.insert(additionalFolderPairs.begin(), newPair);
- }
- else
- {
- bSizerAddFolderPairs->Add(newPair, 0, wxEXPAND);
- additionalFolderPairs.push_back(newPair);
- }
- newEntries.push_back(newPair);
-
- //register events
- newPair->m_bpButtonRemovePair->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainDialog::OnRemoveFolderPair), nullptr, this);
- });
-
- updateGuiForFolderPair();
-
- //wxComboBox screws up miserably if width/height is smaller than the magic number 4! Problem occurs when trying to set tooltip
- //so we have to update window sizes before setting configuration:
- for (auto it = newPairs.begin(); it != newPairs.end(); ++it)//set alternate configuration
- newEntries[it - newPairs.begin()]->setValues(it->leftDirectory,
- it->rightDirectory,
- it->altCmpConfig,
- it->altSyncConfig,
- it->localFilter);
- clearGrid(); //+ GUI update
-}
-
-
-void MainDialog::removeAddFolderPair(size_t pos)
-{
-#ifdef ZEN_WIN
- wxWindowUpdateLocker dummy(m_panelDirectoryPairs); //leads to GUI corruption problems on Linux/OS X!
-#endif
-
- if (pos < additionalFolderPairs.size())
- {
- FolderPairPanel* panel = additionalFolderPairs[pos];
-
- bSizerAddFolderPairs->Detach(panel); //Remove() does not work on Window*, so do it manually
- additionalFolderPairs.erase(additionalFolderPairs.begin() + pos);
- //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 "panel" we need to delay deletion ourselves:
- processAsync2([] {}, [panel] { panel->Destroy(); });
- }
-
- updateGuiForFolderPair();
-
- clearGrid(); //+ GUI update
-}
-
-
-void MainDialog::setAddFolderPairs(const std::vector<zen::FolderPairEnh>& newPairs)
-{
-#ifdef ZEN_WIN
- wxWindowUpdateLocker dummy(m_panelDirectoryPairs); //leads to GUI corruption problems on Linux/OS X!
-#endif
-
- additionalFolderPairs.clear();
- bSizerAddFolderPairs->Clear(true);
-
- //updateGuiForFolderPair(); -> already called in addAddFolderPair()
-
- addAddFolderPair(newPairs);
-}
-
-
-//########################################################################################################
-
-
-//menu events
-void MainDialog::OnMenuGlobalSettings(wxCommandEvent& event)
-{
- zen::showGlobalSettingsDlg(this, globalCfg);
-}
-
-
-void MainDialog::OnMenuExportFileList(wxCommandEvent& event)
-{
- //get a filename
- wxFileDialog filePicker(this, //creating this on freestore leads to memleak!
- wxEmptyString,
- wxEmptyString,
- L"FileList.csv", //default file name
- _("Comma-separated values") + L" (*.csv)|*.csv" + L"|" +_("All files") + L" (*.*)|*",
- wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
- if (filePicker.ShowModal() != wxID_OK)
- return;
-
- wxBusyCursor dummy;
-
- const Zstring filename = utfCvrtTo<Zstring>(filePicker.GetPath());
-
- //http://en.wikipedia.org/wiki/Comma-separated_values
- const lconv* localInfo = ::localeconv(); //always bound according to doc
- const bool haveCommaAsDecimalSep = std::string(localInfo->decimal_point) == ",";
-
- const char CSV_SEP = haveCommaAsDecimalSep ? ';' : ',';
-
- auto fmtValue = [&](const wxString& val) -> Utf8String
- {
- Utf8String&& tmp = utfCvrtTo<Utf8String>(val);
-
- if (contains(tmp, CSV_SEP))
- return '\"' + tmp + '\"';
- else
- return tmp;
- };
-
- Utf8String header; //perf: wxString doesn't model exponential growth and so is out, std::string doesn't give performance guarantee!
- header += BYTE_ORDER_MARK_UTF8;
-
- //base folders
- header += fmtValue(_("Folder Pairs")) + '\n' ;
- std::for_each(begin(folderCmp), end(folderCmp),
- [&](BaseDirPair& baseDirObj)
- {
- header += utfCvrtTo<Utf8String>(baseDirObj.getBaseDirPf<LEFT_SIDE >()) + CSV_SEP;
- header += utfCvrtTo<Utf8String>(baseDirObj.getBaseDirPf<RIGHT_SIDE>()) + '\n';
- });
- header += '\n';
-
- //write header
- auto provLeft = m_gridMainL->getDataProvider();
- auto provMiddle = m_gridMainC->getDataProvider();
- auto provRight = m_gridMainR->getDataProvider();
-
- auto colAttrLeft = m_gridMainL->getColumnConfig();
- auto colAttrMiddle = m_gridMainC->getColumnConfig();
- auto colAttrRight = m_gridMainR->getColumnConfig();
-
- vector_remove_if(colAttrLeft , [](const Grid::ColumnAttribute& ca) { return !ca.visible_; });
- vector_remove_if(colAttrMiddle, [](const Grid::ColumnAttribute& ca) { return !ca.visible_ || static_cast<ColumnTypeMiddle>(ca.type_) == COL_TYPE_CHECKBOX; });
- vector_remove_if(colAttrRight , [](const Grid::ColumnAttribute& ca) { return !ca.visible_; });
-
- if (provLeft && provMiddle && provRight)
- {
- std::for_each(colAttrLeft.begin(), colAttrLeft.end(),
- [&](const Grid::ColumnAttribute& ca)
- {
- header += fmtValue(provLeft->getColumnLabel(ca.type_));
- header += CSV_SEP;
- });
- std::for_each(colAttrMiddle.begin(), colAttrMiddle.end(),
- [&](const Grid::ColumnAttribute& ca)
- {
- header += fmtValue(provMiddle->getColumnLabel(ca.type_));
- header += CSV_SEP;
- });
- if (!colAttrRight.empty())
- {
- std::for_each(colAttrRight.begin(), colAttrRight.end() - 1,
- [&](const Grid::ColumnAttribute& ca)
- {
- header += fmtValue(provRight->getColumnLabel(ca.type_));
- header += CSV_SEP;
- });
- header += fmtValue(provRight->getColumnLabel(colAttrRight.back().type_));
- }
- header += '\n';
-
- try
- {
- //write file
- FileOutput fileOut(filename, zen::FileOutput::ACC_OVERWRITE); //throw FileError
-
- replace(header, '\n', LINE_BREAK);
- fileOut.write(&*header.begin(), header.size()); //throw FileError
-
- //main grid: write rows one after the other instead of creating one big string: memory allocation might fail; think 1 million rows!
- /*
- performance test case "export 600.000 rows" to CSV:
- aproach 1. assemble single temporary string, then write file: 4.6s
- aproach 2. write to buffered file output directly for each row: 6.4s
- */
- const size_t rowCount = m_gridMainL->getRowCount();
- for (size_t row = 0; row < rowCount; ++row)
- {
- Utf8String tmp;
-
- std::for_each(colAttrLeft.begin(), colAttrLeft.end(),
- [&](const Grid::ColumnAttribute& ca)
- {
- tmp += fmtValue(provLeft->getValue(row, ca.type_));
- tmp += CSV_SEP;
- });
- std::for_each(colAttrMiddle.begin(), colAttrMiddle.end(),
- [&](const Grid::ColumnAttribute& ca)
- {
- tmp += fmtValue(provMiddle->getValue(row, ca.type_));
- tmp += CSV_SEP;
- });
- std::for_each(colAttrRight.begin(), colAttrRight.end(),
- [&](const Grid::ColumnAttribute& ca)
- {
- tmp += fmtValue(provRight->getValue(row, ca.type_));
- tmp += CSV_SEP;
- });
- tmp += '\n';
-
- replace(tmp, '\n', LINE_BREAK);
- fileOut.write(&*tmp.begin(), tmp.size()); //throw FileError
- }
-
- flashStatusInformation(_("File list exported"));
- }
- catch (const FileError& e)
- {
- showNotificationDialog(this, DialogInfoType::ERROR2, PopupDialogCfg().setDetailInstructions(e.toString()));
- }
- }
-}
-
-
-void MainDialog::OnMenuCheckVersion(wxCommandEvent& event)
-{
- zen::checkForUpdateNow(this);
-}
-
-
-void MainDialog::OnMenuCheckVersionAutomatically(wxCommandEvent& event)
-{
- globalCfg.gui.lastUpdateCheck = globalCfg.gui.lastUpdateCheck == -1 ? 0 : -1;
- m_menuItemCheckVersionAuto->Check(globalCfg.gui.lastUpdateCheck != -1);
- zen::checkForUpdatePeriodically(this, globalCfg.gui.lastUpdateCheck, [&] { flashStatusInformation(_("Searching for program updates...")); });
-}
-
-
-void MainDialog::OnRegularUpdateCheck(wxIdleEvent& event)
-{
- //execute just once per startup!
- Disconnect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnRegularUpdateCheck), nullptr, this);
-
- if (manualProgramUpdateRequired())
- zen::checkForUpdatePeriodically(this, globalCfg.gui.lastUpdateCheck, [&] { flashStatusInformation(_("Searching for program updates...")); });
-}
-
-
-void MainDialog::OnLayoutWindowAsync(wxIdleEvent& event)
-{
- //execute just once per startup!
- Disconnect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnLayoutWindowAsync), nullptr, this);
-
-#ifdef ZEN_WIN
- wxWindowUpdateLocker dummy(this); //leads to GUI corruption problems on Linux/OS X!
-#endif
-
- //adjust folder pair distortion on startup
- std::for_each(additionalFolderPairs.begin(), additionalFolderPairs.end(),
- [](FolderPairPanel* panel) { panel->Layout(); });
-
- m_panelTopButtons->Layout();
- Layout(); //strangely this layout call works if called in next idle event only
- auiMgr.Update(); //fix view filter distortion
-}
-
-
-void MainDialog::OnMenuAbout(wxCommandEvent& event)
-{
- zen::showAboutDialog(this);
-}
-
-
-void MainDialog::OnShowHelp(wxCommandEvent& event)
-{
- zen::displayHelpEntry(this);
-}
-
-//#########################################################################################################
-
-//language selection
-void MainDialog::switchProgramLanguage(int langID)
-{
- //create new dialog with respect to new language
- xmlAccess::XmlGlobalSettings newGlobalCfg = getGlobalCfgBeforeExit();
- newGlobalCfg.programLanguage = langID;
-
- //show new dialog, then delete old one
- MainDialog::create(getConfig(), activeConfigFiles, &newGlobalCfg, false);
-
- //we don't use Close():
- //1. we don't want to show the prompt to save current config in OnClose()
- //2. after getGlobalCfgBeforeExit() the old main dialog is invalid so we want to force deletion
- Destroy();
-}
-
-
-void MainDialog::OnMenuLanguageSwitch(wxCommandEvent& event)
-{
- std::map<MenuItemID, LanguageID>::const_iterator it = languageMenuItemMap.find(event.GetId());
- if (it != languageMenuItemMap.end())
- switchProgramLanguage(it->second);
-}
-
-//#########################################################################################################
-
-void MainDialog::setViewTypeSyncAction(bool value)
-{
- //if (m_bpButtonViewTypeSyncAction->isActive() == value) return; support polling -> what about initialization?
-
- m_bpButtonViewTypeSyncAction->setActive(value);
- m_bpButtonViewTypeSyncAction->SetToolTip((value ? _("Action") : _("Category")) + L" (F9)");
-
- //toggle display of sync preview in middle grid
- gridview::highlightSyncAction(*m_gridMainC, value);
-
- updateGui();
-}
bgstack15