/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* 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 <address.hxx>
#include <config_features.h>
#include <scitems.hxx>
#include <sfx2/app.hxx>
#include <svx/algitem.hxx>
#include <editeng/boxitem.hxx>
#include <editeng/editobj.hxx>
#include <editeng/langitem.hxx>
#include <editeng/justifyitem.hxx>
#include <o3tl/unit_conversion.hxx>
#include <sfx2/bindings.hxx>
#include <svl/numformat.hxx>
#include <svl/zforlist.hxx>
#include <svl/zformat.hxx>
#include <vcl/weld.hxx>
#include <vcl/virdev.hxx>
#include <stdlib.h>
#include <unotools/charclass.hxx>
#include <vcl/uitest/logger.hxx>
#include <vcl/uitest/eventdescription.hxx>
#include <osl/diagnose.h>
#include <viewfunc.hxx>
#include <tabvwsh.hxx>
#include <docsh.hxx>
#include <attrib.hxx>
#include <patattr.hxx>
#include <sc.hrc>
#include <undocell.hxx>
#include <undoblk.hxx>
#include <refundo.hxx>
#include <olinetab.hxx>
#include <rangenam.hxx>
#include <globstr.hrc>
#include <global.hxx>
#include <stlsheet.hxx>
#include <editutil.hxx>
#include <formulacell.hxx>
#include <scresid.hxx>
#include <inputhdl.hxx>
#include <scmod.hxx>
#include <inputopt.hxx>
#include <compiler.hxx>
#include <docfunc.hxx>
#include <appoptio.hxx>
#include <sizedev.hxx>
#include <editable.hxx>
#include <scui_def.hxx>
#include <funcdesc.hxx>
#include <docuno.hxx>
#include <cellsuno.hxx>
#include <tokenarray.hxx>
#include <rowheightcontext.hxx>
#include <comphelper/lok.hxx>
#include <conditio.hxx>
#include <columnspanset.hxx>
#include <stringutil.hxx>
#include <SparklineList.hxx>
#include <memory>
static void ShowFilteredRows(ScDocument& rDoc, SCTAB nTab, SCCOLROW nStartNo, SCCOLR
OW nEndNo,
bool bShow)
{
SCROW nFirstRow = nStartNo;
SCROW nLastRow = nStartNo;
do
{
if (!rDoc.RowFiltered(nFirstRow, nTab, nullptr, &nLastRow))
rDoc.ShowRows(nFirstRow, nLastRow < nEndNo ? nLastRow : nEndNo, nTab, bShow);
nFirstRow = nLastRow + 1;
} while (nFirstRow <= nEndNo);
}
static void lcl_PostRepaintCondFormat( const ScConditionalFormat *pCondFmt, ScDocShell &rDocSh )
{
if ( pCondFmt )
{
const ScRangeList& rRanges = pCondFmt->GetRange();
rDocSh.PostPaint( rRanges, PaintPartFlags::All );
}
}
static void lcl_PostRepaintSparkLine(sc::SparklineList* pSparklineList, const ScRange& rRange,
ScDocShell& rDocSh)
{
if (pSparklineList)
{
for (auto & rSparkLineGroup : pSparklineList->getSparklineGroups())
{
for (auto & rSparkline : pSparklineList->getSparklinesFor(rSparkLineGroup))
{
if (rSparkline->getInputRange().Contains(rRange))
{
rDocSh.PostPaint(
ScRange(rSparkline->getColumn(), rSparkline->getRow(), rRange.aStart.Tab()),
PaintPartFlags::All, SC_PF_TESTMERGE);
}
}
}
}
}
ScViewFunc::ScViewFunc( vcl::Window* pParent, ScDocShell& rDocSh, ScTabViewShell* pViewShell ) :
ScTabView( pParent, rDocSh, pViewShell ),
bFormatValid( false )
{
}
ScViewFunc::~ScViewFunc()
{
}
namespace {
struct FormulaProcessingContext
{
std::shared_ptr<ScAddress> aPos;
std::shared_ptr<ScCompiler> aComp;
std::shared_ptr<ScDocShellModificator> aModificator;
std::shared_ptr<ScTokenArray> pArr;
std::shared_ptr<ScTokenArray> pArrFirst;
std::shared_ptr<EditTextObject> xTextObject;
ScMarkData aMark;
ScViewFunc& rViewFunc;
OUString aCorrectedFormula;
OUString aFormula;
OUString aString;
SCCOL nCol;
SCROW nRow;
SCTAB nTab;
bool bMatrixExpand;
bool bNumFmtChanged;
bool bRecord;
ScViewData& GetViewData() const
{
return rViewFunc.GetViewData();
}
ScDocFunc& GetDocFunc() const
{
return GetViewData().GetDocFunc();
}
ScDocument& GetDoc() const
{
return GetViewData().GetDocument();
}
};
void collectUIInformation(std::map<OUString, OUString>&& aParameters, const OUString& rAction)
{
EventDescription aDescription;
aDescription.aID = "grid_window" ;
aDescription.aAction = rAction;
aDescription.aParameters = std::move(aParameters);
aDescription.aParent = "MainWindow" ;
aDescription.aKeyWord = "ScGridWinUIObject" ;
UITestLogger::getInstance().logEvent(aDescription);
}
}
void ScViewFunc::StartFormatArea()
{
// anything to do?
if (!ScModule::get()->GetInputOptions().GetExtendFormat())
return ;
// start only with single cell (marked or cursor position)
ScRange aMarkRange;
bool bOk = (GetViewData().GetSimpleArea( aMarkRange ) == SC_MARK_SIMPLE);
if ( bOk && aMarkRange.aStart != aMarkRange.aEnd )
bOk = false ;
if (bOk)
{
bFormatValid = true ;
aFormatSource = aMarkRange.aStart;
aFormatArea = ScRange( aFormatSource );
}
else
bFormatValid = false ; // discard old range
}
bool ScViewFunc::TestFormatArea( SCCOL nCol, SCROW nRow, SCTAB nTab, bool bAttrChanged )
{
// anything to do?
if (!ScModule::get()->GetInputOptions().GetExtendFormat())
return false ;
// Test: treat input with numberformat (bAttrChanged) always as new Attribute
// (discard old Area ). If not wanted, discard if-statement
if ( bAttrChanged )
{
StartFormatArea();
return false ;
}
//! Test if cell empty ???
bool bFound = false ;
ScRange aNewRange = aFormatArea;
if ( bFormatValid && nTab == aFormatSource.Tab() )
{
if ( nRow >= aFormatArea.aStart.Row() && nRow <= aFormatArea.aEnd.Row() )
{
// within range?
if ( nCol >= aFormatArea.aStart.Col() && nCol <= aFormatArea.aEnd.Col() )
{
bFound = true ; // do not change range
}
// left ?
if ( nCol+1 == aFormatArea.aStart.Col() )
{
bFound = true ;
aNewRange.aStart.SetCol( nCol );
}
// right ?
if ( nCol == aFormatArea.aEnd.Col()+1 )
{
bFound = true ;
aNewRange.aEnd.SetCol( nCol );
}
}
if ( nCol >= aFormatArea.aStart.Col() && nCol <= aFormatArea.aEnd.Col() )
{
// top ?
if ( nRow+1 == aFormatArea.aStart.Row() )
{
bFound = true ;
aNewRange.aStart.SetRow( nRow );
}
// bottom ?
if ( nRow == aFormatArea.aEnd.Row()+1 )
{
bFound = true ;
aNewRange.aEnd.SetRow( nRow );
}
}
}
if (bFound)
aFormatArea = aNewRange; // extend
else
bFormatValid = false ; // outside of range -> break
return bFound;
}
void ScViewFunc::DoAutoAttributes( SCCOL nCol, SCROW nRow, SCTAB nTab,
bool bAttrChanged )
{
ScDocShell& rDocSh = GetViewData().GetDocShell();
ScDocument& rDoc = rDocSh.GetDocument();
const ScPatternAttr* pSource = rDoc.GetPattern(
aFormatSource.Col(), aFormatSource.Row(), nTab );
if ( !pSource->GetItem(ATTR_MERGE).IsMerged() )
{
ScRange aRange( nCol, nRow, nTab, nCol, nRow, nTab );
ScMarkData aMark(rDoc.GetSheetLimits());
aMark.SetMarkArea( aRange );
ScDocFunc &rFunc = GetViewData().GetDocFunc();
// pOldPattern is only valid until call to ApplyAttributes!
const ScPatternAttr* pOldPattern = rDoc.GetPattern( nCol, nRow, nTab );
const ScStyleSheet* pSrcStyle = pSource->GetStyleSheet();
if ( pSrcStyle && pSrcStyle != pOldPattern->GetStyleSheet() )
rFunc.ApplyStyle( aMark, pSrcStyle->GetName(), false );
rFunc.ApplyAttributes( aMark, *pSource, false );
}
if ( bAttrChanged ) // value entered with number format?
aFormatSource.Set( nCol, nRow, nTab ); // then set a new source
}
// additional routines
void ScViewData::setupSizeDeviceProviderForColWidth(const ScSizeDeviceProvider& rProv, Fraction& rZoomX, Fraction& rZoomY, double & rPPTX, double &rPPTY)
{
if (rProv.IsPrinter())
{
rPPTX = rProv.GetPPTX();
rPPTY = rProv.GetPPTY();
rZoomX = rZoomY = Fraction(1, 1);
}
else
{
rPPTX = GetPPTX();
rPPTY = GetPPTY();
rZoomX = GetZoomX();
rZoomY = GetZoomY();
}
}
sal_uInt16 ScViewFunc::GetOptimalColWidth( SCCOL nCol, SCTAB nTab, bool bFormula )
{
ScDocShell& rDocSh = GetViewData().GetDocShell();
ScDocument& rDoc = rDocSh.GetDocument();
ScMarkData& rMark = GetViewData().GetMarkData();
ScSizeDeviceProvider aProv(rDocSh);
Fraction aZoomX, aZoomY;
double nPPTX, nPPTY;
GetViewData().setupSizeDeviceProviderForColWidth(aProv, aZoomX, aZoomY, nPPTX, nPPTY);
sal_uInt16 nTwips = rDoc.GetOptimalColWidth( nCol, nTab, aProv.GetDevice(),
nPPTX, nPPTY, aZoomX, aZoomY, bFormula, &rMark );
return nTwips;
}
bool ScViewFunc::SelectionEditable( bool * pOnlyNotBecauseOfMatrix /* = NULL */ )
{
bool bRet;
ScDocument& rDoc = GetViewData().GetDocument();
ScMarkData& rMark = GetViewData().GetMarkData();
if (rMark.IsMarked() || rMark.IsMultiMarked())
bRet = rDoc.IsSelectionEditable( rMark, pOnlyNotBecauseOfMatrix );
else
{
SCCOL nCol = GetViewData().GetCurX();
SCROW nRow = GetViewData().GetCurY();
SCTAB nTab = GetViewData().GetTabNo();
bRet = rDoc.IsBlockEditable( nTab, nCol, nRow, nCol, nRow,
pOnlyNotBecauseOfMatrix );
}
return bRet;
}
static bool lcl_FunctionKnown( sal_uInt16 nOpCode )
{
const ScFunctionList* pFuncList = ScGlobal::GetStarCalcFunctionList();
if ( pFuncList )
{
sal_uLong nCount = pFuncList->GetCount();
for (sal_uLong i=0; i<nCount; i++)
if ( pFuncList->GetFunction(i)->nFIndex == nOpCode )
return true ;
}
return false ;
}
static bool lcl_AddFunction( ScAppOptions& rAppOpt, sal_uInt16 nOpCode )
{
sal_uInt16 nOldCount = rAppOpt.GetLRUFuncListCount();
sal_uInt16* pOldList = rAppOpt.GetLRUFuncList();
sal_uInt16 nPos;
for (nPos=0; nPos<nOldCount; nPos++)
if (pOldList[nPos] == nOpCode) // is the function already in the list?
{
if ( nPos == 0 )
return false ; // already at the top -> no change
// count doesn't change, so the original array is modified
for (sal_uInt16 nCopy=nPos; nCopy>0; nCopy--)
pOldList[nCopy] = pOldList[nCopy-1];
pOldList[0] = nOpCode;
return true ; // list has changed
}
if ( !lcl_FunctionKnown( nOpCode ) )
return false ; // not in function list -> no change
sal_uInt16 nNewCount = std::min( static_cast <sal_uInt16>(nOldCount + 1), sal_uInt16(LRU_MAX) );
sal_uInt16 nNewList[LRU_MAX];
nNewList[0] = nOpCode;
for (nPos=1; nPos<nNewCount; nPos++)
nNewList[nPos] = pOldList[nPos-1];
rAppOpt.SetLRUFuncList( nNewList, nNewCount );
return true ; // list has changed
}
namespace HelperNotifyChanges
{
static void NotifyIfChangesListeners(const ScDocShell &rDocShell, const ScMarkData& rMark,
SCCOL nCol, SCROW nRow, const OUString& rType = u"cell-change" _ustr)
{
ScModelObj* pModelObj = rDocShell.GetModel();
ScRangeList aChangeRanges;
for (const auto & rTab : rMark)
aChangeRanges.push_back( ScRange( nCol, nRow, rTab ) );
if (getMustPropagateChangesModel(pModelObj))
Notify(*pModelObj, aChangeRanges, rType);
else
{
Notify(*pModelObj, aChangeRanges, isDataAreaInvalidateType(rType)
? u"data-area-invalidate" _ustr : u"data-area-extend" _ustr);
}
}
}
namespace
{
class AutoCorrectQuery : public weld::MessageDialogController
{
private :
std::unique_ptr<weld::TextView> m_xError;
public :
AutoCorrectQuery(weld::Window* pParent, const OUString& rFormula)
: weld::MessageDialogController(pParent, u"modules/scalc/ui/warnautocorrect.ui" _ustr, u"WarnAutoCorrect" _ustr, u"grid" _ustr)
, m_xError(m_xBuilder->weld_text_view(u"error" _ustr))
{
m_xDialog->set_default_response(RET_YES);
const int nMaxWidth = m_xError->get_approximate_digit_width() * 65;
const int nMaxHeight = m_xError->get_height_rows(6);
m_xError->set_size_request(nMaxWidth, nMaxHeight);
m_xError->set_text(rFormula);
}
};
}
namespace
{
void runAutoCorrectQueryAsync(const std::shared_ptr<FormulaProcessingContext>& context);
void performAutoFormatAndUpdate(std::u16string_view rString, const ScMarkData& rMark, SCCOL nCol,
SCROW nRow, SCTAB nTab, bool bNumFmtChanged, bool bRecord,
const std::shared_ptr<ScDocShellModificator>& pModificator,
ScViewFunc& rViewFunc)
{
bool bAutoFormat = rViewFunc.TestFormatArea(nCol, nRow, nTab, bNumFmtChanged);
if (bAutoFormat)
rViewFunc.DoAutoAttributes(nCol, nRow, nTab, bNumFmtChanged);
ScViewData& rViewData = rViewFunc.GetViewData();
ScDocShell& rDocSh = rViewData.GetDocShell();
rDocSh.UpdateOle(rViewData);
const OUString aType(rString.empty() ? u"delete-content" : u"cell-change" );
HelperNotifyChanges::NotifyIfChangesListeners(rDocSh, rMark, nCol, nRow, aType);
if (bRecord)
{
ScDocFunc &rFunc = rViewData.GetDocFunc();
rFunc.EndListAction();
}
pModificator->SetDocumentModified();
ScDocument& rDoc = rViewData.GetDocument();
lcl_PostRepaintCondFormat(rDoc.GetCondFormat(nCol, nRow, nTab), rDocSh);
lcl_PostRepaintSparkLine(rDoc.GetSparklineList(nTab), ScRange(nCol, nRow, nTab), rDocSh);
}
void finalizeFormulaProcessing(const std::shared_ptr<FormulaProcessingContext>& context)
{
// to be used in multiple tabs, the formula must be compiled anew
// via ScFormulaCell copy-ctor because of RangeNames,
// the same code-array for all cells is not possible.
// If the array has an error, (it) must be RPN-erased in the newly generated
// cells and the error be set explicitly, so that
// via FormulaCell copy-ctor and Interpreter it will be, when possible,
// ironed out again, too intelligent... e.g.: =1))
FormulaError nError = context->pArr->GetCodeError();
if ( nError == FormulaError::NONE )
{
// update list of recent functions with all functions that
// are not within parentheses
ScModule* pScMod = ScModule::get();
ScAppOptions aAppOpt = pScMod->GetAppOptions();
bool bOptChanged = false ;
formula::FormulaToken** ppToken = context->pArr->GetArray();
sal_uInt16 nTokens = context->pArr->GetLen();
sal_uInt16 nLevel = 0;
for (sal_uInt16 nTP=0; nTP<nTokens; nTP++)
{
formula::FormulaToken* pTok = ppToken[nTP];
OpCode eOp = pTok->GetOpCode();
if ( eOp == ocOpen )
++nLevel;
else if ( eOp == ocClose && nLevel )
--nLevel;
if ( nLevel == 0 && pTok->IsFunction() &&
lcl_AddFunction( aAppOpt, sal::static_int_cast<sal_uInt16>( eOp ) ) )
bOptChanged = true ;
}
if ( bOptChanged )
{
pScMod->SetAppOptions(aAppOpt);
}
if (context->bMatrixExpand)
{
// If the outer function/operator returns an array/matrix then
// enter a matrix formula. ScViewFunc::EnterMatrix() takes care
// of selection/mark of the result dimensions or preselected
// mark. If the user wanted less or a single cell then should
// mark such prior to entering the formula.
const formula::FormulaToken* pToken = context->pArr->LastRPNToken();
if (pToken && (formula::FormulaCompiler::IsMatrixFunction( pToken->GetOpCode())
|| pToken->IsInForceArray()))
{
// Discard this (still empty here) Undo action,
// EnterMatrix() will create its own.
if (context->bRecord)
context->GetDocFunc().EndListAction();
// Use corrected formula string.
context->rViewFunc.EnterMatrix( context->aFormula, context->GetDoc().GetGrammar());
return ;
}
}
}
ScFormulaCell aCell(context->GetDoc(), *context->aPos, std::move(*context->pArr), formula::FormulaGrammar::GRAM_DEFAULT, ScMatrixMode::NONE);
SCTAB i;
SvNumberFormatter* pFormatter = context->GetDoc().GetFormatTable();
for (const auto & rTab : context->aMark)
{
i = rTab;
context->aPos->SetTab( i );
const sal_uInt32 nIndex = context->GetDoc().GetAttr(
context->nCol, context->nRow, i, ATTR_VALUE_FORMAT )->GetValue();
const SvNumFormatType nType = pFormatter->GetType( nIndex);
if (nType == SvNumFormatType::TEXT ||
((context->aString[0] == '+' || context->aString[0] == '-' ) && nError != FormulaError::NONE && context->aString == context->aFormula))
{
if ( context->xTextObject )
{
// A clone of context->xTextObject will be stored in the cell.
context->GetDocFunc().SetEditCell(*(context->aPos), *context->xTextObject, true );
}
else
context->GetDocFunc().SetStringCell(*(context->aPos), context->aFormula, true );
}
else
{
ScFormulaCell* pCell = new ScFormulaCell( aCell, context->GetDoc(), *(context->aPos) );
if ( nError != FormulaError::NONE )
{
pCell->GetCode()->DelRPN();
pCell->SetErrCode( nError );
if (pCell->GetCode()->IsHyperLink())
pCell->GetCode()->SetHyperLink(false );
}
if (nType == SvNumFormatType::LOGICAL)
{
// Reset to General so the actual format can be determined
// after the cell has been interpreted. A sticky boolean
// number format is highly likely unwanted... see tdf#75650.
// General of same locale as current number format.
const SvNumberformat* pEntry = pFormatter->GetEntry( nIndex);
const LanguageType nLang = (pEntry ? pEntry->GetLanguage() : ScGlobal::eLnge);
const sal_uInt32 nFormat = pFormatter->GetStandardFormat( SvNumFormatType::NUMBER, nLang);
ScPatternAttr aPattern(context->GetDoc().getCellAttributeHelper());
aPattern.GetItemSet().Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nFormat));
ScMarkData aMark(context->GetDoc().GetSheetLimits());
aMark.SelectTable( i, true );
aMark.SetMarkArea( ScRange( *(context->aPos)));
context->GetDocFunc().ApplyAttributes( aMark, aPattern, false );
context->bNumFmtChanged = true ;
}
context->GetDocFunc().SetFormulaCell(*(context->aPos), pCell, true );
}
}
performAutoFormatAndUpdate(context->aString, context->aMark, context->nCol,
context->nRow, context->nTab, context->bNumFmtChanged,
context->bRecord, context->aModificator, context->rViewFunc);
}
void parseAndCorrectFormula(std::shared_ptr<FormulaProcessingContext> context)
{
bool bAddEqual = false ;
context->pArr = context->aComp->CompileString(context->aFormula);
bool bCorrected = context->aComp->IsCorrected();
if (bCorrected) {
context->pArrFirst = context->pArr;
context->pArr = context->aComp->CompileString(context->aComp->GetCorrectedFormula());
}
if (context->pArr->GetCodeError() == FormulaError::NONE) {
bAddEqual = true ;
context->aComp->CompileTokenArray();
bCorrected |= context->aComp->IsCorrected();
}
if (bCorrected) {
context->aCorrectedFormula = bAddEqual ? "=" + context->aComp->GetCorrectedFormula()
: context->aComp->GetCorrectedFormula();
if (context->aCorrectedFormula.getLength() == 1) {
// empty formula, just '='
if (context->pArrFirst)
context->pArr = context->pArrFirst;
}
else
{
runAutoCorrectQueryAsync(context);
return ;
}
}
finalizeFormulaProcessing(context);
}
void runAutoCorrectQueryAsync(const std::shared_ptr<FormulaProcessingContext>& context)
{
auto aQueryBox = std::make_shared<AutoCorrectQuery>(context->GetViewData().GetDialogParent(), context->aCorrectedFormula);
weld::DialogController::runAsync(aQueryBox, [context] (int nResult)
{
if (nResult == RET_YES) {
context->aFormula = context->aCorrectedFormula;
parseAndCorrectFormula(context);
} else {
if (context->pArrFirst)
context->pArr = context->pArrFirst;
finalizeFormulaProcessing(context);
}
});
}
}
// actual functions
// input - undo OK
void ScViewFunc::EnterData( SCCOL nCol, SCROW nRow, SCTAB nTab,
const OUString& rString,
const EditTextObject* pData,
bool bMatrixExpand )
{
ScDocument& rDoc = GetViewData().GetDocument();
ScMarkData aMark(GetViewData().GetMarkData());
bool bRecord = rDoc.IsUndoEnabled();
SCTAB i;
ScDocShell& rDocSh = GetViewData().GetDocShell();
ScDocFunc &rFunc = GetViewData().GetDocFunc();
std::shared_ptr<ScDocShellModificator> xModificator = std::make_shared<ScDocShellModificator>(rDocSh);
ScEditableTester aTester( rDoc, nCol,nRow, nCol,nRow, aMark );
if (!aTester.IsEditable())
{
ErrorMessage(aTester.GetMessageId());
PaintArea(nCol, nRow, nCol, nRow); // possibly the edit-engine is still painted there
return ;
}
if ( bRecord )
rFunc.EnterListAction( STR_UNDO_ENTERDATA );
bool bFormula = false ;
// do not check formula if it is a text cell
sal_uInt32 format = rDoc.GetNumberFormat( nCol, nRow, nTab );
SvNumberFormatter* pFormatter = rDoc.GetFormatTable();
// a single '=' character is handled as string (needed for special filters)
if ( pFormatter->GetType(format) != SvNumFormatType::TEXT && rString.getLength() > 1 )
{
if ( rString[0] == '=' )
{
// handle as formula
bFormula = true ;
}
else if ( rString[0] == '+' || rString[0] == '-' )
{
// if there is more than one leading '+' or '-' character, remove the additional ones
sal_Int32 nIndex = 1;
sal_Int32 nLen = rString.getLength();
while ( nIndex < nLen && ( rString[ nIndex ] == '+' || rString[ nIndex ] == '-' ) )
{
++nIndex;
}
OUString aString = rString.replaceAt( 1, nIndex - 1, u"" );
// if the remaining part without the leading '+' or '-' character
// is non-empty and not a number, handle as formula
if ( aString.getLength() > 1 )
{
double fNumber = 0;
if ( !pFormatter->IsNumberFormat( aString, format, fNumber ) )
{
bFormula = true ;
}
}
}
}
bool bNumFmtChanged = false ;
if ( bFormula )
{ // formula, compile with autoCorrection
i = aMark.GetFirstSelected();
auto xPosPtr = std::make_shared<ScAddress>(nCol, nRow, i);
auto xCompPtr = std::make_shared<ScCompiler>(rDoc, *xPosPtr, rDoc.GetGrammar(), true , false );
std::unique_ptr<EditTextObject> xTextObject(pData ? pData->Clone() : nullptr);
//2do: enable/disable autoCorrection via calcoptions
xCompPtr->SetAutoCorrection( true );
if ( rString[0] == '+' || rString[0] == '-' )
{
xCompPtr->SetExtendedErrorDetection( ScCompiler::EXTENDED_ERROR_DETECTION_NAME_BREAK );
}
OUString aFormula( rString );
FormulaProcessingContext context_instance{
std::move(xPosPtr), std::move(xCompPtr), std::move(xModificator), nullptr,
nullptr, std::move(xTextObject), std::move(aMark), *this ,
OUString(), aFormula, rString, nCol,
nRow, nTab, bMatrixExpand, bNumFmtChanged,
bRecord
};
parseAndCorrectFormula(std::make_shared<FormulaProcessingContext>(context_instance));
}
else
{
ScFieldEditEngine& rEngine = rDoc.GetEditEngine();
for (const auto & rTab : aMark)
{
bool bNumFmtSet = false ;
const ScAddress aScAddress(nCol, nRow, rTab);
// tdf#104902 - handle embedded newline
if (ScStringUtil::isMultiline(rString))
{
rEngine.SetTextCurrentDefaults(rString);
rDoc.SetEditText(aScAddress, rEngine.CreateTextObject());
rDocSh.AdjustRowHeight(nRow, nRow, rTab);
}
else
{
rFunc.SetNormalString(bNumFmtSet, aScAddress, rString, false );
}
if (bNumFmtSet)
{
/* FIXME: if set on any sheet results in changed only on
* sheet nTab for TestFormatArea() and DoAutoAttributes() */
bNumFmtChanged = true ;
}
}
performAutoFormatAndUpdate(rString, aMark, nCol, nRow, nTab, bNumFmtChanged, bRecord, xModificator, *this );
}
}
// enter value in single cell (on nTab only)
void ScViewFunc::EnterValue( SCCOL nCol, SCROW nRow, SCTAB nTab, const double & rValue )
{
ScDocument& rDoc = GetViewData().GetDocument();
ScDocShell& rDocSh = GetViewData().GetDocShell();
bool bUndo(rDoc.IsUndoEnabled());
ScDocShellModificator aModificator( rDocSh );
ScEditableTester aTester( rDoc, nTab, nCol,nRow, nCol,nRow );
if (aTester.IsEditable())
{
ScAddress aPos( nCol, nRow, nTab );
ScCellValue aUndoCell;
if (bUndo)
aUndoCell.assign(rDoc, aPos);
rDoc.SetValue( nCol, nRow, nTab, rValue );
// because of ChangeTrack after change in document
if (bUndo)
{
rDocSh.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoEnterValue>(rDocSh, aPos, aUndoCell, rValue));
}
rDocSh.PostPaintCell( aPos );
rDocSh.UpdateOle(GetViewData());
aModificator.SetDocumentModified();
}
else
ErrorMessage(aTester.GetMessageId());
}
void ScViewFunc::EnterData( SCCOL nCol, SCROW nRow, SCTAB nTab,
const EditTextObject& rData, bool bTestSimple )
{
ScDocShell& rDocSh = GetViewData().GetDocShell();
ScMarkData& rMark = GetViewData().GetMarkData();
ScDocument& rDoc = rDocSh.GetDocument();
bool bRecord = rDoc.IsUndoEnabled();
ScDocShellModificator aModificator( rDocSh );
ScEditableTester aTester( rDoc, nTab, nCol,nRow, nCol,nRow );
if (aTester.IsEditable())
{
// test for attribute
bool bSimple = false ;
bool bCommon = false ;
std::unique_ptr<ScPatternAttr> pCellAttrs;
OUString aString;
const ScPatternAttr* pOldPattern = rDoc.GetPattern( nCol, nRow, nTab );
ScTabEditEngine aEngine( *pOldPattern, rDoc.GetEnginePool(), rDoc );
aEngine.SetTextCurrentDefaults(rData);
if (bTestSimple) // test, if simple string without attribute
{
ScEditAttrTester aAttrTester( &aEngine );
bSimple = !aAttrTester.NeedsObject();
bCommon = aAttrTester.NeedsCellAttr();
// formulas have to be recognized even if they're formatted
// (but common attributes are still collected)
if (!bSimple)
{
OUString aParStr(aEngine.GetText( 0 ));
if ( aParStr[0] == '=' )
bSimple = true ;
}
if (bCommon) // attribute for tab
{
pCellAttrs.reset(new ScPatternAttr( *pOldPattern ));
pCellAttrs->GetFromEditItemSet( &aAttrTester.GetAttribs() );
//! remove common attributes from EditEngine?
}
}
// #i97726# always get text for "repeat" of undo action
aString = ScEditUtil::GetMultilineString(aEngine);
// undo
std::unique_ptr<EditTextObject> pUndoData;
ScUndoEnterData::ValuesType aOldValues;
if (bRecord && !bSimple)
{
for (const auto & rTab : rMark)
{
ScUndoEnterData::Value aOldValue;
aOldValue.mnTab = rTab;
aOldValue.maCell.assign(rDoc, ScAddress(nCol, nRow, rTab));
aOldValues.push_back(aOldValue);
}
pUndoData = rData.Clone();
}
// enter data
if (bCommon)
rDoc.ApplyPattern(nCol,nRow,nTab,*pCellAttrs); //! undo
if (bSimple)
{
if (bCommon)
AdjustRowHeight(nRow,nRow,true );
EnterData( nCol, nRow, nTab, aString, nullptr, true /*bMatrixExpand*/);
}
else
{
for (const auto & rTab : rMark)
{
ScAddress aPos(nCol, nRow, rTab);
rDoc.SetEditText(aPos, rData, rDoc.GetEditPool());
}
if ( bRecord )
{ // because of ChangeTrack current first
rDocSh.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoEnterData>(rDocSh, ScAddress(nCol,nRow,nTab), aOldValues, aString, std::move(pUndoData)));
}
HideAllCursors();
AdjustRowHeight(nRow,nRow,true );
for (const auto & rTab : rMark)
rDocSh.PostPaintCell( nCol, nRow, rTab );
ShowAllCursors();
rDocSh.UpdateOle(GetViewData());
bool bIsEmpty = rData.GetParagraphCount() == 0
|| (rData.GetParagraphCount() == 1 && !rData.HasText(0));
const OUString aType(bIsEmpty ? u"delete-content" : u"cell-change" );
HelperNotifyChanges::NotifyIfChangesListeners(rDocSh, rMark, nCol, nRow, aType);
aModificator.SetDocumentModified();
}
lcl_PostRepaintCondFormat( rDoc.GetCondFormat( nCol, nRow, nTab ), rDocSh );
}
else
{
ErrorMessage(aTester.GetMessageId());
PaintArea( nCol, nRow, nCol, nRow ); // possibly the edit-engine is still painted there
}
}
void ScViewFunc::EnterDataAtCursor( const OUString& rString )
{
SCCOL nPosX = GetViewData().GetCurX();
SCROW nPosY = GetViewData().GetCurY();
SCTAB nTab = GetViewData().GetTabNo();
EnterData( nPosX, nPosY, nTab, rString );
// tdf#154174: update repeated data in the cell
GetViewData().GetViewShell()->UpdateInputHandler();
}
void ScViewFunc::EnterMatrix( const OUString& rString, ::formula::FormulaGrammar::Grammar eGram )
{
ScViewData& rData = GetViewData();
const SCCOL nCol = rData.GetCurX();
const SCROW nRow = rData.GetCurY();
const ScMarkData& rMark = rData.GetMarkData();
if ( !rMark.IsMarked() && !rMark.IsMultiMarked() )
{
// nothing marked -> temporarily calculate block
// with size of result formula to get the size
ScDocument& rDoc = rData.GetDocument();
SCTAB nTab = rData.GetTabNo();
ScFormulaCell aFormCell( rDoc, ScAddress(nCol,nRow,nTab), rString, eGram, ScMatrixMode::Formula );
SCSIZE nSizeX;
SCSIZE nSizeY;
aFormCell.GetResultDimensions( nSizeX, nSizeY );
if ( nSizeX != 0 && nSizeY != 0 &&
nCol+nSizeX-1 <= sal::static_int_cast<SCSIZE>(rDoc.MaxCol()) &&
nRow+nSizeY-1 <= sal::static_int_cast<SCSIZE>(rDoc.MaxRow()) )
{
ScRange aResult( nCol, nRow, nTab,
sal::static_int_cast<SCCOL>(nCol+nSizeX-1),
sal::static_int_cast<SCROW>(nRow+nSizeY-1), nTab );
MarkRange( aResult, false );
}
}
ScRange aRange;
if (rData.GetSimpleArea(aRange) == SC_MARK_SIMPLE)
{
ScDocShell& rDocSh = rData.GetDocShell();
bool bSuccess = rDocSh.GetDocFunc().EnterMatrix(
aRange, &rMark, nullptr, rString, false , false , OUString(), eGram );
if (bSuccess)
rDocSh.UpdateOle(GetViewData());
else
PaintArea(nCol, nRow, nCol, nRow); // possibly the edit-engine is still painted there
}
else
ErrorMessage(STR_NOMULTISELECT);
}
SvtScriptType ScViewFunc::GetSelectionScriptType()
{
SvtScriptType nScript = SvtScriptType::NONE;
ScDocument& rDoc = GetViewData().GetDocument();
const ScMarkData& rMark = GetViewData().GetMarkData();
if ( !rMark.IsMarked() && !rMark.IsMultiMarked() )
{
// no selection -> cursor
nScript = rDoc.GetScriptType( GetViewData().GetCurX(),
GetViewData().GetCurY(), GetViewData().GetTabNo());
}
else
{
ScRangeList aRanges;
rMark.FillRangeListWithMarks( &aRanges, false );
nScript = rDoc.GetRangeScriptType(aRanges);
}
if (nScript == SvtScriptType::NONE)
nScript = ScGlobal::GetDefaultScriptType();
return nScript;
}
static void ShrinkToDataArea(ScMarkData& rFuncMark, const ScDocument& rDoc);
const ScPatternAttr* ScViewFunc::GetSelectionPattern()
{
// Don't use UnmarkFiltered in slot state functions, for performance reasons.
// The displayed state is always that of the whole selection including filtered rows.
ScMarkData aMark = GetViewData().GetMarkData();
ScDocument& rDoc = GetViewData().GetDocument();
// tdf#155368 if the selection is the whole sheet, we need to shrink the mark area, otherwise
// we will not return a consistent result
// (consistent compared to what happens in ScViewFunc::ApplySelectionPattern)
ShrinkToDataArea( aMark, rDoc );
if ( aMark.IsMarked() || aMark.IsMultiMarked() )
{
// MarkToMulti is no longer necessary for rDoc.GetSelectionPattern
const ScPatternAttr* pAttr = rDoc.GetSelectionPattern( aMark );
return pAttr;
}
else
{
SCCOL nCol = GetViewData().GetCurX();
SCROW nRow = GetViewData().GetCurY();
SCTAB nTab = GetViewData().GetTabNo();
// copy sheet selection
aMark.SetMarkArea( ScRange( nCol, nRow, nTab ) );
const ScPatternAttr* pAttr = rDoc.GetSelectionPattern( aMark );
return pAttr;
}
}
void ScViewFunc::GetSelectionFrame(
std::shared_ptr<SvxBoxItem>& rLineOuter,
std::shared_ptr<SvxBoxInfoItem>& rLineInner )
{
ScDocument& rDoc = GetViewData().GetDocument();
const ScMarkData& rMark = GetViewData().GetMarkData();
if ( rMark.IsMarked() || rMark.IsMultiMarked() )
{
rDoc.GetSelectionFrame( rMark, *rLineOuter, *rLineInner );
}
else
{
const ScPatternAttr* pAttrs =
rDoc.GetPattern( GetViewData().GetCurX(),
GetViewData().GetCurY(),
GetViewData().GetTabNo() );
rLineOuter.reset(pAttrs->GetItem(ATTR_BORDER).Clone());
rLineInner.reset(pAttrs->GetItem(ATTR_BORDER_INNER).Clone());
rLineInner->SetTable(false );
rLineInner->SetDist(true );
rLineInner->SetMinDist(false );
}
}
// apply attribute - undo OK
//
// complete set ( ATTR_STARTINDEX, ATTR_ENDINDEX )
void ScViewFunc::ApplyAttributes( const SfxItemSet& rDialogSet,
const SfxItemSet& rOldSet,
bool bAdjustBlockHeight)
{
// not editable because of matrix only? attribute OK nonetheless
bool bOnlyNotBecauseOfMatrix;
if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix )
{
ErrorMessage(STR_PROTECTIONERR);
return ;
}
ScDocument& rDoc = GetViewData().GetDocument();
ScPatternAttr aOldAttrs(rDoc.getCellAttributeHelper(), &rOldSet);
ScPatternAttr aNewAttrs(rDoc.getCellAttributeHelper(), &rDialogSet);
aNewAttrs.DeleteUnchanged( &aOldAttrs );
if ( rDialogSet.GetItemState( ATTR_VALUE_FORMAT ) == SfxItemState::SET )
{ // don't reset to default SYSTEM GENERAL if not intended
sal_uInt32 nOldFormat =
rOldSet.Get( ATTR_VALUE_FORMAT ).GetValue();
sal_uInt32 nNewFormat =
rDialogSet.Get( ATTR_VALUE_FORMAT ).GetValue();
if ( nNewFormat != nOldFormat )
{
SvNumberFormatter* pFormatter =
GetViewData().GetDocument().GetFormatTable();
const SvNumberformat* pOldEntry = pFormatter->GetEntry( nOldFormat );
LanguageType eOldLang =
pOldEntry ? pOldEntry->GetLanguage() : LANGUAGE_DONTKNOW;
const SvNumberformat* pNewEntry = pFormatter->GetEntry( nNewFormat );
LanguageType eNewLang =
pNewEntry ? pNewEntry->GetLanguage() : LANGUAGE_DONTKNOW;
if ( eNewLang != eOldLang )
{
aNewAttrs.GetItemSet().Put(
SvxLanguageItem( eNewLang, ATTR_LANGUAGE_FORMAT ) );
// only the language has changed -> do not touch numberformat-attribute
sal_uInt32 nNewMod = nNewFormat % SV_COUNTRY_LANGUAGE_OFFSET;
if ( nNewMod == ( nOldFormat % SV_COUNTRY_LANGUAGE_OFFSET ) &&
nNewMod <= SV_MAX_COUNT_STANDARD_FORMATS )
aNewAttrs.GetItemSet().ClearItem( ATTR_VALUE_FORMAT );
}
}
}
if (rDialogSet.HasItem(ATTR_FONT_LANGUAGE))
// font language has changed. Redo the online spelling.
ResetAutoSpell();
const SvxBoxItem& rOldOuter = rOldSet.Get(ATTR_BORDER);
const SvxBoxItem& rNewOuter = rDialogSet.Get(ATTR_BORDER);
const SvxBoxInfoItem& rOldInner = rOldSet.Get(ATTR_BORDER_INNER);
const SvxBoxInfoItem& rNewInner = rDialogSet.Get(ATTR_BORDER_INNER);
SfxItemSet& rNewSet = aNewAttrs.GetItemSet();
// protect referenced Items from disappearing (was: don't delete yet)
const SfxPoolItemHolder aHoldOuter(*rDialogSet.GetPool() , &rNewOuter);
const SfxPoolItemHolder aHoldInner(*rDialogSet.GetPool() , &rNewInner);
(void )aHoldOuter;
(void )aHoldInner;
rNewSet.ClearItem( ATTR_BORDER );
rNewSet.ClearItem( ATTR_BORDER_INNER );
/*
* establish whether border attribute is to be set:
* 1. new != old
* 2. is one of the borders not-DontCare (since 238.f: IsxxValid())
*
*/
bool bFrame = (rDialogSet.GetItemState( ATTR_BORDER ) != SfxItemState::DEFAULT )
|| (rDialogSet.GetItemState( ATTR_BORDER_INNER ) != SfxItemState::DEFAULT );
if (SfxPoolItem::areSame(rNewOuter, rOldOuter) && SfxPoolItem::areSame(rNewInner, rOldInner))
bFrame = false ;
// this should be intercepted by the pool: ?!??!??
if (bFrame && rNewOuter == rOldOuter && rNewInner == rOldInner)
bFrame = false ;
bFrame = bFrame
&& ( rNewInner.IsValid(SvxBoxInfoItemValidFlags::LEFT)
|| rNewInner.IsValid(SvxBoxInfoItemValidFlags::RIGHT)
|| rNewInner.IsValid(SvxBoxInfoItemValidFlags::TOP)
|| rNewInner.IsValid(SvxBoxInfoItemValidFlags::BOTTOM)
|| rNewInner.IsValid(SvxBoxInfoItemValidFlags::HORI)
|| rNewInner.IsValid(SvxBoxInfoItemValidFlags::VERT) );
if (!bFrame)
ApplySelectionPattern( aNewAttrs ); // standard only
else
{
// if new items are default-items, overwrite the old items:
bool bDefNewOuter = IsStaticDefaultItem(&rNewOuter);
bool bDefNewInner = IsStaticDefaultItem(&rNewInner);
ApplyPatternLines( aNewAttrs,
bDefNewOuter ? rOldOuter : rNewOuter,
bDefNewInner ? &rOldInner : &rNewInner );
}
// adjust height only if needed
if (bAdjustBlockHeight)
AdjustBlockHeight();
// CellContentChanged is called in ApplySelectionPattern / ApplyPatternLines
}
void ScViewFunc::ApplyAttr( const SfxPoolItem& rAttrItem, bool bAdjustBlockHeight )
{
// not editable because of matrix only? attribute OK nonetheless
bool bOnlyNotBecauseOfMatrix;
if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix )
{
ErrorMessage(STR_PROTECTIONERR);
return ;
}
ScDocument& rDoc = GetViewData().GetDocument();
ScPatternAttr aNewAttrs(rDoc.getCellAttributeHelper());
aNewAttrs.GetItemSet().Put( rAttrItem );
// if justify is set (with Buttons), always indentation 0
if ( rAttrItem.Which() == ATTR_HOR_JUSTIFY )
aNewAttrs.GetItemSet().Put( ScIndentItem( 0 ) );
ApplySelectionPattern( aNewAttrs );
// Prevent useless compute
if (bAdjustBlockHeight)
AdjustBlockHeight();
// CellContentChanged is called in ApplySelectionPattern
}
// patterns and borders
void ScViewFunc::ApplyPatternLines( const ScPatternAttr& rAttr, const SvxBoxItem& rNewOuter,
const SvxBoxInfoItem* pNewInner )
{
ScDocument& rDoc = GetViewData().GetDocument();
ScMarkData aFuncMark( GetViewData().GetMarkData() ); // local copy for UnmarkFiltered
ScViewUtil::UnmarkFiltered( aFuncMark, rDoc );
bool bRecord = true ;
if (!rDoc.IsUndoEnabled())
bRecord = false ;
bool bRemoveAdjCellBorder = rNewOuter.IsRemoveAdjacentCellBorder();
ScRange aMarkRange, aMarkRangeWithEnvelope;
aFuncMark.MarkToSimple();
bool bMulti = aFuncMark.IsMultiMarked();
if (bMulti)
aMarkRange = aFuncMark.GetMultiMarkArea();
else if (aFuncMark.IsMarked())
aMarkRange = aFuncMark.GetMarkArea();
else
{
aMarkRange = ScRange( GetViewData().GetCurX(),
GetViewData().GetCurY(), GetViewData().GetTabNo() );
DoneBlockMode();
InitOwnBlockMode( aMarkRange );
aFuncMark.SetMarkArea(aMarkRange);
MarkDataChanged();
}
if ( bRemoveAdjCellBorder )
aFuncMark.GetSelectionCover( aMarkRangeWithEnvelope );
else
aMarkRangeWithEnvelope = aMarkRange;
ScDocShell& rDocSh = GetViewData().GetDocShell();
ScDocShellModificator aModificator( rDocSh );
if (bRecord)
{
ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO ));
SCTAB nStartTab = aMarkRange.aStart.Tab();
SCTAB nTabCount = rDoc.GetTableCount();
bool bCopyOnlyMarked = false ;
if ( !bRemoveAdjCellBorder )
bCopyOnlyMarked = bMulti;
pUndoDoc->InitUndo( rDoc, nStartTab, nStartTab );
for (const auto & rTab : aFuncMark)
if (rTab != nStartTab)
pUndoDoc->AddUndoTab( rTab, rTab );
ScRange aCopyRange = aMarkRangeWithEnvelope;
aCopyRange.aStart.SetTab(0);
aCopyRange.aEnd.SetTab(nTabCount-1);
rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ATTRIB, bCopyOnlyMarked, *pUndoDoc, &aFuncMark );
rDocSh.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoSelectionAttr>(
rDocSh, aFuncMark,
aMarkRange.aStart.Col(), aMarkRange.aStart.Row(), aMarkRange.aStart.Tab(),
aMarkRange.aEnd.Col(), aMarkRange.aEnd.Row(), aMarkRange.aEnd.Tab(),
std::move(pUndoDoc), bCopyOnlyMarked, &rAttr, &rNewOuter, pNewInner, &aMarkRangeWithEnvelope ) );
}
sal_uInt16 nExt = SC_PF_TESTMERGE;
rDocSh.UpdatePaintExt( nExt, aMarkRangeWithEnvelope ); // content before the change
rDoc.ApplySelectionFrame(aFuncMark, rNewOuter, pNewInner);
rDocSh.UpdatePaintExt( nExt, aMarkRangeWithEnvelope ); // content after the change
aFuncMark.MarkToMulti();
rDoc.ApplySelectionPattern( rAttr, aFuncMark );
rDocSh.PostPaint( aMarkRange, PaintPartFlags::Grid, nExt );
rDocSh.UpdateOle(GetViewData());
aModificator.SetDocumentModified();
CellContentChanged();
StartFormatArea();
}
// tdf#147842 if the marked area is the entire sheet, then shrink it to the data area.
// Otherwise ctrl-A, perform-action, will take a very long time as it tries to modify
// cells that we are not using.
static void ShrinkToDataArea(ScMarkData& rFuncMark, const ScDocument& rDoc)
{
// do not make it marked if it is not already marked
if (!rFuncMark.IsMarked())
return ;
if (rFuncMark.IsMultiMarked())
return ;
ScRange aMarkArea = rFuncMark.GetMarkArea();
const ScSheetLimits& rLimits = rDoc.GetSheetLimits();
if (aMarkArea.aStart.Row() != 0 || aMarkArea.aStart.Col() != 0)
return ;
if (aMarkArea.aEnd.Row() != rLimits.MaxRow() || aMarkArea.aEnd.Col() != rLimits.MaxCol())
return ;
if (aMarkArea.aStart.Tab() != aMarkArea.aEnd.Tab())
return ;
SCCOL nStartCol = aMarkArea.aStart.Col();
SCROW nStartRow = aMarkArea.aStart.Row();
SCCOL nEndCol = aMarkArea.aEnd.Col();
SCROW nEndRow = aMarkArea.aEnd.Row();
rDoc.ShrinkToDataArea(aMarkArea.aStart.Tab(), nStartCol, nStartRow, nEndCol, nEndRow);
aMarkArea.aStart.SetCol(nStartCol);
aMarkArea.aStart.SetRow(nStartRow);
aMarkArea.aEnd.SetCol(nEndCol);
aMarkArea.aEnd.SetRow(nEndRow);
rFuncMark.ResetMark();
rFuncMark.SetMarkArea(aMarkArea);
}
// pattern only
void ScViewFunc::ApplySelectionPattern( const ScPatternAttr& rAttr, bool bCursorOnly )
{
ScViewData& rViewData = GetViewData();
ScDocShell& rDocSh = rViewData.GetDocShell();
ScDocument& rDoc = rDocSh.GetDocument();
ScMarkData aFuncMark( rViewData.GetMarkData() ); // local copy for UnmarkFiltered
ScViewUtil::UnmarkFiltered( aFuncMark, rDoc );
bool bRecord = true ;
if (!rDoc.IsUndoEnabled())
bRecord = false ;
// State from old ItemSet doesn't matter for paint flags, as any change will be
// from SfxItemState::SET in the new ItemSet (default is ignored in ApplyPattern).
// New alignment is checked (check in PostPaint isn't enough) in case a right
// alignment is changed to left.
const SfxItemSet& rNewSet = rAttr.GetItemSet();
bool bSetLines = rNewSet.GetItemState( ATTR_BORDER ) == SfxItemState::SET ||
rNewSet.GetItemState( ATTR_SHADOW ) == SfxItemState::SET;
bool bSetAlign = rNewSet.GetItemState( ATTR_HOR_JUSTIFY ) == SfxItemState::SET;
sal_uInt16 nExtFlags = 0;
if ( bSetLines )
nExtFlags |= SC_PF_LINES;
if ( bSetAlign )
nExtFlags |= SC_PF_WHOLEROWS;
ScDocShellModificator aModificator( rDocSh );
bool bMulti = aFuncMark.IsMultiMarked();
aFuncMark.MarkToMulti();
bool bOnlyTab = (!aFuncMark.IsMultiMarked() && !bCursorOnly && aFuncMark.GetSelectCount() > 1);
if (bOnlyTab)
{
SCCOL nCol = rViewData.GetCurX();
SCROW nRow = rViewData.GetCurY();
SCTAB nTab = rViewData.GetTabNo();
aFuncMark.SetMarkArea(ScRange(nCol,nRow,nTab));
aFuncMark.MarkToMulti();
}
ScRangeList aChangeRanges;
if (aFuncMark.IsMultiMarked() && !bCursorOnly)
{
const ScRange& aMarkRange = aFuncMark.GetMultiMarkArea();
SCTAB nTabCount = rDoc.GetTableCount();
for (const auto & rTab : aFuncMark)
{
ScRange aChangeRange( aMarkRange );
aChangeRange.aStart.SetTab( rTab );
aChangeRange.aEnd.SetTab( rTab );
aChangeRanges.push_back( aChangeRange );
}
SCCOL nStartCol = aMarkRange.aStart.Col();
SCROW nStartRow = aMarkRange.aStart.Row();
SCTAB nStartTab = aMarkRange.aStart.Tab();
SCCOL nEndCol = aMarkRange.aEnd.Col();
SCROW nEndRow = aMarkRange.aEnd.Row();
SCTAB nEndTab = aMarkRange.aEnd.Tab();
ScEditDataArray* pEditDataArray = nullptr;
if (bRecord)
{
ScRange aCopyRange = aMarkRange;
aCopyRange.aStart.SetTab(0);
aCopyRange.aEnd.SetTab(nTabCount-1);
ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO ));
pUndoDoc->InitUndo( rDoc, nStartTab, nStartTab );
for (const auto & rTab : aFuncMark)
if (rTab != nStartTab)
pUndoDoc->AddUndoTab( rTab, rTab );
rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ATTRIB, bMulti, *pUndoDoc, &aFuncMark );
aFuncMark.MarkToMulti();
ScUndoSelectionAttr* pUndoAttr = new ScUndoSelectionAttr(
rDocSh, aFuncMark, nStartCol, nStartRow, nStartTab,
nEndCol, nEndRow, nEndTab, std::move(pUndoDoc), bMulti, &rAttr );
rDocSh.GetUndoManager()->AddUndoAction(std::unique_ptr<ScUndoSelectionAttr>(pUndoAttr));
pEditDataArray = pUndoAttr->GetDataArray();
}
rDoc.ApplySelectionPattern( rAttr, aFuncMark, pEditDataArray );
rDocSh.PostPaint( nStartCol, nStartRow, nStartTab,
nEndCol, nEndRow, nEndTab,
PaintPartFlags::Grid, nExtFlags | SC_PF_TESTMERGE );
rDocSh.UpdateOle(GetViewData());
aModificator.SetDocumentModified();
CellContentChanged();
}
else // single cell - simpler undo
{
SCCOL nCol = rViewData.GetCurX();
SCROW nRow = rViewData.GetCurY();
SCTAB nTab = rViewData.GetTabNo();
std::unique_ptr<EditTextObject> pOldEditData;
std::unique_ptr<EditTextObject> pNewEditData;
ScAddress aPos(nCol, nRow, nTab);
ScRefCellValue aCell(rDoc, aPos);
if (aCell.getType() == CELLTYPE_EDIT)
{
const EditTextObject* pEditObj = aCell.getEditText();
pOldEditData = pEditObj->Clone();
rDoc.RemoveEditTextCharAttribs(aPos, rAttr);
pEditObj = rDoc.GetEditText(aPos);
pNewEditData = pEditObj->Clone();
}
aChangeRanges.push_back(ScRange(aPos));
std::optional<ScPatternAttr> pOldPat(*rDoc.GetPattern( nCol, nRow, nTab ));
rDoc.ApplyPattern( nCol, nRow, nTab, rAttr );
const ScPatternAttr* pNewPat = rDoc.GetPattern( nCol, nRow, nTab );
if (bRecord)
{
std::unique_ptr<ScUndoCursorAttr> pUndo(new ScUndoCursorAttr(
rDocSh, nCol, nRow, nTab, &*pOldPat, pNewPat, &rAttr ));
pUndo->SetEditData(std::move(pOldEditData), std::move(pNewEditData));
rDocSh.GetUndoManager()->AddUndoAction(std::move(pUndo));
}
pOldPat.reset(); // is copied in undo (Pool)
rDocSh.PostPaint( nCol,nRow,nTab, nCol,nRow,nTab, PaintPartFlags::Grid, nExtFlags | SC_PF_TESTMERGE );
rDocSh.UpdateOle(GetViewData());
aModificator.SetDocumentModified();
CellContentChanged();
}
ScModelObj* pModelObj = rDocSh.GetModel();
if (HelperNotifyChanges::getMustPropagateChangesModel(pModelObj))
{
css::uno::Sequence< css::beans::PropertyValue > aProperties;
sal_Int32 nCount = 0;
const SfxItemPropertyMap& rMap = ScCellObj::GetCellPropertyMap();
for ( sal_uInt16 nWhich = ATTR_PATTERN_START; nWhich <= ATTR_PATTERN_END; ++nWhich )
{
const SfxPoolItem* pItem = nullptr;
if ( rNewSet.GetItemState( nWhich, true , &pItem ) == SfxItemState::SET && pItem )
{
for ( const auto pEntry : rMap.getPropertyEntries())
{
if ( pEntry->nWID == nWhich )
{
css::uno::Any aVal;
pItem->QueryValue( aVal, pEntry->nMemberId );
aProperties.realloc( nCount + 1 );
auto pProperties = aProperties.getArray();
pProperties[ nCount ].Name = pEntry->aName;
pProperties[ nCount ].Value = std::move(aVal);
++nCount;
}
}
}
}
HelperNotifyChanges::Notify(*pModelObj, aChangeRanges, u"attribute" _ustr, aProperties);
}
StartFormatArea();
}
void ScViewFunc::ApplyUserItemSet( const SfxItemSet& rItemSet )
{
// ItemSet from UI, may have different pool
bool bOnlyNotBecauseOfMatrix;
if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix )
{
ErrorMessage(STR_PROTECTIONERR);
return ;
}
ScPatternAttr aNewAttrs(GetViewData().GetDocument().getCellAttributeHelper());
SfxItemSet& rNewSet = aNewAttrs.GetItemSet();
rNewSet.Put( rItemSet, false );
ApplySelectionPattern( aNewAttrs );
AdjustBlockHeight();
}
const SfxStyleSheet* ScViewFunc::GetStyleSheetFromMarked()
{
// Don't use UnmarkFiltered in slot state functions, for performance reasons.
// The displayed state is always that of the whole selection including filtered rows.
const ScStyleSheet* pSheet = nullptr;
ScViewData& rViewData = GetViewData();
ScDocument& rDoc = rViewData.GetDocument();
ScMarkData& rMark = rViewData.GetMarkData();
if ( rMark.IsMarked() || rMark.IsMultiMarked() )
pSheet = rDoc.GetSelectionStyle( rMark ); // MarkToMulti isn't necessary
else
pSheet = rDoc.GetStyle( rViewData.GetCurX(),
rViewData.GetCurY(),
rViewData.GetTabNo() );
return pSheet;
}
void ScViewFunc::SetStyleSheetToMarked( const SfxStyleSheet* pStyleSheet )
{
// not editable because of matrix only? attribute OK nonetheless
bool bOnlyNotBecauseOfMatrix;
if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix )
{
ErrorMessage(STR_PROTECTIONERR);
return ;
}
if ( !pStyleSheet) return ;
ScViewData& rViewData = GetViewData();
ScDocShell& rDocSh = rViewData.GetDocShell();
ScDocument& rDoc = rDocSh.GetDocument();
ScMarkData aFuncMark( rViewData.GetMarkData() ); // local copy for UnmarkFiltered
ScViewUtil::UnmarkFiltered( aFuncMark, rDoc );
SCTAB nTabCount = rDoc.GetTableCount();
bool bRecord = true ;
if (!rDoc.IsUndoEnabled())
bRecord = false ;
ScDocShellModificator aModificator( rDocSh );
if ( aFuncMark.IsMarked() || aFuncMark.IsMultiMarked() )
{
aFuncMark.MarkToMulti();
const ScRange& aMarkRange = aFuncMark.GetMultiMarkArea();
if ( bRecord )
{
SCTAB nTab = rViewData.GetTabNo();
ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO ));
pUndoDoc->InitUndo( rDoc, nTab, nTab );
for (const auto & rTab : aFuncMark)
if (rTab != nTab)
pUndoDoc->AddUndoTab( rTab, rTab );
ScRange aCopyRange = aMarkRange;
aCopyRange.aStart.SetTab(0);
aCopyRange.aEnd.SetTab(nTabCount-1);
rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ATTRIB, true , *pUndoDoc, &aFuncMark );
aFuncMark.MarkToMulti();
OUString aName = pStyleSheet->GetName();
rDocSh.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoSelectionStyle>( rDocSh, aFuncMark, aMarkRange, aName, std::move(pUndoDoc) ) );
}
rDoc.ApplySelectionStyle( static_cast <const ScStyleSheet&>(*pStyleSheet), aFuncMark );
if (!AdjustBlockHeight())
rViewData.GetDocShell().PostPaint( aMarkRange, PaintPartFlags::Grid );
aFuncMark.MarkToSimple();
}
else
{
SCCOL nCol = rViewData.GetCurX();
SCROW nRow = rViewData.GetCurY();
SCTAB nTab = rViewData.GetTabNo();
if ( bRecord )
{
ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO ));
pUndoDoc->InitUndo( rDoc, nTab, nTab );
for (const auto & rTab : aFuncMark)
if (rTab != nTab)
pUndoDoc->AddUndoTab( rTab, rTab );
ScRange aCopyRange( nCol, nRow, 0, nCol, nRow, nTabCount-1 );
rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ATTRIB, false , *pUndoDoc );
ScRange aMarkRange ( nCol, nRow, nTab );
ScMarkData aUndoMark = aFuncMark;
aUndoMark.SetMultiMarkArea( aMarkRange );
OUString aName = pStyleSheet->GetName();
rDocSh.GetUndoManager()->AddUndoAction(
std::make_unique<ScUndoSelectionStyle>( rDocSh, aUndoMark, aMarkRange, aName, std::move(pUndoDoc) ) );
}
for (const auto & rTab : aFuncMark)
rDoc.ApplyStyle( nCol, nRow, rTab, static_cast <const ScStyleSheet&>(*pStyleSheet) );
if (!AdjustBlockHeight())
rViewData.GetDocShell().PostPaintCell( nCol, nRow, nTab );
}
aModificator.SetDocumentModified();
StartFormatArea();
}
void ScViewFunc::RemoveStyleSheetInUse( const SfxStyleSheetBase* pStyleSheet )
{
if ( !pStyleSheet) return ;
ScViewData& rViewData = GetViewData();
ScDocument& rDoc = rViewData.GetDocument();
ScDocShell& rDocSh = rViewData.GetDocShell();
ScDocShellModificator aModificator( rDocSh );
ScopedVclPtrInstance< VirtualDevice > pVirtDev;
pVirtDev->SetMapMode(MapMode(MapUnit::MapPixel));
rDoc.StyleSheetChanged( pStyleSheet, true , pVirtDev,
rViewData.GetPPTX(),
rViewData.GetPPTY(),
rViewData.GetZoomX(),
rViewData.GetZoomY() );
rDocSh.PostPaint( 0,0,0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB, PaintPartFlags::Grid|PaintPartFlags::Left );
aModificator.SetDocumentModified();
ScInputHandler* pHdl = ScModule::get()->GetInputHdl();
if (pHdl)
pHdl->ForgetLastPattern();
}
void ScViewFunc::UpdateStyleSheetInUse( const SfxStyleSheetBase* pStyleSheet )
{
if ( !pStyleSheet) return ;
ScViewData& rViewData = GetViewData();
ScDocument& rDoc = rViewData.GetDocument();
ScDocShell& rDocSh = rViewData.GetDocShell();
ScDocShellModificator aModificator( rDocSh );
ScopedVclPtrInstance< VirtualDevice > pVirtDev;
pVirtDev->SetMapMode(MapMode(MapUnit::MapPixel));
rDoc.StyleSheetChanged( pStyleSheet, false , pVirtDev,
rViewData.GetPPTX(),
rViewData.GetPPTY(),
rViewData.GetZoomX(),
rViewData.GetZoomY() );
rDocSh.PostPaint( 0,0,0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB, PaintPartFlags::Grid|PaintPartFlags::Left );
aModificator.SetDocumentModified();
ScInputHandler* pHdl = ScModule::get()->GetInputHdl();
if (pHdl)
pHdl->ForgetLastPattern();
}
void ScViewFunc::OnLOKInsertDeleteColumn(SCCOL nStartCol, tools::Long nOffset)
{
if (!comphelper::LibreOfficeKit::isActive() || nOffset == 0)
return ;
SCTAB nCurrentTabIndex = GetViewData().GetTabNo();
SfxViewShell* pCurrentViewShell = GetViewData().GetViewShell();
SfxViewShell* pViewShell = SfxViewShell::GetFirst();
while (pViewShell)
{
ScTabViewShell* pTabViewShell = dynamic_cast <ScTabViewShell*>(pViewShell);
if (pTabViewShell && pTabViewShell->GetDocId() == pCurrentViewShell->GetDocId())
{
if (ScPositionHelper* pPosHelper = pTabViewShell->GetViewData().GetLOKWidthHelper(nCurrentTabIndex))
pPosHelper->invalidateByIndex(nStartCol);
// if we remove a column the cursor position and the current selection
// in other views could need to be moved on the left by one column.
if (pTabViewShell != this )
{
if (pTabViewShell->getPart() == nCurrentTabIndex)
{
SCCOL nX = pTabViewShell->GetViewData().GetCurX();
if (nX > nStartCol)
{
tools::Long offset = nOffset;
if (nOffset + nStartCol > nX)
offset = nX - nStartCol;
else if (nOffset < 0 && nStartCol - nOffset > nX)
offset = -1 * (nX - nStartCol);
ScInputHandler* pInputHdl = pTabViewShell->GetInputHandler();
SCROW nY = pTabViewShell->GetViewData().GetCurY();
pTabViewShell->SetCursor(nX + offset, nY);
if (pInputHdl && pInputHdl->IsInputMode())
{
pInputHdl->SetModified();
}
}
ScMarkData aMultiMark( pTabViewShell->GetViewData().GetMarkData() );
aMultiMark.SetMarking( false );
if (aMultiMark.IsMultiMarked() || aMultiMark.IsMarked())
{
aMultiMark.ShiftCols(pTabViewShell->GetViewData().GetDocument(), nStartCol, nOffset);
pTabViewShell->SetMarkData(aMultiMark);
}
}
else
{
SCROW nX = pTabViewShell->GetViewData().GetCurXForTab(nCurrentTabIndex);
if (nX > nStartCol || (nX == nStartCol && nOffset > 0))
{
pTabViewShell->GetViewData().SetCurXForTab(nX + nOffset, nCurrentTabIndex);
}
}
}
}
pViewShell = SfxViewShell::GetNext(*pViewShell);
}
}
void ScViewFunc::OnLOKInsertDeleteRow(SCROW nStartRow, tools::Long nOffset)
{
if (!comphelper::LibreOfficeKit::isActive() || nOffset == 0)
return ;
SCTAB nCurrentTabIndex = GetViewData().GetTabNo();
SfxViewShell* pCurrentViewShell = GetViewData().GetViewShell();
SfxViewShell* pViewShell = SfxViewShell::GetFirst();
while (pViewShell)
{
ScTabViewShell* pTabViewShell = dynamic_cast <ScTabViewShell*>(pViewShell);
if (pTabViewShell && pTabViewShell->GetDocId() == pCurrentViewShell->GetDocId())
{
if (ScPositionHelper* pPosHelper = pTabViewShell->GetViewData().GetLOKHeightHelper(nCurrentTabIndex))
pPosHelper->invalidateByIndex(nStartRow);
// if we remove a row the cursor position and the current selection
// in other views could need to be moved up by one row.
if (pTabViewShell != this )
{
if (pTabViewShell->getPart() == nCurrentTabIndex)
{
SCROW nY = pTabViewShell->GetViewData().GetCurY();
if (nY > nStartRow)
--> --------------------
--> maximum size reached
--> --------------------
Messung V0.5 C=96 H=98 G=96
¤ Dauer der Verarbeitung: 0.19 Sekunden
(vorverarbeitet)
¤
*© Formatika GbR, Deutschland