diff options
Diffstat (limited to 'sc/source/ui/view/viewfunc.cxx')
-rw-r--r-- | sc/source/ui/view/viewfunc.cxx | 3073 |
1 files changed, 3073 insertions, 0 deletions
diff --git a/sc/source/ui/view/viewfunc.cxx b/sc/source/ui/view/viewfunc.cxx new file mode 100644 index 000000000..d343551c9 --- /dev/null +++ b/sc/source/ui/view/viewfunc.cxx @@ -0,0 +1,3073 @@ +/* -*- 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 <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 <formula/paramclass.hxx> + +#include <viewfunc.hxx> +#include <tabvwsh.hxx> +#include <docsh.hxx> +#include <attrib.hxx> +#include <patattr.hxx> +#include <docpool.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 <memory> + +static void lcl_PostRepaintCondFormat( const ScConditionalFormat *pCondFmt, ScDocShell *pDocSh ) +{ + if( pCondFmt ) + { + const ScRangeList& rRanges = pCondFmt->GetRange(); + + pDocSh->PostPaint( rRanges, PaintPartFlags::All ); + } +} + +ScViewFunc::ScViewFunc( vcl::Window* pParent, ScDocShell& rDocSh, ScTabViewShell* pViewShell ) : + ScTabView( pParent, rDocSh, pViewShell ), + bFormatValid( false ) +{ +} + +ScViewFunc::~ScViewFunc() +{ +} + +namespace { + +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 ( !SC_MOD()->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 ( !SC_MOD()->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* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->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 + +sal_uInt16 ScViewFunc::GetOptimalColWidth( SCCOL nCol, SCTAB nTab, bool bFormula ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScMarkData& rMark = GetViewData().GetMarkData(); + + double nPPTX = GetViewData().GetPPTX(); + double nPPTY = GetViewData().GetPPTY(); + Fraction aZoomX = GetViewData().GetZoomX(); + Fraction aZoomY = GetViewData().GetZoomY(); + + ScSizeDeviceProvider aProv(pDocSh); + if (aProv.IsPrinter()) + { + nPPTX = aProv.GetPPTX(); + nPPTY = aProv.GetPPTY(); + aZoomX = aZoomY = Fraction( 1, 1 ); + } + + 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, ScMarkData& rMark, SCCOL nCol, SCROW nRow) + { + if (ScModelObj *pModelObj = getMustPropagateChangesModel(rDocShell)) + { + ScRangeList aChangeRanges; + for (const auto& rTab : rMark) + aChangeRanges.push_back( ScRange( nCol, nRow, rTab ) ); + + HelperNotifyChanges::Notify(*pModelObj, aChangeRanges, "cell-change"); + } + } +} + +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, "modules/scalc/ui/warnautocorrect.ui", "WarnAutoCorrect", "grid") + , m_xError(m_xBuilder->weld_text_view("error")) + { + 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); + } + }; +} + +// 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 rMark(GetViewData().GetMarkData()); + bool bRecord = rDoc.IsUndoEnabled(); + SCTAB i; + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocFunc &rFunc = GetViewData().GetDocFunc(); + ScDocShellModificator aModificator( *pDocSh ); + + ScEditableTester aTester( rDoc, nCol,nRow, nCol,nRow, rMark ); + 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; + + // a single '=' character is handled as string (needed for special filters) + if ( 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 ) + { + sal_uInt32 nFormat = rDoc.GetNumberFormat( nCol, nRow, nTab ); + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + double fNumber = 0; + if ( !pFormatter->IsNumberFormat( aString, nFormat, fNumber ) ) + { + bFormula = true; + } + } + } + } + + bool bNumFmtChanged = false; + if ( bFormula ) + { // formula, compile with autoCorrection + i = rMark.GetFirstSelected(); + ScAddress aPos( nCol, nRow, i ); + ScCompiler aComp( rDoc, aPos, rDoc.GetGrammar(), true, false ); +//2do: enable/disable autoCorrection via calcoptions + aComp.SetAutoCorrection( true ); + if ( rString[0] == '+' || rString[0] == '-' ) + { + aComp.SetExtendedErrorDetection( ScCompiler::EXTENDED_ERROR_DETECTION_NAME_BREAK ); + } + OUString aFormula( rString ); + std::unique_ptr< ScTokenArray > pArr; + bool bAgain; + do + { + bAgain = false; + bool bAddEqual = false; + pArr = aComp.CompileString( aFormula ); + bool bCorrected = aComp.IsCorrected(); + std::unique_ptr< ScTokenArray > pArrFirst; + if ( bCorrected ) + { // try to parse with first parser-correction + pArrFirst = std::move( pArr ); + pArr = aComp.CompileString( aComp.GetCorrectedFormula() ); + } + if ( pArr->GetCodeError() == FormulaError::NONE ) + { + bAddEqual = true; + aComp.CompileTokenArray(); + bCorrected |= aComp.IsCorrected(); + } + if ( bCorrected ) + { + OUString aCorrectedFormula; + if ( bAddEqual ) + { + aCorrectedFormula = "=" + aComp.GetCorrectedFormula(); + } + else + aCorrectedFormula = aComp.GetCorrectedFormula(); + short nResult; + if ( aCorrectedFormula.getLength() == 1 ) + nResult = RET_NO; // empty formula, just '=' + else + { + AutoCorrectQuery aQueryBox(GetViewData().GetDialogParent(), aCorrectedFormula); + nResult = aQueryBox.run(); + } + if ( nResult == RET_YES ) + { + aFormula = aCorrectedFormula; + bAgain = true; + } + else + { + if ( pArrFirst ) + pArr = std::move( pArrFirst ); + } + } + } while ( bAgain ); + // 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 = pArr->GetCodeError(); + if ( nError == FormulaError::NONE ) + { + // update list of recent functions with all functions that + // are not within parentheses + + ScModule* pScMod = SC_MOD(); + ScAppOptions aAppOpt = pScMod->GetAppOptions(); + bool bOptChanged = false; + + formula::FormulaToken** ppToken = pArr->GetArray(); + sal_uInt16 nTokens = 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 (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 = pArr->LastRPNToken(); + if (pToken && (formula::FormulaCompiler::IsMatrixFunction( pToken->GetOpCode()) + || pToken->IsInForceArray())) + { + // Discard this (still empty here) Undo action, + // EnterMatrix() will create its own. + if (bRecord) + rFunc.EndListAction(); + + // Use corrected formula string. + EnterMatrix( aFormula, rDoc.GetGrammar()); + + return; + } + } + } + + ScFormulaCell aCell(rDoc, aPos, std::move( pArr ), formula::FormulaGrammar::GRAM_DEFAULT, ScMatrixMode::NONE); + + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + for (const auto& rTab : rMark) + { + i = rTab; + aPos.SetTab( i ); + const sal_uInt32 nIndex = rDoc.GetAttr( + nCol, nRow, i, ATTR_VALUE_FORMAT )->GetValue(); + const SvNumFormatType nType = pFormatter->GetType( nIndex); + if (nType == SvNumFormatType::TEXT || + ((rString[0] == '+' || rString[0] == '-') && nError != FormulaError::NONE && rString == aFormula)) + { + if ( pData ) + { + // A clone of pData will be stored in the cell. + rFunc.SetEditCell(aPos, *pData, true); + } + else + rFunc.SetStringCell(aPos, aFormula, true); + } + else + { + ScFormulaCell* pCell = new ScFormulaCell( aCell, rDoc, 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( rDoc.GetPool()); + aPattern.GetItemSet().Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nFormat)); + ScMarkData aMark(rDoc.GetSheetLimits()); + aMark.SelectTable( i, true); + aMark.SetMarkArea( ScRange( aPos)); + rFunc.ApplyAttributes( aMark, aPattern, false); + bNumFmtChanged = true; + } + rFunc.SetFormulaCell(aPos, pCell, true); + } + } + } + else + { + ScFieldEditEngine& rEngine = rDoc.GetEditEngine(); + for (const auto& rTab : rMark) + { + 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()); + pDocSh->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; + } + } + } + + bool bAutoFormat = TestFormatArea(nCol, nRow, nTab, bNumFmtChanged); + + if (bAutoFormat) + DoAutoAttributes(nCol, nRow, nTab, bNumFmtChanged); + + pDocSh->UpdateOle(GetViewData()); + + HelperNotifyChanges::NotifyIfChangesListeners(*pDocSh, rMark, nCol, nRow); + + if ( bRecord ) + rFunc.EndListAction(); + + aModificator.SetDocumentModified(); + lcl_PostRepaintCondFormat( rDoc.GetCondFormat( nCol, nRow, nTab ), pDocSh ); +} + +// 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* pDocSh = GetViewData().GetDocShell(); + + if (!pDocSh) + return; + + bool bUndo(rDoc.IsUndoEnabled()); + ScDocShellModificator aModificator( *pDocSh ); + + 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) + { + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoEnterValue>(pDocSh, aPos, aUndoCell, rValue)); + } + + pDocSh->PostPaintCell( aPos ); + pDocSh->UpdateOle(GetViewData()); + aModificator.SetDocumentModified(); + } + else + ErrorMessage(aTester.GetMessageId()); +} + +void ScViewFunc::EnterData( SCCOL nCol, SCROW nRow, SCTAB nTab, + const EditTextObject& rData, bool bTestSimple ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScMarkData& rMark = GetViewData().GetMarkData(); + ScDocument& rDoc = pDocSh->GetDocument(); + bool bRecord = rDoc.IsUndoEnabled(); + + ScDocShellModificator aModificator( *pDocSh ); + + 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 + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoEnterData>(pDocSh, ScAddress(nCol,nRow,nTab), aOldValues, aString, std::move(pUndoData))); + } + + HideAllCursors(); + + AdjustRowHeight(nRow,nRow,true); + + for (const auto& rTab : rMark) + pDocSh->PostPaintCell( nCol, nRow, rTab ); + + ShowAllCursors(); + + pDocSh->UpdateOle(GetViewData()); + + HelperNotifyChanges::NotifyIfChangesListeners(*pDocSh, rMark, nCol, nRow); + + aModificator.SetDocumentModified(); + } + lcl_PostRepaintCondFormat( rDoc.GetCondFormat( nCol, nRow, nTab ), pDocSh ); + } + 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 ); +} + +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* pDocSh = rData.GetDocShell(); + bool bSuccess = pDocSh->GetDocFunc().EnterMatrix( + aRange, &rMark, nullptr, rString, false, false, OUString(), eGram ); + if (bSuccess) + pDocSh->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; +} + +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. + + const ScMarkData& rMark = GetViewData().GetMarkData(); + ScDocument& rDoc = GetViewData().GetDocument(); + if ( rMark.IsMarked() || rMark.IsMultiMarked() ) + { + // MarkToMulti is no longer necessary for rDoc.GetSelectionPattern + const ScPatternAttr* pAttr = rDoc.GetSelectionPattern( rMark ); + return pAttr; + } + else + { + SCCOL nCol = GetViewData().GetCurX(); + SCROW nRow = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + + ScMarkData aTempMark( rMark ); // copy sheet selection + aTempMark.SetMarkArea( ScRange( nCol, nRow, nTab ) ); + const ScPatternAttr* pAttr = rDoc.GetSelectionPattern( aTempMark ); + 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* pDialogSet, + const SfxItemSet* pOldSet, + bool bAdjustBlockHeight) +{ + // not editable because of matrix only? attribute OK nonetheless + bool bOnlyNotBecauseOfMatrix; + if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + ScPatternAttr aOldAttrs(( SfxItemSet(*pOldSet) )); + ScPatternAttr aNewAttrs(( SfxItemSet(*pDialogSet) )); + aNewAttrs.DeleteUnchanged( &aOldAttrs ); + + if ( pDialogSet->GetItemState( ATTR_VALUE_FORMAT ) == SfxItemState::SET ) + { // don't reset to default SYSTEM GENERAL if not intended + sal_uInt32 nOldFormat = + pOldSet->Get( ATTR_VALUE_FORMAT ).GetValue(); + sal_uInt32 nNewFormat = + pDialogSet->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 (pDialogSet->HasItem(ATTR_FONT_LANGUAGE)) + // font language has changed. Redo the online spelling. + ResetAutoSpell(); + + const SvxBoxItem& rOldOuter = pOldSet->Get(ATTR_BORDER); + const SvxBoxItem& rNewOuter = pDialogSet->Get(ATTR_BORDER); + const SvxBoxInfoItem& rOldInner = pOldSet->Get(ATTR_BORDER_INNER); + const SvxBoxInfoItem& rNewInner = pDialogSet->Get(ATTR_BORDER_INNER); + SfxItemSet& rNewSet = aNewAttrs.GetItemSet(); + SfxItemPool* pNewPool = rNewSet.GetPool(); + + pNewPool->Put(rNewOuter); // don't delete yet + pNewPool->Put(rNewInner); + 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 = (pDialogSet->GetItemState( ATTR_BORDER ) != SfxItemState::DEFAULT) + || (pDialogSet->GetItemState( ATTR_BORDER_INNER ) != SfxItemState::DEFAULT); + + if (&rNewOuter == &rOldOuter && &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 ); + } + + pNewPool->Remove(rNewOuter); // release + pNewPool->Remove(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; + } + + ScPatternAttr aNewAttrs( + SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END>( *GetViewData().GetDocument().GetPool() ) ); + + 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* pDocSh = GetViewData().GetDocShell(); + + ScDocShellModificator aModificator( *pDocSh ); + + 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 ); + + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoSelectionAttr>( + pDocSh, 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; + pDocSh->UpdatePaintExt( nExt, aMarkRangeWithEnvelope ); // content before the change + + rDoc.ApplySelectionFrame(aFuncMark, rNewOuter, pNewInner); + + pDocSh->UpdatePaintExt( nExt, aMarkRangeWithEnvelope ); // content after the change + + aFuncMark.MarkToMulti(); + rDoc.ApplySelectionPattern( rAttr, aFuncMark ); + + pDocSh->PostPaint( aMarkRange, PaintPartFlags::Grid, nExt ); + pDocSh->UpdateOle(GetViewData()); + aModificator.SetDocumentModified(); + CellContentChanged(); + + StartFormatArea(); +} + +// pattern only + +void ScViewFunc::ApplySelectionPattern( const ScPatternAttr& rAttr, bool bCursorOnly ) +{ + ScViewData& rViewData = GetViewData(); + ScDocShell* pDocSh = rViewData.GetDocShell(); + ScDocument& rDoc = pDocSh->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( *pDocSh ); + + 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( + pDocSh, aFuncMark, nStartCol, nStartRow, nStartTab, + nEndCol, nEndRow, nEndTab, std::move(pUndoDoc), bMulti, &rAttr ); + pDocSh->GetUndoManager()->AddUndoAction(std::unique_ptr<ScUndoSelectionAttr>(pUndoAttr)); + pEditDataArray = pUndoAttr->GetDataArray(); + } + + rDoc.ApplySelectionPattern( rAttr, aFuncMark, pEditDataArray ); + + pDocSh->PostPaint( nStartCol, nStartRow, nStartTab, + nEndCol, nEndRow, nEndTab, + PaintPartFlags::Grid, nExtFlags | SC_PF_TESTMERGE ); + pDocSh->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.meType == CELLTYPE_EDIT) + { + const EditTextObject* pEditObj = aCell.mpEditText; + pOldEditData = pEditObj->Clone(); + rDoc.RemoveEditTextCharAttribs(aPos, rAttr); + pEditObj = rDoc.GetEditText(aPos); + pNewEditData = pEditObj->Clone(); + } + + aChangeRanges.push_back(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( + pDocSh, nCol, nRow, nTab, &*pOldPat, pNewPat, &rAttr )); + pUndo->SetEditData(std::move(pOldEditData), std::move(pNewEditData)); + pDocSh->GetUndoManager()->AddUndoAction(std::move(pUndo)); + } + pOldPat.reset(); // is copied in undo (Pool) + + pDocSh->PostPaint( nCol,nRow,nTab, nCol,nRow,nTab, PaintPartFlags::Grid, nExtFlags | SC_PF_TESTMERGE ); + pDocSh->UpdateOle(GetViewData()); + aModificator.SetDocumentModified(); + CellContentChanged(); + } + + ScModelObj* pModelObj = HelperNotifyChanges::getMustPropagateChangesModel(*pDocSh); + if (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 = aVal; + ++nCount; + } + } + } + } + HelperNotifyChanges::Notify(*pModelObj, aChangeRanges, "attribute", 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().GetPool() ); + 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* pDocSh = rViewData.GetDocShell(); + ScDocument& rDoc = pDocSh->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( *pDocSh ); + + 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(); + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoSelectionStyle>( pDocSh, 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(); + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoSelectionStyle>( pDocSh, 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* pDocSh = rViewData.GetDocShell(); + + ScDocShellModificator aModificator( *pDocSh ); + + ScopedVclPtrInstance< VirtualDevice > pVirtDev; + pVirtDev->SetMapMode(MapMode(MapUnit::MapPixel)); + rDoc.StyleSheetChanged( pStyleSheet, true, pVirtDev, + rViewData.GetPPTX(), + rViewData.GetPPTY(), + rViewData.GetZoomX(), + rViewData.GetZoomY() ); + + pDocSh->PostPaint( 0,0,0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB, PaintPartFlags::Grid|PaintPartFlags::Left ); + aModificator.SetDocumentModified(); + + ScInputHandler* pHdl = SC_MOD()->GetInputHdl(); + if (pHdl) + pHdl->ForgetLastPattern(); +} + +void ScViewFunc::UpdateStyleSheetInUse( const SfxStyleSheetBase* pStyleSheet ) +{ + if ( !pStyleSheet) return; + + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + ScDocShell* pDocSh = rViewData.GetDocShell(); + + ScDocShellModificator aModificator( *pDocSh ); + + ScopedVclPtrInstance< VirtualDevice > pVirtDev; + pVirtDev->SetMapMode(MapMode(MapUnit::MapPixel)); + rDoc.StyleSheetChanged( pStyleSheet, false, pVirtDev, + rViewData.GetPPTX(), + rViewData.GetPPTY(), + rViewData.GetZoomX(), + rViewData.GetZoomY() ); + + pDocSh->PostPaint( 0,0,0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB, PaintPartFlags::Grid|PaintPartFlags::Left ); + aModificator.SetDocumentModified(); + + ScInputHandler* pHdl = SC_MOD()->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 || (nX == nStartCol && nOffset > 0)) + { + ScInputHandler* pInputHdl = pTabViewShell->GetInputHandler(); + SCROW nY = pTabViewShell->GetViewData().GetCurY(); + pTabViewShell->SetCursor(nX + nOffset, nY); + if (pInputHdl && pInputHdl->IsInputMode()) + { + pInputHdl->SetModified(); + } + } + + ScMarkData aMultiMark( pTabViewShell->GetViewData().GetMarkData() ); + aMultiMark.SetMarking( false ); + aMultiMark.MarkToMulti(); + if (aMultiMark.IsMultiMarked()) + { + 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 || (nY == nStartRow && nOffset > 0)) + { + ScInputHandler* pInputHdl = pTabViewShell->GetInputHandler(); + SCCOL nX = pTabViewShell->GetViewData().GetCurX(); + pTabViewShell->SetCursor(nX, nY + nOffset); + if (pInputHdl && pInputHdl->IsInputMode()) + { + pInputHdl->SetModified(); + } + } + + ScMarkData aMultiMark( pTabViewShell->GetViewData().GetMarkData() ); + aMultiMark.SetMarking( false ); + aMultiMark.MarkToMulti(); + if (aMultiMark.IsMultiMarked()) + { + aMultiMark.ShiftRows(pTabViewShell->GetViewData().GetDocument(), nStartRow, nOffset); + pTabViewShell->SetMarkData(aMultiMark); + } + } + else + { + SCROW nY = pTabViewShell->GetViewData().GetCurYForTab(nCurrentTabIndex); + if (nY > nStartRow || (nY == nStartRow && nOffset > 0)) + { + pTabViewShell->GetViewData().SetCurYForTab(nY + nOffset, nCurrentTabIndex); + } + } + } + } + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +void ScViewFunc::OnLOKSetWidthOrHeight(SCCOLROW nStart, bool bWidth) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + SCTAB nCurTab = 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 (bWidth) + { + if (ScPositionHelper* pPosHelper = pTabViewShell->GetViewData().GetLOKWidthHelper(nCurTab)) + pPosHelper->invalidateByIndex(nStart); + } + else + { + if (ScPositionHelper* pPosHelper = pTabViewShell->GetViewData().GetLOKHeightHelper(nCurTab)) + pPosHelper->invalidateByIndex(nStart); + } + } + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +// insert cells - undo OK + +bool ScViewFunc::InsertCells( InsCellCmd eCmd, bool bRecord, bool bPartOfPaste ) +{ + ScRange aRange; + ScMarkType eMarkType = GetViewData().GetSimpleArea(aRange); + if (eMarkType == SC_MARK_SIMPLE || eMarkType == SC_MARK_SIMPLE_FILTERED) + { + ScDocShell* pDocSh = GetViewData().GetDocShell(); + const ScMarkData& rMark = GetViewData().GetMarkData(); + bool bSuccess = pDocSh->GetDocFunc().InsertCells( aRange, &rMark, eCmd, bRecord, false, bPartOfPaste ); + if (bSuccess) + { + ResetAutoSpellForContentChange(); + bool bInsertCols = ( eCmd == INS_INSCOLS_BEFORE || eCmd == INS_INSCOLS_AFTER); + bool bInsertRows = ( eCmd == INS_INSROWS_BEFORE || eCmd == INS_INSROWS_AFTER ); + + pDocSh->UpdateOle(GetViewData()); + CellContentChanged(); + + if ( bInsertCols || bInsertRows ) + { + OUString aOperation = bInsertRows ? + OUString("insert-rows"): + OUString("insert-columns"); + HelperNotifyChanges::NotifyIfChangesListeners(*pDocSh, aRange, aOperation); + } + + if (comphelper::LibreOfficeKit::isActive()) + { + if (bInsertCols) + ScTabViewShell::notifyAllViewsHeaderInvalidation(GetViewData().GetViewShell(), COLUMN_HEADER, GetViewData().GetTabNo()); + + if (bInsertRows) + ScTabViewShell::notifyAllViewsHeaderInvalidation(GetViewData().GetViewShell(), ROW_HEADER, GetViewData().GetTabNo()); + + ScTabViewShell::notifyAllViewsSheetGeomInvalidation(GetViewData().GetViewShell(), + bInsertCols, bInsertRows, true /* bSizes*/, + true /* bHidden */, true /* bFiltered */, + true /* bGroups */, GetViewData().GetTabNo()); + } + } + OUString aStartAddress = aRange.aStart.GetColRowString(); + OUString aEndAddress = aRange.aEnd.GetColRowString(); + collectUIInformation({{"RANGE", aStartAddress + ":" + aEndAddress}}, "INSERT_CELLS"); + return bSuccess; + } + else + { + ErrorMessage(STR_NOMULTISELECT); + return false; + } +} + +// delete cells - undo OK + +void ScViewFunc::DeleteCells( DelCellCmd eCmd ) +{ + ScRange aRange; + if ( GetViewData().GetSimpleArea( aRange ) == SC_MARK_SIMPLE ) + { + ScDocShell* pDocSh = GetViewData().GetDocShell(); + const ScMarkData& rMark = GetViewData().GetMarkData(); + +#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT + // #i94841# [Collaboration] if deleting rows is rejected, the content is sometimes wrong + if ( pDocSh->IsDocShared() && ( eCmd == DelCellCmd::Rows || eCmd == DelCellCmd::Cols ) ) + { + ScRange aDelRange( aRange.aStart ); + SCCOLROW nCount = 0; + if ( eCmd == DelCellCmd::Rows ) + { + nCount = sal::static_int_cast< SCCOLROW >( aRange.aEnd.Row() - aRange.aStart.Row() + 1 ); + } + else + { + nCount = sal::static_int_cast< SCCOLROW >( aRange.aEnd.Col() - aRange.aStart.Col() + 1 ); + } + while ( nCount > 0 ) + { + pDocSh->GetDocFunc().DeleteCells( aDelRange, &rMark, eCmd, false ); + --nCount; + } + } + else +#endif + { + pDocSh->GetDocFunc().DeleteCells( aRange, &rMark, eCmd, false ); + } + + ResetAutoSpellForContentChange(); + pDocSh->UpdateOle(GetViewData()); + CellContentChanged(); + + if ( eCmd == DelCellCmd::Rows || eCmd == DelCellCmd::Cols ) + { + OUString aOperation = ( eCmd == DelCellCmd::Rows) ? + OUString("delete-rows"): + OUString("delete-columns"); + HelperNotifyChanges::NotifyIfChangesListeners(*pDocSh, aRange, aOperation); + } + + // put cursor directly behind deleted range + SCCOL nCurX = GetViewData().GetCurX(); + SCROW nCurY = GetViewData().GetCurY(); + if ( eCmd==DelCellCmd::CellsLeft || eCmd==DelCellCmd::Cols ) + nCurX = aRange.aStart.Col(); + else + nCurY = aRange.aStart.Row(); + SetCursor( nCurX, nCurY ); + + if (comphelper::LibreOfficeKit::isActive()) + { + bool bColsDeleted = (eCmd == DelCellCmd::Cols); + bool bRowsDeleted = (eCmd == DelCellCmd::Rows); + if (bColsDeleted) + ScTabViewShell::notifyAllViewsHeaderInvalidation(GetViewData().GetViewShell(), COLUMN_HEADER, GetViewData().GetTabNo()); + + if (bRowsDeleted) + ScTabViewShell::notifyAllViewsHeaderInvalidation(GetViewData().GetViewShell(), ROW_HEADER, GetViewData().GetTabNo()); + + ScTabViewShell::notifyAllViewsSheetGeomInvalidation(GetViewData().GetViewShell(), + bColsDeleted, bRowsDeleted, true /* bSizes*/, + true /* bHidden */, true /* bFiltered */, + true /* bGroups */, GetViewData().GetTabNo()); + } + } + else + { + if (eCmd == DelCellCmd::Cols) + DeleteMulti( false ); + else if (eCmd == DelCellCmd::Rows) + DeleteMulti( true ); + else + ErrorMessage(STR_NOMULTISELECT); + } + + OUString aStartAddress = aRange.aStart.GetColRowString(); + OUString aEndAddress = aRange.aEnd.GetColRowString(); + collectUIInformation({{"RANGE", aStartAddress + ":" + aEndAddress}}, "DELETE_CELLS"); + + Unmark(); +} + +void ScViewFunc::DeleteMulti( bool bRows ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocShellModificator aModificator( *pDocSh ); + SCTAB nTab = GetViewData().GetTabNo(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScMarkData aFuncMark( GetViewData().GetMarkData() ); // local copy for UnmarkFiltered + ScViewUtil::UnmarkFiltered( aFuncMark, rDoc ); + + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + + std::vector<sc::ColRowSpan> aSpans; + if (bRows) + aSpans = aFuncMark.GetMarkedRowSpans(); + else + aSpans = aFuncMark.GetMarkedColSpans(); + + if (aSpans.empty()) + { + SCCOLROW nCurPos = bRows ? GetViewData().GetCurY() : GetViewData().GetCurX(); + aSpans.emplace_back(nCurPos, nCurPos); + } + + // test if allowed + + TranslateId pErrorId; + bool bNeedRefresh = false; + for (size_t i = 0, n = aSpans.size(); i < n && !pErrorId; ++i) + { + SCCOLROW nStart = aSpans[i].mnStart; + SCCOLROW nEnd = aSpans[i].mnEnd; + + SCCOL nStartCol, nEndCol; + SCROW nStartRow, nEndRow; + if ( bRows ) + { + nStartCol = 0; + nEndCol = rDoc.MaxCol(); + nStartRow = static_cast<SCROW>(nStart); + nEndRow = static_cast<SCROW>(nEnd); + } + else + { + nStartCol = static_cast<SCCOL>(nStart); + nEndCol = static_cast<SCCOL>(nEnd); + nStartRow = 0; + nEndRow = rDoc.MaxRow(); + } + + // cell protection (only needed for first range, as all following cells are moved) + if (i == 0) + { + // test to the end of the sheet + ScEditableTester aTester( rDoc, nTab, nStartCol, nStartRow, rDoc.MaxCol(), rDoc.MaxRow() ); + if (!aTester.IsEditable()) + pErrorId = aTester.GetMessageId(); + } + + // merged cells + SCCOL nMergeStartX = nStartCol; + SCROW nMergeStartY = nStartRow; + SCCOL nMergeEndX = nEndCol; + SCROW nMergeEndY = nEndRow; + rDoc.ExtendMerge( nMergeStartX, nMergeStartY, nMergeEndX, nMergeEndY, nTab ); + rDoc.ExtendOverlapped( nMergeStartX, nMergeStartY, nMergeEndX, nMergeEndY, nTab ); + + if ( nMergeStartX != nStartCol || nMergeStartY != nStartRow ) + { + // Disallow deleting parts of a merged cell. + // Deleting the start is allowed (merge is removed), so the end doesn't have to be checked. + + pErrorId = STR_MSSG_DELETECELLS_0; + } + if ( nMergeEndX != nEndCol || nMergeEndY != nEndRow ) + { + // detect if the start of a merged cell is deleted, so the merge flags can be refreshed + + bNeedRefresh = true; + } + } + + if (pErrorId) + { + ErrorMessage(pErrorId); + return; + } + + // proceed + + weld::WaitObject aWait(GetViewData().GetDialogParent()); // important for TrackFormulas in UpdateReference + + ResetAutoSpellForContentChange(); + + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr<ScRefUndoData> pUndoData; + if (bRecord) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nTab, nTab, !bRows, bRows ); // row height + + for (const sc::ColRowSpan & rSpan : aSpans) + { + SCCOLROW nStart = rSpan.mnStart; + SCCOLROW nEnd = rSpan.mnEnd; + if (bRows) + rDoc.CopyToDocument( 0,nStart,nTab, rDoc.MaxCol(), nEnd,nTab, InsertDeleteFlags::ALL,false,*pUndoDoc ); + else + rDoc.CopyToDocument( static_cast<SCCOL>(nStart),0,nTab, + static_cast<SCCOL>(nEnd), rDoc.MaxRow(), nTab, + InsertDeleteFlags::ALL,false,*pUndoDoc ); + } + + // all Formulas because of references + SCTAB nTabCount = rDoc.GetTableCount(); + pUndoDoc->AddUndoTab( 0, nTabCount-1 ); + rDoc.CopyToDocument( 0,0,0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB, InsertDeleteFlags::FORMULA,false,*pUndoDoc ); + + pUndoData.reset(new ScRefUndoData( &rDoc )); + + rDoc.BeginDrawUndo(); + } + + std::vector<sc::ColRowSpan>::const_reverse_iterator ri = aSpans.rbegin(), riEnd = aSpans.rend(); + aFuncMark.SelectOneTable(nTab); + for (; ri != riEnd; ++ri) + { + SCCOLROW nEnd = ri->mnEnd; + SCCOLROW nStart = ri->mnStart; + + if (bRows) + { + rDoc.DeleteObjectsInArea(0, nStart, rDoc.MaxCol(), nEnd, aFuncMark, true); + rDoc.DeleteRow(0, nTab, rDoc.MaxCol(), nTab, nStart, static_cast<SCSIZE>(nEnd - nStart + 1)); + } + else + { + rDoc.DeleteObjectsInArea(nStart, 0, nEnd, rDoc.MaxRow(), aFuncMark, true); + rDoc.DeleteCol(0, nTab, rDoc.MaxRow(), nTab, static_cast<SCCOL>(nStart), static_cast<SCSIZE>(nEnd - nStart + 1)); + } + } + + if (bNeedRefresh) + { + SCCOLROW nFirstStart = aSpans[0].mnStart; + SCCOL nStartCol = bRows ? 0 : static_cast<SCCOL>(nFirstStart); + SCROW nStartRow = bRows ? static_cast<SCROW>(nFirstStart) : 0; + SCCOL nEndCol = rDoc.MaxCol(); + SCROW nEndRow = rDoc.MaxRow(); + + rDoc.RemoveFlagsTab( nStartCol, nStartRow, nEndCol, nEndRow, nTab, ScMF::Hor | ScMF::Ver ); + rDoc.ExtendMerge( nStartCol, nStartRow, nEndCol, nEndRow, nTab, true ); + } + + if (bRecord) + { + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDeleteMulti>( + pDocSh, bRows, bNeedRefresh, nTab, std::vector(aSpans), std::move(pUndoDoc), std::move(pUndoData))); + } + + if (!AdjustRowHeight(0, rDoc.MaxRow(), true)) + { + if (bRows) + { + pDocSh->PostPaint( + 0, aSpans[0].mnStart, nTab, + rDoc.MaxCol(), rDoc.MaxRow(), nTab, (PaintPartFlags::Grid | PaintPartFlags::Left)); + } + else + { + pDocSh->PostPaint( + static_cast<SCCOL>(aSpans[0].mnStart), 0, nTab, + rDoc.MaxCol(), rDoc.MaxRow(), nTab, (PaintPartFlags::Grid | PaintPartFlags::Top)); + } + } + + aModificator.SetDocumentModified(); + + CellContentChanged(); + + // put cursor directly behind the first deleted range + SCCOL nCurX = GetViewData().GetCurX(); + SCROW nCurY = GetViewData().GetCurY(); + if ( bRows ) + nCurY = aSpans[0].mnStart; + else + nCurX = static_cast<SCCOL>(aSpans[0].mnStart); + SetCursor( nCurX, nCurY ); + + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) ); +} + +// delete contents + +void ScViewFunc::DeleteContents( InsertDeleteFlags nFlags ) +{ + ScViewData& rViewData = GetViewData(); + rViewData.SetPasteMode( ScPasteFlags::NONE ); + rViewData.GetViewShell()->UpdateCopySourceOverlay(); + + // not editable because of matrix only? attribute OK nonetheless + bool bOnlyNotBecauseOfMatrix; + bool bEditable = SelectionEditable( &bOnlyNotBecauseOfMatrix ); + if ( !bEditable ) + { + if ( !(bOnlyNotBecauseOfMatrix && + ((nFlags & (InsertDeleteFlags::ATTRIB | InsertDeleteFlags::EDITATTR)) == nFlags)) ) + { + ErrorMessage(bOnlyNotBecauseOfMatrix ? STR_MATRIXFRAGMENTERR : STR_PROTECTIONERR); + return; + } + } + + ScRange aMarkRange; + bool bSimple = false; + + ScDocument& rDoc = GetViewData().GetDocument(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScMarkData aFuncMark( GetViewData().GetMarkData() ); // local copy for UnmarkFiltered + ScViewUtil::UnmarkFiltered( aFuncMark, rDoc ); + + bool bRecord =true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + + if ( !aFuncMark.IsMarked() && !aFuncMark.IsMultiMarked() ) + { + aMarkRange.aStart.SetCol(GetViewData().GetCurX()); + aMarkRange.aStart.SetRow(GetViewData().GetCurY()); + aMarkRange.aStart.SetTab(GetViewData().GetTabNo()); + aMarkRange.aEnd = aMarkRange.aStart; + if ( rDoc.HasAttrib( aMarkRange, HasAttrFlags::Merged ) ) + { + aFuncMark.SetMarkArea( aMarkRange ); + } + else + bSimple = true; + } + + HideAllCursors(); // for if summary is cancelled + + ScDocFunc& rDocFunc = pDocSh->GetDocFunc(); + + // Can we really be sure that we can pass the bApi parameter as false to DeleteCell() and + // DeleteContents() here? (Meaning that this is interactive use.) Is this never invoked from + // scripting and whatnot? + if (bSimple) + rDocFunc.DeleteCell(aMarkRange.aStart, aFuncMark, nFlags, bRecord, /*bApi=*/ false); + else + rDocFunc.DeleteContents(aFuncMark, nFlags, bRecord, /*bApi=*/ false); + + pDocSh->UpdateOle(GetViewData()); + + if (ScModelObj *pModelObj = HelperNotifyChanges::getMustPropagateChangesModel(*pDocSh)) + { + ScRangeList aChangeRanges; + if ( bSimple ) + { + aChangeRanges.push_back( aMarkRange ); + } + else + { + aFuncMark.FillRangeListWithMarks( &aChangeRanges, false ); + } + HelperNotifyChanges::Notify(*pModelObj, aChangeRanges); + } + + CellContentChanged(); + ShowAllCursors(); + + if ( nFlags & InsertDeleteFlags::ATTRIB ) + { + if ( nFlags & InsertDeleteFlags::CONTENTS ) + bFormatValid = false; + else + StartFormatArea(); // delete attribute is also attribute-change + } + OUString aStartAddress = aMarkRange.aStart.GetColRowString(); + OUString aEndAddress = aMarkRange.aEnd.GetColRowString(); + collectUIInformation({{"RANGE", aStartAddress + ":" + aEndAddress}}, "DELETE"); +} + +// column width/row height (via header) - undo OK + +void ScViewFunc::SetWidthOrHeight( + bool bWidth, const std::vector<sc::ColRowSpan>& rRanges, ScSizeMode eMode, + sal_uInt16 nSizeTwips, bool bRecord, const ScMarkData* pMarkData ) +{ + if (rRanges.empty()) + return; + + // Use view's mark if none specified, but do not modify the original data, + // i.e. no MarkToMulti() on that. + ScMarkData aMarkData( pMarkData ? *pMarkData : GetViewData().GetMarkData()); + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + SCCOL nCurX = GetViewData().GetCurX(); + SCROW nCurY = GetViewData().GetCurY(); + SCTAB nFirstTab = aMarkData.GetFirstSelected(); + SCTAB nCurTab = GetViewData().GetTabNo(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + ScDocShellModificator aModificator( *pDocSh ); + + bool bAllowed = true; + for (const SCTAB& nTab : aMarkData) + { + bAllowed = std::all_of(rRanges.begin(), rRanges.end(), + [&bWidth, &rDoc, &nTab](const sc::ColRowSpan& rRange) { + bool bOnlyMatrix; + bool bIsBlockEditable; + if (bWidth) + bIsBlockEditable = rDoc.IsBlockEditable(nTab, rRange.mnStart, 0, rRange.mnEnd, rDoc.MaxRow(), &bOnlyMatrix); + else + bIsBlockEditable = rDoc.IsBlockEditable(nTab, 0, rRange.mnStart, rDoc.MaxCol(), rRange.mnEnd, &bOnlyMatrix); + return bIsBlockEditable || bOnlyMatrix; + }); + if (!bAllowed) + break; + } + + // Allow users to resize cols/rows in readonly docs despite the r/o state. + // It is frustrating to be unable to see content in mis-sized cells. + if( !bAllowed && !pDocSh->IsReadOnly() ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + SCCOLROW nStart = rRanges.front().mnStart; + SCCOLROW nEnd = rRanges.back().mnEnd; + + OnLOKSetWidthOrHeight(nStart, bWidth); + + bool bFormula = false; + if ( eMode == SC_SIZE_OPTIMAL ) + { + const ScViewOptions& rOpts = GetViewData().GetOptions(); + bFormula = rOpts.GetOption( VOPT_FORMULAS ); + } + + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr<ScOutlineTable> pUndoTab; + std::vector<sc::ColRowSpan> aUndoRanges; + + if ( bRecord ) + { + rDoc.BeginDrawUndo(); // Drawing Updates + + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + for (const SCTAB& nTab : aMarkData) + { + if (bWidth) + { + if ( nTab == nFirstTab ) + pUndoDoc->InitUndo( rDoc, nTab, nTab, true ); + else + pUndoDoc->AddUndoTab( nTab, nTab, true ); + rDoc.CopyToDocument( static_cast<SCCOL>(nStart), 0, nTab, + static_cast<SCCOL>(nEnd), rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, + false, *pUndoDoc ); + } + else + { + if ( nTab == nFirstTab ) + pUndoDoc->InitUndo( rDoc, nTab, nTab, false, true ); + else + pUndoDoc->AddUndoTab( nTab, nTab, false, true ); + rDoc.CopyToDocument( 0, nStart, nTab, rDoc.MaxCol(), nEnd, nTab, InsertDeleteFlags::NONE, false, *pUndoDoc ); + } + } + + aUndoRanges = rRanges; + + //! outlines from all tab? + ScOutlineTable* pTable = rDoc.GetOutlineTable( nCurTab ); + if (pTable) + pUndoTab.reset(new ScOutlineTable( *pTable )); + } + + if ( eMode==SC_SIZE_OPTIMAL || eMode==SC_SIZE_VISOPT ) + aMarkData.MarkToMulti(); + + bool bShow = nSizeTwips > 0 || eMode != SC_SIZE_DIRECT; + bool bOutline = false; + + for (const SCTAB& nTab : aMarkData) + { + for (const sc::ColRowSpan & rRange : rRanges) + { + SCCOLROW nStartNo = rRange.mnStart; + SCCOLROW nEndNo = rRange.mnEnd; + + if ( !bWidth ) // height always blockwise + { + if ( eMode==SC_SIZE_OPTIMAL || eMode==SC_SIZE_VISOPT ) + { + bool bAll = ( eMode==SC_SIZE_OPTIMAL ); + if (!bAll) + { + // delete CRFlags::ManualSize for all in range, + // then SetOptimalHeight with bShrink = FALSE + for (SCROW nRow = nStartNo; nRow <= nEndNo; ++nRow) + { + SCROW nLastRow = nRow; + if (rDoc.RowHidden(nRow, nTab, nullptr, &nLastRow)) + { + nRow = nLastRow; + continue; + } + + CRFlags nOld = rDoc.GetRowFlags(nRow, nTab); + if (nOld & CRFlags::ManualSize) + rDoc.SetRowFlags(nRow, nTab, nOld & ~CRFlags::ManualSize); + } + } + + double nPPTX = GetViewData().GetPPTX(); + double nPPTY = GetViewData().GetPPTY(); + Fraction aZoomX = GetViewData().GetZoomX(); + Fraction aZoomY = GetViewData().GetZoomY(); + + ScSizeDeviceProvider aProv(pDocSh); + if (aProv.IsPrinter()) + { + nPPTX = aProv.GetPPTX(); + nPPTY = aProv.GetPPTY(); + aZoomX = aZoomY = Fraction( 1, 1 ); + } + + sc::RowHeightContext aCxt(rDoc.MaxRow(), nPPTX, nPPTY, aZoomX, aZoomY, aProv.GetDevice()); + aCxt.setForceAutoSize(bAll); + aCxt.setExtraHeight(nSizeTwips); + rDoc.SetOptimalHeight(aCxt, nStartNo, nEndNo, nTab, true); + + // Manual-Flag already (re)set in SetOptimalHeight in case of bAll=sal_True + // (set for Extra-Height, else reset). + } + else if ( eMode==SC_SIZE_DIRECT ) + { + if (nSizeTwips) + { + rDoc.SetRowHeightRange( nStartNo, nEndNo, nTab, nSizeTwips ); + rDoc.SetManualHeight( nStartNo, nEndNo, nTab, true ); // height was set manually + } + + rDoc.ShowRows( nStartNo, nEndNo, nTab, nSizeTwips != 0 ); + + if (!bShow && nStartNo <= nCurY && nCurY <= nEndNo && nTab == nCurTab) + { + nCurY = -1; + } + } + else if ( eMode==SC_SIZE_SHOW ) + { + rDoc.ShowRows( nStartNo, nEndNo, nTab, true ); + } + } + else // column width + { + for (SCCOL nCol=static_cast<SCCOL>(nStartNo); nCol<=static_cast<SCCOL>(nEndNo); nCol++) + { + const bool bIsColHidden = rDoc.ColHidden(nCol, nTab); + if ( eMode != SC_SIZE_VISOPT || !bIsColHidden ) + { + sal_uInt16 nThisSize = nSizeTwips; + + if ( eMode==SC_SIZE_OPTIMAL || eMode==SC_SIZE_VISOPT ) + nThisSize = nSizeTwips + GetOptimalColWidth( nCol, nTab, bFormula ); + if ( nThisSize ) + rDoc.SetColWidth( nCol, nTab, nThisSize ); + + // tdf#131073 - Don't show hidden cols after setting optimal col width + if (eMode == SC_SIZE_OPTIMAL) + rDoc.ShowCol(nCol, nTab, !bIsColHidden); + else + rDoc.ShowCol( nCol, nTab, bShow ); + + if (!bShow && nCol == nCurX && nTab == nCurTab) + { + nCurX = -1; + } + } + } + } + + // adjust outline + if (bWidth) + { + if ( rDoc.UpdateOutlineCol( static_cast<SCCOL>(nStartNo), + static_cast<SCCOL>(nEndNo), nTab, bShow ) ) + bOutline = true; + } + else + { + if ( rDoc.UpdateOutlineRow( nStartNo, nEndNo, nTab, bShow ) ) + bOutline = true; + } + } + rDoc.SetDrawPageSize(nTab); + } + + if (!bOutline) + pUndoTab.reset(); + + if (bRecord) + { + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoWidthOrHeight>( + pDocSh, aMarkData, nStart, nCurTab, nEnd, nCurTab, + std::move(pUndoDoc), std::move(aUndoRanges), std::move(pUndoTab), eMode, nSizeTwips, bWidth)); + } + + if (nCurX < 0) + { + MoveCursorRel( 1, 0, SC_FOLLOW_LINE, false ); + } + + if (nCurY < 0) + { + MoveCursorRel( 0, 1, SC_FOLLOW_LINE, false ); + } + + // fdo#36247 Ensure that the drawing layer's map mode scaling factors match + // the new heights and widths. + GetViewData().GetView()->RefreshZoom(); + + for (const SCTAB& nTab : aMarkData) + rDoc.UpdatePageBreaks( nTab ); + + bool bAffectsVisibility = (eMode != SC_SIZE_ORIGINAL && eMode != SC_SIZE_VISOPT); + ScTabViewShell::notifyAllViewsSheetGeomInvalidation(GetViewData().GetViewShell(), + bWidth /* bColumns */, !bWidth /* bRows */, + true /* bSizes*/, bAffectsVisibility /* bHidden */, bAffectsVisibility /* bFiltered */, + false /* bGroups */, nCurTab); + GetViewData().GetView()->UpdateScrollBars(bWidth ? COLUMN_HEADER : ROW_HEADER); + + { + for (const SCTAB& nTab : aMarkData) + { + if (bWidth) + { + if (rDoc.HasAttrib( static_cast<SCCOL>(nStart),0,nTab, + static_cast<SCCOL>(nEnd), rDoc.MaxRow(), nTab, + HasAttrFlags::Merged | HasAttrFlags::Overlapped )) + nStart = 0; + if (nStart > 0) // go upwards because of Lines and cursor + --nStart; + pDocSh->PostPaint( static_cast<SCCOL>(nStart), 0, nTab, + rDoc.MaxCol(), rDoc.MaxRow(), nTab, PaintPartFlags::Grid | PaintPartFlags::Top ); + } + else + { + if (rDoc.HasAttrib( 0,nStart,nTab, rDoc.MaxCol(), nEnd,nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped )) + nStart = 0; + if (nStart != 0) + --nStart; + pDocSh->PostPaint( 0, nStart, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, PaintPartFlags::Grid | PaintPartFlags::Left ); + } + } + + pDocSh->UpdateOle(GetViewData()); + if( !pDocSh->IsReadOnly() ) + aModificator.SetDocumentModified(); + } + + if ( !bWidth ) + return; + + ScModelObj* pModelObj = HelperNotifyChanges::getMustPropagateChangesModel(*pDocSh); + if (!pModelObj) + return; + + ScRangeList aChangeRanges; + for (const SCTAB& nTab : aMarkData) + { + for (const sc::ColRowSpan & rRange : rRanges) + { + SCCOL nStartCol = rRange.mnStart; + SCCOL nEndCol = rRange.mnEnd; + for ( SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol ) + { + aChangeRanges.push_back( ScRange( nCol, 0, nTab ) ); + } + } + } + HelperNotifyChanges::Notify(*pModelObj, aChangeRanges, "column-resize"); +} + +// column width/row height (via marked range) + +void ScViewFunc::SetMarkedWidthOrHeight( bool bWidth, ScSizeMode eMode, sal_uInt16 nSizeTwips ) +{ + ScMarkData& rMark = GetViewData().GetMarkData(); + + rMark.MarkToMulti(); + if (!rMark.IsMultiMarked()) + { + SCCOL nCol = GetViewData().GetCurX(); + SCROW nRow = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + const ScRange aMarkRange( nCol, nRow, nTab); + DoneBlockMode(); + InitOwnBlockMode( aMarkRange ); + rMark.SetMultiMarkArea( aMarkRange ); + MarkDataChanged(); + } + + std::vector<sc::ColRowSpan> aRanges = + bWidth ? rMark.GetMarkedColSpans() : rMark.GetMarkedRowSpans(); + + SetWidthOrHeight(bWidth, aRanges, eMode, nSizeTwips); + + rMark.MarkToSimple(); +} + +void ScViewFunc::ModifyCellSize( ScDirection eDir, bool bOptimal ) +{ + //! step size adjustable + // step size is also minimum + constexpr sal_uInt16 nStepX = STD_COL_WIDTH / 5; + sal_uInt16 nStepY = ScGlobal::nStdRowHeight; + + ScModule* pScMod = SC_MOD(); + bool bAnyEdit = pScMod->IsInputMode(); + SCCOL nCol = GetViewData().GetCurX(); + SCROW nRow = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + + bool bAllowed, bOnlyMatrix; + if ( eDir == DIR_LEFT || eDir == DIR_RIGHT ) + bAllowed = rDoc.IsBlockEditable( nTab, nCol,0, nCol,rDoc.MaxRow(), &bOnlyMatrix ); + else + bAllowed = rDoc.IsBlockEditable( nTab, 0,nRow, rDoc.MaxCol(), nRow, &bOnlyMatrix ); + if ( !bAllowed && !bOnlyMatrix ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + HideAllCursors(); + + sal_uInt16 nWidth = rDoc.GetColWidth( nCol, nTab ); + sal_uInt16 nHeight = rDoc.GetRowHeight( nRow, nTab ); + std::vector<sc::ColRowSpan> aRange(1, sc::ColRowSpan(0,0)); + if ( eDir == DIR_LEFT || eDir == DIR_RIGHT ) + { + if (bOptimal) // width of this single cell + { + if ( bAnyEdit ) + { + // when editing the actual entered width + ScInputHandler* pHdl = pScMod->GetInputHdl( GetViewData().GetViewShell() ); + if (pHdl) + { + tools::Long nEdit = pHdl->GetTextSize().Width(); // in 0.01 mm + + const ScPatternAttr* pPattern = rDoc.GetPattern( nCol, nRow, nTab ); + const SvxMarginItem& rMItem = pPattern->GetItem(ATTR_MARGIN); + sal_uInt16 nMargin = rMItem.GetLeftMargin() + rMItem.GetRightMargin(); + if ( pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue() == SvxCellHorJustify::Left ) + nMargin = sal::static_int_cast<sal_uInt16>( + nMargin + pPattern->GetItem(ATTR_INDENT).GetValue() ); + + nWidth = std::round(o3tl::convert(nEdit * pDocSh->GetOutputFactor(), + o3tl::Length::mm100, o3tl::Length::twip)) + + nMargin + STD_EXTRA_WIDTH; + } + } + else + { + double nPPTX = GetViewData().GetPPTX(); + double nPPTY = GetViewData().GetPPTY(); + Fraction aZoomX = GetViewData().GetZoomX(); + Fraction aZoomY = GetViewData().GetZoomY(); + + ScSizeDeviceProvider aProv(pDocSh); + if (aProv.IsPrinter()) + { + nPPTX = aProv.GetPPTX(); + nPPTY = aProv.GetPPTY(); + aZoomX = aZoomY = Fraction( 1, 1 ); + } + + tools::Long nPixel = rDoc.GetNeededSize( nCol, nRow, nTab, aProv.GetDevice(), + nPPTX, nPPTY, aZoomX, aZoomY, true ); + sal_uInt16 nTwips = static_cast<sal_uInt16>( nPixel / nPPTX ); + if (nTwips != 0) + nWidth = nTwips + STD_EXTRA_WIDTH; + else + nWidth = STD_COL_WIDTH; + } + } + else // increment / decrement + { + if ( eDir == DIR_RIGHT ) + nWidth = sal::static_int_cast<sal_uInt16>( nWidth + nStepX ); + else if ( nWidth > nStepX ) + nWidth = sal::static_int_cast<sal_uInt16>( nWidth - nStepX ); + if ( nWidth < nStepX ) nWidth = nStepX; + if ( nWidth > MAX_COL_WIDTH ) nWidth = MAX_COL_WIDTH; + } + aRange[0].mnStart = nCol; + aRange[0].mnEnd = nCol; + SetWidthOrHeight(true, aRange, SC_SIZE_DIRECT, nWidth); + + // adjust height of this row if width demands/allows this + + if (!bAnyEdit) + { + const ScPatternAttr* pPattern = rDoc.GetPattern( nCol, nRow, nTab ); + bool bNeedHeight = + pPattern->GetItem( ATTR_LINEBREAK ).GetValue() || + pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue() == SvxCellHorJustify::Block; + if (bNeedHeight) + AdjustRowHeight( nRow, nRow, true ); + } + } + else + { + ScSizeMode eMode; + if (bOptimal) + { + eMode = SC_SIZE_OPTIMAL; + nHeight = 0; + } + else + { + eMode = SC_SIZE_DIRECT; + if ( eDir == DIR_BOTTOM ) + nHeight = sal::static_int_cast<sal_uInt16>( nHeight + nStepY ); + else if ( nHeight > nStepY ) + nHeight = sal::static_int_cast<sal_uInt16>( nHeight - nStepY ); + if ( nHeight < nStepY ) nHeight = nStepY; + if ( nHeight > MAX_ROW_HEIGHT ) nHeight = MAX_ROW_HEIGHT; + } + aRange[0].mnStart = nRow; + aRange[0].mnEnd = nRow; + SetWidthOrHeight(false, aRange, eMode, nHeight); + } + + if ( bAnyEdit ) + { + UpdateEditView(); + if ( rDoc.HasAttrib( nCol, nRow, nTab, nCol, nRow, nTab, HasAttrFlags::NeedHeight ) ) + { + ScInputHandler* pHdl = pScMod->GetInputHdl( GetViewData().GetViewShell() ); + if (pHdl) + pHdl->SetModified(); // so that the height is adjusted with Enter + } + } + + ShowAllCursors(); +} + +void ScViewFunc::ProtectSheet( SCTAB nTab, const ScTableProtection& rProtect ) +{ + if (nTab == TABLEID_DOC) + return; + + ScMarkData& rMark = GetViewData().GetMarkData(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScDocFunc &rFunc = pDocSh->GetDocFunc(); + bool bUndo(rDoc.IsUndoEnabled()); + + // modifying several tabs is handled here + + if (bUndo) + { + OUString aUndo = ScResId( STR_UNDO_PROTECT_TAB ); + pDocSh->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, GetViewData().GetViewShell()->GetViewShellId() ); + } + + for (const auto& rTab : rMark) + { + rFunc.ProtectSheet(rTab, rProtect); + } + + if (bUndo) + pDocSh->GetUndoManager()->LeaveListAction(); + + UpdateLayerLocks(); //! broadcast to all views +} + +void ScViewFunc::ProtectDoc( const OUString& rPassword ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocFunc &rFunc = pDocSh->GetDocFunc(); + + rFunc.Protect( TABLEID_DOC, rPassword ); + + UpdateLayerLocks(); //! broadcast to all views +} + +bool ScViewFunc::Unprotect( SCTAB nTab, const OUString& rPassword ) +{ + ScMarkData& rMark = GetViewData().GetMarkData(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScDocFunc &rFunc = pDocSh->GetDocFunc(); + bool bChanged = false; + bool bUndo (rDoc.IsUndoEnabled()); + + if ( nTab == TABLEID_DOC || rMark.GetSelectCount() <= 1 ) + { + bChanged = rFunc.Unprotect( nTab, rPassword, false ); + if (bChanged && nTab != TABLEID_DOC) + SetTabProtectionSymbol(nTab, false); + } + else + { + // modifying several tabs is handled here + + if (bUndo) + { + OUString aUndo = ScResId( STR_UNDO_UNPROTECT_TAB ); + pDocSh->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, GetViewData().GetViewShell()->GetViewShellId() ); + } + + for (const auto& rTab : rMark) + { + if ( rFunc.Unprotect( rTab, rPassword, false ) ) + { + bChanged = true; + SetTabProtectionSymbol( rTab, false); + } + } + + if (bUndo) + pDocSh->GetUndoManager()->LeaveListAction(); + } + + if (bChanged) + UpdateLayerLocks(); //! broadcast to all views + + return bChanged; +} + +void ScViewFunc::SetNoteText( const ScAddress& rPos, const OUString& rNoteText ) +{ + GetViewData().GetDocShell()->GetDocFunc().SetNoteText( rPos, rNoteText, false ); +} + +void ScViewFunc::ReplaceNote( const ScAddress& rPos, const OUString& rNoteText, const OUString* pAuthor, const OUString* pDate ) +{ + GetViewData().GetDocShell()->GetDocFunc().ReplaceNote( rPos, rNoteText, pAuthor, pDate, false ); +} + +void ScViewFunc::SetNumberFormat( SvNumFormatType nFormatType, sal_uLong nAdd ) +{ + // not editable because of matrix only? attribute OK nonetheless + bool bOnlyNotBecauseOfMatrix; + if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + sal_uInt32 nNumberFormat = 0; + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + SvNumberFormatter* pNumberFormatter = rDoc.GetFormatTable(); + LanguageType eLanguage = ScGlobal::eLnge; + ScPatternAttr aNewAttrs( rDoc.GetPool() ); + + // always take language from cursor position, even if there is a selection + + sal_uInt32 nCurrentNumberFormat = rDoc.GetNumberFormat( rViewData.GetCurX(), + rViewData.GetCurY(), + rViewData.GetTabNo()); + const SvNumberformat* pEntry = pNumberFormatter->GetEntry( nCurrentNumberFormat ); + if (pEntry) + eLanguage = pEntry->GetLanguage(); // else keep ScGlobal::eLnge + + nNumberFormat = pNumberFormatter->GetStandardFormat( nFormatType, eLanguage ) + nAdd; + + SfxItemSet& rSet = aNewAttrs.GetItemSet(); + rSet.Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nNumberFormat ) ); + // ATTR_LANGUAGE_FORMAT not + ApplySelectionPattern( aNewAttrs ); +} + +void ScViewFunc::SetNumFmtByStr( const OUString& rCode ) +{ + // not editable because of matrix only? attribute OK nonetheless + bool bOnlyNotBecauseOfMatrix; + if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + + // language always from cursor position + + sal_uInt32 nCurrentNumberFormat = rDoc.GetNumberFormat( rViewData.GetCurX(), rViewData.GetCurY(), + rViewData.GetTabNo()); + const SvNumberformat* pEntry = pFormatter->GetEntry( nCurrentNumberFormat ); + LanguageType eLanguage = pEntry ? pEntry->GetLanguage() : ScGlobal::eLnge; + + // determine index for String + + bool bOk = true; + sal_uInt32 nNumberFormat = pFormatter->GetEntryKey( rCode, eLanguage ); + if ( nNumberFormat == NUMBERFORMAT_ENTRY_NOT_FOUND ) + { + // enter new + + OUString aFormat = rCode; // will be changed + sal_Int32 nErrPos = 0; + SvNumFormatType nType = SvNumFormatType::ALL; //! ??? + bOk = pFormatter->PutEntry( aFormat, nErrPos, nType, nNumberFormat, eLanguage ); + } + + if ( bOk ) // valid format? + { + ScPatternAttr aNewAttrs( rDoc.GetPool() ); + SfxItemSet& rSet = aNewAttrs.GetItemSet(); + rSet.Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nNumberFormat ) ); + rSet.Put( SvxLanguageItem( eLanguage, ATTR_LANGUAGE_FORMAT ) ); + ApplySelectionPattern( aNewAttrs ); + } + + //! else return error / issue warning ??? +} + +void ScViewFunc::ChangeNumFmtDecimals( bool bIncrement ) +{ + // not editable because of matrix only? attribute OK nonetheless + bool bOnlyNotBecauseOfMatrix; + if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + ScDocument& rDoc = GetViewData().GetDocument(); + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + + SCCOL nCol = GetViewData().GetCurX(); + SCROW nRow = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + + sal_uInt32 nOldFormat = rDoc.GetNumberFormat( nCol, nRow, nTab ); + const SvNumberformat* pOldEntry = pFormatter->GetEntry( nOldFormat ); + if (!pOldEntry) + { + OSL_FAIL("numberformat not found !!!"); + return; + } + + // what have we got here? + + sal_uInt32 nNewFormat = nOldFormat; + bool bError = false; + + LanguageType eLanguage = pOldEntry->GetLanguage(); + bool bThousand, bNegRed; + sal_uInt16 nPrecision, nLeading; + pOldEntry->GetFormatSpecialInfo( bThousand, bNegRed, nPrecision, nLeading ); + + SvNumFormatType nOldType = pOldEntry->GetType(); + if ( SvNumFormatType::ALL == ( nOldType & ( + SvNumFormatType::NUMBER | SvNumFormatType::CURRENCY | SvNumFormatType::PERCENT | SvNumFormatType::SCIENTIFIC | SvNumFormatType::TIME ) ) ) + { + // date, fraction, logical, text can not be changed + bError = true; + } + + //! SvNumberformat has a Member bStandard, but doesn't disclose it + bool bWasStandard = ( nOldFormat == pFormatter->GetStandardIndex( eLanguage ) ); + OUString sExponentialStandardFormat = ""; + if (bWasStandard) + { + // with "Standard" the decimal places depend on cell content + // 0 if empty or text -> no decimal places + double nVal = rDoc.GetValue( ScAddress( nCol, nRow, nTab ) ); + + // the ways of the Numberformatters are unfathomable, so try: + OUString aOut; + const Color* pCol; + const_cast<SvNumberformat*>(pOldEntry)->GetOutputString( nVal, aOut, &pCol ); + + nPrecision = 0; + // 'E' for exponential is fixed in Numberformatter + sal_Int32 nIndexE = aOut.indexOf('E'); + if ( nIndexE >= 0 ) + { + sExponentialStandardFormat = aOut.copy( nIndexE ).replace( '-', '+' ); + for ( sal_Int32 i=1 ; i<sExponentialStandardFormat.getLength() ; i++ ) + { + if ( sExponentialStandardFormat[i] >= '1' && sExponentialStandardFormat[i] <= '9' ) + sExponentialStandardFormat = sExponentialStandardFormat.replaceAt( i, 1, u"0" ); + } + aOut = aOut.copy( 0, nIndexE ); // remove exponential part + } + OUString aDecSep( pFormatter->GetFormatDecimalSep( nOldFormat ) ); + sal_Int32 nPos = aOut.indexOf( aDecSep ); + if ( nPos >= 0 ) + nPrecision = aOut.getLength() - nPos - aDecSep.getLength(); + // else keep 0 + } + else + { + if ( (nOldType & SvNumFormatType::SCIENTIFIC) && !bThousand && + (pOldEntry->GetFormatIntegerDigits()%3 == 0) && pOldEntry->GetFormatIntegerDigits() > 0 ) + bThousand = true; + } + + if (!bError) + { + if (bIncrement) + { + if (nPrecision<20) + ++nPrecision; // increment + else + bError = true; // 20 is maximum + } + else + { + if (nPrecision) + --nPrecision; // decrement + else + bError = true; // 0 is minimum + } + } + + if (!bError) + { + OUString aNewPicture = pFormatter->GenerateFormat(nOldFormat, eLanguage, + bThousand, bNegRed, + nPrecision, nLeading) + + sExponentialStandardFormat; + + nNewFormat = pFormatter->GetEntryKey( aNewPicture, eLanguage ); + if ( nNewFormat == NUMBERFORMAT_ENTRY_NOT_FOUND ) + { + sal_Int32 nErrPos = 0; + SvNumFormatType nNewType = SvNumFormatType::ALL; + bool bOk = pFormatter->PutEntry( aNewPicture, nErrPos, + nNewType, nNewFormat, eLanguage ); + OSL_ENSURE( bOk, "incorrect numberformat generated" ); + if (!bOk) + bError = true; + } + } + + if (!bError) + { + ScPatternAttr aNewAttrs( rDoc.GetPool() ); + SfxItemSet& rSet = aNewAttrs.GetItemSet(); + rSet.Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nNewFormat ) ); + // ATTR_LANGUAGE_FORMAT not + ApplySelectionPattern( aNewAttrs ); + } +} + +void ScViewFunc::ChangeIndent( bool bIncrement ) +{ + ScViewData& rViewData = GetViewData(); + ScDocShell* pDocSh = rViewData.GetDocShell(); + ScMarkData& rMark = rViewData.GetMarkData(); + + ScMarkData aWorkMark = rMark; + ScViewUtil::UnmarkFiltered( aWorkMark, pDocSh->GetDocument() ); + aWorkMark.MarkToMulti(); + if (!aWorkMark.IsMultiMarked()) + { + SCCOL nCol = rViewData.GetCurX(); + SCROW nRow = rViewData.GetCurY(); + SCTAB nTab = rViewData.GetTabNo(); + aWorkMark.SetMultiMarkArea( ScRange(nCol,nRow,nTab) ); + } + + bool bSuccess = pDocSh->GetDocFunc().ChangeIndent( aWorkMark, bIncrement, false ); + if (bSuccess) + { + pDocSh->UpdateOle(rViewData); + StartFormatArea(); + + // stuff for sidebar panels + SfxBindings& rBindings = GetViewData().GetBindings(); + rBindings.Invalidate( SID_H_ALIGNCELL ); + rBindings.Invalidate( SID_ATTR_ALIGN_INDENT ); + } +} + +bool ScViewFunc::InsertName( const OUString& rName, const OUString& rSymbol, + const OUString& rType ) +{ + // Type = P,R,C,F (and combinations) + //! undo... + + bool bOk = false; + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + ScRangeName* pList = rDoc.GetRangeName(); + + ScRangeData::Type nType = ScRangeData::Type::Name; + auto pNewEntry = std::make_unique<ScRangeData>( + rDoc, rName, rSymbol, ScAddress( GetViewData().GetCurX(), + GetViewData().GetCurY(), nTab), nType ); + OUString aUpType = rType.toAsciiUpperCase(); + if ( aUpType.indexOf( 'P' ) != -1 ) + nType |= ScRangeData::Type::PrintArea; + if ( aUpType.indexOf( 'R' ) != -1 ) + nType |= ScRangeData::Type::RowHeader; + if ( aUpType.indexOf( 'C' ) != -1 ) + nType |= ScRangeData::Type::ColHeader; + if ( aUpType.indexOf( 'F' ) != -1 ) + nType |= ScRangeData::Type::Criteria; + pNewEntry->AddType(nType); + + if ( pNewEntry->GetErrCode() == FormulaError::NONE ) // text valid? + { + ScDocShellModificator aModificator( *pDocSh ); + + rDoc.PreprocessRangeNameUpdate(); + + // input available yet? Then remove beforehand (=change) + ScRangeData* pData = pList->findByUpperName(ScGlobal::getCharClass().uppercase(rName)); + if (pData) + { // take old Index + pNewEntry->SetIndex(pData->GetIndex()); + pList->erase(*pData); + } + + // don't delete, insert took ownership, even on failure! + if ( pList->insert( pNewEntry.release() ) ) + bOk = true; + + rDoc.CompileHybridFormula(); + + aModificator.SetDocumentModified(); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreasChanged ) ); + } + + return bOk; +} + +void ScViewFunc::CreateNames( CreateNameFlags nFlags ) +{ + bool bDone = false; + ScRange aRange; + if ( GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE ) + bDone = GetViewData().GetDocShell()->GetDocFunc().CreateNames( aRange, nFlags, false ); + + if (!bDone) + ErrorMessage(STR_CREATENAME_MARKERR); +} + +CreateNameFlags ScViewFunc::GetCreateNameFlags() +{ + CreateNameFlags nFlags = CreateNameFlags::NONE; + + SCCOL nStartCol, nEndCol; + SCROW nStartRow, nEndRow; + SCTAB nDummy; + if (GetViewData().GetSimpleArea(nStartCol,nStartRow,nDummy,nEndCol,nEndRow,nDummy) == SC_MARK_SIMPLE) + { + ScDocument& rDoc = GetViewData().GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + bool bOk; + SCCOL i; + SCROW j; + + bOk = true; + SCCOL nFirstCol = nStartCol; + SCCOL nLastCol = nEndCol; + if (nStartCol+1 < nEndCol) { ++nFirstCol; --nLastCol; } + for (i=nFirstCol; i<=nLastCol && bOk; i++) + if (!rDoc.HasStringData( i,nStartRow,nTab )) + bOk = false; + if (bOk) + nFlags |= CreateNameFlags::Top; + else // Bottom only if not Top + { + bOk = true; + for (i=nFirstCol; i<=nLastCol && bOk; i++) + if (!rDoc.HasStringData( i,nEndRow,nTab )) + bOk = false; + if (bOk) + nFlags |= CreateNameFlags::Bottom; + } + + bOk = true; + SCROW nFirstRow = nStartRow; + SCROW nLastRow = nEndRow; + if (nStartRow+1 < nEndRow) { ++nFirstRow; --nLastRow; } + for (j=nFirstRow; j<=nLastRow && bOk; j++) + if (!rDoc.HasStringData( nStartCol,j,nTab )) + bOk = false; + if (bOk) + nFlags |= CreateNameFlags::Left; + else // Right only if not Left + { + bOk = true; + for (j=nFirstRow; j<=nLastRow && bOk; j++) + if (!rDoc.HasStringData( nEndCol,j,nTab )) + bOk = false; + if (bOk) + nFlags |= CreateNameFlags::Right; + } + } + + if (nStartCol == nEndCol) + nFlags &= ~CreateNameFlags( CreateNameFlags::Left | CreateNameFlags::Right ); + if (nStartRow == nEndRow) + nFlags &= ~CreateNameFlags( CreateNameFlags::Top | CreateNameFlags::Bottom ); + + return nFlags; +} + +void ScViewFunc::InsertNameList() +{ + ScAddress aPos( GetViewData().GetCurX(), GetViewData().GetCurY(), GetViewData().GetTabNo() ); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + if ( pDocSh->GetDocFunc().InsertNameList( aPos, false ) ) + pDocSh->UpdateOle(GetViewData()); +} + +void ScViewFunc::UpdateSelectionArea( const ScMarkData& rSel, ScPatternAttr* pAttr ) +{ + ScDocShell* pDocShell = GetViewData().GetDocShell(); + ScRange aMarkRange; + if (rSel.IsMultiMarked() ) + aMarkRange = rSel.GetMultiMarkArea(); + else + aMarkRange = rSel.GetMarkArea(); + + bool bSetLines = false; + bool bSetAlign = false; + if ( pAttr ) + { + const SfxItemSet& rNewSet = pAttr->GetItemSet(); + bSetLines = rNewSet.GetItemState( ATTR_BORDER ) == SfxItemState::SET || + rNewSet.GetItemState( ATTR_SHADOW ) == SfxItemState::SET; + bSetAlign = rNewSet.GetItemState( ATTR_HOR_JUSTIFY ) == SfxItemState::SET; + } + + sal_uInt16 nExtFlags = 0; + if ( bSetLines ) + nExtFlags |= SC_PF_LINES; + if ( bSetAlign ) + nExtFlags |= SC_PF_WHOLEROWS; + + 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(); + pDocShell->PostPaint( nStartCol, nStartRow, nStartTab, + nEndCol, nEndRow, nEndTab, + PaintPartFlags::Grid, nExtFlags | SC_PF_TESTMERGE ); + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + pTabViewShell->AdjustBlockHeight(false, const_cast<ScMarkData*>(&rSel)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |