summaryrefslogtreecommitdiff
path: root/ui/main_dlg.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ui/main_dlg.cpp')
-rw-r--r--ui/main_dlg.cpp3852
1 files changed, 3852 insertions, 0 deletions
diff --git a/ui/main_dlg.cpp b/ui/main_dlg.cpp
new file mode 100644
index 00000000..cc9568fb
--- /dev/null
+++ b/ui/main_dlg.cpp
@@ -0,0 +1,3852 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2010 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+//
+#include "main_dlg.h"
+#include <wx/filename.h>
+#include <stdexcept>
+#include "../shared/system_constants.h"
+#include <wx/clipbrd.h>
+#include <wx/dataobj.h>
+#include <iterator>
+#include <wx/ffile.h>
+#include "../library/custom_grid.h"
+#include "../shared/custom_button.h"
+#include "../shared/custom_combo_box.h"
+#include <wx/msgdlg.h>
+#include "../comparison.h"
+#include "../synchronization.h"
+#include "../algorithm.h"
+#include "../shared/app_main.h"
+#include "../shared/util.h"
+#include "check_version.h"
+#include "gui_status_handler.h"
+#include "sync_cfg.h"
+#include "../shared/localization.h"
+#include "../shared/string_conv.h"
+#include "small_dlgs.h"
+#include "mouse_move_dlg.h"
+#include "progress_indicator.h"
+#include "msg_popup.h"
+#include "../shared/drag_n_drop.h"
+#include "../library/filter.h"
+#include "../structures.h"
+#include <wx/imaglist.h>
+#include <wx/wupdlock.h>
+#include "grid_view.h"
+#include "../library/resources.h"
+#include "../shared/file_handling.h"
+#include "../shared/recycler.h"
+#include "../shared/xml_base.h"
+#include "../shared/standard_paths.h"
+#include "../shared/toggle_button.h"
+#include "folder_pair.h"
+#include "../shared/global_func.h"
+#include <wx/sound.h>
+#include "search.h"
+#include "../shared/help_provider.h"
+#include "is_null_filter.h"
+#include "batch_config.h"
+#include "../shared/check_exist.h"
+#include <wx/display.h>
+
+using namespace ffs3;
+using ffs3::CustomLocale;
+
+
+class MainFolderDragDrop : public DragDropOnMainDlg
+{
+public:
+ MainFolderDragDrop(MainDialog& mainDlg,
+ wxWindow* dropWindow1,
+ wxWindow* dropWindow2,
+ wxDirPickerCtrl* dirPicker,
+ wxComboBox* dirName) :
+
+ DragDropOnMainDlg(dropWindow1, dropWindow2, dirPicker, dirName),
+ mainDlg_(mainDlg) {}
+
+ virtual bool AcceptDrop(const std::vector<wxString>& droppedFiles)
+ {
+ if (droppedFiles.empty())
+ return true;
+
+ switch (xmlAccess::getMergeType(droppedFiles)) //throw ()
+ {
+ case xmlAccess::MERGE_BATCH:
+ if (droppedFiles.size() == 1)
+ {
+ BatchDialog* batchDlg = new BatchDialog(&mainDlg_, droppedFiles[0]);
+ if (batchDlg->ShowModal() == BatchDialog::BATCH_FILE_SAVED)
+ mainDlg_.pushStatusInformation(_("Batch file created successfully!"));
+ return false;
+ }
+ //fall-through for multiple *.ffs_batch files!
+
+ case xmlAccess::MERGE_GUI:
+ case xmlAccess::MERGE_GUI_BATCH:
+ if (droppedFiles.size() == 1)
+ {
+ mainDlg_.loadConfiguration(droppedFiles[0]);
+ return false;
+ }
+ else
+ {
+ xmlAccess::XmlGuiConfig guiCfg;
+ try
+ {
+ convertConfig(droppedFiles, guiCfg); //throw (xmlAccess::XmlError)
+ }
+ catch (const xmlAccess::XmlError& error)
+ {
+ if (error.getSeverity() == xmlAccess::XmlError::WARNING)
+ wxMessageBox(error.msg(), _("Warning"), wxOK | wxICON_WARNING);
+ else
+ {
+ wxMessageBox(error.msg(), _("Error"), wxOK | wxICON_ERROR);
+ return false;
+ }
+ }
+ mainDlg_.setCurrentConfiguration(guiCfg);
+ return false;
+ }
+
+ case xmlAccess::MERGE_OTHER:
+ //=> return true: change directory selection via drag and drop
+ break;
+ }
+
+
+ //disable the sync button
+ mainDlg_.syncPreview->enableSynchronization(false);
+
+ //clear grids
+ mainDlg_.gridDataView->clearAllRows();
+ mainDlg_.updateGuiGrid();
+
+ return true;
+ }
+
+private:
+ MainFolderDragDrop(const MainFolderDragDrop&);
+
+ MainDialog& mainDlg_;
+};
+
+//------------------------------------------------------------------
+/* class hierarchy:
+
+ template<>
+ FolderPairPanelBasic
+ /|\
+ |
+ template<>
+ FolderPairCallback FolderPairGenerated
+ /|\ /|\
+ _________|________ ________|
+ | | |
+ FirstFolderPairCfg 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 FolderPairGenerated part...
+ mainDlg(mainDialog) {}
+
+private:
+ virtual void OnLocalFilterCfgRemoveConfirm(wxCommandEvent& event)
+ {
+ FolderPairPanelBasic<GuiPanel>::OnLocalFilterCfgRemoveConfirm(event);
+ mainDlg.updateFilterConfig(); //update filter
+ }
+
+ virtual void OnAltSyncCfgRemoveConfirm(wxCommandEvent& event)
+ {
+ FolderPairPanelBasic<GuiPanel>::OnAltSyncCfgRemoveConfirm(event);
+ mainDlg.updateSyncConfig();
+ }
+
+ virtual wxWindow* getParentWindow()
+ {
+ return &mainDlg;
+ }
+
+ virtual MainConfiguration getMainConfig() const
+ {
+ return mainDlg.getCurrentConfiguration().mainCfg;
+ }
+
+ virtual void OnAltSyncCfgChange()
+ {
+ mainDlg.updateSyncConfig();
+ }
+
+ virtual void OnLocalFilterCfgChange()
+ {
+ mainDlg.updateFilterConfig(); //re-apply filter
+ }
+
+ MainDialog& mainDlg;
+};
+
+
+class FolderPairPanel :
+ public FolderPairGenerated, //FolderPairPanel "owns" FolderPairGenerated!
+ public FolderPairCallback<FolderPairGenerated>
+{
+public:
+ FolderPairPanel(wxWindow* parent, MainDialog& mainDialog) :
+ FolderPairGenerated(parent),
+ FolderPairCallback<FolderPairGenerated>(static_cast<FolderPairGenerated&>(*this), mainDialog), //pass FolderPairGenerated part...
+ dragDropOnLeft( m_panelLeft, m_dirPickerLeft, m_directoryLeft),
+ dragDropOnRight(m_panelRight, m_dirPickerRight, m_directoryRight) {}
+
+private:
+ //support for drag and drop
+ DragDropOnDlg dragDropOnLeft;
+ DragDropOnDlg dragDropOnRight;
+};
+
+
+class FirstFolderPairCfg : public FolderPairCallback<MainDialogGenerated>
+{
+public:
+ FirstFolderPairCfg(MainDialog& mainDialog) :
+ FolderPairCallback<MainDialogGenerated>(mainDialog, mainDialog),
+
+ //prepare drag & drop
+ dragDropOnLeft(mainDialog,
+ mainDialog.m_panelLeft,
+ mainDialog.m_panelTopLeft,
+ mainDialog.m_dirPickerLeft,
+ mainDialog.m_directoryLeft),
+ dragDropOnRight(mainDialog,
+ mainDialog.m_panelRight,
+ mainDialog.m_panelTopRight,
+ mainDialog.m_dirPickerRight,
+ mainDialog.m_directoryRight) {}
+
+private:
+ //support for drag and drop
+ MainFolderDragDrop dragDropOnLeft;
+ MainFolderDragDrop dragDropOnRight;
+};
+
+
+//workaround for wxWidgets: small hack to update menu items: actually this is a wxWidgets bug (affects Windows- and Linux-build)
+class MenuItemUpdater
+{
+public:
+ MenuItemUpdater(wxMenu* menuToUpdate) : menuToUpdate_(menuToUpdate) {}
+
+ ~MenuItemUpdater()
+ {
+ //start updating menu icons
+ const wxMenuItemList& allItems = menuToUpdate_->GetMenuItems();
+
+ //retrieve menu item positions: unfortunately wxMenu doesn't offer a better way
+ MenuItemMap::iterator j;
+ int index = 0;
+ for (wxMenuItemList::const_iterator i = allItems.begin(); i != allItems.end(); ++i, ++index)
+ if ((j = menuItems.find(*i)) != menuItems.end())
+ j->second = index;
+
+ //finally update items
+ for (MenuItemMap::const_iterator i = menuItems.begin(); i != menuItems.end(); ++i)
+ if (i->second >= 0)
+ {
+ menuToUpdate_->Remove(i->first); //actual workaround
+ menuToUpdate_->Insert(i->second, i->first); //
+ }
+ }
+
+ void addForUpdate(wxMenuItem* newEntry, const wxBitmap& newBitmap)
+ {
+ newEntry->SetBitmap(newBitmap);
+ menuItems.insert(std::pair<wxMenuItem*, int>(newEntry, -1));
+ }
+
+private:
+ typedef std::map<wxMenuItem*, int> MenuItemMap;
+ wxMenu* menuToUpdate_;
+ MenuItemMap menuItems;
+};
+
+
+struct DirNotFound
+{
+ bool operator()(const FolderPairEnh& fp) const
+ {
+ return !dirExists(ffs3::getFormattedDirectoryName(fp.leftDirectory)) ||
+ !dirExists(ffs3::getFormattedDirectoryName(fp.rightDirectory));
+ }
+};
+
+
+//##################################################################################################################################
+MainDialog::MainDialog(const wxString& cfgFileName, xmlAccess::XmlGlobalSettings& settings) :
+ MainDialogGenerated(NULL)
+{
+ xmlAccess::XmlGuiConfig guiCfg; //structure to receive gui settings, already defaulted!!
+
+ const wxString currentConfigFile = cfgFileName.empty() ? lastConfigFileName() : cfgFileName;
+
+ bool loadCfgSuccess = false;
+ if (!cfgFileName.empty() || fileExists(wxToZ(lastConfigFileName())))
+ {
+ //load XML
+ try
+ {
+ std::vector<wxString> filenames;
+ filenames.push_back(currentConfigFile);
+
+ xmlAccess::convertConfig(filenames, guiCfg); //throw (xmlAccess::XmlError)
+
+ loadCfgSuccess = true;
+ }
+ catch (const xmlAccess::XmlError& error)
+ {
+ if (error.getSeverity() == xmlAccess::XmlError::WARNING)
+ wxMessageBox(error.msg(), _("Warning"), wxOK | wxICON_WARNING);
+ else
+ wxMessageBox(error.msg(), _("Error"), wxOK | wxICON_ERROR);
+ }
+ }
+
+ init(guiCfg,
+ settings,
+ !cfgFileName.empty() && loadCfgSuccess);
+
+ setLastUsedConfig(currentConfigFile, loadCfgSuccess ? guiCfg : xmlAccess::XmlGuiConfig()); //simulate changed config on parsing errors
+}
+
+
+MainDialog::MainDialog(const xmlAccess::XmlGuiConfig& guiCfg,
+ xmlAccess::XmlGlobalSettings& settings,
+ bool startComparison) :
+ MainDialogGenerated(NULL)
+{
+ init(guiCfg,
+ settings,
+ startComparison);
+}
+
+
+MainDialog::~MainDialog()
+{
+ //keep non-inline destructor for std::auto_ptr to work with forward declaration
+
+ cleanUp(true); //do NOT include any other code here! cleanUp() is re-used when switching languages
+}
+
+
+void MainDialog::init(const xmlAccess::XmlGuiConfig guiCfg,
+ xmlAccess::XmlGlobalSettings& settings,
+ bool startComparison)
+{
+ wxWindowUpdateLocker dummy(this); //avoid display distortion
+
+ //--------- avoid mirroring this dialog in RTL languages like Hebrew or Arabic --------------------
+ m_panelViewFilter->SetLayoutDirection(wxLayout_LeftToRight);
+ m_panelStatusBar->SetLayoutDirection(wxLayout_LeftToRight);
+// if (GetLayoutDirection() == wxLayout_RightToLeft)
+//{
+// bSizerGridHolder->Detach(m_panelRight);
+// bSizerGridHolder->Detach(m_panelLeft);
+// bSizerGridHolder->Add(m_panelLeft);
+// bSizerGridHolder->Prepend(m_panelRight);
+//bSizerGridHolder->Fit(this);
+//
+// bSizerGridHolder->Layout();
+//}
+//------------------------------------------------------------------------------------------------------
+
+ globalSettings = &settings;
+ gridDataView.reset(new ffs3::GridView);
+ contextMenu.reset(new wxMenu); //initialize right-click context menu; will be dynamically re-created on each R-mouse-click
+
+ compareStatus.reset(new CompareStatus(*this));
+ cleanedUp = false;
+ lastSortColumn = -1;
+ lastSortGrid = NULL;
+
+ updateFileIcons.reset(new IconUpdater(m_gridLeft, m_gridRight));
+
+#ifdef FFS_WIN
+ moveWholeWindow.reset(new MouseMoveWindow(this));
+#endif
+ syncPreview.reset(new SyncPreview(this));
+
+ SetTitle(wxString(wxT("FreeFileSync - ")) + _("Folder Comparison and Synchronization"));
+
+ SetIcon(*GlobalResources::getInstance().programIcon); //set application icon
+
+
+ //notify about (logical) application main window => program won't quit, but stay on this dialog
+ ffs3::AppMainWindow::setMainWindow(this);
+
+ //init handling of first folder pair
+ firstFolderPair.reset(new FirstFolderPairCfg(*this));
+
+ initViewFilterButtons();
+
+ //initialize and load configuration
+ readGlobalSettings();
+ setCurrentConfiguration(guiCfg);
+
+ //set icons for this dialog
+ m_bpButton10->SetBitmapLabel(GlobalResources::getInstance().getImageByName(wxT("exit")));
+ m_buttonCompare->setBitmapFront(GlobalResources::getInstance().getImageByName(wxT("compare")));
+ m_bpButtonSyncConfig->SetBitmapLabel(GlobalResources::getInstance().getImageByName(wxT("syncConfig")));
+ m_bpButtonCmpConfig->SetBitmapLabel(GlobalResources::getInstance().getImageByName(wxT("cmpConfig")));
+ m_bpButtonSave->SetBitmapLabel(GlobalResources::getInstance().getImageByName(wxT("save")));
+ m_bpButtonLoad->SetBitmapLabel(GlobalResources::getInstance().getImageByName(wxT("load")));
+ m_bpButtonAddPair->SetBitmapLabel(GlobalResources::getInstance().getImageByName(wxT("addFolderPair")));
+ m_bitmap15->SetBitmap(GlobalResources::getInstance().getImageByName(wxT("statusEdge")));
+
+ m_bitmapCreate->SetBitmap(GlobalResources::getInstance().getImageByName(wxT("create")));
+ m_bitmapUpdate->SetBitmap(GlobalResources::getInstance().getImageByName(wxT("update")));
+ m_bitmapDelete->SetBitmap(GlobalResources::getInstance().getImageByName(wxT("delete")));
+ m_bitmapData->SetBitmap(GlobalResources::getInstance().getImageByName(wxT("data")));
+
+ bSizer6->Layout(); //wxButtonWithImage size might have changed
+
+ //menu icons: workaround for wxWidgets: small hack to update menu items: actually this is a wxWidgets bug (affects Windows- and Linux-build)
+ MenuItemUpdater updateMenuFile(m_menuFile);
+ updateMenuFile.addForUpdate(m_menuItem10, GlobalResources::getInstance().getImageByName(wxT("compareSmall")));
+ updateMenuFile.addForUpdate(m_menuItem11, GlobalResources::getInstance().getImageByName(wxT("syncSmall")));
+ updateMenuFile.addForUpdate(m_menuItemNew, GlobalResources::getInstance().getImageByName(wxT("newSmall")));
+ updateMenuFile.addForUpdate(m_menuItemSave, GlobalResources::getInstance().getImageByName(wxT("saveSmall")));
+ updateMenuFile.addForUpdate(m_menuItemLoad, GlobalResources::getInstance().getImageByName(wxT("loadSmall")));
+
+ MenuItemUpdater updateMenuAdv(m_menuAdvanced);
+ updateMenuAdv.addForUpdate(m_menuItemGlobSett, GlobalResources::getInstance().getImageByName(wxT("settingsSmall")));
+ updateMenuAdv.addForUpdate(m_menuItem7, GlobalResources::getInstance().getImageByName(wxT("batchSmall")));
+
+ MenuItemUpdater updateMenuHelp(m_menuHelp);
+ updateMenuHelp.addForUpdate(m_menuItemAbout, GlobalResources::getInstance().getImageByName(wxT("aboutSmall")));
+
+#ifdef FFS_WIN
+ //allow moving main dialog by clicking (nearly) anywhere...
+ moveWholeWindow->connectSourceWindow(m_panel71);
+ moveWholeWindow->connectSourceWindow(m_panelBottom);
+ moveWholeWindow->connectSourceWindow(m_panelStatusBar);
+#endif
+
+#ifdef FFS_LINUX
+ if (!ffs3::isPortableVersion()) //disable update check for Linux installer-based version -> handled by .deb
+ m_menuItemCheckVer->Enable(false);
+#endif
+
+ //create language selection menu
+ for (std::vector<LocInfoLine>::const_iterator i = LocalizationInfo::getMapping().begin(); i != LocalizationInfo::getMapping().end(); ++i)
+ {
+ wxMenuItem* newItem = new wxMenuItem(m_menuLanguages, wxID_ANY, i->languageName, wxEmptyString, wxITEM_NORMAL );
+ newItem->SetBitmap(GlobalResources::getInstance().getImageByName(i->languageFlag));
+
+ //map menu item IDs with language IDs: evaluated when processing event handler
+ languageMenuItemMap.insert(std::map<MenuItemID, LanguageID>::value_type(newItem->GetId(), i->languageID));
+
+ //connect event
+ Connect(newItem->GetId(), wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnMenuLanguageSwitch));
+ m_menuLanguages->Append(newItem);
+ }
+
+ //support for CTRL + C and DEL on grids
+ m_gridLeft->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridLeftButtonEvent), NULL, this);
+ m_gridRight->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridRightButtonEvent), NULL, this);
+ m_gridMiddle->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridMiddleButtonEvent), NULL, this);
+
+ Connect(wxEVT_IDLE, wxEventHandler(MainDialog::OnIdleEvent), NULL, this);
+ Connect(wxEVT_SIZE, wxSizeEventHandler(MainDialog::OnResize), NULL, this);
+ Connect(wxEVT_MOVE, wxSizeEventHandler(MainDialog::OnResize), NULL, this);
+
+ m_bpButtonFilter->Connect(wxEVT_RIGHT_DOWN, wxCommandEventHandler(MainDialog::OnGlobalFilterOpenContext), NULL, this);
+
+ //calculate witdh of folder pair manually (if scrollbars are visible)
+ m_scrolledWindowFolderPairs->Connect(wxEVT_SIZE, wxSizeEventHandler(MainDialog::OnResizeFolderPairs), NULL, this);
+
+ //event handler for manual (un-)checking of rows and setting of sync direction
+ m_gridMiddle->Connect(FFS_CHECK_ROWS_EVENT, FFSCheckRowsEventHandler(MainDialog::OnCheckRows), NULL, this);
+ m_gridMiddle->Connect(FFS_SYNC_DIRECTION_EVENT, FFSSyncDirectionEventHandler(MainDialog::OnSetSyncDirection), NULL, this);
+
+ //init grid settings
+ m_gridLeft ->initSettings(m_gridLeft, m_gridMiddle, m_gridRight, gridDataView.get());
+ m_gridMiddle->initSettings(m_gridLeft, m_gridMiddle, m_gridRight, gridDataView.get());
+ m_gridRight ->initSettings(m_gridLeft, m_gridMiddle, m_gridRight, gridDataView.get());
+
+ //disable sync button as long as "compare" hasn't been triggered.
+ syncPreview->enableSynchronization(false);
+
+ //mainly to update row label sizes...
+ updateGuiGrid();
+
+ //integrate the compare status panel (in hidden state)
+ bSizer1->Insert(1, compareStatus->getAsWindow(), 0, wxEXPAND | wxBOTTOM, 5 );
+ Layout(); //avoid screen flicker when panel is shown later
+ compareStatus->getAsWindow()->Hide();
+
+ //correct width of swap button above middle grid
+ const wxSize source = m_gridMiddle->GetSize();
+ const wxSize target = m_bpButtonSwapSides->GetSize();
+ const int spaceToAdd = source.GetX() - target.GetX();
+ bSizerMiddle->Insert(1, spaceToAdd / 2, 0, 0);
+ bSizerMiddle->Insert(0, spaceToAdd - (spaceToAdd / 2), 0, 0);
+
+ //register regular check for update on next idle event
+ Connect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnRegularUpdateCheck), NULL, 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), NULL, this);
+
+//----------------------------------------------------------------------------------------------------------------------------------------------------------------
+ //some convenience: if FFS is started with a *.ffs_gui file as commandline parameter AND all directories contained exist, comparison shall be started right off
+ if (startComparison)
+ {
+ const ffs3::MainConfiguration currMainCfg = getCurrentConfiguration().mainCfg;
+ const bool allFoldersExist = !DirNotFound()(currMainCfg.firstPair) &&
+ std::find_if(currMainCfg.additionalPairs.begin(), currMainCfg.additionalPairs.end(),
+ DirNotFound()) == currMainCfg.additionalPairs.end();
+ if (allFoldersExist)
+ {
+ wxCommandEvent dummy2(wxEVT_COMMAND_BUTTON_CLICKED);
+ m_buttonCompare->GetEventHandler()->AddPendingEvent(dummy2); //simulate button click on "compare"
+ }
+ }
+//----------------------------------------------------------------------------------------------------------------------------------------------------------------
+}
+
+
+void MainDialog::cleanUp(bool saveLastUsedConfig)
+{
+ if (!cleanedUp)
+ {
+ cleanedUp = true;
+
+ //no need for wxEventHandler::Disconnect() here; done automatically when window is destoyed!
+
+ m_gridLeft ->release(); //handle wxGrid-related callback on grid data after MainDialog has died... (Linux only)
+ m_gridMiddle->release();
+ m_gridRight ->release();
+
+ //save configuration
+ if (saveLastUsedConfig)
+ writeConfigurationToXml(lastConfigFileName()); //don't throw exceptions in destructors
+ writeGlobalSettings();
+ }
+}
+
+
+void MainDialog::readGlobalSettings()
+{
+ //apply window size and position at program startup ONLY
+ widthNotMaximized = globalSettings->gui.widthNotMaximized;
+ heightNotMaximized = globalSettings->gui.heightNotMaximized;
+ posXNotMaximized = globalSettings->gui.posXNotMaximized;
+ posYNotMaximized = globalSettings->gui.posYNotMaximized;
+
+ //apply window size and position
+ if ( widthNotMaximized != wxDefaultCoord &&
+ heightNotMaximized != wxDefaultCoord &&
+ posXNotMaximized != wxDefaultCoord &&
+ posYNotMaximized != wxDefaultCoord &&
+ wxDisplay::GetFromPoint(wxPoint(posXNotMaximized, posYNotMaximized)) != wxNOT_FOUND) //make sure upper left corner is in visible view
+ SetSize(posXNotMaximized, posYNotMaximized, widthNotMaximized, heightNotMaximized);
+ else
+ Centre();
+
+ Maximize(globalSettings->gui.isMaximized);
+
+
+ //set column attributes
+ m_gridLeft->setColumnAttributes(globalSettings->gui.columnAttribLeft);
+ m_gridRight->setColumnAttributes(globalSettings->gui.columnAttribRight);
+
+ //load list of last used configuration files (in reverse order)
+ for (std::vector<wxString>::reverse_iterator i = globalSettings->gui.cfgFileHistory.rbegin();
+ i != globalSettings->gui.cfgFileHistory.rend();
+ ++i)
+ addFileToCfgHistory(*i);
+
+ //load list of last used folders
+ for (std::vector<wxString>::reverse_iterator i = globalSettings->gui.folderHistoryLeft.rbegin();
+ i != globalSettings->gui.folderHistoryLeft.rend();
+ ++i)
+ addLeftFolderToHistory(*i);
+ for (std::vector<wxString>::reverse_iterator i = globalSettings->gui.folderHistoryRight.rbegin();
+ i != globalSettings->gui.folderHistoryRight.rend();
+ ++i)
+ addRightFolderToHistory(*i);
+
+ //show/hide file icons
+ m_gridLeft->enableFileIcons(globalSettings->gui.showFileIconsLeft);
+ m_gridRight->enableFileIcons(globalSettings->gui.showFileIconsRight);
+
+ //set selected tab
+ m_notebookBottomLeft->ChangeSelection(globalSettings->gui.selectedTabBottomLeft);
+}
+
+
+void MainDialog::writeGlobalSettings()
+{
+ //write global settings to (global) variable stored in application instance
+ globalSettings->gui.widthNotMaximized = widthNotMaximized;
+ globalSettings->gui.heightNotMaximized = heightNotMaximized;
+ globalSettings->gui.posXNotMaximized = posXNotMaximized;
+ globalSettings->gui.posYNotMaximized = posYNotMaximized;
+ globalSettings->gui.isMaximized = IsMaximized();
+
+ //retrieve column attributes
+ globalSettings->gui.columnAttribLeft = m_gridLeft->getColumnAttributes();
+ globalSettings->gui.columnAttribRight = m_gridRight->getColumnAttributes();
+
+ //write list of last used configuration files
+ globalSettings->gui.cfgFileHistory = cfgFileNames;
+
+ //write list of last used folders
+ globalSettings->gui.folderHistoryLeft.clear();
+ const wxArrayString leftFolderHistory = m_directoryLeft->GetStrings();
+ for (unsigned i = 0; i < leftFolderHistory.GetCount(); ++i)
+ globalSettings->gui.folderHistoryLeft.push_back(leftFolderHistory[i]);
+
+ globalSettings->gui.folderHistoryRight.clear();
+ const wxArrayString rightFolderHistory = m_directoryRight->GetStrings();
+ for (unsigned i = 0; i < rightFolderHistory.GetCount(); ++i)
+ globalSettings->gui.folderHistoryRight.push_back(rightFolderHistory[i]);
+
+ //get selected tab
+ globalSettings->gui.selectedTabBottomLeft = m_notebookBottomLeft->GetSelection();
+}
+
+
+void MainDialog::setSyncDirManually(const std::set<size_t>& rowsToSetOnUiTable, const ffs3::SyncDirection dir)
+{
+ if (rowsToSetOnUiTable.size() > 0)
+ {
+ for (std::set<size_t>::const_iterator i = rowsToSetOnUiTable.begin(); i != rowsToSetOnUiTable.end(); ++i)
+ {
+ FileSystemObject* fsObj = gridDataView->getObject(*i);
+ if (fsObj)
+ {
+ setSyncDirectionRec(dir, *fsObj); //set new direction (recursively)
+ ffs3::setActiveStatus(true, *fsObj); //works recursively for directories
+ }
+ }
+
+ updateGuiGrid();
+ }
+}
+
+
+void MainDialog::filterRangeManually(const std::set<size_t>& rowsToFilterOnUiTable, int leadingRow)
+{
+ if (rowsToFilterOnUiTable.size() > 0)
+ {
+ bool newSelection = false; //default: deselect range
+
+ //leadingRow determines de-/selection of all other rows
+ const FileSystemObject* fsObj = gridDataView->getObject(leadingRow);
+ if (fsObj)
+ newSelection = !fsObj->isActive();
+
+ //if hidefiltered is active, there should be no filtered elements on screen => current element was filtered out
+ assert(!currentCfg.hideFilteredElements || !newSelection);
+
+ //get all lines that need to be filtered
+ std::vector<FileSystemObject*> compRef;
+ gridDataView->getAllFileRef(rowsToFilterOnUiTable, compRef); //everything in compRef is bound
+
+ for (std::vector<FileSystemObject*>::iterator i = compRef.begin(); i != compRef.end(); ++i)
+ ffs3::setActiveStatus(newSelection, **i); //works recursively for directories
+
+ refreshGridAfterFilterChange(400); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true and to handle some graphical artifacts
+ }
+}
+
+
+void MainDialog::OnIdleEvent(wxEvent& event)
+{
+ //small routine to restore status information after some time
+ if (stackObjects.size() > 0 ) //check if there is some work to do
+ {
+ wxLongLong currentTime = wxGetLocalTimeMillis();
+ if (currentTime - lastStatusChange > 2500) //restore stackObject after two seconds
+ {
+ lastStatusChange = currentTime;
+
+ m_staticTextStatusMiddle->SetLabel(stackObjects.top());
+ stackObjects.pop();
+
+ if (stackObjects.empty())
+ m_staticTextStatusMiddle->SetForegroundColour(*wxBLACK); //reset color
+
+ m_panelStatusBar->Layout();
+ }
+ }
+
+ event.Skip();
+}
+
+
+void MainDialog::copySelectionToClipboard(const CustomGrid* selectedGrid)
+{
+ const std::set<size_t> selectedRows = getSelectedRows(selectedGrid);
+ if (selectedRows.size() > 0)
+ {
+ wxString clipboardString;
+
+ for (std::set<size_t>::const_iterator i = selectedRows.begin(); i != selectedRows.end(); ++i)
+ {
+ for (int k = 0; k < const_cast<CustomGrid*>(selectedGrid)->GetNumberCols(); ++k)
+ {
+ clipboardString+= const_cast<CustomGrid*>(selectedGrid)->GetCellValue(static_cast<int>(*i), k);
+ if (k != const_cast<CustomGrid*>(selectedGrid)->GetNumberCols() - 1)
+ clipboardString+= '\t';
+ }
+ clipboardString+= '\n';
+ }
+
+ if (!clipboardString.IsEmpty())
+ // Write text to the clipboard
+ if (wxTheClipboard->Open())
+ {
+ // these data objects are held by the clipboard,
+ // so do not delete them in the app.
+ wxTheClipboard->SetData( new wxTextDataObject(clipboardString) );
+ wxTheClipboard->Close();
+ }
+ }
+}
+
+
+std::set<size_t> MainDialog::getSelectedRows(const CustomGrid* grid) const
+{
+ std::set<size_t> output = grid->getAllSelectedRows();
+
+ //remove invalid rows
+ output.erase(output.lower_bound(gridDataView->rowsOnView()), output.end());
+
+ return output;
+}
+
+
+std::set<size_t> MainDialog::getSelectedRows() const
+{
+ //merge selections from left and right grid
+ std::set<size_t> selection = getSelectedRows(m_gridLeft);
+ std::set<size_t> additional = getSelectedRows(m_gridRight);
+ selection.insert(additional.begin(), additional.end());
+ return selection;
+}
+
+
+class ManualDeletionHandler : private wxEvtHandler, public DeleteFilesHandler
+{
+public:
+ ManualDeletionHandler(MainDialog* main, size_t totalObjToDel) :
+ mainDlg(main),
+ totalObjToDelete(totalObjToDel),
+ abortRequested(false),
+ ignoreErrors(false),
+ deletionCount(0)
+ {
+ mainDlg->disableAllElements(); //disable everything except abort button
+ mainDlg->clearStatusBar();
+
+ //register abort button
+ mainDlg->m_buttonAbort->Connect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ManualDeletionHandler::OnAbortCompare ), NULL, this );
+ }
+
+ ~ManualDeletionHandler()
+ {
+ //de-register abort button
+ mainDlg->m_buttonAbort->Disconnect( wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler( ManualDeletionHandler::OnAbortCompare ), NULL, this );
+
+ mainDlg->enableAllElements();
+ }
+
+ virtual Response reportError(const wxString& errorMessage)
+ {
+ if (abortRequested)
+ throw ffs3::AbortThisProcess();
+
+ if (ignoreErrors)
+ return DeleteFilesHandler::IGNORE_ERROR;
+
+ bool ignoreNextErrors = false;
+ ErrorDlg* errorDlg = new ErrorDlg(NULL,
+ ErrorDlg::BUTTON_IGNORE | ErrorDlg::BUTTON_RETRY | ErrorDlg::BUTTON_ABORT,
+ errorMessage, ignoreNextErrors);
+ const int rv = errorDlg->ShowModal();
+ errorDlg->Destroy();
+ switch (static_cast<ErrorDlg::ReturnCodes>(rv))
+ {
+ case ErrorDlg::BUTTON_IGNORE:
+ ignoreErrors = ignoreNextErrors;
+ return DeleteFilesHandler::IGNORE_ERROR;
+ case ErrorDlg::BUTTON_RETRY:
+ return DeleteFilesHandler::RETRY;
+ case ErrorDlg::BUTTON_ABORT:
+ throw ffs3::AbortThisProcess();
+ }
+
+ assert (false);
+ return DeleteFilesHandler::IGNORE_ERROR; //dummy return value
+ }
+
+ virtual void deletionSuccessful() //called for each file/folder that has been deleted
+ {
+ ++deletionCount;
+
+ if (updateUiIsAllowed()) //test if specific time span between ui updates is over
+ {
+ wxString statusMessage = _("%x / %y objects deleted successfully");
+ statusMessage.Replace(wxT("%x"), ffs3::numberToStringSep(deletionCount), false);
+ statusMessage.Replace(wxT("%y"), ffs3::numberToStringSep(totalObjToDelete), false);
+
+ mainDlg->m_staticTextStatusMiddle->SetLabel(statusMessage);
+ mainDlg->m_panelStatusBar->Layout();
+
+ updateUiNow();
+ }
+
+ if (abortRequested) //test after (implicit) call to wxApp::Yield()
+ throw ffs3::AbortThisProcess();
+ }
+
+private:
+ void OnAbortCompare(wxCommandEvent& event) //handle abort button click
+ {
+ abortRequested = true; //don't throw exceptions in a GUI-Callback!!! (throw ffs3::AbortThisProcess())
+ }
+
+ MainDialog* const mainDlg;
+ const size_t totalObjToDelete;
+
+ bool abortRequested;
+ bool ignoreErrors;
+ size_t deletionCount;
+};
+
+
+void MainDialog::deleteSelectedFiles()
+{
+ //get set of selected rows on view
+ const std::set<size_t> viewSelectionLeft = getSelectedRows(m_gridLeft);
+ const std::set<size_t> viewSelectionRight = getSelectedRows(m_gridRight);
+
+ if (viewSelectionLeft.size() + viewSelectionRight.size())
+ {
+ //map lines from GUI view to grid line references
+ std::vector<FileSystemObject*> compRefLeft;
+ gridDataView->getAllFileRef(viewSelectionLeft, compRefLeft);
+
+ std::vector<FileSystemObject*> compRefRight;
+ gridDataView->getAllFileRef(viewSelectionRight, compRefRight);
+
+
+ int totalDeleteCount = 0;
+
+ if (ffs3::showDeleteDialog(compRefLeft,
+ compRefRight,
+ globalSettings->gui.deleteOnBothSides,
+ globalSettings->gui.useRecyclerForManualDeletion,
+ totalDeleteCount) == DefaultReturnCode::BUTTON_OKAY)
+ {
+ if (globalSettings->gui.useRecyclerForManualDeletion && !ffs3::recycleBinExists())
+ {
+ wxMessageBox(_("Recycle Bin not yet supported for this system!"));
+ return;
+ }
+
+ try
+ {
+ //handle errors when deleting files/folders
+ ManualDeletionHandler statusHandler(this, totalDeleteCount);
+
+ ffs3::deleteFromGridAndHD(gridDataView->getDataTentative(),
+ compRefLeft,
+ compRefRight,
+ globalSettings->gui.deleteOnBothSides,
+ globalSettings->gui.useRecyclerForManualDeletion,
+ getCurrentConfiguration().mainCfg,
+ &statusHandler);
+ }
+ catch (ffs3::AbortThisProcess&) {}
+
+ //remove rows that 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
+ updateGuiGrid(); //call immediately after deleteFromGridAndHD!!!
+
+ m_gridLeft-> ClearSelection();
+ m_gridMiddle->ClearSelection();
+ m_gridRight-> ClearSelection();
+ }
+ }
+}
+
+
+template <SelectedSide side>
+void exstractNames(const FileSystemObject& fsObj, wxString& name, wxString& dir)
+{
+ if (!fsObj.isEmpty<side>())
+ {
+ struct GetNames : public FSObjectVisitor
+ {
+ GetNames(wxString& nameIn, wxString& dirIn) : name_(nameIn), dir_(dirIn) {}
+ virtual void visit(const FileMapping& fileObj)
+ {
+ name_ = zToWx(fileObj.getFullName<side>());
+ dir_ = zToWx(fileObj.getFullName<side>().BeforeLast(common::FILE_NAME_SEPARATOR));
+ }
+ virtual void visit(const SymLinkMapping& linkObj)
+ {
+ name_ = zToWx(linkObj.getFullName<side>());
+ dir_ = zToWx(linkObj.getFullName<side>().BeforeLast(common::FILE_NAME_SEPARATOR));
+ }
+ virtual void visit(const DirMapping& dirObj)
+ {
+ dir_ = name_ = zToWx(dirObj.getFullName<side>());
+ }
+
+ wxString& name_;
+ wxString& dir_;
+ ;
+ } getNames(name, dir);
+ fsObj.accept(getNames);
+ }
+ else
+ {
+ name.clear();
+ dir.clear();
+ }
+}
+
+
+void MainDialog::openExternalApplication(size_t rowNumber, bool leftSide, const wxString& commandline)
+{
+ if (commandline.empty())
+ return;
+
+ wxString command = commandline;
+
+ wxString name;
+ wxString dir;
+ wxString nameCo;
+ wxString dirCo;
+
+ const FileSystemObject* fsObj = gridDataView->getObject(rowNumber);
+ if (fsObj)
+ {
+ if (leftSide)
+ {
+ exstractNames<LEFT_SIDE>( *fsObj, name, dir);
+ exstractNames<RIGHT_SIDE>(*fsObj, nameCo, dirCo);
+ }
+ else
+ {
+ exstractNames<RIGHT_SIDE>(*fsObj, name, dir);
+ exstractNames<LEFT_SIDE>( *fsObj, nameCo, dirCo);
+ }
+#ifdef FFS_WIN
+ if (name.empty())
+ {
+ if (leftSide)
+ wxExecute(wxString(wxT("explorer ")) + zToWx(fsObj->getBaseDirPf<LEFT_SIDE>()));
+ else
+ wxExecute(wxString(wxT("explorer ")) + zToWx(fsObj->getBaseDirPf<RIGHT_SIDE>()));
+ return;
+ }
+#endif
+ }
+ else
+ {
+ //fallback
+ dir = zToWx(ffs3::getFormattedDirectoryName(firstFolderPair->getLeftDir()));
+ dirCo = zToWx(ffs3::getFormattedDirectoryName(firstFolderPair->getRightDir()));
+
+ if (!leftSide)
+ std::swap(dir, dirCo);
+
+#ifdef FFS_WIN
+ wxExecute(wxString(wxT("explorer ")) + dir); //default
+ return;
+#endif
+ }
+
+ command.Replace(wxT("%nameCo"), nameCo, true); //attention: replace %nameCo, %dirCo BEFORE %name, %dir to handle dependency
+ command.Replace(wxT("%dirCo"), dirCo, true);
+ command.Replace(wxT("%name"), name, true);
+ command.Replace(wxT("%dir"), dir, true);
+
+ wxExecute(command);
+}
+
+
+void MainDialog::pushStatusInformation(const wxString& text)
+{
+ lastStatusChange = wxGetLocalTimeMillis();
+ stackObjects.push(m_staticTextStatusMiddle->GetLabel());
+ m_staticTextStatusMiddle->SetLabel(text);
+ m_staticTextStatusMiddle->SetForegroundColour(wxColour(31, 57, 226)); //highlight color: blue
+ m_panelStatusBar->Layout();
+}
+
+
+void MainDialog::clearStatusBar()
+{
+ while (stackObjects.size() > 0)
+ stackObjects.pop();
+
+ m_staticTextStatusMiddle->SetForegroundColour(*wxBLACK); //reset color
+ m_staticTextStatusLeft->SetLabel(wxEmptyString);
+ m_staticTextStatusMiddle->SetLabel(wxEmptyString);
+ m_staticTextStatusRight->SetLabel(wxEmptyString);
+}
+
+
+void MainDialog::disableAllElements()
+{
+ //disenables all elements (except abort button) that might receive user input during long-running processes: comparison, deletion
+ m_bpButtonCmpConfig-> Disable();
+ m_notebookBottomLeft->Disable();
+ m_checkBoxHideFilt-> Disable();
+ m_bpButtonSyncConfig->Disable();
+ m_buttonStartSync-> Disable();
+ m_dirPickerLeft-> Disable();
+ m_dirPickerRight-> Disable();
+ m_bpButtonSwapSides-> Disable();
+ m_bpButtonLeftOnly-> Disable();
+ m_bpButtonLeftNewer-> Disable();
+ m_bpButtonEqual-> Disable();
+ m_bpButtonDifferent-> Disable();
+ m_bpButtonRightNewer->Disable();
+ m_bpButtonRightOnly-> Disable();
+ m_panelLeft-> Disable();
+ m_panelMiddle-> Disable();
+ m_panelRight-> Disable();
+ m_panelTopLeft-> Disable();
+ m_panelTopMiddle-> Disable();
+ m_panelTopRight-> Disable();
+ m_bpButton10-> Disable();
+ m_scrolledWindowFolderPairs->Disable();
+ m_menubar1->EnableTop(0, false);
+ m_menubar1->EnableTop(1, false);
+ m_menubar1->EnableTop(2, false);
+ EnableCloseButton(false);
+
+ //show abort button
+ m_buttonAbort->Enable();
+ m_buttonAbort->Show();
+ m_buttonCompare->Disable();
+ m_buttonCompare->Hide();
+ m_buttonAbort->SetFocus();
+}
+
+
+void MainDialog::enableAllElements()
+{
+ m_bpButtonCmpConfig-> Enable();
+ m_notebookBottomLeft->Enable();
+ m_checkBoxHideFilt-> Enable();
+ m_bpButtonSyncConfig->Enable();
+ m_buttonStartSync-> Enable();
+ m_dirPickerLeft-> Enable();
+ m_dirPickerRight-> Enable();
+ m_bpButtonSwapSides-> Enable();
+ m_bpButtonLeftOnly-> Enable();
+ m_bpButtonLeftNewer-> Enable();
+ m_bpButtonEqual-> Enable();
+ m_bpButtonDifferent-> Enable();
+ m_bpButtonRightNewer->Enable();
+ m_bpButtonRightOnly-> Enable();
+ m_panelLeft-> Enable();
+ m_panelMiddle-> Enable();
+ m_panelRight-> Enable();
+ m_panelTopLeft-> Enable();
+ m_panelTopMiddle-> Enable();
+ m_panelTopRight-> Enable();
+ m_bpButton10-> Enable();
+ m_scrolledWindowFolderPairs->Enable();
+ m_menubar1->EnableTop(0, true);
+ m_menubar1->EnableTop(1, true);
+ m_menubar1->EnableTop(2, true);
+ EnableCloseButton(true);
+
+ //show compare button
+ m_buttonAbort->Disable();
+ m_buttonAbort->Hide();
+ m_buttonCompare->Enable();
+ m_buttonCompare->Show();
+}
+
+
+void MainDialog::OnResize(wxSizeEvent& event)
+{
+ if (!IsMaximized())
+ {
+ int width = 0;
+ int height = 0;
+ int x = 0;
+ int y = 0;
+
+ GetSize(&width, &height);
+ GetPosition(&x, &y);
+
+ if (width > 0 && height > 0 && x >= -200 && y >= -200) //test ALL parameters at once, since width/height are invalid if
+ {
+ //the window is minimized (eg x,y == -32000; height = 28, width = 160)
+ widthNotMaximized = width; //however visible(!) x < 0 and y < 0 are possible with dual monitors
+ heightNotMaximized = height;
+
+ posXNotMaximized = x;
+ posYNotMaximized = y;
+ }
+ }
+
+ event.Skip();
+}
+
+
+void MainDialog::OnResizeFolderPairs(wxSizeEvent& event)
+{
+ //adapt left-shift display distortion caused by scrollbars for multiple folder pairs
+ if (additionalFolderPairs.size() > 0)
+ {
+ const int width = m_panelTopLeft->GetSize().GetWidth();
+
+ for (std::vector<FolderPairPanel*>::iterator i = additionalFolderPairs.begin(); i != additionalFolderPairs.end(); ++i)
+ (*i)->m_panelLeft->SetMinSize(wxSize(width, -1));
+ }
+
+ event.Skip();
+}
+
+
+void MainDialog::onGridLeftButtonEvent(wxKeyEvent& event)
+{
+ const int keyCode = event.GetKeyCode();
+
+ if (event.ControlDown())
+ switch (keyCode)
+ {
+ case 'C':
+ case WXK_INSERT: //CTRL + C || CTRL + INS
+ copySelectionToClipboard(m_gridLeft);
+ break;
+
+ case 'A': //CTRL + A
+ m_gridLeft->SelectAll();
+ break;
+
+ case 'F': //CTRL + F
+ ffs3::startFind(*this, *m_gridLeft, *m_gridRight, globalSettings->gui.textSearchRespectCase);
+ break;
+
+ case WXK_NUMPAD_ADD: //CTRL + '+'
+ m_gridLeft->autoSizeColumns();
+ break;
+ }
+
+ else if (event.AltDown())
+ switch (keyCode)
+ {
+ case WXK_LEFT: //ALT + <-
+ {
+ wxCommandEvent dummy;
+ OnContextSyncDirLeft(dummy);
+ }
+ break;
+
+ case WXK_RIGHT: //ALT + ->
+ {
+ wxCommandEvent dummy;
+ OnContextSyncDirRight(dummy);
+ }
+ break;
+
+ case WXK_UP: /* ALT + /|\ */
+ case WXK_DOWN: /* ALT + \|/ */
+ {
+ wxCommandEvent dummy;
+ OnContextSyncDirNone(dummy);
+ }
+ break;
+ }
+
+ else
+ switch (keyCode)
+ {
+ case WXK_DELETE:
+ case WXK_NUMPAD_DELETE:
+ deleteSelectedFiles();
+ break;
+
+ case WXK_SPACE:
+ {
+ wxCommandEvent dummy;
+ OnContextFilterTemp(dummy);
+ }
+ break;
+
+ case WXK_RETURN:
+ case WXK_NUMPAD_ENTER:
+ {
+ wxCommandEvent dummy(wxEVT_NULL, externalAppIDFirst); //open with first external application
+ OnContextOpenWith(dummy);
+ }
+ break;
+
+ case WXK_F3: //F3
+ case WXK_NUMPAD_F3: //
+ ffs3::findNext(*this, *m_gridLeft, *m_gridRight, globalSettings->gui.textSearchRespectCase);
+ break;
+ }
+
+ //event.Skip(); -> swallow event! don't allow default grid commands!
+}
+
+
+void MainDialog::onGridMiddleButtonEvent(wxKeyEvent& event)
+{
+ const int keyCode = event.GetKeyCode();
+
+ if (event.ControlDown())
+ {
+ if (keyCode == 67 || keyCode == WXK_INSERT) //CTRL + C || CTRL + INS
+ copySelectionToClipboard(m_gridMiddle);
+ }
+
+ //event.Skip(); -> swallow event! don't allow default grid commands!
+}
+
+
+void MainDialog::onGridRightButtonEvent(wxKeyEvent& event)
+{
+ const int keyCode = event.GetKeyCode();
+
+ if (event.ControlDown())
+ switch (keyCode)
+ {
+ case 'C':
+ case WXK_INSERT: //CTRL + C || CTRL + INS
+ copySelectionToClipboard(m_gridRight);
+ break;
+
+ case 'A': //CTRL + A
+ m_gridRight->SelectAll();
+ break;
+
+ case 'F': //CTRL + F
+ ffs3::startFind(*this, *m_gridLeft, *m_gridRight, globalSettings->gui.textSearchRespectCase);
+ break;
+
+ case WXK_NUMPAD_ADD: //CTRL + '+'
+ m_gridRight->autoSizeColumns();
+ break;
+ }
+
+ else if (event.AltDown())
+ switch (keyCode)
+ {
+ case WXK_LEFT: //ALT + <-
+ {
+ wxCommandEvent dummy;
+ OnContextSyncDirLeft(dummy);
+ }
+ break;
+
+ case WXK_RIGHT: //ALT + ->
+ {
+ wxCommandEvent dummy;
+ OnContextSyncDirRight(dummy);
+ }
+ break;
+
+ case WXK_UP: /* ALT + /|\ */
+ case WXK_DOWN: /* ALT + \|/ */
+ {
+ wxCommandEvent dummy;
+ OnContextSyncDirNone(dummy);
+ }
+ break;
+ }
+
+ else
+ switch (keyCode)
+ {
+ case WXK_DELETE:
+ case WXK_NUMPAD_DELETE:
+ deleteSelectedFiles();
+ break;
+
+ case WXK_SPACE:
+ {
+ wxCommandEvent dummy;
+ OnContextFilterTemp(dummy);
+ }
+ break;
+
+ case WXK_RETURN:
+ case WXK_NUMPAD_ENTER:
+ {
+ wxCommandEvent dummy(wxEVT_NULL, externalAppIDFirst); //open with first external application
+ OnContextOpenWith(dummy);
+ }
+ break;
+
+ case WXK_F3: //F3
+ case WXK_NUMPAD_F3: //
+ ffs3::findNext(*this, *m_gridLeft, *m_gridRight, globalSettings->gui.textSearchRespectCase);
+ break;
+ }
+
+ //event.Skip(); -> swallow event! don't allow default grid commands!
+}
+
+
+
+//------------------------------------------------------------
+//temporal variables used by exclude via context menu
+struct SelectedExtension : public wxObject
+{
+ SelectedExtension(const Zstring& ext) : extension(ext) {}
+
+ Zstring extension;
+};
+
+struct FilterObject
+{
+ FilterObject(const Zstring& relName, bool isDirectory) :
+ relativeName(relName),
+ isDir(isDirectory) {}
+ Zstring relativeName;
+ bool isDir;
+};
+
+typedef std::vector<FilterObject> FilterObjList;
+
+struct FilterObjContainer : public wxObject
+{
+ FilterObjContainer(const FilterObjList& objList) : selectedObjects(objList) {}
+
+ FilterObjList selectedObjects;
+};
+//------------------------------------------------------------
+
+
+
+void MainDialog::OnContextRim(wxGridEvent& event)
+{
+ //usability: select row unter right-click if not already selected
+ wxGrid* sourceGrid = dynamic_cast<wxGrid*>(event.GetEventObject());
+ if (sourceGrid != NULL)
+ {
+ const int clickedRow = event.GetRow();
+ const int clickedCol = event.GetCol();
+ if (clickedRow >= 0 && clickedCol >= 0 && !sourceGrid->IsInSelection(clickedRow, 0))
+ {
+ sourceGrid->SelectRow(clickedRow);
+ sourceGrid->SetGridCursor(clickedRow, clickedCol);
+
+ if (sourceGrid == m_gridLeft)
+ m_gridRight->ClearSelection();
+ else if (sourceGrid == m_gridRight)
+ m_gridLeft->ClearSelection();
+ }
+ }
+ //------------------------------------------------------------------------------
+
+
+ const std::set<size_t> selectionLeft = getSelectedRows(m_gridLeft);
+ const std::set<size_t> selectionRight = getSelectedRows(m_gridRight);
+
+ const size_t selectionBegin = selectionLeft.size() + selectionRight.size() == 0 ? 0 :
+ selectionLeft.size() == 0 ? *selectionRight.begin() :
+ selectionRight.size() == 0 ? *selectionLeft.begin() :
+ std::min(*selectionLeft.begin(), *selectionRight.begin());
+
+ const FileSystemObject* fsObj = gridDataView->getObject(selectionBegin);
+
+
+ //#######################################################
+ //re-create context menu
+ contextMenu.reset(new wxMenu);
+
+ if (syncPreview->previewIsEnabled() &&
+ fsObj && fsObj->getSyncOperation() != SO_EQUAL)
+ {
+ if (selectionLeft.size() + selectionRight.size() > 0)
+ {
+ //CONTEXT_SYNC_DIR_LEFT
+ wxMenuItem* menuItemSyncDirLeft = new wxMenuItem(contextMenu.get(), CONTEXT_SYNC_DIR_LEFT, wxString(_("Set direction:")) +
+ wxT(" ") + getSymbol(fsObj->testSyncOperation(true, SYNC_DIR_LEFT)) +
+ wxT("\tALT + LEFT")); //Linux needs a direction, "<-", because it has no context menu icons!
+ menuItemSyncDirLeft->SetBitmap(getSyncOpImage(fsObj->testSyncOperation(true, SYNC_DIR_LEFT)));
+ contextMenu->Append(menuItemSyncDirLeft);
+
+ //CONTEXT_SYNC_DIR_NONE
+ wxMenuItem* menuItemSyncDirNone = new wxMenuItem(contextMenu.get(), CONTEXT_SYNC_DIR_NONE, wxString(_("Set direction:")) +
+ wxT(" ") + getSymbol(fsObj->testSyncOperation(true, SYNC_DIR_NONE)) +
+ wxT("\tALT + UP"));
+ menuItemSyncDirNone->SetBitmap(getSyncOpImage(fsObj->testSyncOperation(true, SYNC_DIR_NONE)));
+ contextMenu->Append(menuItemSyncDirNone);
+
+ //CONTEXT_SYNC_DIR_RIGHT
+ wxMenuItem* menuItemSyncDirRight = new wxMenuItem(contextMenu.get(), CONTEXT_SYNC_DIR_RIGHT, wxString(_("Set direction:")) +
+ wxT(" ") + getSymbol(fsObj->testSyncOperation(true, SYNC_DIR_RIGHT)) +
+ wxT("\tALT + RIGHT"));
+ menuItemSyncDirRight->SetBitmap(getSyncOpImage(fsObj->testSyncOperation(true, SYNC_DIR_RIGHT)));
+ contextMenu->Append(menuItemSyncDirRight);
+
+ contextMenu->AppendSeparator();
+ }
+ }
+
+
+ //CONTEXT_FILTER_TEMP
+ if (fsObj && (selectionLeft.size() + selectionRight.size() > 0))
+ {
+ if (fsObj->isActive())
+ {
+ wxMenuItem* menuItemExclTemp = new wxMenuItem(contextMenu.get(), CONTEXT_FILTER_TEMP, wxString(_("Exclude temporarily")) + wxT("\tSPACE"));
+ menuItemExclTemp->SetBitmap(GlobalResources::getInstance().getImageByName(wxT("checkboxFalse")));
+ contextMenu->Append(menuItemExclTemp);
+ }
+ else
+ {
+ wxMenuItem* menuItemInclTemp = new wxMenuItem(contextMenu.get(), CONTEXT_FILTER_TEMP, wxString(_("Include temporarily")) + wxT("\tSPACE"));
+ menuItemInclTemp->SetBitmap(GlobalResources::getInstance().getImageByName(wxT("checkboxTrue")));
+ contextMenu->Append(menuItemInclTemp);
+ }
+ }
+ else
+ {
+ contextMenu->Append(CONTEXT_FILTER_TEMP, wxString(_("Exclude temporarily")) + wxT("\tSPACE")); //this element should always be visible
+ contextMenu->Enable(CONTEXT_FILTER_TEMP, false);
+ }
+
+ //###############################################################################################
+ //get list of relative file/dir-names for filtering
+ FilterObjList exFilterCandidateObj;
+
+ class AddFilter : public FSObjectVisitor
+ {
+ public:
+ AddFilter(FilterObjList& fl) : filterList_(fl) {}
+ virtual void visit(const FileMapping& fileObj)
+ {
+ filterList_.push_back(FilterObject(fileObj.getObjRelativeName(), false));
+ }
+ virtual void visit(const SymLinkMapping& linkObj)
+ {
+ filterList_.push_back(FilterObject(linkObj.getObjRelativeName(), false));
+ }
+ virtual void visit(const DirMapping& dirObj)
+ {
+ filterList_.push_back(FilterObject(dirObj.getObjRelativeName(), true));
+ }
+
+ private:
+ FilterObjList& filterList_;
+ } newFilterEntry(exFilterCandidateObj);
+
+
+ for (std::set<size_t>::const_iterator i = selectionLeft.begin(); i != selectionLeft.end(); ++i)
+ {
+ const FileSystemObject* currObj = gridDataView->getObject(*i);
+ if (currObj && !currObj->isEmpty<LEFT_SIDE>())
+ currObj->accept(newFilterEntry);
+ }
+ for (std::set<size_t>::const_iterator i = selectionRight.begin(); i != selectionRight.end(); ++i)
+ {
+ const FileSystemObject* currObj = gridDataView->getObject(*i);
+ if (currObj && !currObj->isEmpty<RIGHT_SIDE>())
+ currObj->accept(newFilterEntry);
+ }
+ //###############################################################################################
+
+ //CONTEXT_EXCLUDE_EXT
+ if (exFilterCandidateObj.size() > 0 && !exFilterCandidateObj[0].isDir)
+ {
+ const Zstring filename = exFilterCandidateObj[0].relativeName.AfterLast(common::FILE_NAME_SEPARATOR);
+ if (filename.Find(wxChar('.'), false) != Zstring::npos) //be careful: AfterLast would return the whole string if '.' were not found!
+ {
+ const Zstring extension = filename.AfterLast(DefaultChar('.'));
+
+ //add context menu item
+ wxMenuItem* menuItemExclExt = new wxMenuItem(contextMenu.get(), CONTEXT_EXCLUDE_EXT, wxString(_("Exclude via filter:")) + wxT(" ") + wxT("*.") + zToWx(extension));
+ menuItemExclExt->SetBitmap(GlobalResources::getInstance().getImageByName(wxT("filterSmall")));
+ contextMenu->Append(menuItemExclExt);
+
+ //connect event
+ contextMenu->Connect(CONTEXT_EXCLUDE_EXT,
+ wxEVT_COMMAND_MENU_SELECTED,
+ wxCommandEventHandler(MainDialog::OnContextExcludeExtension),
+ new SelectedExtension(extension), //ownership passed!
+ this);
+ }
+ }
+
+
+ //CONTEXT_EXCLUDE_OBJ
+ wxMenuItem* menuItemExclObj = NULL;
+ if (exFilterCandidateObj.size() == 1)
+ menuItemExclObj = new wxMenuItem(contextMenu.get(), CONTEXT_EXCLUDE_OBJ, wxString(_("Exclude via filter:")) + wxT(" ") + zToWx(exFilterCandidateObj[0].relativeName.AfterLast(common::FILE_NAME_SEPARATOR)));
+ else if (exFilterCandidateObj.size() > 1)
+ menuItemExclObj = new wxMenuItem(contextMenu.get(), CONTEXT_EXCLUDE_OBJ, wxString(_("Exclude via filter:")) + wxT(" ") + _("<multiple selection>"));
+
+ if (menuItemExclObj != NULL)
+ {
+ menuItemExclObj->SetBitmap(GlobalResources::getInstance().getImageByName(wxT("filterSmall")));
+ contextMenu->Append(menuItemExclObj);
+
+ //connect event
+ contextMenu->Connect(CONTEXT_EXCLUDE_OBJ,
+ wxEVT_COMMAND_MENU_SELECTED,
+ wxCommandEventHandler(MainDialog::OnContextExcludeObject),
+ new FilterObjContainer(exFilterCandidateObj), //ownership passed!
+ this);
+ }
+
+
+
+ //CONTEXT_EXTERNAL_APP
+ if (!globalSettings->gui.externelApplications.empty())
+ {
+ contextMenu->AppendSeparator();
+
+ const bool externalAppEnabled = (m_gridLeft->isLeadGrid() || m_gridRight->isLeadGrid()) &&
+ (selectionLeft.size() + selectionRight.size() == 1);
+
+ int newID = externalAppIDFirst;
+ for (xmlAccess::ExternalApps::iterator i = globalSettings->gui.externelApplications.begin();
+ i != globalSettings->gui.externelApplications.end();
+ ++i, ++newID)
+ {
+ //some trick to translate default external apps on the fly: 1. "open in explorer" 2. "start directly"
+ wxString description = wxGetTranslation(i->first);
+ if (description.empty())
+ description = wxT(" "); //wxWidgets doesn't like empty items
+
+ if (i == globalSettings->gui.externelApplications.begin())
+ contextMenu->Append(newID, description + wxT("\t") + wxString(_("D-Click")) + wxT("; ENTER"));
+ else
+ contextMenu->Append(newID, description);
+
+ contextMenu->Enable(newID, externalAppEnabled);
+
+ //register event
+ contextMenu->Connect(newID, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextOpenWith), NULL, this);
+ }
+ }
+
+
+ contextMenu->AppendSeparator();
+
+
+ //CONTEXT_CLIPBOARD
+ contextMenu->Append(CONTEXT_CLIPBOARD, _("Copy to clipboard\tCTRL+C"));
+
+ if ( (m_gridLeft->isLeadGrid() && selectionLeft.size()) ||
+ (m_gridRight->isLeadGrid() && selectionRight.size()))
+ contextMenu->Enable(CONTEXT_CLIPBOARD, true);
+ else
+ contextMenu->Enable(CONTEXT_CLIPBOARD, false);
+
+
+ //CONTEXT_DELETE_FILES
+ contextMenu->Append(CONTEXT_DELETE_FILES, _("Delete files\tDEL"));
+
+ if (selectionLeft.size() + selectionRight.size() == 0)
+ contextMenu->Enable(CONTEXT_DELETE_FILES, false);
+
+
+ //###############################################################################################
+ //connect events
+ contextMenu->Connect(CONTEXT_FILTER_TEMP, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextFilterTemp), NULL, this);
+ contextMenu->Connect(CONTEXT_CLIPBOARD, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextCopyClipboard), NULL, this);
+ contextMenu->Connect(CONTEXT_DELETE_FILES, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextDeleteFiles), NULL, this);
+ contextMenu->Connect(CONTEXT_SYNC_DIR_LEFT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextSyncDirLeft), NULL, this);
+ contextMenu->Connect(CONTEXT_SYNC_DIR_NONE, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextSyncDirNone), NULL, this);
+ contextMenu->Connect(CONTEXT_SYNC_DIR_RIGHT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextSyncDirRight), NULL, this);
+
+ //show context menu
+ PopupMenu(contextMenu.get());
+}
+
+
+void MainDialog::OnContextFilterTemp(wxCommandEvent& event)
+{
+ //merge selections from left and right grid
+ std::set<size_t> selection = getSelectedRows();
+ if (!selection.empty())
+ filterRangeManually(selection, static_cast<int>(*selection.begin()));
+}
+
+
+void MainDialog::OnContextExcludeExtension(wxCommandEvent& event)
+{
+ SelectedExtension* selExtension = dynamic_cast<SelectedExtension*>(event.m_callbackUserData);
+ if (selExtension)
+ {
+ const Zstring newExclude = Zstring(DefaultStr("*.")) + selExtension->extension;
+
+ //add to filter config
+ Zstring& excludeFilter = currentCfg.mainCfg.globalFilter.excludeFilter;
+ if (!excludeFilter.empty() && !excludeFilter.EndsWith(DefaultStr(";")))
+ excludeFilter += DefaultStr("\n");
+ excludeFilter += newExclude + DefaultStr(";"); //';' is appended to 'mark' that next exclude extension entry won't write to new line
+
+ updateFilterButtons();
+
+ //do not fully apply filter, just exclude new items
+ addExcludeFiltering(newExclude, gridDataView->getDataTentative());
+ //applyFiltering(getCurrentConfiguration().mainCfg, gridDataView->getDataTentative());
+ updateGuiGrid();
+
+ if (currentCfg.hideFilteredElements)
+ {
+ m_gridLeft-> ClearSelection();
+ m_gridRight-> ClearSelection();
+ m_gridMiddle->ClearSelection();
+ }
+ }
+}
+
+
+void MainDialog::OnContextExcludeObject(wxCommandEvent& event)
+{
+ FilterObjContainer* objCont = dynamic_cast<FilterObjContainer*>(event.m_callbackUserData);
+ if (objCont)
+ {
+ if (objCont->selectedObjects.size() > 0) //check needed to determine if filtering is needed
+ {
+ Zstring newExclude;
+ for (std::vector<FilterObject>::const_iterator i = objCont->selectedObjects.begin(); i != objCont->selectedObjects.end(); ++i)
+ {
+ if (i != objCont->selectedObjects.begin())
+ newExclude += DefaultStr("\n");
+
+ newExclude += common::FILE_NAME_SEPARATOR + i->relativeName;
+ if (i->isDir)
+ newExclude += common::FILE_NAME_SEPARATOR;
+ }
+
+ //add to filter config
+ Zstring& excludeFilter = currentCfg.mainCfg.globalFilter.excludeFilter;
+ if (!excludeFilter.empty() && !excludeFilter.EndsWith(DefaultStr("\n")))
+ excludeFilter += DefaultStr("\n");
+ excludeFilter += newExclude;
+
+ updateFilterButtons();
+
+ //do not fully apply filter, just exclude new items
+ addExcludeFiltering(newExclude, gridDataView->getDataTentative());
+ //applyFiltering(getCurrentConfiguration().mainCfg, gridDataView->getDataTentative());
+ updateGuiGrid();
+
+ if (currentCfg.hideFilteredElements)
+ {
+ m_gridLeft->ClearSelection();
+ m_gridRight->ClearSelection();
+ m_gridMiddle->ClearSelection();
+ }
+ }
+ }
+}
+
+
+void MainDialog::OnContextCopyClipboard(wxCommandEvent& event)
+{
+ if (m_gridLeft->isLeadGrid())
+ copySelectionToClipboard(m_gridLeft);
+ else if (m_gridRight->isLeadGrid())
+ copySelectionToClipboard(m_gridRight);
+}
+
+
+void MainDialog::OnContextOpenWith(wxCommandEvent& event)
+{
+ if (m_gridLeft->isLeadGrid() || m_gridRight->isLeadGrid())
+ {
+ const CustomGrid* leadGrid = m_gridLeft->isLeadGrid() ?
+ static_cast<CustomGrid*>(m_gridLeft) :
+ static_cast<CustomGrid*>(m_gridRight);
+ std::set<size_t> selection = getSelectedRows(leadGrid);
+
+ const int index = event.GetId() - externalAppIDFirst;
+
+ if ( selection.size() == 1 &&
+ 0 <= index && static_cast<size_t>(index) < globalSettings->gui.externelApplications.size())
+ openExternalApplication(*selection.begin(), m_gridLeft->isLeadGrid(), globalSettings->gui.externelApplications[index].second);
+ }
+}
+
+
+void MainDialog::OnContextDeleteFiles(wxCommandEvent& event)
+{
+ deleteSelectedFiles();
+}
+
+
+void MainDialog::OnContextSyncDirLeft(wxCommandEvent& event)
+{
+ //merge selections from left and right grid
+ const std::set<size_t> selection = getSelectedRows();
+ setSyncDirManually(selection, ffs3::SYNC_DIR_LEFT);
+}
+
+
+void MainDialog::OnContextSyncDirNone(wxCommandEvent& event)
+{
+ //merge selections from left and right grid
+ const std::set<size_t> selection = getSelectedRows();
+ setSyncDirManually(selection, ffs3::SYNC_DIR_NONE);
+}
+
+
+void MainDialog::OnContextSyncDirRight(wxCommandEvent& event)
+{
+ //merge selections from left and right grid
+ const std::set<size_t> selection = getSelectedRows();
+ setSyncDirManually(selection, ffs3::SYNC_DIR_RIGHT);
+}
+
+
+void MainDialog::OnContextRimLabelLeft(wxGridEvent& event)
+{
+ contextMenu.reset(new wxMenu); //re-create context menu
+ contextMenu->Append(CONTEXT_CUSTOMIZE_COLUMN_LEFT, _("Customize..."));
+
+ contextMenu->AppendSeparator();
+
+ wxMenuItem* itemAutoAdjust = new wxMenuItem(contextMenu.get(), CONTEXT_AUTO_ADJUST_COLUMN_LEFT, _("Auto-adjust columns"), wxEmptyString, wxITEM_CHECK);
+ contextMenu->Append(itemAutoAdjust);
+ itemAutoAdjust->Check(globalSettings->gui.autoAdjustColumnsLeft);
+
+ contextMenu->Connect(CONTEXT_CUSTOMIZE_COLUMN_LEFT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextCustColumnLeft), NULL, this);
+ contextMenu->Connect(CONTEXT_AUTO_ADJUST_COLUMN_LEFT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextAutoAdjustLeft), NULL, this);
+
+ PopupMenu(contextMenu.get()); //show context menu
+}
+
+
+void MainDialog::OnContextRimLabelRight(wxGridEvent& event)
+{
+ contextMenu.reset(new wxMenu); //re-create context menu
+ contextMenu->Append(CONTEXT_CUSTOMIZE_COLUMN_RIGHT, _("Customize..."));
+
+ contextMenu->AppendSeparator();
+
+ wxMenuItem* itemAutoAdjust = new wxMenuItem(contextMenu.get(), CONTEXT_AUTO_ADJUST_COLUMN_RIGHT, _("Auto-adjust columns"), wxEmptyString, wxITEM_CHECK);
+ contextMenu->Append(itemAutoAdjust);
+ itemAutoAdjust->Check(globalSettings->gui.autoAdjustColumnsRight);
+
+ contextMenu->Connect(CONTEXT_CUSTOMIZE_COLUMN_RIGHT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextCustColumnRight), NULL, this);
+ contextMenu->Connect(CONTEXT_AUTO_ADJUST_COLUMN_RIGHT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextAutoAdjustRight), NULL, this);
+
+ PopupMenu(contextMenu.get()); //show context menu
+}
+
+
+void MainDialog::OnContextCustColumnLeft(wxCommandEvent& event)
+{
+ xmlAccess::ColumnAttributes colAttr = m_gridLeft->getColumnAttributes();
+
+ if (ffs3::showCustomizeColsDlg(colAttr) == DefaultReturnCode::BUTTON_OKAY)
+ {
+ m_gridLeft->setColumnAttributes(colAttr);
+
+ m_gridLeft->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING)); //hide sort direction indicator on GUI grids
+ m_gridMiddle->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING));
+ m_gridRight->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING));
+ }
+}
+
+
+void MainDialog::OnContextCustColumnRight(wxCommandEvent& event)
+{
+ xmlAccess::ColumnAttributes colAttr = m_gridRight->getColumnAttributes();
+
+ if (ffs3::showCustomizeColsDlg(colAttr) == DefaultReturnCode::BUTTON_OKAY)
+ {
+ m_gridRight->setColumnAttributes(colAttr);
+
+ m_gridLeft->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING)); //hide sort direction indicator on GUI grids
+ m_gridMiddle->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING));
+ m_gridRight->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING));
+ }
+}
+
+
+void MainDialog::OnContextAutoAdjustLeft(wxCommandEvent& event)
+{
+ globalSettings->gui.autoAdjustColumnsLeft = !globalSettings->gui.autoAdjustColumnsLeft;
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnContextAutoAdjustRight(wxCommandEvent& event)
+{
+ globalSettings->gui.autoAdjustColumnsRight = !globalSettings->gui.autoAdjustColumnsRight;
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnContextMiddle(wxGridEvent& event)
+{
+ contextMenu.reset(new wxMenu); //re-create context menu
+
+ contextMenu->Append(CONTEXT_CHECK_ALL, _("Include all rows"));
+ contextMenu->Append(CONTEXT_UNCHECK_ALL, _("Exclude all rows"));
+
+ if (gridDataView->rowsTotal() == 0)
+ {
+ contextMenu->Enable(CONTEXT_CHECK_ALL, false);
+ contextMenu->Enable(CONTEXT_UNCHECK_ALL, false);
+ }
+
+ contextMenu->Connect(CONTEXT_CHECK_ALL, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextIncludeAll), NULL, this);
+ contextMenu->Connect(CONTEXT_UNCHECK_ALL, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextExcludeAll), NULL, this);
+
+ PopupMenu(contextMenu.get()); //show context menu
+}
+
+
+void MainDialog::OnContextMiddleLabel(wxGridEvent& event)
+{
+ contextMenu.reset(new wxMenu); //re-create context menu
+
+ wxMenuItem* itemSyncPreview = new wxMenuItem(contextMenu.get(), CONTEXT_SYNC_PREVIEW, _("Synchronization Preview"));
+ wxMenuItem* itemCmpResult = new wxMenuItem(contextMenu.get(), CONTEXT_COMPARISON_RESULT, _("Comparison Result"));
+
+ if (syncPreview->previewIsEnabled())
+ itemSyncPreview->SetBitmap(GlobalResources::getInstance().getImageByName(wxT("syncViewSmall")));
+ else
+ itemCmpResult->SetBitmap(GlobalResources::getInstance().getImageByName(wxT("cmpViewSmall")));
+
+ contextMenu->Append(itemCmpResult);
+ contextMenu->Append(itemSyncPreview);
+
+ contextMenu->Connect(CONTEXT_SYNC_PREVIEW, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextSyncView), NULL, this);
+ contextMenu->Connect(CONTEXT_COMPARISON_RESULT, wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextComparisonView), NULL, this);
+
+ PopupMenu(contextMenu.get()); //show context menu
+}
+
+
+void MainDialog::OnContextIncludeAll(wxCommandEvent& event)
+{
+ ffs3::setActiveStatus(true, gridDataView->getDataTentative());
+ refreshGridAfterFilterChange(0); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true and to handle some graphical artifacts break;
+}
+
+
+void MainDialog::OnContextExcludeAll(wxCommandEvent& event)
+{
+ ffs3::setActiveStatus(false, gridDataView->getDataTentative());
+ refreshGridAfterFilterChange(400); //call this instead of updateGuiGrid() to add some delay if hideFiltered == true and to handle some graphical artifacts
+}
+
+
+void MainDialog::OnContextComparisonView(wxCommandEvent& event)
+{
+ syncPreview->enablePreview(false); //change view
+}
+
+
+void MainDialog::OnContextSyncView(wxCommandEvent& event)
+{
+ syncPreview->enablePreview(true); //change view
+}
+
+
+void MainDialog::OnDirSelected(wxFileDirPickerEvent& event)
+{
+ //left and right directory text-control and dirpicker are synchronized by MainFolderDragDrop automatically
+
+ //disable the sync button
+ syncPreview->enableSynchronization(false);
+
+ //clear grids
+ gridDataView->clearAllRows();
+ updateGuiGrid();
+
+ event.Skip();
+}
+
+
+wxString getFormattedHistoryElement(const wxString& filename)
+{
+ wxString output = wxFileName(filename).GetFullName();
+ if (output.EndsWith(wxT(".ffs_gui")))
+ output = output.BeforeLast('.');
+ return output;
+}
+
+
+//tests if the same filenames are specified, even if they are relative to the current working directory/include symlinks or \\?\ prefix
+class FindDuplicates
+{
+public:
+ FindDuplicates(const Zstring& name) : m_name(name) {}
+
+ bool operator()(const wxString& other) const
+ {
+ return util::sameFileSpecified(m_name, wxToZ(other));
+ }
+
+private:
+ const Zstring& m_name;
+};
+
+
+void MainDialog::addFileToCfgHistory(const wxString& filename)
+{
+ //only (still) existing files should be included in the list
+ if (util::fileExists(wxToZ(filename), 200) == util::EXISTING_FALSE) //potentially slow network access: wait 200ms
+ return;
+
+ std::vector<wxString>::const_iterator i = find_if(cfgFileNames.begin(), cfgFileNames.end(), FindDuplicates(wxToZ(filename)));
+ if (i != cfgFileNames.end())
+ {
+ //if entry is in the list, then jump to element
+ m_choiceHistory->SetSelection(i - cfgFileNames.begin());
+ }
+ else
+ {
+ cfgFileNames.insert(cfgFileNames.begin(), filename);
+
+ //the default config file should receive another name on GUI
+ if (util::sameFileSpecified(wxToZ(lastConfigFileName()), wxToZ(filename)))
+ m_choiceHistory->Insert(_("<Last session>"), 0); //insert at beginning of list
+ else
+ m_choiceHistory->Insert(getFormattedHistoryElement(filename), 0); //insert at beginning of list
+
+ m_choiceHistory->SetSelection(0);
+ }
+
+ //keep maximal size of history list
+ if (cfgFileNames.size() > globalSettings->gui.cfgHistoryMax)
+ {
+ //delete last rows
+ cfgFileNames.pop_back();
+ m_choiceHistory->Delete(globalSettings->gui.cfgHistoryMax);
+ }
+}
+
+
+void MainDialog::addLeftFolderToHistory(const wxString& leftFolder)
+{
+ m_directoryLeft->addPairToFolderHistory(leftFolder, globalSettings->gui.folderHistLeftMax);
+}
+
+
+void MainDialog::addRightFolderToHistory(const wxString& rightFolder)
+{
+ m_directoryRight->addPairToFolderHistory(rightFolder, globalSettings->gui.folderHistRightMax);
+}
+
+
+void MainDialog::OnSaveConfig(wxCommandEvent& event)
+{
+ trySaveConfig();
+}
+
+
+bool MainDialog::trySaveConfig() //return true if saved successfully
+{
+ wxString defaultFileName = currentConfigFileName.empty() ? wxT("SyncSettings.ffs_gui") : currentConfigFileName;
+ //attention: currentConfigFileName may be an imported *.ffs_batch file! We don't want to overwrite it with a GUI config!
+ if (defaultFileName.EndsWith(wxT(".ffs_batch")))
+ defaultFileName.Replace(wxT(".ffs_batch"), wxT(".ffs_gui"), false);
+
+
+ wxFileDialog* filePicker = new wxFileDialog(this, wxEmptyString, wxEmptyString, defaultFileName, wxString(_("FreeFileSync configuration")) + wxT(" (*.ffs_gui)|*.ffs_gui"), wxFD_SAVE);
+ if (filePicker->ShowModal() == wxID_OK)
+ {
+ const wxString newFileName = filePicker->GetPath();
+
+ if (ffs3::fileExists(wxToZ(newFileName)))
+ {
+ QuestionDlg* messageDlg = new QuestionDlg(this,
+ QuestionDlg::BUTTON_YES | QuestionDlg::BUTTON_CANCEL,
+ wxString(_("File already exists. Overwrite?")) + wxT(" \"") + newFileName + wxT("\""));
+
+ if (messageDlg->ShowModal() != QuestionDlg::BUTTON_YES)
+ return trySaveConfig(); //retry
+ }
+
+ if (writeConfigurationToXml(newFileName))
+ {
+ pushStatusInformation(_("Configuration saved!"));
+ return true;
+ }
+ }
+
+ return false;
+}
+
+
+void MainDialog::OnLoadConfig(wxCommandEvent& event)
+{
+ wxFileDialog* filePicker = new wxFileDialog(this,
+ wxEmptyString,
+ wxEmptyString,
+ wxEmptyString,
+ wxString(_("FreeFileSync configuration")) + wxT(" (*.ffs_gui;*.ffs_batch)|*.ffs_gui;*.ffs_batch"), wxFD_OPEN);
+
+ if (filePicker->ShowModal() == wxID_OK)
+ loadConfiguration(filePicker->GetPath());
+}
+
+
+void MainDialog::OnNewConfig(wxCommandEvent& event)
+{
+ if (!saveOldConfig()) //notify user about changed settings
+ return;
+
+ setCurrentConfiguration(xmlAccess::XmlGuiConfig());
+
+ SetTitle(wxString(wxT("FreeFileSync - ")) + _("Folder Comparison and Synchronization"));
+ currentConfigFileName.clear();
+}
+
+
+void MainDialog::OnLoadFromHistory(wxCommandEvent& event)
+{
+ const int selectedItem = m_choiceHistory->GetSelection();
+ if (0 <= selectedItem && unsigned(selectedItem) < cfgFileNames.size())
+ loadConfiguration(cfgFileNames[selectedItem]);
+}
+
+
+bool MainDialog::saveOldConfig() //return false on user abort
+{
+ //notify user about changed settings
+ if (globalSettings->optDialogs.popupOnConfigChange && !currentConfigFileName.empty()) //only if check is active and non-default config file loaded
+ {
+ if (lastConfigurationSaved != getCurrentConfiguration())
+ {
+ bool dontShowAgain = !globalSettings->optDialogs.popupOnConfigChange;
+
+ QuestionDlg* notifyChangeDlg = new QuestionDlg(this,
+ QuestionDlg::BUTTON_YES | QuestionDlg::BUTTON_NO | QuestionDlg::BUTTON_CANCEL,
+ _("Save changes to current configuration?"),
+ &dontShowAgain);
+
+ switch (notifyChangeDlg->ShowModal())
+ {
+ case QuestionDlg::BUTTON_YES:
+ if (!trySaveConfig())
+ return false;
+ break;
+ case QuestionDlg::BUTTON_NO:
+ globalSettings->optDialogs.popupOnConfigChange = !dontShowAgain;
+ break;
+ case QuestionDlg::BUTTON_CANCEL:
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+
+void MainDialog::loadConfiguration(const wxString& filename)
+{
+ if (!filename.IsEmpty())
+ {
+ if (!saveOldConfig())
+ return;
+
+ if (readConfigurationFromXml(filename))
+ pushStatusInformation(_("Configuration loaded!"));
+ }
+}
+
+
+void MainDialog::OnCfgHistoryKeyEvent(wxKeyEvent& event)
+{
+ const int keyCode = event.GetKeyCode();
+ if (keyCode == WXK_DELETE || keyCode == WXK_NUMPAD_DELETE)
+ {
+ //try to delete the currently selected config history item
+ const int selectedItem = m_choiceHistory->GetCurrentSelection();
+ if ( 0 <= selectedItem &&
+ selectedItem < int(m_choiceHistory->GetCount()) &&
+ selectedItem < int(cfgFileNames.size()))
+ {
+ //delete selected row
+ cfgFileNames.erase(cfgFileNames.begin() + selectedItem);
+ m_choiceHistory->Delete(selectedItem);
+ }
+ }
+ event.Skip();
+}
+
+
+void MainDialog::OnClose(wxCloseEvent &event)
+{
+ if (!saveOldConfig()) //notify user about changed settings
+ return;
+
+ Destroy();
+}
+
+
+void MainDialog::OnQuit(wxCommandEvent &event)
+{
+ if (!saveOldConfig()) //notify user about changed settings
+ return;
+
+ Destroy();
+}
+
+
+void MainDialog::OnCheckRows(FFSCheckRowsEvent& event)
+{
+ const int lowerBound = std::min(event.rowFrom, event.rowTo);
+ const int upperBound = std::max(event.rowFrom, event.rowTo);
+
+ if (0 <= lowerBound)
+ {
+ std::set<size_t> selectedRowsOnView;
+
+ for (int i = lowerBound; i <= std::min(upperBound, int(gridDataView->rowsOnView()) - 1); ++i)
+ selectedRowsOnView.insert(i);
+
+ filterRangeManually(selectedRowsOnView, event.rowFrom);
+ }
+}
+
+
+void MainDialog::OnSetSyncDirection(FFSSyncDirectionEvent& event)
+{
+ const int lowerBound = std::min(event.rowFrom, event.rowTo);
+ const int upperBound = std::max(event.rowFrom, event.rowTo);
+
+ if (0 <= lowerBound)
+ {
+ for (int i = lowerBound; i <= std::min(upperBound, int(gridDataView->rowsOnView()) - 1); ++i)
+ {
+ FileSystemObject* fsObj = gridDataView->getObject(i);
+ if (fsObj)
+ {
+ setSyncDirectionRec(event.direction, *fsObj); //set new direction (recursively)
+ ffs3::setActiveStatus(true, *fsObj); //works recursively for directories
+ }
+ }
+
+ updateGuiGrid();
+ }
+}
+
+
+bool MainDialog::readConfigurationFromXml(const wxString& filename)
+{
+ //load XML
+ xmlAccess::XmlGuiConfig newGuiCfg; //structure to receive gui settings, already defaulted!!
+ bool parsingError = true;
+ try
+ {
+ //allow reading batch configurations also
+ std::vector<wxString> filenames;
+ filenames.push_back(filename);
+
+ xmlAccess::convertConfig(filenames, newGuiCfg); //throw (xmlAccess::XmlError)
+
+ parsingError = false;
+ }
+ catch (const xmlAccess::XmlError& error)
+ {
+ if (error.getSeverity() == xmlAccess::XmlError::WARNING)
+ wxMessageBox(error.msg(), _("Warning"), wxOK | wxICON_WARNING);
+ else
+ {
+ wxMessageBox(error.msg(), _("Error"), wxOK | wxICON_ERROR);
+ return false;
+ }
+ }
+
+ setCurrentConfiguration(newGuiCfg);
+
+ setLastUsedConfig(filename, parsingError ? xmlAccess::XmlGuiConfig() : newGuiCfg); //simulate changed config on parsing errors
+
+ return !parsingError;
+}
+
+
+void MainDialog::setLastUsedConfig(const wxString& filename, const xmlAccess::XmlGuiConfig& guiConfig)
+{
+ addFileToCfgHistory(filename); //put filename on list of last used config files
+
+ //set title
+ if (filename == lastConfigFileName())
+ {
+ SetTitle(wxString(wxT("FreeFileSync - ")) + _("Folder Comparison and Synchronization"));
+ currentConfigFileName.clear();
+ }
+ else
+ {
+ SetTitle(wxString(wxT("FreeFileSync - ")) + filename);
+ currentConfigFileName = filename;
+ }
+
+ lastConfigurationSaved = guiConfig;
+}
+
+
+bool MainDialog::writeConfigurationToXml(const wxString& filename)
+{
+ const xmlAccess::XmlGuiConfig guiCfg = getCurrentConfiguration();
+
+ //write config to XML
+ try
+ {
+ xmlAccess::writeConfig(guiCfg, filename);
+ }
+ catch (const xmlAccess::XmlError& error)
+ {
+ wxMessageBox(error.msg().c_str(), _("Error"), wxOK | wxICON_ERROR);
+ return false;
+ }
+
+ setLastUsedConfig(filename, guiCfg);
+
+ return true;
+}
+
+
+void MainDialog::setCurrentConfiguration(const xmlAccess::XmlGuiConfig& newGuiCfg)
+{
+ currentCfg = newGuiCfg;
+
+ //evaluate new settings...
+
+ //disable the sync button
+ syncPreview->enableSynchronization(false);
+
+ //clear grids
+ gridDataView->clearAllRows();
+ updateGuiGrid();
+
+ //(re-)set view filter buttons
+ initViewFilterButtons();
+
+ updateFilterButtons();
+
+ //set first folder pair
+ firstFolderPair->setValues(currentCfg.mainCfg.firstPair.leftDirectory,
+ currentCfg.mainCfg.firstPair.rightDirectory,
+ currentCfg.mainCfg.firstPair.altSyncConfig,
+ currentCfg.mainCfg.firstPair.localFilter);
+
+ addLeftFolderToHistory( zToWx(currentCfg.mainCfg.firstPair.leftDirectory)); //another hack: wxCombobox::Insert() asynchronously sends message
+ addRightFolderToHistory(zToWx(currentCfg.mainCfg.firstPair.rightDirectory)); //overwriting a later wxCombobox::SetValue()!!! :(
+
+ //clear existing additional folder pairs
+ clearAddFolderPairs();
+
+ //set additional pairs
+ addFolderPair(currentCfg.mainCfg.additionalPairs);
+
+
+ //read GUI layout
+ m_checkBoxHideFilt->SetValue(currentCfg.hideFilteredElements);
+
+ syncPreview->enablePreview(currentCfg.syncPreviewEnabled);
+
+ //###########################################################
+ //update compare variant name
+ m_staticTextCmpVariant->SetLabel(wxString(wxT("(")) + getVariantName(currentCfg.mainCfg.compareVar) + wxT(")"));
+
+ //update sync variant name
+ m_staticTextSyncVariant->SetLabel(wxString(wxT("(")) + currentCfg.mainCfg.getSyncVariantName() + wxT(")"));
+ bSizer6->Layout(); //adapt layout for variant text
+}
+
+
+inline
+FolderPairEnh getEnahncedPair(const FolderPairPanel* panel)
+{
+ return FolderPairEnh(panel->getLeftDir(),
+ panel->getRightDir(),
+ panel->getAltSyncConfig(),
+ panel->getAltFilterConfig());
+}
+
+
+xmlAccess::XmlGuiConfig MainDialog::getCurrentConfiguration() 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->getAltSyncConfig(),
+ firstFolderPair->getAltFilterConfig());
+
+ //add additional pairs
+ guiCfg.mainCfg.additionalPairs.clear();
+ std::transform(additionalFolderPairs.begin(), additionalFolderPairs.end(),
+ std::back_inserter(guiCfg.mainCfg.additionalPairs), getEnahncedPair);
+
+ //sync preview
+ guiCfg.syncPreviewEnabled = syncPreview->previewIsEnabled();
+
+ return guiCfg;
+}
+
+
+const wxString& MainDialog::lastConfigFileName()
+{
+ static wxString instance = ffs3::getConfigDir() + wxT("LastRun.ffs_gui");
+ return instance;
+}
+
+
+void MainDialog::refreshGridAfterFilterChange(const int delay)
+{
+ //signal UI that grids need to be refreshed on next Update()
+ m_gridLeft->ForceRefresh();
+ m_gridMiddle->ForceRefresh();
+ m_gridRight->ForceRefresh();
+ Update(); //show changes resulting from ForceRefresh()
+
+ if (currentCfg.hideFilteredElements)
+ {
+ wxMilliSleep(delay); //some delay to show user the rows he has filtered out before they are removed
+ updateGuiGrid(); //redraw grid to remove excluded elements (keeping sort sequence)
+
+ m_gridLeft->ClearSelection();
+ m_gridRight->ClearSelection();
+ }
+ else
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnHideFilteredButton(wxCommandEvent &event)
+{
+ //toggle showing filtered rows
+ currentCfg.hideFilteredElements = !currentCfg.hideFilteredElements;
+ //make sure, checkbox and "hideFiltered" are in sync
+ m_checkBoxHideFilt->SetValue(currentCfg.hideFilteredElements);
+
+ m_gridLeft->ClearSelection();
+ m_gridRight->ClearSelection();
+ updateGuiGrid();
+
+// event.Skip();
+}
+
+
+void MainDialog::OnConfigureFilter(wxCommandEvent &event)
+{
+ if (showFilterDialog(true, //is main filter dialog
+ currentCfg.mainCfg.globalFilter.includeFilter,
+ currentCfg.mainCfg.globalFilter.excludeFilter) == DefaultReturnCode::BUTTON_OKAY)
+ {
+ updateFilterButtons(); //refresh global filter icon
+ updateFilterConfig(); //re-apply filter
+ }
+
+ //event.Skip()
+}
+
+
+void MainDialog::OnGlobalFilterOpenContext(wxCommandEvent& event)
+{
+ const int menuId = 1234;
+ contextMenu.reset(new wxMenu); //re-create context menu
+ contextMenu->Append(menuId, _("Clear filter settings"));
+ contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnGlobalFilterRemConfirm), NULL, this);
+
+ if (isNullFilter(currentCfg.mainCfg.globalFilter))
+ contextMenu->Enable(menuId, false); //disable menu item, if clicking wouldn't make sense anyway
+
+ PopupMenu(contextMenu.get()); //show context menu
+}
+
+
+void MainDialog::OnGlobalFilterRemConfirm(wxCommandEvent& event)
+{
+ currentCfg.mainCfg.globalFilter = FilterConfig();
+
+ updateFilterButtons(); //refresh global filter icon
+ updateFilterConfig(); //re-apply filter
+}
+
+
+void MainDialog::OnLeftOnlyFiles(wxCommandEvent& event)
+{
+ m_bpButtonLeftOnly->toggle();
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnLeftNewerFiles(wxCommandEvent& event)
+{
+ m_bpButtonLeftNewer->toggle();
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnDifferentFiles(wxCommandEvent& event)
+{
+ m_bpButtonDifferent->toggle();
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnRightNewerFiles(wxCommandEvent& event)
+{
+ m_bpButtonRightNewer->toggle();
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnRightOnlyFiles(wxCommandEvent& event)
+{
+ m_bpButtonRightOnly->toggle();
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnEqualFiles(wxCommandEvent& event)
+{
+ m_bpButtonEqual->toggle();
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnConflictFiles(wxCommandEvent& event)
+{
+ m_bpButtonConflict->toggle();
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnSyncCreateLeft(wxCommandEvent& event)
+{
+ m_bpButtonSyncCreateLeft->toggle();
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnSyncCreateRight(wxCommandEvent& event)
+{
+ m_bpButtonSyncCreateRight->toggle();
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnSyncDeleteLeft(wxCommandEvent& event)
+{
+ m_bpButtonSyncDeleteLeft->toggle();
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnSyncDeleteRight(wxCommandEvent& event)
+{
+ m_bpButtonSyncDeleteRight->toggle();
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnSyncDirLeft(wxCommandEvent& event)
+{
+ m_bpButtonSyncDirOverwLeft->toggle();
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnSyncDirRight(wxCommandEvent& event)
+{
+ m_bpButtonSyncDirOverwRight->toggle();
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnSyncDirNone(wxCommandEvent& event)
+{
+ m_bpButtonSyncDirNone->toggle();
+ updateGuiGrid();
+}
+
+
+void MainDialog::initViewFilterButtons()
+{
+ //compare result buttons
+ m_bpButtonLeftOnly->init(GlobalResources::getInstance().getImageByName(wxT("leftOnlyAct")),
+ _("Hide files that exist on left side only"),
+ GlobalResources::getInstance().getImageByName(wxT("leftOnlyDeact")),
+ _("Show files that exist on left side only"));
+
+ m_bpButtonRightOnly->init(GlobalResources::getInstance().getImageByName(wxT("rightOnlyAct")),
+ _("Hide files that exist on right side only"),
+ GlobalResources::getInstance().getImageByName(wxT("rightOnlyDeact")),
+ _("Show files that exist on right side only"));
+
+ m_bpButtonLeftNewer->init(GlobalResources::getInstance().getImageByName(wxT("leftNewerAct")),
+ _("Hide files that are newer on left"),
+ GlobalResources::getInstance().getImageByName(wxT("leftNewerDeact")),
+ _("Show files that are newer on left"));
+
+ m_bpButtonRightNewer->init(GlobalResources::getInstance().getImageByName(wxT("rightNewerAct")),
+ _("Hide files that are newer on right"),
+ GlobalResources::getInstance().getImageByName(wxT("rightNewerDeact")),
+ _("Show files that are newer on right"));
+
+ m_bpButtonEqual->init(GlobalResources::getInstance().getImageByName(wxT("equalAct")),
+ _("Hide files that are equal"),
+ GlobalResources::getInstance().getImageByName(wxT("equalDeact")),
+ _("Show files that are equal"));
+
+ m_bpButtonDifferent->init(GlobalResources::getInstance().getImageByName(wxT("differentAct")),
+ _("Hide files that are different"),
+ GlobalResources::getInstance().getImageByName(wxT("differentDeact")),
+ _("Show files that are different"));
+
+ m_bpButtonConflict->init(GlobalResources::getInstance().getImageByName(wxT("conflictAct")),
+ _("Hide conflicts"),
+ GlobalResources::getInstance().getImageByName(wxT("conflictDeact")),
+ _("Show conflicts"));
+
+ //sync preview buttons
+ m_bpButtonSyncCreateLeft->init(GlobalResources::getInstance().getImageByName(wxT("syncCreateLeftAct")),
+ _("Hide files that will be created on the left side"),
+ GlobalResources::getInstance().getImageByName(wxT("syncCreateLeftDeact")),
+ _("Show files that will be created on the left side"));
+
+ m_bpButtonSyncCreateRight->init(GlobalResources::getInstance().getImageByName(wxT("syncCreateRightAct")),
+ _("Hide files that will be created on the right side"),
+ GlobalResources::getInstance().getImageByName(wxT("syncCreateRightDeact")),
+ _("Show files that will be created on the right side"));
+
+ m_bpButtonSyncDeleteLeft->init(GlobalResources::getInstance().getImageByName(wxT("syncDeleteLeftAct")),
+ _("Hide files that will be deleted on the left side"),
+ GlobalResources::getInstance().getImageByName(wxT("syncDeleteLeftDeact")),
+ _("Show files that will be deleted on the left side"));
+
+ m_bpButtonSyncDeleteRight->init(GlobalResources::getInstance().getImageByName(wxT("syncDeleteRightAct")),
+ _("Hide files that will be deleted on the right side"),
+ GlobalResources::getInstance().getImageByName(wxT("syncDeleteRightDeact")),
+ _("Show files that will be deleted on the right side"));
+
+ m_bpButtonSyncDirOverwLeft->init(GlobalResources::getInstance().getImageByName(wxT("syncDirLeftAct")),
+ _("Hide files that will be overwritten on left side"),
+ GlobalResources::getInstance().getImageByName(wxT("syncDirLeftDeact")),
+ _("Show files that will be overwritten on left side"));
+
+ m_bpButtonSyncDirOverwRight->init(GlobalResources::getInstance().getImageByName(wxT("syncDirRightAct")),
+ _("Hide files that will be overwritten on right side"),
+ GlobalResources::getInstance().getImageByName(wxT("syncDirRightDeact")),
+ _("Show files that will be overwritten on right side"));
+
+ m_bpButtonSyncDirNone->init(GlobalResources::getInstance().getImageByName(wxT("syncDirNoneAct")),
+ _("Hide files that won't be copied"),
+ GlobalResources::getInstance().getImageByName(wxT("syncDirNoneDeact")),
+ _("Show files that won't be copied"));
+
+ //compare result buttons
+ m_bpButtonLeftOnly-> setActive(true);
+ m_bpButtonRightOnly-> setActive(true);
+ m_bpButtonLeftNewer-> setActive(true);
+ m_bpButtonRightNewer->setActive(true);
+ m_bpButtonEqual-> setActive(false);
+ m_bpButtonDifferent-> setActive(true);
+ m_bpButtonConflict-> setActive(true);
+
+ //sync preview buttons
+ m_bpButtonSyncCreateLeft-> setActive(true);
+ m_bpButtonSyncCreateRight-> setActive(true);
+ m_bpButtonSyncDeleteLeft-> setActive(true);
+ m_bpButtonSyncDeleteRight-> setActive(true);
+ m_bpButtonSyncDirOverwLeft-> setActive(true);
+ m_bpButtonSyncDirOverwRight->setActive(true);
+ m_bpButtonSyncDirNone-> setActive(true);
+}
+
+
+void MainDialog::updateFilterButtons()
+{
+ //prepare filter icon
+ if (m_notebookBottomLeft->GetImageList() == NULL)
+ {
+ wxImageList* panelIcons = new wxImageList(16, 16);
+ panelIcons->Add(wxBitmap(GlobalResources::getInstance().getImageByName(wxT("filterSmall"))));
+ panelIcons->Add(wxBitmap(GlobalResources::getInstance().getImageByName(wxT("filterSmallGrey"))));
+ m_notebookBottomLeft->AssignImageList(panelIcons); //pass ownership
+ }
+
+ //global filter: test for Null-filter
+ if (isNullFilter(currentCfg.mainCfg.globalFilter))
+ {
+ m_bpButtonFilter->SetBitmapLabel(GlobalResources::getInstance().getImageByName(wxT("filterOff")));
+ m_bpButtonFilter->SetToolTip(_("No filter selected"));
+
+ //additional filter icon
+ m_notebookBottomLeft->SetPageImage(1, 1);
+ }
+ else
+ {
+ m_bpButtonFilter->SetBitmapLabel(GlobalResources::getInstance().getImageByName(wxT("filterOn")));
+ m_bpButtonFilter->SetToolTip(_("Filter is active"));
+
+ //show filter icon
+ m_notebookBottomLeft->SetPageImage(1, 0);
+ }
+
+ //update main local filter
+ firstFolderPair->refreshButtons();
+
+ //update folder pairs
+ for (std::vector<FolderPairPanel*>::const_iterator i = additionalFolderPairs.begin(); i != additionalFolderPairs.end(); ++i)
+ {
+ FolderPairPanel* dirPair = *i;
+ dirPair->refreshButtons();
+ }
+}
+
+
+void MainDialog::OnCompare(wxCommandEvent &event)
+{
+ //PERF_START;
+ clearStatusBar();
+
+ wxBusyCursor dummy; //show hourglass cursor
+
+ //prevent temporary memory peak by clearing old result list
+ gridDataView->clearAllRows();
+ updateGuiGrid(); //refresh GUI grid
+
+ bool aborted = false;
+ try
+ {
+ //class handling status display and error messages
+ CompareStatusHandler statusHandler(this);
+
+ //begin comparison
+ ffs3::CompareProcess comparison(currentCfg.mainCfg.handleSymlinks,
+ globalSettings->fileTimeTolerance,
+ globalSettings->ignoreOneHourDiff,
+ globalSettings->optDialogs,
+ &statusHandler);
+
+ //technical representation of comparison data
+ ffs3::FolderComparison newCompareData;
+
+ comparison.startCompareProcess(
+ ffs3::extractCompareCfg(getCurrentConfiguration().mainCfg), //call getCurrentCfg() to get current values for directory pairs!
+ currentCfg.mainCfg.compareVar,
+ newCompareData);
+
+ gridDataView->setData(newCompareData); //newCompareData is invalidated after this call
+
+ //play (optional) sound notification after sync has completed (GUI and batch mode)
+ const wxString soundFile = ffs3::getResourceDir() + wxT("Compare_Complete.wav");
+ if (fileExists(wxToZ(soundFile)))
+ wxSound::Play(soundFile, wxSOUND_ASYNC);
+ }
+ catch (AbortThisProcess&)
+ {
+ aborted = true;
+ }
+
+ if (aborted)
+ {
+ //disable the sync button
+ syncPreview->enableSynchronization(false);
+ m_buttonCompare->SetFocus();
+ updateGuiGrid(); //refresh grid in ANY case! (also on abort)
+ }
+ else
+ {
+ //once compare is finished enable the sync button
+ syncPreview->enableSynchronization(true);
+ m_buttonStartSync->SetFocus();
+
+ //hide sort direction indicator on GUI grids
+ m_gridLeft->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING));
+ m_gridMiddle->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING));
+ m_gridRight->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING));
+
+ //reset last sort selection: used for determining sort direction
+ lastSortColumn = -1;
+ lastSortGrid = NULL;
+
+ m_gridLeft-> ClearSelection();
+ m_gridMiddle->ClearSelection();
+ m_gridRight-> ClearSelection();
+
+ //add to folder history after successful comparison only
+ addLeftFolderToHistory( m_directoryLeft->GetValue());
+ addRightFolderToHistory(m_directoryRight->GetValue());
+
+ //refresh grid in ANY case! (also on abort)
+ updateGuiGrid();
+
+ //prepare status information
+ wxString statusInfo;
+ if (allElementsEqual(gridDataView->getDataTentative()))
+ statusInfo += _("All directories in sync!");
+ pushStatusInformation(statusInfo);
+ }
+}
+
+
+void MainDialog::updateGuiGrid()
+{
+ updateGridViewData(); //update gridDataView and write status information
+
+ //all three grids retrieve their data directly via gridDataView
+ //the only thing left to do is notify the grids to updafte their sizes (nr of rows), since this has to be communicated by the grids via messages
+ m_gridLeft ->updateGridSizes();
+ m_gridMiddle->updateGridSizes();
+ m_gridRight ->updateGridSizes();
+
+ //enlarge label width to display row numbers correctly
+ const int nrOfRows = m_gridLeft->GetNumberRows();
+ if (nrOfRows >= 0)
+ {
+#ifdef FFS_WIN
+ const size_t digitWidth = 8;
+#elif defined FFS_LINUX
+ const size_t digitWidth = 10;
+#endif
+ const size_t nrOfDigits = common::getDigitCount(static_cast<size_t>(nrOfRows));
+ m_gridLeft ->SetRowLabelSize(static_cast<int>(nrOfDigits * digitWidth + 4));
+ m_gridRight->SetRowLabelSize(static_cast<int>(nrOfDigits * digitWidth + 4));
+ }
+
+ //support for column auto adjustment
+ if (globalSettings->gui.autoAdjustColumnsLeft)
+ m_gridLeft->autoSizeColumns();
+ if (globalSettings->gui.autoAdjustColumnsRight)
+ m_gridRight->autoSizeColumns();
+
+ //update sync preview statistics
+ calculatePreview();
+
+ m_gridLeft ->Refresh();
+ m_gridMiddle->Refresh();
+ m_gridRight ->Refresh();
+}
+
+
+void MainDialog::calculatePreview()
+{
+ //update preview of bytes to be transferred:
+ const SyncStatistics st(gridDataView->getDataTentative());
+ const wxString toCreate = ffs3::numberToStringSep(st.getCreate());
+ const wxString toUpdate = ffs3::numberToStringSep(st.getOverwrite());
+ const wxString toDelete = ffs3::numberToStringSep(st.getDelete());
+ const wxString data = ffs3::formatFilesizeToShortString(st.getDataToProcess());
+
+ m_textCtrlCreate->SetValue(toCreate);
+ m_textCtrlUpdate->SetValue(toUpdate);
+ m_textCtrlDelete->SetValue(toDelete);
+ m_textCtrlData->SetValue(data);
+}
+
+
+void MainDialog::OnSwitchView(wxCommandEvent& event)
+{
+ //toggle view
+ syncPreview->enablePreview(!syncPreview->previewIsEnabled());
+}
+
+
+void MainDialog::OnSyncSettings(wxCommandEvent& event)
+{
+ SyncCfgDialog* syncDlg = new SyncCfgDialog(this,
+ currentCfg.mainCfg.compareVar,
+ currentCfg.mainCfg.syncConfiguration,
+ currentCfg.mainCfg.handleDeletion,
+ currentCfg.mainCfg.customDeletionDirectory,
+ &currentCfg.ignoreErrors);
+ if (syncDlg->ShowModal() == SyncCfgDialog::BUTTON_APPLY)
+ updateSyncConfig();
+}
+
+
+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;
+
+ if (ffs3::showCompareCfgDialog(windowPos,
+ currentCfg.mainCfg.compareVar,
+ currentCfg.mainCfg.handleSymlinks) == DefaultReturnCode::BUTTON_OKAY)
+ {
+ //update compare variant name
+ m_staticTextCmpVariant->SetLabel(wxString(wxT("(")) + getVariantName(currentCfg.mainCfg.compareVar) + wxT(")"));
+ bSizer6->Layout(); //adapt layout for variant text
+
+ //disable the sync button
+ syncPreview->enableSynchronization(false);
+
+ //clear grids
+ gridDataView->clearAllRows();
+ updateGuiGrid();
+
+ //convenience: change sync view
+ switch (currentCfg.mainCfg.compareVar)
+ {
+ case CMP_BY_TIME_SIZE:
+ syncPreview->enablePreview(true);
+ break;
+ case CMP_BY_CONTENT:
+ syncPreview->enablePreview(false);
+ break;
+ }
+
+ m_buttonCompare->SetFocus();
+ }
+}
+
+
+void MainDialog::OnStartSync(wxCommandEvent& event)
+{
+ if (!syncPreview->synchronizationIsEnabled())
+ {
+ pushStatusInformation(_("Please run a Compare first before synchronizing!"));
+ return;
+ }
+
+ //show sync preview screen
+ if (globalSettings->optDialogs.showSummaryBeforeSync)
+ {
+ bool dontShowAgain = false;
+
+ if (ffs3::showSyncPreviewDlg(
+ getCurrentConfiguration().mainCfg.getSyncVariantName(),
+ ffs3::SyncStatistics(gridDataView->getDataTentative()),
+ dontShowAgain) != DefaultReturnCode::BUTTON_OKAY)
+ return;
+
+ globalSettings->optDialogs.showSummaryBeforeSync = !dontShowAgain;
+ }
+
+ wxBusyCursor dummy; //show hourglass cursor
+
+ clearStatusBar();
+ try
+ {
+ //PERF_START;
+
+ //class handling status updates and error messages
+ SyncStatusHandler statusHandler(this, currentCfg.ignoreErrors);
+
+ //check if there are files/folders to be sync'ed at all
+ if (!synchronizationNeeded(gridDataView->getDataTentative()))
+ statusHandler.reportInfo(_("Nothing to synchronize according to configuration!")); //inform about this special case
+
+ //start synchronization and mark all elements processed
+ ffs3::SyncProcess synchronization(
+ globalSettings->optDialogs,
+ globalSettings->verifyFileCopy,
+ globalSettings->copyLockedFiles,
+ globalSettings->copyFilePermissions,
+ statusHandler);
+
+ const std::vector<ffs3::FolderPairSyncCfg> syncProcessCfg = ffs3::extractSyncCfg(getCurrentConfiguration().mainCfg);
+ FolderComparison& dataToSync = gridDataView->getDataTentative();
+
+ //make sure syncProcessCfg and dataToSync have same size and correspond!
+ if (syncProcessCfg.size() != dataToSync.size())
+ throw std::logic_error("Programming Error: Contract violation!"); //should never happen: sync button is deactivated if they are not in sync
+
+ synchronization.startSynchronizationProcess(syncProcessCfg, dataToSync);
+
+ //play (optional) sound notification after sync has completed (GUI and batch mode)
+ const wxString soundFile = ffs3::getResourceDir() + wxT("Sync_Complete.wav");
+ if (fileExists(wxToZ(soundFile)))
+ wxSound::Play(soundFile, wxSOUND_ASYNC);
+ }
+ catch (AbortThisProcess&)
+ {
+ //do NOT disable the sync button: user might want to try to sync the REMAINING rows
+ } //enableSynchronization(false);
+
+ //remove rows that empty: just a beautification, invalid rows shouldn't cause issues
+ gridDataView->removeInvalidRows();
+
+ updateGuiGrid();
+
+ m_gridLeft-> ClearSelection();
+ m_gridMiddle->ClearSelection();
+ m_gridRight-> ClearSelection();
+}
+
+
+void MainDialog::OnLeftGridDoubleClick(wxGridEvent& event)
+{
+ if (!globalSettings->gui.externelApplications.empty())
+ openExternalApplication(event.GetRow(), true, globalSettings->gui.externelApplications[0].second);
+ // event.Skip();
+}
+
+
+void MainDialog::OnRightGridDoubleClick(wxGridEvent& event)
+{
+ if (!globalSettings->gui.externelApplications.empty())
+ openExternalApplication(event.GetRow(), false, globalSettings->gui.externelApplications[0].second);
+// event.Skip();
+}
+
+
+void MainDialog::OnSortLeftGrid(wxGridEvent& event)
+{
+ //determine direction for std::sort()
+ const int currentSortColumn = event.GetCol();
+ if (0 <= currentSortColumn && currentSortColumn < int(xmlAccess::COLUMN_TYPE_COUNT))
+ {
+ static bool sortDefault = true;
+ if (lastSortColumn != currentSortColumn || lastSortGrid != m_gridLeft)
+ sortDefault = true;
+ else
+ sortDefault = !sortDefault;
+
+ lastSortColumn = currentSortColumn;
+ lastSortGrid = m_gridLeft;
+
+ GridView::SortType st = GridView::SORT_BY_REL_NAME;
+
+ const xmlAccess::ColumnTypes columnType = m_gridLeft->getTypeAtPos(currentSortColumn);
+ switch (columnType)
+ {
+ case xmlAccess::FULL_PATH:
+ st = GridView::SORT_BY_REL_NAME;
+ break;
+ case xmlAccess::FILENAME:
+ st = GridView::SORT_BY_FILENAME;
+ break;
+ case xmlAccess::REL_PATH:
+ st = GridView::SORT_BY_REL_NAME;
+ break;
+ case xmlAccess::DIRECTORY:
+ st = GridView::SORT_BY_DIRECTORY;
+ break;
+ case xmlAccess::SIZE:
+ st = GridView::SORT_BY_FILESIZE;
+ break;
+ case xmlAccess::DATE:
+ st = GridView::SORT_BY_DATE;
+ break;
+ case xmlAccess::EXTENSION:
+ st = GridView::SORT_BY_EXTENSION;
+ break;
+ }
+
+ const bool sortAscending = sortDefault ?
+ GridView::getDefaultDirection(st) :
+ !GridView::getDefaultDirection(st);
+
+ gridDataView->sortView(st, true, sortAscending);
+
+ updateGuiGrid(); //refresh gridDataView
+
+ //set sort direction indicator on UI
+ m_gridMiddle->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING));
+ m_gridRight->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING));
+ m_gridLeft->setSortMarker(CustomGrid::SortMarker(currentSortColumn, sortAscending ? CustomGrid::ASCENDING : CustomGrid::DESCENDING));
+ }
+}
+
+
+void MainDialog::OnSortMiddleGrid(wxGridEvent& event)
+{
+ //determine direction for std::sort()
+ static bool sortDefault = true;
+ if (lastSortColumn != 0 || lastSortGrid != m_gridMiddle)
+ sortDefault = true;
+ else
+ sortDefault = !sortDefault;
+ lastSortColumn = 0;
+ lastSortGrid = m_gridMiddle;
+
+ //start sort
+ if (syncPreview->previewIsEnabled())
+ gridDataView->sortView(GridView::SORT_BY_SYNC_DIRECTION, true, sortDefault);
+ else
+ gridDataView->sortView(GridView::SORT_BY_CMP_RESULT, true, sortDefault);
+
+ updateGuiGrid(); //refresh gridDataView
+
+ //set sort direction indicator on UI
+ m_gridLeft->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING));
+ m_gridRight->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING));
+ m_gridMiddle->setSortMarker(CustomGrid::SortMarker(0, sortDefault ? CustomGrid::ASCENDING : CustomGrid::DESCENDING));
+}
+
+
+void MainDialog::OnSortRightGrid(wxGridEvent& event)
+{
+ //determine direction for std::sort()
+ const int currentSortColumn = event.GetCol();
+ if (0 <= currentSortColumn && currentSortColumn < int(xmlAccess::COLUMN_TYPE_COUNT))
+ {
+ static bool sortDefault = true;
+ if (lastSortColumn != currentSortColumn || lastSortGrid != m_gridRight)
+ sortDefault = true;
+ else
+ sortDefault = !sortDefault;
+
+ lastSortColumn = currentSortColumn;
+ lastSortGrid = m_gridRight;
+
+ GridView::SortType st = GridView::SORT_BY_REL_NAME;
+
+ const xmlAccess::ColumnTypes columnType = m_gridRight->getTypeAtPos(currentSortColumn);
+ switch (columnType)
+ {
+ case xmlAccess::FULL_PATH:
+ st = GridView::SORT_BY_REL_NAME;
+ break;
+ case xmlAccess::FILENAME:
+ st = GridView::SORT_BY_FILENAME;
+ break;
+ case xmlAccess::REL_PATH:
+ st = GridView::SORT_BY_REL_NAME;
+ break;
+ case xmlAccess::DIRECTORY:
+ st = GridView::SORT_BY_DIRECTORY;
+ break;
+ case xmlAccess::SIZE:
+ st = GridView::SORT_BY_FILESIZE;
+ break;
+ case xmlAccess::DATE:
+ st = GridView::SORT_BY_DATE;
+ break;
+ case xmlAccess::EXTENSION:
+ st = GridView::SORT_BY_EXTENSION;
+ break;
+ }
+
+ const bool sortAscending = sortDefault ?
+ GridView::getDefaultDirection(st) :
+ !GridView::getDefaultDirection(st);
+
+ gridDataView->sortView(st, false, sortAscending);
+
+ updateGuiGrid(); //refresh gridDataView
+
+ //set sort direction indicator on UI
+ m_gridLeft->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING));
+ m_gridMiddle->setSortMarker(CustomGrid::SortMarker(-1, CustomGrid::ASCENDING));
+ m_gridRight->setSortMarker(CustomGrid::SortMarker(currentSortColumn, sortAscending ? CustomGrid::ASCENDING : CustomGrid::DESCENDING));
+ }
+}
+
+
+void MainDialog::OnSwapSides(wxCommandEvent& event)
+{
+ //swap directory names: first pair
+ firstFolderPair->setValues(firstFolderPair->getRightDir(), // swap directories
+ firstFolderPair->getLeftDir(), //
+ firstFolderPair->getAltSyncConfig(),
+ firstFolderPair->getAltFilterConfig());
+
+ //additional pairs
+ for (std::vector<FolderPairPanel*>::const_iterator i = additionalFolderPairs.begin(); i != additionalFolderPairs.end(); ++i)
+ {
+ FolderPairPanel* dirPair = *i;
+ dirPair->setValues(dirPair->getRightDir(), // swap directories
+ dirPair->getLeftDir(), //
+ dirPair->getAltSyncConfig(),
+ dirPair->getAltFilterConfig());
+ }
+
+ //swap view filter
+ bool tmp = m_bpButtonLeftOnly->isActive();
+ m_bpButtonLeftOnly->setActive(m_bpButtonRightOnly->isActive());
+ m_bpButtonRightOnly->setActive(tmp);
+
+ tmp = m_bpButtonLeftNewer->isActive();
+ m_bpButtonLeftNewer->setActive(m_bpButtonRightNewer->isActive());
+ m_bpButtonRightNewer->setActive(tmp);
+
+
+ tmp = m_bpButtonSyncCreateLeft->isActive();
+ m_bpButtonSyncCreateLeft->setActive(m_bpButtonSyncCreateRight->isActive());
+ m_bpButtonSyncCreateRight->setActive(tmp);
+
+ tmp = m_bpButtonSyncDeleteLeft->isActive();
+ m_bpButtonSyncDeleteLeft->setActive(m_bpButtonSyncDeleteRight->isActive());
+ m_bpButtonSyncDeleteRight->setActive(tmp);
+
+ tmp = m_bpButtonSyncDirOverwLeft->isActive();
+ m_bpButtonSyncDirOverwLeft->setActive(m_bpButtonSyncDirOverwRight->isActive());
+ m_bpButtonSyncDirOverwRight->setActive(tmp);
+
+ //swap grid information
+ ffs3::swapGrids(getCurrentConfiguration().mainCfg, gridDataView->getDataTentative());
+ updateGuiGrid();
+}
+
+
+void MainDialog::updateGridViewData()
+{
+ size_t filesOnLeftView = 0;
+ size_t foldersOnLeftView = 0;
+ size_t filesOnRightView = 0;
+ size_t foldersOnRightView = 0;
+ wxULongLong filesizeLeftView;
+ wxULongLong filesizeRightView;
+
+ //disable all buttons per default
+ m_bpButtonLeftOnly-> Show(false);
+ m_bpButtonRightOnly-> Show(false);
+ m_bpButtonLeftNewer-> Show(false);
+ m_bpButtonRightNewer->Show(false);
+ m_bpButtonDifferent-> Show(false);
+ m_bpButtonEqual-> Show(false);
+ m_bpButtonConflict-> Show(false);
+
+ m_bpButtonSyncCreateLeft-> Show(false);
+ m_bpButtonSyncCreateRight-> Show(false);
+ m_bpButtonSyncDeleteLeft-> Show(false);
+ m_bpButtonSyncDeleteRight-> Show(false);
+ m_bpButtonSyncDirOverwLeft-> Show(false);
+ m_bpButtonSyncDirOverwRight->Show(false);
+ m_bpButtonSyncDirNone-> Show(false);
+
+
+ if (syncPreview->previewIsEnabled())
+ {
+ const GridView::StatusSyncPreview result = gridDataView->updateSyncPreview(currentCfg.hideFilteredElements,
+ m_bpButtonSyncCreateLeft-> isActive(),
+ m_bpButtonSyncCreateRight-> isActive(),
+ m_bpButtonSyncDeleteLeft-> isActive(),
+ m_bpButtonSyncDeleteRight-> isActive(),
+ m_bpButtonSyncDirOverwLeft-> isActive(),
+ m_bpButtonSyncDirOverwRight->isActive(),
+ m_bpButtonSyncDirNone-> isActive(),
+ m_bpButtonEqual-> isActive(),
+ m_bpButtonConflict-> isActive());
+
+ filesOnLeftView = result.filesOnLeftView;
+ foldersOnLeftView = result.foldersOnLeftView;
+ filesOnRightView = result.filesOnRightView;
+ foldersOnRightView = result.foldersOnRightView;
+ filesizeLeftView = result.filesizeLeftView;
+ filesizeRightView = result.filesizeRightView;
+
+
+ //sync preview buttons
+ m_bpButtonSyncCreateLeft-> Show(result.existsSyncCreateLeft);
+ m_bpButtonSyncCreateRight-> Show(result.existsSyncCreateRight);
+ m_bpButtonSyncDeleteLeft-> Show(result.existsSyncDeleteLeft);
+ m_bpButtonSyncDeleteRight-> Show(result.existsSyncDeleteRight);
+ m_bpButtonSyncDirOverwLeft-> Show(result.existsSyncDirLeft);
+ m_bpButtonSyncDirOverwRight->Show(result.existsSyncDirRight);
+ m_bpButtonSyncDirNone-> Show(result.existsSyncDirNone);
+ m_bpButtonEqual-> Show(result.existsSyncEqual);
+ m_bpButtonConflict-> Show(result.existsConflict);
+
+ if ( m_bpButtonSyncCreateLeft-> IsShown() ||
+ m_bpButtonSyncCreateRight-> IsShown() ||
+ m_bpButtonSyncDeleteLeft-> IsShown() ||
+ m_bpButtonSyncDeleteRight-> IsShown() ||
+ m_bpButtonSyncDirOverwLeft-> IsShown() ||
+ m_bpButtonSyncDirOverwRight->IsShown() ||
+ m_bpButtonSyncDirNone-> IsShown() ||
+ m_bpButtonEqual-> IsShown() ||
+ m_bpButtonConflict-> IsShown())
+ {
+ m_panelViewFilter->Show();
+ m_panelViewFilter->Layout();
+ }
+ else
+ m_panelViewFilter->Hide();
+
+ }
+ else
+ {
+ const GridView::StatusCmpResult result = gridDataView->updateCmpResult(currentCfg.hideFilteredElements,
+ m_bpButtonLeftOnly-> isActive(),
+ m_bpButtonRightOnly-> isActive(),
+ m_bpButtonLeftNewer-> isActive(),
+ m_bpButtonRightNewer->isActive(),
+ m_bpButtonDifferent-> isActive(),
+ m_bpButtonEqual-> isActive(),
+ m_bpButtonConflict-> isActive());
+
+ filesOnLeftView = result.filesOnLeftView;
+ foldersOnLeftView = result.foldersOnLeftView;
+ filesOnRightView = result.filesOnRightView;
+ foldersOnRightView = result.foldersOnRightView;
+ filesizeLeftView = result.filesizeLeftView;
+ filesizeRightView = result.filesizeRightView;
+
+ //comparison result view buttons
+ m_bpButtonLeftOnly-> Show(result.existsLeftOnly);
+ m_bpButtonRightOnly-> Show(result.existsRightOnly);
+ m_bpButtonLeftNewer-> Show(result.existsLeftNewer);
+ m_bpButtonRightNewer->Show(result.existsRightNewer);
+ m_bpButtonDifferent-> Show(result.existsDifferent);
+ m_bpButtonEqual-> Show(result.existsEqual);
+ m_bpButtonConflict-> Show(result.existsConflict);
+
+ if ( m_bpButtonLeftOnly-> IsShown() ||
+ m_bpButtonRightOnly-> IsShown() ||
+ m_bpButtonLeftNewer-> IsShown() ||
+ m_bpButtonRightNewer->IsShown() ||
+ m_bpButtonDifferent-> IsShown() ||
+ m_bpButtonEqual-> IsShown() ||
+ m_bpButtonConflict-> IsShown())
+ {
+ m_panelViewFilter->Show();
+ m_panelViewFilter->Layout();
+ }
+ else
+ m_panelViewFilter->Hide();
+ }
+
+
+ bSizer3->Layout();
+
+ //update status information
+ clearStatusBar();
+
+
+ wxString statusLeftNew;
+ wxString statusMiddleNew;
+ wxString statusRightNew;
+
+//#################################################
+//format numbers to text:
+
+//show status information on "root" level.
+ if (foldersOnLeftView)
+ {
+ if (foldersOnLeftView == 1)
+ statusLeftNew += _("1 directory");
+ else
+ {
+ wxString folderCount = ffs3::numberToStringSep(foldersOnLeftView);
+
+ wxString outputString = _("%x directories");
+ outputString.Replace(wxT("%x"), folderCount, false);
+ statusLeftNew += outputString;
+ }
+
+ if (filesOnLeftView)
+ statusLeftNew += wxT(" - ");
+ }
+
+ if (filesOnLeftView)
+ {
+ if (filesOnLeftView == 1)
+ statusLeftNew += _("1 file");
+ else
+ {
+ wxString fileCount = ffs3::numberToStringSep(filesOnLeftView);
+
+ wxString outputString = _("%x files");
+ outputString.Replace(wxT("%x"), fileCount, false);
+ statusLeftNew += outputString;
+ }
+ statusLeftNew += wxT(" - ");
+ statusLeftNew += ffs3::formatFilesizeToShortString(filesizeLeftView);
+ }
+
+ const wxString objectsView = ffs3::numberToStringSep(gridDataView->rowsOnView());
+ if (gridDataView->rowsTotal() == 1)
+ {
+ wxString outputString = _("%x of 1 row in view");
+ outputString.Replace(wxT("%x"), objectsView, false);
+ statusMiddleNew = outputString;
+ }
+ else
+ {
+ const wxString objectsTotal = ffs3::numberToStringSep(gridDataView->rowsTotal());
+
+ wxString outputString = _("%x of %y rows in view");
+ outputString.Replace(wxT("%x"), objectsView, false);
+ outputString.Replace(wxT("%y"), objectsTotal, false);
+ statusMiddleNew = outputString;
+ }
+
+ if (foldersOnRightView)
+ {
+ if (foldersOnRightView == 1)
+ statusRightNew += _("1 directory");
+ else
+ {
+ wxString folderCount = ffs3::numberToStringSep(foldersOnRightView);
+
+ wxString outputString = _("%x directories");
+ outputString.Replace(wxT("%x"), folderCount, false);
+ statusRightNew += outputString;
+ }
+
+ if (filesOnRightView)
+ statusRightNew += wxT(" - ");
+ }
+
+ if (filesOnRightView)
+ {
+ if (filesOnRightView == 1)
+ statusRightNew += _("1 file");
+ else
+ {
+ wxString fileCount = ffs3::numberToStringSep(filesOnRightView);
+
+ wxString outputString = _("%x files");
+ outputString.Replace(wxT("%x"), fileCount, false);
+ statusRightNew += outputString;
+ }
+
+ statusRightNew += wxT(" - ");
+ statusRightNew += ffs3::formatFilesizeToShortString(filesizeRightView);
+ }
+
+
+ //avoid screen flicker
+ if (m_staticTextStatusLeft->GetLabel() != statusLeftNew)
+ m_staticTextStatusLeft->SetLabel(statusLeftNew);
+ if (m_staticTextStatusMiddle->GetLabel() != statusMiddleNew)
+ m_staticTextStatusMiddle->SetLabel(statusMiddleNew);
+ if (m_staticTextStatusRight->GetLabel() != statusRightNew)
+ m_staticTextStatusRight->SetLabel(statusRightNew);
+
+ m_panelStatusBar->Layout();
+}
+
+
+void MainDialog::OnAddFolderPair(wxCommandEvent& event)
+{
+ wxWindowUpdateLocker dummy(this); //avoid display distortion
+
+ std::vector<FolderPairEnh> newPairs;
+ newPairs.push_back(getCurrentConfiguration().mainCfg.firstPair);
+
+ addFolderPair(newPairs, true); //add pair in front of additonal pairs
+
+ //clear first pair
+ const FolderPairEnh cfgEmpty;
+ firstFolderPair->setValues(cfgEmpty.leftDirectory,
+ cfgEmpty.rightDirectory,
+ cfgEmpty.altSyncConfig,
+ cfgEmpty.localFilter);
+
+ //disable the sync button
+ syncPreview->enableSynchronization(false);
+
+ //clear grids
+ gridDataView->clearAllRows();
+ updateSyncConfig(); //mainly to update sync dir description text
+}
+
+
+void MainDialog::updateFilterConfig()
+{
+ applyFiltering(getCurrentConfiguration().mainCfg, gridDataView->getDataTentative());
+ refreshGridAfterFilterChange(400);
+}
+
+
+void MainDialog::updateSyncConfig()
+{
+ //update sync variant name
+ m_staticTextSyncVariant->SetLabel(wxString(wxT("(")) + getCurrentConfiguration().mainCfg.getSyncVariantName() + wxT(")"));
+ bSizer6->Layout(); //adapt layout for variant text
+
+
+ class RedetermineCallback : public DeterminationProblem
+ {
+ public:
+ RedetermineCallback(bool& warningSyncDatabase, wxWindow* parent) :
+ warningSyncDatabase_(warningSyncDatabase),
+ parent_(parent) {}
+
+ virtual void reportWarning(const wxString& text)
+ {
+ if (warningSyncDatabase_)
+ {
+ bool dontWarnAgain = false;
+ WarningDlg* warningDlg = new WarningDlg(parent_, //show popup and ask user how to handle warning
+ WarningDlg::BUTTON_IGNORE,
+ text,
+ dontWarnAgain);
+ if (warningDlg->ShowModal() == WarningDlg::BUTTON_IGNORE)
+ warningSyncDatabase_ = !dontWarnAgain;
+ }
+ }
+ private:
+ bool& warningSyncDatabase_;
+ wxWindow* parent_;
+ } redetCallback(globalSettings->optDialogs.warningSyncDatabase, this);
+
+ ffs3::redetermineSyncDirection(getCurrentConfiguration().mainCfg, gridDataView->getDataTentative(), &redetCallback);
+ updateGuiGrid();
+}
+
+
+void MainDialog::OnRemoveTopFolderPair(wxCommandEvent& event)
+{
+ if (additionalFolderPairs.size() > 0)
+ {
+ //get settings from second folder pair
+ const FolderPairEnh cfgSecond = getEnahncedPair(additionalFolderPairs[0]);
+
+ //reset first pair
+ firstFolderPair->setValues(cfgSecond.leftDirectory,
+ cfgSecond.rightDirectory,
+ cfgSecond.altSyncConfig,
+ cfgSecond.localFilter);
+
+ removeAddFolderPair(0); //remove second folder pair (first of additional folder pairs)
+
+//------------------------------------------------------------------
+ //disable the sync button
+ syncPreview->enableSynchronization(false);
+
+ //clear grids
+ gridDataView->clearAllRows();
+ updateSyncConfig(); //mainly to update sync dir description text
+ }
+}
+
+
+void MainDialog::OnRemoveFolderPair(wxCommandEvent& event)
+{
+ const wxObject* const eventObj = event.GetEventObject(); //find folder pair originating the event
+ for (std::vector<FolderPairPanel*>::const_iterator i = additionalFolderPairs.begin(); i != additionalFolderPairs.end(); ++i)
+ if (eventObj == (*i)->m_bpButtonRemovePair)
+ {
+ removeAddFolderPair(i - additionalFolderPairs.begin());
+
+//------------------------------------------------------------------
+ //disable the sync button
+ syncPreview->enableSynchronization(false);
+
+ //clear grids
+ gridDataView->clearAllRows();
+ updateSyncConfig(); //mainly to update sync dir description text
+ return;
+ }
+}
+
+
+const size_t MAX_ADD_FOLDER_PAIRS = 5;
+
+
+void MainDialog::updateGuiForFolderPair()
+{
+ //adapt delete top folder pair button
+ if (additionalFolderPairs.size() == 0)
+ m_bpButtonRemovePair->Hide();
+ else
+ m_bpButtonRemovePair->Show();
+
+ m_panelTopRight->Layout();
+
+ //adapt local filter and sync cfg for first folder pair
+ if ( additionalFolderPairs.size() == 0 &&
+ firstFolderPair->getAltSyncConfig().get() == NULL &&
+ NameFilter(firstFolderPair->getAltFilterConfig().includeFilter,
+ firstFolderPair->getAltFilterConfig().excludeFilter).isNull())
+ {
+ m_bpButtonLocalFilter->Hide();
+ m_bpButtonAltSyncCfg->Hide();
+
+ m_bpButtonSwapSides->SetBitmapLabel(GlobalResources::getInstance().getImageByName(wxT("swap")));
+ }
+ else
+ {
+ m_bpButtonLocalFilter->Show();
+ m_bpButtonAltSyncCfg->Show();
+
+ m_bpButtonSwapSides->SetBitmapLabel(GlobalResources::getInstance().getImageByName(wxT("swapSlim")));
+ }
+
+ m_panelTopMiddle->Layout();
+}
+
+
+void MainDialog::addFolderPair(const std::vector<FolderPairEnh>& newPairs, bool addFront)
+{
+ wxWindowUpdateLocker dummy(this); //avoid display distortion
+
+ if (!newPairs.empty())
+ {
+ int pairHeight = 0;
+ for (std::vector<FolderPairEnh>::const_iterator i = newPairs.begin(); i != newPairs.end(); ++i)
+ {
+ //add new folder pair
+ FolderPairPanel* newPair = new FolderPairPanel(m_scrolledWindowFolderPairs, *this);
+
+ //correct width of middle block
+ newPair->m_panel21->SetMinSize(wxSize(m_gridMiddle->GetSize().GetWidth(), -1));
+
+ //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, 5);
+ additionalFolderPairs.insert(additionalFolderPairs.begin(), newPair);
+ }
+ else
+ {
+ bSizerAddFolderPairs->Add(newPair, 0, wxEXPAND, 5);
+ additionalFolderPairs.push_back(newPair);
+ }
+
+ //get size of scrolled window
+ pairHeight = newPair->GetSize().GetHeight();
+
+ //register events
+ newPair->m_bpButtonRemovePair->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(MainDialog::OnRemoveFolderPair), NULL, this);
+
+ //set alternate configuration
+ newPair->setValues(i->leftDirectory,
+ i->rightDirectory,
+ i->altSyncConfig,
+ i->localFilter);
+ }
+
+ //set size of scrolled window
+ const size_t visiblePairs = std::min(additionalFolderPairs.size(), MAX_ADD_FOLDER_PAIRS); //up to MAX_ADD_FOLDER_PAIRS additional pairs shall be shown
+ m_scrolledWindowFolderPairs->SetMinSize(wxSize( -1, pairHeight * static_cast<int>(visiblePairs)));
+
+ //update controls
+ m_scrolledWindowFolderPairs->Fit(); //adjust scrolled window size
+ m_scrolledWindowFolderPairs->Layout(); //adjust stuff inside scrolled window
+ bSizer1->Layout();
+ }
+
+ updateGuiForFolderPair();
+}
+
+
+void MainDialog::removeAddFolderPair(size_t pos)
+{
+ wxWindowUpdateLocker dummy(this); //avoid display distortion
+
+ if (pos < additionalFolderPairs.size())
+ {
+ //remove folder pairs from window
+ FolderPairPanel* pairToDelete = additionalFolderPairs[pos];
+ const int pairHeight = pairToDelete->GetSize().GetHeight();
+
+ bSizerAddFolderPairs->Detach(pairToDelete); //Remove() does not work on Window*, so do it manually
+ pairToDelete->Destroy(); //
+ additionalFolderPairs.erase(additionalFolderPairs.begin() + pos); //remove element from vector
+
+ //set size of scrolled window
+ const size_t additionalRows = additionalFolderPairs.size();
+ if (additionalRows <= MAX_ADD_FOLDER_PAIRS) //up to MAX_ADD_FOLDER_PAIRS additional pairs shall be shown
+ m_scrolledWindowFolderPairs->SetMinSize(wxSize(-1, pairHeight * static_cast<int>(additionalRows)));
+
+ //update controls
+ m_scrolledWindowFolderPairs->Fit(); //adjust scrolled window size
+ m_scrolledWindowFolderPairs->Layout(); //adjust stuff inside scrolled window
+ bSizer1->Layout();
+ }
+
+ updateGuiForFolderPair();
+}
+
+
+void MainDialog::clearAddFolderPairs()
+{
+ wxWindowUpdateLocker dummy(this); //avoid display distortion
+
+ additionalFolderPairs.clear();
+ bSizerAddFolderPairs->Clear(true);
+
+ m_scrolledWindowFolderPairs->SetMinSize(wxSize(-1, 0));
+ bSizer1->Layout();
+}
+//########################################################################################################
+
+
+//menu events
+void MainDialog::OnMenuGlobalSettings(wxCommandEvent& event)
+{
+ ffs3::showGlobalSettingsDlg(*globalSettings);
+
+ //event.Skip();
+}
+
+
+void MainDialog::OnMenuExportFileList(wxCommandEvent& event)
+{
+ //get a filename
+ const wxString defaultFileName = wxT("FileList.csv"); //proposal
+ wxFileDialog* filePicker = new wxFileDialog(this, wxEmptyString, wxEmptyString, defaultFileName, wxString(_("Comma separated list")) + wxT(" (*.csv)|*.csv"), wxFD_SAVE);
+
+ if (filePicker->ShowModal() == wxID_OK)
+ {
+ const wxString newFileName = filePicker->GetPath();
+ if (ffs3::fileExists(wxToZ(newFileName)))
+ {
+ QuestionDlg* messageDlg = new QuestionDlg(this,
+ QuestionDlg::BUTTON_YES | QuestionDlg::BUTTON_CANCEL,
+ wxString(_("File already exists. Overwrite?")) + wxT(" \"") + newFileName + wxT("\""));
+
+ if (messageDlg->ShowModal() != QuestionDlg::BUTTON_YES)
+ {
+ OnMenuExportFileList(event); //retry
+ return;
+ }
+ }
+
+ wxString exportString;
+ //write legend
+ exportString += wxString(_("Legend")) + wxT('\n');
+ if (syncPreview->previewIsEnabled())
+ {
+ exportString += wxString(wxT("\"")) + getDescription(SO_CREATE_NEW_LEFT) + wxT("\";") + getSymbol(SO_CREATE_NEW_LEFT) + wxT('\n');
+ exportString += wxString(wxT("\"")) + getDescription(SO_CREATE_NEW_RIGHT) + wxT("\";") + getSymbol(SO_CREATE_NEW_RIGHT) + wxT('\n');
+ exportString += wxString(wxT("\"")) + getDescription(SO_DELETE_LEFT) + wxT("\";") + getSymbol(SO_DELETE_LEFT) + wxT('\n');
+ exportString += wxString(wxT("\"")) + getDescription(SO_DELETE_RIGHT) + wxT("\";") + getSymbol(SO_DELETE_RIGHT) + wxT('\n');
+ exportString += wxString(wxT("\"")) + getDescription(SO_OVERWRITE_LEFT) + wxT("\";") + getSymbol(SO_OVERWRITE_LEFT) + wxT('\n');
+ exportString += wxString(wxT("\"")) + getDescription(SO_OVERWRITE_RIGHT) + wxT("\";") + getSymbol(SO_OVERWRITE_RIGHT) + wxT('\n');
+ exportString += wxString(wxT("\"")) + getDescription(SO_DO_NOTHING) + wxT("\";") + getSymbol(SO_DO_NOTHING) + wxT('\n');
+ exportString += wxString(wxT("\"")) + getDescription(SO_EQUAL) + wxT("\";") + getSymbol(SO_EQUAL) + wxT('\n');
+ exportString += wxString(wxT("\"")) + getDescription(SO_UNRESOLVED_CONFLICT) + wxT("\";") + getSymbol(SO_UNRESOLVED_CONFLICT) + wxT('\n');
+ }
+ else
+ {
+ exportString += wxString(wxT("\"")) + getDescription(FILE_LEFT_SIDE_ONLY) + wxT("\";") + getSymbol(FILE_LEFT_SIDE_ONLY) + wxT('\n');
+ exportString += wxString(wxT("\"")) + getDescription(FILE_RIGHT_SIDE_ONLY) + wxT("\";") + getSymbol(FILE_RIGHT_SIDE_ONLY) + wxT('\n');
+ exportString += wxString(wxT("\"")) + getDescription(FILE_LEFT_NEWER) + wxT("\";") + getSymbol(FILE_LEFT_NEWER) + wxT('\n');
+ exportString += wxString(wxT("\"")) + getDescription(FILE_RIGHT_NEWER) + wxT("\";") + getSymbol(FILE_RIGHT_NEWER) + wxT('\n');
+ exportString += wxString(wxT("\"")) + getDescription(FILE_DIFFERENT) + wxT("\";") + getSymbol(FILE_DIFFERENT) + wxT('\n');
+ exportString += wxString(wxT("\"")) + getDescription(FILE_EQUAL) + wxT("\";") + getSymbol(FILE_EQUAL) + wxT('\n');
+ exportString += wxString(wxT("\"")) + getDescription(FILE_CONFLICT) + wxT("\";") + getSymbol(FILE_CONFLICT) + wxT('\n');
+ }
+ exportString += '\n';
+
+ //write header
+ for (int k = 0; k < m_gridLeft->GetNumberCols(); ++k)
+ {
+ exportString += m_gridLeft->GetColLabelValue(k);
+ exportString += ';';
+ }
+
+ for (int k = 0; k < m_gridMiddle->GetNumberCols(); ++k)
+ {
+ exportString += m_gridMiddle->GetColLabelValue(k);
+ exportString += ';';
+ }
+
+ for (int k = 0; k < m_gridRight->GetNumberCols(); ++k)
+ {
+ exportString += m_gridRight->GetColLabelValue(k);
+ if (k != m_gridRight->GetNumberCols() - 1)
+ exportString += ';';
+ }
+ exportString += '\n';
+
+ //begin work
+ for (int i = 0; i < m_gridLeft->GetNumberRows(); ++i)
+ {
+ for (int k = 0; k < m_gridLeft->GetNumberCols(); ++k)
+ {
+ exportString += m_gridLeft->GetCellValue(i, k);
+ exportString += ';';
+ }
+
+ for (int k = 0; k < m_gridMiddle->GetNumberCols(); ++k)
+ {
+ exportString += m_gridMiddle->GetCellValue(i, k);
+ exportString += ';';
+ }
+
+ for (int k = 0; k < m_gridRight->GetNumberCols(); ++k)
+ {
+ exportString += m_gridRight->GetCellValue(i, k);
+ if (k != m_gridRight->GetNumberCols() - 1)
+ exportString+= ';';
+ }
+ exportString+= '\n';
+ }
+
+ //write export file
+ wxFFile output(newFileName.c_str(), wxT("w")); //don't write in binary mode
+ if (output.IsOpened())
+ {
+ output.Write(exportString);
+ pushStatusInformation(_("File list exported!"));
+ }
+ else
+ {
+ wxMessageBox(wxString(_("Error writing file:")) + wxT(" \"") + newFileName + wxT("\""), _("Error"), wxOK | wxICON_ERROR);
+ }
+ }
+}
+
+
+void MainDialog::OnMenuBatchJob(wxCommandEvent& event)
+{
+ //fill batch config structure
+ const xmlAccess::XmlGuiConfig currCfg = getCurrentConfiguration(); //get UP TO DATE config, with updated values for main and additional folders!
+
+ const xmlAccess::XmlBatchConfig batchCfg = convertGuiToBatch(currCfg);
+
+ BatchDialog* batchDlg = new BatchDialog(this, batchCfg);
+ if (batchDlg->ShowModal() == BatchDialog::BATCH_FILE_SAVED)
+ pushStatusInformation(_("Batch file created successfully!"));
+}
+
+
+void MainDialog::OnMenuCheckVersion(wxCommandEvent& event)
+{
+ ffs3::checkForUpdateNow();
+}
+
+
+void MainDialog::OnRegularUpdateCheck(wxIdleEvent& event)
+{
+ //execute just once per startup!
+ Disconnect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnRegularUpdateCheck), NULL, this);
+
+ ffs3::checkForUpdatePeriodically(globalSettings->gui.lastUpdateCheck);
+}
+
+
+void MainDialog::OnLayoutWindowAsync(wxIdleEvent& event)
+{
+ //execute just once per startup!
+ Disconnect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnLayoutWindowAsync), NULL, this);
+
+ wxWindowUpdateLocker dummy(this); //avoid display distortion
+
+ //adjust folder pair distortion on startup
+ m_scrolledWindowFolderPairs->Fit();
+
+ Layout(); //strangely this layout call works if called in next idle event only
+ Refresh();
+}
+
+
+void MainDialog::OnMenuAbout(wxCommandEvent& event)
+{
+ ffs3::showAboutDialog();
+}
+
+
+void MainDialog::OnShowHelp(wxCommandEvent& event)
+{
+ ffs3::displayHelpEntry();
+}
+
+
+void MainDialog::OnMenuQuit(wxCommandEvent& event)
+{
+ if (!saveOldConfig()) //notify user about changed settings
+ return;
+
+ Destroy();
+}
+
+//#########################################################################################################
+
+//language selection
+void MainDialog::switchProgramLanguage(const int langID)
+{
+ //create new dialog with respect to new language
+ CustomLocale::getInstance().setLanguage(langID); //language is a global attribute
+
+ const xmlAccess::XmlGuiConfig currentGuiCfg = getCurrentConfiguration();
+
+ cleanUp(false); //destructor's code: includes updating global settings
+
+ //create new main window and delete old one
+ MainDialog* frame = new MainDialog(currentGuiCfg, *globalSettings, false);
+ frame->Show();
+
+ Destroy();
+}
+
+
+void MainDialog::OnMenuLanguageSwitch(wxCommandEvent& event)
+{
+ std::map<MenuItemID, LanguageID>::const_iterator it = languageMenuItemMap.find(event.GetId());
+
+ if (it != languageMenuItemMap.end())
+ switchProgramLanguage(it->second);
+}
+
+//#########################################################################################################
+
+MainDialog::SyncPreview::SyncPreview(MainDialog* mainDlg) :
+ mainDlg_(mainDlg),
+ syncPreviewEnabled(false),
+ synchronizationEnabled(false) {}
+
+
+bool MainDialog::SyncPreview::previewIsEnabled() const
+{
+ return syncPreviewEnabled;
+}
+
+
+void MainDialog::SyncPreview::enablePreview(bool value)
+{
+ if (value)
+ {
+ syncPreviewEnabled = true;
+
+ //toggle display of sync preview in middle grid
+ mainDlg_->m_gridMiddle->enableSyncPreview(true);
+
+ mainDlg_->Refresh();
+ }
+ else
+ {
+ syncPreviewEnabled = false;
+
+ //toggle display of sync preview in middle grid
+ mainDlg_->m_gridMiddle->enableSyncPreview(false);
+
+ mainDlg_->Refresh();
+ }
+
+ mainDlg_->updateGuiGrid();
+}
+
+
+void MainDialog::SyncPreview::enableSynchronization(bool value)
+{
+ if (value)
+ {
+ synchronizationEnabled = true;
+ mainDlg_->m_buttonStartSync->SetForegroundColour(*wxBLACK);
+ mainDlg_->m_buttonStartSync->setBitmapFront(GlobalResources::getInstance().getImageByName(wxT("sync")));
+ }
+ else
+ {
+ synchronizationEnabled = false;
+ mainDlg_->m_buttonStartSync->SetForegroundColour(wxColor(128, 128, 128)); //Some colors seem to have problems with 16Bit color depth, well this one hasn't!
+ mainDlg_->m_buttonStartSync->setBitmapFront(GlobalResources::getInstance().getImageByName(wxT("syncDisabled")));
+ }
+}
+
+
+bool MainDialog::SyncPreview::synchronizationIsEnabled() const
+{
+ return synchronizationEnabled;
+}
bgstack15