1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
20 #include <markmulti.hxx>
21 #include <markarr.hxx>
22 #include <rangelst.hxx>
23 #include <segmenttree.hxx>
24 #include <sheetlimits.hxx>
26 #include <o3tl/safeint.hxx>
30 ScMultiSel::ScMultiSel(const ScSheetLimits
& rSheetLimits
)
31 : aRowSel(rSheetLimits
), mrSheetLimits(rSheetLimits
)
35 ScMultiSel
& ScMultiSel::operator=(const ScMultiSel
& rOther
)
37 aMultiSelContainer
= rOther
.aMultiSelContainer
;
38 aRowSel
= rOther
.aRowSel
;
42 ScMultiSel
& ScMultiSel::operator=(ScMultiSel
&& rOther
)
44 aMultiSelContainer
= std::move(rOther
.aMultiSelContainer
);
45 aRowSel
= std::move(rOther
.aRowSel
);
50 void ScMultiSel::Clear()
52 aMultiSelContainer
.clear();
56 SCCOL
ScMultiSel::GetMultiSelectionCount() const
59 for (const auto & i
: aMultiSelContainer
)
65 bool ScMultiSel::HasMarks( SCCOL nCol
) const
67 if ( aRowSel
.HasMarks() )
69 return nCol
< static_cast<SCCOL
>(aMultiSelContainer
.size()) && aMultiSelContainer
[nCol
].HasMarks();
72 bool ScMultiSel::HasOneMark( SCCOL nCol
, SCROW
& rStartRow
, SCROW
& rEndRow
) const
74 SCROW nRow1
= -1, nRow2
= -1, nRow3
= -1, nRow4
= -1;
75 bool aResult1
= aRowSel
.HasOneMark( nRow1
, nRow2
);
76 bool aResult2
= nCol
< static_cast<SCCOL
>(aMultiSelContainer
.size())
77 && aMultiSelContainer
[nCol
].HasOneMark( nRow3
, nRow4
);
79 if ( aResult1
|| aResult2
)
81 if ( aResult1
&& aResult2
)
83 if ( ( nRow2
+ 1 ) < nRow3
)
85 if ( ( nRow4
+ 1 ) < nRow1
)
88 auto aRows
= std::minmax( { nRow1
, nRow2
, nRow3
, nRow4
} );
89 rStartRow
= aRows
.first
;
90 rEndRow
= aRows
.second
;
108 bool ScMultiSel::GetMark( SCCOL nCol
, SCROW nRow
) const
110 if ( aRowSel
.GetMark( nRow
) )
112 return nCol
< static_cast<SCCOL
>(aMultiSelContainer
.size()) && aMultiSelContainer
[nCol
].GetMark(nRow
);
115 bool ScMultiSel::IsAllMarked( SCCOL nCol
, SCROW nStartRow
, SCROW nEndRow
) const
117 bool bHasMarks1
= aRowSel
.HasMarks();
118 bool bHasMarks2
= nCol
< static_cast<SCCOL
>(aMultiSelContainer
.size()) && aMultiSelContainer
[nCol
].HasMarks();
120 if ( !bHasMarks1
&& !bHasMarks2
)
123 if ( bHasMarks1
&& bHasMarks2
)
125 if ( aRowSel
.IsAllMarked( nStartRow
, nEndRow
) ||
126 aMultiSelContainer
[nCol
].IsAllMarked( nStartRow
, nEndRow
) )
128 ScMultiSelIter
aMultiIter( *this, nCol
);
129 ScFlatBoolRowSegments::RangeData aRowRange
;
130 bool bRet
= aMultiIter
.GetRangeData( nStartRow
, aRowRange
);
131 return bRet
&& aRowRange
.mbValue
&& aRowRange
.mnRow2
>= nEndRow
;
135 return aRowSel
.IsAllMarked( nStartRow
, nEndRow
);
137 return aMultiSelContainer
[nCol
].IsAllMarked( nStartRow
, nEndRow
);
140 bool ScMultiSel::HasEqualRowsMarked( SCCOL nCol1
, SCCOL nCol2
) const
142 bool bCol1Exists
= nCol1
< static_cast<SCCOL
>(aMultiSelContainer
.size());
143 bool bCol2Exists
= nCol2
< static_cast<SCCOL
>(aMultiSelContainer
.size());
144 if ( bCol1Exists
|| bCol2Exists
)
146 if ( bCol1Exists
&& bCol2Exists
)
147 return aMultiSelContainer
[nCol1
] == aMultiSelContainer
[nCol2
];
148 else if ( bCol1Exists
)
149 return !aMultiSelContainer
[nCol1
].HasMarks();
151 return !aMultiSelContainer
[nCol2
].HasMarks();
157 SCCOL
ScMultiSel::GetStartOfEqualColumns( SCCOL nLastCol
, SCCOL nMinCol
) const
159 if( nMinCol
> nLastCol
)
161 if( nLastCol
>= static_cast<SCCOL
>(aMultiSelContainer
.size()))
163 if( nMinCol
>= static_cast<SCCOL
>(aMultiSelContainer
.size()))
165 SCCOL nCol
= static_cast<SCCOL
>(aMultiSelContainer
.size()) - 1;
166 while( nCol
>= nMinCol
&& aMultiSelContainer
[nCol
] == aRowSel
)
170 SCCOL nCol
= nLastCol
- 1;
171 while( nCol
>= nMinCol
&& aMultiSelContainer
[nCol
] == aMultiSelContainer
[nLastCol
] )
176 SCROW
ScMultiSel::GetNextMarked( SCCOL nCol
, SCROW nRow
, bool bUp
) const
178 if ( nCol
>= static_cast<SCCOL
>(aMultiSelContainer
.size()) || !aMultiSelContainer
[nCol
].HasMarks() )
179 return aRowSel
.GetNextMarked( nRow
, bUp
);
182 nRow1
= aRowSel
.GetNextMarked( nRow
, bUp
);
183 nRow2
= aMultiSelContainer
[nCol
].GetNextMarked( nRow
, bUp
);
184 if ( nRow1
== nRow2
)
191 PutInOrder( nRow1
, nRow2
);
192 return ( bUp
? nRow2
: nRow1
);
195 void ScMultiSel::MarkAllCols( SCROW nStartRow
, SCROW nEndRow
)
197 aMultiSelContainer
.resize(mrSheetLimits
.mnMaxCol
+1, ScMarkArray(mrSheetLimits
));
198 for ( SCCOL nCol
= mrSheetLimits
.mnMaxCol
; nCol
>= 0; --nCol
)
200 aMultiSelContainer
[nCol
].SetMarkArea( nStartRow
, nEndRow
, true );
204 void ScMultiSel::SetMarkArea( SCCOL nStartCol
, SCCOL nEndCol
, SCROW nStartRow
, SCROW nEndRow
, bool bMark
)
206 if ( nStartCol
== 0 && nEndCol
== mrSheetLimits
.mnMaxCol
)
208 aRowSel
.SetMarkArea( nStartRow
, nEndRow
, bMark
);
211 // Remove any per column marks for the row range.
212 for ( auto& aIter
: aMultiSelContainer
)
213 if ( aIter
.HasMarks() )
214 aIter
.SetMarkArea( nStartRow
, nEndRow
, false );
219 // Bad case - we need to extend aMultiSelContainer size to MAXCOL
220 // and move row marks from aRowSel to aMultiSelContainer
221 if ( !bMark
&& aRowSel
.HasMarks() )
223 SCROW nBeg
, nLast
= nEndRow
;
224 if ( aRowSel
.GetMark( nStartRow
) )
227 nLast
= aRowSel
.GetMarkEnd( nStartRow
, false );
231 nBeg
= aRowSel
.GetNextMarked( nStartRow
, false );
232 if ( nBeg
!= mrSheetLimits
.GetMaxRowCount() )
233 nLast
= aRowSel
.GetMarkEnd( nBeg
, false );
236 if ( nBeg
!= mrSheetLimits
.GetMaxRowCount() && nLast
>= nEndRow
&& nBeg
<= nEndRow
)
237 MarkAllCols( nBeg
, nEndRow
);
240 while ( nBeg
!= mrSheetLimits
.GetMaxRowCount() && nLast
< nEndRow
)
242 MarkAllCols( nBeg
, nLast
);
243 nBeg
= aRowSel
.GetNextMarked( nLast
+ 1, false );
244 if ( nBeg
!= mrSheetLimits
.GetMaxRowCount() )
245 nLast
= aRowSel
.GetMarkEnd( nBeg
, false );
247 if ( nBeg
!= mrSheetLimits
.GetMaxRowCount() && nLast
>= nEndRow
&& nBeg
<= nEndRow
)
248 MarkAllCols( nBeg
, nEndRow
);
251 aRowSel
.SetMarkArea( nStartRow
, nEndRow
, false );
254 if (nEndCol
>= static_cast<SCCOL
>(aMultiSelContainer
.size()))
255 aMultiSelContainer
.resize(nEndCol
+1, ScMarkArray(mrSheetLimits
));
256 for ( SCCOL nColIter
= nEndCol
; nColIter
>= nStartCol
; --nColIter
)
257 aMultiSelContainer
[nColIter
].SetMarkArea( nStartRow
, nEndRow
, bMark
);
261 optimised init-from-range-list. Specifically this is optimised for cases
262 where we have very large data columns with lots and lots of ranges.
264 void ScMultiSel::Set( ScRangeList
const & rList
)
270 // sort by row to make the combining/merging faster
271 auto aNewList
= rList
;
272 std::sort(aNewList
.begin(), aNewList
.end(),
273 [](const ScRange
& lhs
, const ScRange
& rhs
)
275 return lhs
.aStart
.Row() < rhs
.aStart
.Row();
278 std::vector
<std::vector
<ScMarkEntry
>> aMarkEntriesPerCol(mrSheetLimits
.mnMaxCol
+1);
281 for (const ScRange
& rRange
: aNewList
)
283 SCCOL nStartCol
= rRange
.aStart
.Col();
284 SCROW nStartRow
= rRange
.aStart
.Row();
285 SCCOL nEndCol
= rRange
.aEnd
.Col();
286 SCROW nEndRow
= rRange
.aEnd
.Row();
287 assert( nEndRow
>= nStartRow
&& "this method assumes the input data has ranges with endrow>=startrow");
288 assert( nEndCol
>= nStartCol
&& "this method assumes the input data has ranges with endcol>=startcol");
289 if ( nStartCol
== 0 && nEndCol
== mrSheetLimits
.mnMaxCol
)
290 aRowSel
.SetMarkArea( nStartRow
, nEndRow
, /*bMark*/true );
293 for ( SCCOL nCol
= nStartCol
; nCol
<= nEndCol
; ++nCol
)
295 auto & rMarkEntries
= aMarkEntriesPerCol
[nCol
];
296 int nEntries
= rMarkEntries
.size();
297 if (nEntries
> 1 && nStartRow
>= rMarkEntries
[nEntries
-2].nRow
+1
298 && nStartRow
<= rMarkEntries
[nEntries
-1].nRow
+1)
300 // overlaps or directly adjacent previous range
301 rMarkEntries
.back().nRow
= std::max(nEndRow
, rMarkEntries
.back().nRow
);
307 rMarkEntries
.emplace_back(ScMarkEntry
{nStartRow
-1, false});
308 rMarkEntries
.emplace_back(ScMarkEntry
{nEndRow
, true});
311 nMaxCol
= std::max(nMaxCol
, nEndCol
);
315 aMultiSelContainer
.resize(nMaxCol
+1, ScMarkArray(mrSheetLimits
));
316 for (SCCOL nCol
= 0; nCol
<=nMaxCol
; ++nCol
)
317 if (!aMarkEntriesPerCol
[nCol
].empty())
318 aMultiSelContainer
[nCol
].Set( std::move(aMarkEntriesPerCol
[nCol
]) );
321 bool ScMultiSel::IsRowMarked( SCROW nRow
) const
323 return aRowSel
.GetMark( nRow
);
326 bool ScMultiSel::IsRowRangeMarked( SCROW nStartRow
, SCROW nEndRow
) const
328 if ( !aRowSel
.GetMark( nStartRow
) )
330 SCROW nLast
= aRowSel
.GetMarkEnd( nStartRow
, false );
331 return ( nLast
>= nEndRow
);
334 ScMarkArray
ScMultiSel::GetMarkArray( SCCOL nCol
) const
336 ScMultiSelIter
aMultiIter( *this, nCol
);
337 ScMarkArray
aMarkArray(mrSheetLimits
);
339 while( aMultiIter
.Next( nTop
, nBottom
) )
340 aMarkArray
.SetMarkArea( nTop
, nBottom
, true );
344 bool ScMultiSel::HasAnyMarks() const
346 if ( aRowSel
.HasMarks() )
348 for ( const auto& aPair
: aMultiSelContainer
)
349 if ( aPair
.HasMarks() )
354 void ScMultiSel::ShiftCols(SCCOL nStartCol
, sal_Int32 nColOffset
)
356 if (nStartCol
> mrSheetLimits
.mnMaxCol
|| nStartCol
>= static_cast<SCCOL
>(aMultiSelContainer
.size()))
361 aMultiSelContainer
.insert(aMultiSelContainer
.begin() + nStartCol
, nColOffset
, ScMarkArray(mrSheetLimits
));
365 sal_Int32 tempOffset
= nStartCol
- nColOffset
>= static_cast<SCCOL
>(aMultiSelContainer
.size()) ? static_cast<SCCOL
>(aMultiSelContainer
.size()) - nStartCol
-1: -1 * nColOffset
;
366 aMultiSelContainer
.erase(aMultiSelContainer
.begin() + nStartCol
, aMultiSelContainer
.begin() + nStartCol
+ tempOffset
);
370 void ScMultiSel::ShiftRows(SCROW nStartRow
, sal_Int32 nRowOffset
)
372 for (auto& aPair
: aMultiSelContainer
)
373 aPair
.Shift(nStartRow
, nRowOffset
);
374 aRowSel
.Shift(nStartRow
, nRowOffset
);
377 const ScMarkArray
* ScMultiSel::GetMultiSelArray( SCCOL nCol
) const
379 if (nCol
>= static_cast<SCCOL
>(aMultiSelContainer
.size()))
381 return &aMultiSelContainer
[nCol
];
384 ScMultiSelIter::ScMultiSelIter( const ScMultiSel
& rMultiSel
, SCCOL nCol
) :
385 aMarkArrayIter(nullptr),
388 bool bHasMarks1
= rMultiSel
.aRowSel
.HasMarks();
389 bool bHasMarks2
= nCol
< static_cast<SCCOL
>(rMultiSel
.aMultiSelContainer
.size())
390 && rMultiSel
.aMultiSelContainer
[nCol
].HasMarks();
392 if (bHasMarks1
&& bHasMarks2
)
394 pRowSegs
.reset( new ScFlatBoolRowSegments(rMultiSel
.mrSheetLimits
.mnMaxRow
) );
395 pRowSegs
->setFalse( 0, rMultiSel
.mrSheetLimits
.mnMaxRow
);
397 ScMarkArrayIter
aMarkIter( &rMultiSel
.aRowSel
);
399 while ( aMarkIter
.Next( nTop
, nBottom
) )
400 pRowSegs
->setTrue( nTop
, nBottom
);
404 ScMarkArrayIter
aMarkIter( &rMultiSel
.aMultiSelContainer
[nCol
] );
406 while ( aMarkIter
.Next( nTop
, nBottom
) )
407 pRowSegs
->setTrue( nTop
, nBottom
);
412 aMarkArrayIter
.reset( &rMultiSel
.aRowSel
);
416 aMarkArrayIter
.reset( &rMultiSel
.aMultiSelContainer
[nCol
]);
420 bool ScMultiSelIter::Next( SCROW
& rTop
, SCROW
& rBottom
)
424 ScFlatBoolRowSegments::RangeData aRowRange
;
425 bool bRet
= pRowSegs
->getRangeData( nNextSegmentStart
, aRowRange
);
426 if ( bRet
&& !aRowRange
.mbValue
)
428 nNextSegmentStart
= aRowRange
.mnRow2
+ 1;
429 bRet
= pRowSegs
->getRangeData( nNextSegmentStart
, aRowRange
);
433 rTop
= aRowRange
.mnRow1
;
434 rBottom
= aRowRange
.mnRow2
;
435 nNextSegmentStart
= rBottom
+ 1;
440 return aMarkArrayIter
.Next( rTop
, rBottom
);
443 bool ScMultiSelIter::GetRangeData( SCROW nRow
, ScFlatBoolRowSegments::RangeData
& rRowRange
) const
446 return pRowSegs
->getRangeData( nRow
, rRowRange
);
450 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */