From 3123c763948e3dcff49ccc2a5a877040afcbe8d1 Mon Sep 17 00:00:00 2001 From: Dimitri Schoolwerth Date: Mon, 25 Jan 2021 21:30:09 +0100 Subject: [PATCH] Fix grid multicell integrity after inserting/deleting rows/columns Deal with possible size changes of a multicell (both main and inside cells) when inserting or deleting rows or columns and, in the case of deletion only, the disappearing of a multicell's main cell. Closes #4238. --- src/generic/grid.cpp | 132 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 130 insertions(+), 2 deletions(-) diff --git a/src/generic/grid.cpp b/src/generic/grid.cpp index d62aafe9d1..53f982f6e6 100644 --- a/src/generic/grid.cpp +++ b/src/generic/grid.cpp @@ -820,13 +820,82 @@ void UpdateCellAttrRowsOrCols(wxGridCellWithAttrArray& attrs, int editPos, size_t count = attrs.GetCount(); for ( size_t n = 0; n < count; n++ ) { + wxGridCellAttr* cellAttr = attrs[n].attr; + int cellRows, cellCols; + cellAttr->GetSize(&cellRows, &cellCols); + wxGridCellCoords& coords = attrs[n].coords; const wxCoord cellRow = coords.GetRow(), cellCol = coords.GetCol(), cellPos = (isEditingRows ? cellRow : cellCol); if ( cellPos < editPos ) + { + // This cell's coords aren't influenced by the editing, however + // do adjust a multicell's main size, if needed. + if ( GetCellSpan(cellRows, cellCols) == wxGrid::CellSpan_Main ) + { + int mainSize = isEditingRows ? cellRows : cellCols; + if ( cellPos + mainSize > editPos ) + { + // Multicell is within affected range: + // Adjust its size. + + if ( editCount >= 0 ) + { + mainSize += editCount; + } + else + { + // Reduce multicell size by number of deletions, but + // never more than the multicell's size minus one: + // cellPos (the main cell) is always less than editPos + // at this point, then with the below code a multicell + // with size 7 is at most reduced by: + // cellPos + 7 - (cellPos + 1) = 7 - 1 = 6. + mainSize -= wxMin(-editCount, + cellPos + mainSize - editPos); + /* + The above was derived from: + first_del = edit + last_del = min(edit - count - 1, cell + size - 1) + size -= (last_del + 1 - first_del) + + eliminating the 1's: + + first_del = edit + last_del = min(edit - count, cell + size) + size -= (last_del - first_del) + + reducing each by edit: + + first_del = 0 + last_del_plus_1 = min(0 - count, cell + size - edit) + size -= (last_del_plus_1 - 0) + + after eliminating the 0's and substitution, leaving: + + size -= min(-count, cell + size - edit) + + E.g. with a multicell of size 7 and at 2 positions + after the main cell 100 positions are deleted then + the size will not (/can't) be reduced by 100 cells + but by: + + cellPos + 7 - editPos = # editPos = cellPos + 2 + cellPos + 7 - (cellPos + 2) = # eliminate cellPos + 7 - 2 = + 5 cells, making the final size 7 - 5 = 2. + */ + } + + cellAttr->SetSize(isEditingRows ? mainSize : cellRows, + isEditingRows ? cellCols : mainSize); + } + } + continue; + } if ( editCount < 0 && cellPos < editPos - editCount ) { @@ -839,9 +908,68 @@ void UpdateCellAttrRowsOrCols(wxGridCellWithAttrArray& attrs, int editPos, continue; } - // Rows/cols inserted or deleted (and this cell still exists): - // Adjust cell coords. + if ( GetCellSpan(cellRows, cellCols) != wxGrid::CellSpan_Inside ) + { + // Rows/cols inserted or deleted (and this cell still exists): + // Adjust cell coords. + coords.Set(cellRow + editRowCount, cellCol + editColCount); + + // Nothing more to do: cell is not an inside cell of a multicell. + continue; + } + + // Handle inside cell's existence, coords, and size. + + const int mainPos = cellPos + (isEditingRows ? cellRows : cellCols); + + if ( editCount < 0 + && mainPos >= editPos && mainPos < editPos - editCount ) + { + // On a position that still exists after deletion but main cell + // of multicell is within deletion range so the multicell is gone: + // Remove the attribute. + attrs.RemoveAt(n); + n--; + count--; + + continue; + } + + // Rows/cols inserted or deleted (and this inside cell still exists): + // Adjust (inside) cell coords. coords.Set(cellRow + editRowCount, cellCol + editColCount); + + if ( mainPos >= editPos ) + { + // Nothing more to do: the multicell that this inside cell is part + // of is moving its main cell as well so offsets to the main cell + // don't change and there are no edits changing the multicell size. + continue; + } + + if ( editCount > 0 && cellPos == editPos ) + { + // At an (old) position that is newly inserted: this is the only + // opportunity to add required inside cells that point to + // the main cell. E.g. with a 2x1 multicell that increases in size + // there's only one inside cell that will be visited while there + // can be multiple insertions. + for ( int i = 0; i < editCount; ++i ) + { + const int adjustRows = i * isEditingRows, + adjustCols = i * !isEditingRows; + + wxGridCellAttr* attr = new wxGridCellAttr; + attr->SetSize(cellRows - adjustRows, cellCols - adjustCols); + + attrs.Add(new wxGridCellWithAttr(cellRow + adjustRows, + cellCol + adjustCols, + attr)); + } + } + + // Let this inside cell's size point to the main cell of the multicell. + cellAttr->SetSize(cellRows - editRowCount, cellCols - editColCount); } }