// ************************************************************************** // * 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 "mainDialog.h" #include #include #include "../shared/systemConstants.h" #include #include #include #include #include "../library/customGrid.h" #include "../shared/customButton.h" #include "../shared/customComboBox.h" #include #include "../comparison.h" #include "../synchronization.h" #include "../algorithm.h" #include "../shared/appMain.h" #include "../shared/util.h" #include "checkVersion.h" #include "guiStatusHandler.h" #include "syncConfig.h" #include "../shared/localization.h" #include "../shared/stringConv.h" #include "smallDialogs.h" #include "mouseMoveWindow.h" #include "progressIndicator.h" #include "messagePopup.h" #include "../shared/dragAndDrop.h" #include "../library/filter.h" #include "../structures.h" #include #include #include "gridView.h" #include "../library/resources.h" #include "../shared/fileHandling.h" #include "../shared/recycler.h" #include "../shared/xmlBase.h" #include "../shared/standardPaths.h" #include "../shared/toggleButton.h" #include "folderPair.h" #include "../shared/globalFunctions.h" #include #include "search.h" #include "../shared/helpProvider.h" #include "isNullFilter.h" #include "batchConfig.h" #include "../shared/checkExist.h" using namespace FreeFileSync; using FreeFileSync::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 wxString& dropName) { const xmlAccess::XmlType fileType = xmlAccess::getXmlType(dropName); //test if ffs config file has been dropped if (fileType == xmlAccess::XML_GUI_CONFIG) { mainDlg_.loadConfiguration(dropName); return false; } //...or a ffs batch file else if (fileType == xmlAccess::XML_BATCH_CONFIG) { BatchDialog* batchDlg = new BatchDialog(&mainDlg_, dropName); if (batchDlg->ShowModal() == BatchDialog::BATCH_FILE_SAVED) mainDlg_.pushStatusInformation(_("Batch file created successfully!")); return false; } //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 FolderPairCallback : public FolderPairPanelBasic //implements callback functionality to MainDialog as imposed by FolderPairPanelBasic { public: FolderPairCallback(GuiPanel& basicPanel, MainDialog& mainDialog) : FolderPairPanelBasic(basicPanel), //pass FolderPairGenerated part... mainDlg(mainDialog) {} private: virtual void OnLocalFilterCfgRemoveConfirm(wxCommandEvent& event) { FolderPairPanelBasic::OnLocalFilterCfgRemoveConfirm(event); mainDlg.updateFilterConfig(); //update filter } virtual void OnAltSyncCfgRemoveConfirm(wxCommandEvent& event) { FolderPairPanelBasic::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 { public: FolderPairPanel(wxWindow* parent, MainDialog& mainDialog) : FolderPairGenerated(parent), FolderPairCallback(static_cast(*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 { public: FirstFolderPairCfg(MainDialog& mainDialog) : FolderPairCallback(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(newEntry, -1)); } private: typedef std::map MenuItemMap; wxMenu* menuToUpdate_; MenuItemMap menuItems; }; struct DirNotFound { bool operator()(const FolderPairEnh& fp) const { return !dirExists(FreeFileSync::getFormattedDirectoryName(fp.leftDirectory)) || !dirExists(FreeFileSync::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 actualConfigFile = cfgFileName.empty() ? lastConfigFileName() : cfgFileName; bool loadCfgSuccess = false; if (!cfgFileName.empty() || fileExists(wxToZ(lastConfigFileName()))) { //load XML try { xmlAccess::readGuiOrBatchConfig(actualConfigFile, guiCfg); //allow reading batch configurations also loadCfgSuccess = true; } catch (const xmlAccess::XmlError& error) { if (error.getSeverity() == xmlAccess::XmlError::WARNING) wxMessageBox(error.show(), _("Warning"), wxOK | wxICON_WARNING); else wxMessageBox(error.show(), _("Error"), wxOK | wxICON_ERROR); } } init(guiCfg, settings, !cfgFileName.empty() && loadCfgSuccess); setLastUsedConfig(actualConfigFile, 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 FreeFileSync::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 FreeFileSync::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 (!FreeFileSync::isPortableVersion()) //disable update check for Linux installer-based version -> handled by .deb m_menuItemCheckVer->Enable(false); #endif //create language selection menu for (std::vector::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::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 FreeFileSync::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->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) 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::reverse_iterator i = globalSettings->gui.cfgFileHistory.rbegin(); i != globalSettings->gui.cfgFileHistory.rend(); ++i) addFileToCfgHistory(*i); //load list of last used folders for (std::vector::reverse_iterator i = globalSettings->gui.folderHistoryLeft.rbegin(); i != globalSettings->gui.folderHistoryLeft.rend(); ++i) addLeftFolderToHistory(*i); for (std::vector::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& rowsToSetOnUiTable, const FreeFileSync::SyncDirection dir) { if (rowsToSetOnUiTable.size() > 0) { for (std::set::const_iterator i = rowsToSetOnUiTable.begin(); i != rowsToSetOnUiTable.end(); ++i) { FileSystemObject* fsObj = gridDataView->getObject(*i); if (fsObj) { setSyncDirectionRec(dir, *fsObj); //set new direction (recursively) FreeFileSync::setActiveStatus(true, *fsObj); //works recursively for directories } } updateGuiGrid(); } } void MainDialog::filterRangeManually(const std::set& 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 compRef; gridDataView->getAllFileRef(rowsToFilterOnUiTable, compRef); //everything in compRef is bound for (std::vector::iterator i = compRef.begin(); i != compRef.end(); ++i) FreeFileSync::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 selectedRows = getSelectedRows(selectedGrid); if (selectedRows.size() > 0) { wxString clipboardString; for (std::set::const_iterator i = selectedRows.begin(); i != selectedRows.end(); ++i) { for (int k = 0; k < const_cast(selectedGrid)->GetNumberCols(); ++k) { clipboardString+= const_cast(selectedGrid)->GetCellValue(static_cast(*i), k); if (k != const_cast(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 MainDialog::getSelectedRows(const CustomGrid* grid) const { std::set output = grid->getAllSelectedRows(); //remove invalid rows output.erase(output.lower_bound(gridDataView->rowsOnView()), output.end()); return output; } std::set MainDialog::getSelectedRows() const { //merge selections from left and right grid std::set selection = getSelectedRows(m_gridLeft); std::set 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 FreeFileSync::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(rv)) { case ErrorDlg::BUTTON_IGNORE: ignoreErrors = ignoreNextErrors; return DeleteFilesHandler::IGNORE_ERROR; case ErrorDlg::BUTTON_RETRY: return DeleteFilesHandler::RETRY; case ErrorDlg::BUTTON_ABORT: throw FreeFileSync::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"), FreeFileSync::numberToStringSep(deletionCount), false); statusMessage.Replace(wxT("%y"), FreeFileSync::numberToStringSep(totalObjToDelete), false); mainDlg->m_staticTextStatusMiddle->SetLabel(statusMessage); mainDlg->m_panelStatusBar->Layout(); updateUiNow(); } if (abortRequested) //test after (implicit) call to wxApp::Yield() throw FreeFileSync::AbortThisProcess(); } private: void OnAbortCompare(wxCommandEvent& event) //handle abort button click { abortRequested = true; //don't throw exceptions in a GUI-Callback!!! (throw FreeFileSync::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 viewSelectionLeft = getSelectedRows(m_gridLeft); const std::set viewSelectionRight = getSelectedRows(m_gridRight); if (viewSelectionLeft.size() + viewSelectionRight.size()) { //map lines from GUI view to grid line references std::vector compRefLeft; gridDataView->getAllFileRef(viewSelectionLeft, compRefLeft); std::vector compRefRight; gridDataView->getAllFileRef(viewSelectionRight, compRefRight); int totalDeleteCount = 0; if (FreeFileSync::showDeleteDialog(compRefLeft, compRefRight, globalSettings->gui.deleteOnBothSides, globalSettings->gui.useRecyclerForManualDeletion, totalDeleteCount) == DefaultReturnCode::BUTTON_OKAY) { if (globalSettings->gui.useRecyclerForManualDeletion && !FreeFileSync::recycleBinExists()) { wxMessageBox(_("Recycle Bin not yet supported for this system!")); return; } try { //handle errors when deleting files/folders ManualDeletionHandler statusHandler(this, totalDeleteCount); FreeFileSync::deleteFromGridAndHD(gridDataView->getDataTentative(), compRefLeft, compRefRight, globalSettings->gui.deleteOnBothSides, globalSettings->gui.useRecyclerForManualDeletion, getCurrentConfiguration().mainCfg, &statusHandler); } catch (FreeFileSync::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 void exstractNames(const FileSystemObject& fsObj, wxString& name, wxString& dir) { if (!fsObj.isEmpty()) { struct GetNames : public FSObjectVisitor { GetNames(wxString& nameIn, wxString& dirIn) : name_(nameIn), dir_(dirIn) {} virtual void visit(const FileMapping& fileObj) { name_ = zToWx(fileObj.getFullName()); dir_ = zToWx(fileObj.getFullName().BeforeLast(globalFunctions::FILE_NAME_SEPARATOR)); } virtual void visit(const SymLinkMapping& linkObj) { name_ = zToWx(linkObj.getFullName()); dir_ = zToWx(linkObj.getFullName().BeforeLast(globalFunctions::FILE_NAME_SEPARATOR)); } virtual void visit(const DirMapping& dirObj) { dir_ = name_ = zToWx(dirObj.getFullName()); } 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( *fsObj, name, dir); exstractNames(*fsObj, nameCo, dirCo); } else { exstractNames(*fsObj, name, dir); exstractNames( *fsObj, nameCo, dirCo); } #ifdef FFS_WIN if (name.empty()) { if (leftSide) wxExecute(wxString(wxT("explorer ")) + zToWx(fsObj->getBaseDirPf())); else wxExecute(wxString(wxT("explorer ")) + zToWx(fsObj->getBaseDirPf())); return; } #endif } else { //fallback dir = zToWx(FreeFileSync::getFormattedDirectoryName(firstFolderPair->getLeftDir())); dirCo = zToWx(FreeFileSync::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 >= 0 && y >= 0) //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; 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::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 FreeFileSync::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: // FreeFileSync::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 FreeFileSync::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: // FreeFileSync::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 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(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 selectionLeft = getSelectedRows(m_gridLeft); const std::set 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::const_iterator i = selectionLeft.begin(); i != selectionLeft.end(); ++i) { const FileSystemObject* currObj = gridDataView->getObject(*i); if (currObj && !currObj->isEmpty()) currObj->accept(newFilterEntry); } for (std::set::const_iterator i = selectionRight.begin(); i != selectionRight.end(); ++i) { const FileSystemObject* currObj = gridDataView->getObject(*i); if (currObj && !currObj->isEmpty()) currObj->accept(newFilterEntry); } //############################################################################################### //CONTEXT_EXCLUDE_EXT if (exFilterCandidateObj.size() > 0 && !exFilterCandidateObj[0].isDir) { const Zstring filename = exFilterCandidateObj[0].relativeName.AfterLast(globalFunctions::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(globalFunctions::FILE_NAME_SEPARATOR))); else if (exFilterCandidateObj.size() > 1) menuItemExclObj = new wxMenuItem(contextMenu.get(), CONTEXT_EXCLUDE_OBJ, wxString(_("Exclude via filter:")) + wxT(" ") + _("")); 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 selection = getSelectedRows(); if (!selection.empty()) filterRangeManually(selection, static_cast(*selection.begin())); } void MainDialog::OnContextExcludeExtension(wxCommandEvent& event) { SelectedExtension* selExtension = dynamic_cast(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(event.m_callbackUserData); if (objCont) { if (objCont->selectedObjects.size() > 0) //check needed to determine if filtering is needed { Zstring newExclude; for (std::vector::const_iterator i = objCont->selectedObjects.begin(); i != objCont->selectedObjects.end(); ++i) { if (i != objCont->selectedObjects.begin()) newExclude += DefaultStr("\n"); newExclude += globalFunctions::FILE_NAME_SEPARATOR + i->relativeName; if (i->isDir) newExclude += globalFunctions::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(m_gridLeft) : static_cast(m_gridRight); std::set selection = getSelectedRows(leadGrid); const int index = event.GetId() - externalAppIDFirst; if ( selection.size() == 1 && 0 <= index && static_cast(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 selection = getSelectedRows(); setSyncDirManually(selection, FreeFileSync::SYNC_DIR_LEFT); } void MainDialog::OnContextSyncDirNone(wxCommandEvent& event) { //merge selections from left and right grid const std::set selection = getSelectedRows(); setSyncDirManually(selection, FreeFileSync::SYNC_DIR_NONE); } void MainDialog::OnContextSyncDirRight(wxCommandEvent& event) { //merge selections from left and right grid const std::set selection = getSelectedRows(); setSyncDirManually(selection, FreeFileSync::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 (FreeFileSync::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 (FreeFileSync::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) { FreeFileSync::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) { FreeFileSync::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 Utility::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 (Utility::fileExists(wxToZ(filename), 200) == Utility::EXISTING_FALSE) //potentially slow network access: wait 200ms return; std::vector::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 (Utility::sameFileSpecified(wxToZ(lastConfigFileName()), wxToZ(filename))) m_choiceHistory->Insert(_(""), 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 (FreeFileSync::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 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) FreeFileSync::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 { xmlAccess::readGuiOrBatchConfig(filename, newGuiCfg); //allow reading batch configurations also parsingError = false; } catch (const xmlAccess::XmlError& error) { if (error.getSeverity() == xmlAccess::XmlError::WARNING) wxMessageBox(error.show(), _("Warning"), wxOK | wxICON_WARNING); else { wxMessageBox(error.show(), _("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::writeGuiConfig(guiCfg, filename); } catch (const xmlAccess::XmlError& error) { wxMessageBox(error.show().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 = FreeFileSync::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::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 FreeFileSync::CompareProcess comparison(currentCfg.mainCfg.handleSymlinks, currentCfg.mainCfg.hidden.fileTimeTolerance, globalSettings->ignoreOneHourDiff, globalSettings->optDialogs, &statusHandler); //technical representation of comparison data FreeFileSync::FolderComparison newCompareData; comparison.startCompareProcess( FreeFileSync::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 = FreeFileSync::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 = globalFunctions::getDigitCount(static_cast(nrOfRows)); m_gridLeft ->SetRowLabelSize(static_cast(nrOfDigits * digitWidth + 4)); m_gridRight->SetRowLabelSize(static_cast(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 = FreeFileSync::numberToStringSep(st.getCreate()); const wxString toUpdate = FreeFileSync::numberToStringSep(st.getOverwrite()); const wxString toDelete = FreeFileSync::numberToStringSep(st.getDelete()); const wxString data = FreeFileSync::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, ¤tCfg.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 (FreeFileSync::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(); 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 (FreeFileSync::showSyncPreviewDlg( getCurrentConfiguration().mainCfg.getSyncVariantName(), FreeFileSync::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 FreeFileSync::SyncProcess synchronization( globalSettings->optDialogs, currentCfg.mainCfg.hidden.verifyFileCopy, globalSettings->copyLockedFiles, statusHandler); const std::vector syncProcessCfg = FreeFileSync::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 = FreeFileSync::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::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 FreeFileSync::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 = FreeFileSync::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 = FreeFileSync::numberToStringSep(filesOnLeftView); wxString outputString = _("%x files"); outputString.Replace(wxT("%x"), fileCount, false); statusLeftNew += outputString; } statusLeftNew += wxT(" - "); statusLeftNew += FreeFileSync::formatFilesizeToShortString(filesizeLeftView); } const wxString objectsView = FreeFileSync::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 = FreeFileSync::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 = FreeFileSync::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 = FreeFileSync::numberToStringSep(filesOnRightView); wxString outputString = _("%x files"); outputString.Replace(wxT("%x"), fileCount, false); statusRightNew += outputString; } statusRightNew += wxT(" - "); statusRightNew += FreeFileSync::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 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); FreeFileSync::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::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& newPairs, bool addFront) { wxWindowUpdateLocker dummy(this); //avoid display distortion if (!newPairs.empty()) { int pairHeight = 0; for (std::vector::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(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(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) { FreeFileSync::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 (FreeFileSync::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 xmlAccess::XmlGuiConfig currCfg = getCurrentConfiguration(); //get UP TO DATE config, with updated values for main and additional folders! xmlAccess::XmlBatchConfig batchCfg; batchCfg.mainCfg = currCfg.mainCfg; if (currentCfg.ignoreErrors) batchCfg.handleError = xmlAccess::ON_ERROR_IGNORE; else batchCfg.handleError = xmlAccess::ON_ERROR_POPUP; BatchDialog* batchDlg = new BatchDialog(this, batchCfg); if (batchDlg->ShowModal() == BatchDialog::BATCH_FILE_SAVED) pushStatusInformation(_("Batch file created successfully!")); } void MainDialog::OnMenuCheckVersion(wxCommandEvent& event) { FreeFileSync::checkForUpdateNow(); } void MainDialog::OnRegularUpdateCheck(wxIdleEvent& event) { //execute just once per startup! Disconnect(wxEVT_IDLE, wxIdleEventHandler(MainDialog::OnRegularUpdateCheck), NULL, this); FreeFileSync::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) { FreeFileSync::showAboutDialog(); } void MainDialog::OnShowHelp(wxCommandEvent& event) { FreeFileSync::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::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; }