summaryrefslogtreecommitdiff
path: root/library/custom_grid.cpp
diff options
context:
space:
mode:
authorDaniel Wilhelm <daniel@wili.li>2014-04-18 17:08:06 +0200
committerDaniel Wilhelm <daniel@wili.li>2014-04-18 17:08:06 +0200
commitfbe76102e941b9f1edaf236788e42678f05fdf9a (patch)
treef5f538316019fa89be8dc478103490c3a826f3ac /library/custom_grid.cpp
parent3.8 (diff)
downloadFreeFileSync-fbe76102e941b9f1edaf236788e42678f05fdf9a.tar.gz
FreeFileSync-fbe76102e941b9f1edaf236788e42678f05fdf9a.tar.bz2
FreeFileSync-fbe76102e941b9f1edaf236788e42678f05fdf9a.zip
3.9
Diffstat (limited to 'library/custom_grid.cpp')
-rw-r--r--library/custom_grid.cpp2104
1 files changed, 2104 insertions, 0 deletions
diff --git a/library/custom_grid.cpp b/library/custom_grid.cpp
new file mode 100644
index 00000000..2537f529
--- /dev/null
+++ b/library/custom_grid.cpp
@@ -0,0 +1,2104 @@
+// **************************************************************************
+// * This file is part of the FreeFileSync project. It is distributed under *
+// * GNU General Public License: http://www.gnu.org/licenses/gpl.html *
+// * Copyright (C) 2008-2010 ZenJu (zhnmju123 AT gmx.de) *
+// **************************************************************************
+//
+#include "custom_grid.h"
+#include "../shared/system_constants.h"
+#include "resources.h"
+#include <wx/dc.h>
+#include "../shared/util.h"
+#include "../shared/string_conv.h"
+#include "resources.h"
+#include <typeinfo>
+#include "../ui/grid_view.h"
+#include "../synchronization.h"
+#include "../shared/custom_tooltip.h"
+#include <wx/dcclient.h>
+#include "icon_buffer.h"
+#include <wx/icon.h>
+
+#ifdef FFS_WIN
+#include <wx/timer.h>
+#include "status_handler.h"
+#include <cmath>
+
+#elif defined FFS_LINUX
+#include <gtk/gtk.h>
+#endif
+
+using namespace ffs3;
+
+
+const size_t 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(int initialRows = 0, int initialCols = 0) : //note: initialRows/initialCols MUST match with GetNumberRows()/GetNumberCols() at initialization!!!
+ wxGridTableBase(),
+ gridDataView(NULL),
+ lastNrRows(initialRows),
+ lastNrCols(initialCols) {}
+
+
+ virtual ~CustomGridTable() {}
+
+
+ void setGridDataTable(const GridView* view)
+ {
+ this->gridDataView = view;
+ }
+
+
+//###########################################################################
+//grid standard input output methods, redirected directly to gridData to improve performance
+
+ virtual int GetNumberRows()
+ {
+ if (gridDataView)
+ return static_cast<int>(std::max(gridDataView->rowsOnView(), MIN_ROW_COUNT));
+ else
+ return 0; //grid is initialized with zero number of rows
+ }
+
+
+ virtual bool IsEmptyCell(int row, int col)
+ {
+ return false; //avoid overlapping cells
+
+ //return (GetValue(row, col) == wxEmptyString);
+ }
+
+
+ virtual void SetValue(int row, int col, const wxString& value)
+ {
+ assert (false); //should not be used, since values are retrieved directly from gridDataView
+ }
+
+ //update dimensions of grid: no need for InsertRows(), AppendRows(), DeleteRows() anymore!!!
+ void updateGridSizes()
+ {
+ const int currentNrRows = GetNumberRows();
+
+ if (lastNrRows < currentNrRows)
+ {
+ if (GetView())
+ {
+ wxGridTableMessage msg(this,
+ wxGRIDTABLE_NOTIFY_ROWS_APPENDED,
+ currentNrRows - lastNrRows);
+
+ GetView()->ProcessTableMessage( msg );
+ }
+ }
+ else if (lastNrRows > currentNrRows)
+ {
+ if (GetView())
+ {
+ wxGridTableMessage msg(this,
+ wxGRIDTABLE_NOTIFY_ROWS_DELETED,
+ 0,
+ lastNrRows - currentNrRows);
+
+ GetView()->ProcessTableMessage( msg );
+ }
+ }
+ lastNrRows = currentNrRows;
+
+ const int currentNrCols = GetNumberCols();
+
+ if (lastNrCols < currentNrCols)
+ {
+ if (GetView())
+ {
+ wxGridTableMessage msg(this,
+ wxGRIDTABLE_NOTIFY_COLS_APPENDED,
+ currentNrCols - lastNrCols);
+
+ GetView()->ProcessTableMessage( msg );
+ }
+ }
+ else if (lastNrCols > currentNrCols)
+ {
+ if (GetView())
+ {
+ wxGridTableMessage msg(this,
+ wxGRIDTABLE_NOTIFY_COLS_DELETED,
+ 0,
+ lastNrCols - currentNrCols);
+
+ GetView()->ProcessTableMessage( msg );
+ }
+ }
+ lastNrCols = currentNrCols;
+ }
+//###########################################################################
+
+
+ virtual wxGridCellAttr* GetAttr(int row, int col, wxGridCellAttr::wxAttrKind kind)
+ {
+ const wxColour color = getRowColor(row);
+
+ //add color to some rows
+ wxGridCellAttr* result = wxGridTableBase::GetAttr(row, col, kind);
+ if (result)
+ {
+ if (result->GetBackgroundColour() == color)
+ {
+ return result;
+ }
+ else //grid attribute might be referenced by other nodes, so clone it!
+ {
+ wxGridCellAttr* attr = result->Clone(); //attr has ref-count 1
+ result->DecRef();
+ result = attr;
+ }
+ }
+ else
+ result = new wxGridCellAttr; //created with ref-count 1
+
+ result->SetBackgroundColour(color);
+
+ return result;
+ }
+
+
+ const FileSystemObject* getRawData(size_t row) const
+ {
+ if (gridDataView)
+ return gridDataView->getObject(row); //returns NULL if request is not valid or not data found
+
+ return NULL;
+ }
+
+protected:
+ static const wxColour COLOR_BLUE;
+ static const wxColour COLOR_GREY;
+ static const wxColour COLOR_ORANGE;
+ static const wxColour COLOR_CMP_RED;
+ static const wxColour COLOR_CMP_BLUE;
+ static const wxColour COLOR_CMP_GREEN;
+ static const wxColour COLOR_SYNC_BLUE;
+ static const wxColour COLOR_SYNC_GREEN;
+ static const wxColour COLOR_YELLOW;
+
+ 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;
+};
+
+//see http://www.latiumsoftware.com/en/articles/00015.php#12 for "safe" colors
+const wxColour CustomGridTable::COLOR_ORANGE( 238, 201, 0);
+const wxColour CustomGridTable::COLOR_BLUE( 80, 110, 255);
+const wxColour CustomGridTable::COLOR_GREY( 212, 208, 200);
+const wxColour CustomGridTable::COLOR_CMP_RED( 249, 163, 165);
+const wxColour CustomGridTable::COLOR_CMP_BLUE( 144, 232, 246);
+const wxColour CustomGridTable::COLOR_CMP_GREEN( 147, 253, 159);
+const wxColour CustomGridTable::COLOR_SYNC_BLUE( 201, 203, 247);
+const wxColour CustomGridTable::COLOR_SYNC_GREEN(197, 248, 190);
+const wxColour CustomGridTable::COLOR_YELLOW( 247, 252, 62);
+
+
+class CustomGridTableRim : public CustomGridTable
+{
+public:
+ virtual ~CustomGridTableRim() {}
+
+ virtual int GetNumberCols()
+ {
+ return static_cast<int>(columnPositions.size());
+ }
+
+ virtual wxString GetColLabelValue( int col )
+ {
+ return CustomGridRim::getTypeName(getTypeAtPos(col));
+ }
+
+
+ void setupColumns(const std::vector<xmlAccess::ColumnTypes>& positions)
+ {
+ columnPositions = positions;
+ updateGridSizes(); //add or remove columns
+ }
+
+
+ xmlAccess::ColumnTypes getTypeAtPos(size_t pos) const
+ {
+ if (pos < columnPositions.size())
+ return columnPositions[pos];
+ else
+ return xmlAccess::DIRECTORY;
+ }
+
+ //get filename in order to retrieve the icon from it
+ virtual Zstring getIconFile(size_t row) const = 0; //return "folder" if row points to a folder
+
+protected:
+ template <SelectedSide side>
+ wxString GetValueSub(int row, int col)
+ {
+ const FileSystemObject* fsObj = getRawData(row);
+ if (fsObj)
+ {
+ if (!fsObj->isEmpty<side>())
+ {
+ struct GetValue : public FSObjectVisitor
+ {
+ GetValue(xmlAccess::ColumnTypes colType) : colType_(colType) {}
+ virtual void visit(const FileMapping& fileObj)
+ {
+ switch (colType_)
+ {
+ case xmlAccess::FULL_PATH:
+ value = zToWx(fileObj.getFullName<side>().BeforeLast(common::FILE_NAME_SEPARATOR));
+ break;
+ case xmlAccess::FILENAME: //filename
+ value = zToWx(fileObj.getShortName<side>());
+ break;
+ case xmlAccess::REL_PATH: //relative path
+ value = zToWx(fileObj.getParentRelativeName());
+ break;
+ case xmlAccess::DIRECTORY:
+ value = zToWx(fileObj.getBaseDirPf<side>());
+ break;
+ case xmlAccess::SIZE: //file size
+ value = ffs3::numberToStringSep(fileObj.getFileSize<side>());
+ break;
+ case xmlAccess::DATE: //date
+ value = ffs3::utcTimeToLocalString(fileObj.getLastWriteTime<side>());
+ break;
+ case xmlAccess::EXTENSION: //file extension
+ value = zToWx(fileObj.getExtension<side>());
+ break;
+ }
+ }
+
+ virtual void visit(const SymLinkMapping& linkObj)
+ {
+ switch (colType_)
+ {
+ case xmlAccess::FULL_PATH:
+ value = zToWx(linkObj.getFullName<side>().BeforeLast(common::FILE_NAME_SEPARATOR));
+ break;
+ case xmlAccess::FILENAME: //filename
+ value = zToWx(linkObj.getShortName<side>());
+ break;
+ case xmlAccess::REL_PATH: //relative path
+ value = zToWx(linkObj.getParentRelativeName());
+ break;
+ case xmlAccess::DIRECTORY:
+ value = zToWx(linkObj.getBaseDirPf<side>());
+ break;
+ case xmlAccess::SIZE: //file size
+ value = _("<Symlink>");
+ break;
+ case xmlAccess::DATE: //date
+ value = ffs3::utcTimeToLocalString(linkObj.getLastWriteTime<side>());
+ break;
+ case xmlAccess::EXTENSION: //file extension
+ value = wxEmptyString;
+ break;
+ }
+ }
+
+ virtual void visit(const DirMapping& dirObj)
+ {
+ switch (colType_)
+ {
+ case xmlAccess::FULL_PATH:
+ value = zToWx(dirObj.getFullName<side>());
+ break;
+ case xmlAccess::FILENAME:
+ value = wxEmptyString;
+ break;
+ case xmlAccess::REL_PATH:
+ value = zToWx(dirObj.getRelativeName<side>());
+ break;
+ case xmlAccess::DIRECTORY:
+ value = zToWx(dirObj.getBaseDirPf<side>());
+ break;
+ case xmlAccess::SIZE: //file size
+ value = _("<Directory>");
+ break;
+ case xmlAccess::DATE: //date
+ value = wxEmptyString;
+ break;
+ case xmlAccess::EXTENSION: //file extension
+ value = wxEmptyString;
+ break;
+ }
+ }
+ xmlAccess::ColumnTypes colType_;
+ wxString value;
+ } getVal(getTypeAtPos(col));
+ fsObj->accept(getVal);
+ return getVal.value;
+ }
+ }
+ //if data is not found:
+ return wxEmptyString;
+ }
+
+ template <SelectedSide side>
+ Zstring getIconFileImpl(size_t row) const //return "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)
+ {
+ //Optimization: if filename exists on both sides, always use left side's file:
+ //Icon should be the same on both sides anyway...
+ if (!fileObj.isEmpty<LEFT_SIDE>() && !fileObj.isEmpty<RIGHT_SIDE>())
+ iconName = fileObj.getFullName<LEFT_SIDE>();
+ else
+ iconName = fileObj.getFullName<side>();
+ }
+ virtual void visit(const SymLinkMapping& linkObj)
+ {
+ iconName = linkObj.getLinkType<side>() == LinkDescriptor::TYPE_DIR ?
+ DefaultStr("folder") :
+ linkObj.getFullName<side>();
+ }
+ virtual void visit(const DirMapping& dirObj)
+ {
+ iconName = DefaultStr("folder");
+ }
+
+ Zstring iconName;
+ } getIcon;
+ fsObj->accept(getIcon);
+ return getIcon.iconName;
+ }
+
+ return Zstring();
+ }
+
+
+private:
+ virtual const wxColour getRowColor(int row) //rows that are filtered out are shown in different color
+ {
+ const FileSystemObject* fsObj = getRawData(row);
+ if (fsObj)
+ {
+ //mark filtered rows
+ if (!fsObj->isActive())
+ return COLOR_BLUE;
+
+ //mark directories and symlinks
+ struct GetRowColor : public FSObjectVisitor
+ {
+ virtual void visit(const FileMapping& fileObj)
+ {
+ rowColor = *wxWHITE;
+ }
+ virtual void visit(const SymLinkMapping& linkObj)
+ {
+ rowColor = COLOR_ORANGE;
+ }
+ virtual void visit(const DirMapping& dirObj)
+ {
+ rowColor = COLOR_GREY;
+ }
+
+ wxColour rowColor;
+ } getCol;
+ fsObj->accept(getCol);
+ return getCol.rowColor;
+ }
+ return *wxWHITE;
+ }
+
+ std::vector<xmlAccess::ColumnTypes> columnPositions;
+};
+
+
+class CustomGridTableLeft : public CustomGridTableRim
+{
+public:
+
+ virtual wxString GetValue(int row, int col)
+ {
+ return CustomGridTableRim::GetValueSub<LEFT_SIDE>(row, col);
+ }
+
+ virtual Zstring getIconFile(size_t row) const //return "folder" if row points to a folder
+ {
+ return getIconFileImpl<LEFT_SIDE>(row);
+ }
+};
+
+
+class CustomGridTableRight : public CustomGridTableRim
+{
+public:
+ virtual wxString GetValue(int row, int col)
+ {
+ return CustomGridTableRim::GetValueSub<RIGHT_SIDE>(row, col);
+ }
+
+ virtual Zstring getIconFile(size_t row) const //return "folder" if row points to a folder
+ {
+ return getIconFileImpl<RIGHT_SIDE>(row);
+ }
+};
+
+
+class CustomGridTableMiddle : public CustomGridTable
+{
+public:
+//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!
+ syncPreviewActive(false) {}
+
+ virtual int GetNumberCols()
+ {
+ return 1;
+ }
+
+ virtual wxString GetColLabelValue( int col )
+ {
+ return wxEmptyString;
+ }
+
+ virtual wxString GetValue(int row, int col) //method used for exporting .csv file only!
+ {
+ const FileSystemObject* fsObj = getRawData(row);
+ if (fsObj)
+ {
+ if (syncPreviewActive) //synchronization preview
+ return getSymbol(fsObj->getSyncOperation());
+ else
+ return getSymbol(fsObj->getCategory());
+ }
+ return wxEmptyString;
+ }
+
+ void enableSyncPreview(bool value)
+ {
+ syncPreviewActive = value;
+ }
+
+ bool syncPreviewIsActive() const
+ {
+ return syncPreviewActive;
+ }
+
+private:
+ virtual const wxColour getRowColor(int row) //rows that are filtered out are shown in different color
+ {
+ const FileSystemObject* fsObj = getRawData(row);
+ if (fsObj)
+ {
+ //mark filtered rows
+ if (!fsObj->isActive())
+ return COLOR_BLUE;
+
+ if (syncPreviewActive) //synchronization preview
+ {
+ switch (fsObj->getSyncOperation()) //evaluate comparison result and sync direction
+ {
+ case SO_CREATE_NEW_LEFT:
+ case SO_DELETE_LEFT:
+ case SO_OVERWRITE_LEFT:
+ return COLOR_SYNC_BLUE;
+ case SO_CREATE_NEW_RIGHT:
+ case SO_DELETE_RIGHT:
+ case SO_OVERWRITE_RIGHT:
+ return COLOR_SYNC_GREEN;
+ case SO_UNRESOLVED_CONFLICT:
+ return COLOR_YELLOW;
+ case SO_DO_NOTHING:
+ case SO_EQUAL:
+ return *wxWHITE;
+ }
+ }
+ else //comparison results view
+ {
+ switch (fsObj->getCategory())
+ {
+ case FILE_LEFT_SIDE_ONLY:
+ case FILE_RIGHT_SIDE_ONLY:
+ return COLOR_CMP_GREEN;
+ case FILE_LEFT_NEWER:
+ case FILE_RIGHT_NEWER:
+ return COLOR_CMP_BLUE;
+ case FILE_DIFFERENT:
+ return COLOR_CMP_RED;
+ case FILE_EQUAL:
+ return *wxWHITE;
+ case FILE_CONFLICT:
+ return COLOR_YELLOW;
+ }
+ }
+ }
+
+ //fallback
+ return *wxWHITE;
+ }
+
+ bool syncPreviewActive; //determines wheter grid shall show compare result or sync preview
+};
+
+//########################################################################################################
+
+
+CustomGrid::CustomGrid(wxWindow *parent,
+ wxWindowID id,
+ const wxPoint& pos,
+ const wxSize& size,
+ long style,
+ const wxString& name) :
+ wxGrid(parent, id, pos, size, style, name),
+ m_gridLeft(NULL),
+ m_gridMiddle(NULL),
+ m_gridRight(NULL),
+ isLeading(false),
+ m_marker(-1, ASCENDING)
+{
+ //set color of selections
+ wxColour darkBlue(40, 35, 140);
+ SetSelectionBackground(darkBlue);
+ SetSelectionForeground(*wxWHITE);
+}
+
+
+void CustomGrid::initSettings(CustomGridLeft* gridLeft,
+ CustomGridMiddle* gridMiddle,
+ CustomGridRight* gridRight,
+ const GridView* gridDataView)
+{
+ assert(this == gridLeft || this == gridRight || this == gridMiddle);
+
+ //these grids will scroll together
+ m_gridLeft = gridLeft;
+ m_gridRight = gridRight;
+ m_gridMiddle = gridMiddle;
+
+ //set underlying grid data
+ setGridDataTable(gridDataView);
+
+ //enhance grid functionality; identify leading grid by keyboard input or scroll action
+ Connect(wxEVT_KEY_DOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SCROLLWIN_TOP, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SCROLLWIN_BOTTOM, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SCROLLWIN_LINEUP, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SCROLLWIN_LINEDOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SCROLLWIN_PAGEUP, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SCROLLWIN_PAGEDOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SCROLLWIN_THUMBTRACK, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SCROLLWIN_THUMBRELEASE, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_GRID_LABEL_LEFT_CLICK, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ Connect(wxEVT_SET_FOCUS, wxEventHandler(CustomGrid::onGridAccess), NULL, this); //used by grid text-search
+ GetGridWindow()->Connect(wxEVT_LEFT_DOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+ GetGridWindow()->Connect(wxEVT_RIGHT_DOWN, wxEventHandler(CustomGrid::onGridAccess), NULL, this);
+
+ GetGridWindow()->Connect(wxEVT_ENTER_WINDOW, wxEventHandler(CustomGrid::adjustGridHeights), NULL, this);
+
+ //parallel grid scrolling: do NOT use DoPrepareDC() to align grids! GDI resource leak! Use regular paint event instead:
+ GetGridWindow()->Connect(wxEVT_PAINT, wxEventHandler(CustomGrid::OnPaintGrid), NULL, this);
+}
+
+
+void CustomGrid::release() //release connection to ffs3::GridView
+{
+ setGridDataTable(NULL);
+}
+
+
+bool CustomGrid::isLeadGrid() const
+{
+ return isLeading;
+}
+
+
+void CustomGrid::RefreshCell(int row, int col)
+{
+ wxRect rectScrolled(CellToRect(row, col));
+
+ CalcScrolledPosition(rectScrolled.x, rectScrolled.y, &rectScrolled.x, &rectScrolled.y);
+
+ GetGridWindow()->RefreshRect(rectScrolled); //note: CellToRect() and YToRow work on m_gridWindow NOT on the whole grid!
+}
+
+
+void CustomGrid::OnPaintGrid(wxEvent& event)
+{
+ if (isLeadGrid()) //avoid back coupling
+ alignOtherGrids(m_gridLeft, m_gridMiddle, m_gridRight); //scroll other grids
+ event.Skip();
+}
+
+
+void moveCursorWhileSelecting(const int anchor, const int oldPos, const int newPos, wxGrid* grid)
+{
+ //note: all positions are valid in this context!
+
+ grid->SetGridCursor( newPos, grid->GetGridCursorCol());
+ grid->MakeCellVisible(newPos, grid->GetGridCursorCol());
+
+ if (oldPos < newPos)
+ {
+ for (int i = oldPos; i < std::min(anchor, newPos); ++i)
+ grid->DeselectRow(i); //remove selection
+
+ for (int i = std::max(oldPos, anchor); i <= newPos; ++i)
+ grid->SelectRow(i, true); //add to selection
+ }
+ else
+ {
+ for (int i = std::max(newPos, anchor) + 1; i <= oldPos; ++i)
+ grid->DeselectRow(i); //remove selection
+
+ for (int i = newPos; i <= std::min(oldPos, anchor); ++i)
+ grid->SelectRow(i, true); //add to selection
+ }
+}
+
+
+void execGridCommands(wxEvent& event, wxGrid* grid)
+{
+ static int anchorRow = 0;
+ if ( grid->GetNumberRows() == 0 ||
+ grid->GetNumberCols() == 0)
+ return;
+
+ const wxKeyEvent* keyEvent = dynamic_cast<const wxKeyEvent*> (&event);
+ if (keyEvent)
+ {
+ //ensure cursorOldPos is always a valid row!
+ const int cursorOldPos = std::max(std::min(grid->GetGridCursorRow(), grid->GetNumberRows() - 1), 0);
+ const int cursorOldColumn = std::max(std::min(grid->GetGridCursorCol(), grid->GetNumberCols() - 1), 0);
+
+ const bool shiftPressed = keyEvent->ShiftDown();
+ const bool altPressed = keyEvent->AltDown();
+ const bool ctrlPressed = keyEvent->ControlDown();
+ const bool noModPressed = !shiftPressed && !altPressed && !ctrlPressed;
+ const int keyCode = keyEvent->GetKeyCode();
+
+
+ //SHIFT + X
+ if (shiftPressed && keyCode == WXK_UP)
+ {
+ const int cursorNewPos = std::max(cursorOldPos - 1, 0);
+ moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid);
+ return; //no event.Skip()
+ }
+ else if (shiftPressed && keyCode == WXK_DOWN)
+ {
+ const int cursorNewPos = std::min(cursorOldPos + 1, grid->GetNumberRows() - 1);
+ moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid);
+ return; //no event.Skip()
+ }
+ else if (shiftPressed && keyCode == WXK_LEFT)
+ {
+ const int cursorColumn = std::max(cursorOldColumn - 1, 0);
+ grid->SetGridCursor(cursorOldPos, cursorColumn);
+ grid->MakeCellVisible(cursorOldPos, cursorColumn);
+ return; //no event.Skip()
+ }
+ else if (shiftPressed && keyCode == WXK_RIGHT)
+ {
+ const int cursorColumn = std::min(cursorOldColumn + 1, grid->GetNumberCols() - 1);
+ grid->SetGridCursor(cursorOldPos, cursorColumn);
+ grid->MakeCellVisible(cursorOldPos, cursorColumn);
+ return; //no event.Skip()
+ }
+ else if (shiftPressed && (keyCode == WXK_PAGEUP || keyCode == WXK_NUMPAD_PAGEUP))
+ {
+ const int rowsPerPage = grid->GetGridWindow()->GetSize().GetHeight() / grid->GetDefaultRowSize();
+ const int cursorNewPos = std::max(cursorOldPos - rowsPerPage, 0);
+ moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid);
+ return; //no event.Skip()
+ }
+ else if (shiftPressed && (keyCode == WXK_PAGEDOWN || keyCode == WXK_NUMPAD_PAGEDOWN))
+ {
+ const int rowsPerPage = grid->GetGridWindow()->GetSize().GetHeight() / grid->GetDefaultRowSize();
+ const int cursorNewPos = std::min(cursorOldPos + rowsPerPage, grid->GetNumberRows() - 1);
+ moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid);
+ return; //no event.Skip()
+ }
+ else if (shiftPressed && (keyCode == WXK_HOME || keyCode == WXK_NUMPAD_HOME))
+ {
+ const int cursorNewPos = 0;
+ moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid);
+ return; //no event.Skip()
+ }
+ else if (shiftPressed && (keyCode == WXK_END || keyCode == WXK_NUMPAD_END))
+ {
+ const int cursorNewPos = grid->GetNumberRows() - 1;
+ moveCursorWhileSelecting(anchorRow, cursorOldPos, cursorNewPos, grid);
+ return; //no event.Skip()
+ }
+
+ //CTRL + X
+ else if (ctrlPressed && keyCode == WXK_LEFT)
+ {
+ grid->SetGridCursor(grid->GetGridCursorRow(), 0);
+ grid->MakeCellVisible(grid->GetGridCursorRow(), 0);
+ return; //no event.Skip()
+ }
+ else if (ctrlPressed && keyCode == WXK_RIGHT)
+ {
+ grid->SetGridCursor(grid->GetGridCursorRow(), grid->GetNumberCols() - 1);
+ grid->MakeCellVisible(grid->GetGridCursorRow(), grid->GetNumberCols() - 1);
+ return; //no event.Skip()
+ }
+ else if ((ctrlPressed && keyCode == WXK_UP) ||
+ ((noModPressed || ctrlPressed) && (keyCode == WXK_HOME || keyCode == WXK_NUMPAD_HOME)))
+ {
+ grid->SetGridCursor(0, grid->GetGridCursorCol());
+ grid->MakeCellVisible(0, grid->GetGridCursorCol());
+ return; //no event.Skip()
+ }
+ else if ((ctrlPressed && keyCode == WXK_DOWN) ||
+ ((noModPressed || ctrlPressed) && (keyCode == WXK_END || keyCode == WXK_NUMPAD_END)))
+ {
+ grid->SetGridCursor(grid->GetNumberRows() - 1, grid->GetGridCursorCol());
+ grid->MakeCellVisible(grid->GetNumberRows() - 1, grid->GetGridCursorCol());
+ return; //no event.Skip()
+ }
+
+ //button without additonal control keys pressed
+ else if (noModPressed && keyCode == WXK_UP)
+ {
+ const int cursorNewPos = std::max(cursorOldPos - 1, 0);
+ grid->SetGridCursor(cursorNewPos, grid->GetGridCursorCol());
+ grid->MakeCellVisible(cursorNewPos, grid->GetGridCursorCol());
+ return; //no event.Skip()
+ }
+ else if (noModPressed && keyCode == WXK_DOWN)
+ {
+ const int cursorNewPos = std::min(cursorOldPos + 1, grid->GetNumberRows() - 1);
+ grid->SetGridCursor(cursorNewPos, grid->GetGridCursorCol());
+ grid->MakeCellVisible(cursorNewPos, grid->GetGridCursorCol());
+ return; //no event.Skip()
+ }
+ else if (noModPressed && keyCode == WXK_LEFT)
+ {
+ const int cursorColumn = std::max(cursorOldColumn - 1, 0);
+ grid->SetGridCursor(cursorOldPos, cursorColumn);
+ grid->MakeCellVisible(cursorOldPos, cursorColumn);
+ return; //no event.Skip()
+ }
+ else if (noModPressed && keyCode == WXK_RIGHT)
+ {
+ const int cursorColumn = std::min(cursorOldColumn + 1, grid->GetNumberCols() - 1);
+ grid->SetGridCursor(cursorOldPos, cursorColumn);
+ grid->MakeCellVisible(cursorOldPos, cursorColumn);
+ return; //no event.Skip()
+ }
+ else if ((noModPressed || ctrlPressed) && (keyCode == WXK_PAGEUP || keyCode == WXK_NUMPAD_PAGEUP))
+ {
+ const int rowsPerPage = grid->GetGridWindow()->GetSize().GetHeight() / grid->GetDefaultRowSize();
+ const int cursorNewPos = std::max(cursorOldPos - rowsPerPage, 0);
+ grid->SetGridCursor(cursorNewPos, grid->GetGridCursorCol());
+ grid->MakeCellVisible(cursorNewPos, grid->GetGridCursorCol());
+ return; //no event.Skip()
+ }
+ else if ((noModPressed || ctrlPressed) && (keyCode == WXK_PAGEDOWN || keyCode == WXK_NUMPAD_PAGEDOWN))
+ {
+ const int rowsPerPage = grid->GetGridWindow()->GetSize().GetHeight() / grid->GetDefaultRowSize();
+ const int cursorNewPos = std::min(cursorOldPos + rowsPerPage, grid->GetNumberRows() - 1);
+ grid->SetGridCursor(cursorNewPos, grid->GetGridCursorCol());
+ grid->MakeCellVisible(cursorNewPos, grid->GetGridCursorCol());
+ return; //no event.Skip()
+ }
+ }
+
+ anchorRow = grid->GetGridCursorRow();
+ event.Skip(); //let event delegate!
+}
+
+
+inline
+bool gridsShouldBeCleared(const wxEvent& event)
+{
+ const wxMouseEvent* mouseEvent = dynamic_cast<const wxMouseEvent*>(&event);
+ if (mouseEvent)
+ {
+ if (mouseEvent->ControlDown() || mouseEvent->ShiftDown())
+ return false;
+
+ if (mouseEvent->ButtonDown(wxMOUSE_BTN_LEFT))
+ return true;
+
+ return false;
+ }
+ else
+ {
+ const wxKeyEvent* keyEvent = dynamic_cast<const wxKeyEvent*>(&event);
+ if (keyEvent)
+ {
+ if (keyEvent->ControlDown() || keyEvent->AltDown() || keyEvent->ShiftDown())
+ return false;
+
+ switch (keyEvent->GetKeyCode())
+ {
+ case WXK_TAB:
+ case WXK_RETURN:
+ case WXK_ESCAPE:
+ case WXK_NUMPAD_ENTER:
+ case WXK_LEFT:
+ case WXK_UP:
+ case WXK_RIGHT:
+ case WXK_DOWN:
+ case WXK_PAGEUP:
+ case WXK_PAGEDOWN:
+ case WXK_NUMPAD_PAGEUP:
+ case WXK_NUMPAD_PAGEDOWN:
+ case WXK_HOME:
+ case WXK_END:
+ case WXK_NUMPAD_HOME:
+ case WXK_NUMPAD_END:
+ return true;
+ }
+
+ return false;
+ }
+ }
+
+ return false;
+}
+
+
+void CustomGrid::onGridAccess(wxEvent& event)
+{
+ if (!isLeading)
+ {
+ //notify other grids of new user focus
+ m_gridLeft->isLeading = m_gridLeft == this;
+ m_gridMiddle->isLeading = m_gridMiddle == this;
+ m_gridRight->isLeading = m_gridRight == this;
+
+ wxGrid::SetFocus();
+ }
+
+ //clear grids
+ if (gridsShouldBeCleared(event))
+ {
+ m_gridLeft->ClearSelection();
+ m_gridMiddle->ClearSelection();
+ m_gridRight->ClearSelection();
+ }
+
+ //update row labels NOW (needed when scrolling if buttons keep being pressed)
+ m_gridLeft->GetGridRowLabelWindow()->Update();
+ m_gridRight->GetGridRowLabelWindow()->Update();
+
+ //support for custom short-cuts (overwriting wxWidgets functionality!)
+ execGridCommands(event, this); //event.Skip is handled here!
+}
+
+
+//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 not NULL because called after initSettings()
+
+ 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();
+ }
+}
+
+
+void CustomGrid::setSortMarker(SortMarker marker)
+{
+ m_marker = marker;
+}
+
+
+void CustomGrid::DrawColLabel(wxDC& dc, int col)
+{
+ wxGrid::DrawColLabel(dc, col);
+
+ if (col == m_marker.first)
+ {
+ if (m_marker.second == ASCENDING)
+ dc.DrawBitmap(GlobalResources::getInstance().getImageByName(wxT("smallUp")), GetColRight(col) - 16 - 2, 2, true); //respect 2-pixel border
+ else
+ dc.DrawBitmap(GlobalResources::getInstance().getImageByName(wxT("smallDown")), GetColRight(col) - 16 - 2, 2, true); //respect 2-pixel border
+ }
+}
+
+
+std::set<size_t> CustomGrid::getAllSelectedRows() const
+{
+ std::set<size_t> output;
+
+ const wxArrayInt selectedRows = this->GetSelectedRows();
+ if (!selectedRows.IsEmpty())
+ {
+ for (size_t i = 0; i < selectedRows.GetCount(); ++i)
+ output.insert(selectedRows[i]);
+ }
+
+ if (!this->GetSelectedCols().IsEmpty()) //if a column is selected this is means all rows are marked for deletion
+ {
+ for (int k = 0; k < const_cast<CustomGrid*>(this)->GetNumberRows(); ++k) //messy wxGrid implementation...
+ output.insert(k);
+ }
+
+ const wxGridCellCoordsArray singlySelected = this->GetSelectedCells();
+ if (!singlySelected.IsEmpty())
+ {
+ for (size_t k = 0; k < singlySelected.GetCount(); ++k)
+ output.insert(singlySelected[k].GetRow());
+ }
+
+ const wxGridCellCoordsArray tmpArrayTop = this->GetSelectionBlockTopLeft();
+ if (!tmpArrayTop.IsEmpty())
+ {
+ wxGridCellCoordsArray tmpArrayBottom = this->GetSelectionBlockBottomRight();
+
+ size_t arrayCount = tmpArrayTop.GetCount();
+
+ if (arrayCount == tmpArrayBottom.GetCount())
+ {
+ for (size_t i = 0; i < arrayCount; ++i)
+ {
+ const int rowTop = tmpArrayTop[i].GetRow();
+ const int rowBottom = tmpArrayBottom[i].GetRow();
+
+ for (int k = rowTop; k <= rowBottom; ++k)
+ output.insert(k);
+ }
+ }
+ }
+
+ //some exception: also add current cursor row to selection if there are no others... hopefully improving usability
+ if (output.empty() && this->isLeadGrid())
+ output.insert(const_cast<CustomGrid*>(this)->GetCursorRow()); //messy wxGrid implementation...
+
+ return output;
+}
+
+
+//############################################################################################
+//CustomGrid specializations
+
+template <bool showFileIcons>
+class GridCellRenderer : public wxGridCellStringRenderer
+{
+public:
+ GridCellRenderer(CustomGridRim::LoadSuccess& loadIconSuccess, const CustomGridTableRim* gridDataTable) :
+ m_loadIconSuccess(loadIconSuccess),
+ m_gridDataTable(gridDataTable) {}
+
+
+ virtual void Draw(wxGrid& grid,
+ wxGridCellAttr& attr,
+ wxDC& dc,
+ const wxRect& rect, //unscrolled rect
+ int row, int col,
+ bool isSelected)
+ {
+ //############## show windows explorer file icons ######################
+
+ if ( showFileIcons && //evaluate at compile time
+ m_gridDataTable->getTypeAtPos(col) == xmlAccess::FILENAME)
+ {
+ if (rect.GetWidth() >= IconBuffer::ICON_SIZE)
+ {
+ // Partitioning:
+ // _____________________
+ // | 2 pix | icon | rest |
+ // ---------------------
+
+ //clear area where icon will be placed
+ wxRect rectShrinked(rect);
+ rectShrinked.SetWidth(IconBuffer::ICON_SIZE + LEFT_BORDER); //add 2 pixel border
+ wxGridCellRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected);
+
+ //draw rest
+ wxRect rest(rect); //unscrolled
+ rest.x += IconBuffer::ICON_SIZE + LEFT_BORDER;
+ rest.width -= IconBuffer::ICON_SIZE + LEFT_BORDER;
+ wxGridCellStringRenderer::Draw(grid, attr, dc, rest, row, col, isSelected);
+
+ //try to draw icon
+ //retrieve grid data
+ const Zstring fileName = m_gridDataTable->getIconFile(row);
+ if (!fileName.empty())
+ {
+ //first check if it is a directory icon:
+ if (fileName == DefaultStr("folder"))
+ {
+ dc.DrawIcon(IconBuffer::getDirectoryIcon(), rectShrinked.GetX() + LEFT_BORDER, rectShrinked.GetY());
+ m_loadIconSuccess[row] = true; //save status of last icon load -> used for async. icon loading
+ }
+ else //retrieve file icon
+ {
+ wxIcon icon;
+ bool iconDrawnFully = false;
+ const bool iconLoaded = IconBuffer::getInstance().requestFileIcon(fileName, &icon); //returns false if icon is not in buffer
+ if (iconLoaded)
+ {
+ dc.DrawIcon(icon, rectShrinked.GetX() + LEFT_BORDER, rectShrinked.GetY());
+
+ //-----------------------------------------------------------------------------------------------
+ //only mark as successful if icon was drawn fully!
+ //(attention: when scrolling, rows get partially updated, which can result in the upper half being blank!)
+
+ //rect where icon was placed
+ wxRect iconRect(rect); //unscrolled
+ iconRect.x += LEFT_BORDER;
+ iconRect.SetWidth(IconBuffer::ICON_SIZE);
+
+ //convert to scrolled coordinates
+ grid.CalcScrolledPosition(iconRect.x, iconRect.y, &iconRect.x, &iconRect.y);
+
+ wxRegionIterator regionsInv(grid.GetGridWindow()->GetUpdateRegion());
+ while (regionsInv)
+ {
+ if (regionsInv.GetRect().Contains(iconRect))
+ {
+ iconDrawnFully = true;
+ break;
+ }
+ ++regionsInv;
+ }
+ }
+ //-----------------------------------------------------------------------------------------------
+ //save status of last icon load -> used for async. icon loading
+ m_loadIconSuccess[row] = iconLoaded && iconDrawnFully;
+ }
+ }
+ return;
+ }
+ }
+
+ //default
+ wxGridCellStringRenderer::Draw(grid, attr, dc, rect, row, col, isSelected);
+ }
+
+
+ virtual wxSize GetBestSize(wxGrid& grid, //adapt reported width if file icons are shown
+ wxGridCellAttr& attr,
+ wxDC& dc,
+ int row, int col)
+ {
+ if ( showFileIcons && //evaluate at compile time
+ m_gridDataTable->getTypeAtPos(col) == xmlAccess::FILENAME)
+ {
+ wxSize rv = wxGridCellStringRenderer::GetBestSize(grid, attr, dc, row, col);
+ rv.SetWidth(rv.GetWidth() + LEFT_BORDER + IconBuffer::ICON_SIZE);
+ return rv;
+ }
+
+ //default
+ return wxGridCellStringRenderer::GetBestSize(grid, attr, dc, row, col);
+ }
+
+
+private:
+ CustomGridRim::LoadSuccess& m_loadIconSuccess;
+ const CustomGridTableRim* const m_gridDataTable;
+
+ static const int LEFT_BORDER = 2;
+};
+
+//----------------------------------------------------------------------------------------
+
+CustomGridRim::CustomGridRim(wxWindow *parent,
+ wxWindowID id,
+ const wxPoint& pos,
+ const wxSize& size,
+ long style,
+ const wxString& name) :
+ CustomGrid(parent, id, pos, size, style, name), fileIconsAreEnabled(false)
+{}
+
+
+void CustomGridRim::updateGridSizes()
+{
+ assert(getGridDataTable());
+ getGridDataTable()->updateGridSizes();
+}
+
+
+xmlAccess::ColumnAttributes CustomGridRim::getDefaultColumnAttributes()
+{
+ xmlAccess::ColumnAttributes defaultColumnSettings;
+
+ xmlAccess::ColumnAttrib newEntry;
+ newEntry.type = xmlAccess::FULL_PATH;
+ newEntry.visible = false;
+ newEntry.position = 0;
+ newEntry.width = 150;
+ defaultColumnSettings.push_back(newEntry);
+
+ newEntry.type = xmlAccess::DIRECTORY;
+ newEntry.position = 1;
+ 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::FILENAME;
+ newEntry.position = 3;
+ newEntry.width = 138;
+ defaultColumnSettings.push_back(newEntry);
+
+ 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);
+
+ newEntry.type = xmlAccess::EXTENSION;
+ newEntry.visible = false;
+ newEntry.position = 6;
+ newEntry.width = 60;
+ defaultColumnSettings.push_back(newEntry);
+
+ return defaultColumnSettings;
+}
+
+
+xmlAccess::ColumnAttributes CustomGridRim::getColumnAttributes()
+{
+ std::sort(columnSettings.begin(), columnSettings.end(), xmlAccess::sortByPositionAndVisibility);
+
+ xmlAccess::ColumnAttributes output;
+ xmlAccess::ColumnAttrib newEntry;
+ for (size_t i = 0; i < columnSettings.size(); ++i)
+ {
+ newEntry = columnSettings[i];
+ if (newEntry.visible)
+ newEntry.width = GetColSize(static_cast<int>(i)); //hidden columns are sorted to the end of vector!
+ output.push_back(newEntry);
+ }
+
+ return output;
+}
+
+
+void CustomGridRim::setColumnAttributes(const xmlAccess::ColumnAttributes& attr)
+{
+ //remove special alignment for column "size"
+ if (GetLayoutDirection() != wxLayout_RightToLeft) //don't change for RTL languages
+ for (int i = 0; i < GetNumberCols(); ++i)
+ if (getTypeAtPos(i) == xmlAccess::SIZE)
+ {
+ wxGridCellAttr* cellAttributes = GetOrCreateCellAttr(0, i);
+ cellAttributes->SetAlignment(wxALIGN_LEFT,wxALIGN_CENTRE);
+ SetColAttr(i, cellAttributes);
+ break;
+ }
+//----------------------------------------------------------------------------------
+
+ columnSettings.clear();
+ if (attr.size() == 0)
+ {
+ //default settings:
+ columnSettings = getDefaultColumnAttributes();
+ }
+ else
+ {
+ for (size_t i = 0; i < xmlAccess::COLUMN_TYPE_COUNT; ++i)
+ {
+ xmlAccess::ColumnAttrib newEntry;
+
+ if (i < attr.size())
+ newEntry = attr[i];
+ else //fix corrupted data:
+ {
+ newEntry.type = static_cast<xmlAccess::ColumnTypes>(xmlAccess::COLUMN_TYPE_COUNT); //sort additional rows to the end
+ newEntry.visible = false;
+ newEntry.position = i;
+ newEntry.width = 100;
+ }
+ columnSettings.push_back(newEntry);
+ }
+
+ std::sort(columnSettings.begin(), columnSettings.end(), xmlAccess::sortByType);
+ for (size_t i = 0; i < xmlAccess::COLUMN_TYPE_COUNT; ++i) //just be sure that each type exists only once
+ columnSettings[i].type = static_cast<xmlAccess::ColumnTypes>(i);
+
+ std::sort(columnSettings.begin(), columnSettings.end(), xmlAccess::sortByPositionOnly);
+ for (size_t i = 0; i < xmlAccess::COLUMN_TYPE_COUNT; ++i) //just be sure that positions are numbered correctly
+ columnSettings[i].position = i;
+ }
+
+ std::sort(columnSettings.begin(), columnSettings.end(), xmlAccess::sortByPositionAndVisibility);
+ std::vector<xmlAccess::ColumnTypes> newPositions;
+ for (size_t i = 0; i < columnSettings.size() && columnSettings[i].visible; ++i) //hidden columns are sorted to the end of vector!
+ newPositions.push_back(columnSettings[i].type);
+
+ //set column positions
+ assert(getGridDataTable());
+ getGridDataTable()->setupColumns(newPositions);
+
+ //set column width (set them after setupColumns!)
+ for (size_t i = 0; i < newPositions.size(); ++i)
+ SetColSize(static_cast<int>(i), columnSettings[i].width);
+
+//--------------------------------------------------------------------------------------------------------
+ //set special alignment for column "size"
+ if (GetLayoutDirection() != wxLayout_RightToLeft) //don't change for RTL languages
+ for (int i = 0; i < GetNumberCols(); ++i)
+ if (getTypeAtPos(i) == xmlAccess::SIZE)
+ {
+ wxGridCellAttr* cellAttributes = GetOrCreateCellAttr(0, i);
+ cellAttributes->SetAlignment(wxALIGN_RIGHT, wxALIGN_CENTRE);
+ SetColAttr(i, cellAttributes); //make filesize right justified on grids
+ break;
+ }
+
+ ClearSelection();
+ ForceRefresh();
+}
+
+
+xmlAccess::ColumnTypes CustomGridRim::getTypeAtPos(size_t pos) const
+{
+ assert(getGridDataTable());
+ return getGridDataTable()->getTypeAtPos(pos);
+}
+
+
+wxString CustomGridRim::getTypeName(xmlAccess::ColumnTypes colType)
+{
+ switch (colType)
+ {
+ case xmlAccess::FULL_PATH:
+ return _("Full path");
+ case xmlAccess::FILENAME:
+ return _("Filename");
+ case xmlAccess::REL_PATH:
+ return _("Relative path");
+ case xmlAccess::DIRECTORY:
+ return _("Directory");
+ case xmlAccess::SIZE:
+ return _("Size");
+ case xmlAccess::DATE:
+ return _("Date");
+ case xmlAccess::EXTENSION:
+ return _("Extension");
+ }
+
+ return wxEmptyString; //dummy
+}
+
+
+CustomGridTableRim* CustomGridRim::getGridDataTable()
+{
+ //let the non-const call the const version: see Meyers Effective C++
+ return const_cast<CustomGridTableRim*>(static_cast<const CustomGridRim*>(this)->getGridDataTable());
+}
+
+
+void CustomGridRim::autoSizeColumns() //performance optimized column resizer (analog to wxGrid::AutoSizeColumns()
+{
+ for (int col = 0; col < GetNumberCols(); ++col)
+ {
+ if (col < 0)
+ return;
+
+ int rowMax = -1;
+ size_t lenMax = 0;
+ for (int row = 0; row < GetNumberRows(); ++row)
+ if (GetCellValue(row, col).size() > lenMax)
+ {
+ lenMax = GetCellValue(row, col).size();
+ rowMax = row;
+ }
+
+ wxCoord extentMax = 0;
+
+ //calculate width of (most likely) widest cell
+ wxClientDC dc(GetGridWindow());
+ if (rowMax > -1)
+ {
+ wxGridCellAttr* attr = GetCellAttr(rowMax, col);
+ if (attr)
+ {
+ wxGridCellRenderer* renderer = attr->GetRenderer(this, rowMax, col);
+ if (renderer)
+ {
+ const wxSize size = renderer->GetBestSize(*this, *attr, dc, rowMax, col);
+ extentMax = std::max(extentMax, size.x);
+ renderer->DecRef();
+ }
+ attr->DecRef();
+ }
+ }
+
+ //consider column label
+ dc.SetFont(GetLabelFont());
+ wxCoord w = 0;
+ wxCoord h = 0;
+ dc.GetMultiLineTextExtent(GetColLabelValue(col), &w, &h );
+ if (GetColLabelTextOrientation() == wxVERTICAL)
+ w = h;
+ extentMax = std::max(extentMax, w);
+
+ extentMax += 15; //leave some space around text
+
+ SetColSize(col, extentMax);
+
+ }
+ Refresh();
+}
+
+
+void CustomGridRim::enableFileIcons(const bool value)
+{
+ fileIconsAreEnabled = value;
+
+ if (value)
+ SetDefaultRenderer(new GridCellRenderer<true>(loadIconSuccess, getGridDataTable())); //SetDefaultRenderer takes ownership!
+ else
+ SetDefaultRenderer(new GridCellRenderer<false>(loadIconSuccess, getGridDataTable()));
+
+ Refresh();
+}
+
+
+CustomGridRim::VisibleRowRange CustomGridRim::getVisibleRows()
+{
+ int dummy = -1;
+ int height = -1;
+ GetGridWindow()->GetClientSize(&dummy, &height);
+
+ if (height >= 0)
+ {
+ int topRowY = -1;
+ CalcUnscrolledPosition(0, 0, &dummy, &topRowY);
+
+ if (topRowY >= 0)
+ {
+ const int topRow = YToRow(topRowY);
+ const int rowCount = static_cast<int>(ceil(height / static_cast<double>(GetDefaultRowSize()))); // = height / rowHeight rounded up
+ const int bottomRow = topRow + rowCount - 1;
+
+ return VisibleRowRange(topRow, bottomRow); //"top" means here top of the screen: => smaller value
+ }
+ }
+
+ return VisibleRowRange(0, 0);
+}
+
+
+void CustomGridRim::getIconsToBeLoaded(std::vector<Zstring>& newLoad) //loads all (not yet) drawn icons
+{
+ newLoad.clear();
+
+ if (fileIconsAreEnabled) //don't check too often! give worker thread some time to fetch data
+ {
+ const CustomGridTableRim* gridDataTable = getGridDataTable();
+ const VisibleRowRange rowsOnScreen = getVisibleRows();
+ const int totalCols = const_cast<CustomGridTableRim*>(gridDataTable)->GetNumberCols();
+ const int totalRows = const_cast<CustomGridTableRim*>(gridDataTable)->GetNumberRows();
+
+ //loop over all visible rows
+ const int firstRow = static_cast<int>(rowsOnScreen.first);
+ const int lastRow = std::min(int(rowsOnScreen.second), totalRows - 1);
+ const int rowNo = lastRow - firstRow + 1;
+
+ for (int i = 0; i < rowNo; ++i)
+ {
+ //alternate when adding rows: first, last, first + 1, last - 1 ...
+ const int currentRow = i % 2 == 0 ?
+ firstRow + i / 2 :
+ lastRow - (i - 1) / 2;
+
+ LoadSuccess::const_iterator j = loadIconSuccess.find(currentRow);
+ if (j != loadIconSuccess.end() && j->second == false) //find failed attempts to load icon
+ {
+ const Zstring fileName = gridDataTable->getIconFile(currentRow);
+ if (!fileName.empty())
+ {
+ //test if they are already loaded in buffer:
+ if (ffs3::IconBuffer::getInstance().requestFileIcon(fileName))
+ {
+ //exists in buffer: refresh Row
+ for (int k = 0; k < totalCols; ++k)
+ if (gridDataTable->getTypeAtPos(k) == xmlAccess::FILENAME)
+ {
+ RefreshCell(currentRow, k);
+ break;
+ }
+ }
+ else //not yet in buffer: mark for async. loading
+ {
+ newLoad.push_back(fileName);
+ }
+ }
+ }
+ }
+ }
+}
+
+//----------------------------------------------------------------------------------------
+
+
+//update file icons periodically: use SINGLE instance to coordinate left and right grid at once
+IconUpdater::IconUpdater(CustomGridLeft* leftGrid, CustomGridRight* rightGrid) :
+ m_leftGrid(leftGrid),
+ m_rightGrid(rightGrid),
+ m_timer(new wxTimer) //connect timer event for async. icon loading
+{
+ m_timer->Connect(wxEVT_TIMER, wxEventHandler(IconUpdater::loadIconsAsynchronously), NULL, this);
+ m_timer->Start(50); //timer interval in ms
+}
+
+
+IconUpdater::~IconUpdater() {} //non-inline destructor for std::auto_ptr to work with forward declaration
+
+
+void IconUpdater::loadIconsAsynchronously(wxEvent& event) //loads all (not yet) drawn icons
+{
+ std::vector<Zstring> iconsLeft;
+ m_leftGrid->getIconsToBeLoaded(iconsLeft);
+
+ std::vector<Zstring> newLoad;
+ m_rightGrid->getIconsToBeLoaded(newLoad);
+
+ //merge vectors
+ newLoad.insert(newLoad.end(), iconsLeft.begin(), iconsLeft.end());
+
+ ffs3::IconBuffer::getInstance().setWorkload(newLoad); //attention: newLoad is invalidated after this call!!!
+
+ //event.Skip();
+}
+
+//----------------------------------------------------------------------------------------
+
+
+CustomGridLeft::CustomGridLeft(wxWindow *parent,
+ wxWindowID id,
+ const wxPoint& pos,
+ const wxSize& size,
+ long style,
+ const wxString& name) :
+ CustomGridRim(parent, id, pos, size, style, name),
+ gridDataTable(NULL) {}
+
+
+bool CustomGridLeft::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;
+ SetTable(gridDataTable, true, wxGrid::wxGridSelectRows); //give ownership to wxGrid: gridDataTable is deleted automatically in wxGrid destructor
+
+ return true;
+}
+
+
+void CustomGridLeft::setGridDataTable(const GridView* gridDataView)
+{
+ //set underlying grid data
+ assert(gridDataTable);
+ gridDataTable->setGridDataTable(gridDataView);
+}
+
+
+const CustomGridTableRim* CustomGridLeft::getGridDataTable() const
+{
+ return gridDataTable;
+}
+
+
+//this method is called when grid view changes: useful for parallel updating of multiple grids
+void CustomGridLeft::alignOtherGrids(CustomGrid* gridLeft, CustomGrid* gridMiddle, CustomGrid* gridRight)
+{
+ int x = 0;
+ int y = 0;
+ GetViewStart(&x, &y);
+
+ gridMiddle->Scroll(-1, y); //scroll in y-direction only
+ gridRight->Scroll(x, y);
+}
+
+
+//----------------------------------------------------------------------------------------
+CustomGridRight::CustomGridRight(wxWindow *parent,
+ wxWindowID id,
+ const wxPoint& pos,
+ const wxSize& size,
+ long style,
+ const wxString& name) :
+ CustomGridRim(parent, id, pos, size, style, name),
+ gridDataTable(NULL) {}
+
+
+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;
+}
+
+
+void CustomGridRight::setGridDataTable(const GridView* gridDataView)
+{
+ //set underlying grid data
+ assert(gridDataTable);
+ gridDataTable->setGridDataTable(gridDataView);
+}
+
+
+const CustomGridTableRim* CustomGridRight::getGridDataTable() const
+{
+ return gridDataTable;
+}
+
+
+//this method is called when grid view changes: useful for parallel updating of multiple grids
+void CustomGridRight::alignOtherGrids(CustomGrid* gridLeft, CustomGrid* gridMiddle, CustomGrid* gridRight)
+{
+ int x = 0;
+ int y = 0;
+ GetViewStart(&x, &y);
+ gridLeft->Scroll(x, y);
+ gridMiddle->Scroll(-1, y);
+}
+
+
+//----------------------------------------------------------------------------------------
+class GridCellRendererMiddle : public wxGridCellStringRenderer
+{
+public:
+ GridCellRendererMiddle(const CustomGridMiddle* middleGrid) : m_gridMiddle(middleGrid) {};
+
+ virtual void Draw(wxGrid& grid,
+ wxGridCellAttr& attr,
+ wxDC& dc,
+ const wxRect& rect,
+ int row, int col,
+ bool isSelected);
+
+private:
+ const CustomGridMiddle* const m_gridMiddle;
+};
+
+
+//define new event types
+const wxEventType FFS_CHECK_ROWS_EVENT = wxNewEventType(); //attention! do NOT place in header to keep (generated) id unique!
+const wxEventType FFS_SYNC_DIRECTION_EVENT = wxNewEventType();
+
+const int CHECK_BOX_IMAGE = 11; //width of checkbox image
+const int CHECK_BOX_WIDTH = CHECK_BOX_IMAGE + 3; //width of first block
+
+// cell:
+// ----------------------------------
+// | checkbox | left | middle | right|
+// ----------------------------------
+
+
+CustomGridMiddle::CustomGridMiddle(wxWindow *parent,
+ wxWindowID id,
+ const wxPoint& pos,
+ const wxSize& size,
+ long style,
+ const wxString& name) :
+ CustomGrid(parent, id, pos, size, style, name),
+ selectionRowBegin(-1),
+ selectionPos(BLOCKPOS_CHECK_BOX),
+ highlightedRow(-1),
+ highlightedPos(BLOCKPOS_CHECK_BOX),
+ gridDataTable(NULL),
+ toolTip(new CustomTooltip)
+{
+ SetLayoutDirection(wxLayout_LeftToRight); //
+ GetGridWindow()->SetLayoutDirection(wxLayout_LeftToRight); //avoid mirroring this dialog in RTL languages like Hebrew or Arabic
+ GetGridColLabelWindow()->SetLayoutDirection(wxLayout_LeftToRight); //
+
+
+ //connect events for dynamic selection of sync direction
+ GetGridWindow()->Connect(wxEVT_MOTION, wxMouseEventHandler(CustomGridMiddle::OnMouseMovement), NULL, this);
+ GetGridWindow()->Connect(wxEVT_LEAVE_WINDOW, wxMouseEventHandler(CustomGridMiddle::OnLeaveWindow), NULL, this);
+ GetGridWindow()->Connect(wxEVT_LEFT_UP, wxMouseEventHandler(CustomGridMiddle::OnLeftMouseUp), NULL, this);
+ GetGridWindow()->Connect(wxEVT_LEFT_DOWN, wxMouseEventHandler(CustomGridMiddle::OnLeftMouseDown), NULL, this);
+}
+
+
+CustomGridMiddle::~CustomGridMiddle() {} //non-inline destructor for std::auto_ptr to work with forward declaration
+
+
+bool CustomGridMiddle::CreateGrid(int numRows, int numCols, wxGrid::wxGridSelectionModes selmode)
+{
+ 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
+ SetDefaultRenderer(new GridCellRendererMiddle(this)); //SetDefaultRenderer takes ownership!
+
+ return true;
+}
+
+
+#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::setGridDataTable(const GridView* gridDataView) //called once on startup
+{
+ //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
+}
+
+
+void CustomGridMiddle::OnMouseMovement(wxMouseEvent& event)
+{
+ const int highlightedRowOld = highlightedRow;
+
+ if (selectionRowBegin == -1) //change highlightning only if currently not dragging mouse
+ {
+ highlightedRow = mousePosToRow(event.GetPosition(), &highlightedPos);
+ if (highlightedRow >= 0)
+ RefreshCell(highlightedRow, 0);
+ if ( highlightedRowOld >= 0 &&
+ highlightedRow != highlightedRowOld)
+ RefreshCell(highlightedRowOld, 0);
+
+ //handle tooltip
+ showToolTip(highlightedRow, GetGridWindow()->ClientToScreen(event.GetPosition()));
+ }
+
+ event.Skip();
+}
+
+
+void CustomGridMiddle::OnLeaveWindow(wxMouseEvent& event)
+{
+ highlightedRow = -1;
+ highlightedPos = BLOCKPOS_CHECK_BOX;
+ Refresh();
+
+ //handle tooltip
+ toolTip->hide();
+}
+
+
+void CustomGridMiddle::showToolTip(int rowNumber, wxPoint pos)
+{
+ const FileSystemObject* const fsObj = gridDataTable->getRawData(rowNumber);
+ if (fsObj == NULL) //if invalid row...
+ {
+ toolTip->hide();
+ return;
+ }
+
+ if (gridDataTable->syncPreviewIsActive()) //synchronization preview
+ {
+ const SyncOperation syncOp = fsObj->getSyncOperation();
+ switch (syncOp)
+ {
+ case SO_CREATE_NEW_LEFT:
+ toolTip->show(getDescription(syncOp), pos, &GlobalResources::getInstance().getImageByName(wxT("syncCreateLeftAct")));
+ break;
+ case SO_CREATE_NEW_RIGHT:
+ toolTip->show(getDescription(syncOp), pos, &GlobalResources::getInstance().getImageByName(wxT("syncCreateRightAct")));
+ break;
+ case SO_DELETE_LEFT:
+ toolTip->show(getDescription(syncOp), pos, &GlobalResources::getInstance().getImageByName(wxT("syncDeleteLeftAct")));
+ break;
+ case SO_DELETE_RIGHT:
+ toolTip->show(getDescription(syncOp), pos, &GlobalResources::getInstance().getImageByName(wxT("syncDeleteRightAct")));
+ break;
+ case SO_OVERWRITE_LEFT:
+ toolTip->show(getDescription(syncOp), pos, &GlobalResources::getInstance().getImageByName(wxT("syncDirLeftAct")));
+ break;
+ case SO_OVERWRITE_RIGHT:
+ toolTip->show(getDescription(syncOp), pos, &GlobalResources::getInstance().getImageByName(wxT("syncDirRightAct")));
+ break;
+ case SO_DO_NOTHING:
+ toolTip->show(getDescription(syncOp), pos, &GlobalResources::getInstance().getImageByName(wxT("syncDirNoneAct")));
+ break;
+ case SO_EQUAL:
+ toolTip->show(getDescription(syncOp), pos, &GlobalResources::getInstance().getImageByName(wxT("equalAct")));
+ break;
+ case SO_UNRESOLVED_CONFLICT:
+ toolTip->show(fsObj->getSyncOpConflict(), pos, &GlobalResources::getInstance().getImageByName(wxT("conflictAct")));
+ break;
+ };
+ }
+ else
+ {
+ const CompareFilesResult cmpRes = fsObj->getCategory();
+ switch (cmpRes)
+ {
+ case FILE_LEFT_SIDE_ONLY:
+ toolTip->show(getDescription(cmpRes), pos, &GlobalResources::getInstance().getImageByName(wxT("leftOnlyAct")));
+ break;
+ case FILE_RIGHT_SIDE_ONLY:
+ toolTip->show(getDescription(cmpRes), pos, &GlobalResources::getInstance().getImageByName(wxT("rightOnlyAct")));
+ break;
+ case FILE_LEFT_NEWER:
+ toolTip->show(getDescription(cmpRes), pos, &GlobalResources::getInstance().getImageByName(wxT("leftNewerAct")));
+ break;
+ case FILE_RIGHT_NEWER:
+ toolTip->show(getDescription(cmpRes), pos, &GlobalResources::getInstance().getImageByName(wxT("rightNewerAct")));
+ break;
+ case FILE_DIFFERENT:
+ toolTip->show(getDescription(cmpRes), pos, &GlobalResources::getInstance().getImageByName(wxT("differentAct")));
+ break;
+ case FILE_EQUAL:
+ toolTip->show(getDescription(cmpRes), pos, &GlobalResources::getInstance().getImageByName(wxT("equalAct")));
+ break;
+ case FILE_CONFLICT:
+ toolTip->show(fsObj->getCatConflict(), pos, &GlobalResources::getInstance().getImageByName(wxT("conflictAct")));
+ break;
+ }
+ }
+}
+
+
+void CustomGridMiddle::OnLeftMouseDown(wxMouseEvent& event)
+{
+ selectionRowBegin = mousePosToRow(event.GetPosition(), &selectionPos);
+ event.Skip();
+}
+
+
+void CustomGridMiddle::OnLeftMouseUp(wxMouseEvent& event)
+{
+ const int rowEndFiltering = mousePosToRow(event.GetPosition());
+
+ if (0 <= selectionRowBegin && 0 <= rowEndFiltering)
+ {
+ switch (selectionPos)
+ {
+ case BLOCKPOS_CHECK_BOX:
+ {
+ //create a custom event
+ FFSCheckRowsEvent evt(selectionRowBegin, rowEndFiltering);
+ AddPendingEvent(evt);
+ }
+ break;
+ case BLOCKPOS_LEFT:
+ {
+ //create a custom event
+ FFSSyncDirectionEvent evt(selectionRowBegin, rowEndFiltering, SYNC_DIR_LEFT);
+ AddPendingEvent(evt);
+ }
+ break;
+ case BLOCKPOS_MIDDLE:
+ {
+ //create a custom event
+ FFSSyncDirectionEvent evt(selectionRowBegin, rowEndFiltering, SYNC_DIR_NONE);
+ AddPendingEvent(evt);
+ }
+ break;
+ case BLOCKPOS_RIGHT:
+ {
+ //create a custom event
+ FFSSyncDirectionEvent evt(selectionRowBegin, rowEndFiltering, SYNC_DIR_RIGHT);
+ AddPendingEvent(evt);
+ }
+ break;
+ }
+ }
+ selectionRowBegin = -1;
+ selectionPos = BLOCKPOS_CHECK_BOX;
+
+ ClearSelection();
+ event.Skip();
+}
+
+
+int CustomGridMiddle::mousePosToRow(const wxPoint pos, BlockPosition* block)
+{
+ int row = -1;
+ int x = -1;
+ int y = -1;
+ CalcUnscrolledPosition( pos.x, pos.y, &x, &y );
+ if (x >= 0 && y >= 0)
+ {
+ row = YToRow(y);
+
+ //determine blockposition within cell (optional)
+ if (block)
+ {
+ *block = BLOCKPOS_CHECK_BOX; //default
+
+ if (row >= 0)
+ {
+ const FileSystemObject* const fsObj = gridDataTable->getRawData(row);
+ if ( fsObj != NULL && //if valid row...
+ gridDataTable->syncPreviewIsActive() &&
+ 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 wxRect rect = CellToRect(row, 0);
+ if (rect.GetWidth() > CHECK_BOX_WIDTH)
+ {
+ const double blockWidth = (rect.GetWidth() - CHECK_BOX_WIDTH) / 3.0;
+ if (rect.GetX() + CHECK_BOX_WIDTH <= x && x < rect.GetX() + rect.GetWidth())
+ {
+ if (x - (rect.GetX() + CHECK_BOX_WIDTH) < blockWidth)
+ *block = BLOCKPOS_LEFT;
+ else if (x - (rect.GetX() + CHECK_BOX_WIDTH) < 2 * blockWidth)
+ *block = BLOCKPOS_MIDDLE;
+ else
+ *block = BLOCKPOS_RIGHT;
+ }
+ }
+ }
+ }
+ }
+ }
+ return row;
+}
+
+
+void CustomGridMiddle::enableSyncPreview(bool value)
+{
+ assert(gridDataTable);
+ gridDataTable->enableSyncPreview(value);
+
+ if (value)
+ GetGridColLabelWindow()->SetToolTip(_("Synchronization Preview"));
+ else
+ GetGridColLabelWindow()->SetToolTip(_("Comparison Result"));
+}
+
+
+void CustomGridMiddle::updateGridSizes()
+{
+ assert(gridDataTable);
+ gridDataTable->updateGridSizes();
+}
+
+
+void GridCellRendererMiddle::Draw(wxGrid& grid,
+ wxGridCellAttr& attr,
+ wxDC& dc,
+ const wxRect& rect,
+ int row, int col,
+ bool isSelected)
+{
+ //retrieve grid data
+ const FileSystemObject* const fsObj = m_gridMiddle->gridDataTable->getRawData(row);
+ if (fsObj != NULL) //if valid row...
+ {
+ if (rect.GetWidth() > CHECK_BOX_WIDTH)
+ {
+ const bool rowIsHighlighted = row == m_gridMiddle->highlightedRow;
+
+ wxRect rectShrinked(rect);
+
+ //clean first block of rect that will receive image of checkbox
+ rectShrinked.SetWidth(CHECK_BOX_WIDTH);
+ wxGridCellRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected);
+
+ //print checkbox into first block
+ rectShrinked.SetX(rect.GetX() + 1);
+
+ //HIGHLIGHTNING (checkbox):
+ if ( rowIsHighlighted &&
+ m_gridMiddle->highlightedPos == CustomGridMiddle::BLOCKPOS_CHECK_BOX)
+ {
+ if (fsObj->isActive())
+ dc.DrawLabel(wxEmptyString, GlobalResources::getInstance().getImageByName(wxT("checkboxTrueFocus")), rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
+ else
+ dc.DrawLabel(wxEmptyString, GlobalResources::getInstance().getImageByName(wxT("checkboxFalseFocus")), rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
+ }
+ //default
+ else if (fsObj->isActive())
+ dc.DrawLabel(wxEmptyString, GlobalResources::getInstance().getImageByName(wxT("checkboxTrue")), rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
+ else
+ dc.DrawLabel(wxEmptyString, GlobalResources::getInstance().getImageByName(wxT("checkboxFalse")), rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
+
+ //clean remaining block of rect that will receive image of checkbox/directions
+ rectShrinked.SetWidth(rect.GetWidth() - CHECK_BOX_WIDTH);
+ rectShrinked.SetX(rect.GetX() + CHECK_BOX_WIDTH);
+ wxGridCellRenderer::Draw(grid, attr, dc, rectShrinked, row, col, isSelected);
+
+ //print remaining block
+ if (m_gridMiddle->gridDataTable->syncPreviewIsActive()) //synchronization preview
+ {
+ //print sync direction into second block
+
+ //HIGHLIGHTNING (sync direction):
+ if ( rowIsHighlighted &&
+ m_gridMiddle->highlightedPos != CustomGridMiddle::BLOCKPOS_CHECK_BOX) //don't allow changing direction for "=="-files
+ switch (m_gridMiddle->highlightedPos)
+ {
+ case CustomGridMiddle::BLOCKPOS_CHECK_BOX:
+ break;
+ case CustomGridMiddle::BLOCKPOS_LEFT:
+ dc.DrawLabel(wxEmptyString, getSyncOpImage(fsObj->testSyncOperation(true, SYNC_DIR_LEFT)), rectShrinked, wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL);
+ break;
+ case CustomGridMiddle::BLOCKPOS_MIDDLE:
+ dc.DrawLabel(wxEmptyString, getSyncOpImage(fsObj->testSyncOperation(true, SYNC_DIR_NONE)), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ break;
+ case CustomGridMiddle::BLOCKPOS_RIGHT:
+ dc.DrawLabel(wxEmptyString, getSyncOpImage(fsObj->testSyncOperation(true, SYNC_DIR_RIGHT)), rectShrinked, wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL);
+ break;
+ }
+ else //default
+ {
+ const wxBitmap& syncOpIcon = getSyncOpImage(fsObj->getSyncOperation());
+ dc.DrawLabel(wxEmptyString, syncOpIcon, rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ }
+ }
+ else //comparison results view
+ {
+ switch (fsObj->getCategory())
+ {
+ case FILE_LEFT_SIDE_ONLY:
+ dc.DrawLabel(wxEmptyString, GlobalResources::getInstance().getImageByName(wxT("leftOnlySmall")), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ break;
+ case FILE_RIGHT_SIDE_ONLY:
+ dc.DrawLabel(wxEmptyString, GlobalResources::getInstance().getImageByName(wxT("rightOnlySmall")), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ break;
+ case FILE_LEFT_NEWER:
+ dc.DrawLabel(wxEmptyString, GlobalResources::getInstance().getImageByName(wxT("leftNewerSmall")), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ break;
+ case FILE_RIGHT_NEWER:
+ dc.DrawLabel(wxEmptyString, GlobalResources::getInstance().getImageByName(wxT("rightNewerSmall")), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ break;
+ case FILE_DIFFERENT:
+ dc.DrawLabel(wxEmptyString, GlobalResources::getInstance().getImageByName(wxT("differentSmall")), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ break;
+ case FILE_EQUAL:
+ dc.DrawLabel(wxEmptyString, GlobalResources::getInstance().getImageByName(wxT("equalSmall")), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ break;
+ case FILE_CONFLICT:
+ dc.DrawLabel(wxEmptyString, GlobalResources::getInstance().getImageByName(wxT("conflictSmall")), rectShrinked, wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
+ break;
+ }
+ }
+
+ return;
+ }
+ }
+
+ //fallback
+ 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 CustomGridMiddle::alignOtherGrids(CustomGrid* gridLeft, CustomGrid* gridMiddle, CustomGrid* gridRight)
+{
+ int x = 0;
+ int y = 0;
+ GetViewStart(&x, &y);
+ gridLeft->Scroll(-1, y);
+ gridRight->Scroll(-1, y);
+}
+
+
+void CustomGridMiddle::DrawColLabel(wxDC& dc, int col)
+{
+ CustomGrid::DrawColLabel(dc, col);
+
+ const wxRect rect(GetColLeft(col), 0, GetColWidth(col), GetColLabelSize());
+
+ if (gridDataTable->syncPreviewIsActive())
+ dc.DrawLabel(wxEmptyString, GlobalResources::getInstance().getImageByName(wxT("syncViewSmall")), rect, wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
+ else
+ dc.DrawLabel(wxEmptyString, GlobalResources::getInstance().getImageByName(wxT("cmpViewSmall")), rect, wxALIGN_CENTER_HORIZONTAL | wxALIGN_CENTER_VERTICAL);
+}
+
+
+const wxBitmap& ffs3::getSyncOpImage(SyncOperation syncOp)
+{
+ switch (syncOp) //evaluate comparison result and sync direction
+ {
+ case SO_CREATE_NEW_LEFT:
+ return GlobalResources::getInstance().getImageByName(wxT("createLeftSmall"));
+ case SO_CREATE_NEW_RIGHT:
+ return GlobalResources::getInstance().getImageByName(wxT("createRightSmall"));
+ case SO_DELETE_LEFT:
+ return GlobalResources::getInstance().getImageByName(wxT("deleteLeftSmall"));
+ case SO_DELETE_RIGHT:
+ return GlobalResources::getInstance().getImageByName(wxT("deleteRightSmall"));
+ case SO_OVERWRITE_RIGHT:
+ return GlobalResources::getInstance().getImageByName(wxT("syncDirRightSmall"));
+ case SO_OVERWRITE_LEFT:
+ return GlobalResources::getInstance().getImageByName(wxT("syncDirLeftSmall"));
+ case SO_DO_NOTHING:
+ return GlobalResources::getInstance().getImageByName(wxT("syncDirNoneSmall"));
+ case SO_EQUAL:
+ return GlobalResources::getInstance().getImageByName(wxT("equalSmall"));
+ case SO_UNRESOLVED_CONFLICT:
+ return GlobalResources::getInstance().getImageByName(wxT("conflictSmall"));
+ }
+
+ return wxNullBitmap; //dummy
+}
+
bgstack15