diff options
Diffstat (limited to 'library')
-rw-r--r-- | library/CustomGrid.cpp | 1006 | ||||
-rw-r--r-- | library/CustomGrid.h | 153 | ||||
-rw-r--r-- | library/FreeFileSync.xpm | 2 | ||||
-rw-r--r-- | library/fileHandling.cpp | 94 | ||||
-rw-r--r-- | library/fileHandling.h | 5 | ||||
-rw-r--r-- | library/filter.cpp | 241 | ||||
-rw-r--r-- | library/filter.h | 15 | ||||
-rw-r--r-- | library/globalFunctions.cpp | 18 | ||||
-rw-r--r-- | library/globalFunctions.h | 43 | ||||
-rw-r--r-- | library/localization.cpp (renamed from library/misc.cpp) | 53 | ||||
-rw-r--r-- | library/localization.h | 32 | ||||
-rw-r--r-- | library/misc.h | 54 | ||||
-rw-r--r-- | library/multithreading.cpp | 1 | ||||
-rw-r--r-- | library/multithreading.h | 2 | ||||
-rw-r--r-- | library/processXml.cpp | 113 | ||||
-rw-r--r-- | library/processXml.h | 62 | ||||
-rw-r--r-- | library/resources.cpp | 24 | ||||
-rw-r--r-- | library/resources.h | 15 | ||||
-rw-r--r-- | library/sorting.h | 325 | ||||
-rw-r--r-- | library/statistics.cpp | 320 | ||||
-rw-r--r-- | library/statistics.h | 67 | ||||
-rw-r--r-- | library/statusHandler.h | 8 | ||||
-rw-r--r-- | library/zstring.cpp | 28 | ||||
-rw-r--r-- | library/zstring.h | 84 |
24 files changed, 1668 insertions, 1097 deletions
diff --git a/library/CustomGrid.cpp b/library/CustomGrid.cpp index b8737343..faa75b2d 100644 --- a/library/CustomGrid.cpp +++ b/library/CustomGrid.cpp @@ -5,39 +5,58 @@ #include "../algorithm.h" #include "resources.h" #include <typeinfo> +#include "../ui/gridView.h" #ifdef FFS_WIN #include <wx/icon.h> #include <wx/msw/wrapwin.h> //includes "windows.h" -#endif // FFS_WIN + +#elif defined FFS_LINUX +#include <gtk/gtk.h> +#endif + + +using namespace FreeFileSync; const unsigned int MIN_ROW_COUNT = 15; //class containing pure grid data: basically the same as wxGridStringTable, but adds cell formatting + +/* +class hierarchy: + CustomGridTable + /|\ + ________________|________________ + | | + CustomGridTableRim | + /|\ | + __________|__________ | + | | | +CustomGridTableLeft CustomGridTableRight CustomGridTableMiddle +*/ + class CustomGridTable : public wxGridTableBase { public: - CustomGridTable() : + CustomGridTable(int initialRows = 0, int initialCols = 0) : //note: initialRows/initialCols MUST match with GetNumberRows()/GetNumberCols() at initialization!!! wxGridTableBase(), - blue(80, 110, 255), - grey(212, 208, 200), - lightRed(235, 57, 61), - lightBlue(63, 206, 233), - lightGreen(54, 218, 2), - gridRefUI(NULL), - gridData(NULL), - lastNrRows(0), - lastNrCols(0) {} + COLOR_BLUE( 80, 110, 255), + COLOR_GREY( 212, 208, 200), + COLOR_LIGHT_RED( 235, 57, 61), + COLOR_LIGHT_BLUE( 63, 206, 233), + COLOR_LIGHT_GREEN(54, 218, 2), + gridDataView(NULL), + lastNrRows(initialRows), + lastNrCols(initialCols) {} virtual ~CustomGridTable() {} - void setGridDataTable(GridView* gridRefUI, FileCompareResult* gridData) + void setGridDataTable(GridView* gridDataView) { - this->gridRefUI = gridRefUI; - this->gridData = gridData; + this->gridDataView = gridDataView; } @@ -46,57 +65,27 @@ public: virtual int GetNumberRows() { - if (gridRefUI) - return std::max(gridRefUI->size(), MIN_ROW_COUNT); + if (gridDataView) + return std::max(gridDataView->elementsOnView(), MIN_ROW_COUNT); else return 0; //grid is initialized with zero number of rows } - virtual int GetNumberCols() //virtual used by middle grid! - { - return columnPositions.size(); - } - - virtual bool IsEmptyCell( int row, int col ) { - return (GetValue(row, col) == wxEmptyString); - } - - - virtual wxString GetValue(int row, int col) = 0; - - - virtual void SetValue(int row, int col, const wxString& value) - { - assert (false); //should not be used, since values are retrieved directly from gridRefUI - } - - virtual void Clear() - { - assert (false); // we don't want to use this, since the visible grid is directly connected to gridRefUI} - } + return false; //avoid overlapping cells - virtual bool InsertRows(size_t pos = 0, size_t numRows = 1) - { - assert (false); // we don't want to use this, since the visible grid is directly connected to gridRefUI} - return true; + //return (GetValue(row, col) == wxEmptyString); } - virtual bool AppendRows(size_t numRows = 1) - { - assert (false); // we don't want to use this, since the visible grid is directly connected to gridRefUI} - return true; - } - virtual bool DeleteRows(size_t pos = 0, size_t numRows = 1) + virtual void SetValue(int row, int col, const wxString& value) { - assert (false); // we don't want to use this, since the visible grid is directly connected to gridRefUI} - return true; + assert (false); //should not be used, since values are retrieved directly from gridDataView } - //update dimensions of grid: no need for InsertRows, AppendRows, DeleteRows anymore!!! + //update dimensions of grid: no need for InsertRows(), AppendRows(), DeleteRows() anymore!!! void updateGridSizes() { const int currentNrRows = GetNumberRows(); @@ -156,12 +145,6 @@ public: //########################################################################### - virtual wxString GetColLabelValue( int col ) - { - return CustomGrid::getTypeName(getTypeAtPos(col)); - } - - virtual wxGridCellAttr* GetAttr(int row, int col, wxGridCellAttr::wxAttrKind kind) { const wxColour& color = getRowColor(row); @@ -190,6 +173,46 @@ public: } + const FileCompareLine* getRawData(const unsigned int row) const + { + if (gridDataView && row < gridDataView->elementsOnView()) + { + return &(*gridDataView)[row]; + } + return NULL; + } + +protected: + const wxColour COLOR_BLUE; + const wxColour COLOR_GREY; + const wxColour COLOR_LIGHT_RED; + const wxColour COLOR_LIGHT_BLUE; + const wxColour COLOR_LIGHT_GREEN; + + const GridView* gridDataView; //(very fast) access to underlying grid data :) + +private: + virtual const wxColour& getRowColor(int row) = 0; //rows that are filtered out are shown in different color + + int lastNrRows; + int lastNrCols; +}; + + +class CustomGridTableRim : public CustomGridTable +{ +public: + virtual int GetNumberCols() + { + return columnPositions.size(); + } + + virtual wxString GetColLabelValue( int col ) + { + return CustomGridRim::getTypeName(getTypeAtPos(col)); + } + + void setupColumns(const std::vector<xmlAccess::ColumnTypes>& positions) { columnPositions = positions; @@ -206,269 +229,225 @@ public: } - const FileCompareLine* getRawData(const unsigned int row) - { - if (gridRefUI && row < gridRefUI->size()) - { - const FileCompareLine& cmpLine = (*gridData)[(*gridRefUI)[row]]; - return &cmpLine; - } - return NULL; - } - - -protected: - virtual const wxColour& getRowColor(int row) = 0; //rows that are filtered out are shown in different color - +private: std::vector<xmlAccess::ColumnTypes> columnPositions; - - wxColour blue; - wxColour grey; - wxColour lightRed; - wxColour lightBlue; - wxColour lightGreen; - GridView* gridRefUI; //(very fast) access to underlying grid data :) - FileCompareResult* gridData; - int lastNrRows; - int lastNrCols; }; -class CustomGridTableLeft : public CustomGridTable +class CustomGridTableLeft : public CustomGridTableRim { public: - CustomGridTableLeft() : CustomGridTable() {} - ~CustomGridTableLeft() {} + virtual wxString GetValue(int row, int col) + { + const FileCompareLine* gridLine = getRawData(row); + if (gridLine) + { + if (gridLine->fileDescrLeft.objType == FileDescrLine::TYPE_DIRECTORY) + { + switch (getTypeAtPos(col)) + { + case xmlAccess::FULL_NAME: + return gridLine->fileDescrLeft.fullName.c_str(); + case xmlAccess::FILENAME: + return wxEmptyString; + case xmlAccess::REL_PATH: + return gridLine->fileDescrLeft.relativeName.c_str(); + case xmlAccess::DIRECTORY: + return gridDataView->getFolderPair(row).leftDirectory.c_str(); + case xmlAccess::SIZE: //file size + return _("<Directory>"); + case xmlAccess::DATE: //date + return wxEmptyString; + } + } + else if (gridLine->fileDescrLeft.objType == FileDescrLine::TYPE_FILE) + { + switch (getTypeAtPos(col)) + { + case xmlAccess::FULL_NAME: + return gridLine->fileDescrLeft.fullName.c_str(); + case xmlAccess::FILENAME: //filename + return wxString(gridLine->fileDescrLeft.relativeName.c_str()).AfterLast(GlobalResources::FILE_NAME_SEPARATOR); + case xmlAccess::REL_PATH: //relative path + return wxString(gridLine->fileDescrLeft.relativeName.c_str()).BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + case xmlAccess::DIRECTORY: + return gridDataView->getFolderPair(row).leftDirectory.c_str(); + case xmlAccess::SIZE: //file size + return globalFunctions::includeNumberSeparator(gridLine->fileDescrLeft.fileSize.ToString()); + case xmlAccess::DATE: //date + return FreeFileSync::utcTimeToLocalString(gridLine->fileDescrLeft.lastWriteTimeRaw); + } + } + } + //if data is not found: + return wxEmptyString; + } +private: virtual const wxColour& getRowColor(int row) //rows that are filtered out are shown in different color { - if (gridRefUI && unsigned(row) < gridRefUI->size()) + const FileCompareLine* gridLine = getRawData(row); + if (gridLine) { - const FileCompareLine cmpLine = (*gridData)[(*gridRefUI)[row]]; - //mark filtered rows - if (!cmpLine.selectedForSynchronization) - return blue; + if (!gridLine->selectedForSynchronization) + return COLOR_BLUE; //mark directories - else if (cmpLine.fileDescrLeft.objType == FileDescrLine::TYPE_DIRECTORY) - return grey; + else if (gridLine->fileDescrLeft.objType == FileDescrLine::TYPE_DIRECTORY) + return COLOR_GREY; else return *wxWHITE; } return *wxWHITE; } +}; - //virtual impl. - wxString GetValue(int row, int col) +class CustomGridTableRight : public CustomGridTableRim +{ +public: + virtual wxString GetValue(int row, int col) { - if (gridRefUI) + const FileCompareLine* gridLine = getRawData(row); + if (gridLine) { - if (unsigned(row) < gridRefUI->size()) + if (gridLine->fileDescrRight.objType == FileDescrLine::TYPE_DIRECTORY) { - const FileCompareLine& gridLine = (*gridData)[(*gridRefUI)[row]]; - - if (gridLine.fileDescrLeft.objType == FileDescrLine::TYPE_DIRECTORY) + switch (getTypeAtPos(col)) { - switch (getTypeAtPos(col)) - { - case xmlAccess::FULL_NAME: - return gridLine.fileDescrLeft.fullName.c_str(); - case xmlAccess::FILENAME: //filename - return wxEmptyString; - case xmlAccess::REL_PATH: //relative path - return gridLine.fileDescrLeft.relativeName.c_str(); - case xmlAccess::SIZE: //file size - return _("<Directory>"); - case xmlAccess::DATE: //date - return wxEmptyString; - } + case xmlAccess::FULL_NAME: + return gridLine->fileDescrRight.fullName.c_str(); + case xmlAccess::FILENAME: //filename + return wxEmptyString; + case xmlAccess::REL_PATH: //relative path + return gridLine->fileDescrRight.relativeName.c_str(); + case xmlAccess::DIRECTORY: + return gridDataView->getFolderPair(row).rightDirectory.c_str(); + case xmlAccess::SIZE: //file size + return _("<Directory>"); + case xmlAccess::DATE: //date + return wxEmptyString; } - else if (gridLine.fileDescrLeft.objType == FileDescrLine::TYPE_FILE) + } + else if (gridLine->fileDescrRight.objType == FileDescrLine::TYPE_FILE) + { + switch (getTypeAtPos(col)) { - switch (getTypeAtPos(col)) - { - case xmlAccess::FULL_NAME: - return gridLine.fileDescrLeft.fullName.c_str(); - case xmlAccess::FILENAME: //filename - return wxString(gridLine.fileDescrLeft.relativeName.c_str()).AfterLast(GlobalResources::FILE_NAME_SEPARATOR); - case xmlAccess::REL_PATH: //relative path - return wxString(gridLine.fileDescrLeft.relativeName.c_str()).BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); - case xmlAccess::SIZE: //file size - { - wxString fileSize = gridLine.fileDescrLeft.fileSize.ToString(); //tmp string - return globalFunctions::includeNumberSeparator(fileSize); - } - case xmlAccess::DATE: //date - return FreeFileSync::utcTimeToLocalString(gridLine.fileDescrLeft.lastWriteTimeRaw); - } + case xmlAccess::FULL_NAME: + return gridLine->fileDescrRight.fullName.c_str(); + case xmlAccess::FILENAME: //filename + return wxString(gridLine->fileDescrRight.relativeName.c_str()).AfterLast(GlobalResources::FILE_NAME_SEPARATOR); + case xmlAccess::REL_PATH: //relative path + return wxString(gridLine->fileDescrRight.relativeName.c_str()).BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); + case xmlAccess::DIRECTORY: + return gridDataView->getFolderPair(row).rightDirectory.c_str(); + case xmlAccess::SIZE: //file size + return globalFunctions::includeNumberSeparator(gridLine->fileDescrRight.fileSize.ToString()); + case xmlAccess::DATE: //date + return FreeFileSync::utcTimeToLocalString(gridLine->fileDescrRight.lastWriteTimeRaw); } } } //if data is not found: return wxEmptyString; } + +private: + virtual const wxColour& getRowColor(int row) //rows that are filtered out are shown in different color + { + const FileCompareLine* gridLine = getRawData(row); + if (gridLine) + { + //mark filtered rows + if (!gridLine->selectedForSynchronization) + return COLOR_BLUE; + //mark directories + else if (gridLine->fileDescrRight.objType == FileDescrLine::TYPE_DIRECTORY) + return COLOR_GREY; + else + return *wxWHITE; + } + return *wxWHITE; + } }; class CustomGridTableMiddle : public CustomGridTable { public: - CustomGridTableMiddle() : CustomGridTable() - { - lastNrCols = 1; //ensure CustomGridTable::updateGridSizes() is working correctly - } +//middle grid is created (first wxWidgets internal call to GetNumberCols()) with one column + CustomGridTableMiddle() : CustomGridTable(0, GetNumberCols()) {} //attention: static binding to virtual GetNumberCols() in a Constructor! - ~CustomGridTableMiddle() {} - - //virtual impl. - int GetNumberCols() + virtual int GetNumberCols() { return 1; } - - virtual const wxColour& getRowColor(int row) //rows that are filtered out are shown in different color + virtual wxString GetColLabelValue( int col ) { - if (gridRefUI && unsigned(row) < gridRefUI->size()) - { - const FileCompareLine cmpLine = (*gridData)[(*gridRefUI)[row]]; - - //mark filtered rows - if (!cmpLine.selectedForSynchronization) - return blue; - else - switch (cmpLine.cmpResult) - { - case FILE_LEFT_SIDE_ONLY: - case FILE_RIGHT_SIDE_ONLY: - return lightGreen; - case FILE_LEFT_NEWER: - case FILE_RIGHT_NEWER: - return lightBlue; - case FILE_DIFFERENT: - return lightRed; - default: - return *wxWHITE; - } - } - return *wxWHITE; + return wxEmptyString; } virtual wxString GetValue(int row, int col) { - if (gridRefUI) + const FileCompareLine* gridLine = getRawData(row); + if (gridLine) { - if (unsigned(row) < gridRefUI->size()) + switch (gridLine->cmpResult) { - const FileCompareLine& gridLine = (*gridData)[(*gridRefUI)[row]]; - - switch (gridLine.cmpResult) - { - case FILE_LEFT_SIDE_ONLY: - return wxT("<|"); - case FILE_RIGHT_SIDE_ONLY: - return wxT("|>"); - case FILE_RIGHT_NEWER: - return wxT(">>"); - case FILE_LEFT_NEWER: - return wxT("<<"); - case FILE_DIFFERENT: - return wxT("!="); - case FILE_EQUAL: - return wxT("=="); - default: - assert (false); - return wxEmptyString; - } + case FILE_LEFT_SIDE_ONLY: + return wxT("<|"); + case FILE_RIGHT_SIDE_ONLY: + return wxT("|>"); + case FILE_RIGHT_NEWER: + return wxT(">>"); + case FILE_LEFT_NEWER: + return wxT("<<"); + case FILE_DIFFERENT: + return wxT("!="); + case FILE_EQUAL: + return wxT("=="); + default: + assert (false); + return wxEmptyString; } } //if data is not found: return wxEmptyString; } -}; - - -class CustomGridTableRight : public CustomGridTable -{ -public: - CustomGridTableRight() : CustomGridTable() {} - ~CustomGridTableRight() {} +private: virtual const wxColour& getRowColor(int row) //rows that are filtered out are shown in different color { - if (gridRefUI && unsigned(row) < gridRefUI->size()) + const FileCompareLine* gridLine = getRawData(row); + if (gridLine) { - const FileCompareLine cmpLine = (*gridData)[(*gridRefUI)[row]]; - //mark filtered rows - if (!cmpLine.selectedForSynchronization) - return blue; - //mark directories - else if (cmpLine.fileDescrRight.objType == FileDescrLine::TYPE_DIRECTORY) - return grey; + if (!gridLine->selectedForSynchronization) + return COLOR_BLUE; else - return *wxWHITE; - } - return *wxWHITE; - } - - //virtual impl. - wxString GetValue(int row, int col) - { - if (gridRefUI) - { - if (unsigned(row) < gridRefUI->size()) - { - const FileCompareLine& gridLine = (*gridData)[(*gridRefUI)[row]]; - - if (gridLine.fileDescrRight.objType == FileDescrLine::TYPE_DIRECTORY) - { - switch (getTypeAtPos(col)) - { - case xmlAccess::FULL_NAME: - return gridLine.fileDescrRight.fullName.c_str(); - case xmlAccess::FILENAME: //filename - return wxEmptyString; - case xmlAccess::REL_PATH: //relative path - return gridLine.fileDescrRight.relativeName.c_str(); - case xmlAccess::SIZE: //file size - return _("<Directory>"); - case xmlAccess::DATE: //date - return wxEmptyString; - } - } - else if (gridLine.fileDescrRight.objType == FileDescrLine::TYPE_FILE) + switch (gridLine->cmpResult) { - switch (getTypeAtPos(col)) - { - case xmlAccess::FULL_NAME: - return gridLine.fileDescrRight.fullName.c_str(); - case xmlAccess::FILENAME: //filename - return wxString(gridLine.fileDescrRight.relativeName.c_str()).AfterLast(GlobalResources::FILE_NAME_SEPARATOR); - case xmlAccess::REL_PATH: //relative path - return wxString(gridLine.fileDescrRight.relativeName.c_str()).BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); - case xmlAccess::SIZE: //file size - { - wxString fileSize = gridLine.fileDescrRight.fileSize.ToString(); //tmp string - return globalFunctions::includeNumberSeparator(fileSize); - } - case xmlAccess::DATE: //date - return FreeFileSync::utcTimeToLocalString(gridLine.fileDescrRight.lastWriteTimeRaw); - } + case FILE_LEFT_SIDE_ONLY: + case FILE_RIGHT_SIDE_ONLY: + return COLOR_LIGHT_GREEN; + case FILE_LEFT_NEWER: + case FILE_RIGHT_NEWER: + return COLOR_LIGHT_BLUE; + case FILE_DIFFERENT: + return COLOR_LIGHT_RED; + default: + return *wxWHITE; } - } } - //if data is not found: - return wxEmptyString; + return *wxWHITE; } }; - - //######################################################################################################## + CustomGrid::CustomGrid(wxWindow *parent, wxWindowID id, const wxPoint& pos, @@ -476,10 +455,10 @@ CustomGrid::CustomGrid(wxWindow *parent, long style, const wxString& name) : wxGrid(parent, id, pos, size, style, name), - leadGrid(NULL), - scrollbarsEnabled(true), - m_gridLeft(NULL), m_gridMiddle(NULL), m_gridRight(NULL), - gridDataTable(NULL), + m_gridLeft(NULL), + m_gridMiddle(NULL), + m_gridRight(NULL), + isLeading(false), currentSortColumn(-1), sortMarker(NULL) { @@ -500,32 +479,14 @@ CustomGrid::CustomGrid(wxWindow *parent, Connect(wxEVT_SCROLLWIN_THUMBRELEASE, wxEventHandler(CustomGrid::onGridAccess), NULL, this); Connect(wxEVT_GRID_LABEL_LEFT_CLICK, wxEventHandler(CustomGrid::onGridAccess), NULL, this); GetGridWindow()->Connect(wxEVT_LEFT_DOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this); + + GetGridWindow()->Connect(wxEVT_ENTER_WINDOW, wxEventHandler(CustomGrid::adjustGridHeights), NULL, this); } -void CustomGrid::initSettings(const bool enableScrollbars, - const bool showFileIcons, - CustomGrid* gridLeft, - CustomGrid* gridRight, - CustomGrid* gridMiddle, - GridView* gridRefUI, - FileCompareResult* gridData) +bool CustomGrid::isLeadGrid() const { - scrollbarsEnabled = enableScrollbars; - - //these grids will scroll together - assert(gridLeft && gridRight && gridMiddle); - m_gridLeft = gridLeft; - m_gridRight = gridRight; - m_gridMiddle = gridMiddle; - - //set underlying grid data - assert(gridDataTable); - gridDataTable->setGridDataTable(gridRefUI, gridData); - - this->initGridRenderer(showFileIcons); - - GetGridWindow()->Connect(wxEVT_ENTER_WINDOW, wxEventHandler(CustomGrid::adjustGridHeights), NULL, this); + return isLeading; } @@ -705,14 +666,17 @@ void additionalGridCommands(wxEvent& event, wxGrid* grid) void CustomGrid::onGridAccess(wxEvent& event) { - if (leadGrid != this) + if (!isLeading) { - leadGrid = this; + isLeading = true; - //notify grids of new user focus - m_gridLeft->leadGrid = this; - m_gridMiddle->leadGrid = this; - m_gridRight->leadGrid = this; + //notify other grids of new user focus + if (m_gridLeft != this) + m_gridLeft->isLeading = false; + if (m_gridMiddle != this) + m_gridMiddle->isLeading = false; + if (m_gridRight != this) + m_gridRight->isLeading = false; wxGrid::SetFocus(); } @@ -728,119 +692,194 @@ void CustomGrid::onGridAccess(wxEvent& event) } -const wxGrid* CustomGrid::getLeadGrid() +//workaround: ensure that all grids are properly aligned: add some extra window space to grids that have no horizontal scrollbar +void CustomGrid::adjustGridHeights(wxEvent& event) { - return leadGrid; + if (m_gridLeft && m_gridRight && m_gridMiddle) + { + int y1 = 0; + int y2 = 0; + int y3 = 0; + int dummy = 0; + + m_gridLeft->GetViewStart(&dummy, &y1); + m_gridRight->GetViewStart(&dummy, &y2); + m_gridMiddle->GetViewStart(&dummy, &y3); + + if (y1 != y2 || y2 != y3) + { + int yMax = std::max(y1, std::max(y2, y3)); + + if (m_gridLeft->isLeadGrid()) //do not handle case (y1 == yMax) here!!! Avoid back coupling! + m_gridLeft->SetMargins(0, 0); + else if (y1 < yMax) + m_gridLeft->SetMargins(0, 30); + + if (m_gridRight->isLeadGrid()) + m_gridRight->SetMargins(0, 0); + else if (y2 < yMax) + m_gridRight->SetMargins(0, 30); + + if (m_gridMiddle->isLeadGrid()) + m_gridMiddle->SetMargins(0, 0); + else if (y3 < yMax) + m_gridMiddle->SetMargins(0, 30); + + m_gridLeft->ForceRefresh(); + m_gridRight->ForceRefresh(); + m_gridMiddle->ForceRefresh(); + } + } } -bool CustomGrid::isLeadGrid() +void CustomGrid::setSortMarker(const int sortColumn, const wxBitmap* bitmap) { - return leadGrid == static_cast<const wxGrid*>(this); + currentSortColumn = sortColumn; + sortMarker = bitmap; } -//overwrite virtual method to finally get rid of the scrollbars -void CustomGrid::SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh) +void CustomGrid::DrawColLabel(wxDC& dc, int col) { - if (scrollbarsEnabled) - wxWindow::SetScrollbar(orientation, position, thumbSize, range, refresh); - else - wxWindow::SetScrollbar(orientation, 0, 0, 0, refresh); + wxGrid::DrawColLabel(dc, col); + + if (col == currentSortColumn) + dc.DrawBitmap(*sortMarker, GetColRight(col) - 16 - 2, 2, true); //respect 2-pixel border } +//############################################################################################ +//CustomGrid specializations -//workaround: ensure that all grids are properly aligned: add some extra window space to grids that have no horizontal scrollbar -void CustomGrid::adjustGridHeights(wxEvent& event) //m_gridLeft, m_gridRight, m_gridMiddle are not NULL in this context +template <bool leftSide, bool showFileIcons> +class GridCellRenderer : public wxGridCellStringRenderer { - int y1 = 0; - int y2 = 0; - int y3 = 0; - int dummy = 0; +public: + GridCellRenderer(CustomGridTableRim* gridDataTable) : m_gridDataTable(gridDataTable) {}; - m_gridLeft->GetViewStart(&dummy, &y1); - m_gridRight->GetViewStart(&dummy, &y2); - m_gridMiddle->GetViewStart(&dummy, &y3); - if (y1 != y2 || y2 != y3) + virtual void Draw(wxGrid& grid, + wxGridCellAttr& attr, + wxDC& dc, + const wxRect& rect, + int row, int col, + bool isSelected) { - int yMax = std::max(y1, std::max(y2, y3)); - - if (leadGrid == m_gridLeft) //do not handle case (y1 == yMax) here!!! Avoid back coupling! - m_gridLeft->SetMargins(0, 0); - else if (y1 < yMax) - m_gridLeft->SetMargins(0, 30); - - if (leadGrid == m_gridRight) - m_gridRight->SetMargins(0, 0); - else if (y2 < yMax) - m_gridRight->SetMargins(0, 30); - - if (leadGrid == m_gridMiddle) - m_gridMiddle->SetMargins(0, 0); - else if (y3 < yMax) - m_gridMiddle->SetMargins(0, 30); - - m_gridLeft->ForceRefresh(); - m_gridRight->ForceRefresh(); - m_gridMiddle->ForceRefresh(); - } -} +#ifdef FFS_WIN + //############## show windows explorer file icons ###################### + if (showFileIcons) //evaluate at compile time + { + const int ICON_SIZE = 16; //size in pixel -void CustomGrid::updateGridSizes() -{ - assert(gridDataTable); - gridDataTable->updateGridSizes(); -} + if ( m_gridDataTable->getTypeAtPos(col) == xmlAccess::FILENAME && + rect.GetWidth() >= ICON_SIZE) + { + //retrieve grid data + const FileCompareLine* rowData = m_gridDataTable->getRawData(row); + if (rowData) //valid row + { + const DefaultChar* filename; + if (leftSide) //evaluate at compile time + filename = rowData->fileDescrLeft.fullName.c_str(); + else + filename = rowData->fileDescrRight.fullName.c_str(); + if (*filename != DefaultChar(0)) //test if filename is empty + { + // Get the file icon. + SHFILEINFO fileInfo; + fileInfo.hIcon = 0; //initialize hIcon -void CustomGrid::setSortMarker(const int sortColumn, const wxBitmap* bitmap) -{ - currentSortColumn = sortColumn; - sortMarker = bitmap; -} + if (SHGetFileInfo(filename, //NOTE: CoInitializeEx()/CoUninitialize() implicitly called by wxWidgets on program startup! + 0, + &fileInfo, + sizeof(fileInfo), + SHGFI_ICON | SHGFI_SMALLICON)) + { + //clear area where icon will be placed + wxRect rectShrinked(rect); + rectShrinked.SetWidth(ICON_SIZE + 2); //add 2 pixel border + wxGridCellRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected); + //draw icon + if (fileInfo.hIcon != 0) //fix for weird error: SHGetFileInfo() might return successfully WITHOUT filling fileInfo.hIcon!! + { //bug report: https://sourceforge.net/tracker/?func=detail&aid=2768004&group_id=234430&atid=1093080 + wxIcon icon; + icon.SetHICON(static_cast<WXHICON>(fileInfo.hIcon)); + icon.SetSize(ICON_SIZE, ICON_SIZE); -void CustomGrid::DrawColLabel(wxDC& dc, int col) -{ - wxGrid::DrawColLabel(dc, col); + dc.DrawIcon(icon, rectShrinked.GetX() + 2, rectShrinked.GetY()); - if (col == currentSortColumn) - dc.DrawBitmap(*sortMarker, GetColRight(col) - 16 - 2, 2, true); //respect 2-pixel border + if (!DestroyIcon(fileInfo.hIcon)) + throw RuntimeException(wxString(wxT("Error deallocating Icon handle!\n\n")) + FreeFileSync::getLastErrorFormatted()); + } + + //draw rest + rectShrinked.SetWidth(rect.GetWidth() - ICON_SIZE - 2); + rectShrinked.SetX(rect.GetX() + ICON_SIZE + 2); + wxGridCellStringRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected); + return; + } + } + } + } + } + //default + wxGridCellStringRenderer::Draw(grid, attr, dc, rect, row, col, isSelected); + +#elif defined FFS_LINUX + wxGridCellStringRenderer::Draw(grid, attr, dc, rect, row, col, isSelected); +#endif + } + +private: + const CustomGridTableRim* const m_gridDataTable; +}; + + + +void CustomGridRim::updateGridSizes() +{ + assert(gridDataTable); + gridDataTable->updateGridSizes(); } -xmlAccess::ColumnAttributes CustomGrid::getDefaultColumnAttributes() +xmlAccess::ColumnAttributes CustomGridRim::getDefaultColumnAttributes() { xmlAccess::ColumnAttributes defaultColumnSettings; xmlAccess::ColumnAttrib newEntry; - newEntry.type = xmlAccess::FULL_NAME; newEntry.visible = false; newEntry.position = 0; newEntry.width = 150; defaultColumnSettings.push_back(newEntry); - newEntry.type = xmlAccess::FILENAME; - newEntry.visible = true; + newEntry.type = xmlAccess::DIRECTORY; newEntry.position = 1; - newEntry.width = 138; + newEntry.width = 140; defaultColumnSettings.push_back(newEntry); newEntry.type = xmlAccess::REL_PATH; + newEntry.visible = true; newEntry.position = 2; newEntry.width = 118; defaultColumnSettings.push_back(newEntry); - newEntry.type = xmlAccess::SIZE; + newEntry.type = xmlAccess::FILENAME; newEntry.position = 3; - newEntry.width = 67; + newEntry.width = 138; defaultColumnSettings.push_back(newEntry); - newEntry.type = xmlAccess::DATE; + newEntry.type = xmlAccess::SIZE; newEntry.position = 4; + newEntry.width = 70; + defaultColumnSettings.push_back(newEntry); + + newEntry.type = xmlAccess::DATE; + newEntry.position = 5; newEntry.width = 113; defaultColumnSettings.push_back(newEntry); @@ -848,7 +887,7 @@ xmlAccess::ColumnAttributes CustomGrid::getDefaultColumnAttributes() } -xmlAccess::ColumnAttributes CustomGrid::getColumnAttributes() +xmlAccess::ColumnAttributes CustomGridRim::getColumnAttributes() { std::sort(columnSettings.begin(), columnSettings.end(), xmlAccess::sortByPositionAndVisibility); @@ -866,7 +905,7 @@ xmlAccess::ColumnAttributes CustomGrid::getColumnAttributes() } -void CustomGrid::setColumnAttributes(const xmlAccess::ColumnAttributes& attr) +void CustomGridRim::setColumnAttributes(const xmlAccess::ColumnAttributes& attr) { //remove special alignment for column "size" for (int i = 0; i < GetNumberCols(); ++i) @@ -940,14 +979,14 @@ void CustomGrid::setColumnAttributes(const xmlAccess::ColumnAttributes& attr) } -xmlAccess::ColumnTypes CustomGrid::getTypeAtPos(unsigned pos) const +xmlAccess::ColumnTypes CustomGridRim::getTypeAtPos(unsigned pos) const { assert(gridDataTable); return gridDataTable->getTypeAtPos(pos); } -wxString CustomGrid::getTypeName(xmlAccess::ColumnTypes colType) +wxString CustomGridRim::getTypeName(xmlAccess::ColumnTypes colType) { switch (colType) { @@ -957,6 +996,8 @@ wxString CustomGrid::getTypeName(xmlAccess::ColumnTypes colType) return _("Filename"); case xmlAccess::REL_PATH: return _("Relative path"); + case xmlAccess::DIRECTORY: + return _("Directory"); case xmlAccess::SIZE: return _("Size"); case xmlAccess::DATE: @@ -966,139 +1007,121 @@ wxString CustomGrid::getTypeName(xmlAccess::ColumnTypes colType) } } -//############################################################################################ -//CustomGrid specializations +//---------------------------------------------------------------------------------------- CustomGridLeft::CustomGridLeft(wxWindow *parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString& name) : - CustomGrid(parent, id, pos, size, style, name) {} + CustomGridRim(parent, id, pos, size, style, name) {} -template <bool leftSide, bool showFileIcons> -class GridCellRenderer : public wxGridCellStringRenderer +bool CustomGridLeft::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode) { -public: - GridCellRenderer(CustomGridTable* gridDataTable) : m_gridDataTable(gridDataTable) {}; - - - virtual void Draw(wxGrid& grid, - wxGridCellAttr& attr, - wxDC& dc, - const wxRect& rect, - int row, int col, - bool isSelected) - { -#ifdef FFS_WIN - //############## show windows explorer file icons ###################### - - if (showFileIcons) //evaluate at compile time - { - const int ICON_SIZE = 16; //size in pixel - - if ( m_gridDataTable->getTypeAtPos(col) == xmlAccess::FILENAME && - rect.GetWidth() >= ICON_SIZE) - { - //retrieve grid data - const FileCompareLine* rowData = m_gridDataTable->getRawData(row); - if (rowData) //valid row - { - const DefaultChar* filename; - if (leftSide) //evaluate at compile time - filename = rowData->fileDescrLeft.fullName.c_str(); - else - filename = rowData->fileDescrRight.fullName.c_str(); + //use custom wxGridTableBase class for management of large sets of formatted data. + //This is done in CreateGrid instead of SetTable method since source code is generated and wxFormbuilder invokes CreatedGrid by default. + gridDataTable = new CustomGridTableLeft; + SetTable(gridDataTable, true, wxGrid::wxGridSelectRows); //give ownership to wxGrid: gridDataTable is deleted automatically in wxGrid destructor - if (*filename != 0) //test if filename is empty - { - // Get the file icon. - SHFILEINFO fileInfo; - if (SHGetFileInfo(filename, - 0, - &fileInfo, - sizeof(fileInfo), - SHGFI_ICON | SHGFI_SMALLICON)) - { - wxIcon icon; - icon.SetHICON((WXHICON)fileInfo.hIcon); - icon.SetSize(ICON_SIZE, ICON_SIZE); + return true; +} - //clear area where icon will be placed - wxRect rectShrinked(rect); - rectShrinked.SetWidth(ICON_SIZE + 2); //add 2 pixel border - dc.SetPen(*wxWHITE_PEN); - dc.SetBrush(*wxWHITE_BRUSH); - dc.DrawRectangle(rectShrinked); +void CustomGridLeft::initSettings(const bool showFileIcons, + CustomGrid* gridLeft, + CustomGrid* gridRight, + CustomGrid* gridMiddle, + GridView* gridDataView) +{ + //these grids will scroll together + m_gridLeft = gridLeft; + m_gridRight = gridRight; + m_gridMiddle = gridMiddle; - //draw icon - dc.DrawIcon(icon, rectShrinked.GetX() + 2, rectShrinked.GetY()); + //set underlying grid data + assert(gridDataTable); + gridDataTable->setGridDataTable(gridDataView); - rectShrinked.SetWidth(rect.GetWidth() - ICON_SIZE - 2); - rectShrinked.SetX(rect.GetX() + ICON_SIZE + 2); - wxGridCellStringRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected); + if (showFileIcons) + SetDefaultRenderer(new GridCellRenderer<true, true>(gridDataTable)); //SetDefaultRenderer takes ownership! + else + SetDefaultRenderer(new GridCellRenderer<true, false>(gridDataTable)); +} - if (!DestroyIcon(fileInfo.hIcon)) - throw RuntimeException(wxString(wxT("Error deallocating Icon handle!\n\n")) + FreeFileSync::getLastErrorFormatted()); - return; - } - } - } - } - } - //default - wxGridCellStringRenderer::Draw(grid, attr, dc, rect, row, col, isSelected); +//this method is called when grid view changes: useful for parallel updating of multiple grids +void CustomGridLeft::DoPrepareDC(wxDC& dc) +{ + wxScrollHelper::DoPrepareDC(dc); -#elif defined FFS_LINUX - wxGridCellStringRenderer::Draw(grid, attr, dc, rect, row, col, isSelected); -#endif + int x, y = 0; + if (isLeadGrid()) //avoid back coupling + { + GetViewStart(&x, &y); + m_gridMiddle->Scroll(-1, y); //scroll in y-direction only + m_gridRight->Scroll(x, y); } +} -private: - CustomGridTable* m_gridDataTable; -}; +//---------------------------------------------------------------------------------------- +CustomGridRight::CustomGridRight(wxWindow *parent, + wxWindowID id, + const wxPoint& pos, + const wxSize& size, + long style, + const wxString& name) : + CustomGridRim(parent, id, pos, size, style, name) {} -bool CustomGridLeft::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode) +bool CustomGridRight::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode) { - //use custom wxGridTableBase class for management of large sets of formatted data. - //This is done in CreateGrid instead of SetTable method since source code is generated and wxFormbuilder invokes CreatedGrid by default. - gridDataTable = new CustomGridTableLeft(); + gridDataTable = new CustomGridTableRight; SetTable(gridDataTable, true, wxGrid::wxGridSelectRows); //give ownership to wxGrid: gridDataTable is deleted automatically in wxGrid destructor return true; } +void CustomGridRight::initSettings(const bool showFileIcons, + CustomGrid* gridLeft, + CustomGrid* gridRight, + CustomGrid* gridMiddle, + GridView* gridDataView) +{ + //these grids will scroll together + m_gridLeft = gridLeft; + m_gridRight = gridRight; + m_gridMiddle = gridMiddle; + + //set underlying grid data + assert(gridDataTable); + gridDataTable->setGridDataTable(gridDataView); + + if (showFileIcons) + SetDefaultRenderer(new GridCellRenderer<false, true>(gridDataTable)); //SetDefaultRenderer takes ownership! + else + SetDefaultRenderer(new GridCellRenderer<false, false>(gridDataTable)); +} + + //this method is called when grid view changes: useful for parallel updating of multiple grids -void CustomGridLeft::DoPrepareDC(wxDC& dc) +void CustomGridRight::DoPrepareDC(wxDC& dc) { wxScrollHelper::DoPrepareDC(dc); int x, y = 0; - if (this == leadGrid) //avoid back coupling + if (isLeadGrid()) //avoid back coupling { GetViewStart(&x, &y); - m_gridMiddle->Scroll(-1, y); //scroll in y-direction only - m_gridRight->Scroll(x, y); + m_gridLeft->Scroll(x, y); + m_gridMiddle->Scroll(-1, y); } } -void CustomGridLeft::initGridRenderer(const bool showFileIcons) -{ - if (showFileIcons) - SetDefaultRenderer(new GridCellRenderer<true, true>(gridDataTable)); //SetDefaultRenderer takes ownership! - else - SetDefaultRenderer(new GridCellRenderer<true, false>(gridDataTable)); -} - - //---------------------------------------------------------------------------------------- CustomGridMiddle::CustomGridMiddle(wxWindow *parent, wxWindowID id, @@ -1106,7 +1129,8 @@ CustomGridMiddle::CustomGridMiddle(wxWindow *parent, const wxSize& size, long style, const wxString& name) : - CustomGrid(parent, id, pos, size, style, name) + CustomGrid(parent, id, pos, size, style, name), + gridDataTable(NULL) { const wxString header = _("Legend"); wxString toolTip = header + wxT("\n") + @@ -1121,6 +1145,42 @@ CustomGridMiddle::CustomGridMiddle(wxWindow *parent, } +void CustomGridMiddle::initSettings(CustomGrid* gridLeft, + CustomGrid* gridRight, + CustomGrid* gridMiddle, + FreeFileSync::GridView* gridDataView) +{ + //these grids will scroll together + m_gridLeft = gridLeft; + m_gridRight = gridRight; + m_gridMiddle = gridMiddle; + + //set underlying grid data + assert(gridDataTable); + gridDataTable->setGridDataTable(gridDataView); + +#ifdef FFS_LINUX //get rid of scrollbars; Linux: change policy for GtkScrolledWindow + GtkWidget* gridWidget = wxWindow::m_widget; + GtkScrolledWindow* scrolledWindow = GTK_SCROLLED_WINDOW(gridWidget); + gtk_scrolled_window_set_policy(scrolledWindow, GTK_POLICY_NEVER, GTK_POLICY_NEVER); +#endif +} + + +#ifdef FFS_WIN //get rid of scrollbars; Windows: overwrite virtual method +void CustomGridMiddle::SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh) +{ + wxWindow::SetScrollbar(orientation, 0, 0, 0, refresh); +} +#endif + +void CustomGridMiddle::updateGridSizes() +{ + assert(gridDataTable); + gridDataTable->updateGridSizes(); +} + + class GridCellRendererMiddle : public wxGridCellStringRenderer { public: @@ -1148,12 +1208,10 @@ public: //clean first block of rect that will receive image of checkbox rectShrinked.SetWidth(shift); - dc.SetPen(*wxWHITE_PEN); - dc.SetBrush(*wxWHITE_BRUSH); - dc.DrawRectangle(rectShrinked); + wxGridCellRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected); //print image into first block - rectShrinked.SetX(1); + rectShrinked.SetX(rect.GetX() + 1); if (rowData->selectedForSynchronization) dc.DrawLabel(wxEmptyString, *globalResource.bitmapCheckBoxTrue, rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); else @@ -1161,18 +1219,18 @@ public: //print second block (default): display compare result rectShrinked.SetWidth(rect.GetWidth() - shift); - rectShrinked.SetX(shift); + rectShrinked.SetX(rect.GetX() + shift); wxGridCellStringRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected); } private: - CustomGridTable* m_gridDataTable; + const CustomGridTable* const m_gridDataTable; }; bool CustomGridMiddle::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode) { - gridDataTable = new CustomGridTableMiddle(); + gridDataTable = new CustomGridTableMiddle; SetTable(gridDataTable, true, wxGrid::wxGridSelectRows); //give ownership to wxGrid: gridDataTable is deleted automatically in wxGrid destructor //display checkboxes (representing bool values) if row is enabled for synchronization @@ -1188,7 +1246,7 @@ void CustomGridMiddle::DoPrepareDC(wxDC& dc) wxScrollHelper::DoPrepareDC(dc); int x, y = 0; - if (this == leadGrid) //avoid back coupling + if (isLeadGrid()) //avoid back coupling { GetViewStart(&x, &y); m_gridLeft->Scroll(-1, y); @@ -1196,45 +1254,3 @@ void CustomGridMiddle::DoPrepareDC(wxDC& dc) } } - -//---------------------------------------------------------------------------------------- -CustomGridRight::CustomGridRight(wxWindow *parent, - wxWindowID id, - const wxPoint& pos, - const wxSize& size, - long style, - const wxString& name) : - CustomGrid(parent, id, pos, size, style, name) {} - - -bool CustomGridRight::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode) -{ - gridDataTable = new CustomGridTableRight(); - SetTable(gridDataTable, true, wxGrid::wxGridSelectRows); //give ownership to wxGrid: gridDataTable is deleted automatically in wxGrid destructor - - return true; -} - - -//this method is called when grid view changes: useful for parallel updating of multiple grids -void CustomGridRight::DoPrepareDC(wxDC& dc) -{ - wxScrollHelper::DoPrepareDC(dc); - - int x, y = 0; - if (this == leadGrid) //avoid back coupling - { - GetViewStart(&x, &y); - m_gridLeft->Scroll(x, y); - m_gridMiddle->Scroll(-1, y); - } -} - - -void CustomGridRight::initGridRenderer(const bool showFileIcons) -{ - if (showFileIcons) - SetDefaultRenderer(new GridCellRenderer<false, true>(gridDataTable)); //SetDefaultRenderer takes ownership! - else - SetDefaultRenderer(new GridCellRenderer<false, false>(gridDataTable)); -} diff --git a/library/CustomGrid.h b/library/CustomGrid.h index 14d62255..802db231 100644 --- a/library/CustomGrid.h +++ b/library/CustomGrid.h @@ -3,15 +3,33 @@ #include <vector> #include <wx/grid.h> -#include "../FreeFileSync.h" +#include "../structures.h" #include "processXml.h" -using namespace FreeFileSync; - class CustomGridTable; +class CustomGridTableRim; +class CustomGridTableMiddle; + +namespace FreeFileSync +{ + class GridView; +} //################################################################################## +/* +class hierarchy: + CustomGrid + /|\ + ____________|____________ + | | + CustomGridRim | + /|\ | + ________|_______ | + | | | +CustomGridLeft CustomGridRight CustomGridMiddle +*/ + class CustomGrid : public wxGrid { public: @@ -24,27 +42,46 @@ public: virtual ~CustomGrid() {} - //overwrite virtual method to finally get rid of the scrollbars - virtual void SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh = true); - virtual void DrawColLabel(wxDC& dc, int col); - void initSettings(const bool enableScrollbars, - const bool showFileIcons, - CustomGrid* gridLeft, - CustomGrid* gridRight, - CustomGrid* gridMiddle, - GridView* gridRefUI, - FileCompareResult* gridData); + //set sort direction indicator on UI + void setSortMarker(const int sortColumn, const wxBitmap* bitmap = &wxNullBitmap); + + bool isLeadGrid() const; - virtual void initGridRenderer(const bool showFileIcons) = 0; +protected: + CustomGrid* m_gridLeft; + CustomGrid* m_gridMiddle; + CustomGrid* m_gridRight; + +private: + void onGridAccess(wxEvent& event); + void adjustGridHeights(wxEvent& event); + + bool isLeading; //identify grid that has user focus + int currentSortColumn; + const wxBitmap* sortMarker; +}; + + +//############## SPECIALIZATIONS ################### +class CustomGridRim : public CustomGrid +{ +public: + CustomGridRim(wxWindow *parent, + wxWindowID id, + const wxPoint& pos, + const wxSize& size, + long style, + const wxString& name) : + CustomGrid(parent, id, pos, size, style, name), + gridDataTable(NULL) {} + + ~CustomGridRim() {} //notify wxGrid that underlying table size has changed void updateGridSizes(); - //set sort direction indicator on UI - void setSortMarker(const int sortColumn, const wxBitmap* bitmap = &wxNullBitmap); - //set visibility, position and width of columns static xmlAccess::ColumnAttributes getDefaultColumnAttributes(); xmlAccess::ColumnAttributes getColumnAttributes(); @@ -54,30 +91,15 @@ public: static wxString getTypeName(xmlAccess::ColumnTypes colType); - const wxGrid* getLeadGrid(); - bool isLeadGrid(); - protected: - void onGridAccess(wxEvent& event); - void adjustGridHeights(wxEvent& event); + CustomGridTableRim* gridDataTable; +private: xmlAccess::ColumnAttributes columnSettings; //set visibility, position and width of columns - - const wxGrid* leadGrid; //grid that has user focus - bool scrollbarsEnabled; - CustomGrid* m_gridLeft; - CustomGrid* m_gridMiddle; - CustomGrid* m_gridRight; - - CustomGridTable* gridDataTable; - - int currentSortColumn; - const wxBitmap* sortMarker; }; -//############## SPECIALIZATIONS ################### -class CustomGridLeft : public CustomGrid +class CustomGridLeft : public CustomGridRim { public: CustomGridLeft(wxWindow *parent, @@ -89,12 +111,41 @@ public: ~CustomGridLeft() {} + virtual bool CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode = wxGrid::wxGridSelectCells); + + void initSettings(const bool showFileIcons, //workaround: though this coding better belongs into a constructor + CustomGrid* gridLeft, //this is not possible due to source code generation (information not available at time of construction) + CustomGrid* gridRight, + CustomGrid* gridMiddle, + FreeFileSync::GridView* gridDataView); + //this method is called when grid view changes: useful for parallel updating of multiple grids virtual void DoPrepareDC(wxDC& dc); +}; + + +class CustomGridRight : public CustomGridRim +{ +public: + CustomGridRight(wxWindow *parent, + wxWindowID id, + const wxPoint& pos = wxDefaultPosition, + const wxSize& size = wxDefaultSize, + long style = wxWANTS_CHARS, + const wxString& name = wxGridNameStr); + + ~CustomGridRight() {} virtual bool CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode = wxGrid::wxGridSelectCells); - virtual void initGridRenderer(const bool showFileIcons); + void initSettings(const bool showFileIcons, //workaround: though this coding better belongs into a constructor + CustomGrid* gridLeft, //this is not possible due to source code generation (information not available at time of construction) + CustomGrid* gridRight, + CustomGrid* gridMiddle, + FreeFileSync::GridView* gridDataView); + + //this method is called when grid view changes: useful for parallel updating of multiple grids + virtual void DoPrepareDC(wxDC& dc); }; @@ -110,33 +161,25 @@ public: ~CustomGridMiddle() {} - //this method is called when grid view changes: useful for parallel updating of multiple grids - virtual void DoPrepareDC(wxDC& dc); - virtual bool CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode = wxGrid::wxGridSelectCells); - virtual void initGridRenderer(const bool showFileIcons) {} -}; - + void initSettings(CustomGrid* gridLeft, //workaround: though this coding better belongs into a constructor + CustomGrid* gridRight, //this is not possible due to source code generation (information not available at time of construction) + CustomGrid* gridMiddle, + FreeFileSync::GridView* gridDataView); -class CustomGridRight : public CustomGrid -{ -public: - CustomGridRight(wxWindow *parent, - wxWindowID id, - const wxPoint& pos = wxDefaultPosition, - const wxSize& size = wxDefaultSize, - long style = wxWANTS_CHARS, - const wxString& name = wxGridNameStr); + //notify wxGrid that underlying table size has changed + void updateGridSizes(); - ~CustomGridRight() {} +#ifdef FFS_WIN //get rid of scrollbars; Windows: overwrite virtual method + virtual void SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh = true); +#endif //this method is called when grid view changes: useful for parallel updating of multiple grids virtual void DoPrepareDC(wxDC& dc); - virtual bool CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode = wxGrid::wxGridSelectCells); - - virtual void initGridRenderer(const bool showFileIcons); +private: + CustomGridTableMiddle* gridDataTable; }; #endif // CUSTOMGRID_H_INCLUDED diff --git a/library/FreeFileSync.xpm b/library/FreeFileSync.xpm index 4165c5a1..339ccccb 100644 --- a/library/FreeFileSync.xpm +++ b/library/FreeFileSync.xpm @@ -1,5 +1,5 @@ /* XPM */ -static char * FreeFileSync_xpm[] = { +static const char * FreeFileSync_xpm[] = { "32 32 390 2", " c None", ". c #005927", diff --git a/library/fileHandling.cpp b/library/fileHandling.cpp index 0dccdec7..4f8caec6 100644 --- a/library/fileHandling.cpp +++ b/library/fileHandling.cpp @@ -3,6 +3,7 @@ #include <wx/msgdlg.h> #include "../algorithm.h" #include <wx/filename.h> +#include "globalFunctions.h" #ifdef FFS_WIN #include <wx/msw/wrapwin.h> //includes "windows.h" @@ -67,20 +68,25 @@ private: bool recycleBinAvailable; }; -//global instance of recycle bin -RecycleBin recyclerInstance; + +inline +RecycleBin& getRecycleBin() +{ + static RecycleBin instance; //lazy creation of RecycleBin + return instance; +} bool FreeFileSync::recycleBinExists() { - return recyclerInstance.recycleBinExists(); + return getRecycleBin().recycleBinExists(); } inline bool moveToRecycleBin(const Zstring& filename) throw(RuntimeException) { - return recyclerInstance.moveToRecycleBin(filename); + return getRecycleBin().moveToRecycleBin(filename); } @@ -266,18 +272,19 @@ void FreeFileSync::removeDirectory(const Zstring& directory, const bool useRecyc class CloseHandleOnExit { public: - CloseHandleOnExit(HANDLE searchHandle) : m_searchHandle(searchHandle) {} + CloseHandleOnExit(HANDLE fileHandle) : fileHandle_(fileHandle) {} ~CloseHandleOnExit() { - FindClose(m_searchHandle); + CloseHandle(fileHandle_); } private: - HANDLE m_searchHandle; + HANDLE fileHandle_; }; + typedef DWORD WINAPI (*GetFinalPath)( HANDLE hFile, LPTSTR lpszFilePath, @@ -295,7 +302,7 @@ public: //get a handle to the DLL module containing required functionality hKernel = ::LoadLibrary(wxT("kernel32.dll")); if (hKernel) - getFinalPathNameByHandle = (GetFinalPath)(::GetProcAddress(hKernel, "GetFinalPathNameByHandleW")); //load unicode version! + getFinalPathNameByHandle = reinterpret_cast<GetFinalPath>(::GetProcAddress(hKernel, "GetFinalPathNameByHandleW")); //load unicode version! } ~DllHandler() @@ -309,8 +316,13 @@ private: HINSTANCE hKernel; }; -//global instance -DllHandler dynamicWinApi; + +inline +DllHandler& getDllHandler() //lazy creation of DllHandler +{ + static DllHandler instance; + return instance; +} Zstring resolveDirectorySymlink(const Zstring& dirLinkName) //get full target path of symbolic link to a directory @@ -328,13 +340,13 @@ Zstring resolveDirectorySymlink(const Zstring& dirLinkName) //get full target pa CloseHandleOnExit dummy(hDir); - if (dynamicWinApi.getFinalPathNameByHandle == NULL ) + if (getDllHandler().getFinalPathNameByHandle == NULL ) throw FileError(Zstring(_("Error loading library function:")) + wxT("\n\"") + wxT("GetFinalPathNameByHandleW") + wxT("\"")); const unsigned BUFFER_SIZE = 10000; TCHAR targetPath[BUFFER_SIZE]; - const DWORD rv = dynamicWinApi.getFinalPathNameByHandle( + const DWORD rv = getDllHandler().getFinalPathNameByHandle( hDir, targetPath, BUFFER_SIZE, @@ -638,6 +650,21 @@ void FreeFileSync::copyFile(const Zstring& sourceFile, #ifdef FFS_WIN +class CloseFindHandleOnExit +{ +public: + CloseFindHandleOnExit(HANDLE searchHandle) : searchHandle_(searchHandle) {} + + ~CloseFindHandleOnExit() + { + FindClose(searchHandle_); + } + +private: + HANDLE searchHandle_; +}; + + inline void setWin32FileInformation(const FILETIME& lastWriteTime, const DWORD fileSizeHigh, const DWORD fileSizeLow, FreeFileSync::FileInfo& output) { @@ -650,6 +677,7 @@ void setWin32FileInformation(const FILETIME& lastWriteTime, const DWORD fileSize output.fileSize = wxULongLong(fileSizeHigh, fileSizeLow); } + inline bool setWin32FileInformationFromSymlink(const Zstring linkName, FreeFileSync::FileInfo& output) { @@ -679,6 +707,7 @@ bool setWin32FileInformationFromSymlink(const Zstring linkName, FreeFileSync::Fi return true; } + #elif defined FFS_LINUX class CloseDirOnExit { @@ -713,14 +742,13 @@ public: } #ifdef FFS_WIN - Zstring directoryFormatted = directory; - if (!FreeFileSync::endsWithPathSeparator(directoryFormatted)) - directoryFormatted += GlobalResources::FILE_NAME_SEPARATOR; - - const Zstring filespec = directoryFormatted + DefaultChar('*'); + //ensure directoryFormatted ends with backslash + const Zstring directoryFormatted = FreeFileSync::endsWithPathSeparator(directory) ? + directory : + directory + GlobalResources::FILE_NAME_SEPARATOR; WIN32_FIND_DATA fileMetaData; - HANDLE searchHandle = FindFirstFile(filespec.c_str(), //pointer to name of file to search for + HANDLE searchHandle = FindFirstFile((directoryFormatted + DefaultChar('*')).c_str(), //pointer to name of file to search for &fileMetaData); //pointer to returned information if (searchHandle == INVALID_HANDLE_VALUE) @@ -736,11 +764,11 @@ public: else return true; } - CloseHandleOnExit dummy(searchHandle); + CloseFindHandleOnExit dummy(searchHandle); do { //don't return "." and ".." - const wxChar* name = fileMetaData.cFileName; + const wxChar* const name = fileMetaData.cFileName; if ( name[0] == wxChar('.') && ((name[1] == wxChar('.') && name[2] == wxChar('\0')) || name[1] == wxChar('\0'))) @@ -800,14 +828,10 @@ public: return true; #elif defined FFS_LINUX - Zstring directoryFormatted = directory; - if (FreeFileSync::endsWithPathSeparator(directoryFormatted)) - directoryFormatted = directoryFormatted.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); - - DIR* dirObj = opendir(directoryFormatted.c_str()); + DIR* dirObj = opendir(directory.c_str()); if (dirObj == NULL) { - Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directoryFormatted + wxT("\"") ; + Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory+ wxT("\"") ; if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()) == wxDIR_STOP) return false; else @@ -819,13 +843,15 @@ public: while (!(errno = 0) && (dirEntry = readdir(dirObj)) != NULL) //set errno to 0 as unfortunately this isn't done when readdir() returns NULL when it is finished { //don't return "." and ".." - const wxChar* name = dirEntry->d_name; + const wxChar* const name = dirEntry->d_name; if ( name[0] == wxChar('.') && ((name[1] == wxChar('.') && name[2] == wxChar('\0')) || name[1] == wxChar('\0'))) continue; - const Zstring fullName = directoryFormatted + GlobalResources::FILE_NAME_SEPARATOR + name; + const Zstring fullName = FreeFileSync::endsWithPathSeparator(directory) ? //e.g. "/" + directory + name : + directory + GlobalResources::FILE_NAME_SEPARATOR + name; struct stat fileInfo; if (lstat(fullName.c_str(), &fileInfo) != 0) //lstat() does not resolve symlinks @@ -887,7 +913,7 @@ public: return true; //everything okay //else: we have a problem... report it: - const Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directoryFormatted + wxT("\"") ; + const Zstring errorMessage = Zstring(_("Error traversing directory:")) + wxT("\n\"") + directory + wxT("\"") ; if (m_sink->OnError(errorMessage + wxT("\n\n") + FreeFileSync::getLastErrorFormatted()) == wxDIR_STOP) return false; else @@ -904,15 +930,21 @@ void FreeFileSync::traverseInDetail(const Zstring& directory, const bool traverseDirectorySymlinks, FullDetailFileTraverser* sink) { + Zstring directoryFormatted = directory; +#ifdef FFS_LINUX //remove trailing slash + if (directoryFormatted.size() > 1 && FreeFileSync::endsWithPathSeparator(directoryFormatted)) + directoryFormatted = directoryFormatted.BeforeLast(GlobalResources::FILE_NAME_SEPARATOR); +#endif + if (traverseDirectorySymlinks) { TraverseRecursively<true> filewalker(sink); - filewalker.traverse(directory, 0); + filewalker.traverse(directoryFormatted, 0); } else { TraverseRecursively<false> filewalker(sink); - filewalker.traverse(directory, 0); + filewalker.traverse(directoryFormatted, 0); } } diff --git a/library/fileHandling.h b/library/fileHandling.h index 6c9a0400..de068d1f 100644 --- a/library/fileHandling.h +++ b/library/fileHandling.h @@ -1,7 +1,6 @@ #ifndef RECYCLER_H_INCLUDED #define RECYCLER_H_INCLUDED -#include "globalFunctions.h" #include <wx/dir.h> #include "zstring.h" @@ -11,7 +10,7 @@ public: FileError(const Zstring& message) : errorMessage(message) {} - Zstring show() const + const Zstring& show() const { return errorMessage; } @@ -25,7 +24,7 @@ namespace FreeFileSync { struct FileInfo { - wxULongLong fileSize; //unit: bytes! + wxULongLong fileSize; //unit: bytes! wxLongLong lastWriteTimeRaw; //number of seconds since Jan. 1st 1970 UTC }; diff --git a/library/filter.cpp b/library/filter.cpp new file mode 100644 index 00000000..d5255367 --- /dev/null +++ b/library/filter.cpp @@ -0,0 +1,241 @@ +#include "filter.h" +#include "zstring.h" +#include <wx/string.h> +#include <set> +#include <vector> +#include "resources.h" + + +void compoundStringToTable(const Zstring& compoundInput, const DefaultChar* delimiter, std::vector<Zstring>& output) +{ + output.clear(); + Zstring input(compoundInput); + + //make sure input ends with delimiter - no problem with empty strings here + if (!input.EndsWith(delimiter)) + input += delimiter; + + unsigned int indexStart = 0; + unsigned int indexEnd = 0; + while ((indexEnd = input.find(delimiter, indexStart)) != Zstring::npos) + { + if (indexStart != indexEnd) //do not add empty strings + { + Zstring newEntry = input.substr(indexStart, indexEnd - indexStart); + + newEntry.Trim(true); //remove whitespace characters from right + newEntry.Trim(false); //remove whitespace characters from left + + if (!newEntry.empty()) + output.push_back(newEntry); + } + indexStart = indexEnd + 1; + } +} + + +inline +void mergeVectors(std::vector<Zstring>& changing, const std::vector<Zstring>& input) +{ + for (std::vector<Zstring>::const_iterator i = input.begin(); i != input.end(); ++i) + changing.push_back(*i); +} + + +inline +void formatFilterString(Zstring& filter) +{ +#ifdef FFS_WIN + //Windows does NOT distinguish between upper/lower-case + filter.MakeLower(); +#elif defined FFS_LINUX + //Linux DOES distinguish between upper/lower-case +//nothing to do here +#else + adapt; +#endif +} + + +std::vector<Zstring> compoundStringToFilter(const Zstring& filterString) +{ + //delimiters may be ';' or '\n' + std::vector<Zstring> filterList; + std::vector<Zstring> filterPreProcessing; + compoundStringToTable(filterString, wxT(";"), filterPreProcessing); + + for (std::vector<Zstring>::const_iterator i = filterPreProcessing.begin(); i != filterPreProcessing.end(); ++i) + { + std::vector<Zstring> newEntries; + compoundStringToTable(*i, wxT("\n"), newEntries); + mergeVectors(filterList, newEntries); + } + + return filterList; +} + + +inline +void addFilterEntry(const Zstring& filtername, std::set<Zstring>& fileFilter, std::set<Zstring>& directoryFilter) +{ + //Test if filtername ends with GlobalResources::FILE_NAME_SEPARATOR, ignoring '*' and '?'. + //If so, treat as filter for directory and add to directoryFilter. + if (!filtername.empty()) + { + const DefaultChar* filter = filtername.c_str(); + int i = filtername.length() - 1; + while (filter[i] == DefaultChar('*') || filter[i] == DefaultChar('?')) + { + --i; + + if (i == -1) + break; + } + + if (i >= 0 && filter[i] == GlobalResources::FILE_NAME_SEPARATOR) //last FILE_NAME_SEPARATOR found + { + if (i != int(filtername.length()) - 1) // "name\*" + { + fileFilter.insert(filtername); + directoryFilter.insert(filtername); + } + //else: "name\" -> not inserted directly + + if (i > 0) // "name\*" or "name\": add "name" to directory filter + directoryFilter.insert(Zstring(filtername.c_str(), i)); + } + else + { + fileFilter.insert(filtername); + directoryFilter.insert(filtername); + } + } +} + + +inline +bool matchesFilter(const Zstring& name, const std::set<Zstring>& filter) +{ + for (std::set<Zstring>::iterator j = filter.begin(); j != filter.end(); ++j) + if (name.Matches(*j)) + return true; + + return false; +} + + +void FreeFileSync::filterGridData(FolderComparison& folderCmp, const wxString& includeFilter, const wxString& excludeFilter) +{ + //no need for regular expressions! In tests wxRegex was by factor of 10 slower than wxString::Matches()!! + + //load filter into vectors of strings + //delimiters may be ';' or '\n' + std::vector<Zstring> includeList = compoundStringToFilter(includeFilter.c_str()); + std::vector<Zstring> excludeList = compoundStringToFilter(excludeFilter.c_str()); + +//############################################################## + //setup include/exclude filters for files and directories + std::set<Zstring> filterFileIn; + std::set<Zstring> filterFolderIn; + for (std::vector<Zstring>::iterator i = includeList.begin(); i != includeList.end(); ++i) + { + formatFilterString(*i); //format entry + addFilterEntry(*i, filterFileIn, filterFolderIn); + } + + std::set<Zstring> filterFileEx; + std::set<Zstring> filterFolderEx; + for (std::vector<Zstring>::iterator i = excludeList.begin(); i != excludeList.end(); ++i) + { + formatFilterString(*i); //format entry + addFilterEntry(*i, filterFileEx, filterFolderEx); + } + +//############################################################## + + //execute filtering... + for (FolderComparison::iterator j = folderCmp.begin(); j != folderCmp.end(); ++j) + { + FileComparison& fileCmp = j->fileCmp; + + for (FileComparison::iterator i = fileCmp.begin(); i != fileCmp.end(); ++i) + { + Zstring filenameLeft = i->fileDescrLeft.fullName; + Zstring filenameRight = i->fileDescrRight.fullName; + + formatFilterString(filenameLeft); + formatFilterString(filenameRight); + + + //left hand side + if (i->fileDescrLeft.objType == FileDescrLine::TYPE_FILE) + { + if ( !matchesFilter(filenameLeft, filterFileIn) || //process include filters + matchesFilter(filenameLeft, filterFileEx)) //process exclude filters + { + i->selectedForSynchronization = false; + continue; + } + } + else if (i->fileDescrLeft.objType == FileDescrLine::TYPE_DIRECTORY) + { + if ( !matchesFilter(filenameLeft, filterFolderIn) || //process include filters + matchesFilter(filenameLeft, filterFolderEx)) //process exclude filters + { + i->selectedForSynchronization = false; + continue; + } + } + + //right hand side + if (i->fileDescrRight.objType == FileDescrLine::TYPE_FILE) + { + if ( !matchesFilter(filenameRight, filterFileIn) || //process include filters + matchesFilter(filenameRight, filterFileEx)) //process exclude filters + { + i->selectedForSynchronization = false; + continue; + } + } + else if (i->fileDescrRight.objType == FileDescrLine::TYPE_DIRECTORY) + { + if ( !matchesFilter(filenameRight, filterFolderIn) || //process include filters + matchesFilter(filenameRight, filterFolderEx)) //process exclude filters + { + i->selectedForSynchronization = false; + continue; + } + } + + i->selectedForSynchronization = true; + } + } +} + + +template <bool includeRows> +inline +void inOrExcludeAllRows(FreeFileSync::FolderComparison& folderCmp) +{ + //remove all filters on folderCmp + for (FreeFileSync::FolderComparison::iterator j = folderCmp.begin(); j != folderCmp.end(); ++j) + { + FreeFileSync::FileComparison& fileCmp = j->fileCmp; + for (FreeFileSync::FileComparison::iterator i = fileCmp.begin(); i != fileCmp.end(); ++i) + i->selectedForSynchronization = includeRows; + } +} + + +void FreeFileSync::includeAllRowsOnGrid(FolderComparison& folderCmp) +{ + //remove all filters on currentGridData + inOrExcludeAllRows<true>(folderCmp); +} + + +void FreeFileSync::excludeAllRowsOnGrid(FolderComparison& folderCmp) +{ + //exclude all rows on currentGridData + inOrExcludeAllRows<false>(folderCmp); +} diff --git a/library/filter.h b/library/filter.h new file mode 100644 index 00000000..3d0598e1 --- /dev/null +++ b/library/filter.h @@ -0,0 +1,15 @@ +#ifndef FFS_FILTER_H_INCLUDED +#define FFS_FILTER_H_INCLUDED + +#include "../structures.h" + + +namespace FreeFileSync +{ + void filterGridData(FolderComparison& folderCmp, const wxString& includeFilter, const wxString& excludeFilter); + void includeAllRowsOnGrid(FolderComparison& folderCmp); + void excludeAllRowsOnGrid(FolderComparison& folderCmp); +} + + +#endif // FFS_FILTER_H_INCLUDED diff --git a/library/globalFunctions.cpp b/library/globalFunctions.cpp index 4b387293..7c4a1b92 100644 --- a/library/globalFunctions.cpp +++ b/library/globalFunctions.cpp @@ -2,6 +2,7 @@ #include "resources.h" #include <wx/msgdlg.h> #include <wx/file.h> +#include <fstream> std::string globalFunctions::numberToString(const unsigned int number) @@ -20,6 +21,14 @@ std::string globalFunctions::numberToString(const int number) } +std::string globalFunctions::numberToString(const long number) +{ + char result[100]; + sprintf(result, "%ld", number); + return std::string(result); +} + + std::string globalFunctions::numberToString(const float number) { char result[100]; @@ -52,14 +61,18 @@ int globalFunctions::stringToInt(const std::string& number) } -inline +long globalFunctions::stringToLong(const std::string& number) +{ + return atol(number.c_str()); +} + + double globalFunctions::stringToDouble(const std::string& number) { return atof(number.c_str()); } -inline int globalFunctions::wxStringToInt(const wxString& number) { long result = 0; @@ -70,7 +83,6 @@ int globalFunctions::wxStringToInt(const wxString& number) } -inline double globalFunctions::wxStringToDouble(const wxString& number) { double result = 0; diff --git a/library/globalFunctions.h b/library/globalFunctions.h index 98e8cd1c..622babcb 100644 --- a/library/globalFunctions.h +++ b/library/globalFunctions.h @@ -6,10 +6,9 @@ #include <vector> #include <set> #include <wx/string.h> -#include <fstream> #include <wx/stream.h> #include <wx/stopwatch.h> - +#include <wx/longlong.h> namespace globalFunctions { @@ -28,6 +27,7 @@ namespace globalFunctions std::string numberToString(const unsigned int number); //convert number to string std::string numberToString(const int number); //convert number to string + std::string numberToString(const long number); //convert number to string std::string numberToString(const float number); //convert number to string wxString numberToWxString(const unsigned int number); //convert number to wxString @@ -35,6 +35,7 @@ namespace globalFunctions wxString numberToWxString(const float number); //convert number to wxString int stringToInt( const std::string& number); //convert String to number + long stringToLong( const std::string& number); //convert String to number double stringToDouble(const std::string& number); //convert String to number int wxStringToInt( const wxString& number); //convert wxString to number @@ -47,6 +48,12 @@ namespace globalFunctions int readInt(wxInputStream& stream); //read int from file stream void writeInt(wxOutputStream& stream, const int number); //write int to filestream + + inline + wxLongLong convertToSigned(const wxULongLong number) + { + return wxLongLong(number.GetHi(), number.GetLo()); + } } @@ -54,7 +61,7 @@ namespace globalFunctions class Performance { public: - wxDEPRECATED(Performance()); //generates compiler warnings as a reminder to remove code after measurements + wxDEPRECATED(Performance()); //generate compiler warnings as a reminder to remove code after measurements ~Performance(); void showResult(); @@ -113,26 +120,26 @@ private: template <class T> void removeRowsFromVector(std::vector<T>& grid, const std::set<int>& rowsToRemove) { - std::vector<T> temp; - int rowToSkip = -1; //keep it an INT! - - std::set<int>::iterator rowToSkipIndex = rowsToRemove.begin(); + if (rowsToRemove.size() > 0) + { + std::vector<T> temp; - if (rowToSkipIndex != rowsToRemove.end()) - rowToSkip = *rowToSkipIndex; + std::set<int>::iterator rowToSkipIndex = rowsToRemove.begin(); + int rowToSkip = *rowToSkipIndex; - for (int i = 0; i < int(grid.size()); ++i) - { - if (i != rowToSkip) - temp.push_back(grid[i]); - else + for (int i = 0; i < int(grid.size()); ++i) { - ++rowToSkipIndex; - if (rowToSkipIndex != rowsToRemove.end()) - rowToSkip = *rowToSkipIndex; + if (i != rowToSkip) + temp.push_back(grid[i]); + else + { + ++rowToSkipIndex; + if (rowToSkipIndex != rowsToRemove.end()) + rowToSkip = *rowToSkipIndex; + } } + grid.swap(temp); } - grid.swap(temp); } diff --git a/library/misc.cpp b/library/localization.cpp index 0d5761d6..328f37be 100644 --- a/library/misc.cpp +++ b/library/localization.cpp @@ -1,7 +1,39 @@ -#include "misc.h" +#include "localization.h" #include <wx/msgdlg.h> #include "resources.h" #include "globalFunctions.h" +#include <fstream> +#include <set> + +//_("Browse") <- dummy string for wxDirPickerCtrl to be recognized by automatic text extraction! + + +struct TranslationLine +{ + wxString original; + wxString translation; + + bool operator<(const TranslationLine& b) const + { + return (original < b.original); + } +}; + +class Translation : public std::set<TranslationLine> {}; + + +CustomLocale::CustomLocale() : + wxLocale(), + currentLanguage(wxLANGUAGE_ENGLISH) +{ + translationDB = new Translation; +} + + +CustomLocale::~CustomLocale() +{ + delete translationDB; +} inline @@ -49,12 +81,6 @@ void exchangeEscapeChars(wxString& data) } -CustomLocale::CustomLocale() : - wxLocale(), - currentLanguage(wxLANGUAGE_ENGLISH) -{} - - void CustomLocale::setLanguage(const int language) { currentLanguage = language; @@ -89,6 +115,9 @@ void CustomLocale::setLanguage(const int language) case wxLANGUAGE_PORTUGUESE: languageFile = "Languages/portuguese.lng"; break; + case wxLANGUAGE_PORTUGUESE_BRAZILIAN: + languageFile = "Languages/portuguese_br.lng"; + break; case wxLANGUAGE_SLOVENIAN: languageFile = "Languages/slovenian.lng"; break; @@ -100,7 +129,7 @@ void CustomLocale::setLanguage(const int language) currentLanguage = wxLANGUAGE_ENGLISH; } - static bool initialized = false; //wxLocale is a "static" too! + static bool initialized = false; //wxLocale is a global too! if (!initialized) { initialized = true; @@ -108,7 +137,7 @@ void CustomLocale::setLanguage(const int language) } //load language file into buffer - translationDB.clear(); + translationDB->clear(); const int bufferSize = 100000; char temp[bufferSize]; if (!languageFile.empty()) @@ -137,7 +166,7 @@ void CustomLocale::setLanguage(const int language) else { currentLine.translation = formattedString; - translationDB.insert(currentLine); + translationDB->insert(currentLine); } } langFile.close(); @@ -160,8 +189,8 @@ const wxChar* CustomLocale::GetString(const wxChar* szOrigString, const wxChar* currentLine.original = szOrigString; //look for translation in buffer table - Translation::iterator i; - if ((i = translationDB.find(currentLine)) != translationDB.end()) + const Translation::iterator i = translationDB->find(currentLine); + if (i != translationDB->end()) return i->translation.c_str(); //fallback diff --git a/library/localization.h b/library/localization.h new file mode 100644 index 00000000..cf29d06d --- /dev/null +++ b/library/localization.h @@ -0,0 +1,32 @@ +#ifndef MISC_H_INCLUDED +#define MISC_H_INCLUDED + +#include <wx/intl.h> + +class Translation; + + +class CustomLocale : public wxLocale +{ +public: + CustomLocale(); + ~CustomLocale(); + + void setLanguage(const int language); + + int getLanguage() const + { + return currentLanguage; + } + + const wxChar* GetString(const wxChar* szOrigString, const wxChar* szDomain = NULL) const; + + static const std::string FfsLanguageDat; + +private: + Translation* translationDB; + int currentLanguage; +}; + + +#endif // MISC_H_INCLUDED diff --git a/library/misc.h b/library/misc.h deleted file mode 100644 index c3e960ff..00000000 --- a/library/misc.h +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef MISC_H_INCLUDED -#define MISC_H_INCLUDED - -#include <wx/string.h> -#include <set> -#include <wx/intl.h> -#include <wx/panel.h> - - -struct TranslationLine -{ - wxString original; - wxString translation; - - bool operator>(const TranslationLine& b ) const - { - return (original > b.original); - } - bool operator<(const TranslationLine& b) const - { - return (original < b.original); - } - bool operator==(const TranslationLine& b) const - { - return (original == b.original); - } -}; -typedef std::set<TranslationLine> Translation; - - -class CustomLocale : public wxLocale -{ -public: - CustomLocale(); - ~CustomLocale() {} - - void setLanguage(const int language); - - int getLanguage() - { - return currentLanguage; - } - - const wxChar* GetString(const wxChar* szOrigString, const wxChar* szDomain = NULL) const; - - static const std::string FfsLanguageDat; - -private: - Translation translationDB; - int currentLanguage; -}; - - -#endif // MISC_H_INCLUDED diff --git a/library/multithreading.cpp b/library/multithreading.cpp index 106d1aa7..6c2612f1 100644 --- a/library/multithreading.cpp +++ b/library/multithreading.cpp @@ -1,4 +1,5 @@ #include "multithreading.h" +#include "statusHandler.h" #include <wx/utils.h> //#include <wx/msw/wrapwin.h> //includes "windows.h" diff --git a/library/multithreading.h b/library/multithreading.h index bf0da145..21c5bcf2 100644 --- a/library/multithreading.h +++ b/library/multithreading.h @@ -1,9 +1,9 @@ #ifndef MULTITHREADING_H_INCLUDED #define MULTITHREADING_H_INCLUDED -#include "statusHandler.h" #include <wx/thread.h> +class StatusHandler; class WorkerThread; diff --git a/library/processXml.cpp b/library/processXml.cpp index 00737633..c16b9d76 100644 --- a/library/processXml.cpp +++ b/library/processXml.cpp @@ -3,11 +3,18 @@ #include <wx/ffile.h> #include <wx/intl.h> #include "globalFunctions.h" +#include "tinyxml/tinyxml.h" #ifdef FFS_WIN #include <wx/msw/wrapwin.h> //includes "windows.h" #endif +using namespace FreeFileSync; + +const wxString xmlAccess::LAST_CONFIG_FILE = wxT("LastRun.ffs_gui"); +const wxString xmlAccess::GLOBAL_CONFIG_FILE = wxT("GlobalSettings.xml"); + + //small helper functions bool readXmlElementValue(std::string& output, const TiXmlElement* parent, const std::string& name); bool readXmlElementValue(int& output, const TiXmlElement* parent, const std::string& name); @@ -151,18 +158,18 @@ xmlAccess::XmlBatchConfig xmlAccess::readBatchConfig(const wxString& filename) xmlAccess::XmlGlobalSettings xmlAccess::readGlobalSettings() { //load XML - if (!wxFileExists(FreeFileSync::GLOBAL_CONFIG_FILE)) - throw FileError(Zstring(_("File does not exist:")) + wxT(" \"") + FreeFileSync::GLOBAL_CONFIG_FILE.c_str() + wxT("\"")); + if (!wxFileExists(xmlAccess::GLOBAL_CONFIG_FILE)) + throw FileError(Zstring(_("File does not exist:")) + wxT(" \"") + xmlAccess::GLOBAL_CONFIG_FILE.c_str() + wxT("\"")); - XmlConfigInput inputFile(FreeFileSync::GLOBAL_CONFIG_FILE, XML_GLOBAL_SETTINGS); + XmlConfigInput inputFile(xmlAccess::GLOBAL_CONFIG_FILE, XML_GLOBAL_SETTINGS); XmlGlobalSettings outputCfg; if (!inputFile.loadedSuccessfully()) - throw FileError(Zstring(_("Error reading file:")) + wxT(" \"") + FreeFileSync::GLOBAL_CONFIG_FILE.c_str() + wxT("\"")); + throw FileError(Zstring(_("Error reading file:")) + wxT(" \"") + xmlAccess::GLOBAL_CONFIG_FILE.c_str() + wxT("\"")); if (!inputFile.readXmlGlobalSettings(outputCfg)) - throw FileError(Zstring(_("Error parsing configuration file:")) + wxT(" \"") + FreeFileSync::GLOBAL_CONFIG_FILE.c_str() + wxT("\"")); + throw FileError(Zstring(_("Error parsing configuration file:")) + wxT(" \"") + xmlAccess::GLOBAL_CONFIG_FILE.c_str() + wxT("\"")); return outputCfg; } @@ -194,12 +201,12 @@ void xmlAccess::writeBatchConfig(const wxString& filename, const XmlBatchConfig& void xmlAccess::writeGlobalSettings(const XmlGlobalSettings& outputCfg) { - XmlConfigOutput outputFile(FreeFileSync::GLOBAL_CONFIG_FILE, XML_GLOBAL_SETTINGS); + XmlConfigOutput outputFile(xmlAccess::GLOBAL_CONFIG_FILE, XML_GLOBAL_SETTINGS); //populate and write XML tree if ( !outputFile.writeXmlGlobalSettings(outputCfg) || //add GUI layout configuration settings !outputFile.writeToFile()) //save XML - throw FileError(Zstring(_("Error writing file:")) + wxT(" \"") + FreeFileSync::GLOBAL_CONFIG_FILE.c_str() + wxT("\"")); + throw FileError(Zstring(_("Error writing file:")) + wxT(" \"") + xmlAccess::GLOBAL_CONFIG_FILE.c_str() + wxT("\"")); return; } @@ -273,6 +280,19 @@ bool readXmlElementValue(int& output, const TiXmlElement* parent, const std::str } +bool readXmlElementValue(long& output, const TiXmlElement* parent, const std::string& name) +{ + std::string temp; + if (readXmlElementValue(temp, parent, name)) + { + output = globalFunctions::stringToLong(temp); + return true; + } + else + return false; +} + + bool readXmlElementValue(CompareVariant& output, const TiXmlElement* parent, const std::string& name) { int dummy = 0; @@ -396,6 +416,7 @@ bool XmlConfigInput::readXmlMainConfig(MainConfiguration& mainCfg, std::vector<F newPair.rightDirectory = wxString::FromUTF8(tempString.c_str()).c_str(); directoryPairs.push_back(newPair); + folderPair = folderPair->NextSiblingElement(); } @@ -435,17 +456,11 @@ bool XmlConfigInput::readXmlGuiConfig(xmlAccess::XmlGuiConfig& outputCfg) TiXmlHandle hRoot(root); - //read GUI layout - TiXmlElement* mainWindow = hRoot.FirstChild("GuiConfig").FirstChild("Windows").FirstChild("Main").ToElement(); - if (mainWindow) - { - readXmlElementValue(outputCfg.hideFilteredElements, mainWindow, "HideFiltered"); - } - - TiXmlElement* guiConfig = hRoot.FirstChild("GuiConfig").ToElement(); if (guiConfig) { + readXmlElementValue(outputCfg.hideFilteredElements, guiConfig, "HideFiltered"); + readXmlElementValue(outputCfg.ignoreErrors, guiConfig, "IgnoreErrors"); } @@ -506,6 +521,9 @@ bool XmlConfigInput::readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputC //copy symbolic links to files readXmlElementValue(outputCfg.shared.copyFileSymlinks, global, "CopyFileSymlinks"); + + //last update check + readXmlElementValue(outputCfg.shared.lastUpdateCheck, global, "LastCheckForUpdates"); } TiXmlElement* warnings = hRoot.FirstChild("Shared").FirstChild("Warnings").ToElement(); @@ -516,6 +534,9 @@ bool XmlConfigInput::readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputC //significant difference check readXmlElementValue(outputCfg.shared.warningSignificantDifference, warnings, "CheckForSignificantDifference"); + + //check free disk space + readXmlElementValue(outputCfg.shared.warningNotEnoughDiskSpace, warnings, "CheckForFreeDiskSpace"); } //gui specific global settings (optional) @@ -532,6 +553,7 @@ bool XmlConfigInput::readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputC readXmlElementValue(outputCfg.gui.deleteOnBothSides, mainWindow, "ManualDeletionOnBothSides"); readXmlElementValue(outputCfg.gui.useRecyclerForManualDeletion, mainWindow, "ManualDeletionUseRecycler"); readXmlElementValue(outputCfg.gui.showFileIcons, mainWindow, "ShowFileIcons"); + readXmlElementValue(outputCfg.gui.popupOnConfigChange, mainWindow, "PopupOnConfigChange"); //########################################################### //read column attributes @@ -585,10 +607,28 @@ bool XmlConfigInput::readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputC //load folder history elements const TiXmlElement* historyLeft = TiXmlHandle(mainWindow).FirstChild("FolderHistoryLeft").ToElement(); + if (historyLeft) + { + //load max. history size + const char* histSizeMax = historyLeft->Attribute("MaximumSize"); + if (histSizeMax) //may be NULL! + outputCfg.gui.folderHistLeftMax = globalFunctions::stringToInt(histSizeMax); + + //load config history elements + readXmlElementTable(outputCfg.gui.folderHistoryLeft, historyLeft, "Folder"); + } + const TiXmlElement* historyRight = TiXmlHandle(mainWindow).FirstChild("FolderHistoryRight").ToElement(); + if (historyRight) + { + //load max. history size + const char* histSizeMax = historyRight->Attribute("MaximumSize"); + if (histSizeMax) //may be NULL! + outputCfg.gui.folderHistRightMax = globalFunctions::stringToInt(histSizeMax); - readXmlElementTable(outputCfg.gui.folderHistoryLeft, historyLeft, "Folder"); - readXmlElementTable(outputCfg.gui.folderHistoryRight, historyRight, "Folder"); + //load config history elements + readXmlElementTable(outputCfg.gui.folderHistoryRight, historyRight, "Folder"); + } } TiXmlElement* gui = hRoot.FirstChild("Gui").ToElement(); @@ -607,7 +647,7 @@ bool XmlConfigInput::readXmlGlobalSettings(xmlAccess::XmlGlobalSettings& outputC //load max. history size const char* histSizeMax = cfgHistory->Attribute("MaximumSize"); if (histSizeMax) //may be NULL! - outputCfg.gui.cfgHistoryMaxItems = globalFunctions::stringToInt(histSizeMax); + outputCfg.gui.cfgHistoryMax = globalFunctions::stringToInt(histSizeMax); //load config history elements readXmlElementTable(outputCfg.gui.cfgFileHistory, cfgHistory, "File"); @@ -649,7 +689,7 @@ XmlConfigOutput::XmlConfigOutput(const wxString& fileName, const xmlAccess::XmlT bool XmlConfigOutput::writeToFile() { //workaround to get a FILE* from a unicode filename - wxFFile dummyFile(m_fileName, wxT("wb")); //save in binary mode for Linux portability of config files + wxFFile dummyFile(m_fileName, wxT("w")); //no need for "binary" mode here if (!dummyFile.IsOpened()) return false; @@ -676,6 +716,12 @@ void addXmlElement(TiXmlElement* parent, const std::string& name, const int valu } +void addXmlElement(TiXmlElement* parent, const std::string& name, const long value) +{ + addXmlElement(parent, name, globalFunctions::numberToString(value)); +} + + void addXmlElement(TiXmlElement* parent, const std::string& name, const SyncConfiguration::Direction value) { if (value == SyncConfiguration::SYNC_DIR_LEFT) @@ -794,13 +840,7 @@ bool XmlConfigOutput::writeXmlGuiConfig(const xmlAccess::XmlGuiConfig& inputCfg) TiXmlElement* guiConfig = new TiXmlElement("GuiConfig"); root->LinkEndChild(guiConfig); - TiXmlElement* windows = new TiXmlElement("Windows"); - guiConfig->LinkEndChild(windows); - - TiXmlElement* mainWindow = new TiXmlElement("Main"); - windows->LinkEndChild(mainWindow); - - addXmlElement(mainWindow, "HideFiltered", inputCfg.hideFilteredElements); + addXmlElement(guiConfig, "HideFiltered", inputCfg.hideFilteredElements); addXmlElement(guiConfig, "IgnoreErrors", inputCfg.ignoreErrors); @@ -851,6 +891,9 @@ bool XmlConfigOutput::writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& //copy symbolic links to files addXmlElement(global, "CopyFileSymlinks", inputCfg.shared.copyFileSymlinks); + //last update check + addXmlElement(global, "LastCheckForUpdates", inputCfg.shared.lastUpdateCheck); + //warnings TiXmlElement* warnings = new TiXmlElement("Warnings"); global->LinkEndChild(warnings); @@ -861,6 +904,9 @@ bool XmlConfigOutput::writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& //significant difference check addXmlElement(warnings, "CheckForSignificantDifference", inputCfg.shared.warningSignificantDifference); + //check free disk space + addXmlElement(warnings, "CheckForFreeDiskSpace", inputCfg.shared.warningNotEnoughDiskSpace); + //################################################################### //write global gui settings @@ -884,7 +930,8 @@ bool XmlConfigOutput::writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& addXmlElement(mainWindow, "ManualDeletionOnBothSides", inputCfg.gui.deleteOnBothSides); addXmlElement(mainWindow, "ManualDeletionUseRecycler", inputCfg.gui.useRecyclerForManualDeletion); - addXmlElement(mainWindow, "ShowFileIcons", inputCfg.gui.showFileIcons); + addXmlElement(mainWindow, "ShowFileIcons" , inputCfg.gui.showFileIcons); + addXmlElement(mainWindow, "PopupOnConfigChange" , inputCfg.gui.popupOnConfigChange); //write column attributes TiXmlElement* leftColumn = new TiXmlElement("LeftColumns"); @@ -925,7 +972,10 @@ bool XmlConfigOutput::writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& TiXmlElement* historyRight = new TiXmlElement("FolderHistoryRight"); mainWindow->LinkEndChild(historyRight); + historyLeft->SetAttribute("MaximumSize", globalFunctions::numberToString(inputCfg.gui.folderHistLeftMax)); addXmlElementTable(historyLeft, "Folder", inputCfg.gui.folderHistoryLeft); + + historyRight->SetAttribute("MaximumSize", globalFunctions::numberToString(inputCfg.gui.folderHistRightMax)); addXmlElementTable(historyRight, "Folder", inputCfg.gui.folderHistoryRight); @@ -936,7 +986,7 @@ bool XmlConfigOutput::writeXmlGlobalSettings(const xmlAccess::XmlGlobalSettings& TiXmlElement* cfgHistory = new TiXmlElement("ConfigHistory"); gui->LinkEndChild(cfgHistory); - cfgHistory->SetAttribute("MaximumSize", globalFunctions::numberToString(inputCfg.gui.cfgHistoryMaxItems)); + cfgHistory->SetAttribute("MaximumSize", globalFunctions::numberToString(inputCfg.gui.cfgHistoryMax)); addXmlElementTable(cfgHistory, "File", inputCfg.gui.cfgFileHistory); //################################################################### @@ -988,10 +1038,6 @@ int xmlAccess::retrieveSystemLanguage() case wxLANGUAGE_CHINESE_TAIWAN: return wxLANGUAGE_CHINESE_SIMPLIFIED; - //variants of wxLANGUAGE_PORTUGUESE - case wxLANGUAGE_PORTUGUESE_BRAZILIAN: - return wxLANGUAGE_PORTUGUESE; - //variants of wxLANGUAGE_SPANISH case wxLANGUAGE_SPANISH_ARGENTINA: case wxLANGUAGE_SPANISH_BOLIVIA: @@ -1019,6 +1065,8 @@ int xmlAccess::retrieveSystemLanguage() //case wxLANGUAGE_POLISH: //case wxLANGUAGE_SLOVENIAN: //case wxLANGUAGE_HUNGARIAN: + //case wxLANGUAGE_PORTUGUESE: + //case wxLANGUAGE_PORTUGUESE_BRAZILIAN: default: return lang; @@ -1048,4 +1096,5 @@ void xmlAccess::XmlGlobalSettings::_Shared::resetWarnings() { warningDependentFolders = true; warningSignificantDifference = true; + warningNotEnoughDiskSpace = true; } diff --git a/library/processXml.h b/library/processXml.h index c24ad0e7..763edbca 100644 --- a/library/processXml.h +++ b/library/processXml.h @@ -1,14 +1,14 @@ #ifndef PROCESSXML_H_INCLUDED #define PROCESSXML_H_INCLUDED -#include "../FreeFileSync.h" -#include "tinyxml/tinyxml.h" - -using namespace FreeFileSync; - +#include "../structures.h" +#include "fileHandling.h" namespace xmlAccess { + extern const wxString LAST_CONFIG_FILE; + extern const wxString GLOBAL_CONFIG_FILE; + enum OnError { ON_ERROR_POPUP, @@ -30,9 +30,10 @@ namespace xmlAccess REL_PATH, SIZE, DATE, - FULL_NAME + FULL_NAME, + DIRECTORY }; - const unsigned COLUMN_TYPE_COUNT = 5; + const unsigned COLUMN_TYPE_COUNT = 6; struct ColumnAttrib { @@ -48,13 +49,28 @@ namespace xmlAccess struct XmlGuiConfig { - XmlGuiConfig() : hideFilteredElements(false), ignoreErrors(false) {} //initialize values + XmlGuiConfig() : + hideFilteredElements(false), + ignoreErrors(false) {} //initialize values - MainConfiguration mainCfg; - std::vector<FolderPair> directoryPairs; + FreeFileSync::MainConfiguration mainCfg; + std::vector<FreeFileSync::FolderPair> directoryPairs; bool hideFilteredElements; bool ignoreErrors; //reaction on error situation during synchronization + + bool operator==(const XmlGuiConfig& other) const + { + return mainCfg == other.mainCfg && + directoryPairs == other.directoryPairs && + hideFilteredElements == other.hideFilteredElements && + ignoreErrors == other.ignoreErrors; + } + + bool operator!=(const XmlGuiConfig& other) const + { + return !(*this == other); + } }; @@ -62,8 +78,8 @@ namespace xmlAccess { XmlBatchConfig() : silent(false), handleError(ON_ERROR_POPUP) {} - MainConfiguration mainCfg; - std::vector<FolderPair> directoryPairs; + FreeFileSync::MainConfiguration mainCfg; + std::vector<FreeFileSync::FolderPair> directoryPairs; bool silent; OnError handleError; //reaction on error situation during synchronization @@ -83,7 +99,8 @@ namespace xmlAccess programLanguage(retrieveSystemLanguage()), fileTimeTolerance(2), //default 2s: FAT vs NTFS traverseDirectorySymlinks(false), - copyFileSymlinks(supportForSymbolicLinks()) + copyFileSymlinks(supportForSymbolicLinks()), + lastUpdateCheck(0) { resetWarnings(); } @@ -92,12 +109,14 @@ namespace xmlAccess unsigned fileTimeTolerance; //max. allowed file time deviation bool traverseDirectorySymlinks; bool copyFileSymlinks; //copy symbolic link instead of target file + long lastUpdateCheck; //time of last update check //warnings void resetWarnings(); bool warningDependentFolders; bool warningSignificantDifference; + bool warningNotEnoughDiskSpace; } shared; //--------------------------------------------------------------------- @@ -114,10 +133,13 @@ namespace xmlAccess #elif defined FFS_LINUX commandLineFileManager(wxT("konqueror \"%path\"")), #endif - cfgHistoryMaxItems(10), + cfgHistoryMax(10), + folderHistLeftMax(12), + folderHistRightMax(12), deleteOnBothSides(false), useRecyclerForManualDeletion(FreeFileSync::recycleBinExists()), //enable if OS supports it; else user will have to activate first and then get an error message - showFileIcons(true) {} + showFileIcons(true), + popupOnConfigChange(true) {} int widthNotMaximized; int heightNotMaximized; @@ -127,14 +149,22 @@ namespace xmlAccess ColumnAttributes columnAttribLeft; ColumnAttributes columnAttribRight; + wxString commandLineFileManager; + std::vector<wxString> cfgFileHistory; - unsigned cfgHistoryMaxItems; + unsigned int cfgHistoryMax; + std::vector<wxString> folderHistoryLeft; + unsigned int folderHistLeftMax; + std::vector<wxString> folderHistoryRight; + unsigned int folderHistRightMax; + bool deleteOnBothSides; bool useRecyclerForManualDeletion; bool showFileIcons; + bool popupOnConfigChange; } gui; //--------------------------------------------------------------------- diff --git a/library/resources.cpp b/library/resources.cpp index f8624ed3..23d1ac37 100644 --- a/library/resources.cpp +++ b/library/resources.cpp @@ -83,7 +83,6 @@ GlobalResources::GlobalResources() bitmapResource[wxT("statusComparing.png")] = (bitmapStatusComparing = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("statusSyncing.png")] = (bitmapStatusSyncing = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("logo.png")] = (bitmapLogo = new wxBitmap(wxNullBitmap)); - bitmapResource[wxT("finished.png")] = (bitmapFinished = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("statusEdge.png")] = (bitmapStatusEdge = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("add pair.png")] = (bitmapAddFolderPair = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("remove pair.png")] = (bitmapRemoveFolderPair = new wxBitmap(wxNullBitmap)); @@ -105,12 +104,25 @@ GlobalResources::GlobalResources() bitmapResource[wxT("settings_small.png")] = (bitmapSettingsSmall = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("recycler.png")] = (bitmapRecycler = new wxBitmap(wxNullBitmap)); bitmapResource[wxT("shift.png")] = (bitmapShift = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("question.png")] = (bitmapQuestion = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("china.png")] = (bitmapChina = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("holland.png")] = (bitmapHolland = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("england.png")] = (bitmapEngland = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("france.png")] = (bitmapFrance = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("germany.png")] = (bitmapGermany = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("hungary.png")] = (bitmapHungary = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("italy.png")] = (bitmapItaly = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("japan.png")] = (bitmapJapan = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("poland.png")] = (bitmapPoland = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("portugal.png")] = (bitmapPortugal = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("brazil.png")] = (bitmapBrazil = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("slovakia.png")] = (bitmapSlovakia = new wxBitmap(wxNullBitmap)); + bitmapResource[wxT("spain.png")] = (bitmapSpain = new wxBitmap(wxNullBitmap)); //init all the other resource files animationMoney = new wxAnimation(wxNullAnimation); animationSync = new wxAnimation(wxNullAnimation); - - programIcon = &wxNullIcon; + programIcon = new wxIcon(wxNullIcon); } @@ -159,7 +171,7 @@ void GlobalResources::load() std::map<wxString, wxBitmap*>::iterator bmp; while ((entry = resourceFile.GetNextEntry())) { - wxString name = entry->GetName(); + const wxString name = entry->GetName(); //search if entry is available in map if ((bmp = bitmapResource.find(name)) != bitmapResource.end()) @@ -172,9 +184,9 @@ void GlobalResources::load() } #ifdef FFS_WIN - programIcon = new wxIcon(wxT("ffsIcon1")); + *programIcon = wxIcon(wxT("ffsIcon1")); #else #include "FreeFileSync.xpm" - programIcon = new wxIcon(FreeFileSync_xpm); + *programIcon = wxIcon(FreeFileSync_xpm); #endif } diff --git a/library/resources.h b/library/resources.h index 6bac0d86..5de79ec2 100644 --- a/library/resources.h +++ b/library/resources.h @@ -82,7 +82,6 @@ public: wxBitmap* bitmapStatusComparing; wxBitmap* bitmapStatusSyncing; wxBitmap* bitmapLogo; - wxBitmap* bitmapFinished; wxBitmap* bitmapStatusEdge; wxBitmap* bitmapAddFolderPair; wxBitmap* bitmapRemoveFolderPair; @@ -104,6 +103,20 @@ public: wxBitmap* bitmapSettingsSmall; wxBitmap* bitmapRecycler; wxBitmap* bitmapShift; + wxBitmap* bitmapQuestion; + wxBitmap* bitmapChina; + wxBitmap* bitmapHolland; + wxBitmap* bitmapEngland; + wxBitmap* bitmapFrance; + wxBitmap* bitmapGermany; + wxBitmap* bitmapHungary; + wxBitmap* bitmapItaly; + wxBitmap* bitmapJapan; + wxBitmap* bitmapPoland; + wxBitmap* bitmapPortugal; + wxBitmap* bitmapBrazil; + wxBitmap* bitmapSlovakia; + wxBitmap* bitmapSpain; wxAnimation* animationMoney; wxAnimation* animationSync; diff --git a/library/sorting.h b/library/sorting.h deleted file mode 100644 index 171cca6d..00000000 --- a/library/sorting.h +++ /dev/null @@ -1,325 +0,0 @@ -#ifndef SORTING_H_INCLUDED -#define SORTING_H_INCLUDED - -#include "../FreeFileSync.h" -#include "resources.h" -#include "globalFunctions.h" - -using namespace FreeFileSync; - -enum SideToSort -{ - SORT_ON_LEFT, - SORT_ON_RIGHT, -}; - - -template <SideToSort side> -inline -void getDescrLine(const FileCompareLine& a, const FileCompareLine& b, const FileDescrLine*& descrLineA, const FileDescrLine*& descrLineB) -{ - if (side == SORT_ON_LEFT) - { - descrLineA = &a.fileDescrLeft; - descrLineB = &b.fileDescrLeft; - } - else if (side == SORT_ON_RIGHT) - { - descrLineA = &a.fileDescrRight; - descrLineB = &b.fileDescrRight; - } - else - assert(false); -} - - -template <bool sortAscending> -inline -bool stringSmallerThan(const wxChar* stringA, const wxChar* stringB) -{ -#ifdef FFS_WIN //case-insensitive comparison! - return sortAscending ? - FreeFileSync::compareStringsWin32(stringA, stringB) < 0 : //way faster than wxString::CmpNoCase() in windows build!!! - FreeFileSync::compareStringsWin32(stringA, stringB) > 0; -#else - while (*stringA == *stringB) - { - if (*stringA == wxChar(0)) //strings are equal - return false; - - ++stringA; - ++stringB; - } - return sortAscending ? *stringA < *stringB : *stringA > *stringB; //wxChar(0) is handled correctly -#endif -} - - -inline -int compareString(const wxChar* stringA, const wxChar* stringB, const int lengthA, const int lengthB) -{ -#ifdef FFS_WIN //case-insensitive comparison! - return FreeFileSync::compareStringsWin32(stringA, stringB, lengthA, lengthB); //way faster than wxString::CmpNoCase() in the windows build!!! -#else - int i = 0; - if (lengthA == lengthB) - { - for (i = 0; i < lengthA; ++i) - { - if (stringA[i] != stringB[i]) - break; - } - return i == lengthA ? 0 : stringA[i] < stringB[i] ? -1 : 1; - } - else if (lengthA < lengthB) - { - for (i = 0; i < lengthA; ++i) - { - if (stringA[i] != stringB[i]) - break; - } - return i == lengthA ? -1 : stringA[i] < stringB[i] ? -1 : 1; - } - else - { - for (i = 0; i < lengthB; ++i) - { - if (stringA[i] != stringB[i]) - break; - } - return i == lengthB ? 1 : stringA[i] < stringB[i] ? -1 : 1; - } -#endif -} - - -template <bool sortAscending, SideToSort side> -inline -bool sortByFileName(const FileCompareLine& a, const FileCompareLine& b) -{ - const FileDescrLine* descrLineA = NULL; - const FileDescrLine* descrLineB = NULL; - getDescrLine<side>(a, b, descrLineA, descrLineB); - - //presort types: first files, then directories then empty rows - if (descrLineA->objType == FileDescrLine::TYPE_NOTHING) - return false; //empty rows always last - else if (descrLineB->objType == FileDescrLine::TYPE_NOTHING) - return true; //empty rows always last - - - if (descrLineA->objType == FileDescrLine::TYPE_DIRECTORY) //sort directories by relative name - { - if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) - return stringSmallerThan<sortAscending>(descrLineA->relativeName.c_str(), descrLineB->relativeName.c_str()); - else - return false; - } - else - { - if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) - return true; - else - { - const wxChar* stringA = descrLineA->relativeName.c_str(); - const wxChar* stringB = descrLineB->relativeName.c_str(); - - size_t pos = descrLineA->relativeName.findFromEnd(GlobalResources::FILE_NAME_SEPARATOR); //start search beginning from end - if (pos != std::string::npos) - stringA += pos + 1; - - pos = descrLineB->relativeName.findFromEnd(GlobalResources::FILE_NAME_SEPARATOR); //start search beginning from end - if (pos != std::string::npos) - stringB += pos + 1; - - return stringSmallerThan<sortAscending>(stringA, stringB); - } - } -} - - -template <bool sortAscending, SideToSort side> -bool sortByRelativeName(const FileCompareLine& a, const FileCompareLine& b) -{ - const FileDescrLine* descrLineA = NULL; - const FileDescrLine* descrLineB = NULL; - getDescrLine<side>(a, b, descrLineA, descrLineB); - - //extract relative name and filename - const wxChar* relStringA = descrLineA->relativeName.c_str(); //mustn't be NULL for CompareString() API to work correctly - const wxChar* fileStringA = relStringA; - int relLengthA = 0; - int fileLengthA = 0; - - if (descrLineA->objType == FileDescrLine::TYPE_DIRECTORY) - relLengthA = descrLineA->relativeName.length(); - else if (descrLineA->objType == FileDescrLine::TYPE_FILE) - { - relLengthA = descrLineA->relativeName.findFromEnd(GlobalResources::FILE_NAME_SEPARATOR); //start search beginning from end - if (relLengthA == wxNOT_FOUND) - { - relLengthA = 0; - fileLengthA = descrLineA->relativeName.length(); - } - else - { - fileStringA += relLengthA + 1; - fileLengthA = descrLineA->relativeName.length() - (relLengthA + 1); - } - } - else - return false; //empty rows should be on end of list - - - const wxChar* relStringB = descrLineB->relativeName.c_str(); //mustn't be NULL for CompareString() API to work correctly - const wxChar* fileStringB = relStringB; - int relLengthB = 0; - int fileLengthB = 0; - - if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) - relLengthB = descrLineB->relativeName.length(); - else if (descrLineB->objType == FileDescrLine::TYPE_FILE) - { - relLengthB = descrLineB->relativeName.findFromEnd(GlobalResources::FILE_NAME_SEPARATOR); //start search beginning from end - if (relLengthB == wxNOT_FOUND) - { - relLengthB = 0; - fileLengthB = descrLineB->relativeName.length(); - } - else - { - fileStringB += relLengthB + 1; - fileLengthB = descrLineB->relativeName.length() - (relLengthB + 1); - } - } - else - return true; //empty rows should be on end of list - - //compare relative names without filenames first - int rv = compareString(relStringA, relStringB, relLengthA, relLengthB); - if (rv != 0) - return sortAscending ? (rv < 0) : (rv > 0); - else //compare the filenames - { - if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) //directories shall appear before files - return false; - else if (descrLineA->objType == FileDescrLine::TYPE_DIRECTORY) - return true; - - return sortAscending ? - compareString(fileStringA, fileStringB, fileLengthA, fileLengthB) < 0 : - compareString(fileStringA, fileStringB, fileLengthA, fileLengthB) > 0; - } -} - - -template <bool sortAscending, SideToSort side> -inline -bool sortByFullName(const FileCompareLine& a, const FileCompareLine& b) -{ - const FileDescrLine* descrLineA = NULL; - const FileDescrLine* descrLineB = NULL; - getDescrLine<side>(a, b, descrLineA, descrLineB); - - //presort types: first files, then directories then empty rows - if (descrLineA->objType == FileDescrLine::TYPE_NOTHING) - return false; //empty rows always last - else if (descrLineB->objType == FileDescrLine::TYPE_NOTHING) - return true; //empty rows always last - else -#ifdef FFS_WIN //case-insensitive comparison! - return sortAscending ? - FreeFileSync::compareStringsWin32(descrLineA->fullName.c_str(), descrLineB->fullName.c_str()) < 0 : //way faster than wxString::CmpNoCase() in windows build!!! - FreeFileSync::compareStringsWin32(descrLineA->fullName.c_str(), descrLineB->fullName.c_str()) > 0; -#else - return sortAscending ? - descrLineA->fullName.Cmp(descrLineB->fullName) < 0 : - descrLineA->fullName.Cmp(descrLineB->fullName) > 0; -#endif -} - - -template <bool sortAscending, SideToSort side> -inline -bool sortByFileSize(const FileCompareLine& a, const FileCompareLine& b) -{ - const FileDescrLine* descrLineA = NULL; - const FileDescrLine* descrLineB = NULL; - getDescrLine<side>(a, b, descrLineA, descrLineB); - - //presort types: first files, then directories then empty rows - if (descrLineA->objType == FileDescrLine::TYPE_NOTHING) - return false; //empty rows always last - else if (descrLineB->objType == FileDescrLine::TYPE_NOTHING) - return true; //empty rows always last - - - if (descrLineA->objType == FileDescrLine::TYPE_DIRECTORY) //sort directories by relative name - { - if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) - return stringSmallerThan<sortAscending>(descrLineA->relativeName.c_str(), descrLineB->relativeName.c_str()); - else - return false; - } - else - { - if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) - return true; - else - return sortAscending ? - descrLineA->fileSize > descrLineB->fileSize : //sortAscending == true shall result in list beginning with largest files first - descrLineA->fileSize < descrLineB->fileSize; - } -} - - -template <bool sortAscending, SideToSort side> -inline -bool sortByDate(const FileCompareLine& a, const FileCompareLine& b) -{ - const FileDescrLine* descrLineA = NULL; - const FileDescrLine* descrLineB = NULL; - getDescrLine<side>(a, b, descrLineA, descrLineB); - - //presort types: first files, then directories then empty rows - if (descrLineA->objType == FileDescrLine::TYPE_NOTHING) - return false; //empty rows always last - else if (descrLineB->objType == FileDescrLine::TYPE_NOTHING) - return true; //empty rows always last - - if (descrLineA->objType == FileDescrLine::TYPE_DIRECTORY) //sort directories by relative name - { - if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) - return stringSmallerThan<sortAscending>(descrLineA->relativeName.c_str(), descrLineB->relativeName.c_str()); - else - return false; - } - else - { - if (descrLineB->objType == FileDescrLine::TYPE_DIRECTORY) - return true; - else - return sortAscending ? - descrLineA->lastWriteTimeRaw < descrLineB->lastWriteTimeRaw : - descrLineA->lastWriteTimeRaw > descrLineB->lastWriteTimeRaw; - } -} - - -template <bool sortAscending> -inline -bool sortByCmpResult(const FileCompareLine& a, const FileCompareLine& b) -{ - //presort result: equal shall appear at end of list - if (a.cmpResult == FILE_EQUAL) - return false; - if (b.cmpResult == FILE_EQUAL) - return true; - - return sortAscending ? - a.cmpResult < b.cmpResult : - a.cmpResult > b.cmpResult; -} - - -#endif // SORTING_H_INCLUDED diff --git a/library/statistics.cpp b/library/statistics.cpp new file mode 100644 index 00000000..ec03c59e --- /dev/null +++ b/library/statistics.cpp @@ -0,0 +1,320 @@ +#include "statistics.h" + +#include <wx/ffile.h> +#include "globalFunctions.h" +#include "statusHandler.h" +#include "../algorithm.h" +#include <limits> + + +RetrieveStatistics::~RetrieveStatistics() +{ //write statistics to a file + wxFFile outputFile(wxT("statistics.dat"), wxT("w")); + + outputFile.Write(wxT("Time(ms);Objects;Data\n")); + + for (std::vector<statEntry>::const_iterator i = data.begin(); i != data.end(); ++i) + { + outputFile.Write(globalFunctions::numberToWxString(int(i->time))); + outputFile.Write(wxT(";")); + outputFile.Write(globalFunctions::numberToWxString(i->objects)); + outputFile.Write(wxT(";")); + outputFile.Write(globalFunctions::numberToWxString(float(i->value))); + outputFile.Write(wxT("\n")); + } +} + + +void RetrieveStatistics::writeEntry(const double value, const int objects) +{ + statEntry newEntry; + newEntry.value = value; + newEntry.objects = objects; + newEntry.time = timer.Time(); + data.push_back(newEntry); +} + + +//######################################################################################## + +inline +bool isNull(const double number) +{ + return globalFunctions::abs(number) <= std::numeric_limits<double>::epsilon(); +} + + +inline +wxString Statistics::formatRemainingTime(const double timeInMs) const +{ + bool unitSec = true; + double remainingTime = timeInMs / 1000; + wxString unit = _(" sec"); + if (remainingTime > 55) + { + unitSec = false; + remainingTime /= 60; + unit = _(" min"); + if (remainingTime > 59) + { + remainingTime /= 60; + unit = _(" hour(s)"); + if (remainingTime > 23) + { + remainingTime /= 24; + unit = _(" day(s)"); + } + } + } + + + int formattedTime = globalFunctions::round(remainingTime); + + //reduce precision to 5 seconds + if (unitSec && formattedTime % 5 != 0) + formattedTime += 5 - formattedTime % 5; //"ceiling" + + + //avoid "jumping back and forth" when fluctuating around .5 + if (remainingTimeLast < formattedTime) + { + if (unitSec) + { + formattedTime = globalFunctions::round(remainingTime); + formattedTime -= formattedTime % 5; //"floor" + } + else + formattedTime = int(remainingTime); //"floor" + } + remainingTimeLast = formattedTime; + + return globalFunctions::numberToWxString(formattedTime) + unit; + //+ wxT("(") + globalFunctions::numberToWxString(globalFunctions::round(timeInMs / 1000)) + wxT(")"); +} + + +Statistics::Statistics(const int totalObjectCount, + const double totalDataAmount, + const unsigned windowSizeRemainingTime, + const unsigned windowSizeBytesPerSecond) : + objectsTotal(totalObjectCount), + dataTotal(totalDataAmount), + windowSizeRemTime(windowSizeRemainingTime), + windowSizeBPS(windowSizeBytesPerSecond), + windowMax(std::max(windowSizeRemainingTime, windowSizeBytesPerSecond)), + remainingTimeLast(256*256*256*100) //something "big" +{} + + +void Statistics::addMeasurement(const int objectsCurrent, const double dataCurrent) +{ + record newEntry; + newEntry.objects = objectsCurrent; + newEntry.data = dataCurrent; + newEntry.time = timer.Time(); + + //insert new record + measurements.push_back(newEntry); + + //remove all records earlier than "currentTime - windowSize" + const long newBegin = newEntry.time - windowMax; + while (measurements.size() > 0 && measurements.front().time < newBegin) + measurements.pop_front(); +} + + +wxString Statistics::getRemainingTime() const +{ + if (measurements.size() > 0) + { + //find start of records "window" + const record backElement = measurements.back(); + const long frontTime = backElement.time - windowSizeRemTime; + std::list<record>::const_iterator frontElement = measurements.end(); + do + { + --frontElement; + } + while (frontElement != measurements.begin() && frontElement->time > frontTime); + + const double timeDelta = backElement.time - frontElement->time; + const double dataDelta = backElement.data - frontElement->data; + + const double dataRemaining = dataTotal - backElement.data; + + if (!isNull(dataDelta)) + return formatRemainingTime(dataRemaining * timeDelta / dataDelta); + } + + return wxT("-"); //fallback +} + + +wxString Statistics::getBytesPerSecond() const +{ + if (measurements.size() > 0) + { + //find start of records "window" + const long frontTime = measurements.back().time - windowSizeBPS; + std::list<record>::const_iterator frontElement = measurements.end(); + do + { + --frontElement; + } + while (frontElement != measurements.begin() && frontElement->time > frontTime); + + const double timeDelta = measurements.back().time - frontElement->time; + const double dataDelta = measurements.back().data - frontElement->data; + + if (!isNull(timeDelta)) + return FreeFileSync::formatFilesizeToShortString(dataDelta * 1000 / timeDelta) + _("/sec"); + } + + return wxT("-"); //fallback +} + + +void Statistics::pauseTimer() +{ + timer.Pause(); +} + + +void Statistics::resumeTimer() +{ + timer.Resume(); +} + +/* +class for calculation of remaining time: +---------------------------------------- +"filesize |-> time" is an affine linear function f(x) = z_1 + z_2 x + +For given n measurements, sizes x_0, ..., x_n and times f_0, ..., f_n, the function f (as a polynom of degree 1) can be lineary approximated by + +z_1 = (r - s * q / p) / ((n + 1) - s * s / p) +z_2 = (q - s * z_1) / p = (r - (n + 1) z_1) / s + +with +p := x_0^2 + ... + x_n^2 +q := f_0 x_0 + ... + f_n x_n +r := f_0 + ... + f_n +s := x_0 + ... + x_n + +=> the time to process N files with amount of data D is: N * z_1 + D * z_2 + +Problem: +-------- +Times f_0, ..., f_n can be very small so that precision of the PC clock is poor. +=> Times have to be accumulated to enhance precision: +Copying of m files with sizes x_i and times f_i (i = 1, ..., m) takes sum_i f(x_i) := m * z_1 + z_2 * sum x_i = sum f_i +With X defined as the accumulated sizes and F the accumulated times this gives: (in theory...) +m * z_1 + z_2 * X = F <=> +z_1 + z_2 * X / m = F / m + +=> we obtain a new (artificial) measurement with size X / m and time F / m to be used in the linear approximation above + + +Statistics::Statistics(const int totalObjectCount, const double totalDataAmount, const unsigned recordCount) : + objectsTotal(totalObjectCount), + dataTotal(totalDataAmount), + recordsMax(recordCount), + objectsLast(0), + dataLast(0), + timeLast(wxGetLocalTimeMillis()), + z1_current(0), + z2_current(0), + dummyRecordPresent(false) {} + + +wxString Statistics::getRemainingTime(const int objectsCurrent, const double dataCurrent) +{ + //add new measurement point + const int m = objectsCurrent - objectsLast; + if (m != 0) + { + objectsLast = objectsCurrent; + + const double X = dataCurrent - dataLast; + dataLast = dataCurrent; + + const wxLongLong timeCurrent = wxGetLocalTimeMillis(); + const double F = (timeCurrent - timeLast).ToDouble(); + timeLast = timeCurrent; + + record newEntry; + newEntry.x_i = X / m; + newEntry.f_i = F / m; + + //remove dummy record + if (dummyRecordPresent) + { + measurements.pop_back(); + dummyRecordPresent = false; + } + + //insert new record + measurements.push_back(newEntry); + if (measurements.size() > recordsMax) + measurements.pop_front(); + } + else //dataCurrent increased without processing new objects: + { //modify last measurement until m != 0 + const double X = dataCurrent - dataLast; //do not set dataLast, timeLast variables here, but write dummy record instead + if (!isNull(X)) + { + const wxLongLong timeCurrent = wxGetLocalTimeMillis(); + const double F = (timeCurrent - timeLast).ToDouble(); + + record modifyEntry; + modifyEntry.x_i = X; + modifyEntry.f_i = F; + + //insert dummy record + if (!dummyRecordPresent) + { + measurements.push_back(modifyEntry); + if (measurements.size() > recordsMax) + measurements.pop_front(); + dummyRecordPresent = true; + } + else //modify dummy record + measurements.back() = modifyEntry; + } + } + + //calculate remaining time based on stored measurement points + double p = 0; + double q = 0; + double r = 0; + double s = 0; + for (std::list<record>::const_iterator i = measurements.begin(); i != measurements.end(); ++i) + { + const double x_i = i->x_i; + const double f_i = i->f_i; + p += x_i * x_i; + q += f_i * x_i; + r += f_i; + s += x_i; + } + + if (!isNull(p)) + { + const double n = measurements.size(); + const double tmp = (n - s * s / p); + + if (!isNull(tmp) && !isNull(s)) + { + const double z1 = (r - s * q / p) / tmp; + const double z2 = (r - n * z1) / s; //not (n + 1) here, since n already is the number of measurements + + //refresh current values for z1, z2 + z1_current = z1; + z2_current = z2; + } + } + + return formatRemainingTime((objectsTotal - objectsCurrent) * z1_current + (dataTotal - dataCurrent) * z2_current); +} + +*/ diff --git a/library/statistics.h b/library/statistics.h new file mode 100644 index 00000000..3d4179db --- /dev/null +++ b/library/statistics.h @@ -0,0 +1,67 @@ +#ifndef STATISTICS_H_INCLUDED +#define STATISTICS_H_INCLUDED + +#include <vector> +#include <wx/stopwatch.h> +#include <list> + +class RetrieveStatistics +{ +public: + wxDEPRECATED(RetrieveStatistics() {}) //generate compiler warnings as a reminder to remove code after measurements + ~RetrieveStatistics(); + + void writeEntry(const double value, const int objects); + +private: + struct statEntry + { + long time; + int objects; + double value; + }; + + std::vector<statEntry> data; + wxStopWatch timer; +}; + + +class Statistics +{ +public: + Statistics(const int totalObjectCount, + const double totalDataAmount, + const unsigned windowSizeRemainingTime, //time in ms + const unsigned windowSizeBytesPerSecond); //time in ms + + void addMeasurement(const int objectsCurrent, const double dataCurrent); + wxString getRemainingTime() const; //returns the remaining time in milliseconds + wxString getBytesPerSecond() const; + + void pauseTimer(); + void resumeTimer(); + +private: + wxString formatRemainingTime(const double timeInMs) const; + + const int objectsTotal; + const double dataTotal; + + const unsigned windowSizeRemTime; //"window width" of statistics used for calculation of remaining time in ms + const unsigned windowSizeBPS; // + const unsigned windowMax; + + mutable int remainingTimeLast; //used for "smoothening" remaining time + + struct record + { + int objects; + double data; + long time; + }; + + std::list<record> measurements; + wxStopWatch timer; +}; + +#endif // STATISTICS_H_INCLUDED diff --git a/library/statusHandler.h b/library/statusHandler.h index 11517efb..18d9f129 100644 --- a/library/statusHandler.h +++ b/library/statusHandler.h @@ -1,7 +1,9 @@ #ifndef STATUSHANDLER_H_INCLUDED #define STATUSHANDLER_H_INCLUDED -#include "zstring.h" +#include <wx/longlong.h> + +class Zstring; const int UI_UPDATE_INTERVAL = 100; //perform ui updates not more often than necessary, 100 seems to be a good value with only a minimal performance loss @@ -45,8 +47,8 @@ public: //these methods have to be implemented in the derived classes to handle error and status information virtual void updateStatusText(const Zstring& text) = 0; - virtual void initNewProcess(int objectsTotal, double dataTotal, Process processID) = 0; //informs about the total amount of data that will be processed from now on - virtual void updateProcessedData(int objectsProcessed, double dataProcessed) = 0; //called periodically after data was processed + virtual void initNewProcess(int objectsTotal, wxLongLong dataTotal, Process processID) = 0; //informs about the total amount of data that will be processed from now on + virtual void updateProcessedData(int objectsProcessed, wxLongLong dataProcessed) = 0; //called periodically after data was processed //this method is triggered repeatedly by requestUiRefresh() and can be used to refresh the ui by dispatching pending events virtual void forceUiRefresh() = 0; diff --git a/library/zstring.cpp b/library/zstring.cpp index d704e741..abded9d0 100644 --- a/library/zstring.cpp +++ b/library/zstring.cpp @@ -2,23 +2,28 @@ #include <wx/intl.h> #include "globalFunctions.h" - #ifdef FFS_WIN #include <wx/msw/wrapwin.h> //includes "windows.h" #endif //FFS_WIN -#ifdef __WXDEBUG__ -int allocCount = 0; //test Zstring for memory leaks -void testZstringForMemoryLeak() +#ifdef __WXDEBUG__ +AllocationCount::~AllocationCount() { - if (allocCount != 0) + if (count != 0) #ifdef FFS_WIN - MessageBox(NULL, wxT("Fatal Error! Allocation problem with Zstring! (No problem if it occures while Unit testing only!)"), wxString::Format(wxT("%i"), allocCount), 0); + MessageBox(NULL, wxT("Fatal Error! Allocation problem with Zstring! (No problem if it occurs while Unit testing only!)"), wxString::Format(wxT("%i"), count), 0); #else - throw; -#endif //FFS_WIN + std::abort(); +#endif +} + + +AllocationCount& AllocationCount::getGlobal() +{ + static AllocationCount global; + return global; } #endif @@ -45,11 +50,10 @@ int FreeFileSync::compareStringsWin32(const wchar_t* a, const wchar_t* b, const #endif -size_t Zstring::Replace(const DefaultChar* old, const DefaultChar* replacement, bool replaceAll) +Zstring& Zstring::Replace(const DefaultChar* old, const DefaultChar* replacement, bool replaceAll) { const size_t oldLen = defaultLength(old); const size_t replacementLen = defaultLength(replacement); - size_t uiCount = 0; //count of replacements made size_t pos = 0; while (true) @@ -61,13 +65,11 @@ size_t Zstring::Replace(const DefaultChar* old, const DefaultChar* replacement, replace(pos, oldLen, replacement, replacementLen); pos += replacementLen; //move past the string that was replaced - ++uiCount; //increase replace count - // stop now? if (!replaceAll) break; } - return uiCount; + return *this; } diff --git a/library/zstring.h b/library/zstring.h index 2a10efa9..2b6bc475 100644 --- a/library/zstring.h +++ b/library/zstring.h @@ -61,7 +61,7 @@ public: #endif int Cmp(const DefaultChar* other) const; int Cmp(const Zstring& other) const; - size_t Replace(const DefaultChar* old, const DefaultChar* replacement, bool replaceAll = true); + Zstring& Replace(const DefaultChar* old, const DefaultChar* replacement, bool replaceAll = true); Zstring AfterLast(DefaultChar ch) const; Zstring BeforeLast(DefaultChar ch) const; size_t Find(DefaultChar ch, bool fromEnd) const; @@ -87,10 +87,12 @@ public: Zstring& operator=(const Zstring& source); Zstring& operator=(const DefaultChar* source); - bool operator==(const Zstring& other) const; - bool operator==(const DefaultChar* other) const; - bool operator!=(const Zstring& other) const; - bool operator!=(const DefaultChar* other) const; + bool operator == (const Zstring& other) const; + bool operator == (const DefaultChar* other) const; + bool operator < (const Zstring& other) const; + bool operator < (const DefaultChar* other) const; + bool operator != (const Zstring& other) const; + bool operator != (const DefaultChar* other) const; DefaultChar operator[](const size_t pos) const; @@ -98,9 +100,9 @@ public: Zstring& operator+=(const DefaultChar* other); Zstring& operator+=(DefaultChar ch); - Zstring operator+(const Zstring& string2) const; - Zstring operator+(const DefaultChar* string2) const; - Zstring operator+(const DefaultChar ch) const; + const Zstring operator+(const Zstring& string2) const; + const Zstring operator+(const DefaultChar* string2) const; + const Zstring operator+(const DefaultChar ch) const; static const size_t npos = static_cast<size_t>(-1); @@ -235,8 +237,27 @@ wchar_t defaultToLower(const wchar_t ch) #ifdef __WXDEBUG__ -extern int allocCount; //test Zstring for memory leaks -void testZstringForMemoryLeak(); +class AllocationCount //small test for memory leaks in Zstring +{ +public: + void inc() + { + ++count; + } + + void dec() + { + --count; + } + + static AllocationCount& getGlobal(); + +private: + AllocationCount() : count(0) {} + ~AllocationCount(); + + int count; +}; #endif @@ -265,14 +286,7 @@ void Zstring::allocate(const size_t newLength, newDescr->capacity = newCapacity; #ifdef __WXDEBUG__ - ++allocCount; //test Zstring for memory leaks - - static bool isRegistered = false; - if (!isRegistered) - { - isRegistered = true; - atexit(testZstringForMemoryLeak); - } + AllocationCount::getGlobal().inc(); //test Zstring for memory leaks #endif } @@ -287,7 +301,7 @@ Zstring::Zstring() static Zstring emptyString(L""); #endif - emptyString.incRef(); //implicitly handle case "this == &source" and avoid this check + emptyString.incRef(); descr = emptyString.descr; data = emptyString.data; } @@ -347,9 +361,9 @@ void Zstring::decRef() if (--descr->refCount == 0) { free(descr); //this must NEVER be changed!! E.g. Trim() relies on descr being start of allocated memory block - descr = 0; + descr = NULL; #ifdef __WXDEBUG__ - --allocCount; //test Zstring for memory leaks + AllocationCount::getGlobal().dec(); //test Zstring for memory leaks #endif } } @@ -504,28 +518,42 @@ int Zstring::Cmp(const Zstring& other) const inline -bool Zstring::operator==(const Zstring& other) const +bool Zstring::operator == (const Zstring& other) const { return length() != other.length() ? false : defaultCompare(c_str(), other.c_str()) == 0; } inline -bool Zstring::operator==(const DefaultChar* other) const +bool Zstring::operator == (const DefaultChar* other) const { return defaultCompare(c_str(), other) == 0; //overload using strcmp(char*, char*) should be fastest! } inline -bool Zstring::operator!=(const Zstring& other) const +bool Zstring::operator < (const Zstring& other) const +{ + return defaultCompare(c_str(), other.c_str()) < 0; +} + + +inline +bool Zstring::operator < (const DefaultChar* other) const +{ + return defaultCompare(c_str(), other) < 0; //overload using strcmp(char*, char*) should be fastest! +} + + +inline +bool Zstring::operator != (const Zstring& other) const { return length() != other.length() ? true: defaultCompare(c_str(), other.c_str()) != 0; } inline -bool Zstring::operator!=(const DefaultChar* other) const +bool Zstring::operator != (const DefaultChar* other) const { return defaultCompare(c_str(), other) != 0; //overload using strcmp(char*, char*) should be fastest! } @@ -590,21 +618,21 @@ DefaultChar Zstring::operator[](const size_t pos) const inline -Zstring Zstring::operator+(const Zstring& string2) const +const Zstring Zstring::operator+(const Zstring& string2) const { return Zstring(*this)+=string2; } inline -Zstring Zstring::operator+(const DefaultChar* string2) const +const Zstring Zstring::operator+(const DefaultChar* string2) const { return Zstring(*this)+=string2; } inline -Zstring Zstring::operator+(const DefaultChar ch) const +const Zstring Zstring::operator+(const DefaultChar ch) const { return Zstring(*this)+=ch; } |