summaryrefslogtreecommitdiffstats
path: root/sc/source/ui/docshell
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:06:44 +0000
commited5640d8b587fbcfed7dd7967f3de04b37a76f26 (patch)
tree7a5f7c6c9d02226d7471cb3cc8fbbf631b415303 /sc/source/ui/docshell
parentInitial commit. (diff)
downloadlibreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.tar.xz
libreoffice-ed5640d8b587fbcfed7dd7967f3de04b37a76f26.zip
Adding upstream version 4:7.4.7.upstream/4%7.4.7upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sc/source/ui/docshell')
-rw-r--r--sc/source/ui/docshell/arealink.cxx498
-rw-r--r--sc/source/ui/docshell/autostyl.cxx191
-rw-r--r--sc/source/ui/docshell/datastream.cxx549
-rw-r--r--sc/source/ui/docshell/dbdocfun.cxx1790
-rw-r--r--sc/source/ui/docshell/dbdocimp.cxx628
-rw-r--r--sc/source/ui/docshell/docfunc.cxx5922
-rw-r--r--sc/source/ui/docshell/docfuncutil.cxx116
-rw-r--r--sc/source/ui/docshell/docsh.cxx3482
-rw-r--r--sc/source/ui/docshell/docsh2.cxx187
-rw-r--r--sc/source/ui/docshell/docsh3.cxx1331
-rw-r--r--sc/source/ui/docshell/docsh4.cxx2788
-rw-r--r--sc/source/ui/docshell/docsh5.cxx1037
-rw-r--r--sc/source/ui/docshell/docsh6.cxx517
-rw-r--r--sc/source/ui/docshell/docsh8.cxx1082
-rw-r--r--sc/source/ui/docshell/docshimp.hxx38
-rw-r--r--sc/source/ui/docshell/documentlinkmgr.cxx274
-rw-r--r--sc/source/ui/docshell/editable.cxx162
-rw-r--r--sc/source/ui/docshell/externalrefmgr.cxx3319
-rw-r--r--sc/source/ui/docshell/impex.cxx2887
-rw-r--r--sc/source/ui/docshell/macromgr.cxx201
-rw-r--r--sc/source/ui/docshell/olinefun.cxx792
-rw-r--r--sc/source/ui/docshell/pagedata.cxx100
-rw-r--r--sc/source/ui/docshell/pntlock.cxx43
-rw-r--r--sc/source/ui/docshell/servobj.cxx258
-rw-r--r--sc/source/ui/docshell/sizedev.cxx63
-rw-r--r--sc/source/ui/docshell/tablink.cxx594
-rw-r--r--sc/source/ui/docshell/tpstat.cxx70
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: */