From 1b158caa83a440180f62a59493ba7505f97fcf66 Mon Sep 17 00:00:00 2001 From: DietmarSchwertberger Date: Sat, 9 Apr 2022 21:45:59 +0200 Subject: [PATCH] Improve usability of wxGrid actions using mouse dragging Implement auto scrolling and handle ESCAPE to cancel the actions done by dragging the mouse, such as resizing or selecting an area. Closes #22292. --- include/wx/generic/grid.h | 23 +++- include/wx/generic/gridsel.h | 1 + include/wx/generic/private/grid.h | 6 +- src/generic/grid.cpp | 195 +++++++++++++++++++++++++++--- src/generic/gridsel.cpp | 14 +++ 5 files changed, 220 insertions(+), 19 deletions(-) diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index 825d69102e..9b821d2237 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -100,6 +100,7 @@ class WXDLLIMPEXP_FWD_CORE wxGridCornerLabelWindow; class WXDLLIMPEXP_FWD_CORE wxGridEvent; class WXDLLIMPEXP_FWD_CORE wxGridRowLabelWindow; class WXDLLIMPEXP_FWD_CORE wxGridWindow; +class WXDLLIMPEXP_FWD_CORE wxGridSubwindow; class WXDLLIMPEXP_FWD_CORE wxGridTypeRegistry; class WXDLLIMPEXP_FWD_CORE wxGridSelection; @@ -2804,10 +2805,19 @@ protected: // or -1 if there is no resize operation in progress. int m_dragRowOrCol; + // Original row or column size when resizing; used when the user cancels + int m_dragRowOrColOldSize; + // true if a drag operation is in progress; when this is true, // m_startDragPos is valid, i.e. not wxDefaultPosition bool m_isDragging; + // true if a drag operation was canceled + // (mouse event Dragging() might still be active until LeftUp) + // m_isDragging can only be set after m_cancelledDragging is cleared. + // This is done when a mouse event happens with left button up. + bool m_cancelledDragging; + // the position (in physical coordinates) where the user started dragging // the mouse or wxDefaultPosition if mouse isn't being dragged // @@ -2816,6 +2826,10 @@ protected: // setting m_isDragging to true wxPoint m_startDragPos; + // the position of the last mouse event + // used for detection of the movement direction + wxPoint m_lastMousePos; + bool m_waitForSlowClick; wxCursor m_rowResizeCursor; @@ -2960,6 +2974,13 @@ private: // release the mouse capture if it's currently captured void EndDraggingIfNecessary(); + // helper for Process...MouseEvent to block re-triggering m_isDragging + bool CheckIfDragCancelled(wxMouseEvent *event); + + // helper for Process...MouseEvent to scroll + void CheckDoDragScroll(wxGridSubwindow *eventGridWindow, wxGridSubwindow *gridWindow, + wxPoint posEvent, int direction); + // return true if the grid should be refreshed right now bool ShouldRefresh() const { @@ -3029,7 +3050,7 @@ private: void DoColHeaderClick(int col); - void DoStartResizeRowOrCol(int col); + void DoStartResizeRowOrCol(int col, int size); void DoStartMoveRow(int col); void DoStartMoveCol(int col); diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index 4a895e4166..e2dcaaf704 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -112,6 +112,7 @@ public: wxVectorGridBlockCoords& GetBlocks() { return m_selection; } void EndSelecting(); + void CancelSelecting(); private: void SelectBlockNoEvent(const wxGridBlockCoords& block) diff --git a/include/wx/generic/private/grid.h b/include/wx/generic/private/grid.h index 3d7ab59a71..8893c406ad 100644 --- a/include/wx/generic/private/grid.h +++ b/include/wx/generic/private/grid.h @@ -281,6 +281,8 @@ public: wxGrid *GetOwner() { return m_owner; } + virtual bool IsFrozen() const { return false; } + protected: void OnMouseCaptureLost(wxMouseCaptureLostEvent& event); @@ -298,8 +300,6 @@ public: { } - virtual bool IsFrozen() const { return false; } - private: void OnPaint( wxPaintEvent& event ); void OnMouseEvent( wxMouseEvent& event ); @@ -330,8 +330,6 @@ public: { } - virtual bool IsFrozen() const { return false; } - private: void OnPaint( wxPaintEvent& event ); void OnMouseEvent( wxMouseEvent& event ); diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 98e8e9fe20..cf548ab76e 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -13,7 +13,6 @@ - Make Begin/EndBatch() the same as the generic Freeze/Thaw() - Review the column reordering code, it's a mess. - - Implement row reordering after dealing with the columns. */ // For compilers that support precompilation, includes "wx/wx.h". @@ -3034,8 +3033,11 @@ void wxGrid::Init() m_dragMoveCol = -1; m_dragLastPos = -1; m_dragRowOrCol = -1; + m_dragRowOrColOldSize = -1; m_isDragging = false; + m_cancelledDragging = false; m_startDragPos = wxDefaultPosition; + m_lastMousePos = wxDefaultPosition; m_sortCol = wxNOT_FOUND; m_sortIsAscending = true; @@ -3864,12 +3866,99 @@ void wxGrid::PrepareDCFor(wxDC &dc, wxGridWindow *gridWindow) dc.SetDeviceOrigin(dcOrigin.x, dcOrigin.y); } +void wxGrid::CheckDoDragScroll(wxGridSubwindow *eventGridWindow, wxGridSubwindow *gridWindow, + wxPoint posEvent, int direction) +{ + // helper for Process{Row|Col}LabelMouseEvent, ProcessGridCellMouseEvent: + // scroll when at the edges or outside the window w. respect to direction + // eventGridWindow: the window that received the mouse event + // gridWindow: the same or the corresponding non-frozen window + + if ( !m_isDragging ) + { + // drag is just starting + m_lastMousePos = posEvent; + return; + } + + int w, h; + eventGridWindow->GetSize(&w, &h); + // ViewStart is scroll position in scroll units + wxPoint scrollPos = GetViewStart(); + wxPoint newScrollPos = wxPoint(wxDefaultCoord, wxDefaultCoord); + + if ( direction & wxHORIZONTAL ) + { + // check x direction + if ( eventGridWindow->IsFrozen() && posEvent.x < w ) + { + // in the frozen window, moving left? + if ( scrollPos.x > 0 && posEvent.x < m_lastMousePos.x ) + newScrollPos.x = scrollPos.x - 1; + } + else if ( eventGridWindow->IsFrozen() && posEvent.x >= w ) + { + // frozen window was left, add the width of the non-frozen window + w += gridWindow->GetSize().x; + } + + if ( posEvent.x < 0 && scrollPos.x > 0 ) + newScrollPos.x = scrollPos.x - 1; + else if ( posEvent.x >= w ) + newScrollPos.x = scrollPos.x + 1; + } + + if ( direction & wxVERTICAL ) + { + // check y direction + if ( eventGridWindow->IsFrozen() && posEvent.y < h ) + { + // in the frozen window, moving upward? + if ( scrollPos.y && posEvent.y < m_lastMousePos.y ) + newScrollPos.y = scrollPos.y - 1; + } + else if ( eventGridWindow->IsFrozen() && posEvent.y >= h ) + { + // frozen window was left, add the height of the non-frozen window + h += gridWindow->GetSize().y; + } + + if ( posEvent.y < 0 && scrollPos.y > 0 ) + newScrollPos.y = scrollPos.y - 1; + else if ( posEvent.y >= h ) + newScrollPos.y = scrollPos.y + 1; + } + + if ( newScrollPos.x != wxDefaultCoord || newScrollPos.y != wxDefaultCoord ) + Scroll(newScrollPos); + + m_lastMousePos = posEvent; +} + +bool wxGrid::CheckIfDragCancelled(wxMouseEvent *event) +{ + // helper for Process{Row|Col}LabelMouseEvent, ProcessGridCellMouseEvent: + // block re-triggering m_isDragging + if ( !m_cancelledDragging ) + return false; + + if ( event->LeftIsDown() ) + return true; + + m_cancelledDragging = false; + + return false; +} + void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindow* rowLabelWin ) { int y; wxGridWindow *gridWindow = rowLabelWin->IsFrozen() ? m_frozenRowGridWin : m_gridWin; - event.SetPosition(event.GetPosition() + GetGridWindowOffset(gridWindow)); + // store position, before it's modified in the next step + const wxPoint posEvent = event.GetPosition(); + + event.SetPosition(posEvent + GetGridWindowOffset(gridWindow)); // for drag, we could be moving from the window sending the event to the other if ( rowLabelWin->IsFrozen() && event.GetPosition().y > rowLabelWin->GetClientSize().y ) @@ -3878,6 +3967,15 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo CalcGridWindowUnscrolledPosition(0, event.GetPosition().y, NULL, &y, gridWindow); int row = YToRow( y ); + if ( CheckIfDragCancelled(&event) ) + return; + + if ( event.Dragging() && (m_winCapture == rowLabelWin) ) + { + // scroll when at the edges or outside the window + CheckDoDragScroll(rowLabelWin, m_rowLabelWin, posEvent, wxVERTICAL); + } + if ( event.Dragging() ) { if (!m_isDragging) @@ -3995,7 +4093,7 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo row = YToEdgeOfRow(y); if ( row != wxNOT_FOUND && CanDragRowSize(row) ) { - DoStartResizeRowOrCol(row); + DoStartResizeRowOrCol(row, GetRowSize(row)); ChangeCursorMode(WXGRID_CURSOR_RESIZE_ROW, rowLabelWin); } else // not a request to start resizing @@ -4157,6 +4255,7 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo ChangeCursorMode(WXGRID_CURSOR_SELECT_CELL, rowLabelWin); m_dragLastPos = -1; + m_lastMousePos = wxDefaultPosition; m_isDragging = false; } @@ -4311,13 +4410,14 @@ void wxGrid::DoColHeaderClick(int col) } } -void wxGrid::DoStartResizeRowOrCol(int col) +void wxGrid::DoStartResizeRowOrCol(int col, int size) { // Hide the editor if it's currently shown to avoid any weird interactions // with it while dragging the row/column separator. AcceptCellEditControlIfShown(); m_dragRowOrCol = col; + m_dragRowOrColOldSize = size; } void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindow* colLabelWin ) @@ -4325,7 +4425,10 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo int x; wxGridWindow *gridWindow = colLabelWin->IsFrozen() ? m_frozenColGridWin : m_gridWin; - event.SetPosition(event.GetPosition() + GetGridWindowOffset(gridWindow)); + // store position, before it's modified in the next step + const wxPoint posEvent = event.GetPosition(); + + event.SetPosition(posEvent + GetGridWindowOffset(gridWindow)); // for drag, we could be moving from the window sending the event to the other if (colLabelWin->IsFrozen() && event.GetPosition().x > colLabelWin->GetClientSize().x) @@ -4334,6 +4437,16 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo CalcGridWindowUnscrolledPosition(event.GetPosition().x, 0, &x, NULL, gridWindow); int col = XToCol(x); + + if ( CheckIfDragCancelled(&event) ) + return; + + if ( event.Dragging() && (m_winCapture == colLabelWin) ) + { + // scroll when at the edges or outside the window + CheckDoDragScroll(colLabelWin, GetColLabelWindow(), posEvent, wxHORIZONTAL); + } + if ( event.Dragging() ) { if (!m_isDragging) @@ -4452,7 +4565,7 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo int colEdge = XToEdgeOfCol(x); if ( colEdge != wxNOT_FOUND && CanDragColSize(colEdge) ) { - DoStartResizeRowOrCol(colEdge); + DoStartResizeRowOrCol(colEdge, GetColSize(colEdge)); ChangeCursorMode(WXGRID_CURSOR_RESIZE_COL, colLabelWin); } else // not a request to start resizing @@ -4611,6 +4724,7 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo ChangeCursorMode(WXGRID_CURSOR_SELECT_CELL, GetColLabelWindow()); m_dragLastPos = -1; + m_lastMousePos = wxDefaultPosition; m_isDragging = false; } @@ -4736,6 +4850,7 @@ void wxGrid::DoAfterDraggingEnd() m_isDragging = false; m_startDragPos = wxDefaultPosition; + m_lastMousePos = wxDefaultPosition; m_cursorMode = WXGRID_CURSOR_SELECT_CELL; m_winCapture->SetCursor( *wxSTANDARD_CURSOR ); @@ -4803,9 +4918,6 @@ void wxGrid::ChangeCursorMode(CursorMode mode, case WXGRID_CURSOR_MOVE_ROW: case WXGRID_CURSOR_MOVE_COL: - // Currently we don't capture mouse when moving columns, which is - // almost certainly wrong. - captureMouse = false; win->SetCursor( wxCursor(wxCURSOR_HAND) ); break; @@ -4931,12 +5043,16 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, { int dragRowOrCol = wxNOT_FOUND; if ( m_cursorMode == WXGRID_CURSOR_RESIZE_COL ) + { dragRowOrCol = XToEdgeOfCol(pos.x); + DoStartResizeRowOrCol(dragRowOrCol, GetColSize(dragRowOrCol)); + } else + { dragRowOrCol = YToEdgeOfRow(pos.y); + DoStartResizeRowOrCol(dragRowOrCol, GetRowSize(dragRowOrCol)); + } wxCHECK_RET( dragRowOrCol != -1, "Can't determine row or column in resizing mode" ); - - DoStartResizeRowOrCol(dragRowOrCol); } break; @@ -5115,13 +5231,20 @@ void wxGrid::ProcessGridCellMouseEvent(wxMouseEvent& event, wxGridWindow *eventG // the window receiving the event might not be the same as the one under // the mouse (e.g. in the case of a dragging event started in one window, // but continuing over another one) + + if ( CheckIfDragCancelled(&event) ) + return; + wxGridWindow *gridWindow = DevicePosToGridWindow(event.GetPosition() + eventGridWindow->GetPosition()); if ( !gridWindow ) gridWindow = eventGridWindow; - event.SetPosition(event.GetPosition() + eventGridWindow->GetPosition() - + // store position, before it's modified in the next step + const wxPoint posEvent = event.GetPosition(); + + event.SetPosition(posEvent + eventGridWindow->GetPosition() - wxPoint(m_rowLabelWidth, m_colLabelHeight)); wxPoint pos = CalcGridWindowUnscrolledPosition(event.GetPosition(), gridWindow); @@ -5152,6 +5275,13 @@ void wxGrid::ProcessGridCellMouseEvent(wxMouseEvent& event, wxGridWindow *eventG const bool isDraggingWithLeft = event.Dragging() && event.LeftIsDown(); + if ( isDraggingWithLeft && (m_winCapture == eventGridWindow) ) + { + // scroll when at the edges or outside the window + CheckDoDragScroll(eventGridWindow, m_gridWin, posEvent, + wxHORIZONTAL | wxVERTICAL); + } + // While dragging the mouse, only releasing the left mouse button, which // cancels the drag operation, is processed (above) and any other events // are just ignored while it's in progress. @@ -5295,7 +5425,7 @@ void wxGrid::DoEndDragResizeCol(const wxMouseEvent& event, wxGridWindow* gridWin void wxGrid::DoHeaderStartDragResizeCol(int col) { - DoStartResizeRowOrCol(col); + DoStartResizeRowOrCol(col, GetColSize(col)); } void wxGrid::DoHeaderDragResizeCol(int width) @@ -6185,7 +6315,44 @@ void wxGrid::OnKeyDown( wxKeyEvent& event ) break; case WXK_ESCAPE: - ClearSelection(); + if ( m_isDragging && m_winCapture ) + { + switch ( m_cursorMode ) + { + case WXGRID_CURSOR_MOVE_COL: + case WXGRID_CURSOR_MOVE_ROW: + // end row/column moving + m_winCapture->Refresh(); + m_dragLastPos = -1; + break; + + case WXGRID_CURSOR_RESIZE_ROW: + case WXGRID_CURSOR_RESIZE_COL: + // reset to size from before dragging + (m_cursorMode == WXGRID_CURSOR_RESIZE_ROW + ? static_cast(wxGridRowOperations()) + : static_cast(wxGridColumnOperations()) + ).SetLineSize(this, m_dragRowOrCol, m_dragRowOrColOldSize); + + m_dragRowOrCol = -1; + break; + + case WXGRID_CURSOR_SELECT_CELL: + case WXGRID_CURSOR_SELECT_ROW: + case WXGRID_CURSOR_SELECT_COL: + if ( m_selection ) + m_selection->CancelSelecting(); + break; + } + EndDraggingIfNecessary(); + + // ensure that a new drag operation is only started after a LeftUp + m_cancelledDragging = true; + } + else + { + ClearSelection(); + } break; case WXK_TAB: diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index 81ee2a4641..bf8f617480 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -72,6 +72,20 @@ void wxGridSelection::EndSelecting() m_grid->GetEventHandler()->ProcessEvent(gridEvt); } +void wxGridSelection::CancelSelecting() +{ + // It's possible that nothing was selected finally, e.g. the mouse could + // have been dragged around only to return to the starting cell, just don't + // do anything in this case. + if ( !IsSelection() ) + return; + + const wxGridBlockCoords& block = m_selection.back(); + m_grid->RefreshBlock(block.GetTopLeft(), block.GetBottomRight()); + m_selection.pop_back(); +} + + bool wxGridSelection::IsInSelection( int row, int col ) const { // Check whether the given cell is contained in one of the selected blocks.