diff options
author | Daniel Wilhelm <daniel@wili.li> | 2016-03-16 21:32:07 +0100 |
---|---|---|
committer | Daniel Wilhelm <daniel@wili.li> | 2016-03-16 21:32:07 +0100 |
commit | ce3574cf7ff2ee68608b4d001f5a6dd1e36b2252 (patch) | |
tree | 576b1741351e1cd34f0fcce49f98df9c17e10912 /wx+ | |
parent | 7.6 (diff) | |
download | FreeFileSync-ce3574cf7ff2ee68608b4d001f5a6dd1e36b2252.tar.gz FreeFileSync-ce3574cf7ff2ee68608b4d001f5a6dd1e36b2252.tar.bz2 FreeFileSync-ce3574cf7ff2ee68608b4d001f5a6dd1e36b2252.zip |
7.7
Diffstat (limited to 'wx+')
-rw-r--r-- | wx+/font_size.h | 2 | ||||
-rw-r--r-- | wx+/graph.cpp | 25 | ||||
-rw-r--r-- | wx+/graph.h | 18 | ||||
-rw-r--r-- | wx+/grid.cpp | 482 | ||||
-rw-r--r-- | wx+/grid.h | 105 | ||||
-rw-r--r-- | wx+/image_resources.cpp | 7 | ||||
-rw-r--r-- | wx+/image_resources.h | 1 | ||||
-rw-r--r-- | wx+/image_tools.h | 16 | ||||
-rw-r--r-- | wx+/tooltip.h | 5 |
9 files changed, 365 insertions, 296 deletions
diff --git a/wx+/font_size.h b/wx+/font_size.h index 2302e056..2858afb6 100644 --- a/wx+/font_size.h +++ b/wx+/font_size.h @@ -69,7 +69,7 @@ void setMainInstructionFont(wxWindow& control) 0, // _In_ int iStateId, TMT_TEXTCOLOR, // _In_ int iPropId, &cr) == S_OK) // _Out_ COLORREF *pColor - control.SetForegroundColour(wxColour(cr)); + control.SetForegroundColour(wxColor(cr)); } #elif defined ZEN_LINUX //https://developer.gnome.org/hig-book/3.2/hig-book.html#alert-text diff --git a/wx+/graph.cpp b/wx+/graph.cpp index bf1f1567..f39d29f5 100644 --- a/wx+/graph.cpp +++ b/wx+/graph.cpp @@ -19,9 +19,6 @@ using namespace zen; const wxEventType zen::wxEVT_GRAPH_SELECTION = wxNewEventType(); -//for some buggy reason MSVC isn't able to use a temporary as a default argument -const std::shared_ptr<LabelFormatter> Graph2D::MainAttributes::defaultFormat = std::make_shared<DecimalNumberFormatter>(); - double zen::nextNiceNumber(double blockSize) //round to next number which is a convenient to read block size { @@ -48,25 +45,25 @@ wxColor getDefaultColor(size_t pos) switch (pos % 10) { case 0: - return wxColor(0, 69, 134); //blue + return { 0, 69, 134 }; //blue case 1: - return wxColor(255, 66, 14); //red + return { 255, 66, 14 }; //red case 2: - return wxColor(255, 211, 32); //yellow + return { 255, 211, 32 }; //yellow case 3: - return wxColor(87, 157, 28); //green + return { 87, 157, 28 }; //green case 4: - return wxColor(126, 0, 33); //royal + return { 126, 0, 33 }; //royal case 5: - return wxColor(131, 202, 255); //light blue + return { 131, 202, 255 }; //light blue case 6: - return wxColor(49, 64, 4); //dark green + return { 49, 64, 4 }; //dark green case 7: - return wxColor(174, 207, 0); //light green + return { 174, 207, 0 }; //light green case 8: - return wxColor(75, 31, 111); //purple + return { 75, 31, 111 }; //purple case 9: - return wxColor(255, 149, 14); //orange + return { 255, 149, 14 }; //orange } assert(false); return *wxBLACK; @@ -574,7 +571,7 @@ void Graph2D::render(wxDC& dc) const { //paint graph background (excluding label area) - wxDCPenChanger dummy (dc, wxColour(130, 135, 144)); //medium grey, the same Win7 uses for other frame borders => not accessible! but no big deal... + wxDCPenChanger dummy (dc, wxColor(130, 135, 144)); //medium grey, the same Win7 uses for other frame borders => not accessible! but no big deal... wxDCBrushChanger dummy2(dc, attr.backgroundColor); //accessibility: consider system text and background colors; small drawback: color of graphs is NOT connected to the background! => responsibility of client to use correct colors diff --git a/wx+/graph.h b/wx+/graph.h index 7b61858d..b9873bd8 100644 --- a/wx+/graph.h +++ b/wx+/graph.h @@ -179,18 +179,18 @@ public: { public: CurveAttributes() {} //required by GCC - CurveAttributes& setColor (const wxColour& col) { color = col; autoColor = false; return *this; } - CurveAttributes& fillCurveArea(const wxColour& col) { fillColor = col; drawCurveArea = true; return *this; } + CurveAttributes& setColor (const wxColor& col) { color = col; autoColor = false; return *this; } + CurveAttributes& fillCurveArea(const wxColor& col) { fillColor = col; drawCurveArea = true; return *this; } CurveAttributes& setLineWidth(size_t width) { lineWidth = static_cast<int>(width); return *this; } private: friend class Graph2D; bool autoColor = true; - wxColour color; + wxColor color; bool drawCurveArea = false; - wxColour fillColor; + wxColor fillColor; int lineWidth = 2; }; @@ -239,16 +239,14 @@ public: MainAttributes& setAutoSize() { minXauto = maxXauto = minYauto = maxYauto = true; return *this; } - static const std::shared_ptr<LabelFormatter> defaultFormat; - - MainAttributes& setLabelX(PosLabelX posX, size_t height = 25, const std::shared_ptr<LabelFormatter>& newLabelFmt = defaultFormat) + MainAttributes& setLabelX(PosLabelX posX, size_t height = 25, std::shared_ptr<LabelFormatter> newLabelFmt = std::make_shared<DecimalNumberFormatter>()) { labelposX = posX; xLabelHeight = static_cast<int>(height); labelFmtX = newLabelFmt; return *this; } - MainAttributes& setLabelY(PosLabelY posY, size_t width = 60, const std::shared_ptr<LabelFormatter>& newLabelFmt = defaultFormat) + MainAttributes& setLabelY(PosLabelY posY, size_t width = 60, std::shared_ptr<LabelFormatter> newLabelFmt = std::make_shared<DecimalNumberFormatter>()) { labelposY = posY; yLabelWidth = static_cast<int>(width); @@ -258,7 +256,7 @@ public: MainAttributes& setCornerText(const wxString& txt, PosCorner pos) { cornerTexts[pos] = txt; return *this; } - MainAttributes& setBackgroundColor(const wxColour& col) { backgroundColor = col; return *this; } + MainAttributes& setBackgroundColor(const wxColor& col) { backgroundColor = col; return *this; } MainAttributes& setSelectionMode(SelMode mode) { mouseSelMode = mode; return *this; } @@ -285,7 +283,7 @@ public: std::map<PosCorner, wxString> cornerTexts; - wxColour backgroundColor = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); + wxColor backgroundColor = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); SelMode mouseSelMode = SELECT_RECTANGLE; }; void setAttributes(const MainAttributes& newAttr) { attr = newAttr; Refresh(); } diff --git a/wx+/grid.cpp b/wx+/grid.cpp index 26186a09..56556797 100644 --- a/wx+/grid.cpp +++ b/wx+/grid.cpp @@ -26,8 +26,11 @@ using namespace zen; -wxColor zen::getColorSelectionGradientFrom() { return wxColor(137, 172, 255); } //blue: HSL: 158, 255, 196 HSV: 222, 0.46, 1 -wxColor zen::getColorSelectionGradientTo () { return wxColor(225, 234, 255); } // HSL: 158, 255, 240 HSV: 222, 0.12, 1 +wxColor zen::getColorSelectionGradientFrom() { return { 137, 172, 255 }; } //blue: HSL: 158, 255, 196 HSV: 222, 0.46, 1 +wxColor zen::getColorSelectionGradientTo () { return { 225, 234, 255 }; } // HSL: 158, 255, 240 HSV: 222, 0.12, 1 + +const int GridData::COLUMN_GAP_LEFT = 4; + void zen::clearArea(wxDC& dc, const wxRect& rect, const wxColor& col) { @@ -36,43 +39,41 @@ void zen::clearArea(wxDC& dc, const wxRect& rect, const wxColor& col) dc.DrawRectangle(rect); } -const int GridData::COLUMN_GAP_LEFT = 4; namespace { +//let's NOT create wxWidgets objects statically: //------------------------------ Grid Parameters -------------------------------- -wxColor getColorLabelText() { return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); } +inline wxColor getColorLabelText() { return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT); } +inline wxColor getColorGridLine() { return { 192, 192, 192 }; } //light grey -wxColor getColorLabelGradientFrom () { return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); } -wxColor getColorLabelGradientTo () { return wxColour(200, 200, 200); } //light grey +inline wxColor getColorLabelGradientFrom() { return wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW); } +inline wxColor getColorLabelGradientTo () { return { 200, 200, 200 }; } //light grey -wxColor getColorLabelGradientFocusFrom() { return getColorLabelGradientFrom(); } -wxColor getColorLabelGradientFocusTo () { return getColorSelectionGradientFrom(); } +inline wxColor getColorLabelGradientFocusFrom() { return getColorLabelGradientFrom(); } +inline wxColor getColorLabelGradientFocusTo () { return getColorSelectionGradientFrom(); } -const double MOUSE_DRAG_ACCELERATION = 1.5; //unit: [rows / (pixel * sec)] -> same value as Explorer! +const double MOUSE_DRAG_ACCELERATION = 1.5; //unit: [rows / (pixel * sec)] -> same value like Explorer! const int DEFAULT_COL_LABEL_BORDER = 6; //top + bottom border in addition to label height -//const int COLUMN_LABEL_BORDER = GridData::COLUMN_GAP_LEFT; const int COLUMN_MOVE_DELAY = 5; //unit: [pixel] (from Explorer) const int COLUMN_MIN_WIDTH = 40; //only honored when resizing manually! const int ROW_LABEL_BORDER = 3; const int COLUMN_RESIZE_TOLERANCE = 6; //unit [pixel] const int COLUMN_FILL_GAP_TOLERANCE = 10; //enlarge column to fill full width when resizing -const wxColor colorGridLine = wxColour(192, 192, 192); //light grey - const bool fillGapAfterColumns = true; //draw rows/column label to fill full window width; may become an instance variable some time? } //---------------------------------------------------------------------------------------------------------------- const wxEventType zen::EVENT_GRID_MOUSE_LEFT_DOUBLE = wxNewEventType(); -const wxEventType zen::EVENT_GRID_COL_LABEL_MOUSE_LEFT = wxNewEventType(); -const wxEventType zen::EVENT_GRID_COL_LABEL_MOUSE_RIGHT = wxNewEventType(); -const wxEventType zen::EVENT_GRID_COL_RESIZE = wxNewEventType(); const wxEventType zen::EVENT_GRID_MOUSE_LEFT_DOWN = wxNewEventType(); const wxEventType zen::EVENT_GRID_MOUSE_LEFT_UP = wxNewEventType(); const wxEventType zen::EVENT_GRID_MOUSE_RIGHT_DOWN = wxNewEventType(); const wxEventType zen::EVENT_GRID_MOUSE_RIGHT_UP = wxNewEventType(); const wxEventType zen::EVENT_GRID_SELECT_RANGE = wxNewEventType(); +const wxEventType zen::EVENT_GRID_COL_LABEL_MOUSE_LEFT = wxNewEventType(); +const wxEventType zen::EVENT_GRID_COL_LABEL_MOUSE_RIGHT = wxNewEventType(); +const wxEventType zen::EVENT_GRID_COL_RESIZE = wxNewEventType(); //---------------------------------------------------------------------------------------------------------------- void GridData::renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool enabled, bool selected) @@ -81,7 +82,7 @@ void GridData::renderRowBackgound(wxDC& dc, const wxRect& rect, size_t row, bool } -void GridData::renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected) +void GridData::renderCell(wxDC& dc, const wxRect& rect, size_t row, ColumnType colType, bool enabled, bool selected, HoverArea rowHover) { wxRect rectTmp = drawCellBorder(dc, rect); @@ -99,7 +100,7 @@ int GridData::getBestSize(wxDC& dc, size_t row, ColumnType colType) wxRect GridData::drawCellBorder(wxDC& dc, const wxRect& rect) //returns remaining rectangle { - wxDCPenChanger dummy2(dc, wxPen(colorGridLine, 1, wxSOLID)); + wxDCPenChanger dummy2(dc, wxPen(getColorGridLine(), 1, wxSOLID)); dc.DrawLine(rect.GetBottomLeft(), rect.GetBottomRight()); dc.DrawLine(rect.GetBottomRight(), rect.GetTopRight() + wxPoint(0, -1)); @@ -298,7 +299,7 @@ protected: //wxWidgets bug: tooltip multiline property is defined by first tooltip text containing newlines or not (same is true for maximum width) if (!tt) SetToolTip(new wxToolTip(L"a b\n\ - a b")); //ugly, but is working (on Windows) + a b")); //ugly, but working (on Windows) tt = GetToolTip(); //should be bound by now assert(tt); if (tt) @@ -447,14 +448,16 @@ public: return -1; } - int getRowHeight() const { return std::max(1, rowHeight); } //guarantees to return size >= 1 ! - void setRowHeight(int height) { rowHeight = height; } + int getRowHeight() const { return rowHeight; } //guarantees to return size >= 1 ! + void setRowHeight(int height) { assert(height > 0); rowHeight = std::max(1, height); } - wxRect getRowLabelArea(ptrdiff_t row) const + wxRect getRowLabelArea(size_t row) const //returns empty rect if row not found { assert(GetClientAreaOrigin() == wxPoint()); - return wxRect(wxPoint(0, rowHeight * row), - wxSize(GetClientSize().GetWidth(), rowHeight)); + if (row < refParent().getRowCount()) + return wxRect(wxPoint(0, rowHeight * row), + wxSize(GetClientSize().GetWidth(), rowHeight)); + return wxRect(); } std::pair<ptrdiff_t, ptrdiff_t> getRowsOnClient(const wxRect& clientRect) const //returns range [begin, end) @@ -503,8 +506,8 @@ private: auto rowRange = getRowsOnClient(rect); //returns range [begin, end) for (auto row = rowRange.first; row < rowRange.second; ++row) { - wxRect singleLabelArea = getRowLabelArea(row); - if (singleLabelArea.GetHeight() > 0) + wxRect singleLabelArea = getRowLabelArea(row); //returns empty rect if row not found + if (singleLabelArea.height > 0) { singleLabelArea.y = refParent().CalcScrolledPosition(singleLabelArea.GetTopLeft()).y; drawRowLabel(dc, singleLabelArea, row); @@ -653,7 +656,7 @@ private: const int clientWidth = GetClientSize().GetWidth(); //need reliable, stable width in contrast to rect.width if (totalWidth < clientWidth) - drawColumnLabel(dc, wxRect(labelAreaTL, wxSize(clientWidth - totalWidth, colLabelHeight)), absWidths.size(), DUMMY_COLUMN_TYPE); + drawColumnLabel(dc, wxRect(labelAreaTL, wxSize(clientWidth - totalWidth, colLabelHeight)), absWidths.size(), ColumnType::NONE); } } @@ -661,9 +664,9 @@ private: { if (auto dataView = refParent().getDataProvider()) { - const bool isHighlighted = activeResizing ? col == activeResizing->getColumn () : //highlight column on mouse-over - activeMove ? col == activeMove ->getColumnFrom() : - highlightCol ? col == *highlightCol : + const bool isHighlighted = activeResizing ? col == activeResizing ->getColumn () : //highlight column on mouse-over + activeClickOrMove ? col == activeClickOrMove->getColumnFrom() : + highlightCol ? col == *highlightCol : false; RecursiveDcClipper clip(dc, rect); @@ -671,11 +674,11 @@ private: //draw move target location if (refParent().allowColumnMove) - if (activeMove && activeMove->isRealMove()) + if (activeClickOrMove && activeClickOrMove->isRealMove()) { - if (col + 1 == activeMove->refColumnTo()) //handle pos 1, 2, .. up to "at end" position + if (col + 1 == activeClickOrMove->refColumnTo()) //handle pos 1, 2, .. up to "at end" position dc.GradientFillLinear(wxRect(rect.GetTopRight(), rect.GetBottomRight() + wxPoint(-2, 0)), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH); - else if (col == activeMove->refColumnTo() && col == 0) //pos 0 + else if (col == activeClickOrMove->refColumnTo() && col == 0) //pos 0 dc.GradientFillLinear(wxRect(rect.GetTopLeft(), rect.GetBottomLeft() + wxPoint(2, 0)), getColorLabelGradientFrom(), *wxBLUE, wxSOUTH); } } @@ -687,7 +690,7 @@ private: refParent().getMainWin().SetFocus(); activeResizing.reset(); - activeMove.reset(); + activeClickOrMove.reset(); if (Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition())) { @@ -698,7 +701,7 @@ private: activeResizing = std::make_unique<ColumnResizing>(*this, action->col, *colWidth, event.GetPosition().x); } else //a move or single click - activeMove = std::make_unique<ColumnMove>(*this, action->col, event.GetPosition().x); + activeClickOrMove = std::make_unique<ColumnMove>(*this, action->col, event.GetPosition().x); } event.Skip(); } @@ -707,14 +710,14 @@ private: { activeResizing.reset(); //nothing else to do, actual work done by onMouseMovement() - if (activeMove) + if (activeClickOrMove) { - if (activeMove->isRealMove()) + if (activeClickOrMove->isRealMove()) { if (refParent().allowColumnMove) { - const auto colFrom = activeMove->getColumnFrom(); - auto colTo = activeMove->refColumnTo(); + const size_t colFrom = activeClickOrMove->getColumnFrom(); + size_t colTo = activeClickOrMove->refColumnTo(); if (colTo > colFrom) //simulate "colFrom" deletion --colTo; @@ -724,10 +727,10 @@ private: } else //notify single label click { - if (const Opt<ColumnType> colType = refParent().colToType(activeMove->getColumnFrom())) - sendEventNow(GridClickEvent(EVENT_GRID_COL_LABEL_MOUSE_LEFT, event, -1, *colType)); + if (const Opt<ColumnType> colType = refParent().colToType(activeClickOrMove->getColumnFrom())) + sendEventNow(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_LEFT, event, *colType)); } - activeMove.reset(); + activeClickOrMove.reset(); } refParent().updateWindowSizes(); //looks strange if done during onMouseMovement() @@ -738,7 +741,7 @@ private: void onMouseCaptureLost(wxMouseCaptureLostEvent& event) override { activeResizing.reset(); - activeMove.reset(); + activeClickOrMove.reset(); Refresh(); //event.Skip(); -> we DID handle it! } @@ -770,22 +773,22 @@ private: refParent().setColumnWidth(newWidth, col, ALLOW_GRID_EVENT); //check if there's a small gap after last column, if yes, fill it - int gapWidth = GetClientSize().GetWidth() - refParent().getColWidthsSum(GetClientSize().GetWidth()); + const int gapWidth = GetClientSize().GetWidth() - refParent().getColWidthsSum(GetClientSize().GetWidth()); if (std::abs(gapWidth) < COLUMN_FILL_GAP_TOLERANCE) refParent().setColumnWidth(newWidth + gapWidth, col, ALLOW_GRID_EVENT); refParent().Refresh(); //refresh columns on main grid as well! } - else if (activeMove) + else if (activeClickOrMove) { const int clientPosX = event.GetPosition().x; - if (std::abs(clientPosX - activeMove->getStartPosX()) > COLUMN_MOVE_DELAY) //real move (not a single click) + if (std::abs(clientPosX - activeClickOrMove->getStartPosX()) > COLUMN_MOVE_DELAY) //real move (not a single click) { - activeMove->setRealMove(); + activeClickOrMove->setRealMove(); const ptrdiff_t col = refParent().clientPosToMoveTargetColumn(event.GetPosition()); if (col >= 0) - activeMove->refColumnTo() = col; + activeClickOrMove->refColumnTo() = col; } } else @@ -806,13 +809,13 @@ private: } } - //update tooltip const std::wstring toolTip = [&] { const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); - if (const Opt<ColumnType> ct = refParent().getColumnAtPos(absPos.x)) + const ColumnType colType = refParent().getColumnAtPos(absPos.x).colType; //returns ColumnType::NONE if no column at x position! + if (colType != ColumnType::NONE) if (auto prov = refParent().getDataProvider()) - return prov->getToolTip(*ct); + return prov->getToolTip(colType); return std::wstring(); }(); setToolTip(toolTip); @@ -833,19 +836,19 @@ private: if (const Opt<ColAction> action = refParent().clientPosToColumnAction(event.GetPosition())) { if (const Opt<ColumnType> colType = refParent().colToType(action->col)) - sendEventNow(GridClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, event, -1, *colType)); //notify right click + sendEventNow(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, event, *colType)); //notify right click else assert(false); } else //notify right click (on free space after last column) if (fillGapAfterColumns) - sendEventNow(GridClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, event, -1, DUMMY_COLUMN_TYPE)); + sendEventNow(GridLabelClickEvent(EVENT_GRID_COL_LABEL_MOUSE_RIGHT, event, ColumnType::NONE)); event.Skip(); } std::unique_ptr<ColumnResizing> activeResizing; - std::unique_ptr<ColumnMove> activeMove; + std::unique_ptr<ColumnMove> activeClickOrMove; Opt<size_t> highlightCol; //column during mouse-over }; @@ -892,17 +895,6 @@ private: wxDCTextColourChanger dummy(dc, wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT)); //use user setting for labels - const int rowHeight = rowLabelWin_.getRowHeight(); - - //why again aren't we using RowLabelWin::getRowsOnClient() here? - const wxPoint topLeft = refParent().CalcUnscrolledPosition(rect.GetTopLeft()); - const wxPoint bottomRight = refParent().CalcUnscrolledPosition(rect.GetBottomRight()); - - const int rowFirst = std::max(topLeft .y / rowHeight, 0); // [rowFirst, rowLast) - const int rowLast = std::min(bottomRight.y / rowHeight + 1, static_cast<int>(refParent().getRowCount())); - - wxPoint cellAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0))); //client coordinates - std::vector<ColumnWidth> absWidths = refParent().getColWidths(); //resolve stretched widths { int totalRowWidth = 0; @@ -917,8 +909,12 @@ private: { RecursiveDcClipper dummy2(dc, rect); //do NOT draw background on cells outside of invalidated rect invalidating foreground text! + wxPoint cellAreaTL(refParent().CalcScrolledPosition(wxPoint(0, 0))); //client coordinates + const int rowHeight = rowLabelWin_.getRowHeight(); + const auto rowRange = rowLabelWin_.getRowsOnClient(rect); //returns range [begin, end) + //draw background lines - for (int row = rowFirst; row < rowLast; ++row) + for (auto row = rowRange.first; row < rowRange.second; ++row) { const wxRect rowRect(cellAreaTL + wxPoint(0, row * rowHeight), wxSize(totalRowWidth, rowHeight)); RecursiveDcClipper dummy3(dc, rowRect); @@ -932,11 +928,11 @@ private: return; //done if (cellAreaTL.x + cw.width_ > rect.x) - for (int row = rowFirst; row < rowLast; ++row) + for (auto row = rowRange.first; row < rowRange.second; ++row) { const wxRect cellRect(cellAreaTL.x, cellAreaTL.y + row * rowHeight, cw.width_, rowHeight); RecursiveDcClipper dummy3(dc, cellRect); - prov->renderCell(dc, cellRect, row, cw.type_, refParent().IsThisEnabled(), drawAsSelected(row)); + prov->renderCell(dc, cellRect, row, cw.type_, refParent().IsThisEnabled(), drawAsSelected(row), getRowHoverToDraw(row)); } cellAreaTL.x += cw.width_; } @@ -944,6 +940,18 @@ private: } } + HoverArea getRowHoverToDraw(ptrdiff_t row) const + { + if (activeSelection) + { + if (activeSelection->getFirstClick().row_ == row) + return activeSelection->getFirstClick().hoverArea_; + } + else if (highlight.row == row) + return highlight.rowHover; + return HoverArea::NONE; + } + bool drawAsSelected(size_t row) const { if (activeSelection) //check if user is currently selecting with mouse @@ -964,14 +972,14 @@ private: void onMouseLeftDouble(wxMouseEvent& event) override { - const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); - const auto row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range - if (row >= 0) + if (auto prov = refParent().getDataProvider()) { - const Opt<ColumnType> ct = refParent().getColumnAtPos(absPos.x); - const ColumnType colType = ct ? *ct : DUMMY_COLUMN_TYPE; + const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); + const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range + const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position! + const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth); //client is interested in all double-clicks, even those outside of the grid! - sendEventNow(GridClickEvent(EVENT_GRID_MOUSE_LEFT_DOUBLE, event, row, colType)); + sendEventNow(GridClickEvent(EVENT_GRID_MOUSE_LEFT_DOUBLE, event, row, rowHover)); } event.Skip(); } @@ -981,36 +989,37 @@ private: if (wxWindow::FindFocus() != this) //doesn't seem to happen automatically for right mouse button SetFocus(); - const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); - const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range + const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); + const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range + const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position! + assert(row >= 0); if (row >= 0) - { - const Opt<ColumnType> ct = refParent().getColumnAtPos(absPos.x); - const ColumnType colType = ct ? *ct : DUMMY_COLUMN_TYPE; - - if (!event.RightDown() || !refParent().isSelected(row)) //do NOT start a new selection if user right-clicks on a selected area! + if (auto prov = refParent().getDataProvider()) { - if (event.ControlDown()) - activeSelection = std::make_unique<MouseSelection>(*this, row, !refParent().isSelected(row)); - else if (event.ShiftDown()) - { - activeSelection = std::make_unique<MouseSelection>(*this, selectionAnchor, true); - refParent().clearSelection(ALLOW_GRID_EVENT); - } - else + const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth); + GridClickEvent mouseEvent(event.RightDown() ? EVENT_GRID_MOUSE_RIGHT_DOWN : EVENT_GRID_MOUSE_LEFT_DOWN, event, row, rowHover); + + if (!event.RightDown() || !refParent().isSelected(row)) //do NOT start a new selection if user right-clicks on a selected area! { - activeSelection = std::make_unique<MouseSelection>(*this, row, true); - refParent().clearSelection(ALLOW_GRID_EVENT); + if (event.ControlDown()) + activeSelection = std::make_unique<MouseSelection>(*this, row, !refParent().isSelected(row), mouseEvent); + else if (event.ShiftDown()) + { + activeSelection = std::make_unique<MouseSelection>(*this, selectionAnchor, true, mouseEvent); + refParent().clearSelection(ALLOW_GRID_EVENT); + } + else + { + activeSelection = std::make_unique<MouseSelection>(*this, row, true, mouseEvent); + refParent().clearSelection(ALLOW_GRID_EVENT); + } } - } - - //notify event *after* potential "clearSelection(true)" above: a client should first receive a GridRangeSelectEvent for clearing the grid, if necessary, - //then GridClickEvent and the associated GridRangeSelectEvent one after the other - GridClickEvent mouseEvent(event.RightDown() ? EVENT_GRID_MOUSE_RIGHT_DOWN : EVENT_GRID_MOUSE_LEFT_DOWN, event, row, colType); - sendEventNow(mouseEvent); + //notify event *after* potential "clearSelection(true)" above: a client should first receive a GridRangeSelectEvent for clearing the grid, if necessary, + //then GridClickEvent and the associated GridRangeSelectEvent one after the other + sendEventNow(mouseEvent); - Refresh(); - } + Refresh(); + } event.Skip(); //allow changing focus } @@ -1038,19 +1047,25 @@ private: refParent().selectRangeAndNotify(activeSelection->getStartRow (), //from activeSelection->getCurrentRow(), //to - activeSelection->isPositiveSelect()); + activeSelection->isPositiveSelect(), + &activeSelection->getFirstClick()); activeSelection.reset(); } - //this one may point to row which is not in visible area! - const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); - - const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range - const Opt<ColumnType> ct = refParent().getColumnAtPos(absPos.x); - const ColumnType colType = ct ? *ct : DUMMY_COLUMN_TYPE; //we probably should notify even if colInfo is invalid! + if (auto prov = refParent().getDataProvider()) + { + //this one may point to row which is not in visible area! + const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); + const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range + const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position! + const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth); + //notify click event after the range selection! e.g. this makes sure the selection is applied before showing a context menu + sendEventNow(GridClickEvent(event.RightUp() ? EVENT_GRID_MOUSE_RIGHT_UP : EVENT_GRID_MOUSE_LEFT_UP, event, row, rowHover)); + } - //notify click event after the range selection! e.g. this makes sure the selection is applied before showing a context menu - sendEventNow(GridClickEvent(event.RightUp() ? EVENT_GRID_MOUSE_RIGHT_UP : EVENT_GRID_MOUSE_LEFT_UP, event, row, colType)); + //update highlight and tooltip: on OS X no mouse movement event is generated after a mouse button click (unlike on Windows) + event.SetPosition(ScreenToClient(wxGetMousePosition())); //mouse position may have changed within above callbacks (e.g. context menu was shown)! + onMouseMovement(event); Refresh(); event.Skip(); //allow changing focus @@ -1059,41 +1074,61 @@ private: void onMouseCaptureLost(wxMouseCaptureLostEvent& event) override { activeSelection.reset(); + highlight.row = -1; Refresh(); //event.Skip(); -> we DID handle it! } void onMouseMovement(wxMouseEvent& event) override { - if (activeSelection) - activeSelection->evalMousePos(); //eval on both mouse movement + timer event! - - //change tooltip - const std::wstring toolTip = [&] + if (auto prov = refParent().getDataProvider()) { const ptrdiff_t rowCount = refParent().getRowCount(); - const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); + const wxPoint absPos = refParent().CalcUnscrolledPosition(event.GetPosition()); + const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range + const ColumnPosInfo cpi = refParent().getColumnAtPos(absPos.x); //returns ColumnType::NONE if no column at x position! + const HoverArea rowHover = prov->getRowMouseHover(row, cpi.colType, cpi.cellRelativePosX, cpi.colWidth); - const ptrdiff_t row = rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range - const Opt<ColumnType> ct = refParent().getColumnAtPos(absPos.x); - if (ct && 0 <= row && row < rowCount) - if (auto prov = refParent().getDataProvider()) - return prov->getToolTip(row, *ct); - return std::wstring(); - }(); + const std::wstring toolTip = [&] + { + if (cpi.colType != ColumnType::NONE && 0 <= row && row < rowCount) + return prov->getToolTip(row, cpi.colType); + return std::wstring(); + }(); + setToolTip(toolTip); //show even during mouse selection! + + if (activeSelection) + activeSelection->evalMousePos(); //call on both mouse movement + timer event! + else + { + refreshHighlight(highlight); + highlight.row = row; + highlight.rowHover = rowHover; + refreshHighlight(highlight); //multiple Refresh() calls are condensed into single one! + } + } + event.Skip(); + } - setToolTip(toolTip); + void onLeaveWindow(wxMouseEvent& event) override //wxEVT_LEAVE_WINDOW does not respect mouse capture! + { + if (!activeSelection) + { + refreshHighlight(highlight); + highlight.row = -1; + } event.Skip(); } + void onFocus(wxFocusEvent& event) override { Refresh(); event.Skip(); } class MouseSelection : private wxEvtHandler { public: - MouseSelection(MainWin& wnd, size_t rowStart, bool positiveSelect) : - wnd_(wnd), rowStart_(rowStart), rowCurrent_(rowStart), positiveSelect_(positiveSelect) + MouseSelection(MainWin& wnd, size_t rowStart, bool positiveSelect, const GridClickEvent& firstClick) : + wnd_(wnd), rowStart_(rowStart), rowCurrent_(rowStart), positiveSelect_(positiveSelect), firstClick_(firstClick) { wnd_.CaptureMouse(); timer.Connect(wxEVT_TIMER, wxEventHandler(MouseSelection::onTimer), nullptr, this); @@ -1105,13 +1140,14 @@ private: size_t getStartRow () const { return rowStart_; } size_t getCurrentRow () const { return rowCurrent_; } bool isPositiveSelect() const { return positiveSelect_; } //are we selecting or unselecting? + const GridClickEvent& getFirstClick() const { return firstClick_; } void evalMousePos() { double deltaTime = 0; if (ticksPerSec_ > 0) { - const TickVal now = getTicks(); //isValid() on error + const TickVal now = getTicks(); //!isValid() on error deltaTime = static_cast<double>(dist(tickCountLast, now)) / ticksPerSec_; //unit: [sec] tickCountLast = now; } @@ -1146,27 +1182,25 @@ private: autoScroll(overlapPixX, toScrollX); autoScroll(overlapPixY, toScrollY); - if (toScrollX != 0 || toScrollY != 0) + if (static_cast<int>(toScrollX) != 0 || static_cast<int>(toScrollY) != 0) { wnd_.refParent().scrollDelta(static_cast<int>(toScrollX), static_cast<int>(toScrollY)); // toScrollX -= static_cast<int>(toScrollX); //rounds down for positive numbers, up for negative, toScrollY -= static_cast<int>(toScrollY); //exactly what we want } - { - //select current row *after* scrolling - wxPoint clientPosTrimmed = clientPos; - numeric::clamp(clientPosTrimmed.y, 0, clientSize.GetHeight() - 1); //do not select row outside client window! - - const wxPoint absPos = wnd_.refParent().CalcUnscrolledPosition(clientPosTrimmed); - const ptrdiff_t newRow = wnd_.rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range - if (newRow >= 0) - if (rowCurrent_ != newRow) - { - rowCurrent_ = newRow; - wnd_.Refresh(); - } - } + //select current row *after* scrolling + wxPoint clientPosTrimmed = clientPos; + numeric::clamp(clientPosTrimmed.y, 0, clientSize.GetHeight() - 1); //do not select row outside client window! + + const wxPoint absPos = wnd_.refParent().CalcUnscrolledPosition(clientPosTrimmed); + const ptrdiff_t newRow = wnd_.rowLabelWin_.getRowAtPos(absPos.y); //return -1 for invalid position; >= rowCount if out of range + if (newRow >= 0) + if (rowCurrent_ != newRow) + { + rowCurrent_ = newRow; + wnd_.Refresh(); + } } private: @@ -1176,13 +1210,20 @@ private: const size_t rowStart_; ptrdiff_t rowCurrent_; const bool positiveSelect_; + const GridClickEvent firstClick_; wxTimer timer; - double toScrollX = 0; //count outstanding scroll units to scroll while dragging mouse + double toScrollX = 0; //count outstanding scroll unit fractions while dragging mouse double toScrollY = 0; // TickVal tickCountLast = getTicks(); const std::int64_t ticksPerSec_ = ticksPerSec(); }; + struct MouseHighlight + { + ptrdiff_t row = -1; + HoverArea rowHover = HoverArea::NONE; + }; + void ScrollWindow(int dx, int dy, const wxRect* rect) override { wxWindow::ScrollWindow(dx, dy, rect); @@ -1213,10 +1254,26 @@ private: rowLabelWin_.Update(); //update while dragging scroll thumb } + void refreshRow(size_t row) + { + const wxRect& rowArea = rowLabelWin_.getRowLabelArea(row); //returns empty rect if row not found + const wxPoint topLeft = refParent().CalcScrolledPosition(wxPoint(0, rowArea.y)); //absolute -> client coordinates + wxRect cellArea(topLeft, wxSize(refParent().getColWidthsSum(GetClientSize().GetWidth()), rowArea.height)); + RefreshRect(cellArea, false); + } + + void refreshHighlight(const MouseHighlight& hl) + { + const ptrdiff_t rowCount = refParent().getRowCount(); + if (0 <= hl.row && hl.row < rowCount && hl.rowHover != HoverArea::NONE) //no highlight? => NOP! + refreshRow(hl.row); + } + RowLabelWin& rowLabelWin_; ColLabelWin& colLabelWin_; std::unique_ptr<MouseSelection> activeSelection; //bound while user is selecting with mouse + MouseHighlight highlight; //current mouse highlight (superseeded by activeSelection if available) ptrdiff_t cursorRow = 0; size_t selectionAnchor = 0; @@ -1427,14 +1484,10 @@ void Grid::onKeyDown(wxKeyEvent& event) int keyCode = event.GetKeyCode(); if (GetLayoutDirection() == wxLayout_RightToLeft) { - if (keyCode == WXK_LEFT) + if (keyCode == WXK_LEFT || keyCode == WXK_NUMPAD_LEFT) keyCode = WXK_RIGHT; - else if (keyCode == WXK_RIGHT) + else if (keyCode == WXK_RIGHT || keyCode == WXK_NUMPAD_RIGHT) keyCode = WXK_LEFT; - else if (keyCode == WXK_NUMPAD_LEFT) - keyCode = WXK_NUMPAD_RIGHT; - else if (keyCode == WXK_NUMPAD_RIGHT) - keyCode = WXK_NUMPAD_LEFT; } const ptrdiff_t rowCount = getRowCount(); @@ -1547,7 +1600,7 @@ void Grid::onKeyDown(wxKeyEvent& event) case 'A': //Ctrl + A - select all if (event.ControlDown()) - selectRangeAndNotify(0, rowCount); + selectRangeAndNotify(0, rowCount, true /*positive*/, nullptr /*mouseInitiated*/); break; case WXK_NUMPAD_ADD: //CTRL + '+' - auto-size all @@ -1581,7 +1634,7 @@ void Grid::selectAllRows(GridEventPolicy rangeEventPolicy) if (rangeEventPolicy == ALLOW_GRID_EVENT) //notify event, even if we're not triggered by user interaction { - GridRangeSelectEvent selEvent(0, getRowCount(), true); + GridRangeSelectEvent selEvent(0, getRowCount(), true, nullptr); if (wxEvtHandler* evtHandler = GetEventHandler()) evtHandler->ProcessEvent(selEvent); } @@ -1595,7 +1648,7 @@ void Grid::clearSelection(GridEventPolicy rangeEventPolicy) if (rangeEventPolicy == ALLOW_GRID_EVENT) //notify event, even if we're not triggered by user interaction { - GridRangeSelectEvent unselectionEvent(0, getRowCount(), false); + GridRangeSelectEvent unselectionEvent(0, getRowCount(), false, nullptr); if (wxEvtHandler* evtHandler = GetEventHandler()) evtHandler->ProcessEvent(unselectionEvent); } @@ -1645,7 +1698,7 @@ void Grid::Refresh(bool eraseBackground, const wxRect* rect) updateWindowSizes(); } - if (selection.size() != rowCountNew) //clear selection only when needed (consider setSelectedRows()) + if (selection.maxSize() != rowCountNew) //clear selection only when needed (consider setSelectedRows()) selection.init(rowCountNew); wxScrolledWindow::Refresh(eraseBackground, rect); @@ -1667,8 +1720,11 @@ void Grid::setColumnConfig(const std::vector<Grid::ColumnAttribute>& attr) std::vector<VisibleColumn> visCols; for (const ColumnAttribute& ca : attr) + { + assert(ca.type_ != ColumnType::NONE); if (ca.visible_) visCols.emplace_back(ca.type_, ca.offset_, ca.stretch_); + } //"ownership" of visible columns is now within Grid visibleCols = visCols; @@ -1742,34 +1798,16 @@ void Grid::showScrollBars(Grid::ScrollBarStatus horizontal, Grid::ScrollBarStatu #endif updateWindowSizes(); +} +#if defined ZEN_WIN || defined ZEN_MAC +void Grid::SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh) +{ /* wxWidgets >= 2.9 ShowScrollbars() is next to useless since it doesn't honor wxSHOW_SB_ALWAYS on OS X, so let's ditch it and avoid more non-portability surprises - - #if wxCHECK_VERSION(2, 9, 0) - auto mapStatus = [](ScrollBarStatus sbStatus) -> wxScrollbarVisibility - { - switch (sbStatus) - { - case SB_SHOW_AUTOMATIC: - return wxSHOW_SB_DEFAULT; - case SB_SHOW_ALWAYS: - return wxSHOW_SB_ALWAYS; - case SB_SHOW_NEVER: - return wxSHOW_SB_NEVER; - } - assert(false); - return wxSHOW_SB_DEFAULT; - }; - ShowScrollbars(mapStatus(horizontal), mapStatus(vertical)); - #endif */ -} -#if defined ZEN_WIN || defined ZEN_MAC -void Grid::SetScrollbar(int orientation, int position, int thumbSize, int range, bool refresh) -{ ScrollBarStatus sbStatus = SB_SHOW_AUTOMATIC; if (orientation == wxHORIZONTAL) sbStatus = showScrollbarX; @@ -1806,24 +1844,6 @@ wxWindow& Grid::getMainWin () { return *mainWin_; } const wxWindow& Grid::getMainWin() const { return *mainWin_; } -wxRect Grid::getColumnLabelArea(ColumnType colType) const -{ - std::vector<ColumnWidth> absWidths = getColWidths(); //resolve negative/stretched widths - - auto iterCol = std::find_if(absWidths.begin(), absWidths.end(), [&](const ColumnWidth& cw) { return cw.type_ == colType; }); - if (iterCol != absWidths.end()) - { - ptrdiff_t posX = 0; - for (auto it = absWidths.begin(); it != iterCol; ++it) - posX += it->width_; - - return wxRect(wxPoint(posX, 0), wxSize(iterCol->width_, colLabelHeight)); - } - - return wxRect(); -} - - Opt<Grid::ColAction> Grid::clientPosToColumnAction(const wxPoint& pos) const { const int absPosX = CalcUnscrolledPosition(pos).x; @@ -1862,7 +1882,7 @@ void Grid::moveColumn(size_t colFrom, size_t colTo) colTo < visibleCols.size() && colTo != colFrom) { - const auto colAtt = visibleCols[colFrom]; + const VisibleColumn colAtt = visibleCols[colFrom]; visibleCols.erase (visibleCols.begin() + colFrom); visibleCols.insert(visibleCols.begin() + colTo, colAtt); } @@ -1871,35 +1891,35 @@ void Grid::moveColumn(size_t colFrom, size_t colTo) ptrdiff_t Grid::clientPosToMoveTargetColumn(const wxPoint& pos) const { - std::vector<ColumnWidth> absWidths = getColWidths(); //resolve negative/stretched widths const int absPosX = CalcUnscrolledPosition(pos).x; - int accuWidth = 0; - for (auto iterCol = absWidths.begin(); iterCol != absWidths.end(); ++iterCol) + int accWidth = 0; + std::vector<ColumnWidth> absWidths = getColWidths(); //resolve negative/stretched widths + for (auto itCol = absWidths.begin(); itCol != absWidths.end(); ++itCol) { - const int width = iterCol->width_; //beware dreaded unsigned conversions! - accuWidth += width; + const int width = itCol->width_; //beware dreaded unsigned conversions! + accWidth += width; - if (absPosX < accuWidth - width / 2) - return iterCol - absWidths.begin(); + if (absPosX < accWidth - width / 2) + return itCol - absWidths.begin(); } return absWidths.size(); } -Opt<ColumnType> Grid::colToType(size_t col) const +ColumnType Grid::colToType(size_t col) const { if (col < visibleCols.size()) return visibleCols[col].type_; - return NoValue(); + return ColumnType::NONE; } ptrdiff_t Grid::getRowAtPos(int posY) const { return rowLabelWin_->getRowAtPos(posY); } -Opt<ColumnType> Grid::getColumnAtPos(int posX) const +Grid::ColumnPosInfo Grid::getColumnAtPos(int posX) const { if (posX >= 0) { @@ -1908,18 +1928,44 @@ Opt<ColumnType> Grid::getColumnAtPos(int posX) const { accWidth += cw.width_; if (posX < accWidth) - return cw.type_; + return { cw.type_, posX + cw.width_ - accWidth, cw.width_ }; } } - return NoValue(); + return { ColumnType::NONE, 0, 0 }; } -wxRect Grid::getCellArea(size_t row, ColumnType colType) const +wxRect Grid::getColumnLabelArea(ColumnType colType) const { - const wxRect& colArea = getColumnLabelArea(colType); - const wxRect& rowArea = rowLabelWin_->getRowLabelArea(row); - return wxRect(wxPoint(colArea.x, rowArea.y), wxSize(colArea.width, rowArea.height)); + std::vector<ColumnWidth> absWidths = getColWidths(); //resolve negative/stretched widths + + //colType is not unique in general, but *this* function expects it! + assert(std::count_if(absWidths.begin(), absWidths.end(), [&](const ColumnWidth& cw) { return cw.type_ == colType; }) <= 1); + + auto itCol = std::find_if(absWidths.begin(), absWidths.end(), [&](const ColumnWidth& cw) { return cw.type_ == colType; }); + if (itCol != absWidths.end()) + { + ptrdiff_t posX = 0; + for (auto it = absWidths.begin(); it != itCol; ++it) + posX += it->width_; + + return wxRect(wxPoint(posX, 0), wxSize(itCol->width_, colLabelHeight)); + } + return wxRect(); +} + + +void Grid::refreshCell(size_t row, ColumnType colType) +{ + const wxRect& colArea = getColumnLabelArea(colType); //returns empty rect if column not found + const wxRect& rowArea = rowLabelWin_->getRowLabelArea(row); //returns empty rect if row not found + if (colArea.height > 0 && rowArea.height > 0) + { + const wxPoint topLeft = CalcScrolledPosition(wxPoint(colArea.x, rowArea.y)); //absolute -> client coordinates + const wxRect cellArea(topLeft, wxSize(colArea.width, rowArea.height)); + + getMainWin().RefreshRect(cellArea, false); + } } @@ -1929,7 +1975,7 @@ void Grid::setGridCursor(size_t row) makeRowVisible(row); selection.clear(); //clear selection, do NOT fire event - selectRangeAndNotify(row, row); //set new selection + fire event + selectRangeAndNotify(row, row, true /*positive*/, nullptr /*mouseInitiated*/); //set new selection + fire event mainWin_->Refresh(); rowLabelWin_->Refresh(); //row labels! (Kubuntu) @@ -1944,7 +1990,7 @@ void Grid::selectWithCursor(ptrdiff_t row) makeRowVisible(row); selection.clear(); //clear selection, do NOT fire event - selectRangeAndNotify(anchorRow, row); //set new selection + fire event + selectRangeAndNotify(anchorRow, row, true /*positive*/, nullptr /*mouseInitiated*/); //set new selection + fire event mainWin_->Refresh(); rowLabelWin_->Refresh(); @@ -1953,7 +1999,7 @@ void Grid::selectWithCursor(ptrdiff_t row) void Grid::makeRowVisible(size_t row) { - const wxRect labelRect = rowLabelWin_->getRowLabelArea(row); //returns empty rect if column not found + const wxRect labelRect = rowLabelWin_->getRowLabelArea(row); //returns empty rect if row not found if (labelRect.height > 0) { int scrollPosX = 0; @@ -1966,16 +2012,16 @@ void Grid::makeRowVisible(size_t row) const int clientPosY = CalcScrolledPosition(labelRect.GetTopLeft()).y; if (clientPosY < 0) { - const int scrollPosY = labelRect.GetTopLeft().y / pixelsPerUnitY; + const int scrollPosY = labelRect.y / pixelsPerUnitY; Scroll(scrollPosX, scrollPosY); updateWindowSizes(); //may show horizontal scroll bar } - else if (clientPosY + labelRect.GetHeight() > rowLabelWin_->GetClientSize().GetHeight()) + else if (clientPosY + labelRect.height > rowLabelWin_->GetClientSize().GetHeight()) { auto execScroll = [&](int clientHeight) { - const int scrollPosY = std::ceil((labelRect.GetTopLeft().y - clientHeight + - labelRect.GetHeight()) / static_cast<double>(pixelsPerUnitY)); + const int scrollPosY = std::ceil((labelRect.y - clientHeight + + labelRect.height) / static_cast<double>(pixelsPerUnitY)); Scroll(scrollPosX, scrollPosY); updateWindowSizes(); //may show horizontal scroll bar }; @@ -1992,7 +2038,7 @@ void Grid::makeRowVisible(size_t row) } -void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive) +void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive, const GridClickEvent* mouseInitiated) { //sort + convert to half-open range auto rowFirst = std::min(rowFrom, rowTo); @@ -2005,7 +2051,7 @@ void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positiv selection.selectRange(rowFirst, rowLast, positive); //notify event - GridRangeSelectEvent selectionEvent(rowFirst, rowLast, positive); + GridRangeSelectEvent selectionEvent(rowFirst, rowLast, positive, mouseInitiated); if (wxEvtHandler* evtHandler = GetEventHandler()) evtHandler->ProcessEvent(selectionEvent); @@ -2015,14 +2061,14 @@ void Grid::selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positiv void Grid::scrollTo(size_t row) { - const wxRect labelRect = rowLabelWin_->getRowLabelArea(row); //returns empty rect if column not found + const wxRect labelRect = rowLabelWin_->getRowLabelArea(row); //returns empty rect if row not found if (labelRect.height > 0) { int pixelsPerUnitY = 0; GetScrollPixelsPerUnit(nullptr, &pixelsPerUnitY); if (pixelsPerUnitY > 0) { - const int scrollPosYNew = labelRect.GetTopLeft().y / pixelsPerUnitY; + const int scrollPosYNew = labelRect.y / pixelsPerUnitY; int scrollPosXOld = 0; int scrollPosYOld = 0; GetViewStart(&scrollPosXOld, &scrollPosYOld); @@ -18,13 +18,10 @@ namespace zen { -typedef enum { DUMMY_COLUMN_TYPE = static_cast<unsigned int>(-1) } ColumnType; - -//----- events ------------------------------------------------------------------------ -extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_LEFT; //generates: GridClickEvent -extern const wxEventType EVENT_GRID_COL_LABEL_MOUSE_RIGHT; // -extern const wxEventType EVENT_GRID_COL_RESIZE; //generates: GridColumnResizeEvent +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 @@ -34,48 +31,63 @@ 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, ColumnType colType) : wxMouseEvent(me), row_(row), colType_(colType) { SetEventType(et); } + 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 ColumnType colType_; //may be DUMMY_COLUMN_TYPE -}; - -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_; + const HoverArea hoverArea_; //may be HoverArea::NONE }; struct GridRangeSelectEvent : public wxCommandEvent { - GridRangeSelectEvent(size_t rowFirst, size_t rowLast, bool positive) : wxCommandEvent(EVENT_GRID_SELECT_RANGE), positive_(positive), rowFirst_(rowFirst), rowLast_(rowLast) { assert(rowFirst <= rowLast); } + 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<GridClickEvent>()) { assert(rowFirst <= rowLast); } wxEvent* Clone() const override { return new GridRangeSelectEvent(*this); } - const bool positive_; //"false" when clearing selection! const size_t rowFirst_; //selected range: [rowFirst_, rowLast_) const size_t rowLast_; + const bool positive_; //"false" when clearing selection! + Opt<GridClickEvent> 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 }; -typedef void (wxEvtHandler::*GridClickEventFunction )(GridClickEvent&); -typedef void (wxEvtHandler::*GridColumnResizeEventFunction)(GridColumnResizeEvent&); -typedef void (wxEvtHandler::*GridRangeSelectEventFunction )(GridRangeSelectEvent&); -#define GridClickEventHandler(func) \ - (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridClickEventFunction, &func) +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 GridColumnResizeEventHandler(func) \ - (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridColumnResizeEventFunction, &func) +#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) -#define GridRangeSelectEventHandler(func) \ - (wxObjectEventFunction)(wxEventFunction)wxStaticCastEvent(GridRangeSelectEventFunction, &func) //------------------------------------------------------------------------------------------------------------ class Grid; wxColor getColorSelectionGradientFrom(); @@ -90,12 +102,13 @@ public: virtual size_t getRowCount() const = 0; - //grid area + //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); // - virtual int getBestSize (wxDC& dc, size_t row, ColumnType colType ); //must correspond to renderCell()! + 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; @@ -105,15 +118,16 @@ public: static const int COLUMN_GAP_LEFT; //for left-aligned text protected: //optional helper routines - 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 void drawCellText (wxDC& dc, const wxRect& rect, const std::wstring& text, bool enabled, int alignment = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); + 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 void drawCellText (wxDC& dc, const wxRect& rect, const std::wstring& text, bool enabled, int alignment = wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL); - 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); + 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, @@ -179,9 +193,16 @@ public: const wxWindow& getMainWin() const; ptrdiff_t getRowAtPos(int posY) const; //return -1 for invalid position, >= rowCount if out of range; absolute coordinates! - Opt<ColumnType> getColumnAtPos(int posX) const; - wxRect getCellArea(size_t row, ColumnType colType) const; //returns empty rect if column not found; 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; } @@ -231,7 +252,7 @@ private: public: void init(size_t rowCount) { rowSelectionValue.resize(rowCount); clear(); } - size_t size() const { return rowSelectionValue.size(); } + size_t maxSize() const { return rowSelectionValue.size(); } std::vector<size_t> get() const { @@ -294,7 +315,7 @@ private: wxRect getColumnLabelArea(ColumnType colType) const; //returns empty rect if column not found - void selectRangeAndNotify(ptrdiff_t rowFrom, ptrdiff_t rowTo, bool positive = true); //select inclusive range [rowFrom, rowTo] + notify event! + 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); } @@ -307,7 +328,7 @@ private: void moveColumn(size_t colFrom, size_t colTo); ptrdiff_t clientPosToMoveTargetColumn(const wxPoint& pos) const; //return < 0 on error - Opt<ColumnType> colToType(size_t col) const; + ColumnType colToType(size_t col) const; //returns ColumnType::NONE on error /* Visual layout: diff --git a/wx+/image_resources.cpp b/wx+/image_resources.cpp index 98cdc7a8..4ba62bb9 100644 --- a/wx+/image_resources.cpp +++ b/wx+/image_resources.cpp @@ -50,12 +50,18 @@ public: } void init(const Zstring& filepath); + void cleanup() + { + bitmaps.clear(); + anims.clear(); + } const wxBitmap& getImage (const wxString& name) const; const wxAnimation& getAnimation(const wxString& name) const; private: GlobalResources() {} + ~GlobalResources() { assert(bitmaps.empty() && anims.empty()); } //don't leave wxWidgets objects for static destruction! GlobalResources (const GlobalResources&) = delete; GlobalResources& operator=(const GlobalResources&) = delete; @@ -127,6 +133,7 @@ const wxAnimation& GlobalResources::getAnimation(const wxString& name) const void zen::initResourceImages(const Zstring& filepath) { GlobalResources::instance().init(filepath); } +void zen::cleanupResourceImages() { GlobalResources::instance().cleanup(); } const wxBitmap& zen::getResourceImage(const wxString& name) { return GlobalResources::instance().getImage(name); } diff --git a/wx+/image_resources.h b/wx+/image_resources.h index 30f89848..61623614 100644 --- a/wx+/image_resources.h +++ b/wx+/image_resources.h @@ -14,6 +14,7 @@ namespace zen { void initResourceImages(const Zstring& filepath); //pass resources .zip file at application startup +void cleanupResourceImages(); const wxBitmap& getResourceImage (const wxString& name); const wxAnimation& getResourceAnimation(const wxString& name); diff --git a/wx+/image_tools.h b/wx+/image_tools.h index 5e99653c..554e7b49 100644 --- a/wx+/image_tools.h +++ b/wx+/image_tools.h @@ -50,7 +50,7 @@ void convertToVanillaImage(wxImage& img); //add alpha channel if missing + remov //wxColor gradient(const wxColor& from, const wxColor& to, double fraction); //maps fraction within [0, 1] to an intermediate color -//wxColour hsvColor(double h, double s, double v); //h within [0, 360), s, v within [0, 1] +//wxColor hsvColor(double h, double s, double v); //h within [0, 360), s, v within [0, 1] @@ -208,7 +208,7 @@ wxColor gradient(const wxColor& from, const wxColor& to, double fraction) /* inline -wxColour hsvColor(double h, double s, double v) //h within [0, 360), s, v within [0, 1] +wxColor hsvColor(double h, double s, double v) //h within [0, 360), s, v within [0, 1] { //http://de.wikipedia.org/wiki/HSV-Farbraum @@ -238,17 +238,17 @@ wxColour hsvColor(double h, double s, double v) //h within [0, 360), s, v within switch (h_i) { case 0: - return wxColour(vi, t, p); + return wxColor(vi, t, p); case 1: - return wxColour(q, vi, p); + return wxColor(q, vi, p); case 2: - return wxColour(p, vi, t); + return wxColor(p, vi, t); case 3: - return wxColour(p, q, vi); + return wxColor(p, q, vi); case 4: - return wxColour(t, p, vi); + return wxColor(t, p, vi); case 5: - return wxColour(vi, p, q); + return wxColor(vi, p, q); } assert(false); return *wxBLACK; diff --git a/wx+/tooltip.h b/wx+/tooltip.h index 8ddba819..06953361 100644 --- a/wx+/tooltip.h +++ b/wx+/tooltip.h @@ -15,8 +15,7 @@ namespace zen class Tooltip { public: - Tooltip(wxWindow& parent) : //parent needs to live at least as long as this instance! - tipWindow(nullptr), parent_(parent) {} + Tooltip(wxWindow& parent) : parent_(parent) {} //parent needs to live at least as long as this instance! void show(const wxString& text, wxPoint mousePos, //absolute screen coordinates @@ -25,7 +24,7 @@ public: private: class TooltipDialogGenerated; - TooltipDialogGenerated* tipWindow; + TooltipDialogGenerated* tipWindow = nullptr; wxWindow& parent_; }; } |