/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
/*
* This file is part of the LibreOffice project.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* This file incorporates work covered by the following license notice:
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed
* with this work for additional information regarding copyright
* ownership. The ASF licenses this file to you under the Apache
* License, Version 2.0 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.apache.org/licenses/LICENSE-2.0 .
*/
#include <scitems.hxx>
#include <comphelper/lok.hxx>
#include <o3tl/safeint.hxx>
#include <o3tl/string_view.hxx>
#include <sfx2/app.hxx>
#include <editeng/editobj.hxx>
#include <editeng/justifyitem.hxx>
#include <sfx2/linkmgr.hxx>
#include <sfx2/bindings.hxx>
#include <utility>
#include <vcl/weld.hxx>
#include <vcl/stdtext.hxx>
#include <vcl/svapp.hxx>
#include <svx/svdocapt.hxx>
#include <sal/log.hxx>
#include <unotools/charclass.hxx>
#include <osl/diagnose.h>
#include <com/sun/star/container/XNameContainer.hpp>
#include <com/sun/star/script/ModuleType.hpp>
#include <com/sun/star/script/XLibraryContainer.hpp>
#include <com/sun/star/script/vba/XVBAModuleInfo.hpp>
#include <docfunc.hxx>
#include <sc.hrc>
#include <strings.hrc>
#include <arealink.hxx>
#include <attrib.hxx>
#include <dociter.hxx>
#include <autoform.hxx>
#include <formulacell.hxx>
#include <cellmergeoption.hxx>
#include <detdata.hxx>
#include <detfunc.hxx>
#include <docpool.hxx>
#include <docsh.hxx>
#include <drwlayer.hxx>
#include <editutil.hxx>
#include <globstr.hrc>
#include <olinetab.hxx>
#include <patattr.hxx>
#include <rangenam.hxx>
#include <refundo.hxx>
#include <scresid.hxx>
#include <stlpool.hxx>
#include <stlsheet.hxx>
#include <tablink.hxx>
#include <tabvwsh.hxx>
#include <uiitems.hxx>
#include <undoblk.hxx>
#include <undocell.hxx>
#include <undodraw.hxx>
#include <undotab.hxx>
#include <sizedev.hxx>
#include <scmod.hxx>
#include <inputhdl.hxx>
#include <editable.hxx>
#include <compiler.hxx>
#include <scui_def.hxx>
#include <tabprotection.hxx>
#include <clipparam.hxx>
#include <externalrefmgr.hxx>
#include <undorangename.hxx>
#include <progress.hxx>
#include <dpobject.hxx>
#include <stringutil.hxx>
#include <cellvalue.hxx>
#include <tokenarray.hxx>
#include <rowheightcontext.hxx>
#include <cellvalues.hxx>
#include <undoconvert.hxx>
#include <docfuncutil.hxx>
#include <sheetevents.hxx>
#include <conditio.hxx>
#include <columnspanset.hxx>
#include <validat.hxx>
#include <SparklineGroup.hxx>
#include <SparklineAttributes.hxx>
#include <SparklineData.hxx>
#include <undo/UndoInsertSparkline.hxx>
#include <undo/UndoDeleteSparkline.hxx>
#include <undo/UndoDeleteSparklineGroup.hxx>
#include <undo/UndoEditSparklineGroup.hxx>
#include <undo/UndoUngroupSparklines.hxx>
#include <undo/UndoGroupSparklines.hxx>
#include <undo/UndoEditSparkline.hxx>
#include <config_features.h>
#include <memory>
#include <basic/basmgr.hxx>
#include <set>
#include <vector>
#include <sfx2/viewfrm.hxx>
using namespace com::sun::star;
using ::std::vector;
#define AUTOFORMAT_WARN_SIZE 0x10ffffUL
void ScDocFunc::NotifyDrawUndo( std::unique_ptr<SdrUndoAction> pUndoAction)
{
// #i101118# if drawing layer collects the undo actions, add it there
ScDrawLayer* pDrawLayer = rDocShell.GetDocument().GetDrawLayer();
if ( pDrawLayer && pDrawLayer->IsRecording() )
pDrawLayer->AddCalcUndo( std::move(pUndoAction) );
else
rDocShell.GetUndoManager()->AddUndoAction( std::make_unique<ScUndoDraw>( std::move(pU
ndoAction), rDocShell ) );
rDocShell.SetDrawModified();
// the affected sheet isn't known, so all stream positions are invalidated
ScDocument& rDoc = rDocShell.GetDocument();
SCTAB nTabCount = rDoc.GetTableCount();
for (SCTAB nTab=0; nTab<nTabCount; nTab++)
rDoc.SetStreamValid(nTab, false );
}
// paint row above the range (because of lines after AdjustRowHeight)
static void lcl_PaintAbove( ScDocShell& rDocShell, const ScRange& rRange )
{
SCROW nRow = rRange.aStart.Row();
if ( nRow > 0 )
{
SCTAB nTab = rRange.aStart.Tab(); //! all of them?
--nRow;
ScDocument& rDoc = rDocShell.GetDocument();
rDocShell.PostPaint( ScRange(0,nRow,nTab,rDoc.MaxCol(),nRow,nTab), PaintPartFlags::Grid );
}
}
bool ScDocFunc::AdjustRowHeight( const ScRange& rRange, bool bPaint, bool bApi )
{
ScDocument& rDoc = rDocShell.GetDocument();
SfxViewShell* pSomeViewForThisDoc = rDocShell.GetBestViewShell(false );
if ( rDoc.IsImportingXML() )
{
// for XML import, all row heights are updated together after importing
return false ;
}
if ( rDoc.IsAdjustHeightLocked() )
{
return false ;
}
SCTAB nTab = rRange.aStart.Tab();
SCROW nStartRow = rRange.aStart.Row();
SCROW nEndRow = rRange.aEnd.Row();
ScSizeDeviceProvider aProv( rDocShell );
Fraction aOne(1,1);
sc::RowHeightContext aCxt(rDoc.MaxRow(), aProv.GetPPTX(), aProv.GetPPTY(), aOne, aOne, aProv.GetDevice());
bool bChanged = rDoc.SetOptimalHeight(aCxt, nStartRow, nEndRow, nTab, bApi);
// tdf#76183: recalculate objects' positions
if (bChanged)
{
if (comphelper::LibreOfficeKit::isActive())
{
SfxViewShell* pViewShell = SfxViewShell::GetFirst();
while (pViewShell)
{
ScTabViewShell* pTabViewShell = dynamic_cast <ScTabViewShell*>(pViewShell);
if (pTabViewShell && pSomeViewForThisDoc && pTabViewShell->GetDocId() == pSomeViewForThisDoc->GetDocId())
{
if (ScPositionHelper* pPosHelper = pTabViewShell->GetViewData().GetLOKHeightHelper(nTab))
pPosHelper->invalidateByIndex(nStartRow);
}
pViewShell = SfxViewShell::GetNext(*pViewShell);
}
}
rDoc.SetDrawPageSize(nTab);
}
if ( bPaint && bChanged )
rDocShell.PostPaint(ScRange(0, nStartRow, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab),
PaintPartFlags::Grid | PaintPartFlags::Left);
if (comphelper::LibreOfficeKit::isActive())
{
ScTabViewShell::notifyAllViewsHeaderInvalidation(pSomeViewForThisDoc, ROW_HEADER, nTab);
ScTabViewShell::notifyAllViewsSheetGeomInvalidation(
pSomeViewForThisDoc, false /* bColumns */, true /* bRows */, true /* bSizes*/,
false /* bHidden */, false /* bFiltered */, false /* bGroups */, nTab);
}
return bChanged;
}
bool ScDocFunc::DetectiveAddPred(const ScAddress& rPos)
{
ScDocShellModificator aModificator( rDocShell );
rDocShell.MakeDrawLayer();
ScDocument& rDoc = rDocShell.GetDocument();
bool bUndo (rDoc.IsUndoEnabled());
ScDrawLayer* pModel = rDoc.GetDrawLayer();
SCCOL nCol = rPos.Col();
SCROW nRow = rPos.Row();
SCTAB nTab = rPos.Tab();
if (bUndo)
pModel->BeginCalcUndo(false );
bool bDone = ScDetectiveFunc(rDoc, nTab).ShowPred( nCol, nRow );
std::unique_ptr<SdrUndoGroup> pUndo;
if (bUndo)
pUndo = pModel->GetCalcUndo();
if (bDone)
{
ScDetOpData aOperation( ScAddress(nCol,nRow,nTab), SCDETOP_ADDPRED );
rDoc.AddDetectiveOperation( aOperation );
if (bUndo)
{
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoDetective>( rDocShell, std::move(pUndo), &aOperation ) );
}
aModificator.SetDocumentModified();
SfxBindings* pBindings = rDocShell.GetViewBindings();
if (pBindings)
pBindings->Invalidate( SID_DETECTIVE_REFRESH );
}
return bDone;
}
bool ScDocFunc::DetectiveDelPred(const ScAddress& rPos)
{
ScDocument& rDoc = rDocShell.GetDocument();
bool bUndo(rDoc.IsUndoEnabled());
ScDrawLayer* pModel = rDoc.GetDrawLayer();
if (!pModel)
return false ;
ScDocShellModificator aModificator( rDocShell );
SCCOL nCol = rPos.Col();
SCROW nRow = rPos.Row();
SCTAB nTab = rPos.Tab();
if (bUndo)
pModel->BeginCalcUndo(false );
bool bDone = ScDetectiveFunc(rDoc, nTab).DeletePred( nCol, nRow );
std::unique_ptr<SdrUndoGroup> pUndo;
if (bUndo)
pUndo = pModel->GetCalcUndo();
if (bDone)
{
ScDetOpData aOperation( ScAddress(nCol,nRow,nTab), SCDETOP_DELPRED );
rDoc.AddDetectiveOperation( aOperation );
if (bUndo)
{
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoDetective>( rDocShell, std::move(pUndo), &aOperation ) );
}
aModificator.SetDocumentModified();
SfxBindings* pBindings = rDocShell.GetViewBindings();
if (pBindings)
pBindings->Invalidate( SID_DETECTIVE_REFRESH );
}
return bDone;
}
bool ScDocFunc::DetectiveAddSucc(const ScAddress& rPos)
{
ScDocShellModificator aModificator( rDocShell );
rDocShell.MakeDrawLayer();
ScDocument& rDoc = rDocShell.GetDocument();
bool bUndo(rDoc.IsUndoEnabled());
ScDrawLayer* pModel = rDoc.GetDrawLayer();
SCCOL nCol = rPos.Col();
SCROW nRow = rPos.Row();
SCTAB nTab = rPos.Tab();
if (bUndo)
pModel->BeginCalcUndo(false );
bool bDone = ScDetectiveFunc(rDoc, nTab).ShowSucc( nCol, nRow );
std::unique_ptr<SdrUndoGroup> pUndo;
if (bUndo)
pUndo = pModel->GetCalcUndo();
if (bDone)
{
ScDetOpData aOperation( ScAddress(nCol,nRow,nTab), SCDETOP_ADDSUCC );
rDoc.AddDetectiveOperation( aOperation );
if (bUndo)
{
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoDetective>( rDocShell, std::move(pUndo), &aOperation ) );
}
aModificator.SetDocumentModified();
SfxBindings* pBindings = rDocShell.GetViewBindings();
if (pBindings)
pBindings->Invalidate( SID_DETECTIVE_REFRESH );
}
return bDone;
}
bool ScDocFunc::DetectiveDelSucc(const ScAddress& rPos)
{
ScDocument& rDoc = rDocShell.GetDocument();
bool bUndo (rDoc.IsUndoEnabled());
ScDrawLayer* pModel = rDoc.GetDrawLayer();
if (!pModel)
return false ;
ScDocShellModificator aModificator( rDocShell );
SCCOL nCol = rPos.Col();
SCROW nRow = rPos.Row();
SCTAB nTab = rPos.Tab();
if (bUndo)
pModel->BeginCalcUndo(false );
bool bDone = ScDetectiveFunc(rDoc, nTab).DeleteSucc( nCol, nRow );
std::unique_ptr<SdrUndoGroup> pUndo;
if (bUndo)
pUndo = pModel->GetCalcUndo();
if (bDone)
{
ScDetOpData aOperation( ScAddress(nCol,nRow,nTab), SCDETOP_DELSUCC );
rDoc.AddDetectiveOperation( aOperation );
if (bUndo)
{
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoDetective>( rDocShell, std::move(pUndo), &aOperation ) );
}
aModificator.SetDocumentModified();
SfxBindings* pBindings = rDocShell.GetViewBindings();
if (pBindings)
pBindings->Invalidate( SID_DETECTIVE_REFRESH );
}
return bDone;
}
bool ScDocFunc::DetectiveAddError(const ScAddress& rPos)
{
ScDocShellModificator aModificator( rDocShell );
rDocShell.MakeDrawLayer();
ScDocument& rDoc = rDocShell.GetDocument();
bool bUndo (rDoc.IsUndoEnabled());
ScDrawLayer* pModel = rDoc.GetDrawLayer();
SCCOL nCol = rPos.Col();
SCROW nRow = rPos.Row();
SCTAB nTab = rPos.Tab();
if (bUndo)
pModel->BeginCalcUndo(false );
bool bDone = ScDetectiveFunc(rDoc, nTab).ShowError( nCol, nRow );
std::unique_ptr<SdrUndoGroup> pUndo;
if (bUndo)
pUndo = pModel->GetCalcUndo();
if (bDone)
{
ScDetOpData aOperation( ScAddress(nCol,nRow,nTab), SCDETOP_ADDERROR );
rDoc.AddDetectiveOperation( aOperation );
if (bUndo)
{
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoDetective>( rDocShell, std::move(pUndo), &aOperation ) );
}
aModificator.SetDocumentModified();
SfxBindings* pBindings = rDocShell.GetViewBindings();
if (pBindings)
pBindings->Invalidate( SID_DETECTIVE_REFRESH );
}
return bDone;
}
bool ScDocFunc::DetectiveMarkInvalid(SCTAB nTab)
{
ScDocShellModificator aModificator( rDocShell );
rDocShell.MakeDrawLayer();
ScDocument& rDoc = rDocShell.GetDocument();
bool bUndo (rDoc.IsUndoEnabled());
ScDrawLayer* pModel = rDoc.GetDrawLayer();
std::unique_ptr<weld::WaitObject> xWaitWin(new weld::WaitObject(ScDocShell::GetActiveDialogParent()));
if (bUndo)
pModel->BeginCalcUndo(false );
bool bOverflow;
bool bDone = ScDetectiveFunc(rDoc, nTab).MarkInvalid( bOverflow );
std::unique_ptr<SdrUndoGroup> pUndo;
if (bUndo)
pUndo = pModel->GetCalcUndo();
xWaitWin.reset();
if (bDone)
{
if (pUndo && bUndo)
{
pUndo->SetComment( ScResId( STR_UNDO_DETINVALID ) );
rDocShell.GetUndoManager()->AddUndoAction( std::move(pUndo) );
}
aModificator.SetDocumentModified();
if ( bOverflow )
{
std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(nullptr,
VclMessageType::Info, VclButtonsType::Ok,
ScResId(STR_DETINVALID_OVERFLOW)));
xInfoBox->run();
}
}
return bDone;
}
bool ScDocFunc::DetectiveDelAll(SCTAB nTab)
{
ScDocument& rDoc = rDocShell.GetDocument();
bool bUndo (rDoc.IsUndoEnabled());
ScDrawLayer* pModel = rDoc.GetDrawLayer();
if (!pModel)
return false ;
ScDocShellModificator aModificator( rDocShell );
if (bUndo)
pModel->BeginCalcUndo(false );
bool bDone = ScDetectiveFunc(rDoc, nTab).DeleteAll( ScDetectiveDelete::Detective );
std::unique_ptr<SdrUndoGroup> pUndo;
if (bUndo)
pUndo = pModel->GetCalcUndo();
if (bDone)
{
ScDetOpList* pOldList = rDoc.GetDetOpList();
std::unique_ptr<ScDetOpList> pUndoList;
if (bUndo && pOldList)
pUndoList.reset(new ScDetOpList(*pOldList));
rDoc.ClearDetectiveOperations();
if (bUndo)
{
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoDetective>( rDocShell, std::move(pUndo), nullptr, std::move(pUndoList) ) );
}
aModificator.SetDocumentModified();
SfxBindings* pBindings = rDocShell.GetViewBindings();
if (pBindings)
pBindings->Invalidate( SID_DETECTIVE_REFRESH );
}
return bDone;
}
bool ScDocFunc::DetectiveRefresh( bool bAutomatic )
{
bool bDone = false ;
ScDocument& rDoc = rDocShell.GetDocument();
ScDetOpList* pList = rDoc.GetDetOpList();
if ( pList && pList->Count() )
{
rDocShell.MakeDrawLayer();
ScDrawLayer* pModel = rDoc.GetDrawLayer();
const bool bUndo (rDoc.IsUndoEnabled());
if (bUndo)
pModel->BeginCalcUndo(false );
// Delete in all sheets
SCTAB nTabCount = rDoc.GetTableCount();
for (SCTAB nTab=0; nTab<nTabCount; nTab++)
ScDetectiveFunc( rDoc,nTab ).DeleteAll( ScDetectiveDelete::Arrows ); // don't remove circles
// repeat
size_t nCount = pList->Count();
for (size_t i=0; i < nCount; ++i)
{
const ScDetOpData& rData = pList->GetObject(i);
const ScAddress& aPos = rData.GetPos();
ScDetectiveFunc aFunc( rDoc, aPos.Tab() );
SCCOL nCol = aPos.Col();
SCROW nRow = aPos.Row();
switch (rData.GetOperation())
{
case SCDETOP_ADDSUCC:
aFunc.ShowSucc( nCol, nRow );
break ;
case SCDETOP_DELSUCC:
aFunc.DeleteSucc( nCol, nRow );
break ;
case SCDETOP_ADDPRED:
aFunc.ShowPred( nCol, nRow );
break ;
case SCDETOP_DELPRED:
aFunc.DeletePred( nCol, nRow );
break ;
case SCDETOP_ADDERROR:
aFunc.ShowError( nCol, nRow );
break ;
default :
OSL_FAIL("wrong operation in DetectiveRefresh" );
}
}
if (bUndo)
{
std::unique_ptr<SdrUndoGroup> pUndo = pModel->GetCalcUndo();
if (pUndo)
{
pUndo->SetComment( ScResId( STR_UNDO_DETREFRESH ) );
// associate with the last action
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoDraw>( std::move(pUndo), rDocShell ),
bAutomatic );
}
}
rDocShell.SetDrawModified();
bDone = true ;
}
return bDone;
}
static void lcl_collectAllPredOrSuccRanges(
const ScRangeList& rSrcRanges, vector<ScTokenRef>& rRefTokens, ScDocShell& rDocShell,
bool bPred)
{
ScDocument& rDoc = rDocShell.GetDocument();
vector<ScTokenRef> aRefTokens;
if (rSrcRanges.empty())
return ;
ScRange const & rFrontRange = rSrcRanges.front();
ScDetectiveFunc aDetFunc(rDoc, rFrontRange.aStart.Tab());
for (ScRange const & r : rSrcRanges)
{
if (bPred)
{
aDetFunc.GetAllPreds(
r.aStart.Col(), r.aStart.Row(), r.aEnd.Col(), r.aEnd.Row(), aRefTokens);
}
else
{
aDetFunc.GetAllSuccs(
r.aStart.Col(), r.aStart.Row(), r.aEnd.Col(), r.aEnd.Row(), aRefTokens);
}
}
rRefTokens.swap(aRefTokens);
}
void ScDocFunc::DetectiveCollectAllPreds(const ScRangeList& rSrcRanges, vector<ScTokenRef>& rRefTokens)
{
lcl_collectAllPredOrSuccRanges(rSrcRanges, rRefTokens, rDocShell, true );
}
void ScDocFunc::DetectiveCollectAllSuccs(const ScRangeList& rSrcRanges, vector<ScTokenRef>& rRefTokens)
{
lcl_collectAllPredOrSuccRanges(rSrcRanges, rRefTokens, rDocShell, false );
}
bool ScDocFunc::DeleteContents(
const ScMarkData& rMark, InsertDeleteFlags nFlags, bool bRecord, bool bApi )
{
ScDocShellModificator aModificator( rDocShell );
if ( !rMark.IsMarked() && !rMark.IsMultiMarked() )
{
OSL_FAIL("ScDocFunc::DeleteContents without markings" );
return false ;
}
ScDocument& rDoc = rDocShell.GetDocument();
if (bRecord && !rDoc.IsUndoEnabled())
bRecord = false ;
ScEditableTester aTester( rDoc, rMark );
if (!aTester.IsEditable())
{
if (!bApi)
rDocShell.ErrorMessage(aTester.GetMessageId());
return false ;
}
ScMarkData aMultiMark = rMark;
aMultiMark.SetMarking(false ); // for MarkToMulti
ScDocumentUniquePtr pUndoDoc;
bool bMulti = aMultiMark.IsMultiMarked();
aMultiMark.MarkToMulti();
const ScRange& aMarkRange = aMultiMark.GetMultiMarkArea();
ScRange aExtendedRange(aMarkRange);
if ( rDoc.ExtendMerge( aExtendedRange, true ) )
bMulti = false ;
// no objects on protected tabs
bool bObjects = (nFlags & InsertDeleteFlags::OBJECTS) && !sc::DocFuncUtil::hasProtectedTab(rDoc, rMark);
sal_uInt16 nExtFlags = 0; // extra flags are needed only if attributes are deleted
if ( nFlags & InsertDeleteFlags::ATTRIB )
rDocShell.UpdatePaintExt( nExtFlags, aMarkRange );
// order of operations:
// 1) BeginDrawUndo
// 2) Delete objects (DrawUndo will be filled)
// 3) Copy content for undo and set up undo actions
// 4) Delete content
bool bDrawUndo = bObjects || (nFlags & InsertDeleteFlags::NOTE);
if (bRecord && bDrawUndo)
rDoc.BeginDrawUndo();
if (bObjects)
{
if (bMulti)
rDoc.DeleteObjectsInSelection( aMultiMark );
else
rDoc.DeleteObjectsInArea( aMarkRange.aStart.Col(), aMarkRange.aStart.Row(),
aMarkRange.aEnd.Col(), aMarkRange.aEnd.Row(),
aMultiMark );
}
// To keep track of all non-empty cells within the deleted area.
std::shared_ptr<ScSimpleUndo::DataSpansType> pDataSpans;
if ( bRecord )
{
pUndoDoc = sc::DocFuncUtil::createDeleteContentsUndoDoc(rDoc, aMultiMark, aMarkRange, nFlags, bMulti);
pDataSpans = sc::DocFuncUtil::getNonEmptyCellSpans(rDoc, aMultiMark, aMarkRange);
}
rDoc.DeleteSelection( nFlags, aMultiMark );
// add undo action after drawing undo is complete (objects and note captions)
if ( bRecord )
{
sc::DocFuncUtil::addDeleteContentsUndo(
rDocShell.GetUndoManager(), rDocShell, aMultiMark, aExtendedRange,
std::move(pUndoDoc), nFlags, pDataSpans, bMulti, bDrawUndo);
}
if (!AdjustRowHeight( aExtendedRange, true , bApi ))
rDocShell.PostPaint( aExtendedRange, PaintPartFlags::Grid, nExtFlags );
else if (nExtFlags & SC_PF_LINES)
lcl_PaintAbove( rDocShell, aExtendedRange ); // for lines above the range
aModificator.SetDocumentModified();
return true ;
}
tools::Long ScDocShell::GetTwipWidthHint(const ScAddress& rPos)
{
ScViewData* pViewData = GetViewData();
if (!pViewData)
return -1;
ScSizeDeviceProvider aProv(*this );
Fraction aZoomX, aZoomY;
double nPPTX, nPPTY;
pViewData->setupSizeDeviceProviderForColWidth(aProv, aZoomX, aZoomY, nPPTX, nPPTY);
ScDocument& rDoc = GetDocument();
tools::Long nWidth = rDoc.GetNeededSize(rPos.Col(), rPos.Row(), rPos.Tab(), aProv.GetDevice(),
nPPTX, nPPTY, aZoomX, aZoomY, true /*bWidth*/);
return (nWidth + 2) / nPPTX; // same as ScColumn::GetOptimalColWidth
}
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, ScRange(rPos));
// order of operations:
// 1) BeginDrawUndo
// 2) delete objects (DrawUndo is filled)
// 3) copy contents for undo
// 4) delete contents
// 5) add undo-action
bool bDrawUndo = bObjects || (nFlags & InsertDeleteFlags::NOTE); // needed for shown notes
if (bDrawUndo && bRecord)
rDoc.BeginDrawUndo();
if (bObjects)
rDoc.DeleteObjectsInArea(rPos.Col(), rPos.Row(), rPos.Col(), rPos.Row(), rMark);
// To keep track of all non-empty cells within the deleted area.
std::shared_ptr<ScSimpleUndo::DataSpansType> pDataSpans;
ScDocumentUniquePtr pUndoDoc;
if (bRecord)
{
pUndoDoc = sc::DocFuncUtil::createDeleteContentsUndoDoc(rDoc, rMark, ScRange(rPos), nFlags, false );
pDataSpans = sc::DocFuncUtil::getNonEmptyCellSpans(rDoc, rMark, ScRange(rPos));
}
tools::Long nBefore(rDocShell.GetTwipWidthHint(rPos));
rDoc.DeleteArea(rPos.Col(), rPos.Row(), rPos.Col(), rPos.Row(), rMark, nFlags);
if (bRecord)
{
sc::DocFuncUtil::addDeleteContentsUndo(
rDocShell.GetUndoManager(), rDocShell, rMark, ScRange(rPos), std::move(pUndoDoc),
nFlags, pDataSpans, false , bDrawUndo);
}
if (!AdjustRowHeight(ScRange(rPos), true , bApi))
rDocShell.PostPaint(
rPos.Col(), rPos.Row(), rPos.Tab(), rPos.Col(), rPos.Row(), rPos.Tab(),
PaintPartFlags::Grid, nExtFlags, nBefore);
aModificator.SetDocumentModified();
return true ;
}
bool ScDocFunc::TransliterateText( const ScMarkData& rMark, TransliterationFlags nType,
bool bApi )
{
ScDocShellModificator aModificator( rDocShell );
ScDocument& rDoc = rDocShell.GetDocument();
bool bRecord = true ;
if (!rDoc.IsUndoEnabled())
bRecord = false ;
ScEditableTester aTester( rDoc, rMark );
if (!aTester.IsEditable())
{
if (!bApi)
rDocShell.ErrorMessage(aTester.GetMessageId());
return false ;
}
ScMarkData aMultiMark = rMark;
aMultiMark.SetMarking(false ); // for MarkToMulti
aMultiMark.MarkToMulti();
const ScRange& aMarkRange = aMultiMark.GetMultiMarkArea();
if (bRecord)
{
SCTAB nStartTab = aMarkRange.aStart.Tab();
SCTAB nTabCount = rDoc.GetTableCount();
ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO ));
pUndoDoc->InitUndo( rDoc, nStartTab, nStartTab );
for (const auto & rTab : rMark)
{
if (rTab >= nTabCount)
break ;
if (rTab != nStartTab)
pUndoDoc->AddUndoTab( rTab, rTab );
}
ScRange aCopyRange = aMarkRange;
aCopyRange.aStart.SetTab(0);
aCopyRange.aEnd.SetTab(nTabCount-1);
rDoc.CopyToDocument(aCopyRange, InsertDeleteFlags::CONTENTS, true , *pUndoDoc, &aMultiMark);
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoTransliterate>( rDocShell, aMultiMark, std::move(pUndoDoc), nType ) );
}
rDoc.TransliterateText( aMultiMark, nType );
if (!AdjustRowHeight( aMarkRange, true , true ))
rDocShell.PostPaint( aMarkRange, PaintPartFlags::Grid );
aModificator.SetDocumentModified();
return true ;
}
bool ScDocFunc::SetNormalString( bool & o_rbNumFmtSet, const ScAddress& rPos, const OUString& rText, bool bApi )
{
ScDocShellModificator aModificator( rDocShell );
ScDocument& rDoc = rDocShell.GetDocument();
bool bUndo(rDoc.IsUndoEnabled());
ScEditableTester aTester( rDoc, rPos.Tab(), rPos.Col(),rPos.Row(), rPos.Col(),rPos.Row() );
if (!aTester.IsEditable())
{
if (!bApi)
rDocShell.ErrorMessage(aTester.GetMessageId());
return false ;
}
bool bEditDeleted = (rDoc.GetCellType(rPos) == CELLTYPE_EDIT);
ScUndoEnterData::ValuesType aOldValues;
if (bUndo)
{
ScUndoEnterData::Value aOldValue;
aOldValue.mnTab = rPos.Tab();
aOldValue.maCell.assign(rDoc, rPos);
const ScPatternAttr* pPattern = rDoc.GetPattern( rPos.Col(),rPos.Row(),rPos.Tab() );
if ( const SfxUInt32Item* pItem = pPattern->GetItemSet().GetItemIfSet(
ATTR_VALUE_FORMAT,false ) )
{
aOldValue.mbHasFormat = true ;
aOldValue.mnFormat = pItem->GetValue();
}
else
aOldValue.mbHasFormat = false ;
aOldValues.push_back(aOldValue);
}
tools::Long nBefore(rDocShell.GetTwipWidthHint(rPos));
o_rbNumFmtSet = rDoc.SetString( rPos.Col(), rPos.Row(), rPos.Tab(), rText );
tools::Long nAfter(rDocShell.GetTwipWidthHint(rPos));
if (bUndo)
{
// because of ChangeTracking, UndoAction can be created only after SetString was called
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoEnterData>(rDocShell, rPos, aOldValues, rText, nullptr));
}
if ( bEditDeleted || rDoc.HasAttrib( ScRange(rPos), HasAttrFlags::NeedHeight ) )
AdjustRowHeight( ScRange(rPos), true , bApi );
rDocShell.PostPaintCell( rPos, std::max(nBefore, nAfter) );
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(ScRange(rPos), HasAttrFlags::NeedHeight);
ScCellValue aOldVal;
if (bUndo)
aOldVal.assign(rDoc, rPos);
rDoc.SetValue(rPos, fVal);
if (bUndo)
{
SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager();
ScCellValue aNewVal;
aNewVal.assign(rDoc, rPos);
pUndoMgr->AddUndoAction(std::make_unique<ScUndoSetCell>(rDocShell, rPos, aOldVal, aNewVal));
}
if (bHeight)
AdjustRowHeight(ScRange(rPos), true , !bInteraction);
rDocShell.PostPaintCell( rPos );
aModificator.SetDocumentModified();
// #103934#; notify editline and cell in edit mode
if (!bInteraction)
NotifyInputHandler( rPos );
return true ;
}
void ScDocFunc::SetValueCells( const ScAddress& rPos, const std::vector<double >& aVals, bool bInteraction )
{
ScDocument& rDoc = rDocShell.GetDocument();
// Check for invalid range.
SCROW nLastRow = rPos.Row() + aVals.size() - 1;
if (nLastRow > rDoc.MaxRow())
// out of bound.
return ;
ScRange aRange(rPos);
aRange.aEnd.SetRow(nLastRow);
ScDocShellModificator aModificator(rDocShell);
if (rDoc.IsUndoEnabled())
{
std::unique_ptr<sc::UndoSetCells> pUndoObj(new sc::UndoSetCells(rDocShell, rPos));
rDoc.TransferCellValuesTo(rPos, aVals.size(), pUndoObj->GetOldValues());
pUndoObj->SetNewValues(aVals);
SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager();
pUndoMgr->AddUndoAction(std::move(pUndoObj));
}
rDoc.SetValues(rPos, aVals);
rDocShell.PostPaint(aRange, PaintPartFlags::Grid);
aModificator.SetDocumentModified();
// #103934#; notify editline and cell in edit mode
if (!bInteraction)
NotifyInputHandler(rPos);
}
bool ScDocFunc::SetStringCell( const ScAddress& rPos, const OUString& rStr, bool bInteraction )
{
ScDocShellModificator aModificator( rDocShell );
ScDocument& rDoc = rDocShell.GetDocument();
bool bUndo = rDoc.IsUndoEnabled();
bool bHeight = rDoc.HasAttrib(ScRange(rPos), HasAttrFlags::NeedHeight);
ScCellValue aOldVal;
if (bUndo)
aOldVal.assign(rDoc, rPos);
ScSetStringParam aParam;
aParam.setTextInput();
rDoc.SetString(rPos, rStr, &aParam);
if (bUndo)
{
SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager();
ScCellValue aNewVal;
aNewVal.assign(rDoc, rPos);
pUndoMgr->AddUndoAction(std::make_unique<ScUndoSetCell>(rDocShell, rPos, aOldVal, aNewVal));
}
if (bHeight)
AdjustRowHeight(ScRange(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(ScRange(rPos), HasAttrFlags::NeedHeight);
ScCellValue aOldVal;
if (bUndo)
aOldVal.assign(rDoc, rPos);
rDoc.SetEditText(rPos, rStr.Clone());
if (bUndo)
{
SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager();
ScCellValue aNewVal;
aNewVal.assign(rDoc, rPos);
pUndoMgr->AddUndoAction(std::make_unique<ScUndoSetCell>(rDocShell, rPos, aOldVal, aNewVal));
}
if (bHeight)
AdjustRowHeight(ScRange(rPos), true , !bInteraction);
rDocShell.PostPaintCell( rPos );
aModificator.SetDocumentModified();
// #103934#; notify editline and cell in edit mode
if (!bInteraction)
NotifyInputHandler( rPos );
return true ;
}
bool ScDocFunc::SetStringOrEditCell( const ScAddress& rPos, const OUString& rStr, bool bInteraction )
{
ScDocument& rDoc = rDocShell.GetDocument();
if (ScStringUtil::isMultiline(rStr))
{
ScFieldEditEngine& rEngine = rDoc.GetEditEngine();
rEngine.SetTextCurrentDefaults(rStr);
std::unique_ptr<EditTextObject> pEditText(rEngine.CreateTextObject());
return SetEditCell(rPos, *pEditText, bInteraction);
}
else
return SetStringCell(rPos, rStr, bInteraction);
}
bool ScDocFunc::SetFormulaCell( const ScAddress& rPos, ScFormulaCell* pCell, bool bInteraction )
{
std::unique_ptr<ScFormulaCell> xCell(pCell);
ScDocShellModificator aModificator( rDocShell );
ScDocument& rDoc = rDocShell.GetDocument();
bool bUndo = rDoc.IsUndoEnabled();
bool bHeight = rDoc.HasAttrib(ScRange(rPos), HasAttrFlags::NeedHeight);
ScCellValue aOldVal;
if (bUndo)
aOldVal.assign(rDoc, rPos);
pCell = rDoc.SetFormulaCell(rPos, xCell.release());
// For performance reasons API calls may disable calculation while
// operating and recalculate once when done. If through user interaction
// and AutoCalc is disabled, calculate the formula (without its
// dependencies) once so the result matches the current document's content.
if (bInteraction && !rDoc.GetAutoCalc() && pCell)
{
// calculate just the cell once and set Dirty again
pCell->Interpret();
pCell->SetDirtyVar();
rDoc.PutInFormulaTree( pCell);
}
if (bUndo)
{
SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager();
ScCellValue aNewVal;
aNewVal.assign(rDoc, rPos);
pUndoMgr->AddUndoAction(std::make_unique<ScUndoSetCell>(rDocShell, rPos, aOldVal, aNewVal));
}
if (bHeight)
AdjustRowHeight(ScRange(rPos), true , !bInteraction);
rDocShell.PostPaintCell( rPos );
aModificator.SetDocumentModified();
// #103934#; notify editline and cell in edit mode
if (!bInteraction)
NotifyInputHandler( rPos );
return true ;
}
bool ScDocFunc::SetFormulaCells( const ScAddress& rPos, std::vector<ScFormulaCell*>& rCells, bool bInteraction )
{
ScDocument& rDoc = rDocShell.GetDocument();
const size_t nLength = rCells.size();
if (rPos.Row() + nLength - 1 > o3tl::make_unsigned(rDoc.MaxRow()))
// out of bound
return false ;
ScRange aRange(rPos);
aRange.aEnd.IncRow(nLength - 1);
ScDocShellModificator aModificator( rDocShell );
bool bUndo = rDoc.IsUndoEnabled();
std::unique_ptr<sc::UndoSetCells> pUndoObj;
if (bUndo)
{
pUndoObj.reset(new sc::UndoSetCells(rDocShell, rPos));
rDoc.TransferCellValuesTo(rPos, nLength, pUndoObj->GetOldValues());
}
rDoc.SetFormulaCells(rPos, rCells);
// For performance reasons API calls may disable calculation while
// operating and recalculate once when done. If through user interaction
// and AutoCalc is disabled, calculate the formula (without its
// dependencies) once so the result matches the current document's content.
if (bInteraction && !rDoc.GetAutoCalc())
{
for (auto * pCell : rCells)
{
// calculate just the cell once and set Dirty again
pCell->Interpret();
pCell->SetDirtyVar();
rDoc.PutInFormulaTree( pCell);
}
}
if (bUndo)
{
pUndoObj->SetNewValues(rCells);
SfxUndoManager* pUndoMgr = rDocShell.GetUndoManager();
pUndoMgr->AddUndoAction(std::move(pUndoObj));
}
rDocShell.PostPaint(aRange, PaintPartFlags::Grid);
aModificator.SetDocumentModified();
// #103934#; notify editline and cell in edit mode
if (!bInteraction)
NotifyInputHandler( rPos );
return true ;
}
void ScDocFunc::NotifyInputHandler( const ScAddress& rPos )
{
ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell();
if ( !(pViewSh && &pViewSh->GetViewData().GetDocShell() == &rDocShell) )
return ;
ScInputHandler* pInputHdl = ScModule::get()->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(SfxItemSet _aItemSet, sal_Int32 nTempIndex) :
nIndex(nTempIndex), aItemSet(std::move(_aItemSet)) {}
};
}
void ScDocFunc::PutData( const ScAddress& rPos, ScEditEngineDefaulter& rEngine, bool bApi )
{
// PutData calls PutCell or SetNormalString
bool bRet = false ;
ScDocument& rDoc = rDocShell.GetDocument();
ScEditAttrTester aTester( &rEngine );
bool bEditCell = aTester.NeedsObject();
if ( bEditCell )
{
// #i61702# With bLoseContent set, the content of rEngine isn't restored
// (used in loading XML, where after the removeActionLock call the API object's
// EditEngine isn't accessed again.
bool bLoseContent = rDoc.IsImportingXML();
const bool bUpdateMode = rEngine.SetUpdateLayout(false );
std::vector<std::unique_ptr<ScMyRememberItem>> aRememberItems;
// All paragraph attributes must be removed before calling CreateTextObject,
// not only alignment, so the object doesn't contain the cell attributes as
// paragraph attributes. Before removing the attributes store them in a vector to
// set them back to the EditEngine.
sal_Int32 nCount = rEngine.GetParagraphCount();
for (sal_Int32 i=0; i<nCount; i++)
{
const SfxItemSet& rOld = rEngine.GetParaAttribs( i );
if ( rOld.Count() )
{
if ( !bLoseContent )
{
aRememberItems.push_back(std::make_unique<ScMyRememberItem>(rEngine.GetParaAttribs(i), i));
}
rEngine.SetParaAttribs( i, SfxItemSet( *rOld.GetPool(), rOld.GetRanges() ) );
}
}
// A copy of pNewData will be stored in the cell.
std::unique_ptr<EditTextObject> pNewData(rEngine.CreateTextObject());
bRet = SetEditCell(rPos, *pNewData, !bApi);
// Set the paragraph attributes back to the EditEngine.
for (const auto & rxItem : aRememberItems)
{
rEngine.SetParaAttribs(rxItem->nIndex, rxItem->aItemSet);
}
// #i61702# if the content isn't accessed, there's no need to set the UpdateMode again
if ( bUpdateMode && !bLoseContent )
rEngine.SetUpdateLayout(true );
}
else
{
OUString aText = rEngine.GetText();
if (aText.isEmpty())
{
bool bNumFmtSet = false ;
bRet = SetNormalString( bNumFmtSet, rPos, aText, bApi );
}
else
bRet = SetStringCell(rPos, aText, !bApi);
}
if ( !(bRet && aTester.NeedsCellAttr()) )
return ;
const SfxItemSet& rEditAttr = aTester.GetAttribs();
ScPatternAttr aPattern(rDoc.getCellAttributeHelper());
aPattern.GetFromEditItemSet( &rEditAttr );
aPattern.DeleteUnchanged( rDoc.GetPattern( rPos.Col(), rPos.Row(), rPos.Tab() ) );
aPattern.GetItemSet().ClearItem( ATTR_HOR_JUSTIFY ); // wasn't removed above if no edit object
if ( aPattern.GetItemSet().Count() > 0 )
{
ScMarkData aMark(rDoc.GetSheetLimits());
aMark.SelectTable( rPos.Tab(), true );
aMark.SetMarkArea( ScRange( rPos ) );
ApplyAttributes( aMark, aPattern, bApi );
}
}
bool ScDocFunc::SetCellText(
const ScAddress& rPos, const OUString& rText, bool bInterpret, bool bEnglish, bool bApi,
const formula::FormulaGrammar::Grammar eGrammar )
{
bool bSet = false ;
if ( bInterpret )
{
if ( bEnglish )
{
ScDocument& rDoc = rDocShell.GetDocument();
::std::optional<ScExternalRefManager::ApiGuard> pExtRefGuard;
if (bApi)
pExtRefGuard.emplace(rDoc);
ScInputStringType aRes =
ScStringUtil::parseInputString(rDoc.GetNonThreadedContext(), rText, LANGUAGE_ENGLISH_US);
switch (aRes.meType)
{
case ScInputStringType::Formula:
bSet = SetFormulaCell(rPos, new ScFormulaCell(rDoc, rPos, aRes.maText, eGrammar), !bApi);
break ;
case ScInputStringType::Number:
bSet = SetValueCell(rPos, aRes.mfValue, !bApi);
break ;
case ScInputStringType::Text:
bSet = SetStringOrEditCell(rPos, aRes.maText, !bApi);
break ;
default :
;
}
}
// otherwise keep Null -> SetString with local formulas/number formats
}
else if (!rText.isEmpty())
{
bSet = SetStringOrEditCell(rPos, rText, !bApi);
}
if (!bSet)
{
bool bNumFmtSet = false ;
bSet = SetNormalString( bNumFmtSet, rPos, rText, bApi );
}
return bSet;
}
bool ScDocFunc::ShowNote( const ScAddress& rPos, bool bShow )
{
ScDocument& rDoc = rDocShell.GetDocument();
ScPostIt* pNote = rDoc.GetNote( rPos );
if ( !pNote || (bShow == pNote->IsCaptionShown()) ||
(comphelper::LibreOfficeKit::isActive() && !comphelper::LibreOfficeKit::isTiledAnnotations()) )
return false ;
// move the caption to internal or hidden layer and create undo action
pNote->ShowCaption( rPos, bShow );
if ( rDoc.IsUndoEnabled() )
rDocShell.GetUndoManager()->AddUndoAction( std::make_unique<ScUndoShowHideNote>( rDocShell, rPos, bShow ) );
rDoc.SetStreamValid(rPos.Tab(), false );
ScTabView::OnLOKNoteStateChanged(pNote);
if (ScViewData* pViewData = ScDocShell::GetViewData())
{
if (ScDrawView* pDrawView = pViewData->GetScDrawView())
pDrawView->SyncForGrid( pNote->GetCaption());
}
rDocShell.SetDocumentModified();
return true ;
}
void ScDocFunc::SetNoteText( const ScAddress& rPos, const OUString& rText, bool bApi )
{
ScDocShellModificator aModificator( rDocShell );
ScDocument& rDoc = rDocShell.GetDocument();
ScEditableTester aTester( rDoc, rPos.Tab(), rPos.Col(),rPos.Row(), rPos.Col(),rPos.Row() );
if (!aTester.IsEditable())
{
if (!bApi)
rDocShell.ErrorMessage(aTester.GetMessageId());
return ;
}
OUString aNewText = convertLineEnd(rText, GetSystemLineEnd()); //! is this necessary ???
if ( ScPostIt* pNote = (!aNewText.isEmpty()) ? rDoc.GetOrCreateNote( rPos ) : rDoc.GetNote(rPos) )
pNote->SetText( rPos, aNewText );
//! Undo !!!
rDoc.SetStreamValid(rPos.Tab(), false );
rDocShell.PostPaintCell( rPos );
aModificator.SetDocumentModified();
}
void ScDocFunc::ReplaceNote( const ScAddress& rPos, const OUString& rNoteText, const OUString* pAuthor, const OUString* pDate, bool bApi )
{
ScDocShellModificator aModificator( rDocShell );
ScDocument& rDoc = rDocShell.GetDocument();
ScEditableTester aTester( rDoc, rPos.Tab(), rPos.Col(),rPos.Row(), rPos.Col(),rPos.Row() );
if (aTester.IsEditable())
{
ScDrawLayer* pDrawLayer = rDoc.GetDrawLayer();
SfxUndoManager* pUndoMgr = (pDrawLayer && rDoc.IsUndoEnabled()) ? rDocShell.GetUndoManager() : nullptr;
ScNoteData aOldData;
std::unique_ptr<ScPostIt> pOldNote = rDoc.ReleaseNote( rPos );
sal_uInt32 nNoteId = 0;
if ( pOldNote )
{
nNoteId = pOldNote->GetId();
// ensure existing caption object before draw undo tracking starts
pOldNote->GetOrCreateCaption( rPos );
// rescue note data for undo
aOldData = pOldNote->GetNoteData();
}
// collect drawing undo actions for deleting/inserting caption objects
if ( pUndoMgr )
pDrawLayer->BeginCalcUndo(false );
// delete the note (creates drawing undo action for the caption object)
bool hadOldNote(pOldNote);
pOldNote.reset();
// create new note (creates drawing undo action for the new caption object)
ScNoteData aNewData;
ScPostIt* pNewNote = nullptr;
if ( (pNewNote = ScNoteUtil::CreateNoteFromString( rDoc, rPos, rNoteText, false , true , nNoteId )) )
{
if ( pAuthor ) pNewNote->SetAuthor( *pAuthor );
if ( pDate ) pNewNote->SetDate( *pDate );
// rescue note data for undo
aNewData = pNewNote->GetNoteData();
}
// create the undo action
if ( pUndoMgr && (aOldData.mxCaption || aNewData.mxCaption) )
pUndoMgr->AddUndoAction( std::make_unique<ScUndoReplaceNote>( rDocShell, rPos, aOldData, aNewData, pDrawLayer->GetCalcUndo() ) );
// repaint cell (to make note marker visible)
rDocShell.PostPaintCell( rPos );
rDoc.SetStreamValid(rPos.Tab(), false );
aModificator.SetDocumentModified();
// Let our LOK clients know about the new/modified note
if (pNewNote)
{
ScDocShell::LOKCommentNotify(hadOldNote ? LOKCommentNotificationType::Modify : LOKCommentNotificationType::Add,
rDoc, rPos, pNewNote);
}
}
else if (!bApi)
{
rDocShell.ErrorMessage(aTester.GetMessageId());
}
}
void ScDocFunc::ImportNote( const ScAddress& rPos,
std::unique_ptr<GenerateNoteCaption> xGenerator,
const tools::Rectangle& rCaptionRect, bool bShown )
{
ScDocShellModificator aModificator( rDocShell );
ScDocument& rDoc = rDocShell.GetDocument();
std::unique_ptr<ScPostIt> pOldNote = rDoc.ReleaseNote( rPos );
SAL_WARN_IF(pOldNote, "sc.ui" , "imported data has >1 notes on same cell? at pos " << rPos);
// create new note
ScNoteUtil::CreateNoteFromGenerator(rDoc, rPos, std::move(xGenerator),
rCaptionRect, bShown);
rDoc.SetStreamValid(rPos.Tab(), false );
aModificator.SetDocumentModified();
}
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();
bool bImportingXLSX = rDoc.IsImportingXLSX();
// Cell formats can still be set if the range isn't editable only because of matrix formulas.
// #i62483# When loading XML, the check can be skipped altogether.
bool bOnlyNotBecauseOfMatrix;
if ( !bImportingXML && !rDoc.IsSelectionEditable( rMark, &bOnlyNotBecauseOfMatrix )
&& !bOnlyNotBecauseOfMatrix )
{
if (!bApi)
rDocShell.ErrorMessage(STR_PROTECTIONERR);
return false ;
}
ScDocShellModificator aModificator( rDocShell );
//! Border
ScRange aMultiRange;
bool bMulti = rMark.IsMultiMarked();
if ( bMulti )
aMultiRange = rMark.GetMultiMarkArea();
else
aMultiRange = rMark.GetMarkArea();
if ( bRecord )
{
ScDocumentUniquePtr pUndoDoc( new ScDocument( SCDOCMODE_UNDO ));
pUndoDoc->InitUndo( rDoc, aMultiRange.aStart.Tab(), aMultiRange.aEnd.Tab() );
rDoc.CopyToDocument(aMultiRange, InsertDeleteFlags::ATTRIB, bMulti, *pUndoDoc, &rMark);
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoSelectionAttr>(
rDocShell, rMark,
aMultiRange.aStart.Col(), aMultiRange.aStart.Row(), aMultiRange.aStart.Tab(),
aMultiRange.aEnd.Col(), aMultiRange.aEnd.Row(), aMultiRange.aEnd.Tab(),
std::move(pUndoDoc), bMulti, &rPattern ) );
}
// While loading XML it is not necessary to ask HasAttrib. It needs too much time.
sal_uInt16 nExtFlags = 0;
if ( !bImportingXML && !bImportingXLSX )
rDocShell.UpdatePaintExt( nExtFlags, aMultiRange ); // content before the change
bool bChanged = false ;
rDoc.ApplySelectionPattern( rPattern, rMark, nullptr, &bChanged );
if (bChanged)
{
if ( !bImportingXML && !bImportingXLSX )
rDocShell.UpdatePaintExt( nExtFlags, aMultiRange ); // content after the change
if (!AdjustRowHeight( aMultiRange, true , bApi ))
rDocShell.PostPaint( aMultiRange, PaintPartFlags::Grid, nExtFlags );
else if (nExtFlags & SC_PF_LINES)
lcl_PaintAbove( rDocShell, aMultiRange ); // because of lines above the range
aModificator.SetDocumentModified();
}
return true ;
}
bool ScDocFunc::ApplyStyle( const ScMarkData& rMark, const OUString& rStyleName,
bool bApi )
{
ScDocument& rDoc = rDocShell.GetDocument();
bool bRecord = true ;
if ( !rDoc.IsUndoEnabled() )
bRecord = false ;
bool bImportingXML = rDoc.IsImportingXML();
// Cell formats can still be set if the range isn't editable only because of matrix formulas.
// #i62483# When loading XML, the check can be skipped altogether.
bool bOnlyNotBecauseOfMatrix;
if ( !bImportingXML && !rDoc.IsSelectionEditable( rMark, &bOnlyNotBecauseOfMatrix )
&& !bOnlyNotBecauseOfMatrix )
{
if (!bApi)
rDocShell.ErrorMessage(STR_PROTECTIONERR);
return false ;
}
ScStyleSheet* pStyleSheet = static_cast <ScStyleSheet*>( rDoc.GetStyleSheetPool()->Find(
rStyleName, SfxStyleFamily::Para ));
if (!pStyleSheet)
return false ;
ScDocShellModificator aModificator( rDocShell );
ScRange aMultiRange;
bool bMulti = rMark.IsMultiMarked();
if ( bMulti )
aMultiRange = rMark.GetMultiMarkArea();
else
aMultiRange = rMark.GetMarkArea();
if ( bRecord )
{
ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO ));
SCTAB nStartTab = aMultiRange.aStart.Tab();
SCTAB nTabCount = rDoc.GetTableCount();
pUndoDoc->InitUndo( rDoc, nStartTab, nStartTab );
for (const auto & rTab : rMark)
{
if (rTab >= nTabCount)
break ;
if (rTab != nStartTab)
pUndoDoc->AddUndoTab( rTab, rTab );
}
ScRange aCopyRange = aMultiRange;
aCopyRange.aStart.SetTab(0);
aCopyRange.aEnd.SetTab(nTabCount-1);
rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ATTRIB, bMulti, *pUndoDoc, &rMark );
rDocShell.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoSelectionStyle>(
rDocShell, rMark, aMultiRange, rStyleName, std::move(pUndoDoc) ) );
}
rDoc.ApplySelectionStyle( *pStyleSheet, rMark );
if (!AdjustRowHeight( aMultiRange, true , bApi ))
rDocShell.PostPaint( aMultiRange, PaintPartFlags::Grid );
aModificator.SetDocumentModified();
return true ;
}
namespace {
/**
* Check if this insertion attempt would end up cutting one or more pivot
* tables in half, which is not desirable.
*
* @return true if this insertion can be done safely without shearing any
* existing pivot tables, false otherwise.
*/
bool canInsertCellsByPivot(const ScRange& rRange, const ScMarkData& rMarkData, InsCellCmd eCmd, const ScDocument& rDoc)
{
if (!rDoc.HasPivotTable())
// This document has no pivot tables.
return true ;
const ScDPCollection* pDPs = rDoc.GetDPCollection();
ScRange aRange(rRange); // local copy
switch (eCmd)
{
case INS_INSROWS_BEFORE:
{
aRange.aStart.SetCol(0);
aRange.aEnd.SetCol(rDoc.MaxCol());
[[fallthrough]];
}
case INS_CELLSDOWN:
{
auto bIntersects = std::any_of(rMarkData.begin(), rMarkData.end(), [&pDPs, &aRange](const SCTAB& rTab) {
return pDPs->IntersectsTableByColumns(aRange.aStart.Col(), aRange.aEnd.Col(), aRange.aStart.Row(), rTab); });
if (bIntersects)
// This column range cuts through at least one pivot table. Not good.
return false ;
// Start row must be either at the top or above any pivot tables.
if (aRange.aStart.Row() < 0)
// I don't know how to handle this case.
return false ;
if (aRange.aStart.Row() == 0)
// First row is always allowed.
return true ;
ScRange aTest(aRange);
aTest.aStart.IncRow(-1); // Test one row up.
aTest.aEnd.SetRow(aTest.aStart.Row());
for (const auto & rTab : rMarkData)
{
aTest.aStart.SetTab(rTab);
aTest.aEnd.SetTab(rTab);
if (pDPs->HasTable(aTest))
return false ;
}
}
break ;
case INS_INSCOLS_BEFORE:
{
aRange.aStart.SetRow(0);
aRange.aEnd.SetRow(rDoc.MaxRow());
[[fallthrough]];
}
case INS_CELLSRIGHT:
{
auto bIntersects = std::any_of(rMarkData.begin(), rMarkData.end(), [&pDPs, &aRange](const SCTAB& rTab) {
return pDPs->IntersectsTableByRows(aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Row(), rTab); });
if (bIntersects)
// This column range cuts through at least one pivot table. Not good.
return false ;
// Start row must be either at the top or above any pivot tables.
if (aRange.aStart.Col() < 0)
// I don't know how to handle this case.
return false ;
if (aRange.aStart.Col() == 0)
// First row is always allowed.
return true ;
ScRange aTest(aRange);
aTest.aStart.IncCol(-1); // Test one column to the left.
aTest.aEnd.SetCol(aTest.aStart.Col());
for (const auto & rTab : rMarkData)
{
aTest.aStart.SetTab(rTab);
aTest.aEnd.SetTab(rTab);
if (pDPs->HasTable(aTest))
return false ;
}
}
break ;
default :
;
}
return true ;
}
/**
* Check if this deletion attempt would end up cutting one or more pivot
* tables in half, which is not desirable.
*
* @return true if this deletion can be done safely without shearing any
* existing pivot tables, false otherwise.
*/
bool canDeleteCellsByPivot(const ScRange& rRange, const ScMarkData& rMarkData, DelCellCmd eCmd, const ScDocument& rDoc)
{
if (!rDoc.HasPivotTable())
// This document has no pivot tables.
return true ;
const ScDPCollection* pDPs = rDoc.GetDPCollection();
ScRange aRange(rRange); // local copy
switch (eCmd)
{
case DelCellCmd::Rows:
{
aRange.aStart.SetCol(0);
aRange.aEnd.SetCol(rDoc.MaxCol());
[[fallthrough]];
}
case DelCellCmd::CellsUp:
{
auto bIntersects = std::any_of(rMarkData.begin(), rMarkData.end(), [&pDPs, &aRange](const SCTAB& rTab) {
return pDPs->IntersectsTableByColumns(aRange.aStart.Col(), aRange.aEnd.Col(), aRange.aStart.Row(), rTab); });
if (bIntersects)
// This column range cuts through at least one pivot table. Not good.
return false ;
ScRange aTest(aRange);
for (const auto & rTab : rMarkData)
{
aTest.aStart.SetTab(rTab);
aTest.aEnd.SetTab(rTab);
if (pDPs->HasTable(aTest))
return false ;
}
}
break ;
case DelCellCmd::Cols:
{
aRange.aStart.SetRow(0);
aRange.aEnd.SetRow(rDoc.MaxRow());
[[fallthrough]];
}
case DelCellCmd::CellsLeft:
{
auto bIntersects = std::any_of(rMarkData.begin(), rMarkData.end(), [&pDPs, &aRange](const SCTAB& rTab) {
return pDPs->IntersectsTableByRows(aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Row(), rTab); });
if (bIntersects)
// This column range cuts through at least one pivot table. Not good.
return false ;
ScRange aTest(aRange);
for (const auto & rTab : rMarkData)
{
aTest.aStart.SetTab(rTab);
aTest.aEnd.SetTab(rTab);
if (pDPs->HasTable(aTest))
return false ;
}
}
break ;
default :
;
}
return true ;
}
}
bool ScDocFunc::InsertCells( const ScRange& rRange, const ScMarkData* pTabMark, InsCellCmd eCmd,
bool bRecord, bool bApi, bool bPartOfPaste, size_t nInsertCount )
{
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() + nInsertCount;
SCROW nEndRow = aTargetRange.aEnd.Row() + nInsertCount;
SCTAB nEndTab = aTargetRange.aEnd.Tab();
if ( !rDoc.ValidRow(nStartRow) || !rDoc.ValidRow(nEndRow) )
{
OSL_FAIL("invalid row in InsertCells" );
return false ;
}
SCTAB nTabCount = rDoc.GetTableCount();
SCCOL nPaintStartCol = nStartCol;
SCROW nPaintStartRow = nStartRow;
SCCOL nPaintEndCol = nEndCol;
SCROW nPaintEndRow = nEndRow;
PaintPartFlags nPaintFlags = PaintPartFlags::Grid;
bool bSuccess;
ScTabViewShell* pViewSh = rDocShell.GetBestViewShell(); //preserve current cursor position
SCCOL nCursorCol = 0;
SCROW nCursorRow = 0;
if ( pViewSh )
{
nCursorCol = pViewSh->GetViewData().GetCurX();
nCursorRow = pViewSh->GetViewData().GetCurY();
}
if (bRecord && !rDoc.IsUndoEnabled())
bRecord = false ;
ScMarkData aMark(rDoc.GetSheetLimits());
if (pTabMark)
aMark = *pTabMark;
else
{
SCTAB nCount = 0;
for ( SCTAB i=0; i<nTabCount; i++ )
{
if ( !rDoc.IsScenario(i) )
{
nCount++;
if ( nCount == nEndTab+1 )
{
aMark.SelectTable( i, true );
break ;
}
}
}
}
ScMarkData aFullMark( aMark ); // including scenario sheets
for (const auto & rTab : aMark)
{
if (rTab >= nTabCount)
break ;
for ( SCTAB j = rTab+1; j<nTabCount && rDoc.IsScenario(j); j++ )
aFullMark.SelectTable( j, true );
}
SCTAB nSelCount = aMark.GetSelectCount();
// Adjust also related scenarios
SCCOL nMergeTestStartCol = nStartCol;
SCROW nMergeTestStartRow = nStartRow;
SCCOL nMergeTestEndCol = nEndCol;
SCROW nMergeTestEndRow = nEndRow;
ScRange aExtendMergeRange( aTargetRange );
if ( aTargetRange.aStart == aTargetRange.aEnd && rDoc.HasAttrib(aTargetRange, HasAttrFlags::Merged) )
{
rDoc.ExtendMerge( aExtendMergeRange );
rDoc.ExtendOverlapped( aExtendMergeRange );
nMergeTestEndCol = aExtendMergeRange.aEnd.Col();
nMergeTestEndRow = aExtendMergeRange.aEnd.Row();
nPaintEndCol = nMergeTestEndCol;
nPaintEndRow = nMergeTestEndRow;
}
if ( eCmd == INS_INSROWS_BEFORE || eCmd == INS_INSROWS_AFTER )
{
nMergeTestStartCol = 0;
nMergeTestEndCol = rDoc.MaxCol();
}
if ( eCmd == INS_INSCOLS_BEFORE || eCmd == INS_INSCOLS_AFTER )
{
nMergeTestStartRow = 0;
nMergeTestEndRow = rDoc.MaxRow();
}
if ( eCmd == INS_CELLSDOWN )
nMergeTestEndRow = rDoc.MaxRow();
if ( eCmd == INS_CELLSRIGHT )
nMergeTestEndCol = rDoc.MaxCol();
bool bNeedRefresh = false ;
SCCOL nEditTestEndCol = (eCmd==INS_INSCOLS_BEFORE || eCmd==INS_INSCOLS_AFTER) ? rDoc.MaxCol() : nMergeTestEndCol;
SCROW nEditTestEndRow = (eCmd==INS_INSROWS_BEFORE || eCmd==INS_INSROWS_AFTER) ? rDoc.MaxRow() : nMergeTestEndRow;
ScEditableTester aTester;
switch (eCmd)
{
case INS_INSCOLS_BEFORE:
aTester = ScEditableTester(
rDoc, sc::EditAction::InsertColumnsBefore, nMergeTestStartCol, 0, nMergeTestEndCol, rDoc.MaxRow(), aMark);
break ;
case INS_INSCOLS_AFTER:
aTester = ScEditableTester(
rDoc, sc::EditAction::InsertColumnsAfter, nMergeTestStartCol, 0, nMergeTestEndCol, rDoc.MaxRow(), aMark);
break ;
case INS_INSROWS_BEFORE:
aTester = ScEditableTester(
rDoc, sc::EditAction::InsertRowsBefore, 0, nMergeTestStartRow, rDoc.MaxCol(), nMergeTestEndRow, aMark);
break ;
case INS_INSROWS_AFTER:
aTester = ScEditableTester(
rDoc, sc::EditAction::InsertRowsAfter, 0, nMergeTestStartRow, rDoc.MaxCol(), nMergeTestEndRow, aMark);
break ;
default :
aTester = ScEditableTester(
rDoc, nMergeTestStartCol, nMergeTestStartRow, nEditTestEndCol, nEditTestEndRow, aMark);
}
if (!aTester.IsEditable())
{
if (!bApi)
rDocShell.ErrorMessage(aTester.GetMessageId());
return false ;
}
// Check if this insertion is allowed with respect to pivot table.
if (!canInsertCellsByPivot(aTargetRange, aMark, eCmd, rDoc))
{
if (!bApi)
rDocShell.ErrorMessage(STR_NO_INSERT_DELETE_OVER_PIVOT_TABLE);
return false ;
}
weld::WaitObject aWait( ScDocShell::GetActiveDialogParent() ); // important due to TrackFormulas at UpdateReference
ScDocumentUniquePtr pRefUndoDoc;
std::unique_ptr<ScRefUndoData> pUndoData;
if ( bRecord )
{
pRefUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO ));
pRefUndoDoc->InitUndo( rDoc, 0, nTabCount-1 );
// pRefUndoDoc is filled in InsertCol / InsertRow
pUndoData.reset(new ScRefUndoData( rDoc ));
rDoc.BeginDrawUndo();
}
// #i8302 : we unmerge overwhelming ranges, before insertion all the actions are put in the same ListAction
// the patch comes from mloiseleur and maoyg
bool bInsertMerge = false ;
std::vector<ScRange> qIncreaseRange;
OUString aUndo = ScResId( STR_UNDO_INSERTCELLS );
if (bRecord)
{
ViewShellId nViewShellId(-1);
if (pViewSh)
nViewShellId = pViewSh->GetViewShellId();
rDocShell.GetUndoManager()->EnterListAction( aUndo, aUndo, 0, nViewShellId );
}
std::unique_ptr<ScUndoRemoveMerge> pUndoRemoveMerge;
for (const SCTAB i : aMark)
{
if (i >= nTabCount)
break ;
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 C=94 H=96 G=94
¤ Dauer der Verarbeitung: 0.24 Sekunden
¤
*© Formatika GbR, Deutschland