// ***************************************************************************** // * This file is part of the FreeFileSync project. It is distributed under * // * GNU General Public License: https://www.gnu.org/licenses/gpl-3.0 * // * Copyright (C) Zenju (zenju AT freefilesync DOT org) - All Rights Reserved * // ***************************************************************************** #ifndef GRID_H_834702134831734869987 #define GRID_H_834702134831734869987 #include #include #include #include #include #include //a user-friendly, extensible and high-performance grid control namespace zen { enum class ColumnType { NONE = -1 }; //user-defiend column type enum class HoverArea { NONE = -1 }; //user-defined area for mouse selections for a given row (may span multiple columns or split a single column into multiple areas) //------------------------ events ------------------------------------------------ extern const wxEventType EVENT_GRID_MOUSE_LEFT_DOUBLE; // extern const wxEventType EVENT_GRID_MOUSE_LEFT_DOWN; // extern const wxEventType EVENT_GRID_MOUSE_LEFT_UP; //generates: GridClickEvent extern const wxEventType EVENT_GRID_MOUSE_RIGHT_DOWN; // extern const wxEventType EVENT_GRID_MOUSE_RIGHT_UP; // extern const wxEventType EVENT_GRID_SELECT_RANGE; //generates: GridRangeSelectEvent //NOTE: neither first nor second row need to match EVENT_GRID_MOUSE_LEFT_DOWN/EVENT_GRID_MOUSE_LEFT_UP: user holding SHIFT; moving out of window... extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_LEFT; //generates: GridLabelClickEvent extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_RIGHT; // extern const wxEventType EVENT_GRID_COL_RESIZE; //generates: GridColumnResizeEvent //example: wnd.Connect(EVENT_GRID_COL_LABEL_LEFT_CLICK, GridClickEventHandler(MyDlg::OnLeftClick), nullptr, this); struct GridClickEvent : public wxMouseEvent { GridClickEvent(wxEventType et, const wxMouseEvent& me, ptrdiff_t row, HoverArea hoverArea) : wxMouseEvent(me), row_(row), hoverArea_(hoverArea) { SetEventType(et); } wxEvent* Clone() const override { return new GridClickEvent(*this); } const ptrdiff_t row_; //-1 for invalid position, >= rowCount if out of range const HoverArea hoverArea_; //may be HoverArea::NONE }; struct GridRangeSelectEvent : public wxCommandEvent { GridRangeSelectEvent(size_t rowFirst, size_t rowLast, bool positive, const GridClickEvent* mouseInitiated) : wxCommandEvent(EVENT_GRID_SELECT_RANGE), rowFirst_(rowFirst), rowLast_(rowLast), positive_(positive), mouseInitiated_(mouseInitiated ? *mouseInitiated : Opt()) { assert(rowFirst <= rowLast); } wxEvent* Clone() const override { return new GridRangeSelectEvent(*this); } const size_t rowFirst_; //selected range: [rowFirst_, rowLast_) const size_t rowLast_; const bool positive_; //"false" when clearing selection! Opt mouseInitiated_; //filled unless selection was performed via keyboard shortcuts or is result of Grid::clearSelection() }; struct GridLabelClickEvent : public wxMouseEvent { GridLabelClickEvent(wxEventType et, const wxMouseEvent& me, ColumnType colType) : wxMouseEvent(me), colType_(colType) { SetEventType(et); } wxEvent* Clone() const override { return new GridLabelClickEvent(*this); } const ColumnType colType_; //may be ColumnType::NONE }; struct GridColumnResizeEvent : public wxCommandEvent { GridColumnResizeEvent(int offset, ColumnType colType) : wxCommandEvent(EVENT_GRID_COL_RESIZE), colType_(colType), offset_(offset) {} wxEvent* Clone() const override { return new GridColumnResizeEvent(*this); } const ColumnType colType_; const int offset_; }; using GridClickEventFunction = void (wxEvtHandler::*)(GridClickEvent&); using GridRangeSelectEventFunction = void (wxEvtHandler::*)(GridRangeSelectEvent&); using GridLabelClickEventFunction = void (wxEvtHandler::*)(GridLabelClickEvent&); using GridColumnResizeEventFunction = void (wxEvtHandler::*)(GridColumnResizeEvent&); #define GridClickEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridClickEventFunction, &func) #define GridRangeSelectEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridRangeSelectEventFunction, &func) #define GridLabelClickEventHandler(func) (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridLabelClickEventFunction, &func) #define GridColumnResizeEventHandler(func)(wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridColumnResizeEventFunction, &func) //------------------------------------------------------------------------------------------------------------ class Grid; class GridData { public: virtual ~GridData() {} virtual size_t getRowCount() const = 0; //cell area: virtual std::wstring getValue(size_t row, ColumnType colType) const = 0; virtual void renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected); //default implementation virtual void renderCell (wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover); virtual int getBestSize (wxDC& dc, size_t row, ColumnType colType); //must correspond to renderCell()! virtual std::wstring getToolTip (size_t row, ColumnType colType) const { return std::wstring(); } virtual HoverArea getRowMouseHover(size_t row, ColumnType colType, int cellRelativePosX, int cellWidth) { return HoverArea::NONE; } //label area: virtual std::wstring getColumnLabel(ColumnType colType) const = 0; virtual void renderColumnLabel(Grid& grid, wxDC& dc, const wxRect& rect, ColumnType colType, bool highlighted); //default implementation virtual std::wstring getToolTip(ColumnType colType) const { return std::wstring(); } static const int COLUMN_GAP_LEFT; //for left-aligned text //optional helper routines: static wxSize drawCellText (wxDC& dc, const wxRect& rect, const std::wstring& text, int alignment = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); //returns text extent static wxRect drawCellBorder (wxDC& dc, const wxRect& rect); //returns inner rectangle static void drawCellBackground(wxDC& dc, const wxRect& rect, bool enabled, bool selected, const wxColor& backgroundColor); static wxRect drawColumnLabelBorder (wxDC& dc, const wxRect& rect); //returns inner rectangle static void drawColumnLabelBackground(wxDC& dc, const wxRect& rect, bool highlighted); static void drawColumnLabelText (wxDC& dc, const wxRect& rect, const std::wstring& text); }; enum GridEventPolicy { ALLOW_GRID_EVENT, DENY_GRID_EVENT }; class Grid : public wxScrolledWindow { public: Grid(wxWindow* parent, wxWindowID id = wxID_ANY, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = wxTAB_TRAVERSAL | wxNO_BORDER, const wxString& name = wxPanelNameStr); size_t getRowCount() const; void setRowHeight(int height); struct ColumnAttribute { ColumnAttribute(ColumnType type, int offset, int stretch, bool visible = true) : type_(type), visible_(visible), stretch_(std::max(stretch, 0)), offset_(offset) { assert(stretch >=0 ); } ColumnType type_; bool visible_; //first, client width is partitioned according to all available stretch factors, then "offset_" is added //universal model: a non-stretched column has stretch factor 0 with the "offset" becoming identical to final width! int stretch_; //>= 0 int offset_; }; void setColumnConfig(const std::vector& attr); //set column count + widths std::vector getColumnConfig() const; void setDataProvider(const std::shared_ptr& dataView) { dataView_ = dataView; } /**/ GridData* getDataProvider() { return dataView_.get(); } const GridData* getDataProvider() const { return dataView_.get(); } //----------------------------------------------------------------------------- void setColumnLabelHeight(int height); void showRowLabel(bool visible); enum ScrollBarStatus { SB_SHOW_AUTOMATIC, SB_SHOW_ALWAYS, SB_SHOW_NEVER, }; //alternative until wxScrollHelper::ShowScrollbars() becomes available in wxWidgets 2.9 void showScrollBars(ScrollBarStatus horizontal, ScrollBarStatus vertical); std::vector getSelectedRows() const { return selection_.get(); } void selectAllRows (GridEventPolicy rangeEventPolicy); void clearSelection(GridEventPolicy rangeEventPolicy); //turn off range selection event when calling this function in an event handler to avoid recursion! void scrollDelta(int deltaX, int deltaY); //in scroll units wxWindow& getCornerWin (); wxWindow& getRowLabelWin(); wxWindow& getColLabelWin(); wxWindow& getMainWin (); const wxWindow& getMainWin() const; ptrdiff_t getRowAtPos(int posY) const; //return -1 for invalid position, >= rowCount if out of range; absolute coordinates! struct ColumnPosInfo { ColumnType colType; //ColumnType::NONE no column at x position! int cellRelativePosX; int colWidth; }; ColumnPosInfo getColumnAtPos(int posX) const; //absolute position! void refreshCell(size_t row, ColumnType colType); void enableColumnMove (bool value) { allowColumnMove_ = value; } void enableColumnResize(bool value) { allowColumnResize_ = value; } void setGridCursor(size_t row); //set + show + select cursor (+ emit range selection event) size_t getGridCursor() const; //returns row void scrollTo(size_t row); void Refresh(bool eraseBackground = true, const wxRect* rect = nullptr) override; bool Enable(bool enable = true) override; //############################################################################################################ static wxColor getColorSelectionGradientFrom(); static wxColor getColorSelectionGradientTo(); private: void onPaintEvent(wxPaintEvent& event); void onEraseBackGround(wxEraseEvent& event) {} //[!] void onSizeEvent(wxSizeEvent& event) { updateWindowSizes(); event.Skip(); } void onKeyDown(wxKeyEvent& event); void updateWindowSizes(bool updateScrollbar = true); void selectWithCursor(ptrdiff_t row); void makeRowVisible(size_t row); void redirectRowLabelEvent(wxMouseEvent& event); wxSize GetSizeAvailableForScrollTarget(const wxSize& size) override; //required since wxWidgets 2.9 if SetTargetWindow() is used int getBestColumnSize(size_t col) const; //return -1 on error void autoSizeColumns(GridEventPolicy columnResizeEventPolicy); friend class GridData; class SubWindow; class CornerWin; class RowLabelWin; class ColLabelWin; class MainWin; class Selection { public: void init(size_t rowCount) { rowSelectionValue.resize(rowCount); clear(); } size_t maxSize() const { return rowSelectionValue.size(); } std::vector get() const { std::vector result; for (size_t row = 0; row < rowSelectionValue.size(); ++row) if (rowSelectionValue[row] != 0) result.push_back(row); return result; } void selectAll() { selectRange(0, rowSelectionValue.size(), true); } void clear () { selectRange(0, rowSelectionValue.size(), false); } bool isSelected(size_t row) const { return row < rowSelectionValue.size() ? rowSelectionValue[row] != 0 : false; } void selectRange(size_t rowFirst, size_t rowLast, bool positive = true) //select [rowFirst, rowLast), trims if required! { if (rowFirst <= rowLast) { numeric::clamp(rowFirst, 0, rowSelectionValue.size()); numeric::clamp(rowLast, 0, rowSelectionValue.size()); std::fill(rowSelectionValue.begin() + rowFirst, rowSelectionValue.begin() + rowLast, positive); } else assert(false); } private: std::vector rowSelectionValue; //effectively a vector of size "number of rows" }; struct VisibleColumn { VisibleColumn(ColumnType type, int offset, int stretch) : type_(type), stretch_(stretch), offset_(offset) {} ColumnType type_; int stretch_; //>= 0 int offset_; }; struct ColumnWidth { ColumnWidth(ColumnType type, int width) : type_(type), width_(width) {} ColumnType type_; int width_; }; std::vector getColWidths() const; // std::vector getColWidths(int mainWinWidth) const; //evaluate stretched columns int getColWidthsSum(int mainWinWidth) const; std::vector getColStretchedWidths(int clientWidth) const; //final width = (normalized) (stretchedWidth + offset) Opt getColWidth(size_t col) const { const auto& widths = getColWidths(); if (col < widths.size()) return widths[col].width_; return NoValue(); } void setColumnWidth(int width, size_t col, GridEventPolicy columnResizeEventPolicy, bool notifyAsync = false); wxRect getColumnLabelArea(ColumnType colType) const; //returns empty rect if column not found void selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const GridClickEvent* mouseInitiated); //select inclusive range [rowFrom, rowTo] + notify event! bool isSelected(size_t row) const { return selection_.isSelected(row); } struct ColAction { bool wantResize = false; //"!wantResize" means "move" or "single click" size_t col = 0; }; Opt clientPosToColumnAction(const wxPoint& pos) const; void moveColumn(size_t colFrom, size_t colTo); ptrdiff_t clientPosToMoveTargetColumn(const wxPoint& pos) const; //return < 0 on error ColumnType colToType(size_t col) const; //returns ColumnType::NONE on error /* Visual layout: -------------------------------- |CornerWin | ColLabelWin | |------------------------------| |RowLabelWin | MainWin | | | | -------------------------------- */ CornerWin* cornerWin_; RowLabelWin* rowLabelWin_; ColLabelWin* colLabelWin_; MainWin* mainWin_; ScrollBarStatus showScrollbarX_ = SB_SHOW_AUTOMATIC; ScrollBarStatus showScrollbarY_ = SB_SHOW_AUTOMATIC; int colLabelHeight_ = 0; bool drawRowLabel_ = true; std::shared_ptr dataView_; Selection selection_; bool allowColumnMove_ = true; bool allowColumnResize_ = true; std::vector visibleCols_; //individual widths, type and total column count std::vector oldColAttributes_; //visible + nonvisible columns; use for conversion in setColumnConfig()/getColumnConfig() *only*! size_t rowCountOld_ = 0; //at the time of last Grid::Refresh() }; } #endif //GRID_H_834702134831734869987