diff options
Diffstat (limited to 'sc/source/ui/docshell')
27 files changed, 28919 insertions, 0 deletions
diff --git a/sc/source/ui/docshell/arealink.cxx b/sc/source/ui/docshell/arealink.cxx new file mode 100644 index 000000000..8d3a89a16 --- /dev/null +++ b/sc/source/ui/docshell/arealink.cxx @@ -0,0 +1,498 @@ +/* -*- 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 <sfx2/fcontnr.hxx> +#include <sfx2/linkmgr.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <unotools/charclass.hxx> +#include <osl/diagnose.h> + +#include <arealink.hxx> + +#include <tablink.hxx> +#include <document.hxx> +#include <docsh.hxx> +#include <rangenam.hxx> +#include <dbdata.hxx> +#include <undoblk.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <markdata.hxx> +#include <hints.hxx> +#include <filter.hxx> + +#include <attrib.hxx> +#include <patattr.hxx> +#include <docpool.hxx> + +#include <scabstdlg.hxx> +#include <clipparam.hxx> + + +ScAreaLink::ScAreaLink( SfxObjectShell* pShell, const OUString& rFile, + const OUString& rFilter, const OUString& rOpt, + const OUString& rArea, const ScRange& rDest, + sal_Int32 nRefreshDelaySeconds ) : + ::sfx2::SvBaseLink(SfxLinkUpdateMode::ONCALL,SotClipboardFormatId::SIMPLE_FILE), + ScRefreshTimer ( nRefreshDelaySeconds ), + m_pDocSh(static_cast<ScDocShell*>(pShell)), + aFileName (rFile), + aFilterName (rFilter), + aOptions (rOpt), + aSourceArea (rArea), + aDestArea (rDest), + bAddUndo (true), + bInCreate (false), + bDoInsert (true) +{ + OSL_ENSURE(dynamic_cast< const ScDocShell *>( pShell ) != nullptr, "ScAreaLink with wrong ObjectShell"); + SetRefreshHandler( LINK( this, ScAreaLink, RefreshHdl ) ); + SetRefreshControl( &m_pDocSh->GetDocument().GetRefreshTimerControlAddress() ); +} + +ScAreaLink::~ScAreaLink() +{ + StopRefreshTimer(); +} + +void ScAreaLink::Edit(weld::Window* pParent, const Link<SvBaseLink&,void>& /* rEndEditHdl */ ) +{ + // use own dialog instead of SvBaseLink::Edit... + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr<AbstractScLinkedAreaDlg> pDlg(pFact->CreateScLinkedAreaDlg(pParent)); + pDlg->InitFromOldLink( aFileName, aFilterName, aOptions, aSourceArea, GetRefreshDelaySeconds() ); + if ( pDlg->Execute() == RET_OK ) + { + aOptions = pDlg->GetOptions(); + Refresh( pDlg->GetURL(), pDlg->GetFilter(), + pDlg->GetSource(), pDlg->GetRefreshDelaySeconds() ); + + // copy source data from members (set in Refresh) into link name for dialog + OUString aNewLinkName; + sfx2::MakeLnkName( aNewLinkName, nullptr, aFileName, aSourceArea, &aFilterName ); + SetName( aNewLinkName ); + } +} + +::sfx2::SvBaseLink::UpdateResult ScAreaLink::DataChanged( + const OUString&, const css::uno::Any& ) +{ + // Do not do anything at bInCreate so that update can be called to set + // the status in the LinkManager without changing the data in the document + + if (bInCreate) + return SUCCESS; + + sfx2::LinkManager* pLinkManager=m_pDocSh->GetDocument().GetLinkManager(); + if (pLinkManager!=nullptr) + { + OUString aFile, aArea, aFilter; + sfx2::LinkManager::GetDisplayNames(this, nullptr, &aFile, &aArea, &aFilter); + + // the file dialog returns the filter name with the application prefix + // -> remove prefix + ScDocumentLoader::RemoveAppPrefix( aFilter ); + + // dialog doesn't set area, so keep old one + if (aArea.isEmpty()) + { + aArea = aSourceArea; + + // adjust in dialog: + OUString aNewLinkName; + OUString aTmp = aFilter; + sfx2::MakeLnkName(aNewLinkName, nullptr, aFile, aArea, &aTmp); + aFilter = aTmp; + SetName( aNewLinkName ); + } + + tools::SvRef<sfx2::SvBaseLink> const xThis(this); // keep yourself alive + Refresh( aFile, aFilter, aArea, GetRefreshDelaySeconds() ); + } + + return SUCCESS; +} + +void ScAreaLink::Closed() +{ + // delete link: Undo + + ScDocument& rDoc = m_pDocSh->GetDocument(); + bool bUndo (rDoc.IsUndoEnabled()); + if (bAddUndo && bUndo) + { + m_pDocSh->GetUndoManager()->AddUndoAction( std::make_unique<ScUndoRemoveAreaLink>( m_pDocSh, + aFileName, aFilterName, aOptions, + aSourceArea, aDestArea, GetRefreshDelaySeconds() ) ); + + bAddUndo = false; // only once + } + + SCTAB nDestTab = aDestArea.aStart.Tab(); + rDoc.SetStreamValid(nDestTab, false); + + SvBaseLink::Closed(); +} + +void ScAreaLink::SetDestArea(const ScRange& rNew) +{ + aDestArea = rNew; // for Undo +} + +void ScAreaLink::SetSource(const OUString& rDoc, const OUString& rFlt, const OUString& rOpt, + const OUString& rArea) +{ + aFileName = rDoc; + aFilterName = rFlt; + aOptions = rOpt; + aSourceArea = rArea; + + // also update link name for dialog + OUString aNewLinkName; + sfx2::MakeLnkName( aNewLinkName, nullptr, aFileName, aSourceArea, &aFilterName ); + SetName( aNewLinkName ); +} + +bool ScAreaLink::IsEqual( std::u16string_view rFile, std::u16string_view rFilter, std::u16string_view rOpt, + std::u16string_view rSource, const ScRange& rDest ) const +{ + return aFileName == rFile && aFilterName == rFilter && aOptions == rOpt && + aSourceArea == rSource && aDestArea.aStart == rDest.aStart; +} + +// find a range with name >rAreaName< in >rSrcDoc<, return it in >rRange< +bool ScAreaLink::FindExtRange( ScRange& rRange, const ScDocument& rSrcDoc, const OUString& rAreaName ) +{ + bool bFound = false; + OUString aUpperName = ScGlobal::getCharClass().uppercase(rAreaName); + ScRangeName* pNames = rSrcDoc.GetRangeName(); + if (pNames) // named ranges + { + const ScRangeData* p = pNames->findByUpperName(aUpperName); + if (p && p->IsValidReference(rRange)) + bFound = true; + } + if (!bFound) // database ranges + { + ScDBCollection* pDBColl = rSrcDoc.GetDBCollection(); + if (pDBColl) + { + const ScDBData* pDB = pDBColl->getNamedDBs().findByUpperName(aUpperName); + if (pDB) + { + SCTAB nTab; + SCCOL nCol1, nCol2; + SCROW nRow1, nRow2; + pDB->GetArea(nTab,nCol1,nRow1,nCol2,nRow2); + rRange = ScRange( nCol1,nRow1,nTab, nCol2,nRow2,nTab ); + bFound = true; + } + } + } + if (!bFound) // direct reference (range or cell) + { + ScAddress::Details aDetails(rSrcDoc.GetAddressConvention(), 0, 0); + if ( rRange.ParseAny( rAreaName, rSrcDoc, aDetails ) & ScRefFlags::VALID ) + bFound = true; + } + return bFound; +} + +// execute: + +bool ScAreaLink::Refresh( const OUString& rNewFile, const OUString& rNewFilter, + const OUString& rNewArea, sal_Int32 nNewRefreshDelaySeconds ) +{ + // load document - like TabLink + + if (rNewFile.isEmpty() || rNewFilter.isEmpty()) + return false; + + if (!m_pDocSh->GetEmbeddedObjectContainer().getUserAllowsLinkUpdate()) + return false; + + OUString aNewUrl( ScGlobal::GetAbsDocName( rNewFile, m_pDocSh ) ); + bool bNewUrlName = (aNewUrl != aFileName); + + std::shared_ptr<const SfxFilter> pFilter = m_pDocSh->GetFactory().GetFilterContainer()->GetFilter4FilterName(rNewFilter); + if (!pFilter) + return false; + + ScDocument& rDoc = m_pDocSh->GetDocument(); + + bool bUndo (rDoc.IsUndoEnabled()); + rDoc.SetInLinkUpdate( true ); + + // if new filter was selected, forget options + if ( rNewFilter != aFilterName ) + aOptions.clear(); + + SfxMedium* pMed = ScDocumentLoader::CreateMedium( aNewUrl, pFilter, aOptions); + + // aRef->DoClose() will be closed explicitly, but it is still more safe to use SfxObjectShellLock here + ScDocShell* pSrcShell = new ScDocShell(SfxModelFlags::EMBEDDED_OBJECT | SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS); + SfxObjectShellLock aRef = pSrcShell; + pSrcShell->DoLoad(pMed); + + ScDocument& rSrcDoc = pSrcShell->GetDocument(); + + // options could have been set + OUString aNewOpt = ScDocumentLoader::GetOptions(*pMed); + if (aNewOpt.isEmpty()) + aNewOpt = aOptions; + + // correct source range name list for web query import + OUString aTempArea; + + if( rNewFilter == ScDocShell::GetWebQueryFilterName() ) + aTempArea = ScFormatFilter::Get().GetHTMLRangeNameList( rSrcDoc, rNewArea ); + else + aTempArea = rNewArea; + + // find total size of source area + SCCOL nWidth = 0; + SCROW nHeight = 0; + ScRangeList aSourceRanges; + + if (rNewFilter == SC_TEXT_CSV_FILTER_NAME && aTempArea == "CSV_all") + { + // The dummy All range. All data, including top/left empty + // rows/columns. + aTempArea.clear(); + SCCOL nEndCol = 0; + SCROW nEndRow = 0; + if (rSrcDoc.GetCellArea( 0, nEndCol, nEndRow)) + { + aSourceRanges.push_back( ScRange( 0,0,0, nEndCol, nEndRow, 0)); + nWidth = nEndCol + 1; + nHeight = nEndRow + 2; + } + } + + if (!aTempArea.isEmpty()) + { + sal_Int32 nIdx {0}; + do + { + ScRange aTokenRange; + if( FindExtRange( aTokenRange, rSrcDoc, aTempArea.getToken( 0, ';', nIdx ) ) ) + { + aSourceRanges.push_back( aTokenRange); + // columns: find maximum + nWidth = std::max( nWidth, static_cast<SCCOL>(aTokenRange.aEnd.Col() - aTokenRange.aStart.Col() + 1) ); + // rows: add row range + 1 empty row + nHeight += aTokenRange.aEnd.Row() - aTokenRange.aStart.Row() + 2; + } + } + while (nIdx>0); + } + // remove the last empty row + if( nHeight > 0 ) + nHeight--; + + // delete old data / copy new + + ScAddress aDestPos = aDestArea.aStart; + SCTAB nDestTab = aDestPos.Tab(); + ScRange aOldRange = aDestArea; + ScRange aNewRange = aDestArea; // old range, if file not found or similar + if (nWidth > 0 && nHeight > 0) + { + aNewRange.aEnd.SetCol( aNewRange.aStart.Col() + nWidth - 1 ); + aNewRange.aEnd.SetRow( aNewRange.aStart.Row() + nHeight - 1 ); + } + + //! check CanFitBlock only if bDoInsert is set? + bool bCanDo = rDoc.ValidColRow( aNewRange.aEnd.Col(), aNewRange.aEnd.Row() ) && + rDoc.CanFitBlock( aOldRange, aNewRange ); + if (bCanDo) + { + ScDocShellModificator aModificator( *m_pDocSh ); + + SCCOL nOldEndX = aOldRange.aEnd.Col(); + SCROW nOldEndY = aOldRange.aEnd.Row(); + SCCOL nNewEndX = aNewRange.aEnd.Col(); + SCROW nNewEndY = aNewRange.aEnd.Row(); + ScRange aMaxRange( aDestPos, + ScAddress(std::max(nOldEndX,nNewEndX), std::max(nOldEndY,nNewEndY), nDestTab) ); + + // initialise Undo + + ScDocumentUniquePtr pUndoDoc; + if ( bAddUndo && bUndo ) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + if ( bDoInsert ) + { + if ( nNewEndX != nOldEndX || nNewEndY != nOldEndY ) // range changed? + { + pUndoDoc->InitUndo( rDoc, 0, rDoc.GetTableCount()-1 ); + rDoc.CopyToDocument(0, 0, 0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB, + InsertDeleteFlags::FORMULA, false, *pUndoDoc); // all formulas + } + else + pUndoDoc->InitUndo( rDoc, nDestTab, nDestTab ); // only destination table + rDoc.CopyToDocument(aOldRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pUndoDoc); + } + else // without insertion + { + pUndoDoc->InitUndo( rDoc, nDestTab, nDestTab ); // only destination table + rDoc.CopyToDocument(aMaxRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pUndoDoc); + } + } + + // insert / delete cells + // DeleteAreaTab also deletes MERGE_FLAG attributes + + if (bDoInsert) + rDoc.FitBlock( aOldRange, aNewRange ); // incl. deletion + else + rDoc.DeleteAreaTab( aMaxRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE ); + + // copy data + + if (nWidth > 0 && nHeight > 0) + { + ScDocument aClipDoc( SCDOCMODE_CLIP ); + ScRange aNewTokenRange( aNewRange.aStart ); + for (size_t nRange = 0; nRange < aSourceRanges.size(); ++nRange) + { + ScRange const & rTokenRange( aSourceRanges[nRange]); + SCTAB nSrcTab = rTokenRange.aStart.Tab(); + ScMarkData aSourceMark(rSrcDoc.GetSheetLimits()); + aSourceMark.SelectOneTable( nSrcTab ); // selecting for CopyToClip + aSourceMark.SetMarkArea( rTokenRange ); + + ScClipParam aClipParam(rTokenRange, false); + rSrcDoc.CopyToClip(aClipParam, &aClipDoc, &aSourceMark, false, false); + + if ( aClipDoc.HasAttrib( 0,0,nSrcTab, rDoc.MaxCol(),rDoc.MaxRow(),nSrcTab, + HasAttrFlags::Merged | HasAttrFlags::Overlapped ) ) + { + //! ResetAttrib at document !!! + + ScPatternAttr aPattern( rSrcDoc.GetPool() ); + aPattern.GetItemSet().Put( ScMergeAttr() ); // Defaults + aPattern.GetItemSet().Put( ScMergeFlagAttr() ); + aClipDoc.ApplyPatternAreaTab( 0,0, rDoc.MaxCol(),rDoc.MaxRow(), nSrcTab, aPattern ); + } + + aNewTokenRange.aEnd.SetCol( aNewTokenRange.aStart.Col() + (rTokenRange.aEnd.Col() - rTokenRange.aStart.Col()) ); + aNewTokenRange.aEnd.SetRow( aNewTokenRange.aStart.Row() + (rTokenRange.aEnd.Row() - rTokenRange.aStart.Row()) ); + ScMarkData aDestMark(rDoc.GetSheetLimits()); + aDestMark.SelectOneTable( nDestTab ); + aDestMark.SetMarkArea( aNewTokenRange ); + rDoc.CopyFromClip( aNewTokenRange, aDestMark, InsertDeleteFlags::ALL, nullptr, &aClipDoc, false ); + aNewTokenRange.aStart.SetRow( aNewTokenRange.aEnd.Row() + 2 ); + } + } + else + { + OUString aErr = ScResId(STR_LINKERROR); + rDoc.SetString( aDestPos.Col(), aDestPos.Row(), aDestPos.Tab(), aErr ); + } + + // enter Undo + + if ( bAddUndo && bUndo) + { + ScDocumentUniquePtr pRedoDoc(new ScDocument( SCDOCMODE_UNDO )); + pRedoDoc->InitUndo( rDoc, nDestTab, nDestTab ); + rDoc.CopyToDocument(aNewRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pRedoDoc); + + m_pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoUpdateAreaLink>( m_pDocSh, + aFileName, aFilterName, aOptions, + aSourceArea, aOldRange, GetRefreshDelaySeconds(), + aNewUrl, rNewFilter, aNewOpt, + rNewArea, aNewRange, nNewRefreshDelaySeconds, + std::move(pUndoDoc), std::move(pRedoDoc), bDoInsert ) ); + } + + // remember new settings + + if ( bNewUrlName ) + aFileName = aNewUrl; + if ( rNewFilter != aFilterName ) + aFilterName = rNewFilter; + if ( rNewArea != aSourceArea ) + aSourceArea = rNewArea; + if ( aNewOpt != aOptions ) + aOptions = aNewOpt; + + if ( aNewRange != aDestArea ) + aDestArea = aNewRange; + + if ( nNewRefreshDelaySeconds != GetRefreshDelaySeconds() ) + SetRefreshDelay( nNewRefreshDelaySeconds ); + + SCCOL nPaintEndX = std::max( aOldRange.aEnd.Col(), aNewRange.aEnd.Col() ); + SCROW nPaintEndY = std::max( aOldRange.aEnd.Row(), aNewRange.aEnd.Row() ); + + if ( aOldRange.aEnd.Col() != aNewRange.aEnd.Col() ) + nPaintEndX = rDoc.MaxCol(); + if ( aOldRange.aEnd.Row() != aNewRange.aEnd.Row() ) + nPaintEndY = rDoc.MaxRow(); + + if ( !m_pDocSh->AdjustRowHeight( aDestPos.Row(), nPaintEndY, nDestTab ) ) + m_pDocSh->PostPaint( + ScRange(aDestPos.Col(), aDestPos.Row(), nDestTab, nPaintEndX, nPaintEndY, nDestTab), + PaintPartFlags::Grid); + aModificator.SetDocumentModified(); + } + else + { + // CanFitBlock sal_False -> Problems with summarized cells or table boundary reached! + //! cell protection ??? + + //! Link dialog must set default parent + // "cannot insert rows" + weld::Window* pWin = Application::GetFrameWeld(m_pDocSh->GetDialogParent()); + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pWin, + VclMessageType::Info, VclButtonsType::Ok, + ScResId(STR_MSSG_DOSUBTOTALS_2))); + xInfoBox->run(); + } + + // clean up + + aRef->DoClose(); + + rDoc.SetInLinkUpdate( false ); + + if (bCanDo) + { + // notify Uno objects (for XRefreshListener) + //! also notify Uno objects if file name was changed! + ScLinkRefreshedHint aHint; + aHint.SetAreaLink( aDestPos ); + rDoc.BroadcastUno( aHint ); + } + + return bCanDo; +} + +IMPL_LINK_NOARG(ScAreaLink, RefreshHdl, Timer *, void) +{ + Refresh( aFileName, aFilterName, aSourceArea, GetRefreshDelaySeconds() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/autostyl.cxx b/sc/source/ui/docshell/autostyl.cxx new file mode 100644 index 000000000..5b6eaa30c --- /dev/null +++ b/sc/source/ui/docshell/autostyl.cxx @@ -0,0 +1,191 @@ +/* -*- 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 <time.h> +#include <osl/diagnose.h> + +#include <address.hxx> +#include <autostyl.hxx> +#include <docsh.hxx> + +static sal_uLong TimeNow() // seconds +{ + return static_cast<sal_uLong>(time(nullptr)); +} + +namespace { + +class FindByRange +{ + ScRange maRange; +public: + explicit FindByRange(const ScRange& r) : maRange(r) {} + bool operator() (const ScAutoStyleData& rData) const { return rData.aRange == maRange; } +}; + +class FindByTimeout +{ + sal_uLong mnTimeout; +public: + explicit FindByTimeout(sal_uLong n) : mnTimeout(n) {} + bool operator() (const ScAutoStyleData& rData) const { return rData.nTimeout >= mnTimeout; } +}; + +struct FindNonZeroTimeout +{ + bool operator() (const ScAutoStyleData& rData) const + { + return rData.nTimeout != 0; + } +}; + +} + +ScAutoStyleList::ScAutoStyleList(ScDocShell* pShell) + : pDocSh(pShell) + , aTimer("ScAutoStyleList Timer") + , aInitIdle("ScAutoStyleList InitIdle") + , nTimerStart(0) +{ + aTimer.SetInvokeHandler( LINK( this, ScAutoStyleList, TimerHdl ) ); + aInitIdle.SetInvokeHandler( LINK( this, ScAutoStyleList, InitHdl ) ); + aInitIdle.SetPriority( TaskPriority::HIGHEST ); +} + +ScAutoStyleList::~ScAutoStyleList() +{ +} + +// initial short delay (asynchronous call) + +void ScAutoStyleList::AddInitial( const ScRange& rRange, const OUString& rStyle1, + sal_uLong nTimeout, const OUString& rStyle2 ) +{ + aInitials.emplace_back( rRange, rStyle1, nTimeout, rStyle2 ); + aInitIdle.Start(); +} + +IMPL_LINK_NOARG(ScAutoStyleList, InitHdl, Timer *, void) +{ + std::vector<ScAutoStyleInitData> aLocalInitials(std::move(aInitials)); + for (const auto& rInitial : aLocalInitials) + { + // apply first style immediately + pDocSh->DoAutoStyle(rInitial.aRange, rInitial.aStyle1); + + // add second style to list + if (rInitial.nTimeout) + AddEntry(rInitial.nTimeout, rInitial.aRange, rInitial.aStyle2 ); + } +} + +void ScAutoStyleList::AddEntry( sal_uLong nTimeout, const ScRange& rRange, const OUString& rStyle ) +{ + aTimer.Stop(); + sal_uLong nNow = TimeNow(); + + // Remove the first item with the same range. + std::vector<ScAutoStyleData>::iterator itr = + ::std::find_if(aEntries.begin(), aEntries.end(), FindByRange(rRange)); + + if (itr != aEntries.end()) + aEntries.erase(itr); + + // adjust timeouts of all entries + + if (!aEntries.empty() && nNow != nTimerStart) + { + OSL_ENSURE(nNow>nTimerStart, "Time is running backwards?"); + AdjustEntries((nNow-nTimerStart)*1000); + } + + // find insert position + std::vector<ScAutoStyleData>::iterator iter = + ::std::find_if(aEntries.begin(), aEntries.end(), FindByTimeout(nTimeout)); + + aEntries.insert(iter, ScAutoStyleData(nTimeout,rRange,rStyle)); + + // execute expired, restart timer + + ExecuteEntries(); + StartTimer(nNow); +} + +void ScAutoStyleList::AdjustEntries( sal_uLong nDiff ) // milliseconds +{ + for (auto& rEntry : aEntries) + { + if (rEntry.nTimeout <= nDiff) + rEntry.nTimeout = 0; // expired + else + rEntry.nTimeout -= nDiff; // continue counting + } +} + +void ScAutoStyleList::ExecuteEntries() +{ + // Execute and remove all items with timeout == 0 from the begin position + // until the first item with non-zero timeout value. + std::vector<ScAutoStyleData>::iterator itr = aEntries.begin(), itrEnd = aEntries.end(); + for (; itr != itrEnd; ++itr) + { + if (itr->nTimeout) + break; + + pDocSh->DoAutoStyle(itr->aRange, itr->aStyle); + } + // At this point itr should be on the first item with non-zero timeout, or + // the end position in case all items have timeout == 0. + aEntries.erase(aEntries.begin(), itr); +} + +void ScAutoStyleList::ExecuteAllNow() +{ + aTimer.Stop(); + + for (const auto& rEntry : aEntries) + pDocSh->DoAutoStyle(rEntry.aRange, rEntry.aStyle); + + aEntries.clear(); +} + +void ScAutoStyleList::StartTimer( sal_uLong nNow ) // seconds +{ + // find first entry with Timeout != 0 + std::vector<ScAutoStyleData>::iterator iter = + ::std::find_if(aEntries.begin(),aEntries.end(), FindNonZeroTimeout()); + + if (iter != aEntries.end()) + { + aTimer.SetTimeout(iter->nTimeout); + aTimer.Start(); + } + + nTimerStart = nNow; +} + +IMPL_LINK_NOARG(ScAutoStyleList, TimerHdl, Timer *, void) +{ + sal_uLong nNow = TimeNow(); + AdjustEntries(aTimer.GetTimeout()); // the set waiting time + ExecuteEntries(); + StartTimer(nNow); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/datastream.cxx b/sc/source/ui/docshell/datastream.cxx new file mode 100644 index 000000000..4bcbbaf99 --- /dev/null +++ b/sc/source/ui/docshell/datastream.cxx @@ -0,0 +1,549 @@ +/* -*- 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/. + */ + +#include <datastream.hxx> +#include <datastreamgettime.hxx> + +#include <com/sun/star/frame/XLayoutManager.hpp> +#include <osl/conditn.hxx> +#include <osl/time.h> +#include <salhelper/thread.hxx> +#include <sfx2/viewfrm.hxx> +#include <tools/stream.hxx> +#include <vcl/svapp.hxx> +#include <docsh.hxx> +#include <tabvwsh.hxx> +#include <viewdata.hxx> +#include <stringutil.hxx> +#include <documentlinkmgr.hxx> +#include <o3tl/enumarray.hxx> + +#include <officecfg/Office/Calc.hxx> + +#include <orcus/csv_parser.hpp> + +#include <queue> + +namespace com::sun::star::ui { class XUIElement; } + +namespace sc { + +static o3tl::enumarray<DebugTime, double> fTimes { 0.0, 0.0, 0.0 }; + +double datastream_get_time(DebugTime nIdx) +{ + return fTimes[ nIdx ]; +} + +namespace { + +double getNow() +{ + TimeValue now; + osl_getSystemTime(&now); + return static_cast<double>(now.Seconds) + static_cast<double>(now.Nanosec) / 1000000000.0; +} + +class CSVHandler +{ + DataStream::Line& mrLine; + size_t mnColCount; + size_t mnCols; + const char* mpLineHead; + +public: + CSVHandler( DataStream::Line& rLine, size_t nColCount ) : + mrLine(rLine), mnColCount(nColCount), mnCols(0), mpLineHead(rLine.maLine.getStr()) {} + + static void begin_parse() {} + static void end_parse() {} + static void begin_row() {} + static void end_row() {} + + void cell(const char* p, size_t n, bool /*transient*/) + { + if (mnCols >= mnColCount) + return; + + DataStream::Cell aCell; + if (ScStringUtil::parseSimpleNumber(p, n, '.', ',', aCell.mfValue)) + { + aCell.mbValue = true; + } + else + { + aCell.mbValue = false; + aCell.maStr.Pos = std::distance(mpLineHead, p); + aCell.maStr.Size = n; + } + mrLine.maCells.push_back(aCell); + + ++mnCols; + } +}; + +} + +namespace datastreams { + +class ReaderThread : public salhelper::Thread +{ + std::unique_ptr<SvStream> mpStream; + size_t mnColCount; + bool mbTerminate; + osl::Mutex maMtxTerminate; + + std::queue<std::unique_ptr<DataStream::LinesType>> maPendingLines; + std::queue<std::unique_ptr<DataStream::LinesType>> maUsedLines; + osl::Mutex maMtxLines; + + osl::Condition maCondReadStream; + osl::Condition maCondConsume; + + orcus::csv::parser_config maConfig; + +public: + + ReaderThread(std::unique_ptr<SvStream> pData, size_t nColCount): + Thread("ReaderThread"), + mpStream(std::move(pData)), + mnColCount(nColCount), + mbTerminate(false) + { + maConfig.delimiters.push_back(','); + maConfig.text_qualifier = '"'; + } + + bool isTerminateRequested() + { + osl::MutexGuard aGuard(maMtxTerminate); + return mbTerminate; + } + + void requestTerminate() + { + osl::MutexGuard aGuard(maMtxTerminate); + mbTerminate = true; + } + + void endThread() + { + requestTerminate(); + maCondReadStream.set(); + } + + void waitForNewLines() + { + maCondConsume.wait(); + maCondConsume.reset(); + } + + std::unique_ptr<DataStream::LinesType> popNewLines() + { + auto pLines = std::move(maPendingLines.front()); + maPendingLines.pop(); + return pLines; + } + + void resumeReadStream() + { + if (maPendingLines.size() <= 4) + maCondReadStream.set(); // start producer again + } + + bool hasNewLines() const + { + return !maPendingLines.empty(); + } + + void pushUsedLines( std::unique_ptr<DataStream::LinesType> pLines ) + { + maUsedLines.push(std::move(pLines)); + } + + osl::Mutex& getLinesMutex() + { + return maMtxLines; + } + +private: + virtual void execute() override + { + while (!isTerminateRequested()) + { + std::unique_ptr<DataStream::LinesType> pLines; + osl::ResettableMutexGuard aGuard(maMtxLines); + + if (!maUsedLines.empty()) + { + // Re-use lines from previous runs. + pLines = std::move(maUsedLines.front()); + maUsedLines.pop(); + aGuard.clear(); // unlock + } + else + { + aGuard.clear(); // unlock + pLines.reset(new DataStream::LinesType(10)); + } + + // Read & store new lines from stream. + for (DataStream::Line & rLine : *pLines) + { + rLine.maCells.clear(); + mpStream->ReadLine(rLine.maLine); + CSVHandler aHdl(rLine, mnColCount); + orcus::csv_parser<CSVHandler> parser(rLine.maLine.getStr(), rLine.maLine.getLength(), aHdl, maConfig); + parser.parse(); + } + + aGuard.reset(); // lock + while (!isTerminateRequested() && maPendingLines.size() >= 8) + { + // pause reading for a bit + aGuard.clear(); // unlock + maCondReadStream.wait(); + maCondReadStream.reset(); + aGuard.reset(); // lock + } + maPendingLines.push(std::move(pLines)); + maCondConsume.set(); + if (!mpStream->good()) + requestTerminate(); + } + } +}; + +} + +DataStream::Cell::Cell() : mfValue(0.0), mbValue(true) {} + +DataStream::Cell::Cell( const Cell& r ) : mbValue(r.mbValue) +{ + if (r.mbValue) + mfValue = r.mfValue; + else + { + maStr.Pos = r.maStr.Pos; + maStr.Size = r.maStr.Size; + } +} + +void DataStream::MakeToolbarVisible() +{ + ScViewData* pViewData = ScDocShell::GetViewData(); + if (!pViewData) + return; + + css::uno::Reference< css::frame::XFrame > xFrame = + pViewData->GetViewShell()->GetViewFrame()->GetFrame().GetFrameInterface(); + if (!xFrame.is()) + return; + + css::uno::Reference< css::beans::XPropertySet > xPropSet(xFrame, css::uno::UNO_QUERY); + if (!xPropSet.is()) + return; + + css::uno::Reference< css::frame::XLayoutManager > xLayoutManager; + xPropSet->getPropertyValue("LayoutManager") >>= xLayoutManager; + if (!xLayoutManager.is()) + return; + + static const OUStringLiteral sResourceURL( u"private:resource/toolbar/datastreams" ); + css::uno::Reference< css::ui::XUIElement > xUIElement = xLayoutManager->getElement(sResourceURL); + if (!xUIElement.is()) + { + xLayoutManager->createElement( sResourceURL ); + xLayoutManager->showElement( sResourceURL ); + } +} + +DataStream* DataStream::Set( + ScDocShell *pShell, const OUString& rURL, const ScRange& rRange, + sal_Int32 nLimit, MoveType eMove, sal_uInt32 nSettings) +{ + DataStream* pLink = new DataStream(pShell, rURL, rRange, nLimit, eMove, nSettings); + sc::DocumentLinkManager& rMgr = pShell->GetDocument().GetDocLinkManager(); + rMgr.setDataStream(pLink); + return pLink; +} + +DataStream::DataStream(ScDocShell *pShell, const OUString& rURL, const ScRange& rRange, + sal_Int32 nLimit, MoveType eMove, sal_uInt32 nSettings) : + mpDocShell(pShell), + maDocAccess(mpDocShell->GetDocument()), + meOrigMove(NO_MOVE), + meMove(NO_MOVE), + mbRunning(false), + mbValuesInLine(false), + mbRefreshOnEmptyLine(false), + mnLinesCount(0), + mnLinesSinceRefresh(0), + mfLastRefreshTime(0.0), + mnCurRow(0), + maImportTimer("sc DataStream maImportTimer"), + mbIsFirst(true), + mbIsUpdate(false) +{ + maImportTimer.SetTimeout(0); + maImportTimer.SetInvokeHandler( LINK(this, DataStream, ImportTimerHdl) ); + + Decode(rURL, rRange, nLimit, eMove, nSettings); +} + +DataStream::~DataStream() +{ + if (mbRunning) + StopImport(); + + if (mxReaderThread.is()) + { + mxReaderThread->endThread(); + mxReaderThread->join(); + } + mpLines.reset(); +} + +DataStream::Line DataStream::ConsumeLine() +{ + if (!mpLines || mnLinesCount >= mpLines->size()) + { + mnLinesCount = 0; + if (mxReaderThread->isTerminateRequested()) + return Line(); + + osl::ResettableMutexGuard aGuard(mxReaderThread->getLinesMutex()); + if (mpLines) + mxReaderThread->pushUsedLines(std::move(mpLines)); + + while (!mxReaderThread->hasNewLines()) + { + aGuard.clear(); // unlock + mxReaderThread->waitForNewLines(); + aGuard.reset(); // lock + } + + mpLines = mxReaderThread->popNewLines(); + mxReaderThread->resumeReadStream(); + } + return mpLines->at(mnLinesCount++); +} + +ScRange DataStream::GetRange() const +{ + ScRange aRange = maStartRange; + aRange.aEnd = maEndRange.aEnd; + return aRange; +} + +void DataStream::Decode(const OUString& rURL, const ScRange& rRange, + sal_Int32 nLimit, MoveType eMove, const sal_uInt32 nSettings) +{ + msURL = rURL; + meMove = eMove; + meOrigMove = eMove; + mnSettings = nSettings; + + mbValuesInLine = true; // always true. + + mnCurRow = rRange.aStart.Row(); + + ScRange aRange = rRange; + if (aRange.aStart.Row() != aRange.aEnd.Row()) + // We only allow this range to be one row tall. + aRange.aEnd.SetRow(aRange.aStart.Row()); + + maStartRange = aRange; + maEndRange = aRange; + const auto & rDoc = mpDocShell->GetDocument(); + if (nLimit == 0) + { + // Unlimited + maEndRange.aStart.SetRow(rDoc.MaxRow()); + } + else if (nLimit > 0) + { + // Limited. + maEndRange.aStart.IncRow(nLimit-1); + if (maEndRange.aStart.Row() > rDoc.MaxRow()) + maEndRange.aStart.SetRow(rDoc.MaxRow()); + } + + maEndRange.aEnd.SetRow(maEndRange.aStart.Row()); +} + +void DataStream::StartImport() +{ + if (mbRunning) + return; + + if (!mxReaderThread.is()) + { + std::unique_ptr<SvStream> pStream(new SvFileStream(msURL, StreamMode::READ)); + mxReaderThread = new datastreams::ReaderThread(std::move(pStream), maStartRange.aEnd.Col() - maStartRange.aStart.Col() + 1); + mxReaderThread->launch(); + } + mbRunning = true; + maDocAccess.reset(); + + maImportTimer.Start(); +} + +void DataStream::StopImport() +{ + if (!mbRunning) + return; + + mbRunning = false; + Refresh(); + maImportTimer.Stop(); +} + +void DataStream::SetRefreshOnEmptyLine( bool bVal ) +{ + mbRefreshOnEmptyLine = bVal; +} + +void DataStream::Refresh() +{ + Application::Yield(); + + double fStart = getNow(); + + // Hard recalc will repaint the grid area. + mpDocShell->DoHardRecalc(); + mpDocShell->SetDocumentModified(); + + fTimes[ DebugTime::Recalc ] = getNow() - fStart; + + mfLastRefreshTime = getNow(); + mnLinesSinceRefresh = 0; +} + +void DataStream::MoveData() +{ + switch (meMove) + { + case RANGE_DOWN: + { + if (mnCurRow == maEndRange.aStart.Row()) + meMove = MOVE_UP; + } + break; + case MOVE_UP: + { + mbIsUpdate = true; + // Remove the top row and shift the remaining rows upward. Then + // insert a new row at the end row position. + ScRange aRange = maStartRange; + aRange.aEnd = maEndRange.aEnd; + maDocAccess.shiftRangeUp(aRange); + } + break; + case MOVE_DOWN: + { + mbIsUpdate = true; + // Remove the end row and shift the remaining rows downward by + // inserting a new row at the top row. + ScRange aRange = maStartRange; + aRange.aEnd = maEndRange.aEnd; + maDocAccess.shiftRangeDown(aRange); + } + break; + case NO_MOVE: + default: + ; + } + if(mbIsFirst && mbIsUpdate) + { + sal_Int32 nStreamTimeout = officecfg::Office::Calc::DataStream::UpdateTimeout::get(); + maImportTimer.SetTimeout(nStreamTimeout); + mbIsFirst = false; + } +} + +void DataStream::Text2Doc() +{ + Line aLine = ConsumeLine(); + if (aLine.maCells.empty() && mbRefreshOnEmptyLine) + { + // Empty line detected. Trigger refresh and discard it. + Refresh(); + return; + } + + double fStart = getNow(); + + MoveData(); + { + SCCOL nCol = maStartRange.aStart.Col(); + const char* pLineHead = aLine.maLine.getStr(); + for (const Cell& rCell : aLine.maCells) + { + if (rCell.mbValue) + { + maDocAccess.setNumericCell( + ScAddress(nCol, mnCurRow, maStartRange.aStart.Tab()), rCell.mfValue); + } + else + { + maDocAccess.setStringCell( + ScAddress(nCol, mnCurRow, maStartRange.aStart.Tab()), + OUString(pLineHead+rCell.maStr.Pos, rCell.maStr.Size, RTL_TEXTENCODING_UTF8)); + } + ++nCol; + } + } + + fTimes[ DebugTime::Import ] = getNow() - fStart; + + if (meMove == NO_MOVE) + return; + + if (meMove == RANGE_DOWN) + { + ++mnCurRow; +// mpDocShell->GetViewData().GetView()->AlignToCursor( +// maStartRange.aStart.Col(), mnCurRow, SC_FOLLOW_JUMP); + } + + if (getNow() - mfLastRefreshTime > 0.1 && mnLinesSinceRefresh > 200) + // Refresh no more frequently than every 0.1 second, and wait until at + // least we have processed 200 lines. + Refresh(); + + ++mnLinesSinceRefresh; +} + +bool DataStream::ImportData() +{ + if (!mbValuesInLine) + // We no longer support this mode. To be deleted later. + return false; + + ScViewData* pViewData = ScDocShell::GetViewData(); + if (!pViewData) + return false; + + if (pViewData->GetViewShell()->NeedsRepaint()) + return mbRunning; + + Text2Doc(); + return mbRunning; +} + +IMPL_LINK_NOARG(DataStream, ImportTimerHdl, Timer *, void) +{ + if (ImportData()) + maImportTimer.Start(); +} + +} // namespace sc + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/dbdocfun.cxx b/sc/source/ui/docshell/dbdocfun.cxx new file mode 100644 index 000000000..ee59f3623 --- /dev/null +++ b/sc/source/ui/docshell/dbdocfun.cxx @@ -0,0 +1,1790 @@ +/* -*- 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 <sfx2/app.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <svx/dataaccessdescriptor.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdoole2.hxx> +#include <com/sun/star/sdb/CommandType.hpp> +#include <unotools/charclass.hxx> +#include <comphelper/lok.hxx> +#include <osl/diagnose.h> + +#include <dbdocfun.hxx> +#include <dbdata.hxx> +#include <undodat.hxx> +#include <docsh.hxx> +#include <docfunc.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <globalnames.hxx> +#include <tabvwsh.hxx> +#include <patattr.hxx> +#include <rangenam.hxx> +#include <olinetab.hxx> +#include <dpobject.hxx> +#include <dpsave.hxx> +#include <dociter.hxx> +#include <editable.hxx> +#include <attrib.hxx> +#include <drwlayer.hxx> +#include <dpshttab.hxx> +#include <hints.hxx> +#include <queryentry.hxx> +#include <markdata.hxx> +#include <progress.hxx> +#include <undosort.hxx> +#include <inputopt.hxx> +#include <scmod.hxx> + +#include <chartlis.hxx> +#include <ChartTools.hxx> + +#include <memory> + +using namespace ::com::sun::star; + +bool ScDBDocFunc::AddDBRange( const OUString& rName, const ScRange& rRange ) +{ + + ScDocShellModificator aModificator( rDocShell ); + + ScDocument& rDoc = rDocShell.GetDocument(); + ScDBCollection* pDocColl = rDoc.GetDBCollection(); + bool bUndo (rDoc.IsUndoEnabled()); + + std::unique_ptr<ScDBCollection> pUndoColl; + if (bUndo) + pUndoColl.reset( new ScDBCollection( *pDocColl ) ); + + std::unique_ptr<ScDBData> pNew(new ScDBData( rName, rRange.aStart.Tab(), + rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row() )); + + // #i55926# While loading XML, formula cells only have a single string token, + // so CompileDBFormula would never find any name (index) tokens, and would + // unnecessarily loop through all cells. + bool bCompile = !rDoc.IsImportingXML(); + bool bOk; + if ( bCompile ) + rDoc.PreprocessDBDataUpdate(); + if ( rName == STR_DB_LOCAL_NONAME ) + { + rDoc.SetAnonymousDBData(rRange.aStart.Tab(), std::move(pNew)); + bOk = true; + } + else + { + bOk = pDocColl->getNamedDBs().insert(std::move(pNew)); + } + if ( bCompile ) + rDoc.CompileHybridFormula(); + + if (!bOk) + { + return false; + } + + if (bUndo) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDBData>( &rDocShell, std::move(pUndoColl), + std::make_unique<ScDBCollection>( *pDocColl ) ) ); + } + + aModificator.SetDocumentModified(); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScDbAreasChanged ) ); + return true; +} + +bool ScDBDocFunc::DeleteDBRange(const OUString& rName) +{ + bool bDone = false; + ScDocument& rDoc = rDocShell.GetDocument(); + ScDBCollection* pDocColl = rDoc.GetDBCollection(); + bool bUndo = rDoc.IsUndoEnabled(); + + ScDBCollection::NamedDBs& rDBs = pDocColl->getNamedDBs(); + auto const iter = rDBs.findByUpperName2(ScGlobal::getCharClass().uppercase(rName)); + if (iter != rDBs.end()) + { + ScDocShellModificator aModificator( rDocShell ); + + std::unique_ptr<ScDBCollection> pUndoColl; + if (bUndo) + pUndoColl.reset( new ScDBCollection( *pDocColl ) ); + + rDoc.PreprocessDBDataUpdate(); + rDBs.erase(iter); + rDoc.CompileHybridFormula(); + + if (bUndo) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDBData>( &rDocShell, std::move(pUndoColl), + std::make_unique<ScDBCollection>( *pDocColl ) ) ); + } + + aModificator.SetDocumentModified(); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScDbAreasChanged ) ); + bDone = true; + } + + return bDone; +} + +bool ScDBDocFunc::RenameDBRange( const OUString& rOld, const OUString& rNew ) +{ + bool bDone = false; + ScDocument& rDoc = rDocShell.GetDocument(); + ScDBCollection* pDocColl = rDoc.GetDBCollection(); + bool bUndo = rDoc.IsUndoEnabled(); + ScDBCollection::NamedDBs& rDBs = pDocColl->getNamedDBs(); + auto const iterOld = rDBs.findByUpperName2(ScGlobal::getCharClass().uppercase(rOld)); + const ScDBData* pNew = rDBs.findByUpperName(ScGlobal::getCharClass().uppercase(rNew)); + if (iterOld != rDBs.end() && !pNew) + { + ScDocShellModificator aModificator( rDocShell ); + + std::unique_ptr<ScDBData> pNewData(new ScDBData(rNew, **iterOld)); + + std::unique_ptr<ScDBCollection> pUndoColl( new ScDBCollection( *pDocColl ) ); + + rDoc.PreprocessDBDataUpdate(); + rDBs.erase(iterOld); + bool bInserted = rDBs.insert(std::move(pNewData)); + if (!bInserted) // error -> restore old state + { + rDoc.SetDBCollection(std::move(pUndoColl)); // belongs to the document then + } + + rDoc.CompileHybridFormula(); + + if (bInserted) // insertion worked + { + if (bUndo) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDBData>( &rDocShell, std::move(pUndoColl), + std::make_unique<ScDBCollection>( *pDocColl ) ) ); + } + else + pUndoColl.reset(); + + aModificator.SetDocumentModified(); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScDbAreasChanged ) ); + bDone = true; + } + } + + return bDone; +} + +void ScDBDocFunc::ModifyDBData( const ScDBData& rNewData ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + ScDBCollection* pDocColl = rDoc.GetDBCollection(); + bool bUndo = rDoc.IsUndoEnabled(); + + ScDBData* pData = nullptr; + if (rNewData.GetName() == STR_DB_LOCAL_NONAME) + { + ScRange aRange; + rNewData.GetArea(aRange); + SCTAB nTab = aRange.aStart.Tab(); + pData = rDoc.GetAnonymousDBData(nTab); + } + else + pData = pDocColl->getNamedDBs().findByUpperName(rNewData.GetUpperName()); + + if (!pData) + return; + + ScDocShellModificator aModificator( rDocShell ); + ScRange aOldRange, aNewRange; + pData->GetArea(aOldRange); + rNewData.GetArea(aNewRange); + bool bAreaChanged = ( aOldRange != aNewRange ); // then a recompilation is needed + + std::unique_ptr<ScDBCollection> pUndoColl; + if (bUndo) + pUndoColl.reset( new ScDBCollection( *pDocColl ) ); + + *pData = rNewData; + if (bAreaChanged) + rDoc.CompileDBFormula(); + + if (bUndo) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDBData>( &rDocShell, std::move(pUndoColl), + std::make_unique<ScDBCollection>( *pDocColl ) ) ); + } + + aModificator.SetDocumentModified(); +} + +void ScDBDocFunc::ModifyAllDBData( const ScDBCollection& rNewColl, const std::vector<ScRange>& rDelAreaList ) +{ + ScDocShellModificator aModificator(rDocShell); + ScDocument& rDoc = rDocShell.GetDocument(); + ScDBCollection* pOldColl = rDoc.GetDBCollection(); + std::unique_ptr<ScDBCollection> pUndoColl; + bool bRecord = rDoc.IsUndoEnabled(); + + for (const auto& rDelArea : rDelAreaList) + { + // unregistering target in SBA no longer necessary + const ScAddress& rStart = rDelArea.aStart; + const ScAddress& rEnd = rDelArea.aEnd; + rDocShell.DBAreaDeleted( + rStart.Tab(), rStart.Col(), rStart.Row(), rEnd.Col()); + } + + if (bRecord) + pUndoColl.reset( new ScDBCollection( *pOldColl ) ); + + // register target in SBA no longer necessary + + rDoc.PreprocessDBDataUpdate(); + rDoc.SetDBCollection( std::unique_ptr<ScDBCollection>(new ScDBCollection( rNewColl )) ); + rDoc.CompileHybridFormula(); + pOldColl = nullptr; + rDocShell.PostPaint(ScRange(0, 0, 0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB), PaintPartFlags::Grid); + aModificator.SetDocumentModified(); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScDbAreasChanged ) ); + + if (bRecord) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDBData>(&rDocShell, std::move(pUndoColl), + std::make_unique<ScDBCollection>(rNewColl))); + } +} + +bool ScDBDocFunc::RepeatDB( const OUString& rDBName, bool bApi, bool bIsUnnamed, SCTAB aTab ) +{ + //! use also for ScDBFunc::RepeatDB ! + + bool bDone = false; + ScDocument& rDoc = rDocShell.GetDocument(); + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + ScDBData* pDBData = nullptr; + if (bIsUnnamed) + { + pDBData = rDoc.GetAnonymousDBData( aTab ); + } + else + { + ScDBCollection* pColl = rDoc.GetDBCollection(); + if (pColl) + pDBData = pColl->getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(rDBName)); + } + + if ( pDBData ) + { + ScQueryParam aQueryParam; + pDBData->GetQueryParam( aQueryParam ); + bool bQuery = aQueryParam.GetEntry(0).bDoQuery; + + ScSortParam aSortParam; + pDBData->GetSortParam( aSortParam ); + bool bSort = aSortParam.maKeyState[0].bDoSort; + + ScSubTotalParam aSubTotalParam; + pDBData->GetSubTotalParam( aSubTotalParam ); + bool bSubTotal = aSubTotalParam.bGroupActive[0] && !aSubTotalParam.bRemoveOnly; + + if ( bQuery || bSort || bSubTotal ) + { + bool bQuerySize = false; + ScRange aOldQuery; + ScRange aNewQuery; + if (bQuery && !aQueryParam.bInplace) + { + ScDBData* pDest = rDoc.GetDBAtCursor( aQueryParam.nDestCol, aQueryParam.nDestRow, + aQueryParam.nDestTab, ScDBDataPortion::TOP_LEFT ); + if (pDest && pDest->IsDoSize()) + { + pDest->GetArea( aOldQuery ); + bQuerySize = true; + } + } + + SCTAB nTab; + SCCOL nStartCol; + SCROW nStartRow; + SCCOL nEndCol; + SCROW nEndRow; + pDBData->GetArea( nTab, nStartCol, nStartRow, nEndCol, nEndRow ); + + //! Undo needed data only ? + + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr<ScOutlineTable> pUndoTab; + std::unique_ptr<ScRangeName> pUndoRange; + std::unique_ptr<ScDBCollection> pUndoDB; + + if (bRecord) + { + SCTAB nTabCount = rDoc.GetTableCount(); + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab ); + if (pTable) + { + pUndoTab.reset(new ScOutlineTable( *pTable )); + + // column/row state + SCCOLROW nOutStartCol, nOutEndCol; + SCCOLROW nOutStartRow, nOutEndRow; + pTable->GetColArray().GetRange( nOutStartCol, nOutEndCol ); + pTable->GetRowArray().GetRange( nOutStartRow, nOutEndRow ); + + pUndoDoc->InitUndo( rDoc, nTab, nTab, true, true ); + rDoc.CopyToDocument(static_cast<SCCOL>(nOutStartCol), 0, + nTab, static_cast<SCCOL>(nOutEndCol), rDoc.MaxRow(), nTab, + InsertDeleteFlags::NONE, false, *pUndoDoc); + rDoc.CopyToDocument(0, static_cast<SCROW>(nOutStartRow), + nTab, rDoc.MaxCol(), static_cast<SCROW>(nOutEndRow), nTab, + InsertDeleteFlags::NONE, false, *pUndoDoc); + } + else + pUndoDoc->InitUndo( rDoc, nTab, nTab, false, true ); + + // secure data range - incl. filtering result + rDoc.CopyToDocument(0, nStartRow, nTab, rDoc.MaxCol(), nEndRow, nTab, InsertDeleteFlags::ALL, false, *pUndoDoc); + + // all formulas because of references + rDoc.CopyToDocument(0, 0, 0, rDoc.MaxCol(), rDoc.MaxRow(), nTabCount-1, InsertDeleteFlags::FORMULA, false, *pUndoDoc); + + // ranges of DB and other + ScRangeName* pDocRange = rDoc.GetRangeName(); + if (!pDocRange->empty()) + pUndoRange.reset(new ScRangeName( *pDocRange )); + ScDBCollection* pDocDB = rDoc.GetDBCollection(); + if (!pDocDB->empty()) + pUndoDB.reset(new ScDBCollection( *pDocDB )); + } + + if (bSort && bSubTotal) + { + // sort without SubTotals + + aSubTotalParam.bRemoveOnly = true; // will be reset again further down + DoSubTotals( nTab, aSubTotalParam, false, bApi ); + } + + if (bSort) + { + pDBData->GetSortParam( aSortParam ); // range may have changed + (void)Sort( nTab, aSortParam, false, false, bApi ); + } + if (bQuery) + { + pDBData->GetQueryParam( aQueryParam ); // range may have changed + ScRange aAdvSource; + if (pDBData->GetAdvancedQuerySource(aAdvSource)) + Query( nTab, aQueryParam, &aAdvSource, false, bApi ); + else + Query( nTab, aQueryParam, nullptr, false, bApi ); + + // at not-inplace the table may have been converted +// if ( !aQueryParam.bInplace && aQueryParam.nDestTab != nTab ) +// SetTabNo( nTab ); + } + if (bSubTotal) + { + pDBData->GetSubTotalParam( aSubTotalParam ); // range may have changed + aSubTotalParam.bRemoveOnly = false; + DoSubTotals( nTab, aSubTotalParam, false, bApi ); + } + + if (bRecord) + { + SCTAB nDummyTab; + SCCOL nDummyCol; + SCROW nDummyRow; + SCROW nNewEndRow; + pDBData->GetArea( nDummyTab, nDummyCol,nDummyRow, nDummyCol,nNewEndRow ); + + const ScRange* pOld = nullptr; + const ScRange* pNew = nullptr; + if (bQuerySize) + { + ScDBData* pDest = rDoc.GetDBAtCursor( aQueryParam.nDestCol, aQueryParam.nDestRow, + aQueryParam.nDestTab, ScDBDataPortion::TOP_LEFT ); + if (pDest) + { + pDest->GetArea( aNewQuery ); + pOld = &aOldQuery; + pNew = &aNewQuery; + } + } + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoRepeatDB>( &rDocShell, nTab, + nStartCol, nStartRow, nEndCol, nEndRow, + nNewEndRow, + //nCurX, nCurY, + nStartCol, nStartRow, + std::move(pUndoDoc), std::move(pUndoTab), + std::move(pUndoRange), std::move(pUndoDB), + pOld, pNew ) ); + } + + rDocShell.PostPaint(ScRange(0, 0, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab), + PaintPartFlags::Grid | PaintPartFlags::Left | PaintPartFlags::Top | PaintPartFlags::Size); + bDone = true; + } + else if (!bApi) // "Don't execute any operations" + rDocShell.ErrorMessage(STR_MSSG_REPEATDB_0); + } + + return bDone; +} + +bool ScDBDocFunc::Sort( SCTAB nTab, const ScSortParam& rSortParam, + bool bRecord, bool bPaint, bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + + ScDocument& rDoc = rDocShell.GetDocument(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + ScDBData* pDBData = rDoc.GetDBAtArea( nTab, rSortParam.nCol1, rSortParam.nRow1, + rSortParam.nCol2, rSortParam.nRow2 ); + if (!pDBData) + { + OSL_FAIL( "Sort: no DBData" ); + return false; + } + + bool bCopy = !rSortParam.bInplace; + if ( bCopy && rSortParam.nDestCol == rSortParam.nCol1 && + rSortParam.nDestRow == rSortParam.nRow1 && rSortParam.nDestTab == nTab ) + bCopy = false; + + ScSortParam aLocalParam( rSortParam ); + if ( bCopy ) + { + // Copy the data range to the destination then move the sort range to it. + ScRange aSrcRange(rSortParam.nCol1, rSortParam.nRow1, nTab, rSortParam.nCol2, rSortParam.nRow2, nTab); + ScAddress aDestPos(rSortParam.nDestCol,rSortParam.nDestRow,rSortParam.nDestTab); + + ScDocFunc& rDocFunc = rDocShell.GetDocFunc(); + bool bRet = rDocFunc.MoveBlock(aSrcRange, aDestPos, false, bRecord, bPaint, bApi); + + if (!bRet) + return false; + + aLocalParam.MoveToDest(); + nTab = aLocalParam.nDestTab; + } + + // tdf#119804: If there is a header row/column, it won't be affected by + // sorting; so we can exclude it from the test. + SCROW nStartingRowToEdit = aLocalParam.nRow1; + SCCOL nStartingColToEdit = aLocalParam.nCol1; + if ( aLocalParam.bHasHeader ) + { + if ( aLocalParam.bByRow ) + nStartingRowToEdit++; + else + nStartingColToEdit++; + } + ScEditableTester aTester( rDoc, nTab, nStartingColToEdit, nStartingRowToEdit, + aLocalParam.nCol2, aLocalParam.nRow2, true /*bNoMatrixAtAll*/ ); + if (!aTester.IsEditable()) + { + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + return false; + } + + const ScInputOptions aInputOption = SC_MOD()->GetInputOptions(); + const bool bUpdateRefs = aInputOption.GetSortRefUpdate(); + + // Adjust aLocalParam cols/rows to used data area. Keep sticky top row or + // column (depending on direction) in any case, not just if it has headers, + // so empty leading cells will be sorted to the end. + // aLocalParam.nCol/Row will encompass data content only, extras in + // aLocalParam.aDataAreaExtras. + bool bShrunk = false; + aLocalParam.aDataAreaExtras.resetArea(); + rDoc.ShrinkToUsedDataArea(bShrunk, nTab, aLocalParam.nCol1, aLocalParam.nRow1, + aLocalParam.nCol2, aLocalParam.nRow2, false, aLocalParam.bByRow, + !aLocalParam.bByRow, + (aLocalParam.aDataAreaExtras.anyExtrasWanted() ? + &aLocalParam.aDataAreaExtras : nullptr)); + + SCROW nStartRow = aLocalParam.nRow1; + if (aLocalParam.bByRow && aLocalParam.bHasHeader && nStartRow < aLocalParam.nRow2) + ++nStartRow; + + SCCOL nOverallCol1 = aLocalParam.nCol1; + SCROW nOverallRow1 = aLocalParam.nRow1; + SCCOL nOverallCol2 = aLocalParam.nCol2; + SCROW nOverallRow2 = aLocalParam.nRow2; + if (aLocalParam.aDataAreaExtras.anyExtrasWanted()) + { + // Trailing empty excess columns/rows are excluded from being sorted, + // they stick at the end. Clip them. + const ScDataAreaExtras::Clip eClip = (aLocalParam.bByRow ? + ScDataAreaExtras::Clip::Row : ScDataAreaExtras::Clip::Col); + aLocalParam.aDataAreaExtras.GetOverallRange( nOverallCol1, nOverallRow1, nOverallCol2, nOverallRow2, eClip); + // Make it permanent. + aLocalParam.aDataAreaExtras.SetOverallRange( nOverallCol1, nOverallRow1, nOverallCol2, nOverallRow2); + + if (bUpdateRefs) + { + // With update references the entire range needs to be handled as + // one entity for references pointing within to be moved along, + // even when there's no data content. For huge ranges we may be + // DOOMed then. + aLocalParam.nCol1 = nOverallCol1; + aLocalParam.nRow1 = nOverallRow1; + aLocalParam.nCol2 = nOverallCol2; + aLocalParam.nRow2 = nOverallRow2; + } + } + + if (aLocalParam.aDataAreaExtras.mbCellFormats + && rDoc.HasAttrib( nOverallCol1, nStartRow, nTab, nOverallCol2, nOverallRow2, nTab, + HasAttrFlags::Merged | HasAttrFlags::Overlapped)) + { + // Merge attributes would be mixed up during sorting. + if (!bApi) + rDocShell.ErrorMessage(STR_SORT_ERR_MERGED); + return false; + } + + // execute + + weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() ); + + // Calculate the script types for all cells in the sort range beforehand. + // This will speed up the row height adjustment that takes place after the + // sort. + rDoc.UpdateScriptTypes( + ScAddress(aLocalParam.nCol1,nStartRow,nTab), + aLocalParam.nCol2-aLocalParam.nCol1+1, + aLocalParam.nRow2-nStartRow+1); + + // No point adjusting row heights after the sort when all rows have the same height. + bool bUniformRowHeight = rDoc.HasUniformRowHeight(nTab, nStartRow, nOverallRow2); + + bool bRepeatQuery = false; // repeat existing filter? + ScQueryParam aQueryParam; + pDBData->GetQueryParam( aQueryParam ); + if ( aQueryParam.GetEntry(0).bDoQuery ) + bRepeatQuery = true; + + sc::ReorderParam aUndoParam; + + // don't call ScDocument::Sort with an empty SortParam (may be empty here if bCopy is set) + if (aLocalParam.GetSortKeyCount() && aLocalParam.maKeyState[0].bDoSort) + { + ScProgress aProgress(&rDocShell, ScResId(STR_PROGRESS_SORTING), 0, true); + if (!bRepeatQuery) + bRepeatQuery = rDoc.HasHiddenRows(aLocalParam.nRow1, aLocalParam.nRow2, nTab); + rDoc.Sort(nTab, aLocalParam, bRepeatQuery, bUpdateRefs, &aProgress, &aUndoParam); + } + + if (bRecord) + { + // Set up an undo object. + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<sc::UndoSort>(&rDocShell, aUndoParam)); + } + + pDBData->SetSortParam(rSortParam); + // Remember additional settings on anonymous database ranges. + if (pDBData == rDoc.GetAnonymousDBData( nTab) || rDoc.GetDBCollection()->getAnonDBs().has( pDBData)) + pDBData->UpdateFromSortParam( rSortParam); + + if (comphelper::LibreOfficeKit::isActive()) + { + SfxViewShell* pSomeViewForThisDoc = rDocShell.GetBestViewShell(false); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(pViewShell); + if (pTabViewShell && pTabViewShell->GetDocId() == pSomeViewForThisDoc->GetDocId()) + { + if (ScPositionHelper* pPosHelper = pTabViewShell->GetViewData().GetLOKHeightHelper(nTab)) + pPosHelper->invalidateByIndex(nStartRow); + } + pViewShell = SfxViewShell::GetNext(*pViewShell); + } + + ScTabViewShell::notifyAllViewsSheetGeomInvalidation( + pSomeViewForThisDoc, false /* bColumns */, true /* bRows */, true /* bSizes*/, + true /* bHidden */, true /* bFiltered */, true /* bGroups */, nTab); + } + + if (nStartRow <= aLocalParam.nRow2) + { + ScRange aDirtyRange( + aLocalParam.nCol1, nStartRow, nTab, + aLocalParam.nCol2, aLocalParam.nRow2, nTab); + rDoc.SetDirty( aDirtyRange, true ); + } + + if (bPaint) + { + PaintPartFlags nPaint = PaintPartFlags::Grid; + SCCOL nStartX = nOverallCol1; + SCROW nStartY = nOverallRow1; + SCCOL nEndX = nOverallCol2; + SCROW nEndY = nOverallRow2; + if ( bRepeatQuery ) + { + nPaint |= PaintPartFlags::Left; + nStartX = 0; + nEndX = rDoc.MaxCol(); + } + rDocShell.PostPaint(ScRange(nStartX, nStartY, nTab, nEndX, nEndY, nTab), nPaint); + } + + if (!bUniformRowHeight && nStartRow <= nOverallRow2) + rDocShell.AdjustRowHeight(nStartRow, nOverallRow2, nTab); + + aModificator.SetDocumentModified(); + + return true; +} + +bool ScDBDocFunc::Query( SCTAB nTab, const ScQueryParam& rQueryParam, + const ScRange* pAdvSource, bool bRecord, bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + + ScDocument& rDoc = rDocShell.GetDocument(); + + ScTabViewShell* pViewSh = rDocShell.GetBestViewShell(); + if (pViewSh && ScTabViewShell::isAnyEditViewInRange(pViewSh, /*bColumns*/ false, rQueryParam.nRow1, rQueryParam.nRow2)) + { + return false; + } + + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + ScDBData* pDBData = rDoc.GetDBAtArea( nTab, rQueryParam.nCol1, rQueryParam.nRow1, + rQueryParam.nCol2, rQueryParam.nRow2 ); + if (!pDBData) + { + OSL_FAIL( "Query: no DBData" ); + return false; + } + + // Change from Inplace to non-Inplace, only then cancel Inplace: + // (only if "Persistent" is selected in the dialog) + + if ( !rQueryParam.bInplace && pDBData->HasQueryParam() && rQueryParam.bDestPers ) + { + ScQueryParam aOldQuery; + pDBData->GetQueryParam(aOldQuery); + if (aOldQuery.bInplace) + { + // cancel old filtering + + SCSIZE nEC = aOldQuery.GetEntryCount(); + for (SCSIZE i=0; i<nEC; i++) + aOldQuery.GetEntry(i).bDoQuery = false; + aOldQuery.bDuplicate = true; + Query( nTab, aOldQuery, nullptr, bRecord, bApi ); + } + } + + ScQueryParam aLocalParam( rQueryParam ); // for Paint / destination range + bool bCopy = !rQueryParam.bInplace; // copied in Table::Query + ScDBData* pDestData = nullptr; // range to be copied to + bool bDoSize = false; // adjust destination size (insert/delete) + SCCOL nFormulaCols = 0; // only at bDoSize + bool bKeepFmt = false; + ScRange aOldDest; + ScRange aDestTotal; + if ( bCopy && rQueryParam.nDestCol == rQueryParam.nCol1 && + rQueryParam.nDestRow == rQueryParam.nRow1 && rQueryParam.nDestTab == nTab ) + bCopy = false; + SCTAB nDestTab = nTab; + if ( bCopy ) + { + aLocalParam.MoveToDest(); + nDestTab = rQueryParam.nDestTab; + if ( !rDoc.ValidColRow( aLocalParam.nCol2, aLocalParam.nRow2 ) ) + { + if (!bApi) + rDocShell.ErrorMessage(STR_PASTE_FULL); + return false; + } + + ScEditableTester aTester( rDoc, nDestTab, aLocalParam.nCol1,aLocalParam.nRow1, + aLocalParam.nCol2,aLocalParam.nRow2); + if (!aTester.IsEditable()) + { + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + return false; + } + + pDestData = rDoc.GetDBAtCursor( rQueryParam.nDestCol, rQueryParam.nDestRow, + rQueryParam.nDestTab, ScDBDataPortion::TOP_LEFT ); + if (pDestData) + { + pDestData->GetArea( aOldDest ); + aDestTotal=ScRange( rQueryParam.nDestCol, + rQueryParam.nDestRow, + nDestTab, + rQueryParam.nDestCol + rQueryParam.nCol2 - rQueryParam.nCol1, + rQueryParam.nDestRow + rQueryParam.nRow2 - rQueryParam.nRow1, + nDestTab ); + + bDoSize = pDestData->IsDoSize(); + // test if formulas need to be filled in (nFormulaCols): + if ( bDoSize && aOldDest.aEnd.Col() == aDestTotal.aEnd.Col() ) + { + SCCOL nTestCol = aOldDest.aEnd.Col() + 1; // next to the range + SCROW nTestRow = rQueryParam.nDestRow + + ( aLocalParam.bHasHeader ? 1 : 0 ); + while ( nTestCol <= rDoc.MaxCol() && + rDoc.GetCellType(ScAddress( nTestCol, nTestRow, nTab )) == CELLTYPE_FORMULA ) + { + ++nTestCol; + ++nFormulaCols; + } + } + + bKeepFmt = pDestData->IsKeepFmt(); + if ( bDoSize && !rDoc.CanFitBlock( aOldDest, aDestTotal ) ) + { + if (!bApi) + rDocShell.ErrorMessage(STR_MSSG_DOSUBTOTALS_2); // cannot insert rows + return false; + } + } + } + + // execute + + weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() ); + + bool bKeepSub = false; // repeat existing partial results? + if (rQueryParam.GetEntry(0).bDoQuery) // not at cancellation + { + ScSubTotalParam aSubTotalParam; + pDBData->GetSubTotalParam( aSubTotalParam ); // partial results exist? + + if ( aSubTotalParam.bGroupActive[0] && !aSubTotalParam.bRemoveOnly ) + bKeepSub = true; + } + + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr<ScDBCollection> pUndoDB; + const ScRange* pOld = nullptr; + + if ( bRecord ) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + if (bCopy) + { + pUndoDoc->InitUndo( rDoc, nDestTab, nDestTab, false, true ); + rDoc.CopyToDocument(aLocalParam.nCol1, aLocalParam.nRow1, nDestTab, + aLocalParam.nCol2, aLocalParam.nRow2, nDestTab, + InsertDeleteFlags::ALL, false, *pUndoDoc); + // secure attributes in case they were copied along + + if (pDestData) + { + rDoc.CopyToDocument(aOldDest, InsertDeleteFlags::ALL, false, *pUndoDoc); + pOld = &aOldDest; + } + } + else + { + pUndoDoc->InitUndo( rDoc, nTab, nTab, false, true ); + rDoc.CopyToDocument(0, rQueryParam.nRow1, nTab, rDoc.MaxCol(), rQueryParam.nRow2, nTab, + InsertDeleteFlags::NONE, false, *pUndoDoc); + } + + ScDBCollection* pDocDB = rDoc.GetDBCollection(); + if (!pDocDB->empty()) + pUndoDB.reset(new ScDBCollection( *pDocDB )); + + rDoc.BeginDrawUndo(); + } + + std::unique_ptr<ScDocument> pAttribDoc; + ScRange aAttribRange; + if (pDestData) // delete destination range + { + if ( bKeepFmt ) + { + // smaller of the end columns, header+1 row + aAttribRange = aOldDest; + if ( aAttribRange.aEnd.Col() > aDestTotal.aEnd.Col() ) + aAttribRange.aEnd.SetCol( aDestTotal.aEnd.Col() ); + aAttribRange.aEnd.SetRow( aAttribRange.aStart.Row() + + ( aLocalParam.bHasHeader ? 1 : 0 ) ); + + // also for filled-in formulas + aAttribRange.aEnd.SetCol( aAttribRange.aEnd.Col() + nFormulaCols ); + + pAttribDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pAttribDoc->InitUndo( rDoc, nDestTab, nDestTab, false, true ); + rDoc.CopyToDocument(aAttribRange, InsertDeleteFlags::ATTRIB, false, *pAttribDoc); + } + + if ( bDoSize ) + rDoc.FitBlock( aOldDest, aDestTotal ); + else + rDoc.DeleteAreaTab(aOldDest, InsertDeleteFlags::ALL); // simply delete + } + + // execute filtering on the document + SCSIZE nCount = rDoc.Query( nTab, rQueryParam, bKeepSub ); + pDBData->CalcSaveFilteredCount( nCount ); + if (bCopy) + { + aLocalParam.nRow2 = aLocalParam.nRow1 + nCount; + if (!aLocalParam.bHasHeader && nCount > 0) + --aLocalParam.nRow2; + + if ( bDoSize ) + { + // adjust to the real result range + // (this here is always a reduction) + + ScRange aNewDest( aLocalParam.nCol1, aLocalParam.nRow1, nDestTab, + aLocalParam.nCol2, aLocalParam.nRow2, nDestTab ); + rDoc.FitBlock( aDestTotal, aNewDest, false ); // sal_False - don't delete + + if ( nFormulaCols > 0 ) + { + // fill in formulas + //! Undo (Query and Repeat) !!! + + ScRange aNewForm( aLocalParam.nCol2+1, aLocalParam.nRow1, nDestTab, + aLocalParam.nCol2+nFormulaCols, aLocalParam.nRow2, nDestTab ); + ScRange aOldForm = aNewForm; + aOldForm.aEnd.SetRow( aOldDest.aEnd.Row() ); + rDoc.FitBlock( aOldForm, aNewForm, false ); + + ScMarkData aMark(rDoc.GetSheetLimits()); + aMark.SelectOneTable(nDestTab); + SCROW nFStartY = aLocalParam.nRow1 + ( aLocalParam.bHasHeader ? 1 : 0 ); + + sal_uLong nProgCount = nFormulaCols; + nProgCount *= aLocalParam.nRow2 - nFStartY; + ScProgress aProgress( rDoc.GetDocumentShell(), + ScResId(STR_FILL_SERIES_PROGRESS), nProgCount, true ); + + rDoc.Fill( aLocalParam.nCol2+1, nFStartY, + aLocalParam.nCol2+nFormulaCols, nFStartY, &aProgress, aMark, + aLocalParam.nRow2 - nFStartY, + FILL_TO_BOTTOM, FILL_SIMPLE ); + } + } + + if ( pAttribDoc ) // copy back the memorized attributes + { + // Header + if (aLocalParam.bHasHeader) + { + ScRange aHdrRange = aAttribRange; + aHdrRange.aEnd.SetRow( aHdrRange.aStart.Row() ); + pAttribDoc->CopyToDocument(aHdrRange, InsertDeleteFlags::ATTRIB, false, rDoc); + } + + // Data + SCCOL nAttrEndCol = aAttribRange.aEnd.Col(); + SCROW nAttrRow = aAttribRange.aStart.Row() + ( aLocalParam.bHasHeader ? 1 : 0 ); + for (SCCOL nCol = aAttribRange.aStart.Col(); nCol<=nAttrEndCol; nCol++) + { + const ScPatternAttr* pSrcPattern = pAttribDoc->GetPattern( + nCol, nAttrRow, nDestTab ); + OSL_ENSURE(pSrcPattern,"Pattern is 0"); + if (pSrcPattern) + { + rDoc.ApplyPatternAreaTab( nCol, nAttrRow, nCol, aLocalParam.nRow2, + nDestTab, *pSrcPattern ); + const ScStyleSheet* pStyle = pSrcPattern->GetStyleSheet(); + if (pStyle) + rDoc.ApplyStyleAreaTab( nCol, nAttrRow, nCol, aLocalParam.nRow2, + nDestTab, *pStyle ); + } + } + } + } + + // saving: Inplace always, otherwise depending on setting + // old Inplace-Filter may have already been removed + + bool bSave = rQueryParam.bInplace || rQueryParam.bDestPers; + if (bSave) // memorize + { + pDBData->SetQueryParam( rQueryParam ); + pDBData->SetHeader( rQueryParam.bHasHeader ); //! ??? + pDBData->SetAdvancedQuerySource( pAdvSource ); // after SetQueryParam + } + + if (bCopy) // memorize new DB range + { + // Selection is done afterwards from outside (dbfunc). + // Currently through the DB area at the destination position, + // so a range must be created there in any case. + + ScDBData* pNewData; + if (pDestData) + pNewData = pDestData; // range exists -> adjust (always!) + else // create range + pNewData = rDocShell.GetDBData( + ScRange( aLocalParam.nCol1, aLocalParam.nRow1, nDestTab, + aLocalParam.nCol2, aLocalParam.nRow2, nDestTab ), + SC_DB_MAKE, ScGetDBSelection::ForceMark ); + + if (pNewData) + { + pNewData->SetArea( nDestTab, aLocalParam.nCol1, aLocalParam.nRow1, + aLocalParam.nCol2, aLocalParam.nRow2 ); + + // query parameter is no longer set at the destination, only leads to confusion + // and mistakes with the query parameter at the source range (#37187#) + } + else + { + OSL_FAIL("Target are not available"); + } + } + + if (!bCopy) + { + rDoc.InvalidatePageBreaks(nTab); + rDoc.UpdatePageBreaks( nTab ); + } + + // #i23299# Subtotal functions depend on cell's filtered states. + ScRange aDirtyRange(0 , aLocalParam.nRow1, nDestTab, rDoc.MaxCol(), aLocalParam.nRow2, nDestTab); + rDoc.SetSubTotalCellsDirty(aDirtyRange); + + if ( bRecord ) + { + // create undo action after executing, because of drawing layer undo + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoQuery>( &rDocShell, nTab, rQueryParam, std::move(pUndoDoc), std::move(pUndoDB), + pOld, bDoSize, pAdvSource ) ); + } + + if ( pViewSh ) + { + // could there be horizontal autofilter ? + // maybe it would be better to set bColumns to !rQueryParam.bByRow ? + // anyway at the beginning the value of bByRow is 'false' + // then after the first sort action it becomes 'true' + pViewSh->OnLOKShowHideColRow(/*bColumns*/ false, rQueryParam.nRow1 - 1); + } + + if (bCopy) + { + SCCOL nEndX = aLocalParam.nCol2; + SCROW nEndY = aLocalParam.nRow2; + if (pDestData) + { + if ( aOldDest.aEnd.Col() > nEndX ) + nEndX = aOldDest.aEnd.Col(); + if ( aOldDest.aEnd.Row() > nEndY ) + nEndY = aOldDest.aEnd.Row(); + } + if (bDoSize) + nEndY = rDoc.MaxRow(); + + // remove AutoFilter button flags + rDocShell.DBAreaDeleted(nDestTab, aLocalParam.nCol1, aLocalParam.nRow1, aLocalParam.nCol2); + + rDocShell.PostPaint( + ScRange(aLocalParam.nCol1, aLocalParam.nRow1, nDestTab, nEndX, nEndY, nDestTab), + PaintPartFlags::Grid); + } + else + rDocShell.PostPaint( + ScRange(0, rQueryParam.nRow1, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab), + PaintPartFlags::Grid | PaintPartFlags::Left); + aModificator.SetDocumentModified(); + + return true; +} + +void ScDBDocFunc::DoSubTotals( SCTAB nTab, const ScSubTotalParam& rParam, + bool bRecord, bool bApi ) +{ + //! use also for ScDBFunc::DoSubTotals ! + // then stays outside: + // - mark new range (from DBData) + // - SelectionChanged (?) + + bool bDo = !rParam.bRemoveOnly; // sal_False = only delete + + ScDocument& rDoc = rDocShell.GetDocument(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + ScDBData* pDBData = rDoc.GetDBAtArea( nTab, rParam.nCol1, rParam.nRow1, + rParam.nCol2, rParam.nRow2 ); + if (!pDBData) + { + OSL_FAIL( "SubTotals: no DBData" ); + return; + } + + ScEditableTester aTester( rDoc, nTab, 0,rParam.nRow1+1, rDoc.MaxCol(),rDoc.MaxRow() ); + if (!aTester.IsEditable()) + { + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + return; + } + + if (rDoc.HasAttrib( rParam.nCol1, rParam.nRow1+1, nTab, + rParam.nCol2, rParam.nRow2, nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped )) + { + if (!bApi) + rDocShell.ErrorMessage(STR_MSSG_INSERTCELLS_0); // don't insert into merged + return; + } + + bool bOk = true; + if (rParam.bReplace) + { + if (rDoc.TestRemoveSubTotals( nTab, rParam )) + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(), + VclMessageType::Question, + VclButtonsType::YesNo, ScResId(STR_MSSG_DOSUBTOTALS_1))); // "Delete Data?" + xBox->set_title(ScResId(STR_MSSG_DOSUBTOTALS_0)); // "StarCalc" + bOk = xBox->run() == RET_YES; + } + } + + if (!bOk) + return; + + weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() ); + ScDocShellModificator aModificator( rDocShell ); + + ScSubTotalParam aNewParam( rParam ); // end of range is being changed + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr<ScOutlineTable> pUndoTab; + std::unique_ptr<ScRangeName> pUndoRange; + std::unique_ptr<ScDBCollection> pUndoDB; + + if (bRecord) // secure old data + { + bool bOldFilter = bDo && rParam.bDoSort; + + SCTAB nTabCount = rDoc.GetTableCount(); + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab ); + if (pTable) + { + pUndoTab.reset(new ScOutlineTable( *pTable )); + + // column/row state + SCCOLROW nOutStartCol, nOutEndCol; + SCCOLROW nOutStartRow, nOutEndRow; + pTable->GetColArray().GetRange( nOutStartCol, nOutEndCol ); + pTable->GetRowArray().GetRange( nOutStartRow, nOutEndRow ); + + pUndoDoc->InitUndo( rDoc, nTab, nTab, true, true ); + rDoc.CopyToDocument(static_cast<SCCOL>(nOutStartCol), 0, nTab, static_cast<SCCOL>(nOutEndCol), rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, false, *pUndoDoc); + rDoc.CopyToDocument(0, nOutStartRow, nTab, rDoc.MaxCol(), nOutEndRow, nTab, InsertDeleteFlags::NONE, false, *pUndoDoc); + } + else + pUndoDoc->InitUndo( rDoc, nTab, nTab, false, bOldFilter ); + + // secure data range - incl. filtering result + rDoc.CopyToDocument(0, rParam.nRow1+1,nTab, rDoc.MaxCol(),rParam.nRow2,nTab, + InsertDeleteFlags::ALL, false, *pUndoDoc); + + // all formulas because of references + rDoc.CopyToDocument(0, 0, 0, rDoc.MaxCol(),rDoc.MaxRow(),nTabCount-1, + InsertDeleteFlags::FORMULA, false, *pUndoDoc); + + // ranges of DB and other + ScRangeName* pDocRange = rDoc.GetRangeName(); + if (!pDocRange->empty()) + pUndoRange.reset(new ScRangeName( *pDocRange )); + ScDBCollection* pDocDB = rDoc.GetDBCollection(); + if (!pDocDB->empty()) + pUndoDB.reset(new ScDBCollection( *pDocDB )); + } + +// rDoc.SetOutlineTable( nTab, NULL ); + ScOutlineTable* pOut = rDoc.GetOutlineTable( nTab ); + if (pOut) + pOut->GetRowArray().RemoveAll(); // only delete row outlines + + if (rParam.bReplace) + rDoc.RemoveSubTotals( nTab, aNewParam ); + bool bSuccess = true; + if (bDo) + { + // sort + if ( rParam.bDoSort ) + { + pDBData->SetArea( nTab, aNewParam.nCol1,aNewParam.nRow1, aNewParam.nCol2,aNewParam.nRow2 ); + + // set partial result field to before the sorting + // (Duplicates are omitted, so can be called again) + + ScSortParam aOldSort; + pDBData->GetSortParam( aOldSort ); + ScSortParam aSortParam( aNewParam, aOldSort ); + Sort( nTab, aSortParam, false, false, bApi ); + } + + bSuccess = rDoc.DoSubTotals( nTab, aNewParam ); + rDoc.SetDrawPageSize(nTab); + } + ScRange aDirtyRange( aNewParam.nCol1, aNewParam.nRow1, nTab, + aNewParam.nCol2, aNewParam.nRow2, nTab ); + rDoc.SetDirty( aDirtyRange, true ); + + if (bRecord) + { +// ScDBData* pUndoDBData = pDBData ? new ScDBData( *pDBData ) : NULL; + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoSubTotals>( &rDocShell, nTab, + rParam, aNewParam.nRow2, + std::move(pUndoDoc), std::move(pUndoTab), // pUndoDBData, + std::move(pUndoRange), std::move(pUndoDB) ) ); + } + + if (!bSuccess) + { + // "Cannot insert rows" + if (!bApi) + rDocShell.ErrorMessage(STR_MSSG_DOSUBTOTALS_2); + } + + // memorize + pDBData->SetSubTotalParam( aNewParam ); + pDBData->SetArea( nTab, aNewParam.nCol1,aNewParam.nRow1, aNewParam.nCol2,aNewParam.nRow2 ); + rDoc.CompileDBFormula(); + + rDocShell.PostPaint(ScRange(0, 0, nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab), + PaintPartFlags::Grid | PaintPartFlags::Left | PaintPartFlags::Top | PaintPartFlags::Size); + aModificator.SetDocumentModified(); +} + +namespace { + +bool lcl_EmptyExcept( ScDocument& rDoc, const ScRange& rRange, const ScRange& rExcept ) +{ + ScCellIterator aIter( rDoc, rRange ); + for (bool bHasCell = aIter.first(); bHasCell; bHasCell = aIter.next()) + { + if (!aIter.isEmpty()) // real content? + { + if (!rExcept.Contains(aIter.GetPos())) + return false; // cell found + } + } + + return true; // nothing found - empty +} + +bool isEditable(ScDocShell& rDocShell, const ScRangeList& rRanges, bool bApi) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + if (!rDocShell.IsEditable() || rDoc.GetChangeTrack()) + { + // not recorded -> disallow + if (!bApi) + rDocShell.ErrorMessage(STR_PROTECTIONERR); + + return false; + } + + for (size_t i = 0, n = rRanges.size(); i < n; ++i) + { + const ScRange & r = rRanges[i]; + ScEditableTester aTester(rDoc, r); + if (!aTester.IsEditable()) + { + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + + return false; + } + } + + return true; +} + +void createUndoDoc(ScDocumentUniquePtr& pUndoDoc, ScDocument& rDoc, const ScRange& rRange) +{ + SCTAB nTab = rRange.aStart.Tab(); + pUndoDoc.reset(new ScDocument(SCDOCMODE_UNDO)); + pUndoDoc->InitUndo(rDoc, nTab, nTab); + rDoc.CopyToDocument(rRange, InsertDeleteFlags::ALL, false, *pUndoDoc); +} + +bool checkNewOutputRange(ScDPObject& rDPObj, ScDocShell& rDocShell, ScRange& rNewOut, bool bApi) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + + bool bOverflow = false; + rNewOut = rDPObj.GetNewOutputRange(bOverflow); + + // Test for overlap with source data range. + // TODO: Check with other pivot tables as well. + const ScSheetSourceDesc* pSheetDesc = rDPObj.GetSheetDesc(); + if (pSheetDesc && pSheetDesc->GetSourceRange().Intersects(rNewOut)) + { + // New output range intersteps with the source data. Move it up to + // where the old range is and see if that works. + ScRange aOldRange = rDPObj.GetOutRange(); + SCROW nDiff = aOldRange.aStart.Row() - rNewOut.aStart.Row(); + rNewOut.aStart.SetRow(aOldRange.aStart.Row()); + rNewOut.aEnd.IncRow(nDiff); + if (!rDoc.ValidRow(rNewOut.aStart.Row()) || !rDoc.ValidRow(rNewOut.aEnd.Row())) + bOverflow = true; + } + + if (bOverflow) + { + if (!bApi) + rDocShell.ErrorMessage(STR_PIVOT_ERROR); + + return false; + } + + ScEditableTester aTester(rDoc, rNewOut); + if (!aTester.IsEditable()) + { + // destination area isn't editable + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + + return false; + } + + return true; +} + +} + +bool ScDBDocFunc::DataPilotUpdate( ScDPObject* pOldObj, const ScDPObject* pNewObj, + bool bRecord, bool bApi, bool bAllowMove ) +{ + if (!pOldObj) + { + if (!pNewObj) + return false; + + return CreatePivotTable(*pNewObj, bRecord, bApi); + } + + if (!pNewObj) + return RemovePivotTable(*pOldObj, bRecord, bApi); + + if (pOldObj == pNewObj) + return UpdatePivotTable(*pOldObj, bRecord, bApi); + + OSL_ASSERT(pOldObj && pNewObj && pOldObj != pNewObj); + + ScDocShellModificator aModificator( rDocShell ); + weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() ); + + ScRangeList aRanges; + aRanges.push_back(pOldObj->GetOutRange()); + aRanges.push_back(pNewObj->GetOutRange().aStart); // at least one cell in the output position must be editable. + if (!isEditable(rDocShell, aRanges, bApi)) + return false; + + ScDocumentUniquePtr pOldUndoDoc; + ScDocumentUniquePtr pNewUndoDoc; + + ScDPObject aUndoDPObj(*pOldObj); // for undo or revert on failure + + ScDocument& rDoc = rDocShell.GetDocument(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + if (bRecord) + createUndoDoc(pOldUndoDoc, rDoc, pOldObj->GetOutRange()); + + pNewObj->WriteSourceDataTo(*pOldObj); // copy source data + + ScDPSaveData* pData = pNewObj->GetSaveData(); + OSL_ENSURE( pData, "no SaveData from living DPObject" ); + if (pData) + pOldObj->SetSaveData(*pData); // copy SaveData + + pOldObj->SetAllowMove(bAllowMove); + pOldObj->ReloadGroupTableData(); + pOldObj->SyncAllDimensionMembers(); + pOldObj->InvalidateData(); // before getting the new output area + + // make sure the table has a name (not set by dialog) + if (pOldObj->GetName().isEmpty()) + pOldObj->SetName( rDoc.GetDPCollection()->CreateNewName() ); + + ScRange aNewOut; + if (!checkNewOutputRange(*pOldObj, rDocShell, aNewOut, bApi)) + { + *pOldObj = aUndoDPObj; + return false; + } + + // test if new output area is empty except for old area + if (!bApi) + { + // OutRange of pOldObj (pDestObj) is still old area + if (!lcl_EmptyExcept(rDoc, aNewOut, pOldObj->GetOutRange())) + { + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(), + VclMessageType::Question, VclButtonsType::YesNo, + ScResId(STR_PIVOT_NOTEMPTY))); + xQueryBox->set_default_response(RET_YES); + if (xQueryBox->run() == RET_NO) + { + //! like above (not editable) + *pOldObj = aUndoDPObj; + return false; + } + } + } + + if (bRecord) + createUndoDoc(pNewUndoDoc, rDoc, aNewOut); + + pOldObj->Output(aNewOut.aStart); + rDocShell.PostPaintGridAll(); //! only necessary parts + + if (bRecord) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDataPilot>( + &rDocShell, std::move(pOldUndoDoc), std::move(pNewUndoDoc), &aUndoDPObj, pOldObj, bAllowMove)); + } + + // notify API objects + rDoc.BroadcastUno( ScDataPilotModifiedHint(pOldObj->GetName()) ); + aModificator.SetDocumentModified(); + + return true; +} + +bool ScDBDocFunc::RemovePivotTable(const ScDPObject& rDPObj, bool bRecord, bool bApi) +{ + ScDocShellModificator aModificator(rDocShell); + weld::WaitObject aWait(ScDocShell::GetActiveDialogParent()); + + if (!isEditable(rDocShell, rDPObj.GetOutRange(), bApi)) + return false; + + ScDocument& rDoc = rDocShell.GetDocument(); + + if (!bApi) + { + // If we come from GUI - ask to delete the associated pivot charts too... + std::vector<SdrOle2Obj*> aListOfObjects = + sc::tools::getAllPivotChartsConnectedTo(rDPObj.GetName(), &rDocShell); + + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + + if (pModel && !aListOfObjects.empty()) + { + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(), + VclMessageType::Question, VclButtonsType::YesNo, + ScResId(STR_PIVOT_REMOVE_PIVOTCHART))); + xQueryBox->set_default_response(RET_YES); + if (xQueryBox->run() == RET_NO) + { + return false; + } + else + { + for (SdrOle2Obj* pChartObject : aListOfObjects) + { + rDoc.GetChartListenerCollection()->removeByName(pChartObject->GetName()); + pModel->AddUndo(std::make_unique<SdrUndoDelObj>(*pChartObject)); + pChartObject->getSdrPageFromSdrObject()->RemoveObject(pChartObject->GetOrdNum()); + } + } + } + } + + ScDocumentUniquePtr pOldUndoDoc; + std::unique_ptr<ScDPObject> pUndoDPObj; + + if (bRecord) + pUndoDPObj.reset(new ScDPObject(rDPObj)); // copy old settings for undo + + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + // delete table + + ScRange aRange = rDPObj.GetOutRange(); + SCTAB nTab = aRange.aStart.Tab(); + + if (bRecord) + createUndoDoc(pOldUndoDoc, rDoc, aRange); + + rDoc.DeleteAreaTab( aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), + nTab, InsertDeleteFlags::ALL ); + rDoc.RemoveFlagsTab( aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), + nTab, ScMF::Auto ); + + rDoc.GetDPCollection()->FreeTable(&rDPObj); // object is deleted here + + rDocShell.PostPaintGridAll(); //! only necessary parts + rDocShell.PostPaint(aRange, PaintPartFlags::Grid); + + if (bRecord) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDataPilot>( + &rDocShell, std::move(pOldUndoDoc), nullptr, pUndoDPObj.get(), nullptr, false)); + + // pUndoDPObj is copied + } + + aModificator.SetDocumentModified(); + return true; +} + +bool ScDBDocFunc::CreatePivotTable(const ScDPObject& rDPObj, bool bRecord, bool bApi) +{ + ScDocShellModificator aModificator(rDocShell); + weld::WaitObject aWait(ScDocShell::GetActiveDialogParent()); + + // At least one cell in the output range should be editable. Check in advance. + if (!isEditable(rDocShell, ScRange(rDPObj.GetOutRange().aStart), bApi)) + return false; + + ScDocumentUniquePtr pNewUndoDoc; + + ScDocument& rDoc = rDocShell.GetDocument(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + // output range must be set at pNewObj + std::unique_ptr<ScDPObject> pDestObj(new ScDPObject(rDPObj)); + + ScDPObject& rDestObj = *pDestObj; + + // #i94570# When changing the output position in the dialog, a new table is created + // with the settings from the old table, including the name. + // So we have to check for duplicate names here (before inserting). + if (rDoc.GetDPCollection()->GetByName(rDestObj.GetName())) + rDestObj.SetName(OUString()); // ignore the invalid name, create a new name below + + // Synchronize groups between linked tables + { + const ScDPDimensionSaveData* pGroups = nullptr; + bool bRefFound = rDoc.GetDPCollection()->GetReferenceGroups(rDestObj, &pGroups); + if (bRefFound) + { + ScDPSaveData* pSaveData = rDestObj.GetSaveData(); + if (pSaveData) + pSaveData->SetDimensionData(pGroups); + } + } + + rDoc.GetDPCollection()->InsertNewTable(std::move(pDestObj)); + + rDestObj.ReloadGroupTableData(); + rDestObj.SyncAllDimensionMembers(); + rDestObj.InvalidateData(); // before getting the new output area + + // make sure the table has a name (not set by dialog) + if (rDestObj.GetName().isEmpty()) + rDestObj.SetName(rDoc.GetDPCollection()->CreateNewName()); + + bool bOverflow = false; + ScRange aNewOut = rDestObj.GetNewOutputRange(bOverflow); + + if (bOverflow) + { + if (!bApi) + rDocShell.ErrorMessage(STR_PIVOT_ERROR); + + return false; + } + + { + ScEditableTester aTester(rDoc, aNewOut); + if (!aTester.IsEditable()) + { + // destination area isn't editable + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + + return false; + } + } + + // test if new output area is empty except for old area + if (!bApi) + { + bool bEmpty = rDoc.IsBlockEmpty( + aNewOut.aStart.Col(), aNewOut.aStart.Row(), + aNewOut.aEnd.Col(), aNewOut.aEnd.Row(), aNewOut.aStart.Tab() ); + + if (!bEmpty) + { + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(), + VclMessageType::Question, VclButtonsType::YesNo, + ScResId(STR_PIVOT_NOTEMPTY))); + xQueryBox->set_default_response(RET_YES); + if (xQueryBox->run() == RET_NO) + { + //! like above (not editable) + return false; + } + } + } + + if (bRecord) + createUndoDoc(pNewUndoDoc, rDoc, aNewOut); + + rDestObj.Output(aNewOut.aStart); + rDocShell.PostPaintGridAll(); //! only necessary parts + + if (bRecord) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDataPilot>(&rDocShell, nullptr, std::move(pNewUndoDoc), nullptr, &rDestObj, false)); + } + + // notify API objects + rDoc.BroadcastUno(ScDataPilotModifiedHint(rDestObj.GetName())); + aModificator.SetDocumentModified(); + + return true; +} + +bool ScDBDocFunc::UpdatePivotTable(ScDPObject& rDPObj, bool bRecord, bool bApi) +{ + ScDocShellModificator aModificator( rDocShell ); + weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() ); + + if (!isEditable(rDocShell, rDPObj.GetOutRange(), bApi)) + return false; + + ScDocumentUniquePtr pOldUndoDoc; + ScDocumentUniquePtr pNewUndoDoc; + + ScDPObject aUndoDPObj(rDPObj); // For undo or revert on failure. + + ScDocument& rDoc = rDocShell.GetDocument(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + if (bRecord) + createUndoDoc(pOldUndoDoc, rDoc, rDPObj.GetOutRange()); + + rDPObj.SetAllowMove(false); + rDPObj.ReloadGroupTableData(); + if (!rDPObj.SyncAllDimensionMembers()) + return false; + + rDPObj.InvalidateData(); // before getting the new output area + + // make sure the table has a name (not set by dialog) + if (rDPObj.GetName().isEmpty()) + rDPObj.SetName( rDoc.GetDPCollection()->CreateNewName() ); + + ScRange aNewOut; + if (!checkNewOutputRange(rDPObj, rDocShell, aNewOut, bApi)) + { + rDPObj = aUndoDPObj; + return false; + } + + // test if new output area is empty except for old area + if (!bApi) + { + if (!lcl_EmptyExcept(rDoc, aNewOut, rDPObj.GetOutRange())) + { + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(), + VclMessageType::Question, VclButtonsType::YesNo, + ScResId(STR_PIVOT_NOTEMPTY))); + xQueryBox->set_default_response(RET_YES); + if (xQueryBox->run() == RET_NO) + { + rDPObj = aUndoDPObj; + return false; + } + } + } + + if (bRecord) + createUndoDoc(pNewUndoDoc, rDoc, aNewOut); + + rDPObj.Output(aNewOut.aStart); + rDocShell.PostPaintGridAll(); //! only necessary parts + + if (bRecord) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDataPilot>( + &rDocShell, std::move(pOldUndoDoc), std::move(pNewUndoDoc), &aUndoDPObj, &rDPObj, false)); + } + + // notify API objects + rDoc.BroadcastUno( ScDataPilotModifiedHint(rDPObj.GetName()) ); + aModificator.SetDocumentModified(); + return true; +} + +void ScDBDocFunc::RefreshPivotTables(const ScDPObject* pDPObj, bool bApi) +{ + ScDPCollection* pDPs = rDocShell.GetDocument().GetDPCollection(); + if (!pDPs) + return; + + o3tl::sorted_vector<ScDPObject*> aRefs; + TranslateId pErrId = pDPs->ReloadCache(pDPObj, aRefs); + if (pErrId) + return; + + for (ScDPObject* pObj : aRefs) + { + // This action is intentionally not undoable since it modifies cache. + UpdatePivotTable(*pObj, false, bApi); + } +} + +void ScDBDocFunc::RefreshPivotTableGroups(ScDPObject* pDPObj) +{ + if (!pDPObj) + return; + + ScDPCollection* pDPs = rDocShell.GetDocument().GetDPCollection(); + if (!pDPs) + return; + + ScDPSaveData* pSaveData = pDPObj->GetSaveData(); + if (!pSaveData) + return; + + if (!pDPs->HasTable(pDPObj)) + { + // This table is under construction so no need for a whole update (UpdatePivotTable()). + pDPObj->ReloadGroupTableData(); + return; + } + + // Update all linked tables, if this table is part of the cache (ScDPCollection) + o3tl::sorted_vector<ScDPObject*> aRefs; + if (!pDPs->ReloadGroupsInCache(pDPObj, aRefs)) + return; + + // We allow pDimData being NULL. + const ScDPDimensionSaveData* pDimData = pSaveData->GetExistingDimensionData(); + for (ScDPObject* pObj : aRefs) + { + if (pObj != pDPObj) + { + pSaveData = pObj->GetSaveData(); + if (pSaveData) + pSaveData->SetDimensionData(pDimData); + } + + // This action is intentionally not undoable since it modifies cache. + UpdatePivotTable(*pObj, false, false); + } +} + +// database import + +void ScDBDocFunc::UpdateImport( const OUString& rTarget, const svx::ODataAccessDescriptor& rDescriptor ) +{ + // rTarget is the name of a database range + + ScDocument& rDoc = rDocShell.GetDocument(); + ScDBCollection& rDBColl = *rDoc.GetDBCollection(); + const ScDBData* pData = rDBColl.getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(rTarget)); + if (!pData) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(), + VclMessageType::Info, VclButtonsType::Ok, + ScResId(STR_TARGETNOTFOUND))); + xInfoBox->run(); + return; + } + + SCTAB nTab; + SCCOL nDummyCol; + SCROW nDummyRow; + pData->GetArea( nTab, nDummyCol,nDummyRow,nDummyCol,nDummyRow ); + + ScImportParam aImportParam; + pData->GetImportParam( aImportParam ); + + OUString sDBName; + OUString sDBTable; + sal_Int32 nCommandType = 0; + sDBName = rDescriptor.getDataSource(); + rDescriptor[svx::DataAccessDescriptorProperty::Command] >>= sDBTable; + rDescriptor[svx::DataAccessDescriptorProperty::CommandType] >>= nCommandType; + + aImportParam.aDBName = sDBName; + aImportParam.bSql = ( nCommandType == sdb::CommandType::COMMAND ); + aImportParam.aStatement = sDBTable; + aImportParam.bNative = false; + aImportParam.nType = static_cast<sal_uInt8>( ( nCommandType == sdb::CommandType::QUERY ) ? ScDbQuery : ScDbTable ); + aImportParam.bImport = true; + + bool bContinue = DoImport( nTab, aImportParam, &rDescriptor ); + + // repeat DB operations + + ScTabViewShell* pViewSh = rDocShell.GetBestViewShell(); + if (!pViewSh) + return; + + ScRange aRange; + pData->GetArea(aRange); + pViewSh->MarkRange(aRange); // select + + if ( bContinue ) // error at import -> abort + { + // internal operations, if some are saved + + if ( pData->HasQueryParam() || pData->HasSortParam() || pData->HasSubTotalParam() ) + pViewSh->RepeatDB(); + + // pivot tables which have the range as source data + + rDocShell.RefreshPivotTables(aRange); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/dbdocimp.cxx b/sc/source/ui/docshell/dbdocimp.cxx new file mode 100644 index 000000000..b7a0f95c9 --- /dev/null +++ b/sc/source/ui/docshell/dbdocimp.cxx @@ -0,0 +1,628 @@ +/* -*- 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 <vcl/errinf.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/types.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <svx/dataaccessdescriptor.hxx> +#include <sfx2/viewfrm.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/diagnose_ex.h> + +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/sdb/XCompletedExecution.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/sdbc/XRowSet.hpp> +#include <com/sun/star/sdbc/XResultSetMetaDataSupplier.hpp> +#include <com/sun/star/sdbcx/XRowLocate.hpp> +#include <com/sun/star/task/InteractionHandler.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/frame/FrameSearchFlag.hpp> +#include <com/sun/star/view/XSelectionSupplier.hpp> + +#include <dbdocfun.hxx> +#include <docsh.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <scerrors.hxx> +#include <dbdata.hxx> +#include <markdata.hxx> +#include <undodat.hxx> +#include <progress.hxx> +#include <patattr.hxx> +#include <docpool.hxx> +#include <attrib.hxx> +#include <dbdocutl.hxx> +#include <editable.hxx> +#include <hints.hxx> +#include <miscuno.hxx> +#include <chgtrack.hxx> +#include <refupdatecontext.hxx> + +using namespace com::sun::star; + +constexpr OUStringLiteral SC_SERVICE_ROWSET = u"com.sun.star.sdb.RowSet"; + +//! move to a header file? +constexpr OUStringLiteral SC_DBPROP_DATASOURCENAME = u"DataSourceName"; +constexpr OUStringLiteral SC_DBPROP_COMMAND = u"Command"; +constexpr OUStringLiteral SC_DBPROP_COMMANDTYPE = u"CommandType"; + +void ScDBDocFunc::ShowInBeamer( const ScImportParam& rParam, const SfxViewFrame* pFrame ) +{ + // called after opening the database beamer + + if ( !pFrame || !rParam.bImport ) + return; + + uno::Reference<frame::XFrame> xFrame = pFrame->GetFrame().GetFrameInterface(); + + uno::Reference<frame::XFrame> xBeamerFrame = xFrame->findFrame( + "_beamer", + frame::FrameSearchFlag::CHILDREN); + if (!xBeamerFrame.is()) + return; + + uno::Reference<frame::XController> xController = xBeamerFrame->getController(); + uno::Reference<view::XSelectionSupplier> xControllerSelection(xController, uno::UNO_QUERY); + if (xControllerSelection.is()) + { + sal_Int32 nType = rParam.bSql ? sdb::CommandType::COMMAND : + ( (rParam.nType == ScDbQuery) ? sdb::CommandType::QUERY : + sdb::CommandType::TABLE ); + + svx::ODataAccessDescriptor aSelection; + aSelection.setDataSource(rParam.aDBName); + aSelection[svx::DataAccessDescriptorProperty::Command] <<= rParam.aStatement; + aSelection[svx::DataAccessDescriptorProperty::CommandType] <<= nType; + + xControllerSelection->select(uno::Any(aSelection.createPropertyValueSequence())); + } + else + { + OSL_FAIL("no selection supplier in the beamer!"); + } +} + +void ScDBDocFunc::DoImportUno( const ScAddress& rPos, + const uno::Sequence<beans::PropertyValue>& aArgs ) +{ + svx::ODataAccessDescriptor aDesc( aArgs ); // includes selection and result set + + // create database range + ScDBData* pDBData = rDocShell.GetDBData( ScRange(rPos), SC_DB_IMPORT, ScGetDBSelection::Keep ); + DBG_ASSERT(pDBData, "can't create DB data"); + OUString sTarget = pDBData->GetName(); + + UpdateImport( sTarget, aDesc ); +} + +bool ScDBDocFunc::DoImport( SCTAB nTab, const ScImportParam& rParam, + const svx::ODataAccessDescriptor* pDescriptor ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + ScChangeTrack *pChangeTrack = nullptr; + ScRange aChangedRange; + + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + + ScDBData* pDBData = rDoc.GetDBAtArea( nTab, rParam.nCol1, rParam.nRow1, + rParam.nCol2, rParam.nRow2 ); + if (!pDBData) + { + OSL_FAIL( "DoImport: no DBData" ); + return false; + } + + std::unique_ptr<weld::WaitObject> xWaitWin(new weld::WaitObject(ScDocShell::GetActiveDialogParent())); + ScDocShellModificator aModificator( rDocShell ); + + bool bSuccess = false; + bool bTruncated = false; // for warning + TranslateId pErrStringId; + OUString aErrorMessage; + + SCCOL nCol = rParam.nCol1; + SCROW nRow = rParam.nRow1; + SCCOL nEndCol = nCol; // end of resulting database area + SCROW nEndRow = nRow; + + bool bDoSelection = false; + bool bRealSelection = false; // sal_True if not everything is selected + bool bBookmarkSelection = false; + sal_Int32 nListPos = 0; + sal_Int32 nRowsRead = 0; + sal_Int32 nListCount = 0; + + uno::Sequence<uno::Any> aSelection; + if ( pDescriptor && pDescriptor->has(svx::DataAccessDescriptorProperty::Selection) ) + { + (*pDescriptor)[svx::DataAccessDescriptorProperty::Selection] >>= aSelection; + nListCount = aSelection.getLength(); + if ( nListCount > 0 ) + { + bDoSelection = true; + if ( pDescriptor->has(svx::DataAccessDescriptorProperty::BookmarkSelection) ) + bBookmarkSelection = ScUnoHelpFunctions::GetBoolFromAny( (*pDescriptor)[svx::DataAccessDescriptorProperty::BookmarkSelection] ); + if ( bBookmarkSelection ) + { + // From bookmarks, there's no way to detect if all records are selected. + // Rely on base to pass no selection in that case. + bRealSelection = true; + } + } + } + + uno::Reference<sdbc::XResultSet> xResultSet; + if ( pDescriptor && pDescriptor->has(svx::DataAccessDescriptorProperty::Cursor) ) + xResultSet.set((*pDescriptor)[svx::DataAccessDescriptorProperty::Cursor], uno::UNO_QUERY); + + // ImportDoc - also used for Redo + ScDocumentUniquePtr pImportDoc(new ScDocument( SCDOCMODE_UNDO )); + pImportDoc->InitUndo( rDoc, nTab, nTab ); + + // get data from database into import document + + try + { + // progress bar + // only text (title is still needed, for the cancel button) + ScProgress aProgress( &rDocShell, ScResId(STR_UNDO_IMPORTDATA), 0, true ); + + uno::Reference<sdbc::XRowSet> xRowSet( xResultSet, uno::UNO_QUERY ); + bool bDispose = false; + if ( !xRowSet.is() ) + { + bDispose = true; + xRowSet.set(comphelper::getProcessServiceFactory()->createInstance( + SC_SERVICE_ROWSET ), + uno::UNO_QUERY); + uno::Reference<beans::XPropertySet> xRowProp( xRowSet, uno::UNO_QUERY ); + OSL_ENSURE( xRowProp.is(), "can't get RowSet" ); + if ( xRowProp.is() ) + { + + // set source parameters + + sal_Int32 nType = rParam.bSql ? sdb::CommandType::COMMAND : + ( (rParam.nType == ScDbQuery) ? sdb::CommandType::QUERY : + sdb::CommandType::TABLE ); + + xRowProp->setPropertyValue( SC_DBPROP_DATASOURCENAME, uno::Any(rParam.aDBName) ); + + xRowProp->setPropertyValue( SC_DBPROP_COMMAND, uno::Any(rParam.aStatement) ); + + xRowProp->setPropertyValue( SC_DBPROP_COMMANDTYPE, uno::Any(nType) ); + + uno::Reference<sdb::XCompletedExecution> xExecute( xRowSet, uno::UNO_QUERY ); + if ( xExecute.is() ) + { + uno::Reference<task::XInteractionHandler> xHandler( + task::InteractionHandler::createWithParent(comphelper::getProcessComponentContext(), nullptr), + uno::UNO_QUERY_THROW); + xExecute->executeWithCompletion( xHandler ); + } + else + xRowSet->execute(); + } + } + if ( xRowSet.is() ) + { + + // get column descriptions + + sal_Int32 nColCount = 0; + uno::Reference<sdbc::XResultSetMetaData> xMeta; + uno::Reference<sdbc::XResultSetMetaDataSupplier> xMetaSupp( xRowSet, uno::UNO_QUERY ); + if ( xMetaSupp.is() ) + xMeta = xMetaSupp->getMetaData(); + if ( xMeta.is() ) + nColCount = xMeta->getColumnCount(); // this is the number of real columns + + if ( rParam.nCol1 + nColCount - 1 > rDoc.MaxCol() ) + { + nColCount = 0; + //! error message + } + + uno::Reference<sdbcx::XRowLocate> xLocate; + if ( bBookmarkSelection ) + { + xLocate.set( xRowSet, uno::UNO_QUERY ); + if ( !xLocate.is() ) + { + SAL_WARN( "sc.ui","can't get XRowLocate"); + bDoSelection = bRealSelection = bBookmarkSelection = false; + } + } + + uno::Reference<sdbc::XRow> xRow( xRowSet, uno::UNO_QUERY ); + if ( nColCount > 0 && xRow.is() ) + { + nEndCol = static_cast<SCCOL>( rParam.nCol1 + nColCount - 1 ); + + uno::Sequence<sal_Int32> aColTypes( nColCount ); // column types + uno::Sequence<sal_Bool> aColCurr( nColCount ); // currency flag is not in types + sal_Int32* pTypeArr = aColTypes.getArray(); + sal_Bool* pCurrArr = aColCurr.getArray(); + for (tools::Long i=0; i<nColCount; i++) + { + pTypeArr[i] = xMeta->getColumnType( i+1 ); + pCurrArr[i] = xMeta->isCurrency( i+1 ); + } + + // read column names + nCol = rParam.nCol1; + for (tools::Long i=0; i<nColCount; i++) + { + pImportDoc->SetString( nCol, nRow, nTab, + xMeta->getColumnLabel( i+1 ) ); + ++nCol; + } + ++nRow; + + bool bEnd = false; + if ( !bDoSelection ) + xRowSet->beforeFirst(); + sal_uInt16 nInserted = 0; + while ( !bEnd ) + { + // skip rows that are not selected + if ( !bDoSelection ) + { + bEnd = !xRowSet->next(); + if ( !bEnd ) + ++nRowsRead; + } + else + { + if (nListPos < nListCount) + { + if ( bBookmarkSelection ) + { + bEnd = !xLocate->moveToBookmark(aSelection[nListPos]); + } + else // use record numbers + { + sal_Int32 nNextRow = 0; + aSelection[nListPos] >>= nNextRow; + if ( nRowsRead+1 < nNextRow ) + bRealSelection = true; + nRowsRead = nNextRow; + bEnd = !xRowSet->absolute(nRowsRead); + } + ++nListPos; + } + else + { + if ( !bBookmarkSelection && xRowSet->next() ) + bRealSelection = true; // more data available but not used + bEnd = true; + } + } + + if ( !bEnd ) + { + if ( rDoc.ValidRow(nRow) ) + { + nCol = rParam.nCol1; + for (tools::Long i=0; i<nColCount; i++) + { + ScDatabaseDocUtil::PutData( *pImportDoc, nCol, nRow, nTab, + xRow, i+1, pTypeArr[i], pCurrArr[i] ); + ++nCol; + } + nEndRow = nRow; + ++nRow; + + // progress bar + + ++nInserted; + if (!(nInserted & 15)) + { + aProgress.SetState( 0 ); + } + } + else // past the end of the spreadsheet + { + bEnd = true; // don't continue + bTruncated = true; // warning flag + } + } + } + + bSuccess = true; + } + + if ( bDispose ) + ::comphelper::disposeComponent( xRowSet ); + } + } + catch ( const sdbc::SQLException& rError ) + { + aErrorMessage = rError.Message; + } + catch ( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "sc", "Unexpected exception in database"); + } + + // test for cell protection + + bool bKeepFormat = pDBData->IsKeepFmt(); + bool bMoveCells = pDBData->IsDoSize(); + SCCOL nFormulaCols = 0; // columns to be filled with formulas + if (bMoveCells && nEndCol == rParam.nCol2) + { + // if column count changes, formulas would become invalid anyway + // -> only set nFormulaCols for unchanged column count + + SCCOL nTestCol = rParam.nCol2 + 1; // right of the data + SCROW nTestRow = rParam.nRow1 + 1; // below the title row + while ( nTestCol <= rDoc.MaxCol() && + rDoc.GetCellType(ScAddress( nTestCol, nTestRow, nTab )) == CELLTYPE_FORMULA ) + { + ++nTestCol; + ++nFormulaCols; + } + } + + if (bSuccess) + { + // old and new range editable? + ScEditableTester aTester; + aTester.TestBlock( rDoc, nTab, rParam.nCol1,rParam.nRow1,rParam.nCol2,rParam.nRow2 ); + aTester.TestBlock( rDoc, nTab, rParam.nCol1,rParam.nRow1,nEndCol,nEndRow ); + if ( !aTester.IsEditable() ) + { + pErrStringId = aTester.GetMessageId(); + bSuccess = false; + } + else if ( (pChangeTrack = rDoc.GetChangeTrack()) != nullptr ) + aChangedRange = ScRange(rParam.nCol1, rParam.nRow1, nTab, + nEndCol+nFormulaCols, nEndRow, nTab ); + } + + if ( bSuccess && bMoveCells ) + { + ScRange aOld( rParam.nCol1, rParam.nRow1, nTab, + rParam.nCol2+nFormulaCols, rParam.nRow2, nTab ); + ScRange aNew( rParam.nCol1, rParam.nRow1, nTab, + nEndCol+nFormulaCols, nEndRow, nTab ); + if (!rDoc.CanFitBlock( aOld, aNew )) + { + pErrStringId = STR_MSSG_DOSUBTOTALS_2; // can't insert cells + bSuccess = false; + } + } + + // copy data from import doc into real document + + if ( bSuccess ) + { + if (bKeepFormat) + { + // keep formatting of title and first data row from the document + // CopyToDocument also copies styles, Apply... needs separate calls + + SCCOL nMinEndCol = std::min( rParam.nCol2, nEndCol ); // not too much + nMinEndCol = sal::static_int_cast<SCCOL>( nMinEndCol + nFormulaCols ); // only if column count unchanged + pImportDoc->DeleteAreaTab( 0,0, rDoc.MaxCol(),rDoc.MaxRow(), nTab, InsertDeleteFlags::ATTRIB ); + rDoc.CopyToDocument(rParam.nCol1, rParam.nRow1, nTab, + nMinEndCol, rParam.nRow1, nTab, + InsertDeleteFlags::ATTRIB, false, *pImportDoc); + + SCROW nDataStartRow = rParam.nRow1+1; + for (SCCOL nCopyCol=rParam.nCol1; nCopyCol<=nMinEndCol; nCopyCol++) + { + const ScPatternAttr* pSrcPattern = rDoc.GetPattern( + nCopyCol, nDataStartRow, nTab ); + pImportDoc->ApplyPatternAreaTab( nCopyCol, nDataStartRow, nCopyCol, nEndRow, + nTab, *pSrcPattern ); + const ScStyleSheet* pStyle = pSrcPattern->GetStyleSheet(); + if (pStyle) + pImportDoc->ApplyStyleAreaTab( nCopyCol, nDataStartRow, nCopyCol, nEndRow, + nTab, *pStyle ); + } + } + + // don't set cell protection attribute if table is protected + if (rDoc.IsTabProtected(nTab)) + { + ScPatternAttr aPattern(pImportDoc->GetPool()); + aPattern.GetItemSet().Put( ScProtectionAttr( false,false,false,false ) ); + pImportDoc->ApplyPatternAreaTab( 0,0,rDoc.MaxCol(),rDoc.MaxRow(), nTab, aPattern ); + } + + // copy old data for undo + + SCCOL nUndoEndCol = std::max( nEndCol, rParam.nCol2 ); // rParam = old end + SCROW nUndoEndRow = std::max( nEndRow, rParam.nRow2 ); + + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr<ScDBData> pUndoDBData; + if ( bRecord ) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nTab, nTab ); + + pUndoDBData.reset(new ScDBData( *pDBData )); + } + + ScMarkData aNewMark(rDoc.GetSheetLimits()); + aNewMark.SelectOneTable( nTab ); + + if (bRecord) + { + // do not touch notes (ScUndoImportData does not support drawing undo) + InsertDeleteFlags nCopyFlags = InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE; + + // nFormulaCols is set only if column count is unchanged + rDoc.CopyToDocument(rParam.nCol1, rParam.nRow1, nTab, + nEndCol+nFormulaCols, nEndRow, nTab, + nCopyFlags, false, *pUndoDoc); + if ( rParam.nCol2 > nEndCol ) + rDoc.CopyToDocument(nEndCol+1, rParam.nRow1, nTab, + nUndoEndCol, nUndoEndRow, nTab, + nCopyFlags, false, *pUndoDoc); + if ( rParam.nRow2 > nEndRow ) + rDoc.CopyToDocument(rParam.nCol1, nEndRow+1, nTab, + nUndoEndCol+nFormulaCols, nUndoEndRow, nTab, + nCopyFlags, false, *pUndoDoc); + } + + // move new data + + if (bMoveCells) + { + // clear only the range without the formulas, + // so the formula title and first row are preserved + + ScRange aDelRange( rParam.nCol1, rParam.nRow1, nTab, + rParam.nCol2, rParam.nRow2, nTab ); + rDoc.DeleteAreaTab( aDelRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE ); // without the formulas + + ScRange aOld( rParam.nCol1, rParam.nRow1, nTab, + rParam.nCol2+nFormulaCols, rParam.nRow2, nTab ); + ScRange aNew( rParam.nCol1, rParam.nRow1, nTab, + nEndCol+nFormulaCols, nEndRow, nTab ); + rDoc.FitBlock( aOld, aNew, false ); // Do not delete formulas + } + else if ( nEndCol < rParam.nCol2 ) // DeleteArea calls PutInOrder + rDoc.DeleteArea( nEndCol+1, rParam.nRow1, rParam.nCol2, rParam.nRow2, + aNewMark, InsertDeleteFlags::CONTENTS & ~InsertDeleteFlags::NOTE ); + + // CopyToDocument doesn't remove contents + rDoc.DeleteAreaTab( rParam.nCol1, rParam.nRow1, nEndCol, nEndRow, nTab, InsertDeleteFlags::CONTENTS & ~InsertDeleteFlags::NOTE ); + + // remove each column from ImportDoc after copying to reduce memory usage + bool bOldAutoCalc = rDoc.GetAutoCalc(); + rDoc.SetAutoCalc( false ); // outside of the loop + for (SCCOL nCopyCol = rParam.nCol1; nCopyCol <= nEndCol; nCopyCol++) + { + pImportDoc->CopyToDocument(nCopyCol, rParam.nRow1, nTab, nCopyCol, nEndRow, nTab, + InsertDeleteFlags::ALL, false, rDoc); + pImportDoc->DeleteAreaTab( nCopyCol, rParam.nRow1, nCopyCol, nEndRow, nTab, InsertDeleteFlags::CONTENTS ); + } + rDoc.SetAutoCalc( bOldAutoCalc ); + + if (nFormulaCols > 0) // copy formulas + { + if (bKeepFormat) // formats for formulas + pImportDoc->CopyToDocument(nEndCol+1, rParam.nRow1, nTab, + nEndCol+nFormulaCols, nEndRow, nTab, + InsertDeleteFlags::ATTRIB, false, rDoc); + // fill formulas + ScMarkData aMark(rDoc.GetSheetLimits()); + aMark.SelectOneTable(nTab); + + sal_uLong nProgCount = nFormulaCols; + nProgCount *= nEndRow-rParam.nRow1-1; + ScProgress aProgress( rDoc.GetDocumentShell(), + ScResId(STR_FILL_SERIES_PROGRESS), nProgCount, true ); + + rDoc.Fill( nEndCol+1, rParam.nRow1+1, nEndCol+nFormulaCols, rParam.nRow1+1, + &aProgress, aMark, nEndRow-rParam.nRow1-1, FILL_TO_BOTTOM, FILL_SIMPLE ); + } + + // if new range is smaller, clear old contents + + if (!bMoveCells) // move has happened above + { + if ( rParam.nCol2 > nEndCol ) + rDoc.DeleteArea( nEndCol+1, rParam.nRow1, rParam.nCol2, rParam.nRow2, + aNewMark, InsertDeleteFlags::CONTENTS ); + if ( rParam.nRow2 > nEndRow ) + rDoc.DeleteArea( rParam.nCol1, nEndRow+1, rParam.nCol2, rParam.nRow2, + aNewMark, InsertDeleteFlags::CONTENTS ); + } + + // update database range + pDBData->SetImportParam( rParam ); + pDBData->SetHeader( true ); + pDBData->SetByRow( true ); + pDBData->SetArea( nTab, rParam.nCol1,rParam.nRow1, nEndCol,nEndRow ); + pDBData->SetImportSelection( bRealSelection ); + rDoc.CompileDBFormula(); + + if (bRecord) + { + ScDocumentUniquePtr pRedoDoc = std::move(pImportDoc); + + if (nFormulaCols > 0) // include filled formulas for redo + rDoc.CopyToDocument(rParam.nCol1, rParam.nRow1, nTab, + nEndCol+nFormulaCols, nEndRow, nTab, + InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pRedoDoc); + + std::unique_ptr<ScDBData> pRedoDBData(new ScDBData(*pDBData)); + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoImportData>( &rDocShell, nTab, + rParam, nUndoEndCol, nUndoEndRow, + nFormulaCols, + std::move(pUndoDoc), std::move(pRedoDoc), + std::move(pUndoDBData), std::move(pRedoDBData) ) ); + } + + sc::SetFormulaDirtyContext aCxt; + rDoc.SetAllFormulasDirty(aCxt); + rDocShell.PostPaint(ScRange(0, 0, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab), PaintPartFlags::Grid); + aModificator.SetDocumentModified(); + + ScDBRangeRefreshedHint aHint( rParam ); + rDoc.BroadcastUno( aHint ); + + xWaitWin.reset(); + + if ( bTruncated ) // show warning + ErrorHandler::HandleError(SCWARN_IMPORT_RANGE_OVERFLOW); + } + else + { + xWaitWin.reset(); + + if (aErrorMessage.isEmpty()) + { + if (!pErrStringId) + pErrStringId = STR_MSSG_IMPORTDATA_0; + aErrorMessage = ScResId(pErrStringId); + } + + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(), + VclMessageType::Info, VclButtonsType::Ok, + aErrorMessage)); + xInfoBox->run(); + } + + pImportDoc.reset(); + + if (bSuccess && pChangeTrack) + pChangeTrack->AppendInsert ( aChangedRange ); + + return bSuccess; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/docfunc.cxx b/sc/source/ui/docshell/docfunc.cxx new file mode 100644 index 000000000..752843797 --- /dev/null +++ b/sc/source/ui/docshell/docfunc.cxx @@ -0,0 +1,5922 @@ +/* -*- 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 <comphelper/lok.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/string_view.hxx> +#include <sfx2/app.hxx> +#include <editeng/editobj.hxx> +#include <editeng/justifyitem.hxx> +#include <sfx2/linkmgr.hxx> +#include <sfx2/bindings.hxx> +#include <vcl/weld.hxx> +#include <vcl/stdtext.hxx> +#include <vcl/svapp.hxx> +#include <svx/svdocapt.hxx> +#include <sal/log.hxx> +#include <unotools/charclass.hxx> +#include <osl/diagnose.h> + +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/script/ModuleType.hpp> +#include <com/sun/star/script/XLibraryContainer.hpp> +#include <com/sun/star/script/vba/XVBAModuleInfo.hpp> + +#include <docfunc.hxx> + +#include <sc.hrc> +#include <strings.hrc> + +#include <arealink.hxx> +#include <attrib.hxx> +#include <dociter.hxx> +#include <autoform.hxx> +#include <formulacell.hxx> +#include <cellmergeoption.hxx> +#include <detdata.hxx> +#include <detfunc.hxx> +#include <docpool.hxx> +#include <docsh.hxx> +#include <drwlayer.hxx> +#include <editutil.hxx> +#include <globstr.hrc> +#include <olinetab.hxx> +#include <patattr.hxx> +#include <rangenam.hxx> +#include <refundo.hxx> +#include <scresid.hxx> +#include <stlpool.hxx> +#include <stlsheet.hxx> +#include <tablink.hxx> +#include <tabvwsh.hxx> +#include <uiitems.hxx> +#include <undoblk.hxx> +#include <undocell.hxx> +#include <undodraw.hxx> +#include <undotab.hxx> +#include <sizedev.hxx> +#include <scmod.hxx> +#include <inputhdl.hxx> +#include <editable.hxx> +#include <compiler.hxx> +#include <scui_def.hxx> +#include <tabprotection.hxx> +#include <clipparam.hxx> +#include <externalrefmgr.hxx> +#include <undorangename.hxx> +#include <progress.hxx> +#include <dpobject.hxx> +#include <stringutil.hxx> +#include <cellvalue.hxx> +#include <tokenarray.hxx> +#include <rowheightcontext.hxx> +#include <cellvalues.hxx> +#include <undoconvert.hxx> +#include <docfuncutil.hxx> +#include <sheetevents.hxx> +#include <conditio.hxx> +#include <columnspanset.hxx> +#include <validat.hxx> +#include <SparklineGroup.hxx> +#include <SparklineAttributes.hxx> +#include <SparklineData.hxx> +#include <undo/UndoInsertSparkline.hxx> +#include <undo/UndoDeleteSparkline.hxx> +#include <undo/UndoDeleteSparklineGroup.hxx> +#include <undo/UndoEditSparklineGroup.hxx> +#include <undo/UndoUngroupSparklines.hxx> +#include <undo/UndoGroupSparklines.hxx> +#include <undo/UndoEditSparkline.hxx> +#include <config_features.h> + +#include <memory> +#include <utility> +#include <basic/basmgr.hxx> +#include <set> +#include <vector> +#include <sfx2/viewfrm.hxx> + +using namespace com::sun::star; +using ::std::vector; + +void ScDocFunc::NotifyDrawUndo( std::unique_ptr<SdrUndoAction> pUndoAction) +{ + // #i101118# if drawing layer collects the undo actions, add it there + ScDrawLayer* pDrawLayer = rDocShell.GetDocument().GetDrawLayer(); + if( pDrawLayer && pDrawLayer->IsRecording() ) + pDrawLayer->AddCalcUndo( std::move(pUndoAction) ); + else + rDocShell.GetUndoManager()->AddUndoAction( std::make_unique<ScUndoDraw>( std::move(pUndoAction), &rDocShell ) ); + rDocShell.SetDrawModified(); + + // the affected sheet isn't known, so all stream positions are invalidated + ScDocument& rDoc = rDocShell.GetDocument(); + SCTAB nTabCount = rDoc.GetTableCount(); + for (SCTAB nTab=0; nTab<nTabCount; nTab++) + rDoc.SetStreamValid(nTab, false); +} + +// paint row above the range (because of lines after AdjustRowHeight) + +static void lcl_PaintAbove( ScDocShell& rDocShell, const ScRange& rRange ) +{ + SCROW nRow = rRange.aStart.Row(); + if ( nRow > 0 ) + { + SCTAB nTab = rRange.aStart.Tab(); //! all of them? + --nRow; + ScDocument& rDoc = rDocShell.GetDocument(); + rDocShell.PostPaint( ScRange(0,nRow,nTab,rDoc.MaxCol(),nRow,nTab), PaintPartFlags::Grid ); + } +} + +bool ScDocFunc::AdjustRowHeight( const ScRange& rRange, bool bPaint, bool bApi ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + SfxViewShell* pSomeViewForThisDoc = rDocShell.GetBestViewShell(false); + if ( rDoc.IsImportingXML() ) + { + // for XML import, all row heights are updated together after importing + return false; + } + if ( rDoc.IsAdjustHeightLocked() ) + { + return false; + } + + SCTAB nTab = rRange.aStart.Tab(); + SCROW nStartRow = rRange.aStart.Row(); + SCROW nEndRow = rRange.aEnd.Row(); + + ScSizeDeviceProvider aProv( &rDocShell ); + Fraction aOne(1,1); + + sc::RowHeightContext aCxt(rDoc.MaxRow(), aProv.GetPPTX(), aProv.GetPPTY(), aOne, aOne, aProv.GetDevice()); + bool bChanged = rDoc.SetOptimalHeight(aCxt, nStartRow, nEndRow, nTab, bApi); + // tdf#76183: recalculate objects' positions + if (bChanged) + { + if (comphelper::LibreOfficeKit::isActive()) + { + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(pViewShell); + if (pTabViewShell && pTabViewShell->GetDocId() == pSomeViewForThisDoc->GetDocId()) + { + if (ScPositionHelper* pPosHelper = pTabViewShell->GetViewData().GetLOKHeightHelper(nTab)) + pPosHelper->invalidateByIndex(nStartRow); + } + pViewShell = SfxViewShell::GetNext(*pViewShell); + } + } + rDoc.SetDrawPageSize(nTab); + } + + if ( bPaint && bChanged ) + rDocShell.PostPaint(ScRange(0, nStartRow, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab), + PaintPartFlags::Grid | PaintPartFlags::Left); + + if (comphelper::LibreOfficeKit::isActive()) + { + ScTabViewShell::notifyAllViewsHeaderInvalidation(pSomeViewForThisDoc, ROW_HEADER, nTab); + ScTabViewShell::notifyAllViewsSheetGeomInvalidation( + pSomeViewForThisDoc, false /* bColumns */, true /* bRows */, true /* bSizes*/, + false /* bHidden */, false /* bFiltered */, false /* bGroups */, nTab); + } + + return bChanged; +} + +bool ScDocFunc::DetectiveAddPred(const ScAddress& rPos) +{ + ScDocShellModificator aModificator( rDocShell ); + + rDocShell.MakeDrawLayer(); + ScDocument& rDoc = rDocShell.GetDocument(); + bool bUndo (rDoc.IsUndoEnabled()); + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + SCCOL nCol = rPos.Col(); + SCROW nRow = rPos.Row(); + SCTAB nTab = rPos.Tab(); + + if (bUndo) + pModel->BeginCalcUndo(false); + bool bDone = ScDetectiveFunc(rDoc, nTab).ShowPred( nCol, nRow ); + std::unique_ptr<SdrUndoGroup> pUndo; + if (bUndo) + pUndo = pModel->GetCalcUndo(); + if (bDone) + { + ScDetOpData aOperation( ScAddress(nCol,nRow,nTab), SCDETOP_ADDPRED ); + rDoc.AddDetectiveOperation( aOperation ); + if (bUndo) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDetective>( &rDocShell, std::move(pUndo), &aOperation ) ); + } + aModificator.SetDocumentModified(); + SfxBindings* pBindings = rDocShell.GetViewBindings(); + if (pBindings) + pBindings->Invalidate( SID_DETECTIVE_REFRESH ); + } + + return bDone; +} + +bool ScDocFunc::DetectiveDelPred(const ScAddress& rPos) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + + bool bUndo(rDoc.IsUndoEnabled()); + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + ScDocShellModificator aModificator( rDocShell ); + + SCCOL nCol = rPos.Col(); + SCROW nRow = rPos.Row(); + SCTAB nTab = rPos.Tab(); + + if (bUndo) + pModel->BeginCalcUndo(false); + bool bDone = ScDetectiveFunc(rDoc, nTab).DeletePred( nCol, nRow ); + std::unique_ptr<SdrUndoGroup> pUndo; + if (bUndo) + pUndo = pModel->GetCalcUndo(); + if (bDone) + { + ScDetOpData aOperation( ScAddress(nCol,nRow,nTab), SCDETOP_DELPRED ); + rDoc.AddDetectiveOperation( aOperation ); + if (bUndo) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDetective>( &rDocShell, std::move(pUndo), &aOperation ) ); + } + aModificator.SetDocumentModified(); + SfxBindings* pBindings = rDocShell.GetViewBindings(); + if (pBindings) + pBindings->Invalidate( SID_DETECTIVE_REFRESH ); + } + + return bDone; +} + +bool ScDocFunc::DetectiveAddSucc(const ScAddress& rPos) +{ + ScDocShellModificator aModificator( rDocShell ); + + rDocShell.MakeDrawLayer(); + ScDocument& rDoc = rDocShell.GetDocument(); + + bool bUndo(rDoc.IsUndoEnabled()); + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + SCCOL nCol = rPos.Col(); + SCROW nRow = rPos.Row(); + SCTAB nTab = rPos.Tab(); + + if (bUndo) + pModel->BeginCalcUndo(false); + bool bDone = ScDetectiveFunc(rDoc, nTab).ShowSucc( nCol, nRow ); + std::unique_ptr<SdrUndoGroup> pUndo; + if (bUndo) + pUndo = pModel->GetCalcUndo(); + if (bDone) + { + ScDetOpData aOperation( ScAddress(nCol,nRow,nTab), SCDETOP_ADDSUCC ); + rDoc.AddDetectiveOperation( aOperation ); + if (bUndo) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDetective>( &rDocShell, std::move(pUndo), &aOperation ) ); + } + aModificator.SetDocumentModified(); + SfxBindings* pBindings = rDocShell.GetViewBindings(); + if (pBindings) + pBindings->Invalidate( SID_DETECTIVE_REFRESH ); + } + + return bDone; +} + +bool ScDocFunc::DetectiveDelSucc(const ScAddress& rPos) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + + bool bUndo (rDoc.IsUndoEnabled()); + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + ScDocShellModificator aModificator( rDocShell ); + + SCCOL nCol = rPos.Col(); + SCROW nRow = rPos.Row(); + SCTAB nTab = rPos.Tab(); + + if (bUndo) + pModel->BeginCalcUndo(false); + bool bDone = ScDetectiveFunc(rDoc, nTab).DeleteSucc( nCol, nRow ); + std::unique_ptr<SdrUndoGroup> pUndo; + if (bUndo) + pUndo = pModel->GetCalcUndo(); + if (bDone) + { + ScDetOpData aOperation( ScAddress(nCol,nRow,nTab), SCDETOP_DELSUCC ); + rDoc.AddDetectiveOperation( aOperation ); + if (bUndo) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDetective>( &rDocShell, std::move(pUndo), &aOperation ) ); + } + aModificator.SetDocumentModified(); + SfxBindings* pBindings = rDocShell.GetViewBindings(); + if (pBindings) + pBindings->Invalidate( SID_DETECTIVE_REFRESH ); + } + + return bDone; +} + +bool ScDocFunc::DetectiveAddError(const ScAddress& rPos) +{ + ScDocShellModificator aModificator( rDocShell ); + + rDocShell.MakeDrawLayer(); + ScDocument& rDoc = rDocShell.GetDocument(); + + bool bUndo (rDoc.IsUndoEnabled()); + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + SCCOL nCol = rPos.Col(); + SCROW nRow = rPos.Row(); + SCTAB nTab = rPos.Tab(); + + if (bUndo) + pModel->BeginCalcUndo(false); + bool bDone = ScDetectiveFunc(rDoc, nTab).ShowError( nCol, nRow ); + std::unique_ptr<SdrUndoGroup> pUndo; + if (bUndo) + pUndo = pModel->GetCalcUndo(); + if (bDone) + { + ScDetOpData aOperation( ScAddress(nCol,nRow,nTab), SCDETOP_ADDERROR ); + rDoc.AddDetectiveOperation( aOperation ); + if (bUndo) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDetective>( &rDocShell, std::move(pUndo), &aOperation ) ); + } + aModificator.SetDocumentModified(); + SfxBindings* pBindings = rDocShell.GetViewBindings(); + if (pBindings) + pBindings->Invalidate( SID_DETECTIVE_REFRESH ); + } + + return bDone; +} + +bool ScDocFunc::DetectiveMarkInvalid(SCTAB nTab) +{ + ScDocShellModificator aModificator( rDocShell ); + + rDocShell.MakeDrawLayer(); + ScDocument& rDoc = rDocShell.GetDocument(); + + bool bUndo (rDoc.IsUndoEnabled()); + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + + std::unique_ptr<weld::WaitObject> xWaitWin(new weld::WaitObject(ScDocShell::GetActiveDialogParent())); + if (bUndo) + pModel->BeginCalcUndo(false); + bool bOverflow; + bool bDone = ScDetectiveFunc(rDoc, nTab).MarkInvalid( bOverflow ); + std::unique_ptr<SdrUndoGroup> pUndo; + if (bUndo) + pUndo = pModel->GetCalcUndo(); + xWaitWin.reset(); + if (bDone) + { + if (pUndo && bUndo) + { + pUndo->SetComment( ScResId( STR_UNDO_DETINVALID ) ); + rDocShell.GetUndoManager()->AddUndoAction( std::move(pUndo) ); + } + aModificator.SetDocumentModified(); + if ( bOverflow ) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Info, VclButtonsType::Ok, + ScResId(STR_DETINVALID_OVERFLOW))); + xInfoBox->run(); + } + } + + return bDone; +} + +bool ScDocFunc::DetectiveDelAll(SCTAB nTab) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + + bool bUndo (rDoc.IsUndoEnabled()); + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return false; + + ScDocShellModificator aModificator( rDocShell ); + + if (bUndo) + pModel->BeginCalcUndo(false); + bool bDone = ScDetectiveFunc(rDoc, nTab).DeleteAll( ScDetectiveDelete::Detective ); + std::unique_ptr<SdrUndoGroup> pUndo; + if (bUndo) + pUndo = pModel->GetCalcUndo(); + if (bDone) + { + ScDetOpList* pOldList = rDoc.GetDetOpList(); + std::unique_ptr<ScDetOpList> pUndoList; + if (bUndo && pOldList) + pUndoList.reset(new ScDetOpList(*pOldList)); + + rDoc.ClearDetectiveOperations(); + + if (bUndo) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDetective>( &rDocShell, std::move(pUndo), nullptr, std::move(pUndoList) ) ); + } + aModificator.SetDocumentModified(); + SfxBindings* pBindings = rDocShell.GetViewBindings(); + if (pBindings) + pBindings->Invalidate( SID_DETECTIVE_REFRESH ); + } + + return bDone; +} + +bool ScDocFunc::DetectiveRefresh( bool bAutomatic ) +{ + bool bDone = false; + ScDocument& rDoc = rDocShell.GetDocument(); + + ScDetOpList* pList = rDoc.GetDetOpList(); + if ( pList && pList->Count() ) + { + rDocShell.MakeDrawLayer(); + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + const bool bUndo (rDoc.IsUndoEnabled()); + if (bUndo) + pModel->BeginCalcUndo(false); + + // Delete in all sheets + + SCTAB nTabCount = rDoc.GetTableCount(); + for (SCTAB nTab=0; nTab<nTabCount; nTab++) + ScDetectiveFunc( rDoc,nTab ).DeleteAll( ScDetectiveDelete::Arrows ); // don't remove circles + + // repeat + + size_t nCount = pList->Count(); + for (size_t i=0; i < nCount; ++i) + { + const ScDetOpData& rData = pList->GetObject(i); + const ScAddress& aPos = rData.GetPos(); + ScDetectiveFunc aFunc( rDoc, aPos.Tab() ); + SCCOL nCol = aPos.Col(); + SCROW nRow = aPos.Row(); + switch (rData.GetOperation()) + { + case SCDETOP_ADDSUCC: + aFunc.ShowSucc( nCol, nRow ); + break; + case SCDETOP_DELSUCC: + aFunc.DeleteSucc( nCol, nRow ); + break; + case SCDETOP_ADDPRED: + aFunc.ShowPred( nCol, nRow ); + break; + case SCDETOP_DELPRED: + aFunc.DeletePred( nCol, nRow ); + break; + case SCDETOP_ADDERROR: + aFunc.ShowError( nCol, nRow ); + break; + default: + OSL_FAIL("wrong operation in DetectiveRefresh"); + } + } + + if (bUndo) + { + std::unique_ptr<SdrUndoGroup> pUndo = pModel->GetCalcUndo(); + if (pUndo) + { + pUndo->SetComment( ScResId( STR_UNDO_DETREFRESH ) ); + // associate with the last action + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDraw>( std::move(pUndo), &rDocShell ), + bAutomatic ); + } + } + rDocShell.SetDrawModified(); + bDone = true; + } + return bDone; +} + +static void lcl_collectAllPredOrSuccRanges( + const ScRangeList& rSrcRanges, vector<ScTokenRef>& rRefTokens, ScDocShell& rDocShell, + bool bPred) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + vector<ScTokenRef> aRefTokens; + if (rSrcRanges.empty()) + return; + ScRange const & rFrontRange = rSrcRanges.front(); + ScDetectiveFunc aDetFunc(rDoc, rFrontRange.aStart.Tab()); + for (size_t i = 0, n = rSrcRanges.size(); i < n; ++i) + { + ScRange const & r = rSrcRanges[i]; + if (bPred) + { + aDetFunc.GetAllPreds( + r.aStart.Col(), r.aStart.Row(), r.aEnd.Col(), r.aEnd.Row(), aRefTokens); + } + else + { + aDetFunc.GetAllSuccs( + r.aStart.Col(), r.aStart.Row(), r.aEnd.Col(), r.aEnd.Row(), aRefTokens); + } + } + rRefTokens.swap(aRefTokens); +} + +void ScDocFunc::DetectiveCollectAllPreds(const ScRangeList& rSrcRanges, vector<ScTokenRef>& rRefTokens) +{ + lcl_collectAllPredOrSuccRanges(rSrcRanges, rRefTokens, rDocShell, true); +} + +void ScDocFunc::DetectiveCollectAllSuccs(const ScRangeList& rSrcRanges, vector<ScTokenRef>& rRefTokens) +{ + lcl_collectAllPredOrSuccRanges(rSrcRanges, rRefTokens, rDocShell, false); +} + +bool ScDocFunc::DeleteContents( + const ScMarkData& rMark, InsertDeleteFlags nFlags, bool bRecord, bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + + if ( !rMark.IsMarked() && !rMark.IsMultiMarked() ) + { + OSL_FAIL("ScDocFunc::DeleteContents without markings"); + return false; + } + + ScDocument& rDoc = rDocShell.GetDocument(); + + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + ScEditableTester aTester( rDoc, rMark ); + if (!aTester.IsEditable()) + { + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + return false; + } + + ScMarkData aMultiMark = rMark; + aMultiMark.SetMarking(false); // for MarkToMulti + + ScDocumentUniquePtr pUndoDoc; + bool bMulti = aMultiMark.IsMultiMarked(); + aMultiMark.MarkToMulti(); + const ScRange& aMarkRange = aMultiMark.GetMultiMarkArea(); + ScRange aExtendedRange(aMarkRange); + if ( rDoc.ExtendMerge( aExtendedRange, true ) ) + bMulti = false; + + // no objects on protected tabs + bool bObjects = (nFlags & InsertDeleteFlags::OBJECTS) && !sc::DocFuncUtil::hasProtectedTab(rDoc, rMark); + + sal_uInt16 nExtFlags = 0; // extra flags are needed only if attributes are deleted + if ( nFlags & InsertDeleteFlags::ATTRIB ) + rDocShell.UpdatePaintExt( nExtFlags, aMarkRange ); + + // order of operations: + // 1) BeginDrawUndo + // 2) Delete objects (DrawUndo will be filled) + // 3) Copy content for undo and set up undo actions + // 4) Delete content + + bool bDrawUndo = bObjects || (nFlags & InsertDeleteFlags::NOTE); + if (bRecord && bDrawUndo) + rDoc.BeginDrawUndo(); + + if (bObjects) + { + if (bMulti) + rDoc.DeleteObjectsInSelection( aMultiMark ); + else + rDoc.DeleteObjectsInArea( aMarkRange.aStart.Col(), aMarkRange.aStart.Row(), + aMarkRange.aEnd.Col(), aMarkRange.aEnd.Row(), + aMultiMark ); + } + + // To keep track of all non-empty cells within the deleted area. + std::shared_ptr<ScSimpleUndo::DataSpansType> pDataSpans; + + if ( bRecord ) + { + pUndoDoc = sc::DocFuncUtil::createDeleteContentsUndoDoc(rDoc, aMultiMark, aMarkRange, nFlags, bMulti); + pDataSpans = sc::DocFuncUtil::getNonEmptyCellSpans(rDoc, aMultiMark, aMarkRange); + } + + rDoc.DeleteSelection( nFlags, aMultiMark ); + + // add undo action after drawing undo is complete (objects and note captions) + if( bRecord ) + { + sc::DocFuncUtil::addDeleteContentsUndo( + rDocShell.GetUndoManager(), &rDocShell, aMultiMark, aExtendedRange, + std::move(pUndoDoc), nFlags, pDataSpans, bMulti, bDrawUndo); + } + + if (!AdjustRowHeight( aExtendedRange, true, bApi )) + rDocShell.PostPaint( aExtendedRange, PaintPartFlags::Grid, nExtFlags ); + else if (nExtFlags & SC_PF_LINES) + lcl_PaintAbove( rDocShell, aExtendedRange ); // for lines above the range + + aModificator.SetDocumentModified(); + + return true; +} + +bool ScDocFunc::DeleteCell( + const ScAddress& rPos, const ScMarkData& rMark, InsertDeleteFlags nFlags, bool bRecord, bool bApi ) +{ + ScDocShellModificator aModificator(rDocShell); + + ScDocument& rDoc = rDocShell.GetDocument(); + + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + ScEditableTester aTester(rDoc, rPos.Col(), rPos.Row(), rPos.Col(), rPos.Row(), rMark); + if (!aTester.IsEditable()) + { + rDocShell.ErrorMessage(aTester.GetMessageId()); + return false; + } + + // no objects on protected tabs + bool bObjects = (nFlags & InsertDeleteFlags::OBJECTS) && !sc::DocFuncUtil::hasProtectedTab(rDoc, rMark); + + sal_uInt16 nExtFlags = 0; // extra flags are needed only if attributes are deleted + if (nFlags & InsertDeleteFlags::ATTRIB) + rDocShell.UpdatePaintExt(nExtFlags, rPos); + + // order of operations: + // 1) BeginDrawUndo + // 2) delete objects (DrawUndo is filled) + // 3) copy contents for undo + // 4) delete contents + // 5) add undo-action + + bool bDrawUndo = bObjects || (nFlags & InsertDeleteFlags::NOTE); // needed for shown notes + if (bDrawUndo && bRecord) + rDoc.BeginDrawUndo(); + + if (bObjects) + rDoc.DeleteObjectsInArea(rPos.Col(), rPos.Row(), rPos.Col(), rPos.Row(), rMark); + + // To keep track of all non-empty cells within the deleted area. + std::shared_ptr<ScSimpleUndo::DataSpansType> pDataSpans; + + ScDocumentUniquePtr pUndoDoc; + if (bRecord) + { + pUndoDoc = sc::DocFuncUtil::createDeleteContentsUndoDoc(rDoc, rMark, rPos, nFlags, false); + pDataSpans = sc::DocFuncUtil::getNonEmptyCellSpans(rDoc, rMark, rPos); + } + + rDoc.DeleteArea(rPos.Col(), rPos.Row(), rPos.Col(), rPos.Row(), rMark, nFlags); + + if (bRecord) + { + sc::DocFuncUtil::addDeleteContentsUndo( + rDocShell.GetUndoManager(), &rDocShell, rMark, rPos, std::move(pUndoDoc), + nFlags, pDataSpans, false, bDrawUndo); + } + + if (!AdjustRowHeight(rPos, true, bApi)) + rDocShell.PostPaint( + rPos.Col(), rPos.Row(), rPos.Tab(), rPos.Col(), rPos.Row(), rPos.Tab(), + PaintPartFlags::Grid, nExtFlags); + + aModificator.SetDocumentModified(); + + return true; +} + +bool ScDocFunc::TransliterateText( const ScMarkData& rMark, TransliterationFlags nType, + bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + + ScDocument& rDoc = rDocShell.GetDocument(); + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + + ScEditableTester aTester( rDoc, rMark ); + if (!aTester.IsEditable()) + { + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + return false; + } + + ScMarkData aMultiMark = rMark; + aMultiMark.SetMarking(false); // for MarkToMulti + aMultiMark.MarkToMulti(); + const ScRange& aMarkRange = aMultiMark.GetMultiMarkArea(); + + if (bRecord) + { + SCTAB nStartTab = aMarkRange.aStart.Tab(); + SCTAB nTabCount = rDoc.GetTableCount(); + + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nStartTab, nStartTab ); + for (const auto& rTab : rMark) + { + if (rTab >= nTabCount) + break; + + if (rTab != nStartTab) + pUndoDoc->AddUndoTab( rTab, rTab ); + } + + ScRange aCopyRange = aMarkRange; + aCopyRange.aStart.SetTab(0); + aCopyRange.aEnd.SetTab(nTabCount-1); + rDoc.CopyToDocument(aCopyRange, InsertDeleteFlags::CONTENTS, true, *pUndoDoc, &aMultiMark); + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoTransliterate>( &rDocShell, aMultiMark, std::move(pUndoDoc), nType ) ); + } + + rDoc.TransliterateText( aMultiMark, nType ); + + if (!AdjustRowHeight( aMarkRange, true, true )) + rDocShell.PostPaint( aMarkRange, PaintPartFlags::Grid ); + + aModificator.SetDocumentModified(); + + return true; +} + +bool ScDocFunc::SetNormalString( bool& o_rbNumFmtSet, const ScAddress& rPos, const OUString& rText, bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + ScDocument& rDoc = rDocShell.GetDocument(); + + bool bUndo(rDoc.IsUndoEnabled()); + ScEditableTester aTester( rDoc, rPos.Tab(), rPos.Col(),rPos.Row(), rPos.Col(),rPos.Row() ); + if (!aTester.IsEditable()) + { + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + return false; + } + + bool bEditDeleted = (rDoc.GetCellType(rPos) == CELLTYPE_EDIT); + ScUndoEnterData::ValuesType aOldValues; + + if (bUndo) + { + ScUndoEnterData::Value aOldValue; + + aOldValue.mnTab = rPos.Tab(); + aOldValue.maCell.assign(rDoc, rPos); + + const ScPatternAttr* pPattern = rDoc.GetPattern( rPos.Col(),rPos.Row(),rPos.Tab() ); + if ( const SfxUInt32Item* pItem = pPattern->GetItemSet().GetItemIfSet( + ATTR_VALUE_FORMAT,false) ) + { + aOldValue.mbHasFormat = true; + aOldValue.mnFormat = pItem->GetValue(); + } + else + aOldValue.mbHasFormat = false; + + aOldValues.push_back(aOldValue); + } + + o_rbNumFmtSet = rDoc.SetString( rPos.Col(), rPos.Row(), rPos.Tab(), rText ); + + if (bUndo) + { + // because of ChangeTracking, UndoAction can be created only after SetString was called + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoEnterData>(&rDocShell, rPos, aOldValues, rText, nullptr)); + } + + if ( bEditDeleted || rDoc.HasAttrib( ScRange(rPos), HasAttrFlags::NeedHeight ) ) + AdjustRowHeight( ScRange(rPos), true, bApi ); + + rDocShell.PostPaintCell( rPos ); + aModificator.SetDocumentModified(); + + // notify input handler here the same way as in PutCell + if (bApi) + NotifyInputHandler( rPos ); + + const SfxUInt32Item* pItem = rDoc.GetAttr(rPos, ATTR_VALIDDATA); + const ScValidationData* pData = rDoc.GetValidationEntry(pItem->GetValue()); + if (pData) + { + ScRefCellValue aCell(rDoc, rPos); + if (pData->IsDataValid(aCell, rPos)) + ScDetectiveFunc(rDoc, rPos.Tab()).DeleteCirclesAt(rPos.Col(), rPos.Row()); + } + + return true; +} + +bool ScDocFunc::SetValueCell( const ScAddress& rPos, double fVal, bool bInteraction ) +{ + ScDocShellModificator aModificator( rDocShell ); + ScDocument& rDoc = rDocShell.GetDocument(); + bool bUndo = rDoc.IsUndoEnabled(); + + bool bHeight = rDoc.HasAttrib(rPos, HasAttrFlags::NeedHeight); + + ScCellValue aOldVal; + if (bUndo) + aOldVal.assign(rDoc, rPos); + + rDoc.SetValue(rPos, fVal); + + if (bUndo) + { + SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager(); + ScCellValue aNewVal; + aNewVal.assign(rDoc, rPos); + pUndoMgr->AddUndoAction(std::make_unique<ScUndoSetCell>(&rDocShell, rPos, aOldVal, aNewVal)); + } + + if (bHeight) + AdjustRowHeight(rPos, true, !bInteraction); + + rDocShell.PostPaintCell( rPos ); + aModificator.SetDocumentModified(); + + // #103934#; notify editline and cell in edit mode + if (!bInteraction) + NotifyInputHandler( rPos ); + + return true; +} + +void ScDocFunc::SetValueCells( const ScAddress& rPos, const std::vector<double>& aVals, bool bInteraction ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + + // Check for invalid range. + SCROW nLastRow = rPos.Row() + aVals.size() - 1; + if (nLastRow > rDoc.MaxRow()) + // out of bound. + return; + + ScRange aRange(rPos); + aRange.aEnd.SetRow(nLastRow); + + ScDocShellModificator aModificator(rDocShell); + + if (rDoc.IsUndoEnabled()) + { + std::unique_ptr<sc::UndoSetCells> pUndoObj(new sc::UndoSetCells(&rDocShell, rPos)); + rDoc.TransferCellValuesTo(rPos, aVals.size(), pUndoObj->GetOldValues()); + pUndoObj->SetNewValues(aVals); + SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager(); + pUndoMgr->AddUndoAction(std::move(pUndoObj)); + } + + rDoc.SetValues(rPos, aVals); + + rDocShell.PostPaint(aRange, PaintPartFlags::Grid); + aModificator.SetDocumentModified(); + + // #103934#; notify editline and cell in edit mode + if (!bInteraction) + NotifyInputHandler(rPos); +} + +bool ScDocFunc::SetStringCell( const ScAddress& rPos, const OUString& rStr, bool bInteraction ) +{ + ScDocShellModificator aModificator( rDocShell ); + ScDocument& rDoc = rDocShell.GetDocument(); + bool bUndo = rDoc.IsUndoEnabled(); + + bool bHeight = rDoc.HasAttrib(rPos, HasAttrFlags::NeedHeight); + + ScCellValue aOldVal; + if (bUndo) + aOldVal.assign(rDoc, rPos); + + ScSetStringParam aParam; + aParam.setTextInput(); + rDoc.SetString(rPos, rStr, &aParam); + + if (bUndo) + { + SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager(); + ScCellValue aNewVal; + aNewVal.assign(rDoc, rPos); + pUndoMgr->AddUndoAction(std::make_unique<ScUndoSetCell>(&rDocShell, rPos, aOldVal, aNewVal)); + } + + if (bHeight) + AdjustRowHeight(rPos, true, !bInteraction); + + rDocShell.PostPaintCell( rPos ); + aModificator.SetDocumentModified(); + + // #103934#; notify editline and cell in edit mode + if (!bInteraction) + NotifyInputHandler( rPos ); + + return true; +} + +bool ScDocFunc::SetEditCell( const ScAddress& rPos, const EditTextObject& rStr, bool bInteraction ) +{ + ScDocShellModificator aModificator( rDocShell ); + ScDocument& rDoc = rDocShell.GetDocument(); + bool bUndo = rDoc.IsUndoEnabled(); + + bool bHeight = rDoc.HasAttrib(rPos, HasAttrFlags::NeedHeight); + + ScCellValue aOldVal; + if (bUndo) + aOldVal.assign(rDoc, rPos); + + rDoc.SetEditText(rPos, rStr.Clone()); + + if (bUndo) + { + SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager(); + ScCellValue aNewVal; + aNewVal.assign(rDoc, rPos); + pUndoMgr->AddUndoAction(std::make_unique<ScUndoSetCell>(&rDocShell, rPos, aOldVal, aNewVal)); + } + + if (bHeight) + AdjustRowHeight(rPos, true, !bInteraction); + + rDocShell.PostPaintCell( rPos ); + aModificator.SetDocumentModified(); + + // #103934#; notify editline and cell in edit mode + if (!bInteraction) + NotifyInputHandler( rPos ); + + return true; +} + +bool ScDocFunc::SetStringOrEditCell( const ScAddress& rPos, const OUString& rStr, bool bInteraction ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + + if (ScStringUtil::isMultiline(rStr)) + { + ScFieldEditEngine& rEngine = rDoc.GetEditEngine(); + rEngine.SetTextCurrentDefaults(rStr); + std::unique_ptr<EditTextObject> pEditText(rEngine.CreateTextObject()); + return SetEditCell(rPos, *pEditText, bInteraction); + } + else + return SetStringCell(rPos, rStr, bInteraction); +} + +bool ScDocFunc::SetFormulaCell( const ScAddress& rPos, ScFormulaCell* pCell, bool bInteraction ) +{ + std::unique_ptr<ScFormulaCell> xCell(pCell); + + ScDocShellModificator aModificator( rDocShell ); + ScDocument& rDoc = rDocShell.GetDocument(); + bool bUndo = rDoc.IsUndoEnabled(); + + bool bHeight = rDoc.HasAttrib(rPos, HasAttrFlags::NeedHeight); + + ScCellValue aOldVal; + if (bUndo) + aOldVal.assign(rDoc, rPos); + + pCell = rDoc.SetFormulaCell(rPos, xCell.release()); + + // For performance reasons API calls may disable calculation while + // operating and recalculate once when done. If through user interaction + // and AutoCalc is disabled, calculate the formula (without its + // dependencies) once so the result matches the current document's content. + if (bInteraction && !rDoc.GetAutoCalc() && pCell) + { + // calculate just the cell once and set Dirty again + pCell->Interpret(); + pCell->SetDirtyVar(); + rDoc.PutInFormulaTree( pCell); + } + + if (bUndo) + { + SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager(); + ScCellValue aNewVal; + aNewVal.assign(rDoc, rPos); + pUndoMgr->AddUndoAction(std::make_unique<ScUndoSetCell>(&rDocShell, rPos, aOldVal, aNewVal)); + } + + if (bHeight) + AdjustRowHeight(rPos, true, !bInteraction); + + rDocShell.PostPaintCell( rPos ); + aModificator.SetDocumentModified(); + + // #103934#; notify editline and cell in edit mode + if (!bInteraction) + NotifyInputHandler( rPos ); + + return true; +} + +bool ScDocFunc::SetFormulaCells( const ScAddress& rPos, std::vector<ScFormulaCell*>& rCells, bool bInteraction ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + + const size_t nLength = rCells.size(); + if (rPos.Row() + nLength - 1 > o3tl::make_unsigned(rDoc.MaxRow())) + // out of bound + return false; + + ScRange aRange(rPos); + aRange.aEnd.IncRow(nLength - 1); + + ScDocShellModificator aModificator( rDocShell ); + bool bUndo = rDoc.IsUndoEnabled(); + + std::unique_ptr<sc::UndoSetCells> pUndoObj; + if (bUndo) + { + pUndoObj.reset(new sc::UndoSetCells(&rDocShell, rPos)); + rDoc.TransferCellValuesTo(rPos, nLength, pUndoObj->GetOldValues()); + } + + rDoc.SetFormulaCells(rPos, rCells); + + // For performance reasons API calls may disable calculation while + // operating and recalculate once when done. If through user interaction + // and AutoCalc is disabled, calculate the formula (without its + // dependencies) once so the result matches the current document's content. + if (bInteraction && !rDoc.GetAutoCalc()) + { + for (auto* pCell : rCells) + { + // calculate just the cell once and set Dirty again + pCell->Interpret(); + pCell->SetDirtyVar(); + rDoc.PutInFormulaTree( pCell); + } + } + + if (bUndo) + { + pUndoObj->SetNewValues(rCells); + SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager(); + pUndoMgr->AddUndoAction(std::move(pUndoObj)); + } + + rDocShell.PostPaint(aRange, PaintPartFlags::Grid); + aModificator.SetDocumentModified(); + + // #103934#; notify editline and cell in edit mode + if (!bInteraction) + NotifyInputHandler( rPos ); + + return true; +} + +void ScDocFunc::NotifyInputHandler( const ScAddress& rPos ) +{ + ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell(); + if ( !(pViewSh && pViewSh->GetViewData().GetDocShell() == &rDocShell) ) + return; + + ScInputHandler* pInputHdl = SC_MOD()->GetInputHdl(); + if ( pInputHdl && pInputHdl->GetCursorPos() == rPos ) + { + bool bIsEditMode(pInputHdl->IsEditMode()); + + // set modified if in editmode, because so the string is not set in the InputWindow like in the cell + // (the cell shows the same like the InputWindow) + if (bIsEditMode) + pInputHdl->SetModified(); + pViewSh->UpdateInputHandler(false, !bIsEditMode); + } +} + +namespace { + + struct ScMyRememberItem + { + sal_Int32 nIndex; + SfxItemSet aItemSet; + + ScMyRememberItem(const SfxItemSet& rItemSet, sal_Int32 nTempIndex) : + nIndex(nTempIndex), aItemSet(rItemSet) {} + }; + +} + +void ScDocFunc::PutData( const ScAddress& rPos, ScEditEngineDefaulter& rEngine, bool bApi ) +{ + // PutData calls PutCell or SetNormalString + + bool bRet = false; + ScDocument& rDoc = rDocShell.GetDocument(); + ScEditAttrTester aTester( &rEngine ); + bool bEditCell = aTester.NeedsObject(); + if ( bEditCell ) + { + // #i61702# With bLoseContent set, the content of rEngine isn't restored + // (used in loading XML, where after the removeActionLock call the API object's + // EditEngine isn't accessed again. + bool bLoseContent = rDoc.IsImportingXML(); + + const bool bUpdateMode = rEngine.SetUpdateLayout(false); + + std::vector<std::unique_ptr<ScMyRememberItem>> aRememberItems; + + // All paragraph attributes must be removed before calling CreateTextObject, + // not only alignment, so the object doesn't contain the cell attributes as + // paragraph attributes. Before removing the attributes store them in a vector to + // set them back to the EditEngine. + sal_Int32 nCount = rEngine.GetParagraphCount(); + for (sal_Int32 i=0; i<nCount; i++) + { + const SfxItemSet& rOld = rEngine.GetParaAttribs( i ); + if ( rOld.Count() ) + { + if ( !bLoseContent ) + { + aRememberItems.push_back(std::make_unique<ScMyRememberItem>(rEngine.GetParaAttribs(i), i)); + } + rEngine.SetParaAttribs( i, SfxItemSet( *rOld.GetPool(), rOld.GetRanges() ) ); + } + } + + // A copy of pNewData will be stored in the cell. + std::unique_ptr<EditTextObject> pNewData(rEngine.CreateTextObject()); + bRet = SetEditCell(rPos, *pNewData, !bApi); + + // Set the paragraph attributes back to the EditEngine. + for (const auto& rxItem : aRememberItems) + { + rEngine.SetParaAttribs(rxItem->nIndex, rxItem->aItemSet); + } + + // #i61702# if the content isn't accessed, there's no need to set the UpdateMode again + if ( bUpdateMode && !bLoseContent ) + rEngine.SetUpdateLayout(true); + } + else + { + OUString aText = rEngine.GetText(); + if (aText.isEmpty()) + { + bool bNumFmtSet = false; + bRet = SetNormalString( bNumFmtSet, rPos, aText, bApi ); + } + else + bRet = SetStringCell(rPos, aText, !bApi); + } + + if ( !(bRet && aTester.NeedsCellAttr()) ) + return; + + const SfxItemSet& rEditAttr = aTester.GetAttribs(); + ScPatternAttr aPattern( rDoc.GetPool() ); + aPattern.GetFromEditItemSet( &rEditAttr ); + aPattern.DeleteUnchanged( rDoc.GetPattern( rPos.Col(), rPos.Row(), rPos.Tab() ) ); + aPattern.GetItemSet().ClearItem( ATTR_HOR_JUSTIFY ); // wasn't removed above if no edit object + if ( aPattern.GetItemSet().Count() > 0 ) + { + ScMarkData aMark(rDoc.GetSheetLimits()); + aMark.SelectTable( rPos.Tab(), true ); + aMark.SetMarkArea( ScRange( rPos ) ); + ApplyAttributes( aMark, aPattern, bApi ); + } +} + +bool ScDocFunc::SetCellText( + const ScAddress& rPos, const OUString& rText, bool bInterpret, bool bEnglish, bool bApi, + const formula::FormulaGrammar::Grammar eGrammar ) +{ + bool bSet = false; + if ( bInterpret ) + { + if ( bEnglish ) + { + ScDocument& rDoc = rDocShell.GetDocument(); + + ::std::optional<ScExternalRefManager::ApiGuard> pExtRefGuard; + if (bApi) + pExtRefGuard.emplace(rDoc); + + ScInputStringType aRes = + ScStringUtil::parseInputString(*rDoc.GetFormatTable(), rText, LANGUAGE_ENGLISH_US); + + switch (aRes.meType) + { + case ScInputStringType::Formula: + bSet = SetFormulaCell(rPos, new ScFormulaCell(rDoc, rPos, aRes.maText, eGrammar), !bApi); + break; + case ScInputStringType::Number: + bSet = SetValueCell(rPos, aRes.mfValue, !bApi); + break; + case ScInputStringType::Text: + bSet = SetStringOrEditCell(rPos, aRes.maText, !bApi); + break; + default: + ; + } + } + // otherwise keep Null -> SetString with local formulas/number formats + } + else if (!rText.isEmpty()) + { + bSet = SetStringOrEditCell(rPos, rText, !bApi); + } + + if (!bSet) + { + bool bNumFmtSet = false; + bSet = SetNormalString( bNumFmtSet, rPos, rText, bApi ); + } + return bSet; +} + +bool ScDocFunc::ShowNote( const ScAddress& rPos, bool bShow ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + ScPostIt* pNote = rDoc.GetNote( rPos ); + if( !pNote || (bShow == pNote->IsCaptionShown()) || + (comphelper::LibreOfficeKit::isActive() && !comphelper::LibreOfficeKit::isTiledAnnotations()) ) + return false; + + // move the caption to internal or hidden layer and create undo action + pNote->ShowCaption( rPos, bShow ); + if( rDoc.IsUndoEnabled() ) + rDocShell.GetUndoManager()->AddUndoAction( std::make_unique<ScUndoShowHideNote>( rDocShell, rPos, bShow ) ); + + rDoc.SetStreamValid(rPos.Tab(), false); + + ScTabView::OnLOKNoteStateChanged(pNote); + + if (ScViewData* pViewData = ScDocShell::GetViewData()) + { + if (ScDrawView* pDrawView = pViewData->GetScDrawView()) + pDrawView->SyncForGrid( pNote->GetCaption()); + } + + rDocShell.SetDocumentModified(); + + return true; +} + +void ScDocFunc::SetNoteText( const ScAddress& rPos, const OUString& rText, bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + + ScDocument& rDoc = rDocShell.GetDocument(); + ScEditableTester aTester( rDoc, rPos.Tab(), rPos.Col(),rPos.Row(), rPos.Col(),rPos.Row() ); + if (!aTester.IsEditable()) + { + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + return; + } + + OUString aNewText = convertLineEnd(rText, GetSystemLineEnd()); //! is this necessary ??? + + if( ScPostIt* pNote = (!aNewText.isEmpty()) ? rDoc.GetOrCreateNote( rPos ) : rDoc.GetNote(rPos) ) + pNote->SetText( rPos, aNewText ); + + //! Undo !!! + + rDoc.SetStreamValid(rPos.Tab(), false); + + rDocShell.PostPaintCell( rPos ); + aModificator.SetDocumentModified(); +} + +void ScDocFunc::ReplaceNote( const ScAddress& rPos, const OUString& rNoteText, const OUString* pAuthor, const OUString* pDate, bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + ScDocument& rDoc = rDocShell.GetDocument(); + ScEditableTester aTester( rDoc, rPos.Tab(), rPos.Col(),rPos.Row(), rPos.Col(),rPos.Row() ); + if (aTester.IsEditable()) + { + ScDrawLayer* pDrawLayer = rDoc.GetDrawLayer(); + SfxUndoManager* pUndoMgr = (pDrawLayer && rDoc.IsUndoEnabled()) ? rDocShell.GetUndoManager() : nullptr; + + ScNoteData aOldData; + std::unique_ptr<ScPostIt> pOldNote = rDoc.ReleaseNote( rPos ); + sal_uInt32 nNoteId = 0; + if( pOldNote ) + { + nNoteId = pOldNote->GetId(); + // ensure existing caption object before draw undo tracking starts + pOldNote->GetOrCreateCaption( rPos ); + // rescue note data for undo + aOldData = pOldNote->GetNoteData(); + } + + // collect drawing undo actions for deleting/inserting caption objects + if( pUndoMgr ) + pDrawLayer->BeginCalcUndo(false); + + // delete the note (creates drawing undo action for the caption object) + bool hadOldNote(pOldNote); + pOldNote.reset(); + + // create new note (creates drawing undo action for the new caption object) + ScNoteData aNewData; + ScPostIt* pNewNote = nullptr; + if( (pNewNote = ScNoteUtil::CreateNoteFromString( rDoc, rPos, rNoteText, false, true, nNoteId )) ) + { + if( pAuthor ) pNewNote->SetAuthor( *pAuthor ); + if( pDate ) pNewNote->SetDate( *pDate ); + + // rescue note data for undo + aNewData = pNewNote->GetNoteData(); + } + + // create the undo action + if( pUndoMgr && (aOldData.mxCaption || aNewData.mxCaption) ) + pUndoMgr->AddUndoAction( std::make_unique<ScUndoReplaceNote>( rDocShell, rPos, aOldData, aNewData, pDrawLayer->GetCalcUndo() ) ); + + // repaint cell (to make note marker visible) + rDocShell.PostPaintCell( rPos ); + + rDoc.SetStreamValid(rPos.Tab(), false); + + aModificator.SetDocumentModified(); + + // Let our LOK clients know about the new/modified note + if (pNewNote) + { + ScDocShell::LOKCommentNotify(hadOldNote ? LOKCommentNotificationType::Modify : LOKCommentNotificationType::Add, + &rDoc, rPos, pNewNote); + } + } + else if (!bApi) + { + rDocShell.ErrorMessage(aTester.GetMessageId()); + } +} + +ScPostIt* ScDocFunc::ImportNote( const ScAddress& rPos, const OUString& rNoteText ) +{ + ScDocShellModificator aModificator( rDocShell ); + ScDocument& rDoc = rDocShell.GetDocument(); + + std::unique_ptr<ScPostIt> pOldNote = rDoc.ReleaseNote( rPos ); + SAL_WARN_IF(pOldNote, "sc.ui", "imported data has >1 notes on same cell? at pos " << rPos); + + // create new note + ScPostIt* pNewNote = ScNoteUtil::CreateNoteFromString( rDoc, rPos, rNoteText, false, true, /*nNoteId*/0 ); + + rDoc.SetStreamValid(rPos.Tab(), false); + + aModificator.SetDocumentModified(); + + return pNewNote; +} + +bool ScDocFunc::ApplyAttributes( const ScMarkData& rMark, const ScPatternAttr& rPattern, + bool bApi ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + bool bRecord = true; + if ( !rDoc.IsUndoEnabled() ) + bRecord = false; + + bool bImportingXML = rDoc.IsImportingXML(); + // Cell formats can still be set if the range isn't editable only because of matrix formulas. + // #i62483# When loading XML, the check can be skipped altogether. + bool bOnlyNotBecauseOfMatrix; + if ( !bImportingXML && !rDoc.IsSelectionEditable( rMark, &bOnlyNotBecauseOfMatrix ) + && !bOnlyNotBecauseOfMatrix ) + { + if (!bApi) + rDocShell.ErrorMessage(STR_PROTECTIONERR); + return false; + } + + ScDocShellModificator aModificator( rDocShell ); + + //! Border + + ScRange aMultiRange; + bool bMulti = rMark.IsMultiMarked(); + if ( bMulti ) + aMultiRange = rMark.GetMultiMarkArea(); + else + aMultiRange = rMark.GetMarkArea(); + + if ( bRecord ) + { + ScDocumentUniquePtr pUndoDoc( new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, aMultiRange.aStart.Tab(), aMultiRange.aEnd.Tab() ); + rDoc.CopyToDocument(aMultiRange, InsertDeleteFlags::ATTRIB, bMulti, *pUndoDoc, &rMark); + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoSelectionAttr>( + &rDocShell, rMark, + aMultiRange.aStart.Col(), aMultiRange.aStart.Row(), aMultiRange.aStart.Tab(), + aMultiRange.aEnd.Col(), aMultiRange.aEnd.Row(), aMultiRange.aEnd.Tab(), + std::move(pUndoDoc), bMulti, &rPattern ) ); + } + + // While loading XML it is not necessary to ask HasAttrib. It needs too much time. + sal_uInt16 nExtFlags = 0; + if ( !bImportingXML ) + rDocShell.UpdatePaintExt( nExtFlags, aMultiRange ); // content before the change + + bool bChanged = false; + rDoc.ApplySelectionPattern( rPattern, rMark, nullptr, &bChanged ); + + if(bChanged) + { + if ( !bImportingXML ) + rDocShell.UpdatePaintExt( nExtFlags, aMultiRange ); // content after the change + + if (!AdjustRowHeight( aMultiRange, true, bApi )) + rDocShell.PostPaint( aMultiRange, PaintPartFlags::Grid, nExtFlags ); + else if (nExtFlags & SC_PF_LINES) + lcl_PaintAbove( rDocShell, aMultiRange ); // because of lines above the range + + aModificator.SetDocumentModified(); + } + + return true; +} + +bool ScDocFunc::ApplyStyle( const ScMarkData& rMark, const OUString& rStyleName, + bool bApi ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + bool bRecord = true; + if ( !rDoc.IsUndoEnabled() ) + bRecord = false; + + bool bImportingXML = rDoc.IsImportingXML(); + // Cell formats can still be set if the range isn't editable only because of matrix formulas. + // #i62483# When loading XML, the check can be skipped altogether. + bool bOnlyNotBecauseOfMatrix; + if ( !bImportingXML && !rDoc.IsSelectionEditable( rMark, &bOnlyNotBecauseOfMatrix ) + && !bOnlyNotBecauseOfMatrix ) + { + if (!bApi) + rDocShell.ErrorMessage(STR_PROTECTIONERR); + return false; + } + + ScStyleSheet* pStyleSheet = static_cast<ScStyleSheet*>( rDoc.GetStyleSheetPool()->Find( + rStyleName, SfxStyleFamily::Para )); + if (!pStyleSheet) + return false; + + ScDocShellModificator aModificator( rDocShell ); + + ScRange aMultiRange; + bool bMulti = rMark.IsMultiMarked(); + if ( bMulti ) + aMultiRange = rMark.GetMultiMarkArea(); + else + aMultiRange = rMark.GetMarkArea(); + + if ( bRecord ) + { + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + SCTAB nStartTab = aMultiRange.aStart.Tab(); + SCTAB nTabCount = rDoc.GetTableCount(); + pUndoDoc->InitUndo( rDoc, nStartTab, nStartTab ); + for (const auto& rTab : rMark) + { + if (rTab >= nTabCount) + break; + + if (rTab != nStartTab) + pUndoDoc->AddUndoTab( rTab, rTab ); + } + + ScRange aCopyRange = aMultiRange; + aCopyRange.aStart.SetTab(0); + aCopyRange.aEnd.SetTab(nTabCount-1); + rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ATTRIB, bMulti, *pUndoDoc, &rMark ); + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoSelectionStyle>( + &rDocShell, rMark, aMultiRange, rStyleName, std::move(pUndoDoc) ) ); + + } + + rDoc.ApplySelectionStyle( *pStyleSheet, rMark ); + + if (!AdjustRowHeight( aMultiRange, true, bApi )) + rDocShell.PostPaint( aMultiRange, PaintPartFlags::Grid ); + + aModificator.SetDocumentModified(); + + return true; +} + +namespace { + +/** + * Check if this insertion attempt would end up cutting one or more pivot + * tables in half, which is not desirable. + * + * @return true if this insertion can be done safely without shearing any + * existing pivot tables, false otherwise. + */ +bool canInsertCellsByPivot(const ScRange& rRange, const ScMarkData& rMarkData, InsCellCmd eCmd, const ScDocument& rDoc) +{ + if (!rDoc.HasPivotTable()) + // This document has no pivot tables. + return true; + + const ScDPCollection* pDPs = rDoc.GetDPCollection(); + + ScRange aRange(rRange); // local copy + switch (eCmd) + { + case INS_INSROWS_BEFORE: + { + aRange.aStart.SetCol(0); + aRange.aEnd.SetCol(rDoc.MaxCol()); + [[fallthrough]]; + } + case INS_CELLSDOWN: + { + auto bIntersects = std::any_of(rMarkData.begin(), rMarkData.end(), [&pDPs, &aRange](const SCTAB& rTab) { + return pDPs->IntersectsTableByColumns(aRange.aStart.Col(), aRange.aEnd.Col(), aRange.aStart.Row(), rTab); }); + if (bIntersects) + // This column range cuts through at least one pivot table. Not good. + return false; + + // Start row must be either at the top or above any pivot tables. + if (aRange.aStart.Row() < 0) + // I don't know how to handle this case. + return false; + + if (aRange.aStart.Row() == 0) + // First row is always allowed. + return true; + + ScRange aTest(aRange); + aTest.aStart.IncRow(-1); // Test one row up. + aTest.aEnd.SetRow(aTest.aStart.Row()); + for (const auto& rTab : rMarkData) + { + aTest.aStart.SetTab(rTab); + aTest.aEnd.SetTab(rTab); + if (pDPs->HasTable(aTest)) + return false; + } + } + break; + case INS_INSCOLS_BEFORE: + { + aRange.aStart.SetRow(0); + aRange.aEnd.SetRow(rDoc.MaxRow()); + [[fallthrough]]; + } + case INS_CELLSRIGHT: + { + auto bIntersects = std::any_of(rMarkData.begin(), rMarkData.end(), [&pDPs, &aRange](const SCTAB& rTab) { + return pDPs->IntersectsTableByRows(aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Row(), rTab); }); + if (bIntersects) + // This column range cuts through at least one pivot table. Not good. + return false; + + // Start row must be either at the top or above any pivot tables. + if (aRange.aStart.Col() < 0) + // I don't know how to handle this case. + return false; + + if (aRange.aStart.Col() == 0) + // First row is always allowed. + return true; + + ScRange aTest(aRange); + aTest.aStart.IncCol(-1); // Test one column to the left. + aTest.aEnd.SetCol(aTest.aStart.Col()); + for (const auto& rTab : rMarkData) + { + aTest.aStart.SetTab(rTab); + aTest.aEnd.SetTab(rTab); + if (pDPs->HasTable(aTest)) + return false; + } + } + break; + default: + ; + } + return true; +} + +/** + * Check if this deletion attempt would end up cutting one or more pivot + * tables in half, which is not desirable. + * + * @return true if this deletion can be done safely without shearing any + * existing pivot tables, false otherwise. + */ +bool canDeleteCellsByPivot(const ScRange& rRange, const ScMarkData& rMarkData, DelCellCmd eCmd, const ScDocument& rDoc) +{ + if (!rDoc.HasPivotTable()) + // This document has no pivot tables. + return true; + + const ScDPCollection* pDPs = rDoc.GetDPCollection(); + + ScRange aRange(rRange); // local copy + + switch (eCmd) + { + case DelCellCmd::Rows: + { + aRange.aStart.SetCol(0); + aRange.aEnd.SetCol(rDoc.MaxCol()); + [[fallthrough]]; + } + case DelCellCmd::CellsUp: + { + auto bIntersects = std::any_of(rMarkData.begin(), rMarkData.end(), [&pDPs, &aRange](const SCTAB& rTab) { + return pDPs->IntersectsTableByColumns(aRange.aStart.Col(), aRange.aEnd.Col(), aRange.aStart.Row(), rTab); }); + if (bIntersects) + // This column range cuts through at least one pivot table. Not good. + return false; + + ScRange aTest(aRange); + for (const auto& rTab : rMarkData) + { + aTest.aStart.SetTab(rTab); + aTest.aEnd.SetTab(rTab); + if (pDPs->HasTable(aTest)) + return false; + } + } + break; + case DelCellCmd::Cols: + { + aRange.aStart.SetRow(0); + aRange.aEnd.SetRow(rDoc.MaxRow()); + [[fallthrough]]; + } + case DelCellCmd::CellsLeft: + { + auto bIntersects = std::any_of(rMarkData.begin(), rMarkData.end(), [&pDPs, &aRange](const SCTAB& rTab) { + return pDPs->IntersectsTableByRows(aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Row(), rTab); }); + if (bIntersects) + // This column range cuts through at least one pivot table. Not good. + return false; + + ScRange aTest(aRange); + for (const auto& rTab : rMarkData) + { + aTest.aStart.SetTab(rTab); + aTest.aEnd.SetTab(rTab); + if (pDPs->HasTable(aTest)) + return false; + } + } + break; + default: + ; + } + return true; +} + +} + +bool ScDocFunc::InsertCells( const ScRange& rRange, const ScMarkData* pTabMark, InsCellCmd eCmd, + bool bRecord, bool bApi, bool bPartOfPaste ) +{ + ScDocShellModificator aModificator( rDocShell ); + ScDocument& rDoc = rDocShell.GetDocument(); + + if (rDocShell.GetDocument().GetChangeTrack() && + ((eCmd == INS_CELLSDOWN && (rRange.aStart.Col() != 0 || rRange.aEnd.Col() != rDoc.MaxCol())) || + (eCmd == INS_CELLSRIGHT && (rRange.aStart.Row() != 0 || rRange.aEnd.Row() != rDoc.MaxRow())))) + { + // We should not reach this via UI disabled slots. + assert(bApi); + SAL_WARN("sc.ui","ScDocFunc::InsertCells - no change-tracking of partial cell shift"); + return false; + } + + ScRange aTargetRange( rRange ); + + // If insertion is for full cols/rows and after the current + // selection, then shift the range accordingly + if ( eCmd == INS_INSROWS_AFTER ) + { + ScRange aErrorRange( ScAddress::UNINITIALIZED ); + if (!aTargetRange.Move(0, rRange.aEnd.Row() - rRange.aStart.Row() + 1, 0, aErrorRange, rDoc)) + { + return false; + } + } + if ( eCmd == INS_INSCOLS_AFTER ) + { + ScRange aErrorRange( ScAddress::UNINITIALIZED ); + if (!aTargetRange.Move(rRange.aEnd.Col() - rRange.aStart.Col() + 1, 0, 0, aErrorRange, rDoc)) + { + return false; + } + } + + SCCOL nStartCol = aTargetRange.aStart.Col(); + SCROW nStartRow = aTargetRange.aStart.Row(); + SCTAB nStartTab = aTargetRange.aStart.Tab(); + SCCOL nEndCol = aTargetRange.aEnd.Col(); + SCROW nEndRow = aTargetRange.aEnd.Row(); + SCTAB nEndTab = aTargetRange.aEnd.Tab(); + + if ( !rDoc.ValidRow(nStartRow) || !rDoc.ValidRow(nEndRow) ) + { + OSL_FAIL("invalid row in InsertCells"); + return false; + } + + SCTAB nTabCount = rDoc.GetTableCount(); + SCCOL nPaintStartCol = nStartCol; + SCROW nPaintStartRow = nStartRow; + SCCOL nPaintEndCol = nEndCol; + SCROW nPaintEndRow = nEndRow; + PaintPartFlags nPaintFlags = PaintPartFlags::Grid; + bool bSuccess; + + ScTabViewShell* pViewSh = rDocShell.GetBestViewShell(); //preserve current cursor position + SCCOL nCursorCol = 0; + SCROW nCursorRow = 0; + if( pViewSh ) + { + nCursorCol = pViewSh->GetViewData().GetCurX(); + nCursorRow = pViewSh->GetViewData().GetCurY(); + } + + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + ScMarkData aMark(rDoc.GetSheetLimits()); + if (pTabMark) + aMark = *pTabMark; + else + { + SCTAB nCount = 0; + for( SCTAB i=0; i<nTabCount; i++ ) + { + if( !rDoc.IsScenario(i) ) + { + nCount++; + if( nCount == nEndTab+1 ) + { + aMark.SelectTable( i, true ); + break; + } + } + } + } + + ScMarkData aFullMark( aMark ); // including scenario sheets + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + for( SCTAB j = rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ ) + aFullMark.SelectTable( j, true ); + } + + SCTAB nSelCount = aMark.GetSelectCount(); + + // Adjust also related scenarios + + SCCOL nMergeTestStartCol = nStartCol; + SCROW nMergeTestStartRow = nStartRow; + SCCOL nMergeTestEndCol = nEndCol; + SCROW nMergeTestEndRow = nEndRow; + + ScRange aExtendMergeRange( aTargetRange ); + + if( aTargetRange.aStart == aTargetRange.aEnd && rDoc.HasAttrib(aTargetRange, HasAttrFlags::Merged) ) + { + rDoc.ExtendMerge( aExtendMergeRange ); + rDoc.ExtendOverlapped( aExtendMergeRange ); + nMergeTestEndCol = aExtendMergeRange.aEnd.Col(); + nMergeTestEndRow = aExtendMergeRange.aEnd.Row(); + nPaintEndCol = nMergeTestEndCol; + nPaintEndRow = nMergeTestEndRow; + } + + if ( eCmd == INS_INSROWS_BEFORE || eCmd == INS_INSROWS_AFTER ) + { + nMergeTestStartCol = 0; + nMergeTestEndCol = rDoc.MaxCol(); + } + if ( eCmd == INS_INSCOLS_BEFORE || eCmd == INS_INSCOLS_AFTER ) + { + nMergeTestStartRow = 0; + nMergeTestEndRow = rDoc.MaxRow(); + } + if ( eCmd == INS_CELLSDOWN ) + nMergeTestEndRow = rDoc.MaxRow(); + if ( eCmd == INS_CELLSRIGHT ) + nMergeTestEndCol = rDoc.MaxCol(); + + bool bNeedRefresh = false; + + SCCOL nEditTestEndCol = (eCmd==INS_INSCOLS_BEFORE || eCmd==INS_INSCOLS_AFTER) ? rDoc.MaxCol() : nMergeTestEndCol; + SCROW nEditTestEndRow = (eCmd==INS_INSROWS_BEFORE || eCmd==INS_INSROWS_AFTER) ? rDoc.MaxRow() : nMergeTestEndRow; + + ScEditableTester aTester; + + switch (eCmd) + { + case INS_INSCOLS_BEFORE: + aTester = ScEditableTester( + rDoc, sc::ColRowEditAction::InsertColumnsBefore, nMergeTestStartCol, nMergeTestEndCol, aMark); + break; + case INS_INSCOLS_AFTER: + aTester = ScEditableTester( + rDoc, sc::ColRowEditAction::InsertColumnsAfter, nMergeTestStartCol, nMergeTestEndCol, aMark); + break; + case INS_INSROWS_BEFORE: + aTester = ScEditableTester( + rDoc, sc::ColRowEditAction::InsertRowsBefore, nMergeTestStartRow, nMergeTestEndRow, aMark); + break; + case INS_INSROWS_AFTER: + aTester = ScEditableTester( + rDoc, sc::ColRowEditAction::InsertRowsAfter, nMergeTestStartRow, nMergeTestEndRow, aMark); + break; + default: + aTester = ScEditableTester( + rDoc, nMergeTestStartCol, nMergeTestStartRow, nEditTestEndCol, nEditTestEndRow, aMark); + } + + if (!aTester.IsEditable()) + { + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + return false; + } + + // Check if this insertion is allowed with respect to pivot table. + if (!canInsertCellsByPivot(aTargetRange, aMark, eCmd, rDoc)) + { + if (!bApi) + rDocShell.ErrorMessage(STR_NO_INSERT_DELETE_OVER_PIVOT_TABLE); + return false; + } + + weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() ); // important due to TrackFormulas at UpdateReference + + ScDocumentUniquePtr pRefUndoDoc; + std::unique_ptr<ScRefUndoData> pUndoData; + if ( bRecord ) + { + pRefUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pRefUndoDoc->InitUndo( rDoc, 0, nTabCount-1 ); + + // pRefUndoDoc is filled in InsertCol / InsertRow + + pUndoData.reset(new ScRefUndoData( &rDoc )); + + rDoc.BeginDrawUndo(); + } + + // #i8302 : we unmerge overwhelming ranges, before insertion all the actions are put in the same ListAction + // the patch comes from mloiseleur and maoyg + bool bInsertMerge = false; + std::vector<ScRange> qIncreaseRange; + OUString aUndo = ScResId( STR_UNDO_INSERTCELLS ); + if (bRecord) + { + ViewShellId nViewShellId(-1); + if (pViewSh) + nViewShellId = pViewSh->GetViewShellId(); + rDocShell.GetUndoManager()->EnterListAction( aUndo, aUndo, 0, nViewShellId ); + } + std::unique_ptr<ScUndoRemoveMerge> pUndoRemoveMerge; + + for (const SCTAB i : aMark) + { + if (i >= nTabCount) + break; + + if( rDoc.HasAttrib( nMergeTestStartCol, nMergeTestStartRow, i, nMergeTestEndCol, nMergeTestEndRow, i, HasAttrFlags::Merged | HasAttrFlags::Overlapped ) ) + { + if (eCmd==INS_CELLSRIGHT) + bNeedRefresh = true; + + SCCOL nMergeStartCol = nMergeTestStartCol; + SCROW nMergeStartRow = nMergeTestStartRow; + SCCOL nMergeEndCol = nMergeTestEndCol; + SCROW nMergeEndRow = nMergeTestEndRow; + + rDoc.ExtendMerge( nMergeStartCol, nMergeStartRow, nMergeEndCol, nMergeEndRow, i ); + rDoc.ExtendOverlapped( nMergeStartCol, nMergeStartRow, nMergeEndCol, nMergeEndRow, i ); + + if(( eCmd == INS_CELLSDOWN && ( nMergeStartCol != nMergeTestStartCol || nMergeEndCol != nMergeTestEndCol )) || + (eCmd == INS_CELLSRIGHT && ( nMergeStartRow != nMergeTestStartRow || nMergeEndRow != nMergeTestEndRow )) ) + { + if (!bApi) + rDocShell.ErrorMessage(STR_MSSG_INSERTCELLS_0); + rDocShell.GetUndoManager()->LeaveListAction(); + return false; + } + + SCCOL nTestCol = -1; + SCROW nTestRow1 = -1; + SCROW nTestRow2 = -1; + + ScDocAttrIterator aTestIter( rDoc, i, nMergeTestStartCol, nMergeTestStartRow, nMergeTestEndCol, nMergeTestEndRow ); + ScRange aExtendRange( nMergeTestStartCol, nMergeTestStartRow, i, nMergeTestEndCol, nMergeTestEndRow, i ); + const ScPatternAttr* pPattern = nullptr; + while ( ( pPattern = aTestIter.GetNext( nTestCol, nTestRow1, nTestRow2 ) ) != nullptr ) + { + const ScMergeAttr& rMergeFlag = pPattern->GetItem(ATTR_MERGE); + const ScMergeFlagAttr& rMergeFlagAttr = pPattern->GetItem(ATTR_MERGE_FLAG); + ScMF nNewFlags = rMergeFlagAttr.GetValue() & (ScMF::Hor | ScMF::Ver); + if (rMergeFlag.IsMerged() || nNewFlags == ScMF::Hor || nNewFlags == ScMF::Ver) + { + ScRange aRange( nTestCol, nTestRow1, i ); + rDoc.ExtendOverlapped(aRange); + rDoc.ExtendMerge(aRange, true); + + if( nTestRow1 < nTestRow2 && nNewFlags == ScMF::Hor ) + { + for( SCROW nTestRow = nTestRow1; nTestRow <= nTestRow2; nTestRow++ ) + { + ScRange aTestRange( nTestCol, nTestRow, i ); + rDoc.ExtendOverlapped( aTestRange ); + rDoc.ExtendMerge( aTestRange, true); + ScRange aMergeRange( aTestRange.aStart.Col(),aTestRange.aStart.Row(), i ); + if( !aExtendRange.Contains( aMergeRange ) ) + { + qIncreaseRange.push_back( aTestRange ); + bInsertMerge = true; + } + } + } + else + { + ScRange aMergeRange( aRange.aStart.Col(),aRange.aStart.Row(), i ); + if( !aExtendRange.Contains( aMergeRange ) ) + { + qIncreaseRange.push_back( aRange ); + } + bInsertMerge = true; + } + } + } + + if( bInsertMerge ) + { + if( eCmd == INS_INSROWS_BEFORE || eCmd == INS_INSROWS_AFTER || eCmd == INS_CELLSDOWN ) + { + nStartRow = aExtendMergeRange.aStart.Row(); + nEndRow = aExtendMergeRange.aEnd.Row(); + + if( eCmd == INS_CELLSDOWN ) + nEndCol = nMergeTestEndCol; + else + { + nStartCol = 0; + nEndCol = rDoc.MaxCol(); + } + } + else if( eCmd == INS_CELLSRIGHT || eCmd == INS_INSCOLS_BEFORE || eCmd == INS_INSCOLS_AFTER ) + { + + nStartCol = aExtendMergeRange.aStart.Col(); + nEndCol = aExtendMergeRange.aEnd.Col(); + if( eCmd == INS_CELLSRIGHT ) + { + nEndRow = nMergeTestEndRow; + } + else + { + nStartRow = 0; + nEndRow = rDoc.MaxRow(); + } + } + + if( !qIncreaseRange.empty() ) + { + if (bRecord && !pUndoRemoveMerge) + { + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, *aMark.begin(), *aMark.rbegin()); + pUndoRemoveMerge.reset( new ScUndoRemoveMerge( &rDocShell, rRange, std::move(pUndoDoc) )); + } + + for( const ScRange& aRange : qIncreaseRange ) + { + if( rDoc.HasAttrib( aRange, HasAttrFlags::Overlapped | HasAttrFlags::Merged ) ) + { + UnmergeCells( aRange, bRecord, pUndoRemoveMerge.get() ); + } + } + } + } + else + { + if (!bApi) + rDocShell.ErrorMessage(STR_MSSG_INSERTCELLS_0); + rDocShell.GetUndoManager()->LeaveListAction(); + return false; + } + } + } + + if (bRecord && pUndoRemoveMerge) + { + rDocShell.GetUndoManager()->AddUndoAction( std::move(pUndoRemoveMerge)); + } + + switch (eCmd) + { + case INS_CELLSDOWN: + bSuccess = rDoc.InsertRow( nStartCol, 0, nEndCol, MAXTAB, nStartRow, static_cast<SCSIZE>(nEndRow-nStartRow+1), pRefUndoDoc.get(), &aFullMark ); + nPaintEndRow = rDoc.MaxRow(); + break; + case INS_INSROWS_BEFORE: + case INS_INSROWS_AFTER: + bSuccess = rDoc.InsertRow( 0, 0, rDoc.MaxCol(), MAXTAB, nStartRow, static_cast<SCSIZE>(nEndRow-nStartRow+1), pRefUndoDoc.get(), &aFullMark ); + nPaintStartCol = 0; + nPaintEndCol = rDoc.MaxCol(); + nPaintEndRow = rDoc.MaxRow(); + nPaintFlags |= PaintPartFlags::Left; + break; + case INS_CELLSRIGHT: + bSuccess = rDoc.InsertCol( nStartRow, 0, nEndRow, MAXTAB, nStartCol, static_cast<SCSIZE>(nEndCol-nStartCol+1), pRefUndoDoc.get(), &aFullMark ); + nPaintEndCol = rDoc.MaxCol(); + break; + case INS_INSCOLS_BEFORE: + case INS_INSCOLS_AFTER: + bSuccess = rDoc.InsertCol( 0, 0, rDoc.MaxRow(), MAXTAB, nStartCol, static_cast<SCSIZE>(nEndCol-nStartCol+1), pRefUndoDoc.get(), &aFullMark ); + nPaintStartRow = 0; + nPaintEndRow = rDoc.MaxRow(); + nPaintEndCol = rDoc.MaxCol(); + nPaintFlags |= PaintPartFlags::Top; + break; + default: + OSL_FAIL("Wrong code at inserting"); + bSuccess = false; + break; + } + + if ( bSuccess ) + { + SCTAB nUndoPos = 0; + + if ( bRecord ) + { + std::unique_ptr<SCTAB[]> pTabs(new SCTAB[nSelCount]); + std::unique_ptr<SCTAB[]> pScenarios(new SCTAB[nSelCount]); + nUndoPos = 0; + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + SCTAB nCount = 0; + for( SCTAB j=rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ ) + nCount ++; + + pScenarios[nUndoPos] = nCount; + pTabs[nUndoPos] = rTab; + nUndoPos ++; + } + + if( !bInsertMerge ) + { + rDocShell.GetUndoManager()->LeaveListAction(); + } + + rDocShell.GetUndoManager()->AddUndoAction( std::make_unique<ScUndoInsertCells>( + &rDocShell, ScRange( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab ), + nUndoPos, std::move(pTabs), std::move(pScenarios), eCmd, std::move(pRefUndoDoc), std::move(pUndoData), bPartOfPaste ) ); + } + + // #i8302 : we remerge growing ranges, with the new part inserted + + while( !qIncreaseRange.empty() ) + { + ScRange aRange = qIncreaseRange.back(); + if( !rDoc.HasAttrib( aRange, HasAttrFlags::Overlapped | HasAttrFlags::Merged ) ) + { + switch (eCmd) + { + case INS_CELLSDOWN: + case INS_INSROWS_BEFORE: + case INS_INSROWS_AFTER: + aRange.aEnd.IncRow(static_cast<SCCOL>(nEndRow-nStartRow+1)); + break; + case INS_CELLSRIGHT: + case INS_INSCOLS_BEFORE: + case INS_INSCOLS_AFTER: + aRange.aEnd.IncCol(static_cast<SCCOL>(nEndCol-nStartCol+1)); + break; + default: + break; + } + ScCellMergeOption aMergeOption( + aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row() ); + aMergeOption.maTabs.insert(aRange.aStart.Tab()); + MergeCells(aMergeOption, false, true, true); + } + qIncreaseRange.pop_back(); + } + + if( bInsertMerge ) + rDocShell.GetUndoManager()->LeaveListAction(); + + for (const SCTAB i : aMark) + { + if (i >= nTabCount) + break; + + rDoc.SetDrawPageSize(i); + + if (bNeedRefresh) + rDoc.ExtendMerge( nMergeTestStartCol, nMergeTestStartRow, nMergeTestEndCol, nMergeTestEndRow, i, true ); + else + rDoc.RefreshAutoFilter( nMergeTestStartCol, nMergeTestStartRow, nMergeTestEndCol, nMergeTestEndRow, i ); + + if ( eCmd == INS_INSROWS_BEFORE ||eCmd == INS_INSCOLS_BEFORE || eCmd == INS_INSROWS_AFTER ||eCmd == INS_INSCOLS_AFTER ) + rDoc.UpdatePageBreaks( i ); + + sal_uInt16 nExtFlags = 0; + rDocShell.UpdatePaintExt( nExtFlags, nPaintStartCol, nPaintStartRow, i, nPaintEndCol, nPaintEndRow, i ); + + SCTAB nScenarioCount = 0; + + for( SCTAB j = i+1; j<nTabCount && rDoc.IsScenario(j); j++ ) + nScenarioCount ++; + + bool bAdjusted = ( eCmd == INS_INSROWS_BEFORE || eCmd == INS_INSROWS_AFTER ) ? + AdjustRowHeight(ScRange(0, nStartRow, i, rDoc.MaxCol(), nEndRow, i+nScenarioCount ), true, bApi) : + AdjustRowHeight(ScRange(0, nPaintStartRow, i, rDoc.MaxCol(), nPaintEndRow, i+nScenarioCount ), true, bApi); + if (bAdjusted) + { + // paint only what is not done by AdjustRowHeight + if (nPaintFlags & PaintPartFlags::Top) + rDocShell.PostPaint( nPaintStartCol, nPaintStartRow, i, nPaintEndCol, nPaintEndRow, i+nScenarioCount, PaintPartFlags::Top ); + } + else + rDocShell.PostPaint( nPaintStartCol, nPaintStartRow, i, nPaintEndCol, nPaintEndRow, i+nScenarioCount, nPaintFlags, nExtFlags ); + } + } + else + { + if( bInsertMerge ) + { + while( !qIncreaseRange.empty() ) + { + ScRange aRange = qIncreaseRange.back(); + ScCellMergeOption aMergeOption( + aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row() ); + MergeCells(aMergeOption, false, true, true); + qIncreaseRange.pop_back(); + } + + if( pViewSh ) + { + pViewSh->MarkRange( aTargetRange, false ); + pViewSh->SetCursor( nCursorCol, nCursorRow ); + } + } + + rDocShell.GetUndoManager()->LeaveListAction(); + rDocShell.GetUndoManager()->RemoveLastUndoAction(); + + pRefUndoDoc.reset(); + if (!bApi) + rDocShell.ErrorMessage(STR_INSERT_FULL); // column/row full + } + + // The cursor position needs to be modified earlier than updating + // any enabled edit view which is triggered by SetDocumentModified below. + if (bSuccess) + { + bool bInsertCols = ( eCmd == INS_INSCOLS_BEFORE || eCmd == INS_INSCOLS_AFTER); + bool bInsertRows = ( eCmd == INS_INSROWS_BEFORE || eCmd == INS_INSROWS_AFTER ); + + if (bInsertCols) + { + pViewSh->OnLOKInsertDeleteColumn(rRange.aStart.Col(), 1); + } + + if (bInsertRows) + { + pViewSh->OnLOKInsertDeleteRow(rRange.aStart.Row(), 1); + } + } + + aModificator.SetDocumentModified(); + + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) ); + return bSuccess; +} + +bool ScDocFunc::DeleteCells( const ScRange& rRange, const ScMarkData* pTabMark, DelCellCmd eCmd, + bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + ScDocument& rDoc = rDocShell.GetDocument(); + + if (rDocShell.GetDocument().GetChangeTrack() && + ((eCmd == DelCellCmd::CellsUp && (rRange.aStart.Col() != 0 || rRange.aEnd.Col() != rDoc.MaxCol())) || + (eCmd == DelCellCmd::CellsLeft && (rRange.aStart.Row() != 0 || rRange.aEnd.Row() != rDoc.MaxRow())))) + { + // We should not reach this via UI disabled slots. + assert(bApi); + SAL_WARN("sc.ui","ScDocFunc::DeleteCells - no change-tracking of partial cell shift"); + return false; + } + + 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(); + + if ( !rDoc.ValidRow(nStartRow) || !rDoc.ValidRow(nEndRow) ) + { + OSL_FAIL("invalid row in DeleteCells"); + return false; + } + + SCTAB nTabCount = rDoc.GetTableCount(); + SCCOL nPaintStartCol = nStartCol; + SCROW nPaintStartRow = nStartRow; + SCCOL nPaintEndCol = nEndCol; + SCROW nPaintEndRow = nEndRow; + PaintPartFlags nPaintFlags = PaintPartFlags::Grid; + + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + + ScMarkData aMark(rDoc.GetSheetLimits()); + if (pTabMark) + aMark = *pTabMark; + else + { + SCTAB nCount = 0; + for(SCTAB i=0; i<nTabCount; i++ ) + { + if( !rDoc.IsScenario(i) ) + { + nCount++; + if( nCount == nEndTab+1 ) + { + aMark.SelectTable(i, true); + break; + } + } + } + } + + ScMarkData aFullMark( aMark ); // including scenario sheets + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + for( SCTAB j = rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ ) + aFullMark.SelectTable( j, true ); + } + + SCTAB nSelCount = aMark.GetSelectCount(); + + SCCOL nUndoStartCol = nStartCol; + SCROW nUndoStartRow = nStartRow; + SCCOL nUndoEndCol = nEndCol; + SCROW nUndoEndRow = nEndRow; + + ScRange aExtendMergeRange( rRange ); + + if( rRange.aStart == rRange.aEnd && rDoc.HasAttrib(rRange, HasAttrFlags::Merged) ) + { + rDoc.ExtendMerge( aExtendMergeRange ); + rDoc.ExtendOverlapped( aExtendMergeRange ); + nUndoEndCol = aExtendMergeRange.aEnd.Col(); + nUndoEndRow = aExtendMergeRange.aEnd.Row(); + nPaintEndCol = nUndoEndCol; + nPaintEndRow = nUndoEndRow; + } + + if (eCmd==DelCellCmd::Rows) + { + nUndoStartCol = 0; + nUndoEndCol = rDoc.MaxCol(); + } + if (eCmd==DelCellCmd::Cols) + { + nUndoStartRow = 0; + nUndoEndRow = rDoc.MaxRow(); + } + // Test for cell protection + + SCCOL nEditTestEndX = nUndoEndCol; + if ( eCmd==DelCellCmd::Cols || eCmd==DelCellCmd::CellsLeft ) + nEditTestEndX = rDoc.MaxCol(); + SCROW nEditTestEndY = nUndoEndRow; + if ( eCmd==DelCellCmd::Rows || eCmd==DelCellCmd::CellsUp ) + nEditTestEndY = rDoc.MaxRow(); + + ScEditableTester aTester; + + switch (eCmd) + { + case DelCellCmd::Cols: + aTester = ScEditableTester( + rDoc, sc::ColRowEditAction::DeleteColumns, nUndoStartCol, nUndoEndCol, aMark); + break; + case DelCellCmd::Rows: + aTester = ScEditableTester( + rDoc, sc::ColRowEditAction::DeleteRows, nUndoStartRow, nUndoEndRow, aMark); + break; + default: + aTester = ScEditableTester( + rDoc, nUndoStartCol, nUndoStartRow, nEditTestEndX, nEditTestEndY, aMark); + } + + if (!aTester.IsEditable()) + { + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + return false; + } + + if (!canDeleteCellsByPivot(rRange, aMark, eCmd, rDoc)) + { + if (!bApi) + rDocShell.ErrorMessage(STR_NO_INSERT_DELETE_OVER_PIVOT_TABLE); + return false; + } + // Test for merged cells + + SCCOL nMergeTestEndCol = (eCmd==DelCellCmd::CellsLeft) ? rDoc.MaxCol() : nUndoEndCol; + SCROW nMergeTestEndRow = (eCmd==DelCellCmd::CellsUp) ? rDoc.MaxRow() : nUndoEndRow; + SCCOL nExtendStartCol = nUndoStartCol; + SCROW nExtendStartRow = nUndoStartRow; + bool bNeedRefresh = false; + + //Issue 8302 want to be able to insert into the middle of merged cells + //the patch comes from maoyg + ::std::vector<ScRange> qDecreaseRange; + bool bDeletingMerge = false; + OUString aUndo = ScResId( STR_UNDO_DELETECELLS ); + if (bRecord) + { + ViewShellId nViewShellId(-1); + if (ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell()) + nViewShellId = pViewSh->GetViewShellId(); + rDocShell.GetUndoManager()->EnterListAction( aUndo, aUndo, 0, nViewShellId ); + } + std::unique_ptr<ScUndoRemoveMerge> pUndoRemoveMerge; + + for (const SCTAB i : aMark) + { + if (i >= nTabCount) + break; + + if ( rDoc.HasAttrib( nUndoStartCol, nUndoStartRow, i, nMergeTestEndCol, nMergeTestEndRow, i, HasAttrFlags::Merged | HasAttrFlags::Overlapped )) + { + SCCOL nMergeStartCol = nUndoStartCol; + SCROW nMergeStartRow = nUndoStartRow; + SCCOL nMergeEndCol = nMergeTestEndCol; + SCROW nMergeEndRow = nMergeTestEndRow; + + rDoc.ExtendMerge( nMergeStartCol, nMergeStartRow, nMergeEndCol, nMergeEndRow, i ); + rDoc.ExtendOverlapped( nMergeStartCol, nMergeStartRow, nMergeEndCol, nMergeEndRow, i ); + if( ( eCmd == DelCellCmd::CellsUp && ( nMergeStartCol != nUndoStartCol || nMergeEndCol != nMergeTestEndCol))|| + ( eCmd == DelCellCmd::CellsLeft && ( nMergeStartRow != nUndoStartRow || nMergeEndRow != nMergeTestEndRow))) + { + if (!bApi) + rDocShell.ErrorMessage(STR_MSSG_DELETECELLS_0); + rDocShell.GetUndoManager()->LeaveListAction(); + return false; + } + + nExtendStartCol = nMergeStartCol; + nExtendStartRow = nMergeStartRow; + SCCOL nTestCol = -1; + SCROW nTestRow1 = -1; + SCROW nTestRow2 = -1; + + ScDocAttrIterator aTestIter( rDoc, i, nUndoStartCol, nUndoStartRow, nMergeTestEndCol, nMergeTestEndRow ); + ScRange aExtendRange( nUndoStartCol, nUndoStartRow, i, nMergeTestEndCol, nMergeTestEndRow, i ); + const ScPatternAttr* pPattern = nullptr; + while ( ( pPattern = aTestIter.GetNext( nTestCol, nTestRow1, nTestRow2 ) ) != nullptr ) + { + const ScMergeAttr& rMergeFlag = pPattern->GetItem(ATTR_MERGE); + const ScMergeFlagAttr& rMergeFlagAttr = pPattern->GetItem(ATTR_MERGE_FLAG); + ScMF nNewFlags = rMergeFlagAttr.GetValue() & (ScMF::Hor | ScMF::Ver); + if (rMergeFlag.IsMerged() || nNewFlags == ScMF::Hor || nNewFlags == ScMF::Ver) + { + ScRange aRange( nTestCol, nTestRow1, i ); + rDoc.ExtendOverlapped( aRange ); + rDoc.ExtendMerge( aRange, true ); + + if( nTestRow1 < nTestRow2 && nNewFlags == ScMF::Hor ) + { + for( SCROW nTestRow = nTestRow1; nTestRow <= nTestRow2; nTestRow++ ) + { + ScRange aTestRange( nTestCol, nTestRow, i ); + rDoc.ExtendOverlapped( aTestRange ); + rDoc.ExtendMerge( aTestRange, true ); + ScRange aMergeRange( aTestRange.aStart.Col(),aTestRange.aStart.Row(), i ); + if( !aExtendRange.Contains( aMergeRange ) ) + { + qDecreaseRange.push_back( aTestRange ); + bDeletingMerge = true; + } + } + } + else + { + ScRange aMergeRange( aRange.aStart.Col(),aRange.aStart.Row(), i ); + if( !aExtendRange.Contains( aMergeRange ) ) + { + qDecreaseRange.push_back( aRange ); + } + bDeletingMerge = true; + } + } + } + + if( bDeletingMerge ) + { + + if( eCmd == DelCellCmd::Rows || eCmd == DelCellCmd::CellsUp ) + { + nStartRow = aExtendMergeRange.aStart.Row(); + nEndRow = aExtendMergeRange.aEnd.Row(); + bNeedRefresh = true; + + if( eCmd == DelCellCmd::CellsUp ) + { + nEndCol = aExtendMergeRange.aEnd.Col(); + } + else + { + nStartCol = 0; + nEndCol = rDoc.MaxCol(); + } + } + else if( eCmd == DelCellCmd::CellsLeft || eCmd == DelCellCmd::Cols ) + { + + nStartCol = aExtendMergeRange.aStart.Col(); + nEndCol = aExtendMergeRange.aEnd.Col(); + if( eCmd == DelCellCmd::CellsLeft ) + { + nEndRow = aExtendMergeRange.aEnd.Row(); + bNeedRefresh = true; + } + else + { + nStartRow = 0; + nEndRow = rDoc.MaxRow(); + } + } + + if( !qDecreaseRange.empty() ) + { + if (bRecord && !pUndoRemoveMerge) + { + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, *aMark.begin(), *aMark.rbegin()); + pUndoRemoveMerge.reset( new ScUndoRemoveMerge( &rDocShell, rRange, std::move(pUndoDoc) )); + } + + for( const ScRange& aRange : qDecreaseRange ) + { + if( rDoc.HasAttrib( aRange, HasAttrFlags::Overlapped | HasAttrFlags::Merged ) ) + { + UnmergeCells( aRange, bRecord, pUndoRemoveMerge.get() ); + } + } + } + } + else + { + if (!bApi) + rDocShell.ErrorMessage(STR_MSSG_DELETECELLS_0); + rDocShell.GetUndoManager()->LeaveListAction(); + return false; + } + } + } + + if (bRecord && pUndoRemoveMerge) + { + rDocShell.GetUndoManager()->AddUndoAction( std::move(pUndoRemoveMerge)); + } + + // do it + + weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() ); // important because of TrackFormulas in UpdateReference + + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr<ScDocument> pRefUndoDoc; + std::unique_ptr<ScRefUndoData> pUndoData; + if ( bRecord ) + { + // With the fix for #101329#, UpdateRef always puts cells into pRefUndoDoc at their old position, + // so it's no longer necessary to copy more than the deleted range into pUndoDoc. + + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, 0, nTabCount-1, (eCmd==DelCellCmd::Cols), (eCmd==DelCellCmd::Rows) ); + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + SCTAB nScenarioCount = 0; + + for( SCTAB j = rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ ) + nScenarioCount ++; + + rDoc.CopyToDocument( nUndoStartCol, nUndoStartRow, rTab, nUndoEndCol, nUndoEndRow, rTab+nScenarioCount, + InsertDeleteFlags::ALL | InsertDeleteFlags::NOCAPTIONS, false, *pUndoDoc ); + } + + pRefUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pRefUndoDoc->InitUndo( rDoc, 0, nTabCount-1 ); + + pUndoData.reset(new ScRefUndoData( &rDoc )); + + rDoc.BeginDrawUndo(); + } + + sal_uInt16 nExtFlags = 0; + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + rDocShell.UpdatePaintExt( nExtFlags, nStartCol, nStartRow, rTab, nEndCol, nEndRow, rTab ); + } + + switch (eCmd) + { + case DelCellCmd::CellsUp: + case DelCellCmd::CellsLeft: + rDoc.DeleteObjectsInArea(nStartCol, nStartRow, nEndCol, nEndRow, aMark, true); + break; + case DelCellCmd::Rows: + rDoc.DeleteObjectsInArea(0, nStartRow, rDoc.MaxCol(), nEndRow, aMark, true); + break; + case DelCellCmd::Cols: + rDoc.DeleteObjectsInArea(nStartCol, 0, nEndCol, rDoc.MaxRow(), aMark, true); + break; + default: + break; + } + + + bool bUndoOutline = false; + switch (eCmd) + { + case DelCellCmd::CellsUp: + rDoc.DeleteRow( nStartCol, 0, nEndCol, MAXTAB, nStartRow, static_cast<SCSIZE>(nEndRow-nStartRow+1), pRefUndoDoc.get(), nullptr, &aFullMark ); + nPaintEndRow = rDoc.MaxRow(); + break; + case DelCellCmd::Rows: + rDoc.DeleteRow( 0, 0, rDoc.MaxCol(), MAXTAB, nStartRow, static_cast<SCSIZE>(nEndRow-nStartRow+1), pRefUndoDoc.get(), &bUndoOutline, &aFullMark ); + nPaintStartCol = 0; + nPaintEndCol = rDoc.MaxCol(); + nPaintEndRow = rDoc.MaxRow(); + nPaintFlags |= PaintPartFlags::Left; + break; + case DelCellCmd::CellsLeft: + rDoc.DeleteCol( nStartRow, 0, nEndRow, MAXTAB, nStartCol, static_cast<SCSIZE>(nEndCol-nStartCol+1), pRefUndoDoc.get(), nullptr, &aFullMark ); + nPaintEndCol = rDoc.MaxCol(); + break; + case DelCellCmd::Cols: + rDoc.DeleteCol( 0, 0, rDoc.MaxRow(), MAXTAB, nStartCol, static_cast<SCSIZE>(nEndCol-nStartCol+1), pRefUndoDoc.get(), &bUndoOutline, &aFullMark ); + nPaintStartRow = 0; + nPaintEndRow = rDoc.MaxRow(); + nPaintEndCol = rDoc.MaxCol(); + nPaintFlags |= PaintPartFlags::Top; + break; + default: + OSL_FAIL("Wrong code at deleting"); + break; + } + + //! Test if the size of outline has changed + + if ( bRecord ) + { + for (const auto& rTab : aFullMark) + { + if (rTab >= nTabCount) + break; + + pRefUndoDoc->DeleteAreaTab(nUndoStartCol,nUndoStartRow,nUndoEndCol,nUndoEndRow, rTab, InsertDeleteFlags::ALL); + } + + // for all sheets, so that formulas can be copied + pUndoDoc->AddUndoTab( 0, nTabCount-1 ); + + // copy with bColRowFlags=false (#54194#) + pRefUndoDoc->CopyToDocument(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB,InsertDeleteFlags::FORMULA,false,*pUndoDoc,nullptr,false); + pRefUndoDoc.reset(); + + std::unique_ptr<SCTAB[]> pTabs( new SCTAB[nSelCount]); + std::unique_ptr<SCTAB[]> pScenarios( new SCTAB[nSelCount]); + SCTAB nUndoPos = 0; + + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + SCTAB nCount = 0; + for( SCTAB j=rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ ) + nCount ++; + + pScenarios[nUndoPos] = nCount; + pTabs[nUndoPos] = rTab; + nUndoPos ++; + } + + if( !bDeletingMerge ) + { + rDocShell.GetUndoManager()->LeaveListAction(); + } + + rDocShell.GetUndoManager()->AddUndoAction( std::make_unique<ScUndoDeleteCells>( + &rDocShell, ScRange( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab ), + nUndoPos, std::move(pTabs), std::move(pScenarios), + eCmd, std::move(pUndoDoc), std::move(pUndoData) ) ); + } + + // #i8302 want to be able to insert into the middle of merged cells + // the patch comes from maoyg + + while( !qDecreaseRange.empty() ) + { + ScRange aRange = qDecreaseRange.back(); + + sal_Int32 nDecreaseRowCount = 0; + sal_Int32 nDecreaseColCount = 0; + if( eCmd == DelCellCmd::CellsUp || eCmd == DelCellCmd::Rows ) + { + if( nStartRow >= aRange.aStart.Row() && nStartRow <= aRange.aEnd.Row() && nEndRow>= aRange.aStart.Row() && nEndRow <= aRange.aEnd.Row() ) + nDecreaseRowCount = nEndRow-nStartRow+1; + else if( nStartRow >= aRange.aStart.Row() && nStartRow <= aRange.aEnd.Row() && nEndRow >= aRange.aStart.Row() && nEndRow >= aRange.aEnd.Row() ) + nDecreaseRowCount = aRange.aEnd.Row()-nStartRow+1; + else if( nStartRow >= aRange.aStart.Row() && nStartRow >= aRange.aEnd.Row() && nEndRow>= aRange.aStart.Row() && nEndRow <= aRange.aEnd.Row() ) + nDecreaseRowCount = aRange.aEnd.Row()-nEndRow+1; + } + else if( eCmd == DelCellCmd::CellsLeft || eCmd == DelCellCmd::Cols ) + { + if( nStartCol >= aRange.aStart.Col() && nStartCol <= aRange.aEnd.Col() && nEndCol>= aRange.aStart.Col() && nEndCol <= aRange.aEnd.Col() ) + nDecreaseColCount = nEndCol-nStartCol+1; + else if( nStartCol >= aRange.aStart.Col() && nStartCol <= aRange.aEnd.Col() && nEndCol >= aRange.aStart.Col() && nEndCol >= aRange.aEnd.Col() ) + nDecreaseColCount = aRange.aEnd.Col()-nStartCol+1; + else if( nStartCol >= aRange.aStart.Col() && nStartCol >= aRange.aEnd.Col() && nEndCol>= aRange.aStart.Col() && nEndCol <= aRange.aEnd.Col() ) + nDecreaseColCount = aRange.aEnd.Col()-nEndCol+1; + } + + switch (eCmd) + { + case DelCellCmd::CellsUp: + case DelCellCmd::Rows: + aRange.aEnd.SetRow(static_cast<SCCOL>( aRange.aEnd.Row()-nDecreaseRowCount)); + break; + case DelCellCmd::CellsLeft: + case DelCellCmd::Cols: + aRange.aEnd.SetCol(static_cast<SCCOL>( aRange.aEnd.Col()-nDecreaseColCount)); + break; + default: + break; + } + + if( !rDoc.HasAttrib( aRange, HasAttrFlags::Overlapped | HasAttrFlags::Merged ) ) + { + ScCellMergeOption aMergeOption(aRange); + MergeCells( aMergeOption, false, true, true ); + } + qDecreaseRange.pop_back(); + } + + if( bDeletingMerge ) + rDocShell.GetUndoManager()->LeaveListAction(); + + if ( eCmd==DelCellCmd::Cols || eCmd==DelCellCmd::CellsLeft ) + nMergeTestEndCol = rDoc.MaxCol(); + if ( eCmd==DelCellCmd::Rows || eCmd==DelCellCmd::CellsUp ) + nMergeTestEndRow = rDoc.MaxRow(); + if ( bNeedRefresh ) + { + // #i51445# old merge flag attributes must be deleted also for single cells, + // not only for whole columns/rows + + ScPatternAttr aPattern( rDoc.GetPool() ); + aPattern.GetItemSet().Put( ScMergeFlagAttr() ); + + rDoc.ApplyPatternArea( nExtendStartCol, nExtendStartRow, nMergeTestEndCol, nMergeTestEndRow, aMark, aPattern ); + + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + SCTAB nScenarioCount = 0; + + for( SCTAB j = rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ ) + nScenarioCount ++; + + ScRange aMergedRange( nExtendStartCol, nExtendStartRow, rTab, nMergeTestEndCol, nMergeTestEndRow, rTab+nScenarioCount ); + rDoc.ExtendMerge( aMergedRange, true ); + } + } + + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + rDoc.RefreshAutoFilter( nExtendStartCol, nExtendStartRow, nMergeTestEndCol, nMergeTestEndRow, rTab ); + } + + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + rDoc.SetDrawPageSize(rTab); + + if ( eCmd == DelCellCmd::Cols || eCmd == DelCellCmd::Rows ) + rDoc.UpdatePageBreaks( rTab ); + + rDocShell.UpdatePaintExt( nExtFlags, nPaintStartCol, nPaintStartRow, rTab, nPaintEndCol, nPaintEndRow, rTab ); + + SCTAB nScenarioCount = 0; + + for( SCTAB j = rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ ) + nScenarioCount ++; + + // delete entire rows: do not adjust + if ( eCmd == DelCellCmd::Rows || !AdjustRowHeight(ScRange( 0, nPaintStartRow, rTab, rDoc.MaxCol(), nPaintEndRow, rTab+nScenarioCount ), true, bApi) ) + rDocShell.PostPaint( nPaintStartCol, nPaintStartRow, rTab, nPaintEndCol, nPaintEndRow, rTab+nScenarioCount, nPaintFlags, nExtFlags ); + else + { + // paint only what is not done by AdjustRowHeight + if (nExtFlags & SC_PF_LINES) + lcl_PaintAbove( rDocShell, ScRange( nPaintStartCol, nPaintStartRow, rTab, nPaintEndCol, nPaintEndRow, rTab+nScenarioCount) ); + if (nPaintFlags & PaintPartFlags::Top) + rDocShell.PostPaint( nPaintStartCol, nPaintStartRow, rTab, nPaintEndCol, nPaintEndRow, rTab+nScenarioCount, PaintPartFlags::Top ); + } + } + + // The cursor position needs to be modified earlier than updating + // any enabled edit view which is triggered by SetDocumentModified below. + ScTabViewShell* pViewSh = rDocShell.GetBestViewShell(); + if (pViewSh) + { + if (eCmd == DelCellCmd::Cols) + { + pViewSh->OnLOKInsertDeleteColumn(rRange.aStart.Col(), -1); + } + if (eCmd == DelCellCmd::Rows) + { + pViewSh->OnLOKInsertDeleteRow(rRange.aStart.Row(), -1); + } + } + + aModificator.SetDocumentModified(); + + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) ); + + return true; +} + +bool ScDocFunc::MoveBlock( const ScRange& rSource, const ScAddress& rDestPos, + bool bCut, bool bRecord, bool bPaint, bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + + SCCOL nStartCol = rSource.aStart.Col(); + SCROW nStartRow = rSource.aStart.Row(); + SCTAB nStartTab = rSource.aStart.Tab(); + SCCOL nEndCol = rSource.aEnd.Col(); + SCROW nEndRow = rSource.aEnd.Row(); + SCTAB nEndTab = rSource.aEnd.Tab(); + SCCOL nDestCol = rDestPos.Col(); + SCROW nDestRow = rDestPos.Row(); + SCTAB nDestTab = rDestPos.Tab(); + + ScDocument& rDoc = rDocShell.GetDocument(); + if ( !rDoc.ValidRow(nStartRow) || !rDoc.ValidRow(nEndRow) || !rDoc.ValidRow(nDestRow) ) + { + OSL_FAIL("invalid row in MoveBlock"); + return false; + } + + // adjust related scenarios too - but only when moved within one sheet + bool bScenariosAdded = false; + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + SCTAB nTabCount = rDoc.GetTableCount(); + if ( nDestTab == nStartTab && !rDoc.IsScenario(nEndTab) ) + while ( nEndTab+1 < nTabCount && rDoc.IsScenario(nEndTab+1) ) + { + ++nEndTab; + bScenariosAdded = true; + } + + SCTAB nSrcTabCount = nEndTab-nStartTab+1; + SCTAB nDestEndTab = nDestTab+nSrcTabCount-1; + SCTAB nTab; + + ScDocumentUniquePtr pClipDoc(new ScDocument(SCDOCMODE_CLIP)); + + ScMarkData aSourceMark(rDoc.GetSheetLimits()); + for (nTab=nStartTab; nTab<=nEndTab; nTab++) + aSourceMark.SelectTable( nTab, true ); // select source + aSourceMark.SetMarkArea( rSource ); + + ScDocShellRef aDragShellRef; + if ( rDoc.HasOLEObjectsInArea( rSource ) ) + { + aDragShellRef = new ScDocShell; // DocShell needs a Ref immediately + aDragShellRef->DoInitNew(); + } + ScDrawLayer::SetGlobalDrawPersist( aDragShellRef.get() ); + + ScClipParam aClipParam(ScRange(nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nStartTab), bCut); + rDoc.CopyToClip(aClipParam, pClipDoc.get(), &aSourceMark, bScenariosAdded, true); + + ScDrawLayer::SetGlobalDrawPersist(nullptr); + + SCCOL nOldEndCol = nEndCol; + SCROW nOldEndRow = nEndRow; + bool bClipOver = false; + for (nTab=nStartTab; nTab<=nEndTab; nTab++) + { + SCCOL nTmpEndCol = nOldEndCol; + SCROW nTmpEndRow = nOldEndRow; + if (rDoc.ExtendMerge( nStartCol, nStartRow, nTmpEndCol, nTmpEndRow, nTab )) + bClipOver = true; + if ( nTmpEndCol > nEndCol ) nEndCol = nTmpEndCol; + if ( nTmpEndRow > nEndRow ) nEndRow = nTmpEndRow; + } + + SCCOL nDestEndCol = nDestCol + ( nOldEndCol-nStartCol ); + SCROW nDestEndRow = nDestRow + ( nOldEndRow-nStartRow ); + + SCCOL nUndoEndCol = nDestCol + ( nEndCol-nStartCol ); // extended in destination block + SCROW nUndoEndRow = nDestRow + ( nEndRow-nStartRow ); + + bool bIncludeFiltered = bCut; + if ( !bIncludeFiltered ) + { + // adjust sizes to include only non-filtered rows + + SCCOL nClipX; + SCROW nClipY; + pClipDoc->GetClipArea( nClipX, nClipY, false ); + SCROW nUndoAdd = nUndoEndRow - nDestEndRow; + nDestEndRow = nDestRow + nClipY; + nUndoEndRow = nDestEndRow + nUndoAdd; + } + + if (!rDoc.ValidCol(nUndoEndCol) || !rDoc.ValidRow(nUndoEndRow)) + { + if (!bApi) + rDocShell.ErrorMessage(STR_PASTE_FULL); + return false; + } + + // Test for cell protection + + ScEditableTester aTester; + for (nTab=nDestTab; nTab<=nDestEndTab; nTab++) + aTester.TestBlock( rDoc, nTab, nDestCol,nDestRow, nUndoEndCol,nUndoEndRow ); + if (bCut) + for (nTab=nStartTab; nTab<=nEndTab; nTab++) + aTester.TestBlock( rDoc, nTab, nStartCol,nStartRow, nEndCol,nEndRow ); + + if (!aTester.IsEditable()) + { + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + return false; + } + + // Test for merged cells- when moving after delete + + if (bClipOver && !bCut) + if (rDoc.HasAttrib( nDestCol,nDestRow,nDestTab, nUndoEndCol,nUndoEndRow,nDestEndTab, + HasAttrFlags::Merged | HasAttrFlags::Overlapped )) + { // "Merge of already merged cells not possible" + if (!bApi) + rDocShell.ErrorMessage(STR_MSSG_MOVEBLOCKTO_0); + return false; + } + + // Are there borders in the cells? (for painting) + + sal_uInt16 nSourceExt = 0; + rDocShell.UpdatePaintExt( nSourceExt, nStartCol,nStartRow,nStartTab, nEndCol,nEndRow,nEndTab ); + sal_uInt16 nDestExt = 0; + rDocShell.UpdatePaintExt( nDestExt, nDestCol,nDestRow,nDestTab, nDestEndCol,nDestEndRow,nDestEndTab ); + + // do it + + ScDocumentUniquePtr pUndoDoc; + + if (bRecord) + { + bool bWholeCols = ( nStartRow == 0 && nEndRow == rDoc.MaxRow() ); + bool bWholeRows = ( nStartCol == 0 && nEndCol == rDoc.MaxCol() ); + InsertDeleteFlags nUndoFlags = (InsertDeleteFlags::ALL & ~InsertDeleteFlags::OBJECTS) | InsertDeleteFlags::NOCAPTIONS; + + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nStartTab, nEndTab, bWholeCols, bWholeRows ); + + if (bCut) + { + rDoc.CopyToDocument( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab, + nUndoFlags, false, *pUndoDoc ); + } + + if ( nDestTab != nStartTab ) + pUndoDoc->AddUndoTab( nDestTab, nDestEndTab, bWholeCols, bWholeRows ); + rDoc.CopyToDocument( nDestCol, nDestRow, nDestTab, + nDestEndCol, nDestEndRow, nDestEndTab, + nUndoFlags, false, *pUndoDoc ); + rDoc.BeginDrawUndo(); + } + + bool bSourceHeight = false; // adjust heights? + if (bCut) + { + ScMarkData aDelMark(rDoc.GetSheetLimits()); // only for tables + for (nTab=nStartTab; nTab<=nEndTab; nTab++) + { + rDoc.DeleteAreaTab( nStartCol,nStartRow, nOldEndCol,nOldEndRow, nTab, InsertDeleteFlags::ALL ); + aDelMark.SelectTable( nTab, true ); + } + rDoc.DeleteObjectsInArea( nStartCol,nStartRow, nOldEndCol,nOldEndRow, aDelMark ); + + // Test for merged cells + + if (bClipOver) + if (rDoc.HasAttrib( nDestCol,nDestRow,nDestTab, + nUndoEndCol,nUndoEndRow,nDestEndTab, + HasAttrFlags::Merged | HasAttrFlags::Overlapped )) + { + rDoc.CopyFromClip( rSource, aSourceMark, InsertDeleteFlags::ALL, nullptr, pClipDoc.get() ); + for (nTab=nStartTab; nTab<=nEndTab; nTab++) + { + SCCOL nTmpEndCol = nEndCol; + SCROW nTmpEndRow = nEndRow; + rDoc.ExtendMerge( nStartCol, nStartRow, nTmpEndCol, nTmpEndRow, nTab, true ); + } + + // Report error only after restoring content + if (!bApi) // "Merge of already merged cells not possible" + rDocShell.ErrorMessage(STR_MSSG_MOVEBLOCKTO_0); + + return false; + } + + bSourceHeight = AdjustRowHeight( rSource, false, bApi ); + } + + ScRange aPasteDest( nDestCol, nDestRow, nDestTab, nDestEndCol, nDestEndRow, nDestEndTab ); + + ScMarkData aDestMark(rDoc.GetSheetLimits()); + for (nTab=nDestTab; nTab<=nDestEndTab; nTab++) + aDestMark.SelectTable( nTab, true ); // select destination + aDestMark.SetMarkArea( aPasteDest ); + + /* Do not copy drawing objects here. While pasting, the + function ScDocument::UpdateReference() is called which calls + ScDrawLayer::MoveCells() which may move away inserted objects to wrong + positions (e.g. if source and destination range overlaps).*/ + + rDoc.CopyFromClip( + aPasteDest, aDestMark, InsertDeleteFlags::ALL & ~InsertDeleteFlags::OBJECTS, + pUndoDoc.get(), pClipDoc.get(), true, false, bIncludeFiltered); + + // skipped rows and merged cells don't mix + if ( !bIncludeFiltered && pClipDoc->HasClipFilteredRows() ) + UnmergeCells( aPasteDest, false, nullptr ); + + bool bDestHeight = AdjustRowHeight( + ScRange( 0,nDestRow,nDestTab, rDoc.MaxCol(),nDestEndRow,nDestEndTab ), + false, bApi ); + + /* Paste drawing objects after adjusting formula references + and row heights. There are no cell notes or drawing objects, if the + clipdoc does not contain a drawing layer.*/ + if ( pClipDoc->GetDrawLayer() ) + rDoc.CopyFromClip( aPasteDest, aDestMark, InsertDeleteFlags::OBJECTS, + nullptr, pClipDoc.get(), true, false, bIncludeFiltered ); + + if (bRecord) + { + ScRange aUndoRange(nStartCol, nStartRow, nStartTab, nOldEndCol, nOldEndRow, nEndTab); + ScAddress aDestPos(nDestCol, nDestRow, nDestTab); + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDragDrop>( + &rDocShell, aUndoRange, aDestPos, bCut, std::move(pUndoDoc), bScenariosAdded)); + } + + SCCOL nDestPaintEndCol = nDestEndCol; + SCROW nDestPaintEndRow = nDestEndRow; + for (nTab=nDestTab; nTab<=nDestEndTab; nTab++) + { + SCCOL nTmpEndCol = nDestEndCol; + SCROW nTmpEndRow = nDestEndRow; + rDoc.ExtendMerge( nDestCol, nDestRow, nTmpEndCol, nTmpEndRow, nTab, true ); + if (nTmpEndCol > nDestPaintEndCol) nDestPaintEndCol = nTmpEndCol; + if (nTmpEndRow > nDestPaintEndRow) nDestPaintEndRow = nTmpEndRow; + } + + if (bCut) + for (nTab=nStartTab; nTab<=nEndTab; nTab++) + rDoc.RefreshAutoFilter( nStartCol, nStartRow, nEndCol, nEndRow, nTab ); + + if (bPaint) + { + // destination range: + + SCCOL nPaintStartX = nDestCol; + SCROW nPaintStartY = nDestRow; + SCCOL nPaintEndX = nDestPaintEndCol; + SCROW nPaintEndY = nDestPaintEndRow; + PaintPartFlags nFlags = PaintPartFlags::Grid; + + if ( nStartRow==0 && nEndRow==rDoc.MaxRow() ) // copy widths too? + { + nPaintEndX = rDoc.MaxCol(); + nPaintStartY = 0; + nPaintEndY = rDoc.MaxRow(); + nFlags |= PaintPartFlags::Top; + } + if ( bDestHeight || ( nStartCol == 0 && nEndCol == rDoc.MaxCol() ) ) + { + nPaintEndY = rDoc.MaxRow(); + nPaintStartX = 0; + nPaintEndX = rDoc.MaxCol(); + nFlags |= PaintPartFlags::Left; + } + if ( bScenariosAdded ) + { + nPaintStartX = 0; + nPaintStartY = 0; + nPaintEndX = rDoc.MaxCol(); + nPaintEndY = rDoc.MaxRow(); + } + + rDocShell.PostPaint( nPaintStartX,nPaintStartY,nDestTab, + nPaintEndX,nPaintEndY,nDestEndTab, nFlags, nSourceExt | nDestExt ); + + if ( bCut ) + { + // source range: + + nPaintStartX = nStartCol; + nPaintStartY = nStartRow; + nPaintEndX = nEndCol; + nPaintEndY = nEndRow; + nFlags = PaintPartFlags::Grid; + + if ( bSourceHeight ) + { + nPaintEndY = rDoc.MaxRow(); + nPaintStartX = 0; + nPaintEndX = rDoc.MaxCol(); + nFlags |= PaintPartFlags::Left; + } + if ( bScenariosAdded ) + { + nPaintStartX = 0; + nPaintStartY = 0; + nPaintEndX = rDoc.MaxCol(); + nPaintEndY = rDoc.MaxRow(); + } + + rDocShell.PostPaint( nPaintStartX,nPaintStartY,nStartTab, + nPaintEndX,nPaintEndY,nEndTab, nFlags, nSourceExt ); + } + } + + aModificator.SetDocumentModified(); + + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) ); + + return true; +} + +static uno::Reference< uno::XInterface > GetDocModuleObject( const SfxObjectShell& rDocSh, const OUString& sCodeName ) +{ + uno::Reference< lang::XMultiServiceFactory> xSF(rDocSh.GetModel(), uno::UNO_QUERY); + uno::Reference< container::XNameAccess > xVBACodeNamedObjectAccess; + uno::Reference< uno::XInterface > xDocModuleApiObject; + if ( xSF.is() ) + { + xVBACodeNamedObjectAccess.set( xSF->createInstance("ooo.vba.VBAObjectModuleObjectProvider"), uno::UNO_QUERY ); + xDocModuleApiObject.set( xVBACodeNamedObjectAccess->getByName( sCodeName ), uno::UNO_QUERY ); + } + return xDocModuleApiObject; + +} + +static script::ModuleInfo lcl_InitModuleInfo( const SfxObjectShell& rDocSh, const OUString& sModule ) +{ + script::ModuleInfo sModuleInfo; + sModuleInfo.ModuleType = script::ModuleType::DOCUMENT; + sModuleInfo.ModuleObject = GetDocModuleObject( rDocSh, sModule ); + return sModuleInfo; +} + +void VBA_InsertModule( ScDocument& rDoc, SCTAB nTab, const OUString& sSource ) +{ + SfxObjectShell& rDocSh = *rDoc.GetDocumentShell(); + uno::Reference< script::XLibraryContainer > xLibContainer = rDocSh.GetBasicContainer(); + OSL_ENSURE( xLibContainer.is(), "No BasicContainer!" ); + + uno::Reference< container::XNameContainer > xLib; + if( xLibContainer.is() ) + { + OUString aLibName( "Standard" ); +#if HAVE_FEATURE_SCRIPTING + if ( rDocSh.GetBasicManager() && !rDocSh.GetBasicManager()->GetName().isEmpty() ) + { + aLibName = rDocSh.GetBasicManager()->GetName(); + } +#endif + uno::Any aLibAny = xLibContainer->getByName( aLibName ); + aLibAny >>= xLib; + } + if( !xLib.is() ) + return; + + // if the Module with codename exists then find a new name + sal_Int32 nNum = 1; + OUString genModuleName = "Sheet1"; + while( xLib->hasByName( genModuleName ) ) + genModuleName = "Sheet" + OUString::number( ++nNum ); + + uno::Any aSourceAny; + OUString sTmpSource = sSource; + if ( sTmpSource.isEmpty() ) + sTmpSource = "Rem Attribute VBA_ModuleType=VBADocumentModule\nOption VBASupport 1\n"; + aSourceAny <<= sTmpSource; + uno::Reference< script::vba::XVBAModuleInfo > xVBAModuleInfo( xLib, uno::UNO_QUERY ); + if ( xVBAModuleInfo.is() ) + { + rDoc.SetCodeName( nTab, genModuleName ); + script::ModuleInfo sModuleInfo = lcl_InitModuleInfo( rDocSh, genModuleName ); + xVBAModuleInfo->insertModuleInfo( genModuleName, sModuleInfo ); + xLib->insertByName( genModuleName, aSourceAny ); + } +} + +void VBA_DeleteModule( ScDocShell& rDocSh, const OUString& sModuleName ) +{ + uno::Reference< script::XLibraryContainer > xLibContainer = rDocSh.GetBasicContainer(); + OSL_ENSURE( xLibContainer.is(), "No BasicContainer!" ); + + uno::Reference< container::XNameContainer > xLib; + if( xLibContainer.is() ) + { + OUString aLibName( "Standard" ); +#if HAVE_FEATURE_SCRIPTING + if ( rDocSh.GetBasicManager() && !rDocSh.GetBasicManager()->GetName().isEmpty() ) + { + aLibName = rDocSh.GetBasicManager()->GetName(); + } +#endif + uno::Any aLibAny = xLibContainer->getByName( aLibName ); + aLibAny >>= xLib; + } + if( xLib.is() ) + { + uno::Reference< script::vba::XVBAModuleInfo > xVBAModuleInfo( xLib, uno::UNO_QUERY ); + if( xLib->hasByName( sModuleName ) ) + xLib->removeByName( sModuleName ); + if ( xVBAModuleInfo.is() && xVBAModuleInfo->hasModuleInfo(sModuleName) ) + xVBAModuleInfo->removeModuleInfo( sModuleName ); + + } +} + +bool ScDocFunc::InsertTable( SCTAB nTab, const OUString& rName, bool bRecord, bool bApi ) +{ + bool bSuccess = false; + weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() ); + + ScDocShellModificator aModificator( rDocShell ); + + ScDocument& rDoc = rDocShell.GetDocument(); + + // Strange loop, also basic is loaded too early ( InsertTable ) + // is called via the xml import for sheets in described in ODF + bool bInsertDocModule = false; + + if( !rDocShell.GetDocument().IsImportingXML() ) + { + bInsertDocModule = rDoc.IsInVBAMode(); + } + if ( bInsertDocModule || ( bRecord && !rDoc.IsUndoEnabled() ) ) + bRecord = false; + + if (bRecord) + rDoc.BeginDrawUndo(); // InsertTab generates SdrUndoNewPage + + SCTAB nTabCount = rDoc.GetTableCount(); + bool bAppend = ( nTab >= nTabCount ); + if ( bAppend ) + nTab = nTabCount; // important for Undo + + if (rDoc.InsertTab( nTab, rName )) + { + if (bRecord) + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoInsertTab>( &rDocShell, nTab, bAppend, rName)); + // Update views: + // Only insert vba modules if vba mode ( and not currently importing XML ) + if( bInsertDocModule ) + { + VBA_InsertModule( rDoc, nTab, OUString() ); + } + rDocShell.Broadcast( ScTablesHint( SC_TAB_INSERTED, nTab ) ); + + rDocShell.PostPaintExtras(); + aModificator.SetDocumentModified(); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); + bSuccess = true; + } + else if (!bApi) + rDocShell.ErrorMessage(STR_TABINSERT_ERROR); + + return bSuccess; +} + +bool ScDocFunc::DeleteTable( SCTAB nTab, bool bRecord ) +{ + weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() ); + + ScDocShellModificator aModificator( rDocShell ); + + bool bSuccess = false; + ScDocument& rDoc = rDocShell.GetDocument(); + bool bVbaEnabled = rDoc.IsInVBAMode(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + if ( bVbaEnabled ) + bRecord = false; + bool bWasLinked = rDoc.IsLinked(nTab); + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr<ScRefUndoData> pUndoData; + if (bRecord) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + SCTAB nCount = rDoc.GetTableCount(); + + pUndoDoc->InitUndo( rDoc, nTab, nTab, true, true ); // only nTab with Flags + pUndoDoc->AddUndoTab( 0, nCount-1 ); // all sheets for references + + rDoc.CopyToDocument(0,0,nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab, InsertDeleteFlags::ALL,false, *pUndoDoc ); + OUString aOldName; + rDoc.GetName( nTab, aOldName ); + pUndoDoc->RenameTab( nTab, aOldName ); + if (bWasLinked) + pUndoDoc->SetLink( nTab, rDoc.GetLinkMode(nTab), rDoc.GetLinkDoc(nTab), + rDoc.GetLinkFlt(nTab), rDoc.GetLinkOpt(nTab), + rDoc.GetLinkTab(nTab), + rDoc.GetLinkRefreshDelay(nTab) ); + + if ( rDoc.IsScenario(nTab) ) + { + pUndoDoc->SetScenario( nTab, true ); + OUString aComment; + Color aColor; + ScScenarioFlags nScenFlags; + rDoc.GetScenarioData( nTab, aComment, aColor, nScenFlags ); + pUndoDoc->SetScenarioData( nTab, aComment, aColor, nScenFlags ); + bool bActive = rDoc.IsActiveScenario( nTab ); + pUndoDoc->SetActiveScenario( nTab, bActive ); + } + pUndoDoc->SetVisible( nTab, rDoc.IsVisible( nTab ) ); + pUndoDoc->SetTabBgColor( nTab, rDoc.GetTabBgColor(nTab) ); + auto pSheetEvents = rDoc.GetSheetEvents( nTab ); + pUndoDoc->SetSheetEvents( nTab, std::unique_ptr<ScSheetEvents>(pSheetEvents ? new ScSheetEvents(*pSheetEvents) : nullptr) ); + + // Drawing-Layer has to take care of its own undo!!! + rDoc.BeginDrawUndo(); // DeleteTab generates SdrUndoDelPage + + pUndoData.reset(new ScRefUndoData( &rDoc )); + } + + if (rDoc.DeleteTab(nTab)) + { + if (bRecord) + { + vector<SCTAB> theTabs; + theTabs.push_back(nTab); + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDeleteTab>( &rDocShell, theTabs, std::move(pUndoDoc), std::move(pUndoData) )); + } + // Update views: + if( bVbaEnabled ) + { + OUString sCodeName; + if( rDoc.GetCodeName( nTab, sCodeName ) ) + { + VBA_DeleteModule( rDocShell, sCodeName ); + } + } + rDocShell.Broadcast( ScTablesHint( SC_TAB_DELETED, nTab ) ); + + if (bWasLinked) + { + rDocShell.UpdateLinks(); // update Link-Manager + SfxBindings* pBindings = rDocShell.GetViewBindings(); + if (pBindings) + pBindings->Invalidate(SID_LINKS); + } + + rDocShell.PostPaintExtras(); + aModificator.SetDocumentModified(); + + SfxApplication* pSfxApp = SfxGetpApp(); // Navigator + pSfxApp->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); + pSfxApp->Broadcast( SfxHint( SfxHintId::ScAreasChanged ) ); + pSfxApp->Broadcast( SfxHint( SfxHintId::ScDbAreasChanged ) ); + pSfxApp->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) ); + + bSuccess = true; + } + return bSuccess; +} + +void ScDocFunc::SetTableVisible( SCTAB nTab, bool bVisible, bool bApi ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + bool bUndo(rDoc.IsUndoEnabled()); + if ( rDoc.IsVisible( nTab ) == bVisible ) + return; // nothing to do - ok + + if ( !rDoc.IsDocEditable() ) + { + if (!bApi) + rDocShell.ErrorMessage(STR_PROTECTIONERR); + return; + } + + ScDocShellModificator aModificator( rDocShell ); + + if ( !bVisible && !rDoc.IsImportingXML() ) // #i57869# allow hiding in any order for loading + { + // do not disable all sheets + + sal_uInt16 nVisCount = 0; + SCTAB nCount = rDoc.GetTableCount(); + for (SCTAB i=0; i<nCount && nVisCount<2; i++) + if (rDoc.IsVisible(i)) + ++nVisCount; + + if (nVisCount <= 1) + { + if (!bApi) + rDocShell.ErrorMessage(STR_PROTECTIONERR); //! separate error message? + return; + } + } + + rDoc.SetVisible( nTab, bVisible ); + if (bUndo) + { + std::vector<SCTAB> undoTabs { nTab }; + rDocShell.GetUndoManager()->AddUndoAction( std::make_unique<ScUndoShowHideTab>( &rDocShell, std::move(undoTabs), bVisible ) ); + } + + // update views + if (!bVisible) + rDocShell.Broadcast( ScTablesHint( SC_TAB_HIDDEN, nTab ) ); + + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); + rDocShell.PostPaint(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB, PaintPartFlags::Extras); + aModificator.SetDocumentModified(); +} + +bool ScDocFunc::SetLayoutRTL( SCTAB nTab, bool bRTL ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + bool bUndo(rDoc.IsUndoEnabled()); + if ( rDoc.IsLayoutRTL( nTab ) == bRTL ) + return true; // nothing to do - ok + + //! protection (sheet or document?) + + ScDocShellModificator aModificator( rDocShell ); + + rDoc.SetLayoutRTL( nTab, bRTL, ScObjectHandling::MirrorRTLMode); + + if (bUndo) + { + rDocShell.GetUndoManager()->AddUndoAction( std::make_unique<ScUndoLayoutRTL>( &rDocShell, nTab, bRTL ) ); + } + + rDocShell.PostPaint( 0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB, PaintPartFlags::All ); + aModificator.SetDocumentModified(); + + SfxBindings* pBindings = rDocShell.GetViewBindings(); + if (pBindings) + { + pBindings->Invalidate( FID_TAB_RTL ); + pBindings->Invalidate( SID_ATTR_SIZE ); + } + + return true; +} + +bool ScDocFunc::RenameTable( SCTAB nTab, const OUString& rName, bool bRecord, bool bApi ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + if ( !rDoc.IsDocEditable() ) + { + if (!bApi) + rDocShell.ErrorMessage(STR_PROTECTIONERR); + return false; + } + + ScDocShellModificator aModificator( rDocShell ); + + bool bSuccess = false; + OUString sOldName; + rDoc.GetName(nTab, sOldName); + if (rDoc.RenameTab( nTab, rName )) + { + if (bRecord) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoRenameTab>( &rDocShell, nTab, sOldName, rName)); + } + rDocShell.PostPaintExtras(); + aModificator.SetDocumentModified(); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreasChanged ) ); + + bSuccess = true; + } + return bSuccess; +} + +bool ScDocFunc::SetTabBgColor( SCTAB nTab, const Color& rColor, bool bRecord, bool bApi ) +{ + + ScDocument& rDoc = rDocShell.GetDocument(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + if ( !rDoc.IsDocEditable() || rDoc.IsTabProtected(nTab) ) + { + if (!bApi) + rDocShell.ErrorMessage(STR_PROTECTIONERR); //TODO Check to see what this string is... + return false; + } + + Color aOldTabBgColor = rDoc.GetTabBgColor(nTab); + + bool bSuccess = false; + rDoc.SetTabBgColor(nTab, rColor); + if ( rDoc.GetTabBgColor(nTab) == rColor) + bSuccess = true; + if (bSuccess) + { + if (bRecord) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoTabColor>( &rDocShell, nTab, aOldTabBgColor, rColor)); + } + rDocShell.PostPaintExtras(); + ScDocShellModificator aModificator( rDocShell ); + aModificator.SetDocumentModified(); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); + + bSuccess = true; + } + return bSuccess; +} + +bool ScDocFunc::SetTabBgColor( + ScUndoTabColorInfo::List& rUndoTabColorList, bool bApi ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + + if ( !rDoc.IsDocEditable() ) + { + if (!bApi) + rDocShell.ErrorMessage(STR_PROTECTIONERR); //TODO Get a better String Error... + return false; + } + + sal_uInt16 nTab; + Color aNewTabBgColor; + bool bSuccess = true; + size_t nTabProtectCount = 0; + size_t nTabListCount = rUndoTabColorList.size(); + for ( size_t i = 0; i < nTabListCount; ++i ) + { + ScUndoTabColorInfo& rInfo = rUndoTabColorList[i]; + nTab = rInfo.mnTabId; + if ( !rDoc.IsTabProtected(nTab) ) + { + aNewTabBgColor = rInfo.maNewTabBgColor; + rInfo.maOldTabBgColor = rDoc.GetTabBgColor(nTab); + rDoc.SetTabBgColor(nTab, aNewTabBgColor); + if ( rDoc.GetTabBgColor(nTab) != aNewTabBgColor) + { + bSuccess = false; + break; + } + } + else + { + nTabProtectCount++; + } + } + + if ( nTabProtectCount == nTabListCount ) + { + if (!bApi) + rDocShell.ErrorMessage(STR_PROTECTIONERR); //TODO Get a better String Error... + return false; + } + + if (bSuccess) + { + if (bRecord) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoTabColor>( &rDocShell, std::vector(rUndoTabColorList))); + } + rDocShell.PostPaintExtras(); + ScDocShellModificator aModificator( rDocShell ); + aModificator.SetDocumentModified(); + } + return bSuccess; +} + +//! SetWidthOrHeight - duplicated in ViewFunc !!!!!! +//! Problems: +//! - Optimal height of text cells is different for a printer and a screen +//! - Optimal width needs a selection in order to take only selected cells into account + +static sal_uInt16 lcl_GetOptimalColWidth( ScDocShell& rDocShell, SCCOL nCol, SCTAB nTab ) +{ + ScSizeDeviceProvider aProv(&rDocShell); + OutputDevice* pDev = aProv.GetDevice(); // has pixel MapMode + double nPPTX = aProv.GetPPTX(); + double nPPTY = aProv.GetPPTY(); + + ScDocument& rDoc = rDocShell.GetDocument(); + Fraction aOne(1,1); + sal_uInt16 nTwips = rDoc.GetOptimalColWidth( nCol, nTab, pDev, nPPTX, nPPTY, aOne, aOne, + false/*bFormula*/ ); + + return nTwips; +} + +bool ScDocFunc::SetWidthOrHeight( + bool bWidth, const std::vector<sc::ColRowSpan>& rRanges, SCTAB nTab, + ScSizeMode eMode, sal_uInt16 nSizeTwips, bool bRecord, bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + + if (rRanges.empty()) + return true; + + ScDocument& rDoc = rDocShell.GetDocument(); + if ( bRecord && !rDoc.IsUndoEnabled() ) + bRecord = false; + + // import into read-only document is possible + if ( !rDoc.IsChangeReadOnlyEnabled() && !rDocShell.IsEditable() ) + { + if (!bApi) + rDocShell.ErrorMessage(STR_PROTECTIONERR); //! separate error message? + return false; + } + + SCCOLROW nStart = rRanges[0].mnStart; + SCCOLROW nEnd = rRanges[0].mnEnd; + + if ( eMode == SC_SIZE_OPTIMAL ) + { + //! Option "Show formulas" - but where to get them from? + } + + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr<ScOutlineTable> pUndoTab; + std::vector<sc::ColRowSpan> aUndoRanges; + + if ( bRecord ) + { + rDoc.BeginDrawUndo(); // Drawing Updates + + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + if (bWidth) + { + pUndoDoc->InitUndo( rDoc, nTab, nTab, true ); + rDoc.CopyToDocument( static_cast<SCCOL>(nStart), 0, nTab, static_cast<SCCOL>(nEnd), rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, false, *pUndoDoc ); + } + else + { + pUndoDoc->InitUndo( rDoc, nTab, nTab, false, true ); + rDoc.CopyToDocument( 0, static_cast<SCROW>(nStart), nTab, rDoc.MaxCol(), static_cast<SCROW>(nEnd), nTab, InsertDeleteFlags::NONE, false, *pUndoDoc ); + } + + aUndoRanges = rRanges; + + ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab ); + if (pTable) + pUndoTab.reset(new ScOutlineTable( *pTable )); + } + + bool bShow = nSizeTwips > 0 || eMode != SC_SIZE_DIRECT; + bool bOutline = false; + + for (const sc::ColRowSpan& rRange : rRanges) + { + SCCOLROW nStartNo = rRange.mnStart; + SCCOLROW nEndNo = rRange.mnEnd; + + if ( !bWidth ) // deal with heights always in blocks + { + if ( eMode==SC_SIZE_OPTIMAL || eMode==SC_SIZE_VISOPT ) + { + bool bAll = ( eMode==SC_SIZE_OPTIMAL ); + if (!bAll) + { + // delete for all that have CRFlags::ManualSize enabled + // then SetOptimalHeight with bShrink = FALSE + for (SCROW nRow=nStartNo; nRow<=nEndNo; nRow++) + { + CRFlags nOld = rDoc.GetRowFlags(nRow,nTab); + SCROW nLastRow = -1; + bool bHidden = rDoc.RowHidden(nRow, nTab, nullptr, &nLastRow); + if ( !bHidden && ( nOld & CRFlags::ManualSize ) ) + rDoc.SetRowFlags( nRow, nTab, nOld & ~CRFlags::ManualSize ); + } + } + + ScSizeDeviceProvider aProv( &rDocShell ); + Fraction aOne(1,1); + sc::RowHeightContext aCxt(rDoc.MaxRow(), aProv.GetPPTX(), aProv.GetPPTY(), aOne, aOne, aProv.GetDevice()); + aCxt.setForceAutoSize(bAll); + rDoc.SetOptimalHeight(aCxt, nStartNo, nEndNo, nTab, bApi); + + if (bAll) + rDoc.ShowRows( nStartNo, nEndNo, nTab, true ); + + // Manual flag will be set already in SetOptimalHeight if bAll=true + // (it is on when Extra-Height, otherwise off). + } + else if ( eMode==SC_SIZE_DIRECT || eMode==SC_SIZE_ORIGINAL ) + { + if (nSizeTwips) + { + rDoc.SetRowHeightRange( nStartNo, nEndNo, nTab, nSizeTwips ); + rDoc.SetManualHeight( nStartNo, nEndNo, nTab, true ); // height was set manually + } + if ( eMode != SC_SIZE_ORIGINAL ) + rDoc.ShowRows( nStartNo, nEndNo, nTab, nSizeTwips != 0 ); + } + else if ( eMode==SC_SIZE_SHOW ) + { + rDoc.ShowRows( nStartNo, nEndNo, nTab, true ); + } + } + else // Column widths + { + for (SCCOL nCol=static_cast<SCCOL>(nStartNo); nCol<=static_cast<SCCOL>(nEndNo); nCol++) + { + if ( eMode != SC_SIZE_VISOPT || !rDoc.ColHidden(nCol, nTab) ) + { + sal_uInt16 nThisSize = nSizeTwips; + + if ( eMode==SC_SIZE_OPTIMAL || eMode==SC_SIZE_VISOPT ) + nThisSize = nSizeTwips + + lcl_GetOptimalColWidth( rDocShell, nCol, nTab ); + if ( nThisSize ) + rDoc.SetColWidth( nCol, nTab, nThisSize ); + + if ( eMode != SC_SIZE_ORIGINAL ) + rDoc.ShowCol( nCol, nTab, bShow ); + } + } + } + + // adjust outlines + + if ( eMode != SC_SIZE_ORIGINAL ) + { + if (bWidth) + bOutline = bOutline || rDoc.UpdateOutlineCol( + static_cast<SCCOL>(nStartNo), + static_cast<SCCOL>(nEndNo), nTab, bShow ); + else + bOutline = bOutline || rDoc.UpdateOutlineRow( + static_cast<SCROW>(nStartNo), + static_cast<SCROW>(nEndNo), nTab, bShow ); + } + } + rDoc.SetDrawPageSize(nTab); + + if (!bOutline) + pUndoTab.reset(); + + if (bRecord) + { + ScMarkData aMark(rDoc.GetSheetLimits()); + aMark.SelectOneTable( nTab ); + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoWidthOrHeight>( + &rDocShell, aMark, nStart, nTab, nEnd, nTab, std::move(pUndoDoc), + std::move(aUndoRanges), std::move(pUndoTab), eMode, nSizeTwips, bWidth)); + } + + rDoc.UpdatePageBreaks( nTab ); + + ScTabViewShell* pViewSh = rDocShell.GetBestViewShell(); + if (pViewSh) + pViewSh->OnLOKSetWidthOrHeight(nStart, bWidth); + + rDocShell.PostPaint(0,0,nTab,rDoc.MaxCol(),rDoc.MaxRow(),nTab,PaintPartFlags::All); + aModificator.SetDocumentModified(); + + return false; +} + +bool ScDocFunc::InsertPageBreak( bool bColumn, const ScAddress& rPos, + bool bRecord, bool bSetModified ) +{ + ScDocShellModificator aModificator( rDocShell ); + + ScDocument& rDoc = rDocShell.GetDocument(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + SCTAB nTab = rPos.Tab(); + SfxBindings* pBindings = rDocShell.GetViewBindings(); + + SCCOLROW nPos = bColumn ? static_cast<SCCOLROW>(rPos.Col()) : + static_cast<SCCOLROW>(rPos.Row()); + if (nPos == 0) + return false; // first column / row + + ScBreakType nBreak = bColumn ? + rDoc.HasColBreak(static_cast<SCCOL>(nPos), nTab) : + rDoc.HasRowBreak(static_cast<SCROW>(nPos), nTab); + if (nBreak & ScBreakType::Manual) + return true; + + if (bRecord) + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoPageBreak>( &rDocShell, rPos.Col(), rPos.Row(), nTab, bColumn, true ) ); + + if (bColumn) + rDoc.SetColBreak(static_cast<SCCOL>(nPos), nTab, false, true); + else + rDoc.SetRowBreak(static_cast<SCROW>(nPos), nTab, false, true); + + rDoc.InvalidatePageBreaks(nTab); + rDoc.UpdatePageBreaks( nTab ); + + rDoc.SetStreamValid(nTab, false); + + if (bColumn) + { + rDocShell.PostPaint( static_cast<SCCOL>(nPos)-1, 0, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, PaintPartFlags::Grid ); + if (pBindings) + { + pBindings->Invalidate( FID_INS_COLBRK ); + pBindings->Invalidate( FID_DEL_COLBRK ); + } + } + else + { + rDocShell.PostPaint( 0, static_cast<SCROW>(nPos)-1, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, PaintPartFlags::Grid ); + if (pBindings) + { + pBindings->Invalidate( FID_INS_ROWBRK ); + pBindings->Invalidate( FID_DEL_ROWBRK ); + } + } + if (pBindings) + pBindings->Invalidate( FID_DEL_MANUALBREAKS ); + + if (bSetModified) + aModificator.SetDocumentModified(); + + return true; +} + +bool ScDocFunc::RemovePageBreak( bool bColumn, const ScAddress& rPos, + bool bRecord, bool bSetModified ) +{ + ScDocShellModificator aModificator( rDocShell ); + + ScDocument& rDoc = rDocShell.GetDocument(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + SCTAB nTab = rPos.Tab(); + SfxBindings* pBindings = rDocShell.GetViewBindings(); + + SCCOLROW nPos = bColumn ? static_cast<SCCOLROW>(rPos.Col()) : + static_cast<SCCOLROW>(rPos.Row()); + + ScBreakType nBreak; + if (bColumn) + nBreak = rDoc.HasColBreak(static_cast<SCCOL>(nPos), nTab); + else + nBreak = rDoc.HasRowBreak(static_cast<SCROW>(nPos), nTab); + if (!(nBreak & ScBreakType::Manual)) + // There is no manual break. + return false; + + if (bRecord) + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoPageBreak>( &rDocShell, rPos.Col(), rPos.Row(), nTab, bColumn, false ) ); + + if (bColumn) + rDoc.RemoveColBreak(static_cast<SCCOL>(nPos), nTab, false, true); + else + rDoc.RemoveRowBreak(static_cast<SCROW>(nPos), nTab, false, true); + + rDoc.UpdatePageBreaks( nTab ); + + rDoc.SetStreamValid(nTab, false); + + if (bColumn) + { + rDocShell.PostPaint( static_cast<SCCOL>(nPos)-1, 0, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, PaintPartFlags::Grid ); + if (pBindings) + { + pBindings->Invalidate( FID_INS_COLBRK ); + pBindings->Invalidate( FID_DEL_COLBRK ); + } + } + else + { + rDocShell.PostPaint( 0, nPos-1, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, PaintPartFlags::Grid ); + if (pBindings) + { + pBindings->Invalidate( FID_INS_ROWBRK ); + pBindings->Invalidate( FID_DEL_ROWBRK ); + } + } + if (pBindings) + pBindings->Invalidate( FID_DEL_MANUALBREAKS ); + + if (bSetModified) + aModificator.SetDocumentModified(); + + return true; +} + +void ScDocFunc::ProtectSheet( SCTAB nTab, const ScTableProtection& rProtect ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + + std::unique_ptr<ScTableProtection> p; + if (!rProtect.isProtected() && rDoc.IsUndoEnabled()) + { + // In case of unprotecting, use a copy of passed ScTableProtection object for undo + p = std::make_unique<ScTableProtection>(rProtect); + } + rDoc.SetTabProtection(nTab, &rProtect); + if (rDoc.IsUndoEnabled()) + { + if (!p) + { + // For protection case, use a copy of resulting ScTableProtection for undo + const ScTableProtection* pProtect = rDoc.GetTabProtection(nTab); + p = std::make_unique<ScTableProtection>(*pProtect); + } + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoTabProtect>(&rDocShell, nTab, std::move(p))); + // ownership of unique_ptr now transferred to ScUndoTabProtect. + } + for (SfxViewFrame* fr = SfxViewFrame::GetFirst(&rDocShell); fr; + fr = SfxViewFrame::GetNext(*fr, &rDocShell)) + if (ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(fr->GetViewShell())) + pTabViewShell->SetTabProtectionSymbol(nTab, rProtect.isProtected()); + rDocShell.PostPaintGridAll(); + ScDocShellModificator aModificator(rDocShell); + aModificator.SetDocumentModified(); +} + +void ScDocFunc::ProtectDocument(const ScDocProtection& rProtect) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + + std::unique_ptr<ScDocProtection> p; + if (!rProtect.isProtected() && rDoc.IsUndoEnabled()) + { + // In case of unprotecting, use a copy of passed ScTableProtection object for undo + p = std::make_unique<ScDocProtection>(rProtect); + } + rDoc.SetDocProtection(&rProtect); + if (rDoc.IsUndoEnabled()) + { + if (!p) + { + // For protection case, use a copy of resulting ScTableProtection for undo + ScDocProtection* pProtect = rDoc.GetDocProtection(); + p = std::make_unique<ScDocProtection>(*pProtect); + } + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDocProtect>(&rDocShell, std::move(p))); + // ownership of unique_ptr now transferred to ScUndoTabProtect. + } + + rDocShell.PostPaintGridAll(); + ScDocShellModificator aModificator(rDocShell); + aModificator.SetDocumentModified(); +} + +bool ScDocFunc::Protect( SCTAB nTab, const OUString& rPassword ) +{ + if (nTab == TABLEID_DOC) + { + // document protection + ScDocProtection aProtection; + aProtection.setProtected(true); + aProtection.setPassword(rPassword); + ProtectDocument(aProtection); + + } + else + { + // sheet protection + + const ScTableProtection* pOldProtection = rDocShell.GetDocument().GetTabProtection(nTab); + ::std::unique_ptr<ScTableProtection> pNewProtection(pOldProtection ? new ScTableProtection(*pOldProtection) : new ScTableProtection()); + pNewProtection->setProtected(true); + pNewProtection->setPassword(rPassword); + ProtectSheet(nTab, *pNewProtection); + } + return true; +} + +bool ScDocFunc::Unprotect( SCTAB nTab, const OUString& rPassword, bool bApi ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + + if (nTab == TABLEID_DOC) + { + // document protection + + ScDocProtection* pDocProtect = rDoc.GetDocProtection(); + if (!pDocProtect || !pDocProtect->isProtected()) + // already unprotected (should not happen)! + return true; + + if (!pDocProtect->verifyPassword(rPassword)) + { + if (!bApi) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(), + VclMessageType::Info, VclButtonsType::Ok, + ScResId(SCSTR_WRONGPASSWORD))); + xInfoBox->run(); + } + return false; + } + + ScDocProtection aNewProtection(*pDocProtect); + aNewProtection.setProtected(false); + ProtectDocument(aNewProtection); + + } + else + { + // sheet protection + + const ScTableProtection* pTabProtect = rDoc.GetTabProtection(nTab); + if (!pTabProtect || !pTabProtect->isProtected()) + // already unprotected (should not happen)! + return true; + if (!pTabProtect->verifyPassword(rPassword)) + { + if (!bApi) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(), + VclMessageType::Info, VclButtonsType::Ok, + ScResId(SCSTR_WRONGPASSWORD))); + xInfoBox->run(); + } + return false; + } + + ScTableProtection aNewProtection(*pTabProtect); + aNewProtection.setProtected(false); + ProtectSheet(nTab, aNewProtection); + } + + return true; +} + +void ScDocFunc::ClearItems( const ScMarkData& rMark, const sal_uInt16* pWhich, bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + + ScDocument& rDoc = rDocShell.GetDocument(); + bool bUndo (rDoc.IsUndoEnabled()); + ScEditableTester aTester( rDoc, rMark ); + if (!aTester.IsEditable()) + { + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + return; + } + + // #i12940# ClearItems is called (from setPropertyToDefault) directly with uno object's cached + // MarkData (GetMarkData), so rMark must be changed to multi selection for ClearSelectionItems + // here. + + ScMarkData aMultiMark = rMark; + aMultiMark.SetMarking(false); // for MarkToMulti + aMultiMark.MarkToMulti(); + const ScRange& aMarkRange = aMultiMark.GetMultiMarkArea(); + + if (bUndo) + { + SCTAB nStartTab = aMarkRange.aStart.Tab(); + SCTAB nEndTab = aMarkRange.aEnd.Tab(); + + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nStartTab, nEndTab ); + rDoc.CopyToDocument( aMarkRange, InsertDeleteFlags::ATTRIB, true, *pUndoDoc, &aMultiMark ); + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoClearItems>( &rDocShell, aMultiMark, std::move(pUndoDoc), pWhich ) ); + } + + rDoc.ClearSelectionItems( pWhich, aMultiMark ); + + rDocShell.PostPaint( aMarkRange, PaintPartFlags::Grid, SC_PF_LINES | SC_PF_TESTMERGE ); + aModificator.SetDocumentModified(); + + //! Bindings-Invalidate etc.? +} + +bool ScDocFunc::ChangeIndent( const ScMarkData& rMark, bool bIncrement, bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + + ScDocument& rDoc = rDocShell.GetDocument(); + bool bUndo(rDoc.IsUndoEnabled()); + ScEditableTester aTester( rDoc, rMark ); + if (!aTester.IsEditable()) + { + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + return false; + } + + const ScRange& aMarkRange = rMark.GetMultiMarkArea(); + + if (bUndo) + { + SCTAB nStartTab = aMarkRange.aStart.Tab(); + SCTAB nTabCount = rDoc.GetTableCount(); + + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nStartTab, nStartTab ); + for (const auto& rTab : rMark) + { + if (rTab >= nTabCount) + break; + + if (rTab != nStartTab) + pUndoDoc->AddUndoTab( rTab, rTab ); + } + + ScRange aCopyRange = aMarkRange; + aCopyRange.aStart.SetTab(0); + aCopyRange.aEnd.SetTab(nTabCount-1); + rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ATTRIB, true, *pUndoDoc, &rMark ); + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoIndent>( &rDocShell, rMark, std::move(pUndoDoc), bIncrement ) ); + } + + rDoc.ChangeSelectionIndent( bIncrement, rMark ); + + rDocShell.PostPaint( aMarkRange, PaintPartFlags::Grid, SC_PF_LINES | SC_PF_TESTMERGE ); + aModificator.SetDocumentModified(); + + SfxBindings* pBindings = rDocShell.GetViewBindings(); + if (pBindings) + { + pBindings->Invalidate( SID_ALIGNLEFT ); // ChangeIndent aligns left + pBindings->Invalidate( SID_ALIGNRIGHT ); + pBindings->Invalidate( SID_ALIGNBLOCK ); + pBindings->Invalidate( SID_ALIGNCENTERHOR ); + pBindings->Invalidate( SID_ATTR_LRSPACE ); + pBindings->Invalidate( SID_ATTR_PARA_ADJUST_LEFT ); + pBindings->Invalidate( SID_ATTR_PARA_ADJUST_RIGHT ); + pBindings->Invalidate( SID_ATTR_PARA_ADJUST_BLOCK ); + pBindings->Invalidate( SID_ATTR_PARA_ADJUST_CENTER); + // pseudo slots for Format menu + pBindings->Invalidate( SID_ALIGN_ANY_HDEFAULT ); + pBindings->Invalidate( SID_ALIGN_ANY_LEFT ); + pBindings->Invalidate( SID_ALIGN_ANY_HCENTER ); + pBindings->Invalidate( SID_ALIGN_ANY_RIGHT ); + pBindings->Invalidate( SID_ALIGN_ANY_JUSTIFIED ); + } + + return true; +} + +bool ScDocFunc::AutoFormat( const ScRange& rRange, const ScMarkData* pTabMark, + sal_uInt16 nFormatNo, bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + + ScDocument& rDoc = rDocShell.GetDocument(); + 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(); + + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + ScMarkData aMark(rDoc.GetSheetLimits()); + if (pTabMark) + aMark = *pTabMark; + else + { + for (SCTAB nTab=nStartTab; nTab<=nEndTab; nTab++) + aMark.SelectTable( nTab, true ); + } + + ScAutoFormat* pAutoFormat = ScGlobal::GetOrCreateAutoFormat(); + ScEditableTester aTester( rDoc, nStartCol,nStartRow, nEndCol,nEndRow, aMark ); + if ( nFormatNo < pAutoFormat->size() && aTester.IsEditable() ) + { + weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() ); + + bool bSize = pAutoFormat->findByIndex(nFormatNo)->GetIncludeWidthHeight(); + + SCTAB nTabCount = rDoc.GetTableCount(); + ScDocumentUniquePtr pUndoDoc; + if ( bRecord ) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nStartTab, nStartTab, bSize, bSize ); + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + if (rTab != nStartTab) + pUndoDoc->AddUndoTab( rTab, rTab, bSize, bSize ); + } + + ScRange aCopyRange = rRange; + aCopyRange.aStart.SetTab(0); + aCopyRange.aStart.SetTab(nTabCount-1); + rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ATTRIB, false, *pUndoDoc, &aMark ); + if (bSize) + { + rDoc.CopyToDocument( nStartCol,0,0, nEndCol,rDoc.MaxRow(),nTabCount-1, + InsertDeleteFlags::NONE, false, *pUndoDoc, &aMark ); + rDoc.CopyToDocument( 0,nStartRow,0, rDoc.MaxCol(),nEndRow,nTabCount-1, + InsertDeleteFlags::NONE, false, *pUndoDoc, &aMark ); + } + rDoc.BeginDrawUndo(); + } + + rDoc.AutoFormat( nStartCol, nStartRow, nEndCol, nEndRow, nFormatNo, aMark ); + + if (bSize) + { + std::vector<sc::ColRowSpan> aCols(1, sc::ColRowSpan(nStartCol,nEndCol)); + std::vector<sc::ColRowSpan> aRows(1, sc::ColRowSpan(nStartRow,nEndRow)); + + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + SetWidthOrHeight(true, aCols, rTab, SC_SIZE_VISOPT, STD_EXTRA_WIDTH, false, true); + SetWidthOrHeight(false, aRows, rTab, SC_SIZE_VISOPT, 0, false, false); + rDocShell.PostPaint( 0,0,rTab, rDoc.MaxCol(),rDoc.MaxRow(),rTab, + PaintPartFlags::Grid | PaintPartFlags::Left | PaintPartFlags::Top ); + } + } + else + { + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + bool bAdj = AdjustRowHeight( ScRange(nStartCol, nStartRow, rTab, + nEndCol, nEndRow, rTab), false, bApi ); + if (bAdj) + rDocShell.PostPaint( 0,nStartRow,rTab, rDoc.MaxCol(),rDoc.MaxRow(),rTab, + PaintPartFlags::Grid | PaintPartFlags::Left ); + else + rDocShell.PostPaint( nStartCol, nStartRow, rTab, + nEndCol, nEndRow, rTab, PaintPartFlags::Grid ); + } + } + + if ( bRecord ) // only now is Draw-Undo available + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoAutoFormat>( &rDocShell, rRange, std::move(pUndoDoc), aMark, bSize, nFormatNo ) ); + } + + aModificator.SetDocumentModified(); + } + else if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + + return false; +} + +bool ScDocFunc::EnterMatrix( const ScRange& rRange, const ScMarkData* pTabMark, + const ScTokenArray* pTokenArray, const OUString& rString, bool bApi, bool bEnglish, + const OUString& rFormulaNmsp, const formula::FormulaGrammar::Grammar eGrammar ) +{ + if (ScViewData::SelectionFillDOOM( rRange )) + return false; + + ScDocShellModificator aModificator( rDocShell ); + + bool bSuccess = false; + ScDocument& rDoc = rDocShell.GetDocument(); + 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(); + + ScMarkData aMark(rDoc.GetSheetLimits()); + if (pTabMark) + aMark = *pTabMark; + else + { + for (SCTAB nTab=nStartTab; nTab<=nEndTab; nTab++) + aMark.SelectTable( nTab, true ); + } + + ScEditableTester aTester( rDoc, nStartCol,nStartRow, nEndCol,nEndRow, aMark ); + if ( aTester.IsEditable() ) + { + weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() ); + + ScDocumentUniquePtr pUndoDoc; + + const bool bUndo(rDoc.IsUndoEnabled()); + if (bUndo) + { + //! take selected sheets into account also when undoing + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nStartTab, nEndTab ); + rDoc.CopyToDocument( rRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pUndoDoc ); + } + + // use TokenArray if given, string (and flags) otherwise + if ( pTokenArray ) + { + rDoc.InsertMatrixFormula( nStartCol, nStartRow, nEndCol, nEndRow, + aMark, OUString(), pTokenArray, eGrammar); + } + else if ( rDoc.IsImportingXML() ) + { + ScTokenArray aCode(rDoc); + aCode.AssignXMLString( rString, + ((eGrammar == formula::FormulaGrammar::GRAM_EXTERNAL) ? rFormulaNmsp : OUString())); + rDoc.InsertMatrixFormula( nStartCol, nStartRow, nEndCol, nEndRow, + aMark, OUString(), &aCode, eGrammar); + rDoc.IncXMLImportedFormulaCount( rString.getLength() ); + } + else if (bEnglish) + { + ScCompiler aComp( rDoc, rRange.aStart, eGrammar); + std::unique_ptr<ScTokenArray> pCode = aComp.CompileString( rString ); + rDoc.InsertMatrixFormula( nStartCol, nStartRow, nEndCol, nEndRow, + aMark, OUString(), pCode.get(), eGrammar); + } + else + rDoc.InsertMatrixFormula( nStartCol, nStartRow, nEndCol, nEndRow, + aMark, rString, nullptr, eGrammar); + + if (bUndo) + { + //! take selected sheets into account also when undoing + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoEnterMatrix>( &rDocShell, rRange, std::move(pUndoDoc), rString ) ); + } + + // Err522 painting of DDE-Formulas will be intercepted during interpreting + rDocShell.PostPaint( nStartCol,nStartRow,nStartTab,nEndCol,nEndRow,nEndTab, PaintPartFlags::Grid ); + aModificator.SetDocumentModified(); + + bSuccess = true; + } + else if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + + return bSuccess; +} + +bool ScDocFunc::TabOp( const ScRange& rRange, const ScMarkData* pTabMark, + const ScTabOpParam& rParam, bool bRecord, bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + + bool bSuccess = false; + ScDocument& rDoc = rDocShell.GetDocument(); + 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(); + + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + ScMarkData aMark(rDoc.GetSheetLimits()); + if (pTabMark) + aMark = *pTabMark; + else + { + for (SCTAB nTab=nStartTab; nTab<=nEndTab; nTab++) + aMark.SelectTable( nTab, true ); + } + + ScEditableTester aTester( rDoc, nStartCol,nStartRow, nEndCol,nEndRow, aMark ); + if ( aTester.IsEditable() ) + { + weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() ); + rDoc.SetDirty( rRange, false ); + if ( bRecord ) + { + //! take selected sheets into account also when undoing + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nStartTab, nEndTab ); + rDoc.CopyToDocument( rRange, InsertDeleteFlags::ALL & ~InsertDeleteFlags::NOTE, false, *pUndoDoc ); + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoTabOp>( &rDocShell, + nStartCol, nStartRow, nStartTab, + nEndCol, nEndRow, nEndTab, std::move(pUndoDoc), + rParam.aRefFormulaCell, + rParam.aRefFormulaEnd, + rParam.aRefRowCell, + rParam.aRefColCell, + rParam.meMode) ); + } + rDoc.InsertTableOp(rParam, nStartCol, nStartRow, nEndCol, nEndRow, aMark); + rDocShell.PostPaintGridAll(); + aModificator.SetDocumentModified(); + bSuccess = true; + } + else if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + + return bSuccess; +} + +static ScDirection DirFromFillDir( FillDir eDir ) +{ + if (eDir==FILL_TO_BOTTOM) + return DIR_BOTTOM; + else if (eDir==FILL_TO_RIGHT) + return DIR_RIGHT; + else if (eDir==FILL_TO_TOP) + return DIR_TOP; + else // if (eDir==FILL_TO_LEFT) + return DIR_LEFT; +} + +namespace { + +/** + * Expand the fill range as necessary, to allow copying of adjacent cell(s) + * even when those cells are not in the original range. + */ +void adjustFillRangeForAdjacentCopy(const ScDocument &rDoc, ScRange& rRange, FillDir eDir) +{ + switch (eDir) + { + case FILL_TO_BOTTOM: + { + if (rRange.aStart.Row() == 0) + return; + + if (rRange.aStart.Row() != rRange.aEnd.Row()) + return; + + // Include the above row. + ScAddress& s = rRange.aStart; + s.SetRow(s.Row()-1); + } + break; + case FILL_TO_TOP: + { + if (rRange.aStart.Row() == rDoc.MaxRow()) + return; + + if (rRange.aStart.Row() != rRange.aEnd.Row()) + return; + + // Include the row below. + ScAddress& e = rRange.aEnd; + e.SetRow(e.Row()+1); + } + break; + case FILL_TO_LEFT: + { + if (rRange.aStart.Col() == rDoc.MaxCol()) + return; + + if (rRange.aStart.Col() != rRange.aEnd.Col()) + return; + + // Include the column to the right. + ScAddress& e = rRange.aEnd; + e.SetCol(e.Col()+1); + } + break; + case FILL_TO_RIGHT: + { + if (rRange.aStart.Col() == 0) + return; + + if (rRange.aStart.Col() != rRange.aEnd.Col()) + return; + + // Include the column to the left. + ScAddress& s = rRange.aStart; + s.SetCol(s.Col()-1); + } + break; + default: + ; + } +} + +} + +bool ScDocFunc::FillSimple( const ScRange& rRange, const ScMarkData* pTabMark, + FillDir eDir, bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + ScDocument& rDoc = rDocShell.GetDocument(); + + bool bSuccess = false; + ScRange aRange = rRange; + adjustFillRangeForAdjacentCopy(rDoc, aRange, eDir); + + SCCOL nStartCol = aRange.aStart.Col(); + SCROW nStartRow = aRange.aStart.Row(); + SCTAB nStartTab = aRange.aStart.Tab(); + SCCOL nEndCol = aRange.aEnd.Col(); + SCROW nEndRow = aRange.aEnd.Row(); + SCTAB nEndTab = aRange.aEnd.Tab(); + + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + + ScMarkData aMark(rDoc.GetSheetLimits()); + if (pTabMark) + aMark = *pTabMark; + else + { + for (SCTAB nTab=nStartTab; nTab<=nEndTab; nTab++) + aMark.SelectTable( nTab, true ); + } + + ScEditableTester aTester( rDoc, nStartCol,nStartRow, nEndCol,nEndRow, aMark ); + if ( aTester.IsEditable() ) + { + weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() ); + + ScRange aSourceArea = aRange; + ScRange aDestArea = aRange; + + SCCOLROW nCount = 0; + switch (eDir) + { + case FILL_TO_BOTTOM: + nCount = aSourceArea.aEnd.Row()-aSourceArea.aStart.Row(); + aSourceArea.aEnd.SetRow( aSourceArea.aStart.Row() ); + break; + case FILL_TO_RIGHT: + nCount = aSourceArea.aEnd.Col()-aSourceArea.aStart.Col(); + aSourceArea.aEnd.SetCol( aSourceArea.aStart.Col() ); + break; + case FILL_TO_TOP: + nCount = aSourceArea.aEnd.Row()-aSourceArea.aStart.Row(); + aSourceArea.aStart.SetRow( aSourceArea.aEnd.Row() ); + break; + case FILL_TO_LEFT: + nCount = aSourceArea.aEnd.Col()-aSourceArea.aStart.Col(); + aSourceArea.aStart.SetCol( aSourceArea.aEnd.Col() ); + break; + } + + ScDocumentUniquePtr pUndoDoc; + if ( bRecord ) + { + SCTAB nTabCount = rDoc.GetTableCount(); + SCTAB nDestStartTab = aDestArea.aStart.Tab(); + + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nDestStartTab, nDestStartTab ); + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + if (rTab != nDestStartTab) + pUndoDoc->AddUndoTab( rTab, rTab ); + } + + ScRange aCopyRange = aDestArea; + aCopyRange.aStart.SetTab(0); + aCopyRange.aEnd.SetTab(nTabCount-1); + rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::AUTOFILL, false, *pUndoDoc, &aMark ); + } + + sal_uLong nProgCount; + if (eDir == FILL_TO_BOTTOM || eDir == FILL_TO_TOP) + nProgCount = aSourceArea.aEnd.Col() - aSourceArea.aStart.Col() + 1; + else + nProgCount = aSourceArea.aEnd.Row() - aSourceArea.aStart.Row() + 1; + nProgCount *= nCount; + ScProgress aProgress( rDoc.GetDocumentShell(), + ScResId(STR_FILL_SERIES_PROGRESS), nProgCount, true ); + + rDoc.Fill( aSourceArea.aStart.Col(), aSourceArea.aStart.Row(), + aSourceArea.aEnd.Col(), aSourceArea.aEnd.Row(), &aProgress, + aMark, nCount, eDir, FILL_SIMPLE ); + AdjustRowHeight(aRange, true, bApi); + + if ( bRecord ) // only now is Draw-Undo available + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoAutoFill>( &rDocShell, aDestArea, aSourceArea, std::move(pUndoDoc), aMark, + eDir, FILL_SIMPLE, FILL_DAY, MAXDOUBLE, 1.0, 1e307) ); + } + + rDocShell.PostPaintGridAll(); + aModificator.SetDocumentModified(); + + bSuccess = true; + } + else if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + + return bSuccess; +} + +bool ScDocFunc::FillSeries( const ScRange& rRange, const ScMarkData* pTabMark, + FillDir eDir, FillCmd eCmd, FillDateCmd eDateCmd, + double fStart, double fStep, double fMax, + bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + + bool bSuccess = false; + ScDocument& rDoc = rDocShell.GetDocument(); + 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(); + + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + + ScMarkData aMark(rDoc.GetSheetLimits()); + if (pTabMark) + aMark = *pTabMark; + else + { + for (SCTAB nTab=nStartTab; nTab<=nEndTab; nTab++) + aMark.SelectTable( nTab, true ); + } + + ScEditableTester aTester( rDoc, nStartCol,nStartRow, nEndCol,nEndRow, aMark ); + if ( aTester.IsEditable() ) + { + weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() ); + + ScRange aSourceArea = rRange; + ScRange aDestArea = rRange; + + SCSIZE nCount = rDoc.GetEmptyLinesInBlock( + aSourceArea.aStart.Col(), aSourceArea.aStart.Row(), aSourceArea.aStart.Tab(), + aSourceArea.aEnd.Col(), aSourceArea.aEnd.Row(), aSourceArea.aEnd.Tab(), + DirFromFillDir(eDir) ); + + // keep at least one row/column as source range + SCSIZE nTotLines = ( eDir == FILL_TO_BOTTOM || eDir == FILL_TO_TOP ) ? + static_cast<SCSIZE>( aSourceArea.aEnd.Row() - aSourceArea.aStart.Row() + 1 ) : + static_cast<SCSIZE>( aSourceArea.aEnd.Col() - aSourceArea.aStart.Col() + 1 ); + if ( nCount >= nTotLines ) + nCount = nTotLines - 1; + + switch (eDir) + { + case FILL_TO_BOTTOM: + aSourceArea.aEnd.SetRow( sal::static_int_cast<SCROW>( aSourceArea.aEnd.Row() - nCount ) ); + break; + case FILL_TO_RIGHT: + aSourceArea.aEnd.SetCol( sal::static_int_cast<SCCOL>( aSourceArea.aEnd.Col() - nCount ) ); + break; + case FILL_TO_TOP: + aSourceArea.aStart.SetRow( sal::static_int_cast<SCROW>( aSourceArea.aStart.Row() + nCount ) ); + break; + case FILL_TO_LEFT: + aSourceArea.aStart.SetCol( sal::static_int_cast<SCCOL>( aSourceArea.aStart.Col() + nCount ) ); + break; + } + + ScDocumentUniquePtr pUndoDoc; + if ( bRecord ) + { + SCTAB nTabCount = rDoc.GetTableCount(); + SCTAB nDestStartTab = aDestArea.aStart.Tab(); + + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nDestStartTab, nDestStartTab ); + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + if (rTab != nDestStartTab) + pUndoDoc->AddUndoTab( rTab, rTab ); + } + + rDoc.CopyToDocument( + aDestArea.aStart.Col(), aDestArea.aStart.Row(), 0, + aDestArea.aEnd.Col(), aDestArea.aEnd.Row(), nTabCount-1, + InsertDeleteFlags::AUTOFILL, false, *pUndoDoc, &aMark ); + } + + if (aDestArea.aStart.Col() <= aDestArea.aEnd.Col() && + aDestArea.aStart.Row() <= aDestArea.aEnd.Row()) + { + if ( fStart != MAXDOUBLE ) + { + SCCOL nValX = (eDir == FILL_TO_LEFT) ? aDestArea.aEnd.Col() : aDestArea.aStart.Col(); + SCROW nValY = (eDir == FILL_TO_TOP ) ? aDestArea.aEnd.Row() : aDestArea.aStart.Row(); + SCTAB nTab = aDestArea.aStart.Tab(); + rDoc.SetValue( nValX, nValY, nTab, fStart ); + } + + sal_uLong nProgCount; + if (eDir == FILL_TO_BOTTOM || eDir == FILL_TO_TOP) + nProgCount = aSourceArea.aEnd.Col() - aSourceArea.aStart.Col() + 1; + else + nProgCount = aSourceArea.aEnd.Row() - aSourceArea.aStart.Row() + 1; + nProgCount *= nCount; + ScProgress aProgress( rDoc.GetDocumentShell(), + ScResId(STR_FILL_SERIES_PROGRESS), nProgCount, true ); + + rDoc.Fill( aSourceArea.aStart.Col(), aSourceArea.aStart.Row(), + aSourceArea.aEnd.Col(), aSourceArea.aEnd.Row(), &aProgress, + aMark, nCount, eDir, eCmd, eDateCmd, fStep, fMax ); + AdjustRowHeight(rRange, true, bApi); + + rDocShell.PostPaintGridAll(); + aModificator.SetDocumentModified(); + } + + if ( bRecord ) // only now is Draw-Undo available + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoAutoFill>( &rDocShell, aDestArea, aSourceArea, std::move(pUndoDoc), aMark, + eDir, eCmd, eDateCmd, fStart, fStep, fMax) ); + } + + bSuccess = true; + } + else if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + + return bSuccess; +} + +bool ScDocFunc::FillAuto( ScRange& rRange, const ScMarkData* pTabMark, + FillDir eDir, sal_uLong nCount, bool bApi ) +{ + return FillAuto( rRange, pTabMark, eDir, FILL_AUTO, FILL_DAY, nCount, 1.0/*fStep*/, MAXDOUBLE/*fMax*/, true/*bRecord*/, bApi ); +} + +bool ScDocFunc::FillAuto( ScRange& rRange, const ScMarkData* pTabMark, FillDir eDir, FillCmd eCmd, FillDateCmd eDateCmd, sal_uLong nCount, double fStep, double fMax, bool bRecord, bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + + ScDocument& rDoc = rDocShell.GetDocument(); + 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(); + + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + ScMarkData aMark(rDoc.GetSheetLimits()); + if (pTabMark) + aMark = *pTabMark; + else + { + for (SCTAB nTab=nStartTab; nTab<=nEndTab; nTab++) + aMark.SelectTable( nTab, true ); + } + + ScRange aSourceArea = rRange; + ScRange aDestArea = rRange; + + switch (eDir) + { + case FILL_TO_BOTTOM: + aDestArea.aEnd.SetRow( sal::static_int_cast<SCROW>( aSourceArea.aEnd.Row() + nCount ) ); + break; + case FILL_TO_TOP: + if (nCount > sal::static_int_cast<sal_uLong>( aSourceArea.aStart.Row() )) + { + OSL_FAIL("FillAuto: Row < 0"); + nCount = aSourceArea.aStart.Row(); + } + aDestArea.aStart.SetRow( sal::static_int_cast<SCROW>( aSourceArea.aStart.Row() - nCount ) ); + break; + case FILL_TO_RIGHT: + aDestArea.aEnd.SetCol( sal::static_int_cast<SCCOL>( aSourceArea.aEnd.Col() + nCount ) ); + break; + case FILL_TO_LEFT: + if (nCount > sal::static_int_cast<sal_uLong>( aSourceArea.aStart.Col() )) + { + OSL_FAIL("FillAuto: Col < 0"); + nCount = aSourceArea.aStart.Col(); + } + aDestArea.aStart.SetCol( sal::static_int_cast<SCCOL>( aSourceArea.aStart.Col() - nCount ) ); + break; + default: + OSL_FAIL("Wrong direction with FillAuto"); + break; + } + + // Test for cell protection + //! Source range can be protected !!! + //! but can't contain matrix fragments !!! + + ScEditableTester aTester( rDoc, aDestArea ); + if ( !aTester.IsEditable() ) + { + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + return false; + } + + if ( rDoc.HasSelectedBlockMatrixFragment( nStartCol, nStartRow, + nEndCol, nEndRow, aMark ) ) + { + if (!bApi) + rDocShell.ErrorMessage(STR_MATRIXFRAGMENTERR); + return false; + } + + // FID_FILL_... slots should already had been disabled, check here for API + // calls, no message. + if (ScViewData::SelectionFillDOOM( aDestArea)) + return false; + + weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() ); + + ScDocumentUniquePtr pUndoDoc; + if ( bRecord ) + { + SCTAB nTabCount = rDoc.GetTableCount(); + SCTAB nDestStartTab = aDestArea.aStart.Tab(); + + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nDestStartTab, nDestStartTab ); + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + if (rTab != nDestStartTab) + pUndoDoc->AddUndoTab( rTab, rTab ); + } + + // do not clone note captions in undo document + rDoc.CopyToDocument( + aDestArea.aStart.Col(), aDestArea.aStart.Row(), 0, + aDestArea.aEnd.Col(), aDestArea.aEnd.Row(), nTabCount-1, + InsertDeleteFlags::AUTOFILL, false, *pUndoDoc, &aMark ); + } + + sal_uLong nProgCount; + if (eDir == FILL_TO_BOTTOM || eDir == FILL_TO_TOP) + nProgCount = aSourceArea.aEnd.Col() - aSourceArea.aStart.Col() + 1; + else + nProgCount = aSourceArea.aEnd.Row() - aSourceArea.aStart.Row() + 1; + nProgCount *= nCount; + ScProgress aProgress( rDoc.GetDocumentShell(), + ScResId(STR_FILL_SERIES_PROGRESS), nProgCount, true ); + + rDoc.Fill( aSourceArea.aStart.Col(), aSourceArea.aStart.Row(), + aSourceArea.aEnd.Col(), aSourceArea.aEnd.Row(), &aProgress, + aMark, nCount, eDir, eCmd, eDateCmd, fStep, fMax ); + + AdjustRowHeight(aDestArea, true, bApi); + + if ( bRecord ) // only now is Draw-Undo available + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoAutoFill>( &rDocShell, aDestArea, aSourceArea, std::move(pUndoDoc), aMark, + eDir, eCmd, eDateCmd, MAXDOUBLE, fStep, fMax) ); + } + + rDocShell.PostPaintGridAll(); + aModificator.SetDocumentModified(); + + rRange = aDestArea; // return destination range (for marking) + return true; +} + +bool ScDocFunc::MergeCells( const ScCellMergeOption& rOption, bool bContents, bool bRecord, bool bApi, bool bEmptyMergedCells /*=false*/ ) +{ + using ::std::set; + + ScDocShellModificator aModificator( rDocShell ); + + SCCOL nStartCol = rOption.mnStartCol; + SCROW nStartRow = rOption.mnStartRow; + SCCOL nEndCol = rOption.mnEndCol; + SCROW nEndRow = rOption.mnEndRow; + if ((nStartCol == nEndCol && nStartRow == nEndRow) || rOption.maTabs.empty()) + { + // Nothing to do. Bail out quickly + return true; + } + + ScDocument& rDoc = rDocShell.GetDocument(); + SCTAB nTab1 = *rOption.maTabs.begin(), nTab2 = *rOption.maTabs.rbegin(); + + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + for (const auto& rTab : rOption.maTabs) + { + ScEditableTester aTester( rDoc, rTab, nStartCol, nStartRow, nEndCol, nEndRow ); + if (!aTester.IsEditable()) + { + if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + return false; + } + + if ( rDoc.HasAttrib( nStartCol, nStartRow, rTab, nEndCol, nEndRow, rTab, + HasAttrFlags::Merged | HasAttrFlags::Overlapped ) ) + { + // "Merge of already merged cells not possible" + if (!bApi) + rDocShell.ErrorMessage(STR_MSSG_MERGECELLS_0); + return false; + } + } + + ScDocumentUniquePtr pUndoDoc; + bool bNeedContentsUndo = false; + for (const SCTAB nTab : rOption.maTabs) + { + bool bIsBlockEmpty = ( nStartRow == nEndRow ) + ? rDoc.IsEmptyData( nStartCol+1,nStartRow, nEndCol,nEndRow, nTab ) + : rDoc.IsEmptyData( nStartCol,nStartRow+1, nStartCol,nEndRow, nTab ) && + rDoc.IsEmptyData( nStartCol+1,nStartRow, nEndCol,nEndRow, nTab ); + bool bNeedContents = bContents && !bIsBlockEmpty; + bool bNeedEmpty = bEmptyMergedCells && !bIsBlockEmpty && !bNeedContents; // if DoMergeContents then cells are emptied + + if (bRecord) + { + // test if the range contains other notes which also implies that we need an undo document + bool bHasNotes = rDoc.HasNote(nTab, nStartCol, nStartRow, nEndCol, nEndRow); + if (!pUndoDoc) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo(rDoc, nTab1, nTab2); + } + // note captions are collected by drawing undo + rDoc.CopyToDocument( nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab, + InsertDeleteFlags::ALL|InsertDeleteFlags::NOCAPTIONS, false, *pUndoDoc ); + if( bHasNotes ) + rDoc.BeginDrawUndo(); + } + + if (bNeedContents) + rDoc.DoMergeContents( nStartCol,nStartRow, nEndCol,nEndRow, nTab ); + else if ( bNeedEmpty ) + rDoc.DoEmptyBlock( nStartCol,nStartRow, nEndCol,nEndRow, nTab ); + rDoc.DoMerge( nStartCol,nStartRow, nEndCol,nEndRow, nTab ); + + if (rOption.mbCenter) + { + rDoc.ApplyAttr( nStartCol, nStartRow, nTab, SvxHorJustifyItem( SvxCellHorJustify::Center, ATTR_HOR_JUSTIFY ) ); + rDoc.ApplyAttr( nStartCol, nStartRow, nTab, SvxVerJustifyItem( SvxCellVerJustify::Center, ATTR_VER_JUSTIFY ) ); + } + + if ( !AdjustRowHeight( ScRange( 0,nStartRow,nTab, rDoc.MaxCol(),nEndRow,nTab ), true, bApi ) ) + rDocShell.PostPaint( nStartCol, nStartRow, nTab, + nEndCol, nEndRow, nTab, PaintPartFlags::Grid ); + if (bNeedContents || rOption.mbCenter) + { + ScRange aRange(nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab); + rDoc.SetDirty(aRange, true); + } + + bool bDone = ScDetectiveFunc(rDoc, nTab).DeleteAll( ScDetectiveDelete::Circles ); + if(bDone) + DetectiveMarkInvalid(nTab); + + bNeedContentsUndo |= bNeedContents; + } + + if (pUndoDoc) + { + std::unique_ptr<SdrUndoGroup> pDrawUndo = rDoc.GetDrawLayer() ? rDoc.GetDrawLayer()->GetCalcUndo() : nullptr; + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoMerge>(&rDocShell, rOption, bNeedContentsUndo, std::move(pUndoDoc), std::move(pDrawUndo)) ); + } + + aModificator.SetDocumentModified(); + + SfxBindings* pBindings = rDocShell.GetViewBindings(); + if (pBindings) + { + pBindings->Invalidate( FID_MERGE_ON ); + pBindings->Invalidate( FID_MERGE_OFF ); + pBindings->Invalidate( FID_MERGE_TOGGLE ); + } + + return true; +} + +bool ScDocFunc::UnmergeCells( const ScRange& rRange, bool bRecord, ScUndoRemoveMerge* pUndoRemoveMerge ) +{ + ScCellMergeOption aOption(rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row()); + SCTAB nTab1 = rRange.aStart.Tab(), nTab2 = rRange.aEnd.Tab(); + for (SCTAB i = nTab1; i <= nTab2; ++i) + aOption.maTabs.insert(i); + + return UnmergeCells(aOption, bRecord, pUndoRemoveMerge); +} + +bool ScDocFunc::UnmergeCells( const ScCellMergeOption& rOption, bool bRecord, ScUndoRemoveMerge* pUndoRemoveMerge ) +{ + using ::std::set; + + if (rOption.maTabs.empty()) + // Nothing to unmerge. + return true; + + ScDocShellModificator aModificator( rDocShell ); + ScDocument& rDoc = rDocShell.GetDocument(); + + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + ScDocument* pUndoDoc = (pUndoRemoveMerge ? pUndoRemoveMerge->GetUndoDoc() : nullptr); + assert( pUndoDoc || !pUndoRemoveMerge ); + for (const SCTAB nTab : rOption.maTabs) + { + ScRange aRange = rOption.getSingleRange(nTab); + if ( !rDoc.HasAttrib(aRange, HasAttrFlags::Merged) ) + continue; + + ScRange aExtended = aRange; + rDoc.ExtendMerge(aExtended); + ScRange aRefresh = aExtended; + rDoc.ExtendOverlapped(aRefresh); + + if (bRecord) + { + if (!pUndoDoc) + { + pUndoDoc = new ScDocument( SCDOCMODE_UNDO ); + pUndoDoc->InitUndo(rDoc, *rOption.maTabs.begin(), *rOption.maTabs.rbegin()); + } + rDoc.CopyToDocument(aExtended, InsertDeleteFlags::ATTRIB, false, *pUndoDoc); + } + + const SfxPoolItem& rDefAttr = rDoc.GetPool()->GetDefaultItem( ATTR_MERGE ); + ScPatternAttr aPattern( rDoc.GetPool() ); + aPattern.GetItemSet().Put( rDefAttr ); + rDoc.ApplyPatternAreaTab( aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), nTab, + aPattern ); + + rDoc.RemoveFlagsTab( aExtended.aStart.Col(), aExtended.aStart.Row(), + aExtended.aEnd.Col(), aExtended.aEnd.Row(), nTab, + ScMF::Hor | ScMF::Ver ); + + rDoc.ExtendMerge( aRefresh, true ); + + if ( !AdjustRowHeight( aExtended, true, true ) ) + rDocShell.PostPaint( aExtended, PaintPartFlags::Grid ); + + bool bDone = ScDetectiveFunc(rDoc, nTab).DeleteAll( ScDetectiveDelete::Circles ); + if(bDone) + DetectiveMarkInvalid(nTab); + } + + if (bRecord) + { + if (pUndoRemoveMerge) + { + // If pUndoRemoveMerge was passed, the caller is responsible for + // adding it to Undo. Just add the current option. + pUndoRemoveMerge->AddCellMergeOption( rOption); + } + else + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoRemoveMerge>( &rDocShell, rOption, ScDocumentUniquePtr(pUndoDoc) ) ); + } + } + aModificator.SetDocumentModified(); + + return true; +} + +void ScDocFunc::ModifyRangeNames( const ScRangeName& rNewRanges, SCTAB nTab ) +{ + SetNewRangeNames( std::unique_ptr<ScRangeName>(new ScRangeName(rNewRanges)), true, nTab ); +} + +void ScDocFunc::SetNewRangeNames( std::unique_ptr<ScRangeName> pNewRanges, bool bModifyDoc, SCTAB nTab ) // takes ownership of pNewRanges +{ + ScDocShellModificator aModificator( rDocShell ); + + OSL_ENSURE( pNewRanges, "pNewRanges is 0" ); + ScDocument& rDoc = rDocShell.GetDocument(); + bool bUndo(rDoc.IsUndoEnabled()); + + if (bUndo) + { + ScRangeName* pOld; + if (nTab >=0) + { + pOld = rDoc.GetRangeName(nTab); + } + else + { + pOld = rDoc.GetRangeName(); + } + std::unique_ptr<ScRangeName> pUndoRanges(new ScRangeName(*pOld)); + std::unique_ptr<ScRangeName> pRedoRanges(new ScRangeName(*pNewRanges)); + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoRangeNames>( &rDocShell, std::move(pUndoRanges), std::move(pRedoRanges), nTab ) ); + } + + // #i55926# While loading XML, formula cells only have a single string token, + // so CompileNameFormula would never find any name (index) tokens, and would + // unnecessarily loop through all cells. + bool bCompile = ( !rDoc.IsImportingXML() && rDoc.GetNamedRangesLockCount() == 0 ); + + if ( bCompile ) + rDoc.PreprocessRangeNameUpdate(); + if (nTab >= 0) + rDoc.SetRangeName( nTab, std::move(pNewRanges) ); // takes ownership + else + rDoc.SetRangeName( std::move(pNewRanges) ); // takes ownership + if ( bCompile ) + rDoc.CompileHybridFormula(); + + if (bModifyDoc) + { + aModificator.SetDocumentModified(); + SfxGetpApp()->Broadcast( SfxHint(SfxHintId::ScAreasChanged) ); + } +} + +void ScDocFunc::ModifyAllRangeNames(const std::map<OUString, std::unique_ptr<ScRangeName>>& rRangeMap) +{ + ScDocShellModificator aModificator(rDocShell); + ScDocument& rDoc = rDocShell.GetDocument(); + + if (rDoc.IsUndoEnabled()) + { + std::map<OUString, ScRangeName*> aOldRangeMap; + rDoc.GetRangeNameMap(aOldRangeMap); + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoAllRangeNames>(&rDocShell, aOldRangeMap, rRangeMap)); + } + + rDoc.PreprocessAllRangeNamesUpdate(rRangeMap); + rDoc.SetAllRangeNames(rRangeMap); + rDoc.CompileHybridFormula(); + + aModificator.SetDocumentModified(); + SfxGetpApp()->Broadcast(SfxHint(SfxHintId::ScAreasChanged)); +} + +void ScDocFunc::CreateOneName( ScRangeName& rList, + SCCOL nPosX, SCROW nPosY, SCTAB nTab, + SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2, + bool& rCancel, bool bApi ) +{ + if (rCancel) + return; + + ScDocument& rDoc = rDocShell.GetDocument(); + if (rDoc.HasValueData( nPosX, nPosY, nTab )) + return; + + OUString aName = rDoc.GetString(nPosX, nPosY, nTab); + ScRangeData::MakeValidName(rDoc, aName); + if (aName.isEmpty()) + return; + + OUString aContent( ScRange( nX1, nY1, nTab, nX2, nY2, nTab ).Format( + rDoc, ScRefFlags::RANGE_ABS_3D, ScAddress::Details( rDoc.GetAddressConvention(), nPosY, nPosX))); + + bool bInsert = false; + ScRangeData* pOld = rList.findByUpperName(ScGlobal::getCharClass().uppercase(aName)); + if (pOld) + { + OUString aOldStr = pOld->GetSymbol(); + if (aOldStr != aContent) + { + if (bApi) + bInsert = true; // don't check via API + else + { + OUString aTemplate = ScResId( STR_CREATENAME_REPLACE ); + OUString aMessage = o3tl::getToken(aTemplate, 0, '#' ) + aName + o3tl::getToken(aTemplate, 1, '#' ); + + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(), + VclMessageType::Question, VclButtonsType::YesNo, + aMessage)); + xQueryBox->add_button(GetStandardText(StandardButtonType::Cancel), RET_CANCEL); + xQueryBox->set_default_response(RET_YES); + + short nResult = xQueryBox->run(); + if ( nResult == RET_YES ) + { + rList.erase(*pOld); + bInsert = true; + } + else if ( nResult == RET_CANCEL ) + rCancel = true; + } + } + } + else + bInsert = true; + + if (bInsert) + { + ScRangeData* pData = new ScRangeData( rDoc, aName, aContent, + ScAddress( nPosX, nPosY, nTab)); + if (!rList.insert(pData)) + { + OSL_FAIL("nanu?"); + } + } +} + +bool ScDocFunc::CreateNames( const ScRange& rRange, CreateNameFlags nFlags, bool bApi, SCTAB aTab ) +{ + if (nFlags == CreateNameFlags::NONE) + return false; // was nothing + + ScDocShellModificator aModificator( rDocShell ); + + bool bDone = false; + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + SCCOL nEndCol = rRange.aEnd.Col(); + SCROW nEndRow = rRange.aEnd.Row(); + SCTAB nTab = rRange.aStart.Tab(); + OSL_ENSURE(rRange.aEnd.Tab() == nTab, "CreateNames: multiple tables not possible"); + + bool bValid = true; + if ( nFlags & ( CreateNameFlags::Top | CreateNameFlags::Bottom ) ) + if ( nStartRow == nEndRow ) + bValid = false; + if ( nFlags & ( CreateNameFlags::Left | CreateNameFlags::Right ) ) + if ( nStartCol == nEndCol ) + bValid = false; + + if (bValid) + { + ScDocument& rDoc = rDocShell.GetDocument(); + ScRangeName* pNames; + if (aTab >=0) + pNames = rDoc.GetRangeName(nTab); + else + pNames = rDoc.GetRangeName(); + + if (!pNames) + return false; // shouldn't happen + ScRangeName aNewRanges( *pNames ); + + bool bTop ( nFlags & CreateNameFlags::Top ); + bool bLeft ( nFlags & CreateNameFlags::Left ); + bool bBottom( nFlags & CreateNameFlags::Bottom ); + bool bRight ( nFlags & CreateNameFlags::Right ); + + SCCOL nContX1 = nStartCol; + SCROW nContY1 = nStartRow; + SCCOL nContX2 = nEndCol; + SCROW nContY2 = nEndRow; + + if ( bTop ) + ++nContY1; + if ( bLeft ) + ++nContX1; + if ( bBottom ) + --nContY2; + if ( bRight ) + --nContX2; + + bool bCancel = false; + SCCOL i; + SCROW j; + + if ( bTop ) + for (i=nContX1; i<=nContX2; i++) + CreateOneName( aNewRanges, i,nStartRow,nTab, i,nContY1,i,nContY2, bCancel, bApi ); + if ( bLeft ) + for (j=nContY1; j<=nContY2; j++) + CreateOneName( aNewRanges, nStartCol,j,nTab, nContX1,j,nContX2,j, bCancel, bApi ); + if ( bBottom ) + for (i=nContX1; i<=nContX2; i++) + CreateOneName( aNewRanges, i,nEndRow,nTab, i,nContY1,i,nContY2, bCancel, bApi ); + if ( bRight ) + for (j=nContY1; j<=nContY2; j++) + CreateOneName( aNewRanges, nEndCol,j,nTab, nContX1,j,nContX2,j, bCancel, bApi ); + + if ( bTop && bLeft ) + CreateOneName( aNewRanges, nStartCol,nStartRow,nTab, nContX1,nContY1,nContX2,nContY2, bCancel, bApi ); + if ( bTop && bRight ) + CreateOneName( aNewRanges, nEndCol,nStartRow,nTab, nContX1,nContY1,nContX2,nContY2, bCancel, bApi ); + if ( bBottom && bLeft ) + CreateOneName( aNewRanges, nStartCol,nEndRow,nTab, nContX1,nContY1,nContX2,nContY2, bCancel, bApi ); + if ( bBottom && bRight ) + CreateOneName( aNewRanges, nEndCol,nEndRow,nTab, nContX1,nContY1,nContX2,nContY2, bCancel, bApi ); + + ModifyRangeNames( aNewRanges, aTab ); + bDone = true; + + } + + return bDone; +} + +bool ScDocFunc::InsertNameList( const ScAddress& rStartPos, bool bApi ) +{ + ScDocShellModificator aModificator( rDocShell ); + + bool bDone = false; + ScDocument& rDoc = rDocShell.GetDocument(); + const bool bRecord = rDoc.IsUndoEnabled(); + SCTAB nTab = rStartPos.Tab(); + + //local names have higher priority than global names + ScRangeName* pLocalList = rDoc.GetRangeName(nTab); + sal_uInt16 nValidCount = 0; + for (const auto& rEntry : *pLocalList) + { + const ScRangeData& r = *rEntry.second; + if (!r.HasType(ScRangeData::Type::Database)) + ++nValidCount; + } + ScRangeName* pList = rDoc.GetRangeName(); + for (const auto& rEntry : *pList) + { + const ScRangeData& r = *rEntry.second; + if (!r.HasType(ScRangeData::Type::Database) && !pLocalList->findByUpperName(r.GetUpperName())) + ++nValidCount; + } + + if (nValidCount) + { + SCCOL nStartCol = rStartPos.Col(); + SCROW nStartRow = rStartPos.Row(); + SCCOL nEndCol = nStartCol + 1; + SCROW nEndRow = nStartRow + static_cast<SCROW>(nValidCount) - 1; + + ScEditableTester aTester( rDoc, nTab, nStartCol,nStartRow, nEndCol,nEndRow ); + if (aTester.IsEditable()) + { + ScDocumentUniquePtr pUndoDoc; + + if (bRecord) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nTab, nTab ); + rDoc.CopyToDocument(nStartCol,nStartRow,nTab, nEndCol,nEndRow,nTab, + InsertDeleteFlags::ALL, false, *pUndoDoc); + + rDoc.BeginDrawUndo(); // because of adjusting heights + } + + std::unique_ptr<ScRangeData*[]> ppSortArray(new ScRangeData* [ nValidCount ]); + sal_uInt16 j = 0; + for (const auto& rEntry : *pLocalList) + { + ScRangeData& r = *rEntry.second; + if (!r.HasType(ScRangeData::Type::Database)) + ppSortArray[j++] = &r; + } + for (const auto& [rName, rxData] : *pList) + { + ScRangeData& r = *rxData; + if (!r.HasType(ScRangeData::Type::Database) && !pLocalList->findByUpperName(rName)) + ppSortArray[j++] = &r; + } + qsort( static_cast<void*>(ppSortArray.get()), nValidCount, sizeof(ScRangeData*), + &ScRangeData_QsortNameCompare ); + OUString aName; + OUStringBuffer aContent; + OUString aFormula; + SCROW nOutRow = nStartRow; + for (j=0; j<nValidCount; j++) + { + ScRangeData* pData = ppSortArray[j]; + pData->GetName(aName); + // adjust relative references to the left column in Excel-compliant way: + pData->UpdateSymbol(aContent, ScAddress( nStartCol, nOutRow, nTab )); + aFormula = "=" + aContent; + ScSetStringParam aParam; + aParam.setTextInput(); + rDoc.SetString(ScAddress(nStartCol,nOutRow,nTab), aName, &aParam); + rDoc.SetString(ScAddress(nEndCol,nOutRow,nTab), aFormula, &aParam); + ++nOutRow; + } + + ppSortArray.reset(); + + if (bRecord) + { + ScDocumentUniquePtr pRedoDoc(new ScDocument( SCDOCMODE_UNDO )); + pRedoDoc->InitUndo( rDoc, nTab, nTab ); + rDoc.CopyToDocument(nStartCol,nStartRow,nTab, nEndCol,nEndRow,nTab, + InsertDeleteFlags::ALL, false, *pRedoDoc); + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoListNames>( &rDocShell, + ScRange( nStartCol,nStartRow,nTab, nEndCol,nEndRow,nTab ), + std::move(pUndoDoc), std::move(pRedoDoc) ) ); + } + + if (!AdjustRowHeight(ScRange(0,nStartRow,nTab,rDoc.MaxCol(),nEndRow,nTab), true, true)) + rDocShell.PostPaint( nStartCol,nStartRow,nTab, nEndCol,nEndRow,nTab, PaintPartFlags::Grid ); + + aModificator.SetDocumentModified(); + bDone = true; + } + else if (!bApi) + rDocShell.ErrorMessage(aTester.GetMessageId()); + } + return bDone; +} + +void ScDocFunc::ResizeMatrix( const ScRange& rOldRange, const ScAddress& rNewEnd ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + SCCOL nStartCol = rOldRange.aStart.Col(); + SCROW nStartRow = rOldRange.aStart.Row(); + SCTAB nTab = rOldRange.aStart.Tab(); + + OUString aFormula = rDoc.GetFormula( nStartCol, nStartRow, nTab ); + if ( !(aFormula.startsWith("{") && aFormula.endsWith("}")) ) + return; + + OUString aUndo = ScResId( STR_UNDO_RESIZEMATRIX ); + bool bUndo(rDoc.IsUndoEnabled()); + if (bUndo) + { + ViewShellId nViewShellId(1); + if (ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell()) + nViewShellId = pViewSh->GetViewShellId(); + rDocShell.GetUndoManager()->EnterListAction( aUndo, aUndo, 0, nViewShellId ); + } + + aFormula = aFormula.copy(1, aFormula.getLength()-2); + + ScMarkData aMark(rDoc.GetSheetLimits()); + aMark.SetMarkArea( rOldRange ); + aMark.SelectTable( nTab, true ); + ScRange aNewRange( rOldRange.aStart, rNewEnd ); + + if ( DeleteContents( aMark, InsertDeleteFlags::CONTENTS, true, false/*bApi*/ ) ) + { + // GRAM_API for API compatibility. + if (!EnterMatrix( aNewRange, &aMark, nullptr, aFormula, false/*bApi*/, false, OUString(), formula::FormulaGrammar::GRAM_API )) + { + // try to restore the previous state + EnterMatrix( rOldRange, &aMark, nullptr, aFormula, false/*bApi*/, false, OUString(), formula::FormulaGrammar::GRAM_API ); + } + } + + if (bUndo) + rDocShell.GetUndoManager()->LeaveListAction(); +} + +void ScDocFunc::InsertAreaLink( const OUString& rFile, const OUString& rFilter, + const OUString& rOptions, const OUString& rSource, + const ScRange& rDestRange, sal_Int32 nRefreshDelaySeconds, + bool bFitBlock, bool bApi ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + bool bUndo (rDoc.IsUndoEnabled()); + + sfx2::LinkManager* pLinkManager = rDoc.GetLinkManager(); + + // #i52120# if other area links exist at the same start position, + // remove them first (file format specifies only one link definition + // for a cell) + + sal_uInt16 nLinkCount = pLinkManager->GetLinks().size(); + sal_uInt16 nRemoved = 0; + sal_uInt16 nLinkPos = 0; + while (nLinkPos<nLinkCount) + { + ::sfx2::SvBaseLink* pBase = pLinkManager->GetLinks()[nLinkPos].get(); + ScAreaLink* pLink = dynamic_cast<ScAreaLink*>(pBase); + if (pLink && pLink->GetDestArea().aStart == rDestRange.aStart) + { + if ( bUndo ) + { + if ( !nRemoved ) + { + // group all remove and the insert action + OUString aUndo = ScResId( STR_UNDO_INSERTAREALINK ); + ViewShellId nViewShellId(-1); + if (ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell()) + nViewShellId = pViewSh->GetViewShellId(); + rDocShell.GetUndoManager()->EnterListAction( aUndo, aUndo, 0, nViewShellId ); + } + + ScAreaLink* pOldArea = static_cast<ScAreaLink*>(pBase); + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoRemoveAreaLink>( &rDocShell, + pOldArea->GetFile(), pOldArea->GetFilter(), pOldArea->GetOptions(), + pOldArea->GetSource(), pOldArea->GetDestArea(), pOldArea->GetRefreshDelaySeconds() ) ); + } + pLinkManager->Remove( pBase ); + nLinkCount = pLinkManager->GetLinks().size(); + ++nRemoved; + } + else + ++nLinkPos; + } + + OUString aFilterName = rFilter; + OUString aNewOptions = rOptions; + if (aFilterName.isEmpty()) + ScDocumentLoader::GetFilterName( rFile, aFilterName, aNewOptions, true, !bApi ); + + // remove application prefix from filter name here, so the filter options + // aren't reset when the filter name is changed in ScAreaLink::DataChanged + ScDocumentLoader::RemoveAppPrefix( aFilterName ); + + ScAreaLink* pLink = new ScAreaLink( &rDocShell, rFile, aFilterName, + aNewOptions, rSource, rDestRange, nRefreshDelaySeconds ); + OUString aTmp = aFilterName; + pLinkManager->InsertFileLink( *pLink, sfx2::SvBaseLinkObjectType::ClientFile, rFile, &aTmp, &rSource ); + + // Undo for an empty link + + if (bUndo) + { + rDocShell.GetUndoManager()->AddUndoAction( std::make_unique<ScUndoInsertAreaLink>( &rDocShell, + rFile, aFilterName, aNewOptions, + rSource, rDestRange, nRefreshDelaySeconds ) ); + if ( nRemoved ) + rDocShell.GetUndoManager()->LeaveListAction(); // undo for link update is still separate + } + + // Update has its own undo + if (rDoc.IsExecuteLinkEnabled()) + { + pLink->SetDoInsert(bFitBlock); // if applicable, don't insert anything on first update + pLink->Update(); // no SetInCreate -> carry out update + } + pLink->SetDoInsert(true); // Default = true + + SfxBindings* pBindings = rDocShell.GetViewBindings(); + if (pBindings) + pBindings->Invalidate( SID_LINKS ); + + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) ); // Navigator +} + +void ScDocFunc::ReplaceConditionalFormat( sal_uLong nOldFormat, std::unique_ptr<ScConditionalFormat> pFormat, SCTAB nTab, const ScRangeList& rRanges ) +{ + ScDocShellModificator aModificator(rDocShell); + ScDocument& rDoc = rDocShell.GetDocument(); + if(rDoc.IsTabProtected(nTab)) + return; + + bool bUndo = rDoc.IsUndoEnabled(); + ScDocumentUniquePtr pUndoDoc; + ScRange aCombinedRange = rRanges.Combine(); + ScRange aCompleteRange; + if(bUndo) + { + pUndoDoc.reset(new ScDocument(SCDOCMODE_UNDO)); + pUndoDoc->InitUndo( rDoc, nTab, nTab ); + + if(pFormat) + { + aCompleteRange = aCombinedRange; + } + if(nOldFormat) + { + ScConditionalFormat* pOldFormat = rDoc.GetCondFormList(nTab)->GetFormat(nOldFormat); + if(pOldFormat) + aCompleteRange.ExtendTo(pOldFormat->GetRange().Combine()); + } + + rDoc.CopyToDocument(aCompleteRange.aStart.Col(),aCompleteRange.aStart.Row(),nTab, + aCompleteRange.aEnd.Col(),aCompleteRange.aEnd.Row(),nTab, + InsertDeleteFlags::ALL, false, *pUndoDoc); + } + + std::unique_ptr<ScRange> pRepaintRange; + if(nOldFormat) + { + ScConditionalFormat* pOldFormat = rDoc.GetCondFormList(nTab)->GetFormat(nOldFormat); + if(pOldFormat) + { + pRepaintRange.reset(new ScRange( pOldFormat->GetRange().Combine() )); + rDoc.RemoveCondFormatData(pOldFormat->GetRange(), nTab, pOldFormat->GetKey()); + } + + rDoc.DeleteConditionalFormat(nOldFormat, nTab); + rDoc.SetStreamValid(nTab, false); + } + if(pFormat) + { + if(pRepaintRange) + pRepaintRange->ExtendTo(aCombinedRange); + else + pRepaintRange.reset(new ScRange(aCombinedRange)); + + sal_uLong nIndex = rDoc.AddCondFormat(std::move(pFormat), nTab); + + rDoc.AddCondFormatData(rRanges, nTab, nIndex); + rDoc.SetStreamValid(nTab, false); + } + + if(bUndo) + { + ScDocumentUniquePtr pRedoDoc(new ScDocument(SCDOCMODE_UNDO)); + pRedoDoc->InitUndo( rDoc, nTab, nTab ); + rDoc.CopyToDocument(aCompleteRange.aStart.Col(),aCompleteRange.aStart.Row(),nTab, + aCompleteRange.aEnd.Col(),aCompleteRange.aEnd.Row(),nTab, + InsertDeleteFlags::ALL, false, *pRedoDoc); + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoConditionalFormat>(&rDocShell, std::move(pUndoDoc), std::move(pRedoDoc), aCompleteRange)); + } + + if(pRepaintRange) + rDocShell.PostPaint(*pRepaintRange, PaintPartFlags::Grid); + + aModificator.SetDocumentModified(); + SfxGetpApp()->Broadcast(SfxHint(SfxHintId::ScAreasChanged)); +} + +void ScDocFunc::SetConditionalFormatList( ScConditionalFormatList* pList, SCTAB nTab ) +{ + ScDocShellModificator aModificator(rDocShell); + ScDocument& rDoc = rDocShell.GetDocument(); + if(rDoc.IsTabProtected(nTab)) + return; + + bool bUndo = rDoc.IsUndoEnabled(); + ScDocumentUniquePtr pUndoDoc; + if (bUndo) + { + pUndoDoc.reset(new ScDocument(SCDOCMODE_UNDO)); + pUndoDoc->InitUndo( rDoc, nTab, nTab ); + + ScConditionalFormatList* pOld = rDoc.GetCondFormList(nTab); + + if (pOld) + pUndoDoc->SetCondFormList(new ScConditionalFormatList(*pUndoDoc, *pOld), nTab); + else + pUndoDoc->SetCondFormList(nullptr, nTab); + + } + + // first remove all old entries + ScConditionalFormatList* pOldList = rDoc.GetCondFormList(nTab); + pOldList->RemoveFromDocument(rDoc); + + // then set new entries + pList->AddToDocument(rDoc); + + rDoc.SetCondFormList(pList, nTab); + rDocShell.PostPaintGridAll(); + + if(bUndo) + { + ScDocumentUniquePtr pRedoDoc(new ScDocument(SCDOCMODE_UNDO)); + pRedoDoc->InitUndo( rDoc, nTab, nTab ); + pRedoDoc->SetCondFormList(new ScConditionalFormatList(*pRedoDoc, *pList), nTab); + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoConditionalFormatList>(&rDocShell, std::move(pUndoDoc), std::move(pRedoDoc), nTab)); + } + + rDoc.SetStreamValid(nTab, false); + aModificator.SetDocumentModified(); + SfxGetpApp()->Broadcast(SfxHint(SfxHintId::ScAreasChanged)); +} + +void ScDocFunc::ConvertFormulaToValue( const ScRange& rRange, bool bInteraction ) +{ + ScDocShellModificator aModificator(rDocShell); + ScDocument& rDoc = rDocShell.GetDocument(); + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + + ScEditableTester aTester(rDoc, rRange); + if (!aTester.IsEditable()) + { + if (bInteraction) + rDocShell.ErrorMessage(aTester.GetMessageId()); + return; + } + + sc::TableValues aUndoVals(rRange); + sc::TableValues* pUndoVals = bRecord ? &aUndoVals : nullptr; + + rDoc.ConvertFormulaToValue(rRange, pUndoVals); + + if (bRecord && pUndoVals) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<sc::UndoFormulaToValue>(&rDocShell, *pUndoVals)); + } + + rDocShell.PostPaint(rRange, PaintPartFlags::Grid); + rDocShell.PostDataChanged(); + rDoc.BroadcastCells(rRange, SfxHintId::ScDataChanged); + aModificator.SetDocumentModified(); +} + +void ScDocFunc::EnterListAction(TranslateId pNameResId) +{ + OUString aUndo(ScResId(pNameResId)); + ViewShellId nViewShellId(-1); + if (ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell()) + nViewShellId = pViewSh->GetViewShellId(); + rDocShell.GetUndoManager()->EnterListAction( aUndo, aUndo, 0, nViewShellId ); +} + +void ScDocFunc::EndListAction() +{ + rDocShell.GetUndoManager()->LeaveListAction(); +} + +bool ScDocFunc::InsertSparklines(ScRange const& rDataRange, ScRange const& rSparklineRange, + std::shared_ptr<sc::SparklineGroup> pSparklineGroup) +{ + std::vector<sc::SparklineData> aSparklineDataVector; + + if (rSparklineRange.aStart.Col() == rSparklineRange.aEnd.Col()) + { + sal_Int32 nOutputRowSize = rSparklineRange.aEnd.Row() - rSparklineRange.aStart.Row(); + + auto eInputOrientation = sc::calculateOrientation(nOutputRowSize, rDataRange); + + if (eInputOrientation == sc::RangeOrientation::Unknown) + return false; + + sal_Int32 nIndex = 0; + + for (ScAddress aAddress = rSparklineRange.aStart; aAddress.Row() <= rSparklineRange.aEnd.Row(); + aAddress.IncRow()) + { + ScRange aInputRangeSlice = rDataRange; + if (eInputOrientation == sc::RangeOrientation::Row) + { + aInputRangeSlice.aStart.SetRow(rDataRange.aStart.Row() + nIndex); + aInputRangeSlice.aEnd.SetRow(rDataRange.aStart.Row() + nIndex); + } + else + { + aInputRangeSlice.aStart.SetCol(rDataRange.aStart.Col() + nIndex); + aInputRangeSlice.aEnd.SetCol(rDataRange.aStart.Col() + nIndex); + } + + aSparklineDataVector.emplace_back(aAddress, aInputRangeSlice); + + nIndex++; + } + } + else if (rSparklineRange.aStart.Row() == rSparklineRange.aEnd.Row()) + { + sal_Int32 nOutputColSize = rSparklineRange.aEnd.Col() - rSparklineRange.aStart.Col(); + + auto eInputOrientation = sc::calculateOrientation(nOutputColSize, rDataRange); + + if (eInputOrientation == sc::RangeOrientation::Unknown) + return false; + + sal_Int32 nIndex = 0; + + for (ScAddress aAddress = rSparklineRange.aStart; aAddress.Col() <= rSparklineRange.aEnd.Col(); + aAddress.IncCol()) + { + ScRange aInputRangeSlice = rDataRange; + if (eInputOrientation == sc::RangeOrientation::Row) + { + aInputRangeSlice.aStart.SetRow(rDataRange.aStart.Row() + nIndex); + aInputRangeSlice.aEnd.SetRow(rDataRange.aStart.Row() + nIndex); + } + else + { + aInputRangeSlice.aStart.SetCol(rDataRange.aStart.Col() + nIndex); + aInputRangeSlice.aEnd.SetCol(rDataRange.aStart.Col() + nIndex); + } + + aSparklineDataVector.emplace_back(aAddress, aInputRangeSlice); + + nIndex++; + } + } + + if (aSparklineDataVector.empty()) + return false; + + auto pUndoInsertSparkline = std::make_unique<sc::UndoInsertSparkline>(rDocShell, aSparklineDataVector, pSparklineGroup); + // insert the sparkline by "redoing" + pUndoInsertSparkline->Redo(); + rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndoInsertSparkline)); + + return true; +} + +bool ScDocFunc::DeleteSparkline(ScAddress const& rAddress) +{ + auto& rDocument = rDocShell.GetDocument(); + + if (!rDocument.HasSparkline(rAddress)) + return false; + + auto pUndoDeleteSparkline = std::make_unique<sc::UndoDeleteSparkline>(rDocShell, rAddress); + // delete sparkline by "redoing" + pUndoDeleteSparkline->Redo(); + rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndoDeleteSparkline)); + + return true; +} + +bool ScDocFunc::DeleteSparklineGroup(std::shared_ptr<sc::SparklineGroup> const& pSparklineGroup, SCTAB nTab) +{ + if (!pSparklineGroup) + return false; + + auto& rDocument = rDocShell.GetDocument(); + + if (!rDocument.HasTable(nTab)) + return false; + + auto pUndo = std::make_unique<sc::UndoDeleteSparklineGroup>(rDocShell, pSparklineGroup, nTab); + // delete sparkline group by "redoing" + pUndo->Redo(); + rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndo)); + return true; +} + +bool ScDocFunc::ChangeSparklineGroupAttributes(std::shared_ptr<sc::SparklineGroup> const& pExistingSparklineGroup, + sc::SparklineAttributes const& rNewAttributes) +{ + auto pUndo = std::make_unique<sc::UndoEditSparklneGroup>(rDocShell, pExistingSparklineGroup, rNewAttributes); + // change sparkline group attributes by "redoing" + pUndo->Redo(); + rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndo)); + return true; +} + +bool ScDocFunc::GroupSparklines(ScRange const& rRange, std::shared_ptr<sc::SparklineGroup> const& rpGroup) +{ + auto pUndo = std::make_unique<sc::UndoGroupSparklines>(rDocShell, rRange, rpGroup); + // group sparklines by "redoing" + pUndo->Redo(); + rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndo)); + return true; +} + +bool ScDocFunc::UngroupSparklines(ScRange const& rRange) +{ + auto pUndo = std::make_unique<sc::UndoUngroupSparklines>(rDocShell, rRange); + // ungroup sparklines by "redoing" + pUndo->Redo(); + rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndo)); + return true; +} + +bool ScDocFunc::ChangeSparkline(std::shared_ptr<sc::Sparkline> const& rpSparkline, SCTAB nTab, ScRangeList const& rDataRange) +{ + auto pUndo = std::make_unique<sc::UndoEditSparkline>(rDocShell, rpSparkline, nTab, rDataRange); + // change sparkline by "redoing" + pUndo->Redo(); + rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndo)); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/docfuncutil.cxx b/sc/source/ui/docshell/docfuncutil.cxx new file mode 100644 index 000000000..ce1a25d61 --- /dev/null +++ b/sc/source/ui/docshell/docfuncutil.cxx @@ -0,0 +1,116 @@ +/* -*- 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 <docfuncutil.hxx> +#include <document.hxx> +#include <undobase.hxx> +#include <global.hxx> +#include <undoblk.hxx> +#include <columnspanset.hxx> + +#include <memory> +#include <utility> + +namespace sc { + +bool DocFuncUtil::hasProtectedTab( const ScDocument& rDoc, const ScMarkData& rMark ) +{ + SCTAB nTabCount = rDoc.GetTableCount(); + for (const auto& rTab : rMark) + { + if (rTab >= nTabCount) + break; + + if (rDoc.IsTabProtected(rTab)) + return true; + } + + return false; +} + +ScDocumentUniquePtr DocFuncUtil::createDeleteContentsUndoDoc( + ScDocument& rDoc, const ScMarkData& rMark, const ScRange& rRange, + InsertDeleteFlags nFlags, bool bOnlyMarked ) +{ + ScDocumentUniquePtr pUndoDoc(new ScDocument(SCDOCMODE_UNDO)); + SCTAB nTab = rRange.aStart.Tab(); + pUndoDoc->InitUndo(rDoc, nTab, nTab); + SCTAB nTabCount = rDoc.GetTableCount(); + for (const auto& rTab : rMark) + if (rTab != nTab) + pUndoDoc->AddUndoTab( rTab, rTab ); + ScRange aCopyRange = rRange; + aCopyRange.aStart.SetTab(0); + aCopyRange.aEnd.SetTab(nTabCount-1); + + // in case of "Format/Standard" copy all attributes, because CopyToDocument + // with InsertDeleteFlags::HARDATTR only is too time-consuming: + InsertDeleteFlags nUndoDocFlags = nFlags; + if (nFlags & InsertDeleteFlags::ATTRIB) + nUndoDocFlags |= InsertDeleteFlags::ATTRIB; + if (nFlags & InsertDeleteFlags::EDITATTR) // Edit-Engine-Attribute + nUndoDocFlags |= InsertDeleteFlags::STRING; // -> cells will be changed + if (nFlags & InsertDeleteFlags::NOTE) + nUndoDocFlags |= InsertDeleteFlags::CONTENTS; // copy all cells with their notes + // do not copy note captions to undo document + nUndoDocFlags |= InsertDeleteFlags::NOCAPTIONS; + rDoc.CopyToDocument(aCopyRange, nUndoDocFlags, bOnlyMarked, *pUndoDoc, &rMark); + + return pUndoDoc; +} + +void DocFuncUtil::addDeleteContentsUndo( + SfxUndoManager* pUndoMgr, ScDocShell* pDocSh, const ScMarkData& rMark, + const ScRange& rRange, ScDocumentUniquePtr&& pUndoDoc, InsertDeleteFlags nFlags, + const std::shared_ptr<ScSimpleUndo::DataSpansType>& pSpans, + bool bMulti, bool bDrawUndo ) +{ + std::unique_ptr<ScUndoDeleteContents> pUndo( + new ScUndoDeleteContents( + pDocSh, rMark, rRange, std::move(pUndoDoc), bMulti, nFlags, bDrawUndo)); + pUndo->SetDataSpans(pSpans); + + pUndoMgr->AddUndoAction(std::move(pUndo)); +} + +std::shared_ptr<ScSimpleUndo::DataSpansType> DocFuncUtil::getNonEmptyCellSpans( + const ScDocument& rDoc, const ScMarkData& rMark, const ScRange& rRange ) +{ + auto pDataSpans = std::make_shared<ScSimpleUndo::DataSpansType>(); + for (const SCTAB nTab : rMark) + { + SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col(); + SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row(); + + std::pair<ScSimpleUndo::DataSpansType::iterator,bool> r = + pDataSpans->insert(std::make_pair(nTab, std::make_unique<sc::ColumnSpanSet>())); + + if (r.second) + { + sc::ColumnSpanSet *const pSet = r.first->second.get(); + pSet->scan(rDoc, nTab, nCol1, nRow1, nCol2, nRow2, true); + } + } + + return pDataSpans; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/docsh.cxx b/sc/source/ui/docshell/docsh.cxx new file mode 100644 index 000000000..219101f72 --- /dev/null +++ b/sc/source/ui/docshell/docsh.cxx @@ -0,0 +1,3482 @@ +/* -*- 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 <docsh.hxx> + +#include <config_features.h> +#include <scitems.hxx> +#include <sc.hrc> +#include <vcl/errinf.hxx> +#include <editeng/justifyitem.hxx> +#include <comphelper/fileformat.h> +#include <comphelper/classids.hxx> +#include <comphelper/propertyvalue.hxx> +#include <formula/errorcodes.hxx> +#include <vcl/stdtext.hxx> +#include <vcl/syswin.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <vcl/weld.hxx> +#include <rtl/bootstrap.hxx> +#include <rtl/tencinfo.h> +#include <sal/log.hxx> +#include <svl/PasswordHelper.hxx> +#include <sfx2/app.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dinfdlg.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/event.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/viewfrm.hxx> +#include <svl/documentlockfile.hxx> +#include <svl/fstathelper.hxx> +#include <svl/sharecontrolfile.hxx> +#include <svl/urihelper.hxx> +#include <osl/file.hxx> +#include <chgtrack.hxx> +#include <chgviset.hxx> +#include <com/sun/star/awt/Key.hpp> +#include <com/sun/star/awt/KeyModifier.hpp> +#include <com/sun/star/container/XContentEnumerationAccess.hpp> +#include <com/sun/star/document/UpdateDocMode.hpp> +#include <com/sun/star/script/vba/VBAEventId.hpp> +#include <com/sun/star/script/vba/VBAScriptEventId.hpp> +#include <com/sun/star/script/vba/XVBAEventProcessor.hpp> +#include <com/sun/star/script/vba/XVBAScriptListener.hpp> +#include <com/sun/star/script/vba/XVBACompatibility.hpp> +#include <com/sun/star/sheet/XSpreadsheetView.hpp> +#include <com/sun/star/task/XJob.hpp> +#include <com/sun/star/ui/theModuleUIConfigurationManagerSupplier.hpp> +#include <com/sun/star/ui/XAcceleratorConfiguration.hpp> +#include <com/sun/star/util/VetoException.hpp> +#include <com/sun/star/lang/XSingleComponentFactory.hpp> +#include <ooo/vba/excel/XWorkbook.hpp> +#include <tools/diagnose_ex.h> + +#include <config_folders.h> + +#include <scabstdlg.hxx> +#include <sot/formats.hxx> +#include <svx/dialogs.hrc> + +#include <formulacell.hxx> +#include <global.hxx> +#include <filter.hxx> +#include <scmod.hxx> +#include <tabvwsh.hxx> +#include <docfunc.hxx> +#include <imoptdlg.hxx> +#include <impex.hxx> +#include <scresid.hxx> +#include <strings.hrc> +#include <globstr.hrc> +#include <scerrors.hxx> +#include <brdcst.hxx> +#include <stlpool.hxx> +#include <autostyl.hxx> +#include <attrib.hxx> +#include <asciiopt.hxx> +#include <progress.hxx> +#include <pntlock.hxx> +#include <docuno.hxx> +#include <appoptio.hxx> +#include <formulaopt.hxx> +#include <scdll.hxx> +#include <detdata.hxx> +#include <printfun.hxx> +#include <dociter.hxx> +#include <cellform.hxx> +#include <chartlis.hxx> +#include <hints.hxx> +#include <xmlwrap.hxx> +#include <drwlayer.hxx> +#include <dbdata.hxx> +#include <scextopt.hxx> +#include <compiler.hxx> +#include <warnpassword.hxx> +#include <optsolver.hxx> +#include <sheetdata.hxx> +#include <tabprotection.hxx> +#include <docparam.hxx> +#include "docshimp.hxx" +#include <sizedev.hxx> +#include <refreshtimerprotector.hxx> + +#include <officecfg/Office/Calc.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/tempfile.hxx> +#include <unotools/ucbstreamhelper.hxx> +#include <uiitems.hxx> +#include <dpobject.hxx> +#include <markdata.hxx> +#include <docoptio.hxx> +#include <orcusfilters.hxx> +#include <datastream.hxx> +#include <documentlinkmgr.hxx> +#include <refupdatecontext.hxx> + +#include <memory> +#include <vector> + +#include <comphelper/lok.hxx> +#include <svtools/sfxecode.hxx> +#include <unotools/pathoptions.hxx> + +using namespace com::sun::star; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::lang::XMultiServiceFactory; +using std::shared_ptr; +using ::std::vector; + +// Filter names (like in sclib.cxx) + +constexpr OUStringLiteral pFilterSc50 = u"StarCalc 5.0"; +const char pFilterXML[] = "StarOffice XML (Calc)"; +constexpr OUStringLiteral pFilterLotus = u"Lotus"; +const char pFilterQPro6[] = "Quattro Pro 6.0"; +const char16_t pFilterExcel4[] = u"MS Excel 4.0"; +const char16_t pFilterEx4Temp[] = u"MS Excel 4.0 Vorlage/Template"; +const char pFilterExcel5[] = "MS Excel 5.0/95"; +const char pFilterEx5Temp[] = "MS Excel 5.0/95 Vorlage/Template"; +const char pFilterExcel95[] = "MS Excel 95"; +const char pFilterEx95Temp[] = "MS Excel 95 Vorlage/Template"; +const char pFilterExcel97[] = "MS Excel 97"; +const char pFilterEx97Temp[] = "MS Excel 97 Vorlage/Template"; +constexpr OUStringLiteral pFilterDBase = u"dBase"; +constexpr OUStringLiteral pFilterDif = u"DIF"; +const char16_t pFilterSylk[] = u"SYLK"; +constexpr OUStringLiteral pFilterHtml = u"HTML (StarCalc)"; +constexpr OUStringLiteral pFilterHtmlWebQ = u"calc_HTML_WebQuery"; +const char16_t pFilterRtf[] = u"Rich Text Format (StarCalc)"; + +#define ShellClass_ScDocShell +#include <scslots.hxx> + +SFX_IMPL_INTERFACE(ScDocShell,SfxObjectShell) + +void ScDocShell::InitInterface_Impl() +{ +} + +// GlobalName of the current version: +SFX_IMPL_OBJECTFACTORY( ScDocShell, SvGlobalName(SO3_SC_CLASSID), "scalc" ) + + +void ScDocShell::FillClass( SvGlobalName* pClassName, + SotClipboardFormatId* pFormat, + OUString* pFullTypeName, + sal_Int32 nFileFormat, + bool bTemplate /* = false */) const +{ + if ( nFileFormat == SOFFICE_FILEFORMAT_60 ) + { + *pClassName = SvGlobalName( SO3_SC_CLASSID_60 ); + *pFormat = SotClipboardFormatId::STARCALC_60; + *pFullTypeName = ScResId( SCSTR_LONG_SCDOC_NAME_60 ); + } + else if ( nFileFormat == SOFFICE_FILEFORMAT_8 ) + { + *pClassName = SvGlobalName( SO3_SC_CLASSID_60 ); + *pFormat = bTemplate ? SotClipboardFormatId::STARCALC_8_TEMPLATE : SotClipboardFormatId::STARCALC_8; + *pFullTypeName = ScResId( SCSTR_LONG_SCDOC_NAME_80 ); + } + else + { + OSL_FAIL("Which version?"); + } +} + +std::set<Color> ScDocShell::GetDocColors() +{ + return m_pDocument->GetDocColors(); +} + +void ScDocShell::DoEnterHandler() +{ + ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell(); + if (pViewSh && pViewSh->GetViewData().GetDocShell() == this) + SC_MOD()->InputEnterHandler(); +} + +SCTAB ScDocShell::GetSaveTab() +{ + SCTAB nTab = 0; + ScTabViewShell* pSh = GetBestViewShell(); + if (pSh) + { + const ScMarkData& rMark = pSh->GetViewData().GetMarkData(); + nTab = rMark.GetFirstSelected(); + } + return nTab; +} + +HiddenInformation ScDocShell::GetHiddenInformationState( HiddenInformation nStates ) +{ + // get global state like HiddenInformation::DOCUMENTVERSIONS + HiddenInformation nState = SfxObjectShell::GetHiddenInformationState( nStates ); + + if ( nStates & HiddenInformation::RECORDEDCHANGES ) + { + if ( m_pDocument->GetChangeTrack() && m_pDocument->GetChangeTrack()->GetFirst() ) + nState |= HiddenInformation::RECORDEDCHANGES; + } + if ( nStates & HiddenInformation::NOTES ) + { + SCTAB nTableCount = m_pDocument->GetTableCount(); + bool bFound = false; + for (SCTAB nTab = 0; nTab < nTableCount && !bFound; ++nTab) + { + if (m_pDocument->HasTabNotes(nTab)) //TODO: + bFound = true; + } + + if (bFound) + nState |= HiddenInformation::NOTES; + } + + return nState; +} + +void ScDocShell::BeforeXMLLoading() +{ + m_pDocument->EnableIdle(false); + + // prevent unnecessary broadcasts and updates + OSL_ENSURE(m_pModificator == nullptr, "The Modificator should not exist"); + m_pModificator.reset( new ScDocShellModificator( *this ) ); + + m_pDocument->SetImportingXML( true ); + m_pDocument->EnableExecuteLink( false ); // #i101304# to be safe, prevent nested loading from external references + m_pDocument->EnableUndo( false ); + // prevent unnecessary broadcasts and "half way listeners" + m_pDocument->SetInsertingFromOtherDoc( true ); +} + +void ScDocShell::AfterXMLLoading(bool bRet) +{ + if (GetCreateMode() != SfxObjectCreateMode::ORGANIZER) + { + UpdateLinks(); + // don't prevent establishing of listeners anymore + m_pDocument->SetInsertingFromOtherDoc( false ); + if ( bRet ) + { + ScChartListenerCollection* pChartListener = m_pDocument->GetChartListenerCollection(); + if (pChartListener) + pChartListener->UpdateDirtyCharts(); + + // #95582#; set the table names of linked tables to the new path + SCTAB nTabCount = m_pDocument->GetTableCount(); + for (SCTAB i = 0; i < nTabCount; ++i) + { + if (m_pDocument->IsLinked( i )) + { + OUString aName; + m_pDocument->GetName(i, aName); + OUString aLinkTabName = m_pDocument->GetLinkTab(i); + sal_Int32 nLinkTabNameLength = aLinkTabName.getLength(); + sal_Int32 nNameLength = aName.getLength(); + if (nLinkTabNameLength < nNameLength) + { + + // remove the quotes on begin and end of the docname and restore the escaped quotes + const sal_Unicode* pNameBuffer = aName.getStr(); + if ( *pNameBuffer == '\'' && // all docnames have to have a ' character on the first pos + ScGlobal::UnicodeStrChr( pNameBuffer, SC_COMPILER_FILE_TAB_SEP ) ) + { + OUStringBuffer aDocURLBuffer; + bool bQuote = true; // Document name is always quoted + ++pNameBuffer; + while ( bQuote && *pNameBuffer ) + { + if ( *pNameBuffer == '\'' && *(pNameBuffer-1) != '\\' ) + bQuote = false; + else if( *pNameBuffer != '\\' || *(pNameBuffer+1) != '\'' ) + aDocURLBuffer.append(*pNameBuffer); // If escaped quote: only quote in the name + ++pNameBuffer; + } + + if( *pNameBuffer == SC_COMPILER_FILE_TAB_SEP ) // after the last quote of the docname should be the # char + { + sal_Int32 nIndex = nNameLength - nLinkTabNameLength; + INetURLObject aINetURLObject(aDocURLBuffer); + if(aName.match( aLinkTabName, nIndex) && + (aName[nIndex - 1] == '#') && // before the table name should be the # char + !aINetURLObject.HasError()) // the docname should be a valid URL + { + aName = ScGlobal::GetDocTabName( m_pDocument->GetLinkDoc( i ), m_pDocument->GetLinkTab( i ) ); + m_pDocument->RenameTab(i, aName, true/*bExternalDocument*/); + } + // else; nothing has to happen, because it is a user given name + } + // else; nothing has to happen, because it is a user given name + } + // else; nothing has to happen, because it is a user given name + } + // else; nothing has to happen, because it is a user given name + } + } + + // #i94570# DataPilot table names have to be unique, or the tables can't be accessed by API. + // If no name (or an invalid name, skipped in ScXMLDataPilotTableContext::EndElement) was set, create a new name. + ScDPCollection* pDPCollection = m_pDocument->GetDPCollection(); + if ( pDPCollection ) + { + size_t nDPCount = pDPCollection->GetCount(); + for (size_t nDP=0; nDP<nDPCount; ++nDP) + { + ScDPObject& rDPObj = (*pDPCollection)[nDP]; + if (rDPObj.GetName().isEmpty()) + rDPObj.SetName( pDPCollection->CreateNewName() ); + } + } + } + } + else + m_pDocument->SetInsertingFromOtherDoc( false ); + + m_pDocument->SetImportingXML( false ); + m_pDocument->EnableExecuteLink( true ); + m_pDocument->EnableUndo( true ); + m_bIsEmpty = false; + + if (m_pModificator) + { + ScDocument::HardRecalcState eRecalcState = m_pDocument->GetHardRecalcState(); + // Temporarily set hard-recalc to prevent calling + // ScFormulaCell::Notify() during destruction of the Modificator which + // will set the cells dirty. + if (eRecalcState == ScDocument::HardRecalcState::OFF) + m_pDocument->SetHardRecalcState(ScDocument::HardRecalcState::TEMPORARY); + m_pModificator.reset(); + m_pDocument->SetHardRecalcState(eRecalcState); + } + else + { + OSL_FAIL("The Modificator should exist"); + } + + m_pDocument->EnableIdle(true); +} + +namespace { + +class LoadMediumGuard +{ +public: + explicit LoadMediumGuard(ScDocument* pDoc) : + mpDoc(pDoc) + { + mpDoc->SetLoadingMedium(true); + } + + ~LoadMediumGuard() + { + mpDoc->SetLoadingMedium(false); + } +private: + ScDocument* mpDoc; +}; + +void processDataStream( ScDocShell& rShell, const sc::ImportPostProcessData& rData ) +{ + if (!rData.mpDataStream) + return; + + const sc::ImportPostProcessData::DataStream& r = *rData.mpDataStream; + if (!r.maRange.IsValid()) + return; + + // Break the streamed range into the top range and the height limit. A + // height limit of 0 means unlimited i.e. the streamed data will go all + // the way to the last row. + + ScRange aTopRange = r.maRange; + aTopRange.aEnd.SetRow(aTopRange.aStart.Row()); + sal_Int32 nLimit = r.maRange.aEnd.Row() - r.maRange.aStart.Row() + 1; + if (r.maRange.aEnd.Row() == rShell.GetDocument().MaxRow()) + // Unlimited range. + nLimit = 0; + + sc::DataStream::MoveType eMove = + r.meInsertPos == sc::ImportPostProcessData::DataStream::InsertTop ? + sc::DataStream::MOVE_DOWN : sc::DataStream::RANGE_DOWN; + + sc::DataStream* pStrm = new sc::DataStream(&rShell, r.maURL, aTopRange, nLimit, eMove, 0); + pStrm->SetRefreshOnEmptyLine(r.mbRefreshOnEmpty); + sc::DocumentLinkManager& rMgr = rShell.GetDocument().GetDocLinkManager(); + rMgr.setDataStream(pStrm); +} + +class MessageWithCheck : public weld::MessageDialogController +{ +private: + std::unique_ptr<weld::CheckButton> m_xWarningOnBox; +public: + MessageWithCheck(weld::Window *pParent, const OUString& rUIFile, const OString& rDialogId) + : MessageDialogController(pParent, rUIFile, rDialogId, "ask") + , m_xWarningOnBox(m_xBuilder->weld_check_button("ask")) + { + } + bool get_active() const { return m_xWarningOnBox->get_active(); } + void hide_ask() const { m_xWarningOnBox->set_visible(false); }; +}; + +#if HAVE_FEATURE_SCRIPTING +class VBAScriptListener : public ::cppu::WeakImplHelper< css::script::vba::XVBAScriptListener > +{ +private: + ScDocShell* m_pDocSh; +public: + VBAScriptListener(ScDocShell* pDocSh) : m_pDocSh(pDocSh) + { + } + + // XVBAScriptListener + virtual void SAL_CALL notifyVBAScriptEvent( const ::css::script::vba::VBAScriptEvent& aEvent ) override + { + if (aEvent.Identifier == script::vba::VBAScriptEventId::SCRIPT_STOPPED && + m_pDocSh->GetClipData().is()) + { + m_pDocSh->SetClipData(uno::Reference<datatransfer::XTransferable2>()); + } + } + + // XEventListener + virtual void SAL_CALL disposing( const ::css::lang::EventObject& /*Source*/ ) override + { + } +}; +#endif + +} + +bool ScDocShell::LoadXML( SfxMedium* pLoadMedium, const css::uno::Reference< css::embed::XStorage >& xStor ) +{ + LoadMediumGuard aLoadGuard(m_pDocument.get()); + + // MacroCallMode is no longer needed, state is kept in SfxObjectShell now + + // no Seek(0) here - always loading from storage, GetInStream must not be called + + BeforeXMLLoading(); + + ScXMLImportWrapper aImport(*this, pLoadMedium, xStor); + + bool bRet = false; + ErrCode nError = ERRCODE_NONE; + m_pDocument->LockAdjustHeight(); + if (GetCreateMode() == SfxObjectCreateMode::ORGANIZER) + bRet = aImport.Import(ImportFlags::Styles, nError); + else + bRet = aImport.Import(ImportFlags::All, nError); + + if ( nError ) + pLoadMedium->SetError(nError); + + processDataStream(*this, aImport.GetImportPostProcessData()); + + //if the document was not generated by LibreOffice, do hard recalc in case some other document + //generator saved cached formula results that differ from LibreOffice's calculated results or + //did not use cached formula results. + uno::Reference<document::XDocumentPropertiesSupplier> xDPS(GetModel(), uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> xDocProps = xDPS->getDocumentProperties(); + + ScRecalcOptions nRecalcMode = + static_cast<ScRecalcOptions>(officecfg::Office::Calc::Formula::Load::ODFRecalcMode::get()); + + bool bHardRecalc = false; + if (nRecalcMode == RECALC_ASK) + { + OUString sProductName(utl::ConfigManager::getProductName()); + if (m_pDocument->IsUserInteractionEnabled() && xDocProps->getGenerator().indexOf(sProductName) == -1) + { + // Generator is not LibreOffice. Ask if the user wants to perform + // full re-calculation. + MessageWithCheck aQueryBox(GetActiveDialogParent(), + "modules/scalc/ui/recalcquerydialog.ui", "RecalcQueryDialog"); + aQueryBox.set_primary_text(ScResId(STR_QUERY_FORMULA_RECALC_ONLOAD_ODS)); + aQueryBox.set_default_response(RET_YES); + + if ( officecfg::Office::Calc::Formula::Load::OOXMLRecalcMode::isReadOnly() ) + aQueryBox.hide_ask(); + + bHardRecalc = aQueryBox.run() == RET_YES; + + if (aQueryBox.get_active()) + { + // Always perform selected action in the future. + std::shared_ptr<comphelper::ConfigurationChanges> batch(comphelper::ConfigurationChanges::create()); + officecfg::Office::Calc::Formula::Load::ODFRecalcMode::set(sal_Int32(0), batch); + ScFormulaOptions aOpt = SC_MOD()->GetFormulaOptions(); + aOpt.SetODFRecalcOptions(bHardRecalc ? RECALC_ALWAYS : RECALC_NEVER); + /* XXX is this really supposed to set the ScModule options? + * Not the ScDocShell options? */ + SC_MOD()->SetFormulaOptions(aOpt); + + batch->commit(); + } + } + } + else if (nRecalcMode == RECALC_ALWAYS) + bHardRecalc = true; + + if (bHardRecalc) + DoHardRecalc(); + else + { + // still need to recalc volatile formula cells. + m_pDocument->Broadcast(ScHint(SfxHintId::ScDataChanged, BCA_BRDCST_ALWAYS)); + } + + AfterXMLLoading(bRet); + + m_pDocument->UnlockAdjustHeight(); + return bRet; +} + +bool ScDocShell::SaveXML( SfxMedium* pSaveMedium, const css::uno::Reference< css::embed::XStorage >& xStor ) +{ + m_pDocument->EnableIdle(false); + + ScXMLImportWrapper aImport(*this, pSaveMedium, xStor); + bool bRet(false); + if (GetCreateMode() != SfxObjectCreateMode::ORGANIZER) + bRet = aImport.Export(false); + else + bRet = aImport.Export(true); + + m_pDocument->EnableIdle(true); + + return bRet; +} + +bool ScDocShell::Load( SfxMedium& rMedium ) +{ + LoadMediumGuard aLoadGuard(m_pDocument.get()); + ScRefreshTimerProtector aProt( m_pDocument->GetRefreshTimerControlAddress() ); + + // only the latin script language is loaded + // -> initialize the others from options (before loading) + InitOptions(true); + + // If this is an ODF file being loaded, then by default, use legacy processing + // for tdf#99729 (if required, it will be overridden in *::ReadUserDataSequence()) + if (IsOwnStorageFormat(rMedium)) + { + if (m_pDocument->GetDrawLayer()) + m_pDocument->GetDrawLayer()->SetAnchoredTextOverflowLegacy(true); + } + + GetUndoManager()->Clear(); + + bool bRet = SfxObjectShell::Load(rMedium); + if (bRet) + { + SetInitialLinkUpdate(&rMedium); + + { + // prepare a valid document for XML filter + // (for ConvertFrom, InitNew is called before) + m_pDocument->MakeTable(0); + m_pDocument->GetStyleSheetPool()->CreateStandardStyles(); + m_pDocument->UpdStlShtPtrsFrmNms(); + + if (!m_bUcalcTest) + { + /* Create styles that are imported through Orcus */ + + OUString aURL("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/calc/styles.xml"); + rtl::Bootstrap::expandMacros(aURL); + + OUString aPath; + osl::FileBase::getSystemPathFromFileURL(aURL, aPath); + + ScOrcusFilters* pOrcus = ScFormatFilter::Get().GetOrcusFilters(); + + if (pOrcus) + { + pOrcus->importODS_Styles(*m_pDocument, aPath); + m_pDocument->GetStyleSheetPool()->setAllParaStandard(); + } + } + + bRet = LoadXML( &rMedium, nullptr ); + } + } + + if (!bRet && !rMedium.GetError()) + rMedium.SetError(SVSTREAM_FILEFORMAT_ERROR); + + if (rMedium.GetError()) + SetError(rMedium.GetError()); + + InitItems(); + CalcOutputFactor(); + + // invalidate eventually temporary table areas + if ( bRet ) + m_pDocument->InvalidateTableArea(); + + m_bIsEmpty = false; + FinishedLoading(); + return bRet; +} + +void ScDocShell::Notify( SfxBroadcaster&, const SfxHint& rHint ) +{ + const ScTablesHint* pScHint = dynamic_cast< const ScTablesHint* >( &rHint ); + if (pScHint) + { + if (pScHint->GetTablesHintId() == SC_TAB_INSERTED) + { + uno::Reference< script::vba::XVBAEventProcessor > xVbaEvents = m_pDocument->GetVbaEventProcessor(); + if ( xVbaEvents.is() ) try + { + uno::Sequence< uno::Any > aArgs{ uno::Any(pScHint->GetTab1()) }; + xVbaEvents->processVbaEvent( script::vba::VBAEventId::WORKBOOK_NEWSHEET, aArgs ); + } + catch( uno::Exception& ) + { + } + } + } + + if ( auto pStyleSheetHint = dynamic_cast<const SfxStyleSheetHint*>(&rHint) ) // Template changed + NotifyStyle( *pStyleSheetHint ); + else if ( auto pStlHint = dynamic_cast<const ScAutoStyleHint*>(&rHint) ) + { + //! direct call for AutoStyles + + // this is called synchronously from ScInterpreter::ScStyle, + // modifying the document must be asynchronous + // (handled by AddInitial) + + const ScRange& aRange = pStlHint->GetRange(); + const OUString& aName1 = pStlHint->GetStyle1(); + const OUString& aName2 = pStlHint->GetStyle2(); + sal_uInt32 nTimeout = pStlHint->GetTimeout(); + + if (!m_pAutoStyleList) + m_pAutoStyleList.reset( new ScAutoStyleList(this) ); + m_pAutoStyleList->AddInitial( aRange, aName1, nTimeout, aName2 ); + } + else if ( auto pEventHint = dynamic_cast<const SfxEventHint*>(&rHint) ) + { + SfxEventHintId nEventId = pEventHint->GetEventId(); + + switch ( nEventId ) + { + case SfxEventHintId::LoadFinished: + { +#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT + // the readonly documents should not be opened in shared mode + if ( HasSharedXMLFlagSet() && !SC_MOD()->IsInSharedDocLoading() && !IsReadOnly() ) + { + if ( SwitchToShared( true, false ) ) + { + ScViewData* pViewData = GetViewData(); + ScTabView* pTabView = ( pViewData ? pViewData->GetView() : nullptr ); + if ( pTabView ) + { + pTabView->UpdateLayerLocks(); + } + } + else + { + // switching to shared mode has failed, the document should be opened readonly + // TODO/LATER: And error message should be shown here probably + SetReadOnlyUI(); + } + } +#endif + } + break; + case SfxEventHintId::ViewCreated: + { + #if HAVE_FEATURE_SCRIPTING + uno::Reference<script::vba::XVBACompatibility> xVBACompat(GetBasicContainer(), uno::UNO_QUERY); + if ( !m_xVBAListener.is() && xVBACompat.is() ) + { + m_xVBAListener.set(new VBAScriptListener(this)); + xVBACompat->addVBAScriptListener(m_xVBAListener); + } +#endif + +#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT + if ( IsDocShared() && !SC_MOD()->IsInSharedDocLoading() ) + { + ScAppOptions aAppOptions = SC_MOD()->GetAppOptions(); + if ( aAppOptions.GetShowSharedDocumentWarning() ) + { + MessageWithCheck aWarningBox(ScDocShell::GetActiveDialogParent(), + "modules/scalc/ui/sharedwarningdialog.ui", "SharedWarningDialog"); + aWarningBox.run(); + + bool bChecked = aWarningBox.get_active(); + if (bChecked) + { + aAppOptions.SetShowSharedDocumentWarning(false); + SC_MOD()->SetAppOptions( aAppOptions ); + } + } + } +#endif + try + { + uno::Reference< uno::XComponentContext > xContext( + comphelper::getProcessComponentContext() ); + uno::Reference< lang::XMultiServiceFactory > xServiceManager( + xContext->getServiceManager(), + uno::UNO_QUERY_THROW ); + uno::Reference< container::XContentEnumerationAccess > xEnumAccess( xServiceManager, uno::UNO_QUERY_THROW ); + uno::Reference< container::XEnumeration> xEnum = xEnumAccess->createContentEnumeration( + "com.sun.star.sheet.SpreadsheetDocumentJob" ); + if ( xEnum.is() ) + { + while ( xEnum->hasMoreElements() ) + { + uno::Any aAny = xEnum->nextElement(); + uno::Reference< lang::XSingleComponentFactory > xFactory; + aAny >>= xFactory; + if ( xFactory.is() ) + { + uno::Reference< task::XJob > xJob( xFactory->createInstanceWithContext( xContext ), uno::UNO_QUERY_THROW ); + ScViewData* pViewData = GetViewData(); + SfxViewShell* pViewShell = ( pViewData ? pViewData->GetViewShell() : nullptr ); + SfxViewFrame* pViewFrame = ( pViewShell ? pViewShell->GetViewFrame() : nullptr ); + SfxFrame* pFrame = ( pViewFrame ? &pViewFrame->GetFrame() : nullptr ); + uno::Reference< frame::XController > xController = ( pFrame ? pFrame->GetController() : nullptr ); + uno::Reference< sheet::XSpreadsheetView > xSpreadsheetView( xController, uno::UNO_QUERY_THROW ); + uno::Sequence< beans::NamedValue > aArgsForJob { { "SpreadsheetView", uno::Any( xSpreadsheetView ) } }; + xJob->execute( aArgsForJob ); + } + } + } + } + catch ( uno::Exception & ) + { + } + } + break; + case SfxEventHintId::SaveDoc: + { +#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT + if ( IsDocShared() && !SC_MOD()->IsInSharedDocSaving() ) + { + bool bSuccess = false; + bool bRetry = true; + while ( bRetry ) + { + bRetry = false; + uno::Reference< frame::XModel > xModel; + try + { + // load shared file + xModel.set( LoadSharedDocument(), uno::UNO_SET_THROW ); + uno::Reference< util::XCloseable > xCloseable( xModel, uno::UNO_QUERY_THROW ); + + // check if shared flag is set in shared file + bool bShared = false; + ScModelObj* pDocObj = comphelper::getFromUnoTunnel<ScModelObj>( xModel ); + ScDocShell* pSharedDocShell = ( pDocObj ? dynamic_cast< ScDocShell* >( pDocObj->GetObjectShell() ) : nullptr ); + if ( pSharedDocShell ) + { + bShared = pSharedDocShell->HasSharedXMLFlagSet(); + } + + // #i87870# check if shared status was disabled and enabled again + bool bOwnEntry = false; + bool bEntriesNotAccessible = false; + try + { + ::svt::ShareControlFile aControlFile( GetSharedFileURL() ); + bOwnEntry = aControlFile.HasOwnEntry(); + } + catch ( uno::Exception& ) + { + bEntriesNotAccessible = true; + } + + if ( bShared && bOwnEntry ) + { + uno::Reference< frame::XStorable > xStorable( xModel, uno::UNO_QUERY_THROW ); + + if ( xStorable->isReadonly() ) + { + xCloseable->close( true ); + + OUString aUserName( ScResId( STR_UNKNOWN_USER ) ); + bool bNoLockAccess = false; + try + { + ::svt::DocumentLockFile aLockFile( GetSharedFileURL() ); + LockFileEntry aData = aLockFile.GetLockData(); + if ( !aData[LockFileComponent::OOOUSERNAME].isEmpty() ) + { + aUserName = aData[LockFileComponent::OOOUSERNAME]; + } + else if ( !aData[LockFileComponent::SYSUSERNAME].isEmpty() ) + { + aUserName = aData[LockFileComponent::SYSUSERNAME]; + } + } + catch ( uno::Exception& ) + { + bNoLockAccess = true; + } + + if ( bNoLockAccess ) + { + // TODO/LATER: in future an error regarding impossibility to open file for writing could be shown + ErrorHandler::HandleError( ERRCODE_IO_GENERAL ); + } + else + { + OUString aMessage( ScResId( STR_FILE_LOCKED_SAVE_LATER ) ); + aMessage = aMessage.replaceFirst( "%1", aUserName ); + + std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(GetActiveDialogParent(), + VclMessageType::Warning, VclButtonsType::NONE, + aMessage)); + xWarn->add_button(GetStandardText(StandardButtonType::Retry), RET_RETRY); + xWarn->add_button(GetStandardText(StandardButtonType::Cancel), RET_CANCEL); + xWarn->set_default_response(RET_RETRY); + if (xWarn->run() == RET_RETRY) + { + bRetry = true; + } + } + } + else + { + // merge changes from shared file into temp file + bool bSaveToShared = false; + if ( pSharedDocShell ) + { + bSaveToShared = MergeSharedDocument( pSharedDocShell ); + } + + // close shared file + xCloseable->close( true ); + + // TODO: keep file lock on shared file + + // store to shared file + if ( bSaveToShared ) + { + bool bChangedViewSettings = false; + ScChangeViewSettings* pChangeViewSet = m_pDocument->GetChangeViewSettings(); + if ( pChangeViewSet && pChangeViewSet->ShowChanges() ) + { + pChangeViewSet->SetShowChanges( false ); + pChangeViewSet->SetShowAccepted( false ); + m_pDocument->SetChangeViewSettings( *pChangeViewSet ); + bChangedViewSettings = true; + } + + uno::Reference< frame::XStorable > xStor( GetModel(), uno::UNO_QUERY_THROW ); + // TODO/LATER: More entries from the MediaDescriptor might be interesting for the merge + uno::Sequence< beans::PropertyValue > aValues{ + comphelper::makePropertyValue( + "FilterName", + GetMedium()->GetFilter()->GetFilterName()) + }; + + const SfxStringItem* pPasswordItem = SfxItemSet::GetItem<SfxStringItem>(GetMedium()->GetItemSet(), SID_PASSWORD, false); + if ( pPasswordItem && !pPasswordItem->GetValue().isEmpty() ) + { + aValues.realloc( 2 ); + auto pValues = aValues.getArray(); + pValues[1].Name = "Password"; + pValues[1].Value <<= pPasswordItem->GetValue(); + } + const SfxUnoAnyItem* pEncryptionItem = SfxItemSet::GetItem<SfxUnoAnyItem>(GetMedium()->GetItemSet(), SID_ENCRYPTIONDATA, false); + if (pEncryptionItem) + { + aValues.realloc(aValues.getLength() + 1); + auto pValues = aValues.getArray(); + pValues[aValues.getLength() - 1].Name = "EncryptionData"; + pValues[aValues.getLength() - 1].Value = pEncryptionItem->GetValue(); + } + + SC_MOD()->SetInSharedDocSaving( true ); + xStor->storeToURL( GetSharedFileURL(), aValues ); + SC_MOD()->SetInSharedDocSaving( false ); + + if ( bChangedViewSettings ) + { + pChangeViewSet->SetShowChanges( true ); + pChangeViewSet->SetShowAccepted( true ); + m_pDocument->SetChangeViewSettings( *pChangeViewSet ); + } + } + + bSuccess = true; + GetUndoManager()->Clear(); + } + } + else + { + xCloseable->close( true ); + + if ( bEntriesNotAccessible ) + { + // TODO/LATER: in future an error regarding impossibility to write to share control file could be shown + ErrorHandler::HandleError( ERRCODE_IO_GENERAL ); + } + else + { + std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(GetActiveDialogParent(), + VclMessageType::Warning, VclButtonsType::Ok, + ScResId(STR_DOC_NOLONGERSHARED))); + xWarn->run(); + + SfxBindings* pBindings = GetViewBindings(); + if ( pBindings ) + { + pBindings->ExecuteSynchron( SID_SAVEASDOC ); + } + } + } + } + catch ( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "sc", "SfxEventHintId::SaveDoc" ); + SC_MOD()->SetInSharedDocSaving( false ); + + try + { + uno::Reference< util::XCloseable > xClose( xModel, uno::UNO_QUERY_THROW ); + xClose->close( true ); + } + catch ( uno::Exception& ) + { + } + } + } + + if ( !bSuccess ) + SetError(ERRCODE_IO_ABORT); // this error code will produce no error message, but will break the further saving process + } +#endif + + if (m_pSheetSaveData) + m_pSheetSaveData->SetInSupportedSave(true); + } + break; + case SfxEventHintId::SaveAsDoc: + { + if ( GetDocument().GetExternalRefManager()->containsUnsavedReferences() ) + { + std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(GetActiveDialogParent(), + VclMessageType::Warning, VclButtonsType::YesNo, + ScResId(STR_UNSAVED_EXT_REF))); + if (RET_NO == xWarn->run()) + { + SetError(ERRCODE_IO_ABORT); // this error code will produce no error message, but will break the further saving process + } + } + [[fallthrough]]; + } + case SfxEventHintId::SaveToDoc: + // #i108978# If no event is sent before saving, there will also be no "...DONE" event, + // and SAVE/SAVEAS can't be distinguished from SAVETO. So stream copying is only enabled + // if there is a SAVE/SAVEAS/SAVETO event first. + if (m_pSheetSaveData) + m_pSheetSaveData->SetInSupportedSave(true); + break; + case SfxEventHintId::SaveDocDone: + case SfxEventHintId::SaveAsDocDone: + { + // new positions are used after "save" and "save as", but not "save to" + UseSheetSaveEntries(); // use positions from saved file for next saving + [[fallthrough]]; + } + case SfxEventHintId::SaveToDocDone: + // only reset the flag, don't use the new positions + if (m_pSheetSaveData) + m_pSheetSaveData->SetInSupportedSave(false); + break; + default: + { + } + break; + } + } + else if (rHint.GetId() == SfxHintId::TitleChanged) // Without parameter + { + m_pDocument->SetName( SfxShell::GetName() ); + // RegisterNewTargetNames doesn't exist any longer + SfxGetpApp()->Broadcast(SfxHint( SfxHintId::ScDocNameChanged )); // Navigator + } + else if (rHint.GetId() == SfxHintId::Deinitializing) + { + +#if HAVE_FEATURE_SCRIPTING + uno::Reference<script::vba::XVBACompatibility> xVBACompat(GetBasicContainer(), uno::UNO_QUERY); + if (m_xVBAListener.is() && xVBACompat.is()) + { + xVBACompat->removeVBAScriptListener(m_xVBAListener); + } +#endif + + if (m_pDocument->IsClipboardSource()) + { + // Notes copied to the clipboard have a raw SdrCaptionObj pointer + // copied from this document, forget it as it references this + // document's drawing layer pages and what not, which otherwise when + // pasting to another document after this document was destructed would + // attempt to access non-existing data. Preserve the text data though. + ScDocument* pClipDoc = ScModule::GetClipDoc(); + if (pClipDoc) + pClipDoc->ClosingClipboardSource(); + } + } + + const SfxEventHint* pSfxEventHint = dynamic_cast<const SfxEventHint*>(&rHint); + if (!pSfxEventHint) + return; + + switch( pSfxEventHint->GetEventId() ) + { + case SfxEventHintId::CreateDoc: + { + uno::Any aWorkbook; + aWorkbook <<= mxAutomationWorkbookObject; + uno::Sequence< uno::Any > aArgs{ aWorkbook }; + SC_MOD()->CallAutomationApplicationEventSinks( "NewWorkbook", aArgs ); + } + break; + case SfxEventHintId::OpenDoc: + { + uno::Any aWorkbook; + aWorkbook <<= mxAutomationWorkbookObject; + uno::Sequence< uno::Any > aArgs{ aWorkbook }; + SC_MOD()->CallAutomationApplicationEventSinks( "WorkbookOpen", aArgs ); + } + break; + default: + break; + } +} + +// Load contents for organizer +bool ScDocShell::LoadFrom( SfxMedium& rMedium ) +{ + LoadMediumGuard aLoadGuard(m_pDocument.get()); + ScRefreshTimerProtector aProt( m_pDocument->GetRefreshTimerControlAddress() ); + + weld::WaitObject aWait( GetActiveDialogParent() ); + + bool bRet = false; + + SetInitialLinkUpdate(&rMedium); + + // until loading/saving only the styles in XML is implemented, + // load the whole file + bRet = LoadXML( &rMedium, nullptr ); + InitItems(); + + SfxObjectShell::LoadFrom( rMedium ); + + return bRet; +} + +static void lcl_parseHtmlFilterOption(const OUString& rOption, LanguageType& rLang, bool& rDateConvert) +{ + OUStringBuffer aBuf; + std::vector< OUString > aTokens; + sal_Int32 n = rOption.getLength(); + const sal_Unicode* p = rOption.getStr(); + for (sal_Int32 i = 0; i < n; ++i) + { + const sal_Unicode c = p[i]; + if (c == ' ') + { + if (!aBuf.isEmpty()) + aTokens.push_back( aBuf.makeStringAndClear() ); + } + else + aBuf.append(c); + } + + if (!aBuf.isEmpty()) + aTokens.push_back( aBuf.makeStringAndClear() ); + + rLang = LanguageType( 0 ); + rDateConvert = false; + + if (!aTokens.empty()) + rLang = static_cast<LanguageType>(aTokens[0].toInt32()); + if (aTokens.size() > 1) + rDateConvert = static_cast<bool>(aTokens[1].toInt32()); +} + +bool ScDocShell::ConvertFrom( SfxMedium& rMedium ) +{ + LoadMediumGuard aLoadGuard(m_pDocument.get()); + + bool bRet = false; // sal_False means user quit! + // On error: Set error at stream + + ScRefreshTimerProtector aProt( m_pDocument->GetRefreshTimerControlAddress() ); + + GetUndoManager()->Clear(); + + // Set optimal col width after import? + bool bSetColWidths = false; + bool bSetSimpleTextColWidths = false; + std::map<SCCOL, ScColWidthParam> aColWidthParam; + ScRange aColWidthRange; + // Set optimal row height after import? + bool bSetRowHeights = false; + + vector<ScDocRowHeightUpdater::TabRanges> aRecalcRowRangesArray; + + // All filters need the complete file in one piece (not asynchronously) + // So make sure that we transfer the whole file with CreateFileStream + rMedium.GetPhysicalName(); //! Call CreateFileStream directly, if available + + SetInitialLinkUpdate(&rMedium); + + std::shared_ptr<const SfxFilter> pFilter = rMedium.GetFilter(); + if (pFilter) + { + OUString aFltName = pFilter->GetFilterName(); + + bool bCalc3 = aFltName == "StarCalc 3.0"; + bool bCalc4 = aFltName == "StarCalc 4.0"; + if (!bCalc3 && !bCalc4) + m_pDocument->SetInsertingFromOtherDoc( true ); + + if (aFltName == pFilterXML) + bRet = LoadXML( &rMedium, nullptr ); + else if (aFltName == pFilterLotus) + { + OUString sItStr; + SfxItemSet* pSet = rMedium.GetItemSet(); + const SfxStringItem* pOptionsItem; + if ( pSet && + (pOptionsItem = pSet->GetItemIfSet( SID_FILE_FILTEROPTIONS, true )) ) + { + sItStr = pOptionsItem->GetValue(); + } + + if (sItStr.isEmpty()) + { + // default for lotus import (from API without options): + // IBM_437 encoding + sItStr = ScGlobal::GetCharsetString( RTL_TEXTENCODING_IBM_437 ); + } + + ErrCode eError = ScFormatFilter::Get().ScImportLotus123( rMedium, *m_pDocument, + ScGlobal::GetCharsetValue(sItStr)); + if (eError != ERRCODE_NONE) + { + if (!GetError()) + SetError(eError); + + if( eError.IsWarning() ) + bRet = true; + } + else + bRet = true; + bSetColWidths = true; + bSetRowHeights = true; + } + else if ( aFltName == pFilterExcel4 || aFltName == pFilterExcel5 || + aFltName == pFilterExcel95 || aFltName == pFilterExcel97 || + aFltName == pFilterEx4Temp || aFltName == pFilterEx5Temp || + aFltName == pFilterEx95Temp || aFltName == pFilterEx97Temp ) + { + EXCIMPFORMAT eFormat = EIF_AUTO; + if ( aFltName == pFilterExcel4 || aFltName == pFilterEx4Temp ) + eFormat = EIF_BIFF_LE4; + else if ( aFltName == pFilterExcel5 || aFltName == pFilterExcel95 || + aFltName == pFilterEx5Temp || aFltName == pFilterEx95Temp ) + eFormat = EIF_BIFF5; + else if ( aFltName == pFilterExcel97 || aFltName == pFilterEx97Temp ) + eFormat = EIF_BIFF8; + + MakeDrawLayer(); //! In the filter + CalcOutputFactor(); // prepare update of row height + ErrCode eError = ScFormatFilter::Get().ScImportExcel( rMedium, m_pDocument.get(), eFormat ); + m_pDocument->UpdateFontCharSet(); + if ( m_pDocument->IsChartListenerCollectionNeedsUpdate() ) + m_pDocument->UpdateChartListenerCollection(); //! For all imports? + + // all graphics objects must have names + m_pDocument->EnsureGraphicNames(); + + if (eError != ERRCODE_NONE) + { + if (!GetError()) + SetError(eError); + if( eError.IsWarning() ) + bRet = true; + } + else + bRet = true; + } + else if (aFltName == "Gnumeric Spreadsheet") + { + ScOrcusFilters* pOrcus = ScFormatFilter::Get().GetOrcusFilters(); + if (!pOrcus) + return false; + + bRet = pOrcus->importGnumeric(*m_pDocument, rMedium); + } + else if (aFltName == "MS Excel 2003 XML Orcus") + { + ScOrcusFilters* pOrcus = ScFormatFilter::Get().GetOrcusFilters(); + if (!pOrcus) + return false; + + bRet = pOrcus->importExcel2003XML(*m_pDocument, rMedium); + } + else if (aFltName == SC_TEXT_CSV_FILTER_NAME) + { + SfxItemSet* pSet = rMedium.GetItemSet(); + const SfxStringItem* pOptionsItem; + ScAsciiOptions aOptions; + bool bOptInit = false; + + if ( pSet && + (pOptionsItem = pSet->GetItemIfSet( SID_FILE_FILTEROPTIONS )) ) + { + aOptions.ReadFromString( pOptionsItem->GetValue() ); + bOptInit = true; + } + + if ( !bOptInit ) + { + // default for ascii import (from API without options): + // ISO8859-1/MS_1252 encoding, comma, double quotes + + aOptions.SetCharSet( RTL_TEXTENCODING_MS_1252 ); + aOptions.SetFieldSeps( OUString(',') ); + aOptions.SetTextSep( '"' ); + } + + ErrCode eError = ERRCODE_NONE; + bool bOverflowRow, bOverflowCol, bOverflowCell; + bOverflowRow = bOverflowCol = bOverflowCell = false; + + if( ! rMedium.IsStorage() ) + { + ScImportExport aImpEx( *m_pDocument ); + aImpEx.SetExtOptions( aOptions ); + + SvStream* pInStream = rMedium.GetInStream(); + if (pInStream) + { + pInStream->SetStreamCharSet( aOptions.GetCharSet() ); + pInStream->Seek( 0 ); + bRet = aImpEx.ImportStream( *pInStream, rMedium.GetBaseURL(), SotClipboardFormatId::STRING ); + eError = bRet ? ERRCODE_NONE : SCERR_IMPORT_CONNECT; + m_pDocument->StartAllListeners(); + sc::SetFormulaDirtyContext aCxt; + m_pDocument->SetAllFormulasDirty(aCxt); + + bool bIsMobile = comphelper::LibreOfficeKit::isActive() && SfxViewShell::Current() + && SfxViewShell::Current()->isLOKMobilePhone(); + // for mobile case, we use a copy of the original document and give it a temporary name before editing + // Therefore, the sheet name becomes ugly, long and nonsensical. + if (!bIsMobile) + // The same resulting name has to be handled in + // ScExternalRefCache::initializeDoc() and related, hence + // pass 'true' for RenameTab()'s bExternalDocument for a + // composed name so ValidTabName() will not be checked, + // which could veto the rename in case it contained + // characters that Excel does not handle. If we wanted to + // change that then it needed to be handled in all + // corresponding places of the external references + // manager/cache. Likely then we'd also need a method to + // compose a name excluding such characters. + m_pDocument->RenameTab( 0, INetURLObject( rMedium.GetName()).GetBase(), true/*bExternalDocument*/); + + bOverflowRow = aImpEx.IsOverflowRow(); + bOverflowCol = aImpEx.IsOverflowCol(); + bOverflowCell = aImpEx.IsOverflowCell(); + } + else + { + OSL_FAIL( "No Stream" ); + } + } + + if (eError != ERRCODE_NONE) + { + if (!GetError()) + SetError(eError); + if( eError.IsWarning() ) + bRet = true; + } + else if (!GetError() && (bOverflowRow || bOverflowCol || bOverflowCell)) + { + // precedence: row, column, cell + ErrCode nWarn = (bOverflowRow ? SCWARN_IMPORT_ROW_OVERFLOW : + (bOverflowCol ? SCWARN_IMPORT_COLUMN_OVERFLOW : + SCWARN_IMPORT_CELL_OVERFLOW)); + SetError(nWarn); + } + bSetColWidths = true; + bSetSimpleTextColWidths = true; + } + else if (aFltName == pFilterDBase) + { + OUString sItStr; + SfxItemSet* pSet = rMedium.GetItemSet(); + const SfxStringItem* pOptionsItem; + if ( pSet && + (pOptionsItem = pSet->GetItemIfSet( SID_FILE_FILTEROPTIONS )) ) + { + sItStr = pOptionsItem->GetValue(); + } + + if (sItStr.isEmpty()) + { + // default for dBase import (from API without options): + // IBM_850 encoding + + sItStr = ScGlobal::GetCharsetString( RTL_TEXTENCODING_IBM_850 ); + } + + ScDocRowHeightUpdater::TabRanges aRecalcRanges(0, m_pDocument->MaxRow()); + ErrCode eError = DBaseImport( rMedium.GetPhysicalName(), + ScGlobal::GetCharsetValue(sItStr), aColWidthParam, aRecalcRanges.maRanges ); + aRecalcRowRangesArray.push_back(aRecalcRanges); + + if (eError != ERRCODE_NONE) + { + if (!GetError()) + SetError(eError); + if( eError.IsWarning() ) + bRet = true; + } + else + bRet = true; + + aColWidthRange.aStart.SetRow( 1 ); // Except for the column header + bSetColWidths = true; + bSetSimpleTextColWidths = true; + } + else if (aFltName == pFilterDif) + { + SvStream* pStream = rMedium.GetInStream(); + if (pStream) + { + ErrCode eError; + OUString sItStr; + SfxItemSet* pSet = rMedium.GetItemSet(); + const SfxStringItem* pOptionsItem; + if ( pSet && + (pOptionsItem = pSet->GetItemIfSet( SID_FILE_FILTEROPTIONS )) ) + { + sItStr = pOptionsItem->GetValue(); + } + + if (sItStr.isEmpty()) + { + // default for DIF import (from API without options): + // ISO8859-1/MS_1252 encoding + + sItStr = ScGlobal::GetCharsetString( RTL_TEXTENCODING_MS_1252 ); + } + + eError = ScFormatFilter::Get().ScImportDif( *pStream, m_pDocument.get(), ScAddress(0,0,0), + ScGlobal::GetCharsetValue(sItStr)); + if (eError != ERRCODE_NONE) + { + if (!GetError()) + SetError(eError); + + if( eError.IsWarning() ) + bRet = true; + } + else + bRet = true; + } + bSetColWidths = true; + bSetSimpleTextColWidths = true; + bSetRowHeights = true; + } + else if (aFltName == pFilterSylk) + { + ErrCode eError = SCERR_IMPORT_UNKNOWN; + bool bOverflowRow, bOverflowCol, bOverflowCell; + bOverflowRow = bOverflowCol = bOverflowCell = false; + if( !rMedium.IsStorage() ) + { + ScImportExport aImpEx( *m_pDocument ); + + SvStream* pInStream = rMedium.GetInStream(); + if (pInStream) + { + pInStream->Seek( 0 ); + bRet = aImpEx.ImportStream( *pInStream, rMedium.GetBaseURL(), SotClipboardFormatId::SYLK ); + eError = bRet ? ERRCODE_NONE : SCERR_IMPORT_UNKNOWN; + m_pDocument->StartAllListeners(); + sc::SetFormulaDirtyContext aCxt; + m_pDocument->SetAllFormulasDirty(aCxt); + + bOverflowRow = aImpEx.IsOverflowRow(); + bOverflowCol = aImpEx.IsOverflowCol(); + bOverflowCell = aImpEx.IsOverflowCell(); + } + else + { + OSL_FAIL( "No Stream" ); + } + } + + if (eError != ERRCODE_NONE) + { + if (!GetError()) + SetError(eError); + if( eError.IsWarning() ) + bRet = true; + } + else if (!GetError() && (bOverflowRow || bOverflowCol || bOverflowCell)) + { + // precedence: row, column, cell + ErrCode nWarn = (bOverflowRow ? SCWARN_IMPORT_ROW_OVERFLOW : + (bOverflowCol ? SCWARN_IMPORT_COLUMN_OVERFLOW : + SCWARN_IMPORT_CELL_OVERFLOW)); + SetError(nWarn); + } + bSetColWidths = true; + bSetSimpleTextColWidths = true; + bSetRowHeights = true; + } + else if (aFltName == pFilterQPro6) + { + ErrCode eError = ScFormatFilter::Get().ScImportQuattroPro(rMedium.GetInStream(), *m_pDocument); + if (eError != ERRCODE_NONE) + { + if (!GetError()) + SetError(eError); + if( eError.IsWarning() ) + bRet = true; + } + else + bRet = true; + // TODO: Filter should set column widths. Not doing it here, it may + // result in very narrow or wide columns, depending on content. + // Setting row heights makes cells with font size attribution or + // wrapping enabled look nicer... + bSetRowHeights = true; + } + else if (aFltName == pFilterRtf) + { + ErrCode eError = SCERR_IMPORT_UNKNOWN; + if( !rMedium.IsStorage() ) + { + SvStream* pInStream = rMedium.GetInStream(); + if (pInStream) + { + pInStream->Seek( 0 ); + ScRange aRange; + eError = ScFormatFilter::Get().ScImportRTF( *pInStream, rMedium.GetBaseURL(), m_pDocument.get(), aRange ); + if (eError != ERRCODE_NONE) + { + if (!GetError()) + SetError(eError); + + if( eError.IsWarning() ) + bRet = true; + } + else + bRet = true; + m_pDocument->StartAllListeners(); + sc::SetFormulaDirtyContext aCxt; + m_pDocument->SetAllFormulasDirty(aCxt); + bSetColWidths = true; + bSetRowHeights = true; + } + else + { + OSL_FAIL( "No Stream" ); + } + } + + if (eError != ERRCODE_NONE) + { + if (!GetError()) + SetError(eError); + if( eError.IsWarning() ) + bRet = true; + } + } + else if (aFltName == pFilterHtml || aFltName == pFilterHtmlWebQ) + { + ErrCode eError = SCERR_IMPORT_UNKNOWN; + bool bWebQuery = aFltName == pFilterHtmlWebQ; + if( !rMedium.IsStorage() ) + { + SvStream* pInStream = rMedium.GetInStream(); + if (pInStream) + { + LanguageType eLang = LANGUAGE_SYSTEM; + bool bDateConvert = false; + SfxItemSet* pSet = rMedium.GetItemSet(); + const SfxStringItem* pOptionsItem; + if ( pSet && + (pOptionsItem = pSet->GetItemIfSet( SID_FILE_FILTEROPTIONS )) ) + { + OUString aFilterOption = pOptionsItem->GetValue(); + lcl_parseHtmlFilterOption(aFilterOption, eLang, bDateConvert); + } + + pInStream->Seek( 0 ); + ScRange aRange; + // HTML does its own ColWidth/RowHeight + CalcOutputFactor(); + SvNumberFormatter aNumFormatter( comphelper::getProcessComponentContext(), eLang); + eError = ScFormatFilter::Get().ScImportHTML( *pInStream, rMedium.GetBaseURL(), m_pDocument.get(), aRange, + GetOutputFactor(), !bWebQuery, &aNumFormatter, bDateConvert ); + if (eError != ERRCODE_NONE) + { + if (!GetError()) + SetError(eError); + + if( eError.IsWarning() ) + bRet = true; + } + else + bRet = true; + m_pDocument->StartAllListeners(); + + sc::SetFormulaDirtyContext aCxt; + m_pDocument->SetAllFormulasDirty(aCxt); + } + else + { + OSL_FAIL( "No Stream" ); + } + } + + if (eError != ERRCODE_NONE) + { + if (!GetError()) + SetError(eError); + if( eError.IsWarning() ) + bRet = true; + } + } + else + { + if (!GetError()) + { + SAL_WARN("sc.filter", "No match for filter '" << aFltName << "' in ConvertFrom"); + SetError(SCERR_IMPORT_NI); + } + } + + if (!bCalc3) + m_pDocument->SetInsertingFromOtherDoc( false ); + } + else + { + OSL_FAIL("No Filter in ConvertFrom"); + } + + InitItems(); + CalcOutputFactor(); + if ( bRet && (bSetColWidths || bSetRowHeights) ) + { // Adjust column width/row height; base 100% zoom + Fraction aZoom( 1, 1 ); + double nPPTX = ScGlobal::nScreenPPTX * static_cast<double>(aZoom) / GetOutputFactor(); // Factor is printer display ratio + double nPPTY = ScGlobal::nScreenPPTY * static_cast<double>(aZoom); + ScopedVclPtrInstance< VirtualDevice > pVirtDev; + // all sheets (for Excel import) + SCTAB nTabCount = m_pDocument->GetTableCount(); + for (SCTAB nTab=0; nTab<nTabCount; nTab++) + { + SCCOL nEndCol; + SCROW nEndRow; + m_pDocument->GetCellArea( nTab, nEndCol, nEndRow ); + aColWidthRange.aEnd.SetCol( nEndCol ); + aColWidthRange.aEnd.SetRow( nEndRow ); + ScMarkData aMark(m_pDocument->GetSheetLimits()); + aMark.SetMarkArea( aColWidthRange ); + aMark.MarkToMulti(); + + // Order is important: First width, then height + if ( bSetColWidths ) + { + for ( SCCOL nCol=0; nCol <= nEndCol; nCol++ ) + { + if (!bSetSimpleTextColWidths) + aColWidthParam[nCol].mbSimpleText = false; + + sal_uInt16 nWidth = m_pDocument->GetOptimalColWidth( + nCol, nTab, pVirtDev, nPPTX, nPPTY, aZoom, aZoom, false, &aMark, + &aColWidthParam[nCol] ); + m_pDocument->SetColWidth( nCol, nTab, + nWidth + static_cast<sal_uInt16>(ScGlobal::nLastColWidthExtra) ); + } + } + } + + if (bSetRowHeights) + { + // Update all rows in all tables. + ScSizeDeviceProvider aProv(this); + ScDocRowHeightUpdater aUpdater(*m_pDocument, aProv.GetDevice(), aProv.GetPPTX(), aProv.GetPPTY(), nullptr); + aUpdater.update(); + } + else if (!aRecalcRowRangesArray.empty()) + { + // Update only specified row ranges for better performance. + ScSizeDeviceProvider aProv(this); + ScDocRowHeightUpdater aUpdater(*m_pDocument, aProv.GetDevice(), aProv.GetPPTX(), aProv.GetPPTY(), &aRecalcRowRangesArray); + aUpdater.update(); + } + } + FinishedLoading(); + + // invalidate eventually temporary table areas + if ( bRet ) + m_pDocument->InvalidateTableArea(); + + m_bIsEmpty = false; + + return bRet; +} + +bool ScDocShell::LoadExternal( SfxMedium& rMed ) +{ + std::shared_ptr<const SfxFilter> pFilter = rMed.GetFilter(); + if (!pFilter) + return false; + + if (pFilter->GetProviderName() == "orcus") + { + ScOrcusFilters* pOrcus = ScFormatFilter::Get().GetOrcusFilters(); + if (!pOrcus) + return false; + + const OUString& rFilterName = pFilter->GetName(); + if (rFilterName == "gnumeric") + { + if (!pOrcus->importGnumeric(*m_pDocument, rMed)) + return false; + } + else if (rFilterName == "csv") + { + if (!pOrcus->importCSV(*m_pDocument, rMed)) + return false; + } + else if (rFilterName == "xlsx") + { + if (!pOrcus->importXLSX(*m_pDocument, rMed)) + return false; + } + else if (rFilterName == "ods") + { + if (!pOrcus->importODS(*m_pDocument, rMed)) + return false; + } + + FinishedLoading(); + return true; + } + + return false; +} + +ScDocShell::PrepareSaveGuard::PrepareSaveGuard( ScDocShell& rDocShell ) + : mrDocShell( rDocShell) +{ + // DoEnterHandler not here (because of AutoSave), is in ExecuteSave. + + ScChartListenerCollection* pCharts = mrDocShell.m_pDocument->GetChartListenerCollection(); + if (pCharts) + pCharts->UpdateDirtyCharts(); // Charts to be updated. + mrDocShell.m_pDocument->StopTemporaryChartLock(); + if (mrDocShell.m_pAutoStyleList) + mrDocShell.m_pAutoStyleList->ExecuteAllNow(); // Execute template timeouts now. + if (mrDocShell.m_pDocument->HasExternalRefManager()) + { + ScExternalRefManager* pRefMgr = mrDocShell.m_pDocument->GetExternalRefManager(); + if (pRefMgr && pRefMgr->hasExternalData()) + { + pRefMgr->setAllCacheTableReferencedStati( false); + mrDocShell.m_pDocument->MarkUsedExternalReferences(); // Mark tables of external references to be written. + } + } + if (mrDocShell.GetCreateMode()== SfxObjectCreateMode::STANDARD) + mrDocShell.SfxObjectShell::SetVisArea( tools::Rectangle() ); // "Normally" worked on => no VisArea. +} + +ScDocShell::PrepareSaveGuard::~PrepareSaveGuard() COVERITY_NOEXCEPT_FALSE +{ + if (mrDocShell.m_pDocument->HasExternalRefManager()) + { + ScExternalRefManager* pRefMgr = mrDocShell.m_pDocument->GetExternalRefManager(); + if (pRefMgr && pRefMgr->hasExternalData()) + { + // Prevent accidental data loss due to lack of knowledge. + pRefMgr->setAllCacheTableReferencedStati( true); + } + } +} + +bool ScDocShell::Save() +{ + ScRefreshTimerProtector aProt( m_pDocument->GetRefreshTimerControlAddress() ); + + PrepareSaveGuard aPrepareGuard( *this); + + if (const auto pFrame1 = SfxViewFrame::GetFirst(this)) + { + if (auto pSysWin = pFrame1->GetWindow().GetSystemWindow()) + { + pSysWin->SetAccessibleName(OUString()); + } + } + // wait cursor is handled with progress bar + bool bRet = SfxObjectShell::Save(); + if( bRet ) + bRet = SaveXML( GetMedium(), nullptr ); + return bRet; +} + +namespace { + +/** + * Remove the file name from the full path, to keep only the directory path. + */ +void popFileName(OUString& rPath) +{ + if (!rPath.isEmpty()) + { + INetURLObject aURLObj(rPath); + aURLObj.removeSegment(); + rPath = aURLObj.GetMainURL(INetURLObject::DecodeMechanism::NONE); + } +} + +} + +void ScDocShell::TerminateEditing() +{ + // Commit any cell changes before saving. + SC_MOD()->InputEnterHandler(); +} + +bool ScDocShell::SaveAs( SfxMedium& rMedium ) +{ + OUString aCurPath; // empty for new document that hasn't been saved. + const SfxMedium* pCurMedium = GetMedium(); + if (pCurMedium) + { + aCurPath = pCurMedium->GetName(); + popFileName(aCurPath); + } + + if (!aCurPath.isEmpty()) + { + // current document has a path -> not a brand-new document. + OUString aNewPath = rMedium.GetName(); + popFileName(aNewPath); + OUString aRel = URIHelper::simpleNormalizedMakeRelative(aCurPath, aNewPath); + if (!aRel.isEmpty()) + { + // Directory path will change before and after the save. + m_pDocument->InvalidateStreamOnSave(); + } + } + + ScTabViewShell* pViewShell = GetBestViewShell(); + bool bNeedsRehash = ScPassHashHelper::needsPassHashRegen(*m_pDocument, PASSHASH_SHA1); + if (bNeedsRehash) + // legacy xls hash double-hashed by SHA1 is also supported. + bNeedsRehash = ScPassHashHelper::needsPassHashRegen(*m_pDocument, PASSHASH_XL, PASSHASH_SHA1); + if (bNeedsRehash) + { // SHA256 explicitly supported in ODF 1.2, implicitly in ODF 1.1 + bNeedsRehash = ScPassHashHelper::needsPassHashRegen(*m_pDocument, PASSHASH_SHA256); + } + + // skip saving recovery file instead of showing re-type password dialog window + if ( bNeedsRehash && rMedium.GetFilter()->GetFilterName() == "calc8" && + // it seems, utl::MediaDescriptor::PROP_AUTOSAVEEVENT is true at Save As, too, + // so check the backup path + rMedium.GetName().startsWith( SvtPathOptions().GetBackupPath() ) ) + { + SAL_WARN("sc.filter", "Should re-type password for own format, won't export recovery file"); + rMedium.SetError(ERRCODE_SFX_WRONGPASSWORD); + return false; + } + + if (pViewShell && bNeedsRehash) + { + if (!pViewShell->ExecuteRetypePassDlg(PASSHASH_SHA1)) + // password re-type cancelled. Don't save the document. + return false; + } + + ScRefreshTimerProtector aProt( m_pDocument->GetRefreshTimerControlAddress() ); + + PrepareSaveGuard aPrepareGuard( *this); + + // wait cursor is handled with progress bar + bool bRet = SfxObjectShell::SaveAs( rMedium ); + if (bRet) + bRet = SaveXML( &rMedium, nullptr ); + + return bRet; +} + +namespace { + +// Xcl-like column width measured in characters of standard font. +sal_Int32 lcl_ScDocShell_GetColWidthInChars( sal_uInt16 nWidth ) +{ + double f = nWidth; + f *= 1328.0 / 25.0; + f += 90.0; + f *= 1.0 / 23.0; + f /= 256.0; + + return sal_Int32( f ); +} + +void lcl_ScDocShell_GetFixedWidthString( OUString& rStr, const ScDocument& rDoc, + SCTAB nTab, SCCOL nCol, bool bValue, SvxCellHorJustify eHorJust ) +{ + OUString aString = rStr; + sal_Int32 nLen = lcl_ScDocShell_GetColWidthInChars( + rDoc.GetColWidth( nCol, nTab ) ); + //If the text won't fit in the column + if ( nLen < aString.getLength() ) + { + OUStringBuffer aReplacement; + if (bValue) + aReplacement.append("###"); + else + aReplacement.append(aString); + //truncate to the number of characters that should fit, even in the + //bValue case nLen might be < len ### + aString = comphelper::string::truncateToLength(aReplacement, nLen).makeStringAndClear(); + } + if ( nLen > aString.getLength() ) + { + if ( bValue && eHorJust == SvxCellHorJustify::Standard ) + eHorJust = SvxCellHorJustify::Right; + OUStringBuffer aTmp(nLen); + switch ( eHorJust ) + { + case SvxCellHorJustify::Right: + comphelper::string::padToLength( aTmp, nLen - aString.getLength(), ' ' ); + aString = aTmp.append(aString); + break; + case SvxCellHorJustify::Center: + comphelper::string::padToLength( aTmp, (nLen - aString.getLength()) / 2, ' ' ); + [[fallthrough]]; + default: + aTmp.append(aString); + comphelper::string::padToLength( aTmp, nLen, ' ' ); + } + aString = aTmp.makeStringAndClear(); + } + rStr = aString; +} + +void lcl_ScDocShell_WriteEmptyFixedWidthString( SvStream& rStream, + const ScDocument& rDoc, SCTAB nTab, SCCOL nCol ) +{ + OUString aString; + lcl_ScDocShell_GetFixedWidthString( aString, rDoc, nTab, nCol, false, + SvxCellHorJustify::Standard ); + rStream.WriteUnicodeOrByteText( aString ); +} + +template<typename StrT, typename SepCharT> +sal_Int32 getTextSepPos( + const StrT& rStr, const ScImportOptions& rAsciiOpt, const SepCharT& rTextSep, const SepCharT& rFieldSep, bool& rNeedQuotes) +{ + // #i116636# quotes are needed if text delimiter (quote), field delimiter, + // or LF or CR is in the cell text. + sal_Int32 nPos = rStr.indexOf(rTextSep); + rNeedQuotes = rAsciiOpt.bQuoteAllText || (nPos >= 0) || + (rStr.indexOf(rFieldSep) >= 0) || + (rStr.indexOf('\n') >= 0) || + (rStr.indexOf('\r') >= 0); + return nPos; +} + +template<typename StrT, typename StrBufT> +void escapeTextSep(sal_Int32 nPos, const StrT& rStrDelim, StrT& rStr) +{ + while (nPos >= 0) + { + StrBufT aBuf(rStr); + aBuf.insert(nPos, rStrDelim); + rStr = aBuf.makeStringAndClear(); + nPos = rStr.indexOf(rStrDelim, nPos+1+rStrDelim.getLength()); + } +} + +} + +void ScDocShell::AsciiSave( SvStream& rStream, const ScImportOptions& rAsciiOpt, SCTAB nTab ) +{ + sal_Unicode cDelim = rAsciiOpt.nFieldSepCode; + sal_Unicode cStrDelim = rAsciiOpt.nTextSepCode; + rtl_TextEncoding eCharSet = rAsciiOpt.eCharSet; + bool bFixedWidth = rAsciiOpt.bFixedWidth; + bool bSaveNumberAsSuch = rAsciiOpt.bSaveNumberAsSuch; + bool bSaveAsShown = rAsciiOpt.bSaveAsShown; + bool bShowFormulas = rAsciiOpt.bSaveFormulas; + + rtl_TextEncoding eOldCharSet = rStream.GetStreamCharSet(); + rStream.SetStreamCharSet( eCharSet ); + SvStreamEndian nOldNumberFormatInt = rStream.GetEndian(); + OString aStrDelimEncoded; // only used if not Unicode + OUString aStrDelimDecoded; // only used if context encoding + OString aDelimEncoded; + OUString aDelimDecoded; + bool bContextOrNotAsciiEncoding; + if ( eCharSet == RTL_TEXTENCODING_UNICODE ) + { + rStream.StartWritingUnicodeText(); + bContextOrNotAsciiEncoding = false; + } + else + { + aStrDelimEncoded = OString(&cStrDelim, 1, eCharSet); + aDelimEncoded = OString(&cDelim, 1, eCharSet); + rtl_TextEncodingInfo aInfo; + aInfo.StructSize = sizeof(aInfo); + if ( rtl_getTextEncodingInfo( eCharSet, &aInfo ) ) + { + bContextOrNotAsciiEncoding = + (((aInfo.Flags & RTL_TEXTENCODING_INFO_CONTEXT) != 0) || + ((aInfo.Flags & RTL_TEXTENCODING_INFO_ASCII) == 0)); + if ( bContextOrNotAsciiEncoding ) + { + aStrDelimDecoded = OStringToOUString(aStrDelimEncoded, eCharSet); + aDelimDecoded = OStringToOUString(aDelimEncoded, eCharSet); + } + } + else + bContextOrNotAsciiEncoding = false; + } + + SCCOL nStartCol = 0; + SCROW nStartRow = 0; + SCCOL nEndCol; + SCROW nEndRow; + m_pDocument->GetCellArea( nTab, nEndCol, nEndRow ); + + ScProgress aProgress( this, ScResId( STR_SAVE_DOC ), nEndRow, true ); + + OUString aString; + + bool bTabProtect = m_pDocument->IsTabProtected( nTab ); + + SCCOL nCol; + SCROW nRow; + + // Treat the top left cell separator "sep=" special. + // Here nStartRow == 0 && nStartCol == 0 + if (!bFixedWidth && cDelim != 0) + { + // First row iterator. + ScHorizontalCellIterator aIter( *m_pDocument, nTab, nStartCol, nStartRow, nEndCol, nStartRow); + ScRefCellValue* pCell; + // Must be first column and all following cells on this row must be + // empty to fiddle with "sep=". + if ((pCell = aIter.GetNext( nCol, nRow)) != nullptr && nCol == nStartCol && !aIter.GetNext( nCol, nRow)) + { + if (pCell->meType == CELLTYPE_STRING) + { + aString = pCell->mpString->getString(); + if (aString.getLength() <= 5 && aString.startsWithIgnoreAsciiCase("sep=")) + { + // Cell content is /^sep=.?$/ so write current separator. + // Force the quote character to '"' regardless what is set + // for export because that is the only one recognized on + // import. + aString = "sep=" + OUStringChar(cDelim); + if (cStrDelim != 0) + rStream.WriteUniOrByteChar( '"', eCharSet); + if (eCharSet == RTL_TEXTENCODING_UNICODE) + { + write_uInt16s_FromOUString( rStream, aString); + } + else + { + OString aStrEnc = OUStringToOString( aString, eCharSet); + // write byte encoded + rStream.WriteBytes( aStrEnc.getStr(), aStrEnc.getLength()); + } + if (cStrDelim != 0) + rStream.WriteUniOrByteChar( '"', eCharSet); + endlub( rStream ); + ++nStartRow; + } + } + } + } + + SCCOL nNextCol = nStartCol; + SCROW nNextRow = nStartRow; + SCCOL nEmptyCol; + SCROW nEmptyRow; + SvNumberFormatter& rFormatter = *m_pDocument->GetFormatTable(); + + ScHorizontalCellIterator aIter( *m_pDocument, nTab, nStartCol, nStartRow, + nEndCol, nEndRow ); + ScRefCellValue* pCell; + while ( ( pCell = aIter.GetNext( nCol, nRow ) ) != nullptr ) + { + bool bProgress = false; // only upon line change + if ( nNextRow < nRow ) + { // empty rows or/and empty columns up to end of row + bProgress = true; + for ( nEmptyCol = nNextCol; nEmptyCol < nEndCol; nEmptyCol++ ) + { // remaining columns of last row + if ( bFixedWidth ) + lcl_ScDocShell_WriteEmptyFixedWidthString( rStream, + *m_pDocument, nTab, nEmptyCol ); + else if ( cDelim != 0 ) + rStream.WriteUniOrByteChar( cDelim ); + } + endlub( rStream ); + nNextRow++; + for ( nEmptyRow = nNextRow; nEmptyRow < nRow; nEmptyRow++ ) + { // completely empty rows + for ( nEmptyCol = nStartCol; nEmptyCol < nEndCol; nEmptyCol++ ) + { + if ( bFixedWidth ) + lcl_ScDocShell_WriteEmptyFixedWidthString( rStream, + *m_pDocument, nTab, nEmptyCol ); + else if ( cDelim != 0 ) + rStream.WriteUniOrByteChar( cDelim ); + } + endlub( rStream ); + } + for ( nEmptyCol = nStartCol; nEmptyCol < nCol; nEmptyCol++ ) + { // empty columns at beginning of row + if ( bFixedWidth ) + lcl_ScDocShell_WriteEmptyFixedWidthString( rStream, + *m_pDocument, nTab, nEmptyCol ); + else if ( cDelim != 0 ) + rStream.WriteUniOrByteChar( cDelim ); + } + nNextRow = nRow; + } + else if ( nNextCol < nCol ) + { // empty columns in same row + for ( nEmptyCol = nNextCol; nEmptyCol < nCol; nEmptyCol++ ) + { // columns in between + if ( bFixedWidth ) + lcl_ScDocShell_WriteEmptyFixedWidthString( rStream, + *m_pDocument, nTab, nEmptyCol ); + else if ( cDelim != 0 ) + rStream.WriteUniOrByteChar( cDelim ); + } + } + if ( nCol == nEndCol ) + { + bProgress = true; + nNextCol = nStartCol; + nNextRow = nRow + 1; + } + else + nNextCol = nCol + 1; + + CellType eType = pCell->meType; + ScAddress aPos(nCol, nRow, nTab); + if ( bTabProtect ) + { + const ScProtectionAttr* pProtAttr = + m_pDocument->GetAttr( nCol, nRow, nTab, ATTR_PROTECTION ); + if ( pProtAttr->GetHideCell() || + ( eType == CELLTYPE_FORMULA && bShowFormulas && + pProtAttr->GetHideFormula() ) ) + eType = CELLTYPE_NONE; // hide + } + bool bForceQuotes = false; + bool bString; + switch ( eType ) + { + case CELLTYPE_NONE: + aString.clear(); + bString = false; + break; + case CELLTYPE_FORMULA : + { + FormulaError nErrCode; + if ( bShowFormulas ) + { + aString = pCell->mpFormula->GetFormula(); + bString = true; + } + else if ((nErrCode = pCell->mpFormula->GetErrCode()) != FormulaError::NONE) + { + aString = ScGlobal::GetErrorString( nErrCode ); + bString = true; + } + else if (pCell->mpFormula->IsValue()) + { + sal_uInt32 nFormat = m_pDocument->GetNumberFormat(aPos); + if ( bFixedWidth || bSaveAsShown ) + { + const Color* pDummy; + aString = ScCellFormat::GetString(*pCell, nFormat, &pDummy, rFormatter, *m_pDocument); + bString = bSaveAsShown && rFormatter.IsTextFormat( nFormat); + } + else + { + aString = ScCellFormat::GetInputString(*pCell, nFormat, rFormatter, *m_pDocument); + bString = bForceQuotes = !bSaveNumberAsSuch; + } + } + else + { + if ( bSaveAsShown ) + { + sal_uInt32 nFormat = m_pDocument->GetNumberFormat(aPos); + const Color* pDummy; + aString = ScCellFormat::GetString(*pCell, nFormat, &pDummy, rFormatter, *m_pDocument); + } + else + aString = pCell->mpFormula->GetString().getString(); + bString = true; + } + } + break; + case CELLTYPE_STRING : + if ( bSaveAsShown ) + { + sal_uInt32 nFormat = m_pDocument->GetNumberFormat(aPos); + const Color* pDummy; + aString = ScCellFormat::GetString(*pCell, nFormat, &pDummy, rFormatter, *m_pDocument); + } + else + aString = pCell->mpString->getString(); + bString = true; + break; + case CELLTYPE_EDIT : + { + const EditTextObject* pObj = pCell->mpEditText; + EditEngine& rEngine = m_pDocument->GetEditEngine(); + rEngine.SetText( *pObj); + aString = rEngine.GetText(); // including LF + bString = true; + } + break; + case CELLTYPE_VALUE : + { + sal_uInt32 nFormat = m_pDocument->GetNumberFormat( nCol, nRow, nTab ); + if ( bFixedWidth || bSaveAsShown ) + { + const Color* pDummy; + aString = ScCellFormat::GetString(*pCell, nFormat, &pDummy, rFormatter, *m_pDocument); + bString = bSaveAsShown && rFormatter.IsTextFormat( nFormat); + } + else + { + aString = ScCellFormat::GetInputString(*pCell, nFormat, rFormatter, *m_pDocument); + bString = bForceQuotes = !bSaveNumberAsSuch; + } + } + break; + default: + OSL_FAIL( "ScDocShell::AsciiSave: unknown CellType" ); + aString.clear(); + bString = false; + } + + if ( bFixedWidth ) + { + SvxCellHorJustify eHorJust = + m_pDocument->GetAttr( nCol, nRow, nTab, ATTR_HOR_JUSTIFY )->GetValue(); + lcl_ScDocShell_GetFixedWidthString( aString, *m_pDocument, nTab, nCol, + !bString, eHorJust ); + rStream.WriteUnicodeOrByteText( aString ); + } + else + { + OUString aUniString = aString;// TODO: remove that later + if (!bString && cStrDelim != 0 && !aUniString.isEmpty()) + { + sal_Unicode c = aUniString[0]; + bString = (c == cStrDelim || c == ' ' || + aUniString.endsWith(" ") || + aUniString.indexOf(cStrDelim) >= 0); + if (!bString && cDelim != 0) + bString = (aUniString.indexOf(cDelim) >= 0); + } + if ( bString ) + { + if ( cStrDelim != 0 ) //@ BugId 55355 + { + if ( eCharSet == RTL_TEXTENCODING_UNICODE ) + { + bool bNeedQuotes = false; + sal_Int32 nPos = getTextSepPos( + aUniString, rAsciiOpt, cStrDelim, cDelim, bNeedQuotes); + + escapeTextSep<OUString, OUStringBuffer>( + nPos, OUString(cStrDelim), aUniString); + + if ( bNeedQuotes || bForceQuotes ) + rStream.WriteUniOrByteChar( cStrDelim, eCharSet ); + write_uInt16s_FromOUString(rStream, aUniString); + if ( bNeedQuotes || bForceQuotes ) + rStream.WriteUniOrByteChar( cStrDelim, eCharSet ); + } + else + { + // This is nasty. The Unicode to byte encoding + // may convert typographical quotation marks to ASCII + // quotation marks, which may interfere with the delimiter, + // so we have to escape delimiters after the string has + // been encoded. Since this may happen also with UTF-8 + // encoded typographical quotation marks if such was + // specified as a delimiter we have to check for the full + // encoded delimiter string, not just one character. + // Now for RTL_TEXTENCODING_ISO_2022_... and similar brain + // dead encodings where one code point (and especially a + // low ASCII value) may represent different characters, we + // have to convert forth and back and forth again. Same for + // UTF-7 since it is a context sensitive encoding too. + + if ( bContextOrNotAsciiEncoding ) + { + // to byte encoding + OString aStrEnc = OUStringToOString(aUniString, eCharSet); + // back to Unicode + OUString aStrDec = OStringToOUString(aStrEnc, eCharSet); + + // search on re-decoded string + bool bNeedQuotes = false; + sal_Int32 nPos = getTextSepPos( + aStrDec, rAsciiOpt, aStrDelimDecoded, aDelimDecoded, bNeedQuotes); + + escapeTextSep<OUString, OUStringBuffer>( + nPos, aStrDelimDecoded, aStrDec); + + // write byte re-encoded + if ( bNeedQuotes || bForceQuotes ) + rStream.WriteUniOrByteChar( cStrDelim, eCharSet ); + rStream.WriteUnicodeOrByteText( aStrDec, eCharSet ); + if ( bNeedQuotes || bForceQuotes ) + rStream.WriteUniOrByteChar( cStrDelim, eCharSet ); + } + else + { + OString aStrEnc = OUStringToOString(aUniString, eCharSet); + + // search on encoded string + bool bNeedQuotes = false; + sal_Int32 nPos = getTextSepPos( + aStrEnc, rAsciiOpt, aStrDelimEncoded, aDelimEncoded, bNeedQuotes); + + escapeTextSep<OString, OStringBuffer>( + nPos, aStrDelimEncoded, aStrEnc); + + // write byte encoded + if ( bNeedQuotes || bForceQuotes ) + rStream.WriteBytes( + aStrDelimEncoded.getStr(), aStrDelimEncoded.getLength()); + rStream.WriteBytes(aStrEnc.getStr(), aStrEnc.getLength()); + if ( bNeedQuotes || bForceQuotes ) + rStream.WriteBytes( + aStrDelimEncoded.getStr(), aStrDelimEncoded.getLength()); + } + } + } + else + rStream.WriteUnicodeOrByteText( aUniString ); + } + else + rStream.WriteUnicodeOrByteText( aUniString ); + } + + if( nCol < nEndCol ) + { + if(cDelim!=0) //@ BugId 55355 + rStream.WriteUniOrByteChar( cDelim ); + } + else + endlub( rStream ); + + if ( bProgress ) + aProgress.SetStateOnPercent( nRow ); + } + + // write out empty if requested + if ( nNextRow <= nEndRow ) + { + for ( nEmptyCol = nNextCol; nEmptyCol < nEndCol; nEmptyCol++ ) + { // remaining empty columns of last row + if ( bFixedWidth ) + lcl_ScDocShell_WriteEmptyFixedWidthString( rStream, + *m_pDocument, nTab, nEmptyCol ); + else if ( cDelim != 0 ) + rStream.WriteUniOrByteChar( cDelim ); + } + endlub( rStream ); + nNextRow++; + } + for ( nEmptyRow = nNextRow; nEmptyRow <= nEndRow; nEmptyRow++ ) + { // entire empty rows + for ( nEmptyCol = nStartCol; nEmptyCol < nEndCol; nEmptyCol++ ) + { + if ( bFixedWidth ) + lcl_ScDocShell_WriteEmptyFixedWidthString( rStream, + *m_pDocument, nTab, nEmptyCol ); + else if ( cDelim != 0 ) + rStream.WriteUniOrByteChar( cDelim ); + } + endlub( rStream ); + } + + rStream.SetStreamCharSet( eOldCharSet ); + rStream.SetEndian( nOldNumberFormatInt ); +} + +bool ScDocShell::ConvertTo( SfxMedium &rMed ) +{ + ScRefreshTimerProtector aProt( m_pDocument->GetRefreshTimerControlAddress() ); + + // #i6500# don't call DoEnterHandler here (doesn't work with AutoSave), + // it's already in ExecuteSave (as for Save and SaveAs) + + if (m_pAutoStyleList) + m_pAutoStyleList->ExecuteAllNow(); // Execute template timeouts now + if (GetCreateMode()== SfxObjectCreateMode::STANDARD) + SfxObjectShell::SetVisArea( tools::Rectangle() ); // Edited normally -> no VisArea + + OSL_ENSURE( rMed.GetFilter(), "Filter == 0" ); + + bool bRet = false; + OUString aFltName = rMed.GetFilter()->GetFilterName(); + + if (aFltName == pFilterXML) + { + //TODO/LATER: this shouldn't happen! + OSL_FAIL("XML filter in ConvertFrom?!"); + bRet = SaveXML( &rMed, nullptr ); + } + else if (aFltName == pFilterExcel5 || aFltName == pFilterExcel95 || + aFltName == pFilterExcel97 || aFltName == pFilterEx5Temp || + aFltName == pFilterEx95Temp || aFltName == pFilterEx97Temp) + { + weld::WaitObject aWait( GetActiveDialogParent() ); + + bool bDoSave = true; + if( ScTabViewShell* pViewShell = GetBestViewShell() ) + { + ScExtDocOptions* pExtDocOpt = m_pDocument->GetExtDocOptions(); + if( !pExtDocOpt ) + { + m_pDocument->SetExtDocOptions( std::make_unique<ScExtDocOptions>() ); + pExtDocOpt = m_pDocument->GetExtDocOptions(); + } + pViewShell->GetViewData().WriteExtOptions( *pExtDocOpt ); + + /* #i104990# If the imported document contains a medium + password, determine if we can save it, otherwise ask the users + whether they want to save without it. */ + if( (rMed.GetFilter()->GetFilterFlags() & SfxFilterFlags::ENCRYPTION) == SfxFilterFlags::NONE ) + { + SfxItemSet* pItemSet = rMed.GetItemSet(); + if( pItemSet && pItemSet->GetItemState( SID_PASSWORD ) == SfxItemState::SET ) + { + bDoSave = ScWarnPassword::WarningOnPassword( rMed ); + // #i42858# remove password from medium (warn only one time) + if( bDoSave ) + pItemSet->ClearItem( SID_PASSWORD ); + } + } + + if( bDoSave ) + { + bool bNeedRetypePassDlg = ScPassHashHelper::needsPassHashRegen( *m_pDocument, PASSHASH_XL ); + bDoSave = !bNeedRetypePassDlg || pViewShell->ExecuteRetypePassDlg( PASSHASH_XL ); + } + } + + if( bDoSave ) + { + ExportFormatExcel eFormat = ExpBiff5; + if( aFltName == pFilterExcel97 || aFltName == pFilterEx97Temp ) + eFormat = ExpBiff8; + ErrCode eError = ScFormatFilter::Get().ScExportExcel5( rMed, m_pDocument.get(), eFormat, RTL_TEXTENCODING_MS_1252 ); + + if( eError && !GetError() ) + SetError(eError); + + // don't return false for warnings + bRet = eError.IsWarning() || (eError == ERRCODE_NONE); + } + else + { + // export aborted, i.e. "Save without password" warning + SetError(ERRCODE_ABORT); + } + } + else if (aFltName == SC_TEXT_CSV_FILTER_NAME) + { + OUString sItStr; + SfxItemSet* pSet = rMed.GetItemSet(); + const SfxStringItem* pOptionsItem; + if ( pSet && + (pOptionsItem = pSet->GetItemIfSet( SID_FILE_FILTEROPTIONS )) ) + { + sItStr = pOptionsItem->GetValue(); + } + + if ( sItStr.isEmpty() ) + { + // default for ascii export (from API without options): + // ISO8859-1/MS_1252 encoding, comma, double quotes + + ScImportOptions aDefOptions( ',', '"', RTL_TEXTENCODING_MS_1252 ); + sItStr = aDefOptions.BuildString(); + } + + weld::WaitObject aWait( GetActiveDialogParent() ); + ScImportOptions aOptions( sItStr ); + + if (aOptions.nSheetToExport) + { + // Only from command line --convert-to + bRet = true; + + // Verbose only from command line, not UI (in case we actually + // implement that) nor macro filter options. + bool bVerbose = false; + const css::uno::Sequence<css::beans::PropertyValue> & rArgs = rMed.GetArgs(); + const auto pProp = std::find_if( rArgs.begin(), rArgs.end(), + [](const css::beans::PropertyValue& rProp) { return rProp.Name == "ConversionRequestOrigin"; }); + if (pProp != rArgs.end()) + { + OUString aOrigin; + pProp->Value >>= aOrigin; + bVerbose = (aOrigin == "CommandLine"); + } + + SCTAB nStartTab; + SCTAB nCount = m_pDocument->GetTableCount(); + if (aOptions.nSheetToExport == -1) + { + // All sheets. + nStartTab = 0; + } + else if (0 < aOptions.nSheetToExport && aOptions.nSheetToExport <= nCount) + { + // One sheet, 1-based. + nCount = aOptions.nSheetToExport; + nStartTab = nCount - 1; + } + else + { + // Usage error, no export but log. + if (bVerbose) + { + if (aOptions.nSheetToExport < 0) + std::cout << "Bad sheet number string given." << std::endl; + else + std::cout << "No sheet number " << aOptions.nSheetToExport + << ", number of sheets is " << nCount << std::endl; + } + nStartTab = 0; + nCount = 0; + SetError(SCERR_EXPORT_DATA); + bRet = false; + } + + INetURLObject aURLObject(rMed.GetURLObject()); + OUString sExt = aURLObject.CutExtension(); + OUString sBaseName = aURLObject.GetLastName(); + aURLObject.CutLastName(); + + for (SCTAB i = nStartTab; i < nCount; ++i) + { + OUString sTabName; + if (!m_pDocument->GetName(i, sTabName)) + sTabName = OUString::number(i); + INetURLObject aSheetURLObject(aURLObject); + OUString sFileName = sBaseName + "-" + sTabName; + if (!sExt.isEmpty()) + sFileName = sFileName + "." + sExt; + aSheetURLObject.Append(sFileName); + + // log similar to DispatchWatcher::executeDispatchRequests + OUString aOutFile = aSheetURLObject.GetMainURL(INetURLObject::DecodeMechanism::NONE); + if (bVerbose) + { + OUString aDisplayedName; + if (osl::FileBase::E_None != osl::FileBase::getSystemPathFromFileURL(aOutFile, aDisplayedName)) + aDisplayedName = aOutFile; + std::cout << "Writing sheet " << OUStringToOString(sTabName, osl_getThreadTextEncoding()) << " -> " + << OUStringToOString(aDisplayedName, osl_getThreadTextEncoding()) + << std::endl; + + if (FStatHelper::IsDocument(aOutFile)) + std::cout << "Overwriting: " << OUStringToOString(aDisplayedName, osl_getThreadTextEncoding()) + << std::endl ; + } + + std::unique_ptr<SvStream> xStm = ::utl::UcbStreamHelper::CreateStream(aOutFile, StreamMode::TRUNC | StreamMode::WRITE); + if (!xStm) + { + SetError(ERRCODE_IO_CANTCREATE); + bRet = false; + break; + } + AsciiSave(*xStm, aOptions, i); + } + } + else + { + SvStream* pStream = rMed.GetOutStream(); + if (pStream) + { + AsciiSave(*pStream, aOptions, GetSaveTab()); + bRet = true; + + if (m_pDocument->GetTableCount() > 1) + if (!rMed.GetError()) + rMed.SetError(SCWARN_EXPORT_ASCII); + } + } + } + else if (aFltName == pFilterDBase) + { + OUString sCharSet; + SfxItemSet* pSet = rMed.GetItemSet(); + const SfxStringItem* pOptionsItem; + if ( pSet && + (pOptionsItem = pSet->GetItemIfSet( SID_FILE_FILTEROPTIONS )) ) + { + sCharSet = pOptionsItem->GetValue(); + } + + if (sCharSet.isEmpty()) + { + // default for dBase export (from API without options): + // IBM_850 encoding + + sCharSet = ScGlobal::GetCharsetString( RTL_TEXTENCODING_IBM_850 ); + } + + weld::WaitObject aWait( GetActiveDialogParent() ); + // Hack so that Sba can overwrite the opened TempFile. + rMed.CloseOutStream(); + bool bHasMemo = false; + + ErrCode eError = DBaseExport( + rMed.GetPhysicalName(), ScGlobal::GetCharsetValue(sCharSet), bHasMemo); + + INetURLObject aTmpFile( rMed.GetPhysicalName(), INetProtocol::File ); + if ( bHasMemo ) + aTmpFile.setExtension(u"dbt"); + if ( eError != ERRCODE_NONE && !eError.IsWarning() ) + { + if (!GetError()) + SetError(eError); + if ( bHasMemo && IsDocument( aTmpFile ) ) + KillFile( aTmpFile ); + } + else + { + bRet = true; + if ( bHasMemo ) + { + const SfxStringItem* pNameItem = rMed.GetItemSet()->GetItem<SfxStringItem>( SID_FILE_NAME ); + INetURLObject aDbtFile( pNameItem->GetValue(), INetProtocol::File ); + aDbtFile.setExtension(u"dbt"); + + // tdf#40713: don't lose dbt file + // if aDbtFile corresponds exactly to aTmpFile, we just have to return + if (aDbtFile.GetMainURL( INetURLObject::DecodeMechanism::Unambiguous ) == + aTmpFile.GetMainURL( INetURLObject::DecodeMechanism::Unambiguous )) + { + if (eError != ERRCODE_NONE && !GetError()) + SetError(eError); + return bRet; + } + + if ( IsDocument( aDbtFile ) && !KillFile( aDbtFile ) ) + bRet = false; + if ( bRet && !MoveFile( aTmpFile, aDbtFile ) ) + bRet = false; + if ( !bRet ) + { + KillFile( aTmpFile ); + if (eError == ERRCODE_NONE || eError.IsWarning()) + eError = SCERR_EXPORT_DATA; + } + } + if (eError != ERRCODE_NONE && !GetError()) + SetError(eError); + } + } + else if (aFltName == pFilterDif) + { + SvStream* pStream = rMed.GetOutStream(); + if (pStream) + { + OUString sItStr; + SfxItemSet* pSet = rMed.GetItemSet(); + const SfxStringItem* pOptionsItem; + if ( pSet && + (pOptionsItem = pSet->GetItemIfSet( SID_FILE_FILTEROPTIONS )) ) + { + sItStr = pOptionsItem->GetValue(); + } + + if (sItStr.isEmpty()) + { + // default for DIF export (from API without options): + // ISO8859-1/MS_1252 encoding + + sItStr = ScGlobal::GetCharsetString( RTL_TEXTENCODING_MS_1252 ); + } + + weld::WaitObject aWait( GetActiveDialogParent() ); + ScFormatFilter::Get().ScExportDif( *pStream, m_pDocument.get(), ScAddress(0,0,0), + ScGlobal::GetCharsetValue(sItStr) ); + bRet = true; + + if (m_pDocument->GetTableCount() > 1) + if (!rMed.GetError()) + rMed.SetError(SCWARN_EXPORT_ASCII); + } + } + else if (aFltName == pFilterSylk) + { + SvStream* pStream = rMed.GetOutStream(); + if ( pStream ) + { + weld::WaitObject aWait( GetActiveDialogParent() ); + + SCCOL nEndCol; + SCROW nEndRow; + m_pDocument->GetCellArea( 0, nEndCol, nEndRow ); + ScRange aRange( 0,0,0, nEndCol,nEndRow,0 ); + + ScImportExport aImExport( *m_pDocument, aRange ); + aImExport.SetFormulas( true ); + bRet = aImExport.ExportStream( *pStream, rMed.GetBaseURL( true ), SotClipboardFormatId::SYLK ); + } + } + else if (aFltName == pFilterHtml) + { + SvStream* pStream = rMed.GetOutStream(); + if ( pStream ) + { + SfxItemSet* pSet = rMed.GetItemSet(); + OUString sFilterOptions; + + if (const SfxStringItem* pOptionsItem = pSet->GetItemIfSet(SID_FILE_FILTEROPTIONS)) + sFilterOptions = pOptionsItem->GetValue(); + + weld::WaitObject aWait(GetActiveDialogParent()); + ScImportExport aImExport(*m_pDocument); + aImExport.SetStreamPath(rMed.GetName()); + aImExport.SetFilterOptions(sFilterOptions); + bRet = aImExport.ExportStream(*pStream, rMed.GetBaseURL(true), SotClipboardFormatId::HTML); + if (bRet && !aImExport.GetNonConvertibleChars().isEmpty()) + { + SetError(*new StringErrorInfo( + SCWARN_EXPORT_NONCONVERTIBLE_CHARS, + aImExport.GetNonConvertibleChars(), + DialogMask::ButtonsOk | DialogMask::MessageInfo)); + } + } + } + else + { + if (GetError()) + SetError(SCERR_IMPORT_NI); + } + return bRet; +} + +bool ScDocShell::DoSaveCompleted( SfxMedium * pNewStor, bool bRegisterRecent ) +{ + bool bRet = SfxObjectShell::DoSaveCompleted( pNewStor, bRegisterRecent ); + + // SfxHintId::ScDocSaved for change ReadOnly -> Read/Write + Broadcast( SfxHint( SfxHintId::ScDocSaved ) ); + return bRet; +} + +bool ScDocShell::QuerySlotExecutable( sal_uInt16 nSlotId ) +{ + // #i112634# ask VBA event handlers whether to save or print the document + + using namespace ::com::sun::star::script::vba; + + sal_Int32 nVbaEventId = VBAEventId::NO_EVENT; + uno::Sequence< uno::Any > aArgs; + switch( nSlotId ) + { + case SID_SAVEDOC: + case SID_SAVEASDOC: + nVbaEventId = VBAEventId::WORKBOOK_BEFORESAVE; + aArgs = { uno::Any(nSlotId == SID_SAVEASDOC) }; + break; + case SID_PRINTDOC: + case SID_PRINTDOCDIRECT: + nVbaEventId = VBAEventId::WORKBOOK_BEFOREPRINT; + break; + } + + bool bSlotExecutable = true; + if( nVbaEventId != VBAEventId::NO_EVENT ) try + { + uno::Reference< XVBAEventProcessor > xEventProcessor( m_pDocument->GetVbaEventProcessor(), uno::UNO_SET_THROW ); + xEventProcessor->processVbaEvent( nVbaEventId, aArgs ); + } + catch( util::VetoException& ) + { + bSlotExecutable = false; + } + catch( uno::Exception& ) + { + } + return bSlotExecutable; +} + +bool ScDocShell::PrepareClose( bool bUI ) +{ + if(SC_MOD()->GetCurRefDlgId()>0) + { + SfxViewFrame* pFrame = SfxViewFrame::GetFirst( this ); + if( pFrame ) + { + SfxViewShell* p = pFrame->GetViewShell(); + ScTabViewShell* pViewSh = dynamic_cast< ScTabViewShell *>( p ); + if(pViewSh!=nullptr) + { + vcl::Window *pWin=pViewSh->GetWindow(); + if(pWin!=nullptr) pWin->GrabFocus(); + } + } + + return false; + } + if ( m_pDocument->IsInLinkUpdate() || m_pDocument->IsInInterpreter() ) + { + ErrorMessage(STR_CLOSE_ERROR_LINK); + return false; + } + + DoEnterHandler(); + + // start 'Workbook_BeforeClose' VBA event handler for possible veto + if( !IsInPrepareClose() ) + { + try + { + uno::Reference< script::vba::XVBAEventProcessor > xVbaEvents( m_pDocument->GetVbaEventProcessor(), uno::UNO_SET_THROW ); + uno::Sequence< uno::Any > aArgs; + xVbaEvents->processVbaEvent( script::vba::VBAEventId::WORKBOOK_BEFORECLOSE, aArgs ); + } + catch( util::VetoException& ) + { + // if event processor throws VetoException, macro has vetoed close + return false; + } + catch( uno::Exception& ) + { + } + } + // end handler code + + bool bRet = SfxObjectShell::PrepareClose( bUI ); + if (bRet) // true == close + m_pDocument->EnableIdle(false); // Do not mess around with it anymore! + + return bRet; +} + +OUString ScDocShell::GetOwnFilterName() +{ + return pFilterSc50; +} + +OUString ScDocShell::GetHtmlFilterName() +{ + return pFilterHtml; +} + +OUString ScDocShell::GetWebQueryFilterName() +{ + return pFilterHtmlWebQ; +} + +OUString ScDocShell::GetAsciiFilterName() +{ + return SC_TEXT_CSV_FILTER_NAME; +} + +OUString ScDocShell::GetLotusFilterName() +{ + return pFilterLotus; +} + +OUString ScDocShell::GetDBaseFilterName() +{ + return pFilterDBase; +} + +OUString ScDocShell::GetDifFilterName() +{ + return pFilterDif; +} + +bool ScDocShell::HasAutomaticTableName( std::u16string_view rFilter ) +{ + // sal_True for those filters that keep the default table name + // (which is language specific) + + return rFilter == SC_TEXT_CSV_FILTER_NAME + || rFilter == pFilterLotus + || rFilter == pFilterExcel4 + || rFilter == pFilterEx4Temp + || rFilter == pFilterDBase + || rFilter == pFilterDif + || rFilter == pFilterSylk + || rFilter == pFilterHtml + || rFilter == pFilterRtf; +} + +std::unique_ptr<ScDocFunc> ScDocShell::CreateDocFunc() +{ + return std::make_unique<ScDocFuncDirect>( *this ); +} + +ScDocShell::ScDocShell( const SfxModelFlags i_nSfxCreationFlags, const std::shared_ptr<ScDocument>& pDoc ) : + SfxObjectShell( i_nSfxCreationFlags ), + m_pDocument ( pDoc ? pDoc : std::make_shared<ScDocument>( SCDOCMODE_DOCUMENT, this )), + m_aDdeTextFmt(OUString("TEXT")), + m_nPrtToScreenFactor( 1.0 ), + m_pImpl ( new DocShell_Impl ), + m_bHeaderOn ( true ), + m_bFooterOn ( true ), + m_bIsEmpty ( true ), + m_bIsInUndo ( false ), + m_bDocumentModifiedPending( false ), + m_bUpdateEnabled ( true ), + m_bUcalcTest ( false ), + m_bAreasChangedNeedBroadcast( false ), + m_nDocumentLock ( 0 ), + m_nCanUpdate (css::document::UpdateDocMode::ACCORDING_TO_CONFIG) +{ + SetPool( &SC_MOD()->GetPool() ); + + m_bIsInplace = (GetCreateMode() == SfxObjectCreateMode::EMBEDDED); + // Will be reset if not in place + + m_pDocFunc = CreateDocFunc(); + + // SetBaseModel needs exception handling + ScModelObj::CreateAndSet( this ); + + StartListening(*this); + SfxStyleSheetPool* pStlPool = m_pDocument->GetStyleSheetPool(); + if (pStlPool) + StartListening(*pStlPool); + + m_pDocument->GetDBCollection()->SetRefreshHandler( + LINK( this, ScDocShell, RefreshDBDataHdl ) ); + + // InitItems and CalcOutputFactor are called now in Load/ConvertFrom/InitNew +} + +ScDocShell::~ScDocShell() +{ + ResetDrawObjectShell(); // If the Drawing Layer still tries to access it, access it + + SfxStyleSheetPool* pStlPool = m_pDocument->GetStyleSheetPool(); + if (pStlPool) + EndListening(*pStlPool); + EndListening(*this); + + m_pAutoStyleList.reset(); + + SfxApplication *pSfxApp = SfxGetpApp(); + if ( pSfxApp->GetDdeService() ) // Delete DDE for Document + pSfxApp->RemoveDdeTopic( this ); + + m_pDocFunc.reset(); + delete m_pDocument->mpUndoManager; + m_pDocument->mpUndoManager = nullptr; + m_pImpl.reset(); + + m_pPaintLockData.reset(); + + m_pSolverSaveData.reset(); + m_pSheetSaveData.reset(); + m_pFormatSaveData.reset(); + m_pOldAutoDBRange.reset(); + + if (m_pModificator) + { + OSL_FAIL("The Modificator should not exist"); + m_pModificator.reset(); + } +} + +SfxUndoManager* ScDocShell::GetUndoManager() +{ + return m_pDocument->GetUndoManager(); +} + +void ScDocShell::SetModified( bool bModified ) +{ + if ( SfxObjectShell::IsEnableSetModified() ) + { + SfxObjectShell::SetModified( bModified ); + Broadcast( SfxHint( SfxHintId::DocChanged ) ); + } +} + +void ScDocShell::SetDocumentModified() +{ + // BroadcastUno must also happen right away with pPaintLockData + // FIXME: Also for SetDrawModified, if Drawing is connected + // FIXME: Then own Hint? + + if ( m_pPaintLockData ) + { + // #i115009# broadcast BCA_BRDCST_ALWAYS, so a component can read recalculated results + // of RecalcModeAlways formulas (like OFFSET) after modifying cells + m_pDocument->Broadcast(ScHint(SfxHintId::ScDataChanged, BCA_BRDCST_ALWAYS)); + m_pDocument->InvalidateTableArea(); // #i105279# needed here + m_pDocument->BroadcastUno( SfxHint( SfxHintId::DataChanged ) ); + + m_pPaintLockData->SetModified(); // Later on ... + return; + } + + SetDrawModified(); + + if ( m_pDocument->IsAutoCalcShellDisabled() ) + SetDocumentModifiedPending( true ); + else + { + SetDocumentModifiedPending( false ); + m_pDocument->InvalidateStyleSheetUsage(); + m_pDocument->InvalidateTableArea(); + m_pDocument->InvalidateLastTableOpParams(); + m_pDocument->Broadcast(ScHint(SfxHintId::ScDataChanged, BCA_BRDCST_ALWAYS)); + if ( m_pDocument->IsForcedFormulaPending() && m_pDocument->GetAutoCalc() ) + m_pDocument->CalcFormulaTree( true ); + m_pDocument->RefreshDirtyTableColumnNames(); + PostDataChanged(); + + // Detective AutoUpdate: + // Update if formulas were modified (DetectiveDirty) or the list contains + // "Trace Error" entries (Trace Error can look completely different + // after changes to non-formula cells). + + ScDetOpList* pList = m_pDocument->GetDetOpList(); + if ( pList && ( m_pDocument->IsDetectiveDirty() || pList->HasAddError() ) && + pList->Count() && !IsInUndo() && SC_MOD()->GetAppOptions().GetDetectiveAuto() ) + { + GetDocFunc().DetectiveRefresh(true); // sal_True = caused by automatic update + } + m_pDocument->SetDetectiveDirty(false); // always reset, also if not refreshed + } + + if (m_bAreasChangedNeedBroadcast) + { + m_bAreasChangedNeedBroadcast = false; + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreasChanged)); + } + + // notify UNO objects after BCA_BRDCST_ALWAYS etc. + m_pDocument->BroadcastUno( SfxHint( SfxHintId::DataChanged ) ); +} + +/** + * SetDrawModified - without Formula update + * + * Drawing also needs to be updated for the normal SetDocumentModified + * e.g.: when deleting tables etc. + */ +void ScDocShell::SetDrawModified() +{ + bool bUpdate = !IsModified(); + + SetModified(); + + SfxBindings* pBindings = GetViewBindings(); + if (bUpdate && pBindings) + { + pBindings->Invalidate( SID_SAVEDOC ); + pBindings->Invalidate( SID_DOC_MODIFIED ); + } + + if (pBindings) + { + // #i105960# Undo etc used to be volatile. + // They always have to be invalidated, including drawing layer or row height changes + // (but not while pPaintLockData is set). + pBindings->Invalidate( SID_UNDO ); + pBindings->Invalidate( SID_REDO ); + pBindings->Invalidate( SID_REPEAT ); + } + + if ( m_pDocument->IsChartListenerCollectionNeedsUpdate() ) + { + m_pDocument->UpdateChartListenerCollection(); + SfxGetpApp()->Broadcast(SfxHint( SfxHintId::ScDrawChanged )); // Navigator + } + SC_MOD()->AnythingChanged(); +} + +void ScDocShell::SetInUndo(bool bSet) +{ + m_bIsInUndo = bSet; +} + +void ScDocShell::GetDocStat( ScDocStat& rDocStat ) +{ + SfxPrinter* pPrinter = GetPrinter(); + + m_pDocument->GetDocStat( rDocStat ); + rDocStat.nPageCount = 0; + + if ( pPrinter ) + for ( SCTAB i=0; i<rDocStat.nTableCount; i++ ) + rDocStat.nPageCount = sal::static_int_cast<sal_uInt16>( rDocStat.nPageCount + + static_cast<sal_uInt16>(ScPrintFunc( this, pPrinter, i ).GetTotalPages()) ); +} + +std::shared_ptr<SfxDocumentInfoDialog> ScDocShell::CreateDocumentInfoDialog(weld::Window* pParent, const SfxItemSet &rSet) +{ + std::shared_ptr<SfxDocumentInfoDialog> xDlg = std::make_shared<SfxDocumentInfoDialog>(pParent, rSet); + ScDocShell* pDocSh = dynamic_cast< ScDocShell *>( SfxObjectShell::Current() ); + + // Only for statistics, if this Doc is shown; not from the Doc Manager + if( pDocSh == this ) + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + ::CreateTabPage ScDocStatPageCreate = pFact->GetTabPageCreatorFunc(SID_SC_TP_STAT); + OSL_ENSURE(ScDocStatPageCreate, "Tabpage create fail!"); + xDlg->AddFontTabPage(); + xDlg->AddTabPage("calcstats", ScResId(STR_DOC_STAT), ScDocStatPageCreate); + } + return xDlg; +} + +weld::Window* ScDocShell::GetActiveDialogParent() +{ + ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell(); + if ( pViewSh ) + return pViewSh->GetDialogParent(); + return Application::GetDefDialogParent(); +} + +void ScDocShell::SetSolverSaveData( std::unique_ptr<ScOptSolverSave> pData ) +{ + m_pSolverSaveData = std::move(pData); +} + +ScSheetSaveData* ScDocShell::GetSheetSaveData() +{ + if (!m_pSheetSaveData) + m_pSheetSaveData.reset( new ScSheetSaveData ); + + return m_pSheetSaveData.get(); +} + +ScFormatSaveData* ScDocShell::GetFormatSaveData() +{ + if (!m_pFormatSaveData) + m_pFormatSaveData.reset( new ScFormatSaveData ); + + return m_pFormatSaveData.get(); +} + +namespace { + +void removeKeysIfExists(const Reference<ui::XAcceleratorConfiguration>& xScAccel, const vector<const awt::KeyEvent*>& rKeys) +{ + for (const awt::KeyEvent* p : rKeys) + { + if (!p) + continue; + + try + { + xScAccel->removeKeyEvent(*p); + } + catch (const container::NoSuchElementException&) {} + } +} + +} + +void ScDocShell::ResetKeyBindings( ScOptionsUtil::KeyBindingType eType ) +{ + using namespace ::com::sun::star::ui; + + Reference<uno::XComponentContext> xContext = ::comphelper::getProcessComponentContext(); + if (!xContext.is()) + return; + + Reference<XModuleUIConfigurationManagerSupplier> xModuleCfgSupplier( + theModuleUIConfigurationManagerSupplier::get(xContext) ); + + // Grab the Calc configuration. + Reference<XUIConfigurationManager> xConfigMgr = + xModuleCfgSupplier->getUIConfigurationManager( + "com.sun.star.sheet.SpreadsheetDocument"); + + if (!xConfigMgr.is()) + return; + + // shortcut manager + Reference<XAcceleratorConfiguration> xScAccel = xConfigMgr->getShortCutManager(); + + if (!xScAccel.is()) + return; + + vector<const awt::KeyEvent*> aKeys; + aKeys.reserve(9); + + // Backspace key + awt::KeyEvent aBackspace; + aBackspace.KeyCode = awt::Key::BACKSPACE; + aBackspace.Modifiers = 0; + aKeys.push_back(&aBackspace); + + // Delete key + awt::KeyEvent aDelete; + aDelete.KeyCode = awt::Key::DELETE; + aDelete.Modifiers = 0; + aKeys.push_back(&aDelete); + + // Ctrl-D + awt::KeyEvent aCtrlD; + aCtrlD.KeyCode = awt::Key::D; + aCtrlD.Modifiers = awt::KeyModifier::MOD1; + aKeys.push_back(&aCtrlD); + + // Alt-Down + awt::KeyEvent aAltDown; + aAltDown.KeyCode = awt::Key::DOWN; + aAltDown.Modifiers = awt::KeyModifier::MOD2; + aKeys.push_back(&aAltDown); + + // Ctrl-Space + awt::KeyEvent aCtrlSpace; + aCtrlSpace.KeyCode = awt::Key::SPACE; + aCtrlSpace.Modifiers = awt::KeyModifier::MOD1; + aKeys.push_back(&aCtrlSpace); + + // Ctrl-Shift-Space + awt::KeyEvent aCtrlShiftSpace; + aCtrlShiftSpace.KeyCode = awt::Key::SPACE; + aCtrlShiftSpace.Modifiers = awt::KeyModifier::MOD1 | awt::KeyModifier::SHIFT; + aKeys.push_back(&aCtrlShiftSpace); + + // F4 + awt::KeyEvent aF4; + aF4.KeyCode = awt::Key::F4; + aF4.Modifiers = 0; + aKeys.push_back(&aF4); + + // CTRL+SHIFT+F4 + awt::KeyEvent aCtrlShiftF4; + aCtrlShiftF4.KeyCode = awt::Key::F4; + aCtrlShiftF4.Modifiers = awt::KeyModifier::MOD1 | awt::KeyModifier::SHIFT; + aKeys.push_back(&aCtrlShiftF4); + + // SHIFT+F4 + awt::KeyEvent aShiftF4; + aShiftF4.KeyCode = awt::Key::F4; + aShiftF4.Modifiers = awt::KeyModifier::SHIFT; + aKeys.push_back(&aShiftF4); + + // Remove all involved keys first, because swapping commands don't work + // well without doing this. + removeKeysIfExists(xScAccel, aKeys); + xScAccel->store(); + + switch (eType) + { + case ScOptionsUtil::KEY_DEFAULT: + xScAccel->setKeyEvent(aDelete, ".uno:ClearContents"); + xScAccel->setKeyEvent(aBackspace, ".uno:Delete"); + xScAccel->setKeyEvent(aCtrlD, ".uno:FillDown"); + xScAccel->setKeyEvent(aAltDown, ".uno:DataSelect"); + xScAccel->setKeyEvent(aCtrlSpace, ".uno:SelectColumn"); + xScAccel->setKeyEvent(aCtrlShiftSpace, ".uno:SelectAll"); + xScAccel->setKeyEvent(aF4, ".uno:ToggleRelative"); + xScAccel->setKeyEvent(aCtrlShiftF4, ".uno:ViewDataSourceBrowser"); + break; + case ScOptionsUtil::KEY_OOO_LEGACY: + xScAccel->setKeyEvent(aDelete, ".uno:Delete"); + xScAccel->setKeyEvent(aBackspace, ".uno:ClearContents"); + xScAccel->setKeyEvent(aCtrlD, ".uno:DataSelect"); + xScAccel->setKeyEvent(aCtrlShiftSpace, ".uno:SelectColumn"); + xScAccel->setKeyEvent(aF4, ".uno:ViewDataSourceBrowser"); + xScAccel->setKeyEvent(aShiftF4, ".uno:ToggleRelative"); + break; + default: + ; + } + + xScAccel->store(); +} + +void ScDocShell::UseSheetSaveEntries() +{ + if (!m_pSheetSaveData) + return; + + m_pSheetSaveData->UseSaveEntries(); // use positions from saved file for next saving + + bool bHasEntries = false; + SCTAB nTabCount = m_pDocument->GetTableCount(); + SCTAB nTab; + for (nTab = 0; nTab < nTabCount; ++nTab) + if (m_pSheetSaveData->HasStreamPos(nTab)) + bHasEntries = true; + + if (!bHasEntries) + { + // if no positions were set (for example, export to other format), + // reset all "valid" flags + for (nTab = 0; nTab < nTabCount; ++nTab) + m_pDocument->SetStreamValid(nTab, false); + } +} + +// --- ScDocShellModificator ------------------------------------------ + +ScDocShellModificator::ScDocShellModificator( ScDocShell& rDS ) + : + rDocShell( rDS ), + mpProtector(new ScRefreshTimerProtector(rDS.GetDocument().GetRefreshTimerControlAddress())) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + bAutoCalcShellDisabled = rDoc.IsAutoCalcShellDisabled(); + bIdleEnabled = rDoc.IsIdleEnabled(); + rDoc.SetAutoCalcShellDisabled( true ); + rDoc.EnableIdle(false); +} + +ScDocShellModificator::~ScDocShellModificator() COVERITY_NOEXCEPT_FALSE +{ + ScDocument& rDoc = rDocShell.GetDocument(); + rDoc.SetAutoCalcShellDisabled( bAutoCalcShellDisabled ); + if ( !bAutoCalcShellDisabled && rDocShell.IsDocumentModifiedPending() ) + rDocShell.SetDocumentModified(); // last one shuts off the lights + rDoc.EnableIdle(bIdleEnabled); +} + +void ScDocShellModificator::SetDocumentModified() +{ + ScDocument& rDoc = rDocShell.GetDocument(); + rDoc.PrepareFormulaCalc(); + if ( !rDoc.IsImportingXML() ) + { + // temporarily restore AutoCalcShellDisabled + bool bDisabled = rDoc.IsAutoCalcShellDisabled(); + rDoc.SetAutoCalcShellDisabled( bAutoCalcShellDisabled ); + rDocShell.SetDocumentModified(); + rDoc.SetAutoCalcShellDisabled( bDisabled ); + } + else + { + // uno broadcast is necessary for api to work + // -> must also be done during xml import + rDoc.BroadcastUno( SfxHint( SfxHintId::DataChanged ) ); + } +} + +bool ScDocShell::IsChangeRecording() const +{ + ScChangeTrack* pChangeTrack = m_pDocument->GetChangeTrack(); + return pChangeTrack != nullptr; +} + +bool ScDocShell::HasChangeRecordProtection() const +{ + bool bRes = false; + ScChangeTrack* pChangeTrack = m_pDocument->GetChangeTrack(); + if (pChangeTrack) + bRes = pChangeTrack->IsProtected(); + return bRes; +} + +void ScDocShell::SetChangeRecording( bool bActivate, bool /*bLockAllViews*/ ) +{ + bool bOldChangeRecording = IsChangeRecording(); + + if (bActivate) + { + m_pDocument->StartChangeTracking(); + ScChangeViewSettings aChangeViewSet; + aChangeViewSet.SetShowChanges(true); + m_pDocument->SetChangeViewSettings(aChangeViewSet); + } + else + { + m_pDocument->EndChangeTracking(); + PostPaintGridAll(); + } + + if (bOldChangeRecording != IsChangeRecording()) + { + UpdateAcceptChangesDialog(); + // invalidate slots + SfxBindings* pBindings = GetViewBindings(); + if (pBindings) + pBindings->InvalidateAll(false); + } +} + +void ScDocShell::SetProtectionPassword( const OUString &rNewPassword ) +{ + ScChangeTrack* pChangeTrack = m_pDocument->GetChangeTrack(); + if (!pChangeTrack) + return; + + bool bProtected = pChangeTrack->IsProtected(); + + if (!rNewPassword.isEmpty()) + { + // when password protection is applied change tracking must always be active + SetChangeRecording( true ); + + css::uno::Sequence< sal_Int8 > aProtectionHash; + SvPasswordHelper::GetHashPassword( aProtectionHash, rNewPassword ); + pChangeTrack->SetProtection( aProtectionHash ); + } + else + { + pChangeTrack->SetProtection( css::uno::Sequence< sal_Int8 >() ); + } + + if ( bProtected != pChangeTrack->IsProtected() ) + { + UpdateAcceptChangesDialog(); + SetDocumentModified(); + } +} + +bool ScDocShell::GetProtectionHash( /*out*/ css::uno::Sequence< sal_Int8 > &rPasswordHash ) +{ + bool bRes = false; + ScChangeTrack* pChangeTrack = m_pDocument->GetChangeTrack(); + if (pChangeTrack && pChangeTrack->IsProtected()) + { + rPasswordHash = pChangeTrack->GetProtection(); + bRes = true; + } + return bRes; +} + +void ScDocShell::SetIsInUcalc() +{ + m_bUcalcTest = true; +} + +void ScDocShell::RegisterAutomationWorkbookObject(css::uno::Reference< ooo::vba::excel::XWorkbook > const& xWorkbook) +{ + mxAutomationWorkbookObject = xWorkbook; +} + +extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportSLK(SvStream &rStream) +{ + ScDLL::Init(); + ScDocument aDocument; + ScDocOptions aDocOpt = aDocument.GetDocOptions(); + aDocOpt.SetLookUpColRowNames(false); + aDocument.SetDocOptions(aDocOpt); + aDocument.MakeTable(0); + aDocument.EnableExecuteLink(false); + aDocument.SetInsertingFromOtherDoc(true); + aDocument.SetImportingXML(true); + + ScImportExport aImpEx(aDocument); + return aImpEx.ImportStream(rStream, OUString(), SotClipboardFormatId::SYLK); +} + +extern "C" SAL_DLLPUBLIC_EXPORT bool TestImportDBF(SvStream &rStream) +{ + ScDLL::Init(); + + // we need a real file for this filter + + // put it in an empty dir + utl::TempFile aTmpDir(nullptr, true); + aTmpDir.EnableKillingFile(); + OUString sTmpDir = aTmpDir.GetURL(); + + OUString sExtension(".dbf"); + utl::TempFile aTempInput(u"", true, &sExtension, &sTmpDir); + aTempInput.EnableKillingFile(); + + SvStream* pInputStream = aTempInput.GetStream(StreamMode::WRITE); + sal_uInt8 aBuffer[8192]; + while (auto nRead = rStream.ReadBytes(aBuffer, SAL_N_ELEMENTS(aBuffer))) + pInputStream->WriteBytes(aBuffer, nRead); + aTempInput.CloseStream(); + + SfxMedium aMedium(aTempInput.GetURL(), StreamMode::STD_READWRITE); + + ScDocShellRef xDocShell = new ScDocShell(SfxModelFlags::EMBEDDED_OBJECT | + SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS | + SfxModelFlags::DISABLE_DOCUMENT_RECOVERY); + + xDocShell->DoInitNew(); + + ScDocument& rDoc = xDocShell->GetDocument(); + + ScDocOptions aDocOpt = rDoc.GetDocOptions(); + aDocOpt.SetLookUpColRowNames(false); + rDoc.SetDocOptions(aDocOpt); + rDoc.MakeTable(0); + rDoc.EnableExecuteLink(false); + rDoc.SetInsertingFromOtherDoc(true); + + ScDocRowHeightUpdater::TabRanges aRecalcRanges(0, rDoc.MaxRow()); + std::map<SCCOL, ScColWidthParam> aColWidthParam; + ErrCode eError = xDocShell->DBaseImport(aMedium.GetPhysicalName(), RTL_TEXTENCODING_IBM_850, aColWidthParam, aRecalcRanges.maRanges); + + xDocShell->DoClose(); + xDocShell.clear(); + + return eError == ERRCODE_NONE; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/docsh2.cxx b/sc/source/ui/docshell/docsh2.cxx new file mode 100644 index 000000000..04faa6e75 --- /dev/null +++ b/sc/source/ui/docshell/docsh2.cxx @@ -0,0 +1,187 @@ +/* -*- 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 <rtl/bootstrap.hxx> +#include <osl/file.hxx> +#include <svx/drawitem.hxx> +#include <svl/asiancfg.hxx> +#include <editeng/forbiddencharacterstable.hxx> +#include <orcusfilters.hxx> +#include <config_folders.h> +#include <unotools/configmgr.hxx> +#include <comphelper/processfactory.hxx> +#include <o3tl/unit_conversion.hxx> + +#include <drwlayer.hxx> +#include <stlpool.hxx> +#include <docsh.hxx> +#include <docfunc.hxx> +#include <svx/svxids.hrc> +#include <filter.hxx> +#include <functional> + +using namespace com::sun::star; + +bool ScDocShell::InitNew( const uno::Reference < embed::XStorage >& xStor ) +{ + bool bRet = SfxObjectShell::InitNew( xStor ); + + m_pDocument->MakeTable(0); + + // Additional tables are created by the first View, if bIsEmpty is still sal_True + if( bRet ) + { + Size aSize( + o3tl::convert(STD_COL_WIDTH * OLE_STD_CELLS_X, o3tl::Length::twip, o3tl::Length::mm100), + o3tl::convert(ScGlobal::nStdRowHeight * OLE_STD_CELLS_Y, o3tl::Length::twip, + o3tl::Length::mm100)); + // Also adjust start here + SetVisAreaOrSize( tools::Rectangle( Point(), aSize ) ); + } + + // InitOptions sets the document languages, must be called before CreateStandardStyles + InitOptions(false); + + if (ScStyleSheetPool* pStyleSheetPool = m_pDocument->GetStyleSheetPool()) + { + pStyleSheetPool->CreateStandardStyles(); + m_pDocument->UpdStlShtPtrsFrmNms(); + + if (!m_bUcalcTest) + { + /* Create styles that are imported through Orcus */ + + OUString aURL("$BRAND_BASE_DIR/" LIBO_SHARE_FOLDER "/calc/styles.xml"); + rtl::Bootstrap::expandMacros(aURL); + + OUString aPath; + osl::FileBase::getSystemPathFromFileURL(aURL, aPath); + + ScOrcusFilters* pOrcus = ScFormatFilter::Get().GetOrcusFilters(); + if (pOrcus) + { + pOrcus->importODS_Styles(*m_pDocument, aPath); + pStyleSheetPool->setAllParaStandard(); + } + } + } + + // SetDocumentModified is not allowed anymore in Load/InitNew! + InitItems(); + CalcOutputFactor(); + + return bRet; +} + +void ScDocShell::SetEmpty(bool bSet) +{ + m_bIsEmpty = bSet; +} + +void ScDocShell::InitItems() +{ + // Fill AllItemSet for Controller with needed Items: + // Printer Options are set in GetPrinter when printing + UpdateFontList(); + + ScDrawLayer* pDrawLayer = m_pDocument->GetDrawLayer(); + if (pDrawLayer) + { + PutItem( SvxColorListItem ( pDrawLayer->GetColorList(), SID_COLOR_TABLE ) ); + PutItem( SvxGradientListItem( pDrawLayer->GetGradientList(), SID_GRADIENT_LIST ) ); + PutItem( SvxHatchListItem ( pDrawLayer->GetHatchList(), SID_HATCH_LIST ) ); + PutItem( SvxBitmapListItem ( pDrawLayer->GetBitmapList(), SID_BITMAP_LIST ) ); + PutItem( SvxPatternListItem ( pDrawLayer->GetPatternList(), SID_PATTERN_LIST ) ); + PutItem( SvxDashListItem ( pDrawLayer->GetDashList(), SID_DASH_LIST ) ); + PutItem( SvxLineEndListItem ( pDrawLayer->GetLineEndList(), SID_LINEEND_LIST ) ); + + // Other modifications after creation of the DrawLayer + pDrawLayer->SetNotifyUndoActionHdl( std::bind( &ScDocFunc::NotifyDrawUndo, m_pDocFunc.get(), std::placeholders::_1 ) ); + } + else if (!utl::ConfigManager::IsFuzzing()) + { + // always use global color table instead of local copy + PutItem( SvxColorListItem( XColorList::GetStdColorList(), SID_COLOR_TABLE ) ); + } + + if (utl::ConfigManager::IsFuzzing() || + (m_pDocument->GetForbiddenCharacters() && m_pDocument->IsValidAsianCompression() && m_pDocument->IsValidAsianKerning())) + return; + + // get settings from SvxAsianConfig + SvxAsianConfig aAsian; + + if (!m_pDocument->GetForbiddenCharacters()) + { + // set forbidden characters if necessary + const uno::Sequence<lang::Locale> aLocales = aAsian.GetStartEndCharLocales(); + if (aLocales.hasElements()) + { + std::shared_ptr<SvxForbiddenCharactersTable> xForbiddenTable( + SvxForbiddenCharactersTable::makeForbiddenCharactersTable(comphelper::getProcessComponentContext())); + + for (const lang::Locale& rLocale : aLocales) + { + i18n::ForbiddenCharacters aForbidden; + aAsian.GetStartEndChars( rLocale, aForbidden.beginLine, aForbidden.endLine ); + LanguageType eLang = LanguageTag::convertToLanguageType(rLocale); + + xForbiddenTable->SetForbiddenCharacters( eLang, aForbidden ); + } + + m_pDocument->SetForbiddenCharacters( xForbiddenTable ); + } + } + + if ( !m_pDocument->IsValidAsianCompression() ) + { + // set compression mode from configuration if not already set (e.g. XML import) + m_pDocument->SetAsianCompression( aAsian.GetCharDistanceCompression() ); + } + + if ( !m_pDocument->IsValidAsianKerning() ) + { + // set asian punctuation kerning from configuration if not already set (e.g. XML import) + m_pDocument->SetAsianKerning( !aAsian.IsKerningWesternTextOnly() ); // reversed + } +} + +void ScDocShell::ResetDrawObjectShell() +{ + ScDrawLayer* pDrawLayer = m_pDocument->GetDrawLayer(); + if (pDrawLayer) + pDrawLayer->SetObjectShell( nullptr ); +} + +ScDrawLayer* ScDocShell::MakeDrawLayer() +{ + ScDrawLayer* pDrawLayer = m_pDocument->GetDrawLayer(); + if (!pDrawLayer) + { + m_pDocument->InitDrawLayer(this); + pDrawLayer = m_pDocument->GetDrawLayer(); + InitItems(); // including Undo and Basic + Broadcast( SfxHint( SfxHintId::ScDrawLayerNew ) ); + if (m_nDocumentLock) + pDrawLayer->setLock(true); + } + return pDrawLayer; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/docsh3.cxx b/sc/source/ui/docshell/docsh3.cxx new file mode 100644 index 000000000..edab7a530 --- /dev/null +++ b/sc/source/ui/docshell/docsh3.cxx @@ -0,0 +1,1331 @@ +/* -*- 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 <com/sun/star/document/XDocumentPropertiesSupplier.hpp> +#include <com/sun/star/document/XDocumentProperties.hpp> + +#include <scitems.hxx> +#include <rangelst.hxx> +#include <editeng/flstitem.hxx> +#include <editeng/paperinf.hxx> +#include <editeng/sizeitem.hxx> +#include <o3tl/unit_conversion.hxx> +#include <officecfg/Office/Common.hxx> +#include <sal/log.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/app.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/printer.hxx> +#include <svl/numformat.hxx> +#include <svx/pageitem.hxx> +#include <svx/postattr.hxx> +#include <svx/svxids.hrc> +#include <unotools/configmgr.hxx> +#include <vcl/svapp.hxx> +#include <vcl/virdev.hxx> +#include <vcl/weld.hxx> +#include <osl/diagnose.h> + +#include <docsh.hxx> +#include "docshimp.hxx" +#include <scmod.hxx> +#include <tabvwsh.hxx> +#include <viewdata.hxx> +#include <docpool.hxx> +#include <stlpool.hxx> +#include <patattr.hxx> +#include <uiitems.hxx> +#include <hints.hxx> +#include <docoptio.hxx> +#include <viewopti.hxx> +#include <pntlock.hxx> +#include <chgtrack.hxx> +#include <docfunc.hxx> +#include <formulacell.hxx> +#include <chgviset.hxx> +#include <progress.hxx> +#include <redcom.hxx> +#include <inputopt.hxx> +#include <drwlayer.hxx> +#include <inputhdl.hxx> +#include <conflictsdlg.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <markdata.hxx> +#include <memory> +#include <formulaopt.hxx> + +#include <comphelper/lok.hxx> +#include <sfx2/lokhelper.hxx> + +// Redraw - Notifications + +void ScDocShell::PostEditView( ScEditEngineDefaulter* pEditEngine, const ScAddress& rCursorPos ) +{ +// Broadcast( ScEditViewHint( pEditEngine, rCursorPos ) ); + + // Test: only active ViewShell + + ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell(); + if (pViewSh && pViewSh->GetViewData().GetDocShell() == this) + { + ScEditViewHint aHint( pEditEngine, rCursorPos ); + pViewSh->Notify( *this, aHint ); + } +} + +void ScDocShell::PostDataChanged() +{ + Broadcast( SfxHint( SfxHintId::ScDataChanged ) ); + SfxGetpApp()->Broadcast(SfxHint( SfxHintId::ScAnyDataChanged )); // Navigator + m_pDocument->PrepareFormulaCalc(); + //! notify navigator directly! +} + +void ScDocShell::PostPaint( SCCOL nStartCol, SCROW nStartRow, SCTAB nStartTab, + SCCOL nEndCol, SCROW nEndRow, SCTAB nEndTab, PaintPartFlags nPart, + sal_uInt16 nExtFlags ) +{ + ScRange aRange(nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab); + PostPaint(aRange, nPart, nExtFlags); +} + +void ScDocShell::PostPaint( const ScRangeList& rRanges, PaintPartFlags nPart, sal_uInt16 nExtFlags ) +{ + ScRangeList aPaintRanges; + for (size_t i = 0, n = rRanges.size(); i < n; ++i) + { + const ScRange& rRange = rRanges[i]; + SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col(); + SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row(); + SCTAB nTab1 = rRange.aStart.Tab(), nTab2 = rRange.aEnd.Tab(); + + if (!m_pDocument->ValidCol(nCol1)) nCol1 = m_pDocument->MaxCol(); + if (!m_pDocument->ValidRow(nRow1)) nRow1 = m_pDocument->MaxRow(); + if (!m_pDocument->ValidCol(nCol2)) nCol2 = m_pDocument->MaxCol(); + if (!m_pDocument->ValidRow(nRow2)) nRow2 = m_pDocument->MaxRow(); + + if ( m_pPaintLockData ) + { + // #i54081# PaintPartFlags::Extras still has to be broadcast because it changes the + // current sheet if it's invalid. All other flags added to pPaintLockData. + PaintPartFlags nLockPart = nPart & ~PaintPartFlags::Extras; + if ( nLockPart != PaintPartFlags::NONE ) + { + //! nExtFlags ??? + m_pPaintLockData->AddRange( ScRange( nCol1, nRow1, nTab1, + nCol2, nRow2, nTab2 ), nLockPart ); + } + + nPart &= PaintPartFlags::Extras; // for broadcasting + if (nPart == PaintPartFlags::NONE) + continue; + } + + if (nExtFlags & SC_PF_LINES) // respect space for lines + { + //! check for hidden columns/rows! + if (nCol1>0) --nCol1; + if (nCol2<m_pDocument->MaxCol()) ++nCol2; + if (nRow1>0) --nRow1; + if (nRow2<m_pDocument->MaxRow()) ++nRow2; + } + + // expand for the merged ones + if (nExtFlags & SC_PF_TESTMERGE) + m_pDocument->ExtendMerge( nCol1, nRow1, nCol2, nRow2, nTab1 ); + + if ( nCol1 != 0 || nCol2 != m_pDocument->MaxCol() ) + { + // Extend to whole rows if SC_PF_WHOLEROWS is set, or rotated or non-left + // aligned cells are contained (see UpdatePaintExt). + // Special handling for RTL text (#i9731#) is unnecessary now with full + // support of right-aligned text. + + if ( ( nExtFlags & SC_PF_WHOLEROWS ) || + m_pDocument->HasAttrib( nCol1,nRow1,nTab1, + m_pDocument->MaxCol(),nRow2,nTab2, HasAttrFlags::Rotate | HasAttrFlags::RightOrCenter ) ) + { + nCol1 = 0; + nCol2 = m_pDocument->MaxCol(); + } + } + aPaintRanges.push_back(ScRange(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2)); + } + + Broadcast(ScPaintHint(aPaintRanges.Combine(), nPart)); + + // LOK: we are supposed to update the row / columns headers (and actually + // the document size too - cell size affects that, obviously) + if ((nPart & (PaintPartFlags::Top | PaintPartFlags::Left)) && comphelper::LibreOfficeKit::isActive()) + { + ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>(this->GetModel()); + SfxLokHelper::notifyDocumentSizeChangedAllViews(pModel); + } +} + +void ScDocShell::PostPaintGridAll() +{ + PostPaint( 0,0,0, m_pDocument->MaxCol(),m_pDocument->MaxRow(),MAXTAB, PaintPartFlags::Grid ); +} + +void ScDocShell::PostPaintCell( SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + PostPaint( nCol,nRow,nTab, nCol,nRow,nTab, PaintPartFlags::Grid, SC_PF_TESTMERGE ); +} + +void ScDocShell::PostPaintCell( const ScAddress& rPos ) +{ + PostPaintCell( rPos.Col(), rPos.Row(), rPos.Tab() ); +} + +void ScDocShell::PostPaintExtras() +{ + PostPaint( 0,0,0, m_pDocument->MaxCol(),m_pDocument->MaxRow(),MAXTAB, PaintPartFlags::Extras ); +} + +void ScDocShell::UpdatePaintExt( sal_uInt16& rExtFlags, const ScRange& rRange ) +{ + if ( ( rExtFlags & SC_PF_LINES ) == 0 && + m_pDocument->HasAttrib( rRange, HasAttrFlags::Lines | HasAttrFlags::Shadow | HasAttrFlags::Conditional ) ) + { + // If the range contains lines, shadow or conditional formats, + // set SC_PF_LINES to include one extra cell in all directions. + + rExtFlags |= SC_PF_LINES; + } + + if ( ( rExtFlags & SC_PF_WHOLEROWS ) == 0 && + ( rRange.aStart.Col() != 0 || rRange.aEnd.Col() != m_pDocument->MaxCol() ) && + m_pDocument->HasAttrib( rRange, HasAttrFlags::Rotate | HasAttrFlags::RightOrCenter ) ) + { + // If the range contains (logically) right- or center-aligned cells, + // or rotated cells, set SC_PF_WHOLEROWS to paint the whole rows. + // This test isn't needed after the cell changes, because it's also + // tested in PostPaint. UpdatePaintExt may later be changed to do this + // only if called before the changes. + + rExtFlags |= SC_PF_WHOLEROWS; + } +} + +void ScDocShell::UpdatePaintExt( sal_uInt16& rExtFlags, SCCOL nStartCol, SCROW nStartRow, SCTAB nStartTab, + SCCOL nEndCol, SCROW nEndRow, SCTAB nEndTab ) +{ + UpdatePaintExt( rExtFlags, ScRange( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab ) ); +} + +void ScDocShell::LockPaint_Impl(bool bDoc) +{ + if ( !m_pPaintLockData ) + m_pPaintLockData.reset( new ScPaintLockData ); + m_pPaintLockData->IncLevel(bDoc); +} + +void ScDocShell::UnlockPaint_Impl(bool bDoc) +{ + if ( m_pPaintLockData ) + { + if ( m_pPaintLockData->GetLevel(bDoc) ) + m_pPaintLockData->DecLevel(bDoc); + if (!m_pPaintLockData->GetLevel(!bDoc) && !m_pPaintLockData->GetLevel(bDoc)) + { + // Execute Paint now + + // don't continue collecting + std::unique_ptr<ScPaintLockData> pPaint = std::move(m_pPaintLockData); + + ScRangeListRef xRangeList = pPaint->GetRangeList(); + if ( xRangeList.is() ) + { + PaintPartFlags nParts = pPaint->GetParts(); + for ( size_t i = 0, nCount = xRangeList->size(); i < nCount; i++ ) + { + //! nExtFlags ??? + ScRange const & rRange = (*xRangeList)[i]; + PostPaint( rRange.aStart.Col(), rRange.aStart.Row(), rRange.aStart.Tab(), + rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aEnd.Tab(), + nParts ); + } + } + + if ( pPaint->GetModified() ) + SetDocumentModified(); + } + } + else + { + OSL_FAIL("UnlockPaint without LockPaint"); + } +} + +void ScDocShell::LockDocument_Impl(sal_uInt16 nNew) +{ + if (!m_nDocumentLock) + { + ScDrawLayer* pDrawLayer = m_pDocument->GetDrawLayer(); + if (pDrawLayer) + pDrawLayer->setLock(true); + } + m_nDocumentLock = nNew; +} + +void ScDocShell::UnlockDocument_Impl(sal_uInt16 nNew) +{ + m_nDocumentLock = nNew; + if (!m_nDocumentLock) + { + ScDrawLayer* pDrawLayer = m_pDocument->GetDrawLayer(); + if (pDrawLayer) + pDrawLayer->setLock(false); + } +} + +void ScDocShell::SetLockCount(sal_uInt16 nNew) +{ + if (nNew) // set + { + if ( !m_pPaintLockData ) + m_pPaintLockData.reset( new ScPaintLockData ); + m_pPaintLockData->SetDocLevel(nNew-1); + LockDocument_Impl(nNew); + } + else if (m_pPaintLockData) // delete + { + m_pPaintLockData->SetDocLevel(0); // at unlock, execute immediately + UnlockPaint_Impl(true); // now + UnlockDocument_Impl(0); + } +} + +void ScDocShell::LockPaint() +{ + LockPaint_Impl(false); +} + +void ScDocShell::UnlockPaint() +{ + UnlockPaint_Impl(false); +} + +void ScDocShell::LockDocument() +{ + LockPaint_Impl(true); + LockDocument_Impl(m_nDocumentLock + 1); +} + +void ScDocShell::UnlockDocument() +{ + if (m_nDocumentLock) + { + UnlockPaint_Impl(true); + UnlockDocument_Impl(m_nDocumentLock - 1); + } + else + { + OSL_FAIL("UnlockDocument without LockDocument"); + } +} + +void ScDocShell::SetInplace( bool bInplace ) +{ + if (m_bIsInplace != bInplace) + { + m_bIsInplace = bInplace; + CalcOutputFactor(); + } +} + +void ScDocShell::CalcOutputFactor() +{ + if (m_bIsInplace) + { + m_nPrtToScreenFactor = 1.0; // otherwise it does not match the inactive display + return; + } + + bool bTextWysiwyg = SC_MOD()->GetInputOptions().GetTextWysiwyg(); + if (bTextWysiwyg) + { + m_nPrtToScreenFactor = 1.0; + return; + } + + OUString aTestString( + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01234567890123456789"); + tools::Long nPrinterWidth = 0; + const ScPatternAttr* pPattern = &m_pDocument->GetPool()->GetDefaultItem(ATTR_PATTERN); + + vcl::Font aDefFont; + OutputDevice* pRefDev = GetRefDevice(); + MapMode aOldMode = pRefDev->GetMapMode(); + vcl::Font aOldFont = pRefDev->GetFont(); + + pRefDev->SetMapMode(MapMode(MapUnit::MapPixel)); + pPattern->GetFont(aDefFont, SC_AUTOCOL_BLACK, pRefDev); // font color doesn't matter here + pRefDev->SetFont(aDefFont); + nPrinterWidth = pRefDev->PixelToLogic(Size(pRefDev->GetTextWidth(aTestString), 0), MapMode(MapUnit::Map100thMM)).Width(); + pRefDev->SetFont(aOldFont); + pRefDev->SetMapMode(aOldMode); + + ScopedVclPtrInstance< VirtualDevice > pVirtWindow( *Application::GetDefaultDevice() ); + pVirtWindow->SetMapMode(MapMode(MapUnit::MapPixel)); + pPattern->GetFont(aDefFont, SC_AUTOCOL_BLACK, pVirtWindow); // font color doesn't matter here + pVirtWindow->SetFont(aDefFont); + double nWindowWidth = pVirtWindow->GetTextWidth(aTestString) / ScGlobal::nScreenPPTX; + nWindowWidth = o3tl::convert(nWindowWidth, o3tl::Length::twip, o3tl::Length::mm100); + + if (nPrinterWidth && nWindowWidth) + m_nPrtToScreenFactor = nPrinterWidth / nWindowWidth; + else + { + OSL_FAIL("GetTextSize returns 0 ??"); + m_nPrtToScreenFactor = 1.0; + } +} + +void ScDocShell::InitOptions(bool bForLoading) // called from InitNew and Load +{ + // Settings from the SpellCheckCfg get into Doc- and ViewOptions + + LanguageType nDefLang, nCjkLang, nCtlLang; + bool bAutoSpell; + ScModule::GetSpellSettings( nDefLang, nCjkLang, nCtlLang, bAutoSpell ); + ScModule* pScMod = SC_MOD(); + + ScDocOptions aDocOpt = pScMod->GetDocOptions(); + ScFormulaOptions aFormulaOpt = pScMod->GetFormulaOptions(); + ScViewOptions aViewOpt = pScMod->GetViewOptions(); + aDocOpt.SetAutoSpell( bAutoSpell ); + + if (!utl::ConfigManager::IsFuzzing()) + { + // two-digit year entry from Tools->Options->General + aDocOpt.SetYear2000(officecfg::Office::Common::DateFormat::TwoDigitYear::get()); + } + + if (bForLoading) + { + // #i112123# No style:decimal-places attribute means automatic decimals, not the configured default, + // so it must not be taken from the global options. + // Calculation settings are handled separately in ScXMLBodyContext::EndElement. + aDocOpt.SetStdPrecision( SvNumberFormatter::UNLIMITED_PRECISION ); + + // fdo#78294 The default null-date if + // <table:null-date table:date-value='...' /> + // is absent is 1899-12-30 regardless what the configuration is set to. + // Import filters may override this value. + aDocOpt.SetDate( 30, 12, 1899); + } + + m_pDocument->SetDocOptions( aDocOpt ); + m_pDocument->SetViewOptions( aViewOpt ); + SetFormulaOptions( aFormulaOpt, bForLoading ); + + // print options are now set directly before the printing + + m_pDocument->SetLanguage( nDefLang, nCjkLang, nCtlLang ); +} + +Printer* ScDocShell::GetDocumentPrinter() // for OLE +{ + return m_pDocument->GetPrinter(); +} + +SfxPrinter* ScDocShell::GetPrinter(bool bCreateIfNotExist) +{ + return m_pDocument->GetPrinter(bCreateIfNotExist); +} + +void ScDocShell::UpdateFontList() +{ + // pImpl->pFontList = new FontList( GetPrinter(), Application::GetDefaultDevice() ); + m_pImpl->pFontList.reset(new FontList(GetRefDevice(), nullptr)); + SvxFontListItem aFontListItem( m_pImpl->pFontList.get(), SID_ATTR_CHAR_FONTLIST ); + PutItem( aFontListItem ); + + CalcOutputFactor(); +} + +OutputDevice* ScDocShell::GetRefDevice() +{ + return m_pDocument->GetRefDevice(); +} + +sal_uInt16 ScDocShell::SetPrinter( VclPtr<SfxPrinter> const & pNewPrinter, SfxPrinterChangeFlags nDiffFlags ) +{ + SfxPrinter *pOld = m_pDocument->GetPrinter( false ); + if ( pOld && pOld->IsPrinting() ) + return SFX_PRINTERROR_BUSY; + + if (nDiffFlags & SfxPrinterChangeFlags::PRINTER) + { + if ( m_pDocument->GetPrinter() != pNewPrinter ) + { + m_pDocument->SetPrinter( pNewPrinter ); + m_pDocument->SetPrintOptions(); + + // MT: Use UpdateFontList: Will use Printer fonts only if needed! + /* + delete pImpl->pFontList; + pImpl->pFontList = new FontList( pNewPrinter, Application::GetDefaultDevice() ); + SvxFontListItem aFontListItem( pImpl->pFontList, SID_ATTR_CHAR_FONTLIST ); + PutItem( aFontListItem ); + + CalcOutputFactor(); + */ + if ( SC_MOD()->GetInputOptions().GetTextWysiwyg() ) + UpdateFontList(); + + ScModule* pScMod = SC_MOD(); + SfxViewFrame *pFrame = SfxViewFrame::GetFirst( this ); + while (pFrame) + { + SfxViewShell* pSh = pFrame->GetViewShell(); + if (ScTabViewShell* pViewSh = dynamic_cast<ScTabViewShell*>(pSh)) + { + ScInputHandler* pInputHdl = pScMod->GetInputHdl(pViewSh); + if (pInputHdl) + pInputHdl->UpdateRefDevice(); + } + pFrame = SfxViewFrame::GetNext( *pFrame, this ); + } + } + } + else if (nDiffFlags & SfxPrinterChangeFlags::JOBSETUP) + { + SfxPrinter* pOldPrinter = m_pDocument->GetPrinter(); + if (pOldPrinter) + { + pOldPrinter->SetJobSetup( pNewPrinter->GetJobSetup() ); + + // #i6706# Call SetPrinter with the old printer again, so the drawing layer + // RefDevice is set (calling ReformatAllTextObjects and rebuilding charts), + // because the JobSetup (printer device settings) may affect text layout. + m_pDocument->SetPrinter( pOldPrinter ); + CalcOutputFactor(); // also with the new settings + } + } + + if (nDiffFlags & SfxPrinterChangeFlags::OPTIONS) + { + m_pDocument->SetPrintOptions(); //! from new printer ??? + } + + if (nDiffFlags & (SfxPrinterChangeFlags::CHG_ORIENTATION | SfxPrinterChangeFlags::CHG_SIZE)) + { + OUString aStyle = m_pDocument->GetPageStyle( GetCurTab() ); + ScStyleSheetPool* pStPl = m_pDocument->GetStyleSheetPool(); + SfxStyleSheet* pStyleSheet = static_cast<SfxStyleSheet*>(pStPl->Find(aStyle, SfxStyleFamily::Page)); + if (pStyleSheet) + { + SfxItemSet& rSet = pStyleSheet->GetItemSet(); + + if (nDiffFlags & SfxPrinterChangeFlags::CHG_ORIENTATION) + { + const SvxPageItem& rOldItem = rSet.Get(ATTR_PAGE); + bool bWasLand = rOldItem.IsLandscape(); + bool bNewLand = ( pNewPrinter->GetOrientation() == Orientation::Landscape ); + if (bNewLand != bWasLand) + { + SvxPageItem aNewItem( rOldItem ); + aNewItem.SetLandscape( bNewLand ); + rSet.Put( aNewItem ); + + // flip size + Size aOldSize = rSet.Get(ATTR_PAGE_SIZE).GetSize(); + // coverity[swapped_arguments : FALSE] - this is in the correct order + Size aNewSize(aOldSize.Height(),aOldSize.Width()); + SvxSizeItem aNewSItem(ATTR_PAGE_SIZE,aNewSize); + rSet.Put( aNewSItem ); + } + } + if (nDiffFlags & SfxPrinterChangeFlags::CHG_SIZE) + { + SvxSizeItem aPaperSizeItem( ATTR_PAGE_SIZE, SvxPaperInfo::GetPaperSize(pNewPrinter) ); + rSet.Put( aPaperSizeItem ); + } + } + } + + PostPaint(0,0,0,m_pDocument->MaxCol(),m_pDocument->MaxRow(),MAXTAB,PaintPartFlags::All); + + return 0; +} + +ScChangeAction* ScDocShell::GetChangeAction( const ScAddress& rPos ) +{ + ScChangeTrack* pTrack = GetDocument().GetChangeTrack(); + if (!pTrack) + return nullptr; + + SCTAB nTab = rPos.Tab(); + + const ScChangeAction* pFound = nullptr; + const ScChangeAction* pAction = pTrack->GetFirst(); + while (pAction) + { + ScChangeActionType eType = pAction->GetType(); + //! ScViewUtil::IsActionShown( *pAction, *pSettings, *pDoc )... + if ( pAction->IsVisible() && eType != SC_CAT_DELETE_TABS ) + { + const ScBigRange& rBig = pAction->GetBigRange(); + if ( rBig.aStart.Tab() == nTab ) + { + ScRange aRange = rBig.MakeRange( GetDocument() ); + + if ( eType == SC_CAT_DELETE_ROWS ) + aRange.aEnd.SetRow( aRange.aStart.Row() ); + else if ( eType == SC_CAT_DELETE_COLS ) + aRange.aEnd.SetCol( aRange.aStart.Col() ); + + if ( aRange.Contains( rPos ) ) + { + pFound = pAction; // the last one wins + } + } + if ( pAction->GetType() == SC_CAT_MOVE ) + { + ScRange aRange = + static_cast<const ScChangeActionMove*>(pAction)-> + GetFromRange().MakeRange( GetDocument() ); + if ( aRange.Contains( rPos ) ) + { + pFound = pAction; + } + } + } + pAction = pAction->GetNext(); + } + + return const_cast<ScChangeAction*>(pFound); +} + +void ScDocShell::SetChangeComment( ScChangeAction* pAction, const OUString& rComment ) +{ + if (!pAction) + return; + + pAction->SetComment( rComment ); + //! Undo ??? + SetDocumentModified(); + + // Dialog-Notify + ScChangeTrack* pTrack = GetDocument().GetChangeTrack(); + if (pTrack) + { + sal_uLong nNumber = pAction->GetActionNumber(); + pTrack->NotifyModified( ScChangeTrackMsgType::Change, nNumber, nNumber ); + } +} + +void ScDocShell::ExecuteChangeCommentDialog( ScChangeAction* pAction, weld::Window* pParent, bool bPrevNext) +{ + if (!pAction) return; // without action is nothing... + + OUString aComment = pAction->GetComment(); + OUString aAuthor = pAction->GetUser(); + + DateTime aDT = pAction->GetDateTime(); + OUString aDate = ScGlobal::getLocaleData().getDate( aDT ) + " " + + ScGlobal::getLocaleData().getTime( aDT, false ); + + SfxItemSetFixed<SID_ATTR_POSTIT_AUTHOR, SID_ATTR_POSTIT_TEXT> aSet( GetPool() ); + + aSet.Put( SvxPostItTextItem ( aComment, SID_ATTR_POSTIT_TEXT ) ); + aSet.Put( SvxPostItAuthorItem( aAuthor, SID_ATTR_POSTIT_AUTHOR ) ); + aSet.Put( SvxPostItDateItem ( aDate, SID_ATTR_POSTIT_DATE ) ); + + std::unique_ptr<ScRedComDialog> pDlg(new ScRedComDialog( pParent, aSet,this,pAction,bPrevNext)); + + pDlg->Execute(); +} + +void ScDocShell::CompareDocument( ScDocument& rOtherDoc ) +{ + ScChangeTrack* pTrack = m_pDocument->GetChangeTrack(); + if ( pTrack && pTrack->GetFirst() ) + { + //! there are changes -> inquiry if needs to be deleted + } + + m_pDocument->EndChangeTracking(); + m_pDocument->StartChangeTracking(); + + OUString aOldUser; + pTrack = m_pDocument->GetChangeTrack(); + if ( pTrack ) + { + aOldUser = pTrack->GetUser(); + + // check if comparing to same document + + OUString aThisFile; + const SfxMedium* pThisMed = GetMedium(); + if (pThisMed) + aThisFile = pThisMed->GetName(); + OUString aOtherFile; + SfxObjectShell* pOtherSh = rOtherDoc.GetDocumentShell(); + if (pOtherSh) + { + const SfxMedium* pOtherMed = pOtherSh->GetMedium(); + if (pOtherMed) + aOtherFile = pOtherMed->GetName(); + } + bool bSameDoc = ( aThisFile == aOtherFile && !aThisFile.isEmpty() ); + if ( !bSameDoc ) + { + // create change actions from comparing with the name of the user + // who last saved the document + // (only if comparing different documents) + + using namespace ::com::sun::star; + uno::Reference<document::XDocumentPropertiesSupplier> xDPS( + GetModel(), uno::UNO_QUERY_THROW); + uno::Reference<document::XDocumentProperties> xDocProps( + xDPS->getDocumentProperties()); + OSL_ENSURE(xDocProps.is(), "no DocumentProperties"); + OUString aDocUser = xDocProps->getModifiedBy(); + + if ( !aDocUser.isEmpty() ) + pTrack->SetUser( aDocUser ); + } + } + + m_pDocument->CompareDocument( rOtherDoc ); + + pTrack = m_pDocument->GetChangeTrack(); + if ( pTrack ) + pTrack->SetUser( aOldUser ); + + PostPaintGridAll(); + SetDocumentModified(); +} + +// Merge (combine documents) + +static bool lcl_Equal( const ScChangeAction* pA, const ScChangeAction* pB, bool bIgnore100Sec ) +{ + return pA && pB && + pA->GetActionNumber() == pB->GetActionNumber() && + pA->GetType() == pB->GetType() && + pA->GetUser() == pB->GetUser() && + (bIgnore100Sec ? + pA->GetDateTimeUTC().IsEqualIgnoreNanoSec( pB->GetDateTimeUTC() ) : + pA->GetDateTimeUTC() == pB->GetDateTimeUTC()); + // don't compare state if an old change has been accepted +} + +static bool lcl_FindAction( ScDocument& rDoc, const ScChangeAction* pAction, ScDocument& rSearchDoc, const ScChangeAction* pFirstSearchAction, const ScChangeAction* pLastSearchAction, bool bIgnore100Sec ) +{ + if ( !pAction || !pFirstSearchAction || !pLastSearchAction ) + { + return false; + } + + sal_uLong nLastSearchAction = pLastSearchAction->GetActionNumber(); + const ScChangeAction* pA = pFirstSearchAction; + while ( pA && pA->GetActionNumber() <= nLastSearchAction ) + { + if ( pAction->GetType() == pA->GetType() && + pAction->GetUser() == pA->GetUser() && + (bIgnore100Sec ? + pAction->GetDateTimeUTC().IsEqualIgnoreNanoSec( pA->GetDateTimeUTC() ) : + pAction->GetDateTimeUTC() == pA->GetDateTimeUTC() ) && + pAction->GetBigRange() == pA->GetBigRange() ) + { + OUString aActionDesc = pAction->GetDescription(rDoc, true); + OUString aADesc = pA->GetDescription(rSearchDoc, true); + if (aActionDesc == aADesc) + { + OSL_FAIL( "lcl_FindAction(): found equal action!" ); + return true; + } + } + pA = pA->GetNext(); + } + + return false; +} + +void ScDocShell::MergeDocument( ScDocument& rOtherDoc, bool bShared, bool bCheckDuplicates, sal_uLong nOffset, ScChangeActionMergeMap* pMergeMap, bool bInverseMap ) +{ + ScTabViewShell* pViewSh = GetBestViewShell( false ); //! functions to the DocShell + if (!pViewSh) + return; + + ScChangeTrack* pSourceTrack = rOtherDoc.GetChangeTrack(); + if (!pSourceTrack) + return; //! nothing to do - error notification? + + ScChangeTrack* pThisTrack = m_pDocument->GetChangeTrack(); + if ( !pThisTrack ) + { // turn on + m_pDocument->StartChangeTracking(); + pThisTrack = m_pDocument->GetChangeTrack(); + OSL_ENSURE(pThisTrack,"ChangeTracking not enabled?"); + if ( !bShared ) + { + // turn on visual RedLining + ScChangeViewSettings aChangeViewSet; + aChangeViewSet.SetShowChanges(true); + m_pDocument->SetChangeViewSettings(aChangeViewSet); + } + } + + // include Nano seconds in compare? + bool bIgnore100Sec = !pSourceTrack->IsTimeNanoSeconds() || + !pThisTrack->IsTimeNanoSeconds(); + + // find common initial position + sal_uLong nFirstNewNumber = 0; + const ScChangeAction* pSourceAction = pSourceTrack->GetFirst(); + const ScChangeAction* pThisAction = pThisTrack->GetFirst(); + // skip identical actions + while ( lcl_Equal( pSourceAction, pThisAction, bIgnore100Sec ) ) + { + nFirstNewNumber = pSourceAction->GetActionNumber() + 1; + pSourceAction = pSourceAction->GetNext(); + pThisAction = pThisAction->GetNext(); + } + // pSourceAction and pThisAction now point to the first "own" actions + // The common actions before don't interest at all + + //! Inquiry if the documents where equal before the change tracking !!! + + const ScChangeAction* pFirstMergeAction = pSourceAction; + const ScChangeAction* pFirstSearchAction = pThisAction; + + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + const ScChangeAction* pLastSearchAction = pThisTrack->GetLast(); + + // Create MergeChangeData from the following actions + sal_uLong nNewActionCount = 0; + const ScChangeAction* pCount = pSourceAction; + while ( pCount ) + { + if ( bShared || !ScChangeTrack::MergeIgnore( *pCount, nFirstNewNumber ) ) + ++nNewActionCount; + pCount = pCount->GetNext(); + } + if (!nNewActionCount) + return; //! nothing to do - error notification? + // from here on no return + + ScProgress aProgress( this, "...", nNewActionCount, true ); + + sal_uLong nLastMergeAction = pSourceTrack->GetLast()->GetActionNumber(); + // UpdateReference-Undo, valid references for the last common state + pSourceTrack->MergePrepare( pFirstMergeAction, bShared ); + + // adjust MergeChangeData to all yet following actions in this document + // -> references valid for this document + while ( pThisAction ) + { + // #i87049# [Collaboration] Conflict between delete row and insert content is not merged correctly + if ( !bShared || !ScChangeTrack::MergeIgnore( *pThisAction, nFirstNewNumber ) ) + { + ScChangeActionType eType = pThisAction->GetType(); + switch ( eType ) + { + case SC_CAT_INSERT_COLS : + case SC_CAT_INSERT_ROWS : + case SC_CAT_INSERT_TABS : + pSourceTrack->AppendInsert( pThisAction->GetBigRange().MakeRange( GetDocument() ) ); + break; + case SC_CAT_DELETE_COLS : + case SC_CAT_DELETE_ROWS : + case SC_CAT_DELETE_TABS : + { + const ScChangeActionDel* pDel = static_cast<const ScChangeActionDel*>(pThisAction); + if ( pDel->IsTopDelete() && !pDel->IsTabDeleteCol() ) + { // deleted table contains deleted cols, which are not + sal_uLong nStart, nEnd; + pSourceTrack->AppendDeleteRange( + pDel->GetOverAllRange().MakeRange( GetDocument() ), nullptr, nStart, nEnd ); + } + } + break; + case SC_CAT_MOVE : + { + const ScChangeActionMove* pMove = static_cast<const ScChangeActionMove*>(pThisAction); + pSourceTrack->AppendMove( pMove->GetFromRange().MakeRange( GetDocument() ), + pMove->GetBigRange().MakeRange( GetDocument() ), nullptr ); + } + break; + default: + { + // added to avoid warnings + } + } + } + pThisAction = pThisAction->GetNext(); + } + + LockPaint(); // #i73877# no repainting after each action + + // take over MergeChangeData into the current document + bool bHasRejected = false; + OUString aOldUser = pThisTrack->GetUser(); + pThisTrack->SetUseFixDateTime( true ); + ScMarkData& rMarkData = pViewSh->GetViewData().GetMarkData(); + ScMarkData aOldMarkData( rMarkData ); + pSourceAction = pFirstMergeAction; + while ( pSourceAction && pSourceAction->GetActionNumber() <= nLastMergeAction ) + { + bool bMergeAction = false; + if ( bShared ) + { + if ( !bCheckDuplicates || !lcl_FindAction( rOtherDoc, pSourceAction, *m_pDocument, pFirstSearchAction, pLastSearchAction, bIgnore100Sec ) ) + { + bMergeAction = true; + } + } + else + { + if ( !ScChangeTrack::MergeIgnore( *pSourceAction, nFirstNewNumber ) ) + { + bMergeAction = true; + } + } + + if ( bMergeAction ) + { + ScChangeActionType eSourceType = pSourceAction->GetType(); + if ( !bShared && pSourceAction->IsDeletedIn() ) + { + //! does it need to be determined yet if really deleted in + //! _this_ document? + + // lies in a range, which was deleted in this document + // -> is omitted + //! ??? revert deletion action ??? + //! ??? save action somewhere else ??? +#if OSL_DEBUG_LEVEL > 0 + OUString aValue; + if ( eSourceType == SC_CAT_CONTENT ) + aValue = static_cast<const ScChangeActionContent*>(pSourceAction)->GetNewString( m_pDocument.get() ); + SAL_WARN( "sc", aValue << " omitted"); +#endif + } + else + { + //! Take over date/author/comment of the source action! + + pThisTrack->SetUser( pSourceAction->GetUser() ); + pThisTrack->SetFixDateTimeUTC( pSourceAction->GetDateTimeUTC() ); + sal_uLong nOldActionMax = pThisTrack->GetActionMax(); + + bool bExecute = true; + sal_uLong nReject = pSourceAction->GetRejectAction(); + if ( nReject ) + { + if ( bShared ) + { + if ( nReject >= nFirstNewNumber ) + { + nReject += nOffset; + } + ScChangeAction* pOldAction = pThisTrack->GetAction( nReject ); + if ( pOldAction && pOldAction->IsVirgin() ) + { + pThisTrack->Reject( pOldAction ); + bHasRejected = true; + bExecute = false; + } + } + else + { + // decline old action (of the common ones) + ScChangeAction* pOldAction = pThisTrack->GetAction( nReject ); + if (pOldAction && pOldAction->GetState() == SC_CAS_VIRGIN) + { + //! what happens at actions, which were accepted in this document??? + //! error notification or what??? + //! or execute reject change normally + + pThisTrack->Reject(pOldAction); + bHasRejected = true; // for Paint + } + bExecute = false; + } + } + + if ( bExecute ) + { + // execute normally + ScRange aSourceRange = pSourceAction->GetBigRange().MakeRange( GetDocument() ); + rMarkData.SelectOneTable( aSourceRange.aStart.Tab() ); + switch ( eSourceType ) + { + case SC_CAT_CONTENT: + { + //! Test if it was at the very bottom in the document, then automatic + //! row insert ??? + + OSL_ENSURE( aSourceRange.aStart == aSourceRange.aEnd, "huch?" ); + ScAddress aPos = aSourceRange.aStart; + OUString aValue = static_cast<const ScChangeActionContent*>(pSourceAction)->GetNewString( m_pDocument.get() ); + ScMatrixMode eMatrix = ScMatrixMode::NONE; + const ScCellValue& rCell = static_cast<const ScChangeActionContent*>(pSourceAction)->GetNewCell(); + if (rCell.meType == CELLTYPE_FORMULA) + eMatrix = rCell.mpFormula->GetMatrixFlag(); + switch ( eMatrix ) + { + case ScMatrixMode::NONE : + pViewSh->EnterData( aPos.Col(), aPos.Row(), aPos.Tab(), aValue ); + break; + case ScMatrixMode::Formula : + { + SCCOL nCols; + SCROW nRows; + rCell.mpFormula->GetMatColsRows(nCols, nRows); + aSourceRange.aEnd.SetCol( aPos.Col() + nCols - 1 ); + aSourceRange.aEnd.SetRow( aPos.Row() + nRows - 1 ); + aValue = aValue.copy(1, aValue.getLength()-2); // remove the 1st and last characters. + GetDocFunc().EnterMatrix( aSourceRange, + nullptr, nullptr, aValue, false, false, + OUString(), formula::FormulaGrammar::GRAM_DEFAULT ); + } + break; + case ScMatrixMode::Reference : // do nothing + break; + } + } + break; + case SC_CAT_INSERT_TABS : + { + OUString aName; + m_pDocument->CreateValidTabName( aName ); + (void)GetDocFunc().InsertTable( aSourceRange.aStart.Tab(), aName, true, false ); + } + break; + case SC_CAT_INSERT_ROWS: + (void)GetDocFunc().InsertCells( aSourceRange, nullptr, INS_INSROWS_BEFORE, true, false ); + break; + case SC_CAT_INSERT_COLS: + (void)GetDocFunc().InsertCells( aSourceRange, nullptr, INS_INSCOLS_BEFORE, true, false ); + break; + case SC_CAT_DELETE_TABS : + (void)GetDocFunc().DeleteTable( aSourceRange.aStart.Tab(), true ); + break; + case SC_CAT_DELETE_ROWS: + { + const ScChangeActionDel* pDel = static_cast<const ScChangeActionDel*>(pSourceAction); + if ( pDel->IsTopDelete() ) + { + aSourceRange = pDel->GetOverAllRange().MakeRange( GetDocument() ); + (void)GetDocFunc().DeleteCells( aSourceRange, nullptr, DelCellCmd::Rows, false ); + + // #i101099# [Collaboration] Changes are not correctly shown + if ( bShared ) + { + ScChangeAction* pAct = pThisTrack->GetLast(); + if ( pAct && pAct->GetType() == eSourceType && pAct->IsDeletedIn() && !pSourceAction->IsDeletedIn() ) + { + pAct->RemoveAllDeletedIn(); + } + } + } + } + break; + case SC_CAT_DELETE_COLS: + { + const ScChangeActionDel* pDel = static_cast<const ScChangeActionDel*>(pSourceAction); + if ( pDel->IsTopDelete() && !pDel->IsTabDeleteCol() ) + { // deleted table contains deleted cols, which are not + aSourceRange = pDel->GetOverAllRange().MakeRange( GetDocument() ); + (void)GetDocFunc().DeleteCells( aSourceRange, nullptr, DelCellCmd::Cols, false ); + } + } + break; + case SC_CAT_MOVE : + { + const ScChangeActionMove* pMove = static_cast<const ScChangeActionMove*>(pSourceAction); + ScRange aFromRange( pMove->GetFromRange().MakeRange( GetDocument() ) ); + (void)GetDocFunc().MoveBlock( aFromRange, + aSourceRange.aStart, true, true, false, false ); + } + break; + default: + { + // added to avoid warnings + } + } + } + const OUString& rComment = pSourceAction->GetComment(); + if ( !rComment.isEmpty() ) + { + ScChangeAction* pAct = pThisTrack->GetLast(); + if ( pAct && pAct->GetActionNumber() > nOldActionMax ) + pAct->SetComment( rComment ); + else + OSL_FAIL( "MergeDocument: what to do with the comment?!?" ); + } + + // adjust references + pSourceTrack->MergeOwn( const_cast<ScChangeAction*>(pSourceAction), nFirstNewNumber, bShared ); + + // merge action state + if ( bShared && !pSourceAction->IsRejected() ) + { + ScChangeAction* pAct = pThisTrack->GetLast(); + if ( pAct && pAct->GetActionNumber() > nOldActionMax ) + { + ScChangeTrack::MergeActionState( pAct, pSourceAction ); + } + } + + // fill merge map + if ( bShared && pMergeMap ) + { + ScChangeAction* pAct = pThisTrack->GetLast(); + if ( pAct && pAct->GetActionNumber() > nOldActionMax ) + { + sal_uLong nActionMax = pAct->GetActionNumber(); + sal_uLong nActionCount = nActionMax - nOldActionMax; + sal_uLong nAction = nActionMax - nActionCount + 1; + sal_uLong nSourceAction = pSourceAction->GetActionNumber() - nActionCount + 1; + while ( nAction <= nActionMax ) + { + if ( bInverseMap ) + { + (*pMergeMap)[ nAction++ ] = nSourceAction++; + } + else + { + (*pMergeMap)[ nSourceAction++ ] = nAction++; + } + } + } + } + } + aProgress.SetStateCountDown( --nNewActionCount ); + } + pSourceAction = pSourceAction->GetNext(); + } + + rMarkData = std::move(aOldMarkData); + pThisTrack->SetUser(aOldUser); + pThisTrack->SetUseFixDateTime( false ); + + pSourceTrack->Clear(); //! this one is bungled now + + if (bHasRejected) + PostPaintGridAll(); // Reject() doesn't paint itself + + UnlockPaint(); +} + +bool ScDocShell::MergeSharedDocument( ScDocShell* pSharedDocShell ) +{ + if ( !pSharedDocShell ) + { + return false; + } + + ScChangeTrack* pThisTrack = m_pDocument->GetChangeTrack(); + if ( !pThisTrack ) + { + return false; + } + + ScDocument& rSharedDoc = pSharedDocShell->GetDocument(); + ScChangeTrack* pSharedTrack = rSharedDoc.GetChangeTrack(); + if ( !pSharedTrack ) + { + return false; + } + + // reset show changes + ScChangeViewSettings aChangeViewSet; + aChangeViewSet.SetShowChanges( false ); + m_pDocument->SetChangeViewSettings( aChangeViewSet ); + + // find first merge action in this document + bool bIgnore100Sec = !pThisTrack->IsTimeNanoSeconds() || !pSharedTrack->IsTimeNanoSeconds(); + ScChangeAction* pThisAction = pThisTrack->GetFirst(); + ScChangeAction* pSharedAction = pSharedTrack->GetFirst(); + while ( lcl_Equal( pThisAction, pSharedAction, bIgnore100Sec ) ) + { + pThisAction = pThisAction->GetNext(); + pSharedAction = pSharedAction->GetNext(); + } + + if ( pSharedAction ) + { + if ( pThisAction ) + { + // merge own changes into shared document + sal_uLong nActStartShared = pSharedAction->GetActionNumber(); + sal_uLong nActEndShared = pSharedTrack->GetActionMax(); + std::optional<ScDocument> pTmpDoc(std::in_place); + for ( sal_Int32 nIndex = 0; nIndex < m_pDocument->GetTableCount(); ++nIndex ) + { + OUString sTabName; + pTmpDoc->CreateValidTabName( sTabName ); + pTmpDoc->InsertTab( SC_TAB_APPEND, sTabName ); + } + m_pDocument->GetChangeTrack()->Clone( &*pTmpDoc ); + ScChangeActionMergeMap aOwnInverseMergeMap; + pSharedDocShell->MergeDocument( *pTmpDoc, true, true, 0, &aOwnInverseMergeMap, true ); + pTmpDoc.reset(); + sal_uLong nActStartOwn = nActEndShared + 1; + sal_uLong nActEndOwn = pSharedTrack->GetActionMax(); + + // find conflicts + ScConflictsList aConflictsList; + ScConflictsFinder aFinder( pSharedTrack, nActStartShared, nActEndShared, nActStartOwn, nActEndOwn, aConflictsList ); + if ( aFinder.Find() ) + { + ScConflictsListHelper::TransformConflictsList( aConflictsList, nullptr, &aOwnInverseMergeMap ); + bool bLoop = true; + while ( bLoop ) + { + bLoop = false; + weld::Window* pWin = GetActiveDialogParent(); + ScConflictsDlg aDlg(pWin, GetViewData(), &rSharedDoc, aConflictsList); + if (aDlg.run() == RET_CANCEL) + { + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(pWin, + VclMessageType::Question, VclButtonsType::YesNo, + ScResId(STR_DOC_WILLNOTBESAVED))); + xQueryBox->set_default_response(RET_YES); + if (xQueryBox->run() == RET_YES) + { + return false; + } + else + { + bLoop = true; + } + } + } + } + + // undo own changes in shared document + pSharedTrack->Undo( nActStartOwn, nActEndOwn ); + + // clone change track for merging into own document + pTmpDoc.emplace(); + for ( sal_Int32 nIndex = 0; nIndex < m_pDocument->GetTableCount(); ++nIndex ) + { + OUString sTabName; + pTmpDoc->CreateValidTabName( sTabName ); + pTmpDoc->InsertTab( SC_TAB_APPEND, sTabName ); + } + pThisTrack->Clone( &*pTmpDoc ); + + // undo own changes since last save in own document + sal_uLong nStartShared = pThisAction->GetActionNumber(); + ScChangeAction* pAction = pThisTrack->GetLast(); + while ( pAction && pAction->GetActionNumber() >= nStartShared ) + { + pThisTrack->Reject( pAction, true ); + pAction = pAction->GetPrev(); + } + + // #i94841# [Collaboration] When deleting rows is rejected, the content is sometimes wrong + pThisTrack->Undo( nStartShared, pThisTrack->GetActionMax(), true ); + + // merge shared changes into own document + ScChangeActionMergeMap aSharedMergeMap; + MergeDocument( rSharedDoc, true, true, 0, &aSharedMergeMap ); + sal_uLong nEndShared = pThisTrack->GetActionMax(); + + // resolve conflicts for shared non-content actions + if ( !aConflictsList.empty() ) + { + ScConflictsListHelper::TransformConflictsList( aConflictsList, &aSharedMergeMap, nullptr ); + ScConflictsResolver aResolver( pThisTrack, aConflictsList ); + pAction = pThisTrack->GetAction( nEndShared ); + while ( pAction && pAction->GetActionNumber() >= nStartShared ) + { + aResolver.HandleAction( pAction, true /*bIsSharedAction*/, + false /*bHandleContentAction*/, true /*bHandleNonContentAction*/ ); + pAction = pAction->GetPrev(); + } + } + nEndShared = pThisTrack->GetActionMax(); + + // only show changes from shared document + aChangeViewSet.SetShowChanges( true ); + aChangeViewSet.SetShowAccepted( true ); + aChangeViewSet.SetHasActionRange(); + aChangeViewSet.SetTheActionRange( nStartShared, nEndShared ); + m_pDocument->SetChangeViewSettings( aChangeViewSet ); + + // merge own changes back into own document + sal_uLong nStartOwn = nEndShared + 1; + ScChangeActionMergeMap aOwnMergeMap; + MergeDocument( *pTmpDoc, true, true, nEndShared - nStartShared + 1, &aOwnMergeMap ); + pTmpDoc.reset(); + sal_uLong nEndOwn = pThisTrack->GetActionMax(); + + // resolve conflicts for shared content actions and own actions + if ( !aConflictsList.empty() ) + { + ScConflictsListHelper::TransformConflictsList( aConflictsList, nullptr, &aOwnMergeMap ); + ScConflictsResolver aResolver( pThisTrack, aConflictsList ); + pAction = pThisTrack->GetAction( nEndShared ); + while ( pAction && pAction->GetActionNumber() >= nStartShared ) + { + aResolver.HandleAction( pAction, true /*bIsSharedAction*/, + true /*bHandleContentAction*/, false /*bHandleNonContentAction*/ ); + pAction = pAction->GetPrev(); + } + + pAction = pThisTrack->GetAction( nEndOwn ); + while ( pAction && pAction->GetActionNumber() >= nStartOwn ) + { + aResolver.HandleAction( pAction, false /*bIsSharedAction*/, + true /*bHandleContentAction*/, true /*bHandleNonContentAction*/ ); + pAction = pAction->GetPrev(); + } + } + } + else + { + // merge shared changes into own document + sal_uLong nStartShared = pThisTrack->GetActionMax() + 1; + MergeDocument( rSharedDoc, true, true ); + sal_uLong nEndShared = pThisTrack->GetActionMax(); + + // only show changes from shared document + aChangeViewSet.SetShowChanges( true ); + aChangeViewSet.SetShowAccepted( true ); + aChangeViewSet.SetHasActionRange(); + aChangeViewSet.SetTheActionRange( nStartShared, nEndShared ); + m_pDocument->SetChangeViewSettings( aChangeViewSet ); + } + + // update view + PostPaintExtras(); + PostPaintGridAll(); + + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(GetActiveDialogParent(), + VclMessageType::Info, VclButtonsType::Ok, + ScResId(STR_DOC_UPDATED))); + xInfoBox->run(); + } + + return ( pThisAction != nullptr ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/docsh4.cxx b/sc/source/ui/docshell/docsh4.cxx new file mode 100644 index 000000000..f64ecaeaa --- /dev/null +++ b/sc/source/ui/docshell/docsh4.cxx @@ -0,0 +1,2788 @@ +/* -*- 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 <config_features.h> + +#include <boost/property_tree/json_parser.hpp> + +#include <com/sun/star/frame/Desktop.hpp> + +using namespace ::com::sun::star; + +#include <scitems.hxx> +#include <editeng/flstitem.hxx> +#include <sfx2/fcontnr.hxx> +#include <sfx2/infobar.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/docfilt.hxx> +#include <svtools/ehdl.hxx> +#include <svtools/langtab.hxx> +#include <basic/sbxcore.hxx> +#include <basic/sberrors.hxx> +#include <svtools/sfxecode.hxx> +#include <svx/ofaitem.hxx> +#include <svl/stritem.hxx> +#include <svl/whiter.hxx> +#include <vcl/stdtext.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <svx/dataaccessdescriptor.hxx> +#include <svx/drawitem.hxx> +#include <svx/fmshell.hxx> +#include <sfx2/passwd.hxx> +#include <sfx2/filedlghelper.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/sfxdlg.hxx> +#include <svl/PasswordHelper.hxx> +#include <svl/documentlockfile.hxx> +#include <svl/sharecontrolfile.hxx> +#include <unotools/securityoptions.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <sal/log.hxx> +#include <unotools/charclass.hxx> +#include <tools/diagnose_ex.h> +#include <o3tl/string_view.hxx> + +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertyvalue.hxx> +#include <docuno.hxx> + +#include <docsh.hxx> +#include "docshimp.hxx" +#include <docfunc.hxx> +#include <scres.hrc> +#include <strings.hrc> +#include <stlsheet.hxx> +#include <stlpool.hxx> +#include <appoptio.hxx> +#include <globstr.hrc> +#include <global.hxx> +#include <dbdocfun.hxx> +#include <printfun.hxx> +#include <viewdata.hxx> +#include <tabvwsh.hxx> +#include <impex.hxx> +#include <undodat.hxx> +#include <undocell.hxx> +#include <inputhdl.hxx> +#include <dbdata.hxx> +#include <servobj.hxx> +#include <rangenam.hxx> +#include <scmod.hxx> +#include <chgviset.hxx> +#include <reffact.hxx> +#include <chartlis.hxx> +#include <chartpos.hxx> +#include <tablink.hxx> +#include <drwlayer.hxx> +#include <docoptio.hxx> +#include <undostyl.hxx> +#include <rangeseq.hxx> +#include <chgtrack.hxx> +#include <com/sun/star/document/UpdateDocMode.hpp> +#include <scresid.hxx> +#include <scabstdlg.hxx> +#include <sharedocdlg.hxx> +#include <conditio.hxx> +#include <sheetevents.hxx> +#include <formulacell.hxx> +#include <documentlinkmgr.hxx> +#include <memory> +#include <sfx2/notebookbar/SfxNotebookBar.hxx> +#include <helpids.h> +#include <editeng/eeitem.hxx> +#include <editeng/langitem.hxx> + +#include <svx/xdef.hxx> + +void ScDocShell::SetInitialLinkUpdate( const SfxMedium* pMed ) +{ + if (pMed) + { + const SfxUInt16Item* pUpdateDocItem = SfxItemSet::GetItem<SfxUInt16Item>( pMed->GetItemSet(), + SID_UPDATEDOCMODE, false); + m_nCanUpdate = pUpdateDocItem ? pUpdateDocItem->GetValue() : css::document::UpdateDocMode::NO_UPDATE; + } + + // GetLinkUpdateModeState() evaluates m_nCanUpdate so that must have + // been set first. Do not override an already forbidden LinkUpdate (the + // default is allow). + comphelper::EmbeddedObjectContainer& rEmbeddedObjectContainer = getEmbeddedObjectContainer(); + if (rEmbeddedObjectContainer.getUserAllowsLinkUpdate()) + { + // For anything else than LM_ALWAYS we need user confirmation. + rEmbeddedObjectContainer.setUserAllowsLinkUpdate( GetLinkUpdateModeState() == LM_ALWAYS); + } +} + +ScLkUpdMode ScDocShell::GetLinkUpdateModeState() const +{ + ScLkUpdMode nSet; + if (m_nCanUpdate == css::document::UpdateDocMode::NO_UPDATE) + nSet = LM_NEVER; + else if (m_nCanUpdate == css::document::UpdateDocMode::FULL_UPDATE) + nSet = LM_ALWAYS; + else + { + nSet = GetDocument().GetLinkMode(); + if (nSet == LM_UNKNOWN) + { + ScAppOptions aAppOptions = SC_MOD()->GetAppOptions(); + nSet = aAppOptions.GetLinkMode(); + } + } + + if (nSet == LM_ALWAYS + && !(SvtSecurityOptions::isTrustedLocationUriForUpdatingLinks( + GetMedium() == nullptr ? OUString() : GetMedium()->GetName()) + || (IsDocShared() + && SvtSecurityOptions::isTrustedLocationUriForUpdatingLinks( + GetSharedFileURL())))) + { + nSet = LM_ON_DEMAND; + } + if (m_nCanUpdate == css::document::UpdateDocMode::QUIET_UPDATE + && nSet == LM_ON_DEMAND) + { + nSet = LM_NEVER; + } + + return nSet; +} + +void ScDocShell::AllowLinkUpdate() +{ + m_pDocument->SetLinkFormulaNeedingCheck(false); + getEmbeddedObjectContainer().setUserAllowsLinkUpdate(true); +} + +void ScDocShell::ReloadAllLinks() +{ + AllowLinkUpdate(); + + ReloadTabLinks(); + weld::Window *pDialogParent = GetActiveDialogParent(); + m_pDocument->UpdateExternalRefLinks(pDialogParent); + + bool bAnyDde = m_pDocument->GetDocLinkManager().updateDdeOrOleOrWebServiceLinks(pDialogParent); + + if (bAnyDde) + { + // calculate formulas and paint like in the TrackTimeHdl + m_pDocument->TrackFormulas(); + Broadcast(SfxHint(SfxHintId::ScDataChanged)); + + // Should FID_DATACHANGED become asynchronous some time + // (e.g., with Invalidate at Window), an update needs to be forced here. + } + + m_pDocument->UpdateAreaLinks(); +} + +IMPL_LINK_NOARG( ScDocShell, ReloadAllLinksHdl, weld::Button&, void ) +{ + ReloadAllLinks(); + + ScTabViewShell* pViewSh = GetBestViewShell(); + SfxViewFrame* pViewFrame = pViewSh ? pViewSh->GetFrame() : nullptr; + if (pViewFrame) + pViewFrame->RemoveInfoBar(u"enablecontent"); + SAL_WARN_IF(!pViewFrame, "sc", "expected there to be a ViewFrame"); +} + +namespace +{ + class LinkHelp + { + public: + DECL_STATIC_LINK(LinkHelp, DispatchHelpLinksHdl, weld::Button&, void); + }; +} + +IMPL_STATIC_LINK(LinkHelp, DispatchHelpLinksHdl, weld::Button&, rBtn, void) +{ + if (Help* pHelp = Application::GetHelp()) + pHelp->Start(HID_UPDATE_LINK_WARNING, &rBtn); +} + +void ScDocShell::Execute( SfxRequest& rReq ) +{ + const SfxItemSet* pReqArgs = rReq.GetArgs(); + SfxBindings* pBindings = GetViewBindings(); + bool bUndo (m_pDocument->IsUndoEnabled()); + + sal_uInt16 nSlot = rReq.GetSlot(); + switch ( nSlot ) + { + case SID_SC_SETTEXT: + { + const SfxPoolItem* pColItem; + const SfxPoolItem* pRowItem; + const SfxPoolItem* pTabItem; + const SfxPoolItem* pTextItem; + if( pReqArgs && pReqArgs->HasItem( FN_PARAM_1, &pColItem ) && + pReqArgs->HasItem( FN_PARAM_2, &pRowItem ) && + pReqArgs->HasItem( FN_PARAM_3, &pTabItem ) && + pReqArgs->HasItem( SID_SC_SETTEXT, &pTextItem ) ) + { + // parameters are 1-based !!! + SCCOL nCol = static_cast<const SfxInt16Item*>(pColItem)->GetValue() - 1; + SCROW nRow = static_cast<const SfxInt32Item*>(pRowItem)->GetValue() - 1; + SCTAB nTab = static_cast<const SfxInt16Item*>(pTabItem)->GetValue() - 1; + + SCTAB nTabCount = m_pDocument->GetTableCount(); + if ( m_pDocument->ValidCol(nCol) && m_pDocument->ValidRow(nRow) && ValidTab(nTab,nTabCount) ) + { + if ( m_pDocument->IsBlockEditable( nTab, nCol,nRow, nCol, nRow ) ) + { + OUString aVal = static_cast<const SfxStringItem*>(pTextItem)->GetValue(); + m_pDocument->SetString( nCol, nRow, nTab, aVal ); + + PostPaintCell( nCol, nRow, nTab ); + SetDocumentModified(); + + rReq.Done(); + break; + } + else // protected cell + { +#if HAVE_FEATURE_SCRIPTING + SbxBase::SetError( ERRCODE_BASIC_BAD_PARAMETER ); //! which error ? +#endif + break; + } + } + } +#if HAVE_FEATURE_SCRIPTING + SbxBase::SetError( ERRCODE_BASIC_NO_OBJECT ); +#endif + } + break; + + case SID_SBA_IMPORT: + { + if (pReqArgs) + { + const SfxPoolItem* pItem; + svx::ODataAccessDescriptor aDesc; + if ( pReqArgs->GetItemState( nSlot, true, &pItem ) == SfxItemState::SET ) + { + uno::Any aAny = static_cast<const SfxUnoAnyItem*>(pItem)->GetValue(); + uno::Sequence<beans::PropertyValue> aProperties; + if ( aAny >>= aProperties ) + aDesc.initializeFrom( aProperties ); + } + + OUString sTarget; + if ( pReqArgs->GetItemState( FN_PARAM_1, true, &pItem ) == SfxItemState::SET ) + sTarget = static_cast<const SfxStringItem*>(pItem)->GetValue(); + + bool bIsNewArea = true; // Default sal_True (no inquiry) + if ( pReqArgs->GetItemState( FN_PARAM_2, true, &pItem ) == SfxItemState::SET ) + bIsNewArea = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + + // if necessary, create new database area + bool bMakeArea = false; + if (bIsNewArea) + { + ScDBCollection* pDBColl = m_pDocument->GetDBCollection(); + if ( !pDBColl || !pDBColl->getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(sTarget)) ) + { + ScAddress aPos; + if ( aPos.Parse( sTarget, *m_pDocument, m_pDocument->GetAddressConvention() ) & ScRefFlags::VALID ) + { + bMakeArea = true; + if (bUndo) + { + OUString aStrImport = ScResId( STR_UNDO_IMPORTDATA ); + ViewShellId nViewShellId(-1); + if (ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell()) + nViewShellId = pViewSh->GetViewShellId(); + GetUndoManager()->EnterListAction( aStrImport, aStrImport, 0, nViewShellId ); + } + + ScDBData* pDBData = GetDBData( ScRange(aPos), SC_DB_IMPORT, ScGetDBSelection::Keep ); + OSL_ENSURE(pDBData, "Cannot create DB data"); + sTarget = pDBData->GetName(); + } + } + } + + // inquire, before old DB range gets overwritten + bool bDo = true; + if (!bIsNewArea) + { + OUString aTemplate = ScResId( STR_IMPORT_REPLACE ); + OUString aMessage = o3tl::getToken(aTemplate, 0, '#' ) + + sTarget + + o3tl::getToken(aTemplate, 1, '#' ); + + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(nullptr, + VclMessageType::Question, VclButtonsType::YesNo, + aMessage)); + xQueryBox->set_default_response(RET_YES); + bDo = xQueryBox->run() == RET_YES; + } + + if (bDo) + { + ScDBDocFunc(*this).UpdateImport( sTarget, aDesc ); + rReq.Done(); + + // UpdateImport also updates the internal operations + } + else + rReq.Ignore(); + + if ( bMakeArea && bUndo) + GetUndoManager()->LeaveListAction(); + } + else + { + OSL_FAIL( "arguments expected" ); + } + } + break; + + case SID_CHART_SOURCE: + case SID_CHART_ADDSOURCE: + if (pReqArgs) + { + ScDocument& rDoc = GetDocument(); + const SfxPoolItem* pItem; + OUString aChartName, aRangeName; + + ScRange aSingleRange; + ScRangeListRef aRangeListRef; + bool bMultiRange = false; + + bool bColHeaders = true; + bool bRowHeaders = true; + bool bColInit = false; + bool bRowInit = false; + bool bAddRange = (nSlot == SID_CHART_ADDSOURCE); + + if( const SfxStringItem* pChartItem = pReqArgs->GetItemIfSet( SID_CHART_NAME ) ) + aChartName = pChartItem->GetValue(); + + if( const SfxStringItem* pChartItem = pReqArgs->GetItemIfSet( SID_CHART_SOURCE ) ) + aRangeName = pChartItem->GetValue(); + + if( pReqArgs->HasItem( FN_PARAM_1, &pItem ) ) + { + bColHeaders = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + bColInit = true; + } + if( pReqArgs->HasItem( FN_PARAM_2, &pItem ) ) + { + bRowHeaders = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + bRowInit = true; + } + + ScAddress::Details aDetails(rDoc.GetAddressConvention(), 0, 0); + bool bValid = (aSingleRange.ParseAny(aRangeName, rDoc, aDetails) & ScRefFlags::VALID) != ScRefFlags::ZERO; + if (!bValid) + { + aRangeListRef = new ScRangeList; + aRangeListRef->Parse( aRangeName, rDoc, rDoc.GetAddressConvention()); + if ( !aRangeListRef->empty() ) + { + bMultiRange = true; + aSingleRange = aRangeListRef->front(); // for header + bValid = true; + } + else + aRangeListRef.clear(); + } + + ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell(); + if (pViewSh && bValid && !aChartName.isEmpty() ) + { + weld::Window* pParent = pViewSh->GetFrameWeld(); + + SCCOL nCol1 = aSingleRange.aStart.Col(); + SCROW nRow1 = aSingleRange.aStart.Row(); + SCCOL nCol2 = aSingleRange.aEnd.Col(); + SCROW nRow2 = aSingleRange.aEnd.Row(); + SCTAB nTab = aSingleRange.aStart.Tab(); + + //! limit always or not at all ??? + if (!bMultiRange) + m_pDocument->LimitChartArea( nTab, nCol1,nRow1, nCol2,nRow2 ); + + // Dialog for column/row headers + bool bOk = true; + if ( !bAddRange && ( !bColInit || !bRowInit ) ) + { + ScChartPositioner aChartPositioner( *m_pDocument, nTab, nCol1,nRow1, nCol2,nRow2 ); + if (!bColInit) + bColHeaders = aChartPositioner.HasColHeaders(); + if (!bRowInit) + bRowHeaders = aChartPositioner.HasRowHeaders(); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr<AbstractScColRowLabelDlg> pDlg(pFact->CreateScColRowLabelDlg(pParent, bRowHeaders, bColHeaders)); + if ( pDlg->Execute() == RET_OK ) + { + bColHeaders = pDlg->IsRow(); + bRowHeaders = pDlg->IsCol(); + + rReq.AppendItem(SfxBoolItem(FN_PARAM_1, bColHeaders)); + rReq.AppendItem(SfxBoolItem(FN_PARAM_2, bRowHeaders)); + } + else + bOk = false; + } + + if (bOk) // execute + { + if (bMultiRange) + { + if (bUndo) + { + GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoChartData>( this, aChartName, aRangeListRef, + bColHeaders, bRowHeaders, bAddRange ) ); + } + m_pDocument->UpdateChartArea( aChartName, aRangeListRef, + bColHeaders, bRowHeaders, bAddRange ); + } + else + { + ScRange aNewRange( nCol1,nRow1,nTab, nCol2,nRow2,nTab ); + if (bUndo) + { + GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoChartData>( this, aChartName, aNewRange, + bColHeaders, bRowHeaders, bAddRange ) ); + } + m_pDocument->UpdateChartArea( aChartName, aNewRange, + bColHeaders, bRowHeaders, bAddRange ); + } + } + } + else + { + OSL_FAIL("UpdateChartArea: no ViewShell or wrong data"); + } + rReq.Done(); + } + else + { + OSL_FAIL("SID_CHART_SOURCE without arguments"); + } + break; + + case FID_AUTO_CALC: + { + bool bNewVal; + const SfxPoolItem* pItem; + if ( pReqArgs && SfxItemState::SET == pReqArgs->GetItemState( nSlot, true, &pItem ) ) + bNewVal = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + else + bNewVal = !m_pDocument->GetAutoCalc(); // Toggle for menu + m_pDocument->SetAutoCalc( bNewVal ); + SetDocumentModified(); + if (pBindings) + { + pBindings->Invalidate( FID_AUTO_CALC ); + } + rReq.AppendItem( SfxBoolItem( FID_AUTO_CALC, bNewVal ) ); + rReq.Done(); + } + break; + case FID_RECALC: + DoRecalc( rReq.IsAPI() ); + rReq.Done(); + break; + case FID_HARD_RECALC: + DoHardRecalc(); + rReq.Done(); + break; + case SID_UPDATETABLINKS: + { + ScLkUpdMode nSet = GetLinkUpdateModeState(); + + if (nSet == LM_ALWAYS) + { + ReloadAllLinks(); + rReq.Done(); + } + else if (nSet == LM_NEVER) + { + getEmbeddedObjectContainer().setUserAllowsLinkUpdate(false); + rReq.Ignore(); + } + else if (nSet == LM_ON_DEMAND) + { + ScTabViewShell* pViewSh = GetBestViewShell(); + SfxViewFrame* pViewFrame = pViewSh ? pViewSh->GetFrame() : nullptr; + if (pViewFrame) + { + pViewFrame->RemoveInfoBar(u"enablecontent"); + auto pInfoBar = pViewFrame->AppendInfoBar("enablecontent", "", ScResId(STR_RELOAD_TABLES), InfobarType::WARNING); + if (pInfoBar) + { + weld::Button& rHelpBtn = pInfoBar->addButton(); + rHelpBtn.set_label(GetStandardText(StandardButtonType::Help).replaceFirst("~", "")); + rHelpBtn.connect_clicked(LINK(nullptr, LinkHelp, DispatchHelpLinksHdl)); + weld::Button& rBtn = pInfoBar->addButton(); + rBtn.set_label(ScResId(STR_ENABLE_CONTENT)); + rBtn.set_tooltip_text(ScResId(STR_ENABLE_CONTENT_TOOLTIP)); + rBtn.connect_clicked(LINK(this, ScDocShell, ReloadAllLinksHdl)); + } + } + rReq.Done(); + } + } + break; + + case SID_REIMPORT_AFTER_LOAD: + { + // Is called after loading if there are DB areas with omitted data + + bool bDone = false; + ScDBCollection* pDBColl = m_pDocument->GetDBCollection(); + + if ((m_nCanUpdate != css::document::UpdateDocMode::NO_UPDATE) && + (m_nCanUpdate != css::document::UpdateDocMode::QUIET_UPDATE)) + { + ScRange aRange; + ScTabViewShell* pViewSh = GetBestViewShell(); + OSL_ENSURE(pViewSh,"SID_REIMPORT_AFTER_LOAD: no View"); + if (pViewSh && pDBColl) + { + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(GetActiveDialogParent(), + VclMessageType::Question, VclButtonsType::YesNo, + ScResId(STR_REIMPORT_AFTER_LOAD))); + xQueryBox->set_default_response(RET_YES); + if (xQueryBox->run() == RET_YES) + { + ScDBCollection::NamedDBs& rDBs = pDBColl->getNamedDBs(); + for (const auto& rxDB : rDBs) + { + ScDBData& rDBData = *rxDB; + if ( rDBData.IsStripData() && + rDBData.HasImportParam() && !rDBData.HasImportSelection() ) + { + rDBData.GetArea(aRange); + pViewSh->MarkRange(aRange); + + // Import and internal operations like SID_REFRESH_DBAREA + // (inquiry for import not needed here) + + ScImportParam aImportParam; + rDBData.GetImportParam( aImportParam ); + bool bContinue = pViewSh->ImportData( aImportParam ); + rDBData.SetImportParam( aImportParam ); + + // mark (size may have changed) + rDBData.GetArea(aRange); + pViewSh->MarkRange(aRange); + + if ( bContinue ) // error at import -> abort + { + // internal operations, if some where saved + + if ( rDBData.HasQueryParam() || rDBData.HasSortParam() || + rDBData.HasSubTotalParam() ) + pViewSh->RepeatDB(); + + // pivot tables, which have the range as source data + + RefreshPivotTables(aRange); + } + } + } + bDone = true; + } + } + } + + if ( !bDone && pDBColl ) + { + // if not, but then update the dependent formulas + //! also for individual ranges, which cannot be updated + + m_pDocument->CalcAll(); //! only for the dependent + PostDataChanged(); + } + + if (bDone) + rReq.Done(); + else + rReq.Ignore(); + } + break; + + case SID_AUTO_STYLE: + OSL_FAIL("use ScAutoStyleHint instead of SID_AUTO_STYLE"); + break; + + case SID_GET_COLORLIST: + { + const SvxColorListItem* pColItem = GetItem(SID_COLOR_TABLE); + const XColorListRef& pList = pColItem->GetColorList(); + rReq.SetReturnValue(OfaXColorListItem(SID_GET_COLORLIST, pList)); + } + break; + + case FID_CHG_RECORD: + { + ScDocument& rDoc = GetDocument(); + // get argument (recorded macro) + const SfxBoolItem* pItem = rReq.GetArg<SfxBoolItem>(FID_CHG_RECORD); + bool bDo = true; + + // desired state + ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack(); + bool bActivateTracking = (pChangeTrack == nullptr); // toggle + if ( pItem ) + bActivateTracking = pItem->GetValue(); // from argument + + if ( !bActivateTracking ) + { + if ( !pItem ) + { + // no dialog on playing the macro + std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(GetActiveDialogParent(), + VclMessageType::Warning, VclButtonsType::YesNo, + ScResId(STR_END_REDLINING))); + xWarn->set_default_response(RET_NO); + bDo = (xWarn->run() == RET_YES ); + } + + if ( bDo ) + { + if (pChangeTrack) + { + if ( pChangeTrack->IsProtected() ) + bDo = ExecuteChangeProtectionDialog(); + } + if ( bDo ) + { + rDoc.EndChangeTracking(); + PostPaintGridAll(); + } + } + } + else + { + rDoc.StartChangeTracking(); + ScChangeViewSettings aChangeViewSet; + aChangeViewSet.SetShowChanges(true); + rDoc.SetChangeViewSettings(aChangeViewSet); + } + + if ( bDo ) + { + UpdateAcceptChangesDialog(); + + // invalidate slots + if (pBindings) + pBindings->InvalidateAll(false); + if ( !pItem ) + rReq.AppendItem( SfxBoolItem( FID_CHG_RECORD, bActivateTracking ) ); + rReq.Done(); + } + else + rReq.Ignore(); + } + break; + + case SID_CHG_PROTECT : + { + if ( ExecuteChangeProtectionDialog() ) + { + rReq.Done(); + SetDocumentModified(); + } + else + rReq.Ignore(); + } + break; + + case SID_DOCUMENT_MERGE: + case SID_DOCUMENT_COMPARE: + { + bool bDo = true; + ScChangeTrack* pChangeTrack = m_pDocument->GetChangeTrack(); + if ( pChangeTrack && !m_pImpl->bIgnoreLostRedliningWarning ) + { + if ( nSlot == SID_DOCUMENT_COMPARE ) + { //! old changes trace will be lost + std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(GetActiveDialogParent(), + VclMessageType::Warning, VclButtonsType::YesNo, + ScResId(STR_END_REDLINING))); + xWarn->set_default_response(RET_NO); + if (xWarn->run() == RET_YES) + bDo = ExecuteChangeProtectionDialog( true ); + else + bDo = false; + } + else // merge might reject some actions + bDo = ExecuteChangeProtectionDialog( true ); + } + if ( !bDo ) + { + rReq.Ignore(); + break; + } + SfxApplication* pApp = SfxGetpApp(); + const SfxPoolItem* pItem; + const SfxStringItem* pFileNameItem(nullptr); + SfxMedium* pMed = nullptr; + if (pReqArgs) + pFileNameItem = pReqArgs->GetItemIfSet(SID_FILE_NAME); + if (pFileNameItem) + { + OUString aFileName = pFileNameItem->GetValue(); + + OUString aFilterName; + if (const SfxStringItem* pFilterItem = pReqArgs->GetItemIfSet(SID_FILTER_NAME)) + { + aFilterName = pFilterItem->GetValue(); + } + OUString aOptions; + if (const SfxStringItem* pOptionsItem = pReqArgs->GetItemIfSet(SID_FILE_FILTEROPTIONS)) + { + aOptions = pOptionsItem->GetValue(); + } + short nVersion = 0; + const SfxInt16Item* pInt16Item(nullptr); + if (pReqArgs->GetItemState(SID_VERSION, true, &pItem) == SfxItemState::SET) + pInt16Item = dynamic_cast<const SfxInt16Item*>(pItem); + if (pInt16Item) + { + nVersion = pInt16Item->GetValue(); + } + + // no filter specified -> detection + if (aFilterName.isEmpty()) + ScDocumentLoader::GetFilterName( aFileName, aFilterName, aOptions, true, false ); + + // filter name from dialog contains application prefix, + // GetFilter needs name without the prefix. + ScDocumentLoader::RemoveAppPrefix( aFilterName ); + + std::shared_ptr<const SfxFilter> pFilter = ScDocShell::Factory().GetFilterContainer()->GetFilter4FilterName( aFilterName ); + auto pSet = std::make_shared<SfxAllItemSet>( pApp->GetPool() ); + if (!aOptions.isEmpty()) + pSet->Put( SfxStringItem( SID_FILE_FILTEROPTIONS, aOptions ) ); + if ( nVersion != 0 ) + pSet->Put( SfxInt16Item( SID_VERSION, nVersion ) ); + pMed = new SfxMedium( aFileName, StreamMode::STD_READ, pFilter, std::move(pSet) ); + } + else + { + const sfx2::DocumentInserter::Mode mode { nSlot==SID_DOCUMENT_COMPARE + ? sfx2::DocumentInserter::Mode::Compare + : sfx2::DocumentInserter::Mode::Merge}; + // start file dialog asynchronous + m_pImpl->bIgnoreLostRedliningWarning = true; + m_pImpl->pRequest.reset(new SfxRequest( rReq )); + m_pImpl->pDocInserter.reset(); + + ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell(); + weld::Window* pParent = pViewSh ? pViewSh->GetFrameWeld() : nullptr; + m_pImpl->pDocInserter.reset( new ::sfx2::DocumentInserter(pParent, + ScDocShell::Factory().GetFactoryName(), mode ) ); + m_pImpl->pDocInserter->StartExecuteModal( LINK( this, ScDocShell, DialogClosedHdl ) ); + return ; + } + + // now execute in earnest... + SfxErrorContext aEc( ERRCTX_SFX_OPENDOC, pMed->GetName() ); + + // pOtherDocSh->DoClose() will be called explicitly later, but it is still more safe to use SfxObjectShellLock here + ScDocShell* pOtherDocSh = new ScDocShell; + SfxObjectShellLock aDocShTablesRef = pOtherDocSh; + pOtherDocSh->DoLoad( pMed ); + ErrCode nErr = pOtherDocSh->GetErrorCode(); + if (nErr) + ErrorHandler::HandleError( nErr ); // also warnings + + if ( !pOtherDocSh->GetError() ) // only errors + { + bool bHadTrack = ( m_pDocument->GetChangeTrack() != nullptr ); +#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT + sal_uLong nStart = 0; + if ( nSlot == SID_DOCUMENT_MERGE && pChangeTrack ) + { + nStart = pChangeTrack->GetActionMax() + 1; + } +#endif + if ( nSlot == SID_DOCUMENT_COMPARE ) + CompareDocument( pOtherDocSh->GetDocument() ); + else + MergeDocument( pOtherDocSh->GetDocument() ); + + // show "accept changes" dialog + //! get view for this document! + if ( !IsDocShared() ) + { + SfxViewFrame* pViewFrm = SfxViewFrame::Current(); + if ( pViewFrm ) + { + pViewFrm->ShowChildWindow( ScAcceptChgDlgWrapper::GetChildWindowId() ); //@51669 + } + if ( pBindings ) + { + pBindings->Invalidate( FID_CHG_ACCEPT ); + } + } + + rReq.SetReturnValue( SfxInt32Item( nSlot, 0 ) ); //! ??????? + rReq.Done(); + + if (!bHadTrack) // newly turned on -> show as well + { + ScChangeViewSettings* pOldSet = m_pDocument->GetChangeViewSettings(); + if ( !pOldSet || !pOldSet->ShowChanges() ) + { + ScChangeViewSettings aChangeViewSet; + aChangeViewSet.SetShowChanges(true); + m_pDocument->SetChangeViewSettings(aChangeViewSet); + } + } +#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT + else if ( nSlot == SID_DOCUMENT_MERGE && IsDocShared() && pChangeTrack ) + { + sal_uLong nEnd = pChangeTrack->GetActionMax(); + if ( nEnd >= nStart ) + { + // only show changes from merged document + ScChangeViewSettings aChangeViewSet; + aChangeViewSet.SetShowChanges( true ); + aChangeViewSet.SetShowAccepted( true ); + aChangeViewSet.SetHasActionRange(); + aChangeViewSet.SetTheActionRange( nStart, nEnd ); + m_pDocument->SetChangeViewSettings( aChangeViewSet ); + + // update view + PostPaintExtras(); + PostPaintGridAll(); + } + } +#endif + } + pOtherDocSh->DoClose(); // delete happens with the Ref + } + break; + + case SID_DELETE_SCENARIO: + if (pReqArgs) + { + const SfxPoolItem* pItem; + if ( pReqArgs->GetItemState( nSlot, true, &pItem ) == SfxItemState::SET ) + { + if (const SfxStringItem* pStringItem = dynamic_cast<const SfxStringItem*>(pItem)) + { + const OUString& aName = pStringItem->GetValue(); + SCTAB nTab; + if (m_pDocument->GetTable( aName, nTab )) + { + // move DeleteTable from viewfunc to docfunc! + + ScTabViewShell* pSh = GetBestViewShell(); + if ( pSh ) + { + //! omit SetTabNo in DeleteTable? + SCTAB nDispTab = pSh->GetViewData().GetTabNo(); + pSh->DeleteTable( nTab ); + pSh->SetTabNo(nDispTab); + rReq.Done(); + } + } + } + } + } + break; + + case SID_EDIT_SCENARIO: + { + const SfxPoolItem* pItem; + if ( pReqArgs->GetItemState( nSlot, true, &pItem ) == SfxItemState::SET ) + { + if (const SfxStringItem* pStringItem = dynamic_cast<const SfxStringItem*>(pItem)) + { + OUString aName = pStringItem->GetValue(); + SCTAB nTab; + if (m_pDocument->GetTable( aName, nTab )) + { + if (m_pDocument->IsScenario(nTab)) + { + OUString aComment; + Color aColor; + ScScenarioFlags nFlags; + m_pDocument->GetScenarioData( nTab, aComment, aColor, nFlags ); + + // Determine if the Sheet that the Scenario was created on + // is protected. But first we need to find that Sheet. + // Rewind back to the actual sheet. + SCTAB nActualTab = nTab; + do + { + nActualTab--; + } + while(m_pDocument->IsScenario(nActualTab)); + bool bSheetProtected = m_pDocument->IsTabProtected(nActualTab); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr<AbstractScNewScenarioDlg> pNewDlg(pFact->CreateScNewScenarioDlg(GetActiveDialogParent(), aName, true, bSheetProtected)); + pNewDlg->SetScenarioData( aName, aComment, aColor, nFlags ); + if ( pNewDlg->Execute() == RET_OK ) + { + pNewDlg->GetScenarioData( aName, aComment, aColor, nFlags ); + ModifyScenario( nTab, aName, aComment, aColor, nFlags ); + rReq.Done(); + } + } + } + } + } + } + break; + + case SID_ATTR_YEAR2000 : + { + const SfxPoolItem* pItem; + if ( pReqArgs->GetItemState( nSlot, true, &pItem ) == SfxItemState::SET ) + { + if (const SfxUInt16Item* pInt16Item = dynamic_cast<const SfxUInt16Item*>(pItem)) + { + sal_uInt16 nY2k = pInt16Item->GetValue(); + // set always to DocOptions, so that it is also saved for S050 + // (and all inquiries run up until now on it as well). + // SetDocOptions propagates that to the NumberFormatter + ScDocOptions aDocOpt( m_pDocument->GetDocOptions() ); + aDocOpt.SetYear2000( nY2k ); + m_pDocument->SetDocOptions( aDocOpt ); + // the FormShell shall notice it as well + ScTabViewShell* pSh = GetBestViewShell(); + if ( pSh ) + { + FmFormShell* pFSh = pSh->GetFormShell(); + if ( pFSh ) + pFSh->SetY2KState( nY2k ); + } + } + } + } + break; + +#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT + case SID_SHARE_DOC: + { + ScViewData* pViewData = GetViewData(); + if ( !pViewData ) + { + rReq.Ignore(); + break; + } + + weld::Window* pWin = GetActiveDialogParent(); + ScShareDocumentDlg aDlg(pWin, pViewData); + if (aDlg.run() == RET_OK) + { + bool bSetShared = aDlg.IsShareDocumentChecked(); + if ( bSetShared != IsDocShared() ) + { + if ( bSetShared ) + { + bool bContinue = true; + if ( HasName() ) + { + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(pWin, + VclMessageType::Question, VclButtonsType::YesNo, + ScResId(STR_DOC_WILLBESAVED))); + xQueryBox->set_default_response(RET_YES); + if (xQueryBox->run() == RET_NO) + { + bContinue = false; + } + } + if ( bContinue ) + { + EnableSharedSettings( true ); + + SC_MOD()->SetInSharedDocSaving( true ); + if ( !SwitchToShared( true, true ) ) + { + // TODO/LATER: what should be done in case the switch has failed? + // for example in case the user has cancelled the saveAs operation + } + + SC_MOD()->SetInSharedDocSaving( false ); + + InvalidateName(); + GetUndoManager()->Clear(); + + ScTabView* pTabView = pViewData->GetView(); + if ( pTabView ) + { + pTabView->UpdateLayerLocks(); + } + } + } + else + { + uno::Reference< frame::XModel > xModel; + try + { + // load shared file + xModel.set( LoadSharedDocument(), uno::UNO_SET_THROW ); + uno::Reference< util::XCloseable > xCloseable( xModel, uno::UNO_QUERY_THROW ); + + // check if shared flag is set in shared file + bool bShared = false; + ScModelObj* pDocObj = comphelper::getFromUnoTunnel<ScModelObj>( xModel ); + if ( pDocObj ) + { + ScDocShell* pDocShell = dynamic_cast< ScDocShell* >( pDocObj->GetEmbeddedObject() ); + if ( pDocShell ) + { + bShared = pDocShell->HasSharedXMLFlagSet(); + } + } + + // #i87870# check if shared status was disabled and enabled again + bool bOwnEntry = false; + try + { + ::svt::ShareControlFile aControlFile( GetSharedFileURL() ); + bOwnEntry = aControlFile.HasOwnEntry(); + } + catch ( uno::Exception& ) + { + } + + if ( bShared && bOwnEntry ) + { + uno::Reference< frame::XStorable > xStorable( xModel, uno::UNO_QUERY_THROW ); + if ( xStorable->isReadonly() ) + { + xCloseable->close( true ); + + OUString aUserName( ScResId( STR_UNKNOWN_USER ) ); + try + { + ::svt::DocumentLockFile aLockFile( GetSharedFileURL() ); + LockFileEntry aData = aLockFile.GetLockData(); + if ( !aData[LockFileComponent::OOOUSERNAME].isEmpty() ) + { + aUserName = aData[LockFileComponent::OOOUSERNAME]; + } + else if ( !aData[LockFileComponent::SYSUSERNAME].isEmpty() ) + { + aUserName = aData[LockFileComponent::SYSUSERNAME]; + } + } + catch ( uno::Exception& ) + { + } + OUString aMessage( ScResId( STR_FILE_LOCKED_TRY_LATER ) ); + aMessage = aMessage.replaceFirst( "%1", aUserName ); + + std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(pWin, + VclMessageType::Warning, VclButtonsType::Ok, + aMessage)); + xWarn->run(); + } + else + { + std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(pWin, + VclMessageType::Warning, VclButtonsType::YesNo, + ScResId(STR_DOC_DISABLESHARED))); + xWarn->set_default_response(RET_YES); + + if (xWarn->run() == RET_YES) + { + xCloseable->close( true ); + + if ( !SwitchToShared( false, true ) ) + { + // TODO/LATER: what should be done in case the switch has failed? + // for example in case the user has cancelled the saveAs operation + } + + EnableSharedSettings( false ); + + // Do *not* use dispatch mechanism in this place - we don't want others (extensions etc.) to intercept this. + uno::Reference<frame::XStorable> xStorable2( + GetModel(), uno::UNO_QUERY_THROW); + xStorable2->store(); + + ScTabView* pTabView = pViewData->GetView(); + if ( pTabView ) + { + pTabView->UpdateLayerLocks(); + } + } + else + { + xCloseable->close( true ); + } + } + } + else + { + xCloseable->close( true ); + std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(pWin, + VclMessageType::Warning, VclButtonsType::Ok, + ScResId(STR_DOC_NOLONGERSHARED))); + xWarn->run(); + } + } + catch ( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "sc", "SID_SHARE_DOC" ); + SC_MOD()->SetInSharedDocSaving( false ); + + try + { + uno::Reference< util::XCloseable > xClose( xModel, uno::UNO_QUERY_THROW ); + xClose->close( true ); + } + catch ( uno::Exception& ) + { + } + } + } + } + } + rReq.Done(); + } + break; +#endif + case SID_OPEN_CALC: + { + ScViewData* pViewData = GetViewData(); + if (pViewData) + { + SfxStringItem aApp(SID_DOC_SERVICE, "com.sun.star.sheet.SpreadsheetDocument"); + SfxStringItem aTarget(SID_TARGETNAME, "_blank"); + pViewData->GetDispatcher().ExecuteList( + SID_OPENDOC, SfxCallMode::API|SfxCallMode::SYNCHRON, + { &aApp, &aTarget }); + } + } + break; + case SID_NOTEBOOKBAR: + { + const SfxStringItem* pFile = rReq.GetArg<SfxStringItem>( SID_NOTEBOOKBAR ); + + if ( pBindings && sfx2::SfxNotebookBar::IsActive() ) + sfx2::SfxNotebookBar::ExecMethod(*pBindings, pFile ? pFile->GetValue() : ""); + else if ( pBindings ) + sfx2::SfxNotebookBar::CloseMethod(*pBindings); + } + break; + case SID_LANGUAGE_STATUS: + { + OUString aLangText; + const SfxStringItem* pItem = rReq.GetArg<SfxStringItem>(nSlot); + if ( pItem ) + aLangText = pItem->GetValue(); + + if ( !aLangText.isEmpty() ) + { + LanguageType eLang, eLatin, eCjk, eCtl; + static const OUStringLiteral aSelectionLangPrefix(u"Current_"); + static const OUStringLiteral aParagraphLangPrefix(u"Paragraph_"); + static const OUStringLiteral aDocLangPrefix(u"Default_"); + + bool bSelection = false; + bool bParagraph = false; + + ScDocument& rDoc = GetDocument(); + rDoc.GetLanguage( eLatin, eCjk, eCtl ); + + sal_Int32 nPos = 0; + if ( aLangText == "*" ) + { + SfxAbstractDialogFactory* pFact = SfxAbstractDialogFactory::Create(); + ScTabViewShell* pSh = GetBestViewShell(); + ScopedVclPtr<VclAbstractDialog> pDlg(pFact->CreateVclDialog(pSh ? pSh->GetDialogParent() : nullptr, SID_LANGUAGE_OPTIONS)); + pDlg->Execute(); + + rDoc.GetLanguage( eLang, eCjk, eCtl ); + } + else if ( (nPos = aLangText.indexOf(aDocLangPrefix)) != -1 ) + { + aLangText = aLangText.replaceAt(nPos, aDocLangPrefix.getLength(), u""); + + if ( aLangText == "LANGUAGE_NONE" ) + { + eLang = LANGUAGE_NONE; + rDoc.SetLanguage( eLang, eCjk, eCtl ); + } + else if ( aLangText == "RESET_LANGUAGES" ) + { + bool bAutoSpell; + + ScModule::GetSpellSettings(eLang, eCjk, eCtl, bAutoSpell); + rDoc.SetLanguage(eLang, eCjk, eCtl); + } + else + { + eLang = SvtLanguageTable::GetLanguageType( aLangText ); + if ( eLang != LANGUAGE_DONTKNOW && SvtLanguageOptions::GetScriptTypeOfLanguage(eLang) == SvtScriptType::LATIN ) + { + rDoc.SetLanguage( eLang, eCjk, eCtl ); + } + else + { + eLang = eLatin; + } + } + } + else if (-1 != (nPos = aLangText.indexOf( aSelectionLangPrefix ))) + { + bSelection = true; + aLangText = aLangText.replaceAt( nPos, aSelectionLangPrefix.getLength(), u"" ); + } + else if (-1 != (nPos = aLangText.indexOf( aParagraphLangPrefix ))) + { + bParagraph = true; + aLangText = aLangText.replaceAt( nPos, aParagraphLangPrefix.getLength(), u"" ); + } + + if (bSelection || bParagraph) + { + ScViewData* pViewData = GetViewData(); + if (!pViewData) + return; + + EditView* pEditView = pViewData->GetEditView(pViewData->GetActivePart()); + if (!pEditView) + return; + + const LanguageType nLangToUse = SvtLanguageTable::GetLanguageType( aLangText ); + SvtScriptType nScriptType = SvtLanguageOptions::GetScriptTypeOfLanguage( nLangToUse ); + + SfxItemSet aAttrs = pEditView->GetEditEngine()->GetEmptyItemSet(); + if (nScriptType == SvtScriptType::LATIN) + aAttrs.Put( SvxLanguageItem( nLangToUse, EE_CHAR_LANGUAGE ) ); + if (nScriptType == SvtScriptType::COMPLEX) + aAttrs.Put( SvxLanguageItem( nLangToUse, EE_CHAR_LANGUAGE_CTL ) ); + if (nScriptType == SvtScriptType::ASIAN) + aAttrs.Put( SvxLanguageItem( nLangToUse, EE_CHAR_LANGUAGE_CJK ) ); + ESelection aOldSel; + if (bParagraph) + { + ESelection aSel = pEditView->GetSelection(); + aOldSel = aSel; + aSel.nStartPos = 0; + aSel.nEndPos = EE_TEXTPOS_ALL; + pEditView->SetSelection( aSel ); + } + + pEditView->SetAttribs( aAttrs ); + if (bParagraph) + pEditView->SetSelection( aOldSel ); + } + else if ( eLang != eLatin ) + { + if ( ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell() ) + { + ScInputHandler* pInputHandler = SC_MOD()->GetInputHdl(pViewSh); + if ( pInputHandler ) + pInputHandler->UpdateSpellSettings(); + + pViewSh->UpdateDrawTextOutliner(); + } + + SetDocumentModified(); + Broadcast(SfxHint(SfxHintId::LanguageChanged)); + PostPaintGridAll(); + } + } + } + break; + case SID_SPELLCHECK_IGNORE_ALL: + { + ScViewData* pViewData = GetViewData(); + if (!pViewData) + return; + + EditView* pEditView = pViewData->GetEditView(pViewData->GetActivePart()); + if (!pEditView) + return; + + OUString sIgnoreText; + const SfxStringItem* pItem2 = rReq.GetArg<SfxStringItem>(FN_PARAM_1); + if (pItem2) + sIgnoreText = pItem2->GetValue(); + + if(sIgnoreText == "Spelling") + { + ESelection aOldSel = pEditView->GetSelection(); + pEditView->SpellIgnoreWord(); + pEditView->SetSelection( aOldSel ); + } + } + break; + case SID_SPELLCHECK_APPLY_SUGGESTION: + { + ScViewData* pViewData = GetViewData(); + if (!pViewData) + return; + + EditView* pEditView = pViewData->GetEditView(pViewData->GetActivePart()); + if (!pEditView) + return; + + OUString sApplyText; + const SfxStringItem* pItem2 = rReq.GetArg<SfxStringItem>(FN_PARAM_1); + if (pItem2) + sApplyText = pItem2->GetValue(); + + static const OUStringLiteral sSpellingRule(u"Spelling_"); + sal_Int32 nPos = 0; + if(-1 != (nPos = sApplyText.indexOf( sSpellingRule ))) + { + sApplyText = sApplyText.replaceAt(nPos, sSpellingRule.getLength(), u""); + pEditView->InsertText( sApplyText ); + } + } + break; + case SID_REFRESH_VIEW: + { + PostPaintGridAll(); + } + break; + default: + { + // small (?) hack -> forwarding of the slots to TabViewShell + ScTabViewShell* pSh = GetBestViewShell(); + if ( pSh ) + pSh->Execute( rReq ); +#if HAVE_FEATURE_SCRIPTING + else + SbxBase::SetError( ERRCODE_BASIC_NO_ACTIVE_OBJECT ); +#endif + } + } +} + +void UpdateAcceptChangesDialog() +{ + // update "accept changes" dialog + //! notify all views + SfxViewFrame* pViewFrm = SfxViewFrame::Current(); + if ( pViewFrm && pViewFrm->HasChildWindow( FID_CHG_ACCEPT ) ) + { + SfxChildWindow* pChild = pViewFrm->GetChildWindow( FID_CHG_ACCEPT ); + if ( pChild ) + static_cast<ScAcceptChgDlgWrapper*>(pChild)->ReInitDlg(); + } +} + +bool ScDocShell::ExecuteChangeProtectionDialog( bool bJustQueryIfProtected ) +{ + bool bDone = false; + ScChangeTrack* pChangeTrack = m_pDocument->GetChangeTrack(); + if ( pChangeTrack ) + { + bool bProtected = pChangeTrack->IsProtected(); + if ( bJustQueryIfProtected && !bProtected ) + return true; + + OUString aTitle( ScResId( bProtected ? SCSTR_CHG_UNPROTECT : SCSTR_CHG_PROTECT ) ); + OUString aText( ScResId( SCSTR_PASSWORD ) ); + OUString aPassword; + + weld::Window* pWin = ScDocShell::GetActiveDialogParent(); + SfxPasswordDialog aDlg(pWin, &aText); + aDlg.set_title(aTitle); + aDlg.SetMinLen(1); + aDlg.set_help_id(GetStaticInterface()->GetSlot(SID_CHG_PROTECT)->GetCommand()); + aDlg.SetEditHelpId( HID_CHG_PROTECT ); + if ( !bProtected ) + aDlg.ShowExtras(SfxShowExtras::CONFIRM); + if (aDlg.run() == RET_OK) + aPassword = aDlg.GetPassword(); + + if (!aPassword.isEmpty()) + { + if ( bProtected ) + { + if ( SvPasswordHelper::CompareHashPassword(pChangeTrack->GetProtection(), aPassword) ) + { + if ( bJustQueryIfProtected ) + bDone = true; + else + pChangeTrack->SetProtection( {} ); + } + else + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pWin, + VclMessageType::Info, VclButtonsType::Ok, + ScResId(SCSTR_WRONGPASSWORD))); + xInfoBox->run(); + } + } + else + { + css::uno::Sequence< sal_Int8 > aPass; + SvPasswordHelper::GetHashPassword( aPass, aPassword ); + pChangeTrack->SetProtection( aPass ); + } + if ( bProtected != pChangeTrack->IsProtected() ) + { + UpdateAcceptChangesDialog(); + bDone = true; + } + } + } + else if ( bJustQueryIfProtected ) + bDone = true; + return bDone; +} + +void ScDocShell::DoRecalc( bool bApi ) +{ + if (m_pDocument->IsInDocShellRecalc()) + { + SAL_WARN("sc","ScDocShell::DoRecalc tries re-entering while in Recalc; probably Forms->BASIC->Dispatcher."); + return; + } + ScDocShellRecalcGuard aGuard(*m_pDocument); + bool bDone = false; + ScTabViewShell* pSh = GetBestViewShell(); + ScInputHandler* pHdl = ( pSh ? SC_MOD()->GetInputHdl( pSh ) : nullptr ); + if ( pSh ) + { + if ( pHdl && pHdl->IsInputMode() && pHdl->IsFormulaMode() && !bApi ) + { + pHdl->FormulaPreview(); // partial result as QuickHelp + bDone = true; + } + else + { + ScTabView::UpdateInputLine(); // InputEnterHandler + pSh->UpdateInputHandler(); + } + } + if (bDone) // otherwise re-calculate document + return; + + weld::WaitObject aWaitObj( GetActiveDialogParent() ); + if ( pHdl ) + { + // tdf97897 set current cell to Dirty to force recalculation of cell + ScFormulaCell* pFC = m_pDocument->GetFormulaCell( pHdl->GetCursorPos()); + if (pFC) + pFC->SetDirty(); + } + m_pDocument->CalcFormulaTree(); + if ( pSh ) + pSh->UpdateCharts(true); + + m_pDocument->BroadcastUno( SfxHint( SfxHintId::DataChanged ) ); + + // If there are charts, then paint everything, so that PostDataChanged + // and the charts do not come one after the other and parts are painted twice. + + ScChartListenerCollection* pCharts = m_pDocument->GetChartListenerCollection(); + if ( pCharts && pCharts->hasListeners() ) + PostPaintGridAll(); + else + PostDataChanged(); +} + +void ScDocShell::DoHardRecalc() +{ + if (m_pDocument->IsInDocShellRecalc()) + { + SAL_WARN("sc","ScDocShell::DoHardRecalc tries re-entering while in Recalc; probably Forms->BASIC->Dispatcher."); + return; + } + auto start = std::chrono::steady_clock::now(); + ScDocShellRecalcGuard aGuard(*m_pDocument); + weld::WaitObject aWaitObj( GetActiveDialogParent() ); + ScTabViewShell* pSh = GetBestViewShell(); + if ( pSh ) + { + ScTabView::UpdateInputLine(); // InputEnterHandler + pSh->UpdateInputHandler(); + } + m_pDocument->CalcAll(); + GetDocFunc().DetectiveRefresh(); // creates own Undo + if ( pSh ) + pSh->UpdateCharts(true); + + // set notification flags for "calculate" event (used in SfxHintId::DataChanged broadcast) + // (might check for the presence of any formulas on each sheet) + SCTAB nTabCount = m_pDocument->GetTableCount(); + if (m_pDocument->HasAnySheetEventScript( ScSheetEventId::CALCULATE, true )) // search also for VBA handler + for (SCTAB nTab=0; nTab<nTabCount; nTab++) + m_pDocument->SetCalcNotification(nTab); + + // CalcAll doesn't broadcast value changes, so SfxHintId::ScCalcAll is broadcasted globally + // in addition to SfxHintId::DataChanged. + m_pDocument->BroadcastUno( SfxHint( SfxHintId::ScCalcAll ) ); + m_pDocument->BroadcastUno( SfxHint( SfxHintId::DataChanged ) ); + + // use hard recalc also to disable stream-copying of all sheets + // (somewhat consistent with charts) + for (SCTAB nTab=0; nTab<nTabCount; nTab++) + m_pDocument->SetStreamValid(nTab, false); + + PostPaintGridAll(); + auto end = std::chrono::steady_clock::now(); + SAL_INFO("sc.timing", "ScDocShell::DoHardRecalc(): took " << std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count() << "ms"); +} + +void ScDocShell::DoAutoStyle( const ScRange& rRange, const OUString& rStyle ) +{ + ScStyleSheetPool* pStylePool = m_pDocument->GetStyleSheetPool(); + ScStyleSheet* pStyleSheet = pStylePool->FindAutoStyle(rStyle); + if (!pStyleSheet) + return; + + OSL_ENSURE(rRange.aStart.Tab() == rRange.aEnd.Tab(), + "DoAutoStyle with several tables"); + SCTAB nTab = rRange.aStart.Tab(); + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + SCCOL nEndCol = rRange.aEnd.Col(); + SCROW nEndRow = rRange.aEnd.Row(); + m_pDocument->ApplyStyleAreaTab( nStartCol, nStartRow, nEndCol, nEndRow, nTab, *pStyleSheet ); + m_pDocument->ExtendMerge( nStartCol, nStartRow, nEndCol, nEndRow, nTab ); + PostPaint( nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab, PaintPartFlags::Grid ); +} + +void ScDocShell::NotifyStyle( const SfxStyleSheetHint& rHint ) +{ + SfxHintId nId = rHint.GetId(); + const SfxStyleSheetBase* pStyle = rHint.GetStyleSheet(); + if (!pStyle) + return; + + if ( pStyle->GetFamily() == SfxStyleFamily::Page ) + { + if ( nId == SfxHintId::StyleSheetModified ) + { + ScDocShellModificator aModificator( *this ); + + const OUString& aNewName = pStyle->GetName(); + OUString aOldName = aNewName; + const SfxStyleSheetModifiedHint* pExtendedHint = dynamic_cast<const SfxStyleSheetModifiedHint*>(&rHint); // name changed? + if (pExtendedHint) + aOldName = pExtendedHint->GetOldName(); + + if ( aNewName != aOldName ) + m_pDocument->RenamePageStyleInUse( aOldName, aNewName ); + + SCTAB nTabCount = m_pDocument->GetTableCount(); + for (SCTAB nTab=0; nTab<nTabCount; nTab++) + if (m_pDocument->GetPageStyle(nTab) == aNewName) // already adjusted to new + { + m_pDocument->PageStyleModified( nTab, aNewName ); + ScPrintFunc aPrintFunc( this, GetPrinter(), nTab ); + aPrintFunc.UpdatePages(); + } + + aModificator.SetDocumentModified(); + + if (pExtendedHint) + { + SfxBindings* pBindings = GetViewBindings(); + if (pBindings) + { + pBindings->Invalidate( SID_STATUS_PAGESTYLE ); + pBindings->Invalidate( SID_STYLE_FAMILY4 ); + pBindings->Invalidate( FID_RESET_PRINTZOOM ); + pBindings->Invalidate( SID_ATTR_PARA_LEFT_TO_RIGHT ); + pBindings->Invalidate( SID_ATTR_PARA_RIGHT_TO_LEFT ); + } + } + } + } + else if ( pStyle->GetFamily() == SfxStyleFamily::Para ) + { + if ( nId == SfxHintId::StyleSheetModified) + { + const OUString& aNewName = pStyle->GetName(); + OUString aOldName = aNewName; + const SfxStyleSheetModifiedHint* pExtendedHint = dynamic_cast<const SfxStyleSheetModifiedHint*>(&rHint); + if (pExtendedHint) + aOldName = pExtendedHint->GetOldName(); + if ( aNewName != aOldName ) + { + for(SCTAB i = 0; i < m_pDocument->GetTableCount(); ++i) + { + ScConditionalFormatList* pList = m_pDocument->GetCondFormList(i); + if (pList) + pList->RenameCellStyle( aOldName,aNewName ); + } + } + } + } + + // everything else goes via slots... +} + +// like in printfun.cxx +#define ZOOM_MIN 10 + +void ScDocShell::SetPrintZoom( SCTAB nTab, sal_uInt16 nScale, sal_uInt16 nPages ) +{ + OUString aStyleName = m_pDocument->GetPageStyle( nTab ); + ScStyleSheetPool* pStylePool = m_pDocument->GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet = pStylePool->Find( aStyleName, SfxStyleFamily::Page ); + OSL_ENSURE( pStyleSheet, "PageStyle not found" ); + if ( !pStyleSheet ) + return; + + ScDocShellModificator aModificator( *this ); + + SfxItemSet& rSet = pStyleSheet->GetItemSet(); + const bool bUndo(m_pDocument->IsUndoEnabled()); + if (bUndo) + { + sal_uInt16 nOldScale = rSet.Get(ATTR_PAGE_SCALE).GetValue(); + sal_uInt16 nOldPages = rSet.Get(ATTR_PAGE_SCALETOPAGES).GetValue(); + GetUndoManager()->AddUndoAction( std::make_unique<ScUndoPrintZoom>( + this, nTab, nOldScale, nOldPages, nScale, nPages ) ); + } + + rSet.Put( SfxUInt16Item( ATTR_PAGE_SCALE, nScale ) ); + rSet.Put( SfxUInt16Item( ATTR_PAGE_SCALETOPAGES, nPages ) ); + + ScPrintFunc aPrintFunc( this, GetPrinter(), nTab ); + aPrintFunc.UpdatePages(); + aModificator.SetDocumentModified(); + + SfxBindings* pBindings = GetViewBindings(); + if (pBindings) + pBindings->Invalidate( FID_RESET_PRINTZOOM ); +} + +bool ScDocShell::AdjustPrintZoom( const ScRange& rRange ) +{ + bool bChange = false; + SCTAB nTab = rRange.aStart.Tab(); + + OUString aStyleName = m_pDocument->GetPageStyle( nTab ); + ScStyleSheetPool* pStylePool = m_pDocument->GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet = pStylePool->Find( aStyleName, SfxStyleFamily::Page ); + OSL_ENSURE( pStyleSheet, "PageStyle not found" ); + if ( pStyleSheet ) + { + SfxItemSet& rSet = pStyleSheet->GetItemSet(); + bool bHeaders = rSet.Get(ATTR_PAGE_HEADERS).GetValue(); + sal_uInt16 nOldScale = rSet.Get(ATTR_PAGE_SCALE).GetValue(); + sal_uInt16 nOldPages = rSet.Get(ATTR_PAGE_SCALETOPAGES).GetValue(); + std::optional<ScRange> oRepeatCol = m_pDocument->GetRepeatColRange( nTab ); + std::optional<ScRange> oRepeatRow = m_pDocument->GetRepeatRowRange( nTab ); + + // calculate needed scaling for selection + + sal_uInt16 nNewScale = nOldScale; + + tools::Long nBlkTwipsX = 0; + if (bHeaders) + nBlkTwipsX += PRINT_HEADER_WIDTH; + SCCOL nStartCol = rRange.aStart.Col(); + SCCOL nEndCol = rRange.aEnd.Col(); + if ( oRepeatCol && nStartCol >= oRepeatCol->aStart.Col() ) + { + for (SCCOL i=oRepeatCol->aStart.Col(); i<=oRepeatCol->aEnd.Col(); i++ ) + nBlkTwipsX += m_pDocument->GetColWidth( i, nTab ); + if ( nStartCol <= oRepeatCol->aEnd.Col() ) + nStartCol = oRepeatCol->aEnd.Col() + 1; + } + // legacy compilers' own scope for i + { + for ( SCCOL i=nStartCol; i<=nEndCol; i++ ) + nBlkTwipsX += m_pDocument->GetColWidth( i, nTab ); + } + + tools::Long nBlkTwipsY = 0; + if (bHeaders) + nBlkTwipsY += PRINT_HEADER_HEIGHT; + SCROW nStartRow = rRange.aStart.Row(); + SCROW nEndRow = rRange.aEnd.Row(); + if ( oRepeatRow && nStartRow >= oRepeatRow->aStart.Row() ) + { + nBlkTwipsY += m_pDocument->GetRowHeight( oRepeatRow->aStart.Row(), + oRepeatRow->aEnd.Row(), nTab ); + if ( nStartRow <= oRepeatRow->aEnd.Row() ) + nStartRow = oRepeatRow->aEnd.Row() + 1; + } + nBlkTwipsY += m_pDocument->GetRowHeight( nStartRow, nEndRow, nTab ); + + Size aPhysPage; + tools::Long nHdr, nFtr; + ScPrintFunc aOldPrFunc( this, GetPrinter(), nTab ); + aOldPrFunc.GetScaleData( aPhysPage, nHdr, nFtr ); + nBlkTwipsY += nHdr + nFtr; + + if ( nBlkTwipsX == 0 ) // hidden columns/rows may lead to 0 + nBlkTwipsX = 1; + if ( nBlkTwipsY == 0 ) + nBlkTwipsY = 1; + + tools::Long nNeeded = std::min( aPhysPage.Width() * 100 / nBlkTwipsX, + aPhysPage.Height() * 100 / nBlkTwipsY ); + if ( nNeeded < ZOOM_MIN ) + nNeeded = ZOOM_MIN; // boundary + if ( nNeeded < static_cast<tools::Long>(nNewScale) ) + nNewScale = static_cast<sal_uInt16>(nNeeded); + + bChange = ( nNewScale != nOldScale || nOldPages != 0 ); + if ( bChange ) + SetPrintZoom( nTab, nNewScale, 0 ); + } + return bChange; +} + +void ScDocShell::PageStyleModified( std::u16string_view rStyleName, bool bApi ) +{ + ScDocShellModificator aModificator( *this ); + + SCTAB nTabCount = m_pDocument->GetTableCount(); + SCTAB nUseTab = MAXTAB+1; + for (SCTAB nTab=0; nTab<nTabCount && nUseTab>MAXTAB; nTab++) + if ( m_pDocument->GetPageStyle(nTab) == rStyleName && + ( !bApi || m_pDocument->GetPageSize(nTab).Width() ) ) + nUseTab = nTab; + // at bApi only if breaks already shown + + if (ValidTab(nUseTab)) // not used -> nothing to do + { + bool bWarn = false; + + ScPrintFunc aPrintFunc( this, GetPrinter(), nUseTab ); //! cope without CountPages + if (!aPrintFunc.UpdatePages()) // sets breaks on all tabs + bWarn = true; + + if (bWarn && !bApi) + { + weld::Window* pWin = GetActiveDialogParent(); + weld::WaitObject aWaitOff(pWin); + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pWin, + VclMessageType::Info, VclButtonsType::Ok, + ScResId(STR_PRINT_INVALID_AREA))); + xInfoBox->run(); + } + } + + aModificator.SetDocumentModified(); + + SfxBindings* pBindings = GetViewBindings(); + if (pBindings) + { + pBindings->Invalidate( FID_RESET_PRINTZOOM ); + pBindings->Invalidate( SID_ATTR_PARA_LEFT_TO_RIGHT ); + pBindings->Invalidate( SID_ATTR_PARA_RIGHT_TO_LEFT ); + } +} + +void ScDocShell::ExecutePageStyle( const SfxViewShell& rCaller, + SfxRequest& rReq, + SCTAB nCurTab ) +{ + const SfxItemSet* pReqArgs = rReq.GetArgs(); + + switch ( rReq.GetSlot() ) + { + case SID_STATUS_PAGESTYLE: // click on StatusBar control + case SID_FORMATPAGE: + { + if ( pReqArgs == nullptr ) + { + OUString aOldName = m_pDocument->GetPageStyle( nCurTab ); + ScStyleSheetPool* pStylePool = m_pDocument->GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet + = pStylePool->Find( aOldName, SfxStyleFamily::Page ); + + OSL_ENSURE( pStyleSheet, "PageStyle not found! :-/" ); + + if ( pStyleSheet ) + { + ScStyleSaveData aOldData; + const bool bUndo(m_pDocument->IsUndoEnabled()); + if (bUndo) + aOldData.InitFromStyle( pStyleSheet ); + + SfxItemSet& rStyleSet = pStyleSheet->GetItemSet(); + rStyleSet.MergeRange( XATTR_FILL_FIRST, XATTR_FILL_LAST ); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + VclPtr<SfxAbstractTabDialog> pDlg(pFact->CreateScStyleDlg(GetActiveDialogParent(), *pStyleSheet, true)); + + auto pRequest = std::make_shared<SfxRequest>(rReq); + rReq.Ignore(); // the 'old' request is not relevant any more + pDlg->StartExecuteAsync([this, pDlg, pRequest, pStyleSheet, aOldData, aOldName, &rStyleSet, nCurTab, &rCaller, bUndo](sal_Int32 nResult){ + if ( nResult == RET_OK ) + { + const SfxItemSet* pOutSet = pDlg->GetOutputItemSet(); + + weld::WaitObject aWait( GetActiveDialogParent() ); + + OUString aNewName = pStyleSheet->GetName(); + if ( aNewName != aOldName && + m_pDocument->RenamePageStyleInUse( aOldName, aNewName ) ) + { + SfxBindings* pBindings = GetViewBindings(); + if (pBindings) + { + pBindings->Invalidate( SID_STATUS_PAGESTYLE ); + pBindings->Invalidate( FID_RESET_PRINTZOOM ); + } + } + + if ( pOutSet ) + m_pDocument->ModifyStyleSheet( *pStyleSheet, *pOutSet ); + + // memorizing for GetState(): + GetPageOnFromPageStyleSet( &rStyleSet, nCurTab, m_bHeaderOn, m_bFooterOn ); + rCaller.GetViewFrame()->GetBindings().Invalidate( SID_HFEDIT ); + + ScStyleSaveData aNewData; + aNewData.InitFromStyle( pStyleSheet ); + if (bUndo) + { + GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoModifyStyle>( this, SfxStyleFamily::Page, + aOldData, aNewData ) ); + } + + PageStyleModified( aNewName, false ); + pRequest->Done(); + } + pDlg->disposeOnce(); + }); + } + } + } + break; + + case SID_HFEDIT: + { + if ( pReqArgs == nullptr ) + { + OUString aStr( m_pDocument->GetPageStyle( nCurTab ) ); + + ScStyleSheetPool* pStylePool + = m_pDocument->GetStyleSheetPool(); + + SfxStyleSheetBase* pStyleSheet + = pStylePool->Find( aStr, SfxStyleFamily::Page ); + + OSL_ENSURE( pStyleSheet, "PageStyle not found! :-/" ); + + if ( pStyleSheet ) + { + SfxItemSet& rStyleSet = pStyleSheet->GetItemSet(); + + SvxPageUsage eUsage = rStyleSet.Get( ATTR_PAGE ).GetPageUsage(); + bool bShareHeader = rStyleSet + .Get(ATTR_PAGE_HEADERSET) + .GetItemSet() + .Get(ATTR_PAGE_SHARED) + .GetValue(); + bool bShareFooter = rStyleSet + .Get(ATTR_PAGE_FOOTERSET) + .GetItemSet() + .Get(ATTR_PAGE_SHARED) + .GetValue(); + sal_uInt16 nResId = 0; + + switch ( eUsage ) + { + case SvxPageUsage::Left: + case SvxPageUsage::Right: + { + if ( m_bHeaderOn && m_bFooterOn ) + nResId = RID_SCDLG_HFEDIT; + else if ( SvxPageUsage::Right == eUsage ) + { + if ( !m_bHeaderOn && m_bFooterOn ) + nResId = RID_SCDLG_HFEDIT_RIGHTFOOTER; + else if ( m_bHeaderOn && !m_bFooterOn ) + nResId = RID_SCDLG_HFEDIT_RIGHTHEADER; + } + else + { + // #69193a# respect "shared" setting + if ( !m_bHeaderOn && m_bFooterOn ) + nResId = bShareFooter ? + RID_SCDLG_HFEDIT_RIGHTFOOTER : + RID_SCDLG_HFEDIT_LEFTFOOTER; + else if ( m_bHeaderOn && !m_bFooterOn ) + nResId = bShareHeader ? + RID_SCDLG_HFEDIT_RIGHTHEADER : + RID_SCDLG_HFEDIT_LEFTHEADER; + } + } + break; + + case SvxPageUsage::Mirror: + case SvxPageUsage::All: + default: + { + if ( !bShareHeader && !bShareFooter ) + { + if ( m_bHeaderOn && m_bFooterOn ) + nResId = RID_SCDLG_HFEDIT_ALL; + else if ( !m_bHeaderOn && m_bFooterOn ) + nResId = RID_SCDLG_HFEDIT_FOOTER; + else if ( m_bHeaderOn && !m_bFooterOn ) + nResId = RID_SCDLG_HFEDIT_HEADER; + } + else if ( bShareHeader && bShareFooter ) + { + if ( m_bHeaderOn && m_bFooterOn ) + nResId = RID_SCDLG_HFEDIT; + else + { + if ( !m_bHeaderOn && m_bFooterOn ) + nResId = RID_SCDLG_HFEDIT_RIGHTFOOTER; + else if ( m_bHeaderOn && !m_bFooterOn ) + nResId = RID_SCDLG_HFEDIT_RIGHTHEADER; + } + } + else if ( !bShareHeader && bShareFooter ) + { + if ( m_bHeaderOn && m_bFooterOn ) + nResId = RID_SCDLG_HFEDIT_SFTR; + else if ( !m_bHeaderOn && m_bFooterOn ) + nResId = RID_SCDLG_HFEDIT_RIGHTFOOTER; + else if ( m_bHeaderOn && !m_bFooterOn ) + nResId = RID_SCDLG_HFEDIT_HEADER; + } + else if ( bShareHeader && !bShareFooter ) + { + if ( m_bHeaderOn && m_bFooterOn ) + nResId = RID_SCDLG_HFEDIT_SHDR; + else if ( !m_bHeaderOn && m_bFooterOn ) + nResId = RID_SCDLG_HFEDIT_FOOTER; + else if ( m_bHeaderOn && !m_bFooterOn ) + nResId = RID_SCDLG_HFEDIT_RIGHTHEADER; + } + } + } + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + VclPtr<SfxAbstractTabDialog> pDlg(pFact->CreateScHFEditDlg( + GetActiveDialogParent(), + rStyleSet, + aStr, + nResId)); + auto xRequest = std::make_shared<SfxRequest>(rReq); + rReq.Ignore(); // the 'old' request is not relevant any more + pDlg->StartExecuteAsync([this, pDlg, pStyleSheet, xRequest](sal_Int32 nResult){ + if ( nResult == RET_OK ) + { + const SfxItemSet* pOutSet = pDlg->GetOutputItemSet(); + + if ( pOutSet ) + m_pDocument->ModifyStyleSheet( *pStyleSheet, *pOutSet ); + + SetDocumentModified(); + xRequest->Done(); + } + pDlg->disposeOnce(); + }); + } + } + } + break; + + default: + break; + } +} + +void ScDocShell::GetStatePageStyle( SfxItemSet& rSet, + SCTAB nCurTab ) +{ + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + while ( nWhich ) + { + switch (nWhich) + { + case SID_STATUS_PAGESTYLE: + rSet.Put( SfxStringItem( nWhich, m_pDocument->GetPageStyle( nCurTab ) ) ); + break; + + case SID_HFEDIT: + { + OUString aStr = m_pDocument->GetPageStyle( nCurTab ); + ScStyleSheetPool* pStylePool = m_pDocument->GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet = pStylePool->Find( aStr, SfxStyleFamily::Page ); + + OSL_ENSURE( pStyleSheet, "PageStyle not found! :-/" ); + + if ( pStyleSheet ) + { + SfxItemSet& rStyleSet = pStyleSheet->GetItemSet(); + GetPageOnFromPageStyleSet( &rStyleSet, nCurTab, m_bHeaderOn, m_bFooterOn ); + + if ( !m_bHeaderOn && !m_bFooterOn ) + rSet.DisableItem( nWhich ); + } + } + break; + } + + nWhich = aIter.NextWhich(); + } +} + +void ScDocShell::GetState( SfxItemSet &rSet ) +{ + bool bTabView = GetBestViewShell() != nullptr; + + SfxWhichIter aIter(rSet); + for (sal_uInt16 nWhich = aIter.FirstWhich(); nWhich; nWhich = aIter.NextWhich()) + { + if (!bTabView) + { + rSet.DisableItem(nWhich); + continue; + } + + switch (nWhich) + { + case FID_AUTO_CALC: + if ( m_pDocument->GetHardRecalcState() != ScDocument::HardRecalcState::OFF ) + rSet.DisableItem( nWhich ); + else + rSet.Put( SfxBoolItem( nWhich, m_pDocument->GetAutoCalc() ) ); + break; + + case FID_CHG_RECORD: + if ( IsDocShared() ) + rSet.DisableItem( nWhich ); + else + rSet.Put( SfxBoolItem( nWhich, + m_pDocument->GetChangeTrack() != nullptr ) ); + break; + + case SID_CHG_PROTECT: + { + ScChangeTrack* pChangeTrack = m_pDocument->GetChangeTrack(); + if ( pChangeTrack && !IsDocShared() ) + rSet.Put( SfxBoolItem( nWhich, + pChangeTrack->IsProtected() ) ); + else + rSet.DisableItem( nWhich ); + } + break; + + case SID_DOCUMENT_COMPARE: + { + if ( IsDocShared() ) + { + rSet.DisableItem( nWhich ); + } + } + break; + + // When a formula is edited, FID_RECALC must be enabled in any case. Recalc for + // the doc was disabled once because of a bug if AutoCalc was on, but is now + // always enabled because of another bug. + + case SID_TABLES_COUNT: + rSet.Put( SfxInt16Item( nWhich, m_pDocument->GetTableCount() ) ); + break; + + case SID_ATTR_YEAR2000 : + rSet.Put( SfxUInt16Item( nWhich, + m_pDocument->GetDocOptions().GetYear2000() ) ); + break; + + case SID_SHARE_DOC: + { + if ( IsReadOnly() || GetObjectShell()->isExportLocked() ) + { + rSet.DisableItem( nWhich ); + } + } + break; + + case SID_ATTR_CHAR_FONTLIST: + rSet.Put( SvxFontListItem( m_pImpl->pFontList.get(), nWhich ) ); + break; + + case SID_NOTEBOOKBAR: + { + if (GetViewBindings()) + { + bool bVisible = sfx2::SfxNotebookBar::StateMethod(*GetViewBindings(), + u"modules/scalc/ui/"); + rSet.Put( SfxBoolItem( SID_NOTEBOOKBAR, bVisible ) ); + } + } + break; + + case SID_LANGUAGE_STATUS: + { + LanguageType eLatin, eCjk, eCtl; + + GetDocument().GetLanguage( eLatin, eCjk, eCtl ); + OUString sLanguage = SvtLanguageTable::GetLanguageString(eLatin); + if (comphelper::LibreOfficeKit::isActive()) { + if (eLatin == LANGUAGE_NONE) + sLanguage += ";-"; + else + sLanguage += ";" + LanguageTag(eLatin).getBcp47(false); + } + rSet.Put(SfxStringItem(nWhich, sLanguage)); + } + break; + + default: + { + } + break; + } + } +} + +void ScDocShell::Draw( OutputDevice* pDev, const JobSetup & /* rSetup */, sal_uInt16 nAspect ) +{ + + SCTAB nVisTab = m_pDocument->GetVisibleTab(); + if (!m_pDocument->HasTable(nVisTab)) + return; + + vcl::text::ComplexTextLayoutFlags nOldLayoutMode = pDev->GetLayoutMode(); + pDev->SetLayoutMode( vcl::text::ComplexTextLayoutFlags::Default ); // even if it's the same, to get the metafile action + + if ( nAspect == ASPECT_THUMBNAIL ) + { + tools::Rectangle aBoundRect = GetVisArea( ASPECT_THUMBNAIL ); + ScViewData aTmpData( *this, nullptr ); + aTmpData.SetTabNo(nVisTab); + SnapVisArea( aBoundRect ); + aTmpData.SetScreen( aBoundRect ); + ScPrintFunc::DrawToDev( *m_pDocument, pDev, 1.0, aBoundRect, &aTmpData, true ); + } + else + { + tools::Rectangle aOldArea = SfxObjectShell::GetVisArea(); + tools::Rectangle aNewArea = aOldArea; + ScViewData aTmpData( *this, nullptr ); + aTmpData.SetTabNo(nVisTab); + SnapVisArea( aNewArea ); + if ( aNewArea != aOldArea && (m_pDocument->GetPosLeft() > 0 || m_pDocument->GetPosTop() > 0) ) + SfxObjectShell::SetVisArea( aNewArea ); + aTmpData.SetScreen( aNewArea ); + ScPrintFunc::DrawToDev( *m_pDocument, pDev, 1.0, aNewArea, &aTmpData, true ); + } + + pDev->SetLayoutMode( nOldLayoutMode ); +} + +tools::Rectangle ScDocShell::GetVisArea( sal_uInt16 nAspect ) const +{ + SfxObjectCreateMode eShellMode = GetCreateMode(); + if ( eShellMode == SfxObjectCreateMode::ORGANIZER ) + { + // without contents we also don't know how large are the contents; + // return empty rectangle, it will then be calculated after the loading + return tools::Rectangle(); + } + + if( nAspect == ASPECT_THUMBNAIL ) + { + SCTAB nVisTab = m_pDocument->GetVisibleTab(); + if (!m_pDocument->HasTable(nVisTab)) + { + nVisTab = 0; + const_cast<ScDocShell*>(this)->m_pDocument->SetVisibleTab(nVisTab); + } + Size aSize = m_pDocument->GetPageSize(nVisTab); + const tools::Long SC_PREVIEW_SIZE_X = 10000; + const tools::Long SC_PREVIEW_SIZE_Y = 12400; + tools::Rectangle aArea( 0,0, SC_PREVIEW_SIZE_X, SC_PREVIEW_SIZE_Y); + if (aSize.Width() > aSize.Height()) + { + aArea.SetRight( SC_PREVIEW_SIZE_Y ); + aArea.SetBottom( SC_PREVIEW_SIZE_X ); + } + + bool bNegativePage = m_pDocument->IsNegativePage( m_pDocument->GetVisibleTab() ); + if ( bNegativePage ) + ScDrawLayer::MirrorRectRTL( aArea ); + SnapVisArea( aArea ); + return aArea; + } + else if( nAspect == ASPECT_CONTENT && eShellMode != SfxObjectCreateMode::EMBEDDED ) + { + // fetch visarea like after loading + + SCTAB nVisTab = m_pDocument->GetVisibleTab(); + if (!m_pDocument->HasTable(nVisTab)) + { + nVisTab = 0; + const_cast<ScDocShell*>(this)->m_pDocument->SetVisibleTab(nVisTab); + } + SCCOL nStartCol; + SCROW nStartRow; + m_pDocument->GetDataStart( nVisTab, nStartCol, nStartRow ); + SCCOL nEndCol; + SCROW nEndRow; + m_pDocument->GetPrintArea( nVisTab, nEndCol, nEndRow ); + if (nStartCol>nEndCol) + nStartCol = nEndCol; + if (nStartRow>nEndRow) + nStartRow = nEndRow; + tools::Rectangle aNewArea = m_pDocument + ->GetMMRect( nStartCol,nStartRow, nEndCol,nEndRow, nVisTab ); + return aNewArea; + } + else + return SfxObjectShell::GetVisArea( nAspect ); +} + +namespace { + +[[nodiscard]] +tools::Long SnapHorizontal( const ScDocument& rDoc, SCTAB nTab, tools::Long nVal, SCCOL& rStartCol ) +{ + SCCOL nCol = 0; + tools::Long nTwips = o3tl::convert(nVal, o3tl::Length::mm100, o3tl::Length::twip); + tools::Long nSnap = 0; + while ( nCol<rDoc.MaxCol() ) + { + tools::Long nAdd = rDoc.GetColWidth(nCol, nTab); + if ( nSnap + nAdd/2 < nTwips || nCol < rStartCol ) + { + nSnap += nAdd; + ++nCol; + } + else + break; + } + nVal = o3tl::convert(nSnap, o3tl::Length::twip, o3tl::Length::mm100); + rStartCol = nCol; + return nVal; +} + +[[nodiscard]] +tools::Long SnapVertical( const ScDocument& rDoc, SCTAB nTab, tools::Long nVal, SCROW& rStartRow ) +{ + SCROW nRow = 0; + tools::Long nTwips = o3tl::convert(nVal, o3tl::Length::mm100, o3tl::Length::twip); + tools::Long nSnap = 0; + + bool bFound = false; + for (SCROW i = nRow; i <= rDoc.MaxRow(); ++i) + { + SCROW nLastRow; + if (rDoc.RowHidden(i, nTab, nullptr, &nLastRow)) + { + i = nLastRow; + continue; + } + + nRow = i; + tools::Long nAdd = rDoc.GetRowHeight(i, nTab); + if ( nSnap + nAdd/2 < nTwips || nRow < rStartRow ) + { + nSnap += nAdd; + ++nRow; + } + else + { + bFound = true; + break; + } + } + if (!bFound) + nRow = rDoc.MaxRow(); // all hidden down to the bottom + + nVal = o3tl::convert(nSnap, o3tl::Length::twip, o3tl::Length::mm100); + rStartRow = nRow; + return nVal; +} + +} + +void ScDocShell::SnapVisArea( tools::Rectangle& rRect ) const +{ + SCTAB nTab = m_pDocument->GetVisibleTab(); + tools::Long nOrigTop = rRect.Top(); + tools::Long nOrigLeft = rRect.Left(); + bool bNegativePage = m_pDocument->IsNegativePage( nTab ); + if ( bNegativePage ) + ScDrawLayer::MirrorRectRTL( rRect ); // calculate with positive (LTR) values + + SCCOL nCol = m_pDocument->GetPosLeft(); + tools::Long nSetLeft = SnapHorizontal( *m_pDocument, nTab, rRect.Left(), nCol ); + rRect.SetLeft( nSetLeft ); + ++nCol; // at least one column + tools::Long nCorrectionLeft = (nOrigLeft == 0 && nCol > 0) ? nSetLeft : 0; // initial correction + rRect.SetRight( SnapHorizontal( *m_pDocument, nTab, rRect.Right() + nCorrectionLeft, nCol )); + + SCROW nRow = m_pDocument->GetPosTop(); + tools::Long nSetTop = SnapVertical( *m_pDocument, nTab, rRect.Top(), nRow ); + rRect.SetTop( nSetTop ); + ++nRow; // at least one row + tools::Long nCorrectionTop = (nOrigTop == 0 && nRow > 0) ? nSetTop : 0; // initial correction + rRect.SetBottom( SnapVertical( *m_pDocument, nTab, rRect.Bottom() + nCorrectionTop, nRow )); + + if ( bNegativePage ) + ScDrawLayer::MirrorRectRTL( rRect ); // back to real rectangle +} + +void ScDocShell::GetPageOnFromPageStyleSet( const SfxItemSet* pStyleSet, + SCTAB nCurTab, + bool& rbHeader, + bool& rbFooter ) +{ + if ( !pStyleSet ) + { + ScStyleSheetPool* pStylePool = m_pDocument->GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet = pStylePool-> + Find( m_pDocument->GetPageStyle( nCurTab ), + SfxStyleFamily::Page ); + + OSL_ENSURE( pStyleSheet, "PageStyle not found! :-/" ); + + if ( pStyleSheet ) + pStyleSet = &pStyleSheet->GetItemSet(); + else + rbHeader = rbFooter = false; + } + + OSL_ENSURE( pStyleSet, "PageStyle-Set not found! :-(" ); + if (!pStyleSet) + return; + + const SvxSetItem* pSetItem = nullptr; + const SfxItemSet* pSet = nullptr; + + pSetItem = &pStyleSet->Get( ATTR_PAGE_HEADERSET ); + pSet = &pSetItem->GetItemSet(); + rbHeader = pSet->Get(ATTR_PAGE_ON).GetValue(); + + pSetItem = &pStyleSet->Get( ATTR_PAGE_FOOTERSET ); + pSet = &pSetItem->GetItemSet(); + rbFooter = pSet->Get(ATTR_PAGE_ON).GetValue(); +} + +#if defined(_WIN32) +bool ScDocShell::DdeGetData( const OUString& rItem, + const OUString& rMimeType, + css::uno::Any & rValue ) +{ + SotClipboardFormatId eFormatId = SotExchange::GetFormatIdFromMimeType( rMimeType ); + if (SotClipboardFormatId::STRING == eFormatId || SotClipboardFormatId::STRING_TSVC == eFormatId) + { + if( rItem.equalsIgnoreAsciiCase( "Format" ) ) + { + OString aFmtByte(OUStringToOString(m_aDdeTextFmt, + osl_getThreadTextEncoding())); + rValue <<= css::uno::Sequence< sal_Int8 >( + reinterpret_cast<const sal_Int8*>(aFmtByte.getStr()), + aFmtByte.getLength() + 1 ); + return true; + } + ScImportExport aObj( *m_pDocument, rItem ); + if ( !aObj.IsRef() ) + return false; // invalid range + + if( m_aDdeTextFmt[0] == 'F' ) + aObj.SetFormulas( true ); + if( m_aDdeTextFmt == "SYLK" || + m_aDdeTextFmt == "FSYLK" ) + { + OString aData; + if( aObj.ExportByteString( aData, osl_getThreadTextEncoding(), + SotClipboardFormatId::SYLK ) ) + { + rValue <<= css::uno::Sequence< sal_Int8 >( + reinterpret_cast<const sal_Int8*>(aData.getStr()), + aData.getLength() + 1 ); + return true; + } + else + return false; + } + if( m_aDdeTextFmt == "CSV" || + m_aDdeTextFmt == "FCSV" ) + aObj.SetSeparator( ',' ); + aObj.SetExportTextOptions( ScExportTextOptions( ScExportTextOptions::ToSpace, 0, false ) ); + return aObj.ExportData( rMimeType, rValue ); + } + + ScImportExport aObj( *m_pDocument, rItem ); + aObj.SetExportTextOptions( ScExportTextOptions( ScExportTextOptions::ToSpace, 0, false ) ); + return aObj.IsRef() && aObj.ExportData( rMimeType, rValue ); +} + +bool ScDocShell::DdeSetData( const OUString& rItem, + const OUString& rMimeType, + const css::uno::Any & rValue ) +{ + SotClipboardFormatId eFormatId = SotExchange::GetFormatIdFromMimeType( rMimeType ); + if (SotClipboardFormatId::STRING == eFormatId || SotClipboardFormatId::STRING_TSVC == eFormatId) + { + if( rItem.equalsIgnoreAsciiCase( "Format" ) ) + { + if ( ScByteSequenceToString::GetString( m_aDdeTextFmt, rValue, osl_getThreadTextEncoding() ) ) + { + m_aDdeTextFmt = m_aDdeTextFmt.toAsciiUpperCase(); + return true; + } + return false; + } + ScImportExport aObj( *m_pDocument, rItem ); + if( m_aDdeTextFmt[0] == 'F' ) + aObj.SetFormulas( true ); + if( m_aDdeTextFmt == "SYLK" || + m_aDdeTextFmt == "FSYLK" ) + { + OUString aData; + if ( ScByteSequenceToString::GetString( aData, rValue, osl_getThreadTextEncoding() ) ) + { + return aObj.ImportString( aData, SotClipboardFormatId::SYLK ); + } + return false; + } + if( m_aDdeTextFmt == "CSV" || + m_aDdeTextFmt == "FCSV" ) + aObj.SetSeparator( ',' ); + OSL_ENSURE( false, "Implementation is missing" ); + return false; + } + /*ScImportExport aObj( aDocument, rItem ); + return aObj.IsRef() && ScImportExport::ImportData( rMimeType, rValue );*/ + OSL_ENSURE( false, "Implementation is missing" ); + return false; +} +#endif + +::sfx2::SvLinkSource* ScDocShell::DdeCreateLinkSource( const OUString& rItem ) +{ + // only check for valid item string - range is parsed again in ScServerObject ctor + + // named range? + OUString aPos = rItem; + ScRangeName* pRange = m_pDocument->GetRangeName(); + if( pRange ) + { + const ScRangeData* pData = pRange->findByUpperName(ScGlobal::getCharClass().uppercase(aPos)); + if (pData) + { + if( pData->HasType( ScRangeData::Type::RefArea ) + || pData->HasType( ScRangeData::Type::AbsArea ) + || pData->HasType( ScRangeData::Type::AbsPos ) ) + aPos = pData->GetSymbol(); // continue with the name's contents + } + } + + // Address in DDE function must be always parsed as CONV_OOO so that it + // would always work regardless of current address conversion. We do this + // because the address item in a DDE entry is *not* normalized when saved + // into ODF. + ScRange aRange; + bool bValid = ( (aRange.Parse(aPos, *m_pDocument, formula::FormulaGrammar::CONV_OOO ) & ScRefFlags::VALID) || + (aRange.aStart.Parse(aPos, *m_pDocument, formula::FormulaGrammar::CONV_OOO) & ScRefFlags::VALID) ); + + ScServerObject* pObj = nullptr; // NULL = error + if ( bValid ) + pObj = new ScServerObject( this, rItem ); + + // GetLinkManager()->InsertServer() is in the ScServerObject ctor + + return pObj; +} + +void ScDocShell::LOKCommentNotify(LOKCommentNotificationType nType, const ScDocument* pDocument, const ScAddress& rPos, const ScPostIt* pNote) +{ + if ( !pDocument->IsDocVisible() || // don't want callbacks until document load + !comphelper::LibreOfficeKit::isActive() || + comphelper::LibreOfficeKit::isTiledAnnotations() ) + return; + + boost::property_tree::ptree aAnnotation; + aAnnotation.put("action", (nType == LOKCommentNotificationType::Add ? "Add" : + (nType == LOKCommentNotificationType::Remove ? "Remove" : + (nType == LOKCommentNotificationType::Modify ? "Modify" : "???")))); + + assert(pNote); + aAnnotation.put("id", pNote->GetId()); + aAnnotation.put("tab", rPos.Tab()); + + if (nType != LOKCommentNotificationType::Remove) + { + aAnnotation.put("author", pNote->GetAuthor()); + aAnnotation.put("dateTime", pNote->GetDate()); + aAnnotation.put("text", pNote->GetText()); + + // Calculating the cell cursor position + ScViewData* pViewData = GetViewData(); + if (pViewData && pViewData->GetActiveWin()) + { + bool bInPrintTwips = comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs); + OString aRectString; + if (bInPrintTwips) + { + Point aTopLeft = pViewData->GetPrintTwipsPos(rPos.Col(), rPos.Row()); + tools::Long nSizeX, nSizeY; + pViewData->GetMergeSizePrintTwips(rPos.Col(), rPos.Row(), nSizeX, nSizeY); + aRectString = tools::Rectangle(aTopLeft, Size(nSizeX - 1, nSizeY - 1)).toString(); + } + else + { + Point aTopLeft = pViewData->GetScrPos(rPos.Col(), rPos.Row(), + pViewData->GetActivePart(), true); + tools::Long nSizeXPix, nSizeYPix; + pViewData->GetMergeSizePixel(rPos.Col(), rPos.Row(), nSizeXPix, nSizeYPix); + const double fPPTX = pViewData->GetPPTX(); + const double fPPTY = pViewData->GetPPTY(); + aRectString = tools::Rectangle(Point(aTopLeft.getX() / fPPTX, aTopLeft.getY() / fPPTY), + Size(nSizeXPix / fPPTX, nSizeYPix / fPPTY)).toString(); + } + aAnnotation.put("cellPos", aRectString); + } + } + + boost::property_tree::ptree aTree; + aTree.add_child("comment", aAnnotation); + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + std::string aPayload = aStream.str(); + + ScViewData* pViewData = GetViewData(); + SfxViewShell* pThisViewShell = ( pViewData ? pViewData->GetViewShell() : nullptr ); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + if (pThisViewShell == nullptr || pViewShell->GetDocId() == pThisViewShell->GetDocId()) + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_COMMENT, aPayload.c_str()); + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +ScViewData* ScDocShell::GetViewData() +{ + SfxViewShell* pCur = SfxViewShell::Current(); + ScTabViewShell* pViewSh = dynamic_cast< ScTabViewShell *>( pCur ); + return pViewSh ? &pViewSh->GetViewData() : nullptr; +} + +SCTAB ScDocShell::GetCurTab() +{ + //! this must be made non-static and use a ViewShell from this document! + + ScViewData* pViewData = GetViewData(); + + return pViewData ? pViewData->GetTabNo() : static_cast<SCTAB>(0); +} + +ScTabViewShell* ScDocShell::GetBestViewShell( bool bOnlyVisible ) +{ + ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell(); + // wrong Doc? + if( pViewSh && pViewSh->GetViewData().GetDocShell() != this ) + pViewSh = nullptr; + if( !pViewSh ) + { + // 1. find ViewShell + SfxViewFrame* pFrame = SfxViewFrame::GetFirst( this, bOnlyVisible ); + if( pFrame ) + { + SfxViewShell* p = pFrame->GetViewShell(); + pViewSh = dynamic_cast< ScTabViewShell *>( p ); + } + } + return pViewSh; +} + +SfxBindings* ScDocShell::GetViewBindings() +{ + // used to invalidate slots after changes to this document + + SfxViewShell* pViewSh = GetBestViewShell(); + if (pViewSh) + return &pViewSh->GetViewFrame()->GetBindings(); + else + return nullptr; +} + +ScDocShell* ScDocShell::GetShellByNum( sal_uInt16 nDocNo ) // static +{ + ScDocShell* pFound = nullptr; + SfxObjectShell* pShell = SfxObjectShell::GetFirst(); + sal_uInt16 nShellCnt = 0; + + while ( pShell && !pFound ) + { + if ( auto pDocSh = dynamic_cast<ScDocShell*>(pShell) ) + { + if ( nShellCnt == nDocNo ) + pFound = pDocSh; + else + ++nShellCnt; + } + pShell = SfxObjectShell::GetNext( *pShell ); + } + + return pFound; +} + +IMPL_LINK( ScDocShell, DialogClosedHdl, sfx2::FileDialogHelper*, _pFileDlg, void ) +{ + OSL_ENSURE( _pFileDlg, "ScDocShell::DialogClosedHdl(): no file dialog" ); + OSL_ENSURE( m_pImpl->pDocInserter, "ScDocShell::DialogClosedHdl(): no document inserter" ); + + if ( ERRCODE_NONE == _pFileDlg->GetError() ) + { + sal_uInt16 nSlot = m_pImpl->pRequest->GetSlot(); + std::unique_ptr<SfxMedium> pMed = m_pImpl->pDocInserter->CreateMedium(); + // #i87094# If a .odt was selected pMed is NULL. + if (pMed) + { + m_pImpl->pRequest->AppendItem( SfxStringItem( SID_FILE_NAME, pMed->GetName() ) ); + if ( SID_DOCUMENT_COMPARE == nSlot ) + { + if ( pMed->GetFilter() ) + m_pImpl->pRequest->AppendItem( + SfxStringItem( SID_FILTER_NAME, pMed->GetFilter()->GetFilterName() ) ); + OUString sOptions = ScDocumentLoader::GetOptions( *pMed ); + if ( !sOptions.isEmpty() ) + m_pImpl->pRequest->AppendItem( SfxStringItem( SID_FILE_FILTEROPTIONS, sOptions ) ); + } + const SfxPoolItem* pItem = nullptr; + const SfxInt16Item* pInt16Item(nullptr); + SfxItemSet* pSet = pMed->GetItemSet(); + if (pSet && pSet->GetItemState(SID_VERSION, true, &pItem) == SfxItemState::SET) + { + pInt16Item = dynamic_cast<const SfxInt16Item*>(pItem); + } + if (pInt16Item) + { + m_pImpl->pRequest->AppendItem( *pItem ); + } + + Execute( *(m_pImpl->pRequest) ); + } + } + + m_pImpl->bIgnoreLostRedliningWarning = false; +} + +#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT + +void ScDocShell::EnableSharedSettings( bool bEnable ) +{ + SetDocumentModified(); + + if ( bEnable ) + { + m_pDocument->EndChangeTracking(); + m_pDocument->StartChangeTracking(); + + // hide accept or reject changes dialog + sal_uInt16 nId = ScAcceptChgDlgWrapper::GetChildWindowId(); + SfxViewFrame* pViewFrame = SfxViewFrame::Current(); + if ( pViewFrame && pViewFrame->HasChildWindow( nId ) ) + { + pViewFrame->ToggleChildWindow( nId ); + SfxBindings* pBindings = GetViewBindings(); + if ( pBindings ) + { + pBindings->Invalidate( FID_CHG_ACCEPT ); + } + } + } + else + { + m_pDocument->EndChangeTracking(); + } + + ScChangeViewSettings aChangeViewSet; + aChangeViewSet.SetShowChanges( false ); + m_pDocument->SetChangeViewSettings( aChangeViewSet ); +} + +uno::Reference< frame::XModel > ScDocShell::LoadSharedDocument() +{ + uno::Reference< frame::XModel > xModel; + try + { + SC_MOD()->SetInSharedDocLoading( true ); + uno::Reference< frame::XDesktop2 > xLoader = frame::Desktop::create( ::comphelper::getProcessComponentContext() ); + uno::Sequence aArgs{ comphelper::makePropertyValue("Hidden", true) }; + + if ( GetMedium() ) + { + const SfxStringItem* pPasswordItem = SfxItemSet::GetItem<SfxStringItem>(GetMedium()->GetItemSet(), SID_PASSWORD, false); + if ( pPasswordItem && !pPasswordItem->GetValue().isEmpty() ) + { + aArgs.realloc( 2 ); + auto pArgs = aArgs.getArray(); + pArgs[1].Name = "Password"; + pArgs[1].Value <<= pPasswordItem->GetValue(); + } + const SfxUnoAnyItem* pEncryptionItem = SfxItemSet::GetItem<SfxUnoAnyItem>(GetMedium()->GetItemSet(), SID_ENCRYPTIONDATA, false); + if (pEncryptionItem) + { + aArgs.realloc(aArgs.getLength() + 1); + auto pArgs = aArgs.getArray(); + pArgs[aArgs.getLength() - 1].Name = "EncryptionData"; + pArgs[aArgs.getLength() - 1].Value = pEncryptionItem->GetValue(); + } + } + + xModel.set( + xLoader->loadComponentFromURL( GetSharedFileURL(), "_blank", 0, aArgs ), + uno::UNO_QUERY_THROW ); + SC_MOD()->SetInSharedDocLoading( false ); + } + catch ( uno::Exception& ) + { + OSL_FAIL( "ScDocShell::LoadSharedDocument(): caught exception" ); + SC_MOD()->SetInSharedDocLoading( false ); + try + { + uno::Reference< util::XCloseable > xClose( xModel, uno::UNO_QUERY_THROW ); + xClose->close( true ); + return uno::Reference< frame::XModel >(); + } + catch ( uno::Exception& ) + { + return uno::Reference< frame::XModel >(); + } + } + return xModel; +} + +#endif + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/docsh5.cxx b/sc/source/ui/docshell/docsh5.cxx new file mode 100644 index 000000000..d1749fee1 --- /dev/null +++ b/sc/source/ui/docshell/docsh5.cxx @@ -0,0 +1,1037 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <sal/config.h> + +#include <cassert> + +#include <osl/diagnose.h> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <sfx2/app.hxx> +#include <sfx2/bindings.hxx> +#include <unotools/charclass.hxx> + +#include <com/sun/star/script/vba/XVBACompatibility.hpp> + +#include <docsh.hxx> +#include <global.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <globalnames.hxx> +#include <undodat.hxx> +#include <undotab.hxx> +#include <undoblk.hxx> +#include <dpobject.hxx> +#include <dpshttab.hxx> +#include <dbdocfun.hxx> +#include <consoli.hxx> +#include <dbdata.hxx> +#include <progress.hxx> +#include <olinetab.hxx> +#include <patattr.hxx> +#include <attrib.hxx> +#include <docpool.hxx> +#include <uiitems.hxx> +#include <sc.hrc> +#include <sizedev.hxx> +#include <clipparam.hxx> +#include <rowheightcontext.hxx> +#include <refupdatecontext.hxx> + +using com::sun::star::script::XLibraryContainer; +using com::sun::star::script::vba::XVBACompatibility; +using com::sun::star::container::XNameContainer; +using com::sun::star::uno::Reference; +using com::sun::star::uno::UNO_QUERY; + +using ::std::unique_ptr; +using ::std::vector; + +// former viewfunc/dbfunc methods + +void ScDocShell::ErrorMessage(TranslateId pGlobStrId) +{ + //! StopMarking at the (active) view? + + weld::Window* pParent = GetActiveDialogParent(); + weld::WaitObject aWaitOff( pParent ); + bool bFocus = pParent && pParent->has_focus(); + + if (pGlobStrId && pGlobStrId == STR_PROTECTIONERR) + { + if (IsReadOnly()) + { + pGlobStrId = STR_READONLYERR; + } + } + + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pParent, + VclMessageType::Info, VclButtonsType::Ok, + ScResId(pGlobStrId))); + xInfoBox->run(); + + if (bFocus) + pParent->grab_focus(); +} + +bool ScDocShell::IsEditable() const +{ + // import into read-only document is possible - must be extended if other filters use api + // #i108547# MSOOXML filter uses "IsChangeReadOnlyEnabled" property + + return !IsReadOnly() || m_pDocument->IsImportingXML() || m_pDocument->IsChangeReadOnlyEnabled(); +} + +void ScDocShell::DBAreaDeleted( SCTAB nTab, SCCOL nX1, SCROW nY1, SCCOL nX2 ) +{ + ScDocShellModificator aModificator( *this ); + // the auto-filter is in the first row of the area + m_pDocument->RemoveFlagsTab( nX1, nY1, nX2, nY1, nTab, ScMF::Auto ); + PostPaint( nX1, nY1, nTab, nX2, nY1, nTab, PaintPartFlags::Grid ); + // No SetDocumentModified, as the unnamed database range might have to be restored later. + // The UNO hint is broadcast directly instead, to keep UNO objects in valid state. + m_pDocument->BroadcastUno( SfxHint( SfxHintId::DataChanged ) ); +} + +ScDBData* ScDocShell::GetDBData( const ScRange& rMarked, ScGetDBMode eMode, ScGetDBSelection eSel ) +{ + SCCOL nCol = rMarked.aStart.Col(); + SCROW nRow = rMarked.aStart.Row(); + SCTAB nTab = rMarked.aStart.Tab(); + + SCCOL nStartCol = nCol; + SCROW nStartRow = nRow; + SCTAB nStartTab = nTab; + SCCOL nEndCol = rMarked.aEnd.Col(); + SCROW nEndRow = rMarked.aEnd.Row(); + // Not simply GetDBAtCursor: The continuous data range for "unnamed" (GetDataArea) may be + // located next to the cursor; so a named DB range needs to be searched for there as well. + ScDBCollection* pColl = m_pDocument->GetDBCollection(); + ScDBData* pData = m_pDocument->GetDBAtArea( nTab, nStartCol, nStartRow, nEndCol, nEndRow ); + if (!pData) + pData = pColl->GetDBNearCursor(nCol, nRow, nTab ); + + bool bSelected = ( eSel == ScGetDBSelection::ForceMark || + (rMarked.aStart != rMarked.aEnd && eSel != ScGetDBSelection::RowDown) ); + bool bOnlyDown = (!bSelected && eSel == ScGetDBSelection::RowDown && rMarked.aStart.Row() == rMarked.aEnd.Row()); + + bool bUseThis = false; + if (pData) + { + // take range, if nothing else is marked + + SCTAB nDummy; + SCCOL nOldCol1; + SCROW nOldRow1; + SCCOL nOldCol2; + SCROW nOldRow2; + pData->GetArea( nDummy, nOldCol1,nOldRow1, nOldCol2,nOldRow2 ); + bool bIsNoName = ( pData->GetName() == STR_DB_LOCAL_NONAME ); + + if (!bSelected) + { + bUseThis = true; + if ( bIsNoName && (eMode == SC_DB_MAKE || eMode == SC_DB_AUTOFILTER) ) + { + // If nothing marked or only one row marked, adapt + // "unnamed" to contiguous area. + nStartCol = nCol; + nStartRow = nRow; + if (bOnlyDown) + { + nEndCol = rMarked.aEnd.Col(); + nEndRow = rMarked.aEnd.Row(); + } + else + { + nEndCol = nStartCol; + nEndRow = nStartRow; + } + m_pDocument->GetDataArea( nTab, nStartCol, nStartRow, nEndCol, nEndRow, false, bOnlyDown ); + if ( nOldCol1 != nStartCol || nOldCol2 != nEndCol || nOldRow1 != nStartRow ) + bUseThis = false; // doesn't fit at all + else if ( nOldRow2 != nEndRow ) + { + // extend range to new end row + pData->SetArea( nTab, nOldCol1,nOldRow1, nOldCol2,nEndRow ); + } + } + } + else + { + if ( nOldCol1 == nStartCol && nOldRow1 == nStartRow && + nOldCol2 == nEndCol && nOldRow2 == nEndRow ) // marked precisely? + bUseThis = true; + else + bUseThis = false; // always take marking (Bug 11964) + } + + // never take "unnamed" for import + + if ( bUseThis && eMode == SC_DB_IMPORT && bIsNoName ) + bUseThis = false; + } + + if ( bUseThis ) + { + pData->GetArea( nStartTab, nStartCol,nStartRow, nEndCol,nEndRow ); + } + else if ( eMode == SC_DB_OLD ) + { + pData = nullptr; // nothing found + } + else + { + if ( !bSelected ) + { // continuous range + nStartCol = nCol; + nStartRow = nRow; + if (bOnlyDown) + { + nEndCol = rMarked.aEnd.Col(); + nEndRow = rMarked.aEnd.Row(); + } + else + { + nEndCol = nStartCol; + nEndRow = nStartRow; + } + m_pDocument->GetDataArea( nTab, nStartCol, nStartRow, nEndCol, nEndRow, false, bOnlyDown ); + } + + bool bHasHeader = m_pDocument->HasColHeader( nStartCol,nStartRow, nEndCol,nEndRow, nTab ); + + ScDBData* pNoNameData = m_pDocument->GetAnonymousDBData(nTab); + if ( eMode != SC_DB_IMPORT && pNoNameData) + { + // Do not reset AutoFilter range during temporary operations on + // other ranges, use the document global temporary anonymous range + // instead. But, if AutoFilter is to be toggled then do use the + // sheet-local DB range. + bool bSheetLocal = true; + if (eMode != SC_DB_AUTOFILTER && pNoNameData->HasAutoFilter()) + { + bSheetLocal = false; + pNoNameData = m_pDocument->GetAnonymousDBData(); + if (!pNoNameData) + { + m_pDocument->SetAnonymousDBData( std::unique_ptr<ScDBData>(new ScDBData( STR_DB_LOCAL_NONAME, + nTab, nStartCol, nStartRow, nEndCol, nEndRow, true, bHasHeader) ) ); + pNoNameData = m_pDocument->GetAnonymousDBData(); + } + // ScDocShell::CancelAutoDBRange() would restore the + // sheet-local anonymous DBData from pOldAutoDBRange, unset so + // that won't happen with data of a previous sheet-local + // DBData. + m_pOldAutoDBRange.reset(); + } + else if (!m_pOldAutoDBRange) + { + // store the old unnamed database range with its settings for undo + // (store at the first change, get the state before all changes) + m_pOldAutoDBRange.reset( new ScDBData( *pNoNameData ) ); + } + else if (m_pOldAutoDBRange->GetTab() != pNoNameData->GetTab()) + { + // Different sheet-local unnamed DB range than the previous one. + *m_pOldAutoDBRange = *pNoNameData; + } + + SCCOL nOldX1; // take old range away cleanly + SCROW nOldY1; //! (UNDO ???) + SCCOL nOldX2; + SCROW nOldY2; + SCTAB nOldTab; + pNoNameData->GetArea( nOldTab, nOldX1, nOldY1, nOldX2, nOldY2 ); + + // If previously bHasHeader was set and the new range starts on the + // same row and intersects the old column range, then don't reset + // bHasHeader but assume that the new range still has headers, just + // some are empty or numeric. + if (!bHasHeader && pNoNameData->HasHeader() && nTab == nOldTab && nStartRow == nOldY1 && + nStartCol <= nOldY2 && nOldY1 <= nEndCol) + bHasHeader = true; + + // Remove AutoFilter button flags only for sheet-local DB range, + // not if a temporary is used. + if (bSheetLocal) + DBAreaDeleted( nOldTab, nOldX1, nOldY1, nOldX2 ); + + pNoNameData->SetSortParam( ScSortParam() ); // reset parameter + pNoNameData->SetQueryParam( ScQueryParam() ); + pNoNameData->SetSubTotalParam( ScSubTotalParam() ); + + pNoNameData->SetArea( nTab, nStartCol,nStartRow, nEndCol,nEndRow ); // set anew + pNoNameData->SetByRow( true ); + pNoNameData->SetHeader( bHasHeader ); + pNoNameData->SetAutoFilter( false ); + } + else + { + std::unique_ptr<ScDBCollection> pUndoColl; + + if (eMode==SC_DB_IMPORT) + { + m_pDocument->PreprocessDBDataUpdate(); + pUndoColl.reset( new ScDBCollection( *pColl ) ); // Undo for import range + + OUString aImport = ScResId( STR_DBNAME_IMPORT ); + tools::Long nCount = 0; + const ScDBData* pDummy = nullptr; + ScDBCollection::NamedDBs& rDBs = pColl->getNamedDBs(); + OUString aNewName; + do + { + ++nCount; + aNewName = aImport + OUString::number( nCount ); + pDummy = rDBs.findByUpperName(ScGlobal::getCharClass().uppercase(aNewName)); + } + while (pDummy); + pNoNameData = new ScDBData( aNewName, nTab, + nStartCol,nStartRow, nEndCol,nEndRow, + true, bHasHeader ); + bool ins = rDBs.insert(std::unique_ptr<ScDBData>(pNoNameData)); + assert(ins); (void)ins; + } + else + { + pNoNameData = new ScDBData(STR_DB_LOCAL_NONAME, nTab, + nStartCol,nStartRow, nEndCol,nEndRow, + true, bHasHeader ); + m_pDocument->SetAnonymousDBData(nTab, std::unique_ptr<ScDBData>(pNoNameData)); + } + + if ( pUndoColl ) + { + m_pDocument->CompileHybridFormula(); + + GetUndoManager()->AddUndoAction( std::make_unique<ScUndoDBData>( this, + std::move(pUndoColl), + std::make_unique<ScDBCollection>( *pColl ) ) ); + } + + // no longer needed to register new range at the Sba + + // announce "Import1", etc., at the Navigator + if (eMode==SC_DB_IMPORT) + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScDbAreasChanged ) ); + } + pData = pNoNameData; + } + + return pData; +} + +ScDBData* ScDocShell::GetAnonymousDBData(const ScRange& rRange) +{ + ScDBCollection* pColl = m_pDocument->GetDBCollection(); + if (!pColl) + return nullptr; + + ScDBData* pData = pColl->getAnonDBs().getByRange(rRange); + if (!pData) + return nullptr; + + if (!pData->HasHeader()) + { + bool bHasHeader = m_pDocument->HasColHeader( + rRange.aStart.Col(), rRange.aStart.Row(), rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aStart.Tab()); + pData->SetHeader(bHasHeader); + } + + return pData; +} + +std::unique_ptr<ScDBData> ScDocShell::GetOldAutoDBRange() +{ + return std::move(m_pOldAutoDBRange); +} + +void ScDocShell::CancelAutoDBRange() +{ + // called when dialog is cancelled +//moggi:TODO + if ( !m_pOldAutoDBRange ) + return; + + SCTAB nTab = GetCurTab(); + ScDBData* pDBData = m_pDocument->GetAnonymousDBData(nTab); + if ( pDBData ) + { + SCCOL nRangeX1; + SCROW nRangeY1; + SCCOL nRangeX2; + SCROW nRangeY2; + SCTAB nRangeTab; + pDBData->GetArea( nRangeTab, nRangeX1, nRangeY1, nRangeX2, nRangeY2 ); + DBAreaDeleted( nRangeTab, nRangeX1, nRangeY1, nRangeX2 ); + + *pDBData = *m_pOldAutoDBRange; // restore old settings + + if ( m_pOldAutoDBRange->HasAutoFilter() ) + { + // restore AutoFilter buttons + m_pOldAutoDBRange->GetArea( nRangeTab, nRangeX1, nRangeY1, nRangeX2, nRangeY2 ); + m_pDocument->ApplyFlagsTab( nRangeX1, nRangeY1, nRangeX2, nRangeY1, nRangeTab, ScMF::Auto ); + PostPaint( nRangeX1, nRangeY1, nRangeTab, nRangeX2, nRangeY1, nRangeTab, PaintPartFlags::Grid ); + } + } + + m_pOldAutoDBRange.reset(); +} + + // adjust height + //! merge with docfunc + +bool ScDocShell::AdjustRowHeight( SCROW nStartRow, SCROW nEndRow, SCTAB nTab ) +{ + ScSizeDeviceProvider aProv(this); + Fraction aZoom(1,1); + sc::RowHeightContext aCxt(m_pDocument->MaxRow(), aProv.GetPPTX(), aProv.GetPPTY(), aZoom, aZoom, aProv.GetDevice()); + bool bChange = m_pDocument->SetOptimalHeight(aCxt, nStartRow,nEndRow, nTab, true); + + if (bChange) + { + // tdf#76183: recalculate objects' positions + m_pDocument->SetDrawPageSize(nTab); + + PostPaint( 0,nStartRow,nTab, m_pDocument->MaxCol(),m_pDocument->MaxRow(),nTab, PaintPartFlags::Grid|PaintPartFlags::Left ); + } + + return bChange; +} + +void ScDocShell::UpdateAllRowHeights( const ScMarkData* pTabMark ) +{ + // update automatic row heights + + ScSizeDeviceProvider aProv(this); + Fraction aZoom(1,1); + sc::RowHeightContext aCxt(m_pDocument->MaxRow(), aProv.GetPPTX(), aProv.GetPPTY(), aZoom, aZoom, aProv.GetDevice()); + m_pDocument->UpdateAllRowHeights(aCxt, pTabMark); +} + +void ScDocShell::UpdatePendingRowHeights( SCTAB nUpdateTab, bool bBefore ) +{ + bool bIsUndoEnabled = m_pDocument->IsUndoEnabled(); + m_pDocument->EnableUndo( false ); + m_pDocument->LockStreamValid( true ); // ignore draw page size (but not formula results) + if ( bBefore ) // check all sheets up to nUpdateTab + { + SCTAB nTabCount = m_pDocument->GetTableCount(); + if ( nUpdateTab >= nTabCount ) + nUpdateTab = nTabCount-1; // nUpdateTab is inclusive + + ScMarkData aUpdateSheets(m_pDocument->GetSheetLimits()); + SCTAB nTab; + for (nTab=0; nTab<=nUpdateTab; ++nTab) + if ( m_pDocument->IsPendingRowHeights( nTab ) ) + aUpdateSheets.SelectTable( nTab, true ); + + if (aUpdateSheets.GetSelectCount()) + UpdateAllRowHeights(&aUpdateSheets); // update with a single progress bar + + for (nTab=0; nTab<=nUpdateTab; ++nTab) + if ( aUpdateSheets.GetTableSelect( nTab ) ) + { + m_pDocument->UpdatePageBreaks( nTab ); + m_pDocument->SetPendingRowHeights( nTab, false ); + } + } + else // only nUpdateTab + { + if ( m_pDocument->IsPendingRowHeights( nUpdateTab ) ) + { + AdjustRowHeight( 0, m_pDocument->MaxRow(), nUpdateTab ); + m_pDocument->UpdatePageBreaks( nUpdateTab ); + m_pDocument->SetPendingRowHeights( nUpdateTab, false ); + } + } + m_pDocument->LockStreamValid( false ); + m_pDocument->EnableUndo( bIsUndoEnabled ); +} + +void ScDocShell::RefreshPivotTables( const ScRange& rSource ) +{ + ScDPCollection* pColl = m_pDocument->GetDPCollection(); + if (!pColl) + return; + + ScDBDocFunc aFunc(*this); + for (size_t i = 0, n = pColl->GetCount(); i < n; ++i) + { + ScDPObject& rOld = (*pColl)[i]; + + const ScSheetSourceDesc* pSheetDesc = rOld.GetSheetDesc(); + if (pSheetDesc && pSheetDesc->GetSourceRange().Intersects(rSource)) + aFunc.UpdatePivotTable(rOld, true, false); + } +} + +static OUString lcl_GetAreaName( ScDocument* pDoc, const ScArea* pArea ) +{ + ScDBData* pData = pDoc->GetDBAtArea( pArea->nTab, pArea->nColStart, pArea->nRowStart, + pArea->nColEnd, pArea->nRowEnd ); + if (pData) + return pData->GetName(); + + OUString aName; + pDoc->GetName( pArea->nTab, aName ); + return aName; +} + +void ScDocShell::DoConsolidate( const ScConsolidateParam& rParam, bool bRecord ) +{ + ScConsData aData; + + sal_uInt16 nPos; + SCCOL nColSize = 0; + SCROW nRowSize = 0; + bool bErr = false; + for (nPos=0; nPos<rParam.nDataAreaCount; nPos++) + { + ScArea const & rArea = rParam.pDataAreas[nPos]; + nColSize = std::max( nColSize, SCCOL( rArea.nColEnd - rArea.nColStart + 1 ) ); + nRowSize = std::max( nRowSize, SCROW( rArea.nRowEnd - rArea.nRowStart + 1 ) ); + + // test if source data were moved + if (rParam.bReferenceData) + if (rArea.nTab == rParam.nTab && rArea.nRowEnd >= rParam.nRow) + bErr = true; + } + + if (bErr) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(GetActiveDialogParent(), + VclMessageType::Info, VclButtonsType::Ok, + ScResId(STR_CONSOLIDATE_ERR1))); + xInfoBox->run(); + return; + } + + // execute + + weld::WaitObject aWait( GetActiveDialogParent() ); + ScDocShellModificator aModificator( *this ); + + ScRange aOldDest; + ScDBData* pDestData = m_pDocument->GetDBAtCursor( rParam.nCol, rParam.nRow, rParam.nTab, ScDBDataPortion::TOP_LEFT ); + if (pDestData) + pDestData->GetArea(aOldDest); + + aData.SetSize( nColSize, nRowSize ); + aData.SetFlags( rParam.eFunction, rParam.bByCol, rParam.bByRow, rParam.bReferenceData ); + if ( rParam.bByCol || rParam.bByRow ) + for (nPos=0; nPos<rParam.nDataAreaCount; nPos++) + { + ScArea const & rArea = rParam.pDataAreas[nPos]; + aData.AddFields( m_pDocument.get(), rArea.nTab, rArea.nColStart, rArea.nRowStart, + rArea.nColEnd, rArea.nRowEnd ); + } + aData.DoneFields(); + for (nPos=0; nPos<rParam.nDataAreaCount; nPos++) + { + ScArea const & rArea = rParam.pDataAreas[nPos]; + aData.AddData( m_pDocument.get(), rArea.nTab, rArea.nColStart, rArea.nRowStart, + rArea.nColEnd, rArea.nRowEnd ); + aData.AddName( lcl_GetAreaName(m_pDocument.get(), &rArea) ); + } + + aData.GetSize( nColSize, nRowSize ); + if (bRecord && nColSize > 0 && nRowSize > 0) + { + std::unique_ptr<ScDBData> pUndoData(pDestData ? new ScDBData(*pDestData) : nullptr); + + SCTAB nDestTab = rParam.nTab; + ScArea aDestArea( rParam.nTab, rParam.nCol, rParam.nRow, + rParam.nCol+nColSize-1, rParam.nRow+nRowSize-1 ); + if (rParam.bByCol) ++aDestArea.nColEnd; + if (rParam.bByRow) ++aDestArea.nRowEnd; + + if (rParam.bReferenceData) + { + SCTAB nTabCount = m_pDocument->GetTableCount(); + SCROW nInsertCount = aData.GetInsertCount(); + + // old outlines + ScOutlineTable* pTable = m_pDocument->GetOutlineTable( nDestTab ); + std::unique_ptr<ScOutlineTable> pUndoTab(pTable ? new ScOutlineTable( *pTable ) : nullptr); + + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( *m_pDocument, 0, nTabCount-1, false, true ); + + // row state + m_pDocument->CopyToDocument(0, 0, nDestTab, m_pDocument->MaxCol(), m_pDocument->MaxRow(), nDestTab, + InsertDeleteFlags::NONE, false, *pUndoDoc); + + // all formulas + m_pDocument->CopyToDocument(0, 0, 0, m_pDocument->MaxCol(), m_pDocument->MaxRow(), nTabCount-1, + InsertDeleteFlags::FORMULA, false, *pUndoDoc); + + // complete output rows + m_pDocument->CopyToDocument(0, aDestArea.nRowStart, nDestTab, + m_pDocument->MaxCol(),aDestArea.nRowEnd, nDestTab, + InsertDeleteFlags::ALL, false, *pUndoDoc); + + // old output range + if (pDestData) + m_pDocument->CopyToDocument(aOldDest, InsertDeleteFlags::ALL, false, *pUndoDoc); + + GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoConsolidate>( this, aDestArea, rParam, std::move(pUndoDoc), + true, nInsertCount, std::move(pUndoTab), std::move(pUndoData) ) ); + } + else + { + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( *m_pDocument, aDestArea.nTab, aDestArea.nTab ); + + m_pDocument->CopyToDocument(aDestArea.nColStart, aDestArea.nRowStart, aDestArea.nTab, + aDestArea.nColEnd, aDestArea.nRowEnd, aDestArea.nTab, + InsertDeleteFlags::ALL, false, *pUndoDoc); + + // old output range + if (pDestData) + m_pDocument->CopyToDocument(aOldDest, InsertDeleteFlags::ALL, false, *pUndoDoc); + + GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoConsolidate>( this, aDestArea, rParam, std::move(pUndoDoc), + false, 0, nullptr, std::move(pUndoData) ) ); + } + } + + if (pDestData) // delete / adjust destination range + { + m_pDocument->DeleteAreaTab(aOldDest, InsertDeleteFlags::CONTENTS); + pDestData->SetArea( rParam.nTab, rParam.nCol, rParam.nRow, + rParam.nCol + nColSize - 1, rParam.nRow + nRowSize - 1 ); + pDestData->SetHeader( rParam.bByRow ); + } + + aData.OutputToDocument( *m_pDocument, rParam.nCol, rParam.nRow, rParam.nTab ); + + SCCOL nPaintStartCol = rParam.nCol; + SCROW nPaintStartRow = rParam.nRow; + SCCOL nPaintEndCol = nPaintStartCol + nColSize - 1; + SCROW nPaintEndRow = nPaintStartRow + nRowSize - 1; + PaintPartFlags nPaintFlags = PaintPartFlags::Grid; + if (rParam.bByCol) + ++nPaintEndRow; + if (rParam.bByRow) + ++nPaintEndCol; + if (rParam.bReferenceData) + { + nPaintStartCol = 0; + nPaintEndCol = m_pDocument->MaxCol(); + nPaintEndRow = m_pDocument->MaxRow(); + nPaintFlags |= PaintPartFlags::Left | PaintPartFlags::Size; + } + if (pDestData) + { + if ( aOldDest.aEnd.Col() > nPaintEndCol ) + nPaintEndCol = aOldDest.aEnd.Col(); + if ( aOldDest.aEnd.Row() > nPaintEndRow ) + nPaintEndRow = aOldDest.aEnd.Row(); + } + PostPaint( nPaintStartCol, nPaintStartRow, rParam.nTab, + nPaintEndCol, nPaintEndRow, rParam.nTab, nPaintFlags ); + aModificator.SetDocumentModified(); +} + +void ScDocShell::UseScenario( SCTAB nTab, const OUString& rName, bool bRecord ) +{ + if (!m_pDocument->IsScenario(nTab)) + { + SCTAB nTabCount = m_pDocument->GetTableCount(); + SCTAB nSrcTab = SCTAB_MAX; + SCTAB nEndTab = nTab; + OUString aCompare; + while ( nEndTab+1 < nTabCount && m_pDocument->IsScenario(nEndTab+1) ) + { + ++nEndTab; + if (nSrcTab > MAXTAB) // still searching for the scenario? + { + m_pDocument->GetName( nEndTab, aCompare ); + if (aCompare == rName) + nSrcTab = nEndTab; // found + } + } + if (ValidTab(nSrcTab)) + { + if ( m_pDocument->TestCopyScenario( nSrcTab, nTab ) ) // test cell protection + { + ScDocShellModificator aModificator( *this ); + ScMarkData aScenMark(m_pDocument->GetSheetLimits()); + m_pDocument->MarkScenario( nSrcTab, nTab, aScenMark ); + const ScRange& aMultiRange = aScenMark.GetMultiMarkArea(); + SCCOL nStartCol = aMultiRange.aStart.Col(); + SCROW nStartRow = aMultiRange.aStart.Row(); + SCCOL nEndCol = aMultiRange.aEnd.Col(); + SCROW nEndRow = aMultiRange.aEnd.Row(); + + if (bRecord) + { + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( *m_pDocument, nTab,nEndTab ); // also all scenarios + // shown table: + m_pDocument->CopyToDocument(nStartCol, nStartRow, nTab, + nEndCol, nEndRow, nTab, InsertDeleteFlags::ALL, + true, *pUndoDoc, &aScenMark); + // scenarios + for (SCTAB i=nTab+1; i<=nEndTab; i++) + { + pUndoDoc->SetScenario( i, true ); + OUString aComment; + Color aColor; + ScScenarioFlags nScenFlags; + m_pDocument->GetScenarioData( i, aComment, aColor, nScenFlags ); + pUndoDoc->SetScenarioData( i, aComment, aColor, nScenFlags ); + bool bActive = m_pDocument->IsActiveScenario( i ); + pUndoDoc->SetActiveScenario( i, bActive ); + // At copy-back scenarios also contents + if ( nScenFlags & ScScenarioFlags::TwoWay ) + m_pDocument->CopyToDocument(0, 0, i, m_pDocument->MaxCol(), m_pDocument->MaxRow(), i, + InsertDeleteFlags::ALL, false, *pUndoDoc ); + } + + GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoUseScenario>( this, aScenMark, + ScArea( nTab,nStartCol,nStartRow,nEndCol,nEndRow ), + std::move(pUndoDoc), rName ) ); + } + + m_pDocument->CopyScenario( nSrcTab, nTab ); + + sc::SetFormulaDirtyContext aCxt; + m_pDocument->SetAllFormulasDirty(aCxt); + + // paint all, because the active scenario may be modified in other ranges; + //! only if there are visible frames? + PostPaint( 0,0,nTab, m_pDocument->MaxCol(),m_pDocument->MaxRow(),nTab, PaintPartFlags::Grid ); + aModificator.SetDocumentModified(); + } + else + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(GetActiveDialogParent(), + VclMessageType::Info, VclButtonsType::Ok, + ScResId(STR_PROTECTIONERR))); + xInfoBox->run(); + } + } + else + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(GetActiveDialogParent(), + VclMessageType::Info, VclButtonsType::Ok, + ScResId(STR_SCENARIO_NOTFOUND))); + xInfoBox->run(); + } + } + else + { + OSL_FAIL( "UseScenario on Scenario-Sheet" ); + } +} + +void ScDocShell::ModifyScenario( SCTAB nTab, const OUString& rName, const OUString& rComment, + const Color& rColor, ScScenarioFlags nFlags ) +{ + // Undo + OUString aOldName; + m_pDocument->GetName( nTab, aOldName ); + OUString aOldComment; + Color aOldColor; + ScScenarioFlags nOldFlags; + m_pDocument->GetScenarioData( nTab, aOldComment, aOldColor, nOldFlags ); + GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoScenarioFlags>(this, nTab, + aOldName, rName, aOldComment, rComment, + aOldColor, rColor, nOldFlags, nFlags) ); + + // execute + ScDocShellModificator aModificator( *this ); + m_pDocument->RenameTab( nTab, rName ); + m_pDocument->SetScenarioData( nTab, rComment, rColor, nFlags ); + PostPaintGridAll(); + aModificator.SetDocumentModified(); + + if (aOldName != rName) + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); + + SfxBindings* pBindings = GetViewBindings(); + if (pBindings) + pBindings->Invalidate( SID_SELECT_SCENARIO ); +} + +SCTAB ScDocShell::MakeScenario( SCTAB nTab, const OUString& rName, const OUString& rComment, + const Color& rColor, ScScenarioFlags nFlags, + ScMarkData& rMark, bool bRecord ) +{ + rMark.MarkToMulti(); + if (rMark.IsMultiMarked()) + { + SCTAB nNewTab = nTab + 1; + while (m_pDocument->IsScenario(nNewTab)) + ++nNewTab; + + bool bCopyAll = ( (nFlags & ScScenarioFlags::CopyAll) != ScScenarioFlags::NONE ); + const ScMarkData* pCopyMark = nullptr; + if (!bCopyAll) + pCopyMark = &rMark; + + ScDocShellModificator aModificator( *this ); + + if (bRecord) + m_pDocument->BeginDrawUndo(); // drawing layer must do its own undo actions + + if (m_pDocument->CopyTab( nTab, nNewTab, pCopyMark )) + { + if (bRecord) + { + GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoMakeScenario>( this, nTab, nNewTab, + rName, rComment, rColor, nFlags, rMark )); + } + + m_pDocument->RenameTab( nNewTab, rName); + m_pDocument->SetScenario( nNewTab, true ); + m_pDocument->SetScenarioData( nNewTab, rComment, rColor, nFlags ); + + ScMarkData aDestMark = rMark; + aDestMark.SelectOneTable( nNewTab ); + + //! test for filter / buttons / merging + + ScPatternAttr aProtPattern( m_pDocument->GetPool() ); + aProtPattern.GetItemSet().Put( ScProtectionAttr( true ) ); + m_pDocument->ApplyPatternAreaTab( 0,0, m_pDocument->MaxCol(),m_pDocument->MaxRow(), nNewTab, aProtPattern ); + + ScPatternAttr aPattern( m_pDocument->GetPool() ); + aPattern.GetItemSet().Put( ScMergeFlagAttr( ScMF::Scenario ) ); + aPattern.GetItemSet().Put( ScProtectionAttr( true ) ); + m_pDocument->ApplySelectionPattern( aPattern, aDestMark ); + + if (!bCopyAll) + m_pDocument->SetVisible( nNewTab, false ); + + // this is the active scenario, then + m_pDocument->CopyScenario( nNewTab, nTab, true ); // sal_True - don't copy anything from scenario + + if (nFlags & ScScenarioFlags::ShowFrame) + PostPaint( 0,0,nTab, m_pDocument->MaxCol(),m_pDocument->MaxRow(),nTab, PaintPartFlags::Grid ); // paint frames + PostPaintExtras(); // table tab + aModificator.SetDocumentModified(); + + // A scenario tab is like a hidden sheet, broadcasting also + // notifies ScTabViewShell to add an ScViewData::maTabData entry. + Broadcast( ScTablesHint( SC_TAB_INSERTED, nNewTab )); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); + + return nNewTab; + } + } + return nTab; +} + +sal_uLong ScDocShell::TransferTab( ScDocShell& rSrcDocShell, SCTAB nSrcPos, + SCTAB nDestPos, bool bInsertNew, + bool bNotifyAndPaint ) +{ + ScDocument& rSrcDoc = rSrcDocShell.GetDocument(); + + // set the transferred area to the copyparam to make adjusting formulas possible + ScClipParam aParam; + ScRange aRange(0, 0, nSrcPos, m_pDocument->MaxCol(), m_pDocument->MaxRow(), nSrcPos); + aParam.maRanges.push_back(aRange); + rSrcDoc.SetClipParam(aParam); + + sal_uLong nErrVal = m_pDocument->TransferTab( rSrcDoc, nSrcPos, nDestPos, + bInsertNew ); // no insert + + // TransferTab doesn't copy drawing objects with bInsertNew=FALSE + if ( nErrVal > 0 && !bInsertNew) + m_pDocument->TransferDrawPage( rSrcDoc, nSrcPos, nDestPos ); + + if(nErrVal>0 && rSrcDoc.IsScenario( nSrcPos )) + { + OUString aComment; + Color aColor; + ScScenarioFlags nFlags; + + rSrcDoc.GetScenarioData( nSrcPos, aComment,aColor, nFlags); + m_pDocument->SetScenario(nDestPos,true); + m_pDocument->SetScenarioData(nDestPos,aComment,aColor,nFlags); + bool bActive = rSrcDoc.IsActiveScenario(nSrcPos); + m_pDocument->SetActiveScenario(nDestPos, bActive ); + + bool bVisible = rSrcDoc.IsVisible(nSrcPos); + m_pDocument->SetVisible(nDestPos,bVisible ); + + } + + if ( nErrVal > 0 && rSrcDoc.IsTabProtected( nSrcPos ) ) + m_pDocument->SetTabProtection(nDestPos, rSrcDoc.GetTabProtection(nSrcPos)); + if ( bNotifyAndPaint ) + { + Broadcast( ScTablesHint( SC_TAB_INSERTED, nDestPos ) ); + PostPaintExtras(); + PostPaintGridAll(); + } + return nErrVal; +} + +bool ScDocShell::MoveTable( SCTAB nSrcTab, SCTAB nDestTab, bool bCopy, bool bRecord ) +{ + ScDocShellModificator aModificator( *this ); + + // #i92477# be consistent with ScDocFunc::InsertTable: any index past the last sheet means "append" + // #i101139# nDestTab must be the target position, not APPEND (for CopyTabProtection etc.) + if ( nDestTab >= m_pDocument->GetTableCount() ) + nDestTab = m_pDocument->GetTableCount(); + + if (bCopy) + { + if (bRecord) + m_pDocument->BeginDrawUndo(); // drawing layer must do its own undo actions + + OUString sSrcCodeName; + m_pDocument->GetCodeName( nSrcTab, sSrcCodeName ); + if (!m_pDocument->CopyTab( nSrcTab, nDestTab )) + { + //! EndDrawUndo? + return false; + } + else + { + SCTAB nAdjSource = nSrcTab; + if ( nDestTab <= nSrcTab ) + ++nAdjSource; // new position of source table after CopyTab + + if ( m_pDocument->IsTabProtected( nAdjSource ) ) + m_pDocument->CopyTabProtection(nAdjSource, nDestTab); + + if (bRecord) + { + unique_ptr< vector<SCTAB> > pSrcList(new vector<SCTAB>(1, nSrcTab)); + unique_ptr< vector<SCTAB> > pDestList(new vector<SCTAB>(1, nDestTab)); + GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoCopyTab>(this, std::move(pSrcList), std::move(pDestList))); + } + + bool bVbaEnabled = m_pDocument->IsInVBAMode(); + if ( bVbaEnabled ) + { + OUString aLibName( "Standard" ); + Reference< XLibraryContainer > xLibContainer = GetBasicContainer(); + Reference< XVBACompatibility > xVBACompat( xLibContainer, UNO_QUERY ); + + if ( xVBACompat.is() ) + { + aLibName = xVBACompat->getProjectName(); + } + + SCTAB nTabToUse = nDestTab; + if ( nDestTab == SC_TAB_APPEND ) + nTabToUse = m_pDocument->GetMaxTableNumber() - 1; + OUString sSource; + try + { + Reference< XNameContainer > xLib; + if( xLibContainer.is() ) + { + css::uno::Any aLibAny = xLibContainer->getByName( aLibName ); + aLibAny >>= xLib; + } + if( xLib.is() ) + { + xLib->getByName( sSrcCodeName ) >>= sSource; + } + } + catch ( const css::uno::Exception& ) + { + } + VBA_InsertModule( *m_pDocument, nTabToUse, sSource ); + } + } + Broadcast( ScTablesHint( SC_TAB_COPIED, nSrcTab, nDestTab ) ); + } + else + { + if ( m_pDocument->GetChangeTrack() ) + return false; + + if ( nSrcTab<nDestTab && nDestTab!=SC_TAB_APPEND ) + nDestTab--; + + if ( nSrcTab == nDestTab ) + { + //! allow only for api calls? + return true; // nothing to do, but valid + } + + std::optional<ScProgress> pProgress(std::in_place, this, ScResId(STR_UNDO_MOVE_TAB), + m_pDocument->GetCodeCount(), true); + bool bDone = m_pDocument->MoveTab( nSrcTab, nDestTab, &*pProgress ); + pProgress.reset(); + if (!bDone) + { + return false; + } + else if (bRecord) + { + unique_ptr< vector<SCTAB> > pSrcList(new vector<SCTAB>(1, nSrcTab)); + unique_ptr< vector<SCTAB> > pDestList(new vector<SCTAB>(1, nDestTab)); + GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoMoveTab>(this, std::move(pSrcList), std::move(pDestList))); + } + + Broadcast( ScTablesHint( SC_TAB_MOVED, nSrcTab, nDestTab ) ); + } + + PostPaintGridAll(); + PostPaintExtras(); + aModificator.SetDocumentModified(); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); + + return true; +} + +IMPL_LINK( ScDocShell, RefreshDBDataHdl, Timer*, pRefreshTimer, void ) +{ + ScDBDocFunc aFunc(*this); + + ScDBData* pDBData = static_cast<ScDBData*>(pRefreshTimer); + ScImportParam aImportParam; + pDBData->GetImportParam( aImportParam ); + if (aImportParam.bImport && !pDBData->HasImportSelection()) + { + ScRange aRange; + pDBData->GetArea( aRange ); + bool bContinue = aFunc.DoImport( aRange.aStart.Tab(), aImportParam, nullptr ); //! Api-Flag as parameter + // internal operations (sort, query, subtotal) only if no error + if (bContinue) + { + aFunc.RepeatDB( pDBData->GetName(), true, true ); + RefreshPivotTables(aRange); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/docsh6.cxx b/sc/source/ui/docshell/docsh6.cxx new file mode 100644 index 000000000..caefdfc0f --- /dev/null +++ b/sc/source/ui/docshell/docsh6.cxx @@ -0,0 +1,517 @@ +/* -*- 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 <svx/pageitem.hxx> +#include <sfx2/linkmgr.hxx> + +#include <docsh.hxx> + +#include <stlpool.hxx> +#include <global.hxx> +#include <viewdata.hxx> +#include <tabvwsh.hxx> +#include <tablink.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <scmod.hxx> +#include <compiler.hxx> +#include <interpre.hxx> +#include <formulaopt.hxx> + +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +#include <memory> +#include <utility> + +namespace { + +struct ScStylePair +{ + SfxStyleSheetBase *pSource; + SfxStyleSheetBase *pDest; +}; + +} + +// Ole + +void ScDocShell::SetVisArea( const tools::Rectangle & rVisArea ) +{ + // with the SnapVisArea call in SetVisAreaOrSize, it's safe to always + // use both the size and position of the VisArea + SetVisAreaOrSize( rVisArea ); +} + +static void lcl_SetTopRight( tools::Rectangle& rRect, const Point& rPos ) +{ + Size aSize = rRect.GetSize(); + rRect.SetRight( rPos.X() ); + rRect.SetLeft( rPos.X() - aSize.Width() + 1 ); + rRect.SetTop( rPos.Y() ); + rRect.SetBottom( rPos.Y() + aSize.Height() - 1 ); +} + +void ScDocShell::SetVisAreaOrSize( const tools::Rectangle& rVisArea ) +{ + bool bNegativePage = m_pDocument->IsNegativePage( m_pDocument->GetVisibleTab() ); + + tools::Rectangle aArea = rVisArea; + // when loading, don't check for negative values, because the sheet orientation + // might be set later + if ( !m_pDocument->IsImportingXML() ) + { + if ( ( bNegativePage ? (aArea.Right() > 0) : (aArea.Left() < 0) ) || aArea.Top() < 0 ) + { + // VisArea start position can't be negative. + // Move the VisArea, otherwise only the upper left position would + // be changed in SnapVisArea, and the size would be wrong. + + Point aNewPos( 0, std::max( aArea.Top(), tools::Long(0) ) ); + if ( bNegativePage ) + { + aNewPos.setX( std::min( aArea.Right(), tools::Long(0) ) ); + lcl_SetTopRight( aArea, aNewPos ); + } + else + { + aNewPos.setX( std::max( aArea.Left(), tools::Long(0) ) ); + aArea.SetPos( aNewPos ); + } + } + } + + // adjust position here! + + // when loading an ole object, the VisArea is set from the document's + // view settings and must be used as-is (document content may not be complete yet). + if ( !m_pDocument->IsImportingXML() ) + SnapVisArea( aArea ); + + //TODO/LATER: it's unclear which IPEnv is used here + /* + SvInPlaceEnvironment* pEnv = GetIPEnv(); + if (pEnv) + { + vcl::Window* pWin = pEnv->GetEditWin(); + pEnv->MakeScale( aArea.GetSize(), MapUnit::Map100thMM, + pWin->LogicToPixel( aArea.GetSize() ) ); + } */ + + //TODO/LATER: formerly in SvInplaceObject + SfxObjectShell::SetVisArea( aArea ); + + if (m_bIsInplace) // adjust zoom in the InPlace View + { + ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell(); + if (pViewSh) + { + if (pViewSh->GetViewData().GetDocShell() == this) + pViewSh->UpdateOleZoom(); + } + } + + if (!m_pDocument->IsEmbedded()) + return; + + ScRange aOld; + m_pDocument->GetEmbedded( aOld); + m_pDocument->SetEmbedded( m_pDocument->GetVisibleTab(), aArea ); + ScRange aNew; + m_pDocument->GetEmbedded( aNew); + if (aOld != aNew) + PostPaint(0,0,0,m_pDocument->MaxCol(),m_pDocument->MaxRow(),MAXTAB,PaintPartFlags::Grid); + + //TODO/LATER: currently not implemented + //ViewChanged( ASPECT_CONTENT ); // show in the container as well +} + +bool ScDocShell::IsOle() const +{ + return (GetCreateMode() == SfxObjectCreateMode::EMBEDDED); +} + +void ScDocShell::UpdateOle(const ScViewData& rViewData, bool bSnapSize) +{ + // if it isn't Ole at all, one can be spared the calculations + // (VisArea will then be reset at the save) + + if (GetCreateMode() == SfxObjectCreateMode::STANDARD) + return; + + tools::Rectangle aOldArea = SfxObjectShell::GetVisArea(); + tools::Rectangle aNewArea = aOldArea; + + bool bEmbedded = m_pDocument->IsEmbedded(); + if (bEmbedded) + aNewArea = m_pDocument->GetEmbeddedRect(); + else + { + SCTAB nTab = rViewData.GetTabNo(); + if ( nTab != m_pDocument->GetVisibleTab() ) + m_pDocument->SetVisibleTab( nTab ); + + bool bNegativePage = m_pDocument->IsNegativePage( nTab ); + SCCOL nX = rViewData.GetPosX(SC_SPLIT_LEFT); + if ( nX != m_pDocument->GetPosLeft() ) + m_pDocument->SetPosLeft( nX ); + SCROW nY = rViewData.GetPosY(SC_SPLIT_BOTTOM); + if ( nY != m_pDocument->GetPosTop() ) + m_pDocument->SetPosTop( nY ); + tools::Rectangle aMMRect = m_pDocument->GetMMRect( nX,nY, nX,nY, nTab ); + if (bNegativePage) + lcl_SetTopRight( aNewArea, aMMRect.TopRight() ); + else + aNewArea.SetPos( aMMRect.TopLeft() ); + if (bSnapSize) + SnapVisArea(aNewArea); // uses the new VisibleTab + } + + if (aNewArea != aOldArea) + SetVisAreaOrSize( aNewArea ); // the start must also be adjusted here +} + +// Style stuff for Organizer, etc. + +SfxStyleSheetBasePool* ScDocShell::GetStyleSheetPool() +{ + return static_cast<SfxStyleSheetBasePool*>(m_pDocument->GetStyleSheetPool()); +} + +// After loading styles from another document (LoadStyles, Insert), the SetItems +// (ATTR_PAGE_HEADERSET, ATTR_PAGE_FOOTERSET) must be converted to the correct pool +// before the source pool is deleted. + +static void lcl_AdjustPool( SfxStyleSheetBasePool* pStylePool ) +{ + SfxStyleSheetBase *pStyle = pStylePool->First(SfxStyleFamily::Page); + while ( pStyle ) + { + SfxItemSet& rStyleSet = pStyle->GetItemSet(); + + if (const SvxSetItem* pItem = rStyleSet.GetItemIfSet(ATTR_PAGE_HEADERSET,false)) + { + const SfxItemSet& rSrcSet = pItem->GetItemSet(); + SfxItemSet aDestSet(*rStyleSet.GetPool(),rSrcSet.GetRanges()); + aDestSet.Put(rSrcSet); + rStyleSet.Put(SvxSetItem(ATTR_PAGE_HEADERSET, std::move(aDestSet))); + } + if (const SvxSetItem* pItem = rStyleSet.GetItemIfSet(ATTR_PAGE_FOOTERSET,false)) + { + const SfxItemSet& rSrcSet = pItem->GetItemSet(); + SfxItemSet aDestSet(*rStyleSet.GetPool(),rSrcSet.GetRanges()); + aDestSet.Put(rSrcSet); + rStyleSet.Put(SvxSetItem(ATTR_PAGE_FOOTERSET, std::move(aDestSet))); + } + + pStyle = pStylePool->Next(); + } +} + +void ScDocShell::LoadStyles( SfxObjectShell &rSource ) +{ + m_pDocument->StylesToNames(); + + SfxObjectShell::LoadStyles(rSource); + lcl_AdjustPool( GetStyleSheetPool() ); // adjust SetItems + + m_pDocument->UpdStlShtPtrsFrmNms(); + + UpdateAllRowHeights(); + + // Paint + + PostPaint( 0,0,0, m_pDocument->MaxCol(),m_pDocument->MaxRow(),MAXTAB, PaintPartFlags::Grid | PaintPartFlags::Left ); +} + +void ScDocShell::LoadStylesArgs( ScDocShell& rSource, bool bReplace, bool bCellStyles, bool bPageStyles ) +{ + // similar to LoadStyles, but with selectable behavior for XStyleLoader::loadStylesFromURL call + + if ( !bCellStyles && !bPageStyles ) // nothing to do + return; + + ScStyleSheetPool* pSourcePool = rSource.GetDocument().GetStyleSheetPool(); + ScStyleSheetPool* pDestPool = m_pDocument->GetStyleSheetPool(); + + SfxStyleFamily eFamily = bCellStyles ? + ( bPageStyles ? SfxStyleFamily::All : SfxStyleFamily::Para ) : + SfxStyleFamily::Page; + SfxStyleSheetIterator aIter( pSourcePool, eFamily ); + sal_uInt16 nSourceCount = aIter.Count(); + if ( nSourceCount == 0 ) + return; // no source styles + + std::unique_ptr<ScStylePair[]> pStyles(new ScStylePair[ nSourceCount ]); + sal_uInt16 nFound = 0; + + // first create all new styles + + SfxStyleSheetBase* pSourceStyle = aIter.First(); + while (pSourceStyle) + { + OUString aName = pSourceStyle->GetName(); + SfxStyleSheetBase* pDestStyle = pDestPool->Find( pSourceStyle->GetName(), pSourceStyle->GetFamily() ); + if ( pDestStyle ) + { + // touch existing styles only if replace flag is set + if ( bReplace ) + { + pStyles[nFound].pSource = pSourceStyle; + pStyles[nFound].pDest = pDestStyle; + ++nFound; + } + } + else + { + pStyles[nFound].pSource = pSourceStyle; + pStyles[nFound].pDest = &pDestPool->Make( aName, pSourceStyle->GetFamily(), pSourceStyle->GetMask() ); + ++nFound; + } + + pSourceStyle = aIter.Next(); + } + + // then copy contents (after inserting all styles, for parent etc.) + + for ( sal_uInt16 i = 0; i < nFound; ++i ) + { + pStyles[i].pDest->GetItemSet().PutExtended( + pStyles[i].pSource->GetItemSet(), SfxItemState::DONTCARE, SfxItemState::DEFAULT); + if(pStyles[i].pSource->HasParentSupport()) + pStyles[i].pDest->SetParent(pStyles[i].pSource->GetParent()); + // follow is never used + } + + lcl_AdjustPool( GetStyleSheetPool() ); // adjust SetItems + UpdateAllRowHeights(); + PostPaint( 0,0,0, m_pDocument->MaxCol(),m_pDocument->MaxRow(),MAXTAB, PaintPartFlags::Grid | PaintPartFlags::Left ); // Paint +} + +void ScDocShell::ReconnectDdeLink(SfxObjectShell& rServer) +{ + ::sfx2::LinkManager* pLinkManager = m_pDocument->GetLinkManager(); + if (!pLinkManager) + return; + + pLinkManager->ReconnectDdeLink(rServer); +} + +void ScDocShell::UpdateLinks() +{ + typedef std::unordered_set<OUString> StrSetType; + + sfx2::LinkManager* pLinkManager = m_pDocument->GetLinkManager(); + StrSetType aNames; + + // out with the no longer used links + + size_t nCount = pLinkManager->GetLinks().size(); + for (size_t k=nCount; k>0; ) + { + --k; + ::sfx2::SvBaseLink* pBase = pLinkManager->GetLinks()[k].get(); + if (ScTableLink* pTabLink = dynamic_cast<ScTableLink*>(pBase)) + { + if (pTabLink->IsUsed()) + aNames.insert(pTabLink->GetFileName()); + else // no longer used -> delete + { + pTabLink->SetAddUndo(true); + pLinkManager->Remove(k); + } + } + } + + // enter new links + + SCTAB nTabCount = m_pDocument->GetTableCount(); + for (SCTAB i = 0; i < nTabCount; ++i) + { + if (!m_pDocument->IsLinked(i)) + continue; + + OUString aDocName = m_pDocument->GetLinkDoc(i); + OUString aFltName = m_pDocument->GetLinkFlt(i); + OUString aOptions = m_pDocument->GetLinkOpt(i); + sal_uLong nRefresh = m_pDocument->GetLinkRefreshDelay(i); + bool bThere = false; + for (SCTAB j = 0; j < i && !bThere; ++j) // several times in the document? + { + if (m_pDocument->IsLinked(j) + && m_pDocument->GetLinkDoc(j) == aDocName + && m_pDocument->GetLinkFlt(j) == aFltName + && m_pDocument->GetLinkOpt(j) == aOptions) + // Ignore refresh delay in compare, it should be the + // same for identical links and we don't want dupes + // if it ain't. + bThere = true; + } + + if (!bThere) // already entered as filter? + { + if (!aNames.insert(aDocName).second) + bThere = true; + } + + if (!bThere) + { + ScTableLink* pLink = new ScTableLink( this, aDocName, aFltName, aOptions, nRefresh ); + pLink->SetInCreate(true); + pLinkManager->InsertFileLink(*pLink, sfx2::SvBaseLinkObjectType::ClientFile, aDocName, &aFltName); + pLink->Update(); + pLink->SetInCreate(false); + } + } +} + +void ScDocShell::ReloadTabLinks() +{ + sfx2::LinkManager* pLinkManager = m_pDocument->GetLinkManager(); + + bool bAny = false; + size_t nCount = pLinkManager->GetLinks().size(); + for (size_t i=0; i<nCount; i++ ) + { + ::sfx2::SvBaseLink* pBase = pLinkManager->GetLinks()[i].get(); + if (ScTableLink* pTabLink = dynamic_cast<ScTableLink*>(pBase)) + { +// pTabLink->SetAddUndo(sal_False); //! merge Undos + + // Painting only after Update() makes no sense: + // ScTableLink::Refresh() will post a Paint only is bDoPaint is true + // pTabLink->SetPaint(false); // Paint only once at the end + pTabLink->Update(); + //pTabLink->SetPaint(true); +// pTabLink->SetAddUndo(sal_True); + bAny = true; + } + } + + if ( bAny ) + { + // Paint only once + PostPaint( ScRange(0,0,0,m_pDocument->MaxCol(),m_pDocument->MaxRow(),MAXTAB), + PaintPartFlags::Grid | PaintPartFlags::Top | PaintPartFlags::Left ); + + SetDocumentModified(); + } +} + +void ScDocShell::SetFormulaOptions( const ScFormulaOptions& rOpt, bool bForLoading ) +{ + m_pDocument->SetGrammar( rOpt.GetFormulaSyntax() ); + + // This is nasty because it resets module globals from within a docshell! + // For actual damage caused see fdo#82183 where an unconditional + // ScGlobal::ResetFunctionList() (without checking GetUseEnglishFuncName()) + // lead to a crash because the function list was still used by the Formula + // Wizard when loading the second document. + // Do the stupid stuff only when we're not called while loading a document. + + /* TODO: bForLoading is a workaround, rather get rid of setting any + * globals from per document instances like ScDocShell. */ + + /* XXX this is utter crap, we rely on the options being set here at least + * once, for the very first document, empty or loaded. */ + static bool bInitOnce = true; + + if (!bForLoading || bInitOnce) + { + bool bForceInit = bInitOnce; + bInitOnce = false; + if (bForceInit || rOpt.GetUseEnglishFuncName() != SC_MOD()->GetFormulaOptions().GetUseEnglishFuncName()) + { + // This needs to be called first since it may re-initialize the entire + // opcode map. + if (rOpt.GetUseEnglishFuncName()) + { + // switch native symbols to English. + ScAddress aAddress; + ScCompiler aComp( *m_pDocument, aAddress); + ScCompiler::OpCodeMapPtr xMap = aComp.GetOpCodeMap(css::sheet::FormulaLanguage::ENGLISH); + ScCompiler::SetNativeSymbols(xMap); + } + else + // re-initialize native symbols with localized function names. + ScCompiler::ResetNativeSymbols(); + + // Force re-population of function names for the function wizard, function tip etc. + ScGlobal::ResetFunctionList(); + } + + // Update the separators. + ScCompiler::UpdateSeparatorsNative( + rOpt.GetFormulaSepArg(), rOpt.GetFormulaSepArrayCol(), rOpt.GetFormulaSepArrayRow()); + + // Global interpreter settings. + ScInterpreter::SetGlobalConfig(rOpt.GetCalcConfig()); + } + + // Per document interpreter settings. + m_pDocument->SetCalcConfig( rOpt.GetCalcConfig() ); +} + +void ScDocShell::CheckConfigOptions() +{ + if (IsConfigOptionsChecked()) + // no need to check repeatedly. + return; + + OUString aDecSep = ScGlobal::getLocaleData().getNumDecimalSep(); + OUString aDecSepAlt = ScGlobal::getLocaleData().getNumDecimalSepAlt(); + + ScModule* pScMod = SC_MOD(); + const ScFormulaOptions& rOpt=pScMod->GetFormulaOptions(); + const OUString& aSepArg = rOpt.GetFormulaSepArg(); + const OUString& aSepArrRow = rOpt.GetFormulaSepArrayRow(); + const OUString& aSepArrCol = rOpt.GetFormulaSepArrayCol(); + + if (aDecSep == aSepArg || aDecSep == aSepArrRow || aDecSep == aSepArrCol || + aDecSepAlt == aSepArg || aDecSepAlt == aSepArrRow || aDecSepAlt == aSepArrCol) + { + // One of arg separators conflicts with the current decimal + // separator. Reset them to default. + ScFormulaOptions aNew = rOpt; + aNew.ResetFormulaSeparators(); + SetFormulaOptions(aNew); + pScMod->SetFormulaOptions(aNew); + + // Launch a nice warning dialog to let the users know of this change. + ScTabViewShell* pViewShell = GetBestViewShell(); + if (pViewShell) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pViewShell->GetFrameWeld(), + VclMessageType::Info, VclButtonsType::Ok, + ScResId(STR_OPTIONS_WARN_SEPARATORS))); + xInfoBox->run(); + } + + // For now, this is the only option setting that could launch info + // dialog. But in the future we may want to implement a nicer + // dialog to display a list of warnings in case we have several + // pieces of information to display. + } + + SetConfigOptionsChecked(true); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/docsh8.cxx b/sc/source/ui/docshell/docsh8.cxx new file mode 100644 index 000000000..683ac2cbe --- /dev/null +++ b/sc/source/ui/docshell/docsh8.cxx @@ -0,0 +1,1082 @@ +/* -*- 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 <config_features.h> + +#include <vcl/errinf.hxx> +#include <tools/urlobj.hxx> +#include <svl/converter.hxx> +#include <svl/numformat.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/propertysequence.hxx> +#include <comphelper/types.hxx> +#include <ucbhelper/content.hxx> +#include <svx/txenctab.hxx> +#include <unotools/sharedunocomponent.hxx> +#include <unotools/charclass.hxx> +#include <rtl/character.hxx> +#include <rtl/tencinfo.h> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <tools/diagnose_ex.h> +#include <o3tl/string_view.hxx> + +#include <com/sun/star/sdb/CommandType.hpp> +#include <com/sun/star/sdbc/DataType.hpp> +#include <com/sun/star/sdbc/SQLException.hpp> +#include <com/sun/star/sdbc/XConnection.hpp> +#include <com/sun/star/sdbc/DriverManager.hpp> +#include <com/sun/star/sdbc/XResultSetUpdate.hpp> +#include <com/sun/star/sdbc/XRow.hpp> +#include <com/sun/star/sdbc/XRowSet.hpp> +#include <com/sun/star/sdbc/XRowUpdate.hpp> +#include <com/sun/star/sdbc/XResultSetMetaDataSupplier.hpp> +#include <com/sun/star/sdbcx/XAppend.hpp> +#include <com/sun/star/sdbcx/XColumnsSupplier.hpp> +#include <com/sun/star/sdbcx/XDataDefinitionSupplier.hpp> +#include <com/sun/star/sdbcx/XDataDescriptorFactory.hpp> +#include <com/sun/star/sdbcx/XTablesSupplier.hpp> +#include <com/sun/star/lang/XMultiServiceFactory.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/ucb/NameClash.hpp> +#include <com/sun/star/ucb/TransferInfo.hpp> +#include <com/sun/star/ucb/XCommandInfo.hpp> + +#include <scerrors.hxx> +#include <docsh.hxx> +#include <progress.hxx> +#include <editutil.hxx> +#include <cellform.hxx> +#include <dbdocutl.hxx> +#include <dociter.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <svl/zformat.hxx> +#include <svl/intitem.hxx> +#include <patattr.hxx> +#include <scitems.hxx> +#include <docpool.hxx> +#include <segmenttree.hxx> +#include <docparam.hxx> +#include <cellvalue.hxx> + +#include <unordered_set> +#include <vector> + +using namespace com::sun::star; +using ::std::vector; + +#if HAVE_FEATURE_DBCONNECTIVITY + +constexpr OUStringLiteral SC_SERVICE_ROWSET = u"com.sun.star.sdb.RowSet"; + +//! move to a header file? +constexpr OUStringLiteral SC_DBPROP_ACTIVECONNECTION = u"ActiveConnection"; +constexpr OUStringLiteral SC_DBPROP_COMMAND = u"Command"; +constexpr OUStringLiteral SC_DBPROP_COMMANDTYPE = u"CommandType"; +constexpr OUStringLiteral SC_DBPROP_PROPCHANGE_NOTIFY = u"PropertyChangeNotificationEnabled"; + +constexpr OUStringLiteral SC_DBPROP_NAME = u"Name"; +constexpr OUStringLiteral SC_DBPROP_TYPE = u"Type"; +constexpr OUStringLiteral SC_DBPROP_PRECISION = u"Precision"; +constexpr OUStringLiteral SC_DBPROP_SCALE = u"Scale"; + +constexpr OUStringLiteral SC_DBPROP_EXTENSION = u"Extension"; +constexpr OUStringLiteral SC_DBPROP_CHARSET = u"CharSet"; + +namespace +{ + ErrCode lcl_getDBaseConnection(uno::Reference<sdbc::XDriverManager2>& _rDrvMgr, uno::Reference<sdbc::XConnection>& _rConnection, OUString& _rTabName, std::u16string_view rFullFileName, rtl_TextEncoding eCharSet) + { + INetURLObject aURL; + aURL.SetSmartProtocol( INetProtocol::File ); + aURL.SetSmartURL( rFullFileName ); + _rTabName = aURL.getBase( INetURLObject::LAST_SEGMENT, true, + INetURLObject::DecodeMechanism::Unambiguous ); + OUString aExtension = aURL.getExtension(); + aURL.removeSegment(); + aURL.removeFinalSlash(); + OUString aPath = aURL.GetMainURL(INetURLObject::DecodeMechanism::NONE); + uno::Reference<uno::XComponentContext> xContext = comphelper::getProcessComponentContext(); + + _rDrvMgr.set( sdbc::DriverManager::create( xContext ) ); + + // get connection + + const OUString aConnUrl{"sdbc:dbase:" + aPath}; + + // sdbc:dbase is based on the css.sdbc.FILEConnectionProperties UNOIDL service, so we can + // transport the raw rtl_TextEncoding value instead of having to translate it into an IANA + // character set name string (which might not exist for certain eCharSet values, like + // RTL_TEXTENCODING_MS_950): + uno::Sequence<beans::PropertyValue> aProps( comphelper::InitPropertySequence({ + { SC_DBPROP_EXTENSION, uno::Any(aExtension) }, + { SC_DBPROP_CHARSET, uno::Any(eCharSet) } + })); + + _rConnection = _rDrvMgr->getConnectionWithInfo( aConnUrl, aProps ); + return ERRCODE_NONE; + } +} + +#endif // HAVE_FEATURE_DBCONNECTIVITY + +// MoveFile/KillFile/IsDocument: similar to SfxContentHelper + +bool ScDocShell::MoveFile( const INetURLObject& rSourceObj, const INetURLObject& rDestObj ) +{ + bool bMoveData = true; + bool bRet = true, bKillSource = false; + if ( rSourceObj.GetProtocol() != rDestObj.GetProtocol() ) + { + bMoveData = false; + bKillSource = true; + } + OUString aName = rDestObj.getName(); + INetURLObject aDestPathObj = rDestObj; + aDestPathObj.removeSegment(); + aDestPathObj.setFinalSlash(); + + try + { + ::ucbhelper::Content aDestPath( aDestPathObj.GetMainURL(INetURLObject::DecodeMechanism::NONE), + uno::Reference< css::ucb::XCommandEnvironment >(), + comphelper::getProcessComponentContext() ); + uno::Reference< css::ucb::XCommandInfo > xInfo = aDestPath.getCommands(); + OUString aTransferName = "transfer"; + if ( xInfo->hasCommandByName( aTransferName ) ) + { + aDestPath.executeCommand( aTransferName, uno::Any( + css::ucb::TransferInfo( bMoveData, rSourceObj.GetMainURL(INetURLObject::DecodeMechanism::NONE), aName, + css::ucb::NameClash::ERROR ) ) ); + } + else + { + OSL_FAIL( "transfer command not available" ); + } + } + catch( uno::Exception& ) + { + // ucb may throw different exceptions on failure now + bRet = false; + } + + if ( bKillSource ) + KillFile( rSourceObj ); + + return bRet; +} + +bool ScDocShell::KillFile( const INetURLObject& rURL ) +{ + bool bRet = true; + try + { + ::ucbhelper::Content aCnt( rURL.GetMainURL(INetURLObject::DecodeMechanism::NONE), + uno::Reference< css::ucb::XCommandEnvironment >(), + comphelper::getProcessComponentContext() ); + aCnt.executeCommand( "delete", css::uno::Any( true ) ); + } + catch( uno::Exception& ) + { + // ucb may throw different exceptions on failure now + bRet = false; + } + + return bRet; +} + +bool ScDocShell::IsDocument( const INetURLObject& rURL ) +{ + bool bRet = false; + try + { + ::ucbhelper::Content aCnt( rURL.GetMainURL(INetURLObject::DecodeMechanism::NONE), + uno::Reference< css::ucb::XCommandEnvironment >(), + comphelper::getProcessComponentContext() ); + bRet = aCnt.isDocument(); + } + catch( uno::Exception& ) + { + // ucb may throw different exceptions on failure now - warning only + TOOLS_WARN_EXCEPTION( "sc", "Any other exception" ); + } + + return bRet; +} + +#if HAVE_FEATURE_DBCONNECTIVITY + +static void lcl_setScalesToColumns(ScDocument& rDoc, const vector<tools::Long>& rScales) +{ + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + if (!pFormatter) + return; + + SCCOL nColCount = static_cast<SCCOL>(rScales.size()); + for (SCCOL i = 0; i < nColCount; ++i) + { + if (rScales[i] < 0) + continue; + + sal_uInt32 nOldFormat = rDoc.GetNumberFormat(i, 0, 0); + const SvNumberformat* pOldEntry = pFormatter->GetEntry(nOldFormat); + if (!pOldEntry) + continue; + + LanguageType eLang = pOldEntry->GetLanguage(); + bool bThousand, bNegRed; + sal_uInt16 nPrecision, nLeading; + pOldEntry->GetFormatSpecialInfo(bThousand, bNegRed, nPrecision, nLeading); + + nPrecision = static_cast<sal_uInt16>(rScales[i]); + OUString aNewPicture = pFormatter->GenerateFormat(nOldFormat, eLang, + bThousand, bNegRed, nPrecision, nLeading); + + sal_uInt32 nNewFormat = pFormatter->GetEntryKey(aNewPicture, eLang); + if (nNewFormat == NUMBERFORMAT_ENTRY_NOT_FOUND) + { + sal_Int32 nErrPos = 0; + SvNumFormatType nNewType = SvNumFormatType::ALL; + bool bOk = pFormatter->PutEntry( + aNewPicture, nErrPos, nNewType, nNewFormat, eLang); + + if (!bOk) + continue; + } + + ScPatternAttr aNewAttrs( rDoc.GetPool() ); + SfxItemSet& rSet = aNewAttrs.GetItemSet(); + rSet.Put( SfxUInt32Item(ATTR_VALUE_FORMAT, nNewFormat) ); + rDoc.ApplyPatternAreaTab(i, 0, i, rDoc.MaxRow(), 0, aNewAttrs); + } +} + +#endif // HAVE_FEATURE_DBCONNECTIVITY + +ErrCode ScDocShell::DBaseImport( const OUString& rFullFileName, rtl_TextEncoding eCharSet, + std::map<SCCOL, ScColWidthParam>& aColWidthParam, ScFlatBoolRowSegments& rRowHeightsRecalc ) +{ +#if !HAVE_FEATURE_DBCONNECTIVITY + (void) rFullFileName; + (void) eCharSet; + (void) aColWidthParam; + (void) rRowHeightsRecalc; + + return ERRCODE_IO_GENERAL; +#else + + ErrCode nErr = ERRCODE_NONE; + + try + { + tools::Long i; + sal_Int32 nColCount = 0; + OUString aTabName; + uno::Reference<sdbc::XDriverManager2> xDrvMan; + uno::Reference<sdbc::XConnection> xConnection; + ErrCode nRet = lcl_getDBaseConnection(xDrvMan,xConnection,aTabName,rFullFileName,eCharSet); + if ( !xConnection.is() || !xDrvMan.is() ) + return nRet; + ::utl::DisposableComponent aConnectionHelper(xConnection); + + ScProgress aProgress( this, ScResId( STR_LOAD_DOC ), 0, true ); + uno::Reference<lang::XMultiServiceFactory> xFactory = comphelper::getProcessServiceFactory(); + uno::Reference<sdbc::XRowSet> xRowSet( xFactory->createInstance(SC_SERVICE_ROWSET), + uno::UNO_QUERY); + ::utl::DisposableComponent aRowSetHelper(xRowSet); + uno::Reference<beans::XPropertySet> xRowProp( xRowSet, uno::UNO_QUERY ); + OSL_ENSURE( xRowProp.is(), "can't get RowSet" ); + if (!xRowProp.is()) return SCERR_IMPORT_CONNECT; + + xRowProp->setPropertyValue( SC_DBPROP_ACTIVECONNECTION, uno::Any(xConnection) ); + + xRowProp->setPropertyValue( SC_DBPROP_COMMANDTYPE, uno::Any(sdb::CommandType::TABLE) ); + + xRowProp->setPropertyValue( SC_DBPROP_COMMAND, uno::Any(aTabName) ); + + xRowProp->setPropertyValue( SC_DBPROP_PROPCHANGE_NOTIFY, uno::Any(false) ); + + xRowSet->execute(); + + uno::Reference<sdbc::XResultSetMetaData> xMeta; + uno::Reference<sdbc::XResultSetMetaDataSupplier> xMetaSupp( xRowSet, uno::UNO_QUERY ); + if ( xMetaSupp.is() ) + xMeta = xMetaSupp->getMetaData(); + if ( xMeta.is() ) + nColCount = xMeta->getColumnCount(); // this is the number of real columns + + if ( nColCount > m_pDocument->MaxCol()+1 ) + { + nColCount = m_pDocument->MaxCol()+1; + nErr = SCWARN_IMPORT_COLUMN_OVERFLOW; // warning + } + + uno::Reference<sdbc::XRow> xRow( xRowSet, uno::UNO_QUERY ); + OSL_ENSURE( xRow.is(), "can't get Row" ); + if (!xRow.is()) return SCERR_IMPORT_CONNECT; + + // currency flag is not needed for dBase + uno::Sequence<sal_Int32> aColTypes( nColCount ); // column types + sal_Int32* pTypeArr = aColTypes.getArray(); + for (i=0; i<nColCount; i++) + pTypeArr[i] = xMeta->getColumnType( i+1 ); + + // read column names + //! add type descriptions + + aProgress.SetState( 0 ); + + vector<tools::Long> aScales(nColCount, -1); + for (i=0; i<nColCount; i++) + { + OUString aHeader = xMeta->getColumnLabel( i+1 ); + + switch ( pTypeArr[i] ) + { + case sdbc::DataType::BIT: + aHeader += ",L"; + break; + case sdbc::DataType::DATE: + aHeader += ",D"; + break; + case sdbc::DataType::LONGVARCHAR: + aHeader += ",M"; + break; + case sdbc::DataType::VARCHAR: + aHeader += ",C," + OUString::number( xMeta->getColumnDisplaySize( i+1 ) ); + break; + case sdbc::DataType::DECIMAL: + { + tools::Long nPrec = xMeta->getPrecision( i+1 ); + tools::Long nScale = xMeta->getScale( i+1 ); + aHeader += ",N," + + OUString::number( + SvDbaseConverter::ConvertPrecisionToDbase( + nPrec, nScale ) ) + + "," + + OUString::number( nScale ); + aScales[i] = nScale; + } + break; + } + + m_pDocument->SetString( static_cast<SCCOL>(i), 0, 0, aHeader ); + } + + lcl_setScalesToColumns(*m_pDocument, aScales); + + SCROW nRow = 1; // 0 is column titles + bool bEnd = false; + while ( !bEnd && xRowSet->next() ) + { + if ( nRow <= m_pDocument->MaxRow() ) + { + bool bSimpleRow = true; + SCCOL nCol = 0; + for (i=0; i<nColCount; i++) + { + ScDatabaseDocUtil::StrData aStrData; + ScDatabaseDocUtil::PutData( *m_pDocument, nCol, nRow, 0, + xRow, i+1, pTypeArr[i], false, + &aStrData ); + + if (aStrData.mnStrLength > aColWidthParam[nCol].mnMaxTextLen) + { + aColWidthParam[nCol].mnMaxTextLen = aStrData.mnStrLength; + aColWidthParam[nCol].mnMaxTextRow = nRow; + } + + if (!aStrData.mbSimpleText) + { + bSimpleRow = false; + aColWidthParam[nCol].mbSimpleText = false; + } + + ++nCol; + } + if (!bSimpleRow) + rRowHeightsRecalc.setTrue(nRow, nRow); + ++nRow; + } + else // past the end of the spreadsheet + { + bEnd = true; // don't continue + nErr = SCWARN_IMPORT_RANGE_OVERFLOW; // warning message + } + } + } + catch ( sdbc::SQLException& ) + { + nErr = SCERR_IMPORT_CONNECT; + } + catch ( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "sc", "Unexpected exception in database"); + nErr = ERRCODE_IO_GENERAL; + } + + return nErr; +#endif // HAVE_FEATURE_DBCONNECTIVITY +} + +#if HAVE_FEATURE_DBCONNECTIVITY + +namespace { + +void lcl_GetColumnTypes( + ScDocShell& rDocShell, const ScRange& rDataRange, bool bHasFieldNames, + OUString* pColNames, sal_Int32* pColTypes, sal_Int32* pColLengths, + sal_Int32* pColScales, bool& bHasMemo, rtl_TextEncoding eCharSet ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + SvNumberFormatter* pNumFmt = rDoc.GetFormatTable(); + + SCTAB nTab = rDataRange.aStart.Tab(); + SCCOL nFirstCol = rDataRange.aStart.Col(); + SCROW nFirstRow = rDataRange.aStart.Row(); + SCCOL nLastCol = rDataRange.aEnd.Col(); + SCROW nLastRow = rDataRange.aEnd.Row(); + + typedef std::unordered_set<OUString> StrSetType; + StrSetType aFieldNames; + + tools::Long nField = 0; + SCROW nFirstDataRow = ( bHasFieldNames ? nFirstRow + 1 : nFirstRow ); + for ( SCCOL nCol = nFirstCol; nCol <= nLastCol; nCol++ ) + { + bool bTypeDefined = false; + bool bPrecDefined = false; + sal_Int32 nFieldLen = 0; + sal_Int32 nPrecision = 0; + sal_Int32 nDbType = sdbc::DataType::SQLNULL; + OUString aFieldName; + + // Fieldname[,Type[,Width[,Prec]]] + // Type etc.: L; D; C[,W]; N[,W[,P]] + if ( bHasFieldNames ) + { + OUString aString {rDoc.GetString(nCol, nFirstRow, nTab).toAsciiUpperCase()}; + sal_Int32 nIdx {0}; + aFieldName = aString.getToken( 0, ',', nIdx); + if ( nIdx>0 ) + { + aString = aString.replaceAll(" ", ""); + switch ( o3tl::getToken(aString, 0, ',', nIdx )[0] ) + { + case 'L' : + nDbType = sdbc::DataType::BIT; + nFieldLen = 1; + bTypeDefined = true; + bPrecDefined = true; + break; + case 'D' : + nDbType = sdbc::DataType::DATE; + nFieldLen = 8; + bTypeDefined = true; + bPrecDefined = true; + break; + case 'M' : + nDbType = sdbc::DataType::LONGVARCHAR; + nFieldLen = 10; + bTypeDefined = true; + bPrecDefined = true; + bHasMemo = true; + break; + case 'C' : + nDbType = sdbc::DataType::VARCHAR; + bTypeDefined = true; + bPrecDefined = true; + break; + case 'N' : + nDbType = sdbc::DataType::DECIMAL; + bTypeDefined = true; + break; + } + if ( bTypeDefined && !nFieldLen && nIdx>0 ) + { + nFieldLen = o3tl::toInt32(o3tl::getToken(aString, 0, ',', nIdx )); + if ( !bPrecDefined && nIdx>0 ) + { + OUString aTmp( aString.getToken( 0, ',', nIdx ) ); + if ( CharClass::isAsciiNumeric(aTmp) ) + { + nPrecision = aTmp.toInt32(); + if (nPrecision && nFieldLen < nPrecision+1) + nFieldLen = nPrecision + 1; // include decimal separator + bPrecDefined = true; + } + } + } + } + + // Check field name and generate valid field name if necessary. + // First character has to be alphabetical, subsequent characters + // have to be alphanumerical or underscore. + // "_DBASELOCK" is reserved (obsolete because first character is + // not alphabetical). + // No duplicated names. + if ( !rtl::isAsciiAlpha(aFieldName[0]) ) + aFieldName = "N" + aFieldName; + OUStringBuffer aTmpStr; + sal_Unicode c; + for ( const sal_Unicode* p = aFieldName.getStr(); ( c = *p ) != 0; p++ ) + { + if ( rtl::isAsciiAlpha(c) || rtl::isAsciiDigit(c) || c == '_' ) + aTmpStr.append(c); + else + aTmpStr.append("_"); + } + aFieldName = aTmpStr.makeStringAndClear(); + if ( aFieldName.getLength() > 10 ) + aFieldName = aFieldName.copy(0, 10); + + if (!aFieldNames.insert(aFieldName).second) + { // Duplicated field name, append numeric suffix. + sal_uInt16 nSub = 1; + OUString aFixPart( aFieldName ); + do + { + ++nSub; + OUString aVarPart = OUString::number( nSub ); + if ( aFixPart.getLength() + aVarPart.getLength() > 10 ) + aFixPart = aFixPart.copy( 0, 10 - aVarPart.getLength() ); + aFieldName = aFixPart + aVarPart; + } while (!aFieldNames.insert(aFieldName).second); + } + } + else + { + aFieldName = "N" + OUString::number(nCol+1); + } + + if ( !bTypeDefined ) + { // Field type. + ScRefCellValue aCell(rDoc, ScAddress(nCol, nFirstDataRow, nTab)); + if (aCell.isEmpty() || aCell.hasString()) + nDbType = sdbc::DataType::VARCHAR; + else + { + sal_uInt32 nFormat = rDoc.GetNumberFormat( nCol, nFirstDataRow, nTab ); + switch ( pNumFmt->GetType( nFormat ) ) + { + case SvNumFormatType::LOGICAL : + nDbType = sdbc::DataType::BIT; + nFieldLen = 1; + break; + case SvNumFormatType::DATE : + nDbType = sdbc::DataType::DATE; + nFieldLen = 8; + break; + case SvNumFormatType::TIME : + case SvNumFormatType::DATETIME : + nDbType = sdbc::DataType::VARCHAR; + break; + default: + nDbType = sdbc::DataType::DECIMAL; + } + } + } + // Field length. + if ( nDbType == sdbc::DataType::VARCHAR && !nFieldLen ) + { // Determine maximum field width. + nFieldLen = rDoc.GetMaxStringLen( nTab, nCol, nFirstDataRow, + nLastRow, eCharSet ); + if ( nFieldLen == 0 ) + nFieldLen = 1; + } + else if ( nDbType == sdbc::DataType::DECIMAL ) + { // Determine maximum field width and precision. + sal_Int32 nLen; + sal_uInt16 nPrec; + nLen = rDoc.GetMaxNumberStringLen( nPrec, nTab, nCol, + nFirstDataRow, nLastRow ); + // dBaseIII precision limit: 15 + if ( nPrecision > 15 ) + nPrecision = 15; + if ( nPrec > 15 ) + nPrec = 15; + if ( bPrecDefined && nPrecision != nPrec ) + { + if (nPrecision < nPrec) + { + // This is a hairy case. User defined nPrecision but a + // number format has more precision. Modifying a dBase + // field may as well render the resulting file useless for + // an application that relies on its defined structure, + // especially if we are resaving an already existing file. + // So who's right, the user who (or the loaded file that) + // defined the field, or the user who applied the format? + // Commit f59e350d1733125055f1144f8b3b1b0a46f6d1ca gave the + // format a higher priority, which is debatable. + SAL_WARN( "sc", "lcl_GetColumnTypes: conflicting dBase field precision for " + << aFieldName << " (" << nPrecision << "<" << nPrec << ")"); + + // Adjust length to larger predefined integer part. There + // may be a reason that the field was prepared for larger + // numbers. + if (nFieldLen - nPrecision > nLen - nPrec) + nLen = nFieldLen - (nPrecision ? nPrecision+1 : 0) + 1 + nPrec; + // And override precision. + nPrecision = nPrec; + } + else + { +#if 1 + // Adjust length to predefined precision. + nLen = nLen + ( nPrecision - nPrec ); +#else + /* If the above override for (nPrecision < nPrec) was not in place then + * nPrecision could be 0 and this would be the code path to correctly + * calculate nLen. But as is, nPrecision is never 0 here, see CID#982304 */ + + // Adjust length to predefined precision. + if ( nPrecision ) + nLen = nLen + ( nPrecision - nPrec ); + else + nLen -= nPrec+1; // also remove the decimal separator +#endif + } + } + if (nFieldLen < nLen) + { + if (!bTypeDefined) + nFieldLen = nLen; + else + { + // Again a hairy case and conflict. Furthermore, the + // larger overall length may be a result of only a higher + // precision obtained from formats. + SAL_WARN( "sc", "lcl_GetColumnTypes: conflicting dBase field length for " + << aFieldName << " (" << nFieldLen << "<" << nLen << ")"); + nFieldLen = nLen; + } + } + if ( !bPrecDefined ) + nPrecision = nPrec; + if ( nFieldLen == 0 ) + nFieldLen = 1; + else if ( nFieldLen > 19 ) + nFieldLen = 19; // dBaseIII numeric field length limit: 19 + if ( nPrecision && nFieldLen < nPrecision + 2 ) + nFieldLen = nPrecision + 2; // 0. must fit into + // 538 MUST: Sdb internal representation adds 2 to the field length! + // To give the user what he wants we must subtract it here. + //! CAVEAT! There is no way to define a numeric field with a length + //! of 1 and no decimals! + nFieldLen = SvDbaseConverter::ConvertPrecisionToOdbc( nFieldLen, nPrecision ); + } + if ( nFieldLen > 254 ) + { + if ( nDbType == sdbc::DataType::VARCHAR ) + { // Too long for a normal text field => memo field. + nDbType = sdbc::DataType::LONGVARCHAR; + nFieldLen = 10; + bHasMemo = true; + } + else + nFieldLen = 254; // bad luck... + } + + pColNames[nField] = aFieldName; + pColTypes[nField] = nDbType; + pColLengths[nField] = nFieldLen; + pColScales[nField] = nPrecision; + + ++nField; + } +} + +void lcl_getLongVarCharEditString( OUString& rString, + const ScRefCellValue& rCell, ScFieldEditEngine& rEditEngine ) +{ + if (!rCell.mpEditText) + return; + + rEditEngine.SetTextCurrentDefaults(*rCell.mpEditText); + rString = rEditEngine.GetText( LINEEND_CRLF ); +} + +void lcl_getLongVarCharString( + OUString& rString, ScDocument& rDoc, SCCOL nCol, SCROW nRow, SCTAB nTab, SvNumberFormatter& rNumFmt ) +{ + const Color* pColor; + ScAddress aPos(nCol, nRow, nTab); + sal_uInt32 nFormat = rDoc.GetNumberFormat(aPos); + rString = ScCellFormat::GetString(rDoc, aPos, nFormat, &pColor, rNumFmt); +} + +} + +#endif // HAVE_FEATURE_DBCONNECTIVITY + +ErrCode ScDocShell::DBaseExport( const OUString& rFullFileName, rtl_TextEncoding eCharSet, bool& bHasMemo ) +{ +#if !HAVE_FEATURE_DBCONNECTIVITY + (void) rFullFileName; + (void) eCharSet; + (void) bHasMemo; + + return ERRCODE_IO_GENERAL; +#else + // remove the file so the dBase driver doesn't find an invalid file + INetURLObject aDeleteObj( rFullFileName, INetProtocol::File ); + KillFile( aDeleteObj ); + + ErrCode nErr = ERRCODE_NONE; + + SCCOL nFirstCol, nLastCol; + SCROW nFirstRow, nLastRow; + SCTAB nTab = GetSaveTab(); + m_pDocument->GetDataStart( nTab, nFirstCol, nFirstRow ); + m_pDocument->GetCellArea( nTab, nLastCol, nLastRow ); + if ( nFirstCol > nLastCol ) + nFirstCol = nLastCol; + if ( nFirstRow > nLastRow ) + nFirstRow = nLastRow; + ScProgress aProgress( this, ScResId( STR_SAVE_DOC ), + nLastRow - nFirstRow, true ); + SvNumberFormatter* pNumFmt = m_pDocument->GetFormatTable(); + + bool bHasFieldNames = true; + for ( SCCOL nDocCol = nFirstCol; nDocCol <= nLastCol && bHasFieldNames; nDocCol++ ) + { // only Strings in first row => are field names + if ( !m_pDocument->HasStringData( nDocCol, nFirstRow, nTab ) ) + bHasFieldNames = false; + } + + sal_Int32 nColCount = nLastCol - nFirstCol + 1; + uno::Sequence<OUString> aColNames( nColCount ); + uno::Sequence<sal_Int32> aColTypes( nColCount ); + uno::Sequence<sal_Int32> aColLengths( nColCount ); + uno::Sequence<sal_Int32> aColScales( nColCount ); + + ScRange aDataRange( nFirstCol, nFirstRow, nTab, nLastCol, nLastRow, nTab ); + lcl_GetColumnTypes( *this, aDataRange, bHasFieldNames, + aColNames.getArray(), aColTypes.getArray(), + aColLengths.getArray(), aColScales.getArray(), + bHasMemo, eCharSet ); + // also needed for exception catch + SCROW nDocRow = 0; + ScFieldEditEngine aEditEngine(m_pDocument.get(), m_pDocument->GetEditPool()); + OUString aString; + + try + { + uno::Reference<sdbc::XDriverManager2> xDrvMan; + uno::Reference<sdbc::XConnection> xConnection; + OUString aTabName; + ErrCode nRet = lcl_getDBaseConnection(xDrvMan,xConnection,aTabName,rFullFileName,eCharSet); + if ( !xConnection.is() || !xDrvMan.is() ) + return nRet; + ::utl::DisposableComponent aConnectionHelper(xConnection); + + // get dBase driver + uno::Reference< sdbcx::XDataDefinitionSupplier > xDDSup( xDrvMan->getDriverByURL( xConnection->getMetaData()->getURL() ), uno::UNO_QUERY ); + if ( !xDDSup.is() ) + return SCERR_EXPORT_CONNECT; + + // create table + uno::Reference<sdbcx::XTablesSupplier> xTablesSupp =xDDSup->getDataDefinitionByConnection( xConnection ); + OSL_ENSURE( xTablesSupp.is(), "can't get Data Definition" ); + if (!xTablesSupp.is()) return SCERR_EXPORT_CONNECT; + + uno::Reference<container::XNameAccess> xTables = xTablesSupp->getTables(); + OSL_ENSURE( xTables.is(), "can't get Tables" ); + if (!xTables.is()) return SCERR_EXPORT_CONNECT; + + uno::Reference<sdbcx::XDataDescriptorFactory> xTablesFact( xTables, uno::UNO_QUERY ); + OSL_ENSURE( xTablesFact.is(), "can't get tables factory" ); + if (!xTablesFact.is()) return SCERR_EXPORT_CONNECT; + + uno::Reference<sdbcx::XAppend> xTablesAppend( xTables, uno::UNO_QUERY ); + OSL_ENSURE( xTablesAppend.is(), "can't get tables XAppend" ); + if (!xTablesAppend.is()) return SCERR_EXPORT_CONNECT; + + uno::Reference<beans::XPropertySet> xTableDesc = xTablesFact->createDataDescriptor(); + OSL_ENSURE( xTableDesc.is(), "can't get table descriptor" ); + if (!xTableDesc.is()) return SCERR_EXPORT_CONNECT; + + xTableDesc->setPropertyValue( SC_DBPROP_NAME, uno::Any(aTabName) ); + + // create columns + + uno::Reference<sdbcx::XColumnsSupplier> xColumnsSupp( xTableDesc, uno::UNO_QUERY ); + OSL_ENSURE( xColumnsSupp.is(), "can't get columns supplier" ); + if (!xColumnsSupp.is()) return SCERR_EXPORT_CONNECT; + + uno::Reference<container::XNameAccess> xColumns = xColumnsSupp->getColumns(); + OSL_ENSURE( xColumns.is(), "can't get columns" ); + if (!xColumns.is()) return SCERR_EXPORT_CONNECT; + + uno::Reference<sdbcx::XDataDescriptorFactory> xColumnsFact( xColumns, uno::UNO_QUERY ); + OSL_ENSURE( xColumnsFact.is(), "can't get columns factory" ); + if (!xColumnsFact.is()) return SCERR_EXPORT_CONNECT; + + uno::Reference<sdbcx::XAppend> xColumnsAppend( xColumns, uno::UNO_QUERY ); + OSL_ENSURE( xColumnsAppend.is(), "can't get columns XAppend" ); + if (!xColumnsAppend.is()) return SCERR_EXPORT_CONNECT; + + const OUString* pColNames = aColNames.getConstArray(); + const sal_Int32* pColTypes = aColTypes.getConstArray(); + const sal_Int32* pColLengths = aColLengths.getConstArray(); + const sal_Int32* pColScales = aColScales.getConstArray(); + sal_Int32 nCol; + + for (nCol=0; nCol<nColCount; nCol++) + { + uno::Reference<beans::XPropertySet> xColumnDesc = xColumnsFact->createDataDescriptor(); + OSL_ENSURE( xColumnDesc.is(), "can't get column descriptor" ); + if (!xColumnDesc.is()) return SCERR_EXPORT_CONNECT; + + xColumnDesc->setPropertyValue( SC_DBPROP_NAME, uno::Any(pColNames[nCol]) ); + + xColumnDesc->setPropertyValue( SC_DBPROP_TYPE, uno::Any(pColTypes[nCol]) ); + + xColumnDesc->setPropertyValue( SC_DBPROP_PRECISION, uno::Any(pColLengths[nCol]) ); + + xColumnDesc->setPropertyValue( SC_DBPROP_SCALE, uno::Any(pColScales[nCol]) ); + + xColumnsAppend->appendByDescriptor( xColumnDesc ); + } + + xTablesAppend->appendByDescriptor( xTableDesc ); + + // get row set for writing + uno::Reference<lang::XMultiServiceFactory> xFactory = comphelper::getProcessServiceFactory(); + uno::Reference<sdbc::XRowSet> xRowSet( xFactory->createInstance(SC_SERVICE_ROWSET), + uno::UNO_QUERY); + ::utl::DisposableComponent aRowSetHelper(xRowSet); + uno::Reference<beans::XPropertySet> xRowProp( xRowSet, uno::UNO_QUERY ); + OSL_ENSURE( xRowProp.is(), "can't get RowSet" ); + if (!xRowProp.is()) return SCERR_EXPORT_CONNECT; + + xRowProp->setPropertyValue( SC_DBPROP_ACTIVECONNECTION, uno::Any(xConnection) ); + + xRowProp->setPropertyValue( SC_DBPROP_COMMANDTYPE, uno::Any(sal_Int32(sdb::CommandType::TABLE)) ); + + xRowProp->setPropertyValue( SC_DBPROP_COMMAND, uno::Any(aTabName) ); + + xRowSet->execute(); + + // write data rows + + uno::Reference<sdbc::XResultSetUpdate> xResultUpdate( xRowSet, uno::UNO_QUERY ); + OSL_ENSURE( xResultUpdate.is(), "can't get XResultSetUpdate" ); + if (!xResultUpdate.is()) return SCERR_EXPORT_CONNECT; + + uno::Reference<sdbc::XRowUpdate> xRowUpdate( xRowSet, uno::UNO_QUERY ); + OSL_ENSURE( xRowUpdate.is(), "can't get XRowUpdate" ); + if (!xRowUpdate.is()) return SCERR_EXPORT_CONNECT; + + SCROW nFirstDataRow = ( bHasFieldNames ? nFirstRow + 1 : nFirstRow ); + double fVal; + + for ( nDocRow = nFirstDataRow; nDocRow <= nLastRow; nDocRow++ ) + { + xResultUpdate->moveToInsertRow(); + + for (nCol=0; nCol<nColCount; nCol++) + { + SCCOL nDocCol = sal::static_int_cast<SCCOL>( nFirstCol + nCol ); + + switch (pColTypes[nCol]) + { + case sdbc::DataType::LONGVARCHAR: + { + ScRefCellValue aCell(*m_pDocument, ScAddress(nDocCol, nDocRow, nTab)); + if (!aCell.isEmpty()) + { + if (aCell.meType == CELLTYPE_EDIT) + { // preserve paragraphs + lcl_getLongVarCharEditString(aString, aCell, aEditEngine); + } + else + { + lcl_getLongVarCharString( + aString, *m_pDocument, nDocCol, nDocRow, nTab, *pNumFmt); + } + xRowUpdate->updateString( nCol+1, aString ); + } + else + xRowUpdate->updateNull( nCol+1 ); + } + break; + + case sdbc::DataType::VARCHAR: + aString = m_pDocument->GetString(nDocCol, nDocRow, nTab); + xRowUpdate->updateString( nCol+1, aString ); + if ( nErr == ERRCODE_NONE && pColLengths[nCol] < aString.getLength() ) + nErr = SCWARN_EXPORT_DATALOST; + break; + + case sdbc::DataType::DATE: + { + fVal = m_pDocument->GetValue( nDocCol, nDocRow, nTab ); + // differentiate between 0 with value and 0 no-value + bool bIsNull = (fVal == 0.0); + if ( bIsNull ) + bIsNull = !m_pDocument->HasValueData( nDocCol, nDocRow, nTab ); + if ( bIsNull ) + { + xRowUpdate->updateNull( nCol+1 ); + if ( nErr == ERRCODE_NONE && + m_pDocument->HasStringData( nDocCol, nDocRow, nTab ) ) + nErr = SCWARN_EXPORT_DATALOST; + } + else + { + Date aDate = pNumFmt->GetNullDate(); // tools date + aDate.AddDays(fVal); //! approxfloor? + xRowUpdate->updateDate( nCol+1, aDate.GetUNODate() ); + } + } + break; + + case sdbc::DataType::DECIMAL: + case sdbc::DataType::BIT: + fVal = m_pDocument->GetValue( nDocCol, nDocRow, nTab ); + if ( fVal == 0.0 && nErr == ERRCODE_NONE && + m_pDocument->HasStringData( nDocCol, nDocRow, nTab ) ) + nErr = SCWARN_EXPORT_DATALOST; + if ( pColTypes[nCol] == sdbc::DataType::BIT ) + xRowUpdate->updateBoolean( nCol+1, ( fVal != 0.0 ) ); + else + xRowUpdate->updateDouble( nCol+1, fVal ); + break; + + default: + OSL_FAIL( "ScDocShell::DBaseExport: unknown FieldType" ); + if ( nErr == ERRCODE_NONE ) + nErr = SCWARN_EXPORT_DATALOST; + fVal = m_pDocument->GetValue( nDocCol, nDocRow, nTab ); + xRowUpdate->updateDouble( nCol+1, fVal ); + } + } + + xResultUpdate->insertRow(); + + //! error handling and recovery of old + //! ScDocShell::SbaSdbExport is still missing! + + aProgress.SetStateOnPercent( nDocRow - nFirstRow ); + } + + comphelper::disposeComponent( xRowSet ); + comphelper::disposeComponent( xConnection ); + } + catch ( const sdbc::SQLException& aException ) + { + sal_Int32 nError = aException.ErrorCode; + TOOLS_WARN_EXCEPTION("sc", "ScDocShell::DBaseExport"); + + if (nError == 22018 || nError == 22001) + { + // SQL error 22018: Character not in target encoding. + // SQL error 22001: String length exceeds field width (after encoding). + bool bEncErr = (nError == 22018); + bool bIsOctetTextEncoding = rtl_isOctetTextEncoding( eCharSet); + OSL_ENSURE( !bEncErr || bIsOctetTextEncoding, "ScDocShell::DBaseExport: encoding error and not an octet textencoding"); + SCCOL nDocCol = nFirstCol; + const sal_Int32* pColTypes = aColTypes.getConstArray(); + const sal_Int32* pColLengths = aColLengths.getConstArray(); + ScHorizontalCellIterator aIter( *m_pDocument, nTab, nFirstCol, + nDocRow, nLastCol, nDocRow); + bool bTest = true; + while (bTest) + { + ScRefCellValue* pCell = aIter.GetNext( nDocCol, nDocRow); + if (!pCell) + break; + SCCOL nCol = nDocCol - nFirstCol; + switch (pColTypes[nCol]) + { + case sdbc::DataType::LONGVARCHAR: + { + if (pCell->meType == CELLTYPE_EDIT) + lcl_getLongVarCharEditString(aString, *pCell, aEditEngine); + else + lcl_getLongVarCharString( + aString, *m_pDocument, nDocCol, nDocRow, nTab, *pNumFmt); + } + break; + + case sdbc::DataType::VARCHAR: + aString = m_pDocument->GetString(nDocCol, nDocRow, nTab); + break; + + // NOTE: length of DECIMAL fields doesn't need to be + // checked here, the database driver adjusts the field + // width accordingly. + + default: + bTest = false; + } + if (bTest) + { + sal_Int32 nLen; + if (bIsOctetTextEncoding) + { + OString aOString; + if (!aString.convertToString( &aOString, eCharSet, + RTL_UNICODETOTEXT_FLAGS_UNDEFINED_ERROR | + RTL_UNICODETOTEXT_FLAGS_INVALID_ERROR)) + { + bTest = false; + bEncErr = true; + } + nLen = aOString.getLength(); + if (!bTest) + SAL_WARN("sc", "ScDocShell::DBaseExport encoding error, string with default replacements: ``" << aString << "''"); + } + else + nLen = aString.getLength() * sizeof(sal_Unicode); + if (!bEncErr && + pColTypes[nCol] != sdbc::DataType::LONGVARCHAR && + pColLengths[nCol] < nLen) + { + bTest = false; + SAL_INFO("sc", "ScDocShell::DBaseExport: field width: " << pColLengths[nCol] << ", encoded length: " << nLen); + } + } + else + bTest = true; + } + OUString sPosition(ScAddress(nDocCol, nDocRow, nTab).GetColRowString()); + OUString sEncoding(SvxTextEncodingTable::GetTextString(eCharSet)); + nErr = *new TwoStringErrorInfo( (bEncErr ? SCERR_EXPORT_ENCODING : + SCERR_EXPORT_FIELDWIDTH), sPosition, sEncoding, + DialogMask::ButtonsOk | DialogMask::MessageError); + } + else if ( !aException.Message.isEmpty() ) + nErr = *new StringErrorInfo( SCERR_EXPORT_SQLEXCEPTION, aException.Message, DialogMask::ButtonsOk | DialogMask::MessageError); + else + nErr = SCERR_EXPORT_DATA; + } + catch ( uno::Exception& ) + { + TOOLS_WARN_EXCEPTION( "sc", "Unexpected exception in database"); + nErr = ERRCODE_IO_GENERAL; + } + + return nErr; +#endif // HAVE_FEATURE_DBCONNECTIVITY +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/docshimp.hxx b/sc/source/ui/docshell/docshimp.hxx new file mode 100644 index 000000000..58cfb23ac --- /dev/null +++ b/sc/source/ui/docshell/docshimp.hxx @@ -0,0 +1,38 @@ +/* -*- 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 . + */ +#pragma once + +#include <svtools/ctrltool.hxx> +#include <sfx2/docinsert.hxx> +#include <sfx2/request.hxx> + +struct DocShell_Impl +{ + bool bIgnoreLostRedliningWarning; + std::unique_ptr<FontList> pFontList; + std::unique_ptr<sfx2::DocumentInserter> pDocInserter; + std::unique_ptr<SfxRequest> pRequest; + + DocShell_Impl() : + bIgnoreLostRedliningWarning( false ) + {} + +}; + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/documentlinkmgr.cxx b/sc/source/ui/docshell/documentlinkmgr.cxx new file mode 100644 index 000000000..79a86d08b --- /dev/null +++ b/sc/source/ui/docshell/documentlinkmgr.cxx @@ -0,0 +1,274 @@ +/* -*- 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 <comphelper/doublecheckedinit.hxx> +#include <documentlinkmgr.hxx> +#include <datastream.hxx> +#include <ddelink.hxx> +#include <webservicelink.hxx> +#include <strings.hrc> +#include <scresid.hxx> +#include <sfx2/linkmgr.hxx> +#include <sfx2/linksrc.hxx> +#include <o3tl/deleter.hxx> +#include <svx/svdoole2.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> + +#include <memory> + +namespace sc { + +struct DocumentLinkManagerImpl +{ + SfxObjectShell* mpShell; + std::unique_ptr<DataStream, o3tl::default_delete<DataStream>> mpDataStream; + std::atomic<sfx2::LinkManager*> mpLinkManager; + + DocumentLinkManagerImpl(const DocumentLinkManagerImpl&) = delete; + const DocumentLinkManagerImpl& operator=(const DocumentLinkManagerImpl&) = delete; + + explicit DocumentLinkManagerImpl(SfxObjectShell* pShell) + : mpShell(pShell), mpLinkManager(nullptr) {} + + ~DocumentLinkManagerImpl() + { + // Shared base links + sfx2::LinkManager* linkManager = mpLinkManager; + if (linkManager) + { + sfx2::SvLinkSources aTemp = linkManager->GetServers(); + for (const auto& pLinkSource : aTemp) + pLinkSource->Closed(); + + if (!linkManager->GetLinks().empty()) + linkManager->Remove(0, linkManager->GetLinks().size()); + } + delete linkManager; + } +}; + +DocumentLinkManager::DocumentLinkManager( SfxObjectShell* pShell ) : + mpImpl(new DocumentLinkManagerImpl(pShell)) {} + +DocumentLinkManager::~DocumentLinkManager() +{ +} + +void DocumentLinkManager::setDataStream( DataStream* p ) +{ + mpImpl->mpDataStream.reset(p); +} + +DataStream* DocumentLinkManager::getDataStream() +{ + return mpImpl->mpDataStream.get(); +} + +const DataStream* DocumentLinkManager::getDataStream() const +{ + return mpImpl->mpDataStream.get(); +} + +sfx2::LinkManager* DocumentLinkManager::getLinkManager( bool bCreate ) +{ + if (bCreate && mpImpl->mpShell) + return comphelper::doubleCheckedInit( mpImpl->mpLinkManager, + [this]() { return new sfx2::LinkManager(mpImpl->mpShell); } ); + return mpImpl->mpLinkManager; +} + +const sfx2::LinkManager* DocumentLinkManager::getExistingLinkManager() const +{ + return mpImpl->mpLinkManager; +} + +bool DocumentLinkManager::idleCheckLinks() +{ + sfx2::LinkManager* pMgr = mpImpl->mpLinkManager; + if (!pMgr) + return false; + + bool bAnyLeft = false; + const sfx2::SvBaseLinks& rLinks = pMgr->GetLinks(); + for (const auto & rLink : rLinks) + { + sfx2::SvBaseLink* pBase = rLink.get(); + ScDdeLink* pDdeLink = dynamic_cast<ScDdeLink*>(pBase); + if (!pDdeLink || !pDdeLink->NeedsUpdate()) + continue; + + pDdeLink->TryUpdate(); + if (pDdeLink->NeedsUpdate()) // Was not successful? + bAnyLeft = true; + } + + return bAnyLeft; +} + +bool DocumentLinkManager::hasDdeLinks() const +{ + return hasDdeOrOleOrWebServiceLinks(true, false, false); +} + +bool DocumentLinkManager::hasDdeOrOleOrWebServiceLinks() const +{ + return hasDdeOrOleOrWebServiceLinks(true, true, true); +} + +bool DocumentLinkManager::hasDdeOrOleOrWebServiceLinks(bool bDde, bool bOle, bool bWebService) const +{ + sfx2::LinkManager* pMgr = mpImpl->mpLinkManager; + if (!pMgr) + return false; + + const sfx2::SvBaseLinks& rLinks = pMgr->GetLinks(); + for (const auto & rLink : rLinks) + { + sfx2::SvBaseLink* pBase = rLink.get(); + if (bDde && dynamic_cast<ScDdeLink*>(pBase)) + return true; + if (bOle && (dynamic_cast<SdrEmbedObjectLink*>(pBase) || dynamic_cast<SdrIFrameLink*>(pBase))) + return true; + if (bWebService && dynamic_cast<ScWebServiceLink*>(pBase)) + return true; + } + + return false; +} + +bool DocumentLinkManager::updateDdeOrOleOrWebServiceLinks(weld::Window* pWin) +{ + sfx2::LinkManager* pMgr = mpImpl->mpLinkManager; + if (!pMgr) + return false; + + const sfx2::SvBaseLinks& rLinks = pMgr->GetLinks(); + + // If the update takes longer, reset all values so that nothing + // old (wrong) is left behind + bool bAny = false; + for (const auto & rLink : rLinks) + { + sfx2::SvBaseLink* pBase = rLink.get(); + + SdrEmbedObjectLink* pOleLink = dynamic_cast<SdrEmbedObjectLink*>(pBase); + if (pOleLink) + { + pOleLink->Update(); + continue; + } + + SdrIFrameLink* pIFrameLink = dynamic_cast<SdrIFrameLink*>(pBase); + if (pIFrameLink) + { + pIFrameLink->Update(); + continue; + } + + ScWebServiceLink* pWebserviceLink = dynamic_cast<ScWebServiceLink*>(pBase); + if (pWebserviceLink) + { + pWebserviceLink->Update(); + continue; + } + + ScDdeLink* pDdeLink = dynamic_cast<ScDdeLink*>(pBase); + if (!pDdeLink) + continue; + + if (pDdeLink->Update()) + bAny = true; + else + { + // Update failed. Notify the user. + const OUString& aFile = pDdeLink->GetTopic(); + const OUString& aElem = pDdeLink->GetItem(); + const OUString& aType = pDdeLink->GetAppl(); + + OUString sMessage = + ScResId(SCSTR_DDEDOC_NOT_LOADED) + + "\n\n" + "Source : " + + aFile + + "\nElement : " + + aElem + + "\nType : " + + aType; + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pWin, + VclMessageType::Warning, VclButtonsType::Ok, + sMessage)); + xBox->run(); + } + } + + pMgr->CloseCachedComps(); + + return bAny; +} + +void DocumentLinkManager::updateDdeLink( std::u16string_view rAppl, std::u16string_view rTopic, std::u16string_view rItem ) +{ + sfx2::LinkManager* pMgr = mpImpl->mpLinkManager; + if (!pMgr) + return; + + const sfx2::SvBaseLinks& rLinks = pMgr->GetLinks(); + + for (const auto & rLink : rLinks) + { + ::sfx2::SvBaseLink* pBase = rLink.get(); + ScDdeLink* pDdeLink = dynamic_cast<ScDdeLink*>(pBase); + if (!pDdeLink) + continue; + + if ( pDdeLink->GetAppl() == rAppl && + pDdeLink->GetTopic() == rTopic && + pDdeLink->GetItem() == rItem ) + { + pDdeLink->TryUpdate(); + // Could be multiple (Mode), so continue searching + } + } +} + +size_t DocumentLinkManager::getDdeLinkCount() const +{ + sfx2::LinkManager* pMgr = mpImpl->mpLinkManager; + if (!pMgr) + return 0; + + size_t nDdeCount = 0; + const sfx2::SvBaseLinks& rLinks = pMgr->GetLinks(); + for (const auto & rLink : rLinks) + { + ::sfx2::SvBaseLink* pBase = rLink.get(); + ScDdeLink* pDdeLink = dynamic_cast<ScDdeLink*>(pBase); + if (!pDdeLink) + continue; + + ++nDdeCount; + } + + return nDdeCount; +} + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/editable.cxx b/sc/source/ui/docshell/editable.cxx new file mode 100644 index 000000000..86bbb9f2e --- /dev/null +++ b/sc/source/ui/docshell/editable.cxx @@ -0,0 +1,162 @@ +/* -*- 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 <editable.hxx> +#include <document.hxx> +#include <viewfunc.hxx> +#include <globstr.hrc> + +ScEditableTester::ScEditableTester() : + mbIsEditable(true), + mbOnlyMatrix(true) +{ +} + +ScEditableTester::ScEditableTester( const ScDocument& rDoc, SCTAB nTab, + SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, bool bNoMatrixAtAll ) : + mbIsEditable(true), + mbOnlyMatrix(true) +{ + TestBlock( rDoc, nTab, nStartCol, nStartRow, nEndCol, nEndRow, bNoMatrixAtAll ); +} + +ScEditableTester::ScEditableTester( const ScDocument& rDoc, + SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, + const ScMarkData& rMark ) : + mbIsEditable(true), + mbOnlyMatrix(true) +{ + TestSelectedBlock( rDoc, nStartCol, nStartRow, nEndCol, nEndRow, rMark ); +} + +ScEditableTester::ScEditableTester( const ScDocument& rDoc, const ScRange& rRange ) : + mbIsEditable(true), + mbOnlyMatrix(true) +{ + TestRange( rDoc, rRange ); +} + +ScEditableTester::ScEditableTester( const ScDocument& rDoc, const ScMarkData& rMark ) : + mbIsEditable(true), + mbOnlyMatrix(true) +{ + TestSelection( rDoc, rMark ); +} + +ScEditableTester::ScEditableTester( ScViewFunc* pView ) : + mbIsEditable(true), + mbOnlyMatrix(true) +{ + bool bThisMatrix; + if ( !pView->SelectionEditable( &bThisMatrix ) ) + { + mbIsEditable = false; + if ( !bThisMatrix ) + mbOnlyMatrix = false; + } +} + +ScEditableTester::ScEditableTester( + const ScDocument& rDoc, sc::ColRowEditAction eAction, SCCOLROW nStart, SCCOLROW nEnd, const ScMarkData& rMark ) : + ScEditableTester() +{ + TestBlockForAction(rDoc, eAction, nStart, nEnd, rMark); +} + +void ScEditableTester::TestBlock( const ScDocument& rDoc, SCTAB nTab, + SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, bool bNoMatrixAtAll ) +{ + if (mbIsEditable || mbOnlyMatrix) + { + bool bThisMatrix; + if (!rDoc.IsBlockEditable( nTab, nStartCol, nStartRow, nEndCol, nEndRow, &bThisMatrix, bNoMatrixAtAll)) + { + mbIsEditable = false; + if ( !bThisMatrix ) + mbOnlyMatrix = false; + } + } +} + +void ScEditableTester::TestSelectedBlock( const ScDocument& rDoc, + SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, + const ScMarkData& rMark ) +{ + SCTAB nTabCount = rDoc.GetTableCount(); + for (const auto& rTab : rMark) + { + if (rTab >= nTabCount) + break; + + TestBlock( rDoc, rTab, nStartCol, nStartRow, nEndCol, nEndRow, false ); + } +} + +void ScEditableTester::TestRange( const ScDocument& rDoc, const ScRange& rRange ) +{ + 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++) + TestBlock( rDoc, nTab, nStartCol, nStartRow, nEndCol, nEndRow, false ); +} + +void ScEditableTester::TestSelection( const ScDocument& rDoc, const ScMarkData& rMark ) +{ + if (mbIsEditable || mbOnlyMatrix) + { + bool bThisMatrix; + if ( !rDoc.IsSelectionEditable( rMark, &bThisMatrix ) ) + { + mbIsEditable = false; + if ( !bThisMatrix ) + mbOnlyMatrix = false; + } + } +} + +void ScEditableTester::TestBlockForAction( + const ScDocument& rDoc, sc::ColRowEditAction eAction, SCCOLROW nStart, SCCOLROW nEnd, + const ScMarkData& rMark ) +{ + mbOnlyMatrix = false; + + for (const auto& rTab : rMark) + { + if (!mbIsEditable) + return; + + mbIsEditable = rDoc.IsEditActionAllowed(eAction, rTab, nStart, nEnd); + } +} + +TranslateId ScEditableTester::GetMessageId() const +{ + if (mbIsEditable) + return {}; + else if (mbOnlyMatrix) + return STR_MATRIXFRAGMENTERR; + else + return STR_PROTECTIONERR; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/externalrefmgr.cxx b/sc/source/ui/docshell/externalrefmgr.cxx new file mode 100644 index 000000000..fe731684a --- /dev/null +++ b/sc/source/ui/docshell/externalrefmgr.cxx @@ -0,0 +1,3319 @@ +/* -*- 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 <externalrefmgr.hxx> +#include <document.hxx> +#include <token.hxx> +#include <tokenarray.hxx> +#include <address.hxx> +#include <tablink.hxx> +#include <docsh.hxx> +#include <scextopt.hxx> +#include <rangenam.hxx> +#include <formulacell.hxx> +#include <viewdata.hxx> +#include <tabvwsh.hxx> +#include <sc.hrc> +#include <globstr.hrc> +#include <scresid.hxx> +#include <cellvalue.hxx> +#include <defaultsoptions.hxx> +#include <scmod.hxx> + +#include <o3tl/safeint.hxx> +#include <osl/file.hxx> +#include <sfx2/app.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/event.hxx> +#include <sfx2/fcontnr.hxx> +#include <sfx2/objsh.hxx> +#include <svl/itemset.hxx> +#include <svl/numformat.hxx> +#include <svl/stritem.hxx> +#include <svl/urihelper.hxx> +#include <svl/sharedstringpool.hxx> +#include <sfx2/linkmgr.hxx> +#include <tools/urlobj.hxx> +#include <unotools/charclass.hxx> +#include <unotools/configmgr.hxx> +#include <unotools/ucbhelper.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <stringutil.hxx> +#include <scmatrix.hxx> +#include <columnspanset.hxx> +#include <column.hxx> +#include <com/sun/star/document/MacroExecMode.hpp> +#include <com/sun/star/document/UpdateDocMode.hpp> +#include <sal/log.hxx> + +#include <memory> +#include <algorithm> + +using ::std::unique_ptr; +using ::com::sun::star::uno::Any; +using ::std::vector; +using ::std::find_if; +using ::std::for_each; +using ::std::distance; +using ::std::pair; +using namespace formula; + +#define SRCDOC_LIFE_SPAN 30000 // 5 minutes (in 100th of a sec) +#define SRCDOC_SCAN_INTERVAL 1000*30 // every 30 seconds (in msec) + +namespace { + +class TabNameSearchPredicate +{ +public: + explicit TabNameSearchPredicate(const OUString& rSearchName) : + maSearchName(ScGlobal::getCharClass().uppercase(rSearchName)) + { + } + + bool operator()(const ScExternalRefCache::TableName& rTabNameSet) const + { + // Ok, I'm doing case insensitive search here. + return rTabNameSet.maUpperName == maSearchName; + } + +private: + OUString maSearchName; +}; + +class FindSrcFileByName +{ +public: + explicit FindSrcFileByName(const OUString& rMatchName) : + mrMatchName(rMatchName) + { + } + + bool operator()(const ScExternalRefManager::SrcFileData& rSrcData) const + { + return rSrcData.maFileName == mrMatchName; + } + +private: + const OUString& mrMatchName; +}; + +class NotifyLinkListener +{ +public: + NotifyLinkListener(sal_uInt16 nFileId, ScExternalRefManager::LinkUpdateType eType) : + mnFileId(nFileId), meType(eType) {} + + void operator() (ScExternalRefManager::LinkListener* p) const + { + p->notify(mnFileId, meType); + } +private: + sal_uInt16 mnFileId; + ScExternalRefManager::LinkUpdateType meType; +}; + +struct UpdateFormulaCell +{ + void operator() (ScFormulaCell* pCell) const + { + // Check to make sure the cell really contains svExternal*. + // External names, external cell and range references all have a + // token of svExternal*. Additionally check for INDIRECT() that can be + // called with any constructed URI string. + ScTokenArray* pCode = pCell->GetCode(); + if (!pCode->HasExternalRef() && !pCode->HasOpCode(ocIndirect)) + return; + + if (pCode->GetCodeError() != FormulaError::NONE) + { + // Clear the error code, or a cell with error won't get re-compiled. + pCode->SetCodeError(FormulaError::NONE); + pCell->SetCompile(true); + pCell->CompileTokenArray(); + } + + pCell->SetDirty(); + } +}; + +class RemoveFormulaCell +{ +public: + explicit RemoveFormulaCell(ScFormulaCell* p) : mpCell(p) {} + void operator() (pair<const sal_uInt16, ScExternalRefManager::RefCellSet>& r) const + { + r.second.erase(mpCell); + } +private: + ScFormulaCell* mpCell; +}; + +class ConvertFormulaToStatic +{ +public: + explicit ConvertFormulaToStatic(ScDocument* pDoc) : mpDoc(pDoc) {} + void operator() (ScFormulaCell* pCell) const + { + ScAddress aPos = pCell->aPos; + + // We don't check for empty cells because empty external cells are + // treated as having a value of 0. + + if (pCell->IsValue()) + { + // Turn this into value cell. + mpDoc->SetValue(aPos, pCell->GetValue()); + } + else + { + // string cell otherwise. + ScSetStringParam aParam; + aParam.setTextInput(); + mpDoc->SetString(aPos, pCell->GetString().getString(), &aParam); + } + } +private: + ScDocument* mpDoc; +}; + +/** + * Check whether a named range contains an external reference to a + * particular document. + */ +bool hasRefsToSrcDoc(ScRangeData& rData, sal_uInt16 nFileId) +{ + ScTokenArray* pArray = rData.GetCode(); + if (!pArray) + return false; + + formula::FormulaTokenArrayPlainIterator aIter(*pArray); + formula::FormulaToken* p = aIter.GetNextReference(); + for (; p; p = aIter.GetNextReference()) + { + if (!p->IsExternalRef()) + continue; + + if (p->GetIndex() == nFileId) + return true; + } + return false; +} + +class EraseRangeByIterator +{ + ScRangeName& mrRanges; +public: + explicit EraseRangeByIterator(ScRangeName& rRanges) : mrRanges(rRanges) {} + void operator() (const ScRangeName::const_iterator& itr) + { + mrRanges.erase(itr); + } +}; + +/** + * Remove all named ranges that contain references to specified source + * document. + */ +void removeRangeNamesBySrcDoc(ScRangeName& rRanges, sal_uInt16 nFileId) +{ + ScRangeName::const_iterator itr = rRanges.begin(), itrEnd = rRanges.end(); + vector<ScRangeName::const_iterator> v; + for (; itr != itrEnd; ++itr) + { + if (hasRefsToSrcDoc(*itr->second, nFileId)) + v.push_back(itr); + } + for_each(v.begin(), v.end(), EraseRangeByIterator(rRanges)); +} + +} + +ScExternalRefCache::Table::Table() + : mbReferenced( true ) + // Prevent accidental data loss due to lack of knowledge. +{ +} + +ScExternalRefCache::Table::~Table() +{ +} + +void ScExternalRefCache::Table::clear() +{ + maRows.clear(); + maCachedRanges.RemoveAll(); + mbReferenced = true; +} + +void ScExternalRefCache::Table::setReferenced( bool bReferenced ) +{ + mbReferenced = bReferenced; +} + +bool ScExternalRefCache::Table::isReferenced() const +{ + return mbReferenced; +} + +void ScExternalRefCache::Table::setCell(SCCOL nCol, SCROW nRow, TokenRef const & pToken, sal_uLong nFmtIndex, bool bSetCacheRange) +{ + using ::std::pair; + RowsDataType::iterator itrRow = maRows.find(nRow); + if (itrRow == maRows.end()) + { + // This row does not exist yet. + pair<RowsDataType::iterator, bool> res = maRows.emplace( + nRow, RowDataType()); + + if (!res.second) + return; + + itrRow = res.first; + } + + // Insert this token into the specified column location. I don't need to + // check for existing data. Just overwrite it. + RowDataType& rRow = itrRow->second; + ScExternalRefCache::Cell aCell; + aCell.mxToken = pToken; + aCell.mnFmtIndex = nFmtIndex; + rRow.emplace(nCol, aCell); + if (bSetCacheRange) + setCachedCell(nCol, nRow); +} + +ScExternalRefCache::TokenRef ScExternalRefCache::Table::getCell(SCCOL nCol, SCROW nRow, sal_uInt32* pnFmtIndex) const +{ + RowsDataType::const_iterator itrTable = maRows.find(nRow); + if (itrTable == maRows.end()) + { + // this table doesn't have the specified row. + return getEmptyOrNullToken(nCol, nRow); + } + + const RowDataType& rRowData = itrTable->second; + RowDataType::const_iterator itrRow = rRowData.find(nCol); + if (itrRow == rRowData.end()) + { + // this row doesn't have the specified column. + return getEmptyOrNullToken(nCol, nRow); + } + + const Cell& rCell = itrRow->second; + if (pnFmtIndex) + *pnFmtIndex = rCell.mnFmtIndex; + + return rCell.mxToken; +} + +bool ScExternalRefCache::Table::hasRow( SCROW nRow ) const +{ + RowsDataType::const_iterator itrRow = maRows.find(nRow); + return itrRow != maRows.end(); +} + +template< typename P > +void ScExternalRefCache::Table::getAllRows(vector<SCROW>& rRows, P predicate) const +{ + vector<SCROW> aRows; + aRows.reserve(maRows.size()); + for (const auto& rEntry : maRows) + if (predicate(rEntry)) + aRows.push_back(rEntry.first); + + // hash map is not ordered, so we need to explicitly sort it. + ::std::sort(aRows.begin(), aRows.end()); + rRows.swap(aRows); +} + +void ScExternalRefCache::Table::getAllRows(vector<SCROW>& rRows, SCROW nLow, SCROW nHigh) const +{ + getAllRows(rRows, + [nLow, nHigh](std::pair<SCROW, RowDataType> rEntry) { return (nLow <= rEntry.first && rEntry.first <= nHigh); }); +} + +void ScExternalRefCache::Table::getAllRows(vector<SCROW>& rRows) const +{ + getAllRows(rRows, [](std::pair<SCROW, RowDataType>) { return true; } ); +} + +::std::pair< SCROW, SCROW > ScExternalRefCache::Table::getRowRange() const +{ + ::std::pair< SCROW, SCROW > aRange( 0, 0 ); + if( !maRows.empty() ) + { + // iterate over entire container (hash map is not sorted by key) + auto itMinMax = std::minmax_element(maRows.begin(), maRows.end(), + [](const RowsDataType::value_type& a, const RowsDataType::value_type& b) { return a.first < b.first; }); + aRange.first = itMinMax.first->first; + aRange.second = itMinMax.second->first + 1; + } + return aRange; +} + +template< typename P > +void ScExternalRefCache::Table::getAllCols(SCROW nRow, vector<SCCOL>& rCols, P predicate) const +{ + RowsDataType::const_iterator itrRow = maRows.find(nRow); + if (itrRow == maRows.end()) + // this table doesn't have the specified row. + return; + + const RowDataType& rRowData = itrRow->second; + vector<SCCOL> aCols; + aCols.reserve(rRowData.size()); + for (const auto& rCol : rRowData) + if (predicate(rCol)) + aCols.push_back(rCol.first); + + // hash map is not ordered, so we need to explicitly sort it. + ::std::sort(aCols.begin(), aCols.end()); + rCols.swap(aCols); +} + +void ScExternalRefCache::Table::getAllCols(SCROW nRow, vector<SCCOL>& rCols, SCCOL nLow, SCCOL nHigh) const +{ + getAllCols(nRow, rCols, + [nLow, nHigh](std::pair<SCCOL, Cell> rCol) { return nLow <= rCol.first && rCol.first <= nHigh; } ); +} + +void ScExternalRefCache::Table::getAllCols(SCROW nRow, vector<SCCOL>& rCols) const +{ + getAllCols(nRow, rCols, [](std::pair<SCCOL, Cell>) { return true; } ); +} + +::std::pair< SCCOL, SCCOL > ScExternalRefCache::Table::getColRange( SCROW nRow ) const +{ + ::std::pair< SCCOL, SCCOL > aRange( 0, 0 ); + + RowsDataType::const_iterator itrRow = maRows.find( nRow ); + if (itrRow == maRows.end()) + // this table doesn't have the specified row. + return aRange; + + const RowDataType& rRowData = itrRow->second; + if( !rRowData.empty() ) + { + // iterate over entire container (hash map is not sorted by key) + auto itMinMax = std::minmax_element(rRowData.begin(), rRowData.end(), + [](const RowDataType::value_type& a, const RowDataType::value_type& b) { return a.first < b.first; }); + aRange.first = itMinMax.first->first; + aRange.second = itMinMax.second->first + 1; + } + return aRange; +} + +void ScExternalRefCache::Table::getAllNumberFormats(vector<sal_uInt32>& rNumFmts) const +{ + for (const auto& rRow : maRows) + { + const RowDataType& rRowData = rRow.second; + for (const auto& rCol : rRowData) + { + const Cell& rCell = rCol.second; + rNumFmts.push_back(rCell.mnFmtIndex); + } + } +} + +bool ScExternalRefCache::Table::isRangeCached(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) const +{ + return maCachedRanges.Contains(ScRange(nCol1, nRow1, 0, nCol2, nRow2, 0)); +} + +void ScExternalRefCache::Table::setCachedCell(SCCOL nCol, SCROW nRow) +{ + setCachedCellRange(nCol, nRow, nCol, nRow); +} + +void ScExternalRefCache::Table::setCachedCellRange(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) +{ + ScRange aRange(nCol1, nRow1, 0, nCol2, nRow2, 0); + maCachedRanges.Join(aRange); +} + +void ScExternalRefCache::Table::setWholeTableCached() +{ + setCachedCellRange(0, 0, MAXCOL, MAXROW); +} + +bool ScExternalRefCache::Table::isInCachedRanges(SCCOL nCol, SCROW nRow) const +{ + return maCachedRanges.Contains(ScRange(nCol, nRow, 0, nCol, nRow, 0)); +} + +ScExternalRefCache::TokenRef ScExternalRefCache::Table::getEmptyOrNullToken( + SCCOL nCol, SCROW nRow) const +{ + if (isInCachedRanges(nCol, nRow)) + { + TokenRef p(new ScEmptyCellToken(false, false)); + return p; + } + return TokenRef(); +} + +ScExternalRefCache::TableName::TableName(const OUString& rUpper, const OUString& rReal) : + maUpperName(rUpper), maRealName(rReal) +{ +} + +ScExternalRefCache::CellFormat::CellFormat() : + mbIsSet(false), mnType(SvNumFormatType::ALL), mnIndex(0) +{ +} + +ScExternalRefCache::ScExternalRefCache(const ScDocument& rDoc) + : mrDoc(rDoc) +{ +} + +ScExternalRefCache::~ScExternalRefCache() {} + +const OUString* ScExternalRefCache::getRealTableName(sal_uInt16 nFileId, const OUString& rTabName) const +{ + osl::MutexGuard aGuard(&maMtxDocs); + + DocDataType::const_iterator itrDoc = maDocs.find(nFileId); + if (itrDoc == maDocs.end()) + { + // specified document is not cached. + return nullptr; + } + + const DocItem& rDoc = itrDoc->second; + TableNameIndexMap::const_iterator itrTabId = rDoc.findTableNameIndex( rTabName); + if (itrTabId == rDoc.maTableNameIndex.end()) + { + // the specified table is not in cache. + return nullptr; + } + + return &rDoc.maTableNames[itrTabId->second].maRealName; +} + +const OUString* ScExternalRefCache::getRealRangeName(sal_uInt16 nFileId, const OUString& rRangeName) const +{ + osl::MutexGuard aGuard(&maMtxDocs); + + DocDataType::const_iterator itrDoc = maDocs.find(nFileId); + if (itrDoc == maDocs.end()) + { + // specified document is not cached. + return nullptr; + } + + const DocItem& rDoc = itrDoc->second; + NamePairMap::const_iterator itr = rDoc.maRealRangeNameMap.find( + ScGlobal::getCharClass().uppercase(rRangeName)); + if (itr == rDoc.maRealRangeNameMap.end()) + // range name not found. + return nullptr; + + return &itr->second; +} + +ScExternalRefCache::TokenRef ScExternalRefCache::getCellData( + sal_uInt16 nFileId, const OUString& rTabName, SCCOL nCol, SCROW nRow, sal_uInt32* pnFmtIndex) +{ + osl::MutexGuard aGuard(&maMtxDocs); + + DocDataType::const_iterator itrDoc = maDocs.find(nFileId); + if (itrDoc == maDocs.end()) + { + // specified document is not cached. + return TokenRef(); + } + + const DocItem& rDoc = itrDoc->second; + TableNameIndexMap::const_iterator itrTabId = rDoc.findTableNameIndex( rTabName); + if (itrTabId == rDoc.maTableNameIndex.end()) + { + // the specified table is not in cache. + return TokenRef(); + } + + const TableTypeRef& pTableData = rDoc.maTables[itrTabId->second]; + if (!pTableData) + { + // the table data is not instantiated yet. + return TokenRef(); + } + + return pTableData->getCell(nCol, nRow, pnFmtIndex); +} + +ScExternalRefCache::TokenArrayRef ScExternalRefCache::getCellRangeData( + sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange) +{ + osl::MutexGuard aGuard(&maMtxDocs); + + DocDataType::iterator itrDoc = maDocs.find(nFileId); + if (itrDoc == maDocs.end()) + // specified document is not cached. + return TokenArrayRef(); + + DocItem& rDoc = itrDoc->second; + + TableNameIndexMap::const_iterator itrTabId = rDoc.findTableNameIndex( rTabName); + if (itrTabId == rDoc.maTableNameIndex.end()) + // the specified table is not in cache. + return TokenArrayRef(); + + const ScAddress& s = rRange.aStart; + const ScAddress& e = rRange.aEnd; + + const SCTAB nTab1 = s.Tab(), nTab2 = e.Tab(); + const SCCOL nCol1 = s.Col(), nCol2 = e.Col(); + const SCROW nRow1 = s.Row(), nRow2 = e.Row(); + + // Make sure I have all the tables cached. + size_t nTabFirstId = itrTabId->second; + size_t nTabLastId = nTabFirstId + nTab2 - nTab1; + if (nTabLastId >= rDoc.maTables.size()) + // not all tables are cached. + return TokenArrayRef(); + + ScRange aCacheRange( nCol1, nRow1, static_cast<SCTAB>(nTabFirstId), nCol2, nRow2, static_cast<SCTAB>(nTabLastId)); + + RangeArrayMap::const_iterator itrRange = rDoc.maRangeArrays.find( aCacheRange); + if (itrRange != rDoc.maRangeArrays.end()) + // Cache hit! + return itrRange->second; + + std::unique_ptr<ScRange> pNewRange; + TokenArrayRef pArray; + bool bFirstTab = true; + for (size_t nTab = nTabFirstId; nTab <= nTabLastId; ++nTab) + { + TableTypeRef pTab = rDoc.maTables[nTab]; + if (!pTab) + return TokenArrayRef(); + + SCCOL nDataCol1 = nCol1, nDataCol2 = nCol2; + SCROW nDataRow1 = nRow1, nDataRow2 = nRow2; + + if (!pTab->isRangeCached(nDataCol1, nDataRow1, nDataCol2, nDataRow2)) + { + // specified range is not entirely within cached ranges. + return TokenArrayRef(); + } + + SCSIZE nMatrixColumns = static_cast<SCSIZE>(nDataCol2-nDataCol1+1); + SCSIZE nMatrixRows = static_cast<SCSIZE>(nDataRow2-nDataRow1+1); + ScMatrixRef xMat = new ScMatrix( nMatrixColumns, nMatrixRows); + + // Needed in shrink and fill. + vector<SCROW> aRows; + pTab->getAllRows(aRows, nDataRow1, nDataRow2); + bool bFill = true; + + // Check if size could be allocated and if not skip the fill, there's + // one error element instead. But retry first with the actual data area + // if that is smaller than the original range, which works for most + // functions just not some that operate/compare with the original size + // and expect empty values in non-data areas. + // Restrict this though to ranges of entire columns or rows, other + // ranges might be on purpose. (Other special cases to handle?) + /* TODO: sparse matrix could help */ + SCSIZE nMatCols, nMatRows; + xMat->GetDimensions( nMatCols, nMatRows); + if (nMatCols != nMatrixColumns || nMatRows != nMatrixRows) + { + bFill = false; + if (aRows.empty()) + { + // There's no data at all. Set the one matrix element to empty + // for column-repeated and row-repeated access. + xMat->PutEmpty(0,0); + } + else if ((nCol1 == 0 && nCol2 == MAXCOL) || (nRow1 == 0 && nRow2 == MAXROW)) + { + nDataRow1 = aRows.front(); + nDataRow2 = aRows.back(); + SCCOL nMinCol = std::numeric_limits<SCCOL>::max(); + SCCOL nMaxCol = std::numeric_limits<SCCOL>::min(); + for (const auto& rRow : aRows) + { + vector<SCCOL> aCols; + pTab->getAllCols(rRow, aCols, nDataCol1, nDataCol2); + if (!aCols.empty()) + { + nMinCol = std::min( nMinCol, aCols.front()); + nMaxCol = std::max( nMaxCol, aCols.back()); + } + } + + if (nMinCol <= nMaxCol && ((o3tl::make_unsigned(nMaxCol-nMinCol+1) < nMatrixColumns) || + (o3tl::make_unsigned(nDataRow2-nDataRow1+1) < nMatrixRows))) + { + nMatrixColumns = static_cast<SCSIZE>(nMaxCol-nMinCol+1); + nMatrixRows = static_cast<SCSIZE>(nDataRow2-nDataRow1+1); + xMat = new ScMatrix( nMatrixColumns, nMatrixRows); + xMat->GetDimensions( nMatCols, nMatRows); + if (nMatCols == nMatrixColumns && nMatRows == nMatrixRows) + { + nDataCol1 = nMinCol; + nDataCol2 = nMaxCol; + bFill = true; + } + } + } + } + + if (bFill) + { + // Only fill non-empty cells, for better performance. + for (SCROW nRow : aRows) + { + vector<SCCOL> aCols; + pTab->getAllCols(nRow, aCols, nDataCol1, nDataCol2); + for (SCCOL nCol : aCols) + { + TokenRef pToken = pTab->getCell(nCol, nRow); + if (!pToken) + // This should never happen! + return TokenArrayRef(); + + SCSIZE nC = nCol - nDataCol1, nR = nRow - nDataRow1; + switch (pToken->GetType()) + { + case svDouble: + xMat->PutDouble(pToken->GetDouble(), nC, nR); + break; + case svString: + xMat->PutString(pToken->GetString(), nC, nR); + break; + default: + ; + } + } + } + + if (!bFirstTab) + pArray->AddOpCode(ocSep); + + ScMatrixToken aToken(xMat); + if (!pArray) + pArray = std::make_shared<ScTokenArray>(mrDoc); + pArray->AddToken(aToken); + + bFirstTab = false; + + if (!pNewRange) + pNewRange.reset(new ScRange(nDataCol1, nDataRow1, nTab, nDataCol2, nDataRow2, nTab)); + else + pNewRange->ExtendTo(ScRange(nDataCol1, nDataRow1, nTab, nDataCol2, nDataRow2, nTab)); + } + } + + rDoc.maRangeArrays.emplace(aCacheRange, pArray); + if (pNewRange && *pNewRange != aCacheRange) + rDoc.maRangeArrays.emplace(*pNewRange, pArray); + + return pArray; +} + +ScExternalRefCache::TokenArrayRef ScExternalRefCache::getRangeNameTokens(sal_uInt16 nFileId, const OUString& rName) +{ + osl::MutexGuard aGuard(&maMtxDocs); + + DocItem* pDoc = getDocItem(nFileId); + if (!pDoc) + return TokenArrayRef(); + + RangeNameMap& rMap = pDoc->maRangeNames; + RangeNameMap::const_iterator itr = rMap.find( + ScGlobal::getCharClass().uppercase(rName)); + if (itr == rMap.end()) + return TokenArrayRef(); + + return itr->second; +} + +void ScExternalRefCache::setRangeNameTokens(sal_uInt16 nFileId, const OUString& rName, TokenArrayRef pArray) +{ + osl::MutexGuard aGuard(&maMtxDocs); + + DocItem* pDoc = getDocItem(nFileId); + if (!pDoc) + return; + + OUString aUpperName = ScGlobal::getCharClass().uppercase(rName); + RangeNameMap& rMap = pDoc->maRangeNames; + rMap.emplace(aUpperName, pArray); + pDoc->maRealRangeNameMap.emplace(aUpperName, rName); +} + +bool ScExternalRefCache::isValidRangeName(sal_uInt16 nFileId, const OUString& rName) const +{ + osl::MutexGuard aGuard(&maMtxDocs); + + DocItem* pDoc = getDocItem(nFileId); + if (!pDoc) + return false; + + OUString aUpperName = ScGlobal::getCharClass().uppercase(rName); + const RangeNameMap& rMap = pDoc->maRangeNames; + return rMap.count(aUpperName) > 0; +} + +void ScExternalRefCache::setRangeName(sal_uInt16 nFileId, const OUString& rName) +{ + osl::MutexGuard aGuard(&maMtxDocs); + + DocItem* pDoc = getDocItem(nFileId); + if (!pDoc) + return; + + OUString aUpperName = ScGlobal::getCharClass().uppercase(rName); + pDoc->maRealRangeNameMap.emplace(aUpperName, rName); +} + +void ScExternalRefCache::setCellData(sal_uInt16 nFileId, const OUString& rTabName, SCCOL nCol, SCROW nRow, + TokenRef const & pToken, sal_uLong nFmtIndex) +{ + if (!isDocInitialized(nFileId)) + return; + + using ::std::pair; + DocItem* pDocItem = getDocItem(nFileId); + if (!pDocItem) + return; + + DocItem& rDoc = *pDocItem; + + // See if the table by this name already exists. + TableNameIndexMap::const_iterator itrTabName = rDoc.findTableNameIndex( rTabName); + if (itrTabName == rDoc.maTableNameIndex.end()) + // Table not found. Maybe the table name or the file id is wrong ??? + return; + + TableTypeRef& pTableData = rDoc.maTables[itrTabName->second]; + if (!pTableData) + pTableData = std::make_shared<Table>(); + + pTableData->setCell(nCol, nRow, pToken, nFmtIndex); + pTableData->setCachedCell(nCol, nRow); +} + +void ScExternalRefCache::setCellRangeData(sal_uInt16 nFileId, const ScRange& rRange, const vector<SingleRangeData>& rData, + const TokenArrayRef& pArray) +{ + using ::std::pair; + if (rData.empty() || !isDocInitialized(nFileId)) + // nothing to cache + return; + + // First, get the document item for the given file ID. + DocItem* pDocItem = getDocItem(nFileId); + if (!pDocItem) + return; + + DocItem& rDoc = *pDocItem; + + // Now, find the table position of the first table to cache. + const OUString& rFirstTabName = rData.front().maTableName; + TableNameIndexMap::const_iterator itrTabName = rDoc.findTableNameIndex( rFirstTabName); + if (itrTabName == rDoc.maTableNameIndex.end()) + { + // table index not found. + return; + } + + size_t nTabFirstId = itrTabName->second; + SCROW nRow1 = rRange.aStart.Row(), nRow2 = rRange.aEnd.Row(); + SCCOL nCol1 = rRange.aStart.Col(), nCol2 = rRange.aEnd.Col(); + size_t i = nTabFirstId; + for (const auto& rItem : rData) + { + TableTypeRef& pTabData = rDoc.maTables[i]; + if (!pTabData) + pTabData = std::make_shared<Table>(); + + const ScMatrixRef& pMat = rItem.mpRangeData; + SCSIZE nMatCols, nMatRows; + pMat->GetDimensions( nMatCols, nMatRows); + if (nMatCols > o3tl::make_unsigned(nCol2 - nCol1) && nMatRows > o3tl::make_unsigned(nRow2 - nRow1)) + { + ScMatrix::DoubleOpFunction aDoubleFunc = [=](size_t row, size_t col, double val) -> void + { + pTabData->setCell(col + nCol1, row + nRow1, new formula::FormulaDoubleToken(val), 0, false); + }; + ScMatrix::BoolOpFunction aBoolFunc = [=](size_t row, size_t col, bool val) -> void + { + pTabData->setCell(col + nCol1, row + nRow1, new formula::FormulaDoubleToken(val ? 1.0 : 0.0), 0, false); + }; + ScMatrix::StringOpFunction aStringFunc = [=](size_t row, size_t col, svl::SharedString val) -> void + { + pTabData->setCell(col + nCol1, row + nRow1, new formula::FormulaStringToken(val), 0, false); + }; + ScMatrix::EmptyOpFunction aEmptyFunc = [](size_t /*row*/, size_t /*col*/) -> void + { + // Nothing. Empty cell. + }; + pMat->ExecuteOperation(std::pair<size_t, size_t>(0, 0), + std::pair<size_t, size_t>(nRow2-nRow1, nCol2-nCol1), + std::move(aDoubleFunc), std::move(aBoolFunc), std::move(aStringFunc), std::move(aEmptyFunc)); + // Mark the whole range 'cached'. + pTabData->setCachedCellRange(nCol1, nRow1, nCol2, nRow2); + } + else + { + // This may happen due to a matrix not been allocated earlier, in + // which case it should have exactly one error element. + SAL_WARN("sc.ui","ScExternalRefCache::setCellRangeData - matrix size mismatch"); + if (nMatCols != 1 || nMatRows != 1) + SAL_WARN("sc.ui","ScExternalRefCache::setCellRangeData - not a one element matrix"); + else + { + FormulaError nErr = GetDoubleErrorValue( pMat->GetDouble(0,0)); + SAL_WARN("sc.ui","ScExternalRefCache::setCellRangeData - matrix error value is " << static_cast<int>(nErr) << + (nErr == FormulaError::MatrixSize ? ", ok" : ", not ok")); + } + } + ++i; + } + + size_t nTabLastId = nTabFirstId + rRange.aEnd.Tab() - rRange.aStart.Tab(); + ScRange aCacheRange( nCol1, nRow1, static_cast<SCTAB>(nTabFirstId), nCol2, nRow2, static_cast<SCTAB>(nTabLastId)); + + rDoc.maRangeArrays.emplace(aCacheRange, pArray); +} + +bool ScExternalRefCache::isDocInitialized(sal_uInt16 nFileId) +{ + DocItem* pDoc = getDocItem(nFileId); + if (!pDoc) + return false; + + return pDoc->mbInitFromSource; +} + +static bool lcl_getStrictTableDataIndex(const ScExternalRefCache::TableNameIndexMap& rMap, const OUString& rName, size_t& rIndex) +{ + ScExternalRefCache::TableNameIndexMap::const_iterator itr = rMap.find(rName); + if (itr == rMap.end()) + return false; + + rIndex = itr->second; + return true; +} + +bool ScExternalRefCache::DocItem::getTableDataIndex( const OUString& rTabName, size_t& rIndex ) const +{ + ScExternalRefCache::TableNameIndexMap::const_iterator itr = findTableNameIndex(rTabName); + if (itr == maTableNameIndex.end()) + return false; + + rIndex = itr->second; + return true; +} + +namespace { +OUString getFirstSheetName() +{ + // Get Custom prefix. + const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions(); + // Form sheet name identical to the first generated sheet name when + // creating an internal document, e.g. 'Sheet1'. + return rOpt.GetInitTabPrefix() + "1"; +} +} + +void ScExternalRefCache::initializeDoc(sal_uInt16 nFileId, const vector<OUString>& rTabNames, + const OUString& rBaseName) +{ + DocItem* pDoc = getDocItem(nFileId); + if (!pDoc) + return; + + size_t n = rTabNames.size(); + + // table name list - the list must include all table names in the source + // document and only to be populated when loading the source document, not + // when loading cached data from, say, Excel XCT/CRN records. + vector<TableName> aNewTabNames; + aNewTabNames.reserve(n); + for (const auto& rTabName : rTabNames) + { + TableName aNameItem(ScGlobal::getCharClass().uppercase(rTabName), rTabName); + aNewTabNames.push_back(aNameItem); + } + pDoc->maTableNames.swap(aNewTabNames); + + // data tables - preserve any existing data that may have been set during + // file import. + vector<TableTypeRef> aNewTables(n); + for (size_t i = 0; i < n; ++i) + { + size_t nIndex; + if (lcl_getStrictTableDataIndex(pDoc->maTableNameIndex, pDoc->maTableNames[i].maUpperName, nIndex)) + { + aNewTables[i] = pDoc->maTables[nIndex]; + } + } + pDoc->maTables.swap(aNewTables); + + // name index map + TableNameIndexMap aNewNameIndex; + for (size_t i = 0; i < n; ++i) + aNewNameIndex.emplace(pDoc->maTableNames[i].maUpperName, i); + pDoc->maTableNameIndex.swap(aNewNameIndex); + + // Setup name for Sheet1 vs base name to be able to load documents + // that store the base name as table name, or vice versa. + pDoc->maSingleTableNameAlias.clear(); + if (!rBaseName.isEmpty() && pDoc->maTableNames.size() == 1) + { + OUString aSheetName = getFirstSheetName(); + // If the one and only table name matches exactly, carry on the base + // file name for further alias use. If instead the table name matches + // the base name, carry on the sheet name as alias. + if (ScGlobal::GetTransliteration().isEqual( pDoc->maTableNames[0].maRealName, aSheetName)) + pDoc->maSingleTableNameAlias = rBaseName; + else if (ScGlobal::GetTransliteration().isEqual( pDoc->maTableNames[0].maRealName, rBaseName)) + pDoc->maSingleTableNameAlias = aSheetName; + } + + pDoc->mbInitFromSource = true; +} + +ScExternalRefCache::TableNameIndexMap::const_iterator ScExternalRefCache::DocItem::findTableNameIndex( + const OUString& rTabName ) const +{ + const OUString aTabNameUpper = ScGlobal::getCharClass().uppercase( rTabName); + TableNameIndexMap::const_iterator itrTabName = maTableNameIndex.find( aTabNameUpper); + if (itrTabName != maTableNameIndex.end()) + return itrTabName; + + // Since some time for external references to CSV files the base name is + // used as sheet name instead of Sheet1, check if we can resolve that. + // Also helps users that got accustomed to one or the other way. + if (maSingleTableNameAlias.isEmpty() || maTableNameIndex.size() != 1) + return itrTabName; + + // maSingleTableNameAlias has been set up only if the original file loaded + // had exactly one sheet and internal sheet name was Sheet1 or localized or + // customized equivalent, or base name. + if (aTabNameUpper == ScGlobal::getCharClass().uppercase( maSingleTableNameAlias)) + return maTableNameIndex.begin(); + + return itrTabName; +} + +bool ScExternalRefCache::DocItem::getSingleTableNameAlternative( OUString& rTabName ) const +{ + if (maSingleTableNameAlias.isEmpty() || maTableNames.size() != 1) + return false; + if (ScGlobal::GetTransliteration().isEqual( rTabName, maTableNames[0].maRealName)) + { + rTabName = maSingleTableNameAlias; + return true; + } + if (ScGlobal::GetTransliteration().isEqual( rTabName, maSingleTableNameAlias)) + { + rTabName = maTableNames[0].maRealName; + return true; + } + return false; +} + +bool ScExternalRefCache::getSrcDocTable( const ScDocument& rSrcDoc, const OUString& rTabName, SCTAB& rTab, + sal_uInt16 nFileId ) const +{ + bool bFound = rSrcDoc.GetTable( rTabName, rTab); + if (!bFound) + { + // Check the one table alias alternative. + const DocItem* pDoc = getDocItem( nFileId ); + if (pDoc) + { + OUString aTabName( rTabName); + if (pDoc->getSingleTableNameAlternative( aTabName)) + bFound = rSrcDoc.GetTable( aTabName, rTab); + } + } + return bFound; +} + +OUString ScExternalRefCache::getTableName(sal_uInt16 nFileId, size_t nCacheId) const +{ + if( DocItem* pDoc = getDocItem( nFileId ) ) + if( nCacheId < pDoc->maTableNames.size() ) + return pDoc->maTableNames[ nCacheId ].maRealName; + return OUString(); +} + +void ScExternalRefCache::getAllTableNames(sal_uInt16 nFileId, vector<OUString>& rTabNames) const +{ + rTabNames.clear(); + DocItem* pDoc = getDocItem(nFileId); + if (!pDoc) + return; + + size_t n = pDoc->maTableNames.size(); + rTabNames.reserve(n); + for (const auto& rTableName : pDoc->maTableNames) + rTabNames.push_back(rTableName.maRealName); +} + +SCTAB ScExternalRefCache::getTabSpan( sal_uInt16 nFileId, const OUString& rStartTabName, const OUString& rEndTabName ) const +{ + DocItem* pDoc = getDocItem(nFileId); + if (!pDoc) + return -1; + + vector<TableName>::const_iterator itrBeg = pDoc->maTableNames.begin(); + vector<TableName>::const_iterator itrEnd = pDoc->maTableNames.end(); + + vector<TableName>::const_iterator itrStartTab = ::std::find_if( itrBeg, itrEnd, + TabNameSearchPredicate( rStartTabName)); + if (itrStartTab == itrEnd) + return -1; + + vector<TableName>::const_iterator itrEndTab = ::std::find_if( itrBeg, itrEnd, + TabNameSearchPredicate( rEndTabName)); + if (itrEndTab == itrEnd) + return 0; + + size_t nStartDist = ::std::distance( itrBeg, itrStartTab); + size_t nEndDist = ::std::distance( itrBeg, itrEndTab); + return nStartDist <= nEndDist ? static_cast<SCTAB>(nEndDist - nStartDist + 1) : -static_cast<SCTAB>(nStartDist - nEndDist + 1); +} + +void ScExternalRefCache::getAllNumberFormats(vector<sal_uInt32>& rNumFmts) const +{ + osl::MutexGuard aGuard(&maMtxDocs); + + using ::std::sort; + using ::std::unique; + + vector<sal_uInt32> aNumFmts; + for (const auto& rEntry : maDocs) + { + const vector<TableTypeRef>& rTables = rEntry.second.maTables; + for (const TableTypeRef& pTab : rTables) + { + if (!pTab) + continue; + + pTab->getAllNumberFormats(aNumFmts); + } + } + + // remove duplicates. + sort(aNumFmts.begin(), aNumFmts.end()); + aNumFmts.erase(unique(aNumFmts.begin(), aNumFmts.end()), aNumFmts.end()); + rNumFmts.swap(aNumFmts); +} + +bool ScExternalRefCache::setCacheDocReferenced( sal_uInt16 nFileId ) +{ + DocItem* pDocItem = getDocItem(nFileId); + if (!pDocItem) + return areAllCacheTablesReferenced(); + + for (auto& rxTab : pDocItem->maTables) + { + if (rxTab) + rxTab->setReferenced(true); + } + addCacheDocToReferenced( nFileId); + return areAllCacheTablesReferenced(); +} + +bool ScExternalRefCache::setCacheTableReferenced( sal_uInt16 nFileId, const OUString& rTabName, size_t nSheets ) +{ + DocItem* pDoc = getDocItem(nFileId); + if (pDoc) + { + size_t nIndex = 0; + if (pDoc->getTableDataIndex( rTabName, nIndex)) + { + size_t nStop = ::std::min( nIndex + nSheets, pDoc->maTables.size()); + for (size_t i = nIndex; i < nStop; ++i) + { + TableTypeRef pTab = pDoc->maTables[i]; + if (pTab) + { + if (!pTab->isReferenced()) + { + pTab->setReferenced(true); + addCacheTableToReferenced( nFileId, i); + } + } + } + } + } + return areAllCacheTablesReferenced(); +} + +void ScExternalRefCache::setAllCacheTableReferencedStati( bool bReferenced ) +{ + osl::MutexGuard aGuard(&maMtxDocs); + + if (bReferenced) + { + maReferenced.reset(0); + for (auto& rEntry : maDocs) + { + ScExternalRefCache::DocItem& rDocItem = rEntry.second; + for (auto& rxTab : rDocItem.maTables) + { + if (rxTab) + rxTab->setReferenced(true); + } + } + } + else + { + size_t nDocs = 0; + auto itrMax = std::max_element(maDocs.begin(), maDocs.end(), + [](const DocDataType::value_type& a, const DocDataType::value_type& b) { return a.first < b.first; }); + if (itrMax != maDocs.end()) + nDocs = itrMax->first + 1; + maReferenced.reset( nDocs); + + for (auto& [nFileId, rDocItem] : maDocs) + { + size_t nTables = rDocItem.maTables.size(); + ReferencedStatus::DocReferenced & rDocReferenced = maReferenced.maDocs[nFileId]; + // All referenced => non-existing tables evaluate as completed. + rDocReferenced.maTables.resize( nTables, true); + for (size_t i=0; i < nTables; ++i) + { + TableTypeRef & xTab = rDocItem.maTables[i]; + if (xTab) + { + xTab->setReferenced(false); + rDocReferenced.maTables[i] = false; + rDocReferenced.mbAllTablesReferenced = false; + // An addCacheTableToReferenced() actually may have + // resulted in mbAllReferenced been set. Clear it. + maReferenced.mbAllReferenced = false; + } + } + } + } +} + +void ScExternalRefCache::addCacheTableToReferenced( sal_uInt16 nFileId, size_t nIndex ) +{ + if (nFileId >= maReferenced.maDocs.size()) + return; + + ::std::vector<bool> & rTables = maReferenced.maDocs[nFileId].maTables; + size_t nTables = rTables.size(); + if (nIndex >= nTables) + return; + + if (!rTables[nIndex]) + { + rTables[nIndex] = true; + size_t i = 0; + while (i < nTables && rTables[i]) + ++i; + if (i == nTables) + { + maReferenced.maDocs[nFileId].mbAllTablesReferenced = true; + maReferenced.checkAllDocs(); + } + } +} + +void ScExternalRefCache::addCacheDocToReferenced( sal_uInt16 nFileId ) +{ + if (nFileId >= maReferenced.maDocs.size()) + return; + + if (!maReferenced.maDocs[nFileId].mbAllTablesReferenced) + { + ::std::vector<bool> & rTables = maReferenced.maDocs[nFileId].maTables; + size_t nSize = rTables.size(); + for (size_t i=0; i < nSize; ++i) + rTables[i] = true; + maReferenced.maDocs[nFileId].mbAllTablesReferenced = true; + maReferenced.checkAllDocs(); + } +} + +void ScExternalRefCache::getAllCachedDataSpans( const ScDocument& rSrcDoc, sal_uInt16 nFileId, sc::ColumnSpanSet& rSet ) const +{ + const DocItem* pDocItem = getDocItem(nFileId); + if (!pDocItem) + // This document is not cached. + return; + + const std::vector<TableTypeRef>& rTables = pDocItem->maTables; + for (size_t nTab = 0, nTabCount = rTables.size(); nTab < nTabCount; ++nTab) + { + TableTypeRef pTab = rTables[nTab]; + if (!pTab) + continue; + + std::vector<SCROW> aRows; + pTab->getAllRows(aRows); + for (SCROW nRow : aRows) + { + std::vector<SCCOL> aCols; + pTab->getAllCols(nRow, aCols); + for (SCCOL nCol : aCols) + { + rSet.set(rSrcDoc, nTab, nCol, nRow, true); + } + } + } +} + +ScExternalRefCache::ReferencedStatus::ReferencedStatus() : + mbAllReferenced(false) +{ + reset(0); +} + +void ScExternalRefCache::ReferencedStatus::reset( size_t nDocs ) +{ + if (nDocs) + { + mbAllReferenced = false; + DocReferencedVec aRefs( nDocs); + maDocs.swap( aRefs); + } + else + { + mbAllReferenced = true; + DocReferencedVec aRefs; + maDocs.swap( aRefs); + } +} + +void ScExternalRefCache::ReferencedStatus::checkAllDocs() +{ + if (std::all_of(maDocs.begin(), maDocs.end(), [](const DocReferenced& rDoc) { return rDoc.mbAllTablesReferenced; })) + mbAllReferenced = true; +} + +ScExternalRefCache::TableTypeRef ScExternalRefCache::getCacheTable(sal_uInt16 nFileId, size_t nTabIndex) const +{ + DocItem* pDoc = getDocItem(nFileId); + if (!pDoc || nTabIndex >= pDoc->maTables.size()) + return TableTypeRef(); + + return pDoc->maTables[nTabIndex]; +} + +ScExternalRefCache::TableTypeRef ScExternalRefCache::getCacheTable(sal_uInt16 nFileId, const OUString& rTabName, + bool bCreateNew, size_t* pnIndex, const OUString* pExtUrl) +{ + // In API, the index is transported as cached sheet ID of type sal_Int32 in + // sheet::SingleReference.Sheet or sheet::ComplexReference.Reference1.Sheet + // in a sheet::FormulaToken, choose a sensible value for N/A. Effectively + // being 0xffffffff + const size_t nNotAvailable = static_cast<size_t>( static_cast<sal_Int32>( -1)); + + DocItem* pDoc = getDocItem(nFileId); + if (!pDoc) + { + if (pnIndex) *pnIndex = nNotAvailable; + return TableTypeRef(); + } + + DocItem& rDoc = *pDoc; + + size_t nIndex; + if (rDoc.getTableDataIndex(rTabName, nIndex)) + { + // specified table found. + if( pnIndex ) *pnIndex = nIndex; + if (bCreateNew && !rDoc.maTables[nIndex]) + rDoc.maTables[nIndex] = std::make_shared<Table>(); + + return rDoc.maTables[nIndex]; + } + + if (!bCreateNew) + { + if (pnIndex) *pnIndex = nNotAvailable; + return TableTypeRef(); + } + + // If this is the first table to be created propagate the base name or + // Sheet1 as an alias. For subsequent tables remove it again. + if (rDoc.maTableNames.empty()) + { + if (pExtUrl) + { + const OUString aBaseName( INetURLObject( *pExtUrl).GetBase()); + const OUString aSheetName( getFirstSheetName()); + if (ScGlobal::GetTransliteration().isEqual( rTabName, aSheetName)) + pDoc->maSingleTableNameAlias = aBaseName; + else if (ScGlobal::GetTransliteration().isEqual( rTabName, aBaseName)) + pDoc->maSingleTableNameAlias = aSheetName; + } + } + else + { + rDoc.maSingleTableNameAlias.clear(); + } + + // Specified table doesn't exist yet. Create one. + OUString aTabNameUpper = ScGlobal::getCharClass().uppercase(rTabName); + nIndex = rDoc.maTables.size(); + if( pnIndex ) *pnIndex = nIndex; + TableTypeRef pTab = std::make_shared<Table>(); + rDoc.maTables.push_back(pTab); + rDoc.maTableNames.emplace_back(aTabNameUpper, rTabName); + rDoc.maTableNameIndex.emplace(aTabNameUpper, nIndex); + return pTab; +} + +void ScExternalRefCache::clearCache(sal_uInt16 nFileId) +{ + osl::MutexGuard aGuard(&maMtxDocs); + maDocs.erase(nFileId); +} + +void ScExternalRefCache::clearCacheTables(sal_uInt16 nFileId) +{ + osl::MutexGuard aGuard(&maMtxDocs); + DocItem* pDocItem = getDocItem(nFileId); + if (!pDocItem) + // This document is not cached at all. + return; + + // Clear all cache table content, but keep the tables. + std::vector<TableTypeRef>& rTabs = pDocItem->maTables; + for (TableTypeRef & pTab : rTabs) + { + if (!pTab) + continue; + + pTab->clear(); + } + + // Clear the external range name caches. + pDocItem->maRangeNames.clear(); + pDocItem->maRangeArrays.clear(); + pDocItem->maRealRangeNameMap.clear(); +} + +ScExternalRefCache::DocItem* ScExternalRefCache::getDocItem(sal_uInt16 nFileId) const +{ + osl::MutexGuard aGuard(&maMtxDocs); + + using ::std::pair; + DocDataType::iterator itrDoc = maDocs.find(nFileId); + if (itrDoc == maDocs.end()) + { + // specified document is not cached. + pair<DocDataType::iterator, bool> res = maDocs.emplace( + nFileId, DocItem()); + + if (!res.second) + // insertion failed. + return nullptr; + + itrDoc = res.first; + } + + return &itrDoc->second; +} + +ScExternalRefLink::ScExternalRefLink(ScDocument& rDoc, sal_uInt16 nFileId) : + ::sfx2::SvBaseLink(::SfxLinkUpdateMode::ONCALL, SotClipboardFormatId::SIMPLE_FILE), + mnFileId(nFileId), + mrDoc(rDoc), + mbDoRefresh(true) +{ +} + +ScExternalRefLink::~ScExternalRefLink() +{ +} + +void ScExternalRefLink::Closed() +{ + ScExternalRefManager* pMgr = mrDoc.GetExternalRefManager(); + pMgr->breakLink(mnFileId); +} + +::sfx2::SvBaseLink::UpdateResult ScExternalRefLink::DataChanged(const OUString& /*rMimeType*/, const Any& /*rValue*/) +{ + if (!mbDoRefresh) + return SUCCESS; + + OUString aFile, aFilter; + sfx2::LinkManager::GetDisplayNames(this, nullptr, &aFile, nullptr, &aFilter); + ScExternalRefManager* pMgr = mrDoc.GetExternalRefManager(); + + if (!pMgr->isFileLoadable(aFile)) + return ERROR_GENERAL; + + const OUString* pCurFile = pMgr->getExternalFileName(mnFileId); + if (!pCurFile) + return ERROR_GENERAL; + + if (*pCurFile == aFile) + { + // Refresh the current source document. + if (!pMgr->refreshSrcDocument(mnFileId)) + return ERROR_GENERAL; + } + else + { + // The source document has changed. + ScViewData* pViewData = ScDocShell::GetViewData(); + if (!pViewData) + return ERROR_GENERAL; + + ScDocShell* pDocShell = pViewData->GetDocShell(); + ScDocShellModificator aMod(*pDocShell); + pMgr->switchSrcFile(mnFileId, aFile, aFilter); + aMod.SetDocumentModified(); + } + + return SUCCESS; +} + +void ScExternalRefLink::Edit(weld::Window* pParent, const Link<SvBaseLink&,void>& /*rEndEditHdl*/) +{ + SvBaseLink::Edit(pParent, Link<SvBaseLink&,void>()); +} + +void ScExternalRefLink::SetDoRefresh(bool b) +{ + mbDoRefresh = b; +} + +static FormulaToken* convertToToken( ScDocument& rHostDoc, const ScDocument& rSrcDoc, ScRefCellValue& rCell ) +{ + if (rCell.hasEmptyValue()) + { + bool bInherited = (rCell.meType == CELLTYPE_FORMULA); + return new ScEmptyCellToken(bInherited, false); + } + + switch (rCell.meType) + { + case CELLTYPE_EDIT: + case CELLTYPE_STRING: + { + OUString aStr = rCell.getString(&rSrcDoc); + svl::SharedString aSS = rHostDoc.GetSharedStringPool().intern(aStr); + return new formula::FormulaStringToken(aSS); + } + case CELLTYPE_VALUE: + return new formula::FormulaDoubleToken(rCell.mfValue); + case CELLTYPE_FORMULA: + { + ScFormulaCell* pFCell = rCell.mpFormula; + FormulaError nError = pFCell->GetErrCode(); + if (nError != FormulaError::NONE) + return new FormulaErrorToken( nError); + else if (pFCell->IsValue()) + { + double fVal = pFCell->GetValue(); + return new formula::FormulaDoubleToken(fVal); + } + else + { + svl::SharedString aSS = rHostDoc.GetSharedStringPool().intern( pFCell->GetString().getString()); + return new formula::FormulaStringToken(aSS); + } + } + default: + OSL_FAIL("attempted to convert an unknown cell type."); + } + + return nullptr; +} + +static std::unique_ptr<ScTokenArray> convertToTokenArray( + ScDocument& rHostDoc, const ScDocument& rSrcDoc, ScRange& rRange, vector<ScExternalRefCache::SingleRangeData>& rCacheData ) +{ + ScAddress& s = rRange.aStart; + ScAddress& e = rRange.aEnd; + + const SCTAB nTab1 = s.Tab(), nTab2 = e.Tab(); + const SCCOL nCol1 = s.Col(), nCol2 = e.Col(); + const SCROW nRow1 = s.Row(), nRow2 = e.Row(); + + if (nTab2 != nTab1) + // For now, we don't support multi-sheet ranges intentionally because + // we don't have a way to express them in a single token. In the + // future we can introduce a new stack variable type svMatrixList with + // a new token type that can store a 3D matrix value and convert a 3D + // range to it. + return nullptr; + + std::unique_ptr<ScRange> pUsedRange; + + unique_ptr<ScTokenArray> pArray(new ScTokenArray(rSrcDoc)); + bool bFirstTab = true; + vector<ScExternalRefCache::SingleRangeData>::iterator + itrCache = rCacheData.begin(), itrCacheEnd = rCacheData.end(); + + for (SCTAB nTab = nTab1; nTab <= nTab2 && itrCache != itrCacheEnd; ++nTab, ++itrCache) + { + // Only loop within the data area. + SCCOL nDataCol1 = nCol1, nDataCol2 = nCol2; + SCROW nDataRow1 = nRow1, nDataRow2 = nRow2; + bool bShrunk; + if (!rSrcDoc.ShrinkToUsedDataArea( bShrunk, nTab, nDataCol1, nDataRow1, nDataCol2, nDataRow2, false)) + // no data within specified range. + continue; + + if (pUsedRange) + // Make sure the used area only grows, not shrinks. + pUsedRange->ExtendTo(ScRange(nDataCol1, nDataRow1, 0, nDataCol2, nDataRow2, 0)); + else + pUsedRange.reset(new ScRange(nDataCol1, nDataRow1, 0, nDataCol2, nDataRow2, 0)); + + SCSIZE nMatrixColumns = static_cast<SCSIZE>(nCol2-nCol1+1); + SCSIZE nMatrixRows = static_cast<SCSIZE>(nRow2-nRow1+1); + ScMatrixRef xMat = new ScMatrix( nMatrixColumns, nMatrixRows); + + // Check if size could be allocated and if not skip the fill, there's + // one error element instead. But retry first with the actual data area + // if that is smaller than the original range, which works for most + // functions just not some that operate/compare with the original size + // and expect empty values in non-data areas. + // Restrict this though to ranges of entire columns or rows, other + // ranges might be on purpose. (Other special cases to handle?) + /* TODO: sparse matrix could help */ + SCSIZE nMatCols, nMatRows; + xMat->GetDimensions( nMatCols, nMatRows); + if (nMatCols == nMatrixColumns && nMatRows == nMatrixRows) + { + rSrcDoc.FillMatrix(*xMat, nTab, nCol1, nRow1, nCol2, nRow2, &rHostDoc.GetSharedStringPool()); + } + else if ((nCol1 == 0 && nCol2 == rSrcDoc.MaxCol()) || (nRow1 == 0 && nRow2 == rSrcDoc.MaxRow())) + { + if ((o3tl::make_unsigned(nDataCol2-nDataCol1+1) < nMatrixColumns) || + (o3tl::make_unsigned(nDataRow2-nDataRow1+1) < nMatrixRows)) + { + nMatrixColumns = static_cast<SCSIZE>(nDataCol2-nDataCol1+1); + nMatrixRows = static_cast<SCSIZE>(nDataRow2-nDataRow1+1); + xMat = new ScMatrix( nMatrixColumns, nMatrixRows); + xMat->GetDimensions( nMatCols, nMatRows); + if (nMatCols == nMatrixColumns && nMatRows == nMatrixRows) + rSrcDoc.FillMatrix(*xMat, nTab, nDataCol1, nDataRow1, nDataCol2, nDataRow2, &rHostDoc.GetSharedStringPool()); + } + } + + if (!bFirstTab) + pArray->AddOpCode(ocSep); + + ScMatrixToken aToken(xMat); + pArray->AddToken(aToken); + + itrCache->mpRangeData = xMat; + + bFirstTab = false; + } + + if (!pUsedRange) + return nullptr; + + s.SetCol(pUsedRange->aStart.Col()); + s.SetRow(pUsedRange->aStart.Row()); + e.SetCol(pUsedRange->aEnd.Col()); + e.SetRow(pUsedRange->aEnd.Row()); + + return pArray; +} + +static std::unique_ptr<ScTokenArray> lcl_fillEmptyMatrix(const ScDocument& rDoc, const ScRange& rRange) +{ + SCSIZE nC = static_cast<SCSIZE>(rRange.aEnd.Col()-rRange.aStart.Col()+1); + SCSIZE nR = static_cast<SCSIZE>(rRange.aEnd.Row()-rRange.aStart.Row()+1); + ScMatrixRef xMat = new ScMatrix(nC, nR); + + ScMatrixToken aToken(xMat); + unique_ptr<ScTokenArray> pArray(new ScTokenArray(rDoc)); + pArray->AddToken(aToken); + return pArray; +} + +namespace { +bool isLinkUpdateAllowedInDoc(const ScDocument& rDoc) +{ + SfxObjectShell* pDocShell = rDoc.GetDocumentShell(); + if (!pDocShell) + return rDoc.IsFunctionAccess(); + + return pDocShell->GetEmbeddedObjectContainer().getUserAllowsLinkUpdate(); +} +} + +ScExternalRefManager::ScExternalRefManager(ScDocument& rDoc) : + mrDoc(rDoc), + maRefCache(rDoc), + mbInReferenceMarking(false), + mbUserInteractionEnabled(true), + mbDocTimerEnabled(true), + maSrcDocTimer( "sc::ScExternalRefManager maSrcDocTimer" ) +{ + maSrcDocTimer.SetInvokeHandler( LINK(this, ScExternalRefManager, TimeOutHdl) ); + maSrcDocTimer.SetTimeout(SRCDOC_SCAN_INTERVAL); +} + +ScExternalRefManager::~ScExternalRefManager() +{ + clear(); +} + +OUString ScExternalRefManager::getCacheTableName(sal_uInt16 nFileId, size_t nTabIndex) const +{ + return maRefCache.getTableName(nFileId, nTabIndex); +} + +ScExternalRefCache::TableTypeRef ScExternalRefManager::getCacheTable(sal_uInt16 nFileId, size_t nTabIndex) const +{ + return maRefCache.getCacheTable(nFileId, nTabIndex); +} + +ScExternalRefCache::TableTypeRef ScExternalRefManager::getCacheTable( + sal_uInt16 nFileId, const OUString& rTabName, bool bCreateNew, size_t* pnIndex, const OUString* pExtUrl) +{ + return maRefCache.getCacheTable(nFileId, rTabName, bCreateNew, pnIndex, pExtUrl); +} + +ScExternalRefManager::LinkListener::LinkListener() +{ +} + +ScExternalRefManager::LinkListener::~LinkListener() +{ +} + +ScExternalRefManager::ApiGuard::ApiGuard(const ScDocument& rDoc) : + mpMgr(rDoc.GetExternalRefManager()), + mbOldInteractionEnabled(mpMgr->mbUserInteractionEnabled) +{ + // We don't want user interaction handled in the API. + mpMgr->mbUserInteractionEnabled = false; +} + +ScExternalRefManager::ApiGuard::~ApiGuard() +{ + // Restore old value. + mpMgr->mbUserInteractionEnabled = mbOldInteractionEnabled; +} + +void ScExternalRefManager::getAllCachedTableNames(sal_uInt16 nFileId, vector<OUString>& rTabNames) const +{ + maRefCache.getAllTableNames(nFileId, rTabNames); +} + +SCTAB ScExternalRefManager::getCachedTabSpan( sal_uInt16 nFileId, const OUString& rStartTabName, const OUString& rEndTabName ) const +{ + return maRefCache.getTabSpan( nFileId, rStartTabName, rEndTabName); +} + +void ScExternalRefManager::getAllCachedNumberFormats(vector<sal_uInt32>& rNumFmts) const +{ + maRefCache.getAllNumberFormats(rNumFmts); +} + +sal_uInt16 ScExternalRefManager::getExternalFileCount() const +{ + return static_cast< sal_uInt16 >( maSrcFiles.size() ); +} + +void ScExternalRefManager::markUsedByLinkListeners() +{ + bool bAllMarked = false; + for (const auto& [rFileId, rLinkListeners] : maLinkListeners) + { + if (!rLinkListeners.empty()) + bAllMarked = maRefCache.setCacheDocReferenced(rFileId); + + if (bAllMarked) + break; + /* TODO: LinkListeners should remember the table they're listening to. + * As is, listening to one table will mark all tables of the document + * being referenced. */ + } +} + +void ScExternalRefManager::markUsedExternalRefCells() +{ + for (const auto& rEntry : maRefCells) + { + for (ScFormulaCell* pCell : rEntry.second) + { + bool bUsed = pCell->MarkUsedExternalReferences(); + if (bUsed) + // Return true when at least one cell references external docs. + return; + } + } +} + +bool ScExternalRefManager::setCacheTableReferenced( sal_uInt16 nFileId, const OUString& rTabName, size_t nSheets ) +{ + return maRefCache.setCacheTableReferenced( nFileId, rTabName, nSheets); +} + +void ScExternalRefManager::setAllCacheTableReferencedStati( bool bReferenced ) +{ + mbInReferenceMarking = !bReferenced; + maRefCache.setAllCacheTableReferencedStati( bReferenced ); +} + +void ScExternalRefManager::storeRangeNameTokens(sal_uInt16 nFileId, const OUString& rName, const ScTokenArray& rArray) +{ + ScExternalRefCache::TokenArrayRef pNewArray; + if (!rArray.HasExternalRef()) + { + // Parse all tokens in this external range data, and replace each absolute + // reference token with an external reference token, and cache them. + pNewArray = std::make_shared<ScTokenArray>(mrDoc); + FormulaTokenArrayPlainIterator aIter(rArray); + for (const FormulaToken* pToken = aIter.First(); pToken; pToken = aIter.Next()) + { + bool bTokenAdded = false; + switch (pToken->GetType()) + { + case svSingleRef: + { + const ScSingleRefData& rRef = *pToken->GetSingleRef(); + OUString aTabName; + if (SCTAB nCacheId = rRef.Tab(); nCacheId >= 0) + aTabName = maRefCache.getTableName(nFileId, nCacheId); + ScExternalSingleRefToken aNewToken(nFileId, svl::SharedString(aTabName), // string not interned + *pToken->GetSingleRef()); + pNewArray->AddToken(aNewToken); + bTokenAdded = true; + } + break; + case svDoubleRef: + { + const ScSingleRefData& rRef = *pToken->GetSingleRef(); + OUString aTabName; + if (SCTAB nCacheId = rRef.Tab(); nCacheId >= 0) + aTabName = maRefCache.getTableName(nFileId, nCacheId); + ScExternalDoubleRefToken aNewToken(nFileId, svl::SharedString(aTabName), // string not interned + *pToken->GetDoubleRef()); + pNewArray->AddToken(aNewToken); + bTokenAdded = true; + } + break; + default: + ; // nothing + } + + if (!bTokenAdded) + pNewArray->AddToken(*pToken); + } + } + else + pNewArray = rArray.Clone(); + + maRefCache.setRangeNameTokens(nFileId, rName, pNewArray); +} + +namespace { + +/** + * Put a single cell data into internal cache table. + * + * @param pFmt optional cell format index that may need to be stored with + * the cell value. + */ +void putCellDataIntoCache( + ScExternalRefCache& rRefCache, const ScExternalRefCache::TokenRef& pToken, + sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rCell, + const ScExternalRefCache::CellFormat* pFmt) +{ + // Now, insert the token into cache table but don't cache empty cells. + if (pToken->GetType() != formula::svEmptyCell) + { + sal_uLong nFmtIndex = (pFmt && pFmt->mbIsSet) ? pFmt->mnIndex : 0; + rRefCache.setCellData(nFileId, rTabName, rCell.Col(), rCell.Row(), pToken, nFmtIndex); + } +} + +/** + * Put the data into our internal cache table. + * + * @param rRefCache cache table set. + * @param pArray single range data to be returned. + * @param nFileId external file ID + * @param rTabName name of the table where the data should be cached. + * @param rCacheData range data to be cached. + * @param rCacheRange original cache range, including the empty region if + * any. + * @param rDataRange reduced cache range that includes only the non-empty + * data area. + */ +void putRangeDataIntoCache( + ScExternalRefCache& rRefCache, ScExternalRefCache::TokenArrayRef& pArray, + sal_uInt16 nFileId, const OUString& rTabName, + const vector<ScExternalRefCache::SingleRangeData>& rCacheData, + const ScRange& rCacheRange, const ScRange& rDataRange) +{ + if (pArray) + // Cache these values. + rRefCache.setCellRangeData(nFileId, rDataRange, rCacheData, pArray); + else + { + // Array is empty. Fill it with an empty matrix of the required size. + pArray = lcl_fillEmptyMatrix(rRefCache.getDoc(), rCacheRange); + + // Make sure to set this range 'cached', to prevent unnecessarily + // accessing the src document time and time again. + ScExternalRefCache::TableTypeRef pCacheTab = + rRefCache.getCacheTable(nFileId, rTabName, true, nullptr, nullptr); + if (pCacheTab) + pCacheTab->setCachedCellRange( + rCacheRange.aStart.Col(), rCacheRange.aStart.Row(), rCacheRange.aEnd.Col(), rCacheRange.aEnd.Row()); + } +} + +/** + * When accessing an external document for the first time, we need to + * populate the cache with all its sheet names (whether they are referenced + * or not) in the correct order. Many client codes that use external + * references make this assumption. + * + * @param rRefCache cache table set. + * @param pSrcDoc source document instance. + * @param nFileId external file ID associated with the source document. + */ +void initDocInCache(ScExternalRefCache& rRefCache, const ScDocument* pSrcDoc, sal_uInt16 nFileId) +{ + if (!pSrcDoc) + return; + + if (rRefCache.isDocInitialized(nFileId)) + // Already initialized. No need to do this twice. + return; + + SCTAB nTabCount = pSrcDoc->GetTableCount(); + if (!nTabCount) + return; + + // Populate the cache with all table names in the source document. + vector<OUString> aTabNames; + aTabNames.reserve(nTabCount); + for (SCTAB i = 0; i < nTabCount; ++i) + { + OUString aName; + pSrcDoc->GetName(i, aName); + aTabNames.push_back(aName); + } + + // Obtain the base name, don't bother if there are more than one sheets. + OUString aBaseName; + if (nTabCount == 1) + { + const SfxObjectShell* pShell = pSrcDoc->GetDocumentShell(); + if (pShell && pShell->GetMedium()) + { + OUString aName = pShell->GetMedium()->GetName(); + aBaseName = INetURLObject( aName).GetBase(); + } + } + + rRefCache.initializeDoc(nFileId, aTabNames, aBaseName); +} + +} + +bool ScExternalRefManager::getSrcDocTable( const ScDocument& rSrcDoc, const OUString& rTabName, SCTAB& rTab, + sal_uInt16 nFileId ) const +{ + return maRefCache.getSrcDocTable( rSrcDoc, rTabName, rTab, nFileId); +} + +ScExternalRefCache::TokenRef ScExternalRefManager::getSingleRefToken( + sal_uInt16 nFileId, const OUString& rTabName, const ScAddress& rCell, + const ScAddress* pCurPos, SCTAB* pTab, ScExternalRefCache::CellFormat* pFmt) +{ + if (pCurPos) + insertRefCell(nFileId, *pCurPos); + + maybeLinkExternalFile(nFileId); + + if (pTab) + *pTab = -1; + + if (pFmt) + pFmt->mbIsSet = false; + + ScDocument* pSrcDoc = getInMemorySrcDocument(nFileId); + if (pSrcDoc) + { + // source document already loaded in memory. Re-use this instance. + SCTAB nTab; + if (!getSrcDocTable( *pSrcDoc, rTabName, nTab, nFileId)) + { + // specified table name doesn't exist in the source document. + ScExternalRefCache::TokenRef pToken(new FormulaErrorToken(FormulaError::NoRef)); + return pToken; + } + + if (pTab) + *pTab = nTab; + + ScExternalRefCache::TokenRef pToken = + getSingleRefTokenFromSrcDoc( + nFileId, *pSrcDoc, ScAddress(rCell.Col(),rCell.Row(),nTab), pFmt); + + putCellDataIntoCache(maRefCache, pToken, nFileId, rTabName, rCell, pFmt); + return pToken; + } + + // Check if the given table name and the cell position is cached. + sal_uInt32 nFmtIndex = 0; + ScExternalRefCache::TokenRef pToken = maRefCache.getCellData( + nFileId, rTabName, rCell.Col(), rCell.Row(), &nFmtIndex); + if (pToken) + { + // Cache hit ! + fillCellFormat(nFmtIndex, pFmt); + return pToken; + } + + // reference not cached. read from the source document. + pSrcDoc = getSrcDocument(nFileId); + if (!pSrcDoc) + { + // Source document not reachable. + if (!isLinkUpdateAllowedInDoc(mrDoc)) + { + // Indicate with specific error. + pToken.reset(new FormulaErrorToken(FormulaError::LinkFormulaNeedingCheck)); + } + else + { + // Throw a reference error. + pToken.reset(new FormulaErrorToken(FormulaError::NoRef)); + } + return pToken; + } + + SCTAB nTab; + if (!getSrcDocTable( *pSrcDoc, rTabName, nTab, nFileId)) + { + // specified table name doesn't exist in the source document. + pToken.reset(new FormulaErrorToken(FormulaError::NoRef)); + return pToken; + } + + if (pTab) + *pTab = nTab; + + SCCOL nDataCol1 = 0, nDataCol2 = pSrcDoc->MaxCol(); + SCROW nDataRow1 = 0, nDataRow2 = pSrcDoc->MaxRow(); + bool bData = pSrcDoc->ShrinkToDataArea(nTab, nDataCol1, nDataRow1, nDataCol2, nDataRow2); + if (!bData || rCell.Col() < nDataCol1 || nDataCol2 < rCell.Col() || rCell.Row() < nDataRow1 || nDataRow2 < rCell.Row()) + { + // requested cell is outside the data area. Don't even bother caching + // this data, but add it to the cached range to prevent accessing the + // source document time and time again. + ScExternalRefCache::TableTypeRef pCacheTab = + maRefCache.getCacheTable(nFileId, rTabName, true, nullptr, nullptr); + if (pCacheTab) + pCacheTab->setCachedCell(rCell.Col(), rCell.Row()); + + pToken.reset(new ScEmptyCellToken(false, false)); + return pToken; + } + + pToken = getSingleRefTokenFromSrcDoc( + nFileId, *pSrcDoc, ScAddress(rCell.Col(),rCell.Row(),nTab), pFmt); + + putCellDataIntoCache(maRefCache, pToken, nFileId, rTabName, rCell, pFmt); + return pToken; +} + +ScExternalRefCache::TokenArrayRef ScExternalRefManager::getDoubleRefTokens( + sal_uInt16 nFileId, const OUString& rTabName, const ScRange& rRange, const ScAddress* pCurPos) +{ + if (pCurPos) + insertRefCell(nFileId, *pCurPos); + + maybeLinkExternalFile(nFileId); + + ScRange aDataRange(rRange); + ScDocument* pSrcDoc = getInMemorySrcDocument(nFileId); + if (pSrcDoc) + { + // Document already loaded in memory. + vector<ScExternalRefCache::SingleRangeData> aCacheData; + ScExternalRefCache::TokenArrayRef pArray = + getDoubleRefTokensFromSrcDoc(*pSrcDoc, rTabName, aDataRange, aCacheData); + + // Put the data into cache. + putRangeDataIntoCache(maRefCache, pArray, nFileId, rTabName, aCacheData, rRange, aDataRange); + return pArray; + } + + // Check if the given table name and the cell position is cached. + ScExternalRefCache::TokenArrayRef pArray = + maRefCache.getCellRangeData(nFileId, rTabName, rRange); + if (pArray) + // Cache hit ! + return pArray; + + pSrcDoc = getSrcDocument(nFileId); + if (!pSrcDoc) + { + // Source document is not reachable. Throw a reference error. + pArray = std::make_shared<ScTokenArray>(maRefCache.getDoc()); + pArray->AddToken(FormulaErrorToken(FormulaError::NoRef)); + return pArray; + } + + vector<ScExternalRefCache::SingleRangeData> aCacheData; + pArray = getDoubleRefTokensFromSrcDoc(*pSrcDoc, rTabName, aDataRange, aCacheData); + + // Put the data into cache. + putRangeDataIntoCache(maRefCache, pArray, nFileId, rTabName, aCacheData, rRange, aDataRange); + return pArray; +} + +ScExternalRefCache::TokenArrayRef ScExternalRefManager::getRangeNameTokens( + sal_uInt16 nFileId, const OUString& rName, const ScAddress* pCurPos) +{ + if (pCurPos) + insertRefCell(nFileId, *pCurPos); + + maybeLinkExternalFile(nFileId); + + OUString aName = rName; // make a copy to have the casing corrected. + ScDocument* pSrcDoc = getInMemorySrcDocument(nFileId); + if (pSrcDoc) + { + // Document already loaded in memory. + ScExternalRefCache::TokenArrayRef pArray = + getRangeNameTokensFromSrcDoc(nFileId, *pSrcDoc, aName); + + if (pArray) + // Cache this range name array. + maRefCache.setRangeNameTokens(nFileId, aName, pArray); + + return pArray; + } + + ScExternalRefCache::TokenArrayRef pArray = maRefCache.getRangeNameTokens(nFileId, rName); + if (pArray) + // This range name is cached. + return pArray; + + pSrcDoc = getSrcDocument(nFileId); + if (!pSrcDoc) + // failed to load document from disk. + return ScExternalRefCache::TokenArrayRef(); + + pArray = getRangeNameTokensFromSrcDoc(nFileId, *pSrcDoc, aName); + + if (pArray) + // Cache this range name array. + maRefCache.setRangeNameTokens(nFileId, aName, pArray); + + return pArray; +} + +namespace { + +bool hasRangeName(const ScDocument& rDoc, const OUString& rName) +{ + ScRangeName* pExtNames = rDoc.GetRangeName(); + OUString aUpperName = ScGlobal::getCharClass().uppercase(rName); + const ScRangeData* pRangeData = pExtNames->findByUpperName(aUpperName); + return pRangeData != nullptr; +} + +} + +bool ScExternalRefManager::isValidRangeName(sal_uInt16 nFileId, const OUString& rName) +{ + maybeLinkExternalFile(nFileId); + ScDocument* pSrcDoc = getInMemorySrcDocument(nFileId); + if (pSrcDoc) + { + // Only check the presence of the name. + if (hasRangeName(*pSrcDoc, rName)) + { + maRefCache.setRangeName(nFileId, rName); + return true; + } + return false; + } + + if (maRefCache.isValidRangeName(nFileId, rName)) + // Range name is cached. + return true; + + pSrcDoc = getSrcDocument(nFileId); + if (!pSrcDoc) + // failed to load document from disk. + return false; + + if (hasRangeName(*pSrcDoc, rName)) + { + maRefCache.setRangeName(nFileId, rName); + return true; + } + + return false; +} + +void ScExternalRefManager::refreshAllRefCells(sal_uInt16 nFileId) +{ + RefCellMap::iterator itrFile = maRefCells.find(nFileId); + if (itrFile == maRefCells.end()) + return; + + RefCellSet& rRefCells = itrFile->second; + for_each(rRefCells.begin(), rRefCells.end(), UpdateFormulaCell()); + + ScViewData* pViewData = ScDocShell::GetViewData(); + if (!pViewData) + return; + + ScTabViewShell* pVShell = pViewData->GetViewShell(); + if (!pVShell) + return; + + // Repainting the grid also repaints the texts, but is there a better way + // to refresh texts? + pVShell->Invalidate(FID_REPAINT); + pVShell->PaintGrid(); +} + +namespace { + +void insertRefCellByIterator( + const ScExternalRefManager::RefCellMap::iterator& itr, ScFormulaCell* pCell) +{ + if (pCell) + { + itr->second.insert(pCell); + pCell->SetIsExtRef(); + } +} + +} + +void ScExternalRefManager::insertRefCell(sal_uInt16 nFileId, const ScAddress& rCell) +{ + RefCellMap::iterator itr = maRefCells.find(nFileId); + if (itr == maRefCells.end()) + { + RefCellSet aRefCells; + pair<RefCellMap::iterator, bool> r = maRefCells.emplace( + nFileId, aRefCells); + if (!r.second) + // insertion failed. + return; + + itr = r.first; + } + + insertRefCellByIterator(itr, mrDoc.GetFormulaCell(rCell)); +} + +void ScExternalRefManager::insertRefCellFromTemplate( ScFormulaCell* pTemplateCell, ScFormulaCell* pCell ) +{ + if (!pTemplateCell || !pCell) + return; + + for (RefCellMap::iterator itr = maRefCells.begin(); itr != maRefCells.end(); ++itr) + { + if (itr->second.find(pTemplateCell) != itr->second.end()) + insertRefCellByIterator(itr, pCell); + } +} + +bool ScExternalRefManager::hasCellExternalReference(const ScAddress& rCell) +{ + ScFormulaCell* pCell = mrDoc.GetFormulaCell(rCell); + + if (pCell) + return std::any_of(maRefCells.begin(), maRefCells.end(), + [&pCell](const RefCellMap::value_type& rEntry) { return rEntry.second.find(pCell) != rEntry.second.end(); }); + + return false; +} + +void ScExternalRefManager::enableDocTimer( bool bEnable ) +{ + if (mbDocTimerEnabled == bEnable) + return; + + mbDocTimerEnabled = bEnable; + if (mbDocTimerEnabled) + { + if (!maDocShells.empty()) + { + for (auto& rEntry : maDocShells) + rEntry.second.maLastAccess = tools::Time(tools::Time::SYSTEM); + + maSrcDocTimer.Start(); + } + } + else + maSrcDocTimer.Stop(); +} + +void ScExternalRefManager::fillCellFormat(sal_uLong nFmtIndex, ScExternalRefCache::CellFormat* pFmt) const +{ + if (!pFmt) + return; + + SvNumFormatType nFmtType = mrDoc.GetFormatTable()->GetType(nFmtIndex); + if (nFmtType != SvNumFormatType::UNDEFINED) + { + pFmt->mbIsSet = true; + pFmt->mnIndex = nFmtIndex; + pFmt->mnType = nFmtType; + } +} + +ScExternalRefCache::TokenRef ScExternalRefManager::getSingleRefTokenFromSrcDoc( + sal_uInt16 nFileId, ScDocument& rSrcDoc, const ScAddress& rPos, + ScExternalRefCache::CellFormat* pFmt) +{ + // Get the cell from src doc, and convert it into a token. + ScRefCellValue aCell(rSrcDoc, rPos); + ScExternalRefCache::TokenRef pToken(convertToToken(mrDoc, rSrcDoc, aCell)); + + if (!pToken) + { + // Generate an error for unresolvable cells. + pToken.reset( new FormulaErrorToken( FormulaError::NoValue)); + } + + // Get number format information. + sal_uInt32 nFmtIndex = rSrcDoc.GetNumberFormat(rPos.Col(), rPos.Row(), rPos.Tab()); + nFmtIndex = getMappedNumberFormat(nFileId, nFmtIndex, rSrcDoc); + fillCellFormat(nFmtIndex, pFmt); + return pToken; +} + +ScExternalRefCache::TokenArrayRef ScExternalRefManager::getDoubleRefTokensFromSrcDoc( + const ScDocument& rSrcDoc, const OUString& rTabName, ScRange& rRange, + vector<ScExternalRefCache::SingleRangeData>& rCacheData) +{ + ScExternalRefCache::TokenArrayRef pArray; + SCTAB nTab1; + + if (!rSrcDoc.GetTable(rTabName, nTab1)) + { + // specified table name doesn't exist in the source document. + pArray = std::make_shared<ScTokenArray>(rSrcDoc); + pArray->AddToken(FormulaErrorToken(FormulaError::NoRef)); + return pArray; + } + + ScRange aRange(rRange); + aRange.PutInOrder(); + SCTAB nTabSpan = aRange.aEnd.Tab() - aRange.aStart.Tab(); + + vector<ScExternalRefCache::SingleRangeData> aCacheData; + aCacheData.reserve(nTabSpan+1); + aCacheData.emplace_back(); + aCacheData.back().maTableName = ScGlobal::getCharClass().uppercase(rTabName); + + for (SCTAB i = 1; i < nTabSpan + 1; ++i) + { + OUString aTabName; + if (!rSrcDoc.GetName(nTab1 + 1, aTabName)) + // source document doesn't have any table by the specified name. + break; + + aCacheData.emplace_back(); + aCacheData.back().maTableName = ScGlobal::getCharClass().uppercase(aTabName); + } + + aRange.aStart.SetTab(nTab1); + aRange.aEnd.SetTab(nTab1 + nTabSpan); + + pArray = convertToTokenArray(mrDoc, rSrcDoc, aRange, aCacheData); + rRange = aRange; + rCacheData.swap(aCacheData); + return pArray; +} + +ScExternalRefCache::TokenArrayRef ScExternalRefManager::getRangeNameTokensFromSrcDoc( + sal_uInt16 nFileId, const ScDocument& rSrcDoc, OUString& rName) +{ + ScRangeName* pExtNames = rSrcDoc.GetRangeName(); + OUString aUpperName = ScGlobal::getCharClass().uppercase(rName); + const ScRangeData* pRangeData = pExtNames->findByUpperName(aUpperName); + if (!pRangeData) + return ScExternalRefCache::TokenArrayRef(); + + // Parse all tokens in this external range data, and replace each absolute + // reference token with an external reference token, and cache them. Also + // register the source document with the link manager if it's a new + // source. + + ScExternalRefCache::TokenArrayRef pNew = std::make_shared<ScTokenArray>(rSrcDoc); + + ScTokenArray aCode(*pRangeData->GetCode()); + FormulaTokenArrayPlainIterator aIter(aCode); + for (const FormulaToken* pToken = aIter.First(); pToken; pToken = aIter.Next()) + { + bool bTokenAdded = false; + switch (pToken->GetType()) + { + case svSingleRef: + { + const ScSingleRefData& rRef = *pToken->GetSingleRef(); + OUString aTabName; + rSrcDoc.GetName(rRef.Tab(), aTabName); + ScExternalSingleRefToken aNewToken(nFileId, svl::SharedString( aTabName), // string not interned + *pToken->GetSingleRef()); + pNew->AddToken(aNewToken); + bTokenAdded = true; + } + break; + case svDoubleRef: + { + const ScSingleRefData& rRef = *pToken->GetSingleRef(); + OUString aTabName; + rSrcDoc.GetName(rRef.Tab(), aTabName); + ScExternalDoubleRefToken aNewToken(nFileId, svl::SharedString( aTabName), // string not interned + *pToken->GetDoubleRef()); + pNew->AddToken(aNewToken); + bTokenAdded = true; + } + break; + default: + ; // nothing + } + + if (!bTokenAdded) + pNew->AddToken(*pToken); + } + + rName = pRangeData->GetName(); // Get the correctly-cased name. + return pNew; +} + +ScDocument* ScExternalRefManager::getInMemorySrcDocument(sal_uInt16 nFileId) +{ + const OUString* pFileName = getExternalFileName(nFileId); + if (!pFileName) + return nullptr; + + // Do not load document until it was allowed. + if (!isLinkUpdateAllowedInDoc(mrDoc)) + return nullptr; + + ScDocument* pSrcDoc = nullptr; + ScDocShell* pShell = static_cast<ScDocShell*>(SfxObjectShell::GetFirst(checkSfxObjectShell<ScDocShell>, false)); + while (pShell) + { + SfxMedium* pMedium = pShell->GetMedium(); + if (pMedium && !pMedium->GetName().isEmpty()) + { + // TODO: We should make the case sensitivity platform dependent. + if (pFileName->equalsIgnoreAsciiCase(pMedium->GetName())) + { + // Found ! + pSrcDoc = &pShell->GetDocument(); + break; + } + } + else + { + // handle unsaved documents here + OUString aName = pShell->GetName(); + if (pFileName->equalsIgnoreAsciiCase(aName)) + { + // Found ! + SrcShell aSrcDoc; + aSrcDoc.maShell = pShell; + maUnsavedDocShells.emplace(nFileId, aSrcDoc); + StartListening(*pShell); + pSrcDoc = &pShell->GetDocument(); + break; + } + } + pShell = static_cast<ScDocShell*>(SfxObjectShell::GetNext(*pShell, checkSfxObjectShell<ScDocShell>, false)); + } + + initDocInCache(maRefCache, pSrcDoc, nFileId); + return pSrcDoc; +} + +ScDocument* ScExternalRefManager::getSrcDocument(sal_uInt16 nFileId) +{ + if (!mrDoc.IsExecuteLinkEnabled()) + return nullptr; + + DocShellMap::iterator itrEnd = maDocShells.end(); + DocShellMap::iterator itr = maDocShells.find(nFileId); + + if (itr != itrEnd) + { + // document already loaded. + + SfxObjectShell* p = itr->second.maShell.get(); + itr->second.maLastAccess = tools::Time( tools::Time::SYSTEM ); + return &static_cast<ScDocShell*>(p)->GetDocument(); + } + + itrEnd = maUnsavedDocShells.end(); + itr = maUnsavedDocShells.find(nFileId); + if (itr != itrEnd) + { + //document is unsaved document + + SfxObjectShell* p = itr->second.maShell.get(); + itr->second.maLastAccess = tools::Time( tools::Time::SYSTEM ); + return &static_cast<ScDocShell*>(p)->GetDocument(); + } + + const OUString* pFile = getExternalFileName(nFileId); + if (!pFile) + // no file name associated with this ID. + return nullptr; + + SrcShell aSrcDoc; + try + { + OUString aFilter; + aSrcDoc.maShell = loadSrcDocument(nFileId, aFilter); + } + catch (const css::uno::Exception&) + { + } + if (!aSrcDoc.maShell.is()) + { + // source document could not be loaded. + return nullptr; + } + + return &cacheNewDocShell(nFileId, aSrcDoc); +} + +SfxObjectShellRef ScExternalRefManager::loadSrcDocument(sal_uInt16 nFileId, OUString& rFilter) +{ + // Do not load document until it was allowed. + if (!isLinkUpdateAllowedInDoc(mrDoc)) + return nullptr; + + const SrcFileData* pFileData = getExternalFileData(nFileId); + if (!pFileData) + return nullptr; + + // Always load the document by using the path created from the relative + // path. If the referenced document is not there, simply exit. The + // original file name should be used only when the relative path is not + // given. + OUString aFile = pFileData->maFileName; + maybeCreateRealFileName(nFileId); + if (!pFileData->maRealFileName.isEmpty()) + aFile = pFileData->maRealFileName; + + if (!isFileLoadable(aFile)) + return nullptr; + + OUString aOptions = pFileData->maFilterOptions; + if ( !pFileData->maFilterName.isEmpty() ) + rFilter = pFileData->maFilterName; // don't overwrite stored filter with guessed filter + else + ScDocumentLoader::GetFilterName(aFile, rFilter, aOptions, true, false); + std::shared_ptr<const SfxFilter> pFilter = ScDocShell::Factory().GetFilterContainer()->GetFilter4FilterName(rFilter); + + if (pFileData->maRelativeName.isEmpty()) + { + // Generate a relative file path. + INetURLObject aBaseURL(getOwnDocumentName()); + aBaseURL.insertName(u"content.xml"); + + OUString aStr = URIHelper::simpleNormalizedMakeRelative( + aBaseURL.GetMainURL(INetURLObject::DecodeMechanism::NONE), aFile); + + setRelativeFileName(nFileId, aStr); + } + + std::unique_ptr<SfxItemSet> pSet(new SfxAllItemSet(SfxGetpApp()->GetPool())); + if (!aOptions.isEmpty()) + pSet->Put(SfxStringItem(SID_FILE_FILTEROPTIONS, aOptions)); + + // make medium hidden to prevent assertion from progress bar + pSet->Put( SfxBoolItem(SID_HIDDEN, true) ); + + // If the current document is allowed to execute macros then the referenced + // document may execute macros according to the security configuration. + // Similar for UpdateDocMode to update links, just that if we reach here + // the user already allowed updates and intermediate documents are expected + // to update as well. When loading the document ScDocShell::Load() will + // check through ScDocShell::GetLinkUpdateModeState() if its location is + // trusted. + SfxObjectShell* pShell = mrDoc.GetDocumentShell(); + if (pShell) + { + SfxMedium* pMedium = pShell->GetMedium(); + if (pMedium) + { + const SfxUInt16Item* pItem = pMedium->GetItemSet()->GetItemIfSet( SID_MACROEXECMODE, false ); + if (pItem && + pItem->GetValue() != css::document::MacroExecMode::NEVER_EXECUTE) + pSet->Put( SfxUInt16Item( SID_MACROEXECMODE, css::document::MacroExecMode::USE_CONFIG)); + } + + pSet->Put( SfxUInt16Item( SID_UPDATEDOCMODE, css::document::UpdateDocMode::FULL_UPDATE)); + } + + unique_ptr<SfxMedium> pMedium(new SfxMedium(aFile, StreamMode::STD_READ, pFilter, std::move(pSet))); + if (pMedium->GetError() != ERRCODE_NONE) + return nullptr; + + // To load encrypted documents with password, user interaction needs to be enabled. + pMedium->UseInteractionHandler(mbUserInteractionEnabled); + + ScDocShell* pNewShell = new ScDocShell(SfxModelFlags::EXTERNAL_LINK); + SfxObjectShellRef aRef = pNewShell; + + // increment the recursive link count of the source document. + ScExtDocOptions* pExtOpt = mrDoc.GetExtDocOptions(); + sal_uInt32 nLinkCount = pExtOpt ? pExtOpt->GetDocSettings().mnLinkCnt : 0; + ScDocument& rSrcDoc = pNewShell->GetDocument(); + rSrcDoc.EnableExecuteLink(false); // to prevent circular access of external references. + rSrcDoc.EnableUndo(false); + rSrcDoc.LockAdjustHeight(); + rSrcDoc.EnableUserInteraction(false); + + ScExtDocOptions* pExtOptNew = rSrcDoc.GetExtDocOptions(); + if (!pExtOptNew) + { + rSrcDoc.SetExtDocOptions(std::make_unique<ScExtDocOptions>()); + pExtOptNew = rSrcDoc.GetExtDocOptions(); + } + pExtOptNew->GetDocSettings().mnLinkCnt = nLinkCount + 1; + + if (!pNewShell->DoLoad(pMedium.release())) + { + aRef->DoClose(); + aRef.clear(); + return aRef; + } + + // with UseInteractionHandler, options may be set by dialog during DoLoad + OUString aNew = ScDocumentLoader::GetOptions(*pNewShell->GetMedium()); + if (!aNew.isEmpty() && aNew != aOptions) + aOptions = aNew; + setFilterData(nFileId, rFilter, aOptions); // update the filter data, including the new options + + return aRef; +} + +ScDocument& ScExternalRefManager::cacheNewDocShell( sal_uInt16 nFileId, SrcShell& rSrcShell ) +{ + if (mbDocTimerEnabled && maDocShells.empty()) + // If this is the first source document insertion, start up the timer. + maSrcDocTimer.Start(); + + maDocShells.emplace(nFileId, rSrcShell); + SfxObjectShell& rShell = *rSrcShell.maShell; + ScDocument& rSrcDoc = static_cast<ScDocShell&>(rShell).GetDocument(); + initDocInCache(maRefCache, &rSrcDoc, nFileId); + return rSrcDoc; +} + +bool ScExternalRefManager::isFileLoadable(const OUString& rFile) const +{ + if (rFile.isEmpty()) + return false; + + if (isOwnDocument(rFile)) + return false; + OUString aPhysical; + if (osl::FileBase::getSystemPathFromFileURL(rFile, aPhysical) + == osl::FileBase::E_None) + { + // #i114504# try IsFolder/Exists only for file URLs + + if (utl::UCBContentHelper::IsFolder(rFile)) + return false; + + return utl::UCBContentHelper::Exists(rFile); + } + else + return true; // for http and others, Exists doesn't work, but the URL can still be opened +} + +void ScExternalRefManager::maybeLinkExternalFile( sal_uInt16 nFileId, bool bDeferFilterDetection ) +{ + if (maLinkedDocs.count(nFileId)) + // file already linked, or the link has been broken. + return; + + // Source document not linked yet. Link it now. + const OUString* pFileName = getExternalFileName(nFileId); + if (!pFileName) + return; + + OUString aFilter, aOptions; + const SrcFileData* pFileData = getExternalFileData(nFileId); + if (pFileData) + { + aFilter = pFileData->maFilterName; + aOptions = pFileData->maFilterOptions; + } + + // Filter detection may access external links; defer it until we are allowed. + if (!bDeferFilterDetection) + bDeferFilterDetection = !isLinkUpdateAllowedInDoc(mrDoc); + + // If a filter was already set (for example, loading the cached table), + // don't call GetFilterName which has to access the source file. + // If filter detection is deferred, the next successful loadSrcDocument() + // will update SrcFileData filter name. + if (aFilter.isEmpty() && !bDeferFilterDetection) + ScDocumentLoader::GetFilterName(*pFileName, aFilter, aOptions, true, false); + sfx2::LinkManager* pLinkMgr = mrDoc.GetLinkManager(); + if (!pLinkMgr) + { + SAL_WARN( "sc.ui", "ScExternalRefManager::maybeLinkExternalFile: pLinkMgr==NULL"); + return; + } + ScExternalRefLink* pLink = new ScExternalRefLink(mrDoc, nFileId); + OSL_ENSURE(pFileName, "ScExternalRefManager::maybeLinkExternalFile: file name pointer is NULL"); + pLinkMgr->InsertFileLink(*pLink, sfx2::SvBaseLinkObjectType::ClientFile, *pFileName, + (aFilter.isEmpty() && bDeferFilterDetection ? nullptr : &aFilter)); + + pLink->SetDoRefresh(false); + pLink->Update(); + pLink->SetDoRefresh(true); + + maLinkedDocs.emplace(nFileId, true); +} + +void ScExternalRefManager::addFilesToLinkManager() +{ + if (maSrcFiles.empty()) + return; + + SAL_WARN_IF( maSrcFiles.size() >= SAL_MAX_UINT16, + "sc.ui", "ScExternalRefManager::addFilesToLinkManager: files overflow"); + const sal_uInt16 nSize = static_cast<sal_uInt16>( std::min<size_t>( maSrcFiles.size(), SAL_MAX_UINT16)); + for (sal_uInt16 nFileId = 0; nFileId < nSize; ++nFileId) + maybeLinkExternalFile( nFileId, true); +} + +void ScExternalRefManager::SrcFileData::maybeCreateRealFileName(std::u16string_view rOwnDocName) +{ + if (maRelativeName.isEmpty()) + // No relative path given. Nothing to do. + return; + + if (!maRealFileName.isEmpty()) + // Real file name already created. Nothing to do. + return; + + // Formulate the absolute file path from the relative path. + const OUString& rRelPath = maRelativeName; + INetURLObject aBaseURL(rOwnDocName); + aBaseURL.insertName(u"content.xml"); + bool bWasAbs = false; + maRealFileName = aBaseURL.smartRel2Abs(rRelPath, bWasAbs).GetMainURL(INetURLObject::DecodeMechanism::NONE); +} + +void ScExternalRefManager::maybeCreateRealFileName(sal_uInt16 nFileId) +{ + if (nFileId >= maSrcFiles.size()) + return; + + maSrcFiles[nFileId].maybeCreateRealFileName(getOwnDocumentName()); +} + +OUString ScExternalRefManager::getOwnDocumentName() const +{ + if (utl::ConfigManager::IsFuzzing()) + return "file:///tmp/document"; + + SfxObjectShell* pShell = mrDoc.GetDocumentShell(); + if (!pShell) + // This should not happen! + return OUString(); + + SfxMedium* pMed = pShell->GetMedium(); + if (!pMed) + return OUString(); + + return pMed->GetName(); +} + +bool ScExternalRefManager::isOwnDocument(std::u16string_view rFile) const +{ + return getOwnDocumentName() == rFile; +} + +void ScExternalRefManager::convertToAbsName(OUString& rFile) const +{ + // unsaved documents have no AbsName + ScDocShell* pShell = static_cast<ScDocShell*>(SfxObjectShell::GetFirst(checkSfxObjectShell<ScDocShell>, false)); + while (pShell) + { + if (rFile == pShell->GetName()) + return; + + pShell = static_cast<ScDocShell*>(SfxObjectShell::GetNext(*pShell, checkSfxObjectShell<ScDocShell>, false)); + } + + SfxObjectShell* pDocShell = mrDoc.GetDocumentShell(); + rFile = ScGlobal::GetAbsDocName(rFile, pDocShell); +} + +sal_uInt16 ScExternalRefManager::getExternalFileId(const OUString& rFile) +{ + vector<SrcFileData>::const_iterator itrBeg = maSrcFiles.begin(), itrEnd = maSrcFiles.end(); + vector<SrcFileData>::const_iterator itr = find_if(itrBeg, itrEnd, FindSrcFileByName(rFile)); + if (itr != itrEnd) + { + size_t nId = distance(itrBeg, itr); + return static_cast<sal_uInt16>(nId); + } + + SrcFileData aData; + aData.maFileName = rFile; + maSrcFiles.push_back(aData); + return static_cast<sal_uInt16>(maSrcFiles.size() - 1); +} + +const OUString* ScExternalRefManager::getExternalFileName(sal_uInt16 nFileId, bool bForceOriginal) +{ + if (nFileId >= maSrcFiles.size()) + return nullptr; + + if (bForceOriginal) + return &maSrcFiles[nFileId].maFileName; + + maybeCreateRealFileName(nFileId); + + if (!maSrcFiles[nFileId].maRealFileName.isEmpty()) + return &maSrcFiles[nFileId].maRealFileName; + + return &maSrcFiles[nFileId].maFileName; +} + +sal_uInt16 ScExternalRefManager::convertFileIdToUsedFileId(sal_uInt16 nFileId) +{ + if (!mbSkipUnusedFileIds) + return nFileId; + else + return maConvertFileIdToUsedFileId[nFileId]; +} + +void ScExternalRefManager::setSkipUnusedFileIds(std::vector<sal_uInt16>& rExternFileIds) +{ + mbSkipUnusedFileIds = true; + maConvertFileIdToUsedFileId.resize(maSrcFiles.size()); + std::fill(maConvertFileIdToUsedFileId.begin(), maConvertFileIdToUsedFileId.end(), 0); + int nUsedCount = 0; + for (auto nEntry : rExternFileIds) + { + maConvertFileIdToUsedFileId[nEntry] = nUsedCount++; + } +} + +void ScExternalRefManager::disableSkipUnusedFileIds() +{ + mbSkipUnusedFileIds = false; +} + +std::vector<OUString> ScExternalRefManager::getAllCachedExternalFileNames() const +{ + std::vector<OUString> aNames; + aNames.reserve(maSrcFiles.size()); + for (const SrcFileData& rData : maSrcFiles) + { + aNames.push_back(rData.maFileName); + } + + return aNames; +} + +bool ScExternalRefManager::hasExternalFile(sal_uInt16 nFileId) const +{ + return nFileId < maSrcFiles.size(); +} + +bool ScExternalRefManager::hasExternalFile(const OUString& rFile) const +{ + return ::std::any_of(maSrcFiles.begin(), maSrcFiles.end(), FindSrcFileByName(rFile)); +} + +const ScExternalRefManager::SrcFileData* ScExternalRefManager::getExternalFileData(sal_uInt16 nFileId) const +{ + if (nFileId >= maSrcFiles.size()) + return nullptr; + + return &maSrcFiles[nFileId]; +} + +const OUString* ScExternalRefManager::getRealTableName(sal_uInt16 nFileId, const OUString& rTabName) const +{ + return maRefCache.getRealTableName(nFileId, rTabName); +} + +const OUString* ScExternalRefManager::getRealRangeName(sal_uInt16 nFileId, const OUString& rRangeName) const +{ + return maRefCache.getRealRangeName(nFileId, rRangeName); +} + +template<typename MapContainer> +static void lcl_removeByFileId(sal_uInt16 nFileId, MapContainer& rMap) +{ + typename MapContainer::iterator itr = rMap.find(nFileId); + if (itr != rMap.end()) + { + // Close this document shell. + itr->second.maShell->DoClose(); + rMap.erase(itr); + } +} + +void ScExternalRefManager::clearCache(sal_uInt16 nFileId) +{ + maRefCache.clearCache(nFileId); +} + +namespace { + +class RefCacheFiller : public sc::ColumnSpanSet::ColumnAction +{ + svl::SharedStringPool& mrStrPool; + + ScExternalRefCache& mrRefCache; + ScExternalRefCache::TableTypeRef mpRefTab; + sal_uInt16 mnFileId; + ScColumn* mpCurCol; + sc::ColumnBlockConstPosition maBlockPos; + +public: + RefCacheFiller( svl::SharedStringPool& rStrPool, ScExternalRefCache& rRefCache, sal_uInt16 nFileId ) : + mrStrPool(rStrPool), mrRefCache(rRefCache), mnFileId(nFileId), mpCurCol(nullptr) {} + + virtual void startColumn( ScColumn* pCol ) override + { + mpCurCol = pCol; + if (!mpCurCol) + return; + + mpCurCol->InitBlockPosition(maBlockPos); + mpRefTab = mrRefCache.getCacheTable(mnFileId, mpCurCol->GetTab()); + } + + virtual void execute( SCROW nRow1, SCROW nRow2, bool bVal ) override + { + if (!mpCurCol || !bVal) + return; + + if (!mpRefTab) + return; + + for (SCROW nRow = nRow1; nRow <= nRow2; ++nRow) + { + ScExternalRefCache::TokenRef pTok; + ScRefCellValue aCell = mpCurCol->GetCellValue(maBlockPos, nRow); + switch (aCell.meType) + { + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + { + OUString aStr = aCell.getString(&mpCurCol->GetDoc()); + svl::SharedString aSS = mrStrPool.intern(aStr); + pTok.reset(new formula::FormulaStringToken(aSS)); + } + break; + case CELLTYPE_VALUE: + pTok.reset(new formula::FormulaDoubleToken(aCell.mfValue)); + break; + case CELLTYPE_FORMULA: + { + sc::FormulaResultValue aRes = aCell.mpFormula->GetResult(); + switch (aRes.meType) + { + case sc::FormulaResultValue::Value: + pTok.reset(new formula::FormulaDoubleToken(aRes.mfValue)); + break; + case sc::FormulaResultValue::String: + { + // Re-intern the string to the host document pool. + svl::SharedString aInterned = mrStrPool.intern(aRes.maString.getString()); + pTok.reset(new formula::FormulaStringToken(aInterned)); + } + break; + case sc::FormulaResultValue::Error: + case sc::FormulaResultValue::Invalid: + default: + pTok.reset(new FormulaErrorToken(FormulaError::NoValue)); + } + } + break; + default: + pTok.reset(new FormulaErrorToken(FormulaError::NoValue)); + } + + if (pTok) + { + // Cache this cell. + mpRefTab->setCell(mpCurCol->GetCol(), nRow, pTok, mpCurCol->GetNumberFormat(mpCurCol->GetDoc().GetNonThreadedContext(), nRow)); + mpRefTab->setCachedCell(mpCurCol->GetCol(), nRow); + } + } + }; +}; + +} + +bool ScExternalRefManager::refreshSrcDocument(sal_uInt16 nFileId) +{ + SfxObjectShellRef xDocShell; + try + { + OUString aFilter; + xDocShell = loadSrcDocument(nFileId, aFilter); + } + catch ( const css::uno::Exception& ) {} + + if (!xDocShell.is()) + // Failed to load the document. Bail out. + return false; + + ScDocShell& rDocSh = static_cast<ScDocShell&>(*xDocShell); + ScDocument& rSrcDoc = rDocSh.GetDocument(); + + sc::ColumnSpanSet aCachedArea; + maRefCache.getAllCachedDataSpans(rSrcDoc, nFileId, aCachedArea); + + // Clear the existing cache, and refill it. Make sure we keep the + // existing cache table instances here. + maRefCache.clearCacheTables(nFileId); + RefCacheFiller aAction(mrDoc.GetSharedStringPool(), maRefCache, nFileId); + aCachedArea.executeColumnAction(rSrcDoc, aAction); + + DocShellMap::iterator it = maDocShells.find(nFileId); + if (it != maDocShells.end()) + { + it->second.maShell->DoClose(); + it->second.maShell = xDocShell; + it->second.maLastAccess = tools::Time(tools::Time::SYSTEM); + } + else + { + SrcShell aSrcDoc; + aSrcDoc.maShell = xDocShell; + aSrcDoc.maLastAccess = tools::Time(tools::Time::SYSTEM); + cacheNewDocShell(nFileId, aSrcDoc); + } + + // Update all cells containing names from this source document. + refreshAllRefCells(nFileId); + + notifyAllLinkListeners(nFileId, LINK_MODIFIED); + + return true; +} + +void ScExternalRefManager::breakLink(sal_uInt16 nFileId) +{ + // Turn all formula cells referencing this external document into static + // cells. + RefCellMap::iterator itrRefs = maRefCells.find(nFileId); + if (itrRefs != maRefCells.end()) + { + // Make a copy because removing the formula cells below will modify + // the original container. + RefCellSet aSet = itrRefs->second; + for_each(aSet.begin(), aSet.end(), ConvertFormulaToStatic(&mrDoc)); + maRefCells.erase(nFileId); + } + + // Remove all named ranges that reference this document. + + // Global named ranges. + ScRangeName* pRanges = mrDoc.GetRangeName(); + if (pRanges) + removeRangeNamesBySrcDoc(*pRanges, nFileId); + + // Sheet-local named ranges. + for (SCTAB i = 0, n = mrDoc.GetTableCount(); i < n; ++i) + { + pRanges = mrDoc.GetRangeName(i); + if (pRanges) + removeRangeNamesBySrcDoc(*pRanges, nFileId); + } + + clearCache(nFileId); + lcl_removeByFileId(nFileId, maDocShells); + + if (maDocShells.empty()) + maSrcDocTimer.Stop(); + + LinkedDocMap::iterator itr = maLinkedDocs.find(nFileId); + if (itr != maLinkedDocs.end()) + itr->second = false; + + notifyAllLinkListeners(nFileId, LINK_BROKEN); +} + +void ScExternalRefManager::switchSrcFile(sal_uInt16 nFileId, const OUString& rNewFile, const OUString& rNewFilter) +{ + maSrcFiles[nFileId].maFileName = rNewFile; + maSrcFiles[nFileId].maRelativeName.clear(); + maSrcFiles[nFileId].maRealFileName.clear(); + if (maSrcFiles[nFileId].maFilterName != rNewFilter) + { + // Filter type has changed. + maSrcFiles[nFileId].maFilterName = rNewFilter; + maSrcFiles[nFileId].maFilterOptions.clear(); + } + refreshSrcDocument(nFileId); +} + +void ScExternalRefManager::setRelativeFileName(sal_uInt16 nFileId, const OUString& rRelUrl) +{ + if (nFileId >= maSrcFiles.size()) + return; + maSrcFiles[nFileId].maRelativeName = rRelUrl; +} + +void ScExternalRefManager::setFilterData(sal_uInt16 nFileId, const OUString& rFilterName, const OUString& rOptions) +{ + if (nFileId >= maSrcFiles.size()) + return; + maSrcFiles[nFileId].maFilterName = rFilterName; + maSrcFiles[nFileId].maFilterOptions = rOptions; +} + +void ScExternalRefManager::clear() +{ + for (auto& rEntry : maLinkListeners) + { + for (auto& it : rEntry.second) + { + it->notify(0, OH_NO_WE_ARE_GOING_TO_DIE); + } + } + + for (auto& rEntry : maDocShells) + rEntry.second.maShell->DoClose(); + + maDocShells.clear(); + maSrcDocTimer.Stop(); +} + +bool ScExternalRefManager::hasExternalData() const +{ + return !maSrcFiles.empty(); +} + +void ScExternalRefManager::resetSrcFileData(const OUString& rBaseFileUrl) +{ + for (auto& rSrcFile : maSrcFiles) + { + // Re-generate relative file name from the absolute file name. + OUString aAbsName = rSrcFile.maRealFileName; + if (aAbsName.isEmpty()) + aAbsName = rSrcFile.maFileName; + + rSrcFile.maRelativeName = URIHelper::simpleNormalizedMakeRelative( + rBaseFileUrl, aAbsName); + } +} + +void ScExternalRefManager::updateAbsAfterLoad() +{ + OUString aOwn( getOwnDocumentName() ); + for (auto& rSrcFile : maSrcFiles) + { + // update maFileName to the real file name, + // to be called when the original name is no longer needed (after CompileXML) + + rSrcFile.maybeCreateRealFileName( aOwn ); + OUString aReal = rSrcFile.maRealFileName; + if (!aReal.isEmpty()) + rSrcFile.maFileName = aReal; + } +} + +void ScExternalRefManager::removeRefCell(ScFormulaCell* pCell) +{ + for_each(maRefCells.begin(), maRefCells.end(), RemoveFormulaCell(pCell)); +} + +void ScExternalRefManager::addLinkListener(sal_uInt16 nFileId, LinkListener* pListener) +{ + LinkListenerMap::iterator itr = maLinkListeners.find(nFileId); + if (itr == maLinkListeners.end()) + { + pair<LinkListenerMap::iterator, bool> r = maLinkListeners.emplace( + nFileId, LinkListeners()); + if (!r.second) + { + OSL_FAIL("insertion of new link listener list failed"); + return; + } + + itr = r.first; + } + + LinkListeners& rList = itr->second; + rList.insert(pListener); +} + +void ScExternalRefManager::removeLinkListener(sal_uInt16 nFileId, LinkListener* pListener) +{ + LinkListenerMap::iterator itr = maLinkListeners.find(nFileId); + if (itr == maLinkListeners.end()) + // no listeners for a specified file. + return; + + LinkListeners& rList = itr->second; + rList.erase(pListener); + + if (rList.empty()) + // No more listeners for this file. Remove its entry. + maLinkListeners.erase(itr); +} + +void ScExternalRefManager::removeLinkListener(LinkListener* pListener) +{ + for (auto& rEntry : maLinkListeners) + rEntry.second.erase(pListener); +} + +void ScExternalRefManager::notifyAllLinkListeners(sal_uInt16 nFileId, LinkUpdateType eType) +{ + LinkListenerMap::iterator itr = maLinkListeners.find(nFileId); + if (itr == maLinkListeners.end()) + // no listeners for a specified file. + return; + + LinkListeners& rList = itr->second; + for_each(rList.begin(), rList.end(), NotifyLinkListener(nFileId, eType)); +} + +void ScExternalRefManager::purgeStaleSrcDocument(sal_Int32 nTimeOut) +{ + // To avoid potentially freezing Calc, we close one stale document at a time. + DocShellMap::iterator itr = std::find_if(maDocShells.begin(), maDocShells.end(), + [nTimeOut](const DocShellMap::value_type& rEntry) { + // in 100th of a second. + sal_Int32 nSinceLastAccess = (tools::Time( tools::Time::SYSTEM ) - rEntry.second.maLastAccess).GetTime(); + return nSinceLastAccess >= nTimeOut; + }); + if (itr != maDocShells.end()) + { + // Timed out. Let's close this. + itr->second.maShell->DoClose(); + maDocShells.erase(itr); + } + + if (maDocShells.empty()) + maSrcDocTimer.Stop(); +} + +sal_uInt32 ScExternalRefManager::getMappedNumberFormat(sal_uInt16 nFileId, sal_uInt32 nNumFmt, const ScDocument& rSrcDoc) +{ + NumFmtMap::iterator itr = maNumFormatMap.find(nFileId); + if (itr == maNumFormatMap.end()) + { + // Number formatter map is not initialized for this external document. + pair<NumFmtMap::iterator, bool> r = maNumFormatMap.emplace( + nFileId, SvNumberFormatterMergeMap()); + + if (!r.second) + // insertion failed. + return nNumFmt; + + itr = r.first; + mrDoc.GetFormatTable()->MergeFormatter(*rSrcDoc.GetFormatTable()); + SvNumberFormatterMergeMap aMap = mrDoc.GetFormatTable()->ConvertMergeTableToMap(); + itr->second.swap(aMap); + } + const SvNumberFormatterMergeMap& rMap = itr->second; + SvNumberFormatterMergeMap::const_iterator itrNumFmt = rMap.find(nNumFmt); + if (itrNumFmt != rMap.end()) + // mapped value found. + return itrNumFmt->second; + + return nNumFmt; +} + +void ScExternalRefManager::transformUnsavedRefToSavedRef( SfxObjectShell* pShell ) +{ + DocShellMap::iterator itr = maUnsavedDocShells.begin(); + while( itr != maUnsavedDocShells.end() ) + { + if ( itr->second.maShell.get() == pShell ) + { + // found that the shell is marked as unsaved + OUString aFileURL = pShell->GetMedium()->GetURLObject().GetMainURL(INetURLObject::DecodeMechanism::ToIUri); + switchSrcFile(itr->first, aFileURL, OUString()); + EndListening(*pShell); + itr = maUnsavedDocShells.erase(itr); + } + else + ++itr; + } +} + +void ScExternalRefManager::Notify( SfxBroadcaster&, const SfxHint& rHint ) +{ + const SfxEventHint* pEventHint = dynamic_cast<const SfxEventHint*>(&rHint); + if ( !pEventHint ) + return; + + SfxEventHintId nEventId = pEventHint->GetEventId(); + switch ( nEventId ) + { + case SfxEventHintId::PrepareCloseDoc: + { + std::unique_ptr<weld::MessageDialog> xWarn(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(), + VclMessageType::Warning, VclButtonsType::Ok, + ScResId(STR_CLOSE_WITH_UNSAVED_REFS))); + xWarn->run(); + } + break; + case SfxEventHintId::SaveDocDone: + case SfxEventHintId::SaveAsDocDone: + { + SfxObjectShell* pObjShell = static_cast<const SfxEventHint&>( rHint ).GetObjShell(); + transformUnsavedRefToSavedRef(pObjShell); + } + break; + default: + break; + } +} + +IMPL_LINK(ScExternalRefManager, TimeOutHdl, Timer*, pTimer, void) +{ + if (pTimer == &maSrcDocTimer) + purgeStaleSrcDocument(SRCDOC_LIFE_SPAN); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/impex.cxx b/sc/source/ui/docshell/impex.cxx new file mode 100644 index 000000000..3f80f88ef --- /dev/null +++ b/sc/source/ui/docshell/impex.cxx @@ -0,0 +1,2887 @@ +/* -*- 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 <comphelper/processfactory.hxx> +#include <i18nlangtag/languagetag.hxx> +#include <i18nutil/unicode.hxx> +#include <sot/formats.hxx> +#include <sfx2/mieclip.hxx> +#include <com/sun/star/i18n/CalendarFieldIndex.hpp> +#include <sal/log.hxx> +#include <unotools/charclass.hxx> +#include <osl/module.hxx> +#include <o3tl/string_view.hxx> + +#include <global.hxx> +#include <docsh.hxx> +#include <undoblk.hxx> +#include <rangenam.hxx> +#include <tabvwsh.hxx> +#include <filter.hxx> +#include <asciiopt.hxx> +#include <formulacell.hxx> +#include <cellform.hxx> +#include <progress.hxx> +#include <scitems.hxx> +#include <editable.hxx> +#include <compiler.hxx> +#include <warnbox.hxx> +#include <clipparam.hxx> +#include <impex.hxx> +#include <editutil.hxx> +#include <patattr.hxx> +#include <docpool.hxx> +#include <stringutil.hxx> +#include <cellvalue.hxx> +#include <tokenarray.hxx> +#include <documentimport.hxx> +#include <refundo.hxx> +#include <mtvelements.hxx> + +#include <globstr.hrc> +#include <scresid.hxx> +#include <o3tl/safeint.hxx> +#include <tools/svlibrary.h> +#include <unotools/configmgr.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <editeng/editobj.hxx> +#include <svl/numformat.hxx> +#include <rtl/character.hxx> +#include <rtl/math.hxx> +#include <sax/tools/converter.hxx> + +#include <memory> +#include <string_view> + +#include <osl/endian.h> + +// We don't want to end up with 2GB read in one line just because of malformed +// multiline fields, so chop it _somewhere_, which is twice supported columns +// times arbitrary maximum cell content length, 2*1024*64K=128M, and because +// it's sal_Unicode that's 256MB. If it's 2GB of data without LF we're out of +// luck anyway. +constexpr sal_Int32 nArbitraryCellLengthLimit = SAL_MAX_UINT16; +constexpr sal_Int32 nArbitraryLineLengthLimit = 2 * MAXCOLCOUNT * nArbitraryCellLengthLimit; + +namespace +{ + const char SYLK_LF[] = "\x1b :"; + + bool lcl_IsEndianSwap( const SvStream& rStrm ) + { + #ifdef OSL_BIGENDIAN + return rStrm.GetEndian() != SvStreamEndian::BIG; + #else + return rStrm.GetEndian() != SvStreamEndian::LITTLE; + #endif + } +} + +namespace { + +enum class SylkVersion +{ + SCALC3, // Wrote wrongly quoted strings and unescaped semicolons. + OOO32, // Correct strings, plus multiline content. + OWN, // Place our new versions, if any, before this value. + OTHER // Assume that aliens wrote correct strings. +}; + +} + +// Whole document without Undo +ScImportExport::ScImportExport( ScDocument& r ) + : pDocSh( dynamic_cast< ScDocShell* >(r.GetDocumentShell()) ), rDoc( r ), + nSizeLimit( 0 ), nMaxImportRow(!utl::ConfigManager::IsFuzzing() ? rDoc.MaxRow() : SCROWS32K), + cSep( '\t' ), cStr( '"' ), + bFormulas( false ), bIncludeFiltered( true ), + bAll( true ), bSingle( true ), bUndo( false ), + bOverflowRow( false ), bOverflowCol( false ), bOverflowCell( false ), + mbApi( true ), mbImportBroadcast(false), mbOverwriting( false ) +{ + pUndoDoc = nullptr; + pExtOptions = nullptr; +} + +// Insert am current cell without range(es) +ScImportExport::ScImportExport( ScDocument& r, const ScAddress& rPt ) + : pDocSh( dynamic_cast< ScDocShell* >(r.GetDocumentShell()) ), rDoc( r ), + aRange( rPt ), + nSizeLimit( 0 ), nMaxImportRow(!utl::ConfigManager::IsFuzzing() ? rDoc.MaxRow() : SCROWS32K), + cSep( '\t' ), cStr( '"' ), + bFormulas( false ), bIncludeFiltered( true ), + bAll( false ), bSingle( true ), bUndo( pDocSh != nullptr ), + bOverflowRow( false ), bOverflowCol( false ), bOverflowCell( false ), + mbApi( true ), mbImportBroadcast(false), mbOverwriting( false ) +{ + pUndoDoc = nullptr; + pExtOptions = nullptr; +} + +// ctor with a range is only used for export +//! ctor with a string (and bSingle=true) is also used for DdeSetData +ScImportExport::ScImportExport( ScDocument& r, const ScRange& rRange ) + : pDocSh( dynamic_cast<ScDocShell* >(r.GetDocumentShell()) ), rDoc( r ), + aRange( rRange ), + nSizeLimit( 0 ), nMaxImportRow(!utl::ConfigManager::IsFuzzing() ? rDoc.MaxRow() : SCROWS32K), + cSep( '\t' ), cStr( '"' ), + bFormulas( false ), bIncludeFiltered( true ), + bAll( false ), bSingle( false ), bUndo( pDocSh != nullptr ), + bOverflowRow( false ), bOverflowCol( false ), bOverflowCell( false ), + mbApi( true ), mbImportBroadcast(false), mbOverwriting( false ) +{ + pUndoDoc = nullptr; + pExtOptions = nullptr; + // Only one sheet (table) supported + aRange.aEnd.SetTab( aRange.aStart.Tab() ); +} + +// Evaluate input string - either range, cell or the whole document (when error) +// If a View exists, the TabNo of the view will be used. +ScImportExport::ScImportExport( ScDocument& r, const OUString& rPos ) + : pDocSh( dynamic_cast< ScDocShell* >(r.GetDocumentShell()) ), rDoc( r ), + nSizeLimit( 0 ), nMaxImportRow(!utl::ConfigManager::IsFuzzing() ? rDoc.MaxRow() : SCROWS32K), + cSep( '\t' ), cStr( '"' ), + bFormulas( false ), bIncludeFiltered( true ), + bAll( false ), bSingle( true ), bUndo( pDocSh != nullptr ), + bOverflowRow( false ), bOverflowCol( false ), bOverflowCell( false ), + mbApi( true ), mbImportBroadcast(false), mbOverwriting( false ) +{ + pUndoDoc = nullptr; + pExtOptions = nullptr; + + SCTAB nTab = ScDocShell::GetCurTab(); + aRange.aStart.SetTab( nTab ); + OUString aPos( rPos ); + // Named range? + ScRangeName* pRange = rDoc.GetRangeName(); + if (pRange) + { + const ScRangeData* pData = pRange->findByUpperName(ScGlobal::getCharClass().uppercase(aPos)); + if (pData) + { + if( pData->HasType( ScRangeData::Type::RefArea ) + || pData->HasType( ScRangeData::Type::AbsArea ) + || pData->HasType( ScRangeData::Type::AbsPos ) ) + { + aPos = pData->GetSymbol(); + } + } + } + formula::FormulaGrammar::AddressConvention eConv = rDoc.GetAddressConvention(); + // Range? + if (aRange.Parse(aPos, rDoc, eConv) & ScRefFlags::VALID) + bSingle = false; + // Cell? + else if (aRange.aStart.Parse(aPos, rDoc, eConv) & ScRefFlags::VALID) + aRange.aEnd = aRange.aStart; + else + bAll = true; +} + +ScImportExport::~ScImportExport() COVERITY_NOEXCEPT_FALSE +{ + pUndoDoc.reset(); + pExtOptions.reset(); +} + +void ScImportExport::SetExtOptions( const ScAsciiOptions& rOpt ) +{ + if ( pExtOptions ) + *pExtOptions = rOpt; + else + pExtOptions.reset(new ScAsciiOptions( rOpt )); + + // "normal" Options + + cSep = ScAsciiOptions::GetWeightedFieldSep( rOpt.GetFieldSeps(), false); + cStr = rOpt.GetTextSep(); +} + +void ScImportExport::SetFilterOptions(const OUString& rFilterOptions) +{ + maFilterOptions = rFilterOptions; +} + +bool ScImportExport::IsFormatSupported( SotClipboardFormatId nFormat ) +{ + return nFormat == SotClipboardFormatId::STRING + || nFormat == SotClipboardFormatId::STRING_TSVC + || nFormat == SotClipboardFormatId::SYLK + || nFormat == SotClipboardFormatId::LINK + || nFormat == SotClipboardFormatId::HTML + || nFormat == SotClipboardFormatId::HTML_SIMPLE + || nFormat == SotClipboardFormatId::DIF; +} + +// Prepare for Undo +bool ScImportExport::StartPaste() +{ + if ( !bAll ) + { + ScEditableTester aTester( rDoc, aRange ); + if ( !aTester.IsEditable() ) + { + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(ScDocShell::GetActiveDialogParent(), + VclMessageType::Info, VclButtonsType::Ok, + ScResId(aTester.GetMessageId()))); + xInfoBox->run(); + return false; + } + } + if( bUndo && pDocSh && rDoc.IsUndoEnabled()) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, aRange.aStart.Tab(), aRange.aEnd.Tab() ); + rDoc.CopyToDocument(aRange, InsertDeleteFlags::ALL | InsertDeleteFlags::NOCAPTIONS, false, *pUndoDoc); + } + return true; +} + +// Create Undo/Redo actions, Invalidate/Repaint +void ScImportExport::EndPaste(bool bAutoRowHeight) +{ + bool bHeight = bAutoRowHeight && pDocSh && pDocSh->AdjustRowHeight( + aRange.aStart.Row(), aRange.aEnd.Row(), aRange.aStart.Tab() ); + + if( pUndoDoc && rDoc.IsUndoEnabled() && pDocSh ) + { + ScDocumentUniquePtr pRedoDoc(new ScDocument( SCDOCMODE_UNDO )); + pRedoDoc->InitUndo( rDoc, aRange.aStart.Tab(), aRange.aEnd.Tab() ); + rDoc.CopyToDocument(aRange, InsertDeleteFlags::ALL | InsertDeleteFlags::NOCAPTIONS, false, *pRedoDoc); + ScMarkData aDestMark(pRedoDoc->GetSheetLimits()); + aDestMark.SetMarkArea(aRange); + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoPaste>(pDocSh, aRange, aDestMark, std::move(pUndoDoc), std::move(pRedoDoc), InsertDeleteFlags::ALL, nullptr)); + } + pUndoDoc.reset(); + if( pDocSh ) + { + if (!bHeight) + pDocSh->PostPaint( aRange, PaintPartFlags::Grid ); + pDocSh->SetDocumentModified(); + } + ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell(); + if ( pViewSh ) + pViewSh->UpdateInputHandler(); + +} + +bool ScImportExport::ExportData( const OUString& rMimeType, + css::uno::Any & rValue ) +{ + SvMemoryStream aStrm; + SotClipboardFormatId fmtId = SotExchange::GetFormatIdFromMimeType(rMimeType); + if (fmtId == SotClipboardFormatId::STRING) + aStrm.SetStreamCharSet(RTL_TEXTENCODING_UNICODE); + // mba: no BaseURL for data exchange + if (ExportStream(aStrm, OUString(), fmtId)) + { + if (fmtId == SotClipboardFormatId::STRING) + { + assert(aStrm.TellEnd() % sizeof(sal_Unicode) == 0); + rValue <<= OUString(static_cast<const sal_Unicode*>(aStrm.GetData()), + aStrm.TellEnd() / sizeof(sal_Unicode)); + } + else + { + aStrm.WriteUChar(0); + rValue <<= css::uno::Sequence<sal_Int8>(static_cast<sal_Int8 const*>(aStrm.GetData()), + aStrm.TellEnd()); + } + return true; + } + return false; +} + +bool ScImportExport::ImportString( const OUString& rText, SotClipboardFormatId nFmt ) +{ + switch ( nFmt ) + { + // formats supporting unicode + case SotClipboardFormatId::STRING : + case SotClipboardFormatId::STRING_TSVC : + { + ScImportStringStream aStrm( rText); + return ImportStream( aStrm, OUString(), nFmt ); + // ImportStream must handle RTL_TEXTENCODING_UNICODE + } + default: + { + rtl_TextEncoding eEnc = osl_getThreadTextEncoding(); + OString aTmp( rText.getStr(), rText.getLength(), eEnc ); + SvMemoryStream aStrm( const_cast<char *>(aTmp.getStr()), aTmp.getLength() * sizeof(char), StreamMode::READ ); + aStrm.SetStreamCharSet( eEnc ); + SetNoEndianSwap( aStrm ); //! no swapping in memory + return ImportStream( aStrm, OUString(), nFmt ); + } + } +} + +bool ScImportExport::ExportString( OUString& rText, SotClipboardFormatId nFmt ) +{ + if ( nFmt != SotClipboardFormatId::STRING && nFmt != SotClipboardFormatId::STRING_TSVC ) + { + SAL_WARN("sc.ui", "ScImportExport::ExportString: Unicode not supported for other formats than SotClipboardFormatId::STRING[_TSV]"); + rtl_TextEncoding eEnc = osl_getThreadTextEncoding(); + OString aTmp; + bool bOk = ExportByteString( aTmp, eEnc, nFmt ); + rText = OStringToOUString( aTmp, eEnc ); + return bOk; + } + // nSizeLimit not needed for OUString + + SvMemoryStream aStrm; + aStrm.SetStreamCharSet( RTL_TEXTENCODING_UNICODE ); + SetNoEndianSwap( aStrm ); //! no swapping in memory + // mba: no BaseURL for data exc + if( ExportStream( aStrm, OUString(), nFmt ) ) + { + aStrm.WriteUInt16( 0 ); + rText = OUString( static_cast<const sal_Unicode*>(aStrm.GetData()) ); + return true; + } + rText.clear(); + return false; + + // ExportStream must handle RTL_TEXTENCODING_UNICODE +} + +bool ScImportExport::ExportByteString( OString& rText, rtl_TextEncoding eEnc, SotClipboardFormatId nFmt ) +{ + OSL_ENSURE( eEnc != RTL_TEXTENCODING_UNICODE, "ScImportExport::ExportByteString: Unicode not supported" ); + if ( eEnc == RTL_TEXTENCODING_UNICODE ) + eEnc = osl_getThreadTextEncoding(); + + if (!nSizeLimit) + nSizeLimit = SAL_MAX_UINT16; + + SvMemoryStream aStrm; + aStrm.SetStreamCharSet( eEnc ); + SetNoEndianSwap( aStrm ); //! no swapping in memory + // mba: no BaseURL for data exchange + if( ExportStream( aStrm, OUString(), nFmt ) ) + { + aStrm.WriteChar( 0 ); + if( aStrm.TellEnd() <= nSizeLimit ) + { + rText = static_cast<const char*>(aStrm.GetData()); + return true; + } + } + rText.clear(); + return false; +} + +bool ScImportExport::ImportStream( SvStream& rStrm, const OUString& rBaseURL, SotClipboardFormatId nFmt ) +{ + if( nFmt == SotClipboardFormatId::STRING || nFmt == SotClipboardFormatId::STRING_TSVC ) + { + if( ExtText2Doc( rStrm ) ) // evaluate pExtOptions + return true; + } + if( nFmt == SotClipboardFormatId::SYLK ) + { + if( Sylk2Doc( rStrm ) ) + return true; + } + if( nFmt == SotClipboardFormatId::DIF ) + { + if( Dif2Doc( rStrm ) ) + return true; + } + if( nFmt == SotClipboardFormatId::RTF || nFmt == SotClipboardFormatId::RICHTEXT ) + { + if( RTF2Doc( rStrm, rBaseURL ) ) + return true; + } + if( nFmt == SotClipboardFormatId::LINK ) + return true; // Link-Import? + if ( nFmt == SotClipboardFormatId::HTML ) + { + if( HTML2Doc( rStrm, rBaseURL ) ) + return true; + } + if ( nFmt == SotClipboardFormatId::HTML_SIMPLE ) + { + MSE40HTMLClipFormatObj aMSE40ClpObj; // needed to skip the header data + SvStream* pHTML = aMSE40ClpObj.IsValid( rStrm ); + if ( pHTML && HTML2Doc( *pHTML, rBaseURL ) ) + return true; + } + + return false; +} + +bool ScImportExport::ExportStream( SvStream& rStrm, const OUString& rBaseURL, SotClipboardFormatId nFmt ) +{ + if( nFmt == SotClipboardFormatId::STRING || nFmt == SotClipboardFormatId::STRING_TSVC ) + { + if( Doc2Text( rStrm ) ) + return true; + } + if( nFmt == SotClipboardFormatId::SYLK ) + { + if( Doc2Sylk( rStrm ) ) + return true; + } + if( nFmt == SotClipboardFormatId::DIF ) + { + if( Doc2Dif( rStrm ) ) + return true; + } + if( nFmt == SotClipboardFormatId::LINK && !bAll ) + { + OUString aDocName; + if ( rDoc.IsClipboard() ) + aDocName = ScGlobal::GetClipDocName(); + else + { + SfxObjectShell* pShell = rDoc.GetDocumentShell(); + if (pShell) + aDocName = pShell->GetTitle( SFX_TITLE_FULLNAME ); + } + + OSL_ENSURE( !aDocName.isEmpty(), "ClipBoard document has no name! :-/" ); + if( !aDocName.isEmpty() ) + { + // Always use Calc A1 syntax for paste link. + OUString aRefName; + ScRefFlags nFlags = ScRefFlags::VALID | ScRefFlags::TAB_3D; + if( bSingle ) + aRefName = aRange.aStart.Format(nFlags, &rDoc, formula::FormulaGrammar::CONV_OOO); + else + { + if( aRange.aStart.Tab() != aRange.aEnd.Tab() ) + nFlags |= ScRefFlags::TAB2_3D; + aRefName = aRange.Format(rDoc, nFlags, formula::FormulaGrammar::CONV_OOO); + } + OUString aAppName = Application::GetAppName(); + + // extra bits are used to tell the client to prefer external + // reference link. + + WriteUnicodeOrByteString( rStrm, aAppName, true ); + WriteUnicodeOrByteString( rStrm, aDocName, true ); + WriteUnicodeOrByteString( rStrm, aRefName, true ); + WriteUnicodeOrByteString( rStrm, "calc:extref", true ); + if ( rStrm.GetStreamCharSet() == RTL_TEXTENCODING_UNICODE ) + rStrm.WriteUInt16( 0 ); + else + rStrm.WriteChar( 0 ); + return rStrm.GetError() == ERRCODE_NONE; + } + } + if( nFmt == SotClipboardFormatId::HTML ) + { + if( Doc2HTML( rStrm, rBaseURL ) ) + return true; + } + if( nFmt == SotClipboardFormatId::RTF || nFmt == SotClipboardFormatId::RICHTEXT ) + { + if( Doc2RTF( rStrm ) ) + return true; + } + + return false; +} + +void ScImportExport::WriteUnicodeOrByteString( SvStream& rStrm, const OUString& rString, bool bZero ) +{ + rtl_TextEncoding eEnc = rStrm.GetStreamCharSet(); + if ( eEnc == RTL_TEXTENCODING_UNICODE ) + { + if ( !lcl_IsEndianSwap( rStrm ) ) + rStrm.WriteBytes(rString.getStr(), rString.getLength() * sizeof(sal_Unicode)); + else + { + const sal_Unicode* p = rString.getStr(); + const sal_Unicode* const pStop = p + rString.getLength(); + while ( p < pStop ) + { + rStrm.WriteUInt16( *p ); + } + } + if ( bZero ) + rStrm.WriteUInt16( 0 ); + } + else + { + OString aByteStr(OUStringToOString(rString, eEnc)); + rStrm.WriteOString( aByteStr ); + if ( bZero ) + rStrm.WriteChar( 0 ); + } +} + +// This function could be replaced by endlub() +void ScImportExport::WriteUnicodeOrByteEndl( SvStream& rStrm ) +{ + if ( rStrm.GetStreamCharSet() == RTL_TEXTENCODING_UNICODE ) + { // same as endl() but unicode + switch ( rStrm.GetLineDelimiter() ) + { + case LINEEND_CR : + rStrm.WriteUInt16( '\r' ); + break; + case LINEEND_LF : + rStrm.WriteUInt16( '\n' ); + break; + default: + rStrm.WriteUInt16( '\r' ).WriteUInt16( '\n' ); + } + } + else + endl( rStrm ); +} + +sal_Int32 ScImportExport::CountVisualWidth(const OUString& rStr, sal_Int32& nIdx, sal_Int32 nMaxWidth) +{ + sal_Int32 nWidth = 0; + while(nIdx < rStr.getLength() && nWidth < nMaxWidth) + { + sal_uInt32 nCode = rStr.iterateCodePoints(&nIdx); + + if (unicode::isCJKIVSCharacter(nCode) || (nCode >= 0x3000 && nCode <= 0x303F)) + nWidth += 2; + else if (!unicode::isIVSSelector(nCode)) + nWidth += 1; + } + + if (nIdx < rStr.getLength()) + { + sal_Int32 nTmpIdx = nIdx; + sal_uInt32 nCode = rStr.iterateCodePoints(&nTmpIdx); + + if (unicode::isIVSSelector(nCode)) + nIdx = nTmpIdx; + } + return nWidth; +} + +sal_Int32 ScImportExport::CountVisualWidth(const OUString& rStr) +{ + sal_Int32 nIdx = 0; + return CountVisualWidth(rStr, nIdx, SAL_MAX_INT32); +} + +void ScImportExport::SetNoEndianSwap( SvStream& rStrm ) +{ +#ifdef OSL_BIGENDIAN + rStrm.SetEndian( SvStreamEndian::BIG ); +#else + rStrm.SetEndian( SvStreamEndian::LITTLE ); +#endif +} + +static inline bool lcl_isFieldEnd( sal_Unicode c, const sal_Unicode* pSeps ) +{ + return !c || ScGlobal::UnicodeStrChr( pSeps, c); +} + +namespace { + +enum QuoteType +{ + FIELDSTART_QUOTE, + FIRST_QUOTE, + SECOND_QUOTE, + FIELDEND_QUOTE, + DONTKNOW_QUOTE +}; + +} + +/** Determine if *p is a quote that ends a quoted field. + + Precondition: we are parsing a quoted field already and *p is a quote. + + @return + FIELDEND_QUOTE if end of field quote + DONTKNOW_QUOTE anything else + */ +static QuoteType lcl_isFieldEndQuote( const sal_Unicode* p, const sal_Unicode* pSeps, sal_Unicode& rcDetectSep ) +{ + // Due to broken CSV generators that don't double embedded quotes check if + // a field separator immediately or with trailing spaces follows the quote, + // only then end the field, or at end of string. + constexpr sal_Unicode cBlank = ' '; + if (p[1] == cBlank && ScGlobal::UnicodeStrChr( pSeps, cBlank)) + return FIELDEND_QUOTE; + // Detect a possible blank separator if it's not already in the list (which + // was checked right above for p[1]==cBlank). + const bool bBlankSep = (p[1] == cBlank && !rcDetectSep && p[2] && p[2] != cBlank); + while (p[1] == cBlank) + ++p; + if (lcl_isFieldEnd( p[1], pSeps)) + return FIELDEND_QUOTE; + // Extended separator detection after a closing quote (with or without + // blanks). Note that nQuotes is incremented *after* the call so is not yet + // even here, and that with separator detection we reach here only if + // lcl_isEscapedOrFieldEndQuote() did not already detect FIRST_QUOTE or + // SECOND_QUOTE for an escaped embedded quote, thus nQuotes does not have + // to be checked. + if (!rcDetectSep) + { + constexpr sal_Unicode vSep[] = { ',', '\t', ';' }; + for (const sal_Unicode c : vSep) + { + if (p[1] == c) + { + rcDetectSep = c; + return FIELDEND_QUOTE; + } + } + } + // Blank separator is least significant, after others. + if (bBlankSep) + { + rcDetectSep = cBlank; + return FIELDEND_QUOTE; + } + return DONTKNOW_QUOTE; +} + +/** Determine if *p is a quote that is escaped by being doubled or ends a + quoted field. + + Precondition: *p is a quote. + + @param nQuotes + Quote characters encountered so far. + Odd (after opening quote) means either no embedded quotes or only quote + pairs so far. + Even means either not in a quoted field or already one quote + encountered, the first of a pair. + + @return + FIELDSTART_QUOTE if first quote in a field, either starting content or + embedded so caller should check beforehand. + FIRST_QUOTE if first of a doubled quote + SECOND_QUOTE if second of a doubled quote + FIELDEND_QUOTE if end of field quote + DONTKNOW_QUOTE if an unescaped quote we don't consider as end of field, + do not increment nQuotes in caller then! + */ +static QuoteType lcl_isEscapedOrFieldEndQuote( sal_Int32 nQuotes, const sal_Unicode* p, + const sal_Unicode* pSeps, sal_Unicode cStr, sal_Unicode& rcDetectSep ) +{ + if ((nQuotes & 1) == 0) + { + if (p[-1] == cStr) + return SECOND_QUOTE; + else + { + SAL_WARN( "sc", "lcl_isEscapedOrFieldEndQuote: really want a FIELDSTART_QUOTE?"); + return FIELDSTART_QUOTE; + } + } + if (p[1] == cStr) + return FIRST_QUOTE; + return lcl_isFieldEndQuote( p, pSeps, rcDetectSep); +} + +/** Append characters of [p1,p2) to rField. + + @returns TRUE if ok; FALSE if data overflow, truncated + */ +static bool lcl_appendLineData( OUString& rField, const sal_Unicode* p1, const sal_Unicode* p2 ) +{ + if (rField.getLength() + (p2 - p1) <= nArbitraryCellLengthLimit) + { + rField += std::u16string_view( p1, p2 - p1 ); + return true; + } + else + { + SAL_WARN( "sc", "lcl_appendLineData: data overflow"); + rField += std::u16string_view( p1, nArbitraryCellLengthLimit - rField.getLength() ); + return false; + } +} + +namespace { + +enum class DoubledQuoteMode +{ + KEEP_ALL, // both are taken, additionally start and end quote are included in string + ESCAPE, // escaped quote, one is taken, one ignored +}; + +} + +/** Scan for a quoted string. + + Precondition: initial current position *p is a cStr quote. + + For DoubledQuoteMode::ESCAPE, if after the closing quote there is a field + end (with or without trailing blanks and as determined by + lcl_isFieldEndQuote()), then the content is appended to rField with quotes + processed and removed. Else if no field end after the quoted string was + detected, nothing is appended and processing continues and is repeated + until the next quote. If no closing quote at a field end was found at all, + nothing is appended and the initial position is returned and caller has to + decide, usually just taking all as literal data. + + For DoubledQuoteMode::KEEP_ALL, the string up to and including the closing + quote is appended to rField and the next position returned, regardless + whether there is a field separator following or not. + + */ +static const sal_Unicode* lcl_ScanString( const sal_Unicode* p, OUString& rField, + const sal_Unicode* pSeps, sal_Unicode cStr, DoubledQuoteMode eMode, bool& rbOverflowCell ) +{ + OUString aString; + bool bClosingQuote = (eMode == DoubledQuoteMode::KEEP_ALL); + const sal_Unicode* const pStart = p; + if (eMode != DoubledQuoteMode::KEEP_ALL) + p++; //! jump over opening quote + bool bCont; + do + { + bCont = false; + const sal_Unicode* p0 = p; + for( ;; ) + { + if (!*p) + { + // Encountering end of data after an opening quote is not a + // quoted string, ReadCsvLine() concatenated lines with '\n' + // for a properly quoted embedded linefeed. + if (eMode == DoubledQuoteMode::KEEP_ALL) + // Caller would append that data anyway, so we can do it + // already here. + break; + + return pStart; + } + + if( *p == cStr ) + { + if ( *++p != cStr ) + { + // break or continue for loop + if (eMode == DoubledQuoteMode::ESCAPE) + { + sal_Unicode cDetectSep = 0xffff; // No separator detection here. + if (lcl_isFieldEndQuote( p-1, pSeps, cDetectSep) == FIELDEND_QUOTE) + { + bClosingQuote = true; + break; + } + else + continue; + } + else + break; + } + // doubled quote char + switch ( eMode ) + { + case DoubledQuoteMode::KEEP_ALL : + p++; // both for us (not breaking for-loop) + break; + case DoubledQuoteMode::ESCAPE : + p++; // one for us (breaking for-loop) + bCont = true; // and more + break; + } + if ( eMode == DoubledQuoteMode::ESCAPE ) + break; + } + else + p++; + } + if ( p0 < p ) + { + if (!lcl_appendLineData( aString, p0, ((eMode != DoubledQuoteMode::KEEP_ALL && (*p || *(p-1) == cStr)) ? p-1 : p))) + rbOverflowCell = true; + } + } while ( bCont ); + + if (!bClosingQuote) + return pStart; + + if (!aString.isEmpty()) + rField += aString; + + return p; +} + +static void lcl_UnescapeSylk( OUString & rString, SylkVersion eVersion ) +{ + // Older versions didn't escape the semicolon. + // Older versions quoted the string and doubled embedded quotes, but not + // the semicolons, which was plain wrong. + if (eVersion >= SylkVersion::OOO32) + rString = rString.replaceAll(";;", ";"); + else + rString = rString.replaceAll("\"\"", "\""); + + rString = rString.replaceAll(SYLK_LF, "\n"); +} + +static const sal_Unicode* lcl_ScanSylkString( const sal_Unicode* p, + OUString& rString, SylkVersion eVersion ) +{ + const sal_Unicode* pStartQuote = p; + const sal_Unicode* pEndQuote = nullptr; + while( *(++p) ) + { + if( *p == '"' ) + { + pEndQuote = p; + if (eVersion >= SylkVersion::OOO32) + { + if (*(p+1) == ';') + { + if (*(p+2) == ';') + { + p += 2; // escaped ';' + pEndQuote = nullptr; + } + else + break; // end field + } + } + else + { + if (*(p+1) == '"') + { + ++p; // escaped '"' + pEndQuote = nullptr; + } + else if (*(p+1) == ';') + break; // end field + } + } + } + if (!pEndQuote) + pEndQuote = p; // Take all data as string. + rString += std::u16string_view(pStartQuote + 1, pEndQuote - pStartQuote - 1 ); + lcl_UnescapeSylk( rString, eVersion); + return p; +} + +static const sal_Unicode* lcl_ScanSylkFormula( const sal_Unicode* p, + OUString& rString, SylkVersion eVersion ) +{ + const sal_Unicode* pStart = p; + if (eVersion >= SylkVersion::OOO32) + { + while (*p) + { + if (*p == ';') + { + if (*(p+1) == ';') + ++p; // escaped ';' + else + break; // end field + } + ++p; + } + rString += std::u16string_view( pStart, p - pStart); + lcl_UnescapeSylk( rString, eVersion); + } + else + { + // Nasty. If in old versions the formula contained a semicolon, it was + // quoted and embedded quotes were doubled, but semicolons were not. If + // there was no semicolon, it could still contain quotes and doubled + // embedded quotes if it was something like ="a""b", which was saved as + // E"a""b" as is and has to be preserved, even if older versions + // couldn't even load it correctly. However, theoretically another + // field might follow and thus the line contain a semicolon again, such + // as ...;E"a""b";... + bool bQuoted = false; + if (*p == '"') + { + // May be a quoted expression or just a string constant expression + // with quotes. + while (*(++p)) + { + if (*p == '"') + { + if (*(p+1) == '"') + ++p; // escaped '"' + else + break; // closing '"', had no ';' yet + } + else if (*p == ';') + { + bQuoted = true; // ';' within quoted expression + break; + } + } + p = pStart; + } + if (bQuoted) + p = lcl_ScanSylkString( p, rString, eVersion); + else + { + while (*p && *p != ';') + ++p; + rString += std::u16string_view( pStart, p - pStart); + } + } + return p; +} + +static void lcl_DoubleEscapeChar( OUString& rString, sal_Unicode cStr ) +{ + sal_Int32 n = 0; + while( ( n = rString.indexOf( cStr, n ) ) != -1 ) + { + rString = rString.replaceAt( n, 0, rtl::OUStringChar(cStr) ); + n += 2; + } +} + +static void lcl_WriteString( SvStream& rStrm, OUString& rString, sal_Unicode cQuote, sal_Unicode cEsc ) +{ + if (cEsc) + lcl_DoubleEscapeChar( rString, cEsc ); + + if (cQuote) + { + rString = OUStringChar(cQuote) + rString + OUStringChar(cQuote); + } + + ScImportExport::WriteUnicodeOrByteString( rStrm, rString ); +} + +static void lcl_WriteSimpleString( SvStream& rStrm, const OUString& rString ) +{ + ScImportExport::WriteUnicodeOrByteString( rStrm, rString ); +} + +bool ScImportExport::Text2Doc( SvStream& rStrm ) +{ + bool bOk = true; + + sal_Unicode pSeps[2]; + pSeps[0] = cSep; + pSeps[1] = 0; + + ScSetStringParam aSetStringParam; + aSetStringParam.mbCheckLinkFormula = true; + + SCCOL nStartCol = aRange.aStart.Col(); + SCROW nStartRow = aRange.aStart.Row(); + SCCOL nEndCol = aRange.aEnd.Col(); + SCROW nEndRow = aRange.aEnd.Row(); + sal_uInt64 nOldPos = rStrm.Tell(); + rStrm.StartReadingUnicodeText( rStrm.GetStreamCharSet() ); + bool bData = !bSingle; + if( !bSingle) + bOk = StartPaste(); + + while( bOk ) + { + OUString aLine; + OUString aCell; + SCROW nRow = nStartRow; + rStrm.Seek( nOldPos ); + for( ;; ) + { + rStrm.ReadUniOrByteStringLine( aLine, rStrm.GetStreamCharSet(), nArbitraryLineLengthLimit ); + // tdf#125440 When inserting tab separated string, consider quotes as field markers + DoubledQuoteMode mode = aLine.indexOf("\t") >= 0 ? DoubledQuoteMode::ESCAPE : DoubledQuoteMode::KEEP_ALL; + if( rStrm.eof() ) + break; + SCCOL nCol = nStartCol; + const sal_Unicode* p = aLine.getStr(); + while( *p ) + { + aCell.clear(); + const sal_Unicode* q = p; + if (*p == cStr) + { + // Look for a pairing quote. + q = p = lcl_ScanString( p, aCell, pSeps, cStr, mode, bOverflowCell ); + } + // All until next separator. + while (*p && *p != cSep) + ++p; + if (!lcl_appendLineData( aCell, q, p)) + bOverflowCell = true; // display warning on import + if (*p) + ++p; + if (rDoc.ValidCol(nCol) && rDoc.ValidRow(nRow) ) + { + if( bSingle ) + { + if (nCol>nEndCol) nEndCol = nCol; + if (nRow>nEndRow) nEndRow = nRow; + } + if( bData && nCol <= nEndCol && nRow <= nEndRow ) + rDoc.SetString( nCol, nRow, aRange.aStart.Tab(), aCell, &aSetStringParam ); + } + else // too many columns/rows + { + if (!rDoc.ValidRow(nRow)) + bOverflowRow = true; // display warning on import + if (!rDoc.ValidCol(nCol)) + bOverflowCol = true; // display warning on import + } + ++nCol; + } + ++nRow; + } + + if( !bData ) + { + aRange.aEnd.SetCol( nEndCol ); + aRange.aEnd.SetRow( nEndRow ); + bOk = StartPaste(); + bData = true; + } + else + break; + } + + EndPaste(); + if (bOk && mbImportBroadcast) + { + rDoc.BroadcastCells(aRange, SfxHintId::ScDataChanged); + pDocSh->PostDataChanged(); + } + + return bOk; +} + +// Extended Ascii-Import + +static bool lcl_PutString( + ScDocumentImport& rDocImport, bool bUseDocImport, + SCCOL nCol, SCROW nRow, SCTAB nTab, const OUString& rStr, sal_uInt8 nColFormat, + SvNumberFormatter* pFormatter, bool bDetectNumFormat, bool bEvaluateFormulas, bool bSkipEmptyCells, + const ::utl::TransliterationWrapper& rTransliteration, CalendarWrapper& rCalendar, + const ::utl::TransliterationWrapper* pSecondTransliteration, CalendarWrapper* pSecondCalendar ) +{ + ScDocument& rDoc = rDocImport.getDoc(); + bool bMultiLine = false; + if ( nColFormat == SC_COL_SKIP || !rDoc.ValidCol(nCol) || !rDoc.ValidRow(nRow) ) + return bMultiLine; + if ( rStr.isEmpty() ) + { + if ( !bSkipEmptyCells ) + { // delete destination cell + if ( bUseDocImport ) + rDocImport.setAutoInput(ScAddress(nCol, nRow, nTab), rStr ); + else + rDoc.SetString( nCol, nRow, nTab, rStr ); + } + return false; + } + + const bool bForceFormulaText = (!bEvaluateFormulas && rStr[0] == '='); + if (nColFormat == SC_COL_TEXT || bForceFormulaText) + { + if ( bUseDocImport ) + { + double fDummy; + sal_uInt32 nIndex = 0; + if (bForceFormulaText || rDoc.GetFormatTable()->IsNumberFormat(rStr, nIndex, fDummy)) + { + // Set the format of this cell to Text. + // This is only necessary for ScDocumentImport, + // ScDocument::SetTextCell() forces it by ScSetStringParam. + sal_uInt32 nFormat = rDoc.GetFormatTable()->GetStandardFormat(SvNumFormatType::TEXT); + ScPatternAttr aNewAttrs(rDoc.GetPool()); + SfxItemSet& rSet = aNewAttrs.GetItemSet(); + rSet.Put( SfxUInt32Item(ATTR_VALUE_FORMAT, nFormat) ); + rDoc.ApplyPattern(nCol, nRow, nTab, aNewAttrs); + } + if (ScStringUtil::isMultiline(rStr)) + { + ScFieldEditEngine& rEngine = rDoc.GetEditEngine(); + rEngine.SetTextCurrentDefaults(rStr); + rDocImport.setEditCell(ScAddress(nCol, nRow, nTab), rEngine.CreateTextObject()); + return true; + } + else + { + rDocImport.setStringCell(ScAddress(nCol, nRow, nTab), rStr); + return false; + } + } + else + { + rDoc.SetTextCell(ScAddress(nCol, nRow, nTab), rStr); + return bMultiLine; + } + } + + if ( nColFormat == SC_COL_ENGLISH ) + { + //! SetString with Extra-Flag ??? + + SvNumberFormatter* pDocFormatter = rDoc.GetFormatTable(); + sal_uInt32 nEnglish = pDocFormatter->GetStandardIndex(LANGUAGE_ENGLISH_US); + double fVal; + if ( pDocFormatter->IsNumberFormat( rStr, nEnglish, fVal ) ) + { + // Numberformat will not be set to English + if ( bUseDocImport ) + rDocImport.setNumericCell( ScAddress( nCol, nRow, nTab ), fVal ); + else + rDoc.SetValue( nCol, nRow, nTab, fVal ); + return bMultiLine; + } + // else, continue with SetString + } + else if ( nColFormat != SC_COL_STANDARD ) // Datumformats + { + const sal_uInt16 nMaxNumberParts = 7; // Y-M-D h:m:s.t + const sal_Int32 nLen = rStr.getLength(); + sal_Int32 nStart[nMaxNumberParts]; + sal_Int32 nEnd[nMaxNumberParts]; + + bool bIso; + sal_uInt16 nDP, nMP, nYP; + switch ( nColFormat ) + { + case SC_COL_YMD: nDP = 2; nMP = 1; nYP = 0; bIso = true; break; + case SC_COL_MDY: nDP = 1; nMP = 0; nYP = 2; bIso = false; break; + case SC_COL_DMY: + default: nDP = 0; nMP = 1; nYP = 2; bIso = false; break; + } + + sal_uInt16 nFound = 0; + bool bInNum = false; + for (sal_Int32 nPos = 0; nPos < nLen && (bInNum || nFound < nMaxNumberParts); ++nPos) + { + bool bLetter = false; + if (rtl::isAsciiDigit(rStr[nPos]) || + (((!bInNum && nFound==nMP) || (bInNum && nFound==nMP+1)) + && (bLetter = ScGlobal::getCharClass().isLetterNumeric( rStr, nPos)))) + { + if (!bInNum) + { + bInNum = true; + nStart[nFound] = nPos; + ++nFound; + } + nEnd[nFound-1] = nPos; + if (bIso && (bLetter || (2 <= nFound && nFound <= 6 && nPos > nStart[nFound-1] + 1))) + // Each M,D,h,m,s at most 2 digits. + bIso = false; + } + else + { + bInNum = false; + if (bIso) + { + // ([+-])YYYY-MM-DD([T ]hh:mm(:ss(.fff)))(([+-])TZ) + // XXX NOTE: timezone is accepted here, but number + // formatter parser will not, so the end result will be + // type Text to preserve timezone information. + switch (rStr[nPos]) + { + case '+': + if (nFound >= 5 && nPos == nEnd[nFound-1] + 1) + // Accept timezone offset. + ; + else if (nPos > 0) + // Accept one leading sign. + bIso = false; + break; + case '-': + if (nFound >= 5 && nPos == nEnd[nFound-1] + 1) + // Accept timezone offset. + ; + else if (nFound == 0 && nPos > 0) + // Accept one leading sign. + bIso = false; + else if (nFound < 1 || 2 < nFound || nPos != nEnd[nFound-1] + 1) + // Not immediately after 1 or 1-2 + bIso = false; + break; + case 'T': + case ' ': + if (nFound != 3 || nPos != nEnd[nFound-1] + 1) + // Not immediately after 1-2-3 + bIso = false; + break; + case ':': + if (nFound < 4 || 5 < nFound || nPos != nEnd[nFound-1] + 1) + // Not at 1-2-3T4:5: + bIso = false; + break; + case '.': + case ',': + if (nFound != 6 || nPos != nEnd[nFound-1] + 1) + // Not at 1-2-3T4:5:6. + bIso = false; + break; + case 'Z': + if (nFound >= 5 && nPos == nEnd[nFound-1] + 1) + // Accept Zero timezone. + ; + else + bIso = false; + break; + default: + bIso = false; + } + } + } + } + + if (nFound < 3) + bIso = false; + + if (bIso) + { + // Leave conversion and detection of various possible number + // formats to the number formatter. ISO is recognized in any locale + // so we can directly use the document's formatter. + sal_uInt32 nFormat = 0; + double fVal = 0.0; + SvNumberFormatter* pDocFormatter = rDoc.GetFormatTable(); + if (pDocFormatter->IsNumberFormat( rStr, nFormat, fVal)) + { + if (pDocFormatter->GetType(nFormat) & SvNumFormatType::DATE) + { + ScAddress aPos(nCol,nRow,nTab); + if (bUseDocImport) + rDocImport.setNumericCell(aPos, fVal); + else + rDoc.SetValue(aPos, fVal); + rDoc.SetNumberFormat(aPos, nFormat); + + return bMultiLine; // success + } + } + // If we reach here it is type Text (e.g. timezone or trailing + // characters). Handled below. + } + + if ( nFound == 1 ) + { + // try to break one number (without separators) into date fields + + sal_Int32 nDateStart = nStart[0]; + sal_Int32 nDateLen = nEnd[0] + 1 - nDateStart; + + if ( nDateLen >= 5 && nDateLen <= 8 && + ScGlobal::getCharClass().isNumeric( rStr.copy( nDateStart, nDateLen ) ) ) + { + // 6 digits: 2 each for day, month, year + // 8 digits: 4 for year, 2 each for day and month + // 5 or 7 digits: first field is shortened by 1 + + bool bLongYear = ( nDateLen >= 7 ); + bool bShortFirst = ( nDateLen == 5 || nDateLen == 7 ); + + sal_uInt16 nFieldStart = nDateStart; + for (sal_uInt16 nPos=0; nPos<3; nPos++) + { + sal_uInt16 nFieldEnd = nFieldStart + 1; // default: 2 digits + if ( bLongYear && nPos == nYP ) + nFieldEnd += 2; // 2 extra digits for long year + if ( bShortFirst && nPos == 0 ) + --nFieldEnd; // first field shortened? + + nStart[nPos] = nFieldStart; + nEnd[nPos] = nFieldEnd; + nFieldStart = nFieldEnd + 1; + } + nFound = 3; + } + } + + if (!bIso && nFound >= 3) + { + using namespace ::com::sun::star; + bool bSecondCal = false; + sal_uInt16 nDay = static_cast<sal_uInt16>(o3tl::toInt32(rStr.subView( nStart[nDP], nEnd[nDP]+1-nStart[nDP] ))); + sal_uInt16 nYear = static_cast<sal_uInt16>(o3tl::toInt32(rStr.subView( nStart[nYP], nEnd[nYP]+1-nStart[nYP] ))); + OUString aMStr = rStr.copy( nStart[nMP], nEnd[nMP]+1-nStart[nMP] ); + sal_Int16 nMonth = static_cast<sal_Int16>(aMStr.toInt32()); + if (!nMonth) + { + static constexpr OUStringLiteral aSepShortened = u"SEP"; + uno::Sequence< i18n::CalendarItem2 > xMonths; + sal_Int32 i, nMonthCount; + // first test all month names from local international + xMonths = rCalendar.getMonths(); + nMonthCount = xMonths.getLength(); + for (i=0; i<nMonthCount && !nMonth; i++) + { + if ( rTransliteration.isEqual( aMStr, xMonths[i].FullName ) || + rTransliteration.isEqual( aMStr, xMonths[i].AbbrevName ) ) + nMonth = sal::static_int_cast<sal_Int16>( i+1 ); + else if ( i == 8 && rTransliteration.isEqual( "SEPT", + xMonths[i].AbbrevName ) && + rTransliteration.isEqual( aMStr, aSepShortened ) ) + { // correct English abbreviation is SEPT, + // but data mostly contains SEP only + nMonth = sal::static_int_cast<sal_Int16>( i+1 ); + } + } + // if none found, then test english month names + if ( !nMonth && pSecondCalendar && pSecondTransliteration ) + { + xMonths = pSecondCalendar->getMonths(); + nMonthCount = xMonths.getLength(); + for (i=0; i<nMonthCount && !nMonth; i++) + { + if ( pSecondTransliteration->isEqual( aMStr, xMonths[i].FullName ) || + pSecondTransliteration->isEqual( aMStr, xMonths[i].AbbrevName ) ) + { + nMonth = sal::static_int_cast<sal_Int16>( i+1 ); + bSecondCal = true; + } + else if ( i == 8 && pSecondTransliteration->isEqual( + aMStr, aSepShortened ) ) + { // correct English abbreviation is SEPT, + // but data mostly contains SEP only + nMonth = sal::static_int_cast<sal_Int16>( i+1 ); + bSecondCal = true; + } + } + } + } + + SvNumberFormatter* pDocFormatter = rDoc.GetFormatTable(); + if ( nYear < 100 ) + nYear = pDocFormatter->ExpandTwoDigitYear( nYear ); + + CalendarWrapper* pCalendar = (bSecondCal ? pSecondCalendar : &rCalendar); + sal_Int16 nNumMonths = pCalendar->getNumberOfMonthsInYear(); + if ( nDay && nMonth && nDay<=31 && nMonth<=nNumMonths ) + { + --nMonth; + pCalendar->setValue( i18n::CalendarFieldIndex::DAY_OF_MONTH, nDay ); + pCalendar->setValue( i18n::CalendarFieldIndex::MONTH, nMonth ); + pCalendar->setValue( i18n::CalendarFieldIndex::YEAR, nYear ); + sal_Int16 nHour, nMinute, nSecond; + // #i14974# The imported value should have no fractional value, so set the + // time fields to zero (ICU calendar instance defaults to current date/time) + nHour = nMinute = nSecond = 0; + if (nFound > 3) + nHour = static_cast<sal_Int16>(o3tl::toInt32(rStr.subView( nStart[3], nEnd[3]+1-nStart[3]))); + if (nFound > 4) + nMinute = static_cast<sal_Int16>(o3tl::toInt32(rStr.subView( nStart[4], nEnd[4]+1-nStart[4]))); + if (nFound > 5) + nSecond = static_cast<sal_Int16>(o3tl::toInt32(rStr.subView( nStart[5], nEnd[5]+1-nStart[5]))); + // do not use calendar's milliseconds, to avoid fractional part truncation + double fFrac = 0.0; + if (nFound > 6) + { + sal_Unicode cDec = '.'; + OUString aT = OUStringChar(cDec) + rStr.subView( nStart[6], nEnd[6]+1-nStart[6]); + rtl_math_ConversionStatus eStatus; + double fV = rtl::math::stringToDouble( aT, cDec, 0, &eStatus ); + if (eStatus == rtl_math_ConversionStatus_Ok) + fFrac = fV / 86400.0; + } + sal_Int32 nPos; + if (nFound > 3 && 1 <= nHour && nHour <= 12 // nHour 0 and >=13 can't be AM/PM + && (nPos = nEnd[nFound-1] + 1) < nLen) + { + // Dreaded AM/PM may be following. + while (nPos < nLen && rStr[nPos] == ' ') + ++nPos; + if (nPos < nLen) + { + sal_Int32 nStop = nPos; + while (nStop < nLen && rStr[nStop] != ' ') + ++nStop; + OUString aAmPm = rStr.copy( nPos, nStop - nPos); + // For AM only 12 needs to be treated, whereas for PM + // it must not. Check both, locale and second/English + // strings. + if (nHour == 12 && + (rTransliteration.isEqual( aAmPm, pFormatter->GetLocaleData()->getTimeAM()) || + (pSecondTransliteration && pSecondTransliteration->isEqual( aAmPm, "AM")))) + { + nHour = 0; + } + else if (nHour < 12 && + (rTransliteration.isEqual( aAmPm, pFormatter->GetLocaleData()->getTimePM()) || + (pSecondTransliteration && pSecondTransliteration->isEqual( aAmPm, "PM")))) + { + nHour += 12; + } + } + } + pCalendar->setValue( i18n::CalendarFieldIndex::HOUR, nHour ); + pCalendar->setValue( i18n::CalendarFieldIndex::MINUTE, nMinute ); + pCalendar->setValue( i18n::CalendarFieldIndex::SECOND, nSecond ); + pCalendar->setValue( i18n::CalendarFieldIndex::MILLISECOND, 0 ); + if ( pCalendar->isValid() ) + { + double fDiff = DateTime(pDocFormatter->GetNullDate()) - + pCalendar->getEpochStart(); + // #i14974# must use getLocalDateTime to get the same + // date values as set above + double fDays = pCalendar->getLocalDateTime() + fFrac; + fDays -= fDiff; + + LanguageType eLatin, eCjk, eCtl; + rDoc.GetLanguage( eLatin, eCjk, eCtl ); + LanguageType eDocLang = eLatin; //! which language for date formats? + + SvNumFormatType nType = (nFound > 3 ? SvNumFormatType::DATETIME : SvNumFormatType::DATE); + sal_uLong nFormat = pDocFormatter->GetStandardFormat( nType, eDocLang ); + // maybe there is a special format including seconds or milliseconds + if (nFound > 5) + nFormat = pDocFormatter->GetStandardFormat( fDays, nFormat, nType, eDocLang); + + ScAddress aPos(nCol,nRow,nTab); + if ( bUseDocImport ) + rDocImport.setNumericCell(aPos, fDays); + else + rDoc.SetValue( aPos, fDays ); + rDoc.SetNumberFormat(aPos, nFormat); + + return bMultiLine; // success + } + } + } + } + + // Standard or date not determined -> SetString / EditCell + if( rStr.indexOf( '\n' ) == -1 ) + { + if (!bDetectNumFormat && nColFormat == SC_COL_STANDARD) + { + // Import a strict ISO 8601 date(+time) string even without + // "Detect special numbers" or "Date (YMD)". + do + { + // Simple pre-check before calling more expensive parser. + // ([+-])(Y)YYYY-MM-DD + if (rStr.getLength() < 10) + break; + const sal_Int32 n1 = rStr.indexOf('-', 1); + if (n1 < 4) + break; + const sal_Int32 n2 = rStr.indexOf('-', n1 + 1); + if (n2 < 7 || n1 + 3 < n2) + break; + + css::util::DateTime aDateTime; + if (!sax::Converter::parseDateTime( aDateTime, rStr)) + break; + + sal_uInt32 nFormat = 0; + double fVal = 0.0; + SvNumberFormatter* pDocFormatter = rDoc.GetFormatTable(); + if (pDocFormatter->IsNumberFormat( rStr, nFormat, fVal)) + { + if (pDocFormatter->GetType(nFormat) & SvNumFormatType::DATE) + { + ScAddress aPos(nCol,nRow,nTab); + if (bUseDocImport) + rDocImport.setNumericCell(aPos, fVal); + else + rDoc.SetValue(aPos, fVal); + rDoc.SetNumberFormat(aPos, nFormat); + + return bMultiLine; // success + } + } + } + while(false); + } + + ScSetStringParam aParam; + aParam.mpNumFormatter = pFormatter; + aParam.mbDetectNumberFormat = bDetectNumFormat; + aParam.meSetTextNumFormat = ScSetStringParam::SpecialNumberOnly; + aParam.mbHandleApostrophe = false; + aParam.mbCheckLinkFormula = true; + if ( bUseDocImport ) + rDocImport.setAutoInput(ScAddress(nCol, nRow, nTab), rStr, &aParam); + else + rDoc.SetString( nCol, nRow, nTab, rStr, &aParam ); + } + else + { + bMultiLine = true; + ScFieldEditEngine& rEngine = rDoc.GetEditEngine(); + rEngine.SetTextCurrentDefaults(rStr); + if ( bUseDocImport ) + rDocImport.setEditCell(ScAddress(nCol, nRow, nTab), rEngine.CreateTextObject()); + else + rDoc.SetEditText( ScAddress( nCol, nRow, nTab ), rEngine.CreateTextObject() ); + } + return bMultiLine; +} + +static OUString lcl_GetFixed( const OUString& rLine, sal_Int32 nStart, sal_Int32 nNext, + bool& rbIsQuoted, bool& rbOverflowCell ) +{ + sal_Int32 nLen = rLine.getLength(); + if (nNext > nLen) + nNext = nLen; + if ( nNext <= nStart ) + return OUString(); + + const sal_Unicode* pStr = rLine.getStr(); + + sal_Int32 nSpace = nNext; + while ( nSpace > nStart && pStr[nSpace-1] == ' ' ) + --nSpace; + + rbIsQuoted = (pStr[nStart] == '"' && pStr[nSpace-1] == '"'); + if (rbIsQuoted) + { + bool bFits = (nSpace - nStart - 3 <= nArbitraryCellLengthLimit); + if (bFits) + return rLine.copy(nStart+1, std::max< sal_Int32 >(0, nSpace-nStart-2)); + else + { + SAL_WARN( "sc", "lcl_GetFixed: line doesn't fit into data"); + rbOverflowCell = true; + return rLine.copy(nStart+1, nArbitraryCellLengthLimit); + } + } + else + { + bool bFits = (nSpace - nStart <= nArbitraryCellLengthLimit); + if (bFits) + return rLine.copy(nStart, nSpace-nStart); + else + { + SAL_WARN( "sc", "lcl_GetFixed: line doesn't fit into data"); + rbOverflowCell = true; + return rLine.copy(nStart, nArbitraryCellLengthLimit); + } + } +} + +bool ScImportExport::ExtText2Doc( SvStream& rStrm ) +{ + if (!pExtOptions) + return Text2Doc( rStrm ); + + sal_uInt64 const nOldPos = rStrm.Tell(); + sal_uInt64 const nRemaining = rStrm.remainingSize(); + std::unique_ptr<ScProgress> xProgress( new ScProgress( pDocSh, + ScResId( STR_LOAD_DOC ), nRemaining, true )); + rStrm.StartReadingUnicodeText( rStrm.GetStreamCharSet() ); + + SCCOL nStartCol = aRange.aStart.Col(); + SCCOL nEndCol = aRange.aEnd.Col(); + SCROW nStartRow = aRange.aStart.Row(); + const SCTAB nTab = aRange.aStart.Tab(); + + bool bFixed = pExtOptions->IsFixedLen(); + OUString aSeps = pExtOptions->GetFieldSeps(); // Need non-const for ReadCsvLine(), + const sal_Unicode* pSeps = aSeps.getStr(); // but it will be const anyway (asserted below). + bool bMerge = pExtOptions->IsMergeSeps(); + bool bRemoveSpace = pExtOptions->IsRemoveSpace(); + sal_uInt16 nInfoCount = pExtOptions->GetInfoCount(); + const sal_Int32* pColStart = pExtOptions->GetColStart(); + const sal_uInt8* pColFormat = pExtOptions->GetColFormat(); + tools::Long nSkipLines = pExtOptions->GetStartRow(); + + LanguageType eDocLang = pExtOptions->GetLanguage(); + SvNumberFormatter aNumFormatter( comphelper::getProcessComponentContext(), eDocLang); + bool bDetectNumFormat = pExtOptions->IsDetectSpecialNumber(); + bool bEvaluateFormulas = pExtOptions->IsEvaluateFormulas(); + bool bSkipEmptyCells = pExtOptions->IsSkipEmptyCells(); + + // For date recognition + ::utl::TransliterationWrapper aTransliteration( + comphelper::getProcessComponentContext(), TransliterationFlags::IGNORE_CASE ); + aTransliteration.loadModuleIfNeeded( eDocLang ); + CalendarWrapper aCalendar( comphelper::getProcessComponentContext() ); + aCalendar.loadDefaultCalendar( + LanguageTag::convertToLocale( eDocLang ) ); + std::unique_ptr< ::utl::TransliterationWrapper > pEnglishTransliteration; + std::unique_ptr< CalendarWrapper > pEnglishCalendar; + if ( eDocLang != LANGUAGE_ENGLISH_US ) + { + pEnglishTransliteration.reset(new ::utl::TransliterationWrapper ( + comphelper::getProcessComponentContext(), TransliterationFlags::IGNORE_CASE )); + aTransliteration.loadModuleIfNeeded( LANGUAGE_ENGLISH_US ); + pEnglishCalendar.reset(new CalendarWrapper ( comphelper::getProcessComponentContext() )); + pEnglishCalendar->loadDefaultCalendar( + LanguageTag::convertToLocale( LANGUAGE_ENGLISH_US ) ); + } + + OUString aLine; + OUString aCell; + sal_uInt16 i; + SCROW nRow = nStartRow; + sal_Unicode cDetectSep = 0xffff; // No separator detection here. + + while(--nSkipLines>0) + { + aLine = ReadCsvLine(rStrm, !bFixed, aSeps, cStr, cDetectSep); // content is ignored + if ( rStrm.eof() ) + break; + } + + // Determine range for Undo. + // We don't need this during import of a file to a new sheet or document... + bool bDetermineRange = bUndo; + bool bColumnsAreDetermined = false; + + // Row heights don't need to be adjusted on the fly if EndPaste() is called + // afterwards, which happens only if bDetermineRange. This variable also + // survives the toggle of bDetermineRange down at the end of the do{} loop. + bool bRangeIsDetermined = bDetermineRange; + + bool bQuotedAsText = pExtOptions && pExtOptions->IsQuotedAsText(); + + sal_uInt64 nOriginalStreamPos = rStrm.Tell(); + + SCROW nFirstUpdateRowHeight = SCROW_MAX; + SCROW nLastUpdateRowHeight = -1; + + ScDocumentImport aDocImport(rDoc); + do + { + for( ;; ) + { + aLine = ReadCsvLine(rStrm, !bFixed, aSeps, cStr, cDetectSep); + if ( rStrm.eof() && aLine.isEmpty() ) + break; + + assert(pSeps == aSeps.getStr()); + + if ( nRow > rDoc.MaxRow() ) + { + bOverflowRow = true; // display warning on import + break; // for + } + + if (!bDetermineRange) + EmbeddedNullTreatment( aLine); + + sal_Int32 nLineLen = aLine.getLength(); + SCCOL nCol = nStartCol; + bool bMultiLine = false; + if ( bFixed ) // Fixed line length + { + if (bDetermineRange) + { + if (!bColumnsAreDetermined) + { + // Yes, the check is nCol<=rDoc.MaxCol()+1, +1 because it + // is only an overflow if there is really data following to + // be put behind the last column, which doesn't happen if + // info is SC_COL_SKIP. + for (i=0; i < nInfoCount && nCol <= rDoc.MaxCol()+1; ++i) + { + const sal_uInt8 nFmt = pColFormat[i]; + if (nFmt != SC_COL_SKIP) // otherwise don't increment nCol either + { + if (nCol > rDoc.MaxCol()) + bOverflowCol = true; // display warning on import + ++nCol; + } + } + bColumnsAreDetermined = true; + } + } + else + { + sal_Int32 nStartIdx = 0; + // Same maxcol+1 check reason as above. + for (i=0; i < nInfoCount && nCol <= rDoc.MaxCol()+1; ++i) + { + sal_Int32 nNextIdx = nStartIdx; + if (i + 1 < nInfoCount) + CountVisualWidth( aLine, nNextIdx, pColStart[i+1] - pColStart[i] ); + else + nNextIdx = nLineLen; + sal_uInt8 nFmt = pColFormat[i]; + if (nFmt != SC_COL_SKIP) // otherwise don't increment nCol either + { + if (nCol > rDoc.MaxCol()) + bOverflowCol = true; // display warning on import + else + { + bool bIsQuoted = false; + aCell = lcl_GetFixed( aLine, nStartIdx, nNextIdx, bIsQuoted, bOverflowCell ); + if (bIsQuoted && bQuotedAsText) + nFmt = SC_COL_TEXT; + + bMultiLine |= lcl_PutString( + aDocImport, !mbOverwriting, nCol, nRow, nTab, aCell, nFmt, + &aNumFormatter, bDetectNumFormat, bEvaluateFormulas, bSkipEmptyCells, + aTransliteration, aCalendar, + pEnglishTransliteration.get(), pEnglishCalendar.get()); + } + ++nCol; + } + nStartIdx = nNextIdx; + } + } + } + else // Search for the separator + { + SCCOL nSourceCol = 0; + sal_uInt16 nInfoStart = 0; + const sal_Unicode* p = aLine.getStr(); + // Yes, the check is nCol<=rDoc.MaxCol()+1, +1 because it is only an + // overflow if there is really data following to be put behind + // the last column, which doesn't happen if info is + // SC_COL_SKIP. + while (*p && nCol <= rDoc.MaxCol()+1) + { + bool bIsQuoted = false; + p = ScImportExport::ScanNextFieldFromString( p, aCell, + cStr, pSeps, bMerge, bIsQuoted, bOverflowCell, bRemoveSpace ); + + sal_uInt8 nFmt = SC_COL_STANDARD; + for ( i=nInfoStart; i<nInfoCount; i++ ) + { + if ( pColStart[i] == nSourceCol + 1 ) // pColStart is 1-based + { + nFmt = pColFormat[i]; + nInfoStart = i + 1; // ColInfos are in succession + break; // for + } + } + if ( nFmt != SC_COL_SKIP ) + { + if (nCol > rDoc.MaxCol()) + bOverflowCol = true; // display warning on import + else if (!bDetermineRange) + { + if (bIsQuoted && bQuotedAsText) + nFmt = SC_COL_TEXT; + + bMultiLine |= lcl_PutString( + aDocImport, !mbOverwriting, nCol, nRow, nTab, aCell, nFmt, + &aNumFormatter, bDetectNumFormat, bEvaluateFormulas, bSkipEmptyCells, + aTransliteration, aCalendar, + pEnglishTransliteration.get(), pEnglishCalendar.get()); + } + ++nCol; + } + + ++nSourceCol; + } + } + if (nEndCol < nCol) + nEndCol = nCol; //! points to the next free or even rDoc.MaxCol()+2 + + if (!bDetermineRange) + { + if (bMultiLine && !bRangeIsDetermined && pDocSh) + { // Adjust just once at the end for a whole range. + nFirstUpdateRowHeight = std::min( nFirstUpdateRowHeight, nRow ); + nLastUpdateRowHeight = std::max( nLastUpdateRowHeight, nRow ); + } + xProgress->SetStateOnPercent( rStrm.Tell() - nOldPos ); + } + ++nRow; + } + // so far nRow/nEndCol pointed to the next free + if (nRow > nStartRow) + --nRow; + if (nEndCol > nStartCol) + nEndCol = ::std::min( static_cast<SCCOL>(nEndCol - 1), rDoc.MaxCol()); + + if (bDetermineRange) + { + aRange.aEnd.SetCol( nEndCol ); + aRange.aEnd.SetRow( nRow ); + + if ( !mbApi && nStartCol != nEndCol && + !rDoc.IsBlockEmpty( nStartCol + 1, nStartRow, nEndCol, nRow, nTab ) ) + { + ScReplaceWarnBox aBox(ScDocShell::GetActiveDialogParent()); + if (aBox.run() != RET_YES) + { + return false; + } + } + + rStrm.Seek( nOriginalStreamPos ); + nRow = nStartRow; + if (!StartPaste()) + { + EndPaste(false); + return false; + } + } + + bDetermineRange = !bDetermineRange; // toggle + } while (!bDetermineRange); + + if ( !mbOverwriting ) + aDocImport.finalize(); + + xProgress.reset(); // make room for AdjustRowHeight progress + + if( nFirstUpdateRowHeight < nLastUpdateRowHeight && pDocSh ) + pDocSh->AdjustRowHeight( nFirstUpdateRowHeight, nLastUpdateRowHeight, nTab); + + if (bRangeIsDetermined) + EndPaste(false); + + if (mbImportBroadcast && !mbOverwriting) + { + rDoc.BroadcastCells(aRange, SfxHintId::ScDataChanged); + pDocSh->PostDataChanged(); + } + return true; +} + +void ScImportExport::EmbeddedNullTreatment( OUString & rStr ) +{ + // A nasty workaround for data with embedded NULL characters. As long as we + // can't handle them properly as cell content (things assume 0-terminated + // strings at too many places) simply strip all NULL characters from raw + // data. Excel does the same. See fdo#57841 for sample data. + + // The normal case is no embedded NULL, check first before de-/allocating + // ustring stuff. + sal_Unicode cNull = 0; + if (sal_Int32 pos = rStr.indexOf(cNull); pos >= 0) + { + rStr = rStr.replaceAll(std::u16string_view(&cNull, 1), u"", pos); + } +} + +const sal_Unicode* ScImportExport::ScanNextFieldFromString( const sal_Unicode* p, + OUString& rField, sal_Unicode cStr, const sal_Unicode* pSeps, bool bMergeSeps, bool& rbIsQuoted, + bool& rbOverflowCell, bool bRemoveSpace ) +{ + rbIsQuoted = false; + rField.clear(); + const sal_Unicode cBlank = ' '; + if (cStr && !ScGlobal::UnicodeStrChr(pSeps, cBlank)) + { + // Cope with broken generators that put leading blanks before a quoted + // field, like "field1", "field2", "..." + // NOTE: this is not in conformance with http://tools.ietf.org/html/rfc4180 + const sal_Unicode* pb = p; + while (*pb == cBlank) + ++pb; + if (*pb == cStr) + p = pb; + } + if (cStr && *p == cStr) // String in quotes + { + rbIsQuoted = true; + const sal_Unicode* p1; + p1 = p = lcl_ScanString( p, rField, pSeps, cStr, DoubledQuoteMode::ESCAPE, rbOverflowCell ); + while (!lcl_isFieldEnd( *p, pSeps)) + p++; + // Append remaining unquoted and undelimited data (dirty, dirty) to + // this field. + if (p > p1) + { + const sal_Unicode* ptrim_f = p; + if ( bRemoveSpace ) + { + while ( ptrim_f > p1 && ( *(ptrim_f - 1) == cBlank ) ) + --ptrim_f; + } + if (!lcl_appendLineData( rField, p1, ptrim_f)) + rbOverflowCell = true; + } + if( *p ) + p++; + } + else // up to delimiter + { + const sal_Unicode* p0 = p; + while (!lcl_isFieldEnd( *p, pSeps)) + p++; + const sal_Unicode* ptrim_i = p0; + const sal_Unicode* ptrim_f = p; // [ptrim_i,ptrim_f) is cell data after trimming + if ( bRemoveSpace ) + { + while ( ptrim_i < ptrim_f && *ptrim_i == cBlank ) + ++ptrim_i; + while ( ptrim_f > ptrim_i && ( *(ptrim_f - 1) == cBlank ) ) + --ptrim_f; + } + if (!lcl_appendLineData( rField, ptrim_i, ptrim_f)) + rbOverflowCell = true; + if( *p ) + p++; + } + if ( bMergeSeps ) // skip following delimiters + { + while (*p && ScGlobal::UnicodeStrChr( pSeps, *p)) + p++; + } + return p; +} + +namespace { + +/** + * Check if a given string has any line break characters or separators. + * + * @param rStr string to inspect. + * @param cSep separator character. + */ +bool hasLineBreaksOrSeps( const OUString& rStr, sal_Unicode cSep ) +{ + const sal_Unicode* p = rStr.getStr(); + for (sal_Int32 i = 0, n = rStr.getLength(); i < n; ++i, ++p) + { + sal_Unicode c = *p; + if (c == cSep) + // separator found. + return true; + + switch (c) + { + case '\n': + case '\r': + // line break found. + return true; + default: + ; + } + } + return false; +} + +} + +bool ScImportExport::Doc2Text( SvStream& rStrm ) +{ + SCCOL nCol; + SCROW nRow; + SCCOL nStartCol = aRange.aStart.Col(); + SCROW nStartRow = aRange.aStart.Row(); + SCTAB nStartTab = aRange.aStart.Tab(); + SCCOL nEndCol = aRange.aEnd.Col(); + SCROW nEndRow = aRange.aEnd.Row(); + SCTAB nEndTab = aRange.aEnd.Tab(); + + if (!rDoc.GetClipParam().isMultiRange() && nStartTab == nEndTab) + if (!rDoc.ShrinkToDataArea( nStartTab, nStartCol, nStartRow, nEndCol, nEndRow )) + return false; + + OUString aCellStr; + + bool bConvertLF = (GetSystemLineEnd() != LINEEND_LF); + + // We need to cache sc::ColumnBlockPosition per each column, tab is always nStartTab. + std::vector< sc::ColumnBlockPosition > blockPos( nEndCol - nStartCol + 1 ); + for( SCCOL i = nStartCol; i <= nEndCol; ++i ) + rDoc.InitColumnBlockPosition( blockPos[ i - nStartCol ], nStartTab, i ); + for (nRow = nStartRow; nRow <= nEndRow; nRow++) + { + if (bIncludeFiltered || !rDoc.RowFiltered( nRow, nStartTab )) + { + for (nCol = nStartCol; nCol <= nEndCol; nCol++) + { + ScAddress aPos(nCol, nRow, nStartTab); + sal_uInt32 nNumFmt = rDoc.GetNumberFormat(aPos); + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + + ScRefCellValue aCell(rDoc, aPos, blockPos[ nCol - nStartCol ]); + switch (aCell.meType) + { + case CELLTYPE_FORMULA: + { + if (bFormulas) + { + aCellStr = aCell.mpFormula->GetFormula(); + if( aCellStr.indexOf( cSep ) != -1 ) + lcl_WriteString( rStrm, aCellStr, cStr, cStr ); + else + lcl_WriteSimpleString( rStrm, aCellStr ); + } + else + { + const Color* pColor; + aCellStr = ScCellFormat::GetString(aCell, nNumFmt, &pColor, *pFormatter, rDoc); + + bool bMultiLineText = ( aCellStr.indexOf( '\n' ) != -1 ); + if( bMultiLineText ) + { + if( mExportTextOptions.meNewlineConversion == ScExportTextOptions::ToSpace ) + aCellStr = aCellStr.replaceAll( "\n", " " ); + else if ( mExportTextOptions.meNewlineConversion == ScExportTextOptions::ToSystem && bConvertLF ) + aCellStr = convertLineEnd(aCellStr, GetSystemLineEnd()); + } + + if( mExportTextOptions.mcSeparatorConvertTo && cSep ) + aCellStr = aCellStr.replaceAll( OUStringChar(cSep), OUStringChar(mExportTextOptions.mcSeparatorConvertTo) ); + + if( mExportTextOptions.mbAddQuotes && ( aCellStr.indexOf( cSep ) != -1 ) ) + lcl_WriteString( rStrm, aCellStr, cStr, cStr ); + else + lcl_WriteSimpleString( rStrm, aCellStr ); + } + } + break; + case CELLTYPE_VALUE: + { + const Color* pColor; + aCellStr = ScCellFormat::GetString(aCell, nNumFmt, &pColor, *pFormatter, rDoc); + lcl_WriteSimpleString( rStrm, aCellStr ); + } + break; + case CELLTYPE_NONE: + break; + default: + { + const Color* pColor; + aCellStr = ScCellFormat::GetString(aCell, nNumFmt, &pColor, *pFormatter, rDoc); + + bool bMultiLineText = ( aCellStr.indexOf( '\n' ) != -1 ); + if( bMultiLineText ) + { + if( mExportTextOptions.meNewlineConversion == ScExportTextOptions::ToSpace ) + aCellStr = aCellStr.replaceAll( "\n", " " ); + else if ( mExportTextOptions.meNewlineConversion == ScExportTextOptions::ToSystem && bConvertLF ) + aCellStr = convertLineEnd(aCellStr, GetSystemLineEnd()); + } + + if( mExportTextOptions.mcSeparatorConvertTo && cSep ) + aCellStr = aCellStr.replaceAll( OUStringChar(cSep), OUStringChar(mExportTextOptions.mcSeparatorConvertTo) ); + + if( mExportTextOptions.mbAddQuotes && hasLineBreaksOrSeps(aCellStr, cSep) ) + lcl_WriteString( rStrm, aCellStr, cStr, cStr ); + else + lcl_WriteSimpleString( rStrm, aCellStr ); + } + } + if( nCol < nEndCol ) + lcl_WriteSimpleString( rStrm, OUString(cSep) ); + } + // Do not append a line feed for one single cell. + // NOTE: this Doc2Text() is only called for clipboard via + // ScImportExport::ExportStream(). + if (nStartRow != nEndRow || nStartCol != nEndCol) + WriteUnicodeOrByteEndl( rStrm ); + if( rStrm.GetError() != ERRCODE_NONE ) + break; + if( nSizeLimit && rStrm.Tell() > nSizeLimit ) + break; + } + } + + return rStrm.GetError() == ERRCODE_NONE; +} + +bool ScImportExport::Sylk2Doc( SvStream& rStrm ) +{ + bool bOk = true; + bool bMyDoc = false; + SylkVersion eVersion = SylkVersion::OTHER; + + // US-English separators for StringToDouble + sal_Unicode const cDecSep = '.'; + sal_Unicode const cGrpSep = ','; + + SCCOL nStartCol = aRange.aStart.Col(); + SCROW nStartRow = aRange.aStart.Row(); + SCCOL nEndCol = aRange.aEnd.Col(); + SCROW nEndRow = aRange.aEnd.Row(); + sal_uInt64 nOldPos = rStrm.Tell(); + bool bData = !bSingle; + ::std::vector< sal_uInt32 > aFormats; + + if( !bSingle) + bOk = StartPaste(); + + while( bOk ) + { + OUString aLine; + OUString aText; + OStringBuffer aByteLine; + SCCOL nCol = nStartCol; + SCROW nRow = nStartRow; + SCCOL nRefCol = nCol; + SCROW nRefRow = nRow; + rStrm.Seek( nOldPos ); + for( ;; ) + { + //! allow unicode + rStrm.ReadLine( aByteLine ); + aLine = OStringToOUString(aByteLine, rStrm.GetStreamCharSet()); + if( rStrm.eof() ) + break; + bool bInvalidCol = false; + bool bInvalidRow = false; + const sal_Unicode* p = aLine.getStr(); + sal_Unicode cTag = *p++; + if( cTag == 'C' ) // Content + { + if( *p++ != ';' ) + return false; + + bool bInvalidRefCol = false; + bool bInvalidRefRow = false; + while( *p ) + { + sal_Unicode ch = *p++; + ch = ScGlobal::ToUpperAlpha( ch ); + switch( ch ) + { + case 'X': + { + bInvalidCol = false; + bool bFail = o3tl::checked_add<SCCOL>(o3tl::toInt32(std::u16string_view(p)), nStartCol - 1, nCol); + if (bFail || nCol < 0 || rDoc.MaxCol() < nCol) + { + SAL_WARN("sc.ui","ScImportExport::Sylk2Doc - ;X invalid nCol=" << nCol); + nCol = std::clamp<SCCOL>(nCol, 0, rDoc.MaxCol()); + bInvalidCol = bOverflowCol = true; + } + break; + } + case 'Y': + { + bInvalidRow = false; + bool bFail = o3tl::checked_add(o3tl::toInt32(std::u16string_view(p)), nStartRow - 1, nRow); + if (bFail || nRow < 0 || nMaxImportRow < nRow) + { + SAL_WARN("sc.ui","ScImportExport::Sylk2Doc - ;Y invalid nRow=" << nRow); + nRow = std::clamp<SCROW>(nRow, 0, nMaxImportRow); + bInvalidRow = bOverflowRow = true; + } + break; + } + case 'C': + { + bInvalidRefCol = false; + bool bFail = o3tl::checked_add<SCCOL>(o3tl::toInt32(std::u16string_view(p)), nStartCol - 1, nRefCol); + if (bFail || nRefCol < 0 || rDoc.MaxCol() < nRefCol) + { + SAL_WARN("sc.ui","ScImportExport::Sylk2Doc - ;C invalid nRefCol=" << nRefCol); + nRefCol = std::clamp<SCCOL>(nRefCol, 0, rDoc.MaxCol()); + bInvalidRefCol = bOverflowCol = true; + } + break; + } + case 'R': + { + bInvalidRefRow = false; + bool bFail = o3tl::checked_add(o3tl::toInt32(std::u16string_view(p)), nStartRow - 1, nRefRow); + if (bFail || nRefRow < 0 || nMaxImportRow < nRefRow) + { + SAL_WARN("sc.ui","ScImportExport::Sylk2Doc - ;R invalid nRefRow=" << nRefRow); + nRefRow = std::clamp<SCROW>(nRefRow, 0, nMaxImportRow); + bInvalidRefRow = bOverflowRow = true; + } + break; + } + case 'K': + { + if( !bSingle && + ( nCol < nStartCol || nCol > nEndCol + || nRow < nStartRow || nRow > nEndRow + || nCol > rDoc.MaxCol() || nRow > nMaxImportRow + || bInvalidCol || bInvalidRow ) ) + break; + if( !bData ) + { + if( nRow > nEndRow ) + nEndRow = nRow; + if( nCol > nEndCol ) + nEndCol = nCol; + break; + } + bool bText; + if( *p == '"' ) + { + bText = true; + aText.clear(); + p = lcl_ScanSylkString( p, aText, eVersion); + } + else + bText = false; + const sal_Unicode* q = p; + while( *q && *q != ';' ) + q++; + if ( (*q != ';' || *(q+1) != 'I') && !bInvalidCol && !bInvalidRow ) + { // don't ignore value + if( bText ) + { + rDoc.EnsureTable(aRange.aStart.Tab()); + rDoc.SetTextCell( + ScAddress(nCol, nRow, aRange.aStart.Tab()), aText); + } + else + { + double fVal = rtl_math_uStringToDouble( p, + aLine.getStr() + aLine.getLength(), + cDecSep, cGrpSep, nullptr, nullptr ); + rDoc.SetValue( nCol, nRow, aRange.aStart.Tab(), fVal ); + } + } + } + break; + case 'E': + case 'M': + { + if ( ch == 'M' ) + { + if ( nRefCol < nCol ) + nRefCol = nCol; + if ( nRefRow < nRow ) + nRefRow = nRow; + if ( !bData ) + { + if( nRefRow > nEndRow ) + nEndRow = nRefRow; + if( nRefCol > nEndCol ) + nEndCol = nRefCol; + } + } + if( !bMyDoc || !bData ) + break; + aText = "="; + p = lcl_ScanSylkFormula( p, aText, eVersion); + + if (bInvalidCol || bInvalidRow || (ch == 'M' && (bInvalidRefCol || bInvalidRefRow))) + break; + + ScAddress aPos( nCol, nRow, aRange.aStart.Tab() ); + /* FIXME: do we want GRAM_ODFF_A1 instead? At the + * end it probably should be GRAM_ODFF_R1C1, since + * R1C1 is what Excel writes in SYLK, or even + * better GRAM_ENGLISH_XL_R1C1. */ + const formula::FormulaGrammar::Grammar eGrammar = formula::FormulaGrammar::GRAM_PODF_A1; + ScCompiler aComp(rDoc, aPos, eGrammar); + std::unique_ptr<ScTokenArray> xCode(aComp.CompileString(aText)); // ctor/InsertMatrixFormula did copy TokenArray + rDoc.CheckLinkFormulaNeedingCheck(*xCode); + if ( ch == 'M' ) + { + ScMarkData aMark(rDoc.GetSheetLimits()); + aMark.SelectTable( aPos.Tab(), true ); + rDoc.InsertMatrixFormula( nCol, nRow, nRefCol, + nRefRow, aMark, OUString(), xCode.get() ); + } + else + { + ScFormulaCell* pFCell = new ScFormulaCell( + rDoc, aPos, *xCode, eGrammar, ScMatrixMode::NONE); + rDoc.SetFormulaCell(aPos, pFCell); + } + } + break; + } + while( *p && *p != ';' ) + p++; + if( *p ) + p++; + } + } + else if( cTag == 'F' ) // Format + { + if( *p++ != ';' ) + return false; + sal_Int32 nFormat = -1; + while( *p ) + { + sal_Unicode ch = *p++; + ch = ScGlobal::ToUpperAlpha( ch ); + switch( ch ) + { + case 'X': + { + bInvalidCol = false; + bool bFail = o3tl::checked_add<SCCOL>(o3tl::toInt32(std::u16string_view(p)), nStartCol - 1, nCol); + if (bFail || nCol < 0 || rDoc.MaxCol() < nCol) + { + SAL_WARN("sc.ui","ScImportExport::Sylk2Doc - ;X invalid nCol=" << nCol); + nCol = std::clamp<SCCOL>(nCol, 0, rDoc.MaxCol()); + bInvalidCol = bOverflowCol = true; + } + break; + } + case 'Y': + { + bInvalidRow = false; + bool bFail = o3tl::checked_add(o3tl::toInt32(std::u16string_view(p)), nStartRow - 1, nRow); + if (bFail || nRow < 0 || nMaxImportRow < nRow) + { + SAL_WARN("sc.ui","ScImportExport::Sylk2Doc - ;Y invalid nRow=" << nRow); + nRow = std::clamp<SCROW>(nRow, 0, nMaxImportRow); + bInvalidRow = bOverflowRow = true; + } + break; + } + case 'P' : + if ( bData ) + { + // F;P<n> sets format code of P;P<code> at + // current position, or at ;X;Y if specified. + // Note that ;X;Y may appear after ;P + const sal_Unicode* p0 = p; + while( *p && *p != ';' ) + p++; + OUString aNumber(p0, p - p0); + nFormat = aNumber.toInt32(); + } + break; + } + while( *p && *p != ';' ) + p++; + if( *p ) + p++; + } + if ( !bData ) + { + if( nRow > nEndRow ) + nEndRow = nRow; + if( nCol > nEndCol ) + nEndCol = nCol; + } + if ( 0 <= nFormat && o3tl::make_unsigned(nFormat) < aFormats.size() && !bInvalidCol && !bInvalidRow ) + { + sal_uInt32 nKey = aFormats[nFormat]; + rDoc.ApplyAttr( nCol, nRow, aRange.aStart.Tab(), + SfxUInt32Item( ATTR_VALUE_FORMAT, nKey ) ); + } + } + else if( cTag == 'P' ) + { + if ( bData && *p == ';' && *(p+1) == 'P' ) + { + OUString aCode( p+2 ); + + sal_uInt32 nKey; + sal_Int32 nCheckPos; + + if (aCode.getLength() > 2048 && utl::ConfigManager::IsFuzzing()) + { + // consider an excessive length as a failure when fuzzing + nCheckPos = 1; + } + else + { + // unescape doubled semicolons + aCode = aCode.replaceAll(";;", ";"); + // get rid of Xcl escape characters + aCode = aCode.replaceAll("\x1b", ""); + SvNumFormatType nType; + rDoc.GetFormatTable()->PutandConvertEntry( aCode, nCheckPos, nType, nKey, + LANGUAGE_ENGLISH_US, ScGlobal::eLnge, false); + } + + if ( nCheckPos ) + nKey = 0; + + aFormats.push_back( nKey ); + } + } + else if (cTag == 'I' && *p == 'D' && aLine.getLength() > 4) + { + aLine = aLine.copy(4); + if (aLine == "CALCOOO32") + eVersion = SylkVersion::OOO32; + else if (aLine == "SCALC3") + eVersion = SylkVersion::SCALC3; + bMyDoc = (eVersion <= SylkVersion::OWN); + } + else if( cTag == 'E' ) // End + break; + } + if( !bData ) + { + aRange.aEnd.SetCol( nEndCol ); + aRange.aEnd.SetRow( nEndRow ); + bOk = StartPaste(); + bData = true; + } + else + break; + } + + EndPaste(); + return bOk; +} + +bool ScImportExport::Doc2Sylk( SvStream& rStrm ) +{ + SCCOL nCol; + SCROW nRow; + SCCOL nStartCol = aRange.aStart.Col(); + SCROW nStartRow = aRange.aStart.Row(); + SCCOL nEndCol = aRange.aEnd.Col(); + SCROW nEndRow = aRange.aEnd.Row(); + OUString aCellStr; + OUString aValStr; + lcl_WriteSimpleString( rStrm, "ID;PCALCOOO32" ); + WriteUnicodeOrByteEndl( rStrm ); + + for (nRow = nStartRow; nRow <= nEndRow; nRow++) + { + for (nCol = nStartCol; nCol <= nEndCol; nCol++) + { + OUString aBufStr; + double nVal; + bool bForm = false; + SCROW r = nRow - nStartRow + 1; + SCCOL c = nCol - nStartCol + 1; + ScRefCellValue aCell(rDoc, ScAddress(nCol, nRow, aRange.aStart.Tab())); + CellType eType = aCell.meType; + switch( eType ) + { + case CELLTYPE_FORMULA: + bForm = bFormulas; + if( rDoc.HasValueData( nCol, nRow, aRange.aStart.Tab()) ) + goto hasvalue; + else + goto hasstring; + + case CELLTYPE_VALUE: + hasvalue: + nVal = rDoc.GetValue( nCol, nRow, aRange.aStart.Tab() ); + + aValStr = ::rtl::math::doubleToUString( nVal, + rtl_math_StringFormat_Automatic, + rtl_math_DecimalPlaces_Max, '.', true ); + + aBufStr = "C;X" + + OUString::number( c ) + + ";Y" + + OUString::number( r ) + + ";K" + + aValStr; + lcl_WriteSimpleString( rStrm, aBufStr ); + goto checkformula; + + case CELLTYPE_STRING: + case CELLTYPE_EDIT: + hasstring: + aCellStr = rDoc.GetString(nCol, nRow, aRange.aStart.Tab()); + aCellStr = aCellStr.replaceAll("\n", SYLK_LF); + + aBufStr = "C;X" + + OUString::number( c ) + + ";Y" + + OUString::number( r ) + + ";K"; + lcl_WriteSimpleString( rStrm, aBufStr ); + lcl_WriteString( rStrm, aCellStr, '"', ';' ); + + checkformula: + if( bForm ) + { + const ScFormulaCell* pFCell = aCell.mpFormula; + switch ( pFCell->GetMatrixFlag() ) + { + case ScMatrixMode::Reference : + aCellStr.clear(); + break; + default: + aCellStr = pFCell->GetFormula( formula::FormulaGrammar::GRAM_PODF_A1); + /* FIXME: do we want GRAM_ODFF_A1 instead? At + * the end it probably should be + * GRAM_ODFF_R1C1, since R1C1 is what Excel + * writes in SYLK, or even better + * GRAM_ENGLISH_XL_R1C1. */ + } + if ( pFCell->GetMatrixFlag() != ScMatrixMode::NONE && + aCellStr.startsWith("{") && + aCellStr.endsWith("}") ) + { // cut off matrix {} characters + aCellStr = aCellStr.copy(1, aCellStr.getLength()-2); + } + if ( aCellStr[0] == '=' ) + aCellStr = aCellStr.copy(1); + OUString aPrefix; + switch ( pFCell->GetMatrixFlag() ) + { + case ScMatrixMode::Formula : + { // diff expression with 'M' M$-extension + SCCOL nC; + SCROW nR; + pFCell->GetMatColsRows( nC, nR ); + nC += c - 1; + nR += r - 1; + aPrefix = ";R" + + OUString::number( nR ) + + ";C" + + OUString::number( nC ) + + ";M"; + } + break; + case ScMatrixMode::Reference : + { // diff expression with 'I' M$-extension + ScAddress aPos; + (void)pFCell->GetMatrixOrigin( rDoc, aPos ); + aPrefix = ";I;R" + + OUString::number( aPos.Row() - nStartRow + 1 ) + + ";C" + + OUString::number( aPos.Col() - nStartCol + 1 ); + } + break; + default: + // formula Expression + aPrefix = ";E"; + } + lcl_WriteSimpleString( rStrm, aPrefix ); + if ( !aCellStr.isEmpty() ) + lcl_WriteString( rStrm, aCellStr, 0, ';' ); + } + WriteUnicodeOrByteEndl( rStrm ); + break; + + default: + { + // added to avoid warnings + } + } + } + } + lcl_WriteSimpleString( rStrm, OUString( 'E' ) ); + WriteUnicodeOrByteEndl( rStrm ); + return rStrm.GetError() == ERRCODE_NONE; +} + +bool ScImportExport::Doc2HTML( SvStream& rStrm, const OUString& rBaseURL ) +{ + // rtl_TextEncoding is ignored in ScExportHTML, read from Load/Save HTML options + ScFormatFilter::Get().ScExportHTML( rStrm, rBaseURL, &rDoc, aRange, RTL_TEXTENCODING_DONTKNOW, bAll, + aStreamPath, aNonConvertibleChars, maFilterOptions ); + return rStrm.GetError() == ERRCODE_NONE; +} + +bool ScImportExport::Doc2RTF( SvStream& rStrm ) +{ + // rtl_TextEncoding is ignored in ScExportRTF + ScFormatFilter::Get().ScExportRTF( rStrm, &rDoc, aRange, RTL_TEXTENCODING_DONTKNOW ); + return rStrm.GetError() == ERRCODE_NONE; +} + +bool ScImportExport::Doc2Dif( SvStream& rStrm ) +{ + // for DIF in the clipboard, IBM_850 is always used + ScFormatFilter::Get().ScExportDif( rStrm, &rDoc, aRange, RTL_TEXTENCODING_IBM_850 ); + return true; +} + +bool ScImportExport::Dif2Doc( SvStream& rStrm ) +{ + SCTAB nTab = aRange.aStart.Tab(); + ScDocumentUniquePtr pImportDoc( new ScDocument( SCDOCMODE_UNDO ) ); + pImportDoc->InitUndo( rDoc, nTab, nTab ); + + // for DIF in the clipboard, IBM_850 is always used + ScFormatFilter::Get().ScImportDif( rStrm, pImportDoc.get(), aRange.aStart, RTL_TEXTENCODING_IBM_850 ); + + SCCOL nEndCol; + SCROW nEndRow; + pImportDoc->GetCellArea( nTab, nEndCol, nEndRow ); + // if there are no cells in the imported content, nEndCol/nEndRow may be before the start + if ( nEndCol < aRange.aStart.Col() ) + nEndCol = aRange.aStart.Col(); + if ( nEndRow < aRange.aStart.Row() ) + nEndRow = aRange.aStart.Row(); + aRange.aEnd = ScAddress( nEndCol, nEndRow, nTab ); + + bool bOk = StartPaste(); + if (bOk) + { + InsertDeleteFlags nFlags = InsertDeleteFlags::ALL & ~InsertDeleteFlags::STYLES; + rDoc.DeleteAreaTab( aRange, nFlags ); + pImportDoc->CopyToDocument(aRange, nFlags, false, rDoc); + EndPaste(); + } + + return bOk; +} + +bool ScImportExport::RTF2Doc( SvStream& rStrm, const OUString& rBaseURL ) +{ + std::unique_ptr<ScEEAbsImport> pImp = ScFormatFilter::Get().CreateRTFImport( &rDoc, aRange ); + if (!pImp) + return false; + pImp->Read( rStrm, rBaseURL ); + aRange = pImp->GetRange(); + + bool bOk = StartPaste(); + if (bOk) + { + InsertDeleteFlags const nFlags = InsertDeleteFlags::ALL & ~InsertDeleteFlags::STYLES; + rDoc.DeleteAreaTab( aRange, nFlags ); + pImp->WriteToDocument(); + EndPaste(); + } + return bOk; +} + +bool ScImportExport::HTML2Doc( SvStream& rStrm, const OUString& rBaseURL ) +{ + std::unique_ptr<ScEEAbsImport> pImp = ScFormatFilter::Get().CreateHTMLImport( &rDoc, rBaseURL, aRange); + if (!pImp) + return false; + pImp->Read( rStrm, rBaseURL ); + aRange = pImp->GetRange(); + + bool bOk = StartPaste(); + if (bOk) + { + // ScHTMLImport may call ScDocument::InitDrawLayer, resulting in + // a Draw Layer but no Draw View -> create Draw Layer and View here + if (pDocSh) + pDocSh->MakeDrawLayer(); + + InsertDeleteFlags const nFlags = InsertDeleteFlags::ALL & ~InsertDeleteFlags::STYLES; + rDoc.DeleteAreaTab( aRange, nFlags ); + + if (pExtOptions) + { + // Pick up import options if available. + LanguageType eLang = pExtOptions->GetLanguage(); + SvNumberFormatter aNumFormatter( comphelper::getProcessComponentContext(), eLang); + bool bSpecialNumber = pExtOptions->IsDetectSpecialNumber(); + pImp->WriteToDocument(false, 1.0, &aNumFormatter, bSpecialNumber); + } + else + // Regular import, with no options. + pImp->WriteToDocument(); + + EndPaste(); + } + return bOk; +} + +#ifndef DISABLE_DYNLOADING + +extern "C" { static void thisModule() {} } + +#else + +extern "C" { +ScFormatFilterPlugin* ScFilterCreate(); +} + +#endif + +typedef ScFormatFilterPlugin * (*FilterFn)(); +ScFormatFilterPlugin &ScFormatFilter::Get() +{ + static ScFormatFilterPlugin *plugin = []() + { +#ifndef DISABLE_DYNLOADING + OUString sFilterLib(SVLIBRARY("scfilt")); + static ::osl::Module aModule; + bool bLoaded = aModule.is(); + if (!bLoaded) + bLoaded = aModule.loadRelative(&thisModule, sFilterLib); + if (!bLoaded) + bLoaded = aModule.load(sFilterLib); + if (bLoaded) + { + oslGenericFunction fn = aModule.getFunctionSymbol( "ScFilterCreate" ); + if (fn != nullptr) + return reinterpret_cast<FilterFn>(fn)(); + } + assert(false); + return static_cast<ScFormatFilterPlugin*>(nullptr); +#else + return ScFilterCreate(); +#endif + }(); + + return *plugin; +} + +// Precondition: pStr is guaranteed to be non-NULL and points to a 0-terminated +// array. +static const sal_Unicode* lcl_UnicodeStrChr( const sal_Unicode* pStr, + sal_Unicode c ) +{ + while (*pStr) + { + if (*pStr == c) + return pStr; + ++pStr; + } + return nullptr; +} + +ScImportStringStream::ScImportStringStream( const OUString& rStr ) + : SvMemoryStream( const_cast<sal_Unicode *>(rStr.getStr()), + rStr.getLength() * sizeof(sal_Unicode), StreamMode::READ) +{ + SetStreamCharSet( RTL_TEXTENCODING_UNICODE ); +#ifdef OSL_BIGENDIAN + SetEndian(SvStreamEndian::BIG); +#else + SetEndian(SvStreamEndian::LITTLE); +#endif +} + +OUString ReadCsvLine( SvStream &rStream, bool bEmbeddedLineBreak, + OUString& rFieldSeparators, sal_Unicode cFieldQuote, sal_Unicode& rcDetectSep, sal_uInt32 nMaxSourceLines ) +{ + enum RetryState + { + FORBID, + ALLOW, + RETRY, + RETRIED + } eRetryState = (bEmbeddedLineBreak && rcDetectSep == 0 ? RetryState::ALLOW : RetryState::FORBID); + + sal_uInt64 nStreamPos = (eRetryState == RetryState::ALLOW ? rStream.Tell() : 0); + +Label_RetryWithNewSep: + + if (eRetryState == RetryState::RETRY) + { + eRetryState = RetryState::RETRIED; + rStream.Seek( nStreamPos); + } + + OUString aStr; + rStream.ReadUniOrByteStringLine(aStr, rStream.GetStreamCharSet(), nArbitraryLineLengthLimit); + + if (bEmbeddedLineBreak) + { + sal_Int32 nFirstLineLength = aStr.getLength(); + sal_uInt64 nFirstLineStreamPos = rStream.Tell(); + sal_uInt32 nLine = 0; + + const sal_Unicode* pSeps = rFieldSeparators.getStr(); + + QuoteType eQuoteState = FIELDEND_QUOTE; + bool bFieldStart = true; + + sal_Int32 nLastOffset = 0; + sal_Int32 nQuotes = 0; + while (!rStream.eof() && aStr.getLength() < nArbitraryLineLengthLimit) + { + const sal_Unicode * p = aStr.getStr() + nLastOffset; + const sal_Unicode * const pStop = aStr.getStr() + aStr.getLength(); + while (p < pStop) + { + if (!*p) + { + // Skip embedded null-characters. They don't change + // anything and are handled at a higher level. + ++p; + continue; + } + + if (nQuotes) + { + if (*p == cFieldQuote) + { + if (bFieldStart) + { + ++nQuotes; + bFieldStart = false; + eQuoteState = FIELDSTART_QUOTE; + nFirstLineLength = aStr.getLength(); + nFirstLineStreamPos = rStream.Tell(); + } + // Do not detect a FIELDSTART_QUOTE if not in + // bFieldStart mode, in which case for unquoted content + // we are in FIELDEND_QUOTE state. + else if (eQuoteState != FIELDEND_QUOTE) + { + eQuoteState = lcl_isEscapedOrFieldEndQuote( nQuotes, p, pSeps, cFieldQuote, rcDetectSep); + + if (eRetryState == RetryState::ALLOW && rcDetectSep) + { + eRetryState = RetryState::RETRY; + rFieldSeparators += OUStringChar(rcDetectSep); + pSeps = rFieldSeparators.getStr(); + goto Label_RetryWithNewSep; + } + + // DONTKNOW_QUOTE is an embedded unescaped quote we + // don't count for pairing. + if (eQuoteState != DONTKNOW_QUOTE) + ++nQuotes; + } + } + else if (eQuoteState == FIELDEND_QUOTE) + { + if (bFieldStart) + // If blank is a separator it starts a field, if it + // is not and thus maybe leading before quote we + // are still at start of field regarding quotes. + bFieldStart = (*p == ' ' || lcl_UnicodeStrChr( pSeps, *p) != nullptr); + else + bFieldStart = (lcl_UnicodeStrChr( pSeps, *p) != nullptr); + } + } + else + { + if (*p == cFieldQuote && bFieldStart) + { + nQuotes = 1; + eQuoteState = FIELDSTART_QUOTE; + bFieldStart = false; + nFirstLineLength = aStr.getLength(); + nFirstLineStreamPos = rStream.Tell(); + } + else if (eQuoteState == FIELDEND_QUOTE) + { + // This also skips leading blanks at beginning of line + // if followed by a quote. It's debatable whether we + // actually want that or not, but congruent with what + // ScanNextFieldFromString() does. + if (bFieldStart) + bFieldStart = (*p == ' ' || lcl_UnicodeStrChr( pSeps, *p) != nullptr); + else + bFieldStart = (lcl_UnicodeStrChr( pSeps, *p) != nullptr); + } + } + // A quote character inside a field content does not start + // a quote. + ++p; + } + + if ((nQuotes & 1) == 0) + // We still have a (theoretical?) problem here if due to + // nArbitraryLineLengthLimit (or nMaxSourceLines below) we + // split a string right between a doubled quote pair. + break; + else if (eQuoteState == DONTKNOW_QUOTE) + // A single unescaped quote somewhere in a quote started + // field, most likely that was not meant to have embedded + // linefeeds either. + break; + else if (++nLine >= nMaxSourceLines && nMaxSourceLines > 0) + // Unconditionally increment nLine even if nMaxSourceLines==0 + // so it can be observed in debugger. + break; + else + { + nLastOffset = aStr.getLength(); + OUString aNext; + rStream.ReadUniOrByteStringLine(aNext, rStream.GetStreamCharSet(), nArbitraryLineLengthLimit); + if (!rStream.eof()) + aStr += "\n" + aNext; + } + } + if (nQuotes & 1) + { + // No closing quote at all. A single quote at field start => no + // embedded linefeeds for that field, take only first logical line. + aStr = aStr.copy( 0, nFirstLineLength); + rStream.Seek( nFirstLineStreamPos); + } + } + return aStr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/macromgr.cxx b/sc/source/ui/docshell/macromgr.cxx new file mode 100644 index 000000000..39080c678 --- /dev/null +++ b/sc/source/ui/docshell/macromgr.cxx @@ -0,0 +1,201 @@ +/* -*- 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 <macromgr.hxx> +#include <document.hxx> + +#include <basic/basmgr.hxx> +#include <cppuhelper/implbase.hxx> +#include <sfx2/objsh.hxx> +#include <formulacell.hxx> +#include <config_features.h> +#include <vector> +#include <com/sun/star/container/XContainer.hpp> +#include <com/sun/star/script/XLibraryContainer.hpp> + +using namespace ::com::sun::star; +using ::com::sun::star::uno::Reference; +using ::std::vector; +using ::std::pair; + +/** + * A simple container to keep track of cells that depend on basic modules + * changes. We don't check for duplicates at insertion time; instead, we + * remove duplicates at query time. + */ +class ScUserMacroDepTracker +{ +public: + void addCell(const OUString& rModuleName, ScFormulaCell* pCell) + { + ModuleCellMap::iterator itr = maCells.find(rModuleName); + if (itr == maCells.end()) + { + pair<ModuleCellMap::iterator, bool> r = maCells.emplace( + rModuleName, vector<ScFormulaCell*>()); + + if (!r.second) + // insertion failed. + return; + + itr = r.first; + } + itr->second.push_back(pCell); + } + + void removeCell(const ScFormulaCell* pCell) + { + for (auto& rEntry : maCells) + { + rEntry.second.erase(std::remove(rEntry.second.begin(), rEntry.second.end(), pCell), rEntry.second.end() ); + } + } + + void getCellsByModule(const OUString& rModuleName, vector<ScFormulaCell*>& rCells) + { + ModuleCellMap::iterator itr = maCells.find(rModuleName); + if (itr == maCells.end()) + return; + + vector<ScFormulaCell*>& rCellList = itr->second; + + // Remove duplicates. + std::sort(rCellList.begin(), rCellList.end()); + auto last = std::unique(rCellList.begin(), rCellList.end()); + rCellList.erase(last, rCellList.end()); + + // exception safe copy + vector<ScFormulaCell*> temp(rCellList); + rCells.swap(temp); + } + +private: + typedef std::unordered_map<OUString, vector<ScFormulaCell*>> ModuleCellMap; + ModuleCellMap maCells; +}; + +ScMacroManager::ScMacroManager(ScDocument& rDoc) : + mpDepTracker(new ScUserMacroDepTracker), + mrDoc(rDoc) +{ +} + +ScMacroManager::~ScMacroManager() +{ +} + +typedef ::cppu::WeakImplHelper< css::container::XContainerListener > ContainerListenerHelper; + +namespace { + +class VBAProjectListener : public ContainerListenerHelper +{ + ScMacroManager* mpMacroMgr; +public: + explicit VBAProjectListener( ScMacroManager* pMacroMgr ) : mpMacroMgr( pMacroMgr ) {} + // XEventListener + virtual void SAL_CALL disposing( const lang::EventObject& /*Source*/ ) override {} + + // XContainerListener + virtual void SAL_CALL elementInserted( const container::ContainerEvent& /*Event*/ ) override {} + virtual void SAL_CALL elementReplaced( const container::ContainerEvent& Event ) override + { + OUString sModuleName; + Event.Accessor >>= sModuleName; + mpMacroMgr->InitUserFuncData(); + mpMacroMgr->BroadcastModuleUpdate(sModuleName); + } + virtual void SAL_CALL elementRemoved( const container::ContainerEvent& /*Event*/ ) override {} + +}; + +} + +void ScMacroManager::InitUserFuncData() +{ + // Clear unordered_map + mhFuncToVolatile.clear(); + OUString sProjectName("Standard"); + + Reference< container::XContainer > xModuleContainer; + SfxObjectShell* pShell = mrDoc.GetDocumentShell(); + if (!pShell) + return; +#if HAVE_FEATURE_SCRIPTING + const BasicManager *pBasicManager = pShell->GetBasicManager(); + if (!pBasicManager->GetName().isEmpty()) + { + sProjectName = pBasicManager->GetName(); + } +#endif + try + { + Reference< script::XLibraryContainer > xLibraries( pShell->GetBasicContainer(), uno::UNO_SET_THROW ); + xModuleContainer.set( xLibraries->getByName( sProjectName ), uno::UNO_QUERY_THROW ); + + // remove old listener ( if there was one ) + if ( mxContainerListener.is() ) + xModuleContainer->removeContainerListener( mxContainerListener ); + // Create listener + mxContainerListener = new VBAProjectListener( this ); + xModuleContainer->addContainerListener( mxContainerListener ); + } + catch (const uno::Exception&) + { + } +} + +void ScMacroManager::SetUserFuncVolatile( const OUString& sName, bool isVolatile ) +{ + mhFuncToVolatile[ sName ] = isVolatile; +} + +bool ScMacroManager::GetUserFuncVolatile( const OUString& sName ) +{ + NameBoolMap::iterator it = mhFuncToVolatile.find( sName ); + if ( it == mhFuncToVolatile.end() ) + return false; + return it->second; +} + +void ScMacroManager::AddDependentCell(const OUString& aModuleName, ScFormulaCell* pCell) +{ + mpDepTracker->addCell(aModuleName, pCell); +} + +void ScMacroManager::RemoveDependentCell(const ScFormulaCell* pCell) +{ + mpDepTracker->removeCell(pCell); +} + +void ScMacroManager::BroadcastModuleUpdate(const OUString& aModuleName) +{ + vector<ScFormulaCell*> aCells; + mpDepTracker->getCellsByModule(aModuleName, aCells); + for (ScFormulaCell* pCell : aCells) + { + mrDoc.PutInFormulaTree(pCell); // for F9 recalc + + // for recalc on cell value change. If the cell is not volatile, the + // cell stops listening right away after it gets re-interpreted. + mrDoc.StartListeningArea(BCA_LISTEN_ALWAYS, false, pCell); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/olinefun.cxx b/sc/source/ui/docshell/olinefun.cxx new file mode 100644 index 000000000..76ea01eae --- /dev/null +++ b/sc/source/ui/docshell/olinefun.cxx @@ -0,0 +1,792 @@ +/* -*- 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 <sfx2/bindings.hxx> + +#include <olinefun.hxx> + +#include <docsh.hxx> +#include <olinetab.hxx> +#include <tabvwsh.hxx> +#include <undodat.hxx> +#include <globstr.hrc> +#include <sc.hrc> + +#include <comphelper/lok.hxx> + + +static void lcl_InvalidateOutliner( SfxBindings* pBindings ) +{ + if ( pBindings ) + { + pBindings->Invalidate( SID_OUTLINE_SHOW ); + pBindings->Invalidate( SID_OUTLINE_HIDE ); + pBindings->Invalidate( SID_OUTLINE_REMOVE ); + + pBindings->Invalidate( SID_STATUS_SUM ); // because of enabling/disabling + pBindings->Invalidate( SID_ATTR_SIZE ); + } +} + +//! Move PaintWidthHeight to DocShell ? + +static void lcl_PaintWidthHeight( ScDocShell& rDocShell, SCTAB nTab, + bool bColumns, SCCOLROW nStart, SCCOLROW nEnd ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + + PaintPartFlags nParts = PaintPartFlags::Grid; + SCCOL nStartCol = 0; + SCROW nStartRow = 0; + SCCOL nEndCol = rDoc.MaxCol(); // for testing if merged + SCROW nEndRow = rDoc.MaxRow(); + if ( bColumns ) + { + nParts |= PaintPartFlags::Top; + nStartCol = static_cast<SCCOL>(nStart); + nEndCol = static_cast<SCCOL>(nEnd); + } + else + { + nParts |= PaintPartFlags::Left; + nStartRow = nStart; + nEndRow = nEnd; + } + if (rDoc.HasAttrib( nStartCol,nStartRow,nTab, nEndCol,nEndRow,nTab, + HasAttrFlags::Merged | HasAttrFlags::Overlapped )) + { + nStartCol = 0; + nStartRow = 0; + } + rDocShell.PostPaint( nStartCol,nStartRow,nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab, nParts ); +} + +void ScOutlineDocFunc::MakeOutline( const ScRange& rRange, bool bColumns, bool bRecord, bool bApi ) +{ + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + SCCOL nEndCol = rRange.aEnd.Col(); + SCROW nEndRow = rRange.aEnd.Row(); + SCTAB nTab = rRange.aStart.Tab(); + + ScDocument& rDoc = rDocShell.GetDocument(); + ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab, true ); + std::unique_ptr<ScOutlineTable> pUndoTab; + + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + if (bRecord) + pUndoTab.reset(new ScOutlineTable( *pTable )); + + ScOutlineArray& rArray = bColumns ? pTable->GetColArray() : pTable->GetRowArray(); + + bool bRes; + bool bSize = false; + if ( bColumns ) + bRes = rArray.Insert( nStartCol, nEndCol, bSize ); + else + bRes = rArray.Insert( nStartRow, nEndRow, bSize ); + + if ( bRes ) + { + if (bRecord) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoMakeOutline>( &rDocShell, + nStartCol,nStartRow,nTab,nEndCol,nEndRow,nTab, + std::move(pUndoTab), bColumns, true ) ); + } + + rDoc.SetStreamValid(nTab, false); + + PaintPartFlags nParts = PaintPartFlags::NONE; // Data range hasn't been changed + if ( bColumns ) + nParts |= PaintPartFlags::Top; + else + nParts |= PaintPartFlags::Left; + if ( bSize ) + nParts |= PaintPartFlags::Size; + + rDocShell.PostPaint( 0,0,nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab, nParts ); + rDocShell.SetDocumentModified(); + lcl_InvalidateOutliner( rDocShell.GetViewBindings() ); + } + else + { + if (!bApi) + rDocShell.ErrorMessage(STR_MSSG_MAKEOUTLINE_0); // "Grouping not possible" + } +} + +void ScOutlineDocFunc::RemoveOutline( const ScRange& rRange, bool bColumns, bool bRecord, bool bApi ) +{ + bool bDone = false; + + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + SCCOL nEndCol = rRange.aEnd.Col(); + SCROW nEndRow = rRange.aEnd.Row(); + SCTAB nTab = rRange.aStart.Tab(); + + ScDocument& rDoc = rDocShell.GetDocument(); + + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab ); + if (pTable) + { + std::unique_ptr<ScOutlineTable> pUndoTab; + if (bRecord) + pUndoTab.reset(new ScOutlineTable( *pTable )); + + ScOutlineArray& rArray = bColumns ? pTable->GetColArray() : pTable->GetRowArray(); + + bool bRes; + bool bSize = false; + if ( bColumns ) + bRes = rArray.Remove( nStartCol, nEndCol, bSize ); + else + bRes = rArray.Remove( nStartRow, nEndRow, bSize ); + + if ( bRes ) + { + if (bRecord) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoMakeOutline>( &rDocShell, + nStartCol,nStartRow,nTab, nEndCol,nEndRow,nTab, + std::move(pUndoTab), bColumns, false ) ); + } + + rDoc.SetStreamValid(nTab, false); + + PaintPartFlags nParts = PaintPartFlags::NONE; // Data range hasn't been changed + if ( bColumns ) + nParts |= PaintPartFlags::Top; + else + nParts |= PaintPartFlags::Left; + if ( bSize ) + nParts |= PaintPartFlags::Size; + + rDocShell.PostPaint( 0,0,nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab, nParts ); + rDocShell.SetDocumentModified(); + bDone = true; + lcl_InvalidateOutliner( rDocShell.GetViewBindings() ); + + // we are not enabling again -> no UpdatePageBreaks + } + } + + if (!bDone && !bApi) + rDocShell.ErrorMessage(STR_MSSG_REMOVEOUTLINE_0); // "Ungrouping not possible" +} + +bool ScOutlineDocFunc::RemoveAllOutlines( SCTAB nTab, bool bRecord ) +{ + bool bSuccess = false; + ScDocument& rDoc = rDocShell.GetDocument(); + + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab ); + if (pTable) + { + if (bRecord) + { + SCCOLROW nCol1, nCol2, nRow1, nRow2; + pTable->GetColArray().GetRange( nCol1, nCol2 ); + pTable->GetRowArray().GetRange( nRow1, nRow2 ); + SCCOL nStartCol = static_cast<SCCOL>(nCol1); + SCROW nStartRow = nRow1; + SCCOL nEndCol = static_cast<SCCOL>(nCol2); + SCROW nEndRow = nRow2; + + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nTab, nTab, true, true ); + rDoc.CopyToDocument(nStartCol, 0, nTab, nEndCol, rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, false, *pUndoDoc); + rDoc.CopyToDocument(0, nStartRow, nTab, rDoc.MaxCol(), nEndRow, nTab, InsertDeleteFlags::NONE, false, *pUndoDoc); + + std::unique_ptr<ScOutlineTable> pUndoTab(new ScOutlineTable( *pTable )); + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoRemoveAllOutlines>( &rDocShell, + nStartCol, nStartRow, nTab, + nEndCol, nEndRow, nTab, + std::move(pUndoDoc), std::move(pUndoTab) ) ); + } + + SelectLevel( nTab, true, pTable->GetColArray().GetDepth(), false, false ); + SelectLevel( nTab, false, pTable->GetRowArray().GetDepth(), false, false ); + rDoc.SetOutlineTable( nTab, nullptr ); + + rDoc.UpdatePageBreaks( nTab ); + + rDoc.SetStreamValid(nTab, false); + + rDocShell.PostPaint( 0,0,nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab, + PaintPartFlags::Grid | PaintPartFlags::Left | PaintPartFlags::Top | PaintPartFlags::Size ); + rDocShell.SetDocumentModified(); + lcl_InvalidateOutliner( rDocShell.GetViewBindings() ); + bSuccess = true; + } + + return bSuccess; +} + +void ScOutlineDocFunc::AutoOutline( const ScRange& rRange, bool bRecord ) +{ + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + SCCOL nEndCol = rRange.aEnd.Col(); + SCROW nEndRow = rRange.aEnd.Row(); + SCTAB nTab = rRange.aStart.Tab(); + + ScDocument& rDoc = rDocShell.GetDocument(); + + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab ); + + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr<ScOutlineTable> pUndoTab; + + if ( pTable ) + { + if ( bRecord ) + { + pUndoTab.reset(new ScOutlineTable( *pTable )); + + SCCOLROW nCol1, nCol2, nRow1, nRow2; + pTable->GetColArray().GetRange( nCol1, nCol2 ); + pTable->GetRowArray().GetRange( nRow1, nRow2 ); + SCCOL nOutStartCol = static_cast<SCCOL>(nCol1); + SCROW nOutStartRow = nRow1; + SCCOL nOutEndCol = static_cast<SCCOL>(nCol2); + SCROW nOutEndRow = nRow2; + + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nTab, nTab, true, true ); + rDoc.CopyToDocument(nOutStartCol, 0, nTab, nOutEndCol, rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, false, *pUndoDoc); + rDoc.CopyToDocument(0, nOutStartRow, nTab, rDoc.MaxCol(), nOutEndRow, nTab, InsertDeleteFlags::NONE, false, *pUndoDoc); + } + + // enable + SelectLevel( nTab, true, pTable->GetColArray().GetDepth(), false, false ); + SelectLevel( nTab, false, pTable->GetRowArray().GetDepth(), false, false ); + rDoc.SetOutlineTable( nTab, nullptr ); + } + + rDoc.DoAutoOutline( nStartCol,nStartRow, nEndCol,nEndRow, nTab ); + + if (bRecord) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoAutoOutline>( &rDocShell, + nStartCol, nStartRow, nTab, + nEndCol, nEndRow, nTab, + std::move(pUndoDoc), std::move(pUndoTab) ) ); + } + + rDoc.SetStreamValid(nTab, false); + + rDocShell.PostPaint( 0,0,nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab, PaintPartFlags::Left | PaintPartFlags::Top | PaintPartFlags::Size ); + rDocShell.SetDocumentModified(); + lcl_InvalidateOutliner( rDocShell.GetViewBindings() ); +} + +bool ScOutlineDocFunc::SelectLevel( SCTAB nTab, bool bColumns, sal_uInt16 nLevel, + bool bRecord, bool bPaint ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + ScTabViewShell* pViewSh = rDocShell.GetBestViewShell(); + + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab ); // already there + if (!pTable) + return false; + ScOutlineArray& rArray = bColumns ? pTable->GetColArray() : pTable->GetRowArray(); + + SCCOLROW nStart, nEnd; + rArray.GetRange( nStart, nEnd ); + + // TODO undo can mess things up when another view is editing a cell in the range of group entry + // this is a temporarily workaround + if (!comphelper::LibreOfficeKit::isActive() && bRecord ) + { + std::unique_ptr<ScOutlineTable> pUndoTab(new ScOutlineTable( *pTable )); + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + if (bColumns) + { + pUndoDoc->InitUndo( rDoc, nTab, nTab, true ); + rDoc.CopyToDocument(static_cast<SCCOL>(nStart), 0, nTab, + static_cast<SCCOL>(nEnd), rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, false, + *pUndoDoc); + } + else + { + pUndoDoc->InitUndo( rDoc, nTab, nTab, false, true ); + rDoc.CopyToDocument(0, nStart, nTab, rDoc.MaxCol(), nEnd, nTab, InsertDeleteFlags::NONE, false, *pUndoDoc); + } + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoOutlineLevel>( &rDocShell, + nStart, nEnd, nTab, //! calculate start and end + std::move(pUndoDoc), std::move(pUndoTab), + bColumns, nLevel ) ); + } + + ScSubOutlineIterator aIter( &rArray ); // all entries + ScOutlineEntry* pEntry; + while ((pEntry=aIter.GetNext()) != nullptr) + { + SCCOLROW nThisStart = pEntry->GetStart(); + SCCOLROW nThisEnd = pEntry->GetEnd(); + + sal_uInt16 nThisLevel = aIter.LastLevel(); + bool bShow = (nThisLevel < nLevel); + + if (!bShow && pViewSh && ScTabViewShell::isAnyEditViewInRange(pViewSh, bColumns, nThisStart, nThisEnd)) + continue; + + if (bShow) // enable + { + pEntry->SetHidden( false ); + pEntry->SetVisible( true ); + } + else if ( nThisLevel == nLevel ) // disable + { + pEntry->SetHidden( true ); + pEntry->SetVisible( true ); + } + else // hidden below + { + if (comphelper::LibreOfficeKit::isActive() && nThisLevel > 0) + { + pEntry->SetHidden( true ); + const ScOutlineEntry* pParentEntry = rArray.GetEntryByPos(nThisLevel - 1, nThisStart); + if (pParentEntry && pParentEntry->IsHidden()) + pEntry->SetVisible( false ); + } + else + { + pEntry->SetVisible( false ); + } + } + + for (SCCOLROW i=nThisStart; i<=nThisEnd; i++) + { + if ( bColumns ) + rDoc.ShowCol( static_cast<SCCOL>(i), nTab, bShow ); + else + { + // show several rows together, don't show filtered rows + SCROW nFilterEnd = i; + bool bFiltered = rDoc.RowFiltered( i, nTab, nullptr, &nFilterEnd ); + nFilterEnd = std::min( nThisEnd, nFilterEnd ); + if ( !bShow || !bFiltered ) + rDoc.ShowRows( i, nFilterEnd, nTab, bShow ); + i = nFilterEnd; + } + } + } + + rDoc.SetDrawPageSize(nTab); + rDoc.UpdatePageBreaks( nTab ); + + if ( pViewSh ) + pViewSh->OnLOKShowHideColRow(bColumns, nStart - 1); + + if (bPaint) + lcl_PaintWidthHeight( rDocShell, nTab, bColumns, nStart, nEnd ); + + rDocShell.SetDocumentModified(); + lcl_InvalidateOutliner( rDocShell.GetViewBindings() ); + + return true; +} + +bool ScOutlineDocFunc::ShowMarkedOutlines( const ScRange& rRange, bool bRecord ) +{ + bool bDone = false; + + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + SCCOL nEndCol = rRange.aEnd.Col(); + SCROW nEndRow = rRange.aEnd.Row(); + SCTAB nTab = rRange.aStart.Tab(); + + ScDocument& rDoc = rDocShell.GetDocument(); + + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab ); + + if (pTable) + { + ScOutlineEntry* pEntry; + SCCOLROW nStart; + SCCOLROW nEnd; + SCCOLROW nMin; + SCCOLROW nMax; + SCCOLROW i; + + // TODO undo can mess things up when another view is editing a cell in the range of group entry + // this is a temporarily workaround + if ( !comphelper::LibreOfficeKit::isActive() && bRecord ) + { + std::unique_ptr<ScOutlineTable> pUndoTab(new ScOutlineTable( *pTable )); + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nTab, nTab, true, true ); + rDoc.CopyToDocument(nStartCol, 0, nTab, nEndCol, rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, false, *pUndoDoc); + rDoc.CopyToDocument(0, nStartRow, nTab, rDoc.MaxCol(), nEndRow, nTab, InsertDeleteFlags::NONE, false, *pUndoDoc); + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoOutlineBlock>( &rDocShell, + nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab, + std::move(pUndoDoc), std::move(pUndoTab), true ) ); + } + + // Columns + + nMin=rDoc.MaxCol(); + nMax=0; + ScOutlineArray& rColArray = pTable->GetColArray(); + ScSubOutlineIterator aColIter( &rColArray ); + while ((pEntry=aColIter.GetNext()) != nullptr) + { + nStart = pEntry->GetStart(); + nEnd = pEntry->GetEnd(); + if ( nStart>=nStartCol && nEnd<=nEndCol ) + { + pEntry->SetHidden( false ); + pEntry->SetVisible( true ); + if (nStart<nMin) nMin=nStart; + if (nEnd>nMax) nMax=nEnd; + } + } + const SCCOLROW nMinStartCol = nMin; + for ( i=nMin; i<=nMax; i++ ) + rDoc.ShowCol( static_cast<SCCOL>(i), nTab, true ); + + // Rows + + nMin=rDoc.MaxRow(); + nMax=0; + ScOutlineArray& rRowArray = pTable->GetRowArray(); + ScSubOutlineIterator aRowIter( &rRowArray ); + while ((pEntry=aRowIter.GetNext()) != nullptr) + { + nStart = pEntry->GetStart(); + nEnd = pEntry->GetEnd(); + if ( nStart>=nStartRow && nEnd<=nEndRow ) + { + pEntry->SetHidden( false ); + pEntry->SetVisible( true ); + if (nStart<nMin) nMin=nStart; + if (nEnd>nMax) nMax=nEnd; + } + } + const SCCOLROW nMinStartRow = nMin; + for ( i=nMin; i<=nMax; i++ ) + { + // show several rows together, don't show filtered rows + SCROW nFilterEnd = i; + bool bFiltered = rDoc.RowFiltered( i, nTab, nullptr, &nFilterEnd ); + nFilterEnd = std::min( nMax, nFilterEnd ); + if ( !bFiltered ) + rDoc.ShowRows( i, nFilterEnd, nTab, true ); + i = nFilterEnd; + } + + + rDoc.SetDrawPageSize(nTab); + rDoc.UpdatePageBreaks( nTab ); + + ScTabViewShell* pViewSh = rDocShell.GetBestViewShell(); + if ( pViewSh ) + { + pViewSh->OnLOKShowHideColRow(/*columns: */ true, nMinStartCol - 1); + pViewSh->OnLOKShowHideColRow(/*columns: */ false, nMinStartRow - 1); + } + + rDocShell.PostPaint( 0,0,nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab, PaintPartFlags::Grid | PaintPartFlags::Left | PaintPartFlags::Top ); + rDocShell.SetDocumentModified(); + bDone = true; + + lcl_InvalidateOutliner( rDocShell.GetViewBindings() ); + } + + return bDone; +} + +bool ScOutlineDocFunc::HideMarkedOutlines( const ScRange& rRange, bool bRecord ) +{ + bool bDone = false; + + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + SCCOL nEndCol = rRange.aEnd.Col(); + SCROW nEndRow = rRange.aEnd.Row(); + SCTAB nTab = rRange.aStart.Tab(); + + ScDocument& rDoc = rDocShell.GetDocument(); + + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab ); + + if (pTable) + { + const ScOutlineEntry* pEntry; + size_t nColLevel; + size_t nRowLevel; + sal_uInt16 nCount; + SCCOLROW nStart; + SCCOLROW nEnd; + sal_uInt16 i; + + SCCOLROW nEffStartCol = nStartCol; + SCCOLROW nEffEndCol = nEndCol; + ScOutlineArray& rColArray = pTable->GetColArray(); + rColArray.FindTouchedLevel( nStartCol, nEndCol, nColLevel ); + rColArray.ExtendBlock( nColLevel, nEffStartCol, nEffEndCol ); + SCCOLROW nEffStartRow = nStartRow; + SCCOLROW nEffEndRow = nEndRow; + ScOutlineArray& rRowArray = pTable->GetRowArray(); + rRowArray.FindTouchedLevel( nStartRow, nEndRow, nRowLevel ); + rRowArray.ExtendBlock( nRowLevel, nEffStartRow, nEffEndRow ); + + // TODO undo can mess things up when another view is editing a cell in the range of group entry + // this is a temporarily workaround + if ( !comphelper::LibreOfficeKit::isActive() && bRecord ) + { + std::unique_ptr<ScOutlineTable> pUndoTab(new ScOutlineTable( *pTable )); + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nTab, nTab, true, true ); + rDoc.CopyToDocument(static_cast<SCCOL>(nEffStartCol), 0, nTab, + static_cast<SCCOL>(nEffEndCol), rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, + false, *pUndoDoc); + rDoc.CopyToDocument(0, nEffStartRow, nTab, rDoc.MaxCol(), nEffEndRow, nTab, InsertDeleteFlags::NONE, false, *pUndoDoc); + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoOutlineBlock>( &rDocShell, + nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab, + std::move(pUndoDoc), std::move(pUndoTab), false ) ); + } + + // Columns + + nCount = rColArray.GetCount(nColLevel); + for ( i=0; i<nCount; i++ ) + { + pEntry = rColArray.GetEntry(nColLevel,i); + nStart = pEntry->GetStart(); + nEnd = pEntry->GetEnd(); + + if ( static_cast<SCCOLROW>(nStartCol)<=nEnd && static_cast<SCCOLROW>(nEndCol)>=nStart ) + HideOutline( nTab, true, nColLevel, i, false, false ); + } + + // Rows + + nCount = rRowArray.GetCount(nRowLevel); + for ( i=0; i<nCount; i++ ) + { + pEntry = rRowArray.GetEntry(nRowLevel,i); + nStart = pEntry->GetStart(); + nEnd = pEntry->GetEnd(); + + if ( nStartRow<=nEnd && nEndRow>=nStart ) + HideOutline( nTab, false, nRowLevel, i, false, false ); + } + + rDoc.SetDrawPageSize(nTab); + rDoc.UpdatePageBreaks( nTab ); + + rDocShell.PostPaint( 0,0,nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab, PaintPartFlags::Grid | PaintPartFlags::Left | PaintPartFlags::Top ); + + rDocShell.SetDocumentModified(); + bDone = true; + + lcl_InvalidateOutliner( rDocShell.GetViewBindings() ); + } + + return bDone; +} + +void ScOutlineDocFunc::ShowOutline( SCTAB nTab, bool bColumns, sal_uInt16 nLevel, sal_uInt16 nEntry, + bool bRecord, bool bPaint ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab ); + ScOutlineArray& rArray = bColumns ? pTable->GetColArray() : pTable->GetRowArray(); + ScOutlineEntry* pEntry = rArray.GetEntry( nLevel, nEntry ); + SCCOLROW nStart = pEntry->GetStart(); + SCCOLROW nEnd = pEntry->GetEnd(); + + // TODO undo can mess things up when another view is editing a cell in the range of group entry + // this is a temporarily workaround + if ( !comphelper::LibreOfficeKit::isActive() && bRecord ) + { + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + if (bColumns) + { + pUndoDoc->InitUndo( rDoc, nTab, nTab, true ); + rDoc.CopyToDocument(static_cast<SCCOL>(nStart), 0, nTab, + static_cast<SCCOL>(nEnd), rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, false, + *pUndoDoc); + } + else + { + pUndoDoc->InitUndo( rDoc, nTab, nTab, false, true ); + rDoc.CopyToDocument(0, nStart, nTab, rDoc.MaxCol(), nEnd, nTab, InsertDeleteFlags::NONE, false, *pUndoDoc); + } + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDoOutline>( &rDocShell, + nStart, nEnd, nTab, std::move(pUndoDoc), //! calc start and end + bColumns, nLevel, nEntry, true ) ); + } + + pEntry->SetHidden(false); + SCCOLROW i; + for ( i = nStart; i <= nEnd; i++ ) + { + if ( bColumns ) + rDoc.ShowCol( static_cast<SCCOL>(i), nTab, true ); + else + { + // show several rows together, don't show filtered rows + SCROW nFilterEnd = i; + bool bFiltered = rDoc.RowFiltered( i, nTab, nullptr, &nFilterEnd ); + nFilterEnd = std::min( nEnd, nFilterEnd ); + if ( !bFiltered ) + rDoc.ShowRows( i, nFilterEnd, nTab, true ); + i = nFilterEnd; + } + } + + ScSubOutlineIterator aIter( &rArray, nLevel, nEntry ); + while ((pEntry=aIter.GetNext()) != nullptr) + { + if ( pEntry->IsHidden() ) + { + SCCOLROW nSubStart = pEntry->GetStart(); + SCCOLROW nSubEnd = pEntry->GetEnd(); + if ( bColumns ) + for ( i = nSubStart; i <= nSubEnd; i++ ) + rDoc.ShowCol( static_cast<SCCOL>(i), nTab, false ); + else + rDoc.ShowRows( nSubStart, nSubEnd, nTab, false ); + } + } + + rArray.SetVisibleBelow( nLevel, nEntry, true, true ); + + rDoc.SetDrawPageSize(nTab); + rDoc.InvalidatePageBreaks(nTab); + rDoc.UpdatePageBreaks( nTab ); + + ScTabViewShell* pViewSh = rDocShell.GetBestViewShell(); + if ( pViewSh ) + pViewSh->OnLOKShowHideColRow(bColumns, nStart - 1); + + if (bPaint) + lcl_PaintWidthHeight( rDocShell, nTab, bColumns, nStart, nEnd ); + + rDocShell.SetDocumentModified(); + + lcl_InvalidateOutliner( rDocShell.GetViewBindings() ); +} + +bool ScOutlineDocFunc::HideOutline( SCTAB nTab, bool bColumns, sal_uInt16 nLevel, sal_uInt16 nEntry, + bool bRecord, bool bPaint ) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab ); + ScOutlineArray& rArray = bColumns ? pTable->GetColArray() : pTable->GetRowArray(); + ScOutlineEntry* pEntry = rArray.GetEntry( nLevel, nEntry ); + SCCOLROW nStart = pEntry->GetStart(); + SCCOLROW nEnd = pEntry->GetEnd(); + + ScTabViewShell* pViewSh = rDocShell.GetBestViewShell(); + if (pViewSh && ScTabViewShell::isAnyEditViewInRange(pViewSh, bColumns, nStart, nEnd)) + return false; + + // TODO undo can mess things up when another view is editing a cell in the range of group entry + // this is a temporarily workaround + if ( !comphelper::LibreOfficeKit::isActive() && bRecord ) + { + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + if (bColumns) + { + pUndoDoc->InitUndo( rDoc, nTab, nTab, true ); + rDoc.CopyToDocument(static_cast<SCCOL>(nStart), 0, nTab, + static_cast<SCCOL>(nEnd), rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, false, + *pUndoDoc); + } + else + { + pUndoDoc->InitUndo( rDoc, nTab, nTab, false, true ); + rDoc.CopyToDocument(0, nStart, nTab, rDoc.MaxCol(), nEnd, nTab, InsertDeleteFlags::NONE, false, *pUndoDoc); + } + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDoOutline>( &rDocShell, + nStart, nEnd, nTab, std::move(pUndoDoc), + bColumns, nLevel, nEntry, false ) ); + } + + pEntry->SetHidden(true); + SCCOLROW i; + if ( bColumns ) + for ( i = nStart; i <= nEnd; i++ ) + rDoc.ShowCol( static_cast<SCCOL>(i), nTab, false ); + else + rDoc.ShowRows( nStart, nEnd, nTab, false ); + + rArray.SetVisibleBelow( nLevel, nEntry, false ); + + rDoc.SetDrawPageSize(nTab); + rDoc.InvalidatePageBreaks(nTab); + rDoc.UpdatePageBreaks( nTab ); + + if ( pViewSh ) + pViewSh->OnLOKShowHideColRow(bColumns, nStart - 1); + + if (bPaint) + lcl_PaintWidthHeight( rDocShell, nTab, bColumns, nStart, nEnd ); + + rDocShell.SetDocumentModified(); + + lcl_InvalidateOutliner( rDocShell.GetViewBindings() ); + + + return true; //! always ??? +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/pagedata.cxx b/sc/source/ui/docshell/pagedata.cxx new file mode 100644 index 000000000..4ab70ed9f --- /dev/null +++ b/sc/source/ui/docshell/pagedata.cxx @@ -0,0 +1,100 @@ +/* -*- 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 <string.h> + +#include <pagedata.hxx> + +#include <osl/diagnose.h> + +ScPrintRangeData::ScPrintRangeData() +{ + bTopDown = bAutomatic = true; + nFirstPage = 1; +} + +ScPrintRangeData::~ScPrintRangeData() +{ +} + +void ScPrintRangeData::SetPagesX( size_t nCount, const SCCOL* pData ) +{ + mvPageEndX.resize( nCount ); + memcpy( mvPageEndX.data(), pData, nCount * sizeof(SCCOL) ); +} + +void ScPrintRangeData::SetPagesY( size_t nCount, const SCROW* pData ) +{ + mvPageEndY.resize(nCount); + memcpy( mvPageEndY.data(), pData, nCount * sizeof(SCROW) ); +} + +ScPageBreakData::ScPageBreakData(size_t nMax) +{ + nUsed = 0; + if (nMax) + pData.reset( new ScPrintRangeData[nMax] ); + nAlloc = nMax; +} + +ScPageBreakData::~ScPageBreakData() +{ +} + +ScPrintRangeData& ScPageBreakData::GetData(size_t nPos) +{ + OSL_ENSURE(nPos < nAlloc, "ScPageBreakData::GetData bumm"); + + if ( nPos >= nUsed ) + { + OSL_ENSURE(nPos == nUsed, "ScPageBreakData::GetData wrong order"); + nUsed = nPos+1; + } + + return pData[nPos]; +} + +bool ScPageBreakData::operator==( const ScPageBreakData& rOther ) const +{ + if ( nUsed != rOther.nUsed ) + return false; + + for (size_t i=0; i<nUsed; i++) + if ( pData[i].GetPrintRange() != rOther.pData[i].GetPrintRange() ) + return false; + + //! compare ScPrintRangeData completely ?? + + return true; +} + +void ScPageBreakData::AddPages() +{ + if ( nUsed > 1 ) + { + tools::Long nPage = pData[0].GetFirstPage(); + for (size_t i=0; i+1<nUsed; i++) + { + nPage += static_cast<tools::Long>(pData[i].GetPagesX())*pData[i].GetPagesY(); + pData[i+1].SetFirstPage( nPage ); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/pntlock.cxx b/sc/source/ui/docshell/pntlock.cxx new file mode 100644 index 000000000..39713941c --- /dev/null +++ b/sc/source/ui/docshell/pntlock.cxx @@ -0,0 +1,43 @@ +/* -*- 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 <pntlock.hxx> + +ScPaintLockData::ScPaintLockData() : + nLevel( 0 ), + nDocLevel( 0 ), + nParts( PaintPartFlags::NONE ), + bModified( false ) +{ +} + +ScPaintLockData::~ScPaintLockData() +{ +} + +void ScPaintLockData::AddRange( const ScRange& rRange, PaintPartFlags nP ) +{ + if (!xRangeList.is()) + xRangeList = new ScRangeList; + + xRangeList->Join( rRange ); + nParts |= nP; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/servobj.cxx b/sc/source/ui/docshell/servobj.cxx new file mode 100644 index 000000000..4367c7140 --- /dev/null +++ b/sc/source/ui/docshell/servobj.cxx @@ -0,0 +1,258 @@ +/* -*- 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 <osl/thread.h> +#include <osl/diagnose.h> +#include <sot/exchange.hxx> +#include <sot/formats.hxx> +#include <sfx2/app.hxx> +#include <sfx2/linkmgr.hxx> +#include <servobj.hxx> +#include <docsh.hxx> +#include <impex.hxx> +#include <brdcst.hxx> +#include <rangenam.hxx> +#include <unotools/charclass.hxx> + +using namespace formula; + +static bool lcl_FillRangeFromName( ScRange& rRange, ScDocShell* pDocSh, const OUString& rName ) +{ + if (pDocSh) + { + ScDocument& rDoc = pDocSh->GetDocument(); + ScRangeName* pNames = rDoc.GetRangeName(); + if (pNames) + { + const ScRangeData* pData = pNames->findByUpperName(ScGlobal::getCharClass().uppercase(rName)); + if (pData) + { + if ( pData->IsValidReference( rRange ) ) + return true; + } + } + } + return false; +} + +ScServerObjectSvtListenerForwarder::ScServerObjectSvtListenerForwarder( + ScServerObject* pObjP) + : pObj(pObjP) +{ +} + +ScServerObjectSvtListenerForwarder::~ScServerObjectSvtListenerForwarder() +{ + //! do NOT access pObj +} + +void ScServerObjectSvtListenerForwarder::Notify( const SfxHint& rHint ) +{ + pObj->Notify( aBroadcaster, rHint); +} + +ScServerObject::ScServerObject( ScDocShell* pShell, const OUString& rItem ) : + aForwarder( this ), + pDocSh( pShell ), + bRefreshListener( false ) +{ + // parse item string + + if ( lcl_FillRangeFromName( aRange, pDocSh, rItem ) ) + { + aItemStr = rItem; // must be parsed again on ref update + } + else + { + // parse ref + ScDocument& rDoc = pDocSh->GetDocument(); + SCTAB nTab = ScDocShell::GetCurTab(); + aRange.aStart.SetTab( nTab ); + + // For DDE link, we always must parse references using OOO A1 convention. + + if ( aRange.Parse( rItem, rDoc, FormulaGrammar::CONV_OOO ) & ScRefFlags::VALID ) + { + // area reference + } + else if ( aRange.aStart.Parse( rItem, rDoc, FormulaGrammar::CONV_OOO ) & ScRefFlags::VALID ) + { + // cell reference + aRange.aEnd = aRange.aStart; + } + else + { + OSL_FAIL("ScServerObject: invalid item"); + } + } + + pDocSh->GetDocument().GetLinkManager()->InsertServer( this ); + pDocSh->GetDocument().StartListeningArea( aRange, false, &aForwarder ); + + StartListening(*pDocSh); // to notice if DocShell gets deleted + StartListening(*SfxGetpApp()); // for SfxHintId::ScAreasChanged +} + +ScServerObject::~ScServerObject() +{ + Clear(); +} + +void ScServerObject::Clear() +{ + if (pDocSh) + { + ScDocShell* pTemp = pDocSh; + pDocSh = nullptr; + + pTemp->GetDocument().EndListeningArea(aRange, false, &aForwarder); + pTemp->GetDocument().GetLinkManager()->RemoveServer( this ); + EndListening(*pTemp); + EndListening(*SfxGetpApp()); + } +} + +void ScServerObject::EndListeningAll() +{ + aForwarder.EndListeningAll(); + SfxListener::EndListeningAll(); +} + +bool ScServerObject::GetData( + css::uno::Any & rData /*out param*/, + const OUString & rMimeType, bool /* bSynchron */ ) +{ + if (!pDocSh) + return false; + + // named ranges may have changed -> update aRange + if ( !aItemStr.isEmpty() ) + { + ScRange aNew; + if ( lcl_FillRangeFromName( aNew, pDocSh, aItemStr ) && aNew != aRange ) + { + aRange = aNew; + bRefreshListener = true; + } + } + + if ( bRefreshListener ) + { + // refresh the listeners now (this is called from a timer) + + EndListeningAll(); + pDocSh->GetDocument().StartListeningArea( aRange, false, &aForwarder ); + StartListening(*pDocSh); + StartListening(*SfxGetpApp()); + bRefreshListener = false; + } + + OUString aDdeTextFmt = pDocSh->GetDdeTextFmt(); + ScDocument& rDoc = pDocSh->GetDocument(); + + SotClipboardFormatId eFormatId = SotExchange::GetFormatIdFromMimeType( rMimeType ); + if (SotClipboardFormatId::STRING == eFormatId || SotClipboardFormatId::STRING_TSVC == eFormatId) + { + ScImportExport aObj( rDoc, aRange ); + if( aDdeTextFmt[0] == 'F' ) + aObj.SetFormulas( true ); + if( aDdeTextFmt == "SYLK" || aDdeTextFmt == "FSYLK" ) + { + OString aByteData; + if( aObj.ExportByteString( aByteData, osl_getThreadTextEncoding(), SotClipboardFormatId::SYLK ) ) + { + rData <<= css::uno::Sequence< sal_Int8 >( + reinterpret_cast<const sal_Int8*>(aByteData.getStr()), + aByteData.getLength() + 1 ); + return true; + } + return false; + } + if( aDdeTextFmt == "CSV" || aDdeTextFmt == "FCSV" ) + aObj.SetSeparator( ',' ); + /* TODO: STRING_TSVC could preserve line breaks with added quotes. */ + aObj.SetExportTextOptions( ScExportTextOptions( ScExportTextOptions::ToSpace, ' ', false ) ); + return aObj.ExportData( rMimeType, rData ); + } + + ScImportExport aObj( rDoc, aRange ); + aObj.SetExportTextOptions( ScExportTextOptions( ScExportTextOptions::ToSpace, ' ', false ) ); + if( aObj.IsRef() ) + return aObj.ExportData( rMimeType, rData ); + return false; +} + +void ScServerObject::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + bool bDataChanged = false; + + // DocShell can't be tested via type info, because SfxHintId::Dying comes from the dtor + if ( &rBC == pDocSh ) + { + // from DocShell, only SfxHintId::Dying is interesting + if ( rHint.GetId() == SfxHintId::Dying ) + { + pDocSh = nullptr; + EndListening(*SfxGetpApp()); + // don't access DocShell anymore for EndListening etc. + } + } + else if (dynamic_cast<const SfxApplication*>( &rBC) != nullptr) + { + if ( !aItemStr.isEmpty() && rHint.GetId() == SfxHintId::ScAreasChanged ) + { + // check if named range was modified + ScRange aNew; + if ( lcl_FillRangeFromName( aNew, pDocSh, aItemStr ) && aNew != aRange ) + bDataChanged = true; + } + } + else + { + // must be from Area broadcasters + + const ScHint* pScHint = dynamic_cast<const ScHint*>( &rHint ); + if (pScHint && (pScHint->GetId() == SfxHintId::ScDataChanged)) + bDataChanged = true; + else if (const ScAreaChangedHint *pChgHint = dynamic_cast<const ScAreaChangedHint*>(&rHint)) // position of broadcaster changed + { + const ScRange& aNewRange = pChgHint->GetRange(); + if ( aRange != aNewRange ) + { + bRefreshListener = true; + bDataChanged = true; + } + } + else + { + if (rHint.GetId() == SfxHintId::Dying) + { + // If the range is being deleted, listening must be restarted + // after the deletion is complete (done in GetData) + bRefreshListener = true; + bDataChanged = true; + } + } + } + + if ( bDataChanged && HasDataLinks() ) + SvLinkSource::NotifyDataChanged(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/sizedev.cxx b/sc/source/ui/docshell/sizedev.cxx new file mode 100644 index 000000000..c5b7e416c --- /dev/null +++ b/sc/source/ui/docshell/sizedev.cxx @@ -0,0 +1,63 @@ +/* -*- 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 <sfx2/printer.hxx> +#include <vcl/virdev.hxx> + +#include <sizedev.hxx> +#include <docsh.hxx> +#include <scmod.hxx> +#include <inputopt.hxx> + +ScSizeDeviceProvider::ScSizeDeviceProvider( ScDocShell* pDocSh ) +{ + bool bTextWysiwyg = SC_MOD()->GetInputOptions().GetTextWysiwyg(); + if ( bTextWysiwyg ) + { + pDevice = pDocSh->GetPrinter(); + bOwner = false; + + aOldMapMode = pDevice->GetMapMode(); + pDevice->SetMapMode(MapMode(MapUnit::MapPixel)); // GetNeededSize needs pixel MapMode + // printer has right DigitLanguage already + } + else + { + pDevice = VclPtr<VirtualDevice>::Create(); + pDevice->SetDigitLanguage( SC_MOD()->GetOptDigitLanguage() ); + bOwner = true; + } + + Point aLogic = pDevice->LogicToPixel(Point(1000,1000), MapMode(MapUnit::MapTwip)); + nPPTX = aLogic.X() / 1000.0; + nPPTY = aLogic.Y() / 1000.0; + + if ( !bTextWysiwyg ) + nPPTX /= pDocSh->GetOutputFactor(); +} + +ScSizeDeviceProvider::~ScSizeDeviceProvider() +{ + if (bOwner) + pDevice.disposeAndClear(); + else + pDevice->SetMapMode( aOldMapMode ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/tablink.cxx b/sc/source/ui/docshell/tablink.cxx new file mode 100644 index 000000000..21fe08519 --- /dev/null +++ b/sc/source/ui/docshell/tablink.cxx @@ -0,0 +1,594 @@ +/* -*- 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 <sal/config.h> + +#include <com/sun/star/task/InteractionHandler.hpp> + +#include <sfx2/sfxsids.hrc> +#include <sfx2/app.hxx> +#include <svl/itemset.hxx> +#include <svl/stritem.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/fcontnr.hxx> +#include <sfx2/frame.hxx> +#include <sfx2/linkmgr.hxx> +#include <vcl/weld.hxx> +#include <tools/urlobj.hxx> +#include <unotools/transliterationwrapper.hxx> +#include <unotools/configmgr.hxx> +#include <comphelper/processfactory.hxx> + +#include <tablink.hxx> + +#include <scextopt.hxx> +#include <document.hxx> +#include <docsh.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <undoblk.hxx> +#include <undotab.hxx> +#include <global.hxx> +#include <hints.hxx> +#include <dociter.hxx> +#include <formula/opcode.hxx> +#include <formulaiter.hxx> +#include <tokenarray.hxx> + +struct TableLink_Impl +{ + ScDocShell* m_pDocSh; + Link<sfx2::SvBaseLink&,void> m_aEndEditLink; + + TableLink_Impl() : m_pDocSh( nullptr ) {} +}; + + +ScTableLink::ScTableLink(ScDocShell* pDocSh, const OUString& rFile, + const OUString& rFilter, const OUString& rOpt, + sal_Int32 nRefreshDelaySeconds ): + ::sfx2::SvBaseLink(SfxLinkUpdateMode::ONCALL,SotClipboardFormatId::SIMPLE_FILE), + ScRefreshTimer( nRefreshDelaySeconds ), + pImpl( new TableLink_Impl ), + aFileName(rFile), + aFilterName(rFilter), + aOptions(rOpt), + bInCreate( false ), + bInEdit( false ), + bAddUndo( true ) +{ + pImpl->m_pDocSh = pDocSh; +} + +ScTableLink::ScTableLink(SfxObjectShell* pShell, const OUString& rFile, + const OUString& rFilter, const OUString& rOpt, + sal_Int32 nRefreshDelaySeconds ): + ::sfx2::SvBaseLink(SfxLinkUpdateMode::ONCALL,SotClipboardFormatId::SIMPLE_FILE), + ScRefreshTimer( nRefreshDelaySeconds ), + pImpl( new TableLink_Impl ), + aFileName(rFile), + aFilterName(rFilter), + aOptions(rOpt), + bInCreate( false ), + bInEdit( false ), + bAddUndo( true ) +{ + pImpl->m_pDocSh = static_cast< ScDocShell* >( pShell ); + SetRefreshHandler( LINK( this, ScTableLink, RefreshHdl ) ); + SetRefreshControl( &pImpl->m_pDocSh->GetDocument().GetRefreshTimerControlAddress() ); +} + +ScTableLink::~ScTableLink() +{ + // cancel connection + + StopRefreshTimer(); + ScDocument& rDoc = pImpl->m_pDocSh->GetDocument(); + SCTAB nCount = rDoc.GetTableCount(); + for (SCTAB nTab=0; nTab<nCount; nTab++) + if (rDoc.IsLinked(nTab) && aFileName == rDoc.GetLinkDoc(nTab)) + rDoc.SetLink( nTab, ScLinkMode::NONE, "", "", "", "", 0 ); +} + +void ScTableLink::Edit(weld::Window* pParent, const Link<SvBaseLink&,void>& rEndEditHdl) +{ + pImpl->m_aEndEditLink = rEndEditHdl; + + bInEdit = true; + SvBaseLink::Edit( pParent, LINK( this, ScTableLink, TableEndEditHdl ) ); +} + +::sfx2::SvBaseLink::UpdateResult ScTableLink::DataChanged( + const OUString&, const css::uno::Any& ) +{ + sfx2::LinkManager* pLinkManager=pImpl->m_pDocSh->GetDocument().GetLinkManager(); + if (pLinkManager!=nullptr) + { + OUString aFile, aFilter; + sfx2::LinkManager::GetDisplayNames(this, nullptr, &aFile, nullptr, &aFilter); + + // the file dialog returns the filter name with the application prefix + // -> remove prefix + ScDocumentLoader::RemoveAppPrefix( aFilter ); + + if (!bInCreate) + Refresh( aFile, aFilter, nullptr, GetRefreshDelaySeconds() ); // don't load twice + } + return SUCCESS; +} + +void ScTableLink::Closed() +{ + // delete link: Undo + ScDocument& rDoc = pImpl->m_pDocSh->GetDocument(); + bool bUndo (rDoc.IsUndoEnabled()); + + if (bAddUndo && bUndo) + { + pImpl->m_pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoRemoveLink>( pImpl->m_pDocSh, aFileName ) ); + + bAddUndo = false; // only once + } + + // connection gets cancelled in the dtor + + SvBaseLink::Closed(); +} + +bool ScTableLink::IsUsed() const +{ + return pImpl->m_pDocSh->GetDocument().HasLink( aFileName, aFilterName, aOptions ); +} + +bool ScTableLink::Refresh(const OUString& rNewFile, const OUString& rNewFilter, + const OUString* pNewOptions, sal_Int32 nNewRefreshDelaySeconds ) +{ + // load document + + if (rNewFile.isEmpty() || rNewFilter.isEmpty()) + return false; + + OUString aNewUrl = ScGlobal::GetAbsDocName(rNewFile, pImpl->m_pDocSh); + bool bNewUrlName = aFileName != aNewUrl; + + std::shared_ptr<const SfxFilter> pFilter = pImpl->m_pDocSh->GetFactory().GetFilterContainer()->GetFilter4FilterName(rNewFilter); + if (!pFilter) + return false; + + ScDocument& rDoc = pImpl->m_pDocSh->GetDocument(); + rDoc.SetInLinkUpdate( true ); + + bool bUndo(rDoc.IsUndoEnabled()); + + // if new filter has been selected, forget options + if (aFilterName != rNewFilter) + aOptions.clear(); + if ( pNewOptions ) // options hard-specified? + aOptions = *pNewOptions; + + // always create ItemSet, so that DocShell can set the options + auto pSet = std::make_shared<SfxAllItemSet>( SfxGetpApp()->GetPool() ); + if (!aOptions.isEmpty()) + pSet->Put( SfxStringItem( SID_FILE_FILTEROPTIONS, aOptions ) ); + + SfxMedium* pMed = new SfxMedium(aNewUrl, StreamMode::STD_READ, pFilter, std::move(pSet)); + + if ( bInEdit ) // only if using the edit dialog, + pMed->UseInteractionHandler(true); // enable the filter options dialog + + // aRef->DoClose() will be called explicitly, but it is still more safe to use SfxObjectShellLock here + ScDocShell* pSrcShell = new ScDocShell(SfxModelFlags::EMBEDDED_OBJECT | SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS); + SfxObjectShellLock aRef = pSrcShell; + pSrcShell->DoLoad(pMed); + + // options might have been set + OUString aNewOpt = ScDocumentLoader::GetOptions(*pMed); + if (aNewOpt.isEmpty()) + aNewOpt = aOptions; + + // Undo... + + ScDocumentUniquePtr pUndoDoc; + bool bFirst = true; + if (bAddUndo && bUndo) + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + + // copy tables + + ScDocShellModificator aModificator( *pImpl->m_pDocSh ); + + bool bNotFound = false; + ScDocument& rSrcDoc = pSrcShell->GetDocument(); + + // from text filters that don't set the table name, + // use the one table regardless of link table name + bool bAutoTab = (rSrcDoc.GetTableCount() == 1) && + ScDocShell::HasAutomaticTableName( rNewFilter ); + + SCTAB nCount = rDoc.GetTableCount(); + for (SCTAB nTab=0; nTab<nCount; nTab++) + { + ScLinkMode nMode = rDoc.GetLinkMode(nTab); + if (nMode != ScLinkMode::NONE && aFileName == rDoc.GetLinkDoc(nTab)) + { + OUString aTabName = rDoc.GetLinkTab(nTab); + + // Undo + + if (bAddUndo && bUndo) + { + if (bFirst) + pUndoDoc->InitUndo( rDoc, nTab, nTab, true, true ); + else + pUndoDoc->AddUndoTab( nTab, nTab, true, true ); + bFirst = false; + ScRange aRange(0,0,nTab,rDoc.MaxCol(),rDoc.MaxRow(),nTab); + rDoc.CopyToDocument(aRange, InsertDeleteFlags::ALL, false, *pUndoDoc); + pUndoDoc->TransferDrawPage( rDoc, nTab, nTab ); + pUndoDoc->SetLink( nTab, nMode, aFileName, aFilterName, + aOptions, aTabName, GetRefreshDelaySeconds() ); + pUndoDoc->SetTabBgColor( nTab, rDoc.GetTabBgColor(nTab) ); + } + + // adjust table name of an ExtDocRef + + if ( bNewUrlName && nMode == ScLinkMode::VALUE ) + { + OUString aName; + rDoc.GetName( nTab, aName ); + if ( ScGlobal::GetTransliteration().isEqual( + ScGlobal::GetDocTabName( aFileName, aTabName ), aName ) ) + { + rDoc.RenameTab( nTab, + ScGlobal::GetDocTabName( aNewUrl, aTabName ), + true/*bExternalDocument*/ ); + } + } + + // copy + + SCTAB nSrcTab = 0; + bool bFound = false; + /* #i71497# check if external document is loaded successfully, + otherwise we may find the empty default sheet "Sheet1" in + rSrcDoc, even if the document does not exist. */ + if( pMed->GetError() == ERRCODE_NONE ) + { + // no sheet name -> use first sheet + if ( !aTabName.isEmpty() && !bAutoTab ) + bFound = rSrcDoc.GetTable( aTabName, nSrcTab ); + else + bFound = true; + } + + if (bFound) + rDoc.TransferTab( rSrcDoc, nSrcTab, nTab, false, // don't insert anew + (nMode == ScLinkMode::VALUE) ); // only values? + else + { + rDoc.DeleteAreaTab( 0,0,rDoc.MaxCol(),rDoc.MaxRow(), nTab, InsertDeleteFlags::ALL ); + + bool bShowError = true; + if ( nMode == ScLinkMode::VALUE ) + { + // Value link (used with external references in formulas): + // Look for formulas that reference the sheet, and put errors in the referenced cells. + + ScRangeList aErrorCells; // cells on the linked sheets that need error values + + ScCellIterator aIter(rDoc, ScRange(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB)); // all sheets + for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) + { + if (aIter.getType() != CELLTYPE_FORMULA) + continue; + + ScFormulaCell* pCell = aIter.getFormulaCell(); + ScDetectiveRefIter aRefIter(rDoc, pCell); + ScRange aRefRange; + while ( aRefIter.GetNextRef( aRefRange ) ) + { + if ( aRefRange.aStart.Tab() <= nTab && aRefRange.aEnd.Tab() >= nTab ) + { + // use first cell of range references (don't fill potentially large ranges) + + aErrorCells.Join( ScRange( aRefRange.aStart ) ); + } + } + } + + size_t nRanges = aErrorCells.size(); + if ( nRanges ) // found any? + { + ScTokenArray aTokenArr(rDoc); + aTokenArr.AddOpCode( ocNotAvail ); + aTokenArr.AddOpCode( ocOpen ); + aTokenArr.AddOpCode( ocClose ); + aTokenArr.AddOpCode( ocStop ); + + for (size_t nPos=0; nPos < nRanges; nPos++) + { + const ScRange & rRange = aErrorCells[ nPos ]; + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + SCCOL nEndCol = rRange.aEnd.Col(); + SCROW nEndRow = rRange.aEnd.Row(); + for (SCROW nRow=nStartRow; nRow<=nEndRow; nRow++) + for (SCCOL nCol=nStartCol; nCol<=nEndCol; nCol++) + { + ScAddress aDestPos( nCol, nRow, nTab ); + rDoc.SetFormula(aDestPos, aTokenArr); + } + } + + bShowError = false; + } + // if no references were found, insert error message (don't leave the sheet empty) + } + + if ( bShowError ) + { + // Normal link or no references: put error message on sheet. + + rDoc.SetString( 0,0,nTab, ScResId(STR_LINKERROR) ); + rDoc.SetString( 0,1,nTab, ScResId(STR_LINKERRORFILE) ); + rDoc.SetString( 1,1,nTab, aNewUrl ); + rDoc.SetString( 0,2,nTab, ScResId(STR_LINKERRORTAB) ); + rDoc.SetString( 1,2,nTab, aTabName ); + } + + bNotFound = true; + } + + if ( bNewUrlName || aFilterName != rNewFilter || + aOptions != aNewOpt || pNewOptions || + nNewRefreshDelaySeconds != GetRefreshDelaySeconds() ) + rDoc.SetLink( nTab, nMode, aNewUrl, rNewFilter, aNewOpt, + aTabName, nNewRefreshDelaySeconds ); + } + } + + // memorize new settings + + if ( bNewUrlName ) + aFileName = aNewUrl; + if (aFilterName != rNewFilter) + aFilterName = rNewFilter; + if (aOptions != aNewOpt) + aOptions = aNewOpt; + + // clean up + + aRef->DoClose(); + + // Undo + + if (bAddUndo && bUndo) + pImpl->m_pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoRefreshLink>( pImpl->m_pDocSh, std::move(pUndoDoc) ) ); + + // Paint (may be several tables) + + pImpl->m_pDocSh->PostPaint( ScRange(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB), + PaintPartFlags::Grid | PaintPartFlags::Top | PaintPartFlags::Left | PaintPartFlags::Extras ); + aModificator.SetDocumentModified(); + + if (bNotFound) + { + //! output error ? + } + + rDoc.SetInLinkUpdate( false ); + + // notify Uno objects (for XRefreshListener) + //! also notify Uno objects if file name was changed! + ScLinkRefreshedHint aHint; + aHint.SetSheetLink( aFileName ); + rDoc.BroadcastUno( aHint ); + + return true; +} + +IMPL_LINK_NOARG(ScTableLink, RefreshHdl, Timer *, void) +{ + Refresh( aFileName, aFilterName, nullptr, GetRefreshDelaySeconds() ); +} + +IMPL_LINK( ScTableLink, TableEndEditHdl, ::sfx2::SvBaseLink&, rLink, void ) +{ + pImpl->m_aEndEditLink.Call( rLink ); + bInEdit = false; +} + +// === ScDocumentLoader ================================================== + +OUString ScDocumentLoader::GetOptions( const SfxMedium& rMedium ) +{ + SfxItemSet* pSet = rMedium.GetItemSet(); + const SfxStringItem* pItem; + if ( pSet && (pItem = pSet->GetItemIfSet( SID_FILE_FILTEROPTIONS )) ) + return pItem->GetValue(); + + return OUString(); +} + +bool ScDocumentLoader::GetFilterName( const OUString& rFileName, + OUString& rFilter, OUString& rOptions, + bool bWithContent, bool bWithInteraction ) +{ + SfxObjectShell* pDocSh = SfxObjectShell::GetFirst( checkSfxObjectShell<ScDocShell> ); + while ( pDocSh ) + { + if ( pDocSh->HasName() ) + { + SfxMedium* pMed = pDocSh->GetMedium(); + if ( pMed->GetName() == rFileName ) + { + rFilter = pMed->GetFilter()->GetFilterName(); + rOptions = GetOptions(*pMed); + return true; + } + } + pDocSh = SfxObjectShell::GetNext( *pDocSh, checkSfxObjectShell<ScDocShell> ); + } + + INetURLObject aUrl( rFileName ); + INetProtocol eProt = aUrl.GetProtocol(); + if ( eProt == INetProtocol::NotValid ) // invalid URL? + return false; // abort without creating a medium + + // Filter detection + + std::shared_ptr<const SfxFilter> pSfxFilter; + auto pMedium = std::make_unique<SfxMedium>( rFileName, StreamMode::STD_READ ); + if (pMedium->GetError() == ERRCODE_NONE && !utl::ConfigManager::IsFuzzing()) + { + if ( bWithInteraction ) + pMedium->UseInteractionHandler(true); // #i73992# no longer called from GuessFilter + + SfxFilterMatcher aMatcher("scalc"); + if( bWithContent ) + aMatcher.GuessFilter( *pMedium, pSfxFilter ); + else + aMatcher.GuessFilterIgnoringContent( *pMedium, pSfxFilter ); + } + + bool bOK = false; + if ( pMedium->GetError() == ERRCODE_NONE ) + { + if ( pSfxFilter ) + rFilter = pSfxFilter->GetFilterName(); + else + rFilter = ScDocShell::GetOwnFilterName(); // otherwise Calc file + bOK = !rFilter.isEmpty(); + } + + return bOK; +} + +void ScDocumentLoader::RemoveAppPrefix( OUString& rFilterName ) +{ + OUString aAppPrefix( STRING_SCAPP + ": "); + if (rFilterName.startsWith( aAppPrefix)) + rFilterName = rFilterName.copy( aAppPrefix.getLength()); +} + +SfxMedium* ScDocumentLoader::CreateMedium( const OUString& rFileName, std::shared_ptr<const SfxFilter> const & pFilter, + const OUString& rOptions, weld::Window* pInteractionParent ) +{ + // Always create SfxItemSet so ScDocShell can set options. + auto pSet = std::make_shared<SfxAllItemSet>( SfxGetpApp()->GetPool() ); + if ( !rOptions.isEmpty() ) + pSet->Put( SfxStringItem( SID_FILE_FILTEROPTIONS, rOptions ) ); + + if (pInteractionParent) + { + css::uno::Reference<css::uno::XComponentContext> xContext = comphelper::getProcessComponentContext(); + css::uno::Reference<css::task::XInteractionHandler> xIHdl(css::task::InteractionHandler::createWithParent(xContext, + pInteractionParent->GetXWindow()), css::uno::UNO_QUERY_THROW); + pSet->Put(SfxUnoAnyItem(SID_INTERACTIONHANDLER, css::uno::Any(xIHdl))); + } + + SfxMedium *pRet = new SfxMedium( rFileName, StreamMode::STD_READ, pFilter, std::move(pSet) ); + if (pInteractionParent) + pRet->UseInteractionHandler(true); // to enable the filter options dialog + return pRet; +} + +ScDocumentLoader::ScDocumentLoader(const OUString& rFileName, + OUString& rFilterName, OUString& rOptions, + sal_uInt32 nRekCnt, weld::Window* pInteractionParent, + css::uno::Reference<css::io::XInputStream> xInputStream) + : pDocShell(nullptr) + , pMedium(nullptr) +{ + if ( rFilterName.isEmpty() ) + GetFilterName(rFileName, rFilterName, rOptions, true, pInteractionParent != nullptr); + + std::shared_ptr<const SfxFilter> pFilter = ScDocShell::Factory().GetFilterContainer()->GetFilter4FilterName( rFilterName ); + + pMedium = CreateMedium(rFileName, pFilter, rOptions, pInteractionParent); + if (xInputStream.is()) + pMedium->setStreamToLoadFrom(xInputStream, true); + if ( pMedium->GetError() != ERRCODE_NONE ) + return ; + + pDocShell = new ScDocShell( SfxModelFlags::EMBEDDED_OBJECT | SfxModelFlags::DISABLE_EMBEDDED_SCRIPTS ); + aRef = pDocShell; + + ScDocument& rDoc = pDocShell->GetDocument(); + ScExtDocOptions* pExtDocOpt = rDoc.GetExtDocOptions(); + if( !pExtDocOpt ) + { + rDoc.SetExtDocOptions( std::make_unique<ScExtDocOptions>() ); + pExtDocOpt = rDoc.GetExtDocOptions(); + } + pExtDocOpt->GetDocSettings().mnLinkCnt = nRekCnt; + + pDocShell->DoLoad( pMedium ); + + OUString aNew = GetOptions(*pMedium); // options are set per dialog on load + if (!aNew.isEmpty() && aNew != rOptions) + rOptions = aNew; +} + +ScDocumentLoader::~ScDocumentLoader() +{ + if ( aRef.is() ) + aRef->DoClose(); + else + delete pMedium; +} + +void ScDocumentLoader::ReleaseDocRef() +{ + if ( aRef.is() ) + { + // release reference without calling DoClose - caller must + // have another reference to the doc and call DoClose later + + pDocShell = nullptr; + pMedium = nullptr; + aRef.clear(); + } +} + +ScDocument* ScDocumentLoader::GetDocument() +{ + return pDocShell ? &pDocShell->GetDocument() : nullptr; +} + +bool ScDocumentLoader::IsError() const +{ + if ( pDocShell && pMedium ) + return pMedium->GetError() != ERRCODE_NONE; + else + return true; +} + +OUString ScDocumentLoader::GetTitle() const +{ + if ( pDocShell ) + return pDocShell->GetTitle(); + else + return OUString(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/docshell/tpstat.cxx b/sc/source/ui/docshell/tpstat.cxx new file mode 100644 index 000000000..226c862f3 --- /dev/null +++ b/sc/source/ui/docshell/tpstat.cxx @@ -0,0 +1,70 @@ +/* -*- 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 . + */ + +#undef SC_DLLIMPLEMENTATION + +#include <document.hxx> +#include <docsh.hxx> + +#include <tpstat.hxx> + +// Dokumentinfo-Tabpage: + +std::unique_ptr<SfxTabPage> ScDocStatPage::Create( weld::Container* pPage, weld::DialogController* pController, const SfxItemSet* rSet ) +{ + return std::make_unique<ScDocStatPage>( pPage, pController, *rSet ); +} + +ScDocStatPage::ScDocStatPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet& rSet) + : SfxTabPage(pPage, pController, "modules/scalc/ui/statisticsinfopage.ui", "StatisticsInfoPage", &rSet) + , m_xFtTables(m_xBuilder->weld_label("nosheets")) + , m_xFtCells(m_xBuilder->weld_label("nocells")) + , m_xFtPages(m_xBuilder->weld_label("nopages")) + , m_xFtFormula(m_xBuilder->weld_label("noformula")) + , m_xFrame(m_xBuilder->weld_frame("StatisticsInfoPage")) +{ + ScDocShell* pDocSh = dynamic_cast<ScDocShell*>( SfxObjectShell::Current() ); + ScDocStat aDocStat; + + if ( pDocSh ) + pDocSh->GetDocStat( aDocStat ); + + OUString aInfo = m_xFrame->get_label() + aDocStat.aDocName; + m_xFrame->set_label(aInfo); + m_xFtTables->set_label( OUString::number( aDocStat.nTableCount ) ); + m_xFtCells->set_label( OUString::number( aDocStat.nCellCount ) ); + m_xFtPages->set_label( OUString::number( aDocStat.nPageCount ) ); + m_xFtFormula->set_label( OUString::number( aDocStat.nFormulaCount ) ); + +} + +ScDocStatPage::~ScDocStatPage() +{ +} + +bool ScDocStatPage::FillItemSet( SfxItemSet* /* rSet */ ) +{ + return false; +} + +void ScDocStatPage::Reset( const SfxItemSet* /* rSet */ ) +{ +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |