/*************************************************************** * Purpose: Code for main dialog * Author: ZenJu (zhnmju123@gmx.de) * Created: 2008-07-16 * Copyright: ZenJu () **************************************************************/ #include "mainDialog.h" #include #include "../library/globalFunctions.h" #include #include #include #include "../library/customGrid.h" #include "../library/customButton.h" #include #include "../library/sorting.h" #include #include "../comparison.h" #include "../synchronization.h" #include "../algorithm.h" 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), cmpStatusHandlerTmp(0), cleanedUp(false), lastSortColumn(-1), lastSortGrid(NULL), leadGrid(NULL) { //initialize and load configuration readGlobalSettings(); readConfigurationFromXml(cfgFileName, true); //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_bpButtonSave->SetBitmapLabel(*globalResource.bitmapSave); m_bpButtonLoad->SetBitmapLabel(*globalResource.bitmapLoad); m_bpButtonAddPair->SetBitmapLabel(*globalResource.bitmapAddFolderPair); m_bpButtonRemovePair->SetBitmapLabel(*globalResource.bitmapRemoveFolderPair); m_bpButtonRemovePair->SetBitmapDisabled(*globalResource.bitmapRemoveFolderPairD); m_bitmap15->SetBitmap(*globalResource.bitmapStatusEdge); bSizer6->Layout(); //wxButtonWithImage size might have changed //menu icons m_menuItem10->SetBitmap(*globalResource.bitmapCompareSmall); m_menuItem11->SetBitmap(*globalResource.bitmapSyncSmall); m_menuItem7->SetBitmap(*globalResource.bitmapBatchSmall); m_menuItemGlobSett->SetBitmap(*globalResource.bitmapSettingsSmall); //Workaround for wxWidgets: small hack to update menu items: actually this is a wxWidgets bug (affects Windows- and Linux-build) m_menu1->Remove(m_menuItem10); m_menu1->Remove(m_menuItem11); m_menu1->Insert(0, m_menuItem10); m_menu1->Insert(1, m_menuItem11); m_menu3->Remove(m_menuItem7); m_menu3->Remove(m_menuItemGlobSett); m_menu3->Insert(2, m_menuItem7); m_menu3->Insert(3, m_menuItemGlobSett); //prepare drag & drop m_panel1->SetDropTarget(new MainWindowDropTarget(this, m_panel1)); m_panel2->SetDropTarget(new MainWindowDropTarget(this, m_panel2)); m_panel11->SetDropTarget(new MainWindowDropTarget(this, m_panel1)); m_panel12->SetDropTarget(new MainWindowDropTarget(this, m_panel2)); //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); m_gridRight->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridRightButtonEvent), NULL, this); m_gridMiddle->Connect(wxEVT_KEY_DOWN, wxKeyEventHandler(MainDialog::onGridMiddleButtonEvent), NULL, this); //identify leading grid by keyboard input or scroll action m_gridLeft->Connect(wxEVT_KEY_DOWN, wxEventHandler(MainDialog::onGridLeftAccess), NULL, this); m_gridLeft->Connect(wxEVT_SCROLLWIN_TOP, wxEventHandler(MainDialog::onGridLeftAccess), NULL, this); m_gridLeft->Connect(wxEVT_SCROLLWIN_BOTTOM, wxEventHandler(MainDialog::onGridLeftAccess), NULL, this); m_gridLeft->Connect(wxEVT_SCROLLWIN_LINEUP, wxEventHandler(MainDialog::onGridLeftAccess), NULL, this); m_gridLeft->Connect(wxEVT_SCROLLWIN_LINEDOWN, wxEventHandler(MainDialog::onGridLeftAccess), NULL, this); m_gridLeft->Connect(wxEVT_SCROLLWIN_PAGEUP, wxEventHandler(MainDialog::onGridLeftAccess), NULL, this); m_gridLeft->Connect(wxEVT_SCROLLWIN_PAGEDOWN, wxEventHandler(MainDialog::onGridLeftAccess), NULL, this); m_gridLeft->Connect(wxEVT_SCROLLWIN_THUMBTRACK, wxEventHandler(MainDialog::onGridLeftAccess), NULL, this); m_gridLeft->Connect(wxEVT_SCROLLWIN_THUMBRELEASE, wxEventHandler(MainDialog::onGridLeftAccess), NULL, this); m_gridLeft->Connect(wxEVT_GRID_LABEL_LEFT_CLICK, wxEventHandler(MainDialog::onGridLeftAccess), NULL, this); m_gridLeft->GetGridWindow()->Connect(wxEVT_LEFT_DOWN, wxEventHandler(MainDialog::onGridLeftAccess), NULL, this); m_gridRight->Connect(wxEVT_KEY_DOWN, wxEventHandler(MainDialog::onGridRightAccess), NULL, this); m_gridRight->Connect(wxEVT_SCROLLWIN_TOP, wxEventHandler(MainDialog::onGridRightAccess), NULL, this); m_gridRight->Connect(wxEVT_SCROLLWIN_BOTTOM, wxEventHandler(MainDialog::onGridRightAccess), NULL, this); m_gridRight->Connect(wxEVT_SCROLLWIN_LINEUP, wxEventHandler(MainDialog::onGridRightAccess), NULL, this); m_gridRight->Connect(wxEVT_SCROLLWIN_LINEDOWN, wxEventHandler(MainDialog::onGridRightAccess), NULL, this); m_gridRight->Connect(wxEVT_SCROLLWIN_PAGEUP, wxEventHandler(MainDialog::onGridRightAccess), NULL, this); m_gridRight->Connect(wxEVT_SCROLLWIN_PAGEDOWN, wxEventHandler(MainDialog::onGridRightAccess), NULL, this); m_gridRight->Connect(wxEVT_SCROLLWIN_THUMBTRACK, wxEventHandler(MainDialog::onGridRightAccess), NULL, this); m_gridRight->Connect(wxEVT_SCROLLWIN_THUMBRELEASE, wxEventHandler(MainDialog::onGridRightAccess), NULL, this); m_gridRight->Connect(wxEVT_GRID_LABEL_LEFT_CLICK, wxEventHandler(MainDialog::onGridRightAccess), NULL, this); m_gridRight->GetGridWindow()->Connect(wxEVT_LEFT_DOWN, wxEventHandler(MainDialog::onGridRightAccess), NULL, this); m_gridMiddle->Connect(wxEVT_KEY_DOWN, wxEventHandler(MainDialog::onGridMiddleAccess), NULL, this); m_gridMiddle->Connect(wxEVT_SCROLLWIN_LINEUP, wxEventHandler(MainDialog::onGridMiddleAccess), NULL, this); m_gridMiddle->Connect(wxEVT_SCROLLWIN_LINEDOWN, wxEventHandler(MainDialog::onGridMiddleAccess), NULL, this); m_gridMiddle->GetGridWindow()->Connect(wxEVT_LEFT_DOWN, wxEventHandler(MainDialog::onGridMiddleAccess), NULL, this); Connect(wxEVT_IDLE, wxEventHandler(MainDialog::OnIdleEvent), NULL, this); m_gridMiddle->GetGridWindow()->Connect(wxEVT_LEFT_UP, wxEventHandler(MainDialog::OnGrid3LeftMouseUp), NULL, this); m_gridMiddle->GetGridWindow()->Connect(wxEVT_LEFT_DOWN, wxEventHandler(MainDialog::OnGrid3LeftMouseDown), NULL, this); Connect(wxEVT_SIZE, wxEventHandler(MainDialog::onResizeMainWindow), NULL, this); Connect(wxEVT_MOVE, wxEventHandler(MainDialog::onResizeMainWindow), NULL, this); const wxString header = _("Legend"); wxString toolTip = header + wxT("\n") + wxString().Pad(header.Len(), wxChar('-')) + wxT("\n") + _("<| file on left side only\n") + _("|> file on right side only\n") + _("<< left file is newer\n") + _(">> right file is newer\n") + _("!= files are different\n") + _("== files are equal\n\n"); m_gridMiddle->GetGridWindow()->SetToolTip(toolTip); //init grid settings m_gridLeft->initSettings( true, m_gridLeft, m_gridRight, m_gridMiddle, &gridRefUI, ¤tGridData); m_gridMiddle->initSettings(false, m_gridLeft, m_gridRight, m_gridMiddle, &gridRefUI, ¤tGridData); m_gridRight->initSettings( true, m_gridLeft, m_gridRight, m_gridMiddle, &gridRefUI, ¤tGridData); //disable sync button as long as "compare" hasn't been triggered. enableSynchronization(false); //mainly to update row label sizes... writeGrid(currentGridData); enableSynchronization(false); //initialize language selection switch (programLanguage->getLanguage()) { case wxLANGUAGE_CHINESE_SIMPLIFIED: m_menuItemChineseSimple->Check(); break; case wxLANGUAGE_DUTCH: m_menuItemDutch->Check(); break; case wxLANGUAGE_FRENCH: m_menuItemFrench->Check(); break; case wxLANGUAGE_GERMAN: m_menuItemGerman->Check(); break; case wxLANGUAGE_ITALIAN: m_menuItemItalian->Check(); break; case wxLANGUAGE_JAPANESE: m_menuItemJapanese->Check(); break; case wxLANGUAGE_POLISH: m_menuItemPolish->Check(); break; case wxLANGUAGE_PORTUGUESE: m_menuItemPortuguese->Check(); break; default: m_menuItemEnglish->Check(); } //create the compare status panel in hidden state compareStatus = new CompareStatus(this); bSizer1->Insert(1, compareStatus, 0, wxEXPAND | wxBOTTOM, 5 ); Layout(); //avoid screen flicker when panel is shown later compareStatus->Hide(); //correct width of swap button above middle grid wxSize source = m_gridMiddle->GetSize(); wxSize target = bSizerMiddle->GetSize(); int spaceToAdd = source.GetX() - target.GetX(); bSizerMiddle->Insert(1, spaceToAdd / 2, 0, 0); bSizerMiddle->Insert(0, spaceToAdd - (spaceToAdd / 2), 0, 0); } MainDialog::~MainDialog() { cleanUp(); //do NOT include any other code here! cleanUp() is re-used when switching languages } void MainDialog::cleanUp() { if (!cleanedUp) { cleanedUp = true; //no need for event disconnect here; done automatically //save configuration writeConfigurationToXml(FreeFileSync::LAST_CONFIG_FILE); //don't trow 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); 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) addCfgFileToHistory(*i); m_choiceHistory->SetSelection(0); } 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; } inline bool gridShouldBeCleared(const wxEvent& event) { try //test if CTRL was pressed during a mouse event { const wxMouseEvent& mouseEvent = dynamic_cast (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 (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(); } 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(); } 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(); } void MainDialog::enableSynchronization(bool value) { if (value) { synchronizationEnabled = true; m_buttonSync->SetForegroundColour(*wxBLACK); m_buttonSync->setBitmapFront(*globalResource.bitmapSync); } else { synchronizationEnabled = false; m_buttonSync->SetForegroundColour(wxColor(94, 94, 94)); //grey m_buttonSync->setBitmapFront(*globalResource.bitmapSyncDisabled); } } void MainDialog::filterRangeManually(const std::set& rowsToFilterOnUiTable) { if (rowsToFilterOnUiTable.size() > 0) { int gridSizeUI = gridRefUI.size(); bool newSelection = false; //default: deselect range //leadingRow determines de-/selection of all other rows int leadingRow = *rowsToFilterOnUiTable.begin(); if (0 <= leadingRow && leadingRow < gridSizeUI) newSelection = !currentGridData[gridRefUI[leadingRow]].selectedForSynchronization; if (hideFilteredElements) assert(!newSelection); //if hidefiltered is active, there should be no filtered elements on screen => current element was filtered out //get all lines that need to be filtered (e.g. if a folder is marked, then its subelements should be marked as well) std::set rowsToFilterOnGridData; //rows to filter in backend for (std::set::iterator i = rowsToFilterOnUiTable.begin(); i != rowsToFilterOnUiTable.end(); ++i) { if (0 <= *i && *i < gridSizeUI) { unsigned int gridIndex = gridRefUI[*i]; rowsToFilterOnGridData.insert(gridIndex); FreeFileSync::addSubElements(currentGridData, currentGridData[gridIndex], rowsToFilterOnGridData); } } //toggle selection of filtered rows for (std::set::iterator i = rowsToFilterOnGridData.begin(); i != rowsToFilterOnGridData.end(); ++i) currentGridData[*i].selectedForSynchronization = newSelection; //signal UI that grids need to be refreshed on next Update() m_gridLeft->ForceRefresh(); m_gridRight->ForceRefresh(); m_gridMiddle->ForceRefresh(); Update(); //show changes resulting from ForceRefresh() if (hideFilteredElements) { wxMilliSleep(400); //some delay to show user the rows he has filtered out before they are removed writeGrid(currentGridData); //redraw grid to remove excluded elements (keeping sort sequence) } } //clear selection on grids if (hideFilteredElements) { m_gridLeft->ClearSelection(); m_gridRight->ClearSelection(); } //exception for grid 3 m_gridMiddle->ClearSelection(); } /*grid event choreography: 1. UI-Mouse-Down => OnGridSelectCell 2. UI-Mouse-Up => SelectRangeEvent (if at least two rows are marked) => the decision if a range or a single cell is selected can be made only after Mouse-UP. But SelectRangeEvent unfortunately is not always executed (e.g. if single cell selected) => new choreography: 1. UI-Mouse-Down => OnGrid3LeftMouseDown (notify that filtering was initialized: this is needed since under some circumstances it might happen that the program receives a mouse-up without a preceding mouse-down (double-clicks) 2. UI-Mouse-Up => OnGrid3LeftMouseUp (notify that filtering shall be started on next idle event 3. UI-Mouse-Up => SelectRangeEvent, possibly 4. Idle event => OnIdleEvent */ void MainDialog::OnGrid3LeftMouseDown(wxEvent& event) { filteringInitialized = true; event.Skip(); } void MainDialog::OnGrid3LeftMouseUp(wxEvent& event) { filteringPending = true; event.Skip(); } void MainDialog::OnIdleEvent(wxEvent& event) { //process manually filtered rows if (filteringPending) { filteringPending = false; if (filteringInitialized) //filteringInitialized is being reset after each selection, since strangely it might happen, that the grid receives { //a mouse up event, but no mouse down! (e.g. when window is maximized and cursor is on grid3) filteringInitialized = false; if (m_gridMiddle) filterRangeManually(getSelectedRows(m_gridMiddle)); } } //------------------------------------------------------------------------------ //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 > 2000) //restore stackObject after two seconds { lastStatusChange = currentTime; m_staticTextStatusMiddle->SetLabel(stackObjects.top()); stackObjects.pop(); m_panel7->Layout(); } } event.Skip(); } void MainDialog::copySelectionToClipboard(const wxGrid* selectedGrid) { const std::set selectedRows = getSelectedRows(selectedGrid); if (selectedRows.size() > 0) { wxString clipboardString; for (std::set::iterator i = selectedRows.begin(); i != selectedRows.end(); ++i) { for (int k = 0; k < const_cast(selectedGrid)->GetNumberCols(); ++k) { clipboardString+= const_cast(selectedGrid)->GetCellValue(*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(); } } } void removeInvalidRows(std::set& rows, const int currentUiTableSize) { std::set validRows; //temporal table IS needed here for (std::set::iterator i = rows.begin(); i != rows.end(); ++i) if (0 <= *i) { if (*i >= currentUiTableSize) //set is sorted, so no need to continue here break; validRows.insert(*i); } rows.swap(validRows); } std::set MainDialog::getSelectedRows(const wxGrid* grid) { std::set output; int rowTop, rowBottom; //coords of selection wxArrayInt selectedRows = grid->GetSelectedRows(); if (!selectedRows.IsEmpty()) { for (unsigned int i = 0; i < selectedRows.GetCount(); ++i) output.insert(selectedRows[i]); } 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(grid)->GetNumberRows(); ++k) //messy wxGrid implementation... output.insert(k); } wxGridCellCoordsArray singlySelected = grid->GetSelectedCells(); if (!singlySelected.IsEmpty()) { for (unsigned int k = 0; k < singlySelected.GetCount(); ++k) output.insert(singlySelected[k].GetRow()); } wxGridCellCoordsArray tmpArrayTop = grid->GetSelectionBlockTopLeft(); if (!tmpArrayTop.IsEmpty()) { wxGridCellCoordsArray tmpArrayBottom = grid->GetSelectionBlockBottomRight(); unsigned int arrayCount = tmpArrayTop.GetCount(); if (arrayCount == tmpArrayBottom.GetCount()) { for (unsigned int i = 0; i < arrayCount; ++i) { rowTop = tmpArrayTop[i].GetRow(); rowBottom = tmpArrayBottom[i].GetRow(); for (int k = rowTop; k <= rowBottom; ++k) output.insert(k); } } } //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(grid)->GetCursorRow()); //messy wxGrid implementation... removeInvalidRows(output, gridRefUI.size()); return output; } class DeleteErrorHandler : public ErrorHandler { public: DeleteErrorHandler() : ignoreErrors(false) {} ~DeleteErrorHandler() {} Response reportError(const Zstring& errorMessage) { if (ignoreErrors) return ErrorHandler::IGNORE_ERROR; bool ignoreNextErrors = false; 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; return ErrorHandler::IGNORE_ERROR; case ErrorDlg::BUTTON_RETRY: return ErrorHandler::RETRY; case ErrorDlg::BUTTON_ABORT: throw AbortThisProcess(); default: assert (false); return ErrorHandler::IGNORE_ERROR; //dummy return value } } private: bool ignoreErrors; }; void MainDialog::deleteFilesOnGrid(const std::set& selectedRowsLeft, const std::set& selectedRowsRight) { if (selectedRowsLeft.size() + selectedRowsRight.size()) { //map grid lines from UI to grid lines in backend (gridData) std::set rowsOnGridLeft; for (std::set::iterator i = selectedRowsLeft.begin(); i != selectedRowsLeft.end(); ++i) rowsOnGridLeft.insert(gridRefUI[*i]); std::set rowsOnGridRight; for (std::set::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) { //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; FreeFileSync::deleteFromGridAndHD(currentGridData, rowsOnGridLeft, rowsOnGridRight, globalSettings.gui.deleteOnBothSides, globalSettings.gui.useRecyclerForManualDeletion, &errorHandler); } catch (AbortThisProcess&) {} //redraw grid neccessary to update new dimensions and for UI-Backend data linkage writeGrid(currentGridData); //call immediately after deleteFromGridAndHD!!! m_gridLeft->ClearSelection(); m_gridMiddle->ClearSelection(); m_gridRight->ClearSelection(); } } } void MainDialog::openWithFileManager(int rowNumber, const wxGrid* grid) { wxString command; const FileDescrLine* fileDescr = NULL; if (grid == m_gridLeft) { if (0 <= rowNumber && rowNumber < int(gridRefUI.size())) fileDescr = ¤tGridData[gridRefUI[rowNumber]].fileDescrLeft; #ifdef FFS_WIN command = wxString(wxT("explorer ")) + FreeFileSync::getFormattedDirectoryName(m_directoryLeft->GetValue().c_str()).c_str(); //default #endif // FFS_WIN } else if (grid == m_gridRight) { if (0 <= rowNumber && rowNumber < int(gridRefUI.size())) fileDescr = ¤tGridData[gridRefUI[rowNumber]].fileDescrRight; #ifdef FFS_WIN command = wxString(wxT("explorer ")) + FreeFileSync::getFormattedDirectoryName(m_directoryRight->GetValue().c_str()).c_str(); //default #endif // FFS_WIN } else { assert(false); return; } if (fileDescr) { 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()) wxExecute(command.c_str()); } void MainDialog::pushStatusInformation(const wxString& text) { lastStatusChange = wxGetLocalTimeMillis(); stackObjects.push(m_staticTextStatusMiddle->GetLabel()); m_staticTextStatusMiddle->SetLabel(text); m_panel7->Layout(); } void MainDialog::clearStatusBar() { while (stackObjects.size() > 0) stackObjects.pop(); m_staticTextStatusLeft->SetLabel(wxEmptyString); m_staticTextStatusMiddle->SetLabel(wxEmptyString); m_staticTextStatusRight->SetLabel(wxEmptyString); } void MainDialog::onResizeMainWindow(wxEvent& 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::onGridLeftButtonEvent(wxKeyEvent& event) { const int keyCode = event.GetKeyCode(); 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::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(); } void MainDialog::onGridRightButtonEvent(wxKeyEvent& event) { const int keyCode = event.GetKeyCode(); 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::OnContextMenu(wxGridEvent& event) { std::set selectionLeft = getSelectedRows(m_gridLeft); std::set selectionRight = getSelectedRows(m_gridRight); //####################################################### //re-create context menu contextMenu.reset(new wxMenu); //CONTEXT_FILTER_TEMP if (selectionLeft.size() + selectionRight.size() > 0) { 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 for filtering exFilterCandidateObj.clear(); FilterObject newFilterEntry; for (std::set::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::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); } //############################################################################################### //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! { exFilterCandidateExtension = filename.AfterLast(wxChar('.')); contextMenu->Append(CONTEXT_EXCLUDE_EXT, wxString(_("Exclude via filter:")) + wxT(" ") + wxT("*.") + exFilterCandidateExtension); } } //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(" ") + _("")); contextMenu->AppendSeparator(); //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); else contextMenu->Enable(CONTEXT_CLIPBOARD, false); //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); 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) { int eventId = event.GetId(); if (eventId == CONTEXT_FILTER_TEMP) { //merge selections from left and right grid std::set selection = getSelectedRows(m_gridLeft); std::set additional = getSelectedRows(m_gridRight); for (std::set::const_iterator i = additional.begin(); i != additional.end(); ++i) selection.insert(*i); filterRangeManually(selection); } else if (eventId == CONTEXT_EXCLUDE_EXT) { if (!exFilterCandidateExtension.IsEmpty()) { if (!cfg.excludeFilter.IsEmpty() && !cfg.excludeFilter.EndsWith(wxT(";"))) cfg.excludeFilter+= wxT("\n"); cfg.excludeFilter+= wxString(wxT("*.")) + exFilterCandidateExtension + wxT(";"); //';' is appended to 'mark' that next exclude extension entry won't write to new line cfg.filterIsActive = true; updateFilterButton(m_bpButtonFilter, cfg.filterIsActive); FreeFileSync::filterGridData(currentGridData, cfg.includeFilter, cfg.excludeFilter); writeGrid(currentGridData); if (hideFilteredElements) { m_gridLeft->ClearSelection(); m_gridRight->ClearSelection(); m_gridMiddle->ClearSelection(); } } } else if (eventId == CONTEXT_EXCLUDE_OBJ) { if (exFilterCandidateObj.size() > 0) //check needed to determine if filtering is needed { for (std::vector::const_iterator i = exFilterCandidateObj.begin(); i != exFilterCandidateObj.end(); ++i) { if (!cfg.excludeFilter.IsEmpty() && !cfg.excludeFilter.EndsWith(wxT("\n"))) cfg.excludeFilter+= wxT("\n"); if (i->type == FileDescrLine::TYPE_FILE) { cfg.excludeFilter+= wxString(wxT("*")) + GlobalResources::FILE_NAME_SEPARATOR + i->relativeName; } else if (i->type == FileDescrLine::TYPE_DIRECTORY) { cfg.excludeFilter+= wxString(wxT("*")) + GlobalResources::FILE_NAME_SEPARATOR + i->relativeName + wxT("\n"); cfg.excludeFilter+= wxString(wxT("*")) + GlobalResources::FILE_NAME_SEPARATOR + i->relativeName + GlobalResources::FILE_NAME_SEPARATOR + wxT("*"); } else assert(false); } cfg.filterIsActive = true; updateFilterButton(m_bpButtonFilter, cfg.filterIsActive); FreeFileSync::filterGridData(currentGridData, cfg.includeFilter, cfg.excludeFilter); writeGrid(currentGridData); if (hideFilteredElements) { m_gridLeft->ClearSelection(); m_gridRight->ClearSelection(); m_gridMiddle->ClearSelection(); } } } else if (eventId == CONTEXT_CLIPBOARD) { if (leadGrid == m_gridLeft || leadGrid == m_gridRight) copySelectionToClipboard(leadGrid); } else if (eventId == CONTEXT_EXPLORER) { if (leadGrid == m_gridLeft || leadGrid == m_gridRight) { std::set selection = getSelectedRows(leadGrid); if (selection.size() == 1) openWithFileManager(*selection.begin(), leadGrid); else if (selection.size() == 0) openWithFileManager(-1, leadGrid); } } else if (eventId == CONTEXT_DELETE_FILES) { deleteFilesOnGrid(getSelectedRows(m_gridLeft), getSelectedRows(m_gridRight)); } } 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) { 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::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) { contextMenu.reset(new wxMenu); //re-create context menu contextMenu->Append(CONTEXT_CUSTOMIZE_COLUMN_LEFT, _("Customize columns")); contextMenu->Connect(wxEVT_COMMAND_MENU_SELECTED, wxCommandEventHandler(MainDialog::OnContextColumnSelection), NULL, this); PopupMenu(contextMenu.get()); //show context menu event.Skip(); } void MainDialog::OnContextColumnRight(wxGridEvent& event) { contextMenu.reset(new wxMenu); //re-create context menu contextMenu->Append(CONTEXT_CUSTOMIZE_COLUMN_RIGHT, _("Customize columns")); 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(); if (wxDirExists(newDir)) { wxObject* eventObj = event.GetEventObject(); //first check if event comes from main folder pair if (eventObj == (wxObject*)m_directoryLeft) m_dirPickerLeft->SetPath(newDir); else if (eventObj == (wxObject*)m_directoryRight) m_dirPickerRight->SetPath(newDir); else { //check if event comes from additional pairs for (std::vector::const_iterator i = additionalFolderPairs.begin(); i != additionalFolderPairs.end(); ++i) { FolderPairGenerated* dirPair = *i; if (eventObj == (wxObject*)(dirPair->m_directoryLeft)) { dirPair->m_dirPickerLeft->SetPath(newDir); break; } else if (eventObj == (wxObject*)(dirPair->m_directoryRight)) { dirPair->m_dirPickerRight->SetPath(newDir); break; } } } } event.Skip(); } void MainDialog::OnDirSelected(wxFileDirPickerEvent& event) { const wxString newPath = event.GetPath(); wxObject* eventObj = event.GetEventObject(); //first check if event comes from main folder pair if (eventObj == (wxObject*)m_dirPickerLeft) m_directoryLeft->SetValue(newPath); else if (eventObj == (wxObject*)m_dirPickerRight) m_directoryRight->SetValue(newPath); else //check if event comes from additional pairs { for (std::vector::const_iterator i = additionalFolderPairs.begin(); i != additionalFolderPairs.end(); ++i) { FolderPairGenerated* dirPair = *i; if (eventObj == (wxObject*)(dirPair->m_dirPickerLeft)) { dirPair->m_directoryLeft->SetValue(newPath); break; } else if (eventObj == (wxObject*)(dirPair->m_dirPickerRight)) { dirPair->m_directoryRight->SetValue(newPath); break; } } } //disable the sync button enableSynchronization(false); //clear grids currentGridData.clear(); writeGrid(currentGridData); 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 inline bool sameFileSpecified(const wxString& file1, const wxString& file2) { wxString file1Full = file1; wxString file2Full = file2; if (wxFileName(file1).GetPath() == wxEmptyString) file1Full = wxFileName::GetCwd() + GlobalResources::FILE_NAME_SEPARATOR + file1; if (wxFileName(file2).GetPath() == wxEmptyString) file2Full = wxFileName::GetCwd() + GlobalResources::FILE_NAME_SEPARATOR + file2; return (file1Full == file2Full); } class FindDuplicates { public: FindDuplicates(const wxString& name) : m_name(name) {} bool operator()(const wxString& other) const { return sameFileSpecified(m_name, other); } private: const wxString& m_name; }; void MainDialog::addCfgFileToHistory(const wxString& filename) { //only (still) existing files should be included in the list if (!wxFileExists(filename)) return; std::vector::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_choiceHistory->SetSelection(i - cfgFileNames.begin()); } else { cfgFileNames.insert(cfgFileNames.begin(), filename); //the default config file should receive another name on GUI if (sameFileSpecified(FreeFileSync::LAST_CONFIG_FILE, filename)) m_choiceHistory->Insert(getFormattedHistoryElement(_("")), 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.cfgHistoryMaxItems) { //delete last rows cfgFileNames.pop_back(); m_choiceHistory->Delete(globalSettings.gui.cfgHistoryMaxItems); } } 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; if (wxDirExists(fileName)) { txtCtrl->SetValue(fileName); dirPicker->SetPath(fileName); } else { fileName = wxFileName(fileName).GetPath(); if (wxDirExists(fileName)) { txtCtrl->SetValue(fileName); dirPicker->SetPath(fileName); } } } void MainDialog::OnFilesDropped(FfsFileDropEvent& event) { const wxString droppedFileName = event.m_nameDropped; const wxPanel* dropTarget = event.m_dropTarget; //disable the sync button enableSynchronization(false); //clear grids currentGridData.clear(); writeGrid(currentGridData); xmlAccess::XmlType fileType = xmlAccess::getXmlType(droppedFileName); //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 == m_panel2) setDirectoryFromDrop(droppedFileName, m_directoryRight, m_dirPickerRight); else //test if additional folder pairs are drop targets { for (std::vector::const_iterator i = additionalFolderPairs.begin(); i != additionalFolderPairs.end(); ++i) { FolderPairGenerated* dirPair = *i; if (dropTarget == (dirPair->m_panelLeft)) { setDirectoryFromDrop(droppedFileName, dirPair->m_directoryLeft, dirPair->m_dirPickerLeft); break; } else if (dropTarget == (dirPair->m_panelRight)) { setDirectoryFromDrop(droppedFileName, dirPair->m_directoryRight, dirPair->m_dirPickerRight); break; } } } } void MainDialog::OnSaveConfig(wxCommandEvent& event) { wxString defaultFileName = wxT("SyncSettings.ffs_gui"); 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) { wxString newFileName = filePicker->GetPath(); if (wxFileExists(newFileName)) { wxMessageDialog* messageDlg = new wxMessageDialog(this, wxString(_("File already exists. Overwrite?")) + wxT(" \"") + newFileName + wxT("\""), _("Warning") , wxOK | wxCANCEL); if (messageDlg->ShowModal() != wxID_OK) { pushStatusInformation(_("Save aborted!")); return; } } if (writeConfigurationToXml(newFileName)) pushStatusInformation(_("Configuration saved!")); } } void MainDialog::OnLoadConfig(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()); } 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); } void MainDialog::OnMenuLoadConfig(wxCommandEvent& event) { OnLoadConfig(event); } void MainDialog::loadConfiguration(const wxString& filename) { if (!filename.IsEmpty()) { //clear grids currentGridData.clear(); writeGrid(currentGridData); if (readConfigurationFromXml(filename)) pushStatusInformation(_("Configuration loaded!")); } } void MainDialog::OnChoiceKeyEvent(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 && 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(); } void MainDialog::OnCompareByTimeSize(wxCommandEvent& event) { cfg.compareVar = CMP_BY_TIME_SIZE; updateCompareButtons(); } void MainDialog::OnCompareByContent(wxCommandEvent& event) { cfg.compareVar = CMP_BY_CONTENT; updateCompareButtons(); } void MainDialog::OnClose(wxCloseEvent &event) { Destroy(); } void MainDialog::OnQuit(wxCommandEvent &event) { Destroy(); } 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 xmlAccess::XmlGuiConfig guiCfg; //structure to receive gui settings, already defaulted!! try { guiCfg = xmlAccess::readGuiConfig(filename); } catch (const FileError& error) { if (programStartup) { if (filename == FreeFileSync::LAST_CONFIG_FILE && !wxFileExists(filename)) //do not show error in this case ; else //program startup: show error message and load defaults wxMessageBox(error.show().c_str(), _("Error"), wxOK | wxICON_ERROR); } else { wxMessageBox(error.show().c_str(), _("Error"), wxOK | wxICON_ERROR); return false; } } //load main configuration into instance cfg = guiCfg.mainCfg; //update visible config on main window updateCompareButtons(); updateFilterButton(m_bpButtonFilter, cfg.filterIsActive); //read folder pairs: //clear existing pairs first m_directoryLeft->SetValue(wxEmptyString); m_dirPickerLeft->SetPath(wxEmptyString); m_directoryRight->SetValue(wxEmptyString); m_dirPickerRight->SetPath(wxEmptyString); removeFolderPair(true); //set main folder pair const unsigned int folderPairCount = guiCfg.directoryPairs.size(); if (folderPairCount > 0) { std::vector::const_iterator i = guiCfg.directoryPairs.begin(); m_directoryLeft->SetValue(i->leftDirectory.c_str()); wxString leftDirFormatted = FreeFileSync::getFormattedDirectoryName(i->leftDirectory).c_str(); if (wxDirExists(leftDirFormatted)) m_dirPickerLeft->SetPath(leftDirFormatted); m_directoryRight->SetValue(i->rightDirectory.c_str()); wxString rightDirFormatted = FreeFileSync::getFormattedDirectoryName(i->rightDirectory).c_str(); if (wxDirExists(rightDirFormatted)) m_dirPickerRight->SetPath(rightDirFormatted); //set additional pairs for (std::vector::const_iterator i = guiCfg.directoryPairs.begin() + 1; i != guiCfg.directoryPairs.end(); ++i) addFolderPair(i->leftDirectory.c_str(), i->rightDirectory.c_str()); //adjust folder pair buttons const int additionalFolderCount = folderPairCount - 1; if (additionalFolderCount <= 0) m_bpButtonRemovePair->Disable(); } //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) { xmlAccess::XmlGuiConfig guiCfg; //load structure with basic settings "mainCfg" guiCfg.mainCfg = cfg; guiCfg.directoryPairs = getFolderPairs(); //load structure with gui settings guiCfg.hideFilteredElements = hideFilteredElements; guiCfg.ignoreErrors = ignoreErrors; //write config to XML try { xmlAccess::writeGuiConfig(filename, guiCfg); } catch (const FileError& error) { wxMessageBox(error.show().c_str(), _("Error"), wxOK | wxICON_ERROR); return false; } //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; } void MainDialog::OnShowHelpDialog(wxCommandEvent &event) { HelpDlg* helpDlg = new HelpDlg(this); helpDlg->ShowModal(); } void MainDialog::OnFilterButton(wxCommandEvent &event) { //toggle filter on/off cfg.filterIsActive = !cfg.filterIsActive; //make sure, button-appearance and "filterIsActive" are in sync. updateFilterButton(m_bpButtonFilter, cfg.filterIsActive); if (cfg.filterIsActive) FreeFileSync::filterGridData(currentGridData, cfg.includeFilter, cfg.excludeFilter); else FreeFileSync::includeAllRowsOnGrid(currentGridData); writeGrid(currentGridData); } void MainDialog::OnHideFilteredButton(wxCommandEvent &event) { //toggle showing filtered rows hideFilteredElements = !hideFilteredElements; //make sure, checkbox and "hideFiltered" are in sync m_checkBoxHideFilt->SetValue(hideFilteredElements); m_gridLeft->ClearSelection(); m_gridRight->ClearSelection(); writeGrid(currentGridData); event.Skip(); } void MainDialog::OnConfigureFilter(wxHyperlinkEvent &event) { wxString beforeImage = cfg.includeFilter + wxChar(1) + cfg.excludeFilter; FilterDlg* filterDlg = new FilterDlg(this, cfg.includeFilter, cfg.excludeFilter); if (filterDlg->ShowModal() == FilterDlg::BUTTON_OKAY) { wxString afterImage = cfg.includeFilter + wxChar(1) + cfg.excludeFilter; if (beforeImage != afterImage) //if filter settings are changed: set filtering to "on" { if (afterImage == (wxString(wxT("*")) + wxChar(1))) //default { cfg.filterIsActive = false; FreeFileSync::includeAllRowsOnGrid(currentGridData); } else { cfg.filterIsActive = true; FreeFileSync::filterGridData(currentGridData, cfg.includeFilter, cfg.excludeFilter); } updateFilterButton(m_bpButtonFilter, cfg.filterIsActive); writeGrid(currentGridData); } } //no event.Skip() here, to not start browser } void MainDialog::OnLeftOnlyFiles(wxCommandEvent& event) { leftOnlyFilesActive = !leftOnlyFilesActive; updateViewFilterButtons(); writeGrid(currentGridData); }; void MainDialog::OnLeftNewerFiles(wxCommandEvent& event) { leftNewerFilesActive = !leftNewerFilesActive; updateViewFilterButtons(); writeGrid(currentGridData); }; void MainDialog::OnDifferentFiles(wxCommandEvent& event) { differentFilesActive = !differentFilesActive; updateViewFilterButtons(); writeGrid(currentGridData); }; void MainDialog::OnRightNewerFiles(wxCommandEvent& event) { rightNewerFilesActive = !rightNewerFilesActive; updateViewFilterButtons(); writeGrid(currentGridData); }; void MainDialog::OnRightOnlyFiles(wxCommandEvent& event) { rightOnlyFilesActive = !rightOnlyFilesActive; updateViewFilterButtons(); writeGrid(currentGridData); }; void MainDialog::OnEqualFiles(wxCommandEvent& event) { equalFilesActive = !equalFilesActive; updateViewFilterButtons(); writeGrid(currentGridData); }; void MainDialog::updateViewFilterButtons() { if (leftOnlyFilesActive) { m_bpButtonLeftOnly->SetBitmapLabel(*globalResource.bitmapLeftOnlyAct); m_bpButtonLeftOnly->SetToolTip(_("Hide files that exist on left side only")); } else { m_bpButtonLeftOnly->SetBitmapLabel(*globalResource.bitmapLeftOnlyDeact); m_bpButtonLeftOnly->SetToolTip(_("Show files that exist on left side only")); } if (leftNewerFilesActive) { m_bpButtonLeftNewer->SetBitmapLabel(*globalResource.bitmapLeftNewerAct); m_bpButtonLeftNewer->SetToolTip(_("Hide files that are newer on left")); } else { m_bpButtonLeftNewer->SetBitmapLabel(*globalResource.bitmapLeftNewerDeact); m_bpButtonLeftNewer->SetToolTip(_("Show files that are newer on left")); } if (equalFilesActive) { m_bpButtonEqual->SetBitmapLabel(*globalResource.bitmapEqualAct); m_bpButtonEqual->SetToolTip(_("Hide files that are equal")); } else { m_bpButtonEqual->SetBitmapLabel(*globalResource.bitmapEqualDeact); m_bpButtonEqual->SetToolTip(_("Show files that are equal")); } if (differentFilesActive) { m_bpButtonDifferent->SetBitmapLabel(*globalResource.bitmapDifferentAct); m_bpButtonDifferent->SetToolTip(_("Hide files that are different")); } else { m_bpButtonDifferent->SetBitmapLabel(*globalResource.bitmapDifferentDeact); m_bpButtonDifferent->SetToolTip(_("Show files that are different")); } if (rightNewerFilesActive) { m_bpButtonRightNewer->SetBitmapLabel(*globalResource.bitmapRightNewerAct); m_bpButtonRightNewer->SetToolTip(_("Hide files that are newer on right")); } else { m_bpButtonRightNewer->SetBitmapLabel(*globalResource.bitmapRightNewerDeact); m_bpButtonRightNewer->SetToolTip(_("Show files that are newer on right")); } if (rightOnlyFilesActive) { m_bpButtonRightOnly->SetBitmapLabel(*globalResource.bitmapRightOnlyAct); m_bpButtonRightOnly->SetToolTip(_("Hide files that exist on right side only")); } else { m_bpButtonRightOnly->SetBitmapLabel(*globalResource.bitmapRightOnlyDeact); m_bpButtonRightOnly->SetToolTip(_("Show files that exist on right side only")); } } void MainDialog::updateFilterButton(wxBitmapButton* filterButton, bool isActive) { if (isActive) { filterButton->SetBitmapLabel(*globalResource.bitmapFilterOn); filterButton->SetToolTip(_("Filter active: Press again to deactivate")); } else { filterButton->SetBitmapLabel(*globalResource.bitmapFilterOff); filterButton->SetToolTip(_("Press button to activate filter")); } } void MainDialog::updateCompareButtons() { switch (cfg.compareVar) { case CMP_BY_TIME_SIZE: m_radioBtnSizeDate->SetValue(true); break; case CMP_BY_CONTENT: m_radioBtnContent->SetValue(true); break; default: assert (false); } //disable the sync button enableSynchronization(false); //clear grids currentGridData.clear(); writeGrid(currentGridData); } std::vector MainDialog::getFolderPairs() { std::vector output; //add main pair FolderPair newPair; newPair.leftDirectory = m_directoryLeft->GetValue().c_str(); newPair.rightDirectory = m_directoryRight->GetValue().c_str(); output.push_back(newPair); //add additional pairs for (std::vector::const_iterator i = additionalFolderPairs.begin(); i != additionalFolderPairs.end(); ++i) { FolderPairGenerated* dirPair = *i; 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) { 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 CompareStatusHandler statusHandler(this); cmpStatusHandlerTmp = &statusHandler; #ifdef FFS_WIN FreeFileSync::CompareProcess comparison(globalSettings.shared.traverseSymbolicLinks, globalSettings.shared.handleDstOnFat32, globalSettings.shared.warningDependentFolders, &statusHandler); #elif defined FFS_LINUX FreeFileSync::CompareProcess comparison(globalSettings.shared.traverseSymbolicLinks, false, globalSettings.shared.warningDependentFolders, &statusHandler); #endif 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 std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName); //filter currentGridData if option is set if (cfg.filterIsActive) FreeFileSync::filterGridData(currentGridData, cfg.includeFilter, cfg.excludeFilter); } catch (AbortThisProcess& theException) { aborted = true; } cmpStatusHandlerTmp = 0; if (aborted) { //disable the sync button enableSynchronization(false); m_buttonCompare->SetFocus(); } else { //once compare is finished enable the sync button enableSynchronization(true); m_buttonSync->SetFocus(); //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(); } //refresh grid in ANY case! (also on abort) writeGrid(currentGridData); } void MainDialog::OnAbortCompare(wxCommandEvent& event) { if (cmpStatusHandlerTmp) cmpStatusHandlerTmp->requestAbortion(); } void MainDialog::writeGrid(const FileCompareResult& gridData) { m_gridLeft->BeginBatch(); m_gridMiddle->BeginBatch(); m_gridRight->BeginBatch(); mapGridDataToUI(gridRefUI, gridData); //update gridRefUI updateStatusInformation(gridRefUI); //write status information for gridRefUI //all three grids retrieve their data directly via gridRefUI!!! //the only thing left to do is notify the grids to update 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 int nrOfRows = m_gridLeft->GetNumberRows(); if (nrOfRows >= 1) { int nrOfDigits = int(floor(log10(double(nrOfRows)) + 1)); m_gridLeft->SetRowLabelSize(nrOfDigits * 8 + 4); m_gridRight->SetRowLabelSize(nrOfDigits * 8 + 4); } m_gridLeft->EndBatch(); m_gridMiddle->EndBatch(); m_gridRight->EndBatch(); } void MainDialog::OnSync(wxCommandEvent& event) { 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 if (!synchronizationNeeded(currentGridData, cfg.syncConfiguration)) { wxMessageBox(_("Nothing to synchronize according to configuration!"), _("Information"), wxICON_WARNING); return; } wxBusyCursor dummy; //show hourglass cursor clearStatusBar(); try { //class handling status updates and error messages SyncStatusHandler statusHandler(this, ignoreErrors); //start synchronization and return elements that were not sync'ed in currentGridData FreeFileSync::SyncProcess synchronization( cfg.useRecycleBin, globalSettings.shared.warningSignificantDifference, &statusHandler); synchronization.startSynchronizationProcess(currentGridData, cfg.syncConfiguration); } catch (AbortThisProcess& theException) { //do NOT disable the sync button: user might want to try to sync the REMAINING rows } //enableSynchronization(false); //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 { pushStatusInformation(_("All items have been synchronized!")); enableSynchronization(false); } } } void MainDialog::OnLeftGridDoubleClick(wxGridEvent& event) { openWithFileManager(event.GetRow(), m_gridLeft); event.Skip(); } void MainDialog::OnRightGridDoubleClick(wxGridEvent& event) { openWithFileManager(event.GetRow(), m_gridRight); 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 sortAscending = true; if (lastSortColumn != currentSortColumn || lastSortGrid != m_gridLeft) sortAscending = true; else sortAscending = !sortAscending; lastSortColumn = currentSortColumn; lastSortGrid = m_gridLeft; //start sort xmlAccess::ColumnTypes columnType = m_gridLeft->getTypeAtPos(currentSortColumn); if (columnType == xmlAccess::FULL_NAME) { if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName); //sort by rel name here too! else std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName); } else if (columnType == xmlAccess::FILENAME) { if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByFileName); else std::sort(currentGridData.begin(), currentGridData.end(), sortByFileName); } else if (columnType == xmlAccess::REL_PATH) { if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName); else std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName); } else if (columnType == xmlAccess::SIZE) { if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByFileSize); else std::sort(currentGridData.begin(), currentGridData.end(), sortByFileSize); } else if (columnType == xmlAccess::DATE) { if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByDate); else std::sort(currentGridData.begin(), currentGridData.end(), sortByDate); } else assert(false); writeGrid(currentGridData); //needed to refresh gridRefUI references //set sort direction indicator on UI m_gridMiddle->setSortMarker(-1); m_gridRight->setSortMarker(-1); if (sortAscending) m_gridLeft->setSortMarker(currentSortColumn, globalResource.bitmapSmallUp); else m_gridLeft->setSortMarker(currentSortColumn, globalResource.bitmapSmallDown); } event.Skip(); } 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 sortAscending = true; if (lastSortColumn != currentSortColumn || lastSortGrid != m_gridRight) sortAscending = true; else sortAscending = !sortAscending; lastSortColumn = currentSortColumn; lastSortGrid = m_gridRight; //start sort xmlAccess::ColumnTypes columnType = m_gridRight->getTypeAtPos(currentSortColumn); if (columnType == xmlAccess::FULL_NAME) { if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName); //sort by rel name here too! else std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName); } else if (columnType == xmlAccess::FILENAME) { if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByFileName); else std::sort(currentGridData.begin(), currentGridData.end(), sortByFileName); } else if (columnType == xmlAccess::REL_PATH) { if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName); else std::sort(currentGridData.begin(), currentGridData.end(), sortByRelativeName); } else if (columnType == xmlAccess::SIZE) { if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByFileSize); else std::sort(currentGridData.begin(), currentGridData.end(), sortByFileSize); } else if (columnType == xmlAccess::DATE) { if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByDate); else std::sort(currentGridData.begin(), currentGridData.end(), sortByDate); } else assert(false); writeGrid(currentGridData); //needed to refresh gridRefUI references //set sort direction indicator on UI m_gridLeft->setSortMarker(-1); m_gridMiddle->setSortMarker(-1); if (sortAscending) m_gridRight->setSortMarker(currentSortColumn, globalResource.bitmapSmallUp); else m_gridRight->setSortMarker(currentSortColumn, globalResource.bitmapSmallDown); } event.Skip(); } void MainDialog::OnSortMiddleGrid(wxGridEvent& event) { //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; //start sort if (sortAscending) std::sort(currentGridData.begin(), currentGridData.end(), sortByCmpResult); else std::sort(currentGridData.begin(), currentGridData.end(), sortByCmpResult); writeGrid(currentGridData); //needed to refresh gridRefUI references //set sort direction indicator on UI m_gridLeft->setSortMarker(-1); m_gridRight->setSortMarker(-1); if (sortAscending) m_gridMiddle->setSortMarker(0, globalResource.bitmapSmallUp); else m_gridMiddle->setSortMarker(0, globalResource.bitmapSmallDown); event.Skip(); } void MainDialog::OnSwapDirs( wxCommandEvent& event ) { //swap directory names : main pair wxString tmp = m_directoryLeft->GetValue(); m_directoryLeft->SetValue(m_directoryRight->GetValue()); m_directoryRight->SetValue(tmp); //additional pairs for (std::vector::const_iterator i = additionalFolderPairs.begin(); i != additionalFolderPairs.end(); ++i) { FolderPairGenerated* dirPair = *i; tmp = dirPair->m_directoryLeft->GetValue(); dirPair->m_directoryLeft->SetValue(dirPair->m_directoryRight->GetValue()); 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); event.Skip(); } void MainDialog::updateStatusInformation(const GridView& visibleGrid) { while (stackObjects.size() > 0) stackObjects.pop(); unsigned int filesOnLeftView = 0; unsigned int foldersOnLeftView = 0; unsigned int filesOnRightView = 0; unsigned int foldersOnRightView = 0; wxULongLong filesizeLeftView; wxULongLong filesizeRightView; wxString statusLeftNew; wxString statusMiddleNew; wxString statusRightNew; for (GridView::const_iterator i = visibleGrid.begin(); i != visibleGrid.end(); ++i) { const FileCompareLine& refLine = currentGridData[*i]; //calculate total number of bytes for each side if (refLine.fileDescrLeft.objType == FileDescrLine::TYPE_FILE) { filesizeLeftView+= refLine.fileDescrLeft.fileSize; ++filesOnLeftView; } else if (refLine.fileDescrLeft.objType == FileDescrLine::TYPE_DIRECTORY) ++foldersOnLeftView; if (refLine.fileDescrRight.objType == FileDescrLine::TYPE_FILE) { filesizeRightView+= refLine.fileDescrRight.fileSize; ++filesOnRightView; } else if (refLine.fileDescrRight.objType == FileDescrLine::TYPE_DIRECTORY) ++foldersOnRightView; } //################################################# //format numbers to text: //show status information on "root" level. if (foldersOnLeftView) { if (foldersOnLeftView == 1) statusLeftNew+= _("1 directory"); else { wxString folderCount = globalFunctions::numberToWxString(foldersOnLeftView); globalFunctions::includeNumberSeparator(folderCount); 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 = globalFunctions::numberToWxString(filesOnLeftView); globalFunctions::includeNumberSeparator(fileCount); wxString outputString = _("%x files,"); outputString.Replace(wxT("%x"), fileCount, false); statusLeftNew+= outputString; } statusLeftNew+= wxT(" "); statusLeftNew+= FreeFileSync::formatFilesizeToShortString(filesizeLeftView); } wxString objectsView = globalFunctions::numberToWxString(visibleGrid.size()); globalFunctions::includeNumberSeparator(objectsView); if (currentGridData.size() == 1) { wxString outputString = _("%x of 1 row in view"); outputString.Replace(wxT("%x"), objectsView, false); statusMiddleNew = outputString; } else { wxString objectsTotal = globalFunctions::numberToWxString(currentGridData.size()); globalFunctions::includeNumberSeparator(objectsTotal); 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 = globalFunctions::numberToWxString(foldersOnRightView); globalFunctions::includeNumberSeparator(folderCount); 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 = globalFunctions::numberToWxString(filesOnRightView); globalFunctions::includeNumberSeparator(fileCount); 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_panel7->Layout(); } void MainDialog::mapGridDataToUI(GridView& output, const FileCompareResult& fileCmpResult) { output.clear(); //only show those view filter buttons that really need to be displayed bool leftOnly, rightOnly, leftNewer, rightNewer, different, equal; leftOnly = rightOnly = leftNewer = rightNewer = different = equal = false; unsigned int currentRow = 0; for (FileCompareResult::const_iterator i = fileCmpResult.begin(); i != fileCmpResult.end(); ++i, ++currentRow) { //hide filtered row, if corresponding option is set if (hideFilteredElements && !i->selectedForSynchronization) continue; //process UI filter settings switch (i->cmpResult) { case FILE_LEFT_SIDE_ONLY: leftOnly = true; if (!leftOnlyFilesActive) continue; break; case FILE_RIGHT_SIDE_ONLY: rightOnly = true; if (!rightOnlyFilesActive) continue; break; case FILE_LEFT_NEWER: leftNewer = true; if (!leftNewerFilesActive) continue; break; case FILE_RIGHT_NEWER: rightNewer = true; if (!rightNewerFilesActive) continue; break; case FILE_DIFFERENT: different = true; if (!differentFilesActive) continue; break; case FILE_EQUAL: equal = true; if (!equalFilesActive) continue; break; default: assert (false); } output.push_back(currentRow); } //hide or enable view filter buttons if (leftOnly) m_bpButtonLeftOnly->Show(); else m_bpButtonLeftOnly->Hide(); if (rightOnly) m_bpButtonRightOnly->Show(); else m_bpButtonRightOnly->Hide(); if (leftNewer) m_bpButtonLeftNewer->Show(); else m_bpButtonLeftNewer->Hide(); if (rightNewer) m_bpButtonRightNewer->Show(); else m_bpButtonRightNewer->Hide(); if (different) m_bpButtonDifferent->Show(); else m_bpButtonDifferent->Hide(); if (equal) m_bpButtonEqual->Show(); else m_bpButtonEqual->Hide(); if (leftOnly || rightOnly || leftNewer || rightNewer || different || equal) { m_panel112->Show(); m_panel112->Layout(); } else m_panel112->Hide(); bSizer3->Layout(); } void MainDialog::OnAddFolderPair(wxCommandEvent& event) { addFolderPair(wxEmptyString, wxEmptyString); event.Skip(); } void MainDialog::OnRemoveFolderPair(wxCommandEvent& event) { removeFolderPair(); event.Skip(); } void MainDialog::addFolderPair(const wxString& leftDir, const wxString& rightDir) { //add new folder pair FolderPairGenerated* newPair = new FolderPairGenerated(m_scrolledWindowFolderPairs); newPair->m_bitmap23->SetBitmap(*globalResource.bitmapLink); bSizerFolderPairs->Add(newPair, 0, wxEXPAND, 5); additionalFolderPairs.push_back(newPair); //set size of scrolled window wxSize pairSize = newPair->GetSize(); const int additionalRows = additionalFolderPairs.size(); if (additionalRows <= 3) //up to 3 additional pairs shall be shown m_scrolledWindowFolderPairs->SetMinSize(wxSize( -1, pairSize.GetHeight() * additionalRows)); else //adjust scrollbars m_scrolledWindowFolderPairs->Fit(); //adjust remove button if (additionalRows > 0) m_bpButtonRemovePair->Enable(); m_scrolledWindowFolderPairs->Layout(); bSizer1->Layout(); m_bpButtonSwap->Refresh(); //register events newPair->m_dirPickerLeft->Connect(wxEVT_COMMAND_DIRPICKER_CHANGED, wxFileDirPickerEventHandler(MainDialog::OnDirSelected), NULL, this); newPair->m_dirPickerRight->Connect(wxEVT_COMMAND_DIRPICKER_CHANGED, wxFileDirPickerEventHandler(MainDialog::OnDirSelected), NULL, this); newPair->m_directoryLeft->Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(MainDialog::OnWriteDirManually), NULL, this ); newPair->m_directoryRight->Connect(wxEVT_COMMAND_TEXT_UPDATED, wxCommandEventHandler(MainDialog::OnWriteDirManually), NULL, this ); //prepare drag & drop 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); wxString leftDirFormatted = FreeFileSync::getFormattedDirectoryName(leftDir.c_str()).c_str(); if (wxDirExists(leftDirFormatted)) newPair->m_dirPickerLeft->SetPath(leftDirFormatted); newPair->m_directoryRight->SetValue(rightDir); wxString rightDirFormatted = FreeFileSync::getFormattedDirectoryName(rightDir.c_str()).c_str(); if (wxDirExists(rightDirFormatted)) newPair->m_dirPickerRight->SetPath(rightDirFormatted); } void MainDialog::removeFolderPair(bool removeAll) { if (additionalFolderPairs.size() > 0) { wxSize pairSize; do { //remove folder pairs from window FolderPairGenerated* pairToDelete = additionalFolderPairs.back(); pairSize = pairToDelete->GetSize(); bSizerFolderPairs->Detach(pairToDelete); //Remove() does not work on Window*, so do it manually pairToDelete->Destroy(); // additionalFolderPairs.pop_back(); //remove last element in vector } while (removeAll && additionalFolderPairs.size() > 0); //set size of scrolled window const int additionalRows = additionalFolderPairs.size(); if (additionalRows <= 3) //up to 3 additional pairs shall be shown m_scrolledWindowFolderPairs->SetMinSize(wxSize(-1, pairSize.GetHeight() * additionalRows)); //adjust scrollbars (do not put in else clause) m_scrolledWindowFolderPairs->Fit(); //adjust remove button if (additionalRows <= 0) m_bpButtonRemovePair->Disable(); m_scrolledWindowFolderPairs->Layout(); bSizer1->Layout(); } } //######################################################################################################## CompareStatusHandler::CompareStatusHandler(MainDialog* dlg) : mainDialog(dlg), ignoreErrors(false), currentProcess(StatusHandler::PROCESS_NONE) { //prevent user input during "compare", do not disable maindialog since abort-button would also be disabled //it's not nice, but works - even has the advantage that certain actions are still possible: exit, about.. mainDialog->m_radioBtnSizeDate->Disable(); mainDialog->m_radioBtnContent->Disable(); mainDialog->m_bpButtonFilter->Disable(); mainDialog->m_hyperlinkCfgFilter->Disable(); mainDialog->m_checkBoxHideFilt->Disable(); mainDialog->m_buttonSync->Disable(); mainDialog->m_dirPickerLeft->Disable(); mainDialog->m_dirPickerRight->Disable(); mainDialog->m_bpButtonSwap->Disable(); mainDialog->m_bpButtonLeftOnly->Disable(); mainDialog->m_bpButtonLeftNewer->Disable(); mainDialog->m_bpButtonEqual->Disable(); mainDialog->m_bpButtonDifferent->Disable(); mainDialog->m_bpButtonRightNewer->Disable(); mainDialog->m_bpButtonRightOnly->Disable(); mainDialog->m_panel1->Disable(); mainDialog->m_panel2->Disable(); mainDialog->m_panel3->Disable(); mainDialog->m_panel11->Disable(); mainDialog->m_panel12->Disable(); mainDialog->m_panel13->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(); mainDialog->m_menubar1->EnableTop(0, false); mainDialog->m_menubar1->EnableTop(1, false); mainDialog->m_menubar1->EnableTop(2, false); //display status panel during compare mainDialog->compareStatus->init(); //clear old values mainDialog->compareStatus->Show(); //show abort button mainDialog->m_buttonAbort->Enable(); mainDialog->m_buttonAbort->Show(); mainDialog->m_buttonCompare->Disable(); mainDialog->m_buttonCompare->Hide(); mainDialog->m_buttonAbort->SetFocus(); //updateUiNow(); mainDialog->bSizer1->Layout(); //both sizers need to recalculate! mainDialog->bSizer6->Layout(); mainDialog->Refresh(); } CompareStatusHandler::~CompareStatusHandler() { updateUiNow(); //ui update before enabling buttons again: prevent strange behaviour of delayed button clicks //reenable complete main dialog mainDialog->m_radioBtnSizeDate->Enable(); mainDialog->m_radioBtnContent->Enable(); mainDialog->m_bpButtonFilter->Enable(); mainDialog->m_hyperlinkCfgFilter->Enable(); mainDialog->m_checkBoxHideFilt->Enable(); mainDialog->m_buttonSync->Enable(); mainDialog->m_dirPickerLeft->Enable(); mainDialog->m_dirPickerRight->Enable(); mainDialog->m_bpButtonSwap->Enable(); mainDialog->m_bpButtonLeftOnly->Enable(); mainDialog->m_bpButtonLeftNewer->Enable(); mainDialog->m_bpButtonEqual->Enable(); mainDialog->m_bpButtonDifferent->Enable(); mainDialog->m_bpButtonRightNewer->Enable(); mainDialog->m_bpButtonRightOnly->Enable(); mainDialog->m_panel1->Enable(); mainDialog->m_panel2->Enable(); mainDialog->m_panel3->Enable(); mainDialog->m_panel11->Enable(); mainDialog->m_panel12->Enable(); mainDialog->m_panel13->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(); mainDialog->m_menubar1->EnableTop(0, true); mainDialog->m_menubar1->EnableTop(1, true); mainDialog->m_menubar1->EnableTop(2, true); if (abortRequested) mainDialog->pushStatusInformation(_("Operation aborted!")); mainDialog->m_buttonAbort->Disable(); mainDialog->m_buttonAbort->Hide(); mainDialog->m_buttonCompare->Enable(); //enable compare button mainDialog->m_buttonCompare->Show(); //hide status panel from main window mainDialog->compareStatus->Hide(); mainDialog->Layout(); mainDialog->Refresh(); } inline void CompareStatusHandler::updateStatusText(const Zstring& text) { mainDialog->compareStatus->setStatusText_NoUpdate(text); } void CompareStatusHandler::initNewProcess(int objectsTotal, double dataTotal, Process processID) { currentProcess = processID; if (currentProcess == StatusHandler::PROCESS_SCANNING) ; else if (currentProcess == StatusHandler::PROCESS_COMPARING_CONTENT) { mainDialog->compareStatus->switchToCompareBytewise(objectsTotal, dataTotal); mainDialog->Layout(); } else assert(false); } inline void CompareStatusHandler::updateProcessedData(int objectsProcessed, double dataProcessed) { if (currentProcess == StatusHandler::PROCESS_SCANNING) mainDialog->compareStatus->incScannedObjects_NoUpdate(objectsProcessed); else if (currentProcess == StatusHandler::PROCESS_COMPARING_CONTENT) mainDialog->compareStatus->incProcessedCmpData_NoUpdate(objectsProcessed, dataProcessed); else assert(false); } ErrorHandler::Response CompareStatusHandler::reportError(const Zstring& text) { if (ignoreErrors) return ErrorHandler::IGNORE_ERROR; mainDialog->compareStatus->updateStatusPanelNow(); bool ignoreNextErrors = false; wxString errorMessage = wxString(text.c_str()) + wxT("\n\n") + _("Ignore this error, retry or abort?"); 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: 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() { mainDialog->compareStatus->updateStatusPanelNow(); } void CompareStatusHandler::abortThisProcess() { abortRequested = true; throw AbortThisProcess(); //abort can be triggered by syncStatusFrame } //######################################################################################################## SyncStatusHandler::SyncStatusHandler(wxWindow* dlg, bool ignoreAllErrors) : ignoreErrors(ignoreAllErrors) { syncStatusFrame = new SyncStatus(this, dlg); syncStatusFrame->Show(); updateUiNow(); } SyncStatusHandler::~SyncStatusHandler() { //print the results list unsigned int failedItems = unhandledErrors.GetCount(); wxString result; if (failedItems) { result = wxString(_("Warning: Synchronization failed for %x item(s):")) + wxT("\n\n"); result.Replace(wxT("%x"), globalFunctions::numberToWxString(failedItems), false); for (unsigned int j = 0; j < failedItems; ++j) { //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"); } //notify to syncStatusFrame that current process has ended if (abortRequested) { result+= wxString(_("Synchronization aborted!")) + wxT(" ") + _("You may try to synchronize remaining items again (WITHOUT having to re-compare)!"); syncStatusFrame->setStatusText_NoUpdate(result.c_str()); syncStatusFrame->processHasFinished(SyncStatus::ABORTED); //enable okay and close events } else if (failedItems) { result+= wxString(_("Synchronization completed with errors!")) + wxT(" ") + _("You may try to synchronize remaining items again (WITHOUT having to re-compare)!"); syncStatusFrame->setStatusText_NoUpdate(result.c_str()); syncStatusFrame->processHasFinished(SyncStatus::FINISHED_WITH_ERROR); } else { result+= _("Synchronization completed successfully!"); syncStatusFrame->setStatusText_NoUpdate(result.c_str()); syncStatusFrame->processHasFinished(SyncStatus::FINISHED_WITH_SUCCESS); } } inline void SyncStatusHandler::updateStatusText(const Zstring& text) { syncStatusFrame->setStatusText_NoUpdate(text); } void SyncStatusHandler::initNewProcess(int objectsTotal, double dataTotal, Process processID) { assert (processID == StatusHandler::PROCESS_SYNCHRONIZING); syncStatusFrame->resetGauge(objectsTotal, dataTotal); syncStatusFrame->setCurrentStatus(SyncStatus::SYNCHRONIZING); } inline void SyncStatusHandler::updateProcessedData(int objectsProcessed, double dataProcessed) { syncStatusFrame->incProgressIndicator_NoUpdate(objectsProcessed, dataProcessed); } 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(errorWithTime); return ErrorHandler::IGNORE_ERROR; } syncStatusFrame->updateStatusDialogNow(); bool ignoreNextErrors = false; 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(errorWithTime); return ErrorHandler::IGNORE_ERROR; case ErrorDlg::BUTTON_RETRY: return ErrorHandler::RETRY; case ErrorDlg::BUTTON_ABORT: unhandledErrors.Add(errorWithTime); abortThisProcess(); } 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); } void SyncStatusHandler::forceUiRefresh() { syncStatusFrame->updateStatusDialogNow(); } void SyncStatusHandler::abortThisProcess() { abortRequested = true; throw AbortThisProcess(); //abort can be triggered by syncStatusFrame } //######################################################################################################## //menu events void MainDialog::OnMenuGlobalSettings(wxCommandEvent& event) { wxDialog* settingsDlg = new GlobalSettingsDlg(this, globalSettings); settingsDlg->ShowModal(); event.Skip(); } void MainDialog::OnMenuExportFileList(wxCommandEvent& event) { //get a filename wxString fileName = wxT("FileList.csv"); //proposal wxFileDialog* filePicker = new wxFileDialog(this, wxEmptyString, wxEmptyString, fileName, wxString(_("Comma separated list")) + wxT(" (*.csv)|*.csv"), wxFD_SAVE); if (filePicker->ShowModal() == wxID_OK) { fileName = filePicker->GetPath(); if (wxFileExists(fileName)) { wxMessageDialog* messageDlg = new wxMessageDialog(this, wxString(_("File already exists. Overwrite?")) + wxT(" \"") + fileName + wxT("\""), _("Warning") , wxOK | wxCANCEL); if (messageDlg->ShowModal() != wxID_OK) { pushStatusInformation(_("Save aborted!")); return; } } //begin work wxString exportString; for (unsigned int i = 0; i < gridRefUI.size(); ++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(fileName.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(" \"") + fileName + wxT("\""), _("Error"), wxOK | wxICON_ERROR); } } } void MainDialog::OnMenuBatchJob(wxCommandEvent& event) { //fill batch config structure xmlAccess::XmlBatchConfig batchCfg; batchCfg.mainCfg = cfg; batchCfg.directoryPairs = getFolderPairs(); batchCfg.silent = false; if (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::OnMenuAbout(wxCommandEvent& event) { AboutDlg* aboutDlg = new AboutDlg(this); aboutDlg->ShowModal(); event.Skip(); } void MainDialog::OnMenuQuit(wxCommandEvent& event) { Destroy(); event.Skip(); } //######################################################################################################### //language selection void MainDialog::switchProgramLanguage(const int langID) { programLanguage->setLanguage(langID); //language is a global attribute //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) { switchProgramLanguage(wxLANGUAGE_CHINESE_SIMPLIFIED); } void MainDialog::OnMenuLangDutch(wxCommandEvent& event) { switchProgramLanguage(wxLANGUAGE_DUTCH); } void MainDialog::OnMenuLangEnglish(wxCommandEvent& event) { switchProgramLanguage(wxLANGUAGE_ENGLISH); } void MainDialog::OnMenuLangFrench(wxCommandEvent& event) { switchProgramLanguage(wxLANGUAGE_FRENCH); } void MainDialog::OnMenuLangGerman(wxCommandEvent& event) { switchProgramLanguage(wxLANGUAGE_GERMAN); } void MainDialog::OnMenuLangItalian(wxCommandEvent& event) { switchProgramLanguage(wxLANGUAGE_ITALIAN); } void MainDialog::OnMenuLangJapanese(wxCommandEvent& event) { switchProgramLanguage(wxLANGUAGE_JAPANESE); } void MainDialog::OnMenuLangPolish(wxCommandEvent& event) { switchProgramLanguage(wxLANGUAGE_POLISH); } void MainDialog::OnMenuLangPortuguese(wxCommandEvent& event) { switchProgramLanguage(wxLANGUAGE_PORTUGUESE); }