6961 lines
226 KiB
C++
6961 lines
226 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 <scitems.hxx>
|
|
|
|
#include <editeng/boxitem.hxx>
|
|
#include <editeng/editobj.hxx>
|
|
#include <svx/svditer.hxx>
|
|
#include <sfx2/docfile.hxx>
|
|
#include <svl/numformat.hxx>
|
|
#include <poolcach.hxx>
|
|
#include <svl/zforlist.hxx>
|
|
#include <unotools/charclass.hxx>
|
|
#include <unotools/transliterationwrapper.hxx>
|
|
#include <tools/urlobj.hxx>
|
|
#include <sal/log.hxx>
|
|
#include <osl/diagnose.h>
|
|
|
|
#include <com/sun/star/text/WritingMode2.hpp>
|
|
#include <com/sun/star/script/vba/XVBACompatibility.hpp>
|
|
#include <com/sun/star/sheet/TablePageBreakData.hpp>
|
|
#include <com/sun/star/lang/NotInitializedException.hpp>
|
|
|
|
#include <document.hxx>
|
|
#include <docsh.hxx>
|
|
#include <docuno.hxx>
|
|
#include <table.hxx>
|
|
#include <column.hxx>
|
|
#include <attrib.hxx>
|
|
#include <attarray.hxx>
|
|
#include <patattr.hxx>
|
|
#include <rangenam.hxx>
|
|
#include <poolhelp.hxx>
|
|
#include <docpool.hxx>
|
|
#include <stlpool.hxx>
|
|
#include <stlsheet.hxx>
|
|
#include <globstr.hrc>
|
|
#include <scresid.hxx>
|
|
#include <dbdata.hxx>
|
|
#include <chartlis.hxx>
|
|
#include <rangelst.hxx>
|
|
#include <markdata.hxx>
|
|
#include <drwlayer.hxx>
|
|
#include <validat.hxx>
|
|
#include <prnsave.hxx>
|
|
#include <chgtrack.hxx>
|
|
#include <hints.hxx>
|
|
#include <detdata.hxx>
|
|
#include <dpobject.hxx>
|
|
#include <scmod.hxx>
|
|
#include <dociter.hxx>
|
|
#include <progress.hxx>
|
|
#include <autonamecache.hxx>
|
|
#include <bcaslot.hxx>
|
|
#include <postit.hxx>
|
|
#include <clipparam.hxx>
|
|
#include <defaultsoptions.hxx>
|
|
#include <editutil.hxx>
|
|
#include <stringutil.hxx>
|
|
#include <formulaiter.hxx>
|
|
#include <formulacell.hxx>
|
|
#include <clipcontext.hxx>
|
|
#include <listenercontext.hxx>
|
|
#include <scopetools.hxx>
|
|
#include <refupdatecontext.hxx>
|
|
#include <formulagroup.hxx>
|
|
#include <tokenstringcontext.hxx>
|
|
#include <compressedarray.hxx>
|
|
#include <recursionhelper.hxx>
|
|
#include <SparklineGroup.hxx>
|
|
#include <SparklineList.hxx>
|
|
#include <undomanager.hxx>
|
|
|
|
#include <formula/vectortoken.hxx>
|
|
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <utility>
|
|
#include <unordered_map>
|
|
|
|
#include <comphelper/lok.hxx>
|
|
|
|
#include <vcl/uitest/logger.hxx>
|
|
#include <vcl/uitest/eventdescription.hxx>
|
|
|
|
#include <mtvelements.hxx>
|
|
#include <sfx2/lokhelper.hxx>
|
|
|
|
using ::editeng::SvxBorderLine;
|
|
using namespace ::com::sun::star;
|
|
|
|
namespace WritingMode2 = ::com::sun::star::text::WritingMode2;
|
|
using ::com::sun::star::uno::Sequence;
|
|
using ::com::sun::star::sheet::TablePageBreakData;
|
|
using ::std::set;
|
|
|
|
namespace {
|
|
|
|
std::pair<SCTAB,SCTAB> getMarkedTableRange(const std::vector<ScTableUniquePtr>& rTables, const ScMarkData& rMark)
|
|
{
|
|
SCTAB nTabStart = MAXTAB;
|
|
SCTAB nTabEnd = 0;
|
|
SCTAB nMax = static_cast<SCTAB>(rTables.size());
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
|
|
if (!rTables[rTab])
|
|
continue;
|
|
|
|
if (rTab < nTabStart)
|
|
nTabStart = rTab;
|
|
nTabEnd = rTab;
|
|
}
|
|
|
|
return std::pair<SCTAB,SCTAB>(nTabStart,nTabEnd);
|
|
}
|
|
|
|
void collectUIInformation(std::map<OUString, OUString>&& aParameters, const OUString& rAction)
|
|
{
|
|
EventDescription aDescription;
|
|
aDescription.aID = "grid_window";
|
|
aDescription.aAction = rAction;
|
|
aDescription.aParameters = std::move(aParameters);
|
|
aDescription.aParent = "MainWindow";
|
|
aDescription.aKeyWord = "ScGridWinUIObject";
|
|
|
|
UITestLogger::getInstance().logEvent(aDescription);
|
|
}
|
|
|
|
struct ScDefaultAttr
|
|
{
|
|
SCROW nFirst { 0 };
|
|
SCSIZE nCount { 0 };
|
|
};
|
|
|
|
}
|
|
|
|
typedef std::unordered_map<const ScPatternAttr*, ScDefaultAttr> ScDefaultAttrMap;
|
|
|
|
void ScDocument::MakeTable( SCTAB nTab,bool _bNeedsNameCheck )
|
|
{
|
|
if (!ValidTab(nTab) || HasTable(nTab))
|
|
return;
|
|
|
|
// Get Custom prefix
|
|
const ScDefaultsOptions& rOpt = ScModule::get()->GetDefaultsOptions();
|
|
OUString aString = rOpt.GetInitTabPrefix() + OUString::number(nTab+1);
|
|
if ( _bNeedsNameCheck )
|
|
CreateValidTabName( aString ); // no doubles
|
|
if (nTab < GetTableCount())
|
|
{
|
|
maTabs[nTab].reset( new ScTable(*this, nTab, aString) );
|
|
}
|
|
else
|
|
{
|
|
while (nTab > GetTableCount())
|
|
maTabs.push_back(nullptr);
|
|
maTabs.emplace_back( new ScTable(*this, nTab, aString) );
|
|
}
|
|
maTabs[nTab]->SetLoadingMedium(bLoadingMedium);
|
|
}
|
|
|
|
bool ScDocument::GetHashCode( SCTAB nTab, sal_Int64& rHashCode ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
rHashCode = pTable->GetHashCode();
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::GetName( SCTAB nTab, OUString& rName ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
rName = pTable->GetName();
|
|
return true;
|
|
}
|
|
rName.clear();
|
|
return false;
|
|
}
|
|
|
|
const OUString & ScDocument::GetCopyTabName( SCTAB nTab ) const
|
|
{
|
|
if (ValidTab(nTab) && nTab < static_cast<SCTAB>(maTabNames.size()))
|
|
return maTabNames[nTab];
|
|
return EMPTY_OUSTRING;
|
|
}
|
|
|
|
bool ScDocument::SetCodeName( SCTAB nTab, const OUString& rName )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
pTable->SetCodeName(rName);
|
|
return true;
|
|
}
|
|
SAL_WARN("sc", "can't set code name " << rName );
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::GetCodeName( SCTAB nTab, OUString& rName ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
rName = pTable->GetCodeName();
|
|
return true;
|
|
}
|
|
rName.clear();
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::SetTotalsRowBelow( SCTAB nTab, bool bVal )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
pTable->SetTotalsRowBelow(bVal);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::GetTotalsRowBelow( SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
return pTable->GetTotalsRowBelow();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool ScDocument::GetTable( const OUString& rName, SCTAB& rTab ) const
|
|
{
|
|
static OUString aCacheName, aCacheUpperName;
|
|
|
|
assert(!IsThreadedGroupCalcInProgress());
|
|
if (aCacheName != rName)
|
|
{
|
|
aCacheName = rName;
|
|
// surprisingly slow ...
|
|
aCacheUpperName = ScGlobal::getCharClass().uppercase(rName);
|
|
}
|
|
const OUString aUpperName = aCacheUpperName;
|
|
|
|
for (SCTAB i = 0; i < GetTableCount(); i++)
|
|
if (maTabs[i])
|
|
{
|
|
if (aUpperName == maTabs[i]->GetUpperName())
|
|
{
|
|
rTab = i;
|
|
return true;
|
|
}
|
|
}
|
|
rTab = 0;
|
|
return false;
|
|
}
|
|
|
|
std::vector<OUString> ScDocument::GetAllTableNames() const
|
|
{
|
|
std::vector<OUString> aNames;
|
|
aNames.reserve(maTabs.size());
|
|
for (const auto& a : maTabs)
|
|
{
|
|
// Positions need to be preserved for ScCompiler and address convention
|
|
// context, so still push an empty string for NULL tabs.
|
|
OUString aName;
|
|
if (a)
|
|
{
|
|
const ScTable& rTab = *a;
|
|
aName = rTab.GetName();
|
|
}
|
|
aNames.push_back(aName);
|
|
}
|
|
|
|
return aNames;
|
|
}
|
|
|
|
ScDBData* ScDocument::GetAnonymousDBData(SCTAB nTab)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetAnonymousDBData();
|
|
return nullptr;
|
|
}
|
|
|
|
SCTAB ScDocument::GetTableCount() const
|
|
{
|
|
return static_cast<SCTAB>(maTabs.size());
|
|
}
|
|
|
|
void ScDocument::SetAnonymousDBData(SCTAB nTab, std::unique_ptr<ScDBData> pDBData)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetAnonymousDBData(std::move(pDBData));
|
|
}
|
|
|
|
void ScDocument::SetAnonymousDBData( std::unique_ptr<ScDBData> pDBData )
|
|
{
|
|
mpAnonymousDBData = std::move(pDBData);
|
|
}
|
|
|
|
ScDBData* ScDocument::GetAnonymousDBData()
|
|
{
|
|
return mpAnonymousDBData.get();
|
|
}
|
|
|
|
bool ScDocument::ValidTabName( const OUString& rName )
|
|
{
|
|
if (rName.isEmpty())
|
|
return false;
|
|
sal_Int32 nLen = rName.getLength();
|
|
|
|
#if 1
|
|
// Restrict sheet names to what Excel accepts.
|
|
/* TODO: We may want to remove this restriction for full ODFF compliance.
|
|
* Merely loading and calculating ODF documents using these characters in
|
|
* sheet names is not affected by this, but all sheet name editing and
|
|
* copying functionality is, maybe falling back to "Sheet4" or similar. */
|
|
for (sal_Int32 i = 0; i < nLen; ++i)
|
|
{
|
|
const sal_Unicode c = rName[i];
|
|
switch (c)
|
|
{
|
|
case ':':
|
|
case '\\':
|
|
case '/':
|
|
case '?':
|
|
case '*':
|
|
case '[':
|
|
case ']':
|
|
// these characters are not allowed to match XL's convention.
|
|
return false;
|
|
case '\'':
|
|
if (i == 0 || i == nLen - 1)
|
|
// single quote is not allowed at the first or last
|
|
// character position.
|
|
return false;
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
bool ScDocument::ValidNewTabName( const OUString& rName ) const
|
|
{
|
|
bool bValid = ValidTabName(rName);
|
|
if (!bValid)
|
|
return false;
|
|
OUString aUpperName = ScGlobal::getCharClass().uppercase(rName);
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (!a)
|
|
continue;
|
|
const OUString& rOldName = a->GetUpperName();
|
|
bValid = rOldName != aUpperName;
|
|
if (!bValid)
|
|
break;
|
|
}
|
|
return bValid;
|
|
}
|
|
|
|
void ScDocument::CreateValidTabName(OUString& rName) const
|
|
{
|
|
if ( !ValidTabName(rName) )
|
|
{
|
|
// Find new one
|
|
|
|
// Get Custom prefix
|
|
const ScDefaultsOptions& rOpt = ScModule::get()->GetDefaultsOptions();
|
|
const OUString& aStrTable = rOpt.GetInitTabPrefix();
|
|
|
|
bool bOk = false;
|
|
|
|
// First test if the prefix is valid, if so only avoid doubles
|
|
bool bPrefix = ValidTabName( aStrTable );
|
|
OSL_ENSURE(bPrefix, "Invalid Table Name");
|
|
SCTAB nDummy;
|
|
|
|
for (SCTAB i = GetTableCount() + 1; !bOk ; i++)
|
|
{
|
|
rName = aStrTable + OUString::number(static_cast<sal_Int32>(i));
|
|
if (bPrefix)
|
|
bOk = ValidNewTabName( rName );
|
|
else
|
|
bOk = !GetTable( rName, nDummy );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// testing the supplied Name
|
|
|
|
if ( !ValidNewTabName(rName) )
|
|
{
|
|
SCTAB i = 1;
|
|
OUString aName;
|
|
do
|
|
{
|
|
i++;
|
|
aName = rName + "_" + OUString::number(static_cast<sal_Int32>(i));
|
|
}
|
|
while (!ValidNewTabName(aName) && (i < MAXTAB+1));
|
|
rName = aName;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScDocument::CreateValidTabNames(std::vector<OUString>& aNames, SCTAB nCount) const
|
|
{
|
|
aNames.clear();//ensure that the vector is empty
|
|
|
|
// Get Custom prefix
|
|
const ScDefaultsOptions& rOpt = ScModule::get()->GetDefaultsOptions();
|
|
const OUString& aStrTable = rOpt.GetInitTabPrefix();
|
|
|
|
OUStringBuffer rName;
|
|
|
|
// First test if the prefix is valid, if so only avoid doubles
|
|
bool bPrefix = ValidTabName( aStrTable );
|
|
OSL_ENSURE(bPrefix, "Invalid Table Name");
|
|
SCTAB nDummy;
|
|
SCTAB i = GetTableCount() + 1;
|
|
|
|
for (SCTAB j = 0; j < nCount; ++j)
|
|
{
|
|
bool bOk = false;
|
|
while(!bOk)
|
|
{
|
|
rName = aStrTable;
|
|
rName.append(static_cast<sal_Int32>(i));
|
|
if (bPrefix)
|
|
bOk = ValidNewTabName( rName.toString() );
|
|
else
|
|
bOk = !GetTable( rName.toString(), nDummy );
|
|
i++;
|
|
}
|
|
aNames.push_back(rName.makeStringAndClear());
|
|
}
|
|
}
|
|
|
|
void ScDocument::AppendTabOnLoad(const OUString& rName)
|
|
{
|
|
SCTAB nTabCount = GetTableCount();
|
|
if (!ValidTab(nTabCount))
|
|
// max table count reached. No more tables.
|
|
return;
|
|
|
|
OUString aName = rName;
|
|
CreateValidTabName(aName);
|
|
maTabs.emplace_back( new ScTable(*this, nTabCount, aName) );
|
|
}
|
|
|
|
void ScDocument::SetTabNameOnLoad(SCTAB nTab, const OUString& rName)
|
|
{
|
|
if (!ValidTab(nTab) || GetTableCount() <= nTab)
|
|
return;
|
|
|
|
if (!ValidTabName(rName))
|
|
return;
|
|
|
|
maTabs[nTab]->SetName(rName);
|
|
}
|
|
|
|
void ScDocument::InvalidateStreamOnSave()
|
|
{
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
a->SetStreamValid(false);
|
|
}
|
|
}
|
|
|
|
bool ScDocument::InsertTab(
|
|
SCTAB nPos, const OUString& rName, bool bExternalDocument, bool bUndoDeleteTab )
|
|
{
|
|
// auto-accept any in-process input to prevent move the cell into next sheet in online.
|
|
if (comphelper::LibreOfficeKit::isActive())
|
|
if (ScModule* mod = ScModule::get(); !mod->IsFormulaMode())
|
|
mod->InputEnterHandler();
|
|
|
|
SCTAB nTabCount = GetTableCount();
|
|
bool bValid = ValidTab(nTabCount);
|
|
if ( !bExternalDocument ) // else test rName == "'Doc'!Tab" first
|
|
bValid = (bValid && ValidNewTabName(rName));
|
|
if (bValid)
|
|
{
|
|
if (nPos == SC_TAB_APPEND || nPos >= nTabCount)
|
|
{
|
|
nPos = maTabs.size();
|
|
maTabs.emplace_back( new ScTable(*this, nTabCount, rName) );
|
|
if ( bExternalDocument )
|
|
maTabs[nTabCount]->SetVisible( false );
|
|
}
|
|
else
|
|
{
|
|
if (ValidTab(nPos) && (nPos < nTabCount))
|
|
{
|
|
sc::RefUpdateInsertTabContext aCxt( *this, nPos, 1);
|
|
|
|
ScRange aRange( 0,0,nPos, MaxCol(),MaxRow(),MAXTAB );
|
|
xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,1 );
|
|
xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,1 );
|
|
if (pRangeName)
|
|
pRangeName->UpdateInsertTab(aCxt);
|
|
pDBCollection->UpdateReference(
|
|
URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 );
|
|
if (pDPCollection)
|
|
pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,1 );
|
|
if (pDetOpList)
|
|
pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,1 );
|
|
UpdateChartRef( URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,1 );
|
|
UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,1 );
|
|
if ( pUnoBroadcaster )
|
|
pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,1 ) );
|
|
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
a->UpdateInsertTab(aCxt);
|
|
}
|
|
maTabs.emplace(maTabs.begin() + nPos, new ScTable(*this, nPos, rName));
|
|
|
|
// UpdateBroadcastAreas must be called between UpdateInsertTab,
|
|
// which ends listening, and StartAllListeners, to not modify
|
|
// areas that are to be inserted by starting listeners.
|
|
UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,1);
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
a->UpdateCompile();
|
|
}
|
|
|
|
StartAllListeners();
|
|
|
|
if (pValidationList)
|
|
{
|
|
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
|
|
pValidationList->UpdateInsertTab(aCxt);
|
|
}
|
|
|
|
bValid = true;
|
|
}
|
|
else
|
|
bValid = false;
|
|
}
|
|
}
|
|
|
|
if (bValid)
|
|
{
|
|
sc::SetFormulaDirtyContext aCxt;
|
|
aCxt.mbClearTabDeletedFlag = bUndoDeleteTab;
|
|
aCxt.mnTabDeletedStart = nPos;
|
|
aCxt.mnTabDeletedEnd = nPos;
|
|
SetAllFormulasDirty(aCxt);
|
|
|
|
if (comphelper::LibreOfficeKit::isActive() && GetDrawLayer())
|
|
{
|
|
ScModelObj* pModel = GetDocumentShell()->GetModel();
|
|
SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
|
|
}
|
|
}
|
|
|
|
return bValid;
|
|
}
|
|
|
|
bool ScDocument::InsertTabs( SCTAB nPos, const std::vector<OUString>& rNames,
|
|
bool bNamesValid )
|
|
{
|
|
SCTAB nNewSheets = static_cast<SCTAB>(rNames.size());
|
|
SCTAB nTabCount = GetTableCount();
|
|
bool bValid = bNamesValid || ValidTab(nTabCount+nNewSheets);
|
|
|
|
if (bValid)
|
|
{
|
|
if (nPos == SC_TAB_APPEND || nPos >= nTabCount)
|
|
{
|
|
for ( SCTAB i = 0; i < nNewSheets; ++i )
|
|
{
|
|
maTabs.emplace_back( new ScTable(*this, nTabCount + i, rNames.at(i)) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ValidTab(nPos) && (nPos < nTabCount))
|
|
{
|
|
sc::RefUpdateInsertTabContext aCxt( *this, nPos, nNewSheets);
|
|
ScRange aRange( 0,0,nPos, MaxCol(),MaxRow(),MAXTAB );
|
|
xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,nNewSheets );
|
|
xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,nNewSheets );
|
|
if (pRangeName)
|
|
pRangeName->UpdateInsertTab(aCxt);
|
|
pDBCollection->UpdateReference(
|
|
URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,nNewSheets );
|
|
if (pDPCollection)
|
|
pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,nNewSheets );
|
|
if (pDetOpList)
|
|
pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,nNewSheets );
|
|
UpdateChartRef( URM_INSDEL, 0,0,nPos, MaxCol(),MaxRow(),MAXTAB, 0,0,nNewSheets );
|
|
UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0, nNewSheets );
|
|
if ( pUnoBroadcaster )
|
|
pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,nNewSheets ) );
|
|
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
a->UpdateInsertTab(aCxt);
|
|
}
|
|
for (SCTAB i = 0; i < nNewSheets; ++i)
|
|
{
|
|
maTabs.emplace(maTabs.begin() + nPos + i, new ScTable(*this, nPos + i, rNames.at(i)) );
|
|
}
|
|
|
|
// UpdateBroadcastAreas must be called between UpdateInsertTab,
|
|
// which ends listening, and StartAllListeners, to not modify
|
|
// areas that are to be inserted by starting listeners.
|
|
UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,nNewSheets);
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
a->UpdateCompile();
|
|
}
|
|
|
|
StartAllListeners();
|
|
|
|
if (pValidationList)
|
|
{
|
|
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
|
|
pValidationList->UpdateInsertTab(aCxt);
|
|
}
|
|
|
|
bValid = true;
|
|
}
|
|
else
|
|
bValid = false;
|
|
}
|
|
}
|
|
|
|
if (bValid)
|
|
{
|
|
sc::SetFormulaDirtyContext aCxt;
|
|
SetAllFormulasDirty(aCxt);
|
|
}
|
|
|
|
return bValid;
|
|
}
|
|
|
|
bool ScDocument::DeleteTab( SCTAB nTab )
|
|
{
|
|
bool bValid = false;
|
|
if (HasTable(nTab))
|
|
{
|
|
SCTAB nTabCount = GetTableCount();
|
|
if (nTabCount > 1)
|
|
{
|
|
sc::AutoCalcSwitch aACSwitch(*this, false);
|
|
sc::RefUpdateDeleteTabContext aCxt( *this, nTab, 1);
|
|
sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this);
|
|
|
|
ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTab );
|
|
DelBroadcastAreasInRange( aRange );
|
|
|
|
// #i8180# remove database ranges etc. that are on the deleted tab
|
|
// (restored in undo with ScRefUndoData)
|
|
|
|
xColNameRanges->DeleteOnTab( nTab );
|
|
xRowNameRanges->DeleteOnTab( nTab );
|
|
pDBCollection->DeleteOnTab( nTab );
|
|
if (pDPCollection)
|
|
pDPCollection->DeleteOnTab( nTab );
|
|
if (pDetOpList)
|
|
pDetOpList->DeleteOnTab( nTab );
|
|
DeleteAreaLinksOnTab( nTab );
|
|
|
|
// normal reference update
|
|
|
|
aRange.aEnd.SetTab(GetTableCount() - 1);
|
|
xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1 );
|
|
xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1 );
|
|
if (pRangeName)
|
|
pRangeName->UpdateDeleteTab(aCxt);
|
|
pDBCollection->UpdateReference(
|
|
URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1 );
|
|
if (pDPCollection)
|
|
pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,-1 );
|
|
if (pDetOpList)
|
|
pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,-1 );
|
|
UpdateChartRef( URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1 );
|
|
UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,-1 );
|
|
if (pValidationList)
|
|
{
|
|
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
|
|
pValidationList->UpdateDeleteTab(aCxt);
|
|
}
|
|
if ( pUnoBroadcaster )
|
|
pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,-1 ) );
|
|
|
|
for (auto & pTab : maTabs)
|
|
if (pTab)
|
|
pTab->UpdateDeleteTab(aCxt);
|
|
|
|
// tdf#149502 make sure ScTable destructor called after the erase is finished, when
|
|
// maTabs[x].nTab==x is true again, as it should be always true.
|
|
// In the end of maTabs.erase, maTabs indexes change, but nTab updated before erase.
|
|
// ~ScTable expect that maTabs[x].nTab==x so it shouldn't be called during erase.
|
|
ScTableUniquePtr pErasedTab = std::move(maTabs[nTab]);
|
|
maTabs.erase(maTabs.begin() + nTab);
|
|
delete pErasedTab.release();
|
|
|
|
// UpdateBroadcastAreas must be called between UpdateDeleteTab,
|
|
// which ends listening, and StartAllListeners, to not modify
|
|
// areas that are to be inserted by starting listeners.
|
|
UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,-1);
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
a->UpdateCompile();
|
|
}
|
|
// Excel-Filter deletes some Tables while loading, Listeners will
|
|
// only be triggered after the loading is done.
|
|
if ( !bInsertingFromOtherDoc )
|
|
{
|
|
StartAllListeners();
|
|
|
|
sc::SetFormulaDirtyContext aFormulaDirtyCxt;
|
|
SetAllFormulasDirty(aFormulaDirtyCxt);
|
|
}
|
|
|
|
if (comphelper::LibreOfficeKit::isActive())
|
|
{
|
|
ScModelObj* pModel = GetDocumentShell()->GetModel();
|
|
SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
|
|
}
|
|
|
|
bValid = true;
|
|
}
|
|
}
|
|
return bValid;
|
|
}
|
|
|
|
bool ScDocument::DeleteTabs( SCTAB nTab, SCTAB nSheets )
|
|
{
|
|
bool bValid = false;
|
|
if (HasTable(nTab) && (nTab + nSheets) <= GetTableCount())
|
|
{
|
|
SCTAB nTabCount = GetTableCount();
|
|
if (nTabCount > nSheets)
|
|
{
|
|
sc::AutoCalcSwitch aACSwitch(*this, false);
|
|
sc::RefUpdateDeleteTabContext aCxt( *this, nTab, nSheets);
|
|
sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this);
|
|
|
|
for (SCTAB aTab = 0; aTab < nSheets; ++aTab)
|
|
{
|
|
ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTab + aTab );
|
|
DelBroadcastAreasInRange( aRange );
|
|
|
|
// #i8180# remove database ranges etc. that are on the deleted tab
|
|
// (restored in undo with ScRefUndoData)
|
|
|
|
xColNameRanges->DeleteOnTab( nTab + aTab );
|
|
xRowNameRanges->DeleteOnTab( nTab + aTab );
|
|
pDBCollection->DeleteOnTab( nTab + aTab );
|
|
if (pDPCollection)
|
|
pDPCollection->DeleteOnTab( nTab + aTab );
|
|
if (pDetOpList)
|
|
pDetOpList->DeleteOnTab( nTab + aTab );
|
|
DeleteAreaLinksOnTab( nTab + aTab );
|
|
}
|
|
|
|
if (pRangeName)
|
|
pRangeName->UpdateDeleteTab(aCxt);
|
|
|
|
// normal reference update
|
|
|
|
ScRange aRange( 0, 0, nTab, MaxCol(), MaxRow(), nTabCount - 1 );
|
|
xColNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1*nSheets );
|
|
xRowNameRanges->UpdateReference( URM_INSDEL, this, aRange, 0,0,-1*nSheets );
|
|
pDBCollection->UpdateReference(
|
|
URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1*nSheets );
|
|
if (pDPCollection)
|
|
pDPCollection->UpdateReference( URM_INSDEL, aRange, 0,0,-1*nSheets );
|
|
if (pDetOpList)
|
|
pDetOpList->UpdateReference( this, URM_INSDEL, aRange, 0,0,-1*nSheets );
|
|
UpdateChartRef( URM_INSDEL, 0,0,nTab, MaxCol(),MaxRow(),MAXTAB, 0,0,-1*nSheets );
|
|
UpdateRefAreaLinks( URM_INSDEL, aRange, 0,0,-1*nSheets );
|
|
if (pValidationList)
|
|
{
|
|
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
|
|
pValidationList->UpdateDeleteTab(aCxt);
|
|
}
|
|
if ( pUnoBroadcaster )
|
|
pUnoBroadcaster->Broadcast( ScUpdateRefHint( URM_INSDEL, aRange, 0,0,-1*nSheets ) );
|
|
|
|
for (auto & pTab : maTabs)
|
|
if (pTab)
|
|
pTab->UpdateDeleteTab(aCxt);
|
|
|
|
maTabs.erase(maTabs.begin() + nTab, maTabs.begin() + nTab + nSheets);
|
|
// UpdateBroadcastAreas must be called between UpdateDeleteTab,
|
|
// which ends listening, and StartAllListeners, to not modify
|
|
// areas that are to be inserted by starting listeners.
|
|
UpdateBroadcastAreas( URM_INSDEL, aRange, 0,0,-1*nSheets);
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
a->UpdateCompile();
|
|
}
|
|
// Excel-Filter deletes some Tables while loading, Listeners will
|
|
// only be triggered after the loading is done.
|
|
if ( !bInsertingFromOtherDoc )
|
|
{
|
|
StartAllListeners();
|
|
|
|
sc::SetFormulaDirtyContext aFormulaDirtyCxt;
|
|
SetAllFormulasDirty(aFormulaDirtyCxt);
|
|
}
|
|
|
|
if (comphelper::LibreOfficeKit::isActive())
|
|
{
|
|
ScModelObj* pModel = GetDocumentShell()->GetModel();
|
|
SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
|
|
}
|
|
|
|
bValid = true;
|
|
}
|
|
}
|
|
return bValid;
|
|
}
|
|
|
|
bool ScDocument::RenameTab( SCTAB nTab, const OUString& rName, bool bExternalDocument )
|
|
{
|
|
bool bValid = false;
|
|
SCTAB i;
|
|
if (HasTable(nTab))
|
|
{
|
|
if ( bExternalDocument )
|
|
bValid = true; // composed name
|
|
else
|
|
bValid = ValidTabName(rName);
|
|
for (i = 0; i < GetTableCount() && bValid; i++)
|
|
{
|
|
if (maTabs[i] && (i != nTab))
|
|
{
|
|
OUString aOldName = maTabs[i]->GetName();
|
|
bValid = !ScGlobal::GetTransliteration().isEqual( rName, aOldName );
|
|
}
|
|
}
|
|
if (bValid)
|
|
{
|
|
// #i75258# update charts before renaming, so they can get their live data objects.
|
|
// Once the charts are live, the sheet can be renamed without problems.
|
|
if ( pChartListenerCollection )
|
|
pChartListenerCollection->UpdateChartsContainingTab( nTab );
|
|
maTabs[nTab]->SetName(rName);
|
|
|
|
// If formulas refer to the renamed sheet, the TokenArray remains valid,
|
|
// but the XML stream must be re-generated.
|
|
for (const auto& pTable : maTabs)
|
|
{
|
|
if (pTable)
|
|
{
|
|
pTable->SetStreamValid( false );
|
|
// tdf#156815 Reset solver settings so next time they're loaded they come with
|
|
// the updated sheet name
|
|
pTable->ResetSolverSettings();
|
|
}
|
|
}
|
|
|
|
if (comphelper::LibreOfficeKit::isActive() && GetDrawLayer())
|
|
{
|
|
ScModelObj* pModel = GetDocumentShell()->GetModel();
|
|
SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel);
|
|
}
|
|
}
|
|
}
|
|
|
|
collectUIInformation({{"NewName", rName}}, u"Rename_Sheet"_ustr);
|
|
|
|
return bValid;
|
|
}
|
|
|
|
void ScDocument::SetVisible( SCTAB nTab, bool bVisible )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetVisible(bVisible);
|
|
}
|
|
|
|
bool ScDocument::IsVisible( SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->IsVisible();
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::IsStreamValid( SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->IsStreamValid();
|
|
return false;
|
|
}
|
|
|
|
void ScDocument::SetStreamValid( SCTAB nTab, bool bSet, bool bIgnoreLock )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetStreamValid( bSet, bIgnoreLock );
|
|
}
|
|
|
|
void ScDocument::LockStreamValid( bool bLock )
|
|
{
|
|
mbStreamValidLocked = bLock;
|
|
}
|
|
|
|
bool ScDocument::IsPendingRowHeights( SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->IsPendingRowHeights();
|
|
return false;
|
|
}
|
|
|
|
void ScDocument::SetPendingRowHeights( SCTAB nTab, bool bSet )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetPendingRowHeights(bSet);
|
|
}
|
|
|
|
sal_uInt16 ScDocument::GetSheetOptimalMinRowHeight(SCTAB nTab) const
|
|
{
|
|
const ScTable* pTab = FetchTable(nTab);
|
|
if (!pTab)
|
|
return ScGlobal::nStdRowHeight;
|
|
|
|
return pTab->GetOptimalMinRowHeight();
|
|
}
|
|
|
|
void ScDocument::SetLayoutRTL( SCTAB nTab, bool bRTL, ScObjectHandling eObjectHandling)
|
|
{
|
|
ScTable* pTable = FetchTable(nTab);
|
|
if (!pTable)
|
|
return;
|
|
|
|
if ( bImportingXML )
|
|
{
|
|
// #i57869# only set the LoadingRTL flag, the real setting (including mirroring)
|
|
// is applied in SetImportingXML(false). This is so the shapes can be loaded in
|
|
// normal LTR mode.
|
|
|
|
pTable->SetLoadingRTL( bRTL );
|
|
return;
|
|
}
|
|
|
|
pTable->SetLayoutRTL( bRTL ); // only sets the flag
|
|
pTable->SetDrawPageSize(true, true, eObjectHandling);
|
|
|
|
// objects are already repositioned via SetDrawPageSize, only writing mode is missing
|
|
if (!mpDrawLayer)
|
|
return;
|
|
|
|
SdrPage* pPage = mpDrawLayer->GetPage(static_cast<sal_uInt16>(nTab));
|
|
OSL_ENSURE(pPage,"Page ?");
|
|
if (!pPage)
|
|
return;
|
|
|
|
SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups );
|
|
SdrObject* pObject = aIter.Next();
|
|
while (pObject)
|
|
{
|
|
pObject->SetContextWritingMode( bRTL ? WritingMode2::RL_TB : WritingMode2::LR_TB );
|
|
pObject = aIter.Next();
|
|
}
|
|
}
|
|
|
|
bool ScDocument::IsLayoutRTL( SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->IsLayoutRTL();
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::IsNegativePage( SCTAB nTab ) const
|
|
{
|
|
// Negative page area is always used for RTL layout.
|
|
// The separate method is used to find all RTL handling of drawing objects.
|
|
return IsLayoutRTL( nTab );
|
|
}
|
|
|
|
/* ----------------------------------------------------------------------------
|
|
used search area:
|
|
|
|
GetCellArea - Only Data
|
|
GetTableArea - Data / Attributes
|
|
GetPrintArea - intended for character objects,
|
|
sweeps attributes all the way to bottom / right
|
|
---------------------------------------------------------------------------- */
|
|
|
|
bool ScDocument::GetCellArea( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow ) const
|
|
{
|
|
if (HasTable(nTab))
|
|
return maTabs[nTab]->GetCellArea(rEndCol, rEndRow);
|
|
|
|
rEndCol = 0;
|
|
rEndRow = 0;
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::GetTableArea( SCTAB nTab, SCCOL& rEndCol, SCROW& rEndRow, bool bCalcHiddens) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetTableArea(rEndCol, rEndRow, bCalcHiddens);
|
|
|
|
rEndCol = 0;
|
|
rEndRow = 0;
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::ShrinkToDataArea(SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow) const
|
|
{
|
|
if (!HasTable(nTab))
|
|
return false;
|
|
|
|
SCCOL nCol1, nCol2;
|
|
SCROW nRow1, nRow2;
|
|
maTabs[nTab]->GetFirstDataPos(nCol1, nRow1);
|
|
maTabs[nTab]->GetLastDataPos(nCol2, nRow2);
|
|
|
|
if (nCol1 > nCol2 || nRow1 > nRow2)
|
|
// invalid range.
|
|
return false;
|
|
|
|
// Make sure the area only shrinks, and doesn't grow.
|
|
if (rStartCol < nCol1)
|
|
rStartCol = nCol1;
|
|
if (nCol2 < rEndCol)
|
|
rEndCol = nCol2;
|
|
if (rStartRow < nRow1)
|
|
rStartRow = nRow1;
|
|
if (nRow2 < rEndRow)
|
|
rEndRow = nRow2;
|
|
|
|
if (rStartCol > rEndCol || rStartRow > rEndRow)
|
|
// invalid range.
|
|
return false;
|
|
|
|
return true; // success!
|
|
}
|
|
|
|
bool ScDocument::ShrinkToUsedDataArea( bool& o_bShrunk, SCTAB nTab, SCCOL& rStartCol,
|
|
SCROW& rStartRow, SCCOL& rEndCol, SCROW& rEndRow, bool bColumnsOnly,
|
|
bool bStickyTopRow, bool bStickyLeftCol, ScDataAreaExtras* pDataAreaExtras ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
return pTable->ShrinkToUsedDataArea(
|
|
o_bShrunk, rStartCol, rStartRow, rEndCol, rEndRow, bColumnsOnly, bStickyTopRow,
|
|
bStickyLeftCol, pDataAreaExtras);
|
|
}
|
|
o_bShrunk = false;
|
|
return false;
|
|
}
|
|
|
|
SCROW ScDocument::GetLastDataRow( SCTAB nTab, SCCOL nCol1, SCCOL nCol2, SCROW nLastRow ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetLastDataRow(nCol1, nCol2, nLastRow);
|
|
return -1;
|
|
}
|
|
|
|
// connected area
|
|
|
|
void ScDocument::GetDataArea( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow,
|
|
SCCOL& rEndCol, SCROW& rEndRow, bool bIncludeOld, bool bOnlyDown ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
pTable->GetDataArea( rStartCol, rStartRow, rEndCol, rEndRow, bIncludeOld, bOnlyDown );
|
|
}
|
|
|
|
void ScDocument::GetBackColorArea( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow,
|
|
SCCOL& rEndCol, SCROW& rEndRow ) const
|
|
{
|
|
if (ValidTab(nTab) && nTab < static_cast<SCTAB> (maTabs.size()) && maTabs[nTab])
|
|
maTabs[nTab]->GetBackColorArea( rStartCol, rStartRow, rEndCol, rEndRow );
|
|
}
|
|
|
|
bool ScDocument::GetDataAreaSubrange(ScRange& rRange) const
|
|
{
|
|
SCTAB nTab = rRange.aStart.Tab();
|
|
if (nTab != rRange.aEnd.Tab())
|
|
return true;
|
|
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetDataAreaSubrange(rRange);
|
|
|
|
return true;
|
|
}
|
|
|
|
void ScDocument::LimitChartArea( SCTAB nTab, SCCOL& rStartCol, SCROW& rStartRow,
|
|
SCCOL& rEndCol, SCROW& rEndRow )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->LimitChartArea( rStartCol, rStartRow, rEndCol, rEndRow);
|
|
}
|
|
|
|
void ScDocument::LimitChartIfAll( ScRangeListRef& rRangeList )
|
|
{
|
|
ScRangeListRef aNew = new ScRangeList;
|
|
if (rRangeList.is())
|
|
{
|
|
for ( size_t i = 0, nCount = rRangeList->size(); i < nCount; i++ )
|
|
{
|
|
ScRange aRange( (*rRangeList)[i] );
|
|
if ( ( aRange.aStart.Col() == 0 && aRange.aEnd.Col() == MaxCol() ) ||
|
|
( aRange.aStart.Row() == 0 && aRange.aEnd.Row() == MaxRow() ) )
|
|
{
|
|
SCCOL nStartCol = aRange.aStart.Col();
|
|
SCROW nStartRow = aRange.aStart.Row();
|
|
SCCOL nEndCol = aRange.aEnd.Col();
|
|
SCROW nEndRow = aRange.aEnd.Row();
|
|
SCTAB nTab = aRange.aStart.Tab();
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->LimitChartArea(nStartCol, nStartRow, nEndCol, nEndRow);
|
|
aRange.aStart.SetCol( nStartCol );
|
|
aRange.aStart.SetRow( nStartRow );
|
|
aRange.aEnd.SetCol( nEndCol );
|
|
aRange.aEnd.SetRow( nEndRow );
|
|
}
|
|
aNew->push_back(aRange);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OSL_FAIL("LimitChartIfAll: Ref==0");
|
|
}
|
|
rRangeList = std::move(aNew);
|
|
}
|
|
|
|
static void lcl_GetFirstTabRange( SCTAB& rTabRangeStart, SCTAB& rTabRangeEnd, const ScMarkData* pTabMark, SCTAB aMaxTab )
|
|
{
|
|
// without ScMarkData, leave start/end unchanged
|
|
if ( !pTabMark )
|
|
return;
|
|
|
|
for (SCTAB nTab=0; nTab< aMaxTab; ++nTab)
|
|
if (pTabMark->GetTableSelect(nTab))
|
|
{
|
|
// find first range of consecutive selected sheets
|
|
rTabRangeStart = pTabMark->GetFirstSelected();
|
|
while ( nTab+1 < aMaxTab && pTabMark->GetTableSelect(nTab+1) )
|
|
++nTab;
|
|
rTabRangeEnd = nTab;
|
|
return;
|
|
}
|
|
}
|
|
|
|
static bool lcl_GetNextTabRange( SCTAB& rTabRangeStart, SCTAB& rTabRangeEnd, const ScMarkData* pTabMark, SCTAB aMaxTab )
|
|
{
|
|
if ( pTabMark )
|
|
{
|
|
// find next range of consecutive selected sheets after rTabRangeEnd
|
|
for (SCTAB nTab=rTabRangeEnd+1; nTab< aMaxTab; ++nTab)
|
|
if (pTabMark->GetTableSelect(nTab))
|
|
{
|
|
rTabRangeStart = nTab;
|
|
while ( nTab+1 < aMaxTab && pTabMark->GetTableSelect(nTab+1) )
|
|
++nTab;
|
|
rTabRangeEnd = nTab;
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::CanInsertRow( const ScRange& rRange ) const
|
|
{
|
|
SCCOL nStartCol = rRange.aStart.Col();
|
|
SCROW nStartRow = rRange.aStart.Row();
|
|
SCTAB nStartTab = rRange.aStart.Tab();
|
|
SCCOL nEndCol = rRange.aEnd.Col();
|
|
SCROW nEndRow = rRange.aEnd.Row();
|
|
SCTAB nEndTab = rRange.aEnd.Tab();
|
|
PutInOrder( nStartCol, nEndCol );
|
|
PutInOrder( nStartRow, nEndRow );
|
|
PutInOrder( nStartTab, nEndTab );
|
|
SCSIZE nSize = static_cast<SCSIZE>(nEndRow - nStartRow + 1);
|
|
|
|
bool bTest = true;
|
|
for (SCTAB i = nStartTab; i <= nEndTab && bTest && i < GetTableCount(); i++)
|
|
if (maTabs[i])
|
|
bTest &= maTabs[i]->TestInsertRow( nStartCol, nEndCol, nStartRow, nSize );
|
|
|
|
return bTest;
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct SetDirtyIfPostponedHandler
|
|
{
|
|
void operator() (const ScTableUniquePtr & p)
|
|
{
|
|
if (p)
|
|
p->SetDirtyIfPostponed();
|
|
}
|
|
};
|
|
|
|
struct BroadcastRecalcOnRefMoveHandler
|
|
{
|
|
void operator() (const ScTableUniquePtr & p)
|
|
{
|
|
if (p)
|
|
p->BroadcastRecalcOnRefMove();
|
|
}
|
|
};
|
|
|
|
struct BroadcastRecalcOnRefMoveGuard
|
|
{
|
|
explicit BroadcastRecalcOnRefMoveGuard( ScDocument* pDoc ) :
|
|
aSwitch( *pDoc, false),
|
|
aBulk( pDoc->GetBASM(), SfxHintId::ScDataChanged)
|
|
{
|
|
}
|
|
|
|
private:
|
|
sc::AutoCalcSwitch aSwitch; // first for ctor/dtor order, destroy second
|
|
ScBulkBroadcast aBulk; // second for ctor/dtor order, destroy first
|
|
};
|
|
|
|
}
|
|
|
|
bool ScDocument::InsertRow( SCCOL nStartCol, SCTAB nStartTab,
|
|
SCCOL nEndCol, SCTAB nEndTab,
|
|
SCROW nStartRow, SCSIZE nSize, ScDocument* pRefUndoDoc,
|
|
const ScMarkData* pTabMark )
|
|
{
|
|
SCTAB i;
|
|
|
|
PutInOrder( nStartCol, nEndCol );
|
|
PutInOrder( nStartTab, nEndTab );
|
|
if ( pTabMark )
|
|
{
|
|
nStartTab = 0;
|
|
nEndTab = GetTableCount() - 1;
|
|
}
|
|
|
|
bool bTest = true;
|
|
bool bRet = false;
|
|
bool bOldAutoCalc = GetAutoCalc();
|
|
SetAutoCalc( false ); // avoid multiple calculations
|
|
bool oldDelayedDeleteBroadcasters = IsDelayedDeletingBroadcasters();
|
|
EnableDelayDeletingBroadcasters( true );
|
|
for ( i = nStartTab; i <= nEndTab && bTest && i < GetTableCount(); i++)
|
|
if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
|
|
bTest &= maTabs[i]->TestInsertRow(nStartCol, nEndCol, nStartRow, nSize);
|
|
if (bTest)
|
|
{
|
|
// UpdateBroadcastAreas have to be called before UpdateReference, so that entries
|
|
// aren't shifted that would be rebuild at UpdateReference
|
|
|
|
// handle chunks of consecutive selected sheets together
|
|
SCTAB nTabRangeStart = nStartTab;
|
|
SCTAB nTabRangeEnd = nEndTab;
|
|
lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
|
|
ScRange aShiftedRange(nStartCol, nStartRow, nTabRangeStart, nEndCol, MaxRow(), nTabRangeEnd);
|
|
sc::EndListeningContext aEndListenCxt(*this);
|
|
|
|
std::vector<ScAddress> aGroupPos;
|
|
do
|
|
{
|
|
aShiftedRange.aStart.SetTab(nTabRangeStart);
|
|
aShiftedRange.aEnd.SetTab(nTabRangeEnd);
|
|
|
|
// Collect all formula groups that will get split by the shifting,
|
|
// and end all their listening. Record the position of the top
|
|
// cell of the topmost group, and the position of the bottom cell
|
|
// of the bottommost group.
|
|
EndListeningIntersectedGroups(aEndListenCxt, aShiftedRange, &aGroupPos);
|
|
|
|
UpdateBroadcastAreas(URM_INSDEL, aShiftedRange, 0, static_cast<SCROW>(nSize), 0);
|
|
}
|
|
while (lcl_GetNextTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));
|
|
|
|
lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
|
|
|
|
sc::RefUpdateContext aCxt(*this);
|
|
aCxt.meMode = URM_INSDEL;
|
|
aCxt.maRange = aShiftedRange;
|
|
aCxt.mnRowDelta = nSize;
|
|
do
|
|
{
|
|
aCxt.maRange.aStart.SetTab(nTabRangeStart);
|
|
aCxt.maRange.aEnd.SetTab(nTabRangeEnd);
|
|
UpdateReference(aCxt, pRefUndoDoc, false); // without drawing objects
|
|
}
|
|
while (lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));
|
|
|
|
// UpdateReference should have set "needs listening" flags to those
|
|
// whose references have been modified. We also need to set this flag
|
|
// to those that were in the groups that got split by shifting.
|
|
SetNeedsListeningGroups(aGroupPos);
|
|
|
|
for (i=nStartTab; i<=nEndTab && i < GetTableCount(); i++)
|
|
{
|
|
if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
|
|
{
|
|
maTabs[i]->InsertRow( nStartCol, nEndCol, nStartRow, nSize );
|
|
maTabs[i]->CommentNotifyAddressChange(nStartCol, nStartRow, nEndCol, MaxRow());
|
|
}
|
|
}
|
|
|
|
// UpdateRef for drawing layer must be after inserting,
|
|
// when the new row heights are known.
|
|
for (i=nStartTab; i<=nEndTab && i < GetTableCount(); i++)
|
|
if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
|
|
maTabs[i]->UpdateDrawRef( URM_INSDEL,
|
|
nStartCol, nStartRow, nStartTab, nEndCol, MaxRow(), nEndTab,
|
|
0, static_cast<SCROW>(nSize), 0 );
|
|
|
|
if ( pChangeTrack && pChangeTrack->IsInDeleteUndo() )
|
|
{ // A new Listening is needed when references to deleted ranges are restored,
|
|
// previous Listeners were removed in FormulaCell UpdateReference.
|
|
StartAllListeners();
|
|
}
|
|
else
|
|
{ // Listeners have been removed in UpdateReference
|
|
StartNeededListeners();
|
|
|
|
// At least all cells using range names pointing relative to the
|
|
// moved range must be recalculated, and all cells marked postponed
|
|
// dirty.
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
a->SetDirtyIfPostponed();
|
|
}
|
|
|
|
{
|
|
BroadcastRecalcOnRefMoveGuard g(this);
|
|
std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
|
|
}
|
|
}
|
|
bRet = true;
|
|
}
|
|
EnableDelayDeletingBroadcasters( oldDelayedDeleteBroadcasters );
|
|
SetAutoCalc( bOldAutoCalc );
|
|
if ( bRet && pChartListenerCollection )
|
|
pChartListenerCollection->UpdateDirtyCharts();
|
|
return bRet;
|
|
}
|
|
|
|
bool ScDocument::InsertRow( const ScRange& rRange )
|
|
{
|
|
return InsertRow( rRange.aStart.Col(), rRange.aStart.Tab(),
|
|
rRange.aEnd.Col(), rRange.aEnd.Tab(),
|
|
rRange.aStart.Row(), static_cast<SCSIZE>(rRange.aEnd.Row()-rRange.aStart.Row()+1) );
|
|
}
|
|
|
|
void ScDocument::DeleteRow( SCCOL nStartCol, SCTAB nStartTab,
|
|
SCCOL nEndCol, SCTAB nEndTab,
|
|
SCROW nStartRow, SCSIZE nSize,
|
|
ScDocument* pRefUndoDoc, bool* pUndoOutline,
|
|
const ScMarkData* pTabMark )
|
|
{
|
|
SCTAB i;
|
|
|
|
PutInOrder( nStartCol, nEndCol );
|
|
PutInOrder( nStartTab, nEndTab );
|
|
if ( pTabMark )
|
|
{
|
|
nStartTab = 0;
|
|
nEndTab = GetTableCount() - 1;
|
|
}
|
|
|
|
sc::AutoCalcSwitch aACSwitch(*this, false); // avoid multiple calculations
|
|
|
|
// handle chunks of consecutive selected sheets together
|
|
SCTAB nTabRangeStart = nStartTab;
|
|
SCTAB nTabRangeEnd = nEndTab;
|
|
lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
|
|
do
|
|
{
|
|
if ( ValidRow(nStartRow+nSize) )
|
|
{
|
|
DelBroadcastAreasInRange( ScRange(
|
|
ScAddress( nStartCol, nStartRow, nTabRangeStart ),
|
|
ScAddress( nEndCol, nStartRow+nSize-1, nTabRangeEnd ) ) );
|
|
UpdateBroadcastAreas( URM_INSDEL, ScRange(
|
|
ScAddress( nStartCol, nStartRow+nSize, nTabRangeStart ),
|
|
ScAddress( nEndCol, MaxRow(), nTabRangeEnd )), 0, -static_cast<SCROW>(nSize), 0 );
|
|
}
|
|
else
|
|
DelBroadcastAreasInRange( ScRange(
|
|
ScAddress( nStartCol, nStartRow, nTabRangeStart ),
|
|
ScAddress( nEndCol, MaxRow(), nTabRangeEnd ) ) );
|
|
}
|
|
while (lcl_GetNextTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));
|
|
|
|
sc::RefUpdateContext aCxt(*this);
|
|
const bool bLastRowIncluded = (static_cast<SCROW>(nStartRow + nSize) == GetMaxRowCount() && ValidRow(nStartRow));
|
|
if ( ValidRow(nStartRow+nSize) || bLastRowIncluded )
|
|
{
|
|
lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
|
|
aCxt.meMode = URM_INSDEL;
|
|
aCxt.mnRowDelta = -static_cast<SCROW>(nSize);
|
|
if (bLastRowIncluded)
|
|
{
|
|
// Last row is included, shift a virtually non-existent row in.
|
|
aCxt.maRange = ScRange( nStartCol, GetMaxRowCount(), nTabRangeStart, nEndCol, GetMaxRowCount(), nTabRangeEnd);
|
|
}
|
|
else
|
|
{
|
|
aCxt.maRange = ScRange( nStartCol, nStartRow+nSize, nTabRangeStart, nEndCol, MaxRow(), nTabRangeEnd);
|
|
}
|
|
do
|
|
{
|
|
UpdateReference(aCxt, pRefUndoDoc, true, false);
|
|
}
|
|
while (lcl_GetNextTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));
|
|
}
|
|
|
|
if (pUndoOutline)
|
|
*pUndoOutline = false;
|
|
|
|
// Keep track of the positions of all formula groups that have been joined
|
|
// during row deletion.
|
|
std::vector<ScAddress> aGroupPos;
|
|
|
|
for ( i = nStartTab; i <= nEndTab && i < GetTableCount(); i++)
|
|
{
|
|
if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
|
|
{
|
|
maTabs[i]->DeleteRow(aCxt.maRegroupCols, nStartCol, nEndCol, nStartRow, nSize, pUndoOutline, &aGroupPos);
|
|
maTabs[i]->CommentNotifyAddressChange(nStartCol, nStartRow, nEndCol, MaxRow());
|
|
}
|
|
}
|
|
|
|
// Newly joined groups have some of their members still listening. We
|
|
// need to make sure none of them are listening.
|
|
EndListeningGroups(aGroupPos);
|
|
|
|
// Mark all joined groups for group listening.
|
|
SetNeedsListeningGroups(aGroupPos);
|
|
|
|
if ( ValidRow(nStartRow+nSize) || bLastRowIncluded )
|
|
{
|
|
// Listeners have been removed in UpdateReference
|
|
StartNeededListeners();
|
|
|
|
// At least all cells using range names pointing relative to the moved
|
|
// range must be recalculated, and all cells marked postponed dirty.
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
a->SetDirtyIfPostponed();
|
|
}
|
|
|
|
{
|
|
BroadcastRecalcOnRefMoveGuard g(this);
|
|
std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
|
|
}
|
|
}
|
|
|
|
if (pChartListenerCollection)
|
|
pChartListenerCollection->UpdateDirtyCharts();
|
|
}
|
|
|
|
void ScDocument::DeleteRow( const ScRange& rRange )
|
|
{
|
|
DeleteRow( rRange.aStart.Col(), rRange.aStart.Tab(),
|
|
rRange.aEnd.Col(), rRange.aEnd.Tab(),
|
|
rRange.aStart.Row(), static_cast<SCSIZE>(rRange.aEnd.Row()-rRange.aStart.Row()+1) );
|
|
}
|
|
|
|
bool ScDocument::CanInsertCol( const ScRange& rRange ) const
|
|
{
|
|
SCCOL nStartCol = rRange.aStart.Col();
|
|
SCROW nStartRow = rRange.aStart.Row();
|
|
SCTAB nStartTab = rRange.aStart.Tab();
|
|
SCCOL nEndCol = rRange.aEnd.Col();
|
|
SCROW nEndRow = rRange.aEnd.Row();
|
|
SCTAB nEndTab = rRange.aEnd.Tab();
|
|
PutInOrder( nStartCol, nEndCol );
|
|
PutInOrder( nStartRow, nEndRow );
|
|
PutInOrder( nStartTab, nEndTab );
|
|
SCSIZE nSize = static_cast<SCSIZE>(nEndCol - nStartCol + 1);
|
|
|
|
bool bTest = true;
|
|
for (SCTAB i = nStartTab; i <= nEndTab && bTest && i < GetTableCount(); i++)
|
|
if (maTabs[i])
|
|
bTest &= maTabs[i]->TestInsertCol( nStartRow, nEndRow, nSize );
|
|
|
|
return bTest;
|
|
}
|
|
|
|
bool ScDocument::InsertCol( SCROW nStartRow, SCTAB nStartTab,
|
|
SCROW nEndRow, SCTAB nEndTab,
|
|
SCCOL nStartCol, SCSIZE nSize, ScDocument* pRefUndoDoc,
|
|
const ScMarkData* pTabMark )
|
|
{
|
|
SCTAB i;
|
|
|
|
PutInOrder( nStartRow, nEndRow );
|
|
PutInOrder( nStartTab, nEndTab );
|
|
if ( pTabMark )
|
|
{
|
|
nStartTab = 0;
|
|
nEndTab = GetTableCount() - 1;
|
|
}
|
|
|
|
bool bTest = true;
|
|
bool bRet = false;
|
|
bool bOldAutoCalc = GetAutoCalc();
|
|
SetAutoCalc( false ); // avoid multiple calculations
|
|
bool oldDelayedDeleteBroadcasters = IsDelayedDeletingBroadcasters();
|
|
EnableDelayDeletingBroadcasters( true );
|
|
for ( i = nStartTab; i <= nEndTab && bTest && i < GetTableCount(); i++)
|
|
if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
|
|
bTest &= maTabs[i]->TestInsertCol( nStartRow, nEndRow, nSize );
|
|
if (bTest)
|
|
{
|
|
// handle chunks of consecutive selected sheets together
|
|
SCTAB nTabRangeStart = nStartTab;
|
|
SCTAB nTabRangeEnd = nEndTab;
|
|
lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
|
|
do
|
|
{
|
|
UpdateBroadcastAreas( URM_INSDEL, ScRange(
|
|
ScAddress( nStartCol, nStartRow, nTabRangeStart ),
|
|
ScAddress( MaxCol(), nEndRow, nTabRangeEnd )), static_cast<SCCOL>(nSize), 0, 0 );
|
|
}
|
|
while (lcl_GetNextTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));
|
|
|
|
lcl_GetFirstTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
|
|
|
|
sc::RefUpdateContext aCxt(*this);
|
|
aCxt.meMode = URM_INSDEL;
|
|
aCxt.maRange = ScRange(nStartCol, nStartRow, nTabRangeStart, MaxCol(), nEndRow, nTabRangeEnd);
|
|
aCxt.mnColDelta = nSize;
|
|
do
|
|
{
|
|
UpdateReference(aCxt, pRefUndoDoc, true, false);
|
|
}
|
|
while ( lcl_GetNextTabRange( nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));
|
|
|
|
for (i = nStartTab; i <= nEndTab && i < GetTableCount(); i++)
|
|
if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
|
|
maTabs[i]->InsertCol(aCxt.maRegroupCols, nStartCol, nStartRow, nEndRow, nSize);
|
|
|
|
if ( pChangeTrack && pChangeTrack->IsInDeleteUndo() )
|
|
{ // A new Listening is needed when references to deleted ranges are restored,
|
|
// previous Listeners were removed in FormulaCell UpdateReference.
|
|
StartAllListeners();
|
|
}
|
|
else
|
|
{
|
|
// Listeners have been removed in UpdateReference
|
|
StartNeededListeners();
|
|
// At least all cells using range names pointing relative to the
|
|
// moved range must be recalculated, and all cells marked postponed
|
|
// dirty.
|
|
{
|
|
ScBulkBroadcast aBulkBroadcast(GetBASM(), SfxHintId::ScDataChanged);
|
|
std::for_each(maTabs.begin(), maTabs.end(), SetDirtyIfPostponedHandler());
|
|
}
|
|
// Cells containing functions such as CELL, COLUMN or ROW may have
|
|
// changed their values on relocation. Broadcast them.
|
|
{
|
|
BroadcastRecalcOnRefMoveGuard g(this);
|
|
std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
|
|
}
|
|
}
|
|
bRet = true;
|
|
}
|
|
EnableDelayDeletingBroadcasters( oldDelayedDeleteBroadcasters );
|
|
SetAutoCalc( bOldAutoCalc );
|
|
if ( bRet && pChartListenerCollection )
|
|
pChartListenerCollection->UpdateDirtyCharts();
|
|
return bRet;
|
|
}
|
|
|
|
bool ScDocument::InsertCol( const ScRange& rRange )
|
|
{
|
|
return InsertCol( rRange.aStart.Row(), rRange.aStart.Tab(),
|
|
rRange.aEnd.Row(), rRange.aEnd.Tab(),
|
|
rRange.aStart.Col(), static_cast<SCSIZE>(rRange.aEnd.Col()-rRange.aStart.Col()+1) );
|
|
}
|
|
|
|
void ScDocument::DeleteCol(SCROW nStartRow, SCTAB nStartTab, SCROW nEndRow, SCTAB nEndTab,
|
|
SCCOL nStartCol, SCSIZE nSize, ScDocument* pRefUndoDoc,
|
|
bool* pUndoOutline, const ScMarkData* pTabMark )
|
|
{
|
|
SCTAB i;
|
|
|
|
PutInOrder( nStartRow, nEndRow );
|
|
PutInOrder( nStartTab, nEndTab );
|
|
if ( pTabMark )
|
|
{
|
|
nStartTab = 0;
|
|
nEndTab = GetTableCount() - 1;
|
|
}
|
|
|
|
sc::AutoCalcSwitch aACSwitch(*this, false); // avoid multiple calculations
|
|
ScBulkBroadcast aBulkBroadcast(GetBASM(), SfxHintId::ScDataChanged);
|
|
|
|
// handle chunks of consecutive selected sheets together
|
|
SCTAB nTabRangeStart = nStartTab;
|
|
SCTAB nTabRangeEnd = nEndTab;
|
|
lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
|
|
do
|
|
{
|
|
if ( ValidCol(sal::static_int_cast<SCCOL>(nStartCol+nSize)) )
|
|
{
|
|
DelBroadcastAreasInRange( ScRange(
|
|
ScAddress( nStartCol, nStartRow, nTabRangeStart ),
|
|
ScAddress( sal::static_int_cast<SCCOL>(nStartCol+nSize-1), nEndRow, nTabRangeEnd ) ) );
|
|
UpdateBroadcastAreas( URM_INSDEL, ScRange(
|
|
ScAddress( sal::static_int_cast<SCCOL>(nStartCol+nSize), nStartRow, nTabRangeStart ),
|
|
ScAddress( MaxCol(), nEndRow, nTabRangeEnd )), -static_cast<SCCOL>(nSize), 0, 0 );
|
|
}
|
|
else
|
|
DelBroadcastAreasInRange( ScRange(
|
|
ScAddress( nStartCol, nStartRow, nTabRangeStart ),
|
|
ScAddress( MaxCol(), nEndRow, nTabRangeEnd ) ) );
|
|
}
|
|
while (lcl_GetNextTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));
|
|
|
|
sc::RefUpdateContext aCxt(*this);
|
|
const bool bLastColIncluded = (static_cast<SCCOL>(nStartCol + nSize) == GetMaxColCount() && ValidCol(nStartCol));
|
|
if ( ValidCol(sal::static_int_cast<SCCOL>(nStartCol+nSize)) || bLastColIncluded )
|
|
{
|
|
lcl_GetFirstTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount());
|
|
aCxt.meMode = URM_INSDEL;
|
|
aCxt.mnColDelta = -static_cast<SCCOL>(nSize);
|
|
if (bLastColIncluded)
|
|
{
|
|
// Last column is included, shift a virtually non-existent column in.
|
|
aCxt.maRange = ScRange( GetMaxColCount(), nStartRow, nTabRangeStart, GetMaxColCount(), nEndRow, nTabRangeEnd);
|
|
}
|
|
else
|
|
{
|
|
aCxt.maRange = ScRange( sal::static_int_cast<SCCOL>(nStartCol+nSize), nStartRow, nTabRangeStart,
|
|
MaxCol(), nEndRow, nTabRangeEnd);
|
|
}
|
|
do
|
|
{
|
|
UpdateReference(aCxt, pRefUndoDoc, true, false);
|
|
}
|
|
while (lcl_GetNextTabRange(nTabRangeStart, nTabRangeEnd, pTabMark, GetTableCount()));
|
|
}
|
|
|
|
if (pUndoOutline)
|
|
*pUndoOutline = false;
|
|
|
|
for (i = nStartTab; i <= nEndTab && i < GetTableCount(); ++i)
|
|
{
|
|
if (maTabs[i] && (!pTabMark || pTabMark->GetTableSelect(i)))
|
|
maTabs[i]->DeleteCol(aCxt.maRegroupCols, nStartCol, nStartRow, nEndRow, nSize, pUndoOutline);
|
|
}
|
|
|
|
if ( ValidCol(sal::static_int_cast<SCCOL>(nStartCol+nSize)) || bLastColIncluded )
|
|
{
|
|
// Listeners have been removed in UpdateReference
|
|
StartNeededListeners();
|
|
|
|
// At least all cells using range names pointing relative to the moved
|
|
// range must be recalculated, and all cells marked postponed dirty.
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
a->SetDirtyIfPostponed();
|
|
}
|
|
|
|
{
|
|
BroadcastRecalcOnRefMoveGuard g(this);
|
|
std::for_each(maTabs.begin(), maTabs.end(), BroadcastRecalcOnRefMoveHandler());
|
|
}
|
|
}
|
|
|
|
if (pChartListenerCollection)
|
|
pChartListenerCollection->UpdateDirtyCharts();
|
|
}
|
|
|
|
void ScDocument::DeleteCol( const ScRange& rRange )
|
|
{
|
|
DeleteCol( rRange.aStart.Row(), rRange.aStart.Tab(),
|
|
rRange.aEnd.Row(), rRange.aEnd.Tab(),
|
|
rRange.aStart.Col(), static_cast<SCSIZE>(rRange.aEnd.Col()-rRange.aStart.Col()+1) );
|
|
}
|
|
|
|
// for Area-Links: Insert/delete cells, when the range is changed.
|
|
// (without Paint)
|
|
|
|
static void lcl_GetInsDelRanges( const ScRange& rOld, const ScRange& rNew,
|
|
ScRange& rColRange, bool& rInsCol, bool& rDelCol,
|
|
ScRange& rRowRange, bool& rInsRow, bool& rDelRow )
|
|
{
|
|
OSL_ENSURE( rOld.aStart == rNew.aStart, "FitBlock: Beginning is different" );
|
|
|
|
rInsCol = rDelCol = rInsRow = rDelRow = false;
|
|
|
|
SCCOL nStartX = rOld.aStart.Col();
|
|
SCROW nStartY = rOld.aStart.Row();
|
|
SCCOL nOldEndX = rOld.aEnd.Col();
|
|
SCROW nOldEndY = rOld.aEnd.Row();
|
|
SCCOL nNewEndX = rNew.aEnd.Col();
|
|
SCROW nNewEndY = rNew.aEnd.Row();
|
|
SCTAB nTab = rOld.aStart.Tab();
|
|
|
|
// if more rows, columns are inserted/deleted at the old height.
|
|
bool bGrowY = ( nNewEndY > nOldEndY );
|
|
SCROW nColEndY = bGrowY ? nOldEndY : nNewEndY;
|
|
SCCOL nRowEndX = bGrowY ? nNewEndX : nOldEndX;
|
|
|
|
// Columns
|
|
|
|
if ( nNewEndX > nOldEndX ) // Insert columns
|
|
{
|
|
rColRange = ScRange( nOldEndX+1, nStartY, nTab, nNewEndX, nColEndY, nTab );
|
|
rInsCol = true;
|
|
}
|
|
else if ( nNewEndX < nOldEndX ) // Delete columns
|
|
{
|
|
rColRange = ScRange( nNewEndX+1, nStartY, nTab, nOldEndX, nColEndY, nTab );
|
|
rDelCol = true;
|
|
}
|
|
|
|
// Rows
|
|
|
|
if ( nNewEndY > nOldEndY ) // Insert rows
|
|
{
|
|
rRowRange = ScRange( nStartX, nOldEndY+1, nTab, nRowEndX, nNewEndY, nTab );
|
|
rInsRow = true;
|
|
}
|
|
else if ( nNewEndY < nOldEndY ) // Delete rows
|
|
{
|
|
rRowRange = ScRange( nStartX, nNewEndY+1, nTab, nRowEndX, nOldEndY, nTab );
|
|
rDelRow = true;
|
|
}
|
|
}
|
|
|
|
bool ScDocument::HasPartOfMerged( const ScRange& rRange )
|
|
{
|
|
bool bPart = false;
|
|
SCTAB nTab = rRange.aStart.Tab();
|
|
|
|
SCCOL nStartX = rRange.aStart.Col();
|
|
SCROW nStartY = rRange.aStart.Row();
|
|
SCCOL nEndX = rRange.aEnd.Col();
|
|
SCROW nEndY = rRange.aEnd.Row();
|
|
|
|
if (HasAttrib( nStartX, nStartY, nTab, nEndX, nEndY, nTab,
|
|
HasAttrFlags::Merged | HasAttrFlags::Overlapped ))
|
|
{
|
|
ExtendMerge( nStartX, nStartY, nEndX, nEndY, nTab );
|
|
ExtendOverlapped( nStartX, nStartY, nEndX, nEndY, nTab );
|
|
|
|
bPart = ( nStartX != rRange.aStart.Col() || nEndX != rRange.aEnd.Col() ||
|
|
nStartY != rRange.aStart.Row() || nEndY != rRange.aEnd.Row() );
|
|
}
|
|
return bPart;
|
|
}
|
|
|
|
formula::FormulaTokenRef ScDocument::ResolveStaticReference( const ScAddress& rPos )
|
|
{
|
|
SCTAB nTab = rPos.Tab();
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->ResolveStaticReference(rPos.Col(), rPos.Row());
|
|
return formula::FormulaTokenRef();
|
|
}
|
|
|
|
formula::FormulaTokenRef ScDocument::ResolveStaticReference( const ScRange& rRange )
|
|
{
|
|
SCTAB nTab = rRange.aStart.Tab();
|
|
if (nTab != rRange.aEnd.Tab() || !HasTable(nTab))
|
|
return formula::FormulaTokenRef();
|
|
|
|
return maTabs[nTab]->ResolveStaticReference(
|
|
rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
|
|
}
|
|
|
|
formula::VectorRefArray ScDocument::FetchVectorRefArray( const ScAddress& rPos, SCROW nLength )
|
|
{
|
|
SCTAB nTab = rPos.Tab();
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->FetchVectorRefArray(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1);
|
|
return formula::VectorRefArray();
|
|
}
|
|
|
|
#ifdef DBG_UTIL
|
|
void ScDocument::AssertNoInterpretNeeded( const ScAddress& rPos, SCROW nLength )
|
|
{
|
|
SCTAB nTab = rPos.Tab();
|
|
assert(HasTable(nTab));
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->AssertNoInterpretNeeded(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1);
|
|
}
|
|
#endif
|
|
|
|
void ScDocument::UnlockAdjustHeight()
|
|
{
|
|
assert(nAdjustHeightLock > 0);
|
|
if(nAdjustHeightLock > 0)
|
|
--nAdjustHeightLock;
|
|
}
|
|
|
|
bool ScDocument::HandleRefArrayForParallelism( const ScAddress& rPos, SCROW nLength, const ScFormulaCellGroupRef& mxGroup, ScAddress* pDirtiedAddress)
|
|
{
|
|
SCTAB nTab = rPos.Tab();
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
bool bRet = pTable->HandleRefArrayForParallelism(rPos.Col(), rPos.Row(), rPos.Row()+nLength-1, mxGroup, pDirtiedAddress);
|
|
if (!bRet && pDirtiedAddress && pDirtiedAddress->Row() != -1)
|
|
{
|
|
pDirtiedAddress->SetCol(rPos.Col());
|
|
pDirtiedAddress->SetTab(nTab);
|
|
}
|
|
return bRet;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::CanFitBlock( const ScRange& rOld, const ScRange& rNew )
|
|
{
|
|
if ( rOld == rNew )
|
|
return true;
|
|
|
|
bool bOk = true;
|
|
bool bInsCol,bDelCol,bInsRow,bDelRow;
|
|
ScRange aColRange,aRowRange;
|
|
lcl_GetInsDelRanges( rOld, rNew, aColRange,bInsCol,bDelCol, aRowRange,bInsRow,bDelRow );
|
|
|
|
if ( bInsCol && !CanInsertCol( aColRange ) ) // Cells at the edge ?
|
|
bOk = false;
|
|
if ( bInsRow && !CanInsertRow( aRowRange ) ) // Cells at the edge ?
|
|
bOk = false;
|
|
|
|
if ( bInsCol || bDelCol )
|
|
{
|
|
aColRange.aEnd.SetCol(MaxCol());
|
|
if ( HasPartOfMerged(aColRange) )
|
|
bOk = false;
|
|
}
|
|
if ( bInsRow || bDelRow )
|
|
{
|
|
aRowRange.aEnd.SetRow(MaxRow());
|
|
if ( HasPartOfMerged(aRowRange) )
|
|
bOk = false;
|
|
}
|
|
|
|
return bOk;
|
|
}
|
|
|
|
void ScDocument::FitBlock( const ScRange& rOld, const ScRange& rNew, bool bClear )
|
|
{
|
|
if (bClear)
|
|
DeleteAreaTab( rOld, InsertDeleteFlags::ALL );
|
|
|
|
bool bInsCol,bDelCol,bInsRow,bDelRow;
|
|
ScRange aColRange,aRowRange;
|
|
lcl_GetInsDelRanges( rOld, rNew, aColRange,bInsCol,bDelCol, aRowRange,bInsRow,bDelRow );
|
|
|
|
if ( bInsCol )
|
|
InsertCol( aColRange ); // First insert columns
|
|
if ( bInsRow )
|
|
InsertRow( aRowRange );
|
|
|
|
if ( bDelRow )
|
|
DeleteRow( aRowRange ); // First delete rows
|
|
if ( bDelCol )
|
|
DeleteCol( aColRange );
|
|
|
|
// Expand references to inserted rows
|
|
|
|
if ( bInsCol || bInsRow )
|
|
{
|
|
ScRange aGrowSource = rOld;
|
|
aGrowSource.aEnd.SetCol(std::min( rOld.aEnd.Col(), rNew.aEnd.Col() ));
|
|
aGrowSource.aEnd.SetRow(std::min( rOld.aEnd.Row(), rNew.aEnd.Row() ));
|
|
SCCOL nGrowX = bInsCol ? ( rNew.aEnd.Col() - rOld.aEnd.Col() ) : 0;
|
|
SCROW nGrowY = bInsRow ? ( rNew.aEnd.Row() - rOld.aEnd.Row() ) : 0;
|
|
UpdateGrow( aGrowSource, nGrowX, nGrowY );
|
|
}
|
|
}
|
|
|
|
void ScDocument::DeleteArea(
|
|
SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark,
|
|
InsertDeleteFlags nDelFlag, bool bBroadcast, sc::ColumnSpanSet* pBroadcastSpans )
|
|
{
|
|
sc::AutoCalcSwitch aACSwitch(*this, false);
|
|
|
|
PutInOrder( nCol1, nCol2 );
|
|
PutInOrder( nRow1, nRow2 );
|
|
|
|
std::vector<ScAddress> aGroupPos;
|
|
// Destroy and reconstruct listeners only if content is affected.
|
|
bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag);
|
|
if (bDelContent)
|
|
{
|
|
// Record the positions of top and/or bottom formula groups that intersect
|
|
// the area borders.
|
|
sc::EndListeningContext aCxt(*this);
|
|
ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0);
|
|
for (SCTAB i = 0; i < GetTableCount(); i++)
|
|
{
|
|
if (rMark.GetTableSelect(i))
|
|
{
|
|
aRange.aStart.SetTab(i);
|
|
aRange.aEnd.SetTab(i);
|
|
|
|
EndListeningIntersectedGroups(aCxt, aRange, &aGroupPos);
|
|
}
|
|
}
|
|
aCxt.purgeEmptyBroadcasters();
|
|
}
|
|
|
|
for (SCTAB i = 0; i < GetTableCount(); i++)
|
|
if (maTabs[i])
|
|
if ( rMark.GetTableSelect(i) || bIsUndo )
|
|
maTabs[i]->DeleteArea(nCol1, nRow1, nCol2, nRow2, nDelFlag, bBroadcast, pBroadcastSpans);
|
|
|
|
if (!bDelContent)
|
|
return;
|
|
|
|
// Re-start listeners on those top bottom groups that have been split.
|
|
SetNeedsListeningGroups(aGroupPos);
|
|
StartNeededListeners();
|
|
|
|
// If formula groups were split their listeners were destroyed and may
|
|
// need to be notified now that they're restored, ScTable::DeleteArea()
|
|
// couldn't do that.
|
|
if (aGroupPos.empty())
|
|
return;
|
|
|
|
ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0);
|
|
for (SCTAB i = 0; i < GetTableCount(); i++)
|
|
{
|
|
if (rMark.GetTableSelect(i))
|
|
{
|
|
aRange.aStart.SetTab(i);
|
|
aRange.aEnd.SetTab(i);
|
|
SetDirty( aRange, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScDocument::DeleteAreaTab(SCCOL nCol1, SCROW nRow1,
|
|
SCCOL nCol2, SCROW nRow2,
|
|
SCTAB nTab, InsertDeleteFlags nDelFlag)
|
|
{
|
|
PutInOrder( nCol1, nCol2 );
|
|
PutInOrder( nRow1, nRow2 );
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
bool bOldAutoCalc = GetAutoCalc();
|
|
SetAutoCalc( false ); // avoid multiple calculations
|
|
pTable->DeleteArea(nCol1, nRow1, nCol2, nRow2, nDelFlag);
|
|
SetAutoCalc( bOldAutoCalc );
|
|
}
|
|
}
|
|
|
|
void ScDocument::DeleteAreaTab( const ScRange& rRange, InsertDeleteFlags nDelFlag )
|
|
{
|
|
for ( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); nTab++ )
|
|
DeleteAreaTab( rRange.aStart.Col(), rRange.aStart.Row(),
|
|
rRange.aEnd.Col(), rRange.aEnd.Row(),
|
|
nTab, nDelFlag );
|
|
}
|
|
|
|
void ScDocument::InitUndoSelected(const ScDocument& rSrcDoc, const ScMarkData& rTabSelection,
|
|
bool bColInfo, bool bRowInfo )
|
|
{
|
|
if (bIsUndo)
|
|
{
|
|
Clear();
|
|
|
|
SharePooledResources(&rSrcDoc);
|
|
|
|
for (SCTAB nTab = 0; nTab <= rTabSelection.GetLastSelected(); nTab++)
|
|
if ( rTabSelection.GetTableSelect( nTab ) )
|
|
{
|
|
ScTableUniquePtr pTable(new ScTable(*this, nTab, OUString(), bColInfo, bRowInfo));
|
|
if (nTab < GetTableCount())
|
|
maTabs[nTab] = std::move(pTable);
|
|
else
|
|
maTabs.push_back(std::move(pTable));
|
|
}
|
|
else
|
|
{
|
|
if (nTab < GetTableCount())
|
|
maTabs[nTab]=nullptr;
|
|
else
|
|
maTabs.push_back(nullptr);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OSL_FAIL("InitUndo");
|
|
}
|
|
}
|
|
|
|
void ScDocument::InitUndo( const ScDocument& rSrcDoc, SCTAB nTab1, SCTAB nTab2,
|
|
bool bColInfo, bool bRowInfo )
|
|
{
|
|
if (!bIsUndo)
|
|
{
|
|
OSL_FAIL("InitUndo");
|
|
return;
|
|
}
|
|
|
|
Clear();
|
|
|
|
// Undo document shares its pooled resources with the source document.
|
|
SharePooledResources(&rSrcDoc);
|
|
|
|
if (rSrcDoc.mpShell->GetMedium())
|
|
maFileURL = rSrcDoc.mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
|
|
|
|
if (nTab2 >= GetTableCount())
|
|
maTabs.resize(nTab2 + 1);
|
|
for (SCTAB nTab = nTab1; nTab <= nTab2; nTab++)
|
|
{
|
|
maTabs[nTab].reset(new ScTable(*this, nTab, OUString(), bColInfo, bRowInfo));
|
|
}
|
|
}
|
|
|
|
void ScDocument::AddUndoTab( SCTAB nTab1, SCTAB nTab2, bool bColInfo, bool bRowInfo )
|
|
{
|
|
if (!bIsUndo)
|
|
{
|
|
OSL_FAIL("AddUndoTab");
|
|
return;
|
|
}
|
|
|
|
if (nTab2 >= GetTableCount())
|
|
{
|
|
maTabs.resize(nTab2+1);
|
|
}
|
|
|
|
for (SCTAB nTab = nTab1; nTab <= nTab2; nTab++)
|
|
if (!maTabs[nTab])
|
|
{
|
|
maTabs[nTab].reset( new ScTable(*this, nTab, OUString(), bColInfo, bRowInfo) );
|
|
}
|
|
}
|
|
|
|
void ScDocument::SetCutMode( bool bVal )
|
|
{
|
|
if (bIsClip)
|
|
GetClipParam().mbCutMode = bVal;
|
|
else
|
|
{
|
|
OSL_FAIL("SetCutMode without bIsClip");
|
|
}
|
|
}
|
|
|
|
bool ScDocument::IsCutMode()
|
|
{
|
|
if (bIsClip)
|
|
return GetClipParam().mbCutMode;
|
|
else
|
|
{
|
|
OSL_FAIL("IsCutMode without bIsClip");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void ScDocument::CopyToDocument(SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
|
|
SCCOL nCol2, SCROW nRow2, SCTAB nTab2,
|
|
InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc,
|
|
const ScMarkData* pMarks, bool bColRowFlags )
|
|
{
|
|
if (ValidTab(nTab1) && ValidTab(nTab2))
|
|
{
|
|
ScRange aThisRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2);
|
|
CopyToDocument(aThisRange, nFlags, bOnlyMarked, rDestDoc, pMarks, bColRowFlags);
|
|
}
|
|
}
|
|
|
|
void ScDocument::UndoToDocument(SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
|
|
SCCOL nCol2, SCROW nRow2, SCTAB nTab2,
|
|
InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc)
|
|
{
|
|
PutInOrder( nCol1, nCol2 );
|
|
PutInOrder( nRow1, nRow2 );
|
|
PutInOrder( nTab1, nTab2 );
|
|
if (!(ValidTab(nTab1) && ValidTab(nTab2)))
|
|
return;
|
|
|
|
sc::AutoCalcSwitch aACSwitch(rDestDoc, false); // avoid multiple calculations
|
|
|
|
if (nTab1 > 0)
|
|
CopyToDocument(0, 0, 0, MaxCol(), MaxRow(), nTab1-1, InsertDeleteFlags::FORMULA, false, rDestDoc);
|
|
|
|
sc::CopyToDocContext aCxt(rDestDoc);
|
|
assert(nTab2 < GetTableCount() && nTab2 < rDestDoc.GetTableCount());
|
|
for (SCTAB i = nTab1; i <= nTab2; i++)
|
|
{
|
|
if (maTabs[i] && rDestDoc.maTabs[i])
|
|
maTabs[i]->UndoToTable(aCxt, nCol1, nRow1, nCol2, nRow2, nFlags,
|
|
bOnlyMarked, rDestDoc.maTabs[i].get());
|
|
}
|
|
|
|
if (nTab2 < MAXTAB)
|
|
CopyToDocument(0, 0, nTab2+1, MaxCol(), MaxRow(), MAXTAB, InsertDeleteFlags::FORMULA, false, rDestDoc);
|
|
}
|
|
|
|
void ScDocument::CopyToDocument(const ScRange& rRange,
|
|
InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc,
|
|
const ScMarkData* pMarks, bool bColRowFlags)
|
|
{
|
|
ScRange aNewRange = rRange;
|
|
aNewRange.PutInOrder();
|
|
|
|
if (rDestDoc.aDocName.isEmpty())
|
|
rDestDoc.aDocName = aDocName;
|
|
|
|
sc::AutoCalcSwitch aACSwitch(rDestDoc, false); // avoid multiple calculations
|
|
ScBulkBroadcast aBulkBroadcast(rDestDoc.GetBASM(), SfxHintId::ScDataChanged);
|
|
sc::DelayDeletingBroadcasters delayDeletingBroadcasters(*this);
|
|
|
|
sc::CopyToDocContext aCxt(rDestDoc);
|
|
aCxt.setStartListening(false);
|
|
|
|
SCTAB nMinSizeBothTabs = std::min(GetTableCount(), rDestDoc.GetTableCount());
|
|
for (SCTAB i = aNewRange.aStart.Tab(); i <= aNewRange.aEnd.Tab() && i < nMinSizeBothTabs; i++)
|
|
{
|
|
ScTable* pTab = FetchTable(i);
|
|
ScTable* pDestTab = rDestDoc.FetchTable(i);
|
|
if (!pTab || !pDestTab)
|
|
continue;
|
|
|
|
pTab->CopyToTable(
|
|
aCxt, aNewRange.aStart.Col(), aNewRange.aStart.Row(), aNewRange.aEnd.Col(), aNewRange.aEnd.Row(),
|
|
nFlags, bOnlyMarked, pDestTab, pMarks, false, bColRowFlags,
|
|
/*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true);
|
|
}
|
|
|
|
rDestDoc.StartAllListeners(aNewRange);
|
|
}
|
|
|
|
void ScDocument::UndoToDocument(const ScRange& rRange,
|
|
InsertDeleteFlags nFlags, bool bOnlyMarked, ScDocument& rDestDoc)
|
|
{
|
|
sc::AutoCalcSwitch aAutoCalcSwitch(*this, false);
|
|
|
|
ScRange aNewRange = rRange;
|
|
aNewRange.PutInOrder();
|
|
SCTAB nTab1 = aNewRange.aStart.Tab();
|
|
SCTAB nTab2 = aNewRange.aEnd.Tab();
|
|
|
|
sc::CopyToDocContext aCxt(rDestDoc);
|
|
if (nTab1 > 0)
|
|
CopyToDocument(0, 0, 0, MaxCol(), MaxRow(), nTab1-1, InsertDeleteFlags::FORMULA, false, rDestDoc);
|
|
|
|
SCTAB nMinSizeBothTabs = std::min(GetTableCount(), rDestDoc.GetTableCount());
|
|
for (SCTAB i = nTab1; i <= nTab2 && i < nMinSizeBothTabs; i++)
|
|
{
|
|
if (maTabs[i] && rDestDoc.maTabs[i])
|
|
maTabs[i]->UndoToTable(aCxt, aNewRange.aStart.Col(), aNewRange.aStart.Row(),
|
|
aNewRange.aEnd.Col(), aNewRange.aEnd.Row(),
|
|
nFlags, bOnlyMarked, rDestDoc.maTabs[i].get());
|
|
}
|
|
|
|
if (nTab2 < GetTableCount())
|
|
CopyToDocument(0, 0 , nTab2+1, MaxCol(), MaxRow(), GetTableCount(), InsertDeleteFlags::FORMULA, false, rDestDoc);
|
|
}
|
|
|
|
void ScDocument::CopyToClip(const ScClipParam& rClipParam,
|
|
ScDocument* pClipDoc, const ScMarkData* pMarks,
|
|
bool bKeepScenarioFlags, bool bIncludeObjects )
|
|
{
|
|
OSL_ENSURE( pMarks, "CopyToClip: ScMarkData fails" );
|
|
|
|
if (bIsClip)
|
|
return;
|
|
|
|
if (!pClipDoc)
|
|
{
|
|
SAL_WARN("sc", "CopyToClip: no ClipDoc");
|
|
pClipDoc = ScModule::GetClipDoc();
|
|
}
|
|
|
|
if (mpShell->GetMedium())
|
|
{
|
|
pClipDoc->maFileURL = mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
|
|
// for unsaved files use the title name and adjust during save of file
|
|
if (pClipDoc->maFileURL.isEmpty())
|
|
pClipDoc->maFileURL = mpShell->GetName();
|
|
}
|
|
else
|
|
{
|
|
pClipDoc->maFileURL = mpShell->GetName();
|
|
}
|
|
|
|
//init maTabNames
|
|
for (const auto& rxTab : maTabs)
|
|
{
|
|
if( rxTab )
|
|
{
|
|
OUString aTabName = rxTab->GetName();
|
|
pClipDoc->maTabNames.push_back(aTabName);
|
|
}
|
|
else
|
|
pClipDoc->maTabNames.emplace_back();
|
|
}
|
|
|
|
pClipDoc->aDocName = aDocName;
|
|
pClipDoc->SetClipParam(rClipParam);
|
|
ScRange aClipRange = rClipParam.getWholeRange();
|
|
SCTAB nEndTab = GetTableCount();
|
|
|
|
pClipDoc->ResetClip(this, pMarks);
|
|
|
|
sc::CopyToClipContext aCxt(*pClipDoc, bKeepScenarioFlags);
|
|
CopyRangeNamesToClip(pClipDoc, aClipRange, pMarks);
|
|
|
|
// 1. Copy selected cells
|
|
for (SCTAB i = 0; i < nEndTab; ++i)
|
|
{
|
|
if (!maTabs[i] || i >= pClipDoc->GetTableCount() || !pClipDoc->maTabs[i])
|
|
continue;
|
|
|
|
if ( pMarks && !pMarks->GetTableSelect(i) )
|
|
continue;
|
|
|
|
maTabs[i]->CopyToClip(aCxt, rClipParam.maRanges, pClipDoc->maTabs[i].get());
|
|
}
|
|
|
|
// 2. Copy drawing objects in the selection. Do in after the first "copy cells" pass, because
|
|
// the embedded objects (charts) could reference cells from tabs not (yet) copied; doing it now
|
|
// allows to know what is already copied, to not overwrite attributes of already copied data.
|
|
if (mpDrawLayer && bIncludeObjects)
|
|
{
|
|
for (SCTAB i = 0; i < nEndTab; ++i)
|
|
{
|
|
tools::Rectangle aObjRect = GetMMRect(aClipRange.aStart.Col(), aClipRange.aStart.Row(),
|
|
aClipRange.aEnd.Col(), aClipRange.aEnd.Row(), i);
|
|
mpDrawLayer->CopyToClip(pClipDoc, i, aObjRect);
|
|
}
|
|
}
|
|
|
|
// Make sure to mark overlapped cells.
|
|
pClipDoc->ExtendMerge(aClipRange, true);
|
|
}
|
|
|
|
void ScDocument::CopyStaticToDocument(const ScRange& rSrcRange, SCTAB nDestTab, ScDocument& rDestDoc)
|
|
{
|
|
ScTable* pSrcTab = rSrcRange.aStart.Tab() < GetTableCount() ? maTabs[rSrcRange.aStart.Tab()].get() : nullptr;
|
|
ScTable* pDestTab = nDestTab < rDestDoc.GetTableCount() ? rDestDoc.maTabs[nDestTab].get() : nullptr;
|
|
|
|
if (!pSrcTab || !pDestTab)
|
|
return;
|
|
|
|
rDestDoc.GetFormatTable()->MergeFormatter(*GetFormatTable());
|
|
SvNumberFormatterMergeMap aMap = rDestDoc.GetFormatTable()->ConvertMergeTableToMap();
|
|
|
|
pSrcTab->CopyStaticToDocument(
|
|
rSrcRange.aStart.Col(), rSrcRange.aStart.Row(), rSrcRange.aEnd.Col(), rSrcRange.aEnd.Row(),
|
|
aMap, pDestTab);
|
|
}
|
|
|
|
void ScDocument::CopyCellToDocument( const ScAddress& rSrcPos, const ScAddress& rDestPos, ScDocument& rDestDoc )
|
|
{
|
|
if (!HasTable(rSrcPos.Tab()) || !rDestDoc.HasTable(rDestPos.Tab()))
|
|
return;
|
|
|
|
ScTable& rSrcTab = *maTabs[rSrcPos.Tab()];
|
|
ScTable& rDestTab = *rDestDoc.maTabs[rDestPos.Tab()];
|
|
|
|
rSrcTab.CopyCellToDocument(rSrcPos.Col(), rSrcPos.Row(), rDestPos.Col(), rDestPos.Row(), rDestTab);
|
|
}
|
|
|
|
void ScDocument::CopyTabToClip(SCCOL nCol1, SCROW nRow1,
|
|
SCCOL nCol2, SCROW nRow2,
|
|
SCTAB nTab, ScDocument* pClipDoc)
|
|
{
|
|
if (bIsClip)
|
|
return;
|
|
|
|
if (!pClipDoc)
|
|
{
|
|
SAL_WARN("sc", "CopyTabToClip: no ClipDoc");
|
|
pClipDoc = ScModule::GetClipDoc();
|
|
}
|
|
|
|
if (mpShell->GetMedium())
|
|
{
|
|
pClipDoc->maFileURL = mpShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri);
|
|
// for unsaved files use the title name and adjust during save of file
|
|
if (pClipDoc->maFileURL.isEmpty())
|
|
pClipDoc->maFileURL = mpShell->GetName();
|
|
}
|
|
else
|
|
{
|
|
pClipDoc->maFileURL = mpShell->GetName();
|
|
}
|
|
|
|
//init maTabNames
|
|
for (const auto& rxTab : maTabs)
|
|
{
|
|
if( rxTab )
|
|
{
|
|
OUString aTabName = rxTab->GetName();
|
|
pClipDoc->maTabNames.push_back(aTabName);
|
|
}
|
|
else
|
|
pClipDoc->maTabNames.emplace_back();
|
|
}
|
|
|
|
PutInOrder( nCol1, nCol2 );
|
|
PutInOrder( nRow1, nRow2 );
|
|
|
|
ScClipParam& rClipParam = pClipDoc->GetClipParam();
|
|
pClipDoc->aDocName = aDocName;
|
|
rClipParam.maRanges.RemoveAll();
|
|
rClipParam.maRanges.push_back(ScRange(nCol1, nRow1, 0, nCol2, nRow2, 0));
|
|
pClipDoc->ResetClip( this, nTab );
|
|
|
|
sc::CopyToClipContext aCxt(*pClipDoc, false);
|
|
if (nTab < GetTableCount() && nTab < pClipDoc->GetTableCount())
|
|
if (maTabs[nTab] && pClipDoc->maTabs[nTab])
|
|
maTabs[nTab]->CopyToClip(aCxt, nCol1, nRow1, nCol2, nRow2, pClipDoc->maTabs[nTab].get());
|
|
|
|
pClipDoc->GetClipParam().mbCutMode = false;
|
|
}
|
|
|
|
void ScDocument::TransposeClip(ScDocument* pTransClip, InsertDeleteFlags nFlags, bool bAsLink,
|
|
bool bIncludeFiltered)
|
|
{
|
|
OSL_ENSURE( bIsClip && pTransClip && pTransClip->bIsClip,
|
|
"TransposeClip with wrong Document" );
|
|
|
|
// initialize
|
|
// -> pTransClip has to be deleted before the original document!
|
|
|
|
pTransClip->ResetClip(this, nullptr); // all
|
|
|
|
// Take over range
|
|
|
|
if (pRangeName)
|
|
{
|
|
pTransClip->GetRangeName()->clear();
|
|
for (const auto& rEntry : *pRangeName)
|
|
{
|
|
sal_uInt16 nIndex = rEntry.second->GetIndex();
|
|
ScRangeData* pData = new ScRangeData(*rEntry.second);
|
|
if (pTransClip->pRangeName->insert(pData))
|
|
pData->SetIndex(nIndex);
|
|
}
|
|
}
|
|
|
|
ScRange aCombinedClipRange = GetClipParam().getWholeRange();
|
|
|
|
if (!ValidRow(aCombinedClipRange.aEnd.Row() - aCombinedClipRange.aStart.Row()))
|
|
{
|
|
SAL_WARN("sc", "TransposeClip: Too big");
|
|
return;
|
|
}
|
|
|
|
// Transpose of filtered multi range row selection is a special case since filtering
|
|
// and selection are in the same dimension (i.e. row).
|
|
// The filtered row status and the selection ranges are not available at the same time,
|
|
// handle this case specially, do not use GetClipParam().getWholeRange(),
|
|
// instead loop through the ranges, calculate the row offset and handle filtered rows and
|
|
// create in ScClipParam::transpose() a unified range.
|
|
const bool bIsMultiRangeRowFilteredTranspose
|
|
= !bIncludeFiltered && GetClipParam().isMultiRange()
|
|
&& HasFilteredRows(aCombinedClipRange.aStart.Row(), aCombinedClipRange.aEnd.Row(),
|
|
aCombinedClipRange.aStart.Tab())
|
|
&& GetClipParam().meDirection == ScClipParam::Row;
|
|
|
|
ScRangeList aClipRanges;
|
|
if (bIsMultiRangeRowFilteredTranspose)
|
|
aClipRanges = GetClipParam().maRanges;
|
|
else
|
|
aClipRanges = ScRangeList(aCombinedClipRange);
|
|
|
|
// The data
|
|
ScRange aClipRange;
|
|
SCROW nRowCount = 0; // next consecutive row
|
|
for (size_t j = 0, n = aClipRanges.size(); j < n; ++j)
|
|
{
|
|
aClipRange = aClipRanges[j];
|
|
|
|
SCROW nRowOffset = 0;
|
|
if (bIsMultiRangeRowFilteredTranspose)
|
|
{
|
|
// adjust for the rows that are filtered
|
|
nRowOffset = nRowCount;
|
|
|
|
// calculate filtered rows of current clip range
|
|
SCROW nRowCountNonFiltered = CountNonFilteredRows(
|
|
aClipRange.aStart.Row(), aClipRange.aEnd.Row(), aClipRange.aStart.Tab());
|
|
assert(!bIncludeFiltered && "bIsMultiRangeRowFilteredTranspose can only be true if bIncludeFiltered is false");
|
|
nRowCount += nRowCountNonFiltered; // for next iteration
|
|
}
|
|
|
|
for (SCTAB i = 0; i < GetTableCount(); i++)
|
|
{
|
|
if (maTabs[i])
|
|
{
|
|
OSL_ENSURE(pTransClip->maTabs[i], "TransposeClip: Table not there");
|
|
maTabs[i]->TransposeClip(
|
|
aClipRange.aStart.Col(), aClipRange.aStart.Row(), aClipRange.aEnd.Col(),
|
|
aClipRange.aEnd.Row(), aCombinedClipRange.aStart.Row(), nRowOffset,
|
|
pTransClip->maTabs[i].get(), nFlags, bAsLink, bIncludeFiltered);
|
|
|
|
if ( mpDrawLayer && ( nFlags & InsertDeleteFlags::OBJECTS ) )
|
|
{
|
|
// Drawing objects are copied to the new area without transposing.
|
|
// CopyFromClip is used to adjust the objects to the transposed block's
|
|
// cell range area.
|
|
// (mpDrawLayer in the original clipboard document is set only if there
|
|
// are drawing objects to copy)
|
|
|
|
// ToDo: Loop over blocks of non-filtered rows in case of filtered rows exist.
|
|
pTransClip->InitDrawLayer();
|
|
ScAddress aTransposedEnd(
|
|
static_cast<SCCOL>(aClipRange.aEnd.Row() - aClipRange.aStart.Row() + aClipRange.aStart.Col()),
|
|
static_cast<SCROW>(aClipRange.aEnd.Col() - aClipRange.aStart.Col() + aClipRange.aStart.Row()), i);
|
|
ScRange aDestRange(aClipRange.aStart, aTransposedEnd);
|
|
ScAddress aDestStart = aClipRange.aStart;
|
|
pTransClip->mpDrawLayer->CopyFromClip(mpDrawLayer.get(), i, aClipRange, aDestStart, aDestRange, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
pTransClip->SetClipParam(GetClipParam());
|
|
pTransClip->GetClipParam().transpose(*this, bIncludeFiltered,
|
|
bIsMultiRangeRowFilteredTranspose);
|
|
|
|
// This happens only when inserting...
|
|
|
|
GetClipParam().mbCutMode = false;
|
|
}
|
|
|
|
namespace {
|
|
|
|
void copyUsedNamesToClip(ScRangeName* pClipRangeName, ScRangeName* pRangeName,
|
|
const sc::UpdatedRangeNames::NameIndicesType& rUsedNames)
|
|
{
|
|
pClipRangeName->clear();
|
|
for (const auto& rEntry : *pRangeName) //TODO: also DB and Pivot regions!!!
|
|
{
|
|
sal_uInt16 nIndex = rEntry.second->GetIndex();
|
|
bool bInUse = (rUsedNames.count(nIndex) > 0);
|
|
if (!bInUse)
|
|
continue;
|
|
|
|
ScRangeData* pData = new ScRangeData(*rEntry.second);
|
|
if (pClipRangeName->insert(pData))
|
|
pData->SetIndex(nIndex);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void ScDocument::CopyRangeNamesToClip(ScDocument* pClipDoc, const ScRange& rClipRange, const ScMarkData* pMarks)
|
|
{
|
|
if (!pRangeName || pRangeName->empty())
|
|
return;
|
|
|
|
sc::UpdatedRangeNames aUsedNames; // indexes of named ranges that are used in the copied cells
|
|
SCTAB nMinSizeBothTabs = std::min(GetTableCount(), pClipDoc->GetTableCount());
|
|
for (SCTAB i = 0; i < nMinSizeBothTabs; ++i)
|
|
if (maTabs[i] && pClipDoc->maTabs[i])
|
|
if ( !pMarks || pMarks->GetTableSelect(i) )
|
|
maTabs[i]->FindRangeNamesInUse(
|
|
rClipRange.aStart.Col(), rClipRange.aStart.Row(),
|
|
rClipRange.aEnd.Col(), rClipRange.aEnd.Row(), aUsedNames);
|
|
|
|
/* TODO: handle also sheet-local names */
|
|
sc::UpdatedRangeNames::NameIndicesType aUsedGlobalNames( aUsedNames.getUpdatedNames(-1));
|
|
copyUsedNamesToClip(pClipDoc->GetRangeName(), pRangeName.get(), aUsedGlobalNames);
|
|
}
|
|
|
|
ScDocument::NumFmtMergeHandler::NumFmtMergeHandler(ScDocument& rDoc, const ScDocument& rSrcDoc)
|
|
: mrDoc(rDoc)
|
|
{
|
|
mrDoc.MergeNumberFormatter(rSrcDoc);
|
|
}
|
|
|
|
ScDocument::NumFmtMergeHandler::~NumFmtMergeHandler()
|
|
{
|
|
ScMutationGuard aGuard(mrDoc, ScMutationGuardFlags::CORE);
|
|
mrDoc.pFormatExchangeList = nullptr;
|
|
}
|
|
|
|
void ScDocument::PrepareFormulaCalc()
|
|
{
|
|
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
|
|
mpFormulaGroupCxt.reset();
|
|
}
|
|
|
|
SvtBroadcaster* ScDocument::GetBroadcaster( const ScAddress& rPos )
|
|
{
|
|
ScTable* pTab = FetchTable(rPos.Tab());
|
|
if (!pTab)
|
|
return nullptr;
|
|
|
|
return pTab->GetBroadcaster(rPos.Col(), rPos.Row());
|
|
}
|
|
|
|
const SvtBroadcaster* ScDocument::GetBroadcaster( const ScAddress& rPos ) const
|
|
{
|
|
const ScTable* pTab = FetchTable(rPos.Tab());
|
|
if (!pTab)
|
|
return nullptr;
|
|
|
|
return pTab->GetBroadcaster(rPos.Col(), rPos.Row());
|
|
}
|
|
|
|
void ScDocument::DeleteBroadcasters( sc::ColumnBlockPosition& rBlockPos, const ScAddress& rTopPos, SCROW nLength )
|
|
{
|
|
ScTable* pTab = FetchTable(rTopPos.Tab());
|
|
if (!pTab || nLength <= 0)
|
|
return;
|
|
|
|
pTab->DeleteBroadcasters(rBlockPos, rTopPos.Col(), rTopPos.Row(), rTopPos.Row()+nLength-1);
|
|
}
|
|
|
|
#if DUMP_COLUMN_STORAGE
|
|
void ScDocument::DumpColumnStorage( SCTAB nTab, SCCOL nCol ) const
|
|
{
|
|
const ScTable* pTab = FetchTable(nTab);
|
|
if (!pTab)
|
|
return;
|
|
|
|
pTab->DumpColumnStorage(nCol);
|
|
}
|
|
#endif
|
|
|
|
bool ScDocument::HasTable(SCTAB nTab) const
|
|
{
|
|
return ValidTab(nTab)
|
|
&& nTab < GetTableCount()
|
|
&& maTabs[nTab];
|
|
}
|
|
|
|
ScTable* ScDocument::FetchTable( SCTAB nTab )
|
|
{
|
|
if (!HasTable(nTab))
|
|
return nullptr;
|
|
|
|
return maTabs[nTab].get();
|
|
}
|
|
|
|
const ScTable* ScDocument::FetchTable( SCTAB nTab ) const
|
|
{
|
|
if (!HasTable(nTab))
|
|
return nullptr;
|
|
|
|
return maTabs[nTab].get();
|
|
}
|
|
|
|
ScColumnsRange ScDocument::GetWritableColumnsRange( SCTAB nTab, SCCOL nColBegin, SCCOL nColEnd)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetWritableColumnsRange(nColBegin, nColEnd);
|
|
|
|
SAL_WARN("sc", "GetWritableColumnsRange() called for non-existent table");
|
|
return ScColumnsRange(-1, -1);
|
|
}
|
|
|
|
ScColumnsRange ScDocument::GetAllocatedColumnsRange( SCTAB nTab, SCCOL nColBegin, SCCOL nColEnd) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetAllocatedColumnsRange(nColBegin, nColEnd);
|
|
return ScColumnsRange(-1, -1);
|
|
}
|
|
|
|
ScColumnsRange ScDocument::GetColumnsRange( SCTAB nTab, SCCOL nColBegin, SCCOL nColEnd) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetColumnsRange(nColBegin, nColEnd);
|
|
return ScColumnsRange(-1, -1);
|
|
}
|
|
|
|
void ScDocument::MergeNumberFormatter(const ScDocument& rSrcDoc)
|
|
{
|
|
SvNumberFormatter* pThisFormatter = mxPoolHelper->GetFormTable();
|
|
SvNumberFormatter* pOtherFormatter = rSrcDoc.mxPoolHelper->GetFormTable();
|
|
if (pOtherFormatter && pOtherFormatter != pThisFormatter)
|
|
{
|
|
SvNumberFormatterIndexTable* pExchangeList =
|
|
pThisFormatter->MergeFormatter(*pOtherFormatter);
|
|
if (!pExchangeList->empty())
|
|
{
|
|
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
|
|
pFormatExchangeList = pExchangeList;
|
|
}
|
|
}
|
|
}
|
|
|
|
ScClipParam& ScDocument::GetClipParam()
|
|
{
|
|
if (!mpClipParam)
|
|
mpClipParam.reset(new ScClipParam);
|
|
|
|
return *mpClipParam;
|
|
}
|
|
|
|
void ScDocument::SetClipParam(const ScClipParam& rParam)
|
|
{
|
|
mpClipParam.reset(new ScClipParam(rParam));
|
|
}
|
|
|
|
bool ScDocument::IsClipboardSource() const
|
|
{
|
|
if (bIsClip || mpShell == nullptr || mpShell->IsLoading())
|
|
return false;
|
|
|
|
ScDocument* pClipDoc = ScModule::GetClipDoc();
|
|
return pClipDoc && pClipDoc->bIsClip && pClipDoc->mxPoolHelper.is() && mxPoolHelper.is() &&
|
|
mxPoolHelper->GetDocPool() == pClipDoc->mxPoolHelper->GetDocPool();
|
|
}
|
|
|
|
void ScDocument::StartListeningFromClip(
|
|
sc::StartListeningContext& rStartCxt, sc::EndListeningContext& rEndCxt,
|
|
SCTAB nTab, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->StartListeningFormulaCells(rStartCxt, rEndCxt, nCol1, nRow1, nCol2, nRow2);
|
|
}
|
|
|
|
void ScDocument::StartListeningFromClip( SCCOL nCol1, SCROW nRow1,
|
|
SCCOL nCol2, SCROW nRow2,
|
|
const ScMarkData& rMark, InsertDeleteFlags nInsFlag )
|
|
{
|
|
if (!(nInsFlag & InsertDeleteFlags::CONTENTS))
|
|
return;
|
|
|
|
const auto pSet = std::make_shared<sc::ColumnBlockPositionSet>(*this);
|
|
sc::StartListeningContext aStartCxt(*this, pSet);
|
|
sc::EndListeningContext aEndCxt(*this, pSet, nullptr);
|
|
|
|
for (SCTAB nTab : rMark)
|
|
StartListeningFromClip(aStartCxt, aEndCxt, nTab, nCol1, nRow1, nCol2, nRow2);
|
|
}
|
|
|
|
void ScDocument::SetDirtyFromClip(
|
|
SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark,
|
|
InsertDeleteFlags nInsFlag, sc::ColumnSpanSet& rBroadcastSpans )
|
|
{
|
|
if (nInsFlag & InsertDeleteFlags::CONTENTS)
|
|
{
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
if (maTabs[rTab])
|
|
maTabs[rTab]->SetDirtyFromClip(nCol1, nRow1, nCol2, nRow2, rBroadcastSpans);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ScDocument::InitColumnBlockPosition( sc::ColumnBlockPosition& rBlockPos, SCTAB nTab, SCCOL nCol )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->InitColumnBlockPosition(rBlockPos, nCol);
|
|
return false;
|
|
}
|
|
|
|
void ScDocument::CopyBlockFromClip(
|
|
sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2,
|
|
const ScMarkData& rMark, SCCOL nDx, SCROW nDy )
|
|
{
|
|
TableContainer& rClipTabs = rCxt.getClipDoc()->maTabs;
|
|
SCTAB nTabEnd = rCxt.getTabEnd();
|
|
SCTAB nClipTab = 0;
|
|
for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < GetTableCount(); i++)
|
|
{
|
|
if (maTabs[i] && rMark.GetTableSelect(i) )
|
|
{
|
|
while (!rClipTabs[nClipTab]) nClipTab = (nClipTab+1) % static_cast<SCTAB>(rClipTabs.size());
|
|
|
|
maTabs[i]->CopyFromClip(
|
|
rCxt, nCol1, nRow1, nCol2, nRow2, nDx, nDy, rClipTabs[nClipTab].get());
|
|
|
|
if (rCxt.getClipDoc()->mpDrawLayer && (rCxt.getInsertFlag() & InsertDeleteFlags::OBJECTS))
|
|
{
|
|
// also copy drawing objects
|
|
|
|
// drawing layer must be created before calling CopyFromClip
|
|
// (ScDocShell::MakeDrawLayer also does InitItems etc.)
|
|
OSL_ENSURE( mpDrawLayer, "CopyBlockFromClip: No drawing layer" );
|
|
if ( mpDrawLayer )
|
|
{
|
|
// For GetMMRect, the row heights in the target document must already be valid
|
|
// (copied in an extra step before pasting, or updated after pasting cells, but
|
|
// before pasting objects).
|
|
ScRange aSourceRange(nCol1 - nDx, nRow1 - nDy, nClipTab, nCol2 - nDx, nRow2 - nDy, nClipTab);
|
|
ScRange aDestRange(nCol1, nRow1, i, nCol2, nRow2, i);
|
|
mpDrawLayer->CopyFromClip(rCxt.getClipDoc()->mpDrawLayer.get(), nClipTab, aSourceRange,
|
|
ScAddress( nCol1, nRow1, i ), aDestRange);
|
|
}
|
|
}
|
|
|
|
nClipTab = (nClipTab+1) % static_cast<SCTAB>(rClipTabs.size());
|
|
}
|
|
}
|
|
if (!(rCxt.getInsertFlag() & InsertDeleteFlags::CONTENTS))
|
|
return;
|
|
|
|
nClipTab = 0;
|
|
for (SCTAB i = rCxt.getTabStart(); i <= nTabEnd && i < GetTableCount(); i++)
|
|
{
|
|
if (maTabs[i] && rMark.GetTableSelect(i) )
|
|
{
|
|
while (!rClipTabs[nClipTab]) nClipTab = (nClipTab+1) % static_cast<SCTAB>(rClipTabs.size());
|
|
SCTAB nDz = i - nClipTab;
|
|
|
|
// ranges of consecutive selected tables (in clipboard and dest. doc)
|
|
// must be handled in one UpdateReference call
|
|
SCTAB nFollow = 0;
|
|
while ( i + nFollow < nTabEnd
|
|
&& rMark.GetTableSelect( i + nFollow + 1 )
|
|
&& nClipTab + nFollow < MAXTAB
|
|
&& rClipTabs[(nClipTab + nFollow + 1) % static_cast<SCTAB>(rClipTabs.size())] )
|
|
++nFollow;
|
|
|
|
sc::RefUpdateContext aRefCxt(*this, rCxt.getClipDoc());
|
|
aRefCxt.maRange = ScRange(nCol1, nRow1, i, nCol2, nRow2, i+nFollow);
|
|
aRefCxt.mnColDelta = nDx;
|
|
aRefCxt.mnRowDelta = nDy;
|
|
aRefCxt.mnTabDelta = nDz;
|
|
aRefCxt.setBlockPositionReference(rCxt.getBlockPositionSet()); // share mdds position caching
|
|
if (rCxt.getClipDoc()->GetClipParam().mbCutMode)
|
|
{
|
|
// Update references only if cut originates from the same
|
|
// document we are pasting into.
|
|
if (rCxt.getClipDoc()->GetPool() == GetPool())
|
|
{
|
|
bool bOldInserting = IsInsertingFromOtherDoc();
|
|
SetInsertingFromOtherDoc( true);
|
|
aRefCxt.meMode = URM_MOVE;
|
|
UpdateReference(aRefCxt, rCxt.getUndoDoc(), false);
|
|
|
|
// For URM_MOVE group listeners may have been removed,
|
|
// re-establish them.
|
|
if (!aRefCxt.maRegroupCols.empty())
|
|
{
|
|
/* TODO: holding the ColumnSet in a shared_ptr at
|
|
* RefUpdateContext would eliminate the need of
|
|
* copying it here. */
|
|
auto pColSet = std::make_shared<sc::ColumnSet>( aRefCxt.maRegroupCols);
|
|
StartNeededListeners( pColSet);
|
|
}
|
|
|
|
SetInsertingFromOtherDoc( bOldInserting);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
aRefCxt.meMode = URM_COPY;
|
|
UpdateReference(aRefCxt, rCxt.getUndoDoc(), false);
|
|
}
|
|
|
|
nClipTab = (nClipTab+nFollow+1) % static_cast<SCTAB>(rClipTabs.size());
|
|
i = sal::static_int_cast<SCTAB>( i + nFollow );
|
|
}
|
|
}
|
|
}
|
|
|
|
SCROW ScDocument::CopyNonFilteredFromClip(sc::CopyFromClipContext& rCxt, SCCOL nCol1, SCROW nRow1,
|
|
SCCOL nCol2, SCROW nRow2, const ScMarkData& rMark,
|
|
SCCOL nDx, SCROW& rClipStartRow, SCROW nClipEndRow)
|
|
{
|
|
// call CopyBlockFromClip for ranges of consecutive non-filtered rows
|
|
// nCol1/nRow1 etc. is in target doc
|
|
|
|
// filtered state is taken from first used table in clipboard (as in GetClipArea)
|
|
SCTAB nFlagTab = 0;
|
|
TableContainer& rClipTabs = rCxt.getClipDoc()->maTabs;
|
|
while ( nFlagTab < static_cast<SCTAB>(rClipTabs.size()) && !rClipTabs[nFlagTab] )
|
|
++nFlagTab;
|
|
|
|
SCROW nSourceRow = rClipStartRow;
|
|
SCROW nSourceEnd = nClipEndRow;
|
|
SCROW nDestRow = nRow1;
|
|
SCROW nFilteredRows = 0;
|
|
|
|
while ( nSourceRow <= nSourceEnd && nDestRow <= nRow2 )
|
|
{
|
|
// skip filtered rows
|
|
SCROW nSourceRowOriginal = nSourceRow;
|
|
nSourceRow = rCxt.getClipDoc()->FirstNonFilteredRow(nSourceRow, nSourceEnd, nFlagTab);
|
|
nFilteredRows += nSourceRow - nSourceRowOriginal;
|
|
|
|
if ( nSourceRow <= nSourceEnd )
|
|
{
|
|
// look for more non-filtered rows following
|
|
SCROW nLastRow = nSourceRow;
|
|
(void)rCxt.getClipDoc()->RowFiltered(nSourceRow, nFlagTab, nullptr, &nLastRow);
|
|
SCROW nFollow = nLastRow - nSourceRow;
|
|
|
|
if (nFollow > nSourceEnd - nSourceRow)
|
|
nFollow = nSourceEnd - nSourceRow;
|
|
if (nFollow > nRow2 - nDestRow)
|
|
nFollow = nRow2 - nDestRow;
|
|
|
|
SCROW nNewDy = nDestRow - nSourceRow;
|
|
CopyBlockFromClip(
|
|
rCxt, nCol1, nDestRow, nCol2, nDestRow + nFollow, rMark, nDx, nNewDy);
|
|
|
|
nSourceRow += nFollow + 1;
|
|
nDestRow += nFollow + 1;
|
|
}
|
|
}
|
|
rClipStartRow = nSourceRow;
|
|
return nFilteredRows;
|
|
}
|
|
|
|
namespace {
|
|
|
|
class BroadcastAction : public sc::ColumnSpanSet::ColumnAction
|
|
{
|
|
ScDocument& mrDoc;
|
|
ScColumn* mpCol;
|
|
|
|
public:
|
|
explicit BroadcastAction( ScDocument& rDoc ) : mrDoc(rDoc), mpCol(nullptr) {}
|
|
|
|
virtual void startColumn( ScColumn* pCol ) override
|
|
{
|
|
mpCol = pCol;
|
|
}
|
|
|
|
virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override
|
|
{
|
|
if (!bVal)
|
|
return;
|
|
|
|
assert(mpCol);
|
|
ScRange aRange(mpCol->GetCol(), nRow1, mpCol->GetTab());
|
|
aRange.aEnd.SetRow(nRow2);
|
|
mrDoc.BroadcastCells(aRange, SfxHintId::ScDataChanged);
|
|
};
|
|
};
|
|
|
|
}
|
|
|
|
void ScDocument::CopyFromClip(
|
|
const ScRange& rDestRange, const ScMarkData& rMark, InsertDeleteFlags nInsFlag,
|
|
ScDocument* pRefUndoDoc, ScDocument* pClipDoc, bool bResetCut,
|
|
bool bAsLink, bool bIncludeFiltered, bool bSkipEmptyCells,
|
|
const ScRangeList * pDestRanges )
|
|
{
|
|
if (bIsClip)
|
|
return;
|
|
|
|
if (!pClipDoc)
|
|
{
|
|
OSL_FAIL("CopyFromClip: no ClipDoc");
|
|
pClipDoc = ScModule::GetClipDoc();
|
|
}
|
|
|
|
if (!pClipDoc->bIsClip || !pClipDoc->GetTableCount())
|
|
return;
|
|
|
|
sc::AutoCalcSwitch aACSwitch(*this, false); // temporarily turn off auto calc.
|
|
|
|
NumFmtMergeHandler aNumFmtMergeHdl(*this, *pClipDoc);
|
|
|
|
SCCOL nAllCol1 = rDestRange.aStart.Col();
|
|
SCROW nAllRow1 = rDestRange.aStart.Row();
|
|
SCCOL nAllCol2 = rDestRange.aEnd.Col();
|
|
SCROW nAllRow2 = rDestRange.aEnd.Row();
|
|
|
|
SCCOL nXw = 0;
|
|
SCROW nYw = 0;
|
|
ScRange aClipRange = pClipDoc->GetClipParam().getWholeRange();
|
|
for (SCTAB nTab = 0; nTab < pClipDoc->GetTableCount(); nTab++) // find largest merge overlap
|
|
if (pClipDoc->maTabs[nTab]) // all sheets of the clipboard content
|
|
{
|
|
SCCOL nThisEndX = aClipRange.aEnd.Col();
|
|
SCROW nThisEndY = aClipRange.aEnd.Row();
|
|
pClipDoc->ExtendMerge( aClipRange.aStart.Col(),
|
|
aClipRange.aStart.Row(),
|
|
nThisEndX, nThisEndY, nTab );
|
|
// only extra value from ExtendMerge
|
|
nThisEndX = sal::static_int_cast<SCCOL>( nThisEndX - aClipRange.aEnd.Col() );
|
|
nThisEndY = sal::static_int_cast<SCROW>( nThisEndY - aClipRange.aEnd.Row() );
|
|
if ( nThisEndX > nXw )
|
|
nXw = nThisEndX;
|
|
if ( nThisEndY > nYw )
|
|
nYw = nThisEndY;
|
|
}
|
|
|
|
SCCOL nDestAddX;
|
|
SCROW nDestAddY;
|
|
pClipDoc->GetClipArea( nDestAddX, nDestAddY, bIncludeFiltered );
|
|
nXw = sal::static_int_cast<SCCOL>( nXw + nDestAddX );
|
|
nYw = sal::static_int_cast<SCROW>( nYw + nDestAddY ); // ClipArea, plus ExtendMerge value
|
|
|
|
/* Decide which contents to delete before copying. Delete all
|
|
contents if nInsFlag contains any real content flag.
|
|
#i102056# Notes are pasted from clipboard in a second pass,
|
|
together with the special flag InsertDeleteFlags::ADDNOTES that states to not
|
|
overwrite/delete existing cells but to insert the notes into
|
|
these cells. In this case, just delete old notes from the
|
|
destination area. */
|
|
InsertDeleteFlags nDelFlag = nInsFlag;
|
|
// tdf#163019 - remove formula of the cell to update formula listeners
|
|
if (nInsFlag & InsertDeleteFlags::CONTENTS)
|
|
nDelFlag |= InsertDeleteFlags::FORMULA;
|
|
|
|
// tdf#161189 - remove the note deletion flag if no notes are included
|
|
if ((nInsFlag & (InsertDeleteFlags::CONTENTS | InsertDeleteFlags::ADDNOTES))
|
|
== (InsertDeleteFlags::NOTE | InsertDeleteFlags::ADDNOTES))
|
|
nDelFlag &= ~InsertDeleteFlags::NOTE;
|
|
|
|
if (nInsFlag & InsertDeleteFlags::ATTRIB)
|
|
nDelFlag |= InsertDeleteFlags::ATTRIB;
|
|
|
|
sc::CopyFromClipContext aCxt(*this, pRefUndoDoc, pClipDoc, nInsFlag, bAsLink, bSkipEmptyCells);
|
|
std::pair<SCTAB,SCTAB> aTabRanges = getMarkedTableRange(maTabs, rMark);
|
|
aCxt.setTabRange(aTabRanges.first, aTabRanges.second);
|
|
aCxt.setDeleteFlag(nDelFlag);
|
|
|
|
ScRangeList aLocalRangeList;
|
|
if (!pDestRanges)
|
|
{
|
|
aLocalRangeList.push_back( rDestRange);
|
|
pDestRanges = &aLocalRangeList;
|
|
}
|
|
|
|
bInsertingFromOtherDoc = true; // No Broadcast/Listener created at Insert
|
|
|
|
sc::ColumnSpanSet aBroadcastSpans;
|
|
|
|
SCCOL nClipStartCol = aClipRange.aStart.Col();
|
|
SCROW nClipStartRow = aClipRange.aStart.Row();
|
|
SCROW nClipEndRow = aClipRange.aEnd.Row();
|
|
for ( size_t nRange = 0; nRange < pDestRanges->size(); ++nRange )
|
|
{
|
|
const ScRange & rRange = (*pDestRanges)[nRange];
|
|
SCCOL nCol1 = rRange.aStart.Col();
|
|
SCROW nRow1 = rRange.aStart.Row();
|
|
SCCOL nCol2 = rRange.aEnd.Col();
|
|
SCROW nRow2 = rRange.aEnd.Row();
|
|
|
|
aCxt.setDestRange(nCol1, nRow1, nCol2, nRow2);
|
|
DeleteBeforeCopyFromClip(aCxt, rMark, aBroadcastSpans); // <- this removes existing formula listeners
|
|
|
|
if (CopyOneCellFromClip(aCxt, nCol1, nRow1, nCol2, nRow2))
|
|
continue;
|
|
|
|
SCCOL nC1 = nCol1;
|
|
SCROW nR1 = nRow1;
|
|
SCCOL nC2 = nC1 + nXw;
|
|
if (nC2 > nCol2)
|
|
nC2 = nCol2;
|
|
SCROW nR2 = nR1 + nYw;
|
|
if (nR2 > nRow2)
|
|
nR2 = nRow2;
|
|
|
|
const SCCOLROW nThreshold = 8192;
|
|
bool bPreallocatePattern = ((nInsFlag & InsertDeleteFlags::ATTRIB) && (nRow2 - nRow1 > nThreshold));
|
|
std::vector< SCTAB > vTables;
|
|
|
|
if (bPreallocatePattern)
|
|
{
|
|
for (SCTAB i = aCxt.getTabStart(); i <= aCxt.getTabEnd(); ++i)
|
|
if (maTabs[i] && rMark.GetTableSelect( i ) )
|
|
vTables.push_back( i );
|
|
}
|
|
|
|
do
|
|
{
|
|
// Pasting is done column-wise, when pasting to a filtered
|
|
// area this results in partitioning and we have to
|
|
// remember and reset the start row for each column until
|
|
// it can be advanced for the next chunk of unfiltered
|
|
// rows.
|
|
SCROW nSaveClipStartRow = nClipStartRow;
|
|
do
|
|
{
|
|
nClipStartRow = nSaveClipStartRow;
|
|
SCCOL nDx = nC1 - nClipStartCol;
|
|
SCROW nDy = nR1 - nClipStartRow;
|
|
if ( bIncludeFiltered )
|
|
{
|
|
CopyBlockFromClip(
|
|
aCxt, nC1, nR1, nC2, nR2, rMark, nDx, nDy);
|
|
nClipStartRow += nR2 - nR1 + 1;
|
|
}
|
|
else
|
|
{
|
|
CopyNonFilteredFromClip(aCxt, nC1, nR1, nC2, nR2, rMark, nDx, nClipStartRow,
|
|
nClipEndRow);
|
|
}
|
|
nC1 = nC2 + 1;
|
|
nC2 = std::min(static_cast<SCCOL>(nC1 + nXw), nCol2);
|
|
} while (nC1 <= nCol2);
|
|
if (nClipStartRow > nClipEndRow)
|
|
nClipStartRow = aClipRange.aStart.Row();
|
|
nC1 = nCol1;
|
|
nC2 = nC1 + nXw;
|
|
if (nC2 > nCol2)
|
|
nC2 = nCol2;
|
|
|
|
// Preallocate pattern memory once if further chunks are to be pasted.
|
|
if (bPreallocatePattern && (nR2+1) <= nRow2)
|
|
{
|
|
SCROW nR3 = nR2 + 1;
|
|
for (SCTAB nTab : vTables)
|
|
{
|
|
for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
|
|
{
|
|
// Pattern count of the first chunk pasted.
|
|
SCSIZE nChunk = GetPatternCount( nTab, nCol, nR1, nR2);
|
|
// If it is only one pattern per chunk and chunks are
|
|
// pasted consecutively then it will get its range
|
|
// enlarged for each chunk and no further allocation
|
|
// happens. For non-consecutive chunks we're out of
|
|
// luck in this case.
|
|
if (nChunk > 1)
|
|
{
|
|
SCSIZE nNeeded = nChunk * (nRow2 - nR3 + 1) / (nYw + 1);
|
|
SCSIZE nRemain = GetPatternCount( nTab, nCol, nR3, nRow2);
|
|
if (nNeeded > nRemain)
|
|
{
|
|
SCSIZE nCurr = GetPatternCount( nTab, nCol);
|
|
ReservePatternCount( nTab, nCol, nCurr + nNeeded);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
bPreallocatePattern = false;
|
|
}
|
|
|
|
nR1 = nR2 + 1;
|
|
nR2 = std::min(static_cast<SCROW>(nR1 + nYw), nRow2);
|
|
} while (nR1 <= nRow2);
|
|
}
|
|
|
|
bInsertingFromOtherDoc = false;
|
|
|
|
if (nInsFlag & InsertDeleteFlags::CONTENTS)
|
|
{
|
|
for (SCTAB nTab : rMark)
|
|
aCxt.setListeningFormulaSpans(nTab, nAllCol1, nAllRow1, nAllCol2, nAllRow2);
|
|
}
|
|
|
|
// Create Listener after everything has been inserted
|
|
aCxt.startListeningFormulas();
|
|
|
|
{
|
|
ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);
|
|
|
|
// Set all formula cells dirty, and collect non-empty non-formula cell
|
|
// positions so that we can broadcast on them below.
|
|
SetDirtyFromClip(nAllCol1, nAllRow1, nAllCol2, nAllRow2, rMark, nInsFlag, aBroadcastSpans);
|
|
|
|
BroadcastAction aAction(*this);
|
|
aBroadcastSpans.executeColumnAction(*this, aAction);
|
|
}
|
|
|
|
if (bResetCut)
|
|
pClipDoc->GetClipParam().mbCutMode = false;
|
|
}
|
|
|
|
void ScDocument::CopyMultiRangeFromClip(const ScAddress& rDestPos, const ScMarkData& rMark,
|
|
InsertDeleteFlags nInsFlag, ScDocument* pClipDoc,
|
|
bool bResetCut, bool bAsLink, bool bIncludeFiltered,
|
|
bool bSkipAttrForEmpty)
|
|
{
|
|
if (bIsClip)
|
|
return;
|
|
|
|
if (!pClipDoc->bIsClip || !pClipDoc->GetTableCount())
|
|
// There is nothing in the clip doc to copy.
|
|
return;
|
|
|
|
// Right now, we don't allow pasting into filtered rows, so we don't even handle it here.
|
|
|
|
sc::AutoCalcSwitch aACSwitch(*this, false); // turn of auto calc temporarily.
|
|
NumFmtMergeHandler aNumFmtMergeHdl(*this, *pClipDoc);
|
|
|
|
const ScRange& aDestRange = rMark.GetMarkArea();
|
|
|
|
bInsertingFromOtherDoc = true; // No Broadcast/Listener created at Insert
|
|
|
|
SCCOL nCol1 = rDestPos.Col();
|
|
SCROW nRow1 = rDestPos.Row();
|
|
ScClipParam& rClipParam = pClipDoc->GetClipParam();
|
|
|
|
sc::ColumnSpanSet aBroadcastSpans;
|
|
|
|
if (!bSkipAttrForEmpty)
|
|
{
|
|
// Do the deletion first.
|
|
SCCOL nColSize = rClipParam.getPasteColSize();
|
|
SCROW nRowSize = rClipParam.getPasteRowSize(*pClipDoc, bIncludeFiltered);
|
|
|
|
DeleteArea(nCol1, nRow1, nCol1+nColSize-1, nRow1+nRowSize-1, rMark, InsertDeleteFlags::CONTENTS, false, &aBroadcastSpans);
|
|
}
|
|
|
|
sc::CopyFromClipContext aCxt(*this, nullptr, pClipDoc, nInsFlag, bAsLink, bSkipAttrForEmpty);
|
|
std::pair<SCTAB,SCTAB> aTabRanges = getMarkedTableRange(maTabs, rMark);
|
|
aCxt.setTabRange(aTabRanges.first, aTabRanges.second);
|
|
|
|
for (size_t i = 0, n = rClipParam.maRanges.size(); i < n; ++i)
|
|
{
|
|
const ScRange & rRange = rClipParam.maRanges[i];
|
|
|
|
SCROW nRowCount = rRange.aEnd.Row() - rRange.aStart.Row() + 1;
|
|
SCCOL nDx = static_cast<SCCOL>(nCol1 - rRange.aStart.Col());
|
|
SCROW nDy = static_cast<SCROW>(nRow1 - rRange.aStart.Row());
|
|
SCCOL nCol2 = nCol1 + rRange.aEnd.Col() - rRange.aStart.Col();
|
|
SCROW nEndRow = nRow1 + nRowCount - 1;
|
|
SCROW nFilteredRows = 0;
|
|
|
|
if (bIncludeFiltered)
|
|
{
|
|
CopyBlockFromClip(aCxt, nCol1, nRow1, nCol2, nEndRow, rMark, nDx, nDy);
|
|
}
|
|
else
|
|
{
|
|
SCROW nClipStartRow = rRange.aStart.Row();
|
|
SCROW nClipEndRow = rRange.aEnd.Row();
|
|
nFilteredRows += CopyNonFilteredFromClip(aCxt, nCol1, nRow1, nCol2, nEndRow, rMark, nDx,
|
|
nClipStartRow, nClipEndRow);
|
|
nRowCount -= nFilteredRows;
|
|
}
|
|
|
|
switch (rClipParam.meDirection)
|
|
{
|
|
case ScClipParam::Row:
|
|
// Begin row for the next range being pasted.
|
|
nRow1 += nRowCount;
|
|
break;
|
|
case ScClipParam::Column:
|
|
nCol1 += rRange.aEnd.Col() - rRange.aStart.Col() + 1;
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
}
|
|
|
|
bInsertingFromOtherDoc = false;
|
|
|
|
// Create Listener after everything has been inserted
|
|
StartListeningFromClip(aDestRange.aStart.Col(), aDestRange.aStart.Row(),
|
|
aDestRange.aEnd.Col(), aDestRange.aEnd.Row(), rMark, nInsFlag );
|
|
|
|
{
|
|
ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);
|
|
|
|
// Set formula cells dirty and collect non-formula cells.
|
|
SetDirtyFromClip(
|
|
aDestRange.aStart.Col(), aDestRange.aStart.Row(), aDestRange.aEnd.Col(), aDestRange.aEnd.Row(),
|
|
rMark, nInsFlag, aBroadcastSpans);
|
|
|
|
BroadcastAction aAction(*this);
|
|
aBroadcastSpans.executeColumnAction(*this, aAction);
|
|
}
|
|
|
|
if (bResetCut)
|
|
pClipDoc->GetClipParam().mbCutMode = false;
|
|
}
|
|
|
|
void ScDocument::SetClipArea( const ScRange& rArea, bool bCut )
|
|
{
|
|
if (bIsClip)
|
|
{
|
|
ScClipParam& rClipParam = GetClipParam();
|
|
rClipParam.maRanges.RemoveAll();
|
|
rClipParam.maRanges.push_back(rArea);
|
|
rClipParam.mbCutMode = bCut;
|
|
}
|
|
else
|
|
{
|
|
OSL_FAIL("SetClipArea: No Clip");
|
|
}
|
|
}
|
|
|
|
void ScDocument::GetClipArea(SCCOL& nClipX, SCROW& nClipY, bool bIncludeFiltered)
|
|
{
|
|
if (!bIsClip)
|
|
{
|
|
OSL_FAIL("GetClipArea: No Clip");
|
|
return;
|
|
}
|
|
|
|
ScRangeList& rClipRanges = GetClipParam().maRanges;
|
|
if (rClipRanges.empty())
|
|
// No clip range. Bail out.
|
|
return;
|
|
|
|
ScRange const & rRange = rClipRanges.front();
|
|
SCCOL nStartCol = rRange.aStart.Col();
|
|
SCCOL nEndCol = rRange.aEnd.Col();
|
|
SCROW nStartRow = rRange.aStart.Row();
|
|
SCROW nEndRow = rRange.aEnd.Row();
|
|
for ( size_t i = 1, n = rClipRanges.size(); i < n; ++i )
|
|
{
|
|
ScRange const & rRange2 = rClipRanges[ i ];
|
|
if (rRange2.aStart.Col() < nStartCol)
|
|
nStartCol = rRange2.aStart.Col();
|
|
if (rRange2.aStart.Row() < nStartRow)
|
|
nStartRow = rRange2.aStart.Row();
|
|
if (rRange2.aEnd.Col() > nEndCol)
|
|
nEndCol = rRange2.aEnd.Col();
|
|
if (rRange2.aEnd.Row() > nEndRow)
|
|
nEndRow = rRange2.aEnd.Row();
|
|
}
|
|
|
|
nClipX = nEndCol - nStartCol;
|
|
|
|
if ( bIncludeFiltered )
|
|
nClipY = nEndRow - nStartRow;
|
|
else
|
|
{
|
|
// count non-filtered rows
|
|
// count on first used table in clipboard
|
|
SCTAB nCountTab = 0;
|
|
while (nCountTab < GetTableCount() && !maTabs[nCountTab])
|
|
++nCountTab;
|
|
|
|
SCROW nResult = CountNonFilteredRows(nStartRow, nEndRow, nCountTab);
|
|
|
|
if ( nResult > 0 )
|
|
nClipY = nResult - 1;
|
|
else
|
|
nClipY = 0; // always return at least 1 row
|
|
}
|
|
}
|
|
|
|
void ScDocument::GetClipStart(SCCOL& nClipX, SCROW& nClipY)
|
|
{
|
|
if (bIsClip)
|
|
{
|
|
ScRangeList& rClipRanges = GetClipParam().maRanges;
|
|
if ( !rClipRanges.empty() )
|
|
{
|
|
nClipX = rClipRanges.front().aStart.Col();
|
|
nClipY = rClipRanges.front().aStart.Row();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OSL_FAIL("GetClipStart: No Clip");
|
|
}
|
|
}
|
|
|
|
bool ScDocument::HasClipFilteredRows()
|
|
{
|
|
// count on first used table in clipboard
|
|
SCTAB nCountTab = 0;
|
|
while (nCountTab < GetTableCount() && !maTabs[nCountTab])
|
|
++nCountTab;
|
|
|
|
ScRangeList& rClipRanges = GetClipParam().maRanges;
|
|
if ( rClipRanges.empty() )
|
|
return false;
|
|
|
|
if (maTabs.size() > 0)
|
|
{
|
|
for (size_t i = 0, n = rClipRanges.size(); i < n; ++i)
|
|
{
|
|
ScRange& rRange = rClipRanges[i];
|
|
bool bAnswer
|
|
= maTabs[nCountTab]->HasFilteredRows(rRange.aStart.Row(), rRange.aEnd.Row());
|
|
if (bAnswer)
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ScDocument::MixDocument( const ScRange& rRange, ScPasteFunc nFunction, bool bSkipEmpty,
|
|
ScDocument& rSrcDoc )
|
|
{
|
|
SCTAB nTab1 = rRange.aStart.Tab();
|
|
SCTAB nTab2 = rRange.aEnd.Tab();
|
|
sc::MixDocContext aCxt(*this);
|
|
SCTAB nMinSizeBothTabs = std::min(GetTableCount(), rSrcDoc.GetTableCount());
|
|
for (SCTAB i = nTab1; i <= nTab2 && i < nMinSizeBothTabs; i++)
|
|
{
|
|
ScTable* pTab = FetchTable(i);
|
|
const ScTable* pSrcTab = rSrcDoc.FetchTable(i);
|
|
if (!pTab || !pSrcTab)
|
|
continue;
|
|
|
|
pTab->MixData(
|
|
aCxt, rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(),
|
|
nFunction, bSkipEmpty, pSrcTab);
|
|
}
|
|
}
|
|
|
|
void ScDocument::FillTab( const ScRange& rSrcArea, const ScMarkData& rMark,
|
|
InsertDeleteFlags nFlags, ScPasteFunc nFunction,
|
|
bool bSkipEmpty, bool bAsLink )
|
|
{
|
|
InsertDeleteFlags nDelFlags = nFlags;
|
|
if (nDelFlags & InsertDeleteFlags::CONTENTS)
|
|
nDelFlags |= InsertDeleteFlags::CONTENTS; // Either all contents or delete nothing!
|
|
|
|
SCTAB nSrcTab = rSrcArea.aStart.Tab();
|
|
|
|
if (ScTable* pSourceTable = FetchTable(nSrcTab))
|
|
{
|
|
SCCOL nStartCol = rSrcArea.aStart.Col();
|
|
SCROW nStartRow = rSrcArea.aStart.Row();
|
|
SCCOL nEndCol = rSrcArea.aEnd.Col();
|
|
SCROW nEndRow = rSrcArea.aEnd.Row();
|
|
ScDocumentUniquePtr pMixDoc;
|
|
bool bDoMix = ( bSkipEmpty || nFunction != ScPasteFunc::NONE ) && ( nFlags & InsertDeleteFlags::CONTENTS );
|
|
|
|
bool bOldAutoCalc = GetAutoCalc();
|
|
SetAutoCalc( false ); // avoid multiple calculations
|
|
|
|
sc::CopyToDocContext aCxt(*this);
|
|
sc::MixDocContext aMixDocCxt(*this);
|
|
|
|
SCTAB nCount = GetTableCount();
|
|
for (const SCTAB& i : rMark)
|
|
{
|
|
if (i >= nCount)
|
|
break;
|
|
if (i != nSrcTab && maTabs[i])
|
|
{
|
|
if (bDoMix)
|
|
{
|
|
if (!pMixDoc)
|
|
{
|
|
pMixDoc.reset(new ScDocument(SCDOCMODE_UNDO));
|
|
pMixDoc->InitUndo( *this, i, i );
|
|
}
|
|
else
|
|
pMixDoc->AddUndoTab( i, i );
|
|
|
|
// context used for copying content to the temporary mix document.
|
|
sc::CopyToDocContext aMixCxt(*pMixDoc);
|
|
maTabs[i]->CopyToTable(aMixCxt, nStartCol,nStartRow, nEndCol,nEndRow,
|
|
InsertDeleteFlags::CONTENTS, false, pMixDoc->maTabs[i].get(),
|
|
/*pMarkData*/nullptr, /*bAsLink*/false, /*bColRowFlags*/true,
|
|
/*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true );
|
|
}
|
|
maTabs[i]->DeleteArea( nStartCol,nStartRow, nEndCol,nEndRow, nDelFlags);
|
|
pSourceTable->CopyToTable(aCxt, nStartCol,nStartRow, nEndCol,nEndRow,
|
|
nFlags, false, maTabs[i].get(), nullptr, bAsLink,
|
|
/*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true );
|
|
|
|
if (bDoMix)
|
|
maTabs[i]->MixData(aMixDocCxt, nStartCol,nStartRow, nEndCol,nEndRow,
|
|
nFunction, bSkipEmpty, pMixDoc->maTabs[i].get() );
|
|
}
|
|
}
|
|
|
|
SetAutoCalc( bOldAutoCalc );
|
|
}
|
|
else
|
|
{
|
|
OSL_FAIL("wrong table");
|
|
}
|
|
}
|
|
|
|
void ScDocument::FillTabMarked( SCTAB nSrcTab, const ScMarkData& rMark,
|
|
InsertDeleteFlags nFlags, ScPasteFunc nFunction,
|
|
bool bSkipEmpty, bool bAsLink )
|
|
{
|
|
InsertDeleteFlags nDelFlags = nFlags;
|
|
if (nDelFlags & InsertDeleteFlags::CONTENTS)
|
|
nDelFlags |= InsertDeleteFlags::CONTENTS; // Either all contents or delete nothing!
|
|
|
|
if (ScTable* pSourceTable = FetchTable(nSrcTab))
|
|
{
|
|
ScDocumentUniquePtr pMixDoc;
|
|
bool bDoMix = ( bSkipEmpty || nFunction != ScPasteFunc::NONE ) && ( nFlags & InsertDeleteFlags::CONTENTS );
|
|
|
|
bool bOldAutoCalc = GetAutoCalc();
|
|
SetAutoCalc( false ); // avoid multiple calculations
|
|
|
|
const ScRange& aArea = rMark.GetMultiMarkArea();
|
|
SCCOL nStartCol = aArea.aStart.Col();
|
|
SCROW nStartRow = aArea.aStart.Row();
|
|
SCCOL nEndCol = aArea.aEnd.Col();
|
|
SCROW nEndRow = aArea.aEnd.Row();
|
|
|
|
sc::CopyToDocContext aCxt(*this);
|
|
sc::MixDocContext aMixDocCxt(*this);
|
|
SCTAB nCount = GetTableCount();
|
|
for (const SCTAB& i : rMark)
|
|
{
|
|
if (i >= nCount)
|
|
break;
|
|
if ( i != nSrcTab && maTabs[i] )
|
|
{
|
|
if (bDoMix)
|
|
{
|
|
if (!pMixDoc)
|
|
{
|
|
pMixDoc.reset(new ScDocument(SCDOCMODE_UNDO));
|
|
pMixDoc->InitUndo( *this, i, i );
|
|
}
|
|
else
|
|
pMixDoc->AddUndoTab( i, i );
|
|
|
|
sc::CopyToDocContext aMixCxt(*pMixDoc);
|
|
maTabs[i]->CopyToTable(aMixCxt, nStartCol,nStartRow, nEndCol,nEndRow,
|
|
InsertDeleteFlags::CONTENTS, true, pMixDoc->maTabs[i].get(), &rMark,
|
|
/*bAsLink*/false, /*bColRowFlags*/true, /*bGlobalNamesToLocal*/false,
|
|
/*bCopyCaptions*/true );
|
|
}
|
|
|
|
maTabs[i]->DeleteSelection( nDelFlags, rMark );
|
|
pSourceTable->CopyToTable(aCxt, nStartCol,nStartRow, nEndCol,nEndRow,
|
|
nFlags, true, maTabs[i].get(), &rMark, bAsLink,
|
|
/*bColRowFlags*/true, /*bGlobalNamesToLocal*/false, /*bCopyCaptions*/true );
|
|
|
|
if (bDoMix)
|
|
maTabs[i]->MixMarked(aMixDocCxt, rMark, nFunction, bSkipEmpty, pMixDoc->maTabs[i].get());
|
|
}
|
|
}
|
|
|
|
SetAutoCalc( bOldAutoCalc );
|
|
}
|
|
else
|
|
{
|
|
OSL_FAIL("wrong table");
|
|
}
|
|
}
|
|
|
|
bool ScDocument::SetString( SCCOL nCol, SCROW nRow, SCTAB nTab, const OUString& rString,
|
|
const ScSetStringParam* pParam )
|
|
{
|
|
ScTable* pTab = FetchTable(nTab);
|
|
if (!pTab)
|
|
return false;
|
|
|
|
const ScFormulaCell* pCurCellFormula = pTab->GetFormulaCell(nCol, nRow);
|
|
if (pCurCellFormula && pCurCellFormula->IsShared())
|
|
{
|
|
// In case setting this string affects an existing formula group, end
|
|
// its listening to purge then empty cell broadcasters. Affected
|
|
// remaining split group listeners will be set up again via
|
|
// ScColumn::DetachFormulaCell() and
|
|
// ScColumn::StartListeningUnshared().
|
|
|
|
sc::EndListeningContext aCxt(*this);
|
|
ScAddress aPos(nCol, nRow, nTab);
|
|
EndListeningIntersectedGroup(aCxt, aPos, nullptr);
|
|
aCxt.purgeEmptyBroadcasters();
|
|
}
|
|
|
|
return pTab->SetString(nCol, nRow, nTab, rString, pParam);
|
|
}
|
|
|
|
bool ScDocument::SetString(
|
|
const ScAddress& rPos, const OUString& rString, const ScSetStringParam* pParam )
|
|
{
|
|
return SetString(rPos.Col(), rPos.Row(), rPos.Tab(), rString, pParam);
|
|
}
|
|
|
|
bool ScDocument::SetEditText( const ScAddress& rPos, std::unique_ptr<EditTextObject> pEditText )
|
|
{
|
|
if (ScTable* pTable = FetchTable(rPos.Tab()))
|
|
return pTable->SetEditText(rPos.Col(), rPos.Row(), std::move(pEditText));
|
|
return false;
|
|
}
|
|
|
|
void ScDocument::SetEditText( const ScAddress& rPos, const EditTextObject& rEditText, const SfxItemPool* pEditPool )
|
|
{
|
|
if (ScTable* pTable = FetchTable(rPos.Tab()))
|
|
pTable->SetEditText(rPos.Col(), rPos.Row(), rEditText, pEditPool);
|
|
}
|
|
|
|
void ScDocument::SetEditText( const ScAddress& rPos, const OUString& rStr )
|
|
{
|
|
if (ScTable* pTable = FetchTable(rPos.Tab()))
|
|
{
|
|
ScFieldEditEngine& rEngine = GetEditEngine();
|
|
rEngine.SetTextCurrentDefaults(rStr);
|
|
pTable->SetEditText(rPos.Col(), rPos.Row(), rEngine.CreateTextObject());
|
|
}
|
|
}
|
|
|
|
SCROW ScDocument::GetFirstEditTextRow( const ScRange& rRange ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(rRange.aStart.Tab()))
|
|
return pTable->GetFirstEditTextRow(rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
|
|
return -1;
|
|
}
|
|
|
|
void ScDocument::SetTextCell( const ScAddress& rPos, const OUString& rStr )
|
|
{
|
|
if (ScTable* pTable = FetchTable(rPos.Tab()))
|
|
{
|
|
if (ScStringUtil::isMultiline(rStr))
|
|
{
|
|
ScFieldEditEngine& rEngine = GetEditEngine();
|
|
rEngine.SetTextCurrentDefaults(rStr);
|
|
pTable->SetEditText(rPos.Col(), rPos.Row(), rEngine.CreateTextObject());
|
|
}
|
|
else
|
|
{
|
|
ScSetStringParam aParam;
|
|
aParam.setTextInput();
|
|
pTable->SetString(rPos.Col(), rPos.Row(), rPos.Tab(), rStr, &aParam);
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScDocument::SetEmptyCell( const ScAddress& rPos )
|
|
{
|
|
if (ScTable* pTable = FetchTable(rPos.Tab()))
|
|
pTable->SetEmptyCell(rPos.Col(), rPos.Row());
|
|
}
|
|
|
|
void ScDocument::SetValue( SCCOL nCol, SCROW nRow, SCTAB nTab, const double& rVal )
|
|
{
|
|
SetValue(ScAddress(nCol, nRow, nTab), rVal);
|
|
}
|
|
|
|
void ScDocument::SetValue( const ScAddress& rPos, double fVal )
|
|
{
|
|
ScTable* pTab = FetchTable(rPos.Tab());
|
|
if (!pTab)
|
|
return;
|
|
|
|
const ScFormulaCell* pCurCellFormula = pTab->GetFormulaCell(rPos.Col(), rPos.Row());
|
|
if (pCurCellFormula && pCurCellFormula->IsShared())
|
|
{
|
|
// In case setting this value affects an existing formula group, end
|
|
// its listening to purge then empty cell broadcasters. Affected
|
|
// remaining split group listeners will be set up again via
|
|
// ScColumn::DetachFormulaCell() and
|
|
// ScColumn::StartListeningUnshared().
|
|
|
|
sc::EndListeningContext aCxt(*this);
|
|
EndListeningIntersectedGroup(aCxt, rPos, nullptr);
|
|
aCxt.purgeEmptyBroadcasters();
|
|
}
|
|
|
|
pTab->SetValue(rPos.Col(), rPos.Row(), fVal);
|
|
}
|
|
|
|
OUString ScDocument::GetString( SCCOL nCol, SCROW nRow, SCTAB nTab, ScInterpreterContext* pContext ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetString(nCol, nRow, pContext);
|
|
return OUString();
|
|
}
|
|
|
|
OUString ScDocument::GetString( const ScAddress& rPos, ScInterpreterContext* pContext ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(rPos.Tab()))
|
|
return pTable->GetString(rPos.Col(), rPos.Row(), pContext);
|
|
return OUString();
|
|
}
|
|
|
|
double* ScDocument::GetValueCell( const ScAddress& rPos )
|
|
{
|
|
if (ScTable* pTable = FetchTable(rPos.Tab()))
|
|
return pTable->GetValueCell(rPos.Col(), rPos.Row());
|
|
return nullptr;
|
|
}
|
|
|
|
svl::SharedString ScDocument::GetSharedString( const ScAddress& rPos ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(rPos.Tab()))
|
|
return pTable->GetSharedString(rPos.Col(), rPos.Row());
|
|
return svl::SharedString();
|
|
}
|
|
|
|
std::shared_ptr<sc::FormulaGroupContext>& ScDocument::GetFormulaGroupContext()
|
|
{
|
|
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
|
|
if (!mpFormulaGroupCxt)
|
|
mpFormulaGroupCxt = std::make_shared<sc::FormulaGroupContext>();
|
|
|
|
return mpFormulaGroupCxt;
|
|
}
|
|
|
|
void ScDocument::DiscardFormulaGroupContext()
|
|
{
|
|
assert(!IsThreadedGroupCalcInProgress());
|
|
if( !mbFormulaGroupCxtBlockDiscard )
|
|
mpFormulaGroupCxt.reset();
|
|
}
|
|
|
|
OUString ScDocument::GetInputString(SCCOL nCol, SCROW nRow, SCTAB nTab, bool bForceSystemLocale ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetInputString(nCol, nRow, bForceSystemLocale);
|
|
else
|
|
return OUString();
|
|
}
|
|
|
|
FormulaError ScDocument::GetStringForFormula( const ScAddress& rPos, OUString& rString )
|
|
{
|
|
// Used in formulas (add-in parameters etc), so it must use the same semantics as
|
|
// ScInterpreter::GetCellString: always format values as numbers.
|
|
// The return value is the error code.
|
|
|
|
ScRefCellValue aCell(*this, rPos);
|
|
if (aCell.isEmpty())
|
|
{
|
|
rString.clear();
|
|
return FormulaError::NONE;
|
|
}
|
|
|
|
FormulaError nErr = FormulaError::NONE;
|
|
OUString aStr;
|
|
SvNumberFormatter* pFormatter = GetFormatTable();
|
|
switch (aCell.getType())
|
|
{
|
|
case CELLTYPE_STRING:
|
|
case CELLTYPE_EDIT:
|
|
aStr = aCell.getString(this);
|
|
break;
|
|
case CELLTYPE_FORMULA:
|
|
{
|
|
ScFormulaCell* pFCell = aCell.getFormula();
|
|
nErr = pFCell->GetErrCode();
|
|
if (pFCell->IsValue())
|
|
{
|
|
double fVal = pFCell->GetValue();
|
|
sal_uInt32 nIndex = pFormatter->GetStandardFormat(
|
|
SvNumFormatType::NUMBER,
|
|
ScGlobal::eLnge);
|
|
aStr = pFormatter->GetInputLineString(fVal, nIndex);
|
|
}
|
|
else
|
|
aStr = pFCell->GetString().getString();
|
|
}
|
|
break;
|
|
case CELLTYPE_VALUE:
|
|
{
|
|
double fVal = aCell.getDouble();
|
|
sal_uInt32 nIndex = pFormatter->GetStandardFormat(
|
|
SvNumFormatType::NUMBER,
|
|
ScGlobal::eLnge);
|
|
aStr = pFormatter->GetInputLineString(fVal, nIndex);
|
|
}
|
|
break;
|
|
default:
|
|
;
|
|
}
|
|
|
|
rString = aStr;
|
|
return nErr;
|
|
}
|
|
|
|
const EditTextObject* ScDocument::GetEditText( const ScAddress& rPos ) const
|
|
{
|
|
SCTAB nTab = rPos.Tab();
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetEditText(rPos.Col(), rPos.Row());
|
|
return nullptr;
|
|
}
|
|
|
|
void ScDocument::RemoveEditTextCharAttribs( const ScAddress& rPos, const ScPatternAttr& rAttr )
|
|
{
|
|
if (ScTable* pTable = FetchTable(rPos.Tab()))
|
|
return pTable->RemoveEditTextCharAttribs(rPos.Col(), rPos.Row(), rAttr);
|
|
}
|
|
|
|
double ScDocument::GetValue( const ScAddress& rPos ) const
|
|
{
|
|
SCTAB nTab = rPos.Tab();
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetValue(rPos.Col(), rPos.Row());
|
|
return 0.0;
|
|
}
|
|
|
|
double ScDocument::GetValue( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
|
|
{
|
|
ScAddress aAdr(nCol, nRow, nTab);
|
|
return GetValue(aAdr);
|
|
}
|
|
|
|
sal_uInt32 ScDocument::GetNumberFormat( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetNumberFormat(nCol, nRow);
|
|
return 0;
|
|
}
|
|
|
|
sal_uInt32 ScDocument::GetNumberFormat( const ScRange& rRange ) const
|
|
{
|
|
SCTAB nTab1 = rRange.aStart.Tab(), nTab2 = rRange.aEnd.Tab();
|
|
SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col();
|
|
SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row();
|
|
|
|
if (!HasTable(nTab1) || !HasTable(nTab2))
|
|
return 0;
|
|
|
|
sal_uInt32 nFormat = 0;
|
|
bool bFirstItem = true;
|
|
for (SCTAB nTab = nTab1; nTab <= nTab2 && nTab < GetTableCount() ; ++nTab)
|
|
for (SCCOL nCol = nCol1; nCol <= nCol2; ++nCol)
|
|
{
|
|
sal_uInt32 nThisFormat = maTabs[nTab]->GetNumberFormat(nCol, nRow1, nRow2);
|
|
if (bFirstItem)
|
|
{
|
|
nFormat = nThisFormat;
|
|
bFirstItem = false;
|
|
}
|
|
else if (nThisFormat != nFormat)
|
|
return 0;
|
|
}
|
|
|
|
return nFormat;
|
|
}
|
|
|
|
sal_uInt32 ScDocument::GetNumberFormat( const ScInterpreterContext& rContext, const ScAddress& rPos ) const
|
|
{
|
|
SCTAB nTab = rPos.Tab();
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetNumberFormat( rContext, rPos );
|
|
return 0;
|
|
}
|
|
|
|
void ScDocument::SetNumberFormat( const ScAddress& rPos, sal_uInt32 nNumberFormat )
|
|
{
|
|
assert(!IsThreadedGroupCalcInProgress());
|
|
SCTAB nTab = rPos.Tab();
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetNumberFormat(rPos.Col(), rPos.Row(), nNumberFormat);
|
|
}
|
|
|
|
void ScDocument::GetNumberFormatInfo( const ScInterpreterContext& rContext, SvNumFormatType& nType, sal_uInt32& nIndex,
|
|
const ScAddress& rPos ) const
|
|
{
|
|
SCTAB nTab = rPos.Tab();
|
|
if (nTab < GetTableCount() && maTabs[nTab])
|
|
{
|
|
nIndex = maTabs[nTab]->GetNumberFormat( rContext, rPos );
|
|
nType = rContext.NFGetType(nIndex);
|
|
}
|
|
else
|
|
{
|
|
nType = SvNumFormatType::UNDEFINED;
|
|
nIndex = 0;
|
|
}
|
|
}
|
|
|
|
OUString ScDocument::GetFormula( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetFormula(nCol, nRow);
|
|
|
|
return OUString();
|
|
}
|
|
|
|
const ScFormulaCell* ScDocument::GetFormulaCell( const ScAddress& rPos ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(rPos.Tab()))
|
|
return pTable->GetFormulaCell(rPos.Col(), rPos.Row());
|
|
return nullptr;
|
|
}
|
|
|
|
ScFormulaCell* ScDocument::GetFormulaCell( const ScAddress& rPos )
|
|
{
|
|
if (ScTable* pTable = FetchTable(rPos.Tab()))
|
|
return pTable->GetFormulaCell(rPos.Col(), rPos.Row());
|
|
return nullptr;
|
|
}
|
|
|
|
CellType ScDocument::GetCellType( const ScAddress& rPos ) const
|
|
{
|
|
SCTAB nTab = rPos.Tab();
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetCellType(rPos);
|
|
return CELLTYPE_NONE;
|
|
}
|
|
|
|
CellType ScDocument::GetCellType( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetCellType( nCol, nRow );
|
|
return CELLTYPE_NONE;
|
|
}
|
|
|
|
bool ScDocument::HasStringData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab) ; pTable && nCol < pTable->GetAllocatedColumnsCount())
|
|
return pTable->HasStringData(nCol, nRow);
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::HasValueData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab) ; pTable && nCol < pTable->GetAllocatedColumnsCount())
|
|
return pTable->HasValueData( nCol, nRow );
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::HasValueData( const ScAddress& rPos ) const
|
|
{
|
|
return HasValueData(rPos.Col(), rPos.Row(), rPos.Tab());
|
|
}
|
|
|
|
bool ScDocument::HasStringCells( const ScRange& rRange ) const
|
|
{
|
|
// true, if String- or Edit cells in range
|
|
|
|
SCCOL nStartCol = rRange.aStart.Col();
|
|
SCROW nStartRow = rRange.aStart.Row();
|
|
SCTAB nStartTab = rRange.aStart.Tab();
|
|
SCCOL nEndCol = rRange.aEnd.Col();
|
|
SCROW nEndRow = rRange.aEnd.Row();
|
|
SCTAB nEndTab = rRange.aEnd.Tab();
|
|
|
|
for (SCTAB nTab = nStartTab; nTab <= nEndTab && nTab < GetTableCount(); nTab++)
|
|
{
|
|
if ( maTabs[nTab] && maTabs[nTab]->HasStringCells( nStartCol, nStartRow, nEndCol, nEndRow ) )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::HasSelectionData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
|
|
{
|
|
sal_uInt32 nValidation = GetAttr( nCol, nRow, nTab, ATTR_VALIDDATA )->GetValue();
|
|
if( nValidation )
|
|
{
|
|
const ScValidationData* pData = GetValidationEntry( nValidation );
|
|
if( pData && pData->HasSelectionList() )
|
|
return true;
|
|
}
|
|
return HasStringCells( ScRange( nCol, 0, nTab, nCol, MaxRow(), nTab ) );
|
|
}
|
|
|
|
bool ScDocument::HasValidationData( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
|
|
{
|
|
sal_uInt32 nValidation = GetAttr( nCol, nRow, nTab, ATTR_VALIDDATA )->GetValue();
|
|
if( nValidation )
|
|
{
|
|
const ScValidationData* pData = GetValidationEntry( nValidation );
|
|
if( pData && pData->GetDataMode() != ScValidationMode::SC_VALID_ANY )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void ScDocument::CheckVectorizationState()
|
|
{
|
|
bool bOldAutoCalc = GetAutoCalc();
|
|
bAutoCalc = false; // no multiple calculations
|
|
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
a->CheckVectorizationState();
|
|
}
|
|
|
|
SetAutoCalc(bOldAutoCalc);
|
|
}
|
|
|
|
void ScDocument::SetAllFormulasDirty( const sc::SetFormulaDirtyContext& rCxt )
|
|
{
|
|
bool bOldAutoCalc = GetAutoCalc();
|
|
bAutoCalc = false; // no multiple calculations
|
|
{ // scope for bulk broadcast
|
|
ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
a->SetAllFormulasDirty(rCxt);
|
|
}
|
|
}
|
|
|
|
// Although Charts are also set to dirty in Tracking without AutoCalc
|
|
// if all formulas are dirty, the charts can no longer be caught
|
|
// (#45205#) - that is why all Charts have to be explicitly handled again
|
|
if (pChartListenerCollection)
|
|
pChartListenerCollection->SetDirty();
|
|
|
|
SetAutoCalc( bOldAutoCalc );
|
|
}
|
|
|
|
void ScDocument::SetDirty( const ScRange& rRange, bool bIncludeEmptyCells )
|
|
{
|
|
bool bOldAutoCalc = GetAutoCalc();
|
|
bAutoCalc = false; // no multiple calculations
|
|
{ // scope for bulk broadcast
|
|
ScBulkBroadcast aBulkBroadcast( GetBASM(), SfxHintId::ScDataChanged);
|
|
SCTAB nTab2 = rRange.aEnd.Tab();
|
|
for (SCTAB i = rRange.aStart.Tab(); i <= nTab2 && i < GetTableCount(); i++)
|
|
if (maTabs[i]) maTabs[i]->SetDirty( rRange,
|
|
(bIncludeEmptyCells ? ScColumn::BROADCAST_BROADCASTERS : ScColumn::BROADCAST_DATA_POSITIONS));
|
|
|
|
/* TODO: this now also notifies conditional formatting and does a UNO
|
|
* broadcast, which wasn't done here before. Is that an actually
|
|
* desired side effect, or should we come up with a method that
|
|
* doesn't? */
|
|
if (bIncludeEmptyCells)
|
|
BroadcastCells( rRange, SfxHintId::ScDataChanged, false);
|
|
}
|
|
SetAutoCalc( bOldAutoCalc );
|
|
}
|
|
|
|
void ScDocument::SetTableOpDirty( const ScRange& rRange )
|
|
{
|
|
bool bOldAutoCalc = GetAutoCalc();
|
|
bAutoCalc = false; // no multiple recalculation
|
|
SCTAB nTab2 = rRange.aEnd.Tab();
|
|
for (SCTAB i = rRange.aStart.Tab(); i <= nTab2 && i < GetTableCount(); i++)
|
|
if (maTabs[i]) maTabs[i]->SetTableOpDirty( rRange );
|
|
SetAutoCalc( bOldAutoCalc );
|
|
}
|
|
|
|
void ScDocument::InterpretDirtyCells( const ScRangeList& rRanges )
|
|
{
|
|
if (!GetAutoCalc())
|
|
return;
|
|
|
|
PrepareFormulaCalc();
|
|
|
|
for (size_t nPos=0, nRangeCount = rRanges.size(); nPos < nRangeCount; nPos++)
|
|
{
|
|
const ScRange& rRange = rRanges[nPos];
|
|
for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
|
|
{
|
|
ScTable* pTab = FetchTable(nTab);
|
|
if (!pTab)
|
|
return;
|
|
|
|
pTab->InterpretDirtyCells(
|
|
rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row());
|
|
}
|
|
}
|
|
|
|
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
|
|
mpFormulaGroupCxt.reset();
|
|
}
|
|
|
|
bool ScDocument::InterpretCellsIfNeeded( const ScRangeList& rRanges )
|
|
{
|
|
bool allInterpreted = true;
|
|
for (size_t nPos=0, nRangeCount = rRanges.size(); nPos < nRangeCount; nPos++)
|
|
{
|
|
const ScRange& rRange = rRanges[nPos];
|
|
for (SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab)
|
|
{
|
|
ScTable* pTab = FetchTable(nTab);
|
|
if (!pTab)
|
|
break;
|
|
|
|
if( !pTab->InterpretCellsIfNeeded(
|
|
rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()))
|
|
{
|
|
allInterpreted = false;
|
|
}
|
|
}
|
|
}
|
|
return allInterpreted;
|
|
}
|
|
|
|
void ScDocument::AddTableOpFormulaCell( ScFormulaCell* pCell )
|
|
{
|
|
if (m_TableOpList.empty())
|
|
return;
|
|
|
|
ScInterpreterTableOpParams *const p = m_TableOpList.back();
|
|
if ( p->bCollectNotifications )
|
|
{
|
|
if ( p->bRefresh )
|
|
{ // refresh pointers only
|
|
p->aNotifiedFormulaCells.push_back( pCell );
|
|
}
|
|
else
|
|
{ // init both, address and pointer
|
|
p->aNotifiedFormulaCells.push_back( pCell );
|
|
p->aNotifiedFormulaPos.push_back( pCell->aPos );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScDocument::CalcAll()
|
|
{
|
|
PrepareFormulaCalc();
|
|
ClearLookupCaches(); // Ensure we don't deliver zombie data.
|
|
sc::AutoCalcSwitch aSwitch(*this, true);
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
a->SetDirtyVar();
|
|
}
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
a->CalcAll();
|
|
}
|
|
ClearFormulaTree();
|
|
|
|
// In eternal hard recalc state caches were not added as listeners,
|
|
// invalidate them so the next non-CalcAll() normal lookup will not be
|
|
// presented with outdated data.
|
|
if (GetHardRecalcState() == HardRecalcState::ETERNAL)
|
|
ClearLookupCaches();
|
|
}
|
|
|
|
void ScDocument::CompileAll()
|
|
{
|
|
sc::CompileFormulaContext aCxt(*this);
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
a->CompileAll(aCxt);
|
|
}
|
|
|
|
sc::SetFormulaDirtyContext aFormulaDirtyCxt;
|
|
SetAllFormulasDirty(aFormulaDirtyCxt);
|
|
}
|
|
|
|
void ScDocument::CompileXML()
|
|
{
|
|
bool bOldAutoCalc = GetAutoCalc();
|
|
SetAutoCalc( false );
|
|
ScProgress aProgress( GetDocumentShell(), ScResId(
|
|
STR_PROGRESS_CALCULATING ), GetXMLImportedFormulaCount(), true );
|
|
|
|
sc::CompileFormulaContext aCxt(*this);
|
|
|
|
// set AutoNameCache to speed up automatic name lookup
|
|
OSL_ENSURE( !pAutoNameCache, "AutoNameCache already set" );
|
|
pAutoNameCache.reset( new ScAutoNameCache( *this ) );
|
|
|
|
if (pRangeName)
|
|
pRangeName->CompileUnresolvedXML(aCxt);
|
|
|
|
std::for_each(maTabs.begin(), maTabs.end(),
|
|
[&](ScTableUniquePtr & pTab)
|
|
{
|
|
if (pTab)
|
|
pTab->CompileXML(aCxt, aProgress);
|
|
}
|
|
);
|
|
StartAllListeners();
|
|
|
|
pAutoNameCache.reset(); // valid only during CompileXML, where cell contents don't change
|
|
|
|
if ( pValidationList )
|
|
{
|
|
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
|
|
pValidationList->CompileXML();
|
|
}
|
|
|
|
// Track all formula cells that were appended to the FormulaTrack during
|
|
// import or CompileXML().
|
|
TrackFormulas();
|
|
|
|
SetAutoCalc( bOldAutoCalc );
|
|
}
|
|
|
|
bool ScDocument::CompileErrorCells(FormulaError nErrCode)
|
|
{
|
|
bool bCompiled = false;
|
|
sc::CompileFormulaContext aCxt(*this);
|
|
for (const auto& pTab : maTabs)
|
|
{
|
|
if (pTab && pTab->CompileErrorCells(aCxt, nErrCode))
|
|
bCompiled = true;
|
|
}
|
|
|
|
return bCompiled;
|
|
}
|
|
|
|
void ScDocument::CalcAfterLoad( bool bStartListening )
|
|
{
|
|
if (bIsClip) // Excel data is loaded from the Clipboard to a Clip-Doc
|
|
return; // the calculation is then only performed when inserting into the real document
|
|
|
|
bCalcingAfterLoad = true;
|
|
sc::CompileFormulaContext aCxt(*this);
|
|
{
|
|
for (const auto& pTable : maTabs)
|
|
{
|
|
if (pTable)
|
|
pTable->CalcAfterLoad(aCxt, bStartListening);
|
|
}
|
|
for (const auto& pTable : maTabs)
|
|
{
|
|
if (pTable)
|
|
pTable->SetDirtyAfterLoad();
|
|
}
|
|
}
|
|
bCalcingAfterLoad = false;
|
|
|
|
SetDetectiveDirty(false); // No real changes yet
|
|
|
|
// #i112436# If formula cells are already dirty, they don't broadcast further changes.
|
|
// So the source ranges of charts must be interpreted even if they are not visible,
|
|
// similar to ScMyShapeResizer::CreateChartListener for loading own files (i104899).
|
|
if (pChartListenerCollection)
|
|
{
|
|
const ScChartListenerCollection::ListenersType& rListeners = pChartListenerCollection->getListeners();
|
|
for (auto const& it : rListeners)
|
|
{
|
|
const ScChartListener *const p = it.second.get();
|
|
InterpretDirtyCells(*p->GetRangeList());
|
|
}
|
|
}
|
|
}
|
|
|
|
FormulaError ScDocument::GetErrCode( const ScAddress& rPos ) const
|
|
{
|
|
SCTAB nTab = rPos.Tab();
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetErrCode( rPos );
|
|
return FormulaError::NONE;
|
|
}
|
|
|
|
void ScDocument::ResetChanged( const ScRange& rRange )
|
|
{
|
|
SCTAB nTabSize = GetTableCount();
|
|
SCTAB nTab1 = rRange.aStart.Tab();
|
|
SCTAB nTab2 = rRange.aEnd.Tab();
|
|
for (SCTAB nTab = nTab1; nTab1 <= nTab2 && nTab < nTabSize; ++nTab)
|
|
if (maTabs[nTab])
|
|
maTabs[nTab]->ResetChanged(rRange);
|
|
}
|
|
|
|
// Column widths / Row heights --------------------------------------
|
|
|
|
void ScDocument::SetColWidth( SCCOL nCol, SCTAB nTab, sal_uInt16 nNewWidth )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetColWidth(nCol, nNewWidth);
|
|
}
|
|
|
|
void ScDocument::SetColWidthOnly( SCCOL nCol, SCTAB nTab, sal_uInt16 nNewWidth )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetColWidthOnly(nCol, nNewWidth);
|
|
}
|
|
|
|
void ScDocument::SetRowHeight( SCROW nRow, SCTAB nTab, sal_uInt16 nNewHeight )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetRowHeight(nRow, nNewHeight);
|
|
}
|
|
|
|
void ScDocument::SetRowHeightRange( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, sal_uInt16 nNewHeight )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetRowHeightRange(nStartRow, nEndRow, nNewHeight, 1.0, true);
|
|
}
|
|
|
|
void ScDocument::SetRowHeightOnly( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, sal_uInt16 nNewHeight )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetRowHeightOnly( nStartRow, nEndRow, nNewHeight );
|
|
}
|
|
|
|
void ScDocument::SetManualHeight( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bManual )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetManualHeight( nStartRow, nEndRow, bManual );
|
|
}
|
|
|
|
sal_uInt16 ScDocument::GetColWidth( SCCOL nCol, SCTAB nTab, bool bHiddenAsZero ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetColWidth( nCol, bHiddenAsZero );
|
|
OSL_FAIL("wrong table number");
|
|
return 0;
|
|
}
|
|
|
|
tools::Long ScDocument::GetColWidth( SCCOL nStartCol, SCCOL nEndCol, SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetColWidth(nStartCol, nEndCol);
|
|
return 0;
|
|
}
|
|
|
|
sal_uInt16 ScDocument::GetOriginalWidth( SCCOL nCol, SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetOriginalWidth( nCol );
|
|
OSL_FAIL("wrong table number");
|
|
return 0;
|
|
}
|
|
|
|
sal_uInt16 ScDocument::GetCommonWidth( SCCOL nEndCol, SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetCommonWidth( nEndCol );
|
|
OSL_FAIL("Wrong table number");
|
|
return 0;
|
|
}
|
|
|
|
sal_uInt16 ScDocument::GetOriginalHeight( SCROW nRow, SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetOriginalHeight( nRow );
|
|
OSL_FAIL("Wrong table number");
|
|
return 0;
|
|
}
|
|
|
|
sal_uInt16 ScDocument::GetRowHeight( SCROW nRow, SCTAB nTab, bool bHiddenAsZero ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetRowHeight( nRow, nullptr, nullptr, bHiddenAsZero );
|
|
OSL_FAIL("Wrong sheet number");
|
|
return 0;
|
|
}
|
|
|
|
sal_uInt16 ScDocument::GetRowHeight( SCROW nRow, SCTAB nTab, SCROW* pStartRow, SCROW* pEndRow, bool bHiddenAsZero ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetRowHeight( nRow, pStartRow, pEndRow, bHiddenAsZero );
|
|
OSL_FAIL("Wrong sheet number");
|
|
return 0;
|
|
}
|
|
|
|
tools::Long ScDocument::GetRowHeight( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bHiddenAsZero ) const
|
|
{
|
|
if (nStartRow == nEndRow)
|
|
return GetRowHeight( nStartRow, nTab, bHiddenAsZero ); // faster for a single row
|
|
|
|
// check bounds because this method replaces former for(i=start;i<=end;++i) loops
|
|
if (nStartRow > nEndRow)
|
|
return 0;
|
|
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetRowHeight( nStartRow, nEndRow, bHiddenAsZero );
|
|
|
|
OSL_FAIL("wrong sheet number");
|
|
return 0;
|
|
}
|
|
|
|
SCROW ScDocument::GetRowForHeight( SCTAB nTab, tools::Long nHeight ) const
|
|
{
|
|
return maTabs[nTab]->GetRowForHeight(nHeight);
|
|
}
|
|
|
|
tools::Long ScDocument::GetScaledRowHeight( SCROW nStartRow, SCROW nEndRow,
|
|
SCTAB nTab, double fScale ) const
|
|
{
|
|
// faster for a single row
|
|
if (nStartRow == nEndRow)
|
|
return static_cast<tools::Long>(GetRowHeight( nStartRow, nTab) * fScale);
|
|
|
|
// check bounds because this method replaces former for(i=start;i<=end;++i) loops
|
|
if (nStartRow > nEndRow)
|
|
return 0;
|
|
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetScaledRowHeight( nStartRow, nEndRow, fScale);
|
|
|
|
OSL_FAIL("wrong sheet number");
|
|
return 0;
|
|
}
|
|
|
|
SCROW ScDocument::GetHiddenRowCount( SCROW nRow, SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetHiddenRowCount( nRow );
|
|
OSL_FAIL("wrong table number");
|
|
return 0;
|
|
}
|
|
|
|
tools::Long ScDocument::GetColOffset( SCCOL nCol, SCTAB nTab, bool bHiddenAsZero ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetColOffset( nCol, bHiddenAsZero );
|
|
OSL_FAIL("wrong table number");
|
|
return 0;
|
|
}
|
|
|
|
tools::Long ScDocument::GetRowOffset( SCROW nRow, SCTAB nTab, bool bHiddenAsZero ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetRowOffset( nRow, bHiddenAsZero );
|
|
OSL_FAIL("wrong table number");
|
|
return 0;
|
|
}
|
|
|
|
sal_uInt16 ScDocument::GetOptimalColWidth( SCCOL nCol, SCTAB nTab, OutputDevice* pDev,
|
|
double nPPTX, double nPPTY,
|
|
const Fraction& rZoomX, const Fraction& rZoomY,
|
|
bool bFormula, const ScMarkData* pMarkData,
|
|
const ScColWidthParam* pParam )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetOptimalColWidth(nCol, pDev, nPPTX, nPPTY, rZoomX,
|
|
rZoomY, bFormula, pMarkData, pParam);
|
|
OSL_FAIL("wrong table number");
|
|
return 0;
|
|
}
|
|
|
|
tools::Long ScDocument::GetNeededSize( SCCOL nCol, SCROW nRow, SCTAB nTab,
|
|
OutputDevice* pDev,
|
|
double nPPTX, double nPPTY,
|
|
const Fraction& rZoomX, const Fraction& rZoomY,
|
|
bool bWidth, bool bTotalSize, bool bInPrintTwips )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetNeededSize(nCol, nRow, pDev, nPPTX, nPPTY,
|
|
rZoomX, rZoomY, bWidth, bTotalSize,
|
|
bInPrintTwips);
|
|
OSL_FAIL("wrong table number");
|
|
return 0;
|
|
}
|
|
|
|
bool ScDocument::SetOptimalHeight( sc::RowHeightContext& rCxt, SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bApi )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->SetOptimalHeight(rCxt, nStartRow, nEndRow, bApi);
|
|
return false;
|
|
}
|
|
|
|
void ScDocument::UpdateAllRowHeights( sc::RowHeightContext& rCxt, const ScMarkData* pTabMark )
|
|
{
|
|
// one progress across all (selected) sheets
|
|
|
|
sal_uInt64 nCellCount = 0;
|
|
for (SCTAB nTab = 0; nTab < GetTableCount(); nTab++)
|
|
if ( maTabs[nTab] && ( !pTabMark || pTabMark->GetTableSelect(nTab) ) )
|
|
nCellCount += maTabs[nTab]->GetWeightedCount();
|
|
|
|
ScProgress aProgress( GetDocumentShell(), ScResId(STR_PROGRESS_HEIGHTING), nCellCount, true );
|
|
|
|
sal_uInt64 nProgressStart = 0;
|
|
for (SCTAB nTab = 0; nTab < GetTableCount(); nTab++)
|
|
if ( maTabs[nTab] && ( !pTabMark || pTabMark->GetTableSelect(nTab) ) )
|
|
{
|
|
maTabs[nTab]->SetOptimalHeightOnly(rCxt, 0, MaxRow(), &aProgress, nProgressStart);
|
|
maTabs[nTab]->SetDrawPageSize();
|
|
nProgressStart += maTabs[nTab]->GetWeightedCount();
|
|
}
|
|
}
|
|
|
|
// Column/Row - Flags ----------------------------------------------
|
|
|
|
void ScDocument::ShowCol(SCCOL nCol, SCTAB nTab, bool bShow)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->ShowCol(nCol, bShow);
|
|
}
|
|
|
|
void ScDocument::ShowRow(SCROW nRow, SCTAB nTab, bool bShow)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->ShowRow(nRow, bShow);
|
|
}
|
|
|
|
void ScDocument::ShowRows(SCROW nRow1, SCROW nRow2, SCTAB nTab, bool bShow)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->ShowRows( nRow1, nRow2, bShow );
|
|
}
|
|
|
|
void ScDocument::SetRowFlags( SCROW nRow, SCTAB nTab, CRFlags nNewFlags )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetRowFlags( nRow, nNewFlags );
|
|
}
|
|
|
|
void ScDocument::SetRowFlags( SCROW nStartRow, SCROW nEndRow, SCTAB nTab, CRFlags nNewFlags )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetRowFlags( nStartRow, nEndRow, nNewFlags );
|
|
}
|
|
|
|
CRFlags ScDocument::GetColFlags( SCCOL nCol, SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetColFlags( nCol );
|
|
OSL_FAIL("wrong table number");
|
|
return CRFlags::NONE;
|
|
}
|
|
|
|
CRFlags ScDocument::GetRowFlags( SCROW nRow, SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetRowFlags( nRow );
|
|
OSL_FAIL("wrong table number");
|
|
return CRFlags::NONE;
|
|
}
|
|
|
|
void ScDocument::GetAllRowBreaks(set<SCROW>& rBreaks, SCTAB nTab, bool bPage, bool bManual) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
pTable->GetAllRowBreaks(rBreaks, bPage, bManual);
|
|
}
|
|
|
|
void ScDocument::GetAllColBreaks(set<SCCOL>& rBreaks, SCTAB nTab, bool bPage, bool bManual) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
pTable->GetAllColBreaks(rBreaks, bPage, bManual);
|
|
}
|
|
|
|
ScBreakType ScDocument::HasRowBreak(SCROW nRow, SCTAB nTab) const
|
|
{
|
|
ScBreakType nType = ScBreakType::NONE;
|
|
if (const ScTable* pTable = FetchTable(nTab); pTable && ValidRow(nRow))
|
|
{
|
|
if (pTable->HasRowPageBreak(nRow))
|
|
nType |= ScBreakType::Page;
|
|
|
|
if (pTable->HasRowManualBreak(nRow))
|
|
nType |= ScBreakType::Manual;
|
|
}
|
|
return nType;
|
|
}
|
|
|
|
ScBreakType ScDocument::HasColBreak(SCCOL nCol, SCTAB nTab) const
|
|
{
|
|
ScBreakType nType = ScBreakType::NONE;
|
|
|
|
if (const ScTable* pTable = FetchTable(nTab); pTable && ValidCol(nCol))
|
|
{
|
|
if (pTable->HasColPageBreak(nCol))
|
|
nType |= ScBreakType::Page;
|
|
|
|
if (pTable->HasColManualBreak(nCol))
|
|
nType |= ScBreakType::Manual;
|
|
}
|
|
return nType;
|
|
}
|
|
|
|
void ScDocument::SetRowBreak(SCROW nRow, SCTAB nTab, bool bPage, bool bManual)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab); pTable && ValidRow(nRow))
|
|
pTable->SetRowBreak(nRow, bPage, bManual);
|
|
}
|
|
|
|
void ScDocument::SetColBreak(SCCOL nCol, SCTAB nTab, bool bPage, bool bManual)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab); pTable && ValidCol(nCol))
|
|
pTable->SetColBreak(nCol, bPage, bManual);
|
|
}
|
|
|
|
void ScDocument::RemoveRowBreak(SCROW nRow, SCTAB nTab, bool bPage, bool bManual)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab); pTable && ValidRow(nRow))
|
|
pTable->RemoveRowBreak(nRow, bPage, bManual);
|
|
}
|
|
|
|
void ScDocument::RemoveColBreak(SCCOL nCol, SCTAB nTab, bool bPage, bool bManual)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab); pTable && ValidCol(nCol))
|
|
pTable->RemoveColBreak(nCol, bPage, bManual);
|
|
}
|
|
|
|
Sequence<TablePageBreakData> ScDocument::GetRowBreakData(SCTAB nTab) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetRowBreakData();
|
|
|
|
return Sequence<TablePageBreakData>();
|
|
}
|
|
|
|
bool ScDocument::RowHidden(SCROW nRow, SCTAB nTab, SCROW* pFirstRow, SCROW* pLastRow) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->RowHidden(nRow, pFirstRow, pLastRow);
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::HasHiddenRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->HasHiddenRows(nStartRow, nEndRow);
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::ColHidden(SCCOL nCol, SCTAB nTab, SCCOL* pFirstCol, SCCOL* pLastCol) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->ColHidden(nCol, pFirstCol, pLastCol);
|
|
|
|
if (pFirstCol)
|
|
*pFirstCol = nCol;
|
|
if (pLastCol)
|
|
*pLastCol = nCol;
|
|
return false;
|
|
}
|
|
|
|
void ScDocument::SetRowHidden(SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bHidden)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetRowHidden(nStartRow, nEndRow, bHidden);
|
|
}
|
|
|
|
void ScDocument::SetColHidden(SCCOL nStartCol, SCCOL nEndCol, SCTAB nTab, bool bHidden)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetColHidden(nStartCol, nEndCol, bHidden);
|
|
}
|
|
|
|
SCROW ScDocument::FirstVisibleRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->FirstVisibleRow(nStartRow, nEndRow);
|
|
return 0;
|
|
}
|
|
|
|
SCROW ScDocument::LastVisibleRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->LastVisibleRow(nStartRow, nEndRow);
|
|
return ::std::numeric_limits<SCROW>::max();
|
|
}
|
|
|
|
SCROW ScDocument::CountVisibleRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->CountVisibleRows(nStartRow, nEndRow);
|
|
return 0;
|
|
}
|
|
|
|
bool ScDocument::RowFiltered(SCROW nRow, SCTAB nTab, SCROW* pFirstRow, SCROW* pLastRow) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->RowFiltered(nRow, pFirstRow, pLastRow);
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::HasFilteredRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->HasFilteredRows(nStartRow, nEndRow);
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::ColFiltered(SCCOL nCol, SCTAB nTab) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->ColFiltered(nCol);
|
|
return false;
|
|
}
|
|
|
|
void ScDocument::SetRowFiltered(SCROW nStartRow, SCROW nEndRow, SCTAB nTab, bool bFiltered)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetRowFiltered(nStartRow, nEndRow, bFiltered);
|
|
}
|
|
|
|
SCROW ScDocument::FirstNonFilteredRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->FirstNonFilteredRow(nStartRow, nEndRow);
|
|
return std::numeric_limits<SCROW>::max();
|
|
}
|
|
|
|
SCROW ScDocument::LastNonFilteredRow(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->LastNonFilteredRow(nStartRow, nEndRow);
|
|
return std::numeric_limits<SCROW>::max();
|
|
}
|
|
|
|
SCROW ScDocument::CountNonFilteredRows(SCROW nStartRow, SCROW nEndRow, SCTAB nTab) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->CountNonFilteredRows(nStartRow, nEndRow);
|
|
return 0;
|
|
}
|
|
|
|
bool ScDocument::IsManualRowHeight(SCROW nRow, SCTAB nTab) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->IsManualRowHeight(nRow);
|
|
return false;
|
|
}
|
|
|
|
void ScDocument::SyncColRowFlags()
|
|
{
|
|
for (const auto& pTable : maTabs)
|
|
{
|
|
if (pTable)
|
|
pTable->SyncColRowFlags();
|
|
}
|
|
}
|
|
|
|
SCROW ScDocument::GetLastFlaggedRow( SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetLastFlaggedRow();
|
|
return 0;
|
|
}
|
|
|
|
SCCOL ScDocument::GetLastChangedColFlagsWidth( SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetLastChangedColFlagsWidth();
|
|
return 0;
|
|
}
|
|
|
|
SCROW ScDocument::GetLastChangedRowFlagsWidth( SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetLastChangedRowFlagsWidth();
|
|
return 0;
|
|
}
|
|
|
|
SCCOL ScDocument::GetNextDifferentChangedColFlagsWidth( SCTAB nTab, SCCOL nStart) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
CRFlags nStartFlags = pTable->GetColFlags(nStart);
|
|
sal_uInt16 nStartWidth = pTable->GetOriginalWidth(nStart);
|
|
for (SCCOL nCol : pTable->GetColumnsRange( nStart + 1, MaxCol()))
|
|
{
|
|
if (((nStartFlags & CRFlags::ManualBreak) != (pTable->GetColFlags(nCol) & CRFlags::ManualBreak)) ||
|
|
(nStartWidth != pTable->GetOriginalWidth(nCol)) ||
|
|
((nStartFlags & CRFlags::Hidden) != (pTable->GetColFlags(nCol) & CRFlags::Hidden)) )
|
|
{
|
|
return nCol;
|
|
}
|
|
}
|
|
return MaxCol()+1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
SCROW ScDocument::GetNextDifferentChangedRowFlagsWidth( SCTAB nTab, SCROW nStart) const
|
|
{
|
|
const ScTable* pTable = FetchTable(nTab);
|
|
if (!pTable)
|
|
return 0;
|
|
|
|
const ScBitMaskCompressedArray<SCROW, CRFlags>* pRowFlagsArray = pTable->GetRowFlagsArray();
|
|
if (!pRowFlagsArray)
|
|
return 0;
|
|
|
|
if (!pTable->mpRowHeights || !pTable->mpHiddenRows)
|
|
return 0;
|
|
|
|
size_t nIndex; // ignored
|
|
SCROW nFlagsEndRow;
|
|
SCROW nHiddenEndRow;
|
|
SCROW nHeightEndRow;
|
|
CRFlags nFlags;
|
|
bool bHidden;
|
|
sal_uInt16 nHeight;
|
|
CRFlags nStartFlags = nFlags = pRowFlagsArray->GetValue( nStart, nIndex, nFlagsEndRow);
|
|
bool bStartHidden = bHidden = pTable->RowHidden( nStart, nullptr, &nHiddenEndRow);
|
|
sal_uInt16 nStartHeight = nHeight = pTable->GetRowHeight( nStart, nullptr, &nHeightEndRow, false);
|
|
SCROW nRow;
|
|
while ((nRow = std::min( nHiddenEndRow, std::min( nFlagsEndRow, nHeightEndRow)) + 1) <= MaxRow())
|
|
{
|
|
if (nFlagsEndRow < nRow)
|
|
nFlags = pRowFlagsArray->GetValue( nRow, nIndex, nFlagsEndRow);
|
|
if (nHiddenEndRow < nRow)
|
|
bHidden = pTable->RowHidden( nRow, nullptr, &nHiddenEndRow);
|
|
if (nHeightEndRow < nRow)
|
|
nHeight = pTable->GetRowHeight( nRow, nullptr, &nHeightEndRow, false);
|
|
|
|
if (((nStartFlags & CRFlags::ManualBreak) != (nFlags & CRFlags::ManualBreak)) ||
|
|
((nStartFlags & CRFlags::ManualSize) != (nFlags & CRFlags::ManualSize)) ||
|
|
(bStartHidden != bHidden) ||
|
|
(nStartHeight != nHeight))
|
|
return nRow;
|
|
}
|
|
|
|
return MaxRow()+1;
|
|
}
|
|
|
|
void ScDocument::GetColDefault( SCTAB nTab, SCCOL nCol, SCROW nLastRow, SCROW& nDefault)
|
|
{
|
|
nDefault = 0;
|
|
ScDocAttrIterator aDocAttrItr(*this, nTab, nCol, 0, nCol, nLastRow);
|
|
SCCOL nColumn;
|
|
SCROW nStartRow;
|
|
SCROW nEndRow;
|
|
const ScPatternAttr* pAttr = aDocAttrItr.GetNext(nColumn, nStartRow, nEndRow);
|
|
if (nEndRow >= nLastRow)
|
|
return;
|
|
|
|
ScDefaultAttrMap aMap;
|
|
while (pAttr)
|
|
{
|
|
auto aItr = aMap.find(pAttr);
|
|
if (aItr == aMap.end())
|
|
{
|
|
ScDefaultAttr aAttr;
|
|
aAttr.nCount = static_cast<SCSIZE>(nEndRow - nStartRow + 1);
|
|
aAttr.nFirst = nStartRow;
|
|
aMap.insert({ pAttr, aAttr});
|
|
}
|
|
else
|
|
{
|
|
aItr->second.nCount += static_cast<SCSIZE>(nEndRow - nStartRow + 1);
|
|
}
|
|
pAttr = aDocAttrItr.GetNext(nColumn, nStartRow, nEndRow);
|
|
}
|
|
auto aDefaultItr = aMap.begin();
|
|
auto aItr = aDefaultItr;
|
|
++aItr;
|
|
while (aItr != aMap.end())
|
|
{
|
|
// for entries with equal count, use the one with the lowest start row,
|
|
// don't use the random order of pointer comparisons
|
|
if ( aItr->second.nCount > aDefaultItr->second.nCount ||
|
|
( aItr->second.nCount == aDefaultItr->second.nCount && aItr->second.nFirst < aDefaultItr->second.nFirst ) )
|
|
aDefaultItr = aItr;
|
|
++aItr;
|
|
}
|
|
nDefault = aDefaultItr->second.nFirst;
|
|
}
|
|
|
|
void ScDocument::StripHidden( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2, SCTAB nTab )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->StripHidden( rX1, rY1, rX2, rY2 );
|
|
}
|
|
|
|
void ScDocument::ExtendHidden( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2, SCTAB nTab )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->ExtendHidden( rX1, rY1, rX2, rY2 );
|
|
}
|
|
|
|
// Attribute ----------------------------------------------------------
|
|
|
|
const SfxPoolItem* ScDocument::GetAttr( SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
const SfxPoolItem* pTemp = pTable->GetAttr( nCol, nRow, nWhich );
|
|
if (pTemp)
|
|
return pTemp;
|
|
else
|
|
{
|
|
OSL_FAIL( "Attribute Null" );
|
|
}
|
|
}
|
|
return &mxPoolHelper->GetDocPool()->GetUserOrPoolDefaultItem( nWhich );
|
|
}
|
|
|
|
const SfxPoolItem* ScDocument::GetAttr( SCCOL nCol, SCROW nRow, SCTAB nTab, sal_uInt16 nWhich, SCROW& nStartRow, SCROW& nEndRow ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
const SfxPoolItem* pTemp = pTable->GetAttr( nCol, nRow, nWhich, nStartRow, nEndRow );
|
|
if (pTemp)
|
|
return pTemp;
|
|
else
|
|
{
|
|
OSL_FAIL( "Attribute Null" );
|
|
}
|
|
}
|
|
return &mxPoolHelper->GetDocPool()->GetUserOrPoolDefaultItem( nWhich );
|
|
}
|
|
|
|
const SfxPoolItem* ScDocument::GetAttr( const ScAddress& rPos, sal_uInt16 nWhich ) const
|
|
{
|
|
return GetAttr(rPos.Col(), rPos.Row(), rPos.Tab(), nWhich);
|
|
}
|
|
|
|
const ScPatternAttr* ScDocument::GetPattern( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetPattern( nCol, nRow );
|
|
return nullptr;
|
|
}
|
|
|
|
const ScPatternAttr* ScDocument::GetPattern( const ScAddress& rPos ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(rPos.Tab()))
|
|
return pTable->GetPattern(rPos.Col(), rPos.Row());
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
const ScPatternAttr* ScDocument::GetMostUsedPattern( SCCOL nCol, SCROW nStartRow, SCROW nEndRow, SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetMostUsedPattern(nCol, nStartRow, nEndRow);
|
|
return nullptr;
|
|
}
|
|
|
|
void ScDocument::ApplyAttr( SCCOL nCol, SCROW nRow, SCTAB nTab, const SfxPoolItem& rAttr )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->ApplyAttr( nCol, nRow, rAttr );
|
|
}
|
|
|
|
void ScDocument::ApplyPattern( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScPatternAttr& rAttr )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->ApplyPattern(nCol, nRow, rAttr);
|
|
}
|
|
|
|
void ScDocument::ApplyPatternArea( SCCOL nStartCol, SCROW nStartRow,
|
|
SCCOL nEndCol, SCROW nEndRow,
|
|
const ScMarkData& rMark,
|
|
const ScPatternAttr& rAttr,
|
|
ScEditDataArray* pDataArray,
|
|
bool* const pIsChanged )
|
|
{
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
if (maTabs[rTab])
|
|
maTabs[rTab]->ApplyPatternArea( nStartCol, nStartRow, nEndCol, nEndRow, rAttr, pDataArray, pIsChanged );
|
|
}
|
|
}
|
|
|
|
void ScDocument::ApplyPatternAreaTab( SCCOL nStartCol, SCROW nStartRow,
|
|
SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, const ScPatternAttr& rAttr )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->ApplyPatternArea(nStartCol, nStartRow, nEndCol, nEndRow, rAttr);
|
|
}
|
|
|
|
void ScDocument::ApplyPatternIfNumberformatIncompatible( const ScRange& rRange,
|
|
const ScMarkData& rMark, const ScPatternAttr& rPattern, SvNumFormatType nNewType )
|
|
{
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
if (maTabs[rTab])
|
|
maTabs[rTab]->ApplyPatternIfNumberformatIncompatible( rRange, rPattern, nNewType );
|
|
}
|
|
}
|
|
|
|
void ScDocument::AddCondFormatData( const ScRangeList& rRange, SCTAB nTab, sal_uInt32 nIndex )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->AddCondFormatData(rRange, nIndex);
|
|
}
|
|
|
|
void ScDocument::RemoveCondFormatData( const ScRangeList& rRange, SCTAB nTab, sal_uInt32 nIndex )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->RemoveCondFormatData(rRange, nIndex);
|
|
}
|
|
|
|
void ScDocument::ApplyStyle( SCCOL nCol, SCROW nRow, SCTAB nTab, const ScStyleSheet& rStyle)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->ApplyStyle(nCol, nRow, &rStyle);
|
|
}
|
|
|
|
void ScDocument::ApplyStyleArea( SCCOL nStartCol, SCROW nStartRow,
|
|
SCCOL nEndCol, SCROW nEndRow,
|
|
const ScMarkData& rMark,
|
|
const ScStyleSheet& rStyle)
|
|
{
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
if (maTabs[rTab])
|
|
maTabs[rTab]->ApplyStyleArea( nStartCol, nStartRow, nEndCol, nEndRow, rStyle );
|
|
}
|
|
}
|
|
|
|
void ScDocument::ApplyStyleAreaTab( SCCOL nStartCol, SCROW nStartRow,
|
|
SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, const ScStyleSheet& rStyle)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->ApplyStyleArea( nStartCol, nStartRow, nEndCol, nEndRow, rStyle );
|
|
}
|
|
|
|
void ScDocument::ApplySelectionStyle(const ScStyleSheet& rStyle, const ScMarkData& rMark)
|
|
{
|
|
// ApplySelectionStyle needs multi mark
|
|
if ( rMark.IsMarked() && !rMark.IsMultiMarked() )
|
|
{
|
|
const ScRange& aRange = rMark.GetMarkArea();
|
|
ApplyStyleArea( aRange.aStart.Col(), aRange.aStart.Row(),
|
|
aRange.aEnd.Col(), aRange.aEnd.Row(), rMark, rStyle );
|
|
}
|
|
else
|
|
{
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
if ( maTabs[rTab] )
|
|
maTabs[rTab]->ApplySelectionStyle( rStyle, rMark );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScDocument::ApplySelectionLineStyle( const ScMarkData& rMark,
|
|
const SvxBorderLine* pLine, bool bColorOnly )
|
|
{
|
|
if ( bColorOnly && !pLine )
|
|
return;
|
|
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
if (maTabs[rTab])
|
|
maTabs[rTab]->ApplySelectionLineStyle( rMark, pLine, bColorOnly );
|
|
}
|
|
}
|
|
|
|
const ScStyleSheet* ScDocument::GetStyle( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetStyle(nCol, nRow);
|
|
return nullptr;
|
|
}
|
|
|
|
const ScStyleSheet* ScDocument::GetSelectionStyle( const ScMarkData& rMark ) const
|
|
{
|
|
bool bEqual = true;
|
|
bool bFound;
|
|
|
|
const ScStyleSheet* pStyle = nullptr;
|
|
const ScStyleSheet* pNewStyle;
|
|
|
|
if ( rMark.IsMultiMarked() )
|
|
{
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
|
|
if (maTabs[rTab])
|
|
{
|
|
pNewStyle = maTabs[rTab]->GetSelectionStyle( rMark, bFound );
|
|
if (bFound)
|
|
{
|
|
if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
|
|
bEqual = false; // different
|
|
pStyle = pNewStyle;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( rMark.IsMarked() )
|
|
{
|
|
const ScRange& aRange = rMark.GetMarkArea();
|
|
for (SCTAB i = aRange.aStart.Tab(); i <= aRange.aEnd.Tab() && bEqual && i < GetTableCount(); i++)
|
|
if (maTabs[i] && rMark.GetTableSelect(i))
|
|
{
|
|
pNewStyle = maTabs[i]->GetAreaStyle( bFound,
|
|
aRange.aStart.Col(), aRange.aStart.Row(),
|
|
aRange.aEnd.Col(), aRange.aEnd.Row() );
|
|
if (bFound)
|
|
{
|
|
if ( !pNewStyle || ( pStyle && pNewStyle != pStyle ) )
|
|
bEqual = false; // different
|
|
pStyle = pNewStyle;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bEqual ? pStyle : nullptr;
|
|
}
|
|
|
|
void ScDocument::StyleSheetChanged( const SfxStyleSheetBase* pStyleSheet, bool bRemoved,
|
|
OutputDevice* pDev,
|
|
double nPPTX, double nPPTY,
|
|
const Fraction& rZoomX, const Fraction& rZoomY )
|
|
{
|
|
for (const auto& rTab : maTabs)
|
|
{
|
|
if (rTab)
|
|
{
|
|
rTab->StyleSheetChanged(pStyleSheet, bRemoved, pDev, nPPTX, nPPTY, rZoomX, rZoomY);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ScDocument::IsStyleSheetUsed( const ScStyleSheet& rStyle ) const
|
|
{
|
|
if ( bStyleSheetUsageInvalid || rStyle.GetUsage() == ScStyleSheet::Usage::UNKNOWN )
|
|
{
|
|
SfxStyleSheetIterator aIter( mxPoolHelper->GetStylePool(),
|
|
SfxStyleFamily::Para );
|
|
for ( const SfxStyleSheetBase* pStyle = aIter.First(); pStyle;
|
|
pStyle = aIter.Next() )
|
|
{
|
|
if (pStyle->isScStyleSheet())
|
|
{
|
|
const ScStyleSheet* pScStyle = static_cast<const ScStyleSheet*>( pStyle );
|
|
pScStyle->SetUsage( ScStyleSheet::Usage::NOTUSED );
|
|
}
|
|
}
|
|
|
|
bool bIsUsed = false;
|
|
|
|
for (const auto& pTable : maTabs)
|
|
{
|
|
if (pTable && pTable->IsStyleSheetUsed(rStyle))
|
|
bIsUsed = true;
|
|
}
|
|
|
|
bStyleSheetUsageInvalid = false;
|
|
|
|
return bIsUsed;
|
|
}
|
|
|
|
return rStyle.GetUsage() == ScStyleSheet::Usage::USED;
|
|
}
|
|
|
|
bool ScDocument::ApplyFlagsTab( SCCOL nStartCol, SCROW nStartRow,
|
|
SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, ScMF nFlags )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->ApplyFlags(nStartCol, nStartRow, nEndCol, nEndRow, nFlags);
|
|
|
|
OSL_FAIL("ApplyFlags: wrong table");
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::RemoveFlagsTab( SCCOL nStartCol, SCROW nStartRow,
|
|
SCCOL nEndCol, SCROW nEndRow, SCTAB nTab, ScMF nFlags )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->RemoveFlags(nStartCol, nStartRow, nEndCol, nEndRow, nFlags);
|
|
|
|
OSL_FAIL("RemoveFlags: wrong table");
|
|
return false;
|
|
}
|
|
|
|
void ScDocument::SetPattern( SCCOL nCol, SCROW nRow, SCTAB nTab, const CellAttributeHolder& rHolder )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetPattern(nCol, nRow, rHolder);
|
|
}
|
|
|
|
void ScDocument::SetPattern( const ScAddress& rPos, const CellAttributeHolder& rHolder )
|
|
{
|
|
SetPattern(rPos.Col(), rPos.Row(), rPos.Tab(), rHolder);
|
|
}
|
|
|
|
void ScDocument::SetPattern( const ScAddress& rPos, const ScPatternAttr& rAttr )
|
|
{
|
|
SCTAB nTab = rPos.Tab();
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetPattern(rPos, rAttr);
|
|
}
|
|
|
|
std::unique_ptr<ScPatternAttr> ScDocument::CreateSelectionPattern( const ScMarkData& rMark, bool bDeep )
|
|
{
|
|
ScMergePatternState aState;
|
|
|
|
if ( rMark.IsMultiMarked() ) // multi selection
|
|
{
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
if (maTabs[rTab])
|
|
maTabs[rTab]->MergeSelectionPattern( aState, rMark, bDeep );
|
|
}
|
|
}
|
|
if ( rMark.IsMarked() ) // single selection
|
|
{
|
|
const ScRange& aRange = rMark.GetMarkArea();
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
if (maTabs[rTab])
|
|
maTabs[rTab]->MergePatternArea( aState,
|
|
aRange.aStart.Col(), aRange.aStart.Row(),
|
|
aRange.aEnd.Col(), aRange.aEnd.Row(), bDeep );
|
|
}
|
|
}
|
|
|
|
OSL_ENSURE( aState.pItemSet, "SelectionPattern Null" );
|
|
if (aState.pItemSet)
|
|
{
|
|
std::unique_ptr<ScPatternAttr> pPattern(new ScPatternAttr(getCellAttributeHelper(), &aState.pItemSet.value()));
|
|
if (aState.mbValidPatternId)
|
|
pPattern->SetPAKey(aState.mnPatternId);
|
|
|
|
return pPattern;
|
|
}
|
|
else
|
|
return std::unique_ptr<ScPatternAttr>(new ScPatternAttr(getCellAttributeHelper())); // empty
|
|
}
|
|
|
|
const ScPatternAttr* ScDocument::GetSelectionPattern( const ScMarkData& rMark )
|
|
{
|
|
pSelectionAttr = CreateSelectionPattern( rMark );
|
|
return pSelectionAttr.get();
|
|
}
|
|
|
|
void ScDocument::GetSelectionFrame( const ScMarkData& rMark,
|
|
SvxBoxItem& rLineOuter,
|
|
SvxBoxInfoItem& rLineInner )
|
|
{
|
|
rLineOuter.SetLine(nullptr, SvxBoxItemLine::TOP);
|
|
rLineOuter.SetLine(nullptr, SvxBoxItemLine::BOTTOM);
|
|
rLineOuter.SetLine(nullptr, SvxBoxItemLine::LEFT);
|
|
rLineOuter.SetLine(nullptr, SvxBoxItemLine::RIGHT);
|
|
rLineOuter.SetAllDistances(0);
|
|
|
|
rLineInner.SetLine(nullptr, SvxBoxInfoItemLine::HORI);
|
|
rLineInner.SetLine(nullptr, SvxBoxInfoItemLine::VERT);
|
|
rLineInner.SetTable(true);
|
|
rLineInner.SetDist(true);
|
|
rLineInner.SetMinDist(false);
|
|
|
|
ScLineFlags aFlags;
|
|
|
|
if( rMark.IsMultiMarked() )
|
|
{
|
|
ScRangeList aRangeList;
|
|
rMark.FillRangeListWithMarks( &aRangeList, false );
|
|
size_t nRangeCount = aRangeList.size();
|
|
bool bMultipleRows = false, bMultipleCols = false;
|
|
for( size_t nRangeIdx = 0; nRangeIdx < nRangeCount; ++nRangeIdx )
|
|
{
|
|
const ScRange & rRange = aRangeList[ nRangeIdx ];
|
|
bMultipleRows = ( bMultipleRows || ( rRange.aStart.Row() != rRange.aEnd.Row() ) );
|
|
bMultipleCols = ( bMultipleCols || ( rRange.aStart.Col() != rRange.aEnd.Col() ) );
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
|
|
if (maTabs[rTab])
|
|
maTabs[rTab]->MergeBlockFrame( &rLineOuter, &rLineInner, aFlags,
|
|
rRange.aStart.Col(), rRange.aStart.Row(),
|
|
rRange.aEnd.Col(), rRange.aEnd.Row() );
|
|
}
|
|
}
|
|
rLineInner.EnableHor( bMultipleRows );
|
|
rLineInner.EnableVer( bMultipleCols );
|
|
}
|
|
else if( rMark.IsMarked() )
|
|
{
|
|
const ScRange& aRange = rMark.GetMarkArea();
|
|
rLineInner.EnableHor( aRange.aStart.Row() != aRange.aEnd.Row() );
|
|
rLineInner.EnableVer( aRange.aStart.Col() != aRange.aEnd.Col() );
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
|
|
if (maTabs[rTab])
|
|
maTabs[rTab]->MergeBlockFrame( &rLineOuter, &rLineInner, aFlags,
|
|
aRange.aStart.Col(), aRange.aStart.Row(),
|
|
aRange.aEnd.Col(), aRange.aEnd.Row() );
|
|
}
|
|
}
|
|
|
|
// Evaluate don't care Status
|
|
|
|
rLineInner.SetValid( SvxBoxInfoItemValidFlags::LEFT, ( aFlags.nLeft != SC_LINE_DONTCARE ) );
|
|
rLineInner.SetValid( SvxBoxInfoItemValidFlags::RIGHT, ( aFlags.nRight != SC_LINE_DONTCARE ) );
|
|
rLineInner.SetValid( SvxBoxInfoItemValidFlags::TOP, ( aFlags.nTop != SC_LINE_DONTCARE ) );
|
|
rLineInner.SetValid( SvxBoxInfoItemValidFlags::BOTTOM, ( aFlags.nBottom != SC_LINE_DONTCARE ) );
|
|
rLineInner.SetValid( SvxBoxInfoItemValidFlags::HORI, ( aFlags.nHori != SC_LINE_DONTCARE ) );
|
|
rLineInner.SetValid( SvxBoxInfoItemValidFlags::VERT, ( aFlags.nVert != SC_LINE_DONTCARE ) );
|
|
}
|
|
|
|
static HasAttrFlags OptimizeHasAttrib( HasAttrFlags nMask, const ScDocumentPool* pPool )
|
|
{
|
|
if ( nMask & HasAttrFlags::Rotate )
|
|
{
|
|
// Is attribute used in document?
|
|
// (as in fillinfo)
|
|
|
|
bool bAnyItem = false;
|
|
ItemSurrogates aSurrogates;
|
|
pPool->GetItemSurrogates(aSurrogates, ATTR_ROTATE_VALUE);
|
|
for (const SfxPoolItem* pItem : aSurrogates)
|
|
{
|
|
// 90 or 270 degrees is former SvxOrientationItem - only look for other values
|
|
// (see ScPatternAttr::GetCellOrientation)
|
|
Degree100 nAngle = static_cast<const ScRotateValueItem*>(pItem)->GetValue();
|
|
if ( nAngle && nAngle != 9000_deg100 && nAngle != 27000_deg100 )
|
|
{
|
|
bAnyItem = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!bAnyItem)
|
|
nMask &= ~HasAttrFlags::Rotate;
|
|
}
|
|
return nMask;
|
|
}
|
|
|
|
bool ScDocument::HasAttrib( SCCOL nCol1, SCROW nRow1, SCTAB nTab1,
|
|
SCCOL nCol2, SCROW nRow2, SCTAB nTab2, HasAttrFlags nMask ) const
|
|
{
|
|
nMask = OptimizeHasAttrib( nMask, mxPoolHelper->GetDocPool());
|
|
|
|
if (nMask == HasAttrFlags::NONE)
|
|
return false;
|
|
|
|
for (SCTAB i = nTab1; i <= nTab2 && i < GetTableCount(); i++)
|
|
if (maTabs[i])
|
|
{
|
|
if ( nMask & HasAttrFlags::RightOrCenter )
|
|
{
|
|
// On a RTL sheet, don't start to look for the default left value
|
|
// (which is then logically right), instead always assume true.
|
|
// That way, ScAttrArray::HasAttrib doesn't have to handle RTL sheets.
|
|
|
|
if ( IsLayoutRTL(i) )
|
|
return true;
|
|
}
|
|
|
|
if( maTabs[i]->HasAttrib( nCol1, nRow1, nCol2, nRow2, nMask ))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::HasAttrib( SCCOL nCol, SCROW nRow, SCTAB nTab, HasAttrFlags nMask, SCROW* nStartRow, SCROW* nEndRow ) const
|
|
{
|
|
nMask = OptimizeHasAttrib( nMask, mxPoolHelper->GetDocPool());
|
|
|
|
if (nMask == HasAttrFlags::NONE || nTab >= GetTableCount())
|
|
{
|
|
if( nStartRow )
|
|
*nStartRow = 0;
|
|
if( nEndRow )
|
|
*nEndRow = MaxRow();
|
|
return false;
|
|
}
|
|
|
|
if ( nMask & HasAttrFlags::RightOrCenter )
|
|
{
|
|
// On a RTL sheet, don't start to look for the default left value
|
|
// (which is then logically right), instead always assume true.
|
|
// That way, ScAttrArray::HasAttrib doesn't have to handle RTL sheets.
|
|
|
|
if ( IsLayoutRTL(nTab) )
|
|
{
|
|
if( nStartRow )
|
|
*nStartRow = 0;
|
|
if( nEndRow )
|
|
*nEndRow = MaxRow();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return maTabs[nTab]->HasAttrib( nCol, nRow, nMask, nStartRow, nEndRow );
|
|
}
|
|
|
|
bool ScDocument::HasAttrib( const ScRange& rRange, HasAttrFlags nMask ) const
|
|
{
|
|
return HasAttrib( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(),
|
|
rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(),
|
|
nMask );
|
|
}
|
|
|
|
void ScDocument::FindMaxRotCol( SCTAB nTab, RowInfo* pRowInfo, SCSIZE nArrCount,
|
|
SCCOL nX1, SCCOL nX2 ) const
|
|
{
|
|
if (HasTable(nTab))
|
|
{
|
|
maTabs[nTab]->FindMaxRotCol(pRowInfo, nArrCount, nX1, nX2);
|
|
return;
|
|
}
|
|
OSL_FAIL("FindMaxRotCol: wrong table");
|
|
}
|
|
|
|
void ScDocument::GetBorderLines( SCCOL nCol, SCROW nRow, SCTAB nTab,
|
|
const SvxBorderLine** ppLeft, const SvxBorderLine** ppTop,
|
|
const SvxBorderLine** ppRight, const SvxBorderLine** ppBottom ) const
|
|
{
|
|
//TODO: consider page limits for printing !!!!!
|
|
|
|
const SvxBoxItem* pThisAttr = GetEffItem( nCol, nRow, nTab, ATTR_BORDER );
|
|
OSL_ENSURE(pThisAttr,"where is the attribute?");
|
|
|
|
const SvxBorderLine* pLeftLine = pThisAttr->GetLeft();
|
|
const SvxBorderLine* pTopLine = pThisAttr->GetTop();
|
|
const SvxBorderLine* pRightLine = pThisAttr->GetRight();
|
|
const SvxBorderLine* pBottomLine = pThisAttr->GetBottom();
|
|
|
|
if ( nCol > 0 )
|
|
{
|
|
const SvxBorderLine* pOther = GetEffItem( o3tl::sanitizing_dec(nCol), nRow, nTab, ATTR_BORDER )->GetRight();
|
|
if ( ScHasPriority( pOther, pLeftLine ) )
|
|
pLeftLine = pOther;
|
|
}
|
|
if ( nRow > 0 )
|
|
{
|
|
const SvxBorderLine* pOther = GetEffItem( nCol, nRow-1, nTab, ATTR_BORDER )->GetBottom();
|
|
if ( ScHasPriority( pOther, pTopLine ) )
|
|
pTopLine = pOther;
|
|
}
|
|
if ( nCol < MaxCol() )
|
|
{
|
|
const SvxBorderLine* pOther = GetEffItem( o3tl::sanitizing_inc(nCol), nRow, nTab, ATTR_BORDER )->GetLeft();
|
|
if ( ScHasPriority( pOther, pRightLine ) )
|
|
pRightLine = pOther;
|
|
}
|
|
if ( nRow < MaxRow() )
|
|
{
|
|
const SvxBorderLine* pOther = GetEffItem( nCol, nRow+1, nTab, ATTR_BORDER )->GetTop();
|
|
if ( ScHasPriority( pOther, pBottomLine ) )
|
|
pBottomLine = pOther;
|
|
}
|
|
|
|
if (ppLeft)
|
|
*ppLeft = pLeftLine;
|
|
if (ppTop)
|
|
*ppTop = pTopLine;
|
|
if (ppRight)
|
|
*ppRight = pRightLine;
|
|
if (ppBottom)
|
|
*ppBottom = pBottomLine;
|
|
}
|
|
|
|
bool ScDocument::IsNotesBlockEmpty(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow,
|
|
SCTAB nTab) const
|
|
{
|
|
if (HasTable(nTab))
|
|
return maTabs[nTab]->IsNotesBlockEmpty(nStartCol, nStartRow, nEndCol, nEndRow);
|
|
OSL_FAIL("wrong table number");
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::IsBlockEmpty(SCCOL nStartCol, SCROW nStartRow,
|
|
SCCOL nEndCol, SCROW nEndRow, SCTAB nTab) const
|
|
{
|
|
if (HasTable(nTab))
|
|
return maTabs[nTab]->IsBlockEmpty(nStartCol, nStartRow, nEndCol, nEndRow);
|
|
OSL_FAIL("wrong table number");
|
|
return false;
|
|
}
|
|
|
|
void ScDocument::LockTable(SCTAB nTab)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->LockTable();
|
|
else
|
|
{
|
|
OSL_FAIL("wrong table number");
|
|
}
|
|
}
|
|
|
|
void ScDocument::UnlockTable(SCTAB nTab)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->UnlockTable();
|
|
else
|
|
{
|
|
OSL_FAIL("wrong table number");
|
|
}
|
|
}
|
|
|
|
bool ScDocument::IsBlockEditable( SCTAB nTab, SCCOL nStartCol, SCROW nStartRow,
|
|
SCCOL nEndCol, SCROW nEndRow,
|
|
bool* pOnlyNotBecauseOfMatrix /* = NULL */,
|
|
bool bNoMatrixAtAll ) const
|
|
{
|
|
// import into read-only document is possible
|
|
if (!bImportingXML && !mbChangeReadOnlyEnabled && mpShell && mpShell->IsReadOnly())
|
|
{
|
|
if ( pOnlyNotBecauseOfMatrix )
|
|
*pOnlyNotBecauseOfMatrix = false;
|
|
return false;
|
|
}
|
|
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->IsBlockEditable(nStartCol, nStartRow, nEndCol, nEndRow,
|
|
pOnlyNotBecauseOfMatrix, bNoMatrixAtAll);
|
|
|
|
OSL_FAIL("wrong table number");
|
|
if ( pOnlyNotBecauseOfMatrix )
|
|
*pOnlyNotBecauseOfMatrix = false;
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::IsSelectionEditable( const ScMarkData& rMark,
|
|
bool* pOnlyNotBecauseOfMatrix /* = NULL */ ) const
|
|
{
|
|
// import into read-only document is possible
|
|
if ( !bImportingXML && !mbChangeReadOnlyEnabled && mpShell && mpShell->IsReadOnly() )
|
|
{
|
|
if ( pOnlyNotBecauseOfMatrix )
|
|
*pOnlyNotBecauseOfMatrix = false;
|
|
return false;
|
|
}
|
|
|
|
const ScRange& aRange = rMark.GetMarkArea();
|
|
|
|
bool bOk = true;
|
|
bool bMatrix = ( pOnlyNotBecauseOfMatrix != nullptr );
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
|
|
if ( maTabs[rTab] )
|
|
{
|
|
if (rMark.IsMarked())
|
|
{
|
|
if ( !maTabs[rTab]->IsBlockEditable( aRange.aStart.Col(),
|
|
aRange.aStart.Row(), aRange.aEnd.Col(),
|
|
aRange.aEnd.Row(), pOnlyNotBecauseOfMatrix ) )
|
|
{
|
|
bOk = false;
|
|
if ( pOnlyNotBecauseOfMatrix )
|
|
bMatrix = *pOnlyNotBecauseOfMatrix;
|
|
}
|
|
}
|
|
if (rMark.IsMultiMarked())
|
|
{
|
|
if ( !maTabs[rTab]->IsSelectionEditable( rMark, pOnlyNotBecauseOfMatrix ) )
|
|
{
|
|
bOk = false;
|
|
if ( pOnlyNotBecauseOfMatrix )
|
|
bMatrix = *pOnlyNotBecauseOfMatrix;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bOk && !bMatrix)
|
|
break;
|
|
}
|
|
|
|
if ( pOnlyNotBecauseOfMatrix )
|
|
*pOnlyNotBecauseOfMatrix = ( !bOk && bMatrix );
|
|
|
|
return bOk;
|
|
}
|
|
|
|
bool ScDocument::HasSelectedBlockMatrixFragment( SCCOL nStartCol, SCROW nStartRow,
|
|
SCCOL nEndCol, SCROW nEndRow,
|
|
const ScMarkData& rMark ) const
|
|
{
|
|
bool bOk = true;
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
|
|
if (maTabs[rTab] && maTabs[rTab]->HasBlockMatrixFragment( nStartCol, nStartRow, nEndCol, nEndRow ))
|
|
bOk = false;
|
|
|
|
if (!bOk)
|
|
break;
|
|
}
|
|
|
|
return !bOk;
|
|
}
|
|
|
|
bool ScDocument::GetMatrixFormulaRange( const ScAddress& rCellPos, ScRange& rMatrix )
|
|
{
|
|
// if rCell is part of a matrix formula, return its complete range
|
|
|
|
ScFormulaCell* pFCell = GetFormulaCell(rCellPos);
|
|
if (!pFCell)
|
|
// not a formula cell. Bail out.
|
|
return false;
|
|
|
|
ScAddress aOrigin = rCellPos;
|
|
if (!pFCell->GetMatrixOrigin(*this, aOrigin))
|
|
// Failed to get the address of the matrix origin.
|
|
return false;
|
|
|
|
if (aOrigin != rCellPos)
|
|
{
|
|
pFCell = GetFormulaCell(aOrigin);
|
|
if (!pFCell)
|
|
// The matrix origin cell is not a formula cell !? Something is up...
|
|
return false;
|
|
}
|
|
|
|
SCCOL nSizeX;
|
|
SCROW nSizeY;
|
|
pFCell->GetMatColsRows(nSizeX, nSizeY);
|
|
if (nSizeX <= 0 || nSizeY <= 0)
|
|
{
|
|
// GetMatrixEdge computes also dimensions of the matrix
|
|
// if not already done (may occur if document is loaded
|
|
// from old file format).
|
|
// Needs an "invalid" initialized address.
|
|
aOrigin.SetInvalid();
|
|
pFCell->GetMatrixEdge(*this, aOrigin);
|
|
pFCell->GetMatColsRows(nSizeX, nSizeY);
|
|
}
|
|
|
|
if (nSizeX <= 0 || nSizeY <= 0)
|
|
// Matrix size is still invalid. Give up.
|
|
return false;
|
|
|
|
ScAddress aEnd( aOrigin.Col() + nSizeX - 1,
|
|
aOrigin.Row() + nSizeY - 1,
|
|
aOrigin.Tab() );
|
|
|
|
rMatrix.aStart = aOrigin;
|
|
rMatrix.aEnd = aEnd;
|
|
|
|
return true;
|
|
}
|
|
|
|
void ScDocument::ExtendOverlapped( SCCOL& rStartCol, SCROW& rStartRow,
|
|
SCCOL nEndCol, SCROW nEndRow, SCTAB nTab ) const
|
|
{
|
|
if ( ValidColRow(rStartCol,rStartRow) && ValidColRow(nEndCol,nEndRow) && ValidTab(nTab) )
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
SCCOL nCol;
|
|
SCCOL nOldCol = rStartCol;
|
|
SCROW nOldRow = rStartRow;
|
|
for (nCol=nOldCol; nCol<=nEndCol; nCol++)
|
|
while (GetAttr(nCol,rStartRow,nTab,ATTR_MERGE_FLAG)->IsVerOverlapped())
|
|
--rStartRow;
|
|
|
|
//TODO: pass on ?
|
|
|
|
const ScAttrArray& pAttrArray = pTable->GetColumnData(nOldCol).AttrArray();
|
|
SCSIZE nIndex;
|
|
if ( pAttrArray.Count() )
|
|
pAttrArray.Search( nOldRow, nIndex );
|
|
else
|
|
nIndex = 0;
|
|
SCROW nAttrPos = nOldRow;
|
|
while (nAttrPos<=nEndRow)
|
|
{
|
|
OSL_ENSURE( nIndex < pAttrArray.Count(), "Wrong index in AttrArray" );
|
|
|
|
bool bHorOverlapped;
|
|
if ( pAttrArray.Count() )
|
|
bHorOverlapped = pAttrArray.mvData[nIndex].getScPatternAttr()->GetItem(ATTR_MERGE_FLAG).IsHorOverlapped();
|
|
else
|
|
bHorOverlapped = getCellAttributeHelper().getDefaultCellAttribute().GetItem(ATTR_MERGE_FLAG).IsHorOverlapped();
|
|
|
|
if ( bHorOverlapped )
|
|
{
|
|
SCROW nEndRowSeg = (pAttrArray.Count()) ? pAttrArray.mvData[nIndex].nEndRow : MaxRow();
|
|
SCROW nLoopEndRow = std::min( nEndRow, nEndRowSeg );
|
|
for (SCROW nAttrRow = nAttrPos; nAttrRow <= nLoopEndRow; nAttrRow++)
|
|
{
|
|
SCCOL nTempCol = nOldCol;
|
|
do
|
|
--nTempCol;
|
|
while (GetAttr(nTempCol,nAttrRow,nTab,ATTR_MERGE_FLAG)->IsHorOverlapped());
|
|
if (nTempCol < rStartCol)
|
|
rStartCol = nTempCol;
|
|
}
|
|
}
|
|
if ( pAttrArray.Count() )
|
|
{
|
|
nAttrPos = pAttrArray.mvData[nIndex].nEndRow + 1;
|
|
++nIndex;
|
|
}
|
|
else
|
|
nAttrPos = MaxRow() + 1;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OSL_FAIL("ExtendOverlapped: invalid range");
|
|
}
|
|
}
|
|
|
|
void ScDocument::ExtendMergeSel( SCCOL nStartCol, SCROW nStartRow,
|
|
SCCOL& rEndCol, SCROW& rEndRow,
|
|
const ScMarkData& rMark, bool bRefresh )
|
|
{
|
|
// use all selected sheets from rMark
|
|
|
|
SCCOL nOldEndCol = rEndCol;
|
|
SCROW nOldEndRow = rEndRow;
|
|
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
|
|
if ( maTabs[rTab] )
|
|
{
|
|
SCCOL nThisEndCol = nOldEndCol;
|
|
SCROW nThisEndRow = nOldEndRow;
|
|
ExtendMerge( nStartCol, nStartRow, nThisEndCol, nThisEndRow, rTab, bRefresh );
|
|
if ( nThisEndCol > rEndCol )
|
|
rEndCol = nThisEndCol;
|
|
if ( nThisEndRow > rEndRow )
|
|
rEndRow = nThisEndRow;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ScDocument::ExtendMerge( SCCOL nStartCol, SCROW nStartRow,
|
|
SCCOL& rEndCol, SCROW& rEndRow,
|
|
SCTAB nTab, bool bRefresh )
|
|
{
|
|
bool bFound = false;
|
|
if ( ValidColRow(nStartCol,nStartRow) && ValidColRow(rEndCol,rEndRow) && ValidTab(nTab) )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
bFound = pTable->ExtendMerge( nStartCol, nStartRow, rEndCol, rEndRow, bRefresh );
|
|
|
|
if (bRefresh)
|
|
RefreshAutoFilter( nStartCol, nStartRow, rEndCol, rEndRow, nTab );
|
|
}
|
|
else
|
|
{
|
|
OSL_FAIL("ExtendMerge: invalid range");
|
|
}
|
|
|
|
return bFound;
|
|
}
|
|
|
|
bool ScDocument::ExtendMerge( ScRange& rRange, bool bRefresh )
|
|
{
|
|
bool bFound = false;
|
|
SCTAB nStartTab = rRange.aStart.Tab();
|
|
SCTAB nEndTab = rRange.aEnd.Tab();
|
|
SCCOL nEndCol = rRange.aEnd.Col();
|
|
SCROW nEndRow = rRange.aEnd.Row();
|
|
|
|
PutInOrder( nStartTab, nEndTab );
|
|
for (SCTAB nTab = nStartTab; nTab <= nEndTab && nTab < GetTableCount(); nTab++ )
|
|
{
|
|
SCCOL nExtendCol = rRange.aEnd.Col();
|
|
SCROW nExtendRow = rRange.aEnd.Row();
|
|
if (ExtendMerge( rRange.aStart.Col(), rRange.aStart.Row(),
|
|
nExtendCol, nExtendRow,
|
|
nTab, bRefresh ) )
|
|
{
|
|
bFound = true;
|
|
if (nExtendCol > nEndCol) nEndCol = nExtendCol;
|
|
if (nExtendRow > nEndRow) nEndRow = nExtendRow;
|
|
}
|
|
}
|
|
|
|
rRange.aEnd.SetCol(nEndCol);
|
|
rRange.aEnd.SetRow(nEndRow);
|
|
|
|
return bFound;
|
|
}
|
|
|
|
void ScDocument::ExtendTotalMerge( ScRange& rRange ) const
|
|
{
|
|
// Extend range to merged cells without including any new non-overlapped cells
|
|
ScRange aExt = rRange;
|
|
// ExtendMerge() is non-const, but called without refresh.
|
|
if (!const_cast<ScDocument*>(this)->ExtendMerge( aExt ))
|
|
return;
|
|
|
|
if ( aExt.aEnd.Row() > rRange.aEnd.Row() )
|
|
{
|
|
ScRange aTest = aExt;
|
|
aTest.aStart.SetRow( rRange.aEnd.Row() + 1 );
|
|
if ( HasAttrib( aTest, HasAttrFlags::NotOverlapped ) )
|
|
aExt.aEnd.SetRow(rRange.aEnd.Row());
|
|
}
|
|
if ( aExt.aEnd.Col() > rRange.aEnd.Col() )
|
|
{
|
|
ScRange aTest = aExt;
|
|
aTest.aStart.SetCol( rRange.aEnd.Col() + 1 );
|
|
if ( HasAttrib( aTest, HasAttrFlags::NotOverlapped ) )
|
|
aExt.aEnd.SetCol(rRange.aEnd.Col());
|
|
}
|
|
|
|
rRange = aExt;
|
|
}
|
|
|
|
void ScDocument::ExtendOverlapped( ScRange& rRange ) const
|
|
{
|
|
SCTAB nStartTab = rRange.aStart.Tab();
|
|
SCTAB nEndTab = rRange.aEnd.Tab();
|
|
SCCOL nStartCol = rRange.aStart.Col();
|
|
SCROW nStartRow = rRange.aStart.Row();
|
|
|
|
PutInOrder( nStartTab, nEndTab );
|
|
for (SCTAB nTab = nStartTab; nTab <= nEndTab && nTab < GetTableCount(); nTab++ )
|
|
{
|
|
SCCOL nExtendCol = rRange.aStart.Col();
|
|
SCROW nExtendRow = rRange.aStart.Row();
|
|
ExtendOverlapped( nExtendCol, nExtendRow,
|
|
rRange.aEnd.Col(), rRange.aEnd.Row(), nTab );
|
|
if (nExtendCol < nStartCol)
|
|
{
|
|
nStartCol = nExtendCol;
|
|
}
|
|
if (nExtendRow < nStartRow)
|
|
{
|
|
nStartRow = nExtendRow;
|
|
}
|
|
}
|
|
|
|
rRange.aStart.SetCol(nStartCol);
|
|
rRange.aStart.SetRow(nStartRow);
|
|
}
|
|
|
|
bool ScDocument::RefreshAutoFilter( SCCOL nStartCol, SCROW nStartRow,
|
|
SCCOL nEndCol, SCROW nEndRow, SCTAB nTab )
|
|
{
|
|
SCTAB nDBTab;
|
|
SCCOL nDBStartCol;
|
|
SCROW nDBStartRow;
|
|
SCCOL nDBEndCol;
|
|
SCROW nDBEndRow;
|
|
|
|
// Delete Autofilter
|
|
|
|
bool bChange = RemoveFlagsTab( nStartCol,nStartRow, nEndCol,nEndRow, nTab, ScMF::Auto );
|
|
|
|
// Set Autofilter
|
|
|
|
const ScDBData* pData = nullptr;
|
|
ScDBCollection::NamedDBs& rDBs = pDBCollection->getNamedDBs();
|
|
for (const auto& rxDB : rDBs)
|
|
{
|
|
if (rxDB->HasAutoFilter())
|
|
{
|
|
rxDB->GetArea(nDBTab, nDBStartCol,nDBStartRow, nDBEndCol,nDBEndRow);
|
|
if ( nDBTab==nTab && nDBStartRow<=nEndRow && nDBEndRow>=nStartRow &&
|
|
nDBStartCol<=nEndCol && nDBEndCol>=nStartCol )
|
|
{
|
|
if (ApplyFlagsTab( nDBStartCol,nDBStartRow, nDBEndCol,nDBStartRow,
|
|
nDBTab, ScMF::Auto ))
|
|
bChange = true;
|
|
}
|
|
}
|
|
}
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pData = pTable->GetAnonymousDBData();
|
|
else
|
|
pData = nullptr;
|
|
if (pData && pData->HasAutoFilter())
|
|
{
|
|
pData->GetArea( nDBTab, nDBStartCol,nDBStartRow, nDBEndCol,nDBEndRow );
|
|
if ( nDBTab==nTab && nDBStartRow<=nEndRow && nDBEndRow>=nStartRow &&
|
|
nDBStartCol<=nEndCol && nDBEndCol>=nStartCol )
|
|
{
|
|
if (ApplyFlagsTab( nDBStartCol,nDBStartRow, nDBEndCol,nDBStartRow,
|
|
nDBTab, ScMF::Auto ))
|
|
bChange = true;
|
|
}
|
|
}
|
|
return bChange;
|
|
}
|
|
|
|
void ScDocument::SkipOverlapped( SCCOL& rCol, SCROW& rRow, SCTAB nTab ) const
|
|
{
|
|
while (IsHorOverlapped(rCol, rRow, nTab))
|
|
--rCol;
|
|
while (IsVerOverlapped(rCol, rRow, nTab))
|
|
--rRow;
|
|
}
|
|
|
|
bool ScDocument::IsHorOverlapped( SCCOL nCol, SCROW nRow, SCTAB nTab ) const
|
|
{
|
|
const ScMergeFlagAttr* pAttr = GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG );
|
|
if (pAttr)
|
|
return pAttr->IsHorOverlapped();
|
|
else
|
|
{
|
|
OSL_FAIL("Overlapped: Attr==0");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool ScDocument::IsVerOverlapped( SCCOL nCol, SCROW nRow, SCTAB nTab, SCROW* nStartRow, SCROW* nEndRow ) const
|
|
{
|
|
SCROW dummy;
|
|
const ScMergeFlagAttr* pAttr = GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG,
|
|
nStartRow ? *nStartRow : dummy, nEndRow ? *nEndRow : dummy );
|
|
if (pAttr)
|
|
return pAttr->IsVerOverlapped();
|
|
else
|
|
{
|
|
OSL_FAIL("Overlapped: Attr==0");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void ScDocument::ApplySelectionFrame( const ScMarkData& rMark,
|
|
const SvxBoxItem& rLineOuter,
|
|
const SvxBoxInfoItem* pLineInner )
|
|
{
|
|
ScRangeList aRangeList;
|
|
rMark.FillRangeListWithMarks( &aRangeList, false );
|
|
size_t nRangeCount = aRangeList.size();
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
|
|
if (maTabs[rTab])
|
|
{
|
|
for ( size_t j=0; j < nRangeCount; j++ )
|
|
{
|
|
const ScRange & rRange = aRangeList[ j ];
|
|
maTabs[rTab]->ApplyBlockFrame( rLineOuter, pLineInner,
|
|
rRange.aStart.Col(), rRange.aStart.Row(),
|
|
rRange.aEnd.Col(), rRange.aEnd.Row() );
|
|
}
|
|
}
|
|
}
|
|
if (!rLineOuter.IsRemoveAdjacentCellBorder())
|
|
return;
|
|
|
|
SvxBoxItem aTmp0(rLineOuter);
|
|
aTmp0.SetLine( nullptr, SvxBoxItemLine::TOP );
|
|
aTmp0.SetLine( nullptr, SvxBoxItemLine::BOTTOM );
|
|
aTmp0.SetLine( nullptr, SvxBoxItemLine::LEFT );
|
|
aTmp0.SetLine( nullptr, SvxBoxItemLine::RIGHT );
|
|
SvxBoxItem aLeft( aTmp0 );
|
|
SvxBoxItem aRight( aTmp0 );
|
|
SvxBoxItem aTop( aTmp0 );
|
|
SvxBoxItem aBottom( aTmp0 );
|
|
|
|
SvxBoxInfoItem aTmp1( *pLineInner );
|
|
aTmp1.SetTable( false );
|
|
aTmp1.SetLine( nullptr, SvxBoxInfoItemLine::HORI );
|
|
aTmp1.SetLine( nullptr, SvxBoxInfoItemLine::VERT );
|
|
aTmp1.SetValid( SvxBoxInfoItemValidFlags::ALL, false );
|
|
aTmp1.SetValid( SvxBoxInfoItemValidFlags::DISTANCE );
|
|
SvxBoxInfoItem aLeftInfo( aTmp1 );
|
|
SvxBoxInfoItem aRightInfo( aTmp1 );
|
|
SvxBoxInfoItem aTopInfo( aTmp1 );
|
|
SvxBoxInfoItem aBottomInfo( aTmp1 );
|
|
|
|
if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::TOP ) && !rLineOuter.GetTop())
|
|
aTopInfo.SetValid( SvxBoxInfoItemValidFlags::BOTTOM );
|
|
|
|
if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::BOTTOM ) && !rLineOuter.GetBottom())
|
|
aBottomInfo.SetValid( SvxBoxInfoItemValidFlags::TOP );
|
|
|
|
if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::LEFT ) && !rLineOuter.GetLeft())
|
|
aLeftInfo.SetValid( SvxBoxInfoItemValidFlags::RIGHT );
|
|
|
|
if (pLineInner->IsValid( SvxBoxInfoItemValidFlags::RIGHT ) && !rLineOuter.GetRight())
|
|
aRightInfo.SetValid( SvxBoxInfoItemValidFlags::LEFT );
|
|
|
|
const ScRangeList& rRangeListTopEnvelope = rMark.GetTopEnvelope();
|
|
const ScRangeList& rRangeListBottomEnvelope = rMark.GetBottomEnvelope();
|
|
const ScRangeList& rRangeListLeftEnvelope = rMark.GetLeftEnvelope();
|
|
const ScRangeList& rRangeListRightEnvelope = rMark.GetRightEnvelope();
|
|
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
|
|
if ( maTabs[rTab] )
|
|
{
|
|
size_t nEnvelopeRangeCount = rRangeListTopEnvelope.size();
|
|
for ( size_t j=0; j < nEnvelopeRangeCount; j++ )
|
|
{
|
|
const ScRange & rRange = rRangeListTopEnvelope[ j ];
|
|
maTabs[rTab]->ApplyBlockFrame( aTop, &aTopInfo,
|
|
rRange.aStart.Col(), rRange.aStart.Row(),
|
|
rRange.aEnd.Col(), rRange.aEnd.Row() );
|
|
}
|
|
nEnvelopeRangeCount = rRangeListBottomEnvelope.size();
|
|
for ( size_t j=0; j < nEnvelopeRangeCount; j++ )
|
|
{
|
|
const ScRange & rRange = rRangeListBottomEnvelope[ j ];
|
|
maTabs[rTab]->ApplyBlockFrame( aBottom, &aBottomInfo,
|
|
rRange.aStart.Col(), rRange.aStart.Row(),
|
|
rRange.aEnd.Col(), rRange.aEnd.Row() );
|
|
}
|
|
nEnvelopeRangeCount = rRangeListLeftEnvelope.size();
|
|
for ( size_t j=0; j < nEnvelopeRangeCount; j++ )
|
|
{
|
|
const ScRange & rRange = rRangeListLeftEnvelope[ j ];
|
|
maTabs[rTab]->ApplyBlockFrame( aLeft, &aLeftInfo,
|
|
rRange.aStart.Col(), rRange.aStart.Row(),
|
|
rRange.aEnd.Col(), rRange.aEnd.Row() );
|
|
}
|
|
nEnvelopeRangeCount = rRangeListRightEnvelope.size();
|
|
for ( size_t j=0; j < nEnvelopeRangeCount; j++ )
|
|
{
|
|
const ScRange & rRange = rRangeListRightEnvelope[ j ];
|
|
maTabs[rTab]->ApplyBlockFrame( aRight, &aRightInfo,
|
|
rRange.aStart.Col(), rRange.aStart.Row(),
|
|
rRange.aEnd.Col(), rRange.aEnd.Row() );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScDocument::ApplyFrameAreaTab(const ScRange& rRange,
|
|
const SvxBoxItem& rLineOuter,
|
|
const SvxBoxInfoItem& rLineInner)
|
|
{
|
|
SCTAB nStartTab = rRange.aStart.Tab();
|
|
SCTAB nEndTab = rRange.aStart.Tab();
|
|
for (SCTAB nTab = nStartTab; nTab <= nEndTab && nTab < GetTableCount(); nTab++)
|
|
if (maTabs[nTab])
|
|
maTabs[nTab]->ApplyBlockFrame(rLineOuter, &rLineInner,
|
|
rRange.aStart.Col(), rRange.aStart.Row(),
|
|
rRange.aEnd.Col(), rRange.aEnd.Row());
|
|
}
|
|
|
|
void ScDocument::ApplySelectionPattern( const ScPatternAttr& rAttr, const ScMarkData& rMark, ScEditDataArray* pDataArray, bool* const pIsChanged )
|
|
{
|
|
const SfxItemSet* pSet = &rAttr.GetItemSet();
|
|
bool bSet = false;
|
|
sal_uInt16 i;
|
|
for (i=ATTR_PATTERN_START; i<=ATTR_PATTERN_END && !bSet; i++)
|
|
if (pSet->GetItemState(i) == SfxItemState::SET)
|
|
bSet = true;
|
|
|
|
if (!bSet)
|
|
return;
|
|
|
|
// ApplySelectionCache needs multi mark
|
|
if ( rMark.IsMarked() && !rMark.IsMultiMarked() )
|
|
{
|
|
const ScRange& aRange = rMark.GetMarkArea();
|
|
ApplyPatternArea( aRange.aStart.Col(), aRange.aStart.Row(),
|
|
aRange.aEnd.Col(), aRange.aEnd.Row(), rMark, rAttr, pDataArray, pIsChanged );
|
|
}
|
|
else
|
|
{
|
|
ScItemPoolCache aCache( getCellAttributeHelper(), *pSet );
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
if (maTabs[rTab])
|
|
maTabs[rTab]->ApplySelectionCache( aCache, rMark, pDataArray, pIsChanged );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScDocument::ChangeSelectionIndent( bool bIncrement, const ScMarkData& rMark )
|
|
{
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
if (maTabs[rTab])
|
|
maTabs[rTab]->ChangeSelectionIndent( bIncrement, rMark );
|
|
}
|
|
}
|
|
|
|
void ScDocument::ClearSelectionItems( const sal_uInt16* pWhich, const ScMarkData& rMark )
|
|
{
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
if (maTabs[rTab])
|
|
maTabs[rTab]->ClearSelectionItems( pWhich, rMark );
|
|
}
|
|
}
|
|
|
|
void ScDocument::DeleteSelection( InsertDeleteFlags nDelFlag, const ScMarkData& rMark, bool bBroadcast )
|
|
{
|
|
sc::AutoCalcSwitch aACSwitch(*this, false);
|
|
|
|
std::vector<ScAddress> aGroupPos;
|
|
// Destroy and reconstruct listeners only if content is affected.
|
|
bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag);
|
|
if (bDelContent)
|
|
{
|
|
// Record the positions of top and/or bottom formula groups that
|
|
// intersect the area borders.
|
|
sc::EndListeningContext aCxt(*this);
|
|
ScRangeList aRangeList;
|
|
rMark.FillRangeListWithMarks( &aRangeList, false);
|
|
for (size_t i = 0; i < aRangeList.size(); ++i)
|
|
{
|
|
const ScRange & rRange = aRangeList[i];
|
|
EndListeningIntersectedGroups( aCxt, rRange, &aGroupPos);
|
|
}
|
|
aCxt.purgeEmptyBroadcasters();
|
|
}
|
|
|
|
SCTAB nMax = GetTableCount();
|
|
for (const auto& rTab : rMark)
|
|
{
|
|
if (rTab >= nMax)
|
|
break;
|
|
if (maTabs[rTab])
|
|
maTabs[rTab]->DeleteSelection(nDelFlag, rMark, bBroadcast);
|
|
}
|
|
|
|
if (!bDelContent)
|
|
return;
|
|
|
|
// Re-start listeners on those top bottom groups that have been split.
|
|
SetNeedsListeningGroups(aGroupPos);
|
|
StartNeededListeners();
|
|
|
|
// If formula groups were split their listeners were destroyed and may
|
|
// need to be notified now that they're restored,
|
|
// ScTable::DeleteSelection() couldn't do that.
|
|
if (aGroupPos.empty())
|
|
return;
|
|
|
|
ScRangeList aRangeList;
|
|
rMark.FillRangeListWithMarks( &aRangeList, false);
|
|
for (size_t i = 0; i < aRangeList.size(); ++i)
|
|
{
|
|
SetDirty( aRangeList[i], true);
|
|
}
|
|
//Notify listeners on top and bottom of the group that has been split
|
|
for (size_t i = 0; i < aGroupPos.size(); ++i) {
|
|
ScFormulaCell *pFormulaCell = GetFormulaCell(aGroupPos[i]);
|
|
if (pFormulaCell)
|
|
pFormulaCell->SetDirty(true);
|
|
}
|
|
}
|
|
|
|
void ScDocument::DeleteSelectionTab(
|
|
SCTAB nTab, InsertDeleteFlags nDelFlag, const ScMarkData& rMark )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
sc::AutoCalcSwitch aACSwitch(*this, false);
|
|
|
|
std::vector<ScAddress> aGroupPos;
|
|
// Destroy and reconstruct listeners only if content is affected.
|
|
bool bDelContent = ((nDelFlag & ~InsertDeleteFlags::CONTENTS) != nDelFlag);
|
|
if (bDelContent)
|
|
{
|
|
// Record the positions of top and/or bottom formula groups that
|
|
// intersect the area borders.
|
|
sc::EndListeningContext aCxt(*this);
|
|
ScRangeList aRangeList;
|
|
rMark.FillRangeListWithMarks( &aRangeList, false);
|
|
for (size_t i = 0; i < aRangeList.size(); ++i)
|
|
{
|
|
const ScRange & rRange = aRangeList[i];
|
|
if (rRange.aStart.Tab() <= nTab && nTab <= rRange.aEnd.Tab())
|
|
{
|
|
ScRange aRange( rRange);
|
|
aRange.aStart.SetTab( nTab);
|
|
aRange.aEnd.SetTab( nTab);
|
|
EndListeningIntersectedGroups( aCxt, aRange, &aGroupPos);
|
|
}
|
|
}
|
|
aCxt.purgeEmptyBroadcasters();
|
|
}
|
|
|
|
pTable->DeleteSelection(nDelFlag, rMark);
|
|
|
|
if (bDelContent)
|
|
{
|
|
// Re-start listeners on those top bottom groups that have been split.
|
|
SetNeedsListeningGroups(aGroupPos);
|
|
StartNeededListeners();
|
|
|
|
// If formula groups were split their listeners were destroyed and may
|
|
// need to be notified now that they're restored,
|
|
// ScTable::DeleteSelection() couldn't do that.
|
|
if (!aGroupPos.empty())
|
|
{
|
|
ScRangeList aRangeList;
|
|
rMark.FillRangeListWithMarks( &aRangeList, false);
|
|
for (size_t i = 0; i < aRangeList.size(); ++i)
|
|
{
|
|
const ScRange & rRange = aRangeList[i];
|
|
if (rRange.aStart.Tab() <= nTab && nTab <= rRange.aEnd.Tab())
|
|
{
|
|
ScRange aRange( rRange);
|
|
aRange.aStart.SetTab( nTab);
|
|
aRange.aEnd.SetTab( nTab);
|
|
SetDirty( aRange, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OSL_FAIL("wrong table");
|
|
}
|
|
}
|
|
|
|
ScDocumentPool* ScDocument::GetPool()
|
|
{
|
|
return mxPoolHelper ? mxPoolHelper->GetDocPool() : nullptr;
|
|
}
|
|
|
|
ScStyleSheetPool* ScDocument::GetStyleSheetPool() const
|
|
{
|
|
return mxPoolHelper ? mxPoolHelper->GetStylePool() : nullptr;
|
|
}
|
|
|
|
bool ScDocument::IsEmptyData(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, SCTAB nTab) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->IsEmptyData(nStartCol, nStartRow, nEndCol, nEndRow);
|
|
return true;
|
|
}
|
|
|
|
SCSIZE ScDocument::GetEmptyLinesInBlock( SCCOL nStartCol, SCROW nStartRow, SCTAB nStartTab,
|
|
SCCOL nEndCol, SCROW nEndRow, SCTAB nEndTab, ScDirection eDir )
|
|
{
|
|
PutInOrder(nStartCol, nEndCol);
|
|
PutInOrder(nStartRow, nEndRow);
|
|
PutInOrder(nStartTab, nEndTab);
|
|
if (ScTable* pTable = FetchTable(nStartTab))
|
|
return pTable->GetEmptyLinesInBlock(nStartCol, nStartRow, nEndCol, nEndRow, eDir);
|
|
return 0;
|
|
}
|
|
|
|
void ScDocument::FindAreaPos( SCCOL& rCol, SCROW& rRow, SCTAB nTab, ScMoveDirection eDirection ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
pTable->FindAreaPos(rCol, rRow, eDirection);
|
|
}
|
|
|
|
void ScDocument::GetNextPos( SCCOL& rCol, SCROW& rRow, SCTAB nTab, SCCOL nMovX, SCROW nMovY,
|
|
bool bMarked, bool bUnprotected, const ScMarkData& rMark, SCCOL nTabStartCol ) const
|
|
{
|
|
OSL_ENSURE( !nMovX || !nMovY, "GetNextPos: only X or Y" );
|
|
|
|
ScMarkData aCopyMark = rMark;
|
|
aCopyMark.SetMarking(false);
|
|
aCopyMark.MarkToMulti();
|
|
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
pTable->GetNextPos(rCol, rRow, nMovX, nMovY, bMarked, bUnprotected, aCopyMark, nTabStartCol);
|
|
}
|
|
|
|
// Data operations
|
|
|
|
sal_uInt64 ScDocument::GetCellCount() const
|
|
{
|
|
sal_uInt64 nCellCount = 0;
|
|
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
nCellCount += a->GetCellCount();
|
|
}
|
|
|
|
return nCellCount;
|
|
}
|
|
|
|
sal_uInt64 ScDocument::GetFormulaGroupCount() const
|
|
{
|
|
sal_uInt64 nFormulaGroupCount = 0;
|
|
|
|
ScFormulaGroupIterator aIter( *const_cast<ScDocument*>(this) );
|
|
for ( sc::FormulaGroupEntry* ptr = aIter.first(); ptr; ptr = aIter.next())
|
|
{
|
|
nFormulaGroupCount++;
|
|
}
|
|
|
|
return nFormulaGroupCount;
|
|
}
|
|
|
|
sal_uInt64 ScDocument::GetCodeCount() const
|
|
{
|
|
sal_uInt64 nCodeCount = 0;
|
|
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (a)
|
|
nCodeCount += a->GetCodeCount();
|
|
}
|
|
|
|
return nCodeCount;
|
|
}
|
|
|
|
void ScDocument::PageStyleModified( SCTAB nTab, const OUString& rNewName )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->PageStyleModified( rNewName );
|
|
}
|
|
|
|
void ScDocument::SetPageStyle( SCTAB nTab, const OUString& rName )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetPageStyle( rName );
|
|
}
|
|
|
|
OUString ScDocument::GetPageStyle( SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetPageStyle();
|
|
return OUString();
|
|
}
|
|
|
|
void ScDocument::SetPageSize( SCTAB nTab, const Size& rSize )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetPageSize( rSize );
|
|
}
|
|
|
|
Size ScDocument::GetPageSize( SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetPageSize();
|
|
|
|
OSL_FAIL("invalid tab");
|
|
return Size();
|
|
}
|
|
|
|
void ScDocument::SetRepeatArea( SCTAB nTab, SCCOL nStartCol, SCCOL nEndCol, SCROW nStartRow, SCROW nEndRow )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetRepeatArea( nStartCol, nEndCol, nStartRow, nEndRow );
|
|
}
|
|
|
|
void ScDocument::InvalidatePageBreaks(SCTAB nTab)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->InvalidatePageBreaks();
|
|
}
|
|
|
|
void ScDocument::UpdatePageBreaks( SCTAB nTab, const ScRange* pUserArea )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->UpdatePageBreaks( pUserArea );
|
|
}
|
|
|
|
void ScDocument::RemoveManualBreaks( SCTAB nTab )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->RemoveManualBreaks();
|
|
}
|
|
|
|
bool ScDocument::HasManualBreaks( SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->HasManualBreaks();
|
|
|
|
OSL_FAIL("invalid tab");
|
|
return false;
|
|
}
|
|
|
|
void ScDocument::GetDocStat( ScDocStat& rDocStat )
|
|
{
|
|
rDocStat.nTableCount = GetTableCount();
|
|
rDocStat.aDocName = aDocName;
|
|
rDocStat.nFormulaCount = GetFormulaGroupCount();
|
|
rDocStat.nCellCount = GetCellCount();
|
|
}
|
|
|
|
bool ScDocument::HasPrintRange()
|
|
{
|
|
bool bResult = false;
|
|
|
|
for (const auto& a : maTabs)
|
|
{
|
|
if (!a)
|
|
continue;
|
|
bResult = a->IsPrintEntireSheet() || (a->GetPrintRangeCount() > 0);
|
|
if (bResult)
|
|
break;
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
bool ScDocument::IsPrintEntireSheet( SCTAB nTab ) const
|
|
{
|
|
const ScTable* pTable = FetchTable(nTab);
|
|
return (pTable && pTable->IsPrintEntireSheet());
|
|
}
|
|
|
|
sal_uInt16 ScDocument::GetPrintRangeCount( SCTAB nTab )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetPrintRangeCount();
|
|
return 0;
|
|
}
|
|
|
|
const ScRange* ScDocument::GetPrintRange( SCTAB nTab, sal_uInt16 nPos )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetPrintRange(nPos);
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
std::optional<ScRange> ScDocument::GetRepeatColRange( SCTAB nTab )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetRepeatColRange();
|
|
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<ScRange> ScDocument::GetRepeatRowRange( SCTAB nTab )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetRepeatRowRange();
|
|
return std::nullopt;
|
|
}
|
|
|
|
void ScDocument::ClearPrintRanges( SCTAB nTab )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->ClearPrintRanges();
|
|
}
|
|
|
|
void ScDocument::ClearPrintNamedRanges( SCTAB nTab )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->ClearPrintNamedRanges();
|
|
}
|
|
|
|
void ScDocument::AddPrintRange( SCTAB nTab, const ScRange& rNew )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->AddPrintRange(rNew);
|
|
}
|
|
|
|
void ScDocument::SetPrintEntireSheet( SCTAB nTab )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetPrintEntireSheet();
|
|
}
|
|
|
|
void ScDocument::SetRepeatColRange( SCTAB nTab, std::optional<ScRange> oNew )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetRepeatColRange(std::move(oNew));
|
|
}
|
|
|
|
void ScDocument::SetRepeatRowRange( SCTAB nTab, std::optional<ScRange> oNew )
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetRepeatRowRange(std::move(oNew));
|
|
}
|
|
|
|
std::unique_ptr<ScPrintRangeSaver> ScDocument::CreatePrintRangeSaver() const
|
|
{
|
|
const SCTAB nCount = GetTableCount();
|
|
std::unique_ptr<ScPrintRangeSaver> pNew(new ScPrintRangeSaver( nCount ));
|
|
for (SCTAB i=0; i<nCount; i++)
|
|
if (maTabs[i])
|
|
maTabs[i]->FillPrintSaver( pNew->GetTabData(i) );
|
|
return pNew;
|
|
}
|
|
|
|
void ScDocument::RestorePrintRanges( const ScPrintRangeSaver& rSaver )
|
|
{
|
|
const SCTAB nCount = rSaver.GetTabCount();
|
|
const SCTAB maxIndex = std::min(nCount, GetTableCount());
|
|
for (SCTAB i=0; i<maxIndex; i++)
|
|
if (maTabs[i])
|
|
maTabs[i]->RestorePrintRanges( rSaver.GetTabData(i) );
|
|
}
|
|
|
|
bool ScDocument::NeedPageResetAfterTab( SCTAB nTab ) const
|
|
{
|
|
// The page number count restarts at a sheet, if another template is set at
|
|
// the preceding one (only compare names) and if a pagenumber is specified (not 0)
|
|
|
|
if (nTab + 1 < GetTableCount() && maTabs[nTab] && maTabs[nTab+1])
|
|
{
|
|
const OUString & rNew = maTabs[nTab+1]->GetPageStyle();
|
|
if ( rNew != maTabs[nTab]->GetPageStyle() )
|
|
{
|
|
SfxStyleSheetBase* pStyle = mxPoolHelper->GetStylePool()->Find( rNew, SfxStyleFamily::Page );
|
|
if ( pStyle )
|
|
{
|
|
const SfxItemSet& rSet = pStyle->GetItemSet();
|
|
sal_uInt16 nFirst = rSet.Get(ATTR_PAGE_FIRSTPAGENO).GetValue();
|
|
if ( nFirst != 0 )
|
|
return true; // Specify page number in new template
|
|
}
|
|
}
|
|
}
|
|
|
|
return false; // otherwise not
|
|
}
|
|
|
|
ScUndoManager* ScDocument::GetUndoManager()
|
|
{
|
|
if (!mpUndoManager)
|
|
{
|
|
// to support enhanced text edit for draw objects, use an SdrUndoManager
|
|
ScMutationGuard aGuard(*this, ScMutationGuardFlags::CORE);
|
|
|
|
ScUndoManager* pUndoManager = new ScUndoManager;
|
|
pUndoManager->SetDocShell(GetDocumentShell());
|
|
mpUndoManager = pUndoManager;
|
|
}
|
|
|
|
return mpUndoManager;
|
|
}
|
|
|
|
ScRowBreakIterator* ScDocument::GetRowBreakIterator(SCTAB nTab) const
|
|
{
|
|
if (HasTable(nTab))
|
|
return new ScRowBreakIterator(maTabs[nTab]->maRowPageBreaks);
|
|
return nullptr;
|
|
}
|
|
|
|
void ScDocument::AddSubTotalCell(ScFormulaCell* pCell)
|
|
{
|
|
maSubTotalCells.insert(pCell);
|
|
}
|
|
|
|
void ScDocument::RemoveSubTotalCell(ScFormulaCell* pCell)
|
|
{
|
|
maSubTotalCells.erase(pCell);
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool lcl_hasDirtyRange(const ScDocument& rDoc, ScFormulaCell* pCell, const ScRange& rDirtyRange)
|
|
{
|
|
ScDetectiveRefIter aRefIter(rDoc, pCell);
|
|
ScRange aRange;
|
|
while (aRefIter.GetNextRef(aRange))
|
|
{
|
|
if (aRange.Intersects(rDirtyRange))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
}
|
|
|
|
void ScDocument::SetSubTotalCellsDirty(const ScRange& rDirtyRange)
|
|
{
|
|
// to update the list by skipping cells that no longer contain subtotal function.
|
|
set<ScFormulaCell*> aNewSet;
|
|
|
|
bool bOldRecalc = GetAutoCalc();
|
|
SetAutoCalc(false);
|
|
for (ScFormulaCell* pCell : maSubTotalCells)
|
|
{
|
|
if (pCell->IsSubTotal())
|
|
{
|
|
aNewSet.insert(pCell);
|
|
if (lcl_hasDirtyRange(*this, pCell, rDirtyRange))
|
|
pCell->SetDirty();
|
|
}
|
|
}
|
|
|
|
SetAutoCalc(bOldRecalc);
|
|
maSubTotalCells.swap(aNewSet); // update the list.
|
|
}
|
|
|
|
sal_uInt16 ScDocument::GetTextWidth( const ScAddress& rPos ) const
|
|
{
|
|
SCTAB nTab = rPos.Tab();
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetTextWidth(rPos.Col(), rPos.Row());
|
|
return 0;
|
|
}
|
|
|
|
SvtScriptType ScDocument::GetScriptType( const ScAddress& rPos ) const
|
|
{
|
|
SCTAB nTab = rPos.Tab();
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetScriptType(rPos.Col(), rPos.Row());
|
|
return SvtScriptType::NONE;
|
|
}
|
|
|
|
void ScDocument::SetScriptType( const ScAddress& rPos, SvtScriptType nType )
|
|
{
|
|
SCTAB nTab = rPos.Tab();
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->SetScriptType(rPos.Col(), rPos.Row(), nType);
|
|
}
|
|
|
|
void ScDocument::EnableUndo( bool bVal )
|
|
{
|
|
// The undo manager increases lock count every time undo is disabled.
|
|
// Because of this, we shouldn't disable undo unless it's currently
|
|
// enabled, or else re-enabling it may not actually re-enable undo unless
|
|
// the lock count becomes zero.
|
|
|
|
if (bVal != GetUndoManager()->IsUndoEnabled())
|
|
{
|
|
GetUndoManager()->EnableUndo(bVal);
|
|
if( mpDrawLayer ) mpDrawLayer->EnableUndo(bVal);
|
|
}
|
|
|
|
mbUndoEnabled = bVal;
|
|
}
|
|
|
|
void ScDocument::EnableUserInteraction( bool bVal )
|
|
{
|
|
mbUserInteractionEnabled = bVal;
|
|
}
|
|
|
|
bool ScDocument::IsInVBAMode() const
|
|
{
|
|
if (!mpShell)
|
|
return false;
|
|
|
|
try
|
|
{
|
|
uno::Reference<script::vba::XVBACompatibility> xVBA(
|
|
mpShell->GetBasicContainer(), uno::UNO_QUERY);
|
|
|
|
return xVBA.is() && xVBA->getVBACompatibilityMode();
|
|
}
|
|
catch (const lang::NotInitializedException&) {}
|
|
|
|
return false;
|
|
}
|
|
|
|
// Sparklines
|
|
std::shared_ptr<sc::Sparkline> ScDocument::GetSparkline(ScAddress const& rPosition)
|
|
{
|
|
SCTAB nTab = rPosition.Tab();
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetSparkline(rPosition.Col(), rPosition.Row());
|
|
return std::shared_ptr<sc::Sparkline>();
|
|
}
|
|
|
|
bool ScDocument::HasSparkline(ScAddress const & rPosition)
|
|
{
|
|
return bool(GetSparkline(rPosition));
|
|
}
|
|
|
|
sc::Sparkline* ScDocument::CreateSparkline(ScAddress const& rPosition, std::shared_ptr<sc::SparklineGroup> const& pSparklineGroup)
|
|
{
|
|
SCTAB nTab = rPosition.Tab();
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->CreateSparkline(rPosition.Col(), rPosition.Row(), pSparklineGroup);
|
|
return nullptr;
|
|
}
|
|
|
|
bool ScDocument::DeleteSparkline(ScAddress const & rPosition)
|
|
{
|
|
SCTAB nTab = rPosition.Tab();
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->DeleteSparkline(rPosition.Col(), rPosition.Row());
|
|
return false;
|
|
}
|
|
|
|
sc::SparklineList* ScDocument::GetSparklineList(SCTAB nTab)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return &pTable->GetSparklineList();
|
|
return nullptr;
|
|
}
|
|
|
|
bool ScDocument::HasOneSparklineGroup(ScRange const& rRange)
|
|
{
|
|
std::shared_ptr<sc::SparklineGroup> pSparklineGroup;
|
|
return GetSparklineGroupInRange(rRange, pSparklineGroup);
|
|
}
|
|
|
|
bool ScDocument::GetSparklineGroupInRange(ScRange const& rRange, std::shared_ptr<sc::SparklineGroup>& rGroup)
|
|
{
|
|
std::shared_ptr<sc::SparklineGroup> pFoundGroup;
|
|
SCTAB nTab = rRange.aStart.Tab();
|
|
|
|
for (SCCOL nX = rRange.aStart.Col(); nX <= rRange.aEnd.Col(); nX++)
|
|
{
|
|
for (SCROW nY = rRange.aStart.Row(); nY <= rRange.aEnd.Row(); nY++)
|
|
{
|
|
auto pSparkline = GetSparkline(ScAddress(nX, nY, nTab));
|
|
if (!pSparkline)
|
|
{
|
|
return false;
|
|
}
|
|
else if (!pFoundGroup)
|
|
{
|
|
pFoundGroup = pSparkline->getSparklineGroup();
|
|
}
|
|
else if (pFoundGroup != pSparkline->getSparklineGroup())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
rGroup = std::move(pFoundGroup);
|
|
return true;
|
|
}
|
|
|
|
std::shared_ptr<sc::SparklineGroup> ScDocument::SearchSparklineGroup(tools::Guid const& rGuid)
|
|
{
|
|
for (auto const& rTable : maTabs)
|
|
{
|
|
if (!rTable)
|
|
continue;
|
|
|
|
auto& rSparklineList = rTable->GetSparklineList();
|
|
|
|
for (auto const& pSparklineGroup : rSparklineList.getSparklineGroups())
|
|
{
|
|
if (pSparklineGroup->getID() == rGuid)
|
|
return pSparklineGroup;
|
|
}
|
|
}
|
|
|
|
return std::shared_ptr<sc::SparklineGroup>();
|
|
}
|
|
|
|
// Notes
|
|
|
|
ScPostIt* ScDocument::GetNote(const ScAddress& rPos)
|
|
{
|
|
return GetNote(rPos.Col(), rPos.Row(), rPos.Tab());
|
|
}
|
|
|
|
ScPostIt* ScDocument::GetNote(SCCOL nCol, SCROW nRow, SCTAB nTab)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetNote(nCol, nRow);
|
|
return nullptr;
|
|
}
|
|
|
|
void ScDocument::SetNote(const ScAddress& rPos, std::unique_ptr<ScPostIt> pNote)
|
|
{
|
|
return SetNote(rPos.Col(), rPos.Row(), rPos.Tab(), std::move(pNote));
|
|
}
|
|
|
|
void ScDocument::SetNote(SCCOL nCol, SCROW nRow, SCTAB nTab, std::unique_ptr<ScPostIt> pNote)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
pTable->SetNote(nCol, nRow, std::move(pNote));
|
|
|
|
if (ScDocShell* pDocSh = GetDocumentShell())
|
|
{
|
|
HelperNotifyChanges::NotifyIfChangesListeners(
|
|
*pDocSh, ScRange(nCol, nRow, nTab), u"note"_ustr);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool ScDocument::HasNote(const ScAddress& rPos) const
|
|
{
|
|
return HasNote(rPos.Col(), rPos.Row(), rPos.Tab());
|
|
}
|
|
|
|
bool ScDocument::HasNote(SCCOL nCol, SCROW nRow, SCTAB nTab) const
|
|
{
|
|
if (!ValidColRow(nCol, nRow))
|
|
return false;
|
|
|
|
const ScTable* pTab = FetchTable(nTab);
|
|
if (!pTab)
|
|
return false;
|
|
|
|
if (nCol >= pTab->GetAllocatedColumnsCount())
|
|
return false;
|
|
|
|
const ScPostIt* pNote = pTab->aCol[nCol].GetCellNote(nRow);
|
|
return pNote != nullptr;
|
|
}
|
|
|
|
bool ScDocument::HasNote(SCTAB nTab, SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
nStartCol = pTable->ClampToAllocatedColumns(nStartCol);
|
|
nEndCol = pTable->ClampToAllocatedColumns(nEndCol);
|
|
for (SCCOL nCol = nStartCol; nCol < nEndCol; ++nCol)
|
|
if (pTable->aCol[nCol].HasCellNote(nStartRow, nEndRow))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::HasColNotes(SCCOL nCol, SCTAB nTab) const
|
|
{
|
|
if (!ValidCol(nCol))
|
|
return false;
|
|
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
if (nCol >= pTable->GetAllocatedColumnsCount())
|
|
return false;
|
|
|
|
return pTable->aCol[nCol].HasCellNotes();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::HasTabNotes(SCTAB nTab) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
{
|
|
for (SCCOL nCol=0, nColSize = pTable->aCol.size(); nCol < nColSize; ++nCol)
|
|
if ( HasColNotes(nCol, nTab) )
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ScDocument::HasNotes() const
|
|
{
|
|
for (SCTAB i = 0; i <= MAXTAB; ++i)
|
|
{
|
|
if (HasTabNotes(i))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
std::unique_ptr<ScPostIt> ScDocument::ReleaseNote(const ScAddress& rPos)
|
|
{
|
|
if (ScTable* pTable = FetchTable(rPos.Tab()))
|
|
return pTable->ReleaseNote(rPos.Col(), rPos.Row());
|
|
return nullptr;
|
|
}
|
|
|
|
ScPostIt* ScDocument::GetOrCreateNote(const ScAddress& rPos)
|
|
{
|
|
if (HasNote(rPos))
|
|
return GetNote(rPos);
|
|
else
|
|
return CreateNote(rPos);
|
|
}
|
|
|
|
ScPostIt* ScDocument::CreateNote(const ScAddress& rPos)
|
|
{
|
|
ScPostIt* pPostIt = new ScPostIt(*this, rPos);
|
|
SetNote(rPos, std::unique_ptr<ScPostIt>(pPostIt));
|
|
return pPostIt;
|
|
}
|
|
|
|
size_t ScDocument::GetNoteCount( SCTAB nTab, SCCOL nCol ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetNoteCount(nCol);
|
|
return 0;
|
|
}
|
|
|
|
void ScDocument::CreateAllNoteCaptions()
|
|
{
|
|
for (const auto& pTable : maTabs)
|
|
{
|
|
if (pTable)
|
|
pTable->CreateAllNoteCaptions();
|
|
}
|
|
}
|
|
|
|
void ScDocument::ForgetNoteCaptions( const ScRangeList& rRanges, bool bPreserveData )
|
|
{
|
|
for (size_t i = 0, n = rRanges.size(); i < n; ++i)
|
|
{
|
|
const ScRange & rRange = rRanges[i];
|
|
const ScAddress& s = rRange.aStart;
|
|
const ScAddress& e = rRange.aEnd;
|
|
for (SCTAB nTab = s.Tab(); nTab <= e.Tab(); ++nTab)
|
|
{
|
|
if (ScTable* pTable = FetchTable(nTab))
|
|
pTable->ForgetNoteCaptions(s.Col(), s.Row(), e.Col(), e.Row(), bPreserveData);
|
|
}
|
|
}
|
|
}
|
|
|
|
CommentCaptionState ScDocument::GetAllNoteCaptionsState( const ScRangeList& rRanges )
|
|
{
|
|
CommentCaptionState aTmpState = CommentCaptionState::ALLHIDDEN;
|
|
CommentCaptionState aState = CommentCaptionState::ALLHIDDEN;
|
|
bool bFirstControl = true;
|
|
std::vector<sc::NoteEntry> aNotes;
|
|
|
|
for (size_t i = 0, n = rRanges.size(); i < n; ++i)
|
|
{
|
|
const ScRange & rRange = rRanges[i];
|
|
|
|
for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab )
|
|
{
|
|
aState = maTabs[nTab]->GetAllNoteCaptionsState( rRange, aNotes );
|
|
|
|
if (aState == CommentCaptionState::MIXED)
|
|
return aState;
|
|
|
|
if (bFirstControl) // it is possible that a range is ALLSHOWN, another range is ALLHIDDEN,
|
|
{ // we have to detect that situation as mixed.
|
|
aTmpState = aState;
|
|
bFirstControl = false;
|
|
}
|
|
else if(aTmpState != aState)
|
|
{
|
|
aState = CommentCaptionState::MIXED;
|
|
return aState;
|
|
}
|
|
}
|
|
}
|
|
return aState;
|
|
}
|
|
|
|
ScAddress ScDocument::GetNotePosition( size_t nIndex ) const
|
|
{
|
|
for (size_t nTab = 0; nTab < maTabs.size(); ++nTab)
|
|
{
|
|
for (SCCOL nCol : GetAllocatedColumnsRange(nTab, 0, MaxCol()))
|
|
{
|
|
size_t nColNoteCount = GetNoteCount(nTab, nCol);
|
|
if (!nColNoteCount)
|
|
continue;
|
|
|
|
if (nIndex >= nColNoteCount)
|
|
{
|
|
nIndex -= nColNoteCount;
|
|
continue;
|
|
}
|
|
|
|
SCROW nRow = GetNotePosition(nTab, nCol, nIndex);
|
|
if (nRow >= 0)
|
|
return ScAddress(nCol, nRow, nTab);
|
|
|
|
OSL_FAIL("note not found");
|
|
return ScAddress::INITIALIZE_INVALID;
|
|
}
|
|
}
|
|
|
|
OSL_FAIL("note not found");
|
|
return ScAddress::INITIALIZE_INVALID;
|
|
}
|
|
|
|
ScAddress ScDocument::GetNotePosition( size_t nIndex, SCTAB nTab ) const
|
|
{
|
|
for (SCCOL nCol : GetAllocatedColumnsRange(nTab, 0, MaxCol()))
|
|
{
|
|
size_t nColNoteCount = GetNoteCount(nTab, nCol);
|
|
if (!nColNoteCount)
|
|
continue;
|
|
|
|
if (nIndex >= nColNoteCount)
|
|
{
|
|
nIndex -= nColNoteCount;
|
|
continue;
|
|
}
|
|
|
|
SCROW nRow = GetNotePosition(nTab, nCol, nIndex);
|
|
if (nRow >= 0)
|
|
return ScAddress(nCol, nRow, nTab);
|
|
|
|
OSL_FAIL("note not found");
|
|
return ScAddress::INITIALIZE_INVALID;
|
|
}
|
|
|
|
OSL_FAIL("note not found");
|
|
return ScAddress::INITIALIZE_INVALID;
|
|
}
|
|
|
|
SCROW ScDocument::GetNotePosition( SCTAB nTab, SCCOL nCol, size_t nIndex ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
return pTable->GetNotePosition(nCol, nIndex);
|
|
return -1;
|
|
}
|
|
|
|
void ScDocument::GetAllNoteEntries( std::vector<sc::NoteEntry>& rNotes ) const
|
|
{
|
|
for (const auto & pTable : maTabs)
|
|
{
|
|
if (pTable)
|
|
pTable->GetAllNoteEntries(rNotes);
|
|
}
|
|
}
|
|
|
|
void ScDocument::GetAllNoteEntries( SCTAB nTab, std::vector<sc::NoteEntry>& rNotes ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
pTable->GetAllNoteEntries(rNotes);
|
|
}
|
|
|
|
void ScDocument::GetNotesInRange( const ScRangeList& rRangeList, std::vector<sc::NoteEntry>& rNotes ) const
|
|
{
|
|
for( size_t i = 0; i < rRangeList.size(); ++i)
|
|
{
|
|
const ScRange & rRange = rRangeList[i];
|
|
for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab )
|
|
{
|
|
if (!maTabs[nTab])
|
|
continue;
|
|
maTabs[nTab]->GetNotesInRange( rRange, rNotes );
|
|
}
|
|
}
|
|
}
|
|
|
|
void ScDocument::GetUnprotectedCells( ScRangeList& rRangeList, SCTAB nTab ) const
|
|
{
|
|
if (const ScTable* pTable = FetchTable(nTab))
|
|
pTable->GetUnprotectedCells(rRangeList);
|
|
}
|
|
|
|
bool ScDocument::ContainsNotesInRange( const ScRangeList& rRangeList ) const
|
|
{
|
|
for( size_t i = 0; i < rRangeList.size(); ++i)
|
|
{
|
|
const ScRange & rRange = rRangeList[i];
|
|
for( SCTAB nTab = rRange.aStart.Tab(); nTab <= rRange.aEnd.Tab(); ++nTab )
|
|
{
|
|
if (!maTabs[nTab])
|
|
continue;
|
|
bool bContainsNote = maTabs[nTab]->ContainsNotesInRange( rRange );
|
|
if(bContainsNote)
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void ScDocument::SetAutoNameCache( std::unique_ptr<ScAutoNameCache> pCache )
|
|
{
|
|
pAutoNameCache = std::move(pCache);
|
|
}
|
|
|
|
thread_local ScDocumentThreadSpecific ScDocument::maThreadSpecific;
|
|
|
|
ScRecursionHelper& ScDocument::GetRecursionHelper()
|
|
{
|
|
if (!IsThreadedGroupCalcInProgress())
|
|
{
|
|
if (!maNonThreaded.xRecursionHelper)
|
|
maNonThreaded.xRecursionHelper = std::make_unique<ScRecursionHelper>();
|
|
return *maNonThreaded.xRecursionHelper;
|
|
}
|
|
else
|
|
{
|
|
if (!maThreadSpecific.xRecursionHelper)
|
|
maThreadSpecific.xRecursionHelper = std::make_unique<ScRecursionHelper>();
|
|
return *maThreadSpecific.xRecursionHelper;
|
|
}
|
|
}
|
|
|
|
void ScDocument::SetupContextFromNonThreadedContext(ScInterpreterContext& /*threadedContext*/, int /*threadNumber*/)
|
|
{
|
|
(void)this;
|
|
// lookup cache is now only in pooled ScInterpreterContext's
|
|
}
|
|
|
|
void ScDocument::MergeContextBackIntoNonThreadedContext(ScInterpreterContext& threadedContext, int /*threadNumber*/)
|
|
{
|
|
// Move data from a context used by a calculation thread to the main thread's context.
|
|
// Called from the main thread after the calculation thread has already finished.
|
|
assert(!IsThreadedGroupCalcInProgress());
|
|
maInterpreterContext.maDelayedSetNumberFormat.insert(
|
|
maInterpreterContext.maDelayedSetNumberFormat.end(),
|
|
std::make_move_iterator(threadedContext.maDelayedSetNumberFormat.begin()),
|
|
std::make_move_iterator(threadedContext.maDelayedSetNumberFormat.end()));
|
|
// lookup cache is now only in pooled ScInterpreterContext's
|
|
threadedContext.MergeDefaultFormatKeys(*GetFormatTable());
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|