diff options
author | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 16:56:34 +0200 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2014-04-18 16:56:34 +0200 |
commit | 9084fa27f0f43cfa31dbc3a7ef87e2600c2dc3ca (patch) | |
tree | 61e2edc315a164d6fa3940b7de4b14dda0a9838c /ui/MainDialog.cpp | |
parent | 1.15 (diff) | |
download | FreeFileSync-9084fa27f0f43cfa31dbc3a7ef87e2600c2dc3ca.tar.gz FreeFileSync-9084fa27f0f43cfa31dbc3a7ef87e2600c2dc3ca.tar.bz2 FreeFileSync-9084fa27f0f43cfa31dbc3a7ef87e2600c2dc3ca.zip |
1.16
Diffstat (limited to 'ui/MainDialog.cpp')
-rw-r--r-- | ui/MainDialog.cpp | 1340 |
1 files changed, 788 insertions, 552 deletions
diff --git a/ui/MainDialog.cpp b/ui/MainDialog.cpp index ead2084f..aa630122 100644 --- a/ui/MainDialog.cpp +++ b/ui/MainDialog.cpp @@ -1,10 +1,8 @@ /*************************************************************** - * Name: FreeFileSyncMain.cpp - * Purpose: Code for Application Frame + * Purpose: Code for main dialog * Author: ZenJu (zhnmju123@gmx.de) * Created: 2008-07-16 * Copyright: ZenJu () - * License: **************************************************************/ #include "mainDialog.h" @@ -22,41 +20,33 @@ #include "../synchronization.h" #include "../algorithm.h" -using namespace globalFunctions; -using namespace xmlAccess; - - -extern const wxGrid* leadGrid = NULL; MainDialog::MainDialog(wxFrame* frame, const wxString& cfgFileName, CustomLocale* language, xmlAccess::XmlGlobalSettings& settings) : MainDialogGenerated(frame), globalSettings(settings), + contextMenu(new wxMenu), //initialize right-click context menu; will be dynamically re-created on each R-mouse-click programLanguage(language), filteringInitialized(false), filteringPending(false), synchronizationEnabled(false), - restartOnExit(false), - cmpStatusHandlerTmp(0) + cmpStatusHandlerTmp(0), + cleanedUp(false), + lastSortColumn(-1), + lastSortGrid(NULL), + leadGrid(NULL) { //initialize and load configuration readGlobalSettings(); readConfigurationFromXml(cfgFileName, true); - leftOnlyFilesActive = true; - leftNewerFilesActive = true; - differentFilesActive = true; - rightNewerFilesActive = true; //do not save/load these bool values from harddisk! - rightOnlyFilesActive = true; //it's more convenient to have them defaulted at startup - equalFilesActive = false; - updateViewFilterButtons(); - //set icons for this dialog m_bpButton10->SetBitmapLabel(*globalResource.bitmapExit); m_buttonCompare->setBitmapFront(*globalResource.bitmapCompare); m_buttonSync->setBitmapFront(*globalResource.bitmapSync); m_bpButtonSwap->SetBitmapLabel(*globalResource.bitmapSwap); m_bpButton14->SetBitmapLabel(*globalResource.bitmapHelp); - m_bpButton201->SetBitmapLabel(*globalResource.bitmapSave); + m_bpButtonSave->SetBitmapLabel(*globalResource.bitmapSave); + m_bpButtonLoad->SetBitmapLabel(*globalResource.bitmapLoad); m_bpButtonAddPair->SetBitmapLabel(*globalResource.bitmapAddFolderPair); m_bpButtonRemovePair->SetBitmapLabel(*globalResource.bitmapRemoveFolderPair); m_bpButtonRemovePair->SetBitmapDisabled(*globalResource.bitmapRemoveFolderPairD); @@ -81,14 +71,14 @@ MainDialog::MainDialog(wxFrame* frame, const wxString& cfgFileName, CustomLocale m_menu3->Insert(3, m_menuItemGlobSett); //prepare drag & drop - m_panel1->SetDropTarget(new FileDropEvent(this, m_panel1)); - m_panel2->SetDropTarget(new FileDropEvent(this, m_panel2)); + m_panel1->SetDropTarget(new MainWindowDropTarget(this, m_panel1)); + m_panel2->SetDropTarget(new MainWindowDropTarget(this, m_panel2)); - m_panel11->SetDropTarget(new FileDropEvent(this, m_panel1)); - m_panel12->SetDropTarget(new FileDropEvent(this, m_panel2)); + m_panel11->SetDropTarget(new MainWindowDropTarget(this, m_panel1)); + m_panel12->SetDropTarget(new MainWindowDropTarget(this, m_panel2)); - //initialize right-click context menu; will be dynamically re-created on each R-mouse-click - contextMenu = new wxMenu; + //redirect drag & drop event back to main window + Connect(FFS_DROP_FILE_EVENT, FfsFileDropEventHandler(MainDialog::OnFilesDropped), NULL, this); //support for CTRL + C and DEL m_gridLeft->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridLeftButtonEvent), NULL, this); @@ -199,37 +189,26 @@ MainDialog::MainDialog(wxFrame* frame, const wxString& cfgFileName, CustomLocale int spaceToAdd = source.GetX() - target.GetX(); bSizerMiddle->Insert(1, spaceToAdd / 2, 0, 0); bSizerMiddle->Insert(0, spaceToAdd - (spaceToAdd / 2), 0, 0); - - { //set minimum width for choice load - wxSize old; - if ((old = m_choiceLoad->GetSize()).x < 140) - { - old.x = 140; - m_choiceLoad->SetMinSize(old); - bSizer58->Layout(); - } - } } MainDialog::~MainDialog() { - m_gridLeft->setSortMarker(-1); - m_gridRight->setSortMarker(-1); + cleanUp(); //do NOT include any other code here! cleanUp() is re-used when switching languages +} - //no need for event disconnect here; done automatically - delete contextMenu; +void MainDialog::cleanUp() +{ + if (!cleanedUp) + { + cleanedUp = true; - //save configuration - writeConfigurationToXml(FreeFileSync::LAST_CONFIG_FILE); //don't trow exceptions in destructors - writeGlobalSettings(); + //no need for event disconnect here; done automatically - if (restartOnExit) //this is needed so that restart happens AFTER configuration was written! - { //create new dialog - MainDialog* frame = new MainDialog(NULL, FreeFileSync::LAST_CONFIG_FILE, programLanguage, globalSettings); - frame->SetIcon(*globalResource.programIcon); //set application icon - frame->Show(); + //save configuration + writeConfigurationToXml(FreeFileSync::LAST_CONFIG_FILE); //don't trow exceptions in destructors + writeGlobalSettings(); } } @@ -260,7 +239,7 @@ void MainDialog::readGlobalSettings() i != globalSettings.gui.cfgFileHistory.rend(); ++i) addCfgFileToHistory(*i); - m_choiceLoad->SetSelection(0); + m_choiceHistory->SetSelection(0); } @@ -283,15 +262,69 @@ void MainDialog::writeGlobalSettings() } +inline +bool gridShouldBeCleared(const wxEvent& event) +{ + try //test if CTRL was pressed during a mouse event + { + const wxMouseEvent& mouseEvent = dynamic_cast<const wxMouseEvent&> (event); + if (mouseEvent.ButtonDown(wxMOUSE_BTN_LEFT) && !mouseEvent.ControlDown() && !mouseEvent.ShiftDown()) + return true; + else + return false; + } + catch (std::bad_cast&) {} + + try //test if CTRL was pressed during a key event + { + const wxKeyEvent& keyEvent = dynamic_cast<const wxKeyEvent&> (event); + + if (keyEvent.ShiftDown()) + return false; + + switch (keyEvent.GetKeyCode()) + { + case WXK_SPACE: + case WXK_TAB: + case WXK_RETURN: + case WXK_ESCAPE: + case WXK_NUMPAD_ENTER: + case WXK_LEFT: + case WXK_UP: + case WXK_RIGHT: + case WXK_DOWN: + return true; + + default: + return false; + } + } + catch (std::bad_cast&) {} + + return false; +} + + void MainDialog::onGridLeftAccess(wxEvent& event) { if (leadGrid != m_gridLeft) { leadGrid = m_gridLeft; + + //notify grids of new user focus + m_gridLeft->setLeadGrid(leadGrid); + m_gridMiddle->setLeadGrid(leadGrid); + m_gridRight->setLeadGrid(leadGrid); + m_gridLeft->SetFocus(); + } + if (gridShouldBeCleared(event)) + { + m_gridLeft->ClearSelection(); m_gridRight->ClearSelection(); } + event.Skip(); } @@ -301,10 +334,21 @@ void MainDialog::onGridRightAccess(wxEvent& event) if (leadGrid != m_gridRight) { leadGrid = m_gridRight; + + //notify grids of new user focus + m_gridLeft->setLeadGrid(leadGrid); + m_gridMiddle->setLeadGrid(leadGrid); + m_gridRight->setLeadGrid(leadGrid); + m_gridRight->SetFocus(); + } + if (gridShouldBeCleared(event)) + { m_gridLeft->ClearSelection(); + m_gridRight->ClearSelection(); } + event.Skip(); } @@ -314,9 +358,21 @@ void MainDialog::onGridMiddleAccess(wxEvent& event) if (leadGrid != m_gridMiddle) { leadGrid = m_gridMiddle; + + //notify grids of new user focus + m_gridLeft->setLeadGrid(leadGrid); + m_gridMiddle->setLeadGrid(leadGrid); + m_gridRight->setLeadGrid(leadGrid); + + m_gridMiddle->SetFocus(); + } + + if (gridShouldBeCleared(event)) + { m_gridLeft->ClearSelection(); m_gridRight->ClearSelection(); } + event.Skip(); } @@ -363,9 +419,8 @@ void MainDialog::filterRangeManually(const std::set<int>& rowsToFilterOnUiTable) if (0 <= *i && *i < gridSizeUI) { unsigned int gridIndex = gridRefUI[*i]; - rowsToFilterOnGridData.insert(gridIndex); - FreeFileSync::addSubElements(rowsToFilterOnGridData, currentGridData, currentGridData[gridIndex]); + FreeFileSync::addSubElements(currentGridData, currentGridData[gridIndex], rowsToFilterOnGridData); } } @@ -437,8 +492,8 @@ void MainDialog::OnIdleEvent(wxEvent& event) { //a mouse up event, but no mouse down! (e.g. when window is maximized and cursor is on grid3) filteringInitialized = false; - if (leadGrid) - filterRangeManually(getSelectedRows(leadGrid)); + if (m_gridMiddle) + filterRangeManually(getSelectedRows(m_gridMiddle)); } } @@ -499,7 +554,7 @@ void removeInvalidRows(std::set<int>& rows, const int currentUiTableSize) for (std::set<int>::iterator i = rows.begin(); i != rows.end(); ++i) if (0 <= *i) { - if (*i >= currentUiTableSize) //set is sorted, so no need to continue here + if (*i >= currentUiTableSize) //set is sorted, so no need to continue here break; validRows.insert(*i); } @@ -521,7 +576,7 @@ std::set<int> MainDialog::getSelectedRows(const wxGrid* grid) if (!grid->GetSelectedCols().IsEmpty()) //if a column is selected this is means all rows are marked for deletion { - for (int k = 0; k < const_cast<wxGrid*>(grid)->GetNumberRows(); ++k) + for (int k = 0; k < const_cast<wxGrid*>(grid)->GetNumberRows(); ++k) //messy wxGrid implementation... output.insert(k); } @@ -552,6 +607,10 @@ std::set<int> MainDialog::getSelectedRows(const wxGrid* grid) } } + //some exception: also add current cursor row to selection if there are no others... hopefully improving usability + if (output.empty() && grid == leadGrid) + output.insert(const_cast<wxGrid*>(grid)->GetCursorRow()); //messy wxGrid implementation... + removeInvalidRows(output, gridRefUI.size()); return output; @@ -561,115 +620,86 @@ std::set<int> MainDialog::getSelectedRows(const wxGrid* grid) class DeleteErrorHandler : public ErrorHandler { public: - DeleteErrorHandler(wxWindow* parentWindow, bool& unsolvedErrorOccured) : - parent(parentWindow), - ignoreErrors(false), - unsolvedErrors(unsolvedErrorOccured) {} + DeleteErrorHandler() : + ignoreErrors(false) {} + ~DeleteErrorHandler() {} - Response reportError(const Zstring& text) + Response reportError(const Zstring& errorMessage) { if (ignoreErrors) - { - unsolvedErrors = true; return ErrorHandler::IGNORE_ERROR; - } bool ignoreNextErrors = false; - wxString errorMessage = wxString(text.c_str()) + wxT("\n\n") + _("Information: If you ignore the error or abort a re-compare will be necessary!"); - ErrorDlg* errorDlg = new ErrorDlg(parent, errorMessage, ignoreNextErrors); - + ErrorDlg* errorDlg = new ErrorDlg(NULL, + ErrorDlg::BUTTON_IGNORE | ErrorDlg::BUTTON_RETRY | ErrorDlg::BUTTON_ABORT, + errorMessage.c_str(), ignoreNextErrors); int rv = errorDlg->ShowModal(); + errorDlg->Destroy(); switch (rv) { case ErrorDlg::BUTTON_IGNORE: ignoreErrors = ignoreNextErrors; - unsolvedErrors = true; return ErrorHandler::IGNORE_ERROR; case ErrorDlg::BUTTON_RETRY: return ErrorHandler::RETRY; case ErrorDlg::BUTTON_ABORT: - { - unsolvedErrors = true; throw AbortThisProcess(); - } default: assert (false); + return ErrorHandler::IGNORE_ERROR; //dummy return value } - - return ErrorHandler::IGNORE_ERROR; //dummy return value } -private: - wxWindow* parent; +private: bool ignoreErrors; - bool& unsolvedErrors; }; -void MainDialog::deleteFilesOnGrid(const std::set<int>& rowsToDeleteOnUI) +void MainDialog::deleteFilesOnGrid(const std::set<int>& selectedRowsLeft, const std::set<int>& selectedRowsRight) { - if (rowsToDeleteOnUI.size()) + if (selectedRowsLeft.size() + selectedRowsRight.size()) { //map grid lines from UI to grid lines in backend (gridData) - std::set<int> rowsToDeleteOnGrid; - for (std::set<int>::iterator i = rowsToDeleteOnUI.begin(); i != rowsToDeleteOnUI.end(); ++i) - rowsToDeleteOnGrid.insert(gridRefUI[*i]); - - wxString headerText; - wxString filesToDelete; - - if (cfg.useRecycleBin) - headerText = _("Do you really want to move the following objects(s) to the recycle bin?"); - else - headerText = _("Do you really want to delete the following objects(s)?"); - - for (std::set<int>::iterator i = rowsToDeleteOnGrid.begin(); i != rowsToDeleteOnGrid.end(); ++i) + std::set<int> rowsOnGridLeft; + for (std::set<int>::iterator i = selectedRowsLeft.begin(); i != selectedRowsLeft.end(); ++i) + rowsOnGridLeft.insert(gridRefUI[*i]); + + std::set<int> rowsOnGridRight; + for (std::set<int>::iterator i = selectedRowsRight.begin(); i != selectedRowsRight.end(); ++i) + rowsOnGridRight.insert(gridRefUI[*i]); + + DeleteDialog* confirmDeletion = new DeleteDialog(this, //no destruction needed; attached to main window + currentGridData, + rowsOnGridLeft, + rowsOnGridRight, + globalSettings.gui.deleteOnBothSides, + globalSettings.gui.useRecyclerForManualDeletion); + if (confirmDeletion->ShowModal() == DeleteDialog::BUTTON_OKAY) { - const FileCompareLine& currentCmpLine = currentGridData[*i]; - - if (currentCmpLine.fileDescrLeft.objType != FileDescrLine::TYPE_NOTHING) - filesToDelete += (currentCmpLine.fileDescrLeft.fullName + wxT("\n")).c_str(); - - if (currentCmpLine.fileDescrRight.objType != FileDescrLine::TYPE_NOTHING) - filesToDelete += (currentCmpLine.fileDescrRight.fullName + wxT("\n")).c_str(); - - filesToDelete+= wxT("\n"); - } - - DeleteDialog* confirmDeletion = new DeleteDialog(headerText, filesToDelete, this); //no destruction needed; attached to main window - - switch (confirmDeletion->ShowModal()) - { - case DeleteDialog::BUTTON_OKAY: - { - bool unsolvedErrorOccured = false; //if an error is skipped a re-compare will be necessary! + //Attention! Modifying the grid is highly critical! There MUST NOT BE any accesses to gridRefUI until this reference table is updated + //by writeGrid()!! This is easily missed, e.g. when ClearSelection() or ShowModal() or possibly any other wxWidgets function is called + //that might want to redraw the UI (which implicitly uses the information in gridRefUI and currentGridData (see CustomGrid) try { //handle errors when deleting files/folders - DeleteErrorHandler errorHandler(this, unsolvedErrorOccured); - - FreeFileSync::deleteOnGridAndHD(currentGridData, rowsToDeleteOnGrid, &errorHandler, cfg.useRecycleBin); + DeleteErrorHandler errorHandler; + + FreeFileSync::deleteFromGridAndHD(currentGridData, + rowsOnGridLeft, + rowsOnGridRight, + globalSettings.gui.deleteOnBothSides, + globalSettings.gui.useRecyclerForManualDeletion, + &errorHandler); } - catch (AbortThisProcess& theException) - {} - - //disable the sync button if errors occured during deletion - if (unsolvedErrorOccured) - enableSynchronization(false); + catch (AbortThisProcess&) {} //redraw grid neccessary to update new dimensions and for UI-Backend data linkage - writeGrid(currentGridData); + writeGrid(currentGridData); //call immediately after deleteFromGridAndHD!!! m_gridLeft->ClearSelection(); - m_gridRight->ClearSelection(); m_gridMiddle->ClearSelection(); - } - break; - - case DeleteDialog::BUTTON_CANCEL: - default: - break; + m_gridRight->ClearSelection(); } } } @@ -701,11 +731,20 @@ void MainDialog::openWithFileManager(int rowNumber, const wxGrid* grid) return; } - if (fileDescr && fileDescr->objType != FileDescrLine::TYPE_NOTHING) + if (fileDescr) { - command = globalSettings.gui.commandLineFileManager; - command.Replace(wxT("%x"), fileDescr->fullName.c_str()); - command.Replace(wxT("%path"), wxFileName(fileDescr->fullName.c_str()).GetPath()); + if (fileDescr->objType == FileDescrLine::TYPE_FILE) + { + command = globalSettings.gui.commandLineFileManager; + command.Replace(wxT("%name"), fileDescr->fullName.c_str()); + command.Replace(wxT("%path"), fileDescr->relativeName.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR).c_str()); + } + else if (fileDescr->objType == FileDescrLine::TYPE_DIRECTORY) + { + command = globalSettings.gui.commandLineFileManager; + command.Replace(wxT("%name"), fileDescr->fullName.c_str()); + command.Replace(wxT("%path"), fileDescr->relativeName.c_str()); + } } if (!command.empty()) @@ -716,10 +755,8 @@ void MainDialog::openWithFileManager(int rowNumber, const wxGrid* grid) void MainDialog::pushStatusInformation(const wxString& text) { lastStatusChange = wxGetLocalTimeMillis(); - stackObjects.push(m_staticTextStatusMiddle->GetLabel()); m_staticTextStatusMiddle->SetLabel(text); - m_panel7->Layout(); } @@ -762,158 +799,182 @@ void MainDialog::onResizeMainWindow(wxEvent& event) void MainDialog::onGridLeftButtonEvent(wxKeyEvent& event) { - //CTRL + C || CTRL + INS - if ( event.ControlDown() && event.GetKeyCode() == 67 || - event.ControlDown() && event.GetKeyCode() == WXK_INSERT) - copySelectionToClipboard(m_gridLeft); + const int keyCode = event.GetKeyCode(); - else if (event.GetKeyCode() == WXK_DELETE) - deleteFilesOnGrid(getSelectedRows(m_gridLeft)); + if (event.ControlDown()) + { + if (keyCode == 67 || keyCode == WXK_INSERT) //CTRL + C || CTRL + INS + copySelectionToClipboard(m_gridLeft); + else if (keyCode == 65) //CTRL + A + m_gridLeft->SelectAll(); + } + else if (keyCode == WXK_DELETE || keyCode == WXK_NUMPAD_DELETE) + deleteFilesOnGrid(getSelectedRows(m_gridLeft), getSelectedRows(m_gridRight)); event.Skip(); } -void MainDialog::onGridRightButtonEvent(wxKeyEvent& event) +void MainDialog::onGridMiddleButtonEvent(wxKeyEvent& event) { - //CTRL + C || CTRL + INS - if ( event.ControlDown() && event.GetKeyCode() == 67 || - event.ControlDown() && event.GetKeyCode() == WXK_INSERT) - copySelectionToClipboard(m_gridRight); + const int keyCode = event.GetKeyCode(); - else if (event.GetKeyCode() == WXK_DELETE) - deleteFilesOnGrid(getSelectedRows(m_gridRight)); + if (event.ControlDown()) + { + if (keyCode == 67 || keyCode == WXK_INSERT) //CTRL + C || CTRL + INS + copySelectionToClipboard(m_gridMiddle); + } event.Skip(); } -void MainDialog::onGridMiddleButtonEvent(wxKeyEvent& event) +void MainDialog::onGridRightButtonEvent(wxKeyEvent& event) { - //CTRL + C || CTRL + INS - if ( event.ControlDown() && event.GetKeyCode() == 67 || - event.ControlDown() && event.GetKeyCode() == WXK_INSERT) - copySelectionToClipboard(m_gridMiddle); + const int keyCode = event.GetKeyCode(); - else if (event.GetKeyCode() == WXK_DELETE) - deleteFilesOnGrid(getSelectedRows(m_gridMiddle)); + if (event.ControlDown()) + { + if (keyCode == 67 || keyCode == WXK_INSERT) //CTRL + C || CTRL + INS + copySelectionToClipboard(m_gridRight); + else if (keyCode == 65) //CTRL + A + m_gridRight->SelectAll(); + } + else if (keyCode == WXK_DELETE || keyCode == WXK_NUMPAD_DELETE) + deleteFilesOnGrid(getSelectedRows(m_gridLeft), getSelectedRows(m_gridRight)); event.Skip(); } -void MainDialog::OnOpenContextMenu(wxGridEvent& event) +void MainDialog::OnContextMenu(wxGridEvent& event) { - std::set<int> selection; - - if (leadGrid) - selection = getSelectedRows(leadGrid); - - exFilterCandidateExtension.Clear(); - exFilterCandidateObj.clear(); + std::set<int> selectionLeft = getSelectedRows(m_gridLeft); + std::set<int> selectionRight = getSelectedRows(m_gridRight); //####################################################### -//re-create context menu - delete contextMenu; - contextMenu = new wxMenu; + //re-create context menu + contextMenu.reset(new wxMenu); - //dynamic filter determination - if (selection.size() > 0) + //CONTEXT_FILTER_TEMP + if (selectionLeft.size() + selectionRight.size() > 0) { - const FileCompareLine& firstLine = currentGridData[gridRefUI[*selection.begin()]]; + int selectionBegin = 0; + if (!selectionLeft.size()) + selectionBegin = *selectionRight.begin(); + else if (!selectionRight.size()) + selectionBegin = *selectionLeft.begin(); + else + selectionBegin = std::min(*selectionLeft.begin(), *selectionRight.begin()); + + const FileCompareLine& firstLine = currentGridData[gridRefUI[selectionBegin]]; if (firstLine.selectedForSynchronization) contextMenu->Append(CONTEXT_FILTER_TEMP, _("Exclude temporarily")); else contextMenu->Append(CONTEXT_FILTER_TEMP, _("Include temporarily")); + } + else + { + contextMenu->Append(CONTEXT_FILTER_TEMP, _("Exclude temporarily")); //this element should always be visible + contextMenu->Enable(CONTEXT_FILTER_TEMP, false); + } - //get list of relative file/dir-names into vectors - FilterObject newFilterEntry; - if (leadGrid == m_gridLeft) - for (std::set<int>::iterator i = selection.begin(); i != selection.end(); ++i) - { - const FileCompareLine& line = currentGridData[gridRefUI[*i]]; - newFilterEntry.relativeName = line.fileDescrLeft.relativeName.c_str(); - newFilterEntry.type = line.fileDescrLeft.objType; - if (!newFilterEntry.relativeName.IsEmpty()) - exFilterCandidateObj.push_back(newFilterEntry); - } - else if (leadGrid == m_gridRight) - for (std::set<int>::iterator i = selection.begin(); i != selection.end(); ++i) - { - const FileCompareLine& line = currentGridData[gridRefUI[*i]]; - newFilterEntry.relativeName = line.fileDescrRight.relativeName.c_str(); - newFilterEntry.type = line.fileDescrRight.objType; - if (!newFilterEntry.relativeName.IsEmpty()) - exFilterCandidateObj.push_back(newFilterEntry); - } - +//############################################################################################### + //get list of relative file/dir-names for filtering + exFilterCandidateObj.clear(); + FilterObject newFilterEntry; + for (std::set<int>::iterator i = selectionLeft.begin(); i != selectionLeft.end(); ++i) + { + const FileCompareLine& line = currentGridData[gridRefUI[*i]]; + newFilterEntry.relativeName = line.fileDescrLeft.relativeName.c_str(); + newFilterEntry.type = line.fileDescrLeft.objType; + if (!newFilterEntry.relativeName.IsEmpty()) + exFilterCandidateObj.push_back(newFilterEntry); + } + for (std::set<int>::iterator i = selectionRight.begin(); i != selectionRight.end(); ++i) + { + const FileCompareLine& line = currentGridData[gridRefUI[*i]]; + newFilterEntry.relativeName = line.fileDescrRight.relativeName.c_str(); + newFilterEntry.type = line.fileDescrRight.objType; + if (!newFilterEntry.relativeName.IsEmpty()) + exFilterCandidateObj.push_back(newFilterEntry); + } +//############################################################################################### - if (exFilterCandidateObj.size() > 0 && exFilterCandidateObj[0].type == FileDescrLine::TYPE_FILE) + //CONTEXT_EXCLUDE_EXT + exFilterCandidateExtension.clear(); + if (exFilterCandidateObj.size() > 0 && exFilterCandidateObj[0].type == FileDescrLine::TYPE_FILE) + { + const wxString filename = exFilterCandidateObj[0].relativeName.AfterLast(GlobalResources::FILE_NAME_SEPARATOR); + if (filename.Find(wxChar('.')) != wxNOT_FOUND) //be careful: AfterLast will return the whole string if '.' is not found! { - const wxString filename = exFilterCandidateObj[0].relativeName.AfterLast(GlobalResources::FILE_NAME_SEPARATOR); - if (filename.Find(wxChar('.')) != wxNOT_FOUND) //be careful: AfterLast will return the whole string if '.' is not found! - { - exFilterCandidateExtension = filename.AfterLast(wxChar('.')); - contextMenu->Append(CONTEXT_EXCLUDE_EXT, wxString(_("Exclude via filter:")) + wxT(" ") + wxT("*.") + exFilterCandidateExtension); - } + exFilterCandidateExtension = filename.AfterLast(wxChar('.')); + contextMenu->Append(CONTEXT_EXCLUDE_EXT, wxString(_("Exclude via filter:")) + wxT(" ") + wxT("*.") + exFilterCandidateExtension); } - - if (exFilterCandidateObj.size() == 1) - contextMenu->Append(CONTEXT_EXCLUDE_OBJ, wxString(_("Exclude via filter:")) + wxT(" ") + exFilterCandidateObj[0].relativeName.AfterLast(GlobalResources::FILE_NAME_SEPARATOR)); - else if (exFilterCandidateObj.size() > 1) - contextMenu->Append(CONTEXT_EXCLUDE_OBJ, wxString(_("Exclude via filter:")) + wxT(" ") + _("<multiple selection>")); } - else - contextMenu->Append(CONTEXT_FILTER_TEMP, _("Exclude temporarily")); //this element should always be visible - contextMenu->AppendSeparator(); - contextMenu->Append(CONTEXT_CLIPBOARD, _("Copy to clipboard\tCTRL+C")); - contextMenu->Append(CONTEXT_EXPLORER, _("Open with File Manager\tD-Click")); + //CONTEXT_EXCLUDE_OBJ + if (exFilterCandidateObj.size() == 1) + contextMenu->Append(CONTEXT_EXCLUDE_OBJ, wxString(_("Exclude via filter:")) + wxT(" ") + exFilterCandidateObj[0].relativeName.AfterLast(GlobalResources::FILE_NAME_SEPARATOR)); + else if (exFilterCandidateObj.size() > 1) + contextMenu->Append(CONTEXT_EXCLUDE_OBJ, wxString(_("Exclude via filter:")) + wxT(" ") + _("<multiple selection>")); contextMenu->AppendSeparator(); - contextMenu->Append(CONTEXT_DELETE_FILES, _("Delete files\tDEL")); - contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::onContextMenuSelection), NULL, this); -//####################################################### -//enable/disable context menu entries - if (selection.size() > 0) - { - contextMenu->Enable(CONTEXT_FILTER_TEMP, true); + //CONTEXT_CLIPBOARD + contextMenu->Append(CONTEXT_CLIPBOARD, _("Copy to clipboard\tCTRL+C")); + + if (leadGrid == m_gridLeft && selectionLeft.size() || leadGrid == m_gridRight && selectionRight.size()) contextMenu->Enable(CONTEXT_CLIPBOARD, true); - contextMenu->Enable(CONTEXT_DELETE_FILES, true); - } else - { - contextMenu->Enable(CONTEXT_FILTER_TEMP, false); contextMenu->Enable(CONTEXT_CLIPBOARD, false); - contextMenu->Enable(CONTEXT_DELETE_FILES, false); - } - if ((leadGrid == m_gridLeft || leadGrid == m_gridRight) && selection.size() <= 1) + + //CONTEXT_EXPLORER + contextMenu->Append(CONTEXT_EXPLORER, _("Open with File Manager\tD-Click")); + + if (leadGrid == m_gridLeft && selectionLeft.size() <= 1 || leadGrid == m_gridRight && selectionRight.size() <= 1) contextMenu->Enable(CONTEXT_EXPLORER, true); else contextMenu->Enable(CONTEXT_EXPLORER, false); -//show context menu - PopupMenu(contextMenu); + contextMenu->AppendSeparator(); + + + //CONTEXT_DELETE_FILES + contextMenu->Append(CONTEXT_DELETE_FILES, _("Delete files\tDEL")); + + if (selectionLeft.size() + selectionRight.size() == 0) + contextMenu->Enable(CONTEXT_DELETE_FILES, false); + + +//############################################################################################### + + contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextMenuSelection), NULL, this); + + //show context menu + PopupMenu(contextMenu.get()); event.Skip(); } -void MainDialog::onContextMenuSelection(wxCommandEvent& event) +void MainDialog::OnContextMenuSelection(wxCommandEvent& event) { int eventId = event.GetId(); if (eventId == CONTEXT_FILTER_TEMP) { - if (leadGrid) - { - std::set<int> selection = getSelectedRows(leadGrid); - filterRangeManually(selection); - } + //merge selections from left and right grid + std::set<int> selection = getSelectedRows(m_gridLeft); + std::set<int> additional = getSelectedRows(m_gridRight); + for (std::set<int>::const_iterator i = additional.begin(); i != additional.end(); ++i) + selection.insert(*i); + + filterRangeManually(selection); } + else if (eventId == CONTEXT_EXCLUDE_EXT) { if (!exFilterCandidateExtension.IsEmpty()) @@ -926,7 +987,7 @@ void MainDialog::onContextMenuSelection(wxCommandEvent& event) cfg.filterIsActive = true; updateFilterButton(m_bpButtonFilter, cfg.filterIsActive); - FreeFileSync::filterCurrentGridData(currentGridData, cfg.includeFilter, cfg.excludeFilter); + FreeFileSync::filterGridData(currentGridData, cfg.includeFilter, cfg.excludeFilter); writeGrid(currentGridData); if (hideFilteredElements) @@ -937,6 +998,7 @@ void MainDialog::onContextMenuSelection(wxCommandEvent& event) } } } + else if (eventId == CONTEXT_EXCLUDE_OBJ) { if (exFilterCandidateObj.size() > 0) //check needed to determine if filtering is needed @@ -961,7 +1023,7 @@ void MainDialog::onContextMenuSelection(wxCommandEvent& event) cfg.filterIsActive = true; updateFilterButton(m_bpButtonFilter, cfg.filterIsActive); - FreeFileSync::filterCurrentGridData(currentGridData, cfg.includeFilter, cfg.excludeFilter); + FreeFileSync::filterGridData(currentGridData, cfg.includeFilter, cfg.excludeFilter); writeGrid(currentGridData); if (hideFilteredElements) @@ -972,11 +1034,13 @@ void MainDialog::onContextMenuSelection(wxCommandEvent& event) } } } + else if (eventId == CONTEXT_CLIPBOARD) { - if (leadGrid) + if (leadGrid == m_gridLeft || leadGrid == m_gridRight) copySelectionToClipboard(leadGrid); } + else if (eventId == CONTEXT_EXPLORER) { if (leadGrid == m_gridLeft || leadGrid == m_gridRight) @@ -989,61 +1053,102 @@ void MainDialog::onContextMenuSelection(wxCommandEvent& event) openWithFileManager(-1, leadGrid); } } + else if (eventId == CONTEXT_DELETE_FILES) { - if (leadGrid) - { - std::set<int> selection = getSelectedRows(leadGrid); - deleteFilesOnGrid(selection); - } - } - else if (eventId == CONTEXT_CUSTOMIZE_COLUMN_LEFT) - { - XmlGlobalSettings::ColumnAttributes colAttr = m_gridLeft->getColumnAttributes(); - CustomizeColsDlg* customizeDlg = new CustomizeColsDlg(this, colAttr); - if (customizeDlg->ShowModal() == CustomizeColsDlg::BUTTON_OKAY) - { - m_gridLeft->setColumnAttributes(colAttr); - } + deleteFilesOnGrid(getSelectedRows(m_gridLeft), getSelectedRows(m_gridRight)); } - else if (eventId == CONTEXT_CUSTOMIZE_COLUMN_RIGHT) +} + + +void MainDialog::OnContextMenuMiddle(wxGridEvent& event) +{ + contextMenu.reset(new wxMenu); //re-create context menu + contextMenu->Append(CONTEXT_CHECK_ALL, _("Check all")); + contextMenu->Append(CONTEXT_UNCHECK_ALL, _("Uncheck all")); + + if (currentGridData.size() == 0) { - XmlGlobalSettings::ColumnAttributes colAttr = m_gridRight->getColumnAttributes(); - CustomizeColsDlg* customizeDlg = new CustomizeColsDlg(this, colAttr); - if (customizeDlg->ShowModal() == CustomizeColsDlg::BUTTON_OKAY) - { - m_gridRight->setColumnAttributes(colAttr); - } + contextMenu->Enable(CONTEXT_CHECK_ALL, false); + contextMenu->Enable(CONTEXT_UNCHECK_ALL, false); } + contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextMenuMiddleSelection), NULL, this); + PopupMenu(contextMenu.get()); //show context menu + event.Skip(); } -void MainDialog::OnColumnMenuLeft(wxGridEvent& event) +void MainDialog::OnContextMenuMiddleSelection(wxCommandEvent& event) +{ + int eventId = event.GetId(); + if (eventId == CONTEXT_CHECK_ALL) + { + FreeFileSync::includeAllRowsOnGrid(currentGridData); + writeGrid(currentGridData); + } + else if (eventId == CONTEXT_UNCHECK_ALL) + { + FreeFileSync::excludeAllRowsOnGrid(currentGridData); + writeGrid(currentGridData); + } +} + + +void MainDialog::OnContextColumnLeft(wxGridEvent& event) { - delete contextMenu; - contextMenu = new wxMenu; //re-create context menu + contextMenu.reset(new wxMenu); //re-create context menu contextMenu->Append(CONTEXT_CUSTOMIZE_COLUMN_LEFT, _("Customize columns")); - contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::onContextMenuSelection), NULL, this); - PopupMenu(contextMenu); //show context menu + contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextColumnSelection), NULL, this); + PopupMenu(contextMenu.get()); //show context menu event.Skip(); } -void MainDialog::OnColumnMenuRight(wxGridEvent& event) +void MainDialog::OnContextColumnRight(wxGridEvent& event) { - delete contextMenu; - contextMenu = new wxMenu; //re-create context menu + contextMenu.reset(new wxMenu); //re-create context menu contextMenu->Append(CONTEXT_CUSTOMIZE_COLUMN_RIGHT, _("Customize columns")); - contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::onContextMenuSelection), NULL, this); - PopupMenu(contextMenu); //show context menu + contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextColumnSelection), NULL, this); + PopupMenu(contextMenu.get()); //show context menu event.Skip(); } +void MainDialog::OnContextColumnSelection(wxCommandEvent& event) +{ + int eventId = event.GetId(); + if (eventId == CONTEXT_CUSTOMIZE_COLUMN_LEFT) + { + xmlAccess::ColumnAttributes colAttr = m_gridLeft->getColumnAttributes(); + CustomizeColsDlg* customizeDlg = new CustomizeColsDlg(this, colAttr); + if (customizeDlg->ShowModal() == CustomizeColsDlg::BUTTON_OKAY) + { + m_gridLeft->setColumnAttributes(colAttr); + + m_gridLeft->setSortMarker(-1); //hide sort direction indicator on GUI grids + m_gridMiddle->setSortMarker(-1); + m_gridRight->setSortMarker(-1); + } + } + else if (eventId == CONTEXT_CUSTOMIZE_COLUMN_RIGHT) + { + xmlAccess::ColumnAttributes colAttr = m_gridRight->getColumnAttributes(); + CustomizeColsDlg* customizeDlg = new CustomizeColsDlg(this, colAttr); + if (customizeDlg->ShowModal() == CustomizeColsDlg::BUTTON_OKAY) + { + m_gridRight->setColumnAttributes(colAttr); + m_gridLeft->setSortMarker(-1); //hide sort direction indicator on GUI grids + m_gridMiddle->setSortMarker(-1); + m_gridRight->setSortMarker(-1); + } + } +} + + void MainDialog::OnWriteDirManually(wxCommandEvent& event) { wxString newDir = FreeFileSync::getFormattedDirectoryName(event.GetString().c_str()).c_str(); @@ -1161,38 +1266,53 @@ private: void MainDialog::addCfgFileToHistory(const wxString& filename) { - //the default config file should not be in the history - if (sameFileSpecified(FreeFileSync::LAST_CONFIG_FILE, filename)) - return; - - //only still existing files should be included in the list + //only (still) existing files should be included in the list if (!wxFileExists(filename)) return; - std::vector<wxString>::const_iterator i; if ((i = find_if(cfgFileNames.begin(), cfgFileNames.end(), FindDuplicates(filename))) != cfgFileNames.end()) { //if entry is in the list, then jump to element - m_choiceLoad->SetSelection(i - cfgFileNames.begin() + 1); + m_choiceHistory->SetSelection(i - cfgFileNames.begin()); } else { cfgFileNames.insert(cfgFileNames.begin(), filename); - m_choiceLoad->Insert(getFormattedHistoryElement(filename), 1); //insert after "Load configuration..." - m_choiceLoad->SetSelection(1); + + //the default config file should receive another name on GUI + if (sameFileSpecified(FreeFileSync::LAST_CONFIG_FILE, filename)) + m_choiceHistory->Insert(getFormattedHistoryElement(_("<Last session>")), 0); //insert at beginning of list + else + m_choiceHistory->Insert(getFormattedHistoryElement(filename), 0); //insert at beginning of list + + m_choiceHistory->SetSelection(0); } //keep maximal size of history list - if (cfgFileNames.size() > CFG_HISTORY_LENGTH) + if (cfgFileNames.size() > globalSettings.gui.cfgHistoryMaxItems) { //delete last rows cfgFileNames.pop_back(); - m_choiceLoad->Delete(CFG_HISTORY_LENGTH); //don't forget: m_choiceLoad has (CFG_HISTORY_LENGTH + 1) elements + m_choiceHistory->Delete(globalSettings.gui.cfgHistoryMaxItems); } } -void onFilesDropped(const wxString& elementName, wxTextCtrl* txtCtrl, wxDirPickerCtrl* dirPicker) +bool MainWindowDropTarget::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) +{ + if (!filenames.IsEmpty()) + { + const wxString droppedFileName = filenames[0]; + + //create a custom event on main window: execute event after file dropping is completed! (e.g. after mouse is released) + FfsFileDropEvent evt(droppedFileName, dropTarget); + mainDlg->GetEventHandler()->AddPendingEvent(evt); + } + return false; +} + + +void setDirectoryFromDrop(const wxString& elementName, wxTextCtrl* txtCtrl, wxDirPickerCtrl* dirPicker) { wxString fileName = elementName; @@ -1213,52 +1333,57 @@ void onFilesDropped(const wxString& elementName, wxTextCtrl* txtCtrl, wxDirPicke } -bool FileDropEvent::OnDropFiles(wxCoord x, wxCoord y, const wxArrayString& filenames) +void MainDialog::OnFilesDropped(FfsFileDropEvent& event) { - if (!filenames.IsEmpty()) - { - //disable the sync button - mainDlg->enableSynchronization(false); + const wxString droppedFileName = event.m_nameDropped; + const wxPanel* dropTarget = event.m_dropTarget; - //clear grids - mainDlg->currentGridData.clear(); - mainDlg->writeGrid(mainDlg->currentGridData); + //disable the sync button + enableSynchronization(false); - const wxString droppedFileName = filenames[0]; + //clear grids + currentGridData.clear(); + writeGrid(currentGridData); - //test if ffs config file has been dropped - if (xmlAccess::getXmlType(droppedFileName) == XML_GUI_CONFIG) - { - if (mainDlg->readConfigurationFromXml(droppedFileName)) - mainDlg->pushStatusInformation(_("Configuration loaded!")); - } + xmlAccess::XmlType fileType = xmlAccess::getXmlType(droppedFileName); - //test if main folder pair is drop target - else if (dropTarget == mainDlg->m_panel1) - onFilesDropped(droppedFileName, mainDlg->m_directoryLeft, mainDlg->m_dirPickerLeft); + //test if ffs config file has been dropped + if (fileType == xmlAccess::XML_GUI_CONFIG) + { + if (readConfigurationFromXml(droppedFileName)) + pushStatusInformation(_("Configuration loaded!")); + } + //...or a ffs batch file + else if (fileType == xmlAccess::XML_BATCH_CONFIG) + { + BatchDialog* batchDlg = new BatchDialog(this, droppedFileName); + if (batchDlg->ShowModal() == BatchDialog::BATCH_FILE_SAVED) + pushStatusInformation(_("Batch file created successfully!")); + } + //test if main folder pair is drop target + else if (dropTarget == m_panel1) + setDirectoryFromDrop(droppedFileName, m_directoryLeft, m_dirPickerLeft); - else if (dropTarget == mainDlg->m_panel2) - onFilesDropped(droppedFileName, mainDlg->m_directoryRight, mainDlg->m_dirPickerRight); + else if (dropTarget == m_panel2) + setDirectoryFromDrop(droppedFileName, m_directoryRight, m_dirPickerRight); - else //test if additional folder pairs are drop targets + else //test if additional folder pairs are drop targets + { + for (std::vector<FolderPairGenerated*>::const_iterator i = additionalFolderPairs.begin(); i != additionalFolderPairs.end(); ++i) { - for (std::vector<FolderPairGenerated*>::const_iterator i = mainDlg->additionalFolderPairs.begin(); i != mainDlg->additionalFolderPairs.end(); ++i) + FolderPairGenerated* dirPair = *i; + if (dropTarget == (dirPair->m_panelLeft)) { - FolderPairGenerated* dirPair = *i; - if (dropTarget == (dirPair->m_panelLeft)) - { - onFilesDropped(droppedFileName, dirPair->m_directoryLeft, dirPair->m_dirPickerLeft); - break; - } - else if (dropTarget == (dirPair->m_panelRight)) - { - onFilesDropped(droppedFileName, dirPair->m_directoryRight, dirPair->m_dirPickerRight); - break; - } + setDirectoryFromDrop(droppedFileName, dirPair->m_directoryLeft, dirPair->m_dirPickerLeft); + break; + } + else if (dropTarget == (dirPair->m_panelRight)) + { + setDirectoryFromDrop(droppedFileName, dirPair->m_directoryRight, dirPair->m_dirPickerRight); + break; } } } - return false; } @@ -1266,13 +1391,8 @@ void MainDialog::OnSaveConfig(wxCommandEvent& event) { wxString defaultFileName = wxT("SyncSettings.ffs_gui"); - //try to use currently selected configuration file as default - int selectedItem; - if ((selectedItem = m_choiceLoad->GetSelection()) != wxNOT_FOUND) - if (1 <= selectedItem && unsigned(selectedItem) < m_choiceLoad->GetCount()) - if (unsigned(selectedItem - 1) < cfgFileNames.size()) - defaultFileName = cfgFileNames[selectedItem - 1]; - + if (!proposedConfigFileName.empty()) + defaultFileName = proposedConfigFileName; wxFileDialog* filePicker = new wxFileDialog(this, wxEmptyString, wxEmptyString, defaultFileName, wxString(_("FreeFileSync configuration")) + wxT(" (*.ffs_gui)|*.ffs_gui"), wxFD_SAVE); if (filePicker->ShowModal() == wxID_OK) @@ -1292,86 +1412,64 @@ void MainDialog::OnSaveConfig(wxCommandEvent& event) if (writeConfigurationToXml(newFileName)) pushStatusInformation(_("Configuration saved!")); } - event.Skip(); } void MainDialog::OnLoadConfig(wxCommandEvent& event) { - int selectedItem; - if ((selectedItem = m_choiceLoad->GetSelection()) != wxNOT_FOUND) - { - wxFileDialog* filePicker = NULL; - switch (selectedItem) - { - case 0: //load config from file - filePicker = new wxFileDialog(this, wxEmptyString, wxEmptyString, wxEmptyString, wxString(_("FreeFileSync configuration")) + wxT(" (*.ffs_gui)|*.ffs_gui"), wxFD_OPEN); + wxFileDialog* filePicker = new wxFileDialog(this, wxEmptyString, wxEmptyString, wxEmptyString, wxString(_("FreeFileSync configuration")) + wxT(" (*.ffs_gui)|*.ffs_gui"), wxFD_OPEN); + if (filePicker->ShowModal() == wxID_OK) + loadConfiguration(filePicker->GetPath()); +} - if (filePicker->ShowModal() == wxID_OK) - loadConfiguration(filePicker->GetPath()); - break; - default: - if (1 <= selectedItem && unsigned(selectedItem) < m_choiceLoad->GetCount()) - { - if (unsigned(selectedItem - 1) < cfgFileNames.size()) - loadConfiguration(cfgFileNames[selectedItem - 1]); - } - break; - } - } - event.Skip(); +void MainDialog::OnLoadFromHistory(wxCommandEvent& event) +{ + const int selectedItem = m_choiceHistory->GetSelection(); + if (0 <= selectedItem && unsigned(selectedItem) < cfgFileNames.size()) + loadConfiguration(cfgFileNames[selectedItem]); } void MainDialog::OnMenuSaveConfig(wxCommandEvent& event) { OnSaveConfig(event); - event.Skip(); } void MainDialog::OnMenuLoadConfig(wxCommandEvent& event) { - wxFileDialog* filePicker = new wxFileDialog(this, wxEmptyString, wxEmptyString, wxEmptyString, wxString(_("FreeFileSync configuration")) + wxT(" (*.ffs_gui)|*.ffs_gui"), wxFD_OPEN); - if (filePicker->ShowModal() == wxID_OK) - loadConfiguration(filePicker->GetPath()); + OnLoadConfig(event); } void MainDialog::loadConfiguration(const wxString& filename) { if (!filename.IsEmpty()) - { - if (!wxFileExists(filename)) - wxMessageBox(wxString(_("The selected file does not exist:")) + wxT(" \"") + filename + wxT("\""), _("Warning"), wxOK); - else if (xmlAccess::getXmlType(filename) != XML_GUI_CONFIG) - wxMessageBox(wxString(_("The file does not contain a valid configuration:")) + wxT(" \"") + filename + wxT("\""), _("Warning"), wxOK); - else - { //clear grids - currentGridData.clear(); - writeGrid(currentGridData); + { //clear grids + currentGridData.clear(); + writeGrid(currentGridData); - if (readConfigurationFromXml(filename)) - pushStatusInformation(_("Configuration loaded!")); - } + if (readConfigurationFromXml(filename)) + pushStatusInformation(_("Configuration loaded!")); } } void MainDialog::OnChoiceKeyEvent(wxKeyEvent& event) { - if (event.GetKeyCode() == WXK_DELETE) + const int keyCode = event.GetKeyCode(); + if (keyCode == WXK_DELETE || keyCode == WXK_NUMPAD_DELETE) { //try to delete the currently selected config history item - int selectedItem; - if ((selectedItem = m_choiceLoad->GetCurrentSelection()) != wxNOT_FOUND) - if (1 <= selectedItem && unsigned(selectedItem) < m_choiceLoad->GetCount()) - if (unsigned(selectedItem - 1) < cfgFileNames.size()) - { //delete selected row - cfgFileNames.erase(cfgFileNames.begin() + selectedItem - 1); - m_choiceLoad->Delete(selectedItem); - m_choiceLoad->SetSelection(0); - } + const int selectedItem = m_choiceHistory->GetCurrentSelection(); + if ( 0 <= selectedItem && + unsigned(selectedItem) < m_choiceHistory->GetCount() && + unsigned(selectedItem) < cfgFileNames.size()) + { //delete selected row + cfgFileNames.erase(cfgFileNames.begin() + selectedItem); + m_choiceHistory->Delete(selectedItem); + m_choiceHistory->SetSelection(0); + } } event.Skip(); } @@ -1405,8 +1503,17 @@ void MainDialog::OnQuit(wxCommandEvent &event) bool MainDialog::readConfigurationFromXml(const wxString& filename, bool programStartup) { + leftOnlyFilesActive = true; + leftNewerFilesActive = true; + differentFilesActive = true; + rightNewerFilesActive = true; //do not save/load these bool values from harddisk! + rightOnlyFilesActive = true; //it's more convenient to have them defaulted at startup + equalFilesActive = false; + updateViewFilterButtons(); + + //load XML - XmlGuiConfig guiCfg; //structure to receive gui settings, already defaulted!! + xmlAccess::XmlGuiConfig guiCfg; //structure to receive gui settings, already defaulted!! try { guiCfg = xmlAccess::readGuiConfig(filename); @@ -1469,28 +1576,43 @@ bool MainDialog::readConfigurationFromXml(const wxString& filename, bool program m_bpButtonRemovePair->Disable(); } - //read GUI layout (optional!) + //read GUI layout hideFilteredElements = guiCfg.hideFilteredElements; m_checkBoxHideFilt->SetValue(hideFilteredElements); + ignoreErrors = guiCfg.ignoreErrors; //########################################################### addCfgFileToHistory(filename); //put filename on list of last used config files + + if (filename == FreeFileSync::LAST_CONFIG_FILE) //set title + { + SetTitle(wxString(wxT("FreeFileSync - ")) + _("Folder Comparison and Synchronization")); + proposedConfigFileName.clear(); + } + else + { + SetTitle(wxString(wxT("FreeFileSync - ")) + filename); + proposedConfigFileName = filename; + } + return true; } bool MainDialog::writeConfigurationToXml(const wxString& filename) { - XmlGuiConfig guiCfg; + xmlAccess::XmlGuiConfig guiCfg; //load structure with basic settings "mainCfg" guiCfg.mainCfg = cfg; - getFolderPairs(guiCfg.directoryPairs); + guiCfg.directoryPairs = getFolderPairs(); //load structure with gui settings guiCfg.hideFilteredElements = hideFilteredElements; + guiCfg.ignoreErrors = ignoreErrors; + //write config to XML try { @@ -1504,6 +1626,18 @@ bool MainDialog::writeConfigurationToXml(const wxString& filename) //put filename on list of last used config files addCfgFileToHistory(filename); + + if (filename == FreeFileSync::LAST_CONFIG_FILE) //set title + { + SetTitle(wxString(wxT("FreeFileSync - ")) + _("Folder Comparison and Synchronization")); + proposedConfigFileName.clear(); + } + else + { + SetTitle(wxString(wxT("FreeFileSync - ")) + filename); + proposedConfigFileName = filename; + } + return true; } @@ -1512,7 +1646,6 @@ void MainDialog::OnShowHelpDialog(wxCommandEvent &event) { HelpDlg* helpDlg = new HelpDlg(this); helpDlg->ShowModal(); - event.Skip(); } @@ -1523,12 +1656,11 @@ void MainDialog::OnFilterButton(wxCommandEvent &event) updateFilterButton(m_bpButtonFilter, cfg.filterIsActive); if (cfg.filterIsActive) - FreeFileSync::filterCurrentGridData(currentGridData, cfg.includeFilter, cfg.excludeFilter); + FreeFileSync::filterGridData(currentGridData, cfg.includeFilter, cfg.excludeFilter); else - FreeFileSync::removeFilterOnCurrentGridData(currentGridData); + FreeFileSync::includeAllRowsOnGrid(currentGridData); writeGrid(currentGridData); - event.Skip(); } @@ -1538,6 +1670,9 @@ void MainDialog::OnHideFilteredButton(wxCommandEvent &event) //make sure, checkbox and "hideFiltered" are in sync m_checkBoxHideFilt->SetValue(hideFilteredElements); + m_gridLeft->ClearSelection(); + m_gridRight->ClearSelection(); + writeGrid(currentGridData); event.Skip(); @@ -1558,12 +1693,12 @@ void MainDialog::OnConfigureFilter(wxHyperlinkEvent &event) if (afterImage == (wxString(wxT("*")) + wxChar(1))) //default { cfg.filterIsActive = false; - FreeFileSync::removeFilterOnCurrentGridData(currentGridData); + FreeFileSync::includeAllRowsOnGrid(currentGridData); } else { cfg.filterIsActive = true; - FreeFileSync::filterCurrentGridData(currentGridData, cfg.includeFilter, cfg.excludeFilter); + FreeFileSync::filterGridData(currentGridData, cfg.includeFilter, cfg.excludeFilter); } updateFilterButton(m_bpButtonFilter, cfg.filterIsActive); @@ -1580,7 +1715,6 @@ void MainDialog::OnLeftOnlyFiles(wxCommandEvent& event) leftOnlyFilesActive = !leftOnlyFilesActive; updateViewFilterButtons(); writeGrid(currentGridData); - event.Skip(); }; void MainDialog::OnLeftNewerFiles(wxCommandEvent& event) @@ -1588,7 +1722,6 @@ void MainDialog::OnLeftNewerFiles(wxCommandEvent& event) leftNewerFilesActive = !leftNewerFilesActive; updateViewFilterButtons(); writeGrid(currentGridData); - event.Skip(); }; void MainDialog::OnDifferentFiles(wxCommandEvent& event) @@ -1596,7 +1729,6 @@ void MainDialog::OnDifferentFiles(wxCommandEvent& event) differentFilesActive = !differentFilesActive; updateViewFilterButtons(); writeGrid(currentGridData); - event.Skip(); }; void MainDialog::OnRightNewerFiles(wxCommandEvent& event) @@ -1604,7 +1736,6 @@ void MainDialog::OnRightNewerFiles(wxCommandEvent& event) rightNewerFilesActive = !rightNewerFilesActive; updateViewFilterButtons(); writeGrid(currentGridData); - event.Skip(); }; void MainDialog::OnRightOnlyFiles(wxCommandEvent& event) @@ -1612,7 +1743,6 @@ void MainDialog::OnRightOnlyFiles(wxCommandEvent& event) rightOnlyFilesActive = !rightOnlyFilesActive; updateViewFilterButtons(); writeGrid(currentGridData); - event.Skip(); }; void MainDialog::OnEqualFiles(wxCommandEvent& event) @@ -1620,9 +1750,9 @@ void MainDialog::OnEqualFiles(wxCommandEvent& event) equalFilesActive = !equalFilesActive; updateViewFilterButtons(); writeGrid(currentGridData); - event.Skip(); }; + void MainDialog::updateViewFilterButtons() { if (leftOnlyFilesActive) @@ -1731,82 +1861,39 @@ void MainDialog::updateCompareButtons() } -void MainDialog::getFolderPairs(std::vector<FolderPair>& output, bool formatted) +std::vector<FolderPair> MainDialog::getFolderPairs() { - output.clear(); + std::vector<FolderPair> output; //add main pair FolderPair newPair; - if (formatted) - { - newPair.leftDirectory = FreeFileSync::getFormattedDirectoryName(m_directoryLeft->GetValue().c_str()); - newPair.rightDirectory = FreeFileSync::getFormattedDirectoryName(m_directoryRight->GetValue().c_str()); - } - else - { - newPair.leftDirectory = m_directoryLeft->GetValue().c_str(); - newPair.rightDirectory = m_directoryRight->GetValue().c_str(); - } + newPair.leftDirectory = m_directoryLeft->GetValue().c_str(); + newPair.rightDirectory = m_directoryRight->GetValue().c_str(); output.push_back(newPair); //add additional pairs for (std::vector<FolderPairGenerated*>::const_iterator i = additionalFolderPairs.begin(); i != additionalFolderPairs.end(); ++i) { FolderPairGenerated* dirPair = *i; - if (formatted) - { - newPair.leftDirectory = FreeFileSync::getFormattedDirectoryName(dirPair->m_directoryLeft->GetValue().c_str()); - newPair.rightDirectory = FreeFileSync::getFormattedDirectoryName(dirPair->m_directoryRight->GetValue().c_str()); - } - else - { - newPair.leftDirectory = dirPair->m_directoryLeft->GetValue().c_str(); - newPair.rightDirectory = dirPair->m_directoryRight->GetValue().c_str(); - } - + newPair.leftDirectory = dirPair->m_directoryLeft->GetValue().c_str(); + newPair.rightDirectory = dirPair->m_directoryRight->GetValue().c_str(); output.push_back(newPair); } + + return output; } void MainDialog::OnCompare(wxCommandEvent &event) { - //assemble vector of formatted folder pairs - std::vector<FolderPair> directoryPairsFormatted; - getFolderPairs(directoryPairsFormatted, true); - - //check if folders are valid - wxString errorMessage; - if (!FreeFileSync::foldersAreValidForComparison(directoryPairsFormatted, errorMessage)) - { - wxMessageBox(errorMessage, _("Warning")); - return; - } - - //check if folders have dependencies - if (globalSettings.global.folderDependCheckActive) - { - wxString warningMessage; - if (FreeFileSync::foldersHaveDependencies(directoryPairsFormatted, warningMessage)) - { - bool hideThisDialog = false; - wxString messageText = warningMessage + wxT("\n\n") + - _("Consider this when setting up synchronization rules: You might want to avoid write access to these directories so that synchronization of both does not interfere."); - - //show popup and ask user how to handle warning - WarningDlg* warningDlg = new WarningDlg(this, WarningDlg::BUTTON_IGNORE | WarningDlg::BUTTON_ABORT, messageText, hideThisDialog); - if (warningDlg->ShowModal() == WarningDlg::BUTTON_ABORT) - return; - else - globalSettings.global.folderDependCheckActive = !hideThisDialog; - } - } -//---------------------------------------------- - clearStatusBar(); wxBusyCursor dummy; //show hourglass cursor + //save memory by clearing old result list + currentGridData.clear(); + writeGrid(currentGridData); //refresh GUI grid + bool aborted = false; try { //class handling status display and error messages @@ -1814,24 +1901,28 @@ void MainDialog::OnCompare(wxCommandEvent &event) cmpStatusHandlerTmp = &statusHandler; #ifdef FFS_WIN - FreeFileSync::CompareProcess comparison(false, globalSettings.global.handleDstOnFat32, &statusHandler); + FreeFileSync::CompareProcess comparison(globalSettings.shared.traverseSymbolicLinks, + globalSettings.shared.handleDstOnFat32, + globalSettings.shared.warningDependentFolders, + &statusHandler); #elif defined FFS_LINUX - FreeFileSync::CompareProcess comparison(false, false, &statusHandler); + FreeFileSync::CompareProcess comparison(globalSettings.shared.traverseSymbolicLinks, + false, + globalSettings.shared.warningDependentFolders, + &statusHandler); #endif - comparison.startCompareProcess(directoryPairsFormatted, + comparison.startCompareProcess(getFolderPairs(), cfg.compareVar, currentGridData); //if (output.size < 50000) statusHandler.updateStatusText(_("Sorting file list...")); statusHandler.forceUiRefresh(); //keep total number of scanned files up to date - sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName<true, SORT_ON_LEFT>); + std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName<true, SORT_ON_LEFT>); //filter currentGridData if option is set if (cfg.filterIsActive) - FreeFileSync::filterCurrentGridData(currentGridData, cfg.includeFilter, cfg.excludeFilter); - - writeGrid(currentGridData); //keep it in try/catch to not overwrite status information if compare is aborted + FreeFileSync::filterGridData(currentGridData, cfg.includeFilter, cfg.excludeFilter); } catch (AbortThisProcess& theException) { @@ -1851,10 +1942,20 @@ void MainDialog::OnCompare(wxCommandEvent &event) //hide sort direction indicator on GUI grids m_gridLeft->setSortMarker(-1); + m_gridMiddle->setSortMarker(-1); m_gridRight->setSortMarker(-1); + + //reset last sort selection: used for determining sort direction + lastSortColumn = -1; + lastSortGrid = NULL; + + m_gridLeft->ClearSelection(); + m_gridMiddle->ClearSelection(); + m_gridRight->ClearSelection(); } - event.Skip(); + //refresh grid in ANY case! (also on abort) + writeGrid(currentGridData); } @@ -1862,7 +1963,6 @@ void MainDialog::OnAbortCompare(wxCommandEvent& event) { if (cmpStatusHandlerTmp) cmpStatusHandlerTmp->requestAbortion(); - event.Skip(); } @@ -1898,7 +1998,7 @@ void MainDialog::writeGrid(const FileCompareResult& gridData) void MainDialog::OnSync(wxCommandEvent& event) { - SyncDialog* syncDlg = new SyncDialog(this, currentGridData, cfg, synchronizationEnabled); + SyncDialog* syncDlg = new SyncDialog(this, currentGridData, cfg, ignoreErrors, synchronizationEnabled); if (syncDlg->ShowModal() == SyncDialog::BUTTON_START) { //check if there are files/folders to be sync'ed at all @@ -1915,10 +2015,14 @@ void MainDialog::OnSync(wxCommandEvent& event) try { //class handling status updates and error messages - SyncStatusHandler statusHandler(this, cfg.ignoreErrors); + SyncStatusHandler statusHandler(this, ignoreErrors); //start synchronization and return elements that were not sync'ed in currentGridData - FreeFileSync::SyncProcess synchronization(cfg.useRecycleBin, true, &statusHandler); + FreeFileSync::SyncProcess synchronization( + cfg.useRecycleBin, + globalSettings.shared.warningSignificantDifference, + &statusHandler); + synchronization.startSynchronizationProcess(currentGridData, cfg.syncConfiguration); } catch (AbortThisProcess& theException) @@ -1926,9 +2030,14 @@ void MainDialog::OnSync(wxCommandEvent& event) } //enableSynchronization(false); - //display files that were not processed + //show remaining files that have not been processed: put DIRECTLY after startSynchronizationProcess() and DON'T call any wxWidgets functions + //in between! Else CustomGrid might access the obsolete gridRefUI! writeGrid(currentGridData); + m_gridLeft->ClearSelection(); + m_gridMiddle->ClearSelection(); + m_gridRight->ClearSelection(); + if (currentGridData.size() > 0) pushStatusInformation(_("Not all items were synchronized! Have a look at the list.")); else @@ -1937,7 +2046,6 @@ void MainDialog::OnSync(wxCommandEvent& event) enableSynchronization(false); } } - event.Skip(); } @@ -1957,34 +2065,47 @@ void MainDialog::OnRightGridDoubleClick(wxGridEvent& event) void MainDialog::OnSortLeftGrid(wxGridEvent& event) { - static bool columnSortAscending[CustomGrid::COLUMN_TYPE_COUNT] = {true, true, false, true}; - - int currentSortColumn = event.GetCol(); - if (0 <= currentSortColumn < CustomGrid::COLUMN_TYPE_COUNT) + //determine direction for std::sort() + const int currentSortColumn = event.GetCol(); + if (0 <= currentSortColumn && currentSortColumn < int(xmlAccess::COLUMN_TYPE_COUNT)) { - bool& sortAscending = columnSortAscending[currentSortColumn]; - XmlGlobalSettings::ColumnTypes columnType = m_gridLeft->getTypeAtPos(currentSortColumn); + static bool sortAscending = true; + if (lastSortColumn != currentSortColumn || lastSortGrid != m_gridLeft) + sortAscending = true; + else + sortAscending = !sortAscending; + + lastSortColumn = currentSortColumn; + lastSortGrid = m_gridLeft; - if (columnType == XmlGlobalSettings::FILENAME) + //start sort + xmlAccess::ColumnTypes columnType = m_gridLeft->getTypeAtPos(currentSortColumn); + if (columnType == xmlAccess::FULL_NAME) { - if (sortAscending) sort(currentGridData.begin(), currentGridData.end(), sortByFileName<true, SORT_ON_LEFT>); - else sort(currentGridData.begin(), currentGridData.end(), sortByFileName<false, SORT_ON_LEFT>); + if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName<true, SORT_ON_LEFT>); //sort by rel name here too! + else std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName<false, SORT_ON_LEFT>); } - else if (columnType == XmlGlobalSettings::REL_PATH) + else if (columnType == xmlAccess::FILENAME) { - if (sortAscending) sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName<true, SORT_ON_LEFT>); - else sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName<false, SORT_ON_LEFT>); + if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByFileName<true, SORT_ON_LEFT>); + else std::sort(currentGridData.begin(), currentGridData.end(), sortByFileName<false, SORT_ON_LEFT>); } - else if (columnType == XmlGlobalSettings::SIZE) + else if (columnType == xmlAccess::REL_PATH) { - if (sortAscending) sort(currentGridData.begin(), currentGridData.end(), sortByFileSize<true, SORT_ON_LEFT>); - else sort(currentGridData.begin(), currentGridData.end(), sortByFileSize<false, SORT_ON_LEFT>); + if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName<true, SORT_ON_LEFT>); + else std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName<false, SORT_ON_LEFT>); } - else if (columnType == XmlGlobalSettings::DATE) + else if (columnType == xmlAccess::SIZE) { - if (sortAscending) sort(currentGridData.begin(), currentGridData.end(), sortByDate<true, SORT_ON_LEFT>); - else sort(currentGridData.begin(), currentGridData.end(), sortByDate<false, SORT_ON_LEFT>); + if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByFileSize<true, SORT_ON_LEFT>); + else std::sort(currentGridData.begin(), currentGridData.end(), sortByFileSize<false, SORT_ON_LEFT>); } + else if (columnType == xmlAccess::DATE) + { + if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByDate<true, SORT_ON_LEFT>); + else std::sort(currentGridData.begin(), currentGridData.end(), sortByDate<false, SORT_ON_LEFT>); + } + else assert(false); writeGrid(currentGridData); //needed to refresh gridRefUI references @@ -1995,8 +2116,6 @@ void MainDialog::OnSortLeftGrid(wxGridEvent& event) m_gridLeft->setSortMarker(currentSortColumn, globalResource.bitmapSmallUp); else m_gridLeft->setSortMarker(currentSortColumn, globalResource.bitmapSmallDown); - - sortAscending = !sortAscending; } event.Skip(); } @@ -2004,34 +2123,47 @@ void MainDialog::OnSortLeftGrid(wxGridEvent& event) void MainDialog::OnSortRightGrid(wxGridEvent& event) { - static bool columnSortAscending[CustomGrid::COLUMN_TYPE_COUNT] = {true, true, false, true}; - - int currentSortColumn = event.GetCol(); - if (0 <= currentSortColumn < CustomGrid::COLUMN_TYPE_COUNT) + //determine direction for std::sort() + const int currentSortColumn = event.GetCol(); + if (0 <= currentSortColumn && currentSortColumn < int(xmlAccess::COLUMN_TYPE_COUNT)) { - bool& sortAscending = columnSortAscending[currentSortColumn]; - XmlGlobalSettings::ColumnTypes columnType = m_gridRight->getTypeAtPos(currentSortColumn); + static bool sortAscending = true; + if (lastSortColumn != currentSortColumn || lastSortGrid != m_gridRight) + sortAscending = true; + else + sortAscending = !sortAscending; - if (columnType == XmlGlobalSettings::FILENAME) + lastSortColumn = currentSortColumn; + lastSortGrid = m_gridRight; + + //start sort + xmlAccess::ColumnTypes columnType = m_gridRight->getTypeAtPos(currentSortColumn); + if (columnType == xmlAccess::FULL_NAME) { - if (sortAscending) sort(currentGridData.begin(), currentGridData.end(), sortByFileName<true, SORT_ON_RIGHT>); - else sort(currentGridData.begin(), currentGridData.end(), sortByFileName<false, SORT_ON_RIGHT>); + if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName<true, SORT_ON_RIGHT>); //sort by rel name here too! + else std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName<false, SORT_ON_RIGHT>); } - else if (columnType == XmlGlobalSettings::REL_PATH) + else if (columnType == xmlAccess::FILENAME) { - if (sortAscending) sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName<true, SORT_ON_RIGHT>); - else sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName<false, SORT_ON_RIGHT>); + if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByFileName<true, SORT_ON_RIGHT>); + else std::sort(currentGridData.begin(), currentGridData.end(), sortByFileName<false, SORT_ON_RIGHT>); } - else if (columnType == XmlGlobalSettings::SIZE) + else if (columnType == xmlAccess::REL_PATH) { - if (sortAscending) sort(currentGridData.begin(), currentGridData.end(), sortByFileSize<true, SORT_ON_RIGHT>); - else sort(currentGridData.begin(), currentGridData.end(), sortByFileSize<false, SORT_ON_RIGHT>); + if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName<true, SORT_ON_RIGHT>); + else std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName<false, SORT_ON_RIGHT>); } - else if (columnType == XmlGlobalSettings::DATE) + else if (columnType == xmlAccess::SIZE) { - if (sortAscending) sort(currentGridData.begin(), currentGridData.end(), sortByDate<true, SORT_ON_RIGHT>); - else sort(currentGridData.begin(), currentGridData.end(), sortByDate<false, SORT_ON_RIGHT>); + if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByFileSize<true, SORT_ON_RIGHT>); + else std::sort(currentGridData.begin(), currentGridData.end(), sortByFileSize<false, SORT_ON_RIGHT>); } + else if (columnType == xmlAccess::DATE) + { + if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByDate<true, SORT_ON_RIGHT>); + else std::sort(currentGridData.begin(), currentGridData.end(), sortByDate<false, SORT_ON_RIGHT>); + } + else assert(false); writeGrid(currentGridData); //needed to refresh gridRefUI references @@ -2042,8 +2174,6 @@ void MainDialog::OnSortRightGrid(wxGridEvent& event) m_gridRight->setSortMarker(currentSortColumn, globalResource.bitmapSmallUp); else m_gridRight->setSortMarker(currentSortColumn, globalResource.bitmapSmallDown); - - sortAscending = !sortAscending; } event.Skip(); } @@ -2051,23 +2181,29 @@ void MainDialog::OnSortRightGrid(wxGridEvent& event) void MainDialog::OnSortMiddleGrid(wxGridEvent& event) { - static bool columnSortAscending = true; + //determine direction for std::sort() + static bool sortAscending = true; + if (lastSortColumn != 0 || lastSortGrid != m_gridMiddle) + sortAscending = true; + else + sortAscending = !sortAscending; + lastSortColumn = 0; + lastSortGrid = m_gridMiddle; - if (columnSortAscending) sort(currentGridData.begin(), currentGridData.end(), sortByCmpResult<true>); - else sort(currentGridData.begin(), currentGridData.end(), sortByCmpResult<false>); + //start sort + if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByCmpResult<true>); + else std::sort(currentGridData.begin(), currentGridData.end(), sortByCmpResult<false>); writeGrid(currentGridData); //needed to refresh gridRefUI references //set sort direction indicator on UI m_gridLeft->setSortMarker(-1); m_gridRight->setSortMarker(-1); - if (columnSortAscending) + if (sortAscending) m_gridMiddle->setSortMarker(0, globalResource.bitmapSmallUp); else m_gridMiddle->setSortMarker(0, globalResource.bitmapSmallDown); - columnSortAscending = !columnSortAscending; - event.Skip(); } @@ -2088,6 +2224,11 @@ void MainDialog::OnSwapDirs( wxCommandEvent& event ) dirPair->m_directoryRight->SetValue(tmp); } + //swap view filter + std::swap(leftOnlyFilesActive, rightOnlyFilesActive); + std::swap(leftNewerFilesActive, rightNewerFilesActive); + updateViewFilterButtons(); + //swap grid information FreeFileSync::swapGrids(currentGridData); writeGrid(currentGridData); @@ -2142,7 +2283,7 @@ void MainDialog::updateStatusInformation(const GridView& visibleGrid) statusLeftNew+= _("1 directory"); else { - wxString folderCount = numberToWxString(foldersOnLeftView); + wxString folderCount = globalFunctions::numberToWxString(foldersOnLeftView); globalFunctions::includeNumberSeparator(folderCount); wxString outputString = _("%x directories"); @@ -2160,7 +2301,7 @@ void MainDialog::updateStatusInformation(const GridView& visibleGrid) statusLeftNew+= _("1 file,"); else { - wxString fileCount = numberToWxString(filesOnLeftView); + wxString fileCount = globalFunctions::numberToWxString(filesOnLeftView); globalFunctions::includeNumberSeparator(fileCount); wxString outputString = _("%x files,"); @@ -2171,7 +2312,7 @@ void MainDialog::updateStatusInformation(const GridView& visibleGrid) statusLeftNew+= FreeFileSync::formatFilesizeToShortString(filesizeLeftView); } - wxString objectsView = numberToWxString(visibleGrid.size()); + wxString objectsView = globalFunctions::numberToWxString(visibleGrid.size()); globalFunctions::includeNumberSeparator(objectsView); if (currentGridData.size() == 1) { @@ -2181,7 +2322,7 @@ void MainDialog::updateStatusInformation(const GridView& visibleGrid) } else { - wxString objectsTotal = numberToWxString(currentGridData.size()); + wxString objectsTotal = globalFunctions::numberToWxString(currentGridData.size()); globalFunctions::includeNumberSeparator(objectsTotal); wxString outputString = _("%x of %y rows in view"); @@ -2196,7 +2337,7 @@ void MainDialog::updateStatusInformation(const GridView& visibleGrid) statusRightNew+= _("1 directory"); else { - wxString folderCount = numberToWxString(foldersOnRightView); + wxString folderCount = globalFunctions::numberToWxString(foldersOnRightView); globalFunctions::includeNumberSeparator(folderCount); wxString outputString = _("%x directories"); @@ -2214,7 +2355,7 @@ void MainDialog::updateStatusInformation(const GridView& visibleGrid) statusRightNew+= _("1 file,"); else { - wxString fileCount = numberToWxString(filesOnRightView); + wxString fileCount = globalFunctions::numberToWxString(filesOnRightView); globalFunctions::includeNumberSeparator(fileCount); wxString outputString = _("%x files,"); @@ -2377,8 +2518,8 @@ void MainDialog::addFolderPair(const wxString& leftDir, const wxString& rightDir newPair->m_directoryRight->Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(MainDialog::OnWriteDirManually), NULL, this ); //prepare drag & drop - newPair->m_panelLeft->SetDropTarget(new FileDropEvent(this, newPair->m_panelLeft)); - newPair->m_panelRight->SetDropTarget(new FileDropEvent(this, newPair->m_panelRight)); + newPair->m_panelLeft->SetDropTarget(new MainWindowDropTarget(this, newPair->m_panelLeft)); //ownership passed + newPair->m_panelRight->SetDropTarget(new MainWindowDropTarget(this, newPair->m_panelRight)); //insert directory names if provided newPair->m_directoryLeft->SetValue(leftDir); @@ -2456,8 +2597,9 @@ CompareStatusHandler::CompareStatusHandler(MainDialog* dlg) : mainDialog->m_panel11->Disable(); mainDialog->m_panel12->Disable(); mainDialog->m_panel13->Disable(); - mainDialog->m_bpButton201->Disable(); - mainDialog->m_choiceLoad->Disable(); + mainDialog->m_bpButtonSave->Disable(); + mainDialog->m_bpButtonLoad->Disable(); + mainDialog->m_choiceHistory->Disable(); mainDialog->m_bpButton10->Disable(); mainDialog->m_bpButton14->Disable(); mainDialog->m_scrolledWindowFolderPairs->Disable(); @@ -2509,8 +2651,9 @@ CompareStatusHandler::~CompareStatusHandler() mainDialog->m_panel11->Enable(); mainDialog->m_panel12->Enable(); mainDialog->m_panel13->Enable(); - mainDialog->m_bpButton201->Enable(); - mainDialog->m_choiceLoad->Enable(); + mainDialog->m_bpButtonSave->Enable(); + mainDialog->m_bpButtonLoad->Enable(); + mainDialog->m_choiceHistory->Enable(); mainDialog->m_bpButton10->Enable(); mainDialog->m_bpButton14->Enable(); mainDialog->m_scrolledWindowFolderPairs->Enable(); @@ -2578,29 +2721,68 @@ ErrorHandler::Response CompareStatusHandler::reportError(const Zstring& text) bool ignoreNextErrors = false; wxString errorMessage = wxString(text.c_str()) + wxT("\n\n") + _("Ignore this error, retry or abort?"); - ErrorDlg* errorDlg = new ErrorDlg(mainDialog, errorMessage, ignoreNextErrors); - + ErrorDlg* errorDlg = new ErrorDlg(mainDialog, + ErrorDlg::BUTTON_IGNORE | ErrorDlg::BUTTON_RETRY | ErrorDlg::BUTTON_ABORT, + errorMessage, ignoreNextErrors); int rv = errorDlg->ShowModal(); switch (rv) { case ErrorDlg::BUTTON_IGNORE: ignoreErrors = ignoreNextErrors; return ErrorHandler::IGNORE_ERROR; + case ErrorDlg::BUTTON_RETRY: return ErrorHandler::RETRY; + case ErrorDlg::BUTTON_ABORT: - { - abortRequested = true; - throw AbortThisProcess(); - } - default: - assert (false); + abortThisProcess(); } + assert(false); return ErrorHandler::IGNORE_ERROR; //dummy return value } +void CompareStatusHandler::reportFatalError(const Zstring& errorMessage) +{ + mainDialog->compareStatus->updateStatusPanelNow(); + + bool dummy = false; + ErrorDlg* errorDlg = new ErrorDlg(mainDialog, + ErrorDlg::BUTTON_ABORT, + errorMessage.c_str(), dummy); + errorDlg->ShowModal(); + abortThisProcess(); +} + + +void CompareStatusHandler::reportWarning(const Zstring& warningMessage, bool& dontShowAgain) +{ + if (ignoreErrors) //if errors are ignored, then warnings should also + return; + + mainDialog->compareStatus->updateStatusPanelNow(); + + //show popup and ask user how to handle warning + bool dontWarnAgain = false; + WarningDlg* warningDlg = new WarningDlg(mainDialog, + WarningDlg::BUTTON_IGNORE | WarningDlg::BUTTON_ABORT, + warningMessage.c_str(), + dontWarnAgain); + switch (warningDlg->ShowModal()) + { + case WarningDlg::BUTTON_ABORT: + abortThisProcess(); + + case WarningDlg::BUTTON_IGNORE: + dontShowAgain = dontWarnAgain; + return; + } + + assert(false); +} + + inline void CompareStatusHandler::forceUiRefresh() { @@ -2610,6 +2792,7 @@ void CompareStatusHandler::forceUiRefresh() void CompareStatusHandler::abortThisProcess() { + abortRequested = true; throw AbortThisProcess(); //abort can be triggered by syncStatusFrame } //######################################################################################################## @@ -2635,7 +2818,14 @@ SyncStatusHandler::~SyncStatusHandler() result.Replace(wxT("%x"), globalFunctions::numberToWxString(failedItems), false); for (unsigned int j = 0; j < failedItems; ++j) - result+= unhandledErrors[j] + wxT("\n"); + { //remove linebreaks + wxString errorMessage = unhandledErrors[j]; + for (wxString::iterator i = errorMessage.begin(); i != errorMessage.end(); ++i) + if (*i == wxChar('\n')) + *i = wxChar(' '); + + result += errorMessage + wxT("\n"); + } result+= wxT("\n"); } @@ -2686,37 +2876,80 @@ void SyncStatusHandler::updateProcessedData(int objectsProcessed, double dataPro ErrorHandler::Response SyncStatusHandler::reportError(const Zstring& text) { + //add current time before error message + wxString errorWithTime = wxString(wxT("[")) + wxDateTime::Now().FormatTime() + wxT("] ") + text.c_str(); + if (ignoreErrors) { - unhandledErrors.Add(text.c_str()); + unhandledErrors.Add(errorWithTime); return ErrorHandler::IGNORE_ERROR; } syncStatusFrame->updateStatusDialogNow(); bool ignoreNextErrors = false; - wxString errorMessage = wxString(text.c_str()) + wxT("\n\n") + _("Ignore this error, retry or abort synchronization?"); - ErrorDlg* errorDlg = new ErrorDlg(syncStatusFrame, errorMessage, ignoreNextErrors); - + ErrorDlg* errorDlg = new ErrorDlg(syncStatusFrame, + ErrorDlg::BUTTON_IGNORE | ErrorDlg::BUTTON_RETRY | ErrorDlg::BUTTON_ABORT, + wxString(text) + wxT("\n\n") + _("Ignore this error, retry or abort synchronization?"), + ignoreNextErrors); int rv = errorDlg->ShowModal(); switch (rv) { case ErrorDlg::BUTTON_IGNORE: ignoreErrors = ignoreNextErrors; - unhandledErrors.Add(text.c_str()); + unhandledErrors.Add(errorWithTime); return ErrorHandler::IGNORE_ERROR; + case ErrorDlg::BUTTON_RETRY: return ErrorHandler::RETRY; + case ErrorDlg::BUTTON_ABORT: - { - unhandledErrors.Add(text.c_str()); - abortRequested = true; - throw AbortThisProcess(); + unhandledErrors.Add(errorWithTime); + abortThisProcess(); } - default: - assert (false); - return ErrorHandler::IGNORE_ERROR; + + assert (false); + unhandledErrors.Add(errorWithTime); + return ErrorHandler::IGNORE_ERROR; +} + + +void SyncStatusHandler::reportFatalError(const Zstring& errorMessage) +{ //add current time before error message + wxString errorWithTime = wxString(wxT("[")) + wxDateTime::Now().FormatTime() + wxT("] ") + errorMessage.c_str(); + + unhandledErrors.Add(errorWithTime); + abortThisProcess(); +} + + +void SyncStatusHandler::reportWarning(const Zstring& warningMessage, bool& dontShowAgain) +{ //add current time before warning message + wxString warningWithTime = wxString(wxT("[")) + wxDateTime::Now().FormatTime() + wxT("] ") + warningMessage.c_str(); + + if (ignoreErrors) //if errors are ignored, then warnings should also + return; //no unhandled error situation! + + syncStatusFrame->updateStatusDialogNow(); + + //show popup and ask user how to handle warning + bool dontWarnAgain = false; + WarningDlg* warningDlg = new WarningDlg(syncStatusFrame, + WarningDlg::BUTTON_IGNORE | WarningDlg::BUTTON_ABORT, + warningMessage.c_str(), + dontWarnAgain); + switch (warningDlg->ShowModal()) + { + case WarningDlg::BUTTON_IGNORE: //no unhandled error situation! + dontShowAgain = dontWarnAgain; + return; + + case WarningDlg::BUTTON_ABORT: + unhandledErrors.Add(warningWithTime); + abortThisProcess(); } + + assert(false); } @@ -2728,6 +2961,7 @@ void SyncStatusHandler::forceUiRefresh() void SyncStatusHandler::abortThisProcess() { + abortRequested = true; throw AbortThisProcess(); //abort can be triggered by syncStatusFrame } //######################################################################################################## @@ -2759,7 +2993,6 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) if (messageDlg->ShowModal() != wxID_OK) { pushStatusInformation(_("Save aborted!")); - event.Skip(); return; } } @@ -2801,21 +3034,25 @@ void MainDialog::OnMenuExportFileList(wxCommandEvent& event) wxMessageBox(wxString(_("Error writing file:")) + wxT(" \"") + fileName + wxT("\""), _("Error"), wxOK | wxICON_ERROR); } } - - event.Skip(); } void MainDialog::OnMenuBatchJob(wxCommandEvent& event) { - std::vector<FolderPair> folderPairs; - getFolderPairs(folderPairs); + //fill batch config structure + xmlAccess::XmlBatchConfig batchCfg; + batchCfg.mainCfg = cfg; + batchCfg.directoryPairs = getFolderPairs(); + batchCfg.silent = false; - BatchDialog* batchDlg = new BatchDialog(this, cfg, folderPairs); - if (batchDlg->ShowModal() == BatchDialog::batchFileCreated) - pushStatusInformation(_("Batch file created successfully!")); + if (ignoreErrors) + batchCfg.handleError = xmlAccess::ON_ERROR_IGNORE; + else + batchCfg.handleError = xmlAccess::ON_ERROR_POPUP; - event.Skip(); + BatchDialog* batchDlg = new BatchDialog(this, batchCfg); + if (batchDlg->ShowModal() == BatchDialog::BATCH_FILE_SAVED) + pushStatusInformation(_("Batch file created successfully!")); } @@ -2837,73 +3074,72 @@ void MainDialog::OnMenuQuit(wxCommandEvent& event) //######################################################################################################### //language selection -void MainDialog::changeProgramLanguage(const int langID) +void MainDialog::switchProgramLanguage(const int langID) { programLanguage->setLanguage(langID); //language is a global attribute - restartOnExit = true; + + //create new main window and delete old one + cleanUp(); //destructor's code: includes writing settings to HD + + //create new dialog with respect to new language + MainDialog* frame = new MainDialog(NULL, FreeFileSync::LAST_CONFIG_FILE, programLanguage, globalSettings); + frame->SetIcon(*globalResource.programIcon); //set application icon + frame->Show(); + Destroy(); } void MainDialog::OnMenuLangChineseSimp(wxCommandEvent& event) { - changeProgramLanguage(wxLANGUAGE_CHINESE_SIMPLIFIED); - event.Skip(); + switchProgramLanguage(wxLANGUAGE_CHINESE_SIMPLIFIED); } void MainDialog::OnMenuLangDutch(wxCommandEvent& event) { - changeProgramLanguage(wxLANGUAGE_DUTCH); - event.Skip(); + switchProgramLanguage(wxLANGUAGE_DUTCH); } void MainDialog::OnMenuLangEnglish(wxCommandEvent& event) { - changeProgramLanguage(wxLANGUAGE_ENGLISH); - event.Skip(); + switchProgramLanguage(wxLANGUAGE_ENGLISH); } void MainDialog::OnMenuLangFrench(wxCommandEvent& event) { - changeProgramLanguage(wxLANGUAGE_FRENCH); - event.Skip(); + switchProgramLanguage(wxLANGUAGE_FRENCH); } void MainDialog::OnMenuLangGerman(wxCommandEvent& event) { - changeProgramLanguage(wxLANGUAGE_GERMAN); - event.Skip(); + switchProgramLanguage(wxLANGUAGE_GERMAN); } void MainDialog::OnMenuLangItalian(wxCommandEvent& event) { - changeProgramLanguage(wxLANGUAGE_ITALIAN); - event.Skip(); + switchProgramLanguage(wxLANGUAGE_ITALIAN); } void MainDialog::OnMenuLangJapanese(wxCommandEvent& event) { - changeProgramLanguage(wxLANGUAGE_JAPANESE); - event.Skip(); + switchProgramLanguage(wxLANGUAGE_JAPANESE); } void MainDialog::OnMenuLangPolish(wxCommandEvent& event) { - changeProgramLanguage(wxLANGUAGE_POLISH); - event.Skip(); + switchProgramLanguage(wxLANGUAGE_POLISH); } void MainDialog::OnMenuLangPortuguese(wxCommandEvent& event) { - changeProgramLanguage(wxLANGUAGE_PORTUGUESE); - event.Skip(); + switchProgramLanguage(wxLANGUAGE_PORTUGUESE); } |