diff --git a/include/wx/generic/grid.h b/include/wx/generic/grid.h index c88b2a6122..98f494f376 100644 --- a/include/wx/generic/grid.h +++ b/include/wx/generic/grid.h @@ -19,6 +19,10 @@ #include "wx/scrolwin.h" +#if wxUSE_STD_CONTAINERS_COMPATIBLY + #include +#endif + // ---------------------------------------------------------------------------- // constants // ---------------------------------------------------------------------------- @@ -747,10 +751,205 @@ private: }; +// ---------------------------------------------------------------------------- +// wxGridBlockCoords: location of a block of cells in the grid +// ---------------------------------------------------------------------------- + +struct wxGridBlockDiffResult; + +class WXDLLIMPEXP_CORE wxGridBlockCoords +{ +public: + wxGridBlockCoords() : + m_topRow(-1), + m_leftCol(-1), + m_bottomRow(-1), + m_rightCol(-1) + { + } + + wxGridBlockCoords(int topRow, int leftCol, int bottomRow, int rightCol) : + m_topRow(topRow), + m_leftCol(leftCol), + m_bottomRow(bottomRow), + m_rightCol(rightCol) + { + } + + // default copy ctor is ok + + int GetTopRow() const { return m_topRow; } + void SetTopRow(int row) { m_topRow = row; } + int GetLeftCol() const { return m_leftCol; } + void SetLeftCol(int col) { m_leftCol = col; } + int GetBottomRow() const { return m_bottomRow; } + void SetBottomRow(int row) { m_bottomRow = row; } + int GetRightCol() const { return m_rightCol; } + void SetRightCol(int col) { m_rightCol = col; } + + wxGridCellCoords GetTopLeft() const + { + return wxGridCellCoords(m_topRow, m_leftCol); + } + + wxGridCellCoords GetBottomRight() const + { + return wxGridCellCoords(m_bottomRow, m_rightCol); + } + + wxGridBlockCoords Canonicalize() const + { + wxGridBlockCoords result = *this; + + if ( result.m_topRow > result.m_bottomRow ) + wxSwap(result.m_topRow, result.m_bottomRow); + + if ( result.m_leftCol > result.m_rightCol ) + wxSwap(result.m_leftCol, result.m_rightCol); + + return result; + } + + bool Intersects(const wxGridBlockCoords& other) const + { + return m_topRow <= other.m_bottomRow && m_bottomRow >= other.m_topRow && + m_leftCol <= other.m_rightCol && m_rightCol >= other.m_leftCol; + } + + // Return whether this block contains the given cell. + bool ContainsCell(const wxGridCellCoords& cell) const + { + return m_topRow <= cell.GetRow() && cell.GetRow() <= m_bottomRow && + m_leftCol <= cell.GetCol() && cell.GetCol() <= m_rightCol; + } + + // Return whether this blocks fully contains another one. + bool ContainsBlock(const wxGridBlockCoords& other) const + { + return m_topRow <= other.m_topRow && other.m_bottomRow <= m_bottomRow && + m_leftCol <= other.m_leftCol && other.m_rightCol <= m_rightCol; + } + + // Calculates the result blocks by subtracting the other block from this + // block. splitOrientation can be wxVERTICAL or wxHORIZONTAL. + wxGridBlockDiffResult + Difference(const wxGridBlockCoords& other, int splitOrientation) const; + + // Calculates the symmetric difference of the blocks. + wxGridBlockDiffResult + SymDifference(const wxGridBlockCoords& other) const; + + bool operator==(const wxGridBlockCoords& other) const + { + return m_topRow == other.m_topRow && m_leftCol == other.m_leftCol && + m_bottomRow == other.m_bottomRow && m_rightCol == other.m_rightCol; + } + + bool operator!=(const wxGridBlockCoords& other) const + { + return !(*this == other); + } + + bool operator!() const + { + return m_topRow == -1 && m_leftCol == -1 && + m_bottomRow == -1 && m_rightCol == -1; + } + +private: + int m_topRow; + int m_leftCol; + int m_bottomRow; + int m_rightCol; +}; + +// ---------------------------------------------------------------------------- +// wxGridBlockDiffResult: The helper struct uses as a result type for difference +// functions of wxGridBlockCoords class. +// Parts can be uninitialized (equals to wxGridNoBlockCoords), that means +// that the corresponding part doesn't exists in the result. +// ---------------------------------------------------------------------------- + +struct wxGridBlockDiffResult +{ + wxGridBlockCoords m_parts[4]; +}; + +// ---------------------------------------------------------------------------- +// wxGridBlocks: a range of grid blocks that can be iterated over +// ---------------------------------------------------------------------------- + +class wxGridBlocks +{ + typedef wxVector::const_iterator iterator_impl; + +public: + class iterator + { + public: +#if wxUSE_STD_CONTAINERS_COMPATIBLY + typedef std::forward_iterator_tag iterator_category; +#endif + typedef ptrdiff_t difference_type; + typedef wxGridBlockCoords value_type; + typedef const value_type& reference; + + iterator() : m_it() { } + + reference operator*() const { return *m_it; } + + iterator& operator++() + { ++m_it; return *this; } + iterator operator++(int) + { iterator tmp = *this; ++m_it; return tmp; } + + bool operator==(const iterator& it) const + { return m_it == it.m_it; } + bool operator!=(const iterator& it) const + { return m_it != it.m_it; } + + private: + explicit iterator(iterator_impl it) : m_it(it) { } + + iterator_impl m_it; + + friend class wxGridBlocks; + }; + + iterator begin() const + { + return m_begin; + } + + iterator end() const + { + return m_end; + } + +private: + wxGridBlocks() : + m_begin(), + m_end() + { + } + + wxGridBlocks(iterator_impl begin, iterator_impl end) : + m_begin(begin), + m_end(end) + { + } + + const iterator m_begin; + const iterator m_end; + + friend class wxGrid; +}; + // For comparisons... // -extern WXDLLIMPEXP_CORE wxGridCellCoords wxGridNoCellCoords; -extern WXDLLIMPEXP_CORE wxRect wxGridNoCellRect; +extern WXDLLIMPEXP_CORE wxGridCellCoords wxGridNoCellCoords; +extern WXDLLIMPEXP_CORE wxGridBlockCoords wxGridNoBlockCoords; +extern WXDLLIMPEXP_CORE wxRect wxGridNoCellRect; // An array of cell coords... // @@ -1343,6 +1542,10 @@ public: void MakeCellVisible( const wxGridCellCoords& coords ) { MakeCellVisible( coords.GetRow(), coords.GetCol() ); } + // Returns the topmost row of the current visible area. + int GetFirstFullyVisibleRow() const; + // Returns the leftmost column of the current visible area. + int GetFirstFullyVisibleColumn() const; // ------ grid cursor movement functions // @@ -1791,6 +1994,7 @@ public: bool IsInSelection( const wxGridCellCoords& coords ) const { return IsInSelection( coords.GetRow(), coords.GetCol() ); } + wxGridBlocks GetSelectedBlocks() const; wxGridCellCoordsArray GetSelectedCells() const; wxGridCellCoordsArray GetSelectionBlockTopLeft() const; wxGridCellCoordsArray GetSelectionBlockBottomRight() const; @@ -2332,8 +2536,6 @@ protected: bool m_waitForSlowClick; - wxGridCellCoords m_selectionStart; - wxCursor m_rowResizeCursor; wxCursor m_colResizeCursor; @@ -2387,16 +2589,6 @@ protected: { return SetCurrentCell( wxGridCellCoords(row, col) ); } - // this function is called to extend the block being currently selected - // from mouse and keyboard event handlers - void UpdateBlockBeingSelected(int blockStartRow, int blockStartCol, - int blockEndRow, int blockEndCol); - - void UpdateBlockBeingSelected(const wxGridCellCoords& blockStart, - const wxGridCellCoords& blockEnd) - { UpdateBlockBeingSelected(blockStart.GetRow(), blockStart.GetCol(), - blockEnd.GetRow(), blockEnd.GetCol()); } - virtual bool ShouldScrollToChildOnFocus(wxWindow* WXUNUSED(win)) wxOVERRIDE { return false; } @@ -2566,7 +2758,6 @@ private: void DoGridProcessTab(wxKeyboardState& kbdState); // common implementations of methods defined for both rows and columns - void DeselectLine(int line, const wxGridOperations& oper); bool DoEndDragResizeLine(const wxGridOperations& oper, wxGridWindow *gridWindow); int PosToLinePos(int pos, bool clipToMinMax, const wxGridOperations& oper, @@ -2576,13 +2767,20 @@ private: wxGridWindow *gridWindow) const; int PosToEdgeOfLine(int pos, const wxGridOperations& oper) const; - bool DoMoveCursor(bool expandSelection, + void DoMoveCursorFromKeyboard(const wxKeyboardState& kbdState, + const wxGridDirectionOperations& diroper); + bool DoMoveCursor(const wxKeyboardState& kbdState, const wxGridDirectionOperations& diroper); - bool DoMoveCursorByPage(const wxGridDirectionOperations& diroper); - bool DoMoveCursorByBlock(bool expandSelection, + bool DoMoveCursorByPage(const wxKeyboardState& kbdState, + const wxGridDirectionOperations& diroper); + bool AdvanceByPage(wxGridCellCoords& coords, + const wxGridDirectionOperations& diroper); + bool DoMoveCursorByBlock(const wxKeyboardState& kbdState, const wxGridDirectionOperations& diroper); void AdvanceToNextNonEmpty(wxGridCellCoords& coords, const wxGridDirectionOperations& diroper); + bool AdvanceByBlock(wxGridCellCoords& coords, + const wxGridDirectionOperations& diroper); // common part of {Insert,Delete}{Rows,Cols} bool DoModifyLines(bool (wxGridTableBase::*funcModify)(size_t, size_t), diff --git a/include/wx/generic/gridsel.h b/include/wx/generic/gridsel.h index 125fc02ef9..08377e3b08 100644 --- a/include/wx/generic/gridsel.h +++ b/include/wx/generic/gridsel.h @@ -17,6 +17,10 @@ #include "wx/grid.h" +#include "wx/vector.h" + +typedef wxVector wxVectorGridBlockCoords; + class WXDLLIMPEXP_CORE wxGridSelection { public: @@ -24,8 +28,8 @@ public: wxGrid::wxGridSelectionModes sel = wxGrid::wxGridSelectCells); bool IsSelection(); - bool IsInSelection(int row, int col); - bool IsInSelection(const wxGridCellCoords& coords) + bool IsInSelection(int row, int col) const; + bool IsInSelection(const wxGridCellCoords& coords) const { return IsInSelection(coords.GetRow(), coords.GetCol()); } @@ -48,66 +52,93 @@ public: kbd, sendEvent); } - void SelectCell(int row, int col, - const wxKeyboardState& kbd = wxKeyboardState(), - bool sendEvent = true); - void SelectCell(const wxGridCellCoords& coords, - const wxKeyboardState& kbd = wxKeyboardState(), - bool sendEvent = true) - { - SelectCell(coords.GetRow(), coords.GetCol(), kbd, sendEvent); - } + // This function replaces all the existing selected blocks (which become + // redundant) with a single block covering the entire grid. + void SelectAll(); - void ToggleCellSelection(int row, int col, - const wxKeyboardState& kbd = wxKeyboardState()); - void ToggleCellSelection(const wxGridCellCoords& coords, - const wxKeyboardState& kbd = wxKeyboardState()) - { - ToggleCellSelection(coords.GetRow(), coords.GetCol(), kbd); - } + void DeselectBlock(const wxGridBlockCoords& block, + const wxKeyboardState& kbd = wxKeyboardState(), + bool sendEvent = true ); + // Note that this method refreshes the previously selected blocks and sends + // an event about the selection change. void ClearSelection(); void UpdateRows( size_t pos, int numRows ); void UpdateCols( size_t pos, int numCols ); + // Extend (or shrink) the current selection block (creating it if + // necessary, i.e. if there is no selection at all currently or if the + // current current cell isn't selected, as in this case a new block + // containing it is always added) to the one specified by the start and end + // coordinates of its opposite corners (which don't have to be in + // top/bottom left/right order). + // + // Note that blockStart is equal to wxGrid::m_currentCellCoords almost + // always, but not always (the exception is when we scrolled out from + // the top of the grid and select a column or scrolled right and select + // a row: in this case the lowest visible row/column will be set as + // current, not the first one). + // + // Both components of both blockStart and blockEnd must be valid. + // + // Return true if the current block was actually changed. + bool ExtendCurrentBlock(const wxGridCellCoords& blockStart, + const wxGridCellCoords& blockEnd, + const wxKeyboardState& kbd); + + + // Return the coordinates of the cell from which the selection should + // continue to be extended. This is normally the opposite corner of the + // last selected block from the current cell coordinates. + // + // If there is no selection, just returns the current cell coordinates. + wxGridCellCoords GetExtensionAnchor() const; + + wxGridCellCoordsArray GetCellSelection() const; + wxGridCellCoordsArray GetBlockSelectionTopLeft() const; + wxGridCellCoordsArray GetBlockSelectionBottomRight() const; + wxArrayInt GetRowSelection() const; + wxArrayInt GetColSelection() const; + + wxVectorGridBlockCoords& GetBlocks() { return m_selection; } + private: - int BlockContain( int topRow1, int leftCol1, - int bottomRow1, int rightCol1, - int topRow2, int leftCol2, - int bottomRow2, int rightCol2 ); - // returns 1, if Block1 contains Block2, - // -1, if Block2 contains Block1, - // 0, otherwise - - int BlockContainsCell( int topRow, int leftCol, - int bottomRow, int rightCol, - int row, int col ) - // returns 1, if Block contains Cell, - // 0, otherwise + void SelectBlockNoEvent(const wxGridBlockCoords& block) { - return ( topRow <= row && row <= bottomRow && - leftCol <= col && col <= rightCol ); - } - - void SelectBlockNoEvent(int topRow, int leftCol, - int bottomRow, int rightCol) - { - SelectBlock(topRow, leftCol, bottomRow, rightCol, + SelectBlock(block.GetTopRow(), block.GetLeftCol(), + block.GetBottomRow(), block.GetRightCol(), wxKeyboardState(), false); } - wxGridCellCoordsArray m_cellSelection; - wxGridCellCoordsArray m_blockSelectionTopLeft; - wxGridCellCoordsArray m_blockSelectionBottomRight; - wxArrayInt m_rowSelection; - wxArrayInt m_colSelection; + // Really select the block and don't check for the current selection mode. + void Select(const wxGridBlockCoords& block, + const wxKeyboardState& kbd, bool sendEvent); + + // Ensure that the new "block" becomes part of "blocks", adding it to them + // if necessary and, if we do it, also removing any existing elements of + // "blocks" that become unnecessary because they're entirely contained in + // the new "block". However note that we may also not to have to add it at + // all, if it's already contained in one of the existing blocks. + // + // We don't currently check if the new block is contained by several + // existing blocks, as this would be more difficult and doesn't seem to be + // really needed in practice. + void MergeOrAddBlock(wxVectorGridBlockCoords& blocks, + const wxGridBlockCoords& block); + + // All currently selected blocks. We expect there to be a relatively small + // amount of them, even for very large grids, as each block must be + // selected by the user, so we store them unsorted. + // + // Selection may be empty, but if it isn't, the last block is special, as + // it is the current block, which is affected by operations such as + // extending the current selection from keyboard. + wxVectorGridBlockCoords m_selection; wxGrid *m_grid; wxGrid::wxGridSelectionModes m_selectionMode; - friend class WXDLLIMPEXP_FWD_CORE wxGrid; - wxDECLARE_NO_COPY_CLASS(wxGridSelection); }; diff --git a/include/wx/generic/private/grid.h b/include/wx/generic/private/grid.h index 216491fa89..318e88293b 100644 --- a/include/wx/generic/private/grid.h +++ b/include/wx/generic/private/grid.h @@ -793,9 +793,37 @@ public: // boundary, i.e. is the first/last row/column virtual bool IsAtBoundary(const wxGridCellCoords& coords) const = 0; + // Check if the component of this point in our direction is + // valid, i.e. not -1 + bool IsValid(const wxGridCellCoords& coords) const + { + return m_oper.Select(coords) != -1; + } + + // Make the coordinates with the other component value of -1. + wxGridCellCoords MakeWholeLineCoords(const wxGridCellCoords& coords) const + { + return m_oper.MakeCoords(m_oper.Select(coords), -1); + } + // Increment the component of this point in our direction + // + // Note that this can't be called if IsAtBoundary() is true, use + // TryToAdvance() if this might be the case. virtual void Advance(wxGridCellCoords& coords) const = 0; + // Try to advance in our direction, return true if succeeded or false + // otherwise, i.e. if the coordinates are already at the grid boundary. + bool TryToAdvance(wxGridCellCoords& coords) const + { + if ( IsAtBoundary(coords) ) + return false; + + Advance(coords); + + return true; + } + // Find the line at the given distance, in pixels, away from this one // (this uses clipping, i.e. anything after the last line is counted as the // last one and anything before the first one as 0) diff --git a/interface/wx/grid.h b/interface/wx/grid.h index dfbdb73740..5a48bab795 100644 --- a/interface/wx/grid.h +++ b/interface/wx/grid.h @@ -1816,6 +1816,250 @@ public: bool operator!() const; }; +/** + Represents coordinates of a block of cells in the grid. + + An object of this class contains coordinates of the left top and the bottom right + corners of a block. + + @since 3.1.4 + */ +class wxGridBlockCoords +{ +public: + /** + Default constructor initializes the object to invalid state. + + Initially the coordinates are invalid (-1) and so operator!() for an + uninitialized wxGridBlockCoords returns @true. + */ + wxGridBlockCoords(); + + /** + Constructor taking a coordinates of the left top and the bottom right + corners. + */ + wxGridBlockCoords(int topRow, int leftCol, int bottomRow, int rightCol); + + /** + Return the row of the left top corner. + */ + int GetTopRow() const; + + /** + Set the row of the left top corner. + */ + void SetTopRow(int row); + + /** + Return the column of the left top corner. + */ + int GetLeftCol() const; + + /** + Set the column of the left top corner. + */ + void SetLeftCol(int col); + + /** + Return the row of the bottom right corner. + */ + int GetBottomRow() const; + + /** + Set the row of the bottom right corner. + */ + void SetBottomRow(int row); + + /** + Return the column of the bottom right corner. + */ + int GetRightCol() const; + + /** + Set the column of the bottom right corner. + */ + void SetRightCol(int col); + + /** + Return the coordinates of the top left corner. + */ + wxGridCellCoords GetTopLeft() const + { + return wxGridCellCoords(m_topRow, m_leftCol); + } + + /** + Return the coordinates of the bottom right corner. + */ + wxGridCellCoords GetBottomRight() const + { + return wxGridCellCoords(m_bottomRow, m_rightCol); + } + + /** + Return the canonicalized block where top left coordinates is less + then bottom right coordinates. + */ + wxGridBlockCoords Canonicalize() const; + + /** + Whether the blocks intersect. + + @return + @true, if the block intersects with the other, @false, otherwise. + */ + bool Intersects(const wxGridBlockCoords& other) const + { + return m_topRow <= other.m_bottomRow && m_bottomRow >= other.m_topRow && + m_leftCol <= other.m_rightCol && m_rightCol >= other.m_leftCol; + } + + /** + Check whether this block contains the given cell. + + @return + @true, if the block contains the cell, @false otherwise. + */ + bool ContainsCell(const wxGridCellCoords& cell) const; + + /** + Check whether this block contains another one. + + @return + @true if @a other is entirely contained within this block. + */ + bool ContainsBlock(const wxGridBlockCoords& other) const; + + /** + Calculates the result blocks by subtracting the other block from this + block. + + @param other + The block to subtract from this block. + @param splitOrientation + The block splitting orientation (either @c wxHORIZONTAL or + @c wxVERTICAL). + @return + Up to 4 blocks. If block doesn't exist in the result, it has value + of @c wxGridNoBlockCoords. + */ + wxGridBlockDiffResult + Difference(const wxGridBlockCoords& other, int splitOrientation) const; + + /** + Calculates the symmetric difference of the blocks. + + @param other + The block to subtract from this block. + @return + Up to 4 blocks. If block doesn't exist in the result, it has value + of @c wxGridNoBlockCoords. + */ + wxGridBlockDiffResult + SymDifference(const wxGridBlockCoords& other) const; + + /** + Equality operator. + */ + bool operator==(const wxGridBlockCoords& other) const; + + /** + Inequality operator. + */ + bool operator!=(const wxGridBlockCoords& other) const; + + /** + Checks whether the cells block is invalid. + + Returns @true only if all components are -1. Notice that if one + of the components (but not all) is -1, this method returns @false even + if the object is invalid. This is done because objects in such state + should actually never exist, i.e. either all components should be -1 + or none of them should be -1. + */ + bool operator!() const; + +private: + int m_topRow; + int m_leftCol; + int m_bottomRow; + int m_rightCol; +}; + +/** + @class wxGridBlockDiffResult + + The helper struct uses as a result type for difference functions of + @c wxGridBlockCoords class. + + Parts can be uninitialized (equals to @c wxGridNoBlockCoords), that means + that the corresponding part doesn't exists in the result. + + @since 3.1.4 + */ +struct wxGridBlockDiffResult +{ + wxGridBlockCoords m_parts[4]; +}; + +/** + Represents a collection of grid blocks that can be iterated over. + + This class provides read-only access to the blocks making up the grid + selection in the most general case. + + Note that objects of this class can only be returned by wxGrid, but not + constructed in the application code. + + The preferable way to iterate over it is using C++11 range-for loop: + @code + for ( const auto& block: grid->GetSelectedBlocks() ) { + ... do something with block ... + } + @endcode + When not using C++11, iteration has to be done manually: + @code + wxGridBlocks range = grid->GetSelectedBlocks(); + for ( wxGridBlocks::iterator it = range.begin(); + it != range.end(); + ++it ) { + ... do something with *it ... + } + @endcode + + @since 3.1.4 + */ +class wxGridBlocks +{ +public: + /** + Read-only forward iterator type. + + This is an opaque type, which satisfies the forward iterator + requirements, i.e. provides all the expected operations, such as + comparison, dereference and pre- and post-increment. + */ + class iterator + { + iterator(); + + const wxGridBlockCoords& operator*() const; + + iterator& operator++(); + iterator operator++(int); + + bool operator==(const iterator& it) const; + bool operator!=(const iterator& it) const; + }; + + /// Return iterator corresponding to the beginning of the range. + iterator begin() const; + + /// Return iterator corresponding to the end of the range. + iterator end() const; +}; + /** @class wxGridTableBase @@ -4419,6 +4663,21 @@ public: */ void DeselectCell( int row, int col ); + /** + Returns a range of grid selection blocks. + + The returned range can be iterated over, e.g. with C++11 range-for loop: + @code + for ( const auto block: grid->GetSelectedBlocks() ) { + if ( block.Intersects(myBlock) ) + break; + } + @endcode + + @since 3.1.4 + */ + wxGridBlocks GetSelectedBlocks() const; + /** Returns an array of individually selected cells. @@ -4434,6 +4693,9 @@ public: a grid with a million of columns, we don't want to create an array with a million of entries in this function, instead it returns an empty array and GetSelectedCols() returns an array containing one element). + + The function can be slow for the big grids, use GetSelectedBlocks() + in the new code. */ wxGridCellCoordsArray GetSelectedCells() const; @@ -4445,6 +4707,9 @@ public: individually selected but not those being part of the block selection or being selected in virtue of all of their cells being selected individually, please see GetSelectedCells() for more details. + + The function can be slow for the big grids, use GetSelectedBlocks() + in the new code. */ wxArrayInt GetSelectedCols() const; @@ -4456,6 +4721,9 @@ public: selected but not those being part of the block selection or being selected in virtue of all of their cells being selected individually, please see GetSelectedCells() for more details. + + The function can be slow for the big grids, use GetSelectedBlocks() + in the new code. */ wxArrayInt GetSelectedRows() const; @@ -4471,6 +4739,9 @@ public: Please see GetSelectedCells() for more information about the selection representation in wxGrid. + The function can be slow for the big grids, use GetSelectedBlocks() + in the new code. + @see GetSelectionBlockTopLeft() */ wxGridCellCoordsArray GetSelectionBlockBottomRight() const; @@ -4481,6 +4752,9 @@ public: Please see GetSelectedCells() for more information about the selection representation in wxGrid. + The function can be slow for the big grids, use GetSelectedBlocks() + in the new code. + @see GetSelectionBlockBottomRight() */ wxGridCellCoordsArray GetSelectionBlockTopLeft() const; @@ -4641,6 +4915,17 @@ public: */ void MakeCellVisible(const wxGridCellCoords& coords); + /** + Returns the topmost row of the current visible area. + Returns -1 if the grid doesn't have any rows. + */ + int GetFirstFullyVisibleRow() const; + /** + Returns the leftmost column of the current visible area. + Returns -1 if the grid doesn't have any columns. + */ + int GetFirstFullyVisibleColumn() const; + /** Sets the number of pixels per horizontal scroll increment. diff --git a/misc/msvc/wxWidgets.natvis b/misc/msvc/wxWidgets.natvis index b08b9d8449..ca15f7cdea 100644 --- a/misc/msvc/wxWidgets.natvis +++ b/misc/msvc/wxWidgets.natvis @@ -41,6 +41,14 @@ http://code.msdn.microsoft.com/windowsdesktop/Writing-type-visualizers-2eae77a2# {m_ll} + + R{m_row + 1}C{m_col + 1} + + + + R{m_topRow + 1}C{m_leftCol + 1}:R{m_bottomRow + 1}C{m_rightCol + 1} + + empty {m_pItems[0]} diff --git a/samples/grid/griddemo.cpp b/samples/grid/griddemo.cpp index 6de852ba6f..5e314e21b7 100644 --- a/samples/grid/griddemo.cpp +++ b/samples/grid/griddemo.cpp @@ -432,7 +432,7 @@ GridFrame::GridFrame() grid = new wxGrid( this, wxID_ANY, wxPoint( 0, 0 ), - FromDIP(wxSize( 400, 300 )) ); + FromDIP(wxSize( 800, 450 )) ); #if wxUSE_LOG @@ -440,7 +440,7 @@ GridFrame::GridFrame() wxID_ANY, wxEmptyString, wxDefaultPosition, - wxDefaultSize, + wxSize(-1, 8*GetCharHeight()), wxTE_MULTILINE ); logger = new wxLogTextCtrl( logWin ); @@ -1220,32 +1220,66 @@ void GridFrame::SetCornerLabelValue( wxCommandEvent& WXUNUSED(ev) ) void GridFrame::ShowSelection( wxCommandEvent& WXUNUSED(ev) ) { - switch ( grid->GetSelectionMode() ) + int count = 0; + wxString desc; + const wxGridBlocks& sel = grid->GetSelectedBlocks(); + for ( wxGridBlocks::iterator it = sel.begin(); it != sel.end(); ++it ) { - case wxGrid::wxGridSelectCells: - wxLogMessage("%zu individual cells and " - "%zu blocks of contiguous cells selected", - grid->GetSelectedCells().size(), - grid->GetSelectionBlockTopLeft().size()); - return; + const wxGridBlockCoords& b = *it; - case wxGrid::wxGridSelectRows: - case wxGrid::wxGridSelectColumns: - case wxGrid::wxGridSelectRowsOrColumns: - const wxArrayInt& rows = grid->GetSelectedRows(); - if ( !rows.empty() ) - wxLogMessage("%zu rows selected", rows.size()); + wxString blockDesc; + if ( b.GetLeftCol() == 0 && + b.GetRightCol() == grid->GetNumberCols() - 1 ) + { + if ( b.GetTopRow() == b.GetBottomRow() ) + blockDesc.Printf("row %d", b.GetTopRow() + 1); + else + blockDesc.Printf("rows %d..%d", + b.GetTopRow() + 1, b.GetBottomRow() + 1); + } + else if ( b.GetTopRow() == 0 && + b.GetBottomRow() == grid->GetNumberRows() - 1 ) + { + if ( b.GetLeftCol() == b.GetRightCol() ) + blockDesc.Printf("column %d", b.GetLeftCol() + 1); + else + blockDesc.Printf("columns %d..%d", + b.GetLeftCol() + 1, + b.GetRightCol() + 1); + } + else if ( b.GetTopRow() == b.GetBottomRow() && + b.GetLeftCol() == b.GetRightCol() ) + { + blockDesc.Printf("cell R%dC%d", + b.GetTopRow() + 1, b.GetLeftCol() + 1); + } + else + { + blockDesc.Printf("block R%dC%d - R%dC%d", + b.GetTopRow() + 1, + b.GetLeftCol() + 1, + b.GetBottomRow() + 1, + b.GetRightCol() + 1); + } - const wxArrayInt& cols = grid->GetSelectedCols(); - if ( !cols.empty() ) - wxLogMessage("%zu columns selected", rows.size()); - - if ( rows.empty() && cols.empty() ) - wxLogMessage("No selection"); - return; + if ( count++ ) + desc += "\n\t"; + desc += blockDesc; } - wxLogError("Unknown grid selection mode."); + switch ( count ) + { + case 0: + wxLogMessage("No selection"); + break; + + case 1: + wxLogMessage("Selection: %s", desc); + break; + + default: + wxLogMessage("%d selected blocks:\n\t%s", count, desc); + } } void GridFrame::SelectCells( wxCommandEvent& WXUNUSED(ev) ) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index 608feb1634..adbf19cc61 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -94,6 +94,7 @@ struct DefaultHeaderRenderers // ---------------------------------------------------------------------------- wxGridCellCoords wxGridNoCellCoords( -1, -1 ); +wxGridBlockCoords wxGridNoBlockCoords( -1, -1, -1, -1 ); wxRect wxGridNoCellRect( -1, -1, -1, -1 ); namespace @@ -151,23 +152,6 @@ wxDEFINE_EVENT( wxEVT_GRID_EDITOR_HIDDEN, wxGridEvent ); wxDEFINE_EVENT( wxEVT_GRID_EDITOR_CREATED, wxGridEditorCreatedEvent ); wxDEFINE_EVENT( wxEVT_GRID_TABBING, wxGridEvent ); -// ---------------------------------------------------------------------------- -// private helpers -// ---------------------------------------------------------------------------- - -namespace -{ - - // ensure that first is less or equal to second, swapping the values if - // necessary - void EnsureFirstLessThanSecond(int& first, int& second) - { - if ( first > second ) - wxSwap(first, second); - } - -} // anonymous namespace - // ============================================================================ // implementation // ============================================================================ @@ -1152,6 +1136,205 @@ const wxGridCornerHeaderRenderer& wxGridCellAttrProvider::GetCornerRenderer() return gs_defaultHeaderRenderers.cornerRenderer; } +// ---------------------------------------------------------------------------- +// wxGridBlockCoords +// ---------------------------------------------------------------------------- + +wxGridBlockDiffResult +wxGridBlockCoords::Difference(const wxGridBlockCoords& other, + int splitOrientation) const +{ + wxGridBlockDiffResult result; + + // Check whether the blocks intersect. + if (!Intersects(other)) + { + result.m_parts[0] = *this; + return result; + } + + // Split the block in up to 4 new parts, that don't contain the other + // block, like this (for wxHORIZONTAL): + // |-----------------------------| + // | | + // | part[0] | + // | | + // |-----------------------------| + // | part[2] | other | part[3] | + // |-----------------------------| + // | | + // | part[1] | + // | | + // |-----------------------------| + // And for wxVERTICAL: + // |-----------------------------| + // | | | | + // | | part[2] | | + // | | | | + // | |---------| | + // | part[0] | other | part[1] | + // | |---------| | + // | | | | + // | | part[3] | | + // | | | | + // |-----------------------------| + + if ( splitOrientation == wxHORIZONTAL ) + { + // Part[0]. + if ( m_topRow < other.m_topRow ) + result.m_parts[0] = + wxGridBlockCoords(m_topRow, m_leftCol, + other.m_topRow - 1, m_rightCol); + + // Part[1]. + if ( m_bottomRow > other.m_bottomRow ) + result.m_parts[1] = + wxGridBlockCoords(other.m_bottomRow + 1, m_leftCol, + m_bottomRow, m_rightCol); + + const int maxTopRow = wxMax(m_topRow, other.m_topRow); + const int minBottomRow = wxMin(m_bottomRow, other.m_bottomRow); + + // Part[2]. + if ( m_leftCol < other.m_leftCol ) + result.m_parts[2] = + wxGridBlockCoords(maxTopRow, m_leftCol, + minBottomRow, other.m_leftCol - 1); + + // Part[3]. + if ( m_rightCol > other.m_rightCol ) + result.m_parts[3] = + wxGridBlockCoords(maxTopRow, other.m_rightCol + 1, + minBottomRow, m_rightCol); + } + else // wxVERTICAL + { + // Part[0]. + if ( m_leftCol < other.m_leftCol ) + result.m_parts[0] = + wxGridBlockCoords(m_topRow, m_leftCol, + m_bottomRow, other.m_leftCol - 1); + + // Part[1]. + if ( m_rightCol > other.m_rightCol ) + result.m_parts[1] = + wxGridBlockCoords(m_topRow, other.m_rightCol + 1, + m_bottomRow, m_rightCol); + + const int maxLeftCol = wxMax(m_leftCol, other.m_leftCol); + const int minRightCol = wxMin(m_rightCol, other.m_rightCol); + + // Part[2]. + if ( m_topRow < other.m_topRow ) + result.m_parts[2] = + wxGridBlockCoords(m_topRow, maxLeftCol, + other.m_topRow - 1, minRightCol); + + // Part[3]. + if ( m_bottomRow > other.m_bottomRow ) + result.m_parts[3] = + wxGridBlockCoords(other.m_bottomRow + 1, maxLeftCol, + m_bottomRow, minRightCol); + } + + return result; +} + +wxGridBlockDiffResult +wxGridBlockCoords::SymDifference(const wxGridBlockCoords& other) const +{ + wxGridBlockDiffResult result; + + // Check whether the blocks intersect. + if ( !Intersects(other) ) + { + result.m_parts[0] = *this; + result.m_parts[1] = other; + return result; + } + + // Possible result blocks: + // |------------------| + // | | minUpper->m_topRow + // | part[0] | + // | | maxUpper->m_topRow - 1 + // |-----------------------------| + // | | | | maxUpper->m_topRow + // | part[2] | x | part[3] | + // | | | | minLower->m_bottomRow + // |-----------------------------| + // | | minLower->m_bottomRow + 1 + // | part[1] | + // | | maxLower->m_bottomRow + // |-------------------| + // + // The x marks the intersection of the blocks, which is not a part + // of the result. + + // Part[0]. + int maxUpperRow; + if ( m_topRow != other.m_topRow ) + { + const bool block1Min = m_topRow < other.m_topRow; + const wxGridBlockCoords& minUpper = block1Min ? *this : other; + const wxGridBlockCoords& maxUpper = block1Min ? other : *this; + maxUpperRow = maxUpper.m_topRow; + + result.m_parts[0] = wxGridBlockCoords(minUpper.m_topRow, + minUpper.m_leftCol, + maxUpper.m_topRow - 1, + minUpper.m_rightCol); + } + else + { + maxUpperRow = m_topRow; + } + + // Part[1]. + int minLowerRow; + if ( m_bottomRow != other.m_bottomRow ) + { + const bool block1Min = m_bottomRow < other.m_bottomRow; + const wxGridBlockCoords& minLower = block1Min ? *this : other; + const wxGridBlockCoords& maxLower = block1Min ? other : *this; + minLowerRow = minLower.m_bottomRow; + + result.m_parts[1] = wxGridBlockCoords(minLower.m_bottomRow + 1, + maxLower.m_leftCol, + maxLower.m_bottomRow, + maxLower.m_rightCol); + } + else + { + minLowerRow = m_bottomRow; + } + + // Part[2]. + if ( m_leftCol != other.m_leftCol ) + { + result.m_parts[2] = wxGridBlockCoords(maxUpperRow, + wxMin(m_leftCol, + other.m_leftCol), + minLowerRow, + wxMax(m_leftCol, + other.m_leftCol) - 1); + } + + // Part[3]. + if ( m_rightCol != other.m_rightCol ) + { + result.m_parts[3] = wxGridBlockCoords(maxUpperRow, + wxMin(m_rightCol, + other.m_rightCol) + 1, + minLowerRow, + wxMax(m_rightCol, + other.m_rightCol)); + } + + return result; +} + // ---------------------------------------------------------------------------- // wxGridTableBase // ---------------------------------------------------------------------------- @@ -2532,7 +2715,6 @@ wxGrid::SetTable(wxGridTableBase *table, bool takeOwnership, wxGrid::wxGridSelectionModes selmode ) { - bool checkSelection = false; if ( m_created ) { // stop all processing @@ -2564,7 +2746,6 @@ wxGrid::SetTable(wxGridTableBase *table, m_numCols = 0; m_numFrozenRows = 0; m_numFrozenCols = 0; - checkSelection = true; // kill row and column size arrays m_colWidths.Empty(); @@ -2588,28 +2769,6 @@ wxGrid::SetTable(wxGridTableBase *table, SetNativeHeaderColCount(); m_selection = new wxGridSelection( this, selmode ); - if (checkSelection) - { - // If the newly set table is smaller than the - // original one current cell and selection regions - // might be invalid, - m_selectedBlockCorner = wxGridNoCellCoords; - m_currentCellCoords = - wxGridCellCoords(wxMin(m_numRows, m_currentCellCoords.GetRow()), - wxMin(m_numCols, m_currentCellCoords.GetCol())); - if (m_selectedBlockTopLeft.GetRow() >= m_numRows || - m_selectedBlockTopLeft.GetCol() >= m_numCols) - { - m_selectedBlockTopLeft = wxGridNoCellCoords; - m_selectedBlockBottomRight = wxGridNoCellCoords; - } - else - m_selectedBlockBottomRight = - wxGridCellCoords(wxMin(m_numRows, - m_selectedBlockBottomRight.GetRow()), - wxMin(m_numCols, - m_selectedBlockBottomRight.GetCol())); - } CalcDimensions(); m_created = true; @@ -2719,10 +2878,6 @@ void wxGrid::Init() m_currentCellCoords = wxGridNoCellCoords; - m_selectedBlockTopLeft = - m_selectedBlockBottomRight = - m_selectedBlockCorner = wxGridNoCellCoords; - m_selectionBackground = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHT); m_selectionForeground = wxSystemSettings::GetColour(wxSYS_COLOUR_HIGHLIGHTTEXT); @@ -3515,10 +3670,20 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo case WXGRID_CURSOR_SELECT_ROW: { + if ( !m_selection || m_numRows == 0 || m_numCols == 0 ) + break; + + // We can't extend the selection from non-selected row, + // which may happen if we Ctrl-clicked it initially. + if ( !m_selection->IsInSelection(m_currentCellCoords) ) + break; + if ( (row = YToRow( pos.y )) >= 0 ) { - if ( m_selection ) - m_selection->SelectRow(row, event); + m_selection->ExtendCurrentBlock( + wxGridCellCoords(m_currentCellCoords.GetRow(), 0), + wxGridCellCoords(row, GetNumberCols() - 1), + event); } } break; @@ -3560,26 +3725,55 @@ void wxGrid::ProcessRowLabelMouseEvent( wxMouseEvent& event, wxGridRowLabelWindo if ( row >= 0 && !SendEvent( wxEVT_GRID_LABEL_LEFT_CLICK, row, -1, event ) ) { - if ( !event.ShiftDown() && !event.CmdDown() ) - ClearSelection(); - if ( m_selection ) + // Check if row selection is possible and allowed, before doing + // anything else, including changing the cursor mode to "select + // row". + if ( m_selection && m_numRows > 0 && m_numCols > 0 && + m_selection->GetSelectionMode() != wxGridSelectColumns ) { - if ( event.ShiftDown() ) + bool selectNewRow = false, + makeRowCurrent = false; + + if ( event.ShiftDown() && !event.CmdDown() ) { - m_selection->SelectBlock + // Continue editing the current selection and don't + // move the grid cursor. + m_selection->ExtendCurrentBlock ( - m_currentCellCoords.GetRow(), 0, - row, GetNumberCols() - 1, + wxGridCellCoords(m_currentCellCoords.GetRow(), 0), + wxGridCellCoords(row, GetNumberCols() - 1), event ); + MakeCellVisible(row, -1); + } + else if ( event.CmdDown() && !event.ShiftDown() ) + { + if ( m_selection->IsInSelection(row, 0) ) + { + DeselectRow(row); + makeRowCurrent = true; + } + else + { + makeRowCurrent = + selectNewRow = true; + } } else { - m_selection->SelectRow(row, event); + ClearSelection(); + makeRowCurrent = + selectNewRow = true; } - } - ChangeCursorMode(WXGRID_CURSOR_SELECT_ROW, rowLabelWin); + if ( selectNewRow ) + m_selection->SelectRow(row, event); + + if ( makeRowCurrent ) + SetCurrentCell(row, GetFirstFullyVisibleColumn()); + + ChangeCursorMode(WXGRID_CURSOR_SELECT_ROW, rowLabelWin); + } } } } @@ -3824,8 +4018,19 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo { if ( col != -1 ) { - if ( m_selection ) - m_selection->SelectCol(col, event); + if ( !m_selection || m_numRows == 0 || m_numCols == 0 ) + break; + + // We can't extend the selection from non-selected + // column which may happen if we Ctrl-clicked it + // initially. + if ( !m_selection->IsInSelection(m_currentCellCoords) ) + break; + + m_selection->ExtendCurrentBlock( + wxGridCellCoords(0, m_currentCellCoords.GetCol()), + wxGridCellCoords(GetNumberRows() - 1, col), + event); } } break; @@ -3934,26 +4139,52 @@ void wxGrid::ProcessColLabelMouseEvent( wxMouseEvent& event, wxGridColLabelWindo } else { - if ( !event.ShiftDown() && !event.CmdDown() ) - ClearSelection(); - if ( m_selection ) + if ( m_selection && m_numRows > 0 && m_numCols > 0 && + m_selection->GetSelectionMode() != wxGridSelectRows ) { - if ( event.ShiftDown() ) + bool selectNewCol = false, + makeColCurrent = false; + + if ( event.ShiftDown() && !event.CmdDown() ) { - m_selection->SelectBlock + // Continue editing the current selection and don't + // move the grid cursor. + m_selection->ExtendCurrentBlock ( - 0, m_currentCellCoords.GetCol(), - GetNumberRows() - 1, col, + wxGridCellCoords(0, m_currentCellCoords.GetCol()), + wxGridCellCoords(GetNumberRows() - 1, col), event ); + MakeCellVisible(-1, col); + } + else if ( event.CmdDown() && !event.ShiftDown() ) + { + if ( m_selection->IsInSelection(0, col) ) + { + DeselectCol(col); + makeColCurrent = true; + } + else + { + makeColCurrent = + selectNewCol = true; + } } else { - m_selection->SelectCol(col, event); + ClearSelection(); + makeColCurrent = + selectNewCol = true; } - } - ChangeCursorMode(WXGRID_CURSOR_SELECT_COL, colLabelWin); + if ( selectNewCol ) + m_selection->SelectCol(col, event); + + if ( makeColCurrent ) + SetCurrentCell(GetFirstFullyVisibleRow(), col); + + ChangeCursorMode(WXGRID_CURSOR_SELECT_COL, colLabelWin); + } } } } @@ -4251,53 +4482,51 @@ wxGrid::DoGridCellDrag(wxMouseEvent& event, const wxGridCellCoords& coords, bool isFirstDrag) { - bool performDefault = true ; - if ( coords == wxGridNoCellCoords ) - return performDefault; // we're outside any valid cell + return false; // we're outside any valid cell - // Hide the edit control, so it won't interfere with drag-shrinking. - if ( IsCellEditControlShown() ) + if ( isFirstDrag ) { - HideCellEditControl(); - SaveEditControlValue(); - } + // Hide the edit control, so it won't interfere with drag-shrinking. + if ( IsCellEditControlShown() ) + { + HideCellEditControl(); + SaveEditControlValue(); + } - switch ( event.GetModifiers() ) - { - case wxMOD_CONTROL: - if ( m_selectedBlockCorner == wxGridNoCellCoords) - m_selectedBlockCorner = coords; - if ( isFirstDrag ) - SetGridCursor(coords); - UpdateBlockBeingSelected(m_currentCellCoords, coords); - break; + switch ( event.GetModifiers() ) + { + case wxMOD_CONTROL: + // Ctrl-dragging is special, because we could have started it + // by Ctrl-clicking a previously selected cell, which has the + // effect of deselecting it and in this case we can't start + // drag-selection from it because the selection anchor should + // always be selected itself. + if ( !m_selection->IsInSelection(m_currentCellCoords) ) + return false; + break; - case wxMOD_NONE: - if ( CanDragCell() ) - { - if ( isFirstDrag ) + case wxMOD_NONE: + if ( CanDragCell() ) { - if ( m_selectedBlockCorner == wxGridNoCellCoords) - m_selectedBlockCorner = coords; - // if event is handled by user code, no further processing - if ( SendEvent(wxEVT_GRID_CELL_BEGIN_DRAG, coords, event) != 0 ) - performDefault = false; - - return performDefault; + return SendEvent(wxEVT_GRID_CELL_BEGIN_DRAG, coords, event) == 0; } - } + break; - UpdateBlockBeingSelected(m_currentCellCoords, coords); - break; - - default: - // we don't handle the other key modifiers - event.Skip(); + //default: In all the other cases, we don't have anything special + // to do and we'll just extend the selection below. + } } - return performDefault; + // Note that we don't need to check the modifiers here, it doesn't matter + // which keys are pressed for the current event, as pressing or releasing + // Ctrl later can't change the dragging behaviour. Only the initial state + // of the modifier keys matters. + if ( m_selection ) + m_selection->ExtendCurrentBlock(m_currentCellCoords, coords, event); + + return true; } bool wxGrid::DoGridDragEvent(wxMouseEvent& event, @@ -4336,15 +4565,12 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, return; } - if ( !event.CmdDown() ) - ClearSelection(); - - if ( event.ShiftDown() ) + if ( event.ShiftDown() && !event.CmdDown() ) { if ( m_selection ) { - m_selection->SelectBlock(m_currentCellCoords, coords, event); - m_selectedBlockCorner = coords; + m_selection->ExtendCurrentBlock(m_currentCellCoords, coords, event); + MakeCellVisible(coords); } } else if ( XToEdgeOfCol(pos.x) < 0 && YToEdgeOfRow(pos.y) < 0 ) @@ -4352,19 +4578,31 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, DisableCellEditControl(); MakeCellVisible( coords ); - if ( event.CmdDown() ) + if ( event.CmdDown() && !event.ShiftDown() ) { if ( m_selection ) { - m_selection->ToggleCellSelection(coords, event); + if ( !m_selection->IsInSelection(coords) ) + { + // If the cell is not selected, select it. + m_selection->SelectBlock(coords.GetRow(), coords.GetCol(), + coords.GetRow(), coords.GetCol(), + event); + } + else + { + // Otherwise deselect it. + m_selection->DeselectBlock( + wxGridBlockCoords(coords.GetRow(), coords.GetCol(), + coords.GetRow(), coords.GetCol()), + event); + } } - - m_selectedBlockTopLeft = wxGridNoCellCoords; - m_selectedBlockBottomRight = wxGridNoCellCoords; - m_selectedBlockCorner = coords; } else { + ClearSelection(); + if ( m_selection ) { // In row or column selection mode just clicking on the cell @@ -4390,8 +4628,9 @@ wxGrid::DoGridCellLeftDown(wxMouseEvent& event, m_waitForSlowClick = m_currentCellCoords == coords && coords != wxGridNoCellCoords; - SetCurrentCell( coords ); } + + SetCurrentCell(coords); } } @@ -4429,19 +4668,8 @@ wxGrid::DoGridCellLeftUp(wxMouseEvent& event, m_waitForSlowClick = false; } - else if ( m_selectedBlockTopLeft != wxGridNoCellCoords && - m_selectedBlockBottomRight != wxGridNoCellCoords ) + else if ( m_selection && m_selection->IsSelection() ) { - if ( m_selection ) - { - m_selection->SelectBlock( m_selectedBlockTopLeft, - m_selectedBlockBottomRight, - event ); - } - - m_selectedBlockTopLeft = wxGridNoCellCoords; - m_selectedBlockBottomRight = wxGridNoCellCoords; - // Show the edit control, if it has been hidden for // drag-shrinking. ShowCellEditControl(); @@ -5076,24 +5304,10 @@ wxGrid::SendEvent(wxEventType type, { bool claimed, vetoed; - if ( type == wxEVT_GRID_RANGE_SELECT ) - { - // Right now, it should _never_ end up here! - wxGridRangeSelectEvent gridEvt( GetId(), - type, - this, - m_selectedBlockTopLeft, - m_selectedBlockBottomRight, - true, - mouseEv); - - claimed = GetEventHandler()->ProcessEvent(gridEvt); - vetoed = !gridEvt.IsAllowed(); - } - else if ( type == wxEVT_GRID_LABEL_LEFT_CLICK || - type == wxEVT_GRID_LABEL_LEFT_DCLICK || - type == wxEVT_GRID_LABEL_RIGHT_CLICK || - type == wxEVT_GRID_LABEL_RIGHT_DCLICK ) + if ( type == wxEVT_GRID_LABEL_LEFT_CLICK || + type == wxEVT_GRID_LABEL_LEFT_DCLICK || + type == wxEVT_GRID_LABEL_RIGHT_CLICK || + type == wxEVT_GRID_LABEL_RIGHT_DCLICK ) { wxPoint pos = mouseEv.GetPosition(); @@ -5454,31 +5668,35 @@ void wxGrid::OnKeyDown( wxKeyEvent& event ) switch ( event.GetKeyCode() ) { case WXK_UP: - if ( event.ControlDown() ) - MoveCursorUpBlock( event.ShiftDown() ); - else - MoveCursorUp( event.ShiftDown() ); + DoMoveCursorFromKeyboard + ( + event, + wxGridBackwardOperations(this, wxGridRowOperations()) + ); break; case WXK_DOWN: - if ( event.ControlDown() ) - MoveCursorDownBlock( event.ShiftDown() ); - else - MoveCursorDown( event.ShiftDown() ); + DoMoveCursorFromKeyboard + ( + event, + wxGridForwardOperations(this, wxGridRowOperations()) + ); break; case WXK_LEFT: - if ( event.ControlDown() ) - MoveCursorLeftBlock( event.ShiftDown() ); - else - MoveCursorLeft( event.ShiftDown() ); + DoMoveCursorFromKeyboard + ( + event, + wxGridBackwardOperations(this, wxGridColumnOperations()) + ); break; case WXK_RIGHT: - if ( event.ControlDown() ) - MoveCursorRightBlock( event.ShiftDown() ); - else - MoveCursorRight( event.ShiftDown() ); + DoMoveCursorFromKeyboard + ( + event, + wxGridForwardOperations(this, wxGridColumnOperations()) + ); break; case WXK_RETURN: @@ -5550,13 +5768,12 @@ void wxGrid::OnKeyDown( wxKeyEvent& event ) } else { - // If we're selecting, continue in the same row, which - // may well be different from the one in which we - // started selecting. - if ( event.ShiftDown() && - m_selectedBlockCorner != wxGridNoCellCoords ) + // If we're extending the selection, try to continue in + // the same row, which may well be different from the + // one in which we started selecting. + if ( m_selection && event.ShiftDown() ) { - row = m_selectedBlockCorner.GetRow(); + row = m_selection->GetExtensionAnchor().GetRow(); } else // Just use the current row. { @@ -5585,8 +5802,11 @@ void wxGrid::OnKeyDown( wxKeyEvent& event ) if ( event.ShiftDown() ) { - UpdateBlockBeingSelected(m_currentCellCoords, - wxGridCellCoords(row, col)); + if ( m_selection ) + m_selection->ExtendCurrentBlock( + m_currentCellCoords, + wxGridCellCoords(row, col), + event); MakeCellVisible(row, col); } else @@ -5598,41 +5818,61 @@ void wxGrid::OnKeyDown( wxKeyEvent& event ) break; case WXK_PAGEUP: - MovePageUp(); + DoMoveCursorByPage + ( + event, + wxGridBackwardOperations(this, wxGridRowOperations()) + ); break; case WXK_PAGEDOWN: - MovePageDown(); + DoMoveCursorByPage + ( + event, + wxGridForwardOperations(this, wxGridRowOperations()) + ); break; case WXK_SPACE: - // Ctrl-Space selects the current column, Shift-Space -- the - // current row and Ctrl-Shift-Space -- everything - switch ( m_selection ? event.GetModifiers() : wxMOD_NONE ) + // Ctrl-Space selects the current column or, if there is a + // selection, all columns containing the selected cells, + // Shift-Space -- the current row (or all selection rows) and + // Ctrl-Shift-Space -- everything. { - case wxMOD_CONTROL: - m_selection->SelectCol(m_currentCellCoords.GetCol()); - break; - - case wxMOD_SHIFT: - m_selection->SelectRow(m_currentCellCoords.GetRow()); - break; - - case wxMOD_CONTROL | wxMOD_SHIFT: - m_selection->SelectBlock(0, 0, - m_numRows - 1, m_numCols - 1); - break; - - case wxMOD_NONE: - if ( !IsEditable() ) - { - MoveCursorRight(false); + wxGridCellCoords selStart, selEnd; + switch ( m_selection ? event.GetModifiers() : wxMOD_NONE ) + { + case wxMOD_CONTROL: + selStart.Set(0, m_currentCellCoords.GetCol()); + selEnd.Set(m_numRows - 1, + m_selection->GetExtensionAnchor().GetCol()); break; - } - wxFALLTHROUGH; - default: - event.Skip(); + case wxMOD_SHIFT: + selStart.Set(m_currentCellCoords.GetRow(), 0); + selEnd.Set(m_selection->GetExtensionAnchor().GetRow(), + m_numCols - 1); + break; + + case wxMOD_CONTROL | wxMOD_SHIFT: + selStart.Set(0, 0); + selEnd.Set(m_numRows - 1, m_numCols - 1); + break; + + case wxMOD_NONE: + if ( !IsEditable() ) + { + MoveCursorRight(false); + break; + } + wxFALLTHROUGH; + + default: + event.Skip(); + } + + if ( selStart != wxGridNoCellCoords ) + m_selection->ExtendCurrentBlock(selStart, selEnd, event); } break; @@ -5645,28 +5885,9 @@ void wxGrid::OnKeyDown( wxKeyEvent& event ) m_inOnKeyDown = false; } -void wxGrid::OnKeyUp( wxKeyEvent& event ) +void wxGrid::OnKeyUp( wxKeyEvent& WXUNUSED(event) ) { // try local handlers - // - if ( event.GetKeyCode() == WXK_SHIFT ) - { - if ( m_selectedBlockTopLeft != wxGridNoCellCoords && - m_selectedBlockBottomRight != wxGridNoCellCoords ) - { - if ( m_selection ) - { - m_selection->SelectBlock( - m_selectedBlockTopLeft, - m_selectedBlockBottomRight, - event); - } - } - - m_selectedBlockTopLeft = wxGridNoCellCoords; - m_selectedBlockBottomRight = wxGridNoCellCoords; - m_selectedBlockCorner = wxGridNoCellCoords; - } } void wxGrid::OnChar( wxKeyEvent& event ) @@ -5836,122 +6057,6 @@ bool wxGrid::SetCurrentCell( const wxGridCellCoords& coords ) return true; } -void -wxGrid::UpdateBlockBeingSelected(int blockStartRow, int blockStartCol, - int blockEndRow, int blockEndCol) -{ - m_selectedBlockCorner = wxGridCellCoords(blockEndRow, blockEndCol); - MakeCellVisible(m_selectedBlockCorner); - - int topRow = wxMin(blockStartRow, blockEndRow); - int leftCol = wxMin(blockStartCol, blockEndCol); - int bottomRow = wxMax(blockStartRow, blockEndRow); - int rightCol = wxMax(blockStartCol, blockEndCol); - - if ( m_selection ) - { - switch ( m_selection->GetSelectionMode() ) - { - default: - wxFAIL_MSG( "unknown selection mode" ); - wxFALLTHROUGH; - - case wxGridSelectCells: - // arbitrary blocks selection allowed so just use the cell - // coordinates as is - break; - - case wxGridSelectRows: - // only full rows selection allowed, ensure that we do select - // full rows - leftCol = 0; - rightCol = GetNumberCols() - 1; - break; - - case wxGridSelectColumns: - // same as above but for columns - topRow = 0; - bottomRow = GetNumberRows() - 1; - break; - - case wxGridSelectRowsOrColumns: - // in this mode we can select only full rows or full columns so - // it doesn't make sense to select blocks at all (and we can't - // extend the block because there is no preferred direction, we - // could only extend it to cover the entire grid but this is - // not useful) - return; - } - } - - wxGridCellCoords updateTopLeft = wxGridCellCoords(topRow, leftCol), - updateBottomRight = wxGridCellCoords(bottomRow, rightCol); - - // First the case that we selected a completely new area - if ( m_selectedBlockTopLeft == wxGridNoCellCoords || - m_selectedBlockBottomRight == wxGridNoCellCoords ) - { - RefreshBlock(topRow, leftCol, bottomRow, rightCol); - } - - // Now handle changing an existing selection area. - else if ( m_selectedBlockTopLeft != updateTopLeft || - m_selectedBlockBottomRight != updateBottomRight ) - { - // Compute two optimal update rectangles: - // Either one rectangle is a real subset of the - // other, or they are (almost) disjoint! - - // Store intermediate values - wxCoord oldLeft = m_selectedBlockTopLeft.GetCol(); - wxCoord oldTop = m_selectedBlockTopLeft.GetRow(); - wxCoord oldRight = m_selectedBlockBottomRight.GetCol(); - wxCoord oldBottom = m_selectedBlockBottomRight.GetRow(); - - // Determine the outer/inner coordinates. - EnsureFirstLessThanSecond(oldLeft, leftCol); - EnsureFirstLessThanSecond(oldTop, topRow); - EnsureFirstLessThanSecond(rightCol, oldRight); - EnsureFirstLessThanSecond(bottomRow, oldBottom); - - // Now, either the stuff marked old is the outer - // rectangle or we don't have a situation where one - // is contained in the other. - - if ( oldLeft < leftCol ) - { - // Refresh the newly selected or deselected - // area to the left of the old or new selection. - RefreshBlock(oldTop, oldLeft, oldBottom, leftCol - 1); - } - - if ( oldTop < topRow ) - { - // Refresh the newly selected or deselected - // area above the old or new selection. - RefreshBlock(oldTop, leftCol, topRow - 1, rightCol); - } - - if ( oldRight > rightCol ) - { - // Refresh the newly selected or deselected - // area to the right of the old or new selection. - RefreshBlock(oldTop, rightCol + 1, oldBottom, oldRight); - } - - if ( oldBottom > bottomRow ) - { - // Refresh the newly selected or deselected - // area below the old or new selection. - RefreshBlock(bottomRow + 1, leftCol, oldBottom, rightCol); - } - } - - // change selection - m_selectedBlockTopLeft = updateTopLeft; - m_selectedBlockBottomRight = updateBottomRight; -} - // Note - this function only draws cells that are in the list of // exposed cells (usually set from the update region by // CalcExposedCells) @@ -7632,22 +7737,54 @@ void wxGrid::MakeCellVisible( int row, int col ) { int xpos = -1, ypos = -1; - if ( row >= 0 && row < m_numRows && - col >= 0 && col < m_numCols ) + if ( row < -1 || row >= m_numRows || + col < -1 || col >= m_numCols ) + return; + + const bool processRow = row != -1; + const bool processCol = col != -1; + + // Get the cell rectangle in logical coords. + wxRect r; + wxGridWindow *gridWindow; + + if ( processRow && processCol ) { - // get the cell rectangle in logical coords - wxRect r( CellToRect( row, col ) ); + r = CellToRect(row, col); + gridWindow = CellToGridWindow(row, col); + } + else if ( processRow ) + { + r.SetTop(GetRowTop(row)); + r.SetHeight(GetRowHeight(row)); + gridWindow = row < m_numFrozenRows + ? m_frozenRowGridWin + : m_gridWin; + } + else if ( processCol ) + { + r.SetLeft(GetColLeft(col)); + r.SetWidth(GetColWidth(col)); + gridWindow = col < m_numFrozenCols + ? m_frozenColGridWin + : m_gridWin; + } + else + { + return; + } - wxGridWindow *gridWindow = CellToGridWindow(row, col); - wxPoint gridOffset = GetGridWindowOffset(gridWindow); + wxPoint gridOffset = GetGridWindowOffset(gridWindow); - // convert to device coords - int left, top, right, bottom; - CalcGridWindowScrolledPosition( r.GetLeft(), r.GetTop(), &left, &top, gridWindow ); - CalcGridWindowScrolledPosition( r.GetRight(), r.GetBottom(), &right, &bottom, gridWindow ); + if ( processRow ) + { + // Convert to device coords. + int top, bottom; + CalcGridWindowScrolledPosition(0, r.GetTop(), NULL, &top, gridWindow); + CalcGridWindowScrolledPosition(0, r.GetBottom(), NULL, &bottom, gridWindow); - int cw, ch; - gridWindow->GetClientSize( &cw, &ch ); + int ch; + gridWindow->GetClientSize(NULL, &ch); if ( top < gridOffset.y ) { @@ -7678,6 +7815,17 @@ void wxGrid::MakeCellVisible( int row, int col ) // so just add a full scroll unit... ypos += m_yScrollPixelsPerLine; } + } + + if ( processCol ) + { + // Convert to device coords. + int left, right; + CalcGridWindowScrolledPosition(r.GetLeft(), 0, &left, NULL, gridWindow); + CalcGridWindowScrolledPosition(r.GetRight(), 0, &right, NULL, gridWindow); + + int cw; + gridWindow->GetClientSize(&cw, NULL); // special handling for wide cells - show always left part of the cell! // Otherwise, e.g. when stepping from row to row, it would jump between @@ -7697,52 +7845,160 @@ void wxGrid::MakeCellVisible( int row, int col ) // see comment for ypos above xpos += m_xScrollPixelsPerLine; } + } - if ( xpos != -1 || ypos != -1 ) + if ( xpos == -1 && ypos == -1 ) + return; + + if ( xpos != -1 ) + xpos /= m_xScrollPixelsPerLine; + if ( ypos != -1 ) + ypos /= m_yScrollPixelsPerLine; + Scroll(xpos, ypos); + AdjustScrollbars(); +} + +int wxGrid::GetFirstFullyVisibleRow() const +{ + if ( m_numRows == 0 ) + return -1; + + int row; + if ( GetNumberFrozenRows() > 0 ) + { + row = 0; + } + else + { + int y; + CalcGridWindowUnscrolledPosition(0, 0, + NULL, &y, + m_gridWin); + + row = YToRow(y, true, m_gridWin); + + // If the row is not fully visible (if only 2 pixels is hidden + // the row still looks fully visible). + if ( GetRowTop(row) + 2 < y ) { - if ( xpos != -1 ) - xpos /= m_xScrollPixelsPerLine; - if ( ypos != -1 ) - ypos /= m_yScrollPixelsPerLine; - Scroll( xpos, ypos ); - AdjustScrollbars(); + // Use the next visible row. + for ( ;; ) + { + // But we can't go beyond the last row anyhow. + if ( row == m_numRows - 1 ) + break; + + if ( IsRowShown(++row) ) + break; + } } } + + return row; +} + +int wxGrid::GetFirstFullyVisibleColumn() const +{ + if ( m_numCols == 0 ) + return -1; + + int col; + if ( GetNumberFrozenCols() > 0 ) + { + col = 0; + } + else + { + int x; + CalcGridWindowUnscrolledPosition(0, 0, + &x, NULL, + m_gridWin); + + col = XToCol(x, true, m_gridWin); + + // If the column is not fully visible. + if ( GetColLeft(col) < x ) + { + // Use the next visible column. + for ( ;; ) + { + if ( col == m_numCols - 1 ) + break; + + if ( IsColShown(GetColAt(++col)) ) + break; + } + } + } + + return col; } // // ------ Grid cursor movement functions // +namespace +{ + +// Helper function creating dummy wxKeyboardState object corresponding to the +// value of "expandSelection" flag specified to our public MoveCursorXXX(). +// This function only exists to avoid breaking compatibility in the public API, +// all internal code should use the real, not dummy, wxKeyboardState. +inline +wxKeyboardState DummyKeyboardState(bool expandSelection) +{ + // Normally "expandSelection" is set depending on whether Shift is pressed + // or not, but here we reconstruct the keyboard state from this flag. + return wxKeyboardState(false /* control */, expandSelection /* shift */); +} + +} // anonymous namespace + +void +wxGrid::DoMoveCursorFromKeyboard(const wxKeyboardState& kbdState, + const wxGridDirectionOperations& diroper) +{ + if ( kbdState.ControlDown() ) + DoMoveCursorByBlock(kbdState, diroper); + else + DoMoveCursor(kbdState, diroper); +} + bool -wxGrid::DoMoveCursor(bool expandSelection, +wxGrid::DoMoveCursor(const wxKeyboardState& kbdState, const wxGridDirectionOperations& diroper) { if ( m_currentCellCoords == wxGridNoCellCoords ) return false; - if ( expandSelection ) + // Expand selection if Shift is pressed. + if ( kbdState.ShiftDown() ) { - wxGridCellCoords coords = m_selectedBlockCorner; - if ( coords == wxGridNoCellCoords ) - coords = m_currentCellCoords; - - if ( diroper.IsAtBoundary(coords) ) + if ( !m_selection ) return false; - diroper.Advance(coords); + wxGridCellCoords coords(m_selection->GetExtensionAnchor()); + if ( !diroper.TryToAdvance(coords) ) + return false; - UpdateBlockBeingSelected(m_currentCellCoords, coords); + if ( m_selection->ExtendCurrentBlock(m_currentCellCoords, + coords, + kbdState) ) + { + // We want to show a line (a row or a column), not the end of + // the selection block. And do it only if the selection block + // was actually changed. + MakeCellVisible(diroper.MakeWholeLineCoords(coords)); + } } else // don't expand selection { ClearSelection(); - if ( diroper.IsAtBoundary(m_currentCellCoords) ) - return false; - wxGridCellCoords coords = m_currentCellCoords; - diroper.Advance(coords); + if ( !diroper.TryToAdvance(coords) ) + return false; GoToCell(coords); } @@ -7752,59 +8008,89 @@ wxGrid::DoMoveCursor(bool expandSelection, bool wxGrid::MoveCursorUp(bool expandSelection) { - return DoMoveCursor(expandSelection, + return DoMoveCursor(DummyKeyboardState(expandSelection), wxGridBackwardOperations(this, wxGridRowOperations())); } bool wxGrid::MoveCursorDown(bool expandSelection) { - return DoMoveCursor(expandSelection, + return DoMoveCursor(DummyKeyboardState(expandSelection), wxGridForwardOperations(this, wxGridRowOperations())); } bool wxGrid::MoveCursorLeft(bool expandSelection) { - return DoMoveCursor(expandSelection, + return DoMoveCursor(DummyKeyboardState(expandSelection), wxGridBackwardOperations(this, wxGridColumnOperations())); } bool wxGrid::MoveCursorRight(bool expandSelection) { - return DoMoveCursor(expandSelection, + return DoMoveCursor(DummyKeyboardState(expandSelection), wxGridForwardOperations(this, wxGridColumnOperations())); } -bool wxGrid::DoMoveCursorByPage(const wxGridDirectionOperations& diroper) +bool +wxGrid::AdvanceByPage(wxGridCellCoords& coords, + const wxGridDirectionOperations& diroper) +{ + if ( diroper.IsAtBoundary(coords) ) + return false; + + const int oldRow = coords.GetRow(); + coords.SetRow(diroper.MoveByPixelDistance(oldRow, m_gridWin->GetClientSize().y)); + if ( coords.GetRow() == oldRow ) + diroper.Advance(coords); + + return true; +} + +bool +wxGrid::DoMoveCursorByPage(const wxKeyboardState& kbdState, + const wxGridDirectionOperations& diroper) { if ( m_currentCellCoords == wxGridNoCellCoords ) return false; - if ( diroper.IsAtBoundary(m_currentCellCoords) ) + // We don't handle Ctrl-PageUp/Down, it's not really clear what are they + // supposed to do, so don't do anything for now. + if ( kbdState.ControlDown() ) return false; - const int oldRow = m_currentCellCoords.GetRow(); - int newRow = diroper.MoveByPixelDistance(oldRow, m_gridWin->GetClientSize().y); - if ( newRow == oldRow ) + if ( kbdState.ShiftDown() ) + { + if ( !m_selection ) + return false; + + wxGridCellCoords coords = m_selection->GetExtensionAnchor(); + if ( !AdvanceByPage(coords, diroper) ) + return false; + + if ( m_selection->ExtendCurrentBlock(m_currentCellCoords, coords, kbdState) ) + MakeCellVisible(diroper.MakeWholeLineCoords(coords)); + } + else { wxGridCellCoords coords(m_currentCellCoords); - diroper.Advance(coords); - newRow = coords.GetRow(); - } + if ( !AdvanceByPage(coords, diroper) ) + return false; - GoToCell(newRow, m_currentCellCoords.GetCol()); + ClearSelection(); + GoToCell(coords); + } return true; } bool wxGrid::MovePageUp() { - return DoMoveCursorByPage( + return DoMoveCursorByPage(DummyKeyboardState(false), wxGridBackwardOperations(this, wxGridRowOperations())); } bool wxGrid::MovePageDown() { - return DoMoveCursorByPage( + return DoMoveCursorByPage(DummyKeyboardState(false), wxGridForwardOperations(this, wxGridRowOperations())); } @@ -7823,16 +8109,9 @@ wxGrid::AdvanceToNextNonEmpty(wxGridCellCoords& coords, } bool -wxGrid::DoMoveCursorByBlock(bool expandSelection, - const wxGridDirectionOperations& diroper) +wxGrid::AdvanceByBlock(wxGridCellCoords& coords, + const wxGridDirectionOperations& diroper) { - if ( !m_table || m_currentCellCoords == wxGridNoCellCoords ) - return false; - - if ( diroper.IsAtBoundary(m_currentCellCoords) ) - return false; - - wxGridCellCoords coords(m_currentCellCoords); if ( m_table->IsEmpty(coords) ) { // we are in an empty cell: find the next block of non-empty cells @@ -7840,7 +8119,9 @@ wxGrid::DoMoveCursorByBlock(bool expandSelection, } else // current cell is not empty { - diroper.Advance(coords); + if ( !diroper.TryToAdvance(coords) ) + return false; + if ( m_table->IsEmpty(coords) ) { // we started at the end of a block, find the next one @@ -7862,12 +8143,81 @@ wxGrid::DoMoveCursorByBlock(bool expandSelection, } } - if ( expandSelection ) + return true; +} + +bool +wxGrid::DoMoveCursorByBlock(const wxKeyboardState& kbdState, + const wxGridDirectionOperations& diroper) +{ + if ( !m_table ) + return false; + + wxGridCellCoords coords(m_currentCellCoords); + if ( kbdState.ShiftDown() ) { - UpdateBlockBeingSelected(m_currentCellCoords, coords); + if ( !m_selection ) + return false; + + // Extending selection by block is tricky for several reasons. First of + // all, we need to combine the coordinates of the current cell and the + // anchor point of selection to find our starting point. + // + // To explain why we need to do this, consider the example of moving by + // rows (i.e. vertically). In this case, it's the contents of column + // containing the current cell which should determine the destination + // of Shift-Ctrl-Up/Down movement, just as it does for Ctrl-Up/Down. In + // fact, the column containing the selection anchor might not even be + // visible at all, e.g. if a whole row is selected and that column is + // the last one, so we definitely don't want to use the contents of + // this column to determine the destination row. + // + // So instead of using the anchor itself here, use only its component + // component in "our" direction with the current cell component. + const wxGridCellCoords anchor = m_selection->GetExtensionAnchor(); + + // This is a really ugly hack that we use to check if we're moving by + // rows or columns here, but it's not worth adding a specific method + // just for this, so just check if MakeWholeLineCoords() fixes the + // column or the row as -1 to determine the direction we're moving in. + const bool byRow = diroper.MakeWholeLineCoords(coords).GetCol() == -1; + + if ( byRow ) + coords.SetRow(anchor.GetRow()); + else + coords.SetCol(anchor.GetCol()); + + if ( !AdvanceByBlock(coords, diroper) ) + return false; + + // Second, now that we've found the destination, we need to copy the + // other component, i.e. the one we didn't modify, from the anchor to + // do as if we started from the anchor originally. + // + // Again, to understand why this is necessary, consider the same + // example as above: if we didn't do this, we would lose the column of + // the original anchor completely and end up with a single column + // selection block even if we had started with the full row selection. + if ( byRow ) + coords.SetCol(anchor.GetCol()); + else + coords.SetRow(anchor.GetRow()); + + if ( m_selection->ExtendCurrentBlock(m_currentCellCoords, + coords, + kbdState) ) + { + // We want to show a line (a row or a column), not the end of + // the selection block. And do it only if the selection block + // was actually changed. + MakeCellVisible(diroper.MakeWholeLineCoords(coords)); + } } else { + if ( !AdvanceByBlock(coords, diroper) ) + return false; + ClearSelection(); GoToCell(coords); } @@ -7878,7 +8228,7 @@ wxGrid::DoMoveCursorByBlock(bool expandSelection, bool wxGrid::MoveCursorUpBlock(bool expandSelection) { return DoMoveCursorByBlock( - expandSelection, + DummyKeyboardState(expandSelection), wxGridBackwardOperations(this, wxGridRowOperations()) ); } @@ -7886,7 +8236,7 @@ bool wxGrid::MoveCursorUpBlock(bool expandSelection) bool wxGrid::MoveCursorDownBlock( bool expandSelection ) { return DoMoveCursorByBlock( - expandSelection, + DummyKeyboardState(expandSelection), wxGridForwardOperations(this, wxGridRowOperations()) ); } @@ -7894,7 +8244,7 @@ bool wxGrid::MoveCursorDownBlock( bool expandSelection ) bool wxGrid::MoveCursorLeftBlock( bool expandSelection ) { return DoMoveCursorByBlock( - expandSelection, + DummyKeyboardState(expandSelection), wxGridBackwardOperations(this, wxGridColumnOperations()) ); } @@ -7902,7 +8252,7 @@ bool wxGrid::MoveCursorLeftBlock( bool expandSelection ) bool wxGrid::MoveCursorRightBlock( bool expandSelection ) { return DoMoveCursorByBlock( - expandSelection, + DummyKeyboardState(expandSelection), wxGridForwardOperations(this, wxGridColumnOperations()) ); } @@ -9966,74 +10316,57 @@ void wxGrid::SelectBlock(int topRow, int leftCol, int bottomRow, int rightCol, void wxGrid::SelectAll() { - if ( m_numRows > 0 && m_numCols > 0 ) - { - if ( m_selection ) - m_selection->SelectBlock( 0, 0, m_numRows - 1, m_numCols - 1 ); - } + if ( m_selection ) + m_selection->SelectAll(); } // ---------------------------------------------------------------------------- // cell, row and col deselection // ---------------------------------------------------------------------------- -void wxGrid::DeselectLine(int line, const wxGridOperations& oper) -{ - if ( !m_selection ) - return; - - const wxGridSelectionModes mode = m_selection->GetSelectionMode(); - if ( mode == oper.GetSelectionMode() || - mode == wxGrid::wxGridSelectRowsOrColumns ) - { - const wxGridCellCoords c(oper.MakeCoords(line, 0)); - if ( m_selection->IsInSelection(c) ) - m_selection->ToggleCellSelection(c); - } - else if ( mode != oper.Dual().GetSelectionMode() ) - { - const int nOther = oper.Dual().GetNumberOfLines(this, NULL); - for ( int i = 0; i < nOther; i++ ) - { - const wxGridCellCoords c(oper.MakeCoords(line, i)); - if ( m_selection->IsInSelection(c) ) - m_selection->ToggleCellSelection(c); - } - } - //else: can only select orthogonal lines so no lines in this direction - // could have been selected anyhow -} - void wxGrid::DeselectRow(int row) { - DeselectLine(row, wxGridRowOperations()); + wxCHECK_RET( row >= 0 && row < m_numRows, wxT("invalid row index") ); + + if ( m_selection ) + m_selection->DeselectBlock(wxGridBlockCoords(row, 0, row, m_numRows - 1)); } void wxGrid::DeselectCol(int col) { - DeselectLine(col, wxGridColumnOperations()); + wxCHECK_RET( col >= 0 && col < m_numCols, wxT("invalid column index") ); + + if ( m_selection ) + m_selection->DeselectBlock(wxGridBlockCoords(0, col, m_numCols - 1, col)); } void wxGrid::DeselectCell( int row, int col ) { - if ( m_selection && m_selection->IsInSelection(row, col) ) - m_selection->ToggleCellSelection(row, col); + wxCHECK_RET( row >= 0 && row < m_numRows && + col >= 0 && col < m_numCols, + wxT("invalid cell coords") ); + + if ( m_selection ) + m_selection->DeselectBlock(wxGridBlockCoords(row, col, row, col)); } bool wxGrid::IsSelection() const { - return ( m_selection && (m_selection->IsSelection() || - ( m_selectedBlockTopLeft != wxGridNoCellCoords && - m_selectedBlockBottomRight != wxGridNoCellCoords) ) ); + return m_selection && m_selection->IsSelection(); } bool wxGrid::IsInSelection( int row, int col ) const { - return ( m_selection && (m_selection->IsInSelection( row, col ) || - ( row >= m_selectedBlockTopLeft.GetRow() && - col >= m_selectedBlockTopLeft.GetCol() && - row <= m_selectedBlockBottomRight.GetRow() && - col <= m_selectedBlockBottomRight.GetCol() )) ); + return m_selection && m_selection->IsInSelection(row, col); +} + +wxGridBlocks wxGrid::GetSelectedBlocks() const +{ + if ( !m_selection ) + return wxGridBlocks(); + + const wxVectorGridBlockCoords& blocks = m_selection->GetBlocks(); + return wxGridBlocks(blocks.begin(), blocks.end()); } wxGridCellCoordsArray wxGrid::GetSelectedCells() const @@ -10044,7 +10377,7 @@ wxGridCellCoordsArray wxGrid::GetSelectedCells() const return a; } - return m_selection->m_cellSelection; + return m_selection->GetCellSelection(); } wxGridCellCoordsArray wxGrid::GetSelectionBlockTopLeft() const @@ -10055,7 +10388,7 @@ wxGridCellCoordsArray wxGrid::GetSelectionBlockTopLeft() const return a; } - return m_selection->m_blockSelectionTopLeft; + return m_selection->GetBlockSelectionTopLeft(); } wxGridCellCoordsArray wxGrid::GetSelectionBlockBottomRight() const @@ -10066,7 +10399,7 @@ wxGridCellCoordsArray wxGrid::GetSelectionBlockBottomRight() const return a; } - return m_selection->m_blockSelectionBottomRight; + return m_selection->GetBlockSelectionBottomRight(); } wxArrayInt wxGrid::GetSelectedRows() const @@ -10077,7 +10410,7 @@ wxArrayInt wxGrid::GetSelectedRows() const return a; } - return m_selection->m_rowSelection; + return m_selection->GetRowSelection(); } wxArrayInt wxGrid::GetSelectedCols() const @@ -10088,18 +10421,11 @@ wxArrayInt wxGrid::GetSelectedCols() const return a; } - return m_selection->m_colSelection; + return m_selection->GetColSelection(); } void wxGrid::ClearSelection() { - RefreshBlock(m_selectedBlockTopLeft, m_selectedBlockBottomRight); - RefreshBlock(m_currentCellCoords, m_selectedBlockCorner); - - m_selectedBlockTopLeft = - m_selectedBlockBottomRight = - m_selectedBlockCorner = wxGridNoCellCoords; - if ( m_selection ) m_selection->ClearSelection(); } diff --git a/src/generic/gridsel.cpp b/src/generic/gridsel.cpp index 0bda0caa44..97aeee4f50 100644 --- a/src/generic/gridsel.cpp +++ b/src/generic/gridsel.cpp @@ -26,16 +26,22 @@ #if wxUSE_GRID #include "wx/generic/gridsel.h" +#include "wx/dynarray.h" -// Some explanation for the members of the class: -// m_cellSelection stores individual selected cells -// -- this is only used if m_selectionMode == wxGridSelectCells -// m_blockSelectionTopLeft and m_blockSelectionBottomRight -// store the upper left and lower right corner of selected Blocks -// m_rowSelection and m_colSelection store individual selected -// rows and columns; maybe those are superfluous and should be -// treated as blocks? +namespace +{ + +// The helper function to compare wxIntSortedArray elements. +int CompareInts(int n1, int n2) +{ + return n1 - n2; +} + +} + +WX_DEFINE_SORTED_ARRAY(int, wxIntSortedArray); + wxGridSelection::wxGridSelection( wxGrid * grid, wxGrid::wxGridSelectionModes sel ) @@ -46,66 +52,25 @@ wxGridSelection::wxGridSelection( wxGrid * grid, bool wxGridSelection::IsSelection() { - return ( m_cellSelection.GetCount() || m_blockSelectionTopLeft.GetCount() || - m_rowSelection.GetCount() || m_colSelection.GetCount() ); + return !m_selection.empty(); } -bool wxGridSelection::IsInSelection( int row, int col ) +bool wxGridSelection::IsInSelection( int row, int col ) const { - size_t count; - - // First check whether the given cell is individually selected - // (if m_selectionMode is wxGridSelectCells). - if ( m_selectionMode == wxGrid::wxGridSelectCells ) - { - count = m_cellSelection.GetCount(); - for ( size_t n = 0; n < count; n++ ) - { - wxGridCellCoords& coords = m_cellSelection[n]; - if ( row == coords.GetRow() && col == coords.GetCol() ) - return true; - } - } - - // Now check whether the given cell is - // contained in one of the selected blocks. - count = m_blockSelectionTopLeft.GetCount(); + // Check whether the given cell is contained in one of the selected blocks. + // + // Note that this algorithm is O(N) in number of selected blocks, not in + // number of cells in the grid, so it should be reasonably efficient even + // for very large grids, as the user shouldn't be able to select too many + // blocks. If we still run into problems with this, we should find a more + // efficient way of storing the selection, e.g. using k-d trees. + const size_t count = m_selection.size(); for ( size_t n = 0; n < count; n++ ) { - wxGridCellCoords& coords1 = m_blockSelectionTopLeft[n]; - wxGridCellCoords& coords2 = m_blockSelectionBottomRight[n]; - if ( BlockContainsCell(coords1.GetRow(), coords1.GetCol(), - coords2.GetRow(), coords2.GetCol(), - row, col ) ) + if ( m_selection[n].ContainsCell(wxGridCellCoords(row, col)) ) return true; } - // Now check whether the given cell is - // contained in one of the selected rows - // (unless we are in column selection mode). - if ( m_selectionMode != wxGrid::wxGridSelectColumns ) - { - count = m_rowSelection.GetCount(); - for ( size_t n = 0; n < count; n++ ) - { - if ( row == m_rowSelection[n] ) - return true; - } - } - - // Now check whether the given cell is - // contained in one of the selected columns - // (unless we are in row selection mode). - if ( m_selectionMode != wxGrid::wxGridSelectRows ) - { - count = m_colSelection.GetCount(); - for ( size_t n = 0; n < count; n++ ) - { - if ( col == m_colSelection[n] ) - return true; - } - } - return false; } @@ -127,52 +92,48 @@ void wxGridSelection::SetSelectionMode( wxGrid::wxGridSelectionModes selmode ) } else { - // if changing from cell selection to something else, - // promote selected cells/blocks to whole rows/columns. - size_t n; - while ( ( n = m_cellSelection.GetCount() ) > 0 ) + // Preserve only fully selected rows/columns when switching from cell + // selection mode and discard the selected blocks that are invalid in + // the new selection mode. + const int lastCol = m_grid->GetNumberCols() - 1; + const int lastRow = m_grid->GetNumberRows() - 1; + for ( size_t n = m_selection.size(); n > 0; ) { n--; - wxGridCellCoords& coords = m_cellSelection[n]; - int row = coords.GetRow(); - int col = coords.GetCol(); - m_cellSelection.RemoveAt(n); - if (selmode == wxGrid::wxGridSelectRows) - SelectRow( row ); - else // selmode == wxGridSelectColumns) - SelectCol( col ); - } + const wxGridBlockCoords& block = m_selection[n]; + const int topRow = block.GetTopRow(); + const int leftCol = block.GetLeftCol(); + const int bottomRow = block.GetBottomRow(); + const int rightCol = block.GetRightCol(); - // Note that m_blockSelectionTopLeft's size may be changing! - for ( n = m_blockSelectionTopLeft.GetCount(); n > 0; ) - { - n--; - wxGridCellCoords& coords = m_blockSelectionTopLeft[n]; - int topRow = coords.GetRow(); - int leftCol = coords.GetCol(); - wxGridCellCoords& coords2 = m_blockSelectionBottomRight[n]; - int bottomRow = coords2.GetRow(); - int rightCol = coords2.GetCol(); - - if (selmode == wxGrid::wxGridSelectRows) + bool valid = false; + switch ( selmode ) { - if (leftCol != 0 || rightCol != m_grid->GetNumberCols() - 1 ) - { - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); - SelectBlockNoEvent( topRow, 0, - bottomRow, m_grid->GetNumberCols() - 1); - } + case wxGrid::wxGridSelectCells: + wxFAIL_MSG("unreachable"); + break; + + case wxGrid::wxGridSelectRows: + valid = leftCol == 0 && rightCol == lastCol; + break; + + case wxGrid::wxGridSelectColumns: + valid = topRow == 0 && bottomRow == lastRow; + break; + + case wxGrid::wxGridSelectRowsOrColumns: + valid = (leftCol == 0 && rightCol == lastCol) || + (topRow == 0 && bottomRow == lastRow); + break; } - else // selmode == wxGridSelectColumns) + + if ( !valid ) { - if (topRow != 0 || bottomRow != m_grid->GetNumberRows() - 1 ) + if ( !m_grid->GetBatchCount() ) { - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); - SelectBlockNoEvent(0, leftCol, - m_grid->GetNumberRows() - 1, rightCol); + m_grid->RefreshBlock(block.GetTopLeft(), block.GetBottomRight()); } + m_selection.erase(m_selection.begin() + n); } } @@ -185,185 +146,17 @@ void wxGridSelection::SelectRow(int row, const wxKeyboardState& kbd) if ( m_selectionMode == wxGrid::wxGridSelectColumns ) return; - size_t count, n; - - // Remove single cells contained in newly selected block. - if ( m_selectionMode == wxGrid::wxGridSelectCells ) - { - count = m_cellSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - wxGridCellCoords& coords = m_cellSelection[n]; - if ( BlockContainsCell( row, 0, row, m_grid->GetNumberCols() - 1, - coords.GetRow(), coords.GetCol() ) ) - { - m_cellSelection.RemoveAt(n); - n--; - count--; - } - } - } - - // Simplify list of selected blocks (if possible) - count = m_blockSelectionTopLeft.GetCount(); - bool done = false; - - for ( n = 0; n < count; n++ ) - { - wxGridCellCoords& coords1 = m_blockSelectionTopLeft[n]; - wxGridCellCoords& coords2 = m_blockSelectionBottomRight[n]; - - // Remove block if it is a subset of the row - if ( coords1.GetRow() == row && row == coords2.GetRow() ) - { - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); - n--; - count--; - } - else if ( coords1.GetCol() == 0 && - coords2.GetCol() == m_grid->GetNumberCols() - 1 ) - { - // silently return, if row is contained in block - if ( coords1.GetRow() <= row && row <= coords2.GetRow() ) - return; - // expand block, if it touched row - else if ( coords1.GetRow() == row + 1) - { - coords1.SetRow(row); - done = true; - } - else if ( coords2.GetRow() == row - 1) - { - coords2.SetRow(row); - done = true; - } - } - } - - // Unless we successfully handled the row, - // check whether row is already selected. - if ( !done ) - { - count = m_rowSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - if ( row == m_rowSelection[n] ) - return; - } - - // Add row to selection - m_rowSelection.Add(row); - } - - // Update View: - if ( !m_grid->GetBatchCount() && m_grid->GetNumberCols() != 0 ) - { - m_grid->RefreshBlock(row, 0, row, m_grid->GetNumberCols() - 1); - } - - // Send Event - wxGridRangeSelectEvent gridEvt( m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - wxGridCellCoords( row, 0 ), - wxGridCellCoords( row, m_grid->GetNumberCols() - 1 ), - true, - kbd); - - m_grid->GetEventHandler()->ProcessEvent( gridEvt ); + Select(wxGridBlockCoords(row, 0, row, m_grid->GetNumberCols() - 1), + kbd, true); } void wxGridSelection::SelectCol(int col, const wxKeyboardState& kbd) { if ( m_selectionMode == wxGrid::wxGridSelectRows ) return; - size_t count, n; - // Remove single cells contained in newly selected block. - if ( m_selectionMode == wxGrid::wxGridSelectCells ) - { - count = m_cellSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - wxGridCellCoords& coords = m_cellSelection[n]; - if ( BlockContainsCell( 0, col, m_grid->GetNumberRows() - 1, col, - coords.GetRow(), coords.GetCol() ) ) - { - m_cellSelection.RemoveAt(n); - n--; - count--; - } - } - } - - // Simplify list of selected blocks (if possible) - count = m_blockSelectionTopLeft.GetCount(); - bool done = false; - for ( n = 0; n < count; n++ ) - { - wxGridCellCoords& coords1 = m_blockSelectionTopLeft[n]; - wxGridCellCoords& coords2 = m_blockSelectionBottomRight[n]; - - // Remove block if it is a subset of the column - if ( coords1.GetCol() == col && col == coords2.GetCol() ) - { - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); - n--; - count--; - } - else if ( coords1.GetRow() == 0 && - coords2.GetRow() == m_grid->GetNumberRows() - 1 ) - { - // silently return, if row is contained in block - if ( coords1.GetCol() <= col && col <= coords2.GetCol() ) - return; - // expand block, if it touched col - else if ( coords1.GetCol() == col + 1) - { - coords1.SetCol(col); - done = true; - } - else if ( coords2.GetCol() == col - 1) - { - coords2.SetCol(col); - done = true; - } - } - } - - // Unless we successfully handled the column, - // Check whether col is already selected. - if ( !done ) - { - count = m_colSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - if ( col == m_colSelection[n] ) - return; - } - - // Add col to selection - m_colSelection.Add(col); - } - - // Update View: - if ( !m_grid->GetBatchCount() && m_grid->GetNumberRows() != 0 ) - { - m_grid->RefreshBlock(0, col, m_grid->GetNumberRows() - 1, col); - } - - // Send Event - wxGridRangeSelectEvent gridEvt( m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - wxGridCellCoords( 0, col ), - wxGridCellCoords( m_grid->GetNumberRows() - 1, col ), - true, - kbd ); - - m_grid->GetEventHandler()->ProcessEvent( gridEvt ); + Select(wxGridBlockCoords(0, col, m_grid->GetNumberRows() - 1, col), + kbd, true); } void wxGridSelection::SelectBlock( int topRow, int leftCol, @@ -372,506 +165,192 @@ void wxGridSelection::SelectBlock( int topRow, int leftCol, bool sendEvent ) { // Fix the coordinates of the block if needed. + int allowed = -1; switch ( m_selectionMode ) { - default: - wxFAIL_MSG( "unknown selection mode" ); - wxFALLTHROUGH; - case wxGrid::wxGridSelectCells: - // nothing to do -- in this mode arbitrary blocks can be selected + // In this mode arbitrary blocks can be selected. + allowed = 1; break; case wxGrid::wxGridSelectRows: leftCol = 0; rightCol = m_grid->GetNumberCols() - 1; + allowed = 1; break; case wxGrid::wxGridSelectColumns: topRow = 0; bottomRow = m_grid->GetNumberRows() - 1; + allowed = 1; break; case wxGrid::wxGridSelectRowsOrColumns: - // block selection doesn't make sense for this mode, we could only - // select the entire grid but this wouldn't be useful - return; + // Arbitrary block selection doesn't make sense for this mode, as + // we could only select the entire grid, which wouldn't be useful, + // but we do allow selecting blocks that are already composed of + // only rows or only columns. + if ( topRow == 0 && bottomRow == m_grid->GetNumberRows() - 1 ) + allowed = 1; + else if ( leftCol == 0 && rightCol == m_grid->GetNumberCols() - 1 ) + allowed = 1; + else + allowed = 0; + break; } - if ( topRow > bottomRow ) - { - int temp = topRow; - topRow = bottomRow; - bottomRow = temp; - } - - if ( leftCol > rightCol ) - { - int temp = leftCol; - leftCol = rightCol; - rightCol = temp; - } - - // Handle single cell selection in SelectCell. - // (MB: added check for selection mode here to prevent - // crashes if, for example, we are select rows and the - // grid only has 1 col) - if ( m_selectionMode == wxGrid::wxGridSelectCells && - topRow == bottomRow && leftCol == rightCol ) - { - SelectCell( topRow, leftCol, kbd, sendEvent ); - } - - size_t count, n; - - if ( m_selectionMode == wxGrid::wxGridSelectRows ) - { - // find out which rows are already selected: - wxArrayInt alreadyselected; - alreadyselected.Add(0,bottomRow-topRow+1); - for( n = 0; n < m_rowSelection.GetCount(); n++) - { - int row = m_rowSelection[n]; - if( (row >= topRow) && (row <= bottomRow) ) - { - alreadyselected[ row - topRow ]=1; - } - } - - // add the newly selected rows: - for ( int row = topRow; row <= bottomRow; row++ ) - { - if ( alreadyselected[ row - topRow ] == 0 ) - { - m_rowSelection.Add( row ); - } - } - } - else if ( m_selectionMode == wxGrid::wxGridSelectColumns ) - { - // find out which columns are already selected: - wxArrayInt alreadyselected; - alreadyselected.Add(0,rightCol-leftCol+1); - for( n = 0; n < m_colSelection.GetCount(); n++) - { - int col = m_colSelection[n]; - if( (col >= leftCol) && (col <= rightCol) ) - { - alreadyselected[ col - leftCol ]=1; - } - } - - // add the newly selected columns: - for ( int col = leftCol; col <= rightCol; col++ ) - { - if ( alreadyselected[ col - leftCol ] == 0 ) - { - m_colSelection.Add( col ); - } - } - } - else - { - // Remove single cells contained in newly selected block. - if ( m_selectionMode == wxGrid::wxGridSelectCells ) - { - count = m_cellSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - wxGridCellCoords& coords = m_cellSelection[n]; - if ( BlockContainsCell( topRow, leftCol, bottomRow, rightCol, - coords.GetRow(), coords.GetCol() ) ) - { - m_cellSelection.RemoveAt(n); - n--; - count--; - } - } - } - - // If a block containing the selection is already selected, return, - // if a block contained in the selection is found, remove it. - - count = m_blockSelectionTopLeft.GetCount(); - for ( n = 0; n < count; n++ ) - { - wxGridCellCoords& coords1 = m_blockSelectionTopLeft[n]; - wxGridCellCoords& coords2 = m_blockSelectionBottomRight[n]; - - switch ( BlockContain( coords1.GetRow(), coords1.GetCol(), - coords2.GetRow(), coords2.GetCol(), - topRow, leftCol, bottomRow, rightCol ) ) - { - case 1: - return; - - case -1: - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); - n--; - count--; - break; - - default: - break; - } - } - - // If a row containing the selection is already selected, return, - // if a row contained in newly selected block is found, remove it. - count = m_rowSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - switch ( BlockContain( m_rowSelection[n], 0, - m_rowSelection[n], m_grid->GetNumberCols() - 1, - topRow, leftCol, bottomRow, rightCol ) ) - { - case 1: - return; - - case -1: - m_rowSelection.RemoveAt(n); - n--; - count--; - break; - - default: - break; - } - } - - // Same for columns. - count = m_colSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - switch ( BlockContain( 0, m_colSelection[n], - m_grid->GetNumberRows() - 1, m_colSelection[n], - topRow, leftCol, bottomRow, rightCol ) ) - { - case 1: - return; - - case -1: - m_colSelection.RemoveAt(n); - n--; - count--; - break; - - default: - break; - } - } - - m_blockSelectionTopLeft.Add( wxGridCellCoords( topRow, leftCol ) ); - m_blockSelectionBottomRight.Add( wxGridCellCoords( bottomRow, rightCol ) ); - } - // Update View: - if ( !m_grid->GetBatchCount() ) - { - m_grid->RefreshBlock(topRow, leftCol, bottomRow, rightCol); - } - - // Send Event, if not disabled. - if ( sendEvent ) - { - wxGridRangeSelectEvent gridEvt( m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - wxGridCellCoords( topRow, leftCol ), - wxGridCellCoords( bottomRow, rightCol ), - true, - kbd); - m_grid->GetEventHandler()->ProcessEvent( gridEvt ); - } -} - -void wxGridSelection::SelectCell( int row, int col, - const wxKeyboardState& kbd, - bool sendEvent ) -{ - if ( IsInSelection ( row, col ) ) + wxASSERT_MSG(allowed != -1, "unknown selection mode?"); + if ( !allowed ) return; - wxGridCellCoords selectedTopLeft, selectedBottomRight; - if ( m_selectionMode == wxGrid::wxGridSelectRows ) - { - m_rowSelection.Add( row ); - selectedTopLeft = wxGridCellCoords( row, 0 ); - selectedBottomRight = wxGridCellCoords( row, m_grid->GetNumberCols() - 1 ); - } - else if ( m_selectionMode == wxGrid::wxGridSelectColumns ) - { - m_colSelection.Add( col ); - selectedTopLeft = wxGridCellCoords( 0, col ); - selectedBottomRight = wxGridCellCoords( m_grid->GetNumberRows() - 1, col ); - } - else - { - m_cellSelection.Add( wxGridCellCoords( row, col ) ); - selectedTopLeft = wxGridCellCoords( row, col ); - selectedBottomRight = wxGridCellCoords( row, col ); - } + Select(wxGridBlockCoords(topRow, leftCol, bottomRow, rightCol).Canonicalize(), + kbd, sendEvent); +} - // Update View: - if ( !m_grid->GetBatchCount() ) - { - m_grid->RefreshBlock(selectedTopLeft, selectedBottomRight); - } +void +wxGridSelection::SelectAll() +{ + // There is no need to refresh anything, as Select() will do it anyhow, and + // no need to generate any events, so do not call ClearSelection() here. + m_selection.clear(); - // Send event - if (sendEvent) + const int numRows = m_grid->GetNumberRows(); + const int numCols = m_grid->GetNumberCols(); + + if ( numRows && numCols ) { - wxGridRangeSelectEvent gridEvt( m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - selectedTopLeft, - selectedBottomRight, - true, - kbd); - m_grid->GetEventHandler()->ProcessEvent( gridEvt ); + Select(wxGridBlockCoords(0, 0, numRows - 1, numCols - 1), + wxKeyboardState(), true /* send event */); } } void -wxGridSelection::ToggleCellSelection(int row, int col, - const wxKeyboardState& kbd) +wxGridSelection::DeselectBlock(const wxGridBlockCoords& block, + const wxKeyboardState& kbd, + bool sendEvent) { - // if the cell is not selected, select it - if ( !IsInSelection ( row, col ) ) - { - SelectCell(row, col, kbd); + const wxGridBlockCoords canonicalizedBlock = block.Canonicalize(); - return; - } - - // otherwise deselect it. This can be simple or more or - // less difficult, depending on how the cell is selected. size_t count, n; - // The simplest case: The cell is contained in m_cellSelection - // Then it can't be contained in rows/cols/block (since those - // would remove the cell from m_cellSelection on creation), so - // we just have to remove it from m_cellSelection. - - if ( m_selectionMode == wxGrid::wxGridSelectCells ) - { - count = m_cellSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - const wxGridCellCoords& sel = m_cellSelection[n]; - if ( row == sel.GetRow() && col == sel.GetCol() ) - { - wxGridCellCoords coords = m_cellSelection[n]; - m_cellSelection.RemoveAt(n); - if ( !m_grid->GetBatchCount() ) - { - m_grid->RefreshBlock(coords, coords); - } - - // Send event - wxGridRangeSelectEvent gridEvt( m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - wxGridCellCoords( row, col ), - wxGridCellCoords( row, col ), - false, - kbd ); - m_grid->GetEventHandler()->ProcessEvent( gridEvt ); - - return; - } - } - } - - // The most difficult case: The cell is member of one or even several - // blocks. Split each such block in up to 4 new parts, that don't - // contain the cell to be selected, like this: + // If the selected block intersects with the deselection block, split it + // in up to 4 new parts, that don't contain the block to be selected, like + // this (for rows): // |---------------------------| // | | // | part 1 | // | | // |---------------------------| - // | part 3 |x| part 4 | + // | part 3 | x | part 4 | // |---------------------------| // | | // | part 2 | // | | // |---------------------------| - // (The x marks the newly deselected cell). - // Note: in row selection mode, we only need part1 and part2; - // in column selection mode, we only need part 3 and part4, - // which are expanded to whole columns automatically! + // And for columns: + // |---------------------------| + // | | | | + // | | part 3 | | + // | | | | + // | |---------| | + // | part 1 | x | part 2 | + // | |---------| | + // | | | | + // | | part 4 | | + // | | | | + // |---------------------------| + // (The x marks the newly deselected block). + // Note: in row/column selection mode, we only need part1 and part2 - count = m_blockSelectionTopLeft.GetCount(); + // Blocks to refresh. + wxVectorGridBlockCoords refreshBlocks; + // Add the deselected block. + refreshBlocks.push_back(canonicalizedBlock); + + count = m_selection.size(); for ( n = 0; n < count; n++ ) { - wxGridCellCoords& coords1 = m_blockSelectionTopLeft[n]; - wxGridCellCoords& coords2 = m_blockSelectionBottomRight[n]; - int topRow = coords1.GetRow(); - int leftCol = coords1.GetCol(); - int bottomRow = coords2.GetRow(); - int rightCol = coords2.GetCol(); + const wxGridBlockCoords& selBlock = m_selection[n]; - if ( BlockContainsCell( topRow, leftCol, bottomRow, rightCol, row, col ) ) + // Whether blocks intersect. + if ( !m_selection[n].Intersects(canonicalizedBlock) ) + continue; + + int splitOrientation = -1; + switch ( m_selectionMode ) { - // remove the block - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); - n--; - count--; + case wxGrid::wxGridSelectRows: + splitOrientation = wxHORIZONTAL; + break; - // add up to 4 smaller blocks and set update region - if ( m_selectionMode != wxGrid::wxGridSelectColumns ) - { - if ( topRow < row ) - SelectBlockNoEvent(topRow, leftCol, row - 1, rightCol); - if ( bottomRow > row ) - SelectBlockNoEvent(row + 1, leftCol, bottomRow, rightCol); - } + case wxGrid::wxGridSelectColumns: + splitOrientation = wxVERTICAL; + break; - if ( m_selectionMode != wxGrid::wxGridSelectRows ) + case wxGrid::wxGridSelectCells: + case wxGrid::wxGridSelectRowsOrColumns: + if ( selBlock.GetLeftCol() == 0 && + selBlock.GetRightCol() == m_grid->GetNumberCols() - 1 ) + splitOrientation = wxHORIZONTAL; + else + splitOrientation = wxVERTICAL; + break; + } + + wxASSERT_MSG( splitOrientation != -1, "unknown selection mode" ); + + const wxGridBlockDiffResult result = + selBlock.Difference(canonicalizedBlock, splitOrientation); + + // remove the block (note that selBlock, being a reference, is + // invalidated here and can't be used any more below) + m_selection.erase(m_selection.begin() + n); + n--; + count--; + + for ( int i = 0; i < 2; ++i ) + { + const wxGridBlockCoords& part = result.m_parts[i]; + if ( part != wxGridNoBlockCoords ) + SelectBlockNoEvent(part); + } + + for ( int i = 2; i < 4; ++i ) + { + const wxGridBlockCoords& part = result.m_parts[i]; + if ( part != wxGridNoBlockCoords ) { - if ( leftCol < col ) - SelectBlockNoEvent(row, leftCol, row, col - 1); - if ( rightCol > col ) - SelectBlockNoEvent(row, col + 1, row, rightCol); + // Add part[2] and part[3] only in the cells selection mode. + if ( m_selectionMode == wxGrid::wxGridSelectCells ) + SelectBlockNoEvent(part); + else + MergeOrAddBlock(refreshBlocks, part); } } } - bool rowSelectionWasChanged = false; - // remove a cell from a row, adding up to two new blocks - if ( m_selectionMode != wxGrid::wxGridSelectColumns ) + // Refresh the screen and send events. + count = refreshBlocks.size(); + for ( n = 0; n < count; n++ ) { - count = m_rowSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - if ( m_rowSelection[n] == row ) - { - m_rowSelection.RemoveAt(n); - n--; - count--; + const wxGridBlockCoords& refBlock = refreshBlocks[n]; - rowSelectionWasChanged = true; - - if (m_selectionMode == wxGrid::wxGridSelectCells) - { - if ( col > 0 ) - SelectBlockNoEvent(row, 0, row, col - 1); - if ( col < m_grid->GetNumberCols() - 1 ) - SelectBlockNoEvent( row, col + 1, - row, m_grid->GetNumberCols() - 1); - } - } - } - } - - bool colSelectionWasChanged = false; - // remove a cell from a column, adding up to two new blocks - if ( m_selectionMode != wxGrid::wxGridSelectRows ) - { - count = m_colSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - if ( m_colSelection[n] == col ) - { - m_colSelection.RemoveAt(n); - n--; - count--; - - colSelectionWasChanged = true; - - if (m_selectionMode == wxGrid::wxGridSelectCells) - { - if ( row > 0 ) - SelectBlockNoEvent(0, col, row - 1, col); - if ( row < m_grid->GetNumberRows() - 1 ) - SelectBlockNoEvent(row + 1, col, - m_grid->GetNumberRows() - 1, col); - } - } - } - } - - // Refresh the screen and send the event; according to m_selectionMode, - // we need to either update only the cell, or the whole row/column. - wxRect r; - if ( m_selectionMode == wxGrid::wxGridSelectCells ) - { if ( !m_grid->GetBatchCount() ) { - m_grid->RefreshBlock(row, col, row, col); + m_grid->RefreshBlock(refBlock.GetTopRow(), refBlock.GetLeftCol(), + refBlock.GetBottomRow(), refBlock.GetRightCol()); } - wxGridRangeSelectEvent gridEvt( m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - wxGridCellCoords( row, col ), - wxGridCellCoords( row, col ), - false, - kbd ); - m_grid->GetEventHandler()->ProcessEvent( gridEvt ); - } - else // rows/columns selection mode - { - if ( m_selectionMode != wxGrid::wxGridSelectColumns && - rowSelectionWasChanged ) + if ( sendEvent ) { - int numCols = m_grid->GetNumberCols(); - for ( int colFrom = 0, colTo = 0; colTo <= numCols; ++colTo ) - { - if ( m_colSelection.Index(colTo) >= 0 || colTo == numCols ) - { - if ( colFrom < colTo ) - { - if ( !m_grid->GetBatchCount() ) - { - m_grid->RefreshBlock(row, colFrom, row, colTo - 1); - } - - wxGridRangeSelectEvent gridEvt( m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - wxGridCellCoords( row, colFrom ), - wxGridCellCoords( row, colTo - 1 ), - false, - kbd ); - m_grid->GetEventHandler()->ProcessEvent( gridEvt ); - } - - colFrom = colTo + 1; - } - } - } - - if ( m_selectionMode != wxGrid::wxGridSelectRows && - colSelectionWasChanged ) - { - int numRows = m_grid->GetNumberRows(); - for ( int rowFrom = 0, rowTo = 0; rowTo <= numRows; ++rowTo ) - { - if ( m_rowSelection.Index(rowTo) >= 0 || rowTo == numRows ) - { - if (rowFrom < rowTo) - { - if ( !m_grid->GetBatchCount() ) - { - m_grid->RefreshBlock(rowFrom, col, rowTo - 1, col); - } - - wxGridRangeSelectEvent gridEvt( m_grid->GetId(), - wxEVT_GRID_RANGE_SELECT, - m_grid, - wxGridCellCoords( rowFrom, col ), - wxGridCellCoords( rowTo - 1, col ), - false, - kbd ); - m_grid->GetEventHandler()->ProcessEvent( gridEvt ); - } - - rowFrom = rowTo + 1; - } - } + wxGridRangeSelectEvent gridEvt(m_grid->GetId(), + wxEVT_GRID_RANGE_SELECT, + m_grid, + refBlock.GetTopLeft(), + refBlock.GetBottomRight(), + false, + kbd); + m_grid->GetEventHandler()->ProcessEvent(gridEvt); } } } @@ -882,33 +361,14 @@ void wxGridSelection::ClearSelection() wxRect r; wxGridCellCoords coords1, coords2; - // deselect all individual cells and update the screen - if ( m_selectionMode == wxGrid::wxGridSelectCells ) - { - while ( ( n = m_cellSelection.GetCount() ) > 0) - { - n--; - coords1 = m_cellSelection[n]; - m_cellSelection.RemoveAt(n); - if ( !m_grid->GetBatchCount() ) - { - m_grid->RefreshBlock(coords1, coords1); - -#ifdef __WXMAC__ - m_grid->UpdateGridWindows(); -#endif - } - } - } - // deselect all blocks and update the screen - while ( ( n = m_blockSelectionTopLeft.GetCount() ) > 0) + while ( ( n = m_selection.size() ) > 0) { n--; - coords1 = m_blockSelectionTopLeft[n]; - coords2 = m_blockSelectionBottomRight[n]; - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); + const wxGridBlockCoords& block = m_selection[n]; + coords1 = block.GetTopLeft(); + coords2 = block.GetBottomRight(); + m_selection.erase(m_selection.begin() + n); if ( !m_grid->GetBatchCount() ) { m_grid->RefreshBlock(coords1, coords2); @@ -919,44 +379,6 @@ void wxGridSelection::ClearSelection() } } - // deselect all rows and update the screen - if ( m_selectionMode != wxGrid::wxGridSelectColumns ) - { - while ( ( n = m_rowSelection.GetCount() ) > 0) - { - n--; - int row = m_rowSelection[n]; - m_rowSelection.RemoveAt(n); - if ( !m_grid->GetBatchCount() ) - { - m_grid->RefreshBlock(row, 0, row, m_grid->GetNumberCols() - 1); - -#ifdef __WXMAC__ - m_grid->UpdateGridWindows(); -#endif - } - } - } - - // deselect all columns and update the screen - if ( m_selectionMode != wxGrid::wxGridSelectRows ) - { - while ( ( n = m_colSelection.GetCount() ) > 0) - { - n--; - int col = m_colSelection[n]; - m_colSelection.RemoveAt(n); - if ( !m_grid->GetBatchCount() ) - { - m_grid->RefreshBlock(0, col, m_grid->GetNumberRows() - 1, col); - -#ifdef __WXMAC__ - m_grid->UpdateGridWindows(); -#endif - } - } - } - // One deselection event, indicating deselection of _all_ cells. // (No finer grained events for each of the smaller regions // deselected above!) @@ -975,54 +397,23 @@ void wxGridSelection::ClearSelection() void wxGridSelection::UpdateRows( size_t pos, int numRows ) { - size_t count = m_cellSelection.GetCount(); + size_t count = m_selection.size(); size_t n; - for ( n = 0; n < count; n++ ) - { - wxGridCellCoords& coords = m_cellSelection[n]; - wxCoord row = coords.GetRow(); - if ((size_t)row >= pos) - { - if (numRows > 0) - { - // If rows inserted, increase row counter where necessary - coords.SetRow(row + numRows); - } - else if (numRows < 0) - { - // If rows deleted ... - if ((size_t)row >= pos - numRows) - { - // ...either decrement row counter (if row still exists)... - coords.SetRow(row + numRows); - } - else - { - // ...or remove the attribute - m_cellSelection.RemoveAt(n); - n--; - count--; - } - } - } - } - count = m_blockSelectionTopLeft.GetCount(); for ( n = 0; n < count; n++ ) { - wxGridCellCoords& coords1 = m_blockSelectionTopLeft[n]; - wxGridCellCoords& coords2 = m_blockSelectionBottomRight[n]; - wxCoord row1 = coords1.GetRow(); - wxCoord row2 = coords2.GetRow(); + wxGridBlockCoords& block = m_selection[n]; + wxCoord row1 = block.GetTopRow(); + wxCoord row2 = block.GetBottomRow(); if ((size_t)row2 >= pos) { if (numRows > 0) { // If rows inserted, increase row counter where necessary - coords2.SetRow( row2 + numRows ); + block.SetBottomRow( row2 + numRows ); if ((size_t)row1 >= pos) - coords1.SetRow( row1 + numRows ); + block.SetTopRow( row1 + numRows ); } else if (numRows < 0) { @@ -1030,9 +421,9 @@ void wxGridSelection::UpdateRows( size_t pos, int numRows ) if ((size_t)row2 >= pos - numRows) { // ...either decrement row counter (if row still exists)... - coords2.SetRow( row2 + numRows ); + block.SetBottomRow( row2 + numRows ); if ((size_t)row1 >= pos) - coords1.SetRow( wxMax(row1 + numRows, (int)pos) ); + block.SetTopRow( wxMax(row1 + numRows, (int)pos) ); } else @@ -1040,101 +431,38 @@ void wxGridSelection::UpdateRows( size_t pos, int numRows ) if ((size_t)row1 >= pos) { // ...or remove the attribute - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); + m_selection.erase(m_selection.begin() + n); n--; count--; } else - coords2.SetRow( pos ); + block.SetBottomRow( pos ); } } } } - - count = m_rowSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - int rowOrCol_ = m_rowSelection[n]; - - if ((size_t) rowOrCol_ >= pos) - { - if ( numRows > 0 ) - { - m_rowSelection[n] += numRows; - } - else if ( numRows < 0 ) - { - if ((size_t)rowOrCol_ >= (pos - numRows)) - m_rowSelection[n] += numRows; - else - { - m_rowSelection.RemoveAt( n ); - n--; - count--; - } - } - } - } - // No need to touch selected columns, unless we removed _all_ - // rows, in this case, we remove all columns from the selection. - - if ( !m_grid->GetNumberRows() ) - m_colSelection.Clear(); } void wxGridSelection::UpdateCols( size_t pos, int numCols ) { - size_t count = m_cellSelection.GetCount(); + size_t count = m_selection.size(); size_t n; for ( n = 0; n < count; n++ ) { - wxGridCellCoords& coords = m_cellSelection[n]; - wxCoord col = coords.GetCol(); - if ((size_t)col >= pos) - { - if (numCols > 0) - { - // If rows inserted, increase row counter where necessary - coords.SetCol(col + numCols); - } - else if (numCols < 0) - { - // If rows deleted ... - if ((size_t)col >= pos - numCols) - { - // ...either decrement row counter (if row still exists)... - coords.SetCol(col + numCols); - } - else - { - // ...or remove the attribute - m_cellSelection.RemoveAt(n); - n--; - count--; - } - } - } - } - - count = m_blockSelectionTopLeft.GetCount(); - for ( n = 0; n < count; n++ ) - { - wxGridCellCoords& coords1 = m_blockSelectionTopLeft[n]; - wxGridCellCoords& coords2 = m_blockSelectionBottomRight[n]; - wxCoord col1 = coords1.GetCol(); - wxCoord col2 = coords2.GetCol(); + wxGridBlockCoords& block = m_selection[n]; + wxCoord col1 = block.GetLeftCol(); + wxCoord col2 = block.GetRightCol(); if ((size_t)col2 >= pos) { if (numCols > 0) { // If rows inserted, increase row counter where necessary - coords2.SetCol(col2 + numCols); + block.SetRightCol(col2 + numCols); if ((size_t)col1 >= pos) - coords1.SetCol(col1 + numCols); + block.SetLeftCol(col1 + numCols); } else if (numCols < 0) { @@ -1142,9 +470,9 @@ void wxGridSelection::UpdateCols( size_t pos, int numCols ) if ((size_t)col2 >= pos - numCols) { // ...either decrement col counter (if col still exists)... - coords2.SetCol(col2 + numCols); + block.SetRightCol(col2 + numCols); if ( (size_t) col1 >= pos) - coords1.SetCol( wxMax(col1 + numCols, (int)pos) ); + block.SetLeftCol( wxMax(col1 + numCols, (int)pos) ); } else @@ -1152,63 +480,368 @@ void wxGridSelection::UpdateCols( size_t pos, int numCols ) if ((size_t)col1 >= pos) { // ...or remove the attribute - m_blockSelectionTopLeft.RemoveAt(n); - m_blockSelectionBottomRight.RemoveAt(n); + m_selection.erase(m_selection.begin() + n); n--; count--; } else - coords2.SetCol(pos); + block.SetRightCol(pos); } } } } - - count = m_colSelection.GetCount(); - for ( n = 0; n < count; n++ ) - { - int rowOrCol = m_colSelection[n]; - - if ((size_t)rowOrCol >= pos) - { - if ( numCols > 0 ) - m_colSelection[n] += numCols; - else if ( numCols < 0 ) - { - if ((size_t)rowOrCol >= (pos - numCols)) - m_colSelection[n] += numCols; - else - { - m_colSelection.RemoveAt( n ); - n--; - count--; - } - } - } - } - - // No need to touch selected rows, unless we removed _all_ - // columns, in this case, we remove all rows from the selection. - if ( !m_grid->GetNumberCols() ) - m_rowSelection.Clear(); } -int wxGridSelection::BlockContain( int topRow1, int leftCol1, - int bottomRow1, int rightCol1, - int topRow2, int leftCol2, - int bottomRow2, int rightCol2 ) -// returns 1, if Block1 contains Block2, -// -1, if Block2 contains Block1, -// 0, otherwise +bool wxGridSelection::ExtendCurrentBlock(const wxGridCellCoords& blockStart, + const wxGridCellCoords& blockEnd, + const wxKeyboardState& kbd) { - if ( topRow1 <= topRow2 && bottomRow2 <= bottomRow1 && - leftCol1 <= leftCol2 && rightCol2 <= rightCol1 ) - return 1; - else if ( topRow2 <= topRow1 && bottomRow1 <= bottomRow2 && - leftCol2 <= leftCol1 && rightCol1 <= rightCol2 ) - return -1; + wxASSERT( blockStart.GetRow() != -1 && blockStart.GetCol() != -1 && + blockEnd.GetRow() != -1 && blockEnd.GetCol() != -1 ); - return 0; + // If selection doesn't contain the current cell (which also covers the + // special case of nothing being selected yet), we have to create a new + // block containing it because it doesn't make sense to extend any existing + // block to non-selected current cell. + if ( !IsInSelection(m_grid->GetGridCursorCoords()) ) + { + SelectBlock(blockStart, blockEnd); + return true; + } + + const wxGridBlockCoords& block = *m_selection.rbegin(); + wxGridBlockCoords newBlock = block; + + // Determine if we should try to extend the current block rows and/or + // columns at all. + bool canChangeRow = false, + canChangeCol = false; + + switch ( m_selectionMode ) + { + case wxGrid::wxGridSelectCells: + // Nothing prevents us from doing it in this case. + canChangeRow = + canChangeCol = true; + break; + + case wxGrid::wxGridSelectColumns: + // Rows are always fixed, so prevent us from ever selecting only + // part of a column in this case by leaving canChangeRow false. + canChangeCol = true; + break; + + case wxGrid::wxGridSelectRows: + // Same as above but mirrored. + canChangeRow = true; + break; + + case wxGrid::wxGridSelectRowsOrColumns: + // In this case we may only change component which is not fixed. + if ( block.GetTopRow() != 0 || + block.GetBottomRow() != m_grid->GetNumberRows() - 1 ) + { + // This is a row block, so we can extend it in row direction. + canChangeRow = true; + } + else if ( block.GetLeftCol() != 0 || + block.GetRightCol() != m_grid->GetNumberCols() - 1 ) + { + canChangeCol = true; + } + else // The entire grid is selected. + { + // In this case we can shrink it in either direction. + canChangeRow = + canChangeCol = true; + } + break; + } + + if ( canChangeRow ) + { + // If the new block starts at the same top row as the current one, the + // end block coordinates must correspond to the new bottom row -- and + // vice versa, if the new block starts at the bottom, its other end + // must correspond to the top. + if ( blockStart.GetRow() == block.GetTopRow() ) + { + newBlock.SetBottomRow(blockEnd.GetRow()); + } + else if ( blockStart.GetRow() == block.GetBottomRow() ) + { + newBlock.SetTopRow(blockEnd.GetRow()); + } + else // current and new block don't have common row boundary + { + // This can happen when mixing entire column and cell selection, e.g. + // by Shift-clicking on the column header. In this case, the right + // thing to do is to just expand the current block to the new one + // boundaries, extending the selection to the entire column height when + // a column is selected. However notice that we should not shrink the + // current block here, in order to allow Shift-Left/Right (which don't + // know anything about the column selection and so just use single row + // blocks) to keep the full column selection. + int top = blockStart.GetRow(), + bottom = blockEnd.GetRow(); + if ( top > bottom ) + wxSwap(top, bottom); + + if ( top < newBlock.GetTopRow() ) + newBlock.SetTopRow(top); + if ( bottom > newBlock.GetBottomRow() ) + newBlock.SetBottomRow(bottom); + } + } + + // Same as above but mirrored for columns. + if ( canChangeCol ) + { + if ( blockStart.GetCol() == block.GetLeftCol() ) + { + newBlock.SetRightCol(blockEnd.GetCol()); + } + else if ( blockStart.GetCol() == block.GetRightCol() ) + { + newBlock.SetLeftCol(blockEnd.GetCol()); + } + else + { + int left = blockStart.GetCol(), + right = blockEnd.GetCol(); + if ( left > right ) + wxSwap(left, right); + + if ( left < newBlock.GetLeftCol() ) + newBlock.SetLeftCol(left); + if ( right > newBlock.GetRightCol() ) + newBlock.SetRightCol(right); + } + } + + newBlock = newBlock.Canonicalize(); + + if ( newBlock == block ) + return false; + + // Update View. + if ( !m_grid->GetBatchCount() ) + { + wxGridBlockDiffResult refreshBlocks = block.SymDifference(newBlock); + for ( int i = 0; i < 4; ++i ) + { + const wxGridBlockCoords& refreshBlock = refreshBlocks.m_parts[i]; + m_grid->RefreshBlock(refreshBlock.GetTopLeft(), + refreshBlock.GetBottomRight()); + } + } + + // Update the current block in place. + *m_selection.rbegin() = newBlock; + + // Send Event. + wxGridRangeSelectEvent gridEvt(m_grid->GetId(), + wxEVT_GRID_RANGE_SELECT, + m_grid, + newBlock.GetTopLeft(), + newBlock.GetBottomRight(), + true, + kbd); + m_grid->GetEventHandler()->ProcessEvent(gridEvt); + + return true; +} + +wxGridCellCoords wxGridSelection::GetExtensionAnchor() const +{ + wxGridCellCoords coords = m_grid->m_currentCellCoords; + + // If the current cell isn't selected (which also covers the special case + // of nothing being selected yet), we have to use it as anchor as we need + // to ensure that it will get selected. + if ( !IsInSelection(coords) ) + return coords; + + const wxGridBlockCoords& block = *m_selection.rbegin(); + if ( block.GetTopRow() == coords.GetRow() ) + coords.SetRow(block.GetBottomRow()); + else if ( block.GetBottomRow() == coords.GetRow() ) + coords.SetRow(block.GetTopRow()); + + if ( block.GetLeftCol() == coords.GetCol() ) + coords.SetCol(block.GetRightCol()); + else if ( block.GetRightCol() == coords.GetCol() ) + coords.SetCol(block.GetLeftCol()); + + return coords; +} + +wxGridCellCoordsArray wxGridSelection::GetCellSelection() const +{ + if ( m_selectionMode != wxGrid::wxGridSelectCells ) + return wxGridCellCoordsArray(); + + wxGridCellCoordsArray cells; + const size_t count = m_selection.size(); + cells.reserve(count); + for ( size_t n = 0; n < count; n++ ) + { + const wxGridBlockCoords& block = m_selection[n]; + if ( block.GetTopRow() == block.GetBottomRow() && + block.GetLeftCol() == block.GetRightCol() ) + { + cells.push_back(block.GetTopLeft()); + } + } + return cells; +} + +wxGridCellCoordsArray wxGridSelection::GetBlockSelectionTopLeft() const +{ + // return blocks only in wxGridSelectCells selection mode + if ( m_selectionMode != wxGrid::wxGridSelectCells ) + return wxGridCellCoordsArray(); + + wxGridCellCoordsArray coords; + const size_t count = m_selection.size(); + coords.reserve(count); + for ( size_t n = 0; n < count; n++ ) + { + coords.push_back(m_selection[n].GetTopLeft()); + } + return coords; +} + +wxGridCellCoordsArray wxGridSelection::GetBlockSelectionBottomRight() const +{ + if ( m_selectionMode != wxGrid::wxGridSelectCells ) + return wxGridCellCoordsArray(); + + wxGridCellCoordsArray coords; + const size_t count = m_selection.size(); + coords.reserve(count); + for ( size_t n = 0; n < count; n++ ) + { + coords.push_back(m_selection[n].GetBottomRight()); + } + return coords; +} + +// For compatibility with the existing code, try reconstructing the selected +// rows/columns from the selected blocks we store internally. Of course, this +// only works well in the corresponding selection mode in which the user can +// only select the entire lines in the first place, as otherwise it's difficult to +// efficiently determine that a line is selected because all of its cells +// were selected one by one. But this should work well enough in practice and +// is, anyhow, the best we can do. +wxArrayInt wxGridSelection::GetRowSelection() const +{ + if ( m_selectionMode == wxGrid::wxGridSelectColumns ) + return wxArrayInt(); + + wxIntSortedArray uniqueRows(&CompareInts); + const size_t count = m_selection.size(); + for ( size_t n = 0; n < count; ++n ) + { + const wxGridBlockCoords& block = m_selection[n]; + if ( block.GetLeftCol() == 0 && + block.GetRightCol() == m_grid->GetNumberCols() - 1 ) + { + for ( int r = block.GetTopRow(); r <= block.GetBottomRow(); ++r ) + { + uniqueRows.Add(r); + } + } + } + + wxArrayInt result; + result.reserve(uniqueRows.size()); + for( size_t i = 0; i < uniqueRows.size(); ++i ) + { + result.push_back(uniqueRows[i]); + } + return result; +} + +// See comments for GetRowSelection(). +wxArrayInt wxGridSelection::GetColSelection() const +{ + if ( m_selectionMode == wxGrid::wxGridSelectRows ) + return wxArrayInt(); + + wxIntSortedArray uniqueRows(&CompareInts); + const size_t count = m_selection.size(); + for ( size_t n = 0; n < count; ++n ) + { + const wxGridBlockCoords& block = m_selection[n]; + if ( block.GetTopRow() == 0 && + block.GetBottomRow() == m_grid->GetNumberRows() - 1 ) + { + for ( int c = block.GetLeftCol(); c <= block.GetRightCol(); ++c ) + { + uniqueRows.Add(c); + } + } + } + + wxArrayInt result; + result.reserve(uniqueRows.size()); + for( size_t i = 0; i < uniqueRows.size(); ++i ) + { + result.push_back(uniqueRows[i]); + } + return result; +} + +void +wxGridSelection::Select(const wxGridBlockCoords& block, + const wxKeyboardState& kbd, bool sendEvent) +{ + if (m_grid->GetNumberRows() == 0 || m_grid->GetNumberCols() == 0) + return; + + m_selection.push_back(block); + + // Update View: + if ( !m_grid->GetBatchCount() ) + { + m_grid->RefreshBlock(block.GetTopLeft(), block.GetBottomRight()); + } + + // Send Event, if not disabled. + if ( sendEvent ) + { + wxGridRangeSelectEvent gridEvt( m_grid->GetId(), + wxEVT_GRID_RANGE_SELECT, + m_grid, + block.GetTopLeft(), + block.GetBottomRight(), + true, + kbd); + m_grid->GetEventHandler()->ProcessEvent( gridEvt ); + } +} + +void wxGridSelection::MergeOrAddBlock(wxVectorGridBlockCoords& blocks, + const wxGridBlockCoords& newBlock) +{ + size_t count = blocks.size(); + for ( size_t n = 0; n < count; n++ ) + { + const wxGridBlockCoords& block = blocks[n]; + + if ( block.ContainsBlock(newBlock) ) + return; + + if ( newBlock.ContainsBlock(block) ) + { + blocks.erase(blocks.begin() + n); + n--; + count--; + } + } + + blocks.push_back(newBlock); } #endif diff --git a/tests/controls/gridtest.cpp b/tests/controls/gridtest.cpp index 23de3046a5..d61ba77d21 100644 --- a/tests/controls/gridtest.cpp +++ b/tests/controls/gridtest.cpp @@ -74,6 +74,17 @@ protected: void CheckFirstColAutoSize(int expected); + // Helper to check that the selection is equal to the specified block. + void CheckSelection(const wxGridBlockCoords& block) + { + const wxGridBlocks selected = m_grid->GetSelectedBlocks(); + wxGridBlocks::iterator it = selected.begin(); + + REQUIRE( it != selected.end() ); + CHECK( *it == block ); + CHECK( ++it == selected.end() ); + } + TestableGrid *m_grid; wxDECLARE_NO_COPY_CLASS(GridTestCase); @@ -490,6 +501,35 @@ TEST_CASE_METHOD(GridTestCase, "Grid::Cursor", "[grid]") CHECK(m_grid->GetGridCursorRow() == 0); } +TEST_CASE_METHOD(GridTestCase, "Grid::KeyboardSelection", "[grid][selection]") +{ + m_grid->SetCellValue(1, 1, "R2C2"); + m_grid->SetCellValue(2, 0, "R3C1"); + m_grid->SetCellValue(3, 0, "R4C1"); + m_grid->SetCellValue(4, 0, "R5C1"); + m_grid->SetCellValue(7, 0, "R8C1"); + + CHECK(m_grid->GetGridCursorCoords() == wxGridCellCoords(0, 0)); + + m_grid->MoveCursorRight(true); + CheckSelection(wxGridBlockCoords(0, 0, 0, 1)); + + m_grid->MoveCursorDownBlock(true); + CheckSelection(wxGridBlockCoords(0, 0, 2, 1)); + + m_grid->MoveCursorDownBlock(true); + CheckSelection(wxGridBlockCoords(0, 0, 4, 1)); + + m_grid->MoveCursorDownBlock(true); + CheckSelection(wxGridBlockCoords(0, 0, 7, 1)); + + m_grid->MoveCursorUpBlock(true); + CheckSelection(wxGridBlockCoords(0, 0, 4, 1)); + + m_grid->MoveCursorLeft(true); + CheckSelection(wxGridBlockCoords(0, 0, 4, 0)); +} + TEST_CASE_METHOD(GridTestCase, "Grid::Selection", "[grid]") { m_grid->SelectAll(); @@ -524,44 +564,81 @@ TEST_CASE_METHOD(GridTestCase, "Grid::Selection", "[grid]") CHECK(!m_grid->IsInSelection(3, 0)); } +TEST_CASE_METHOD(GridTestCase, "Grid::SelectionRange", "[grid]") +{ + const wxGridBlocks empty = m_grid->GetSelectedBlocks(); + CHECK( empty.begin() == empty.end() ); + + m_grid->SelectBlock(1, 0, 3, 1); + + wxGridBlocks sel = m_grid->GetSelectedBlocks(); + REQUIRE( sel.begin() != sel.end() ); + CHECK( *sel.begin() == wxGridBlockCoords(1, 0, 3, 1) ); + +#if __cplusplus >= 201103L || wxCHECK_VISUALC_VERSION(10) + m_grid->SelectBlock(4, 0, 7, 1, true); + int index = 0; + for ( const wxGridBlockCoords& block : m_grid->GetSelectedBlocks() ) + { + switch ( index ) + { + case 0: + CHECK(block == wxGridBlockCoords(1, 0, 3, 1)); + break; + case 1: + CHECK(block == wxGridBlockCoords(4, 0, 7, 1)); + break; + default: + FAIL("Unexpected iterations count"); + break; + } + ++index; + } +#endif +} + TEST_CASE_METHOD(GridTestCase, "Grid::SelectEmptyGrid", "[grid]") { - SECTION("Delete rows/columns") + for ( int i = 0; i < 2; ++i ) { - SECTION("No rows") + SECTION(i == 0 ? "No rows" : "No columns") { - m_grid->DeleteRows(0, 10); - REQUIRE( m_grid->GetNumberRows() == 0 ); - } - SECTION("No columns") - { - m_grid->DeleteCols(0, 2); - REQUIRE( m_grid->GetNumberCols() == 0 ); - } - } - - SECTION("Select") - { - SECTION("Move right") - { - m_grid->MoveCursorRight(true); - } - SECTION("Move down") - { - m_grid->MoveCursorDown(true); - } - SECTION("Select row") - { - m_grid->SelectRow(1); - } - SECTION("Select column") - { - m_grid->SelectCol(1); + + if ( i == 0 ) + { + m_grid->DeleteRows(0, 10); + REQUIRE( m_grid->GetNumberRows() == 0 ); + } + else + { + m_grid->DeleteCols(0, 2); + REQUIRE( m_grid->GetNumberCols() == 0 ); + } + + SECTION("Move right") + { + m_grid->MoveCursorRight(true); + } + SECTION("Move down") + { + m_grid->MoveCursorDown(true); + } + SECTION("Select row") + { + m_grid->SelectRow(1); + } + SECTION("Select column") + { + m_grid->SelectCol(1); + } } } + CHECK( m_grid->GetSelectedCells().Count() == 0 ); CHECK( m_grid->GetSelectionBlockTopLeft().Count() == 0 ); CHECK( m_grid->GetSelectionBlockBottomRight().Count() == 0 ); + CHECK( m_grid->GetSelectedRows().Count() == 0 ); + CHECK( m_grid->GetSelectedCols().Count() == 0 ); } TEST_CASE_METHOD(GridTestCase, "Grid::ScrollWhenSelect", "[grid]") @@ -580,12 +657,12 @@ TEST_CASE_METHOD(GridTestCase, "Grid::ScrollWhenSelect", "[grid]") CHECK( m_grid->IsVisible(0, 4) ); m_grid->ClearSelection(); - m_grid->SetGridCursor(1, 1); - for ( int i = 0; i < 5; ++i ) + m_grid->GoToCell(1, 1); + for ( int i = 0; i < 8; ++i ) { m_grid->MoveCursorDown(true); } - CHECK( m_grid->IsVisible(6, 1) ); + CHECK( m_grid->IsVisible(9, 1) ); } TEST_CASE_METHOD(GridTestCase, "Grid::MoveGridCursorUsingEndKey", "[grid]") @@ -825,9 +902,20 @@ TEST_CASE_METHOD(GridTestCase, "Grid::SelectionMode", "[grid]") //We already test this mode in Select CHECK(m_grid->GetSelectionMode() == wxGrid::wxGridSelectCells); + // Select an individual cell and an entire row. + m_grid->SelectBlock(3, 1, 3, 1); + m_grid->SelectRow(5, true /* add to selection */); + + // Test that after switching to row selection mode only the row remains + // selected. + m_grid->SetSelectionMode(wxGrid::wxGridSelectRows); + CHECK( m_grid->IsInSelection(5, 0) ); + CHECK( m_grid->IsInSelection(5, 1) ); + CHECK( !m_grid->IsInSelection(3, 1) ); + //Test row selection be selecting a single cell and checking the whole //row is selected - m_grid->SetSelectionMode(wxGrid::wxGridSelectRows); + m_grid->ClearSelection(); m_grid->SelectBlock(3, 1, 3, 1); wxArrayInt selectedRows = m_grid->GetSelectedRows(); @@ -1259,4 +1347,193 @@ TEST_CASE_METHOD(GridTestCase, "Grid::AutoSizeColumn", "[grid]") } } +// Test wxGridBlockCoords here because it'a a part of grid sources. + +std::ostream& operator<<(std::ostream& os, const wxGridBlockCoords& block) { + os << "wxGridBlockCoords(" << + block.GetTopRow() << ", " << block.GetLeftCol() << ", " << + block.GetBottomRow() << ", " << block.GetRightCol() << ")"; + return os; +} + +TEST_CASE("GridBlockCoords::Canonicalize", "[grid]") +{ + const wxGridBlockCoords block = + wxGridBlockCoords(4, 3, 2, 1).Canonicalize(); + + CHECK(block.GetTopRow() == 2); + CHECK(block.GetLeftCol() == 1); + CHECK(block.GetBottomRow() == 4); + CHECK(block.GetRightCol() == 3); +} + +TEST_CASE("GridBlockCoords::Intersects", "[grid]") +{ + // Inside. + CHECK(wxGridBlockCoords(1, 1, 3, 3).Intersects(wxGridBlockCoords(1, 2, 2, 3))); + + // Intersects. + CHECK(wxGridBlockCoords(1, 1, 3, 3).Intersects(wxGridBlockCoords(2, 2, 4, 4))); + + // Doesn't intersects. + CHECK(!wxGridBlockCoords(1, 1, 3, 3).Intersects(wxGridBlockCoords(4, 4, 6, 6))); +} + +TEST_CASE("GridBlockCoords::ContainsCell", "[grid]") +{ + // Inside. + CHECK(wxGridBlockCoords(1, 1, 3, 3).ContainsCell(wxGridCellCoords(2, 2))); + + // Outside. + CHECK(!wxGridBlockCoords(1, 1, 3, 3).ContainsCell(wxGridCellCoords(5, 5))); +} + +TEST_CASE("GridBlockCoords::ContainsBlock", "[grid]") +{ + wxGridBlockCoords block1(1, 1, 5, 5); + wxGridBlockCoords block2(1, 1, 3, 3); + wxGridBlockCoords block3(2, 2, 7, 7); + wxGridBlockCoords block4(10, 10, 12, 12); + + CHECK( block1.ContainsBlock(block2)); + CHECK(!block2.ContainsBlock(block1)); + CHECK(!block1.ContainsBlock(block3)); + CHECK(!block1.ContainsBlock(block4)); + CHECK(!block3.ContainsBlock(block1)); + CHECK(!block4.ContainsBlock(block1)); +} + +TEST_CASE("GridBlockCoords::Difference", "[grid]") +{ + SECTION("Subtract contained block (splitted horizontally)") + { + const wxGridBlockCoords block1(1, 1, 7, 7); + const wxGridBlockCoords block2(3, 3, 5, 5); + const wxGridBlockDiffResult result = + block1.Difference(block2, wxHORIZONTAL); + + CHECK(result.m_parts[0] == wxGridBlockCoords(1, 1, 2, 7)); + CHECK(result.m_parts[1] == wxGridBlockCoords(6, 1, 7, 7)); + CHECK(result.m_parts[2] == wxGridBlockCoords(3, 1, 5, 2)); + CHECK(result.m_parts[3] == wxGridBlockCoords(3, 6, 5, 7)); + } + + SECTION("Subtract contained block (splitted vertically)") + { + const wxGridBlockCoords block1(1, 1, 7, 7); + const wxGridBlockCoords block2(3, 3, 5, 5); + const wxGridBlockDiffResult result = + block1.Difference(block2, wxVERTICAL); + + CHECK(result.m_parts[0] == wxGridBlockCoords(1, 1, 7, 2)); + CHECK(result.m_parts[1] == wxGridBlockCoords(1, 6, 7, 7)); + CHECK(result.m_parts[2] == wxGridBlockCoords(1, 3, 2, 5)); + CHECK(result.m_parts[3] == wxGridBlockCoords(6, 3, 7, 5)); + } + + SECTION("Blocks intersect by the corner (splitted horizontally)") + { + const wxGridBlockCoords block1(1, 1, 5, 5); + const wxGridBlockCoords block2(3, 3, 7, 7); + const wxGridBlockDiffResult result = + block1.Difference(block2, wxHORIZONTAL); + + CHECK(result.m_parts[0] == wxGridBlockCoords(1, 1, 2, 5)); + CHECK(result.m_parts[1] == wxGridNoBlockCoords); + CHECK(result.m_parts[2] == wxGridBlockCoords(3, 1, 5, 2)); + CHECK(result.m_parts[3] == wxGridNoBlockCoords); + } + + SECTION("Blocks intersect by the corner (splitted vertically)") + { + const wxGridBlockCoords block1(1, 1, 5, 5); + const wxGridBlockCoords block2(3, 3, 7, 7); + const wxGridBlockDiffResult result = + block1.Difference(block2, wxVERTICAL); + + CHECK(result.m_parts[0] == wxGridBlockCoords(1, 1, 5, 2)); + CHECK(result.m_parts[1] == wxGridNoBlockCoords); + CHECK(result.m_parts[2] == wxGridBlockCoords(1, 3, 2, 5)); + CHECK(result.m_parts[3] == wxGridNoBlockCoords); + } + + SECTION("Blocks are the same") + { + const wxGridBlockCoords block1(1, 1, 3, 3); + const wxGridBlockCoords block2(1, 1, 3, 3); + const wxGridBlockDiffResult result = + block1.Difference(block2, wxHORIZONTAL); + + CHECK(result.m_parts[0] == wxGridNoBlockCoords); + CHECK(result.m_parts[1] == wxGridNoBlockCoords); + CHECK(result.m_parts[2] == wxGridNoBlockCoords); + CHECK(result.m_parts[3] == wxGridNoBlockCoords); + } + + SECTION("Blocks doesn't intersects") + { + const wxGridBlockCoords block1(1, 1, 3, 3); + const wxGridBlockCoords block2(5, 5, 7, 7); + const wxGridBlockDiffResult result = + block1.Difference(block2, wxHORIZONTAL); + + CHECK(result.m_parts[0] == wxGridBlockCoords(1, 1, 3, 3)); + CHECK(result.m_parts[1] == wxGridNoBlockCoords); + CHECK(result.m_parts[2] == wxGridNoBlockCoords); + CHECK(result.m_parts[3] == wxGridNoBlockCoords); + } +} + + +TEST_CASE("GridBlockCoords::SymDifference", "[grid]") +{ + SECTION("With contained block") + { + const wxGridBlockCoords block1(1, 1, 7, 7); + const wxGridBlockCoords block2(3, 3, 5, 5); + const wxGridBlockDiffResult result = block1.SymDifference(block2); + + CHECK(result.m_parts[0] == wxGridBlockCoords(1, 1, 2, 7)); + CHECK(result.m_parts[1] == wxGridBlockCoords(6, 1, 7, 7)); + CHECK(result.m_parts[2] == wxGridBlockCoords(3, 1, 5, 2)); + CHECK(result.m_parts[3] == wxGridBlockCoords(3, 6, 5, 7)); + } + + SECTION("Blocks intersect by the corner") + { + const wxGridBlockCoords block1(1, 1, 5, 5); + const wxGridBlockCoords block2(3, 3, 7, 7); + const wxGridBlockDiffResult result = block1.SymDifference(block2); + + CHECK(result.m_parts[0] == wxGridBlockCoords(1, 1, 2, 5)); + CHECK(result.m_parts[1] == wxGridBlockCoords(6, 3, 7, 7)); + CHECK(result.m_parts[2] == wxGridBlockCoords(3, 1, 5, 2)); + CHECK(result.m_parts[3] == wxGridBlockCoords(3, 6, 5, 7)); + } + + SECTION("Blocks are the same") + { + const wxGridBlockCoords block1(1, 1, 3, 3); + const wxGridBlockCoords block2(1, 1, 3, 3); + const wxGridBlockDiffResult result = block1.SymDifference(block2); + + CHECK(result.m_parts[0] == wxGridNoBlockCoords); + CHECK(result.m_parts[1] == wxGridNoBlockCoords); + CHECK(result.m_parts[2] == wxGridNoBlockCoords); + CHECK(result.m_parts[3] == wxGridNoBlockCoords); + } + + SECTION("Blocks doesn't intersects") + { + const wxGridBlockCoords block1(1, 1, 3, 3); + const wxGridBlockCoords block2(5, 5, 7, 7); + const wxGridBlockDiffResult result = block1.SymDifference(block2); + + CHECK(result.m_parts[0] == wxGridBlockCoords(1, 1, 3, 3)); + CHECK(result.m_parts[1] == wxGridBlockCoords(5, 5, 7, 7)); + CHECK(result.m_parts[2] == wxGridNoBlockCoords); + CHECK(result.m_parts[3] == wxGridNoBlockCoords); + } +} + #endif //wxUSE_GRID