summaryrefslogtreecommitdiffstats
path: root/sc/source/core/data/markmulti.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'sc/source/core/data/markmulti.cxx')
-rw-r--r--sc/source/core/data/markmulti.cxx485
1 files changed, 485 insertions, 0 deletions
diff --git a/sc/source/core/data/markmulti.cxx b/sc/source/core/data/markmulti.cxx
new file mode 100644
index 000000000..4c92f5f25
--- /dev/null
+++ b/sc/source/core/data/markmulti.cxx
@@ -0,0 +1,485 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This file is part of the LibreOffice project.
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ *
+ * This file incorporates work covered by the following license notice:
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed
+ * with this work for additional information regarding copyright
+ * ownership. The ASF licenses this file to you under the Apache
+ * License, Version 2.0 (the "License"); you may not use this file
+ * except in compliance with the License. You may obtain a copy of
+ * the License at http://www.apache.org/licenses/LICENSE-2.0 .
+ */
+
+#include <markmulti.hxx>
+#include <markarr.hxx>
+#include <rangelst.hxx>
+#include <segmenttree.hxx>
+#include <sheetlimits.hxx>
+
+#include <o3tl/safeint.hxx>
+
+#include <algorithm>
+
+ScMultiSel::ScMultiSel(const ScSheetLimits& rSheetLimits)
+ : aRowSel(rSheetLimits), mrSheetLimits(rSheetLimits)
+{
+}
+
+ScMultiSel& ScMultiSel::operator=(const ScMultiSel& rOther)
+{
+ aMultiSelContainer = rOther.aMultiSelContainer;
+ aRowSel = rOther.aRowSel;
+ return *this;
+}
+
+ScMultiSel& ScMultiSel::operator=(ScMultiSel&& rOther)
+{
+ aMultiSelContainer = std::move(rOther.aMultiSelContainer);
+ aRowSel = std::move(rOther.aRowSel);
+ return *this;
+}
+
+
+void ScMultiSel::Clear()
+{
+ aMultiSelContainer.clear();
+ aRowSel.Reset();
+}
+
+SCCOL ScMultiSel::GetMultiSelectionCount() const
+{
+ SCCOL nCount = 0;
+ for (const auto & i : aMultiSelContainer)
+ if (i.HasMarks())
+ ++nCount;
+ return nCount;
+}
+
+bool ScMultiSel::HasMarks( SCCOL nCol ) const
+{
+ if ( aRowSel.HasMarks() )
+ return true;
+ return nCol < static_cast<SCCOL>(aMultiSelContainer.size()) && aMultiSelContainer[nCol].HasMarks();
+}
+
+bool ScMultiSel::HasOneMark( SCCOL nCol, SCROW& rStartRow, SCROW& rEndRow ) const
+{
+ SCROW nRow1 = -1, nRow2 = -1, nRow3 = -1, nRow4 = -1;
+ bool aResult1 = aRowSel.HasOneMark( nRow1, nRow2 );
+ bool aResult2 = nCol < static_cast<SCCOL>(aMultiSelContainer.size())
+ && aMultiSelContainer[nCol].HasOneMark( nRow3, nRow4 );
+
+ if ( aResult1 || aResult2 )
+ {
+ if ( aResult1 && aResult2 )
+ {
+ if ( ( nRow2 + 1 ) < nRow3 )
+ return false;
+ if ( ( nRow4 + 1 ) < nRow1 )
+ return false;
+
+ auto aRows = std::minmax( { nRow1, nRow2, nRow3, nRow4 } );
+ rStartRow = aRows.first;
+ rEndRow = aRows.second;
+ return true;
+ }
+ if ( aResult1 )
+ {
+ rStartRow = nRow1;
+ rEndRow = nRow2;
+ return true;
+ }
+
+ rStartRow = nRow3;
+ rEndRow = nRow4;
+ return true;
+ }
+
+ return false;
+}
+
+bool ScMultiSel::GetMark( SCCOL nCol, SCROW nRow ) const
+{
+ if ( aRowSel.GetMark( nRow ) )
+ return true;
+ return nCol < static_cast<SCCOL>(aMultiSelContainer.size()) && aMultiSelContainer[nCol].GetMark(nRow);
+}
+
+bool ScMultiSel::IsAllMarked( SCCOL nCol, SCROW nStartRow, SCROW nEndRow ) const
+{
+ bool bHasMarks1 = aRowSel.HasMarks();
+ bool bHasMarks2 = nCol < static_cast<SCCOL>(aMultiSelContainer.size()) && aMultiSelContainer[nCol].HasMarks();
+
+ if ( !bHasMarks1 && !bHasMarks2 )
+ return false;
+
+ if ( bHasMarks1 && bHasMarks2 )
+ {
+ if ( aRowSel.IsAllMarked( nStartRow, nEndRow ) ||
+ aMultiSelContainer[nCol].IsAllMarked( nStartRow, nEndRow ) )
+ return true;
+ ScMultiSelIter aMultiIter( *this, nCol );
+ ScFlatBoolRowSegments::RangeData aRowRange;
+ bool bRet = aMultiIter.GetRangeData( nStartRow, aRowRange );
+ return bRet && aRowRange.mbValue && aRowRange.mnRow2 >= nEndRow;
+ }
+
+ if ( bHasMarks1 )
+ return aRowSel.IsAllMarked( nStartRow, nEndRow );
+
+ return aMultiSelContainer[nCol].IsAllMarked( nStartRow, nEndRow );
+}
+
+bool ScMultiSel::HasEqualRowsMarked( SCCOL nCol1, SCCOL nCol2 ) const
+{
+ bool bCol1Exists = nCol1 < static_cast<SCCOL>(aMultiSelContainer.size());
+ bool bCol2Exists = nCol2 < static_cast<SCCOL>(aMultiSelContainer.size());
+ if ( bCol1Exists || bCol2Exists )
+ {
+ if ( bCol1Exists && bCol2Exists )
+ return aMultiSelContainer[nCol1] == aMultiSelContainer[nCol2];
+ else if ( bCol1Exists )
+ return !aMultiSelContainer[nCol1].HasMarks();
+ else
+ return !aMultiSelContainer[nCol2].HasMarks();
+ }
+
+ return true;
+}
+
+SCCOL ScMultiSel::GetStartOfEqualColumns( SCCOL nLastCol, SCCOL nMinCol ) const
+{
+ if( nMinCol > nLastCol )
+ return nMinCol;
+ if( nLastCol >= static_cast<SCCOL>(aMultiSelContainer.size()))
+ {
+ if( nMinCol >= static_cast<SCCOL>(aMultiSelContainer.size()))
+ return nMinCol;
+ SCCOL nCol = static_cast<SCCOL>(aMultiSelContainer.size()) - 1;
+ while( nCol >= nMinCol && aMultiSelContainer[nCol] == aRowSel )
+ --nCol;
+ return nCol + 1;
+ }
+ SCCOL nCol = nLastCol - 1;
+ while( nCol >= nMinCol && aMultiSelContainer[nCol] == aMultiSelContainer[nLastCol] )
+ --nCol;
+ return nCol + 1;
+}
+
+SCROW ScMultiSel::GetNextMarked( SCCOL nCol, SCROW nRow, bool bUp ) const
+{
+ if ( nCol >= static_cast<SCCOL>(aMultiSelContainer.size()) || !aMultiSelContainer[nCol].HasMarks() )
+ return aRowSel.GetNextMarked( nRow, bUp );
+
+ SCROW nRow1, nRow2;
+ nRow1 = aRowSel.GetNextMarked( nRow, bUp );
+ nRow2 = aMultiSelContainer[nCol].GetNextMarked( nRow, bUp );
+ if ( nRow1 == nRow2 )
+ return nRow1;
+ if ( nRow1 == -1 )
+ return nRow2;
+ if ( nRow2 == -1 )
+ return nRow1;
+
+ PutInOrder( nRow1, nRow2 );
+ return ( bUp ? nRow2 : nRow1 );
+}
+
+void ScMultiSel::MarkAllCols( SCROW nStartRow, SCROW nEndRow )
+{
+ aMultiSelContainer.resize(mrSheetLimits.mnMaxCol+1, ScMarkArray(mrSheetLimits));
+ for ( SCCOL nCol = mrSheetLimits.mnMaxCol; nCol >= 0; --nCol )
+ {
+ aMultiSelContainer[nCol].SetMarkArea( nStartRow, nEndRow, true );
+ }
+}
+
+void ScMultiSel::SetMarkArea( SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCROW nEndRow, bool bMark )
+{
+ if ( nStartCol == 0 && nEndCol == mrSheetLimits.mnMaxCol )
+ {
+ aRowSel.SetMarkArea( nStartRow, nEndRow, bMark );
+ if ( !bMark )
+ {
+ // Remove any per column marks for the row range.
+ for ( auto& aIter : aMultiSelContainer )
+ if ( aIter.HasMarks() )
+ aIter.SetMarkArea( nStartRow, nEndRow, false );
+ }
+ return;
+ }
+
+ // Bad case - we need to extend aMultiSelContainer size to MAXCOL
+ // and move row marks from aRowSel to aMultiSelContainer
+ if ( !bMark && aRowSel.HasMarks() )
+ {
+ SCROW nBeg, nLast = nEndRow;
+ if ( aRowSel.GetMark( nStartRow ) )
+ {
+ nBeg = nStartRow;
+ nLast = aRowSel.GetMarkEnd( nStartRow, false );
+ }
+ else
+ {
+ nBeg = aRowSel.GetNextMarked( nStartRow, false );
+ if ( nBeg != mrSheetLimits.GetMaxRowCount() )
+ nLast = aRowSel.GetMarkEnd( nBeg, false );
+ }
+
+ if ( nBeg != mrSheetLimits.GetMaxRowCount() && nLast >= nEndRow && nBeg <= nEndRow )
+ MarkAllCols( nBeg, nEndRow );
+ else
+ {
+ while ( nBeg != mrSheetLimits.GetMaxRowCount() && nLast < nEndRow )
+ {
+ MarkAllCols( nBeg, nLast );
+ nBeg = aRowSel.GetNextMarked( nLast + 1, false );
+ if ( nBeg != mrSheetLimits.GetMaxRowCount() )
+ nLast = aRowSel.GetMarkEnd( nBeg, false );
+ }
+ if ( nBeg != mrSheetLimits.GetMaxRowCount() && nLast >= nEndRow && nBeg <= nEndRow )
+ MarkAllCols( nBeg, nEndRow );
+ }
+
+ aRowSel.SetMarkArea( nStartRow, nEndRow, false );
+ }
+
+ if (nEndCol >= static_cast<SCCOL>(aMultiSelContainer.size()))
+ aMultiSelContainer.resize(nEndCol+1, ScMarkArray(mrSheetLimits));
+ for ( SCCOL nColIter = nEndCol; nColIter >= nStartCol; --nColIter )
+ aMultiSelContainer[nColIter].SetMarkArea( nStartRow, nEndRow, bMark );
+}
+
+/**
+ optimised init-from-range-list. Specifically this is optimised for cases
+ where we have very large data columns with lots and lots of ranges.
+*/
+void ScMultiSel::Set( ScRangeList const & rList )
+{
+ Clear();
+ if (rList.size() == 0)
+ return;
+
+ // sort by row to make the combining/merging faster
+ auto aNewList = rList;
+ std::sort(aNewList.begin(), aNewList.end(),
+ [](const ScRange& lhs, const ScRange& rhs)
+ {
+ return lhs.aStart.Row() < rhs.aStart.Row();
+ });
+
+ std::vector<std::vector<ScMarkEntry>> aMarkEntriesPerCol(mrSheetLimits.mnMaxCol+1);
+
+ SCCOL nMaxCol = -1;
+ for (const ScRange& rRange : aNewList)
+ {
+ SCCOL nStartCol = rRange.aStart.Col();
+ SCROW nStartRow = rRange.aStart.Row();
+ SCCOL nEndCol = rRange.aEnd.Col();
+ SCROW nEndRow = rRange.aEnd.Row();
+ assert( nEndRow >= nStartRow && "this method assumes the input data has ranges with endrow>=startrow");
+ assert( nEndCol >= nStartCol && "this method assumes the input data has ranges with endcol>=startcol");
+ if ( nStartCol == 0 && nEndCol == mrSheetLimits.mnMaxCol )
+ aRowSel.SetMarkArea( nStartRow, nEndRow, /*bMark*/true );
+ else
+ {
+ for ( SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol )
+ {
+ auto & rMarkEntries = aMarkEntriesPerCol[nCol];
+ int nEntries = rMarkEntries.size();
+ if (nEntries > 1 && nStartRow >= rMarkEntries[nEntries-2].nRow+1
+ && nStartRow <= rMarkEntries[nEntries-1].nRow+1)
+ {
+ // overlaps or directly adjacent previous range
+ rMarkEntries.back().nRow = std::max(nEndRow, rMarkEntries.back().nRow);
+ }
+ else
+ {
+ // new range
+ if (nStartRow > 0)
+ rMarkEntries.emplace_back(ScMarkEntry{nStartRow-1, false});
+ rMarkEntries.emplace_back(ScMarkEntry{nEndRow, true});
+ }
+ }
+ nMaxCol = std::max(nMaxCol, nEndCol);
+ }
+ }
+
+ aMultiSelContainer.resize(nMaxCol+1, ScMarkArray(mrSheetLimits));
+ for (SCCOL nCol = 0; nCol<=nMaxCol; ++nCol)
+ if (!aMarkEntriesPerCol[nCol].empty())
+ aMultiSelContainer[nCol].Set( std::move(aMarkEntriesPerCol[nCol]) );
+}
+
+bool ScMultiSel::IsRowMarked( SCROW nRow ) const
+{
+ return aRowSel.GetMark( nRow );
+}
+
+bool ScMultiSel::IsRowRangeMarked( SCROW nStartRow, SCROW nEndRow ) const
+{
+ if ( !aRowSel.GetMark( nStartRow ) )
+ return false;
+ SCROW nLast = aRowSel.GetMarkEnd( nStartRow, false );
+ return ( nLast >= nEndRow );
+}
+
+ScMarkArray ScMultiSel::GetMarkArray( SCCOL nCol ) const
+{
+ ScMultiSelIter aMultiIter( *this, nCol );
+ ScMarkArray aMarkArray(mrSheetLimits);
+ SCROW nTop, nBottom;
+ while( aMultiIter.Next( nTop, nBottom ) )
+ aMarkArray.SetMarkArea( nTop, nBottom, true );
+ return aMarkArray;
+}
+
+bool ScMultiSel::HasAnyMarks() const
+{
+ if ( aRowSel.HasMarks() )
+ return true;
+ for ( const auto& aPair : aMultiSelContainer )
+ if ( aPair.HasMarks() )
+ return true;
+ return false;
+}
+
+void ScMultiSel::ShiftCols(SCCOL nStartCol, sal_Int32 nColOffset)
+{
+ if (nStartCol > mrSheetLimits.mnMaxCol)
+ return;
+
+ ScMultiSel aNewMultiSel(*this);
+ Clear();
+
+ if (nColOffset < 0)
+ {
+ // columns that would be moved on the left of nStartCol must be removed
+ const SCCOL nEndPos = std::min<SCCOL>(aNewMultiSel.aMultiSelContainer.size(), nStartCol - nColOffset);
+ for (SCCOL nSearchPos = nStartCol; nSearchPos < nEndPos; ++nSearchPos)
+ aNewMultiSel.aMultiSelContainer[nSearchPos].Reset();
+ }
+
+ SCCOL nCol = 0;
+ for (const auto& aSourceArray : aNewMultiSel.aMultiSelContainer)
+ {
+ SCCOL nDestCol = nCol;
+ if (nDestCol >= nStartCol)
+ {
+ nDestCol += nColOffset;
+ if (nDestCol < 0)
+ nDestCol = 0;
+ else if (nDestCol > mrSheetLimits.mnMaxCol)
+ nDestCol = mrSheetLimits.mnMaxCol;
+ }
+ if (nDestCol >= static_cast<SCCOL>(aMultiSelContainer.size()))
+ aMultiSelContainer.resize(nDestCol, ScMarkArray(mrSheetLimits));
+ aMultiSelContainer[nDestCol] = aSourceArray;
+ ++nCol;
+ }
+ aRowSel = aNewMultiSel.aRowSel;
+
+ if (!(nColOffset > 0 && nStartCol > 0 && o3tl::make_unsigned(nStartCol) < aNewMultiSel.aMultiSelContainer.size()))
+ return;
+
+ // insert nColOffset new columns, and select their cells if they are selected
+ // both in the old column at nStartPos and in the previous column
+ auto& rPrevPos = aNewMultiSel.aMultiSelContainer[nStartCol - 1];
+ auto& rStartPos = aNewMultiSel.aMultiSelContainer[nStartCol];
+ auto& rNewCol = aMultiSelContainer[nStartCol];
+ rNewCol = rStartPos;
+ rNewCol.Intersect(rPrevPos);
+ if (nStartCol + nColOffset >= static_cast<SCCOL>(aNewMultiSel.aMultiSelContainer.size()))
+ aNewMultiSel.aMultiSelContainer.resize(nStartCol + nColOffset, ScMarkArray(mrSheetLimits));
+ for (tools::Long i = 1; i < nColOffset; ++i)
+ aMultiSelContainer[nStartCol + i] = rNewCol;
+}
+
+void ScMultiSel::ShiftRows(SCROW nStartRow, sal_Int32 nRowOffset)
+{
+ for (auto& aPair: aMultiSelContainer)
+ aPair.Shift(nStartRow, nRowOffset);
+ aRowSel.Shift(nStartRow, nRowOffset);
+}
+
+const ScMarkArray* ScMultiSel::GetMultiSelArray( SCCOL nCol ) const
+{
+ if (nCol >= static_cast<SCCOL>(aMultiSelContainer.size()))
+ return nullptr;
+ return &aMultiSelContainer[nCol];
+}
+
+ScMultiSelIter::ScMultiSelIter( const ScMultiSel& rMultiSel, SCCOL nCol ) :
+ aMarkArrayIter(nullptr),
+ nNextSegmentStart(0)
+{
+ bool bHasMarks1 = rMultiSel.aRowSel.HasMarks();
+ bool bHasMarks2 = nCol < static_cast<SCCOL>(rMultiSel.aMultiSelContainer.size())
+ && rMultiSel.aMultiSelContainer[nCol].HasMarks();
+
+ if (bHasMarks1 && bHasMarks2)
+ {
+ pRowSegs.reset( new ScFlatBoolRowSegments(rMultiSel.mrSheetLimits.mnMaxRow) );
+ pRowSegs->setFalse( 0, rMultiSel.mrSheetLimits.mnMaxRow );
+ {
+ ScMarkArrayIter aMarkIter( &rMultiSel.aRowSel );
+ SCROW nTop, nBottom;
+ while ( aMarkIter.Next( nTop, nBottom ) )
+ pRowSegs->setTrue( nTop, nBottom );
+ }
+
+ {
+ ScMarkArrayIter aMarkIter( &rMultiSel.aMultiSelContainer[nCol] );
+ SCROW nTop, nBottom;
+ while ( aMarkIter.Next( nTop, nBottom ) )
+ pRowSegs->setTrue( nTop, nBottom );
+ }
+ }
+ else if (bHasMarks1)
+ {
+ aMarkArrayIter.reset( &rMultiSel.aRowSel);
+ }
+ else if (bHasMarks2)
+ {
+ aMarkArrayIter.reset( &rMultiSel.aMultiSelContainer[nCol]);
+ }
+}
+
+bool ScMultiSelIter::Next( SCROW& rTop, SCROW& rBottom )
+{
+ if (pRowSegs)
+ {
+ ScFlatBoolRowSegments::RangeData aRowRange;
+ bool bRet = pRowSegs->getRangeData( nNextSegmentStart, aRowRange );
+ if ( bRet && !aRowRange.mbValue )
+ {
+ nNextSegmentStart = aRowRange.mnRow2 + 1;
+ bRet = pRowSegs->getRangeData( nNextSegmentStart, aRowRange );
+ }
+ if ( bRet )
+ {
+ rTop = aRowRange.mnRow1;
+ rBottom = aRowRange.mnRow2;
+ nNextSegmentStart = rBottom + 1;
+ }
+ return bRet;
+ }
+
+ return aMarkArrayIter.Next( rTop, rBottom);
+}
+
+bool ScMultiSelIter::GetRangeData( SCROW nRow, ScFlatBoolRowSegments::RangeData& rRowRange ) const
+{
+ assert(pRowSegs);
+ return pRowSegs->getRangeData( nRow, rRowRange);
+}
+
+
+/* vim:set shiftwidth=4 softtabstop=4 expandtab: */