summaryrefslogtreecommitdiff
path: root/ui/custom_grid.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ui/custom_grid.cpp')
-rw-r--r--ui/custom_grid.cpp1414
1 files changed, 1414 insertions, 0 deletions
diff --git a/ui/custom_grid.cpp b/ui/custom_grid.cpp
new file mode 100644
index 00000000..df1e9145
--- /dev/null
+++ b/ui/custom_grid.cpp
@@ -0,0 +1,1414 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) ZenJu (zhnmju123 AT gmx DOT de) - All Rights Reserved *
+// **************************************************************************
+
+#include "custom_grid.h"
+#include <wx/dc.h>
+#include <wx/settings.h>
+#include <zen/i18n.h>
+#include <zen/file_error.h>
+#include <wx+/tooltip.h>
+#include <wx+/format_unit.h>
+#include <wx+/string_conv.h>
+#include <wx+/rtl.h>
+#include "../file_hierarchy.h"
+#include "../lib/resources.h"
+
+using namespace zen;
+using namespace gridview;
+
+
+const wxEventType zen::EVENT_GRID_CHECK_ROWS = wxNewEventType();
+const wxEventType zen::EVENT_GRID_SYNC_DIRECTION = wxNewEventType();
+
+namespace
+{
+const wxColour COLOR_ORANGE (238, 201, 0);
+const wxColour COLOR_GREY (212, 208, 200);
+const wxColour COLOR_YELLOW (247, 252, 62);
+const wxColour COLOR_YELLOW_LIGHT(253, 252, 169);
+const wxColour COLOR_CMP_RED (249, 163, 165);
+const wxColour COLOR_SYNC_BLUE (201, 203, 247);
+const wxColour COLOR_SYNC_GREEN (197, 248, 190);
+const wxColour COLOR_NOT_ACTIVE(228, 228, 228); //light grey
+
+
+const Zstring ICON_FILE_FOLDER = Zstr("folder");
+
+const int CHECK_BOX_IMAGE = 12; //width of checkbox image
+const int CHECK_BOX_WIDTH = CHECK_BOX_IMAGE + 2; //width of first block
+
+const size_t MIN_ROW_COUNT = 10;
+
+/*
+class hierarchy:
+ GridDataBase
+ /|\
+ ________________|________________
+ | |
+ GridDataRim |
+ /|\ |
+ __________|__________ |
+ | | |
+ GridDataLeftRim GridDataRight GridDataMiddle
+*/
+
+
+
+void refreshCell(Grid& grid, int row, ColumnType colType, size_t compPos)
+{
+ wxRect cellArea = grid.getCellArea(row, colType, compPos); //returns empty rect if column not found; absolute coordinates!
+ if (cellArea.height > 0)
+ {
+ cellArea.SetTopLeft(grid.CalcScrolledPosition(cellArea.GetTopLeft()));
+ grid.getMainWin().RefreshRect(cellArea, false);
+ }
+}
+
+
+std::pair<int, int> getVisibleRows(Grid& grid) //returns range [from, to)
+{
+ const wxSize clientSize = grid.getMainWin().GetClientSize();
+ if (clientSize.GetHeight() > 0)
+ {
+ wxPoint topLeft = grid.CalcUnscrolledPosition(wxPoint(0, 0));
+ wxPoint bottom = grid.CalcUnscrolledPosition(wxPoint(0, clientSize.GetHeight() - 1));
+
+ int rowFrom = grid.getRowAtPos(topLeft.y); //returns < 0 if column not found; absolute coordinates!
+ if (rowFrom >= 0)
+ {
+ int rowEnd = grid.getRowAtPos(bottom.y); //returns < 0 if column not found; absolute coordinates!
+ if (rowEnd < 0)
+ rowEnd = grid.getRowCount();
+ else
+ ++rowEnd;
+ return std::make_pair(rowFrom, rowEnd);
+ }
+ }
+ return std::make_pair(0, 0);
+}
+
+
+class IconUpdater;
+class GridEventManager;
+
+
+struct IconManager
+{
+ IconManager(IconBuffer::IconSize sz) : iconBuffer(sz) {}
+
+ IconBuffer iconBuffer;
+ std::unique_ptr<IconUpdater> iconUpdater; //bind ownership to GridDataRim<>!
+};
+
+//########################################################################################################
+
+class GridDataBase : public GridData
+{
+public:
+ GridDataBase(Grid& grid) : grid_(grid) {}
+
+ void holdOwnership(const std::shared_ptr<GridEventManager>& evtMgr) { evtMgr_ = evtMgr; }
+
+protected:
+ Grid& refGrid() { return grid_; }
+ const Grid& refGrid() const { return grid_; }
+
+private:
+ std::shared_ptr<GridEventManager> evtMgr_;
+ Grid& grid_;
+
+};
+
+//########################################################################################################
+
+template <SelectedSide side>
+class GridDataRim : public GridDataBase
+{
+public:
+ GridDataRim(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid, size_t compPos) : GridDataBase(grid), gridDataView_(gridDataView), compPos_(compPos) {}
+
+ void setIconManager(const std::shared_ptr<IconManager>& iconMgr) { iconMgr_ = iconMgr; }
+
+ void addIconsToBeLoaded(std::vector<Zstring>& newLoad) //loads all (not yet) drawn icons
+ {
+ //don't check too often! give worker thread some time to fetch data
+ if (iconMgr_)
+ {
+ const std::pair<int, int>& rowsOnScreen = getVisibleRows(refGrid());
+
+ //loop over all visible rows
+ const int firstRow = rowsOnScreen.first;
+ const int rowCount = rowsOnScreen.second - firstRow;
+
+ for (int i = 0; i < rowCount; ++i)
+ {
+ //alternate when adding rows: first, last, first + 1, last - 1 ... -> Icon buffer will then load reversely, i.e. from inside out
+ const int currentRow = firstRow + (i % 2 == 0 ?
+ i / 2 :
+ rowCount - 1 - (i - 1) / 2);
+
+ if (isFailedLoad(currentRow)) //find failed attempts to load icon
+ {
+ const Zstring fileName = getIconFile(currentRow);
+ if (!fileName.empty())
+ {
+ //test if they are already loaded in buffer:
+ if (iconMgr_->iconBuffer.requestFileIcon(fileName))
+ {
+ //do a *full* refresh for *every* failed load to update partial DC updates while scrolling
+ refreshCell(refGrid(), currentRow, static_cast<ColumnType>(COL_TYPE_FILENAME), compPos_);
+ setFailedLoad(currentRow, false);
+ }
+ else //not yet in buffer: mark for async. loading
+ newLoad.push_back(fileName);
+ }
+ }
+ }
+ }
+ }
+
+ void setFailedLoad(size_t row, bool failed)
+ {
+ if (failedLoads.size() != refGrid().getRowCount())
+ failedLoads.resize(refGrid().getRowCount());
+
+ if (row < failedLoads.size())
+ failedLoads[row] = failed;
+ }
+
+ bool isFailedLoad(size_t row) const { return row < failedLoads.size() ? failedLoads[row] != 0 : false; }
+
+protected:
+ virtual void renderRowBackgound(wxDC& dc, const wxRect& rect, int row, bool enabled, bool selected, bool hasFocus)
+ {
+ if (enabled)
+ {
+ if (selected)
+ dc.GradientFillLinear(rect, getColorSelectionGradientFrom(), getColorSelectionGradientTo(), wxEAST);
+ //ignore focus
+ else
+ clearArea(dc, rect, getBackGroundColor(row));
+ }
+ else
+ clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_BTNFACE));
+ }
+
+ wxColor getBackGroundColor(int row) const
+ {
+ wxColor backGroundCol = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
+
+ if (const FileSystemObject* fsObj = getRawData(row))
+ {
+ //mark filtered rows
+ if (!fsObj->isActive())
+ return COLOR_NOT_ACTIVE;
+ else if (!fsObj->isEmpty<side>()) //always show not existing files/dirs/symlinks as empty
+ {
+ //mark directories and symlinks
+ struct GetRowColor : public FSObjectVisitor
+ {
+ GetRowColor(wxColour& background) : background_(background) {}
+
+ virtual void visit(const FileMapping& fileObj) {}
+ virtual void visit(const SymLinkMapping& linkObj)
+ {
+ background_ = COLOR_ORANGE;
+ }
+ virtual void visit(const DirMapping& dirObj)
+ {
+ background_ = COLOR_GREY;
+ }
+ private:
+ wxColour& background_;
+ } getCol(backGroundCol);
+ fsObj->accept(getCol);
+ }
+ }
+ return backGroundCol;
+ }
+
+ const FileSystemObject* getRawData(int row) const { return gridDataView_ ? gridDataView_->getObject(row) : NULL; }
+
+private:
+ virtual size_t getRowCount() const { return std::max(MIN_ROW_COUNT, gridDataView_ ? gridDataView_->rowsOnView() : 0); }
+
+ virtual wxString getValue(int row, ColumnType colType) const
+ {
+ if (const FileSystemObject* fsObj = getRawData(row))
+ {
+ struct GetTextValue : public FSObjectVisitor
+ {
+ GetTextValue(ColumnTypeRim colType, const FileSystemObject& fso) : colType_(colType), fsObj_(fso) {}
+ virtual void visit(const FileMapping& fileObj)
+ {
+ switch (colType_)
+ {
+ case COL_TYPE_FULL_PATH:
+ value = toWx(beforeLast(fileObj.getFullName<side>(), FILE_NAME_SEPARATOR));
+ break;
+ case COL_TYPE_FILENAME: //filename
+ value = toWx(fileObj.getShortName<side>());
+ break;
+ case COL_TYPE_REL_PATH: //relative path
+ value = toWx(beforeLast(fileObj.getObjRelativeName(), FILE_NAME_SEPARATOR)); //returns empty string if ch not found
+ break;
+ case COL_TYPE_DIRECTORY:
+ value = toWx(fileObj.getBaseDirPf<side>());
+ break;
+ case COL_TYPE_SIZE: //file size
+ if (!fsObj_.isEmpty<side>())
+ value = zen::toStringSep(fileObj.getFileSize<side>());
+ break;
+ case COL_TYPE_DATE: //date
+ if (!fsObj_.isEmpty<side>())
+ value = zen::utcToLocalTimeString(fileObj.getLastWriteTime<side>());
+ break;
+ case COL_TYPE_EXTENSION: //file extension
+ value = toWx(fileObj.getExtension<side>());
+ break;
+ }
+ }
+
+ virtual void visit(const SymLinkMapping& linkObj)
+ {
+ switch (colType_)
+ {
+ case COL_TYPE_FULL_PATH:
+ value = toWx(beforeLast(linkObj.getFullName<side>(), FILE_NAME_SEPARATOR));
+ break;
+ case COL_TYPE_FILENAME: //filename
+ value = toWx(linkObj.getShortName<side>());
+ break;
+ case COL_TYPE_REL_PATH: //relative path
+ value = toWx(beforeLast(linkObj.getObjRelativeName(), FILE_NAME_SEPARATOR)); //returns empty string if ch not found
+ break;
+ case COL_TYPE_DIRECTORY:
+ value = toWx(linkObj.getBaseDirPf<side>());
+ break;
+ case COL_TYPE_SIZE: //file size
+ if (!fsObj_.isEmpty<side>())
+ value = _("<Symlink>");
+ break;
+ case COL_TYPE_DATE: //date
+ if (!fsObj_.isEmpty<side>())
+ value = zen::utcToLocalTimeString(linkObj.getLastWriteTime<side>());
+ break;
+ case COL_TYPE_EXTENSION: //file extension
+ value = wxEmptyString;
+ break;
+ }
+ }
+
+ virtual void visit(const DirMapping& dirObj)
+ {
+ switch (colType_)
+ {
+ case COL_TYPE_FULL_PATH:
+ value = toWx(dirObj.getFullName<side>());
+ break;
+ case COL_TYPE_FILENAME:
+ value = toWx(dirObj.getShortName<side>());
+ break;
+ case COL_TYPE_REL_PATH:
+ value = toWx(beforeLast(dirObj.getObjRelativeName(), FILE_NAME_SEPARATOR)); //returns empty string if ch not found
+ break;
+ case COL_TYPE_DIRECTORY:
+ value = toWx(dirObj.getBaseDirPf<side>());
+ break;
+ case COL_TYPE_SIZE: //file size
+ if (!fsObj_.isEmpty<side>())
+ value = _("<Directory>");
+ break;
+ case COL_TYPE_DATE: //date
+ if (!fsObj_.isEmpty<side>())
+ value = wxEmptyString;
+ break;
+ case COL_TYPE_EXTENSION: //file extension
+ value = wxEmptyString;
+ break;
+ }
+ }
+ ColumnTypeRim colType_;
+ wxString value;
+
+ const FileSystemObject& fsObj_;
+ } getVal(static_cast<ColumnTypeRim>(colType), *fsObj);
+ fsObj->accept(getVal);
+ return getVal.value;
+ }
+ //if data is not found:
+ return wxEmptyString;
+ }
+
+ static const int CELL_BORDER = 2;
+
+
+ virtual void renderCell(Grid& grid, wxDC& dc, const wxRect& rect, int row, ColumnType colType)
+ {
+ wxRect rectTmp = drawCellBorder(dc, rect);
+
+ const bool isActive = [&]() -> bool
+ {
+ if (const FileSystemObject* fsObj = this->getRawData(row))
+ return fsObj->isActive();
+ return true;
+ }();
+
+ //draw file icon
+ if (static_cast<ColumnTypeRim>(colType) == COL_TYPE_FILENAME)
+ {
+ if (iconMgr_)
+ {
+ rectTmp.x += CELL_BORDER;
+ rectTmp.width -= CELL_BORDER;
+
+ const int iconSize = iconMgr_->iconBuffer.getSize();
+ if (rectTmp.GetWidth() >= iconSize)
+ {
+ // Partitioning:
+ // _______________________________
+ // | border | icon | border | text |
+ // -------------------------------
+
+ const Zstring fileName = getIconFile(row);
+ if (!fileName.empty())
+ {
+ wxIcon icon;
+
+ //first check if it is a directory icon:
+ if (fileName == ICON_FILE_FOLDER)
+ icon = iconMgr_->iconBuffer.genericDirIcon();
+ else //retrieve file icon
+ {
+ if (!iconMgr_->iconBuffer.requestFileIcon(fileName, &icon)) //returns false if icon is not in buffer
+ {
+ icon = iconMgr_->iconBuffer.genericFileIcon(); //better than nothing
+ setFailedLoad(row, true); //save status of failed icon load -> used for async. icon loading
+ //falsify only! we want to avoid writing incorrect success values when only partially updating the DC, e.g. when scrolling,
+ //see repaint behavior of ::ScrollWindow() function!
+ }
+ }
+
+ if (icon.IsOk())
+ {
+ //center icon if it is too small
+ const int posX = rectTmp.GetX() + std::max(0, (iconSize - icon.GetWidth()) / 2);
+ const int posY = rectTmp.GetY() + std::max(0, (rectTmp.GetHeight() - icon.GetHeight()) / 2);
+
+ drawIconRtlNoMirror(dc, icon, wxPoint(posX, posY), buffer);
+
+ //convert icon to greyscale if row is not active
+ if (!isActive)
+ {
+ wxBitmap bmp(icon.GetWidth(), icon.GetHeight());
+ wxMemoryDC memDc(bmp);
+ memDc.Blit(0, 0, icon.GetWidth(), icon.GetHeight(), &dc, posX, posY); //blit in
+
+ bmp = wxBitmap(bmp.ConvertToImage().ConvertToGreyscale(1.0/3, 1.0/3, 1.0/3)); //treat all channels equally!
+ memDc.SelectObject(bmp);
+
+ dc.Blit(posX, posY, icon.GetWidth(), icon.GetHeight(), &memDc, 0, 0); //blit out
+ }
+ }
+ }
+ }
+ rectTmp.x += iconSize;
+ rectTmp.width -= iconSize;
+ }
+
+ rectTmp.x += CELL_BORDER;
+ rectTmp.width -= CELL_BORDER;
+
+ drawCellText(dc, rectTmp, getValue(row, colType), isActive, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
+ }
+ else
+ {
+ int alignment = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL;
+
+ //have file size right-justified (but don't change for RTL languages)
+ if (static_cast<ColumnTypeRim>(colType) == COL_TYPE_SIZE && grid.GetLayoutDirection() != wxLayout_RightToLeft)
+ {
+ rectTmp.width -= CELL_BORDER;
+ alignment = wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL;
+ }
+ else
+ {
+ rectTmp.x += CELL_BORDER;
+ rectTmp.width -= CELL_BORDER;
+ }
+
+ drawCellText(dc, rectTmp, getValue(row, colType), isActive, alignment);
+ }
+ }
+
+ virtual size_t getBestSize(wxDC& dc, int row, ColumnType colType)
+ {
+ // Partitioning:
+ // ________________________________________
+ // | border | icon | border | text | border |
+ // ----------------------------------------
+
+ int bestSize = 0;
+ if (static_cast<ColumnTypeRim>(colType) == COL_TYPE_FILENAME && iconMgr_)
+ bestSize += CELL_BORDER + iconMgr_->iconBuffer.getSize();
+
+ bestSize += CELL_BORDER + dc.GetTextExtent(getValue(row, colType)).GetWidth();
+
+ return bestSize + CELL_BORDER + 1; //add additional right border + 1 pix for border line
+ }
+
+ virtual wxString getColumnLabel(ColumnType colType) const
+ {
+ switch (static_cast<ColumnTypeRim>(colType))
+ {
+ case COL_TYPE_FULL_PATH:
+ return _("Full path");
+ case COL_TYPE_FILENAME:
+ return _("Name"); //= short name
+ case COL_TYPE_REL_PATH:
+ return _("Relative path");
+ case COL_TYPE_DIRECTORY:
+ return _("Directory");
+ case COL_TYPE_SIZE:
+ return _("Size");
+ case COL_TYPE_DATE:
+ return _("Date");
+ case COL_TYPE_EXTENSION:
+ return _("Extension");
+ }
+ return wxEmptyString;
+ }
+
+ virtual void renderColumnLabel(Grid& tree, wxDC& dc, const wxRect& rect, ColumnType colType, bool highlighted)
+ {
+ wxRect rectInside = drawColumnLabelBorder(dc, rect);
+ drawColumnLabelBackground(dc, rectInside, highlighted);
+
+ const int COLUMN_BORDER_LEFT = 4;
+
+ rectInside.x += COLUMN_BORDER_LEFT;
+ rectInside.width -= COLUMN_BORDER_LEFT;
+ drawColumnLabelText(dc, rectInside, getColumnLabel(colType));
+
+ //draw sort marker
+ if (gridDataView_)
+ {
+ auto sortInfo = gridDataView_->getSortInfo();
+ if (sortInfo)
+ {
+ if (colType == static_cast<ColumnType>(sortInfo->type_) && (compPos_ == gridview::COMP_LEFT) == sortInfo->onLeft_)
+ {
+ const wxBitmap& marker = GlobalResources::getImage(sortInfo->ascending_ ? L"sortAscending" : L"sortDescending");
+ wxPoint markerBegin = rectInside.GetTopLeft() + wxPoint((rectInside.width - marker.GetWidth()) / 2, 0);
+ dc.DrawBitmap(marker, markerBegin, true); //respect 2-pixel border
+ }
+ }
+ }
+ }
+
+ Zstring getIconFile(size_t row) const //return ICON_FILE_FOLDER if row points to a folder
+ {
+ const FileSystemObject* fsObj = getRawData(row);
+ if (fsObj && !fsObj->isEmpty<side>())
+ {
+ struct GetIcon : public FSObjectVisitor
+ {
+ virtual void visit(const FileMapping& fileObj)
+ {
+ iconName = fileObj.getFullName<side>();
+ }
+ virtual void visit(const SymLinkMapping& linkObj)
+ {
+ iconName = linkObj.getLinkType<side>() == LinkDescriptor::TYPE_DIR ?
+ Zstr("folder") :
+ linkObj.getFullName<side>();
+ }
+ virtual void visit(const DirMapping& dirObj)
+ {
+ iconName = Zstr("folder");
+ }
+
+ Zstring iconName;
+ } getIcon;
+ fsObj->accept(getIcon);
+ return getIcon.iconName;
+ }
+ return Zstring();
+ }
+
+ virtual wxString getToolTip(int row, ColumnType colType) const
+ {
+ wxString toolTip;
+ const FileSystemObject* fsObj = getRawData(row);
+ if (fsObj && !fsObj->isEmpty<side>())
+ {
+ struct AssembleTooltip : public FSObjectVisitor
+ {
+ AssembleTooltip(wxString& tipMsg) : tipMsg_(tipMsg) {}
+
+ virtual void visit(const FileMapping& fileObj)
+ {
+ tipMsg_ = copyStringTo<wxString>(std::wstring() + fileObj.getRelativeName<side>() + L"\n" +
+ _("Size") + L": " + zen::filesizeToShortString(to<Int64>(fileObj.getFileSize<side>())) + L"\n" +
+ _("Date") + L": " + zen::utcToLocalTimeString(fileObj.getLastWriteTime<side>()));
+ }
+
+ virtual void visit(const SymLinkMapping& linkObj)
+ {
+ tipMsg_ = copyStringTo<wxString>(std::wstring() + linkObj.getRelativeName<side>() + L"\n" +
+ _("Date") + L": " + zen::utcToLocalTimeString(linkObj.getLastWriteTime<side>()));
+ }
+
+ virtual void visit(const DirMapping& dirObj)
+ {
+ tipMsg_ = toWx(dirObj.getRelativeName<side>());
+ }
+
+ wxString& tipMsg_;
+ } assembler(toolTip);
+ fsObj->accept(assembler);
+ }
+ return toolTip;
+ }
+
+ std::shared_ptr<const zen::GridView> gridDataView_;
+ std::shared_ptr<IconManager> iconMgr_;
+ std::vector<char> failedLoads; //effectively a vector<bool> of size "number of rows"
+ const size_t compPos_;
+ std::unique_ptr<wxBitmap> buffer; //avoid costs of recreating this temporal variable
+};
+
+
+class GridDataLeft : public GridDataRim<LEFT_SIDE>
+{
+public:
+ GridDataLeft(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid, size_t compPos) : GridDataRim<LEFT_SIDE>(gridDataView, grid, compPos) {}
+
+ void setNavigationMarker(std::vector<const HierarchyObject*>&& markedFiles,
+ std::vector<const HierarchyObject*>&& markedContainer)
+ {
+ markedFiles_ .swap(markedFiles);
+ markedContainer_.swap(markedContainer);
+ }
+
+private:
+ virtual void renderRowBackgound(wxDC& dc, const wxRect& rect, int row, bool enabled, bool selected, bool hasFocus)
+ {
+ GridDataRim<LEFT_SIDE>::renderRowBackgound(dc, rect, row, enabled, selected, hasFocus);
+
+ //mark rows selected on navigation grid:
+ if (enabled && !selected)
+ {
+ const bool markRow = [&]() -> bool
+ {
+ if (const FileSystemObject* fsObj = getRawData(row))
+ {
+ if (dynamic_cast<const FileMapping*>(fsObj) || dynamic_cast<const SymLinkMapping*>(fsObj))
+ {
+ for (auto iter = markedFiles_.begin(); iter != markedFiles_.end(); ++iter)
+ if (*iter == &(fsObj->parent())) //mark files/links wich have the given parent
+ return true;
+ }
+ else if (auto dirObj = dynamic_cast<const DirMapping*>(fsObj))
+ {
+ for (auto iter = markedContainer_.begin(); iter != markedContainer_.end(); ++iter)
+ if (*iter == dirObj) //mark directories which *are* the given HierarchyObject*
+ return true;
+ }
+
+ for (auto iter = markedContainer_.begin(); iter != markedContainer_.end(); ++iter)
+ {
+ //mark all objects which have the HierarchyObject as *any* matching ancestor
+ const HierarchyObject* parent = &(fsObj->parent());
+ for (;;)
+ {
+ if (*iter == parent)
+ return true;
+
+ if (auto dirObj = dynamic_cast<const DirMapping*>(parent))
+ parent = &(dirObj->parent());
+ else
+ break;
+ }
+ }
+ }
+ return false;
+ }();
+
+ if (markRow)
+ {
+ //const wxColor COLOR_TREE_SELECTION_GRADIENT = wxColor(101, 148, 255); //H:158 S:255 V:178
+ const wxColor COLOR_TREE_SELECTION_GRADIENT = getColorSelectionGradientFrom();
+
+ wxRect rectTmp = rect;
+ rectTmp.width /= 20;
+ dc.GradientFillLinear(rectTmp, COLOR_TREE_SELECTION_GRADIENT, GridDataRim<LEFT_SIDE>::getBackGroundColor(row), wxEAST);
+ }
+ }
+ }
+
+ std::vector<const HierarchyObject*> markedFiles_; //mark files/symlinks directly within a container
+ std::vector<const HierarchyObject*> markedContainer_; //mark full container including all child-objects
+ //DO NOT DEREFERENCE!!!! NOT GUARANTEED TO BE VALID!!!
+};
+
+
+class GridDataRight : public GridDataRim<RIGHT_SIDE>
+{
+public:
+ GridDataRight(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid, size_t compPos) : GridDataRim<RIGHT_SIDE>(gridDataView, grid, compPos) {}
+};
+
+
+
+
+//########################################################################################################
+
+class GridDataMiddle : public GridDataBase
+{
+public:
+ GridDataMiddle(const std::shared_ptr<const zen::GridView>& gridDataView, Grid& grid) :
+ GridDataBase(grid),
+ gridDataView_(gridDataView),
+ syncPreviewActive(true) {}
+
+ void onSelectBegin(const wxPoint& clientPos, int row, ColumnType colType)
+ {
+ if (static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE)
+ {
+ refGrid().clearSelection(gridview::COMP_MIDDLE);
+ dragSelection.reset(new std::pair<int, BlockPosition>(row, mousePosToBlock(clientPos, row)));
+ }
+ }
+
+ void onSelectEnd(int row)
+ {
+ refGrid().clearSelection(gridview::COMP_MIDDLE);
+
+ //issue custom event
+ if (dragSelection)
+ {
+ const int rowFrom = dragSelection->first;
+ const int rowTo = row;
+
+ if (0 <= rowFrom && rowFrom < static_cast<int>(refGrid().getRowCount()) &&
+ 0 <= rowTo && rowTo < static_cast<int>(refGrid().getRowCount())) //row is -1 on capture lost!
+ {
+ if (wxEvtHandler* evtHandler = refGrid().GetEventHandler())
+ switch (dragSelection->second)
+ {
+ case BLOCKPOS_CHECK_BOX:
+ {
+ const FileSystemObject* fsObj = getRawData(rowFrom);
+ const bool setIncluded = fsObj ? !fsObj->isActive() : true;
+
+ CheckRowsEvent evt(rowFrom, rowTo, setIncluded);
+ evtHandler->ProcessEvent(evt);
+ }
+ break;
+ case BLOCKPOS_LEFT:
+ {
+ SyncDirectionEvent evt(rowFrom, rowTo, SYNC_DIR_LEFT);
+ evtHandler->ProcessEvent(evt);
+ }
+ break;
+ case BLOCKPOS_MIDDLE:
+ {
+ SyncDirectionEvent evt(rowFrom, rowTo, SYNC_DIR_NONE);
+ evtHandler->ProcessEvent(evt);
+ }
+ break;
+ case BLOCKPOS_RIGHT:
+ {
+ SyncDirectionEvent evt(rowFrom, rowTo, SYNC_DIR_RIGHT);
+ evtHandler->ProcessEvent(evt);
+ }
+ break;
+ }
+ }
+ dragSelection.reset();
+ }
+ }
+
+ void onMouseMovement(const wxPoint& clientPos, int row, ColumnType colType, size_t compPos)
+ {
+ //manage block highlighting and custom tooltip
+ if (dragSelection)
+ {
+ toolTip.hide(); //handle custom tooltip
+ }
+ else
+ {
+ if (compPos == gridview::COMP_MIDDLE && static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE)
+ {
+ if (highlight) //refresh old highlight
+ refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), gridview::COMP_MIDDLE);
+
+ highlight.reset(new std::pair<int, BlockPosition>(row, mousePosToBlock(clientPos, row)));
+ refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), gridview::COMP_MIDDLE);
+
+ //show custom tooltip
+ showToolTip(row, refGrid().getMainWin().ClientToScreen(clientPos));
+ }
+ else
+ onMouseLeave();
+ }
+ }
+
+ void onMouseLeave()
+ {
+ if (highlight)
+ {
+ refreshCell(refGrid(), highlight->first, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), gridview::COMP_MIDDLE);
+ highlight.reset();
+ }
+
+ toolTip.hide(); //handle custom tooltip
+ }
+
+ void setSyncPreviewActive(bool value) { syncPreviewActive = value; }
+
+private:
+ virtual size_t getRowCount() const { return std::max(MIN_ROW_COUNT, gridDataView_ ? gridDataView_->rowsOnView() : 0); }
+
+ virtual wxString getValue(int row, ColumnType colType) const
+ {
+ if (static_cast<ColumnTypeMiddle>(colType) == COL_TYPE_MIDDLE_VALUE)
+ {
+ const FileSystemObject* fsObj = getRawData(row);
+ if (fsObj)
+ return syncPreviewActive ? getSymbol(fsObj->getSyncOperation()) : getSymbol(fsObj->getCategory());
+ }
+ return wxEmptyString;
+ }
+
+
+ virtual void renderRowBackgound(wxDC& dc, const wxRect& rect, int row, bool enabled, bool selected, bool hasFocus)
+ {
+ drawCellBackground(dc, rect, enabled, selected, hasFocus, getBackGroundColor(row));
+ }
+
+ virtual void renderCell(Grid& grid, wxDC& dc, const wxRect& rect, int row, ColumnType colType)
+ {
+ switch (static_cast<ColumnTypeMiddle>(colType))
+ {
+ case COL_TYPE_MIDDLE_VALUE:
+ {
+ wxRect rectInside = drawCellBorder(dc, rect);
+
+ const FileSystemObject* fsObj = getRawData(row);
+ if (fsObj)
+ {
+ //draw checkbox
+ wxRect checkBoxArea = rectInside;
+ checkBoxArea.SetWidth(CHECK_BOX_WIDTH);
+
+ const bool rowHighlighted = dragSelection ? row == dragSelection->first : highlight ? row == highlight->first : false;
+ const BlockPosition highlightBlock = dragSelection ? dragSelection->second : highlight ? highlight->second : BLOCKPOS_CHECK_BOX;
+
+ if (rowHighlighted && highlightBlock == BLOCKPOS_CHECK_BOX)
+ drawBitmapRtlMirror(dc, GlobalResources::getImage(fsObj->isActive() ? L"checkboxTrueFocus" : L"checkboxFalseFocus"), checkBoxArea, wxALIGN_CENTER, buffer);
+ else //default
+ drawBitmapRtlMirror(dc, GlobalResources::getImage(fsObj->isActive() ? L"checkboxTrue" : L"checkboxFalse" ), checkBoxArea, wxALIGN_CENTER, buffer);
+
+ rectInside.width -= CHECK_BOX_WIDTH;
+ rectInside.x += CHECK_BOX_WIDTH;
+
+ //synchronization preview
+ if (syncPreviewActive)
+ {
+ if (rowHighlighted && highlightBlock != BLOCKPOS_CHECK_BOX)
+ switch (highlightBlock)
+ {
+ case BLOCKPOS_CHECK_BOX:
+ break;
+ case BLOCKPOS_LEFT:
+ drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SYNC_DIR_LEFT, true)), rectInside, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, buffer);
+ break;
+ case BLOCKPOS_MIDDLE:
+ drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SYNC_DIR_NONE, true)), rectInside, wxALIGN_CENTER, buffer);
+ break;
+ case BLOCKPOS_RIGHT:
+ drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->testSyncOperation(SYNC_DIR_RIGHT, true)), rectInside, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, buffer);
+ break;
+ }
+ else //default
+ drawBitmapRtlMirror(dc, getSyncOpImage(fsObj->getSyncOperation()), rectInside, wxALIGN_CENTER, buffer);
+ }
+ else //comparison results view
+ drawBitmapRtlMirror(dc, getCmpResultImage(fsObj->getCategory()), rectInside, wxALIGN_CENTER, buffer);
+ }
+ }
+ break;
+ case COL_TYPE_BORDER:
+ clearArea(dc, rect, wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW));
+ break;
+ }
+ }
+
+ virtual wxString getColumnLabel(ColumnType colType) const { return wxEmptyString; }
+
+ virtual void renderColumnLabel(Grid& tree, wxDC& dc, const wxRect& rect, ColumnType colType, bool highlighted)
+ {
+ switch (static_cast<ColumnTypeMiddle>(colType))
+ {
+ case COL_TYPE_MIDDLE_VALUE:
+ drawColumnLabelBackground(dc, rect, highlighted);
+
+ if (syncPreviewActive)
+ dc.DrawLabel(wxEmptyString, GlobalResources::getImage(wxT("syncViewSmall")), rect, wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
+ else
+ dc.DrawLabel(wxEmptyString, GlobalResources::getImage(wxT("cmpViewSmall")), rect, wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
+ break;
+
+ case COL_TYPE_BORDER:
+ drawCellBackground(dc, rect, true, false, true, wxSystemSettings::GetColour(wxSYS_COLOUR_3DSHADOW));
+ break;
+ }
+ }
+
+ const FileSystemObject* getRawData(int row) const { return gridDataView_ ? gridDataView_->getObject(row) : NULL; }
+
+ wxColor getBackGroundColor(int row) const
+ {
+ const FileSystemObject* fsObj = getRawData(row);
+ if (fsObj)
+ {
+ if (!fsObj->isActive())
+ return COLOR_NOT_ACTIVE;
+ else
+ {
+ if (syncPreviewActive) //synchronization preview
+ {
+ switch (fsObj->getSyncOperation()) //evaluate comparison result and sync direction
+ {
+ case SO_DO_NOTHING:
+ case SO_EQUAL:
+ break; //usually white
+
+ case SO_CREATE_NEW_LEFT:
+ case SO_OVERWRITE_LEFT:
+ case SO_DELETE_LEFT:
+ case SO_MOVE_LEFT_SOURCE:
+ case SO_MOVE_LEFT_TARGET:
+ case SO_COPY_METADATA_TO_LEFT:
+ return COLOR_SYNC_BLUE;
+
+ case SO_CREATE_NEW_RIGHT:
+ case SO_OVERWRITE_RIGHT:
+ case SO_DELETE_RIGHT:
+ case SO_MOVE_RIGHT_SOURCE:
+ case SO_MOVE_RIGHT_TARGET:
+ case SO_COPY_METADATA_TO_RIGHT:
+ return COLOR_SYNC_GREEN;
+
+ case SO_UNRESOLVED_CONFLICT:
+ return COLOR_YELLOW;
+ }
+ }
+ else //comparison results view
+ {
+ switch (fsObj->getCategory())
+ {
+ case FILE_LEFT_SIDE_ONLY:
+ case FILE_LEFT_NEWER:
+ return COLOR_SYNC_BLUE; //COLOR_CMP_BLUE;
+
+ case FILE_RIGHT_SIDE_ONLY:
+ case FILE_RIGHT_NEWER:
+ return COLOR_SYNC_GREEN; //COLOR_CMP_GREEN;
+ case FILE_DIFFERENT:
+ return COLOR_CMP_RED;
+ case FILE_EQUAL:
+ break; //usually white
+ case FILE_CONFLICT:
+ return COLOR_YELLOW;
+ case FILE_DIFFERENT_METADATA:
+ return COLOR_YELLOW_LIGHT;
+ }
+ }
+ }
+ }
+ return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
+ }
+
+ enum BlockPosition //each cell can be divided into four blocks concerning mouse selections
+ {
+ BLOCKPOS_CHECK_BOX,
+ BLOCKPOS_LEFT,
+ BLOCKPOS_MIDDLE,
+ BLOCKPOS_RIGHT
+ };
+
+ //determine blockposition within cell
+ BlockPosition mousePosToBlock(const wxPoint& clientPos, int row) const
+ {
+ const int absX = refGrid().CalcUnscrolledPosition(clientPos).x;
+
+ const wxRect rect = refGrid().getCellArea(row, static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), gridview::COMP_MIDDLE); //returns empty rect if column not found; absolute coordinates!
+ if (rect.width > CHECK_BOX_WIDTH && rect.height > 0)
+ {
+ const FileSystemObject* const fsObj = getRawData(row);
+ if (fsObj && syncPreviewActive &&
+ fsObj->getSyncOperation() != SO_EQUAL) //in sync-preview equal files shall be treated as in cmp result, i.e. as full checkbox
+ {
+ // cell:
+ // ----------------------------------
+ // | checkbox | left | middle | right|
+ // ----------------------------------
+
+ const double blockWidth = (rect.GetWidth() - CHECK_BOX_WIDTH) / 3.0;
+ if (rect.GetX() + CHECK_BOX_WIDTH <= absX && absX < rect.GetX() + rect.GetWidth())
+ {
+ if (absX < rect.GetX() + CHECK_BOX_WIDTH + blockWidth)
+ return BLOCKPOS_LEFT;
+ else if (absX < rect.GetX() + CHECK_BOX_WIDTH + 2 * blockWidth)
+ return BLOCKPOS_MIDDLE;
+ else
+ return BLOCKPOS_RIGHT;
+ }
+ }
+
+ }
+ return BLOCKPOS_CHECK_BOX;
+ }
+
+ void showToolTip(int row, wxPoint posScreen)
+ {
+ const FileSystemObject* fsObj = getRawData(row);
+ if (fsObj)
+ {
+ if (syncPreviewActive) //synchronization preview
+ {
+ const wchar_t* imageName = [&]() -> const wchar_t*
+ {
+ const SyncOperation syncOp = fsObj->getSyncOperation();
+ switch (syncOp)
+ {
+ case SO_CREATE_NEW_LEFT:
+ return L"createLeft";
+ case SO_CREATE_NEW_RIGHT:
+ return L"createRight";
+ case SO_DELETE_LEFT:
+ return L"deleteLeft";
+ case SO_DELETE_RIGHT:
+ return L"deleteRight";
+ case SO_MOVE_LEFT_SOURCE:
+ return L"moveLeftSource";
+ case SO_MOVE_LEFT_TARGET:
+ return L"moveLeftTarget";
+ case SO_MOVE_RIGHT_SOURCE:
+ return L"moveRightSource";
+ case SO_MOVE_RIGHT_TARGET:
+ return L"moveRightTarget";
+ case SO_OVERWRITE_LEFT:
+ return L"updateLeft";
+ case SO_COPY_METADATA_TO_LEFT:
+ return L"moveLeft";
+ case SO_OVERWRITE_RIGHT:
+ return L"updateRight";
+ case SO_COPY_METADATA_TO_RIGHT:
+ return L"moveRight";
+ case SO_DO_NOTHING:
+ return L"none";
+ case SO_EQUAL:
+ return L"equal";
+ case SO_UNRESOLVED_CONFLICT:
+ return L"conflict";
+ };
+ assert(false);
+ return L"";
+ }();
+ const auto& img = mirrorIfRtl(GlobalResources::getImage(imageName));
+ toolTip.show(getSyncOpDescription(*fsObj), posScreen, &img);
+ }
+ else
+ {
+ const wchar_t* imageName = [&]() -> const wchar_t*
+ {
+ const CompareFilesResult cmpRes = fsObj->getCategory();
+ switch (cmpRes)
+ {
+ case FILE_LEFT_SIDE_ONLY:
+ return L"leftOnly";
+ case FILE_RIGHT_SIDE_ONLY:
+ return L"rightOnly";
+ case FILE_LEFT_NEWER:
+ return L"leftNewer";
+ case FILE_RIGHT_NEWER:
+ return L"rightNewer";
+ case FILE_DIFFERENT:
+ return L"different";
+ case FILE_EQUAL:
+ return L"equal";
+ case FILE_DIFFERENT_METADATA:
+ return L"conflict";
+ case FILE_CONFLICT:
+ return L"conflict";
+ }
+ assert(false);
+ return L"";
+ }();
+ const auto& img = mirrorIfRtl(GlobalResources::getImage(imageName));
+ toolTip.show(getCategoryDescription(*fsObj), posScreen, &img);
+ }
+ }
+ else
+ toolTip.hide(); //if invalid row...
+ }
+
+ virtual wxString getToolTip(ColumnType colType) const { return syncPreviewActive ? _("Synchronization Preview") : _("Comparison Result"); }
+
+ std::shared_ptr<const zen::GridView> gridDataView_;
+ bool syncPreviewActive;
+ std::unique_ptr<std::pair<int, BlockPosition>> highlight; //(row, block) current mouse highlight
+ std::unique_ptr<std::pair<int, BlockPosition>> dragSelection; //(row, block)
+ std::unique_ptr<wxBitmap> buffer; //avoid costs of recreating this temporal variable
+ zen::Tooltip toolTip;
+};
+
+//########################################################################################################
+
+class GridEventManager : private wxEvtHandler
+{
+public:
+ GridEventManager(Grid& grid,
+ GridDataLeft& provLeft,
+ GridDataMiddle& provMiddle,
+ GridDataRight& provRight) : grid_(grid), provLeft_(provLeft), provMiddle_(provMiddle), provRight_(provRight)
+ {
+ grid_.Connect(EVENT_GRID_COL_RESIZE, GridColumnResizeEventHandler(GridEventManager::onResizeColumn), NULL, this);
+
+ grid_.getMainWin().Connect(wxEVT_MOTION, wxMouseEventHandler(GridEventManager::onMouseMovement), NULL, this);
+ grid_.getMainWin().Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(GridEventManager::onMouseLeave ), NULL, this);
+ grid_.getMainWin().Connect(wxEVT_KEY_DOWN, wxKeyEventHandler (GridEventManager::onKeyDown ), NULL, this);
+
+ grid_.Connect(EVENT_GRID_MOUSE_LEFT_DOWN, GridClickEventHandler (GridEventManager::onSelectBegin), NULL, this);
+ grid_.Connect(EVENT_GRID_SELECT_RANGE, GridRangeSelectEventHandler(GridEventManager::onSelectEnd ), NULL, this);
+ }
+
+private:
+ void onMouseMovement(wxMouseEvent& event)
+ {
+ const wxPoint& topLeftAbs = grid_.CalcUnscrolledPosition(event.GetPosition());
+ const Opt<std::pair<ColumnType, size_t>> colInfo = grid_.getColumnAtPos(topLeftAbs.x);
+ const int row = grid_.getRowAtPos(topLeftAbs.y); //returns < 0 if column not found; absolute coordinates!
+ if (colInfo)
+ {
+ //redirect mouse movement to middle grid component
+ provMiddle_.onMouseMovement(event.GetPosition(), row, colInfo->first, colInfo->second);
+ }
+ event.Skip();
+ }
+
+ void onMouseLeave(wxMouseEvent& event)
+ {
+ provMiddle_.onMouseLeave();
+ event.Skip();
+ }
+
+ void onSelectBegin(GridClickEvent& event)
+ {
+ if (event.compPos_ == gridview::COMP_MIDDLE)
+ provMiddle_.onSelectBegin(event.GetPosition(), event.row_, event.colType_);
+ event.Skip();
+ }
+
+ void onSelectEnd(GridRangeSelectEvent& event)
+ {
+ if (event.compPos_ == gridview::COMP_MIDDLE)
+ provMiddle_.onSelectEnd(event.rowTo_);
+ event.Skip();
+ }
+
+ void onKeyDown(wxKeyEvent& event)
+ {
+ int keyCode = event.GetKeyCode();
+ if (wxTheApp->GetLayoutDirection() == wxLayout_RightToLeft)
+ {
+ if (keyCode == WXK_LEFT)
+ keyCode = WXK_RIGHT;
+ else if (keyCode == WXK_RIGHT)
+ keyCode = WXK_LEFT;
+ else if (keyCode == WXK_NUMPAD_LEFT)
+ keyCode = WXK_NUMPAD_RIGHT;
+ else if (keyCode == WXK_NUMPAD_RIGHT)
+ keyCode = WXK_NUMPAD_LEFT;
+ }
+
+ //skip middle component when navigating via keyboard
+
+ int row = grid_.getGridCursor().first;
+ if (row < 0)
+ row = 0;
+
+ if (event.ShiftDown())
+ ;
+ else if (event.ControlDown())
+ ;
+ else
+ switch (keyCode)
+ {
+ case WXK_LEFT:
+ case WXK_NUMPAD_LEFT:
+ grid_.setGridCursor(row, gridview::COMP_LEFT);
+ return; //swallow event
+
+ case WXK_RIGHT:
+ case WXK_NUMPAD_RIGHT:
+ grid_.setGridCursor(row, gridview::COMP_RIGHT);
+ return; //swallow event
+ }
+
+ event.Skip();
+ }
+
+ void onResizeColumn(GridColumnResizeEvent& event)
+ {
+ auto resizeOtherSide = [&](size_t compPosOther)
+ {
+ std::vector<Grid::ColumnAttribute> colAttr = grid_.getColumnConfig(compPosOther);
+
+ std::for_each(colAttr.begin(), colAttr.end(), [&](Grid::ColumnAttribute& ca)
+ {
+ if (ca.type_ == event.colType_)
+ ca.width_ = event.width_;
+ });
+
+ grid_.setColumnConfig(colAttr, compPosOther); //set column count + widths
+ };
+
+ switch (event.compPos_)
+ {
+ case gridview::COMP_LEFT:
+ resizeOtherSide(gridview::COMP_RIGHT);
+ break;
+ case gridview::COMP_MIDDLE:
+ break;
+ case gridview::COMP_RIGHT:
+ resizeOtherSide(gridview::COMP_LEFT);
+ break;
+ }
+ }
+
+ Grid& grid_;
+ GridDataLeft& provLeft_;
+ GridDataMiddle& provMiddle_;
+ GridDataRight& provRight_;
+};
+}
+
+//########################################################################################################
+
+void gridview::init(Grid& grid, const std::shared_ptr<const zen::GridView>& gridDataView)
+{
+ grid.setComponentCount(3);
+
+ auto provLeft_ = std::make_shared<GridDataLeft >(gridDataView, grid, gridview::COMP_LEFT );
+ auto provMiddle_ = std::make_shared<GridDataMiddle>(gridDataView, grid);
+ auto provRight_ = std::make_shared<GridDataRight >(gridDataView, grid, gridview::COMP_RIGHT);
+
+ grid.setDataProvider(provLeft_ , gridview::COMP_LEFT); //data providers reference grid =>
+ grid.setDataProvider(provMiddle_, gridview::COMP_MIDDLE); //ownership must belong *exclusively* to grid!
+ grid.setDataProvider(provRight_ , gridview::COMP_RIGHT);
+
+ auto evtMgr = std::make_shared<GridEventManager>(grid, *provLeft_, *provMiddle_, *provRight_);
+ provLeft_ ->holdOwnership(evtMgr);
+ provMiddle_->holdOwnership(evtMgr);
+ provRight_ ->holdOwnership(evtMgr);
+
+ grid.enableColumnMove (false, gridview::COMP_MIDDLE);
+ grid.enableColumnResize(false, gridview::COMP_MIDDLE);
+
+ std::vector<Grid::ColumnAttribute> attribMiddle;
+ attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_BORDER), 5));
+ attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_MIDDLE_VALUE), 60));
+ attribMiddle.push_back(Grid::ColumnAttribute(static_cast<ColumnType>(COL_TYPE_BORDER), 5));
+
+ grid.setColumnConfig(attribMiddle, gridview::COMP_MIDDLE);
+}
+
+
+namespace
+{
+std::vector<ColumnAttributeRim> makeConsistent(const std::vector<ColumnAttributeRim>& attribs)
+{
+ std::set<ColumnTypeRim> usedTypes;
+
+ std::vector<ColumnAttributeRim> output;
+ //remove duplicates
+ std::copy_if(attribs.begin(), attribs.end(), std::back_inserter(output),
+ [&](const ColumnAttributeRim& a) { return usedTypes.insert(a.type_).second; });
+
+ //make sure each type is existing! -> should *only* be a problem if user manually messes with globalsettings.xml
+ const auto& defAttr = getDefaultColumnAttributesLeft();
+ std::copy_if(defAttr.begin(), defAttr.end(), std::back_inserter(output),
+ [&](const ColumnAttributeRim& a) { return usedTypes.insert(a.type_).second; });
+
+ return output;
+}
+}
+
+std::vector<Grid::ColumnAttribute> gridview::convertConfig(const std::vector<ColumnAttributeRim>& attribs)
+{
+ const auto& attribClean = makeConsistent(attribs);
+
+ std::vector<Grid::ColumnAttribute> output;
+ std::transform(attribClean.begin(), attribClean.end(), std::back_inserter(output),
+ [&](const ColumnAttributeRim& a) { return Grid::ColumnAttribute(static_cast<ColumnType>(a.type_), a.width_, a.visible_); });
+
+ return output;
+}
+
+
+std::vector<ColumnAttributeRim> gridview::convertConfig(const std::vector<Grid::ColumnAttribute>& attribs)
+{
+ std::vector<ColumnAttributeRim> output;
+
+ std::transform(attribs.begin(), attribs.end(), std::back_inserter(output),
+ [&](const Grid::ColumnAttribute& ca) { return ColumnAttributeRim(static_cast<ColumnTypeRim>(ca.type_), ca.width_, ca.visible_); });
+
+ return makeConsistent(output);
+}
+
+
+namespace
+{
+class IconUpdater : private wxEvtHandler //update file icons periodically: use SINGLE instance to coordinate left and right grid in parallel
+{
+public:
+ IconUpdater(GridDataLeft& provLeft, GridDataRight& provRight, IconBuffer& iconBuffer) : provLeft_(provLeft), provRight_(provRight), iconBuffer_(iconBuffer)
+ {
+ timer.Connect(wxEVT_TIMER, wxEventHandler(IconUpdater::loadIconsAsynchronously), NULL, this);
+ timer.Start(50); //timer interval in ms
+ }
+
+private:
+ void loadIconsAsynchronously(wxEvent& event) //loads all (not yet) drawn icons
+ {
+ std::vector<Zstring> newLoad;
+ provLeft_ .addIconsToBeLoaded(newLoad); //loads all (not yet) drawn icons
+ provRight_.addIconsToBeLoaded(newLoad); //
+ iconBuffer_.setWorkload(newLoad);
+ }
+
+ GridDataLeft& provLeft_;
+ GridDataRight& provRight_;
+ IconBuffer& iconBuffer_;
+ wxTimer timer;
+};
+}
+
+
+void gridview::setIconSize(Grid& grid, IconBuffer::IconSize sz)
+{
+ auto* provLeft = dynamic_cast<GridDataLeft*>(grid.getDataProvider(gridview::COMP_LEFT));
+ auto* provRight = dynamic_cast<GridDataRight*>(grid.getDataProvider(gridview::COMP_RIGHT));
+
+ if (provLeft && provRight)
+ {
+ std::shared_ptr<IconManager> iconMgr = std::make_shared<IconManager>(sz);
+ iconMgr->iconUpdater.reset(new IconUpdater(*provLeft, *provRight, iconMgr->iconBuffer));
+
+ provLeft ->setIconManager(iconMgr);
+ provRight->setIconManager(iconMgr);
+ grid.setRowHeight(iconMgr->iconBuffer.getSize() + 1); //+ 1 for line between rows
+ grid.Refresh();
+ }
+ else
+ assert(false);
+}
+
+
+void gridview::clearSelection(Grid& grid)
+{
+ grid.clearSelection(gridview::COMP_LEFT);
+ grid.clearSelection(gridview::COMP_MIDDLE);
+ grid.clearSelection(gridview::COMP_RIGHT);
+}
+
+
+void gridview::setNavigationMarker(Grid& grid,
+ std::vector<const HierarchyObject*>&& markedFiles,
+ std::vector<const HierarchyObject*>&& markedContainer)
+{
+ if (auto* provLeft = dynamic_cast<GridDataLeft*>(grid.getDataProvider(gridview::COMP_LEFT)))
+ provLeft->setNavigationMarker(std::move(markedFiles), std::move(markedContainer));
+ else
+ assert(false);
+ grid.Refresh();
+}
+
+
+void gridview::setSyncPreviewActive(Grid& grid, bool value)
+{
+ if (auto* provMiddle = dynamic_cast<GridDataMiddle*>(grid.getDataProvider(gridview::COMP_MIDDLE)))
+ provMiddle->setSyncPreviewActive(value);
+ else
+ assert(false);
+}
+
+wxBitmap zen::getSyncOpImage(SyncOperation syncOp)
+{
+ switch (syncOp) //evaluate comparison result and sync direction
+ {
+ case SO_CREATE_NEW_LEFT:
+ return GlobalResources::getImage(L"createLeftSmall");
+ case SO_CREATE_NEW_RIGHT:
+ return GlobalResources::getImage(L"createRightSmall");
+ case SO_DELETE_LEFT:
+ return GlobalResources::getImage(L"deleteLeftSmall");
+ case SO_DELETE_RIGHT:
+ return GlobalResources::getImage(L"deleteRightSmall");
+ case SO_MOVE_LEFT_SOURCE:
+ return GlobalResources::getImage(L"moveLeftSourceSmall");
+ case SO_MOVE_LEFT_TARGET:
+ return GlobalResources::getImage(L"moveLeftTargetSmall");
+ case SO_MOVE_RIGHT_SOURCE:
+ return GlobalResources::getImage(L"moveRightSourceSmall");
+ case SO_MOVE_RIGHT_TARGET:
+ return GlobalResources::getImage(L"moveRightTargetSmall");
+ case SO_OVERWRITE_RIGHT:
+ return GlobalResources::getImage(L"updateRightSmall");
+ case SO_COPY_METADATA_TO_RIGHT:
+ return GlobalResources::getImage(L"moveRightSmall");
+ case SO_OVERWRITE_LEFT:
+ return GlobalResources::getImage(L"updateLeftSmall");
+ case SO_COPY_METADATA_TO_LEFT:
+ return GlobalResources::getImage(L"moveLeftSmall");
+ case SO_DO_NOTHING:
+ return GlobalResources::getImage(L"noneSmall");
+ case SO_EQUAL:
+ return GlobalResources::getImage(L"equalSmall");
+ case SO_UNRESOLVED_CONFLICT:
+ return GlobalResources::getImage(L"conflictSmall");
+ }
+ return wxNullBitmap;
+}
+
+
+wxBitmap zen::getCmpResultImage(CompareFilesResult cmpResult)
+{
+ switch (cmpResult)
+ {
+ case FILE_LEFT_SIDE_ONLY:
+ return GlobalResources::getImage(L"leftOnlySmall");
+ case FILE_RIGHT_SIDE_ONLY:
+ return GlobalResources::getImage(L"rightOnlySmall");
+ case FILE_LEFT_NEWER:
+ return GlobalResources::getImage(L"leftNewerSmall");
+ case FILE_RIGHT_NEWER:
+ return GlobalResources::getImage(L"rightNewerSmall");
+ case FILE_DIFFERENT:
+ return GlobalResources::getImage(L"differentSmall");
+ case FILE_EQUAL:
+ return GlobalResources::getImage(L"equalSmall");
+ case FILE_CONFLICT:
+ case FILE_DIFFERENT_METADATA:
+ return GlobalResources::getImage(L"conflictSmall");
+ }
+ return wxNullBitmap;
+}
bgstack15