From ed5640d8b587fbcfed7dd7967f3de04b37a76f26 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 11:06:44 +0200 Subject: Adding upstream version 4:7.4.7. Signed-off-by: Daniel Baumann --- sc/source/ui/docshell/arealink.cxx | 498 +++ sc/source/ui/docshell/autostyl.cxx | 191 + sc/source/ui/docshell/datastream.cxx | 549 +++ sc/source/ui/docshell/dbdocfun.cxx | 1790 +++++++++ sc/source/ui/docshell/dbdocimp.cxx | 628 +++ sc/source/ui/docshell/docfunc.cxx | 5922 +++++++++++++++++++++++++++++ sc/source/ui/docshell/docfuncutil.cxx | 116 + sc/source/ui/docshell/docsh.cxx | 3482 +++++++++++++++++ sc/source/ui/docshell/docsh2.cxx | 187 + sc/source/ui/docshell/docsh3.cxx | 1331 +++++++ sc/source/ui/docshell/docsh4.cxx | 2788 ++++++++++++++ sc/source/ui/docshell/docsh5.cxx | 1037 +++++ sc/source/ui/docshell/docsh6.cxx | 517 +++ sc/source/ui/docshell/docsh8.cxx | 1082 ++++++ sc/source/ui/docshell/docshimp.hxx | 38 + sc/source/ui/docshell/documentlinkmgr.cxx | 274 ++ sc/source/ui/docshell/editable.cxx | 162 + sc/source/ui/docshell/externalrefmgr.cxx | 3319 ++++++++++++++++ sc/source/ui/docshell/impex.cxx | 2887 ++++++++++++++ sc/source/ui/docshell/macromgr.cxx | 201 + sc/source/ui/docshell/olinefun.cxx | 792 ++++ sc/source/ui/docshell/pagedata.cxx | 100 + sc/source/ui/docshell/pntlock.cxx | 43 + sc/source/ui/docshell/servobj.cxx | 258 ++ sc/source/ui/docshell/sizedev.cxx | 63 + sc/source/ui/docshell/tablink.cxx | 594 +++ sc/source/ui/docshell/tpstat.cxx | 70 + 27 files changed, 28919 insertions(+) create mode 100644 sc/source/ui/docshell/arealink.cxx create mode 100644 sc/source/ui/docshell/autostyl.cxx create mode 100644 sc/source/ui/docshell/datastream.cxx create mode 100644 sc/source/ui/docshell/dbdocfun.cxx create mode 100644 sc/source/ui/docshell/dbdocimp.cxx create mode 100644 sc/source/ui/docshell/docfunc.cxx create mode 100644 sc/source/ui/docshell/docfuncutil.cxx create mode 100644 sc/source/ui/docshell/docsh.cxx create mode 100644 sc/source/ui/docshell/docsh2.cxx create mode 100644 sc/source/ui/docshell/docsh3.cxx create mode 100644 sc/source/ui/docshell/docsh4.cxx create mode 100644 sc/source/ui/docshell/docsh5.cxx create mode 100644 sc/source/ui/docshell/docsh6.cxx create mode 100644 sc/source/ui/docshell/docsh8.cxx create mode 100644 sc/source/ui/docshell/docshimp.hxx create mode 100644 sc/source/ui/docshell/documentlinkmgr.cxx create mode 100644 sc/source/ui/docshell/editable.cxx create mode 100644 sc/source/ui/docshell/externalrefmgr.cxx create mode 100644 sc/source/ui/docshell/impex.cxx create mode 100644 sc/source/ui/docshell/macromgr.cxx create mode 100644 sc/source/ui/docshell/olinefun.cxx create mode 100644 sc/source/ui/docshell/pagedata.cxx create mode 100644 sc/source/ui/docshell/pntlock.cxx create mode 100644 sc/source/ui/docshell/servobj.cxx create mode 100644 sc/source/ui/docshell/sizedev.cxx create mode 100644 sc/source/ui/docshell/tablink.cxx create mode 100644 sc/source/ui/docshell/tpstat.cxx (limited to 'sc/source/ui/docshell') 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 +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + + +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(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& /* rEndEditHdl */ ) +{ + // use own dialog instead of SvBaseLink::Edit... + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr 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 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( 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 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(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( 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 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 +#include + +#include +#include +#include + +static sal_uLong TimeNow() // seconds +{ + return static_cast(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 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::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::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::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::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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include + +namespace com::sun::star::ui { class XUIElement; } + +namespace sc { + +static o3tl::enumarray 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(now.Seconds) + static_cast(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 mpStream; + size_t mnColCount; + bool mbTerminate; + osl::Mutex maMtxTerminate; + + std::queue> maPendingLines; + std::queue> maUsedLines; + osl::Mutex maMtxLines; + + osl::Condition maCondReadStream; + osl::Condition maCondConsume; + + orcus::csv::parser_config maConfig; + +public: + + ReaderThread(std::unique_ptr 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 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 pLines ) + { + maUsedLines.push(std::move(pLines)); + } + + osl::Mutex& getLinesMutex() + { + return maMtxLines; + } + +private: + virtual void execute() override + { + while (!isTerminateRequested()) + { + std::unique_ptr 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 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +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 pUndoColl; + if (bUndo) + pUndoColl.reset( new ScDBCollection( *pDocColl ) ); + + std::unique_ptr 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( &rDocShell, std::move(pUndoColl), + std::make_unique( *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 pUndoColl; + if (bUndo) + pUndoColl.reset( new ScDBCollection( *pDocColl ) ); + + rDoc.PreprocessDBDataUpdate(); + rDBs.erase(iter); + rDoc.CompileHybridFormula(); + + if (bUndo) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique( &rDocShell, std::move(pUndoColl), + std::make_unique( *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 pNewData(new ScDBData(rNew, **iterOld)); + + std::unique_ptr 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( &rDocShell, std::move(pUndoColl), + std::make_unique( *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 pUndoColl; + if (bUndo) + pUndoColl.reset( new ScDBCollection( *pDocColl ) ); + + *pData = rNewData; + if (bAreaChanged) + rDoc.CompileDBFormula(); + + if (bUndo) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique( &rDocShell, std::move(pUndoColl), + std::make_unique( *pDocColl ) ) ); + } + + aModificator.SetDocumentModified(); +} + +void ScDBDocFunc::ModifyAllDBData( const ScDBCollection& rNewColl, const std::vector& rDelAreaList ) +{ + ScDocShellModificator aModificator(rDocShell); + ScDocument& rDoc = rDocShell.GetDocument(); + ScDBCollection* pOldColl = rDoc.GetDBCollection(); + std::unique_ptr 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(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(&rDocShell, std::move(pUndoColl), + std::make_unique(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 pUndoTab; + std::unique_ptr pUndoRange; + std::unique_ptr 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(nOutStartCol), 0, + nTab, static_cast(nOutEndCol), rDoc.MaxRow(), nTab, + InsertDeleteFlags::NONE, false, *pUndoDoc); + rDoc.CopyToDocument(0, static_cast(nOutStartRow), + nTab, rDoc.MaxCol(), static_cast(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( &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(&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(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; iGetArea( 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 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 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( &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 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 pUndoTab; + std::unique_ptr pUndoRange; + std::unique_ptr 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(nOutStartCol), 0, nTab, static_cast(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( &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 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( + &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 aListOfObjects = + sc::tools::getAllPivotChartsConnectedTo(rDPObj.GetName(), &rDocShell); + + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + + if (pModel && !aListOfObjects.empty()) + { + std::unique_ptr 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(*pChartObject)); + pChartObject->getSdrPageFromSdrObject()->RemoveObject(pChartObject->GetOrdNum()); + } + } + } + } + + ScDocumentUniquePtr pOldUndoDoc; + std::unique_ptr 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( + &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 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 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(&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 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( + &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 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 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 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( ( 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 xFrame = pFrame->GetFrame().GetFrameInterface(); + + uno::Reference xBeamerFrame = xFrame->findFrame( + "_beamer", + frame::FrameSearchFlag::CHILDREN); + if (!xBeamerFrame.is()) + return; + + uno::Reference xController = xBeamerFrame->getController(); + uno::Reference 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& 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 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 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 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 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 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 xExecute( xRowSet, uno::UNO_QUERY ); + if ( xExecute.is() ) + { + uno::Reference 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 xMeta; + uno::Reference 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 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 xRow( xRowSet, uno::UNO_QUERY ); + if ( nColCount > 0 && xRow.is() ) + { + nEndCol = static_cast( rParam.nCol1 + nColCount - 1 ); + + uno::Sequence aColTypes( nColCount ); // column types + uno::Sequence aColCurr( nColCount ); // currency flag is not in types + sal_Int32* pTypeArr = aColTypes.getArray(); + sal_Bool* pCurrArr = aColCurr.getArray(); + for (tools::Long i=0; igetColumnType( i+1 ); + pCurrArr[i] = xMeta->isCurrency( i+1 ); + } + + // read column names + nCol = rParam.nCol1; + for (tools::Long i=0; iSetString( 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; iIsKeepFmt(); + 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( 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 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 pRedoDBData(new ScDBData(*pDBData)); + + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique( &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 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace com::sun::star; +using ::std::vector; + +void ScDocFunc::NotifyDrawUndo( std::unique_ptr 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( 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 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(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 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( &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 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( &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 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( &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 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( &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 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( &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 xWaitWin(new weld::WaitObject(ScDocShell::GetActiveDialogParent())); + if (bUndo) + pModel->BeginCalcUndo(false); + bool bOverflow; + bool bDone = ScDetectiveFunc(rDoc, nTab).MarkInvalid( bOverflow ); + std::unique_ptr 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 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 pUndo; + if (bUndo) + pUndo = pModel->GetCalcUndo(); + if (bDone) + { + ScDetOpList* pOldList = rDoc.GetDetOpList(); + std::unique_ptr pUndoList; + if (bUndo && pOldList) + pUndoList.reset(new ScDetOpList(*pOldList)); + + rDoc.ClearDetectiveOperations(); + + if (bUndo) + { + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique( &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; nTabCount(); + 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 pUndo = pModel->GetCalcUndo(); + if (pUndo) + { + pUndo->SetComment( ScResId( STR_UNDO_DETREFRESH ) ); + // associate with the last action + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique( std::move(pUndo), &rDocShell ), + bAutomatic ); + } + } + rDocShell.SetDrawModified(); + bDone = true; + } + return bDone; +} + +static void lcl_collectAllPredOrSuccRanges( + const ScRangeList& rSrcRanges, vector& rRefTokens, ScDocShell& rDocShell, + bool bPred) +{ + ScDocument& rDoc = rDocShell.GetDocument(); + vector 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& rRefTokens) +{ + lcl_collectAllPredOrSuccRanges(rSrcRanges, rRefTokens, rDocShell, true); +} + +void ScDocFunc::DetectiveCollectAllSuccs(const ScRangeList& rSrcRanges, vector& 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 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 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( &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(&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(&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& 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 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(&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(&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 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 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(&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& 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 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> 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(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 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 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( 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 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( 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 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( + &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( 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( + &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) + break; + + for( SCTAB j = rTab+1; j 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 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 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(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(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(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(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 pTabs(new SCTAB[nSelCount]); + std::unique_ptr pScenarios(new SCTAB[nSelCount]); + nUndoPos = 0; + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + SCTAB nCount = 0; + for( SCTAB j=rTab+1; jLeaveListAction(); + } + + rDocShell.GetUndoManager()->AddUndoAction( std::make_unique( + &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(nEndRow-nStartRow+1)); + break; + case INS_CELLSRIGHT: + case INS_INSCOLS_BEFORE: + case INS_INSCOLS_AFTER: + aRange.aEnd.IncCol(static_cast(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; jMarkRange( 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) + break; + + for( SCTAB j = rTab+1; j 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 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 pRefUndoDoc; + std::unique_ptr 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; jInitUndo( 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(nEndRow-nStartRow+1), pRefUndoDoc.get(), nullptr, &aFullMark ); + nPaintEndRow = rDoc.MaxRow(); + break; + case DelCellCmd::Rows: + rDoc.DeleteRow( 0, 0, rDoc.MaxCol(), MAXTAB, nStartRow, static_cast(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(nEndCol-nStartCol+1), pRefUndoDoc.get(), nullptr, &aFullMark ); + nPaintEndCol = rDoc.MaxCol(); + break; + case DelCellCmd::Cols: + rDoc.DeleteCol( 0, 0, rDoc.MaxRow(), MAXTAB, nStartCol, static_cast(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 pTabs( new SCTAB[nSelCount]); + std::unique_ptr pScenarios( new SCTAB[nSelCount]); + SCTAB nUndoPos = 0; + + for (const auto& rTab : aMark) + { + if (rTab >= nTabCount) + break; + + SCTAB nCount = 0; + for( SCTAB j=rTab+1; jLeaveListAction(); + } + + rDocShell.GetUndoManager()->AddUndoAction( std::make_unique( + &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( aRange.aEnd.Row()-nDecreaseRowCount)); + break; + case DelCellCmd::CellsLeft: + case DelCellCmd::Cols: + aRange.aEnd.SetCol(static_cast( 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) + 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; jOnLOKInsertDeleteColumn(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( + &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( &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 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(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 theTabs; + theTabs.push_back(nTab); + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique( &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 undoTabs { nTab }; + rDocShell.GetUndoManager()->AddUndoAction( std::make_unique( &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( &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( &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( &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( &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& 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 pUndoTab; + std::vector 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(nStart), 0, nTab, static_cast(nEnd), rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, false, *pUndoDoc ); + } + else + { + pUndoDoc->InitUndo( rDoc, nTab, nTab, false, true ); + rDoc.CopyToDocument( 0, static_cast(nStart), nTab, rDoc.MaxCol(), static_cast(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(nStartNo); nCol<=static_cast(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(nStartNo), + static_cast(nEndNo), nTab, bShow ); + else + bOutline = bOutline || rDoc.UpdateOutlineRow( + static_cast(nStartNo), + static_cast(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( + &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(rPos.Col()) : + static_cast(rPos.Row()); + if (nPos == 0) + return false; // first column / row + + ScBreakType nBreak = bColumn ? + rDoc.HasColBreak(static_cast(nPos), nTab) : + rDoc.HasRowBreak(static_cast(nPos), nTab); + if (nBreak & ScBreakType::Manual) + return true; + + if (bRecord) + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique( &rDocShell, rPos.Col(), rPos.Row(), nTab, bColumn, true ) ); + + if (bColumn) + rDoc.SetColBreak(static_cast(nPos), nTab, false, true); + else + rDoc.SetRowBreak(static_cast(nPos), nTab, false, true); + + rDoc.InvalidatePageBreaks(nTab); + rDoc.UpdatePageBreaks( nTab ); + + rDoc.SetStreamValid(nTab, false); + + if (bColumn) + { + rDocShell.PostPaint( static_cast(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(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(rPos.Col()) : + static_cast(rPos.Row()); + + ScBreakType nBreak; + if (bColumn) + nBreak = rDoc.HasColBreak(static_cast(nPos), nTab); + else + nBreak = rDoc.HasRowBreak(static_cast(nPos), nTab); + if (!(nBreak & ScBreakType::Manual)) + // There is no manual break. + return false; + + if (bRecord) + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique( &rDocShell, rPos.Col(), rPos.Row(), nTab, bColumn, false ) ); + + if (bColumn) + rDoc.RemoveColBreak(static_cast(nPos), nTab, false, true); + else + rDoc.RemoveRowBreak(static_cast(nPos), nTab, false, true); + + rDoc.UpdatePageBreaks( nTab ); + + rDoc.SetStreamValid(nTab, false); + + if (bColumn) + { + rDocShell.PostPaint( static_cast(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 p; + if (!rProtect.isProtected() && rDoc.IsUndoEnabled()) + { + // In case of unprotecting, use a copy of passed ScTableProtection object for undo + p = std::make_unique(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(*pProtect); + } + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique(&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(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 p; + if (!rProtect.isProtected() && rDoc.IsUndoEnabled()) + { + // In case of unprotecting, use a copy of passed ScTableProtection object for undo + p = std::make_unique(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(*pProtect); + } + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique(&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 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 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 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( &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( &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 aCols(1, sc::ColRowSpan(nStartCol,nEndCol)); + std::vector 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( &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 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( &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( &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( &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( aSourceArea.aEnd.Row() - aSourceArea.aStart.Row() + 1 ) : + static_cast( 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( aSourceArea.aEnd.Row() - nCount ) ); + break; + case FILL_TO_RIGHT: + aSourceArea.aEnd.SetCol( sal::static_int_cast( aSourceArea.aEnd.Col() - nCount ) ); + break; + case FILL_TO_TOP: + aSourceArea.aStart.SetRow( sal::static_int_cast( aSourceArea.aStart.Row() + nCount ) ); + break; + case FILL_TO_LEFT: + aSourceArea.aStart.SetCol( sal::static_int_cast( 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( &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( aSourceArea.aEnd.Row() + nCount ) ); + break; + case FILL_TO_TOP: + if (nCount > sal::static_int_cast( aSourceArea.aStart.Row() )) + { + OSL_FAIL("FillAuto: Row < 0"); + nCount = aSourceArea.aStart.Row(); + } + aDestArea.aStart.SetRow( sal::static_int_cast( aSourceArea.aStart.Row() - nCount ) ); + break; + case FILL_TO_RIGHT: + aDestArea.aEnd.SetCol( sal::static_int_cast( aSourceArea.aEnd.Col() + nCount ) ); + break; + case FILL_TO_LEFT: + if (nCount > sal::static_int_cast( aSourceArea.aStart.Col() )) + { + OSL_FAIL("FillAuto: Col < 0"); + nCount = aSourceArea.aStart.Col(); + } + aDestArea.aStart.SetCol( sal::static_int_cast( 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( &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 pDrawUndo = rDoc.GetDrawLayer() ? rDoc.GetDrawLayer()->GetCalcUndo() : nullptr; + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique(&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( &rDocShell, rOption, ScDocumentUniquePtr(pUndoDoc) ) ); + } + } + aModificator.SetDocumentModified(); + + return true; +} + +void ScDocFunc::ModifyRangeNames( const ScRangeName& rNewRanges, SCTAB nTab ) +{ + SetNewRangeNames( std::unique_ptr(new ScRangeName(rNewRanges)), true, nTab ); +} + +void ScDocFunc::SetNewRangeNames( std::unique_ptr 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 pUndoRanges(new ScRangeName(*pOld)); + std::unique_ptr pRedoRanges(new ScRangeName(*pNewRanges)); + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique( &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>& rRangeMap) +{ + ScDocShellModificator aModificator(rDocShell); + ScDocument& rDoc = rDocShell.GetDocument(); + + if (rDoc.IsUndoEnabled()) + { + std::map aOldRangeMap; + rDoc.GetRangeNameMap(aOldRangeMap); + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique(&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 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(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 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(ppSortArray.get()), nValidCount, sizeof(ScRangeData*), + &ScRangeData_QsortNameCompare ); + OUString aName; + OUStringBuffer aContent; + OUString aFormula; + SCROW nOutRow = nStartRow; + for (j=0; jGetName(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( &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 (nLinkPosGetLinks()[nLinkPos].get(); + ScAreaLink* pLink = dynamic_cast(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(pBase); + rDocShell.GetUndoManager()->AddUndoAction( + std::make_unique( &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( &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 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 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(&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(&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(&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 pSparklineGroup) +{ + std::vector 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(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(rDocShell, rAddress); + // delete sparkline by "redoing" + pUndoDeleteSparkline->Redo(); + rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndoDeleteSparkline)); + + return true; +} + +bool ScDocFunc::DeleteSparklineGroup(std::shared_ptr const& pSparklineGroup, SCTAB nTab) +{ + if (!pSparklineGroup) + return false; + + auto& rDocument = rDocShell.GetDocument(); + + if (!rDocument.HasTable(nTab)) + return false; + + auto pUndo = std::make_unique(rDocShell, pSparklineGroup, nTab); + // delete sparkline group by "redoing" + pUndo->Redo(); + rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndo)); + return true; +} + +bool ScDocFunc::ChangeSparklineGroupAttributes(std::shared_ptr const& pExistingSparklineGroup, + sc::SparklineAttributes const& rNewAttributes) +{ + auto pUndo = std::make_unique(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 const& rpGroup) +{ + auto pUndo = std::make_unique(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(rDocShell, rRange); + // ungroup sparklines by "redoing" + pUndo->Redo(); + rDocShell.GetUndoManager()->AddUndoAction(std::move(pUndo)); + return true; +} + +bool ScDocFunc::ChangeSparkline(std::shared_ptr const& rpSparkline, SCTAB nTab, ScRangeList const& rDataRange) +{ + auto pUndo = std::make_unique(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 +#include +#include +#include +#include +#include + +#include +#include + +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& pSpans, + bool bMulti, bool bDrawUndo ) +{ + std::unique_ptr pUndo( + new ScUndoDeleteContents( + pDocSh, rMark, rRange, std::move(pUndoDoc), bMulti, nFlags, bDrawUndo)); + pUndo->SetDataSpans(pSpans); + + pUndoMgr->AddUndoAction(std::move(pUndo)); +} + +std::shared_ptr DocFuncUtil::getNonEmptyCellSpans( + const ScDocument& rDoc, const ScMarkData& rMark, const ScRange& rRange ) +{ + auto pDataSpans = std::make_shared(); + 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 r = + pDataSpans->insert(std::make_pair(nTab, std::make_unique())); + + 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "docshimp.hxx" +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +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 + +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 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; nDPCreateNewName() ); + } + } + } + } + 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 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()); + } + } + + // 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 xDPS(GetModel(), uno::UNO_QUERY_THROW); + uno::Reference xDocProps = xDPS->getDocumentProperties(); + + ScRecalcOptions nRecalcMode = + static_cast(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 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(&rHint) ) // Template changed + NotifyStyle( *pStyleSheetHint ); + else if ( auto pStlHint = dynamic_cast(&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(&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 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( 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 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(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(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 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 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 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(&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(aTokens[0].toInt32()); + if (aTokens.size() > 1) + rDateConvert = static_cast(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 aColWidthParam; + ScRange aColWidthRange; + // Set optimal row height after import? + bool bSetRowHeights = false; + + vector 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 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(aZoom) / GetOutputFactor(); // Factor is printer display ratio + double nPPTY = ScGlobal::nScreenPPTY * static_cast(aZoom); + ScopedVclPtrInstance< VirtualDevice > pVirtDev; + // all sheets (for Excel import) + SCTAB nTabCount = m_pDocument->GetTableCount(); + for (SCTAB nTab=0; nTabGetCellArea( 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(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 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 +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 +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( + 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( + 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( + 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() ); + 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 & 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 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( 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 ScDocShell::CreateDocFunc() +{ + return std::make_unique( *this ); +} + +ScDocShell::ScDocShell( const SfxModelFlags i_nSfxCreationFlags, const std::shared_ptr& pDoc ) : + SfxObjectShell( i_nSfxCreationFlags ), + m_pDocument ( pDoc ? pDoc : std::make_shared( 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.nPageCount + + static_cast(ScPrintFunc( this, pPrinter, i ).GetTotalPages()) ); +} + +std::shared_ptr ScDocShell::CreateDocumentInfoDialog(weld::Window* pParent, const SfxItemSet &rSet) +{ + std::shared_ptr xDlg = std::make_shared(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 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& xScAccel, const vector& 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 xContext = ::comphelper::getProcessComponentContext(); + if (!xContext.is()) + return; + + Reference xModuleCfgSupplier( + theModuleUIConfigurationManagerSupplier::get(xContext) ); + + // Grab the Calc configuration. + Reference xConfigMgr = + xModuleCfgSupplier->getUIConfigurationManager( + "com.sun.star.sheet.SpreadsheetDocument"); + + if (!xConfigMgr.is()) + return; + + // shortcut manager + Reference xScAccel = xConfigMgr->getShortCutManager(); + + if (!xScAccel.is()) + return; + + vector 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 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +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 aLocales = aAsian.GetStartEndCharLocales(); + if (aLocales.hasElements()) + { + std::shared_ptr 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 +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "docshimp.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +// 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 (nCol2MaxCol()) ++nCol2; + if (nRow1>0) --nRow1; + if (nRow2MaxRow()) ++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(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 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 + // + // 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 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(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(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(pAction)-> + GetFromRange().MakeRange( GetDocument() ); + if ( aRange.Contains( rPos ) ) + { + pFound = pAction; + } + } + } + pAction = pAction->GetNext(); + } + + return const_cast(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 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 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 xDPS( + GetModel(), uno::UNO_QUERY_THROW); + uno::Reference 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(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(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(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(pSourceAction)->GetNewString( m_pDocument.get() ); + ScMatrixMode eMatrix = ScMatrixMode::NONE; + const ScCellValue& rCell = static_cast(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(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(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(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(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 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 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 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 + +#include + +#include + +using namespace ::com::sun::star; + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include "docshimp.hxx" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +void ScDocShell::SetInitialLinkUpdate( const SfxMedium* pMed ) +{ + if (pMed) + { + const SfxUInt16Item* pUpdateDocItem = SfxItemSet::GetItem( 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(pColItem)->GetValue() - 1; + SCROW nRow = static_cast(pRowItem)->GetValue() - 1; + SCTAB nTab = static_cast(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(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(pItem)->GetValue(); + uno::Sequence aProperties; + if ( aAny >>= aProperties ) + aDesc.initializeFrom( aProperties ); + } + + OUString sTarget; + if ( pReqArgs->GetItemState( FN_PARAM_1, true, &pItem ) == SfxItemState::SET ) + sTarget = static_cast(pItem)->GetValue(); + + bool bIsNewArea = true; // Default sal_True (no inquiry) + if ( pReqArgs->GetItemState( FN_PARAM_2, true, &pItem ) == SfxItemState::SET ) + bIsNewArea = static_cast(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 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(pItem)->GetValue(); + bColInit = true; + } + if( pReqArgs->HasItem( FN_PARAM_2, &pItem ) ) + { + bRowHeaders = static_cast(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 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( 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( 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(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 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(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 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 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(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 pFilter = ScDocShell::Factory().GetFilterContainer()->GetFilter4FilterName( aFilterName ); + auto pSet = std::make_shared( 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(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(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 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(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 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( 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 xWarn(Application::CreateMessageDialog(pWin, + VclMessageType::Warning, VclButtonsType::Ok, + aMessage)); + xWarn->run(); + } + else + { + std::unique_ptr 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 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 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( 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(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 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(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(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(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 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; nTabSetCalcNotification(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; nTabSetStreamValid(nTab, false); + + PostPaintGridAll(); + auto end = std::chrono::steady_clock::now(); + SAL_INFO("sc.timing", "ScDocShell::DoHardRecalc(): took " << std::chrono::duration_cast(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(&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; nTabGetPageStyle(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(&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( + 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 oRepeatCol = m_pDocument->GetRepeatColRange( nTab ); + std::optional 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(nNewScale) ) + nNewScale = static_cast(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; nTabMAXTAB; 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 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 pDlg(pFact->CreateScStyleDlg(GetActiveDialogParent(), *pStyleSheet, true)); + + auto pRequest = std::make_shared(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( 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 pDlg(pFact->CreateScHFEditDlg( + GetActiveDialogParent(), + rStyleSet, + aStr, + nResId)); + auto xRequest = std::make_shared(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(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(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