946 lines
32 KiB
C++
946 lines
32 KiB
C++
/* -*- 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 <memory>
|
|
#include <markdata.hxx>
|
|
#include <markarr.hxx>
|
|
#include <markmulti.hxx>
|
|
#include <rangelst.hxx>
|
|
#include <segmenttree.hxx>
|
|
#include <sheetlimits.hxx>
|
|
#include <document.hxx>
|
|
#include <columnspanset.hxx>
|
|
#include <fstalgorithm.hxx>
|
|
#include <unordered_map>
|
|
|
|
#include <osl/diagnose.h>
|
|
|
|
#include <mdds/flat_segment_tree.hpp>
|
|
#include <cassert>
|
|
|
|
|
|
ScMarkData::ScMarkData(const ScSheetLimits& rSheetLimits) :
|
|
aMultiSel(rSheetLimits),
|
|
mrSheetLimits(rSheetLimits)
|
|
{
|
|
ResetMark();
|
|
}
|
|
|
|
ScMarkData& ScMarkData::operator=(const ScMarkData& rOther)
|
|
{
|
|
maTabMarked = rOther.maTabMarked;
|
|
aMarkRange = rOther.aMarkRange;
|
|
aMultiRange = rOther.aMultiRange;
|
|
aMultiSel = rOther.aMultiSel;
|
|
aTopEnvelope = rOther.aTopEnvelope;
|
|
aBottomEnvelope = rOther.aBottomEnvelope;
|
|
aLeftEnvelope = rOther.aLeftEnvelope;
|
|
aRightEnvelope = rOther.aRightEnvelope;
|
|
bMarked = rOther.bMarked;
|
|
bMultiMarked = rOther.bMultiMarked;
|
|
bMarking = rOther.bMarking;
|
|
bMarkIsNeg = rOther.bMarkIsNeg;
|
|
return *this;
|
|
}
|
|
|
|
ScMarkData& ScMarkData::operator=(ScMarkData&& rOther)
|
|
{
|
|
maTabMarked = std::move(rOther.maTabMarked);
|
|
aMarkRange = std::move(rOther.aMarkRange);
|
|
aMultiRange = std::move(rOther.aMultiRange);
|
|
aMultiSel = std::move(rOther.aMultiSel);
|
|
aTopEnvelope = std::move(rOther.aTopEnvelope);
|
|
aBottomEnvelope = std::move(rOther.aBottomEnvelope);
|
|
aLeftEnvelope = std::move(rOther.aLeftEnvelope);
|
|
aRightEnvelope = std::move(rOther.aRightEnvelope);
|
|
bMarked = rOther.bMarked;
|
|
bMultiMarked = rOther.bMultiMarked;
|
|
bMarking = rOther.bMarking;
|
|
bMarkIsNeg = rOther.bMarkIsNeg;
|
|
return *this;
|
|
}
|
|
|
|
|
|
void ScMarkData::ResetMark()
|
|
{
|
|
aMultiSel.Clear();
|
|
|
|
bMarked = bMultiMarked = false;
|
|
bMarking = bMarkIsNeg = false;
|
|
aTopEnvelope.RemoveAll();
|
|
aBottomEnvelope.RemoveAll();
|
|
aLeftEnvelope.RemoveAll();
|
|
aRightEnvelope.RemoveAll();
|
|
}
|
|
|
|
void ScMarkData::SetMarkArea( const ScRange& rRange )
|
|
{
|
|
aMarkRange = rRange;
|
|
aMarkRange.PutInOrder();
|
|
if ( !bMarked )
|
|
{
|
|
// Upon creation of a document ScFormatShell GetTextAttrState
|
|
// may query (default) attributes although no sheet is marked yet.
|
|
// => mark that one.
|
|
if ( !GetSelectCount() )
|
|
maTabMarked.insert( aMarkRange.aStart.Tab() );
|
|
bMarked = true;
|
|
}
|
|
}
|
|
|
|
void ScMarkData::SetMultiMarkArea( const ScRange& rRange, bool bMark, bool bSetupMulti )
|
|
{
|
|
if ( aMultiSel.IsEmpty() )
|
|
{
|
|
// if simple mark range is set, copy to multi marks
|
|
if ( bMarked && !bMarkIsNeg && !bSetupMulti )
|
|
{
|
|
bMarked = false;
|
|
SCCOL nStartCol = aMarkRange.aStart.Col();
|
|
SCCOL nEndCol = aMarkRange.aEnd.Col();
|
|
PutInOrder( nStartCol, nEndCol );
|
|
SetMultiMarkArea( aMarkRange, true, true );
|
|
}
|
|
}
|
|
|
|
SCCOL nStartCol = rRange.aStart.Col();
|
|
SCROW nStartRow = rRange.aStart.Row();
|
|
SCCOL nEndCol = rRange.aEnd.Col();
|
|
SCROW nEndRow = rRange.aEnd.Row();
|
|
PutInOrder( nStartRow, nEndRow );
|
|
PutInOrder( nStartCol, nEndCol );
|
|
|
|
aMultiSel.SetMarkArea( nStartCol, nEndCol, nStartRow, nEndRow, bMark );
|
|
|
|
if ( bMultiMarked ) // Update aMultiRange
|
|
{
|
|
if ( nStartCol < aMultiRange.aStart.Col() )
|
|
aMultiRange.aStart.SetCol( nStartCol );
|
|
if ( nStartRow < aMultiRange.aStart.Row() )
|
|
aMultiRange.aStart.SetRow( nStartRow );
|
|
if ( nEndCol > aMultiRange.aEnd.Col() )
|
|
aMultiRange.aEnd.SetCol( nEndCol );
|
|
if ( nEndRow > aMultiRange.aEnd.Row() )
|
|
aMultiRange.aEnd.SetRow( nEndRow );
|
|
}
|
|
else
|
|
{
|
|
aMultiRange = rRange; // new
|
|
bMultiMarked = true;
|
|
}
|
|
}
|
|
|
|
void ScMarkData::SetAreaTab( SCTAB nTab )
|
|
{
|
|
aMarkRange.aStart.SetTab(nTab);
|
|
aMarkRange.aEnd.SetTab(nTab);
|
|
aMultiRange.aStart.SetTab(nTab);
|
|
aMultiRange.aEnd.SetTab(nTab);
|
|
}
|
|
|
|
void ScMarkData::SelectTable( SCTAB nTab, bool bNew )
|
|
{
|
|
if ( bNew )
|
|
{
|
|
maTabMarked.insert( nTab );
|
|
}
|
|
else
|
|
{
|
|
maTabMarked.erase( nTab );
|
|
}
|
|
}
|
|
|
|
bool ScMarkData::GetTableSelect( SCTAB nTab ) const
|
|
{
|
|
return (maTabMarked.find( nTab ) != maTabMarked.end());
|
|
}
|
|
|
|
void ScMarkData::SelectOneTable( SCTAB nTab )
|
|
{
|
|
maTabMarked.clear();
|
|
maTabMarked.insert( nTab );
|
|
}
|
|
|
|
SCTAB ScMarkData::GetSelectCount() const
|
|
{
|
|
return static_cast<SCTAB> ( maTabMarked.size() );
|
|
}
|
|
|
|
SCTAB ScMarkData::GetFirstSelected() const
|
|
{
|
|
if (!maTabMarked.empty())
|
|
return (*maTabMarked.begin());
|
|
|
|
OSL_FAIL("GetFirstSelected: nothing selected");
|
|
return 0;
|
|
}
|
|
|
|
SCTAB ScMarkData::GetLastSelected() const
|
|
{
|
|
if (!maTabMarked.empty())
|
|
return (*maTabMarked.rbegin());
|
|
|
|
OSL_FAIL("GetLastSelected: nothing selected");
|
|
return 0;
|
|
}
|
|
|
|
void ScMarkData::SetSelectedTabs(const MarkedTabsType& rTabs)
|
|
{
|
|
MarkedTabsType aTabs(rTabs.begin(), rTabs.end());
|
|
maTabMarked.swap(aTabs);
|
|
}
|
|
|
|
void ScMarkData::MarkToMulti()
|
|
{
|
|
if ( bMarked && !bMarking )
|
|
{
|
|
SetMultiMarkArea( aMarkRange, !bMarkIsNeg );
|
|
bMarked = false;
|
|
|
|
// check if all multi mark ranges have been removed
|
|
if ( bMarkIsNeg && !HasAnyMultiMarks() )
|
|
ResetMark();
|
|
}
|
|
}
|
|
|
|
void ScMarkData::MarkToSimple()
|
|
{
|
|
if ( bMarking )
|
|
return;
|
|
|
|
if ( bMultiMarked && bMarked )
|
|
MarkToMulti(); // may result in bMarked and bMultiMarked reset
|
|
|
|
if ( !bMultiMarked )
|
|
return;
|
|
|
|
ScRange aNew = aMultiRange;
|
|
|
|
bool bOk = false;
|
|
SCCOL nStartCol = aNew.aStart.Col();
|
|
SCCOL nEndCol = aNew.aEnd.Col();
|
|
|
|
while ( nStartCol < nEndCol && !aMultiSel.HasMarks( nStartCol ) )
|
|
++nStartCol;
|
|
while ( nStartCol < nEndCol && !aMultiSel.HasMarks( nEndCol ) )
|
|
--nEndCol;
|
|
|
|
// Rows are only taken from MarkArray
|
|
SCROW nStartRow, nEndRow;
|
|
if ( aMultiSel.HasOneMark( nStartCol, nStartRow, nEndRow ) )
|
|
{
|
|
bOk = true;
|
|
SCROW nCmpStart, nCmpEnd;
|
|
for (SCCOL nCol=nStartCol+1; nCol<=nEndCol && bOk; nCol++)
|
|
if ( !aMultiSel.HasOneMark( nCol, nCmpStart, nCmpEnd )
|
|
|| nCmpStart != nStartRow || nCmpEnd != nEndRow )
|
|
bOk = false;
|
|
}
|
|
|
|
if (bOk)
|
|
{
|
|
aNew.aStart.SetCol(nStartCol);
|
|
aNew.aStart.SetRow(nStartRow);
|
|
aNew.aEnd.SetCol(nEndCol);
|
|
aNew.aEnd.SetRow(nEndRow);
|
|
|
|
ResetMark();
|
|
aMarkRange = aNew;
|
|
bMarked = true;
|
|
bMarkIsNeg = false;
|
|
}
|
|
}
|
|
|
|
bool ScMarkData::IsCellMarked( SCCOL nCol, SCROW nRow, bool bNoSimple ) const
|
|
{
|
|
if ( bMarked && !bNoSimple && !bMarkIsNeg )
|
|
if ( aMarkRange.aStart.Col() <= nCol && aMarkRange.aEnd.Col() >= nCol &&
|
|
aMarkRange.aStart.Row() <= nRow && aMarkRange.aEnd.Row() >= nRow )
|
|
return true;
|
|
|
|
if (bMultiMarked)
|
|
{
|
|
//TODO: test here for negative Marking ?
|
|
|
|
return aMultiSel.GetMark( nCol, nRow );
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ScMarkData::IsColumnMarked( SCCOL nCol ) const
|
|
{
|
|
// bMarkIsNeg meanwhile also for columns heads
|
|
//TODO: GetMarkColumnRanges for completely marked column
|
|
|
|
if ( bMarked && !bMarkIsNeg &&
|
|
aMarkRange.aStart.Col() <= nCol && aMarkRange.aEnd.Col() >= nCol &&
|
|
aMarkRange.aStart.Row() == 0 && aMarkRange.aEnd.Row() == mrSheetLimits.mnMaxRow )
|
|
return true;
|
|
|
|
if ( bMultiMarked && aMultiSel.IsAllMarked( nCol, 0, mrSheetLimits.mnMaxRow ) )
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ScMarkData::IsRowMarked( SCROW nRow ) const
|
|
{
|
|
// bMarkIsNeg meanwhile also for row heads
|
|
//TODO: GetMarkRowRanges for completely marked rows
|
|
|
|
if ( bMarked && !bMarkIsNeg &&
|
|
aMarkRange.aStart.Col() == 0 && aMarkRange.aEnd.Col() == mrSheetLimits.mnMaxCol &&
|
|
aMarkRange.aStart.Row() <= nRow && aMarkRange.aEnd.Row() >= nRow )
|
|
return true;
|
|
|
|
if ( bMultiMarked )
|
|
return aMultiSel.IsRowMarked( nRow );
|
|
|
|
return false;
|
|
}
|
|
|
|
void ScMarkData::MarkFromRangeList( const ScRangeList& rList, bool bReset )
|
|
{
|
|
if (bReset)
|
|
{
|
|
maTabMarked.clear();
|
|
ResetMark();
|
|
}
|
|
|
|
size_t nCount = rList.size();
|
|
if ( nCount == 1 && !bMarked && !bMultiMarked )
|
|
{
|
|
const ScRange& rRange = rList[ 0 ];
|
|
SetMarkArea( rRange );
|
|
SelectTable( rRange.aStart.Tab(), true );
|
|
}
|
|
else
|
|
{
|
|
for (size_t i=0; i < nCount; i++)
|
|
{
|
|
const ScRange& rRange = rList[ i ];
|
|
SetMultiMarkArea( rRange );
|
|
SelectTable( rRange.aStart.Tab(), true );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Optimise the case of constructing from a range list, speeds up import.
|
|
*/
|
|
ScMarkData::ScMarkData(const ScSheetLimits& rLimits, const ScRangeList& rList)
|
|
: aMultiSel(rLimits),
|
|
mrSheetLimits(rLimits)
|
|
{
|
|
ResetMark();
|
|
|
|
for (const ScRange& rRange : rList)
|
|
maTabMarked.insert( rRange.aStart.Tab() );
|
|
|
|
if (rList.size() > 1)
|
|
{
|
|
bMultiMarked = true;
|
|
aMultiRange = rList.Combine();
|
|
|
|
aMultiSel.Set( rList );
|
|
}
|
|
else if (rList.size() == 1)
|
|
{
|
|
const ScRange& rRange = rList[ 0 ];
|
|
SetMarkArea( rRange );
|
|
}
|
|
}
|
|
|
|
|
|
void ScMarkData::FillRangeListWithMarks( ScRangeList* pList, bool bClear, SCTAB nForTab ) const
|
|
{
|
|
if (!pList)
|
|
return;
|
|
|
|
if (bClear)
|
|
pList->RemoveAll();
|
|
|
|
//TODO: for multiple selected tables enter multiple ranges !!!
|
|
|
|
if ( bMultiMarked )
|
|
{
|
|
SCTAB nTab = (nForTab < 0 ? aMultiRange.aStart.Tab() : nForTab);
|
|
|
|
SCCOL nStartCol = aMultiRange.aStart.Col();
|
|
SCCOL nEndCol = aMultiRange.aEnd.Col();
|
|
for (SCCOL nCol=nStartCol; nCol<=nEndCol; nCol++)
|
|
{
|
|
if (aMultiSel.HasMarks( nCol ))
|
|
{
|
|
// Feeding column-wise fragments to ScRangeList::Join() is a
|
|
// huge bottleneck, speed this up for multiple columns
|
|
// consisting of identical row sets by building a column span
|
|
// first. This is usually the case for filtered data, for
|
|
// example.
|
|
SCCOL nToCol = nCol+1;
|
|
for ( ; nToCol <= nEndCol; ++nToCol)
|
|
{
|
|
if (!aMultiSel.HasEqualRowsMarked(nCol, nToCol))
|
|
break;
|
|
}
|
|
--nToCol;
|
|
ScRange aRange( nCol, 0, nTab, nToCol, 0, nTab );
|
|
SCROW nTop, nBottom;
|
|
ScMultiSelIter aMultiIter( aMultiSel, nCol );
|
|
while ( aMultiIter.Next( nTop, nBottom ) )
|
|
{
|
|
aRange.aStart.SetRow( nTop );
|
|
aRange.aEnd.SetRow( nBottom );
|
|
pList->Join( aRange );
|
|
}
|
|
nCol = nToCol;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( bMarked )
|
|
{
|
|
if (nForTab < 0)
|
|
pList->push_back( aMarkRange );
|
|
else
|
|
{
|
|
ScRange aRange( aMarkRange );
|
|
aRange.aStart.SetTab( nForTab );
|
|
aRange.aEnd.SetTab( nForTab );
|
|
pList->push_back( aRange );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScMarkData::ExtendRangeListTables( ScRangeList* pList ) const
|
|
{
|
|
if (!pList)
|
|
return;
|
|
|
|
ScRangeList aOldList(*pList);
|
|
pList->RemoveAll(); //TODO: or skip the existing below
|
|
|
|
for (const auto& rTab : maTabMarked)
|
|
for ( size_t i=0, nCount = aOldList.size(); i<nCount; i++)
|
|
{
|
|
ScRange aRange = aOldList[ i ];
|
|
aRange.aStart.SetTab(rTab);
|
|
aRange.aEnd.SetTab(rTab);
|
|
pList->push_back( aRange );
|
|
}
|
|
}
|
|
|
|
ScRangeList ScMarkData::GetMarkedRanges() const
|
|
{
|
|
ScRangeList aRet;
|
|
FillRangeListWithMarks(&aRet, false);
|
|
return aRet;
|
|
}
|
|
|
|
ScRangeList ScMarkData::GetMarkedRangesForTab( SCTAB nTab ) const
|
|
{
|
|
ScRangeList aRet;
|
|
FillRangeListWithMarks(&aRet, false, nTab);
|
|
return aRet;
|
|
}
|
|
|
|
std::vector<sc::ColRowSpan> ScMarkData::GetMarkedRowSpans() const
|
|
{
|
|
typedef mdds::flat_segment_tree<SCCOLROW, bool> SpansType;
|
|
|
|
ScRangeList aRanges = GetMarkedRanges();
|
|
SpansType aSpans(0, mrSheetLimits.mnMaxRow+1, false);
|
|
SpansType::const_iterator itPos = aSpans.begin();
|
|
|
|
for (size_t i = 0, n = aRanges.size(); i < n; ++i)
|
|
{
|
|
const ScRange& r = aRanges[i];
|
|
itPos = aSpans.insert(itPos, r.aStart.Row(), r.aEnd.Row()+1, true).first;
|
|
}
|
|
|
|
return sc::toSpanArray<SCCOLROW,sc::ColRowSpan>(aSpans);
|
|
}
|
|
|
|
std::vector<sc::ColRowSpan> ScMarkData::GetMarkedColSpans() const
|
|
{
|
|
|
|
if (bMultiMarked)
|
|
{
|
|
SCCOL nStartCol = aMultiRange.aStart.Col();
|
|
SCCOL nEndCol = aMultiRange.aEnd.Col();
|
|
if (bMarked)
|
|
{
|
|
// Use segment tree to merge marked with multi marked.
|
|
typedef mdds::flat_segment_tree<SCCOLROW, bool> SpansType;
|
|
SpansType aSpans(0, mrSheetLimits.mnMaxCol+1, false);
|
|
SpansType::const_iterator itPos = aSpans.begin();
|
|
do
|
|
{
|
|
if (aMultiSel.GetRowSelArray().HasMarks())
|
|
{
|
|
itPos = aSpans.insert(itPos, nStartCol, nEndCol+1, true).first;
|
|
break; // do; all columns marked
|
|
}
|
|
|
|
/* XXX if it turns out that span insert is too slow for lots of
|
|
* subsequent columns we could gather each span first and then
|
|
* insert. */
|
|
for (SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol)
|
|
{
|
|
const ScMarkArray* pMultiArray = aMultiSel.GetMultiSelArray( nCol );
|
|
if (pMultiArray && pMultiArray->HasMarks())
|
|
itPos = aSpans.insert(itPos, nCol, nCol+1, true).first;
|
|
}
|
|
}
|
|
while(false);
|
|
|
|
// Merge marked.
|
|
aSpans.insert(itPos, aMarkRange.aStart.Col(), aMarkRange.aEnd.Col()+1, true);
|
|
|
|
return sc::toSpanArray<SCCOLROW,sc::ColRowSpan>(aSpans);
|
|
}
|
|
else
|
|
{
|
|
// A plain vector is sufficient, avoid segment tree and conversion
|
|
// to vector overhead.
|
|
std::vector<sc::ColRowSpan> aVec;
|
|
if (aMultiSel.GetRowSelArray().HasMarks())
|
|
{
|
|
aVec.emplace_back( nStartCol, nEndCol);
|
|
return aVec; // all columns marked
|
|
}
|
|
sc::ColRowSpan aSpan( -1, -1);
|
|
for (SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol)
|
|
{
|
|
const ScMarkArray* pMultiArray = aMultiSel.GetMultiSelArray( nCol );
|
|
if (pMultiArray && pMultiArray->HasMarks())
|
|
{
|
|
if (aSpan.mnStart == -1)
|
|
aSpan.mnStart = nCol;
|
|
aSpan.mnEnd = nCol;
|
|
}
|
|
else
|
|
{
|
|
// Add span gathered so far, if any.
|
|
if (aSpan.mnStart != -1)
|
|
{
|
|
aVec.push_back( aSpan);
|
|
aSpan.mnStart = -1;
|
|
}
|
|
}
|
|
}
|
|
// Add last span, if any.
|
|
if (aSpan.mnStart != -1)
|
|
aVec.push_back( aSpan);
|
|
return aVec;
|
|
}
|
|
}
|
|
|
|
// Only reached if not multi marked.
|
|
std::vector<sc::ColRowSpan> aVec;
|
|
if (bMarked)
|
|
{
|
|
aVec.emplace_back( aMarkRange.aStart.Col(), aMarkRange.aEnd.Col());
|
|
}
|
|
return aVec;
|
|
}
|
|
|
|
bool ScMarkData::IsAllMarked( const ScRange& rRange ) const
|
|
{
|
|
SCCOL nStartCol = rRange.aStart.Col();
|
|
SCROW nStartRow = rRange.aStart.Row();
|
|
SCCOL nEndCol = rRange.aEnd.Col();
|
|
SCROW nEndRow = rRange.aEnd.Row();
|
|
|
|
if ( !bMultiMarked )
|
|
{
|
|
if ( bMarked && !bMarkIsNeg &&
|
|
aMarkRange.aStart.Col() <= nStartCol && aMarkRange.aEnd.Col() >= nEndCol &&
|
|
aMarkRange.aStart.Row() <= nStartRow && aMarkRange.aEnd.Row() >= nEndRow )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool bOk = true;
|
|
|
|
if ( nStartCol == 0 && nEndCol == mrSheetLimits.mnMaxCol )
|
|
return aMultiSel.IsRowRangeMarked( nStartRow, nEndRow );
|
|
|
|
for (SCCOL nCol=nStartCol; nCol<=nEndCol && bOk; nCol++)
|
|
if ( !aMultiSel.IsAllMarked( nCol, nStartRow, nEndRow ) )
|
|
bOk = false;
|
|
|
|
return bOk;
|
|
}
|
|
|
|
SCCOL ScMarkData::GetStartOfEqualColumns( SCCOL nLastCol, SCCOL nMinCol ) const
|
|
{
|
|
if( !bMultiMarked )
|
|
{
|
|
if ( bMarked && !bMarkIsNeg )
|
|
{
|
|
if( aMarkRange.aEnd.Col() >= nMinCol && aMarkRange.aStart.Col() < nLastCol )
|
|
return aMarkRange.aEnd.Col() + 1;
|
|
if( aMarkRange.aEnd.Col() >= nLastCol && aMarkRange.aStart.Col() <= nMinCol )
|
|
return aMarkRange.aStart.Col();
|
|
}
|
|
return nMinCol;
|
|
}
|
|
return aMultiSel.GetStartOfEqualColumns( nLastCol, nMinCol );
|
|
}
|
|
|
|
SCROW ScMarkData::GetNextMarked( SCCOL nCol, SCROW nRow, bool bUp ) const
|
|
{
|
|
if ( !bMultiMarked )
|
|
return nRow;
|
|
|
|
return aMultiSel.GetNextMarked( nCol, nRow, bUp );
|
|
}
|
|
|
|
bool ScMarkData::HasMultiMarks( SCCOL nCol ) const
|
|
{
|
|
if ( !bMultiMarked )
|
|
return false;
|
|
|
|
return aMultiSel.HasMarks( nCol );
|
|
}
|
|
|
|
bool ScMarkData::HasAnyMultiMarks() const
|
|
{
|
|
if ( !bMultiMarked )
|
|
return false;
|
|
|
|
return aMultiSel.HasAnyMarks();
|
|
}
|
|
|
|
void ScMarkData::InsertTab( SCTAB nTab )
|
|
{
|
|
std::set<SCTAB> tabMarked;
|
|
for (const auto& rTab : maTabMarked)
|
|
{
|
|
if (rTab < nTab)
|
|
tabMarked.insert(rTab);
|
|
else
|
|
tabMarked.insert(rTab + 1);
|
|
}
|
|
maTabMarked.swap(tabMarked);
|
|
}
|
|
|
|
void ScMarkData::DeleteTab( SCTAB nTab )
|
|
{
|
|
std::set<SCTAB> tabMarked;
|
|
for (const auto& rTab : maTabMarked)
|
|
{
|
|
if (rTab < nTab)
|
|
tabMarked.insert(rTab);
|
|
else if (rTab > nTab)
|
|
tabMarked.insert(rTab - 1);
|
|
}
|
|
maTabMarked.swap(tabMarked);
|
|
}
|
|
|
|
void ScMarkData::ShiftCols(const ScDocument& rDoc, SCCOL nStartCol, sal_Int32 nColOffset)
|
|
{
|
|
if (bMarked)
|
|
aMarkRange.IncColIfNotLessThan(rDoc, nStartCol, nColOffset);
|
|
if (bMultiMarked)
|
|
{
|
|
aMultiRange.IncColIfNotLessThan(rDoc, nStartCol, nColOffset);
|
|
aMultiSel.ShiftCols(nStartCol, nColOffset);
|
|
}
|
|
}
|
|
|
|
void ScMarkData::ShiftRows(const ScDocument& rDoc, SCROW nStartRow, sal_Int32 nRowOffset)
|
|
{
|
|
if (bMarked)
|
|
aMarkRange.IncRowIfNotLessThan(rDoc, nStartRow, nRowOffset);
|
|
if (bMultiMarked)
|
|
{
|
|
aMultiRange.IncRowIfNotLessThan(rDoc, nStartRow, nRowOffset);
|
|
aMultiSel.ShiftRows(nStartRow, nRowOffset);
|
|
}
|
|
}
|
|
|
|
static void lcl_AddRanges(ScRange& rRangeDest, const ScRange& rNewRange )
|
|
{
|
|
SCCOL nStartCol = rNewRange.aStart.Col();
|
|
SCROW nStartRow = rNewRange.aStart.Row();
|
|
SCCOL nEndCol = rNewRange.aEnd.Col();
|
|
SCROW nEndRow = rNewRange.aEnd.Row();
|
|
PutInOrder( nStartRow, nEndRow );
|
|
PutInOrder( nStartCol, nEndCol );
|
|
if ( nStartCol < rRangeDest.aStart.Col() )
|
|
rRangeDest.aStart.SetCol( nStartCol );
|
|
if ( nStartRow < rRangeDest.aStart.Row() )
|
|
rRangeDest.aStart.SetRow( nStartRow );
|
|
if ( nEndCol > rRangeDest.aEnd.Col() )
|
|
rRangeDest.aEnd.SetCol( nEndCol );
|
|
if ( nEndRow > rRangeDest.aEnd.Row() )
|
|
rRangeDest.aEnd.SetRow( nEndRow );
|
|
}
|
|
|
|
void ScMarkData::GetSelectionCover( ScRange& rRange )
|
|
{
|
|
if( bMultiMarked )
|
|
{
|
|
rRange = aMultiRange;
|
|
SCCOL nStartCol = aMultiRange.aStart.Col(), nEndCol = aMultiRange.aEnd.Col();
|
|
PutInOrder( nStartCol, nEndCol );
|
|
nStartCol = ( nStartCol == 0 ) ? nStartCol : nStartCol - 1;
|
|
nEndCol = ( nEndCol == mrSheetLimits.mnMaxCol ) ? nEndCol : nEndCol + 1;
|
|
std::unique_ptr<ScFlatBoolRowSegments> pPrevColMarkedRows;
|
|
std::unique_ptr<ScFlatBoolRowSegments> pCurColMarkedRows;
|
|
std::unordered_map<SCROW,ScFlatBoolColSegments> aRowToColSegmentsInTopEnvelope;
|
|
std::unordered_map<SCROW,ScFlatBoolColSegments> aRowToColSegmentsInBottomEnvelope;
|
|
ScFlatBoolRowSegments aNoRowsMarked(mrSheetLimits.mnMaxRow);
|
|
aNoRowsMarked.setFalse( 0, mrSheetLimits.mnMaxRow );
|
|
|
|
bool bPrevColUnMarked = false;
|
|
|
|
for ( SCCOL nCol=nStartCol; nCol <= nEndCol; nCol++ )
|
|
{
|
|
SCROW nTop, nBottom;
|
|
bool bCurColUnMarked = !aMultiSel.HasMarks( nCol );
|
|
if ( !bCurColUnMarked )
|
|
{
|
|
pCurColMarkedRows.reset( new ScFlatBoolRowSegments(mrSheetLimits.mnMaxRow) );
|
|
pCurColMarkedRows->setFalse( 0, mrSheetLimits.mnMaxRow );
|
|
ScMultiSelIter aMultiIter( aMultiSel, nCol );
|
|
ScFlatBoolRowSegments::ForwardIterator aPrevItr(
|
|
pPrevColMarkedRows ? *pPrevColMarkedRows
|
|
: aNoRowsMarked); // For finding left envelope
|
|
ScFlatBoolRowSegments::ForwardIterator aPrevItr1(
|
|
pPrevColMarkedRows ? *pPrevColMarkedRows
|
|
: aNoRowsMarked); // For finding right envelope
|
|
SCROW nTopPrev = 0, nBottomPrev = 0; // For right envelope
|
|
while ( aMultiIter.Next( nTop, nBottom ) )
|
|
{
|
|
pCurColMarkedRows->setTrue( nTop, nBottom );
|
|
if( bPrevColUnMarked && ( nCol > nStartCol ))
|
|
{
|
|
ScRange aAddRange(nCol - 1, nTop, aMultiRange.aStart.Tab(),
|
|
nCol - 1, nBottom, aMultiRange.aStart.Tab());
|
|
lcl_AddRanges( rRange, aAddRange ); // Left envelope
|
|
aLeftEnvelope.push_back( aAddRange );
|
|
}
|
|
else if( nCol > nStartCol )
|
|
{
|
|
SCROW nTop1 = nTop, nBottom1 = nTop;
|
|
while( nTop1 <= nBottom && nBottom1 <= nBottom )
|
|
{
|
|
bool bRangeMarked = false;
|
|
const bool bHasValue = aPrevItr.getValue( nTop1, bRangeMarked );
|
|
assert(bHasValue); (void)bHasValue;
|
|
if( bRangeMarked )
|
|
{
|
|
nTop1 = aPrevItr.getLastPos() + 1;
|
|
nBottom1 = nTop1;
|
|
}
|
|
else
|
|
{
|
|
nBottom1 = aPrevItr.getLastPos();
|
|
if( nBottom1 > nBottom )
|
|
nBottom1 = nBottom;
|
|
ScRange aAddRange( nCol - 1, nTop1, aMultiRange.aStart.Tab(),
|
|
nCol - 1, nBottom1, aMultiRange.aStart.Tab() );
|
|
lcl_AddRanges( rRange, aAddRange ); // Left envelope
|
|
aLeftEnvelope.push_back( aAddRange );
|
|
nTop1 = ++nBottom1;
|
|
}
|
|
}
|
|
while( nTopPrev <= nBottom && nBottomPrev <= nBottom )
|
|
{
|
|
bool bRangeMarked;
|
|
const bool bHasValue = aPrevItr1.getValue( nTopPrev, bRangeMarked );
|
|
assert(bHasValue); (void)bHasValue;
|
|
if( bRangeMarked )
|
|
{
|
|
nBottomPrev = aPrevItr1.getLastPos();
|
|
if( nTopPrev < nTop )
|
|
{
|
|
if( nBottomPrev >= nTop )
|
|
{
|
|
nBottomPrev = nTop - 1;
|
|
ScRange aAddRange( nCol, nTopPrev, aMultiRange.aStart.Tab(),
|
|
nCol, nBottomPrev, aMultiRange.aStart.Tab());
|
|
lcl_AddRanges( rRange, aAddRange ); // Right envelope
|
|
aRightEnvelope.push_back( aAddRange );
|
|
nTopPrev = nBottomPrev = (nBottom + 1);
|
|
}
|
|
else
|
|
{
|
|
ScRange aAddRange( nCol, nTopPrev, aMultiRange.aStart.Tab(),
|
|
nCol, nBottomPrev, aMultiRange.aStart.Tab());
|
|
lcl_AddRanges( rRange, aAddRange ); // Right envelope
|
|
aRightEnvelope.push_back( aAddRange );
|
|
nTopPrev = ++nBottomPrev;
|
|
}
|
|
}
|
|
else
|
|
nTopPrev = nBottomPrev = ( nBottom + 1 );
|
|
}
|
|
else
|
|
{
|
|
nBottomPrev = aPrevItr1.getLastPos();
|
|
nTopPrev = ++nBottomPrev;
|
|
}
|
|
}
|
|
}
|
|
if( nTop )
|
|
{
|
|
ScRange aAddRange( nCol, nTop - 1, aMultiRange.aStart.Tab(),
|
|
nCol, nTop - 1, aMultiRange.aStart.Tab());
|
|
lcl_AddRanges( rRange, aAddRange ); // Top envelope
|
|
auto it = aRowToColSegmentsInTopEnvelope.find(nTop - 1);
|
|
if (it == aRowToColSegmentsInTopEnvelope.end())
|
|
it = aRowToColSegmentsInTopEnvelope.emplace(nTop - 1, ScFlatBoolColSegments(mrSheetLimits.mnMaxCol)).first;
|
|
it->second.setTrue( nCol, nCol );
|
|
}
|
|
if( nBottom < mrSheetLimits.mnMaxRow )
|
|
{
|
|
ScRange aAddRange(nCol, nBottom + 1, aMultiRange.aStart.Tab(),
|
|
nCol, nBottom + 1, aMultiRange.aStart.Tab());
|
|
lcl_AddRanges( rRange, aAddRange ); // Bottom envelope
|
|
auto it = aRowToColSegmentsInBottomEnvelope.find(nBottom + 1);
|
|
if (it == aRowToColSegmentsInBottomEnvelope.end())
|
|
it = aRowToColSegmentsInBottomEnvelope.emplace(nBottom + 1, ScFlatBoolColSegments(mrSheetLimits.mnMaxCol)).first;
|
|
it->second.setTrue( nCol, nCol );
|
|
}
|
|
}
|
|
|
|
while( nTopPrev <= mrSheetLimits.mnMaxRow && nBottomPrev <= mrSheetLimits.mnMaxRow && ( nCol > nStartCol ) )
|
|
{
|
|
bool bRangeMarked;
|
|
const bool bHasValue = aPrevItr1.getValue( nTopPrev, bRangeMarked );
|
|
assert(bHasValue); (void)bHasValue;
|
|
if( bRangeMarked )
|
|
{
|
|
nBottomPrev = aPrevItr1.getLastPos();
|
|
ScRange aAddRange(nCol, nTopPrev, aMultiRange.aStart.Tab(),
|
|
nCol, nBottomPrev, aMultiRange.aStart.Tab());
|
|
lcl_AddRanges( rRange, aAddRange ); // Right envelope
|
|
aRightEnvelope.push_back( aAddRange );
|
|
nTopPrev = ++nBottomPrev;
|
|
}
|
|
else
|
|
{
|
|
nBottomPrev = aPrevItr1.getLastPos();
|
|
nTopPrev = ++nBottomPrev;
|
|
}
|
|
}
|
|
}
|
|
else if( nCol > nStartCol )
|
|
{
|
|
bPrevColUnMarked = true;
|
|
SCROW nTopPrev = 0, nBottomPrev = 0;
|
|
bool bRangeMarked = false;
|
|
ScFlatBoolRowSegments::ForwardIterator aPrevItr(
|
|
pPrevColMarkedRows ? *pPrevColMarkedRows : aNoRowsMarked);
|
|
while( nTopPrev <= mrSheetLimits.mnMaxRow && nBottomPrev <= mrSheetLimits.mnMaxRow )
|
|
{
|
|
const bool bHasValue = aPrevItr.getValue(nTopPrev, bRangeMarked);
|
|
assert(bHasValue); (void)bHasValue;
|
|
if( bRangeMarked )
|
|
{
|
|
nBottomPrev = aPrevItr.getLastPos();
|
|
ScRange aAddRange(nCol, nTopPrev, aMultiRange.aStart.Tab(),
|
|
nCol, nBottomPrev, aMultiRange.aStart.Tab());
|
|
lcl_AddRanges( rRange, aAddRange ); // Right envelope
|
|
aRightEnvelope.push_back( aAddRange );
|
|
nTopPrev = ++nBottomPrev;
|
|
}
|
|
else
|
|
{
|
|
nBottomPrev = aPrevItr.getLastPos();
|
|
nTopPrev = ++nBottomPrev;
|
|
}
|
|
}
|
|
}
|
|
if ( bCurColUnMarked )
|
|
pPrevColMarkedRows.reset();
|
|
else
|
|
pPrevColMarkedRows = std::move( pCurColMarkedRows );
|
|
}
|
|
for( auto& rKV : aRowToColSegmentsInTopEnvelope )
|
|
{
|
|
SCCOL nStart = nStartCol;
|
|
ScFlatBoolColSegments::RangeData aRange;
|
|
while( nStart <= nEndCol )
|
|
{
|
|
if( !rKV.second.getRangeData( nStart, aRange ) )
|
|
break;
|
|
if( aRange.mbValue ) // is marked
|
|
aTopEnvelope.push_back( ScRange( aRange.mnCol1, rKV.first, aMultiRange.aStart.Tab(),
|
|
aRange.mnCol2, rKV.first, aMultiRange.aStart.Tab() ) );
|
|
nStart = aRange.mnCol2 + 1;
|
|
}
|
|
}
|
|
for( auto& rKV : aRowToColSegmentsInBottomEnvelope )
|
|
{
|
|
SCCOL nStart = nStartCol;
|
|
ScFlatBoolColSegments::RangeData aRange;
|
|
while( nStart <= nEndCol )
|
|
{
|
|
if( !rKV.second.getRangeData( nStart, aRange ) )
|
|
break;
|
|
if( aRange.mbValue ) // is marked
|
|
aBottomEnvelope.push_back( ScRange( aRange.mnCol1, rKV.first, aMultiRange.aStart.Tab(),
|
|
aRange.mnCol2, rKV.first, aMultiRange.aStart.Tab() ) );
|
|
nStart = aRange.mnCol2 + 1;
|
|
}
|
|
}
|
|
}
|
|
else if( bMarked )
|
|
{
|
|
aMarkRange.PutInOrder();
|
|
SCROW nRow1, nRow2, nRow1New, nRow2New;
|
|
SCCOL nCol1, nCol2, nCol1New, nCol2New;
|
|
SCTAB nTab1, nTab2;
|
|
aMarkRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2 );
|
|
nCol1New = nCol1;
|
|
nCol2New = nCol2;
|
|
nRow1New = nRow1;
|
|
nRow2New = nRow2;
|
|
// Each envelope will have zero or more ranges for single rectangle selection.
|
|
if( nCol1 > 0 )
|
|
{
|
|
aLeftEnvelope.push_back( ScRange( nCol1 - 1, nRow1, nTab1, nCol1 - 1, nRow2, nTab2 ) );
|
|
--nCol1New;
|
|
}
|
|
if( nRow1 > 0 )
|
|
{
|
|
aTopEnvelope.push_back( ScRange( nCol1, nRow1 - 1, nTab1, nCol2, nRow1 - 1, nTab2 ) );
|
|
--nRow1New;
|
|
}
|
|
if( nCol2 < mrSheetLimits.mnMaxCol )
|
|
{
|
|
aRightEnvelope.push_back( ScRange( nCol2 + 1, nRow1, nTab1, nCol2 + 1, nRow2, nTab2 ) );
|
|
++nCol2New;
|
|
}
|
|
if( nRow2 < mrSheetLimits.mnMaxRow )
|
|
{
|
|
aBottomEnvelope.push_back( ScRange( nCol1, nRow2 + 1, nTab1, nCol2, nRow2 + 1, nTab2 ) );
|
|
++nRow2New;
|
|
}
|
|
rRange = ScRange( nCol1New, nRow1New, nTab1, nCol2New, nRow2New, nTab2 );
|
|
}
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|