From 267c6f2ac71f92999e969232431ba04678e7437e Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Mon, 15 Apr 2024 07:54:39 +0200 Subject: Adding upstream version 4:24.2.0. Signed-off-by: Daniel Baumann --- sc/source/ui/view/SparklineShell.cxx | 54 + sc/source/ui/view/auditsh.cxx | 123 + sc/source/ui/view/cellmergeoption.cxx | 49 + sc/source/ui/view/cellsh.cxx | 1325 ++++++ sc/source/ui/view/cellsh1.cxx | 3600 +++++++++++++++ sc/source/ui/view/cellsh2.cxx | 1256 ++++++ sc/source/ui/view/cellsh3.cxx | 1096 +++++ sc/source/ui/view/cellsh4.cxx | 522 +++ sc/source/ui/view/cliputil.cxx | 169 + sc/source/ui/view/colrowba.cxx | 383 ++ sc/source/ui/view/dbfunc.cxx | 466 ++ sc/source/ui/view/dbfunc2.cxx | 41 + sc/source/ui/view/dbfunc3.cxx | 2312 ++++++++++ sc/source/ui/view/dbfunc4.cxx | 73 + sc/source/ui/view/drawutil.cxx | 89 + sc/source/ui/view/drawvie3.cxx | 259 ++ sc/source/ui/view/drawvie4.cxx | 572 +++ sc/source/ui/view/drawview.cxx | 1251 ++++++ sc/source/ui/view/editsh.cxx | 1427 ++++++ sc/source/ui/view/formatsh.cxx | 2108 +++++++++ sc/source/ui/view/gridmerg.cxx | 225 + sc/source/ui/view/gridwin.cxx | 7218 +++++++++++++++++++++++++++++++ sc/source/ui/view/gridwin2.cxx | 1312 ++++++ sc/source/ui/view/gridwin3.cxx | 399 ++ sc/source/ui/view/gridwin4.cxx | 2695 ++++++++++++ sc/source/ui/view/gridwin5.cxx | 420 ++ sc/source/ui/view/gridwin_dbgutil.cxx | 171 + sc/source/ui/view/hdrcont.cxx | 1124 +++++ sc/source/ui/view/hintwin.cxx | 182 + sc/source/ui/view/imapwrap.cxx | 48 + sc/source/ui/view/imapwrap.hxx | 40 + sc/source/ui/view/invmerge.cxx | 162 + sc/source/ui/view/notemark.cxx | 203 + sc/source/ui/view/olinewin.cxx | 1040 +++++ sc/source/ui/view/output.cxx | 2773 ++++++++++++ sc/source/ui/view/output2.cxx | 5248 ++++++++++++++++++++++ sc/source/ui/view/output3.cxx | 267 ++ sc/source/ui/view/overlayobject.cxx | 89 + sc/source/ui/view/pfuncache.cxx | 191 + sc/source/ui/view/pgbrksh.cxx | 53 + sc/source/ui/view/pivotsh.cxx | 167 + sc/source/ui/view/preview.cxx | 1575 +++++++ sc/source/ui/view/prevloc.cxx | 718 +++ sc/source/ui/view/prevwsh.cxx | 1181 +++++ sc/source/ui/view/prevwsh2.cxx | 68 + sc/source/ui/view/printfun.cxx | 3204 ++++++++++++++ sc/source/ui/view/reffact.cxx | 299 ++ sc/source/ui/view/scextopt.cxx | 218 + sc/source/ui/view/select.cxx | 986 +++++ sc/source/ui/view/selectionstate.cxx | 54 + sc/source/ui/view/spellcheckcontext.cxx | 387 ++ sc/source/ui/view/spelldialog.cxx | 281 ++ sc/source/ui/view/spelleng.cxx | 448 ++ sc/source/ui/view/tabcont.cxx | 666 +++ sc/source/ui/view/tabsplit.cxx | 127 + sc/source/ui/view/tabview.cxx | 3162 ++++++++++++++ sc/source/ui/view/tabview2.cxx | 1709 ++++++++ sc/source/ui/view/tabview3.cxx | 3171 ++++++++++++++ sc/source/ui/view/tabview4.cxx | 538 +++ sc/source/ui/view/tabview5.cxx | 704 +++ sc/source/ui/view/tabvwsh.cxx | 121 + sc/source/ui/view/tabvwsh2.cxx | 472 ++ sc/source/ui/view/tabvwsh3.cxx | 1398 ++++++ sc/source/ui/view/tabvwsh4.cxx | 1946 +++++++++ sc/source/ui/view/tabvwsh5.cxx | 390 ++ sc/source/ui/view/tabvwsh8.cxx | 76 + sc/source/ui/view/tabvwsh9.cxx | 203 + sc/source/ui/view/tabvwsha.cxx | 1817 ++++++++ sc/source/ui/view/tabvwshb.cxx | 919 ++++ sc/source/ui/view/tabvwshc.cxx | 775 ++++ sc/source/ui/view/tabvwshd.cxx | 67 + sc/source/ui/view/tabvwshe.cxx | 322 ++ sc/source/ui/view/tabvwshf.cxx | 1094 +++++ sc/source/ui/view/tabvwshg.cxx | 120 + sc/source/ui/view/tabvwshh.cxx | 261 ++ sc/source/ui/view/viewdata.cxx | 4365 +++++++++++++++++++ sc/source/ui/view/viewfun2.cxx | 3487 +++++++++++++++ sc/source/ui/view/viewfun3.cxx | 2028 +++++++++ sc/source/ui/view/viewfun4.cxx | 791 ++++ sc/source/ui/view/viewfun5.cxx | 818 ++++ sc/source/ui/view/viewfun6.cxx | 559 +++ sc/source/ui/view/viewfun7.cxx | 462 ++ sc/source/ui/view/viewfunc.cxx | 3183 ++++++++++++++ sc/source/ui/view/viewutil.cxx | 426 ++ sc/source/ui/view/waitoff.cxx | 51 + 85 files changed, 86849 insertions(+) create mode 100644 sc/source/ui/view/SparklineShell.cxx create mode 100644 sc/source/ui/view/auditsh.cxx create mode 100644 sc/source/ui/view/cellmergeoption.cxx create mode 100644 sc/source/ui/view/cellsh.cxx create mode 100644 sc/source/ui/view/cellsh1.cxx create mode 100644 sc/source/ui/view/cellsh2.cxx create mode 100644 sc/source/ui/view/cellsh3.cxx create mode 100644 sc/source/ui/view/cellsh4.cxx create mode 100644 sc/source/ui/view/cliputil.cxx create mode 100644 sc/source/ui/view/colrowba.cxx create mode 100644 sc/source/ui/view/dbfunc.cxx create mode 100644 sc/source/ui/view/dbfunc2.cxx create mode 100644 sc/source/ui/view/dbfunc3.cxx create mode 100644 sc/source/ui/view/dbfunc4.cxx create mode 100644 sc/source/ui/view/drawutil.cxx create mode 100644 sc/source/ui/view/drawvie3.cxx create mode 100644 sc/source/ui/view/drawvie4.cxx create mode 100644 sc/source/ui/view/drawview.cxx create mode 100644 sc/source/ui/view/editsh.cxx create mode 100644 sc/source/ui/view/formatsh.cxx create mode 100644 sc/source/ui/view/gridmerg.cxx create mode 100644 sc/source/ui/view/gridwin.cxx create mode 100644 sc/source/ui/view/gridwin2.cxx create mode 100644 sc/source/ui/view/gridwin3.cxx create mode 100644 sc/source/ui/view/gridwin4.cxx create mode 100644 sc/source/ui/view/gridwin5.cxx create mode 100644 sc/source/ui/view/gridwin_dbgutil.cxx create mode 100644 sc/source/ui/view/hdrcont.cxx create mode 100644 sc/source/ui/view/hintwin.cxx create mode 100644 sc/source/ui/view/imapwrap.cxx create mode 100644 sc/source/ui/view/imapwrap.hxx create mode 100644 sc/source/ui/view/invmerge.cxx create mode 100644 sc/source/ui/view/notemark.cxx create mode 100644 sc/source/ui/view/olinewin.cxx create mode 100644 sc/source/ui/view/output.cxx create mode 100644 sc/source/ui/view/output2.cxx create mode 100644 sc/source/ui/view/output3.cxx create mode 100644 sc/source/ui/view/overlayobject.cxx create mode 100644 sc/source/ui/view/pfuncache.cxx create mode 100644 sc/source/ui/view/pgbrksh.cxx create mode 100644 sc/source/ui/view/pivotsh.cxx create mode 100644 sc/source/ui/view/preview.cxx create mode 100644 sc/source/ui/view/prevloc.cxx create mode 100644 sc/source/ui/view/prevwsh.cxx create mode 100644 sc/source/ui/view/prevwsh2.cxx create mode 100644 sc/source/ui/view/printfun.cxx create mode 100644 sc/source/ui/view/reffact.cxx create mode 100644 sc/source/ui/view/scextopt.cxx create mode 100644 sc/source/ui/view/select.cxx create mode 100644 sc/source/ui/view/selectionstate.cxx create mode 100644 sc/source/ui/view/spellcheckcontext.cxx create mode 100644 sc/source/ui/view/spelldialog.cxx create mode 100644 sc/source/ui/view/spelleng.cxx create mode 100644 sc/source/ui/view/tabcont.cxx create mode 100644 sc/source/ui/view/tabsplit.cxx create mode 100644 sc/source/ui/view/tabview.cxx create mode 100644 sc/source/ui/view/tabview2.cxx create mode 100644 sc/source/ui/view/tabview3.cxx create mode 100644 sc/source/ui/view/tabview4.cxx create mode 100644 sc/source/ui/view/tabview5.cxx create mode 100644 sc/source/ui/view/tabvwsh.cxx create mode 100644 sc/source/ui/view/tabvwsh2.cxx create mode 100644 sc/source/ui/view/tabvwsh3.cxx create mode 100644 sc/source/ui/view/tabvwsh4.cxx create mode 100644 sc/source/ui/view/tabvwsh5.cxx create mode 100644 sc/source/ui/view/tabvwsh8.cxx create mode 100644 sc/source/ui/view/tabvwsh9.cxx create mode 100644 sc/source/ui/view/tabvwsha.cxx create mode 100644 sc/source/ui/view/tabvwshb.cxx create mode 100644 sc/source/ui/view/tabvwshc.cxx create mode 100644 sc/source/ui/view/tabvwshd.cxx create mode 100644 sc/source/ui/view/tabvwshe.cxx create mode 100644 sc/source/ui/view/tabvwshf.cxx create mode 100644 sc/source/ui/view/tabvwshg.cxx create mode 100644 sc/source/ui/view/tabvwshh.cxx create mode 100644 sc/source/ui/view/viewdata.cxx create mode 100644 sc/source/ui/view/viewfun2.cxx create mode 100644 sc/source/ui/view/viewfun3.cxx create mode 100644 sc/source/ui/view/viewfun4.cxx create mode 100644 sc/source/ui/view/viewfun5.cxx create mode 100644 sc/source/ui/view/viewfun6.cxx create mode 100644 sc/source/ui/view/viewfun7.cxx create mode 100644 sc/source/ui/view/viewfunc.cxx create mode 100644 sc/source/ui/view/viewutil.cxx create mode 100644 sc/source/ui/view/waitoff.cxx (limited to 'sc/source/ui/view') diff --git a/sc/source/ui/view/SparklineShell.cxx b/sc/source/ui/view/SparklineShell.cxx new file mode 100644 index 0000000000..3b3c4f8399 --- /dev/null +++ b/sc/source/ui/view/SparklineShell.cxx @@ -0,0 +1,54 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define ShellClass_SparklineShell +#include + +namespace sc +{ +SFX_IMPL_INTERFACE(SparklineShell, SfxShell) + +void SparklineShell::InitInterface_Impl() { GetStaticInterface()->RegisterPopupMenu("sparkline"); } + +SparklineShell::SparklineShell(ScTabViewShell* pViewShell) + : SfxShell(pViewShell) + , m_pViewShell(pViewShell) +{ + SetPool(&m_pViewShell->GetPool()); + ScViewData& rViewData = m_pViewShell->GetViewData(); + SfxUndoManager* pUndoManager = rViewData.GetSfxDocShell()->GetUndoManager(); + SetUndoManager(pUndoManager); + if (!rViewData.GetDocument().IsUndoEnabled()) + { + pUndoManager->SetMaxUndoActionCount(0); + } + SetName("Sparkline"); + SfxShell::SetContextName( + vcl::EnumContext::GetContextName(vcl::EnumContext::Context::Sparkline)); +} + +SparklineShell::~SparklineShell() = default; + +} // end sc namespace + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/auditsh.cxx b/sc/source/ui/view/auditsh.cxx new file mode 100644 index 0000000000..2ea903f59e --- /dev/null +++ b/sc/source/ui/view/auditsh.cxx @@ -0,0 +1,123 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#define ShellClass_ScAuditingShell +#include + + +SFX_IMPL_INTERFACE(ScAuditingShell, SfxShell) + +void ScAuditingShell::InitInterface_Impl() +{ + GetStaticInterface()->RegisterPopupMenu("audit"); +} + +ScAuditingShell::ScAuditingShell(ScViewData& rData) : + SfxShell(rData.GetViewShell()), + rViewData( rData ), + nFunction( SID_FILL_ADD_PRED ) +{ + SetPool( &rViewData.GetViewShell()->GetPool() ); + SfxUndoManager* pMgr = rViewData.GetSfxDocShell()->GetUndoManager(); + SetUndoManager( pMgr ); + if ( !rViewData.GetDocument().IsUndoEnabled() ) + { + pMgr->SetMaxUndoActionCount( 0 ); + } + SetName("Auditing"); + SfxShell::SetContextName(vcl::EnumContext::GetContextName(vcl::EnumContext::Context::Auditing)); +} + +ScAuditingShell::~ScAuditingShell() +{ +} + +void ScAuditingShell::Execute( const SfxRequest& rReq ) +{ + SfxBindings& rBindings = rViewData.GetBindings(); + sal_uInt16 nSlot = rReq.GetSlot(); + switch ( nSlot ) + { + case SID_FILL_ADD_PRED: + case SID_FILL_DEL_PRED: + case SID_FILL_ADD_SUCC: + case SID_FILL_DEL_SUCC: + nFunction = nSlot; + rBindings.Invalidate( SID_FILL_ADD_PRED ); + rBindings.Invalidate( SID_FILL_DEL_PRED ); + rBindings.Invalidate( SID_FILL_ADD_SUCC ); + rBindings.Invalidate( SID_FILL_DEL_SUCC ); + break; + case SID_CANCEL: // Escape + case SID_FILL_NONE: + rViewData.GetViewShell()->SetAuditShell( false ); + break; + + case SID_FILL_SELECT: + { + const SfxItemSet* pReqArgs = rReq.GetArgs(); + if ( pReqArgs ) + { + const SfxInt16Item* pXItem = pReqArgs->GetItemIfSet( SID_RANGE_COL ); + const SfxInt32Item* pYItem = pReqArgs->GetItemIfSet( SID_RANGE_ROW ); + if ( pXItem && pYItem ) + { + SCCOL nCol = static_cast(pXItem->GetValue()); + SCROW nRow = static_cast(pYItem->GetValue()); + ScViewFunc* pView = rViewData.GetView(); + pView->MoveCursorAbs( nCol, nRow, SC_FOLLOW_LINE, false, false ); + switch ( nFunction ) + { + case SID_FILL_ADD_PRED: + pView->DetectiveAddPred(); + break; + case SID_FILL_DEL_PRED: + pView->DetectiveDelPred(); + break; + case SID_FILL_ADD_SUCC: + pView->DetectiveAddSucc(); + break; + case SID_FILL_DEL_SUCC: + pView->DetectiveDelSucc(); + break; + } + } + } + } + break; + } +} + +void ScAuditingShell::GetState( SfxItemSet& rSet ) +{ + rSet.Put( SfxBoolItem( nFunction, true ) ); // mark active functions +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/cellmergeoption.cxx b/sc/source/ui/view/cellmergeoption.cxx new file mode 100644 index 0000000000..524117080f --- /dev/null +++ b/sc/source/ui/view/cellmergeoption.cxx @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include + +ScCellMergeOption::ScCellMergeOption(const ScRange& rRange) : + mnStartCol(rRange.aStart.Col()), + mnStartRow(rRange.aStart.Row()), + mnEndCol(rRange.aEnd.Col()), + mnEndRow(rRange.aEnd.Row()), + mbCenter(false) +{ + SCTAB nTab1 = rRange.aStart.Tab(); + SCTAB nTab2 = rRange.aEnd.Tab(); + for (SCTAB i = nTab1; i <= nTab2; ++i) + maTabs.insert(i); +} + +ScCellMergeOption::ScCellMergeOption(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, bool bCenter) : + mnStartCol(nStartCol), + mnStartRow(nStartRow), + mnEndCol(nEndCol), + mnEndRow(nEndRow), + mbCenter(bCenter) +{ +} + +ScRange ScCellMergeOption::getSingleRange(SCTAB nTab) const +{ + return ScRange(mnStartCol, mnStartRow, nTab, mnEndCol, mnEndRow, nTab); +} + +ScRange ScCellMergeOption::getFirstSingleRange() const +{ + SCTAB nTab = 0; + if (!maTabs.empty()) + nTab = *maTabs.begin(); + + return getSingleRange(nTab); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/cellsh.cxx b/sc/source/ui/view/cellsh.cxx new file mode 100644 index 0000000000..1a866df3c8 --- /dev/null +++ b/sc/source/ui/view/cellsh.cxx @@ -0,0 +1,1325 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ShellClass_ScCellShell +#define ShellClass_CellMovement +#include + + +SFX_IMPL_INTERFACE(ScCellShell, ScFormatShell) + +void ScCellShell::InitInterface_Impl() +{ + GetStaticInterface()->RegisterObjectBar(SFX_OBJECTBAR_OBJECT, + SfxVisibilityFlags::Standard | SfxVisibilityFlags::Server, + ToolbarId::Objectbar_Format); + + GetStaticInterface()->RegisterPopupMenu("cell"); +} + +ScCellShell::ScCellShell(ScViewData& rData, const VclPtr& frameWin) : + ScFormatShell(rData), + pImpl( new CellShell_Impl() ), + bPastePossible(false), + pFrameWin(frameWin) +{ + SetName("Cell"); + SfxShell::SetContextName(vcl::EnumContext::GetContextName(vcl::EnumContext::Context::Cell)); +} + +ScCellShell::~ScCellShell() +{ + if ( pImpl->m_xClipEvtLstnr.is() ) + { + pImpl->m_xClipEvtLstnr->RemoveListener( GetViewData().GetActiveWin() ); + + // The listener may just now be waiting for the SolarMutex and call the link + // afterwards, in spite of RemoveListener. So the link has to be reset, too. + pImpl->m_xClipEvtLstnr->ClearCallbackLink(); + + pImpl->m_xClipEvtLstnr.clear(); + } + + pImpl->m_pLinkedDlg.disposeAndClear(); + delete pImpl->m_pRequest; +} + +void ScCellShell::GetBlockState( SfxItemSet& rSet ) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + ScRange aMarkRange; + ScMarkType eMarkType = GetViewData().GetSimpleArea( aMarkRange ); + bool bSimpleArea = (eMarkType == SC_MARK_SIMPLE); + bool bOnlyNotBecauseOfMatrix; + bool bEditable = pTabViewShell->SelectionEditable( &bOnlyNotBecauseOfMatrix ); + ScDocument& rDoc = GetViewData().GetDocument(); + ScDocShell* pDocShell = GetViewData().GetDocShell(); + ScMarkData& rMark = GetViewData().GetMarkData(); + SCCOL nCol1, nCol2; + SCROW nRow1, nRow2; + nCol1 = aMarkRange.aStart.Col(); + nRow1 = aMarkRange.aStart.Row(); + nCol2 = aMarkRange.aEnd.Col(); + nRow2 = aMarkRange.aEnd.Row(); + SCTAB nTab = GetViewData().GetTabNo(); + + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + while ( nWhich ) + { + bool bDisable = false; + bool bNeedEdit = true; // need selection be editable? + switch ( nWhich ) + { + case FID_FILL_TO_BOTTOM: // fill to top / bottom + { + bDisable = !bSimpleArea || (nRow1 == 0 && nRow2 == 0); + if (!bDisable && GetViewData().SelectionForbidsCellFill()) + bDisable = true; + if ( !bDisable && bEditable ) + { // do not damage matrix + bDisable = rDoc.HasSelectedBlockMatrixFragment( + nCol1, nRow1, nCol2, nRow1, rMark ); // first row + } + } + break; + case FID_FILL_TO_TOP: + { + bDisable = (!bSimpleArea) || (nRow1 == rDoc.MaxRow() && nRow2 == rDoc.MaxRow()); + if (!bDisable && GetViewData().SelectionForbidsCellFill()) + bDisable = true; + if ( !bDisable && bEditable ) + { // do not damage matrix + bDisable = rDoc.HasSelectedBlockMatrixFragment( + nCol1, nRow2, nCol2, nRow2, rMark ); // last row + } + } + break; + case FID_FILL_TO_RIGHT: // fill to left / right + { + bDisable = !bSimpleArea || (nCol1 == 0 && nCol2 == 0); + if (!bDisable && GetViewData().SelectionForbidsCellFill()) + bDisable = true; + if ( !bDisable && bEditable ) + { // do not damage matrix + bDisable = rDoc.HasSelectedBlockMatrixFragment( + nCol1, nRow1, nCol1, nRow2, rMark ); // first column + } + } + break; + case FID_FILL_TO_LEFT: + { + bDisable = (!bSimpleArea) || (nCol1 == rDoc.MaxCol() && nCol2 == rDoc.MaxCol()); + if (!bDisable && GetViewData().SelectionForbidsCellFill()) + bDisable = true; + if ( !bDisable && bEditable ) + { // do not damage matrix + bDisable = rDoc.HasSelectedBlockMatrixFragment( + nCol2, nRow1, nCol2, nRow2, rMark ); // last column + } + } + break; + + case SID_RANDOM_NUMBER_GENERATOR_DIALOG: + bDisable = !bSimpleArea || GetViewData().SelectionForbidsCellFill(); + break; + case SID_SAMPLING_DIALOG: + case SID_DESCRIPTIVE_STATISTICS_DIALOG: + case SID_ANALYSIS_OF_VARIANCE_DIALOG: + case SID_CORRELATION_DIALOG: + case SID_COVARIANCE_DIALOG: + case SID_INSERT_SPARKLINE: + { + bDisable = !bSimpleArea; + } + break; + case SID_GROUP_SPARKLINES: + case SID_UNGROUP_SPARKLINES: + { + bDisable = !bSimpleArea; + } + break; + + case SID_EDIT_SPARKLINE: + { + bDisable = !rDoc.HasSparkline(GetViewData().GetCurPos()); + } + break; + + case SID_DELETE_SPARKLINE: + case SID_EDIT_SPARKLINE_GROUP: + case SID_DELETE_SPARKLINE_GROUP: + { + bDisable = !rDoc.HasOneSparklineGroup(ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab)); + } + break; + + case FID_FILL_SERIES: // fill block + case SID_OPENDLG_TABOP: // multiple-cell operations, are at least 2 cells marked? + if (rDoc.GetChangeTrack()!=nullptr &&nWhich ==SID_OPENDLG_TABOP) + bDisable = true; + else + bDisable = (!bSimpleArea) || (nCol1 == nCol2 && nRow1 == nRow2); + + if (!bDisable && GetViewData().SelectionForbidsCellFill()) + bDisable = true; + + if ( !bDisable && bEditable && nWhich == FID_FILL_SERIES ) + { // do not damage matrix + bDisable = rDoc.HasSelectedBlockMatrixFragment( + nCol1, nRow1, nCol2, nRow1, rMark ) // first row + || rDoc.HasSelectedBlockMatrixFragment( + nCol1, nRow2, nCol2, nRow2, rMark ) // last row + || rDoc.HasSelectedBlockMatrixFragment( + nCol1, nRow1, nCol1, nRow2, rMark ) // first column + || rDoc.HasSelectedBlockMatrixFragment( + nCol2, nRow1, nCol2, nRow2, rMark ); // last column + } + break; + case FID_FILL_SINGLE_EDIT: + bDisable = false; + break; + case SID_CUT: // cut + bDisable = !bSimpleArea || GetObjectShell()->isContentExtractionLocked(); + break; + case FID_INS_CELL: // insert cells, just simple selection + bDisable = (!bSimpleArea); + break; + + case SID_PASTE: + case SID_PASTE_SPECIAL: + case SID_PASTE_UNFORMATTED: + case SID_PASTE_ONLY_VALUE: + case SID_PASTE_ONLY_TEXT: + case SID_PASTE_ONLY_FORMULA: + case SID_PASTE_TRANSPOSED: + case SID_PASTE_AS_LINK: + case SID_PASTE_TEXTIMPORT_DIALOG: + bDisable = GetViewData().SelectionForbidsPaste(); + break; + + case FID_INS_ROW: + case FID_INS_ROWS_BEFORE: // insert rows + case FID_INS_ROWS_AFTER: + { + sc::ColRowEditAction eAction = sc::ColRowEditAction::InsertRowsBefore; + if (nWhich == FID_INS_ROWS_AFTER) + eAction = sc::ColRowEditAction::InsertRowsAfter; + + bDisable = (!bSimpleArea) || GetViewData().SimpleColMarked(); + if (!bEditable && nCol1 == 0 && nCol2 == rDoc.MaxCol()) + { + // See if row insertions are allowed. + bEditable = rDoc.IsEditActionAllowed(eAction, rMark, nRow1, nRow2); + } + break; + } + case FID_INS_CELLSDOWN: + bDisable = (!bSimpleArea) || GetViewData().SimpleColMarked(); + break; + + case FID_INS_COLUMN: + case FID_INS_COLUMNS_BEFORE: // insert columns + case FID_INS_COLUMNS_AFTER: + { + sc::ColRowEditAction eAction = sc::ColRowEditAction::InsertColumnsBefore; + if (nWhich == FID_INS_COLUMNS_AFTER) + eAction = sc::ColRowEditAction::InsertColumnsAfter; + + bDisable = (!bSimpleArea && eMarkType != SC_MARK_SIMPLE_FILTERED) + || GetViewData().SimpleRowMarked(); + if (!bEditable && nRow1 == 0 && nRow2 == rDoc.MaxRow()) + { + // See if row insertions are allowed. + bEditable = rDoc.IsEditActionAllowed(eAction, rMark, nCol1, nCol2); + } + break; + } + case FID_INS_CELLSRIGHT: + bDisable = (!bSimpleArea) || GetViewData().SimpleRowMarked(); + break; + + case SID_COPY: // copy + // not editable because of matrix only? Do not damage matrix + //! is not called, when protected AND matrix, we will have + //! to live with this... is caught in Copy-Routine, otherwise + //! work is to be done once more + if ( bEditable || !bOnlyNotBecauseOfMatrix ) + bNeedEdit = false; // allowed when protected/ReadOnly + bDisable = GetObjectShell()->isContentExtractionLocked(); + break; + + case SID_AUTOFORMAT: // Autoformat, at least 3x3 selected + bDisable = (!bSimpleArea) + || ((nCol2 - nCol1) < 2) || ((nRow2 - nRow1) < 2); + break; + + case SID_CELL_FORMAT_RESET : + case FID_CELL_FORMAT : + case SID_ENABLE_HYPHENATION : + // not editable because of matrix only? Attribute ok nonetheless + if ( !bEditable && bOnlyNotBecauseOfMatrix ) + bNeedEdit = false; + break; + + case FID_VALIDATION: + { + if ( pDocShell && pDocShell->IsDocShared() ) + { + bDisable = true; + } + } + break; + case SID_TRANSLITERATE_HALFWIDTH: + case SID_TRANSLITERATE_FULLWIDTH: + case SID_TRANSLITERATE_HIRAGANA: + case SID_TRANSLITERATE_KATAKANA: + ScViewUtil::HideDisabledSlot( rSet, GetViewData().GetBindings(), nWhich ); + break; + case SID_CONVERT_FORMULA_TO_VALUE: + { + // Check and see if the marked range has at least one formula cell. + bDisable = !rDoc.HasFormulaCell(aMarkRange); + } + break; + } + if (!bDisable && bNeedEdit && !bEditable) + bDisable = true; + + if (bDisable) + rSet.DisableItem(nWhich); + else if (nWhich == SID_ENABLE_HYPHENATION) + { + // toggle slots need a bool item + rSet.Put( SfxBoolItem( nWhich, false ) ); + } + nWhich = aIter.NextWhich(); + } +} + +// functions, disabled depending on cursor position +// Default: +// SID_INSERT_POSTIT, SID_CHARMAP, SID_OPENDLG_FUNCTION + +void ScCellShell::GetCellState( SfxItemSet& rSet ) +{ + ScDocShell* pDocShell = GetViewData().GetDocShell(); + ScDocument& rDoc = GetViewData().GetDocShell()->GetDocument(); + ScAddress aCursor( GetViewData().GetCurX(), GetViewData().GetCurY(), + GetViewData().GetTabNo() ); + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + while ( nWhich ) + { + bool bDisable = false; + bool bNeedEdit = true; // need cursor position be editable? + switch ( nWhich ) + { + case SID_THESAURUS: + { + CellType eType = rDoc.GetCellType( aCursor ); + bDisable = ( eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT); + if (!bDisable) + { + // test for available languages + LanguageType nLang = ScViewUtil::GetEffLanguage( rDoc, aCursor ); + bDisable = !ScModule::HasThesaurusLanguage( nLang ); + } + } + break; + case SID_OPENDLG_FUNCTION: + { + ScMarkData aMarkData = GetViewData().GetMarkData(); + aMarkData.MarkToSimple(); + const ScRange& aRange = aMarkData.GetMarkArea(); + if(aMarkData.IsMarked()) + { + if (!rDoc.IsBlockEditable( aCursor.Tab(), aRange.aStart.Col(),aRange.aStart.Row(), + aRange.aEnd.Col(),aRange.aEnd.Row() )) + { + bDisable = true; + } + bNeedEdit=false; + } + + } + break; + case SID_INSERT_POSTIT: + { + ScAddress aPos( GetViewData().GetCurX(), GetViewData().GetCurY(), GetViewData().GetTabNo() ); + if( rDoc.GetNote(aPos) ) + { + bDisable = true; + } + else + { + bDisable = false; + if ( pDocShell && pDocShell->IsDocShared() ) + { + bDisable = true; + } + } + } + break; + case SID_EDIT_POSTIT: + { + ScAddress aPos( GetViewData().GetCurX(), GetViewData().GetCurY(), GetViewData().GetTabNo() ); + bDisable = rDoc.GetNote(aPos) == nullptr; + } + break; + } + if (!bDisable && bNeedEdit) + if (!rDoc.IsBlockEditable( aCursor.Tab(), aCursor.Col(),aCursor.Row(), + aCursor.Col(),aCursor.Row() )) + bDisable = true; + if (bDisable) + rSet.DisableItem(nWhich); + nWhich = aIter.NextWhich(); + } +} + +static bool lcl_TestFormat( SvxClipboardFormatItem& rFormats, const TransferableDataHelper& rDataHelper, + SotClipboardFormatId nFormatId ) +{ + if ( rDataHelper.HasFormat( nFormatId ) ) + { + // translated format name strings are no longer inserted here, + // handled by "paste special" dialog / toolbox controller instead. + // Only the object type name has to be set here: + OUString aStrVal; + if ( nFormatId == SotClipboardFormatId::EMBED_SOURCE ) + { + TransferableObjectDescriptor aDesc; + if ( rDataHelper.GetTransferableObjectDescriptor( + SotClipboardFormatId::OBJECTDESCRIPTOR, aDesc ) ) + aStrVal = aDesc.maTypeName; + } + else if ( nFormatId == SotClipboardFormatId::EMBED_SOURCE_OLE + || nFormatId == SotClipboardFormatId::EMBEDDED_OBJ_OLE ) + { + OUString aSource; + SvPasteObjectHelper::GetEmbeddedName( rDataHelper, aStrVal, aSource, nFormatId ); + } + + if ( !aStrVal.isEmpty() ) + rFormats.AddClipbrdFormat( nFormatId, aStrVal ); + else + rFormats.AddClipbrdFormat( nFormatId ); + + return true; + } + + return false; +} + +void ScCellShell::GetPossibleClipboardFormats( SvxClipboardFormatItem& rFormats ) +{ + vcl::Window* pWin = GetViewData().GetActiveWin(); + bool bDraw = ScDrawTransferObj::GetOwnClipboard(ScTabViewShell::GetClipData(pWin)) != nullptr; + + TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromSystemClipboard( pWin ) ); + + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::DRAWING ); + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::SVXB ); + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::GDIMETAFILE ); + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::PNG ); + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::BITMAP ); + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::EMBED_SOURCE ); + + if ( !bDraw ) + { + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::LINK ); + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::STRING ); + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::STRING_TSVC ); + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::DIF ); + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::RTF ); + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::RICHTEXT ); + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::HTML ); + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::HTML_SIMPLE ); + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::BIFF_8 ); + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::BIFF_5 ); + } + + if ( !lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::EMBED_SOURCE_OLE ) ) + lcl_TestFormat( rFormats, aDataHelper, SotClipboardFormatId::EMBEDDED_OBJ_OLE ); +} + +// insert, insert contents + +static bool lcl_IsCellPastePossible( const TransferableDataHelper& rData ) +{ + bool bPossible = false; + css::uno::Reference< css::datatransfer::XTransferable2 > xTransferable(rData.GetXTransferable(), css::uno::UNO_QUERY); + if ( ScTransferObj::GetOwnClipboard(xTransferable) || ScDrawTransferObj::GetOwnClipboard(xTransferable) ) + bPossible = true; + else + { + if ( rData.HasFormat( SotClipboardFormatId::PNG ) || + rData.HasFormat( SotClipboardFormatId::BITMAP ) || + rData.HasFormat( SotClipboardFormatId::GDIMETAFILE ) || + rData.HasFormat( SotClipboardFormatId::SVXB ) || + rData.HasFormat( SotClipboardFormatId::PRIVATE ) || + rData.HasFormat( SotClipboardFormatId::RTF ) || + rData.HasFormat( SotClipboardFormatId::RICHTEXT ) || + rData.HasFormat( SotClipboardFormatId::EMBED_SOURCE ) || + rData.HasFormat( SotClipboardFormatId::LINK_SOURCE ) || + rData.HasFormat( SotClipboardFormatId::EMBED_SOURCE_OLE ) || + rData.HasFormat( SotClipboardFormatId::LINK_SOURCE_OLE ) || + rData.HasFormat( SotClipboardFormatId::EMBEDDED_OBJ_OLE ) || + rData.HasFormat( SotClipboardFormatId::STRING ) || + rData.HasFormat( SotClipboardFormatId::STRING_TSVC ) || + rData.HasFormat( SotClipboardFormatId::SYLK ) || + rData.HasFormat( SotClipboardFormatId::LINK ) || + rData.HasFormat( SotClipboardFormatId::HTML ) || + rData.HasFormat( SotClipboardFormatId::HTML_SIMPLE ) || + rData.HasFormat( SotClipboardFormatId::DIF ) ) + { + bPossible = true; + } + } + return bPossible; +} + +bool ScCellShell::HasClipboardFormat( SotClipboardFormatId nFormatId ) +{ + vcl::Window* pWin = GetViewData().GetActiveWin(); + TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromSystemClipboard( pWin )); + return aDataHelper.HasFormat( nFormatId ); +} + +IMPL_LINK( ScCellShell, ClipboardChanged, TransferableDataHelper*, pDataHelper, void ) +{ + bPastePossible = lcl_IsCellPastePossible( *pDataHelper ); + + SfxBindings& rBindings = GetViewData().GetBindings(); + rBindings.Invalidate( SID_PASTE ); + rBindings.Invalidate( SID_PASTE_SPECIAL ); + rBindings.Invalidate( SID_PASTE_UNFORMATTED ); + rBindings.Invalidate( SID_PASTE_ONLY_VALUE ); + rBindings.Invalidate( SID_PASTE_ONLY_TEXT ); + rBindings.Invalidate( SID_PASTE_ONLY_FORMULA ); + rBindings.Invalidate( SID_PASTE_TRANSPOSED ); + rBindings.Invalidate( SID_PASTE_AS_LINK ); + rBindings.Invalidate( SID_PASTE_TEXTIMPORT_DIALOG ); + rBindings.Invalidate( SID_CLIPBOARD_FORMAT_ITEMS ); +} + +namespace { + +bool checkDestRanges(ScViewData& rViewData) +{ + ScRange aDummy; + ScMarkType eMarkType = rViewData.GetSimpleArea( aDummy); + if (eMarkType != SC_MARK_MULTI) + { + // Single destination range. + if (eMarkType != SC_MARK_SIMPLE && eMarkType != SC_MARK_SIMPLE_FILTERED) + return false; + } + + // Multiple destination ranges. + + // Same as ScViewData::SelectionForbidsPaste() in + // sc/source/ui/view/viewdata.cxx but different return details. + + vcl::Window* pWin = rViewData.GetActiveWin(); + if (!pWin) + return false; + + const ScTransferObj* pOwnClip = ScTransferObj::GetOwnClipboard(ScTabViewShell::GetClipData(pWin)); + if (!pOwnClip) + // If it's not a Calc document, we won't be picky. + return true; + + ScDocument* pClipDoc = pOwnClip->GetDocument(); + if (!pClipDoc) + return false; + + ScRange aSrcRange = pClipDoc->GetClipParam().getWholeRange(); + SCROW nRowSize = aSrcRange.aEnd.Row() - aSrcRange.aStart.Row() + 1; + SCCOL nColSize = aSrcRange.aEnd.Col() - aSrcRange.aStart.Col() + 1; + + if (rViewData.SelectionForbidsPaste( nColSize, nRowSize)) + return false; + + ScMarkData aMark = rViewData.GetMarkData(); + ScRangeList aRanges; + aMark.MarkToSimple(); + aMark.FillRangeListWithMarks(&aRanges, false); + + return ScClipUtil::CheckDestRanges(rViewData.GetDocument(), nColSize, nRowSize, aMark, aRanges); +} + +} + +void ScCellShell::GetClipState( SfxItemSet& rSet ) +{ +// SID_PASTE +// SID_PASTE_SPECIAL +// SID_PASTE_UNFORMATTED +// SID_CLIPBOARD_FORMAT_ITEMS + + if ( !pImpl->m_xClipEvtLstnr.is() ) + { + // create listener + pImpl->m_xClipEvtLstnr = new TransferableClipboardListener( LINK( this, ScCellShell, ClipboardChanged ) ); + vcl::Window* pWin = GetViewData().GetActiveWin(); + pImpl->m_xClipEvtLstnr->AddListener( pWin ); + + // get initial state + TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromSystemClipboard( pWin ) ); + bPastePossible = lcl_IsCellPastePossible( aDataHelper ); + } + + bool bDisable = !bPastePossible; + + // cell protection / multiple selection + + if (!bDisable) + { + SCCOL nCol = GetViewData().GetCurX(); + SCROW nRow = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + ScDocument& rDoc = GetViewData().GetDocShell()->GetDocument(); + if (!rDoc.IsBlockEditable( nTab, nCol,nRow, nCol,nRow )) + bDisable = true; + + if (!bDisable && !checkDestRanges(GetViewData())) + bDisable = true; + } + + if (bDisable) + { + rSet.DisableItem( SID_PASTE ); + rSet.DisableItem( SID_PASTE_SPECIAL ); + rSet.DisableItem( SID_PASTE_UNFORMATTED ); + rSet.DisableItem( SID_PASTE_ONLY_VALUE ); + rSet.DisableItem( SID_PASTE_ONLY_TEXT ); + rSet.DisableItem( SID_PASTE_ONLY_FORMULA ); + rSet.DisableItem( SID_PASTE_TRANSPOSED ); + rSet.DisableItem( SID_PASTE_AS_LINK ); + rSet.DisableItem( SID_PASTE_TEXTIMPORT_DIALOG ); + rSet.DisableItem( SID_CLIPBOARD_FORMAT_ITEMS ); + } + else if ( rSet.GetItemState( SID_CLIPBOARD_FORMAT_ITEMS ) != SfxItemState::UNKNOWN ) + { + SvxClipboardFormatItem aFormats( SID_CLIPBOARD_FORMAT_ITEMS ); + GetPossibleClipboardFormats( aFormats ); + rSet.Put( aFormats ); + } +} + +// only SID_HYPERLINK_GETLINK: + +void ScCellShell::GetHLinkState( SfxItemSet& rSet ) +{ + // always return an item (or inserting will be disabled) + // if the cell at the cursor contains only a link, return that link + + SvxHyperlinkItem aHLinkItem; + if ( !GetViewData().GetView()->HasBookmarkAtCursor( &aHLinkItem ) ) + { + // tdf#80043 - put selected text into item + ScViewData& rData = GetViewData(); + ScDocument& rDoc = rData.GetDocument(); + SCCOL nPosX = rData.GetCurX(); + SCROW nPosY = rData.GetCurY(); + SCTAB nTab = rData.GetTabNo(); + aHLinkItem.SetName(rDoc.GetString(nPosX, nPosY, nTab)); + } + + rSet.Put(aHLinkItem); +} + +void ScCellShell::GetState(SfxItemSet &rSet) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScViewData& rData = GetViewData(); + ScDocument& rDoc = rData.GetDocument(); + ScMarkData& rMark = rData.GetMarkData(); + SCCOL nPosX = rData.GetCurX(); + SCROW nPosY = rData.GetCurY(); + SCTAB nTab = rData.GetTabNo(); + + SCTAB nTabCount = rDoc.GetTableCount(); + SCTAB nTabSelCount = rMark.GetSelectCount(); + + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + while ( nWhich ) + { + switch ( nWhich ) + { + case SID_DETECTIVE_REFRESH: + if (!rDoc.HasDetectiveOperations()) + rSet.DisableItem( nWhich ); + break; + + case SID_RANGE_ADDRESS: + { + ScRange aRange; + if ( rData.GetSimpleArea( aRange ) == SC_MARK_SIMPLE ) + { + OUString aStr(aRange.Format(rDoc, ScRefFlags::VALID | ScRefFlags::TAB_3D)); + rSet.Put( SfxStringItem( nWhich, aStr ) ); + } + } + break; + + case SID_RANGE_NOTETEXT: + { + // always take cursor position, do not use top-left cell of selection + OUString aNoteText; + if ( const ScPostIt* pNote = rDoc.GetNote(nPosX, nPosY, nTab) ) + aNoteText = pNote->GetText(); + rSet.Put( SfxStringItem( nWhich, aNoteText ) ); + } + break; + + case SID_RANGE_ROW: + rSet.Put( SfxInt32Item( SID_RANGE_ROW, nPosY+1 ) ); + break; + + case SID_RANGE_COL: + rSet.Put( SfxInt16Item( SID_RANGE_COL, nPosX+1 ) ); + break; + + case SID_RANGE_TABLE: + rSet.Put( SfxInt16Item( SID_RANGE_TABLE, nTab+1 ) ); + break; + + case SID_RANGE_FORMULA: + { + OUString aString = rDoc.GetFormula( nPosX, nPosY, nTab ); + if( aString.isEmpty() ) + { + aString = rDoc.GetInputString( nPosX, nPosY, nTab ); + } + rSet.Put( SfxStringItem( nWhich, aString ) ); + } + break; + + case SID_RANGE_TEXTVALUE: + { + OUString aString = rDoc.GetString(nPosX, nPosY, nTab); + rSet.Put( SfxStringItem( nWhich, aString ) ); + } + break; + + case SID_STATUS_SELMODE: + { + /* 0: STD Click cancels Sel + * 1: ER Click extends selection + * 2: ERG Click defines further selection + */ + sal_uInt16 nMode = pTabViewShell->GetLockedModifiers(); + + switch ( nMode ) + { + case KEY_SHIFT: nMode = 1; break; + case KEY_MOD1: nMode = 2; break; // Control-key + case 0: + default: + nMode = 0; + } + + rSet.Put( SfxUInt16Item( nWhich, nMode ) ); + } + break; + + case SID_STATUS_DOCPOS: + { + OUString aStr = ScResId( STR_TABLE_COUNT ); + + aStr = aStr.replaceFirst("%1", OUString::number( nTab + 1 ) ); + aStr = aStr.replaceFirst("%2", OUString::number( nTabCount ) ); + + rSet.Put( SfxStringItem( nWhich, aStr ) ); } + break; + + case SID_ROWCOL_SELCOUNT: + { + ScRangeListRef aMarkRanges; + GetViewData().GetMultiArea(aMarkRanges); + const SCCOL nCol1 = aMarkRanges->front().aStart.Col(); + const SCROW nRow1 = aMarkRanges->front().aStart.Row(); + const SCCOL nCol2 = aMarkRanges->front().aEnd.Col(); + const SCROW nRow2 = aMarkRanges->front().aEnd.Row(); + const size_t nRanges = aMarkRanges->size(); + + if ((nRanges == 1 && (nCol2 != nCol1 || nRow1 != nRow2)) || nRanges > 1) + { + bool bSameRows = true; + bool bSameCols = true; + SCROW nRowsSum = 0; + SCCOL nColsSum = 0; + for (size_t i = 0; i < nRanges; ++i) + { + const ScRange& rRange = (*aMarkRanges)[i]; + const SCCOL nRangeCol1 = rRange.aStart.Col(); + const SCROW nRangeRow1 = rRange.aStart.Row(); + const SCCOL nRangeCol2 = rRange.aEnd.Col(); + const SCROW nRangeRow2 = rRange.aEnd.Row(); + bSameRows &= (nRow1 == nRangeRow1 && nRow2 == nRangeRow2); + bSameCols &= (nCol1 == nRangeCol1 && nCol2 == nRangeCol2); + // Sum rows if the number of cols is the same or + // sum columns if the number of rows is the same, + // otherwise do not show any count of selected cells. + if (bSameRows || bSameCols) + { + const auto nCols = nRangeCol2 - nRangeCol1 + 1; + const auto nRows = (bSameCols || nRowsSum == 0) ? + rDoc.CountNonFilteredRows( nRangeRow1, nRangeRow2, rRange.aStart.Tab()) : + nRowsSum; + if (bSameRows) + { + nRowsSum = nRows; + nColsSum += nCols; + } + else if (bSameCols) + { + nRowsSum += nRows; + nColsSum = nCols; + } + } + else + break; + } + // Either the rows or columns are the same among selections + if (bSameRows || bSameCols) + { + const LocaleDataWrapper& rLocaleData + = Application::GetSettings().GetUILocaleDataWrapper(); + OUString aRowArg + = ScResId(STR_SELCOUNT_ROWARG, nRowsSum) + .replaceAll("%d", rLocaleData.getNum(nRowsSum, 0)); + OUString aColArg + = ScResId(STR_SELCOUNT_COLARG, nColsSum) + .replaceAll("%d", rLocaleData.getNum(nColsSum, 0)); + OUString aStr = ScResId(STR_SELCOUNT); + aStr = aStr.replaceAll("%1", aRowArg); + aStr = aStr.replaceAll("%2", aColArg); + rSet.Put(SfxStringItem(nWhich, aStr)); + } + } + else + { + SCSIZE nSelected, nTotal; + rDoc.GetFilterSelCount( nPosX, nPosY, nTab, nSelected, nTotal ); + if( nTotal && nSelected != SCSIZE_MAX ) + { + OUString aStr = ScResId( STR_FILTER_SELCOUNT ); + aStr = aStr.replaceAll( "%1", OUString::number( nSelected ) ); + aStr = aStr.replaceAll( "%2", OUString::number( nTotal ) ); + rSet.Put( SfxStringItem( nWhich, aStr ) ); + } + } + } + break; + + // calculations etc. with date/time/Fail/position&size together + + // #i34458# The SvxStatusItem belongs only into SID_TABLE_CELL. It no longer has to be + // duplicated in SID_ATTR_POSITION or SID_ATTR_SIZE for SvxPosSizeStatusBarControl. + case SID_TABLE_CELL: + { + // Test, if error under cursor + // (not rDoc.GetErrCode, to avoid erasing circular references) + + // In interpreter may happen via rescheduled Basic + if ( rDoc.IsInInterpreter() ) + rSet.Put( SvxStatusItem( SID_TABLE_CELL, "...", StatusCategory::Formula ) ); + else + { + FormulaError nErrCode = FormulaError::NONE; + ScFormulaCell* pCell = rDoc.GetFormulaCell(ScAddress(nPosX, nPosY, nTab)); + if (pCell && !pCell->IsRunning()) + nErrCode = pCell->GetErrCode(); + + OUString aFuncStr; + if ( pTabViewShell->GetFunction( aFuncStr, nErrCode ) ) + { + rSet.Put( SvxStatusItem( SID_TABLE_CELL, aFuncStr, StatusCategory::Formula ) ); + } + } + } + break; + + case SID_DATA_SELECT: + // HasSelectionData includes column content and validity, + // page fields have to be checked separately. + if ( !rDoc.HasSelectionData( nPosX, nPosY, nTab ) && + !pTabViewShell->HasPageFieldDataAtCursor() ) + rSet.DisableItem( nWhich ); + break; + + case FID_CURRENTVALIDATION: + if ( !rDoc.HasValidationData( nPosX, nPosY, nTab )) + rSet.DisableItem( nWhich ); + break; + + case SID_STATUS_SUM: + { + OUString aFuncStr; + if ( pTabViewShell->GetFunction( aFuncStr, FormulaError::NONE ) ) + rSet.Put( SfxStringItem( nWhich, aFuncStr ) ); + } + break; + + case FID_MERGE_ON: + if ( rDoc.GetChangeTrack() || !pTabViewShell->TestMergeCells() ) + rSet.DisableItem( nWhich ); + break; + + case FID_MERGE_OFF: + if ( rDoc.GetChangeTrack() || !pTabViewShell->TestRemoveMerge() ) + rSet.DisableItem( nWhich ); + break; + + case FID_MERGE_TOGGLE: + if ( rDoc.GetChangeTrack() ) + rSet.DisableItem( nWhich ); + else + { + bool bCanMerge = pTabViewShell->TestMergeCells(); + bool bCanSplit = pTabViewShell->TestRemoveMerge(); + if( !bCanMerge && !bCanSplit ) + rSet.DisableItem( nWhich ); + else + rSet.Put( SfxBoolItem( nWhich, bCanSplit ) ); + } + break; + + case FID_INS_ROWBRK: + if ( nPosY==0 || (rDoc.HasRowBreak(nPosY, nTab) & ScBreakType::Manual) || rDoc.IsTabProtected(nTab) ) + rSet.DisableItem( nWhich ); + break; + + case FID_INS_COLBRK: + if ( nPosX==0 || (rDoc.HasColBreak(nPosX, nTab) & ScBreakType::Manual) || rDoc.IsTabProtected(nTab) ) + rSet.DisableItem( nWhich ); + break; + + case FID_DEL_ROWBRK: + if ( nPosY==0 || !(rDoc.HasRowBreak(nPosY, nTab) & ScBreakType::Manual) || rDoc.IsTabProtected(nTab) ) + rSet.DisableItem( nWhich ); + break; + + case FID_DEL_COLBRK: + if ( nPosX==0 || !(rDoc.HasColBreak(nPosX, nTab) & ScBreakType::Manual) || rDoc.IsTabProtected(nTab) ) + rSet.DisableItem( nWhich ); + break; + + case FID_FILL_TAB: + if ( nTabSelCount < 2 ) + rSet.DisableItem( nWhich ); + break; + + case SID_INSERT_CURRENT_DATE: + case SID_INSERT_CURRENT_TIME: + { + if ( rDoc.IsTabProtected(nTab) && + rDoc.HasAttrib(nPosX, nPosY, nTab, nPosX, nPosY, nTab, HasAttrFlags::Protected)) + rSet.DisableItem( nWhich ); + } + break; + + case SID_SELECT_SCENARIO: + { + std::vector aList; + Color aDummyCol; + + if ( !rDoc.IsScenario(nTab) ) + { + OUString aStr; + ScScenarioFlags nFlags; + SCTAB nScTab = nTab + 1; + bool bSheetProtected = rDoc.IsTabProtected(nTab); + + while ( rDoc.IsScenario(nScTab) ) + { + rDoc.GetName( nScTab, aStr ); + aList.push_back(aStr); + rDoc.GetScenarioData( nScTab, aStr, aDummyCol, nFlags ); + aList.push_back(aStr); + // Protection is sal_True if both Sheet and Scenario are protected + aList.push_back((bSheetProtected && (nFlags & ScScenarioFlags::Protected)) ? OUString("1") : OUString("0")); + ++nScTab; + } + } + else + { + OUString aComment; + ScScenarioFlags nDummyFlags; + rDoc.GetScenarioData( nTab, aComment, aDummyCol, nDummyFlags ); + OSL_ENSURE( aList.empty(), "List not empty!" ); + aList.push_back(aComment); + } + + rSet.Put( SfxStringListItem( nWhich, &aList ) ); + } + break; + + case FID_ROW_HIDE: + case FID_ROW_SHOW: + case FID_COL_HIDE: + case FID_COL_SHOW: + case FID_COL_OPT_WIDTH: + case FID_ROW_OPT_HEIGHT: + case FID_DELETE_CELL: + if ( rDoc.IsTabProtected(nTab) || pDocSh->IsReadOnly()) + rSet.DisableItem( nWhich ); + break; + + case SID_OUTLINE_MAKE: + { + if ( GetViewData().GetDocument().GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ) ) + { + //! test for data pilot operation + } + else if (rDoc.GetChangeTrack()!=nullptr || GetViewData().IsMultiMarked()) + { + rSet.DisableItem( nWhich ); + } + } + break; + case SID_OUTLINE_SHOW: + if ( GetViewData().GetDocument().GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ) ) + { + //! test for data pilot operation + } + else if (!pTabViewShell->OutlinePossible(false)) + rSet.DisableItem( nWhich ); + break; + + case SID_OUTLINE_HIDE: + if ( GetViewData().GetDocument().GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ) ) + { + //! test for data pilot operation + } + else if (!pTabViewShell->OutlinePossible(true)) + rSet.DisableItem( nWhich ); + break; + + case SID_OUTLINE_REMOVE: + { + if ( GetViewData().GetDocument().GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ) ) + { + //! test for data pilot operation + } + else + { + bool bCol, bRow; + pTabViewShell->TestRemoveOutline( bCol, bRow ); + if ( !bCol && !bRow ) + rSet.DisableItem( nWhich ); + } + } + break; + + case FID_COL_WIDTH: + { + SfxUInt16Item aWidthItem( FID_COL_WIDTH, rDoc.GetColWidth( nPosX , nTab) ); + rSet.Put( aWidthItem ); + if ( pDocSh->IsReadOnly()) + rSet.DisableItem( nWhich ); + + //XXX disable if not conclusive + } + break; + + case FID_ROW_HEIGHT: + { + SfxUInt16Item aHeightItem( FID_ROW_HEIGHT, rDoc.GetRowHeight( nPosY , nTab) ); + rSet.Put( aHeightItem ); + //XXX disable if not conclusive + if ( pDocSh->IsReadOnly()) + rSet.DisableItem( nWhich ); + } + break; + + case SID_DETECTIVE_FILLMODE: + rSet.Put(SfxBoolItem( nWhich, pTabViewShell->IsAuditShell() )); + break; + + case FID_INPUTLINE_STATUS: + OSL_FAIL( "Old update method. Use ScTabViewShell::UpdateInputHandler()." ); + break; + + case SID_SCENARIOS: // scenarios: + if (!(rMark.IsMarked() || rMark.IsMultiMarked())) // only, if something selected + rSet.DisableItem( nWhich ); + break; + + case FID_NOTE_VISIBLE: + { + const ScPostIt* pNote = rDoc.GetNote(nPosX, nPosY, nTab); + if ( pNote && rDoc.IsBlockEditable( nTab, nPosX,nPosY, nPosX,nPosY ) ) + rSet.Put( SfxBoolItem( nWhich, pNote->IsCaptionShown() ) ); + else + rSet.DisableItem( nWhich ); + } + break; + + case FID_HIDE_NOTE: + case FID_SHOW_NOTE: + { + bool bEnable = false; + bool bSearchForHidden = nWhich == FID_SHOW_NOTE; + if (!rMark.IsMarked() && !rMark.IsMultiMarked()) + { + // Check current cell + const ScPostIt* pNote = rDoc.GetNote(nPosX, nPosY, nTab); + if ( pNote && rDoc.IsBlockEditable( nTab, nPosX,nPosY, nPosX,nPosY ) ) + if ( pNote->IsCaptionShown() != bSearchForHidden) + bEnable = true; + } + else + { + // Check selection range + ScRangeListRef aRangesRef; + rData.GetMultiArea(aRangesRef); + ScRangeList aRanges = *aRangesRef; + std::vector aNotes; + rDoc.GetNotesInRange(aRanges, aNotes); + for(const auto& rNote : aNotes) + { + const ScAddress& rAdr = rNote.maPos; + if( rDoc.IsBlockEditable( rAdr.Tab(), rAdr.Col(), rAdr.Row(), rAdr.Col(), rAdr.Row() )) + { + if (rNote.mpNote->IsCaptionShown() != bSearchForHidden) + { + bEnable = true; + break; + } + } + } + + } + if ( !bEnable ) + rSet.DisableItem( nWhich ); + } + break; + + case FID_SHOW_ALL_NOTES: + case FID_HIDE_ALL_NOTES: + case FID_DELETE_ALL_NOTES: + { + bool bHasNotes = false; + + for (auto const& rTab : rMark.GetSelectedTabs()) + { + if (rDoc.HasTabNotes( rTab )) + { + bHasNotes = true; + break; + } + } + + if ( !bHasNotes ) + rSet.DisableItem( nWhich ); + } + break; + + case SID_TOGGLE_NOTES: + { + bool bHasNotes = false; + ScRangeList aRanges; + + for (auto const& rTab : rMark.GetSelectedTabs()) + { + if (rDoc.HasTabNotes( rTab )) + { + bHasNotes = true; + aRanges.push_back(ScRange(0,0,rTab,rDoc.MaxCol(),rDoc.MaxRow(),rTab)); + } + } + + if ( !bHasNotes ) + rSet.DisableItem( nWhich ); + else + { + CommentCaptionState eState = rDoc.GetAllNoteCaptionsState( aRanges ); + bool bAllNotesInShown = (eState != ALLHIDDEN && eState != MIXED); + rSet.Put( SfxBoolItem( SID_TOGGLE_NOTES, bAllNotesInShown) ); + } + } + break; + + case SID_DELETE_NOTE: + { + bool bEnable = false; + if ( rMark.IsMarked() || rMark.IsMultiMarked() ) + { + if ( rDoc.IsSelectionEditable( rMark ) ) + { + // look for at least one note in selection + ScRangeList aRanges; + rMark.FillRangeListWithMarks( &aRanges, false ); + bEnable = rDoc.ContainsNotesInRange( aRanges ); + } + } + else + { + bEnable = rDoc.IsBlockEditable( nTab, nPosX,nPosY, nPosX,nPosY ) && + rDoc.GetNote(nPosX, nPosY, nTab); + } + if ( !bEnable ) + rSet.DisableItem( nWhich ); + } + break; + + case SID_OPENDLG_CONSOLIDATE: + case SCITEM_CONSOLIDATEDATA: + { + if (rDoc.GetChangeTrack()!=nullptr) + rSet.DisableItem( nWhich); + } + break; + + case SID_CHINESE_CONVERSION: + case SID_HANGUL_HANJA_CONVERSION: + ScViewUtil::HideDisabledSlot( rSet, rData.GetBindings(), nWhich ); + break; + + case FID_USE_NAME: + { + if ( pDocSh && pDocSh->IsDocShared() ) + rSet.DisableItem( nWhich ); + else + { + ScRange aRange; + if ( rData.GetSimpleArea( aRange ) != SC_MARK_SIMPLE ) + rSet.DisableItem( nWhich ); + } + } + break; + + case FID_DEFINE_NAME: + case FID_INSERT_NAME: + case FID_ADD_NAME: + case SID_DEFINE_COLROWNAMERANGES: + { + if ( pDocSh && pDocSh->IsDocShared() ) + { + rSet.DisableItem( nWhich ); + } + } + break; + + case FID_DEFINE_CURRENT_NAME: + { + ScAddress aCurrentAddress( nPosX, nPosY, nTab ); + + if ( !rDoc.IsAddressInRangeName( RangeNameScope::GLOBAL, aCurrentAddress ) && + !rDoc.IsAddressInRangeName( RangeNameScope::SHEET, aCurrentAddress )) + { + rSet.DisableItem( nWhich ); + } + } + break; + + case SID_SPELL_DIALOG: + { + if (rDoc.IsTabProtected(rData.GetTabNo())) + { + bool bVisible = false; + SfxViewFrame* pViewFrame = ( pTabViewShell ? &pTabViewShell->GetViewFrame() : nullptr ); + if ( pViewFrame && pViewFrame->HasChildWindow( nWhich ) ) + { + SfxChildWindow* pChild = pViewFrame->GetChildWindow( nWhich ); + std::shared_ptr xController = pChild ? pChild->GetController() : nullptr; + if (xController && xController->getDialog()->get_visible()) + { + bVisible = true; + } + } + if ( !bVisible ) + { + rSet.DisableItem( nWhich ); + } + } + } + break; + + case SID_OPENDLG_CURRENTCONDFRMT: + case SID_OPENDLG_CURRENTCONDFRMT_MANAGER: + { + const SfxPoolItem* pItem = rDoc.GetAttr( nPosX, nPosY, nTab, ATTR_CONDITIONAL ); + const ScCondFormatItem* pCondFormatItem = static_cast(pItem); + + if ( pCondFormatItem->GetCondFormatData().empty() ) + rSet.DisableItem( nWhich ); + else if ( pCondFormatItem->GetCondFormatData().size() == 1 ) + rSet.DisableItem( SID_OPENDLG_CURRENTCONDFRMT_MANAGER ); + else if ( pCondFormatItem->GetCondFormatData().size() > 1 ) + rSet.DisableItem( SID_OPENDLG_CURRENTCONDFRMT ); + } + break; + + } // switch ( nWitch ) + nWhich = aIter.NextWhich(); + } // while ( nWitch ) +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/cellsh1.cxx b/sc/source/ui/view/cellsh1.cxx new file mode 100644 index 0000000000..558d5a8166 --- /dev/null +++ b/sc/source/ui/view/cellsh1.cxx @@ -0,0 +1,3600 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +using namespace ::com::sun::star; +using namespace ::com::sun::star::beans; +using namespace ::com::sun::star::uno; + +namespace{ +InsertDeleteFlags FlagsFromString(const OUString& rFlagsStr, + InsertDeleteFlags nFlagsMask = InsertDeleteFlags::CONTENTS | InsertDeleteFlags::ATTRIB) +{ + OUString aFlagsStr = rFlagsStr.toAsciiUpperCase(); + InsertDeleteFlags nFlags = InsertDeleteFlags::NONE; + + for (sal_Int32 i=0 ; i < aFlagsStr.getLength(); ++i) + { + switch (aFlagsStr[i]) + { + case 'A': return InsertDeleteFlags::ALL; + case 'S': nFlags |= InsertDeleteFlags::STRING & nFlagsMask; break; + case 'V': nFlags |= InsertDeleteFlags::VALUE & nFlagsMask; break; + case 'D': nFlags |= InsertDeleteFlags::DATETIME & nFlagsMask; break; + case 'F': nFlags |= InsertDeleteFlags::FORMULA & nFlagsMask; break; + case 'N': nFlags |= InsertDeleteFlags::NOTE & nFlagsMask; break; + case 'T': nFlags |= InsertDeleteFlags::ATTRIB & nFlagsMask; break; + case 'O': nFlags |= InsertDeleteFlags::OBJECTS & nFlagsMask; break; + } + } + return nFlags; +} + +OUString FlagsToString( InsertDeleteFlags nFlags, + InsertDeleteFlags nFlagsMask = InsertDeleteFlags::CONTENTS | InsertDeleteFlags::ATTRIB ) +{ + OUString aFlagsStr; + + if( nFlags == InsertDeleteFlags::ALL ) + { + aFlagsStr = "A"; + } + else + { + nFlags &= nFlagsMask; + + if( nFlags & InsertDeleteFlags::STRING ) aFlagsStr += "S"; + if( nFlags & InsertDeleteFlags::VALUE ) aFlagsStr += "V"; + if( nFlags & InsertDeleteFlags::DATETIME ) aFlagsStr += "D"; + if( nFlags & InsertDeleteFlags::FORMULA ) aFlagsStr += "F"; + if( nFlags & InsertDeleteFlags::NOTE ) aFlagsStr += "N"; + if( nFlags & InsertDeleteFlags::ATTRIB ) aFlagsStr += "T"; + if( nFlags & InsertDeleteFlags::OBJECTS ) aFlagsStr += "O"; + } + return aFlagsStr; +} + +void SetTabNoAndCursor( const ScViewData& rViewData, std::u16string_view rCellId ) +{ + ScTabViewShell* pTabViewShell = rViewData.GetViewShell(); + assert(pTabViewShell); + const ScDocument& rDoc = rViewData.GetDocShell()->GetDocument(); + std::vector aNotes; + rDoc.GetAllNoteEntries(aNotes); + + sal_uInt32 nId = o3tl::toUInt32(rCellId); + auto lComp = [nId](const sc::NoteEntry& rNote) { return rNote.mpNote->GetId() == nId; }; + + const auto& aFoundNoteIt = std::find_if(aNotes.begin(), aNotes.end(), lComp); + if (aFoundNoteIt != aNotes.end()) + { + ScAddress aFoundPos = aFoundNoteIt->maPos; + pTabViewShell->SetTabNo(aFoundPos.Tab()); + pTabViewShell->SetCursor(aFoundPos.Col(), aFoundPos.Row()); + } +} + +void InsertCells(ScTabViewShell* pTabViewShell, SfxRequest &rReq, InsCellCmd eCmd) +{ + if (eCmd!=INS_NONE) + { + pTabViewShell->InsertCells( eCmd ); + + if( ! rReq.IsAPI() ) + { + OUString aParam; + + switch( eCmd ) + { + case INS_CELLSDOWN: aParam = "V"; break; + case INS_CELLSRIGHT: aParam = ">"; break; + case INS_INSROWS_BEFORE: aParam = "R"; break; + case INS_INSCOLS_BEFORE: aParam = "C"; break; + default: + { + // added to avoid warnings + } + } + rReq.AppendItem( SfxStringItem( FID_INS_CELL, aParam ) ); + rReq.Done(); + } + } +} + +void DeleteCells(ScTabViewShell* pTabViewShell, SfxRequest &rReq, DelCellCmd eCmd) +{ + if (eCmd != DelCellCmd::NONE ) + { + pTabViewShell->DeleteCells( eCmd ); + + if( ! rReq.IsAPI() ) + { + OUString aParam; + + switch( eCmd ) + { + case DelCellCmd::CellsUp: aParam = "U"; break; + case DelCellCmd::CellsLeft: aParam = "L"; break; + case DelCellCmd::Rows: aParam = "R"; break; + case DelCellCmd::Cols: aParam = "C"; break; + default: + { + // added to avoid warnings + } + } + rReq.AppendItem( SfxStringItem( FID_DELETE_CELL, aParam ) ); + rReq.Done(); + } + } +} +} + +void ScCellShell::ExecuteEdit( SfxRequest& rReq ) +{ + ScModule* pScMod = SC_MOD(); + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + SfxBindings& rBindings = pTabViewShell->GetViewFrame().GetBindings(); + const SfxItemSet* pReqArgs = rReq.GetArgs(); + sal_uInt16 nSlot = rReq.GetSlot(); + + pTabViewShell->HideListBox(); // Autofilter-DropDown-Listbox + + // finish input + if ( GetViewData().HasEditView( GetViewData().GetActivePart() ) ) + { + switch ( nSlot ) + { + case FID_DEFINE_NAME: + case FID_ADD_NAME: + case FID_USE_NAME: + case FID_INSERT_NAME: + case SID_SPELL_DIALOG: + case SID_HANGUL_HANJA_CONVERSION: + case SID_OPENDLG_CONDFRMT: + case SID_OPENDLG_CURRENTCONDFRMT: + case SID_OPENDLG_COLORSCALE: + case SID_OPENDLG_DATABAR: + pScMod->InputEnterHandler(); + pTabViewShell->UpdateInputHandler(); + break; + + default: + break; + } + } + + switch ( nSlot ) + { + + // insert / delete cells / rows / columns + + case FID_INS_ROW: + case FID_INS_ROWS_BEFORE: + pTabViewShell->InsertCells(INS_INSROWS_BEFORE); + rReq.Done(); + break; + + case FID_INS_COLUMN: + case FID_INS_COLUMNS_BEFORE: + pTabViewShell->InsertCells(INS_INSCOLS_BEFORE); + rReq.Done(); + break; + + case FID_INS_ROWS_AFTER: + pTabViewShell->InsertCells(INS_INSROWS_AFTER); + rReq.Done(); + break; + + case FID_INS_COLUMNS_AFTER: + pTabViewShell->InsertCells(INS_INSCOLS_AFTER); + rReq.Done(); + break; + + case FID_INS_CELLSDOWN: + pTabViewShell->InsertCells(INS_CELLSDOWN); + rReq.Done(); + break; + + case FID_INS_CELLSRIGHT: + pTabViewShell->InsertCells(INS_CELLSRIGHT); + rReq.Done(); + break; + + case SID_DEL_ROWS: + pTabViewShell->DeleteCells( DelCellCmd::Rows ); + rReq.Done(); + break; + + case SID_DEL_COLS: + pTabViewShell->DeleteCells( DelCellCmd::Cols ); + rReq.Done(); + break; + + case FID_INS_CELL: + { + InsCellCmd eCmd=INS_NONE; + + if ( pReqArgs ) + { + const SfxPoolItem* pItem; + OUString aFlags; + + if( pReqArgs->HasItem( FID_INS_CELL, &pItem ) ) + aFlags = static_cast(pItem)->GetValue(); + if( !aFlags.isEmpty() ) + { + switch( aFlags[0] ) + { + case 'V': eCmd = INS_CELLSDOWN ;break; + case '>': eCmd = INS_CELLSRIGHT ;break; + case 'R': eCmd = INS_INSROWS_BEFORE ;break; + case 'C': eCmd = INS_INSCOLS_BEFORE ;break; + } + } + } + else + { + if ( GetViewData().SimpleColMarked() ) + eCmd = INS_INSCOLS_BEFORE; + else if ( GetViewData().SimpleRowMarked() ) + eCmd = INS_INSROWS_BEFORE; + else + { + ScDocument& rDoc = GetViewData().GetDocument(); + bool bTheFlag=(rDoc.GetChangeTrack()!=nullptr); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + VclPtr pDlg(pFact->CreateScInsertCellDlg(pTabViewShell->GetFrameWeld(), bTheFlag)); + pDlg->StartExecuteAsync([pDlg, pTabViewShell](sal_Int32 nResult){ + if (nResult == RET_OK) + { + SfxRequest aRequest(pTabViewShell->GetViewFrame(), FID_INS_CELL); + InsertCells(pTabViewShell, aRequest, pDlg->GetInsCellCmd()); + } + pDlg->disposeOnce(); + }); + } + } + + InsertCells(pTabViewShell, rReq, eCmd); + } + break; + + case FID_DELETE_CELL: + { + DelCellCmd eCmd = DelCellCmd::NONE; + + if ( pReqArgs ) + { + const SfxPoolItem* pItem; + OUString aFlags; + + if( pReqArgs->HasItem( FID_DELETE_CELL, &pItem ) ) + aFlags = static_cast(pItem)->GetValue(); + if( !aFlags.isEmpty() ) + { + switch( aFlags[0] ) + { + case 'U': eCmd = DelCellCmd::CellsUp ;break; + case 'L': eCmd = DelCellCmd::CellsLeft ;break; + case 'R': eCmd = DelCellCmd::Rows ;break; + case 'C': eCmd = DelCellCmd::Cols ;break; + } + } + } + else + { + if ( GetViewData().SimpleColMarked() ) + eCmd = DelCellCmd::Cols; + else if ( GetViewData().SimpleRowMarked() ) + eCmd = DelCellCmd::Rows; + else + { + ScRange aRange; + ScDocument& rDoc = GetViewData().GetDocument(); + bool bTheFlag=GetViewData().IsMultiMarked() || + (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE_FILTERED) || + (rDoc.GetChangeTrack() != nullptr); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + VclPtr pDlg(pFact->CreateScDeleteCellDlg( pTabViewShell->GetFrameWeld(), bTheFlag )); + + pDlg->StartExecuteAsync([pDlg, pTabViewShell](sal_Int32 nResult){ + if (nResult == RET_OK) + { + SfxRequest aRequest(pTabViewShell->GetViewFrame(), FID_INS_CELL); + DeleteCells(pTabViewShell, aRequest, pDlg->GetDelCellCmd()); + } + pDlg->disposeOnce(); + }); + } + } + DeleteCells(pTabViewShell, rReq, eCmd); + } + break; + + // delete contents from cells + + case SID_DELETE_CONTENTS: + pTabViewShell->DeleteContents( InsertDeleteFlags::CONTENTS ); + rReq.Done(); + break; + + case SID_DELETE: + { + InsertDeleteFlags nFlags = InsertDeleteFlags::NONE; + + if ( pReqArgs!=nullptr && pTabViewShell->SelectionEditable() ) + { + const SfxPoolItem* pItem; + OUString aFlags('A'); + + if( pReqArgs->HasItem( SID_DELETE, &pItem ) ) + aFlags = static_cast(pItem)->GetValue(); + + nFlags |= FlagsFromString(aFlags, InsertDeleteFlags::ALL); + } + else + { + ScEditableTester aTester( pTabViewShell ); + if (aTester.IsEditable()) + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr pDlg(pFact->CreateScDeleteContentsDlg(pTabViewShell->GetFrameWeld())); + ScDocument& rDoc = GetViewData().GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + if ( rDoc.IsTabProtected(nTab) ) + pDlg->DisableObjects(); + if (pDlg->Execute() == RET_OK) + { + nFlags = pDlg->GetDelContentsCmdBits(); + } + } + else + pTabViewShell->ErrorMessage(aTester.GetMessageId()); + } + + if( nFlags != InsertDeleteFlags::NONE ) + { + pTabViewShell->DeleteContents( nFlags ); + + if( ! rReq.IsAPI() ) + { + OUString aFlags = FlagsToString( nFlags, InsertDeleteFlags::ALL ); + + rReq.AppendItem( SfxStringItem( SID_DELETE, aFlags ) ); + rReq.Done(); + } + } + } + break; + + // fill... + + case FID_FILL_TO_BOTTOM: + pTabViewShell->FillSimple( FILL_TO_BOTTOM ); + rReq.Done(); + break; + + case FID_FILL_TO_RIGHT: + pTabViewShell->FillSimple( FILL_TO_RIGHT ); + rReq.Done(); + break; + + case FID_FILL_TO_TOP: + pTabViewShell->FillSimple( FILL_TO_TOP ); + rReq.Done(); + break; + + case FID_FILL_TO_LEFT: + pTabViewShell->FillSimple( FILL_TO_LEFT ); + rReq.Done(); + break; + + case FID_FILL_TAB: + { + InsertDeleteFlags nFlags = InsertDeleteFlags::NONE; + ScPasteFunc nFunction = ScPasteFunc::NONE; + bool bSkipEmpty = false; + bool bAsLink = false; + + if ( pReqArgs!=nullptr && pTabViewShell->SelectionEditable() ) + { + const SfxPoolItem* pItem; + OUString aFlags('A'); + + if( pReqArgs->HasItem( FID_FILL_TAB, &pItem ) ) + aFlags = static_cast(pItem)->GetValue(); + + nFlags |= FlagsFromString(aFlags); + } + else + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr pDlg(pFact->CreateScInsertContentsDlg(pTabViewShell->GetFrameWeld(), + new OUString(ScResId(STR_FILL_TAB)))); + pDlg->SetFillMode(true); + + if (pDlg->Execute() == RET_OK) + { + nFlags = pDlg->GetInsContentsCmdBits(); + nFunction = pDlg->GetFormulaCmdBits(); + bSkipEmpty = pDlg->IsSkipEmptyCells(); + bAsLink = pDlg->IsLink(); + // there is no MoveMode with fill tabs + } + } + + if( nFlags != InsertDeleteFlags::NONE ) + { + pTabViewShell->FillTab( nFlags, nFunction, bSkipEmpty, bAsLink ); + + if( ! rReq.IsAPI() ) + { + OUString aFlags = FlagsToString( nFlags ); + + rReq.AppendItem( SfxStringItem( FID_FILL_TAB, aFlags ) ); + rReq.Done(); + } + } + } + break; + + case FID_FILL_SERIES: + { + if (GetViewData().SelectionForbidsCellFill()) + // Slot should be already disabled, but in case it wasn't + // don't even attempt to do the evaluation and popup a + // dialog. + break; + + SCCOL nStartCol; + SCROW nStartRow; + SCTAB nStartTab; + SCCOL nEndCol; + SCROW nEndRow; + SCTAB nEndTab; + sal_uInt16 nPossDir = FDS_OPT_NONE; + FillDir eFillDir = FILL_TO_BOTTOM; + FillCmd eFillCmd = FILL_LINEAR; + FillDateCmd eFillDateCmd = FILL_DAY; + double fStartVal = MAXDOUBLE; + double fIncVal = 1; + double fMaxVal = MAXDOUBLE; + bool bDoIt = false; + + GetViewData().GetSimpleArea( nStartCol, nStartRow, nStartTab, + nEndCol, nEndRow, nEndTab ); + + if( nStartCol!=nEndCol ) + { + nPossDir |= FDS_OPT_HORZ; + eFillDir=FILL_TO_RIGHT; + } + + if( nStartRow!=nEndRow ) + { + nPossDir |= FDS_OPT_VERT; + eFillDir=FILL_TO_BOTTOM; + } + + ScDocument& rDoc = GetViewData().GetDocument(); + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + + if( pReqArgs ) + { + const SfxPoolItem* pItem; + OUString aFillDir, aFillCmd, aFillDateCmd; + OUString aFillStep, aFillStart, aFillMax; + sal_uInt32 nKey; + double fTmpVal; + + if( pReqArgs->HasItem( FID_FILL_SERIES, &pItem ) ) + aFillDir = static_cast(pItem)->GetValue(); + if( pReqArgs->HasItem( FN_PARAM_1, &pItem ) ) + aFillCmd = static_cast(pItem)->GetValue(); + if( pReqArgs->HasItem( FN_PARAM_2, &pItem ) ) + aFillDateCmd = static_cast(pItem)->GetValue(); + if( pReqArgs->HasItem( FN_PARAM_3, &pItem ) ) + aFillStep = static_cast(pItem)->GetValue(); + if( pReqArgs->HasItem( FN_PARAM_4, &pItem ) ) + aFillStart = static_cast(pItem)->GetValue(); + if( pReqArgs->HasItem( FN_PARAM_5, &pItem ) ) + aFillMax = static_cast(pItem)->GetValue(); + + if( !aFillDir.isEmpty() ) + switch( aFillDir[0] ) + { + case 'B': case 'b': eFillDir=FILL_TO_BOTTOM; break; + case 'R': case 'r': eFillDir=FILL_TO_RIGHT; break; + case 'T': case 't': eFillDir=FILL_TO_TOP; break; + case 'L': case 'l': eFillDir=FILL_TO_LEFT; break; + } + + if( !aFillCmd.isEmpty() ) + switch( aFillCmd[0] ) + { + case 'S': case 's': eFillCmd=FILL_SIMPLE; break; + case 'L': case 'l': eFillCmd=FILL_LINEAR; break; + case 'G': case 'g': eFillCmd=FILL_GROWTH; break; + case 'D': case 'd': eFillCmd=FILL_DATE; break; + case 'A': case 'a': eFillCmd=FILL_AUTO; break; + } + + if( !aFillDateCmd.isEmpty() ) + switch( aFillDateCmd[0] ) + { + case 'D': case 'd': eFillDateCmd=FILL_DAY; break; + case 'W': case 'w': eFillDateCmd=FILL_WEEKDAY; break; + case 'M': case 'm': eFillDateCmd=FILL_MONTH; break; + case 'Y': case 'y': eFillDateCmd=FILL_YEAR; break; + } + + nKey = 0; + if( pFormatter->IsNumberFormat( aFillStart, nKey, fTmpVal )) + fStartVal = fTmpVal; + + nKey = 0; + if( pFormatter->IsNumberFormat( aFillStep, nKey, fTmpVal )) + fIncVal = fTmpVal; + + nKey = 0; + if( pFormatter->IsNumberFormat( aFillMax, nKey, fTmpVal )) + fMaxVal = fTmpVal; + + bDoIt = true; + + } + else // (pReqArgs == nullptr) => raise Dialog + { + sal_uInt32 nPrivFormat = rDoc.GetNumberFormat( nStartCol, nStartRow, nStartTab ); + CellType eCellType = rDoc.GetCellType( nStartCol, nStartRow, nStartTab ); + const SvNumberformat* pPrivEntry = pFormatter->GetEntry( nPrivFormat ); + const SCSIZE nSelectHeight = nEndRow - nStartRow + 1; + const SCSIZE nSelectWidth = nEndCol - nStartCol + 1; + + if (!pPrivEntry) + { + OSL_FAIL("Numberformat not found !!!"); + } + else + { + SvNumFormatType nPrivType = pPrivEntry->GetType(); + if (nPrivType & SvNumFormatType::DATE) + { + eFillCmd=FILL_DATE; + } + else if(eCellType==CELLTYPE_STRING) + { + eFillCmd=FILL_AUTO; + } + } + + OUString aStartStr; + + // suggest default Startvalue only, when just 1 row or column + if ( nStartCol == nEndCol || nStartRow == nEndRow ) + { + double fInputEndVal = 0.0; + OUString aEndStr; + + const bool forceSystemLocale = true; + aStartStr = rDoc.GetInputString( nStartCol, nStartRow, nStartTab, forceSystemLocale ); + fStartVal = rDoc.GetValue( nStartCol, nStartRow, nStartTab ); + + if(eFillDir==FILL_TO_BOTTOM && nStartRow < nEndRow ) + { + aEndStr = rDoc.GetInputString( nStartCol, nStartRow+1, nStartTab, forceSystemLocale ); + if(!aEndStr.isEmpty()) + { + fInputEndVal = rDoc.GetValue( nStartCol, nStartRow+1, nStartTab ); + fIncVal=fInputEndVal-fStartVal; + } + } + else + { + if(nStartCol < nEndCol) + { + aEndStr = rDoc.GetInputString( nStartCol+1, nStartRow, nStartTab, forceSystemLocale ); + if(!aEndStr.isEmpty()) + { + fInputEndVal = rDoc.GetValue( nStartCol+1, nStartRow, nStartTab ); + fIncVal=fInputEndVal-fStartVal; + } + } + } + if(eFillCmd==FILL_DATE) + { + const Date& rNullDate = rDoc.GetFormatTable()->GetNullDate(); + Date aStartDate = rNullDate; + aStartDate.AddDays(fStartVal); + Date aEndDate = rNullDate; + aEndDate.AddDays(fInputEndVal); + double fTempDate=0; + + if(aStartDate.GetYear()!=aEndDate.GetYear()) + { + eFillDateCmd = FILL_YEAR; + fTempDate=aEndDate.GetYear()-aStartDate.GetYear(); + } + if(aStartDate.GetMonth()!=aEndDate.GetMonth()) + { + eFillDateCmd = FILL_MONTH; + fTempDate=fTempDate*12+aEndDate.GetMonth()-aStartDate.GetMonth(); + } + if(aStartDate.GetDay()==aEndDate.GetDay()) + { + fIncVal=fTempDate; + } + } + } + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr pDlg(pFact->CreateScFillSeriesDlg( pTabViewShell->GetFrameWeld(), + rDoc, + eFillDir, eFillCmd, eFillDateCmd, + aStartStr, fIncVal, fMaxVal, + nSelectHeight, nSelectWidth, nPossDir)); + + if ( nStartCol != nEndCol && nStartRow != nEndRow ) + { + pDlg->SetEdStartValEnabled(false); + } + + if ( pDlg->Execute() == RET_OK ) + { + eFillDir = pDlg->GetFillDir(); + eFillCmd = pDlg->GetFillCmd(); + eFillDateCmd = pDlg->GetFillDateCmd(); + + if(eFillCmd==FILL_AUTO) + { + OUString aStr = pDlg->GetStartStr(); + if(!aStr.isEmpty()) + pTabViewShell->EnterData( nStartCol, nStartRow, nStartTab, aStr ); + } + fStartVal = pDlg->GetStart(); + fIncVal = pDlg->GetStep(); + fMaxVal = pDlg->GetMax(); + bDoIt = true; + } + } + + if( bDoIt ) + { + //nScFillModeMouseModifier = 0; // no Ctrl/Copy + pTabViewShell->FillSeries( eFillDir, eFillCmd, eFillDateCmd, fStartVal, fIncVal, fMaxVal ); + + if( ! rReq.IsAPI() ) + { + OUString aPara; + const Color* pColor = nullptr; + + switch( eFillDir ) + { + case FILL_TO_BOTTOM: aPara = "B"; break; + case FILL_TO_RIGHT: aPara = "R"; break; + case FILL_TO_TOP: aPara = "T"; break; + case FILL_TO_LEFT: aPara = "L"; break; + default: break; + } + rReq.AppendItem( SfxStringItem( FID_FILL_SERIES, aPara ) ); + + switch( eFillCmd ) + { + case FILL_SIMPLE: aPara = "S"; break; + case FILL_LINEAR: aPara = "L"; break; + case FILL_GROWTH: aPara = "G"; break; + case FILL_DATE: aPara = "D"; break; + case FILL_AUTO: aPara = "A"; break; + default: break; + } + rReq.AppendItem( SfxStringItem( FN_PARAM_1, aPara ) ); + + switch( eFillDateCmd ) + { + case FILL_DAY: aPara = "D"; break; + case FILL_WEEKDAY: aPara = "W"; break; + case FILL_MONTH: aPara = "M"; break; + case FILL_YEAR: aPara = "Y"; break; + default: break; + } + rReq.AppendItem( SfxStringItem( FN_PARAM_2, aPara ) ); + + sal_uInt32 nFormatKey = pFormatter->GetStandardFormat(SvNumFormatType::NUMBER, + ScGlobal::eLnge ); + + pFormatter->GetOutputString( fIncVal, nFormatKey, aPara, &pColor ); + rReq.AppendItem( SfxStringItem( FN_PARAM_3, aPara ) ); + + pFormatter->GetOutputString( fStartVal, nFormatKey, aPara, &pColor ); + rReq.AppendItem( SfxStringItem( FN_PARAM_4, aPara ) ); + + pFormatter->GetOutputString( fMaxVal, nFormatKey, aPara, &pColor ); + rReq.AppendItem( SfxStringItem( FN_PARAM_5, aPara ) ); + + rReq.Done(); + } + } + } + break; + + case FID_FILL_AUTO: + { + SCCOL nStartCol; + SCROW nStartRow; + SCCOL nEndCol; + SCROW nEndRow; + + GetViewData().GetFillData( nStartCol, nStartRow, nEndCol, nEndRow ); + SCCOL nFillCol = GetViewData().GetRefEndX(); + SCROW nFillRow = GetViewData().GetRefEndY(); + ScDocument& rDoc = GetViewData().GetDocument(); + + if( pReqArgs != nullptr ) + { + if( const SfxStringItem* pItem = pReqArgs->GetItemIfSet( FID_FILL_AUTO ) ) + { + ScAddress aScAddress; + OUString aArg = pItem->GetValue(); + + if( aScAddress.Parse( aArg, rDoc, rDoc.GetAddressConvention() ) & ScRefFlags::VALID ) + { + nFillRow = aScAddress.Row(); + nFillCol = aScAddress.Col(); + } + } + + SCTAB nStartTab, nEndTab; + GetViewData().GetSimpleArea( nStartCol,nStartRow,nStartTab, + nEndCol,nEndRow,nEndTab ); + } + else // call via mouse + { + // not in a merged cell + + if ( nStartCol == nEndCol && nStartRow == nEndRow ) + { + SCCOL nMergeCol = nStartCol; + SCROW nMergeRow = nStartRow; + if ( GetViewData().GetDocument().ExtendMerge( + nStartCol, nStartRow, nMergeCol, nMergeRow, + GetViewData().GetTabNo() ) ) + { + if ( nFillCol >= nStartCol && nFillCol <= nMergeCol && nFillRow == nStartRow ) + nFillCol = nStartCol; + if ( nFillRow >= nStartRow && nFillRow <= nMergeRow && nFillCol == nStartCol ) + nFillRow = nStartRow; + } + } + } + + if ( nFillCol != nEndCol || nFillRow != nEndRow ) + { + if ( nFillCol==nEndCol || nFillRow==nEndRow ) + { + FillDir eDir = FILL_TO_BOTTOM; + SCCOLROW nCount = 0; + + if ( nFillCol==nEndCol ) + { + if ( nFillRow > nEndRow ) + { + eDir = FILL_TO_BOTTOM; + nCount = nFillRow - nEndRow; + } + else if ( nFillRow < nStartRow ) + { + eDir = FILL_TO_TOP; + nCount = nStartRow - nFillRow; + } + } + else + { + if ( nFillCol > nEndCol ) + { + eDir = FILL_TO_RIGHT; + nCount = nFillCol - nEndCol; + } + else if ( nFillCol < nStartCol ) + { + eDir = FILL_TO_LEFT; + nCount = nStartCol - nFillCol; + } + } + + if ( nCount != 0) + { + pTabViewShell->FillAuto( eDir, nStartCol, nStartRow, nEndCol, nEndRow, nCount ); + + if( ! rReq.IsAPI() ) + { + ScAddress aAdr( nFillCol, nFillRow, 0 ); + OUString aAdrStr(aAdr.Format(ScRefFlags::RANGE_ABS, &rDoc, rDoc.GetAddressConvention())); + + rReq.AppendItem( SfxStringItem( FID_FILL_AUTO, aAdrStr ) ); + rReq.Done(); + } + } + + } + else + { + OSL_FAIL( "Direction not unique for autofill" ); + } + } + } + break; + case FID_FILL_SINGLE_EDIT: + ExecuteFillSingleEdit(); + break; + case SID_RANDOM_NUMBER_GENERATOR_DIALOG: + { + sal_uInt16 nId = ScRandomNumberGeneratorDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + + } + break; + case SID_SAMPLING_DIALOG: + { + sal_uInt16 nId = ScSamplingDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + break; + case SID_DESCRIPTIVE_STATISTICS_DIALOG: + { + sal_uInt16 nId = ScDescriptiveStatisticsDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + break; + case SID_ANALYSIS_OF_VARIANCE_DIALOG: + { + sal_uInt16 nId = ScAnalysisOfVarianceDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + break; + case SID_CORRELATION_DIALOG: + { + sal_uInt16 nId = ScCorrelationDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + break; + case SID_COVARIANCE_DIALOG: + { + sal_uInt16 nId = ScCovarianceDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + break; + case SID_EXPONENTIAL_SMOOTHING_DIALOG: + { + sal_uInt16 nId = ScExponentialSmoothingDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + break; + case SID_MOVING_AVERAGE_DIALOG: + { + sal_uInt16 nId = ScMovingAverageDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + break; + case SID_REGRESSION_DIALOG: + { + sal_uInt16 nId = ScRegressionDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + break; + case SID_TTEST_DIALOG: + { + sal_uInt16 nId = ScTTestDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + + } + break; + case SID_FTEST_DIALOG: + { + sal_uInt16 nId = ScFTestDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + + } + break; + case SID_ZTEST_DIALOG: + { + sal_uInt16 nId = ScZTestDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + + } + break; + case SID_CHI_SQUARE_TEST_DIALOG: + { + sal_uInt16 nId = ScChiSquareTestDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + + } + break; + case SID_FOURIER_ANALYSIS_DIALOG: + { + sal_uInt16 nId = ScFourierAnalysisDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + + } + break; + case SID_SEARCH_RESULTS_DIALOG: + { + const SfxPoolItem* pItem = nullptr; + if (pReqArgs && pReqArgs->HasItem(SID_SEARCH_RESULTS_DIALOG, &pItem)) + { + bool bVisible = static_cast(pItem)->GetValue(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + // The window ID should equal the slot ID, but not a biggie if it wasn't. + sal_uInt16 nId = sc::SearchResultsDlgWrapper::GetChildWindowId(); + rViewFrm.SetChildWindow(nId, bVisible, false); + } + rReq.Done(); + } + break; + + case SID_INSERT_SPARKLINE: + case SID_EDIT_SPARKLINE_GROUP: + { + sal_uInt16 nId = sc::SparklineDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrame = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWindow = rViewFrame.GetChildWindow(nId); + pScMod->SetRefDialog(nId, pWindow == nullptr); + rReq.Done(); + } + break; + + case SID_EDIT_SPARKLINE: + { + sal_uInt16 nId = sc::SparklineDataRangeDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrame = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWindow = rViewFrame.GetChildWindow(nId); + pScMod->SetRefDialog(nId, pWindow == nullptr); + rReq.Done(); + } + break; + + case SID_DELETE_SPARKLINE: + { + pTabViewShell->DeleteContents(InsertDeleteFlags::SPARKLINES); + + rReq.Done(); + } + break; + + case SID_DELETE_SPARKLINE_GROUP: + { + ScRange aMarkRange; + ScMarkType eMarkType = GetViewData().GetSimpleArea(aMarkRange); + if (eMarkType == SC_MARK_SIMPLE) + { + std::shared_ptr pSparklineGroup; + if (GetViewData().GetDocument().GetSparklineGroupInRange(aMarkRange, pSparklineGroup) && pSparklineGroup) + { + GetViewData().GetDocShell()->GetDocFunc().DeleteSparklineGroup(pSparklineGroup, GetViewData().GetTabNo()); + } + } + rReq.Done(); + } + break; + + case SID_GROUP_SPARKLINES: + { + ScRange aRange; + if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE) + { + ScAddress aCursorAddress(GetViewData().GetCurX(), GetViewData().GetCurY(), GetViewData().GetTabNo()); + auto pSparkline = GetViewData().GetDocument().GetSparkline(aCursorAddress); + if (pSparkline) + { + auto const& rpSparklineGroup = pSparkline->getSparklineGroup(); + GetViewData().GetDocShell()->GetDocFunc().GroupSparklines(aRange, rpSparklineGroup); + } + } + rReq.Done(); + } + break; + + case SID_UNGROUP_SPARKLINES: + { + ScRange aRange; + if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE) + { + GetViewData().GetDocShell()->GetDocFunc().UngroupSparklines(aRange); + } + rReq.Done(); + } + break; + + // disposal (Outlines) + // SID_AUTO_OUTLINE, SID_OUTLINE_DELETEALL in Execute (in docsh.idl) + + case SID_OUTLINE_HIDE: + if ( GetViewData().GetDocument().GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ) ) + pTabViewShell->SetDataPilotDetails( false ); + else + pTabViewShell->HideMarkedOutlines(); + rReq.Done(); + break; + + case SID_OUTLINE_SHOW: + { + ScDPObject* pDPObj = GetViewData().GetDocument().GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ); + if ( pDPObj ) + { + Sequence aFilters; + css::sheet::DataPilotFieldOrientation nOrientation; + if ( pTabViewShell->HasSelectionForDrillDown( nOrientation ) ) + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + ScopedVclPtr pDlg( pFact->CreateScDPShowDetailDlg( + pTabViewShell->GetFrameWeld(), *pDPObj, nOrientation ) ); + if ( pDlg->Execute() == RET_OK ) + { + OUString aNewDimName( pDlg->GetDimensionName() ); + pTabViewShell->SetDataPilotDetails( true, &aNewDimName ); + } + } + else if ( !pDPObj->IsServiceData() && + pDPObj->GetDataFieldPositionData( + ScAddress( GetViewData().GetCurX(), GetViewData().GetCurY(), GetViewData().GetTabNo() ), + aFilters ) ) + pTabViewShell->ShowDataPilotSourceData( *pDPObj, aFilters ); + else + pTabViewShell->SetDataPilotDetails(true); + } + else + pTabViewShell->ShowMarkedOutlines(); + rReq.Done(); + } + break; + + case SID_OUTLINE_MAKE: + { + bool bColumns = false; + bool bOk = true; + + if ( GetViewData().GetDocument().GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ) ) + { + ScDPNumGroupInfo aNumInfo; + aNumInfo.mbEnable = true; + aNumInfo.mbAutoStart = true; + aNumInfo.mbAutoEnd = true; + sal_Int32 nParts = 0; + if ( pTabViewShell->HasSelectionForDateGroup( aNumInfo, nParts ) ) + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + const Date& rNullDate( GetViewData().GetDocument().GetFormatTable()->GetNullDate() ); + ScopedVclPtr pDlg( pFact->CreateScDPDateGroupDlg( + pTabViewShell->GetFrameWeld(), + aNumInfo, nParts, rNullDate ) ); + if( pDlg->Execute() == RET_OK ) + { + aNumInfo = pDlg->GetGroupInfo(); + pTabViewShell->DateGroupDataPilot( aNumInfo, pDlg->GetDatePart() ); + } + } + else if ( pTabViewShell->HasSelectionForNumGroup( aNumInfo ) ) + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + ScopedVclPtr pDlg( pFact->CreateScDPNumGroupDlg( + pTabViewShell->GetFrameWeld(), aNumInfo ) ); + if( pDlg->Execute() == RET_OK ) + pTabViewShell->NumGroupDataPilot( pDlg->GetGroupInfo() ); + } + else + pTabViewShell->GroupDataPilot(); + + bOk = false; + } + else if( pReqArgs != nullptr ) + { + const SfxPoolItem* pItem; + bOk = false; + + if( pReqArgs->HasItem( SID_OUTLINE_MAKE, &pItem ) ) + { + OUString aCol = static_cast(pItem)->GetValue(); + aCol = aCol.toAsciiUpperCase(); + + switch( aCol[0] ) + { + case 'R': bColumns=false; bOk = true;break; + case 'C': bColumns=true; bOk = true;break; + } + } + } + else // Dialog, when not whole rows/columns are marked + { + if ( GetViewData().SimpleColMarked() && !GetViewData().SimpleRowMarked() ) + bColumns = true; + else if ( !GetViewData().SimpleColMarked() && GetViewData().SimpleRowMarked() ) + bColumns = false; + else + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + VclPtr pDlg(pFact->CreateAbstractScGroupDlg(pTabViewShell->GetFrameWeld())); + + pDlg->StartExecuteAsync( + [pDlg, pTabViewShell] (sal_Int32 nResult) { + if( RET_OK == nResult ) + { + bool bColumn = pDlg->GetColsChecked(); + pTabViewShell->MakeOutline( bColumn ); + } + pDlg->disposeOnce(); + } + ); + + bOk = false; + } + } + if (bOk) + { + pTabViewShell->MakeOutline( bColumns ); + + if( ! rReq.IsAPI() ) + { + OUString aCol = bColumns ? OUString('C') : OUString('R'); + rReq.AppendItem( SfxStringItem( SID_OUTLINE_MAKE, aCol ) ); + rReq.Done(); + } + } + } + break; + + case SID_OUTLINE_REMOVE: + { + bool bColumns = false; + bool bOk = true; + + if ( GetViewData().GetDocument().GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ) ) + { + pTabViewShell->UngroupDataPilot(); + bOk = false; + } + else if( pReqArgs != nullptr ) + { + const SfxPoolItem* pItem; + bOk = false; + + if( pReqArgs->HasItem( SID_OUTLINE_REMOVE, &pItem ) ) + { + OUString aCol = static_cast(pItem)->GetValue(); + aCol = aCol.toAsciiUpperCase(); + + switch (aCol[0]) + { + case 'R': bColumns=false; bOk = true;break; + case 'C': bColumns=true; bOk = true;break; + } + } + } + else // Dialog only when removal for rows and columns is possible + { + bool bColPoss, bRowPoss; + pTabViewShell->TestRemoveOutline( bColPoss, bRowPoss ); + // TODO: handle this case in LOK too + if ( bColPoss && bRowPoss && !comphelper::LibreOfficeKit::isActive() ) + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + VclPtr pDlg(pFact->CreateAbstractScGroupDlg(pTabViewShell->GetFrameWeld(), true)); + + pDlg->StartExecuteAsync( + [pDlg, pTabViewShell] (sal_Int32 nResult) { + if( RET_OK == nResult ) + { + bool bColumn = pDlg->GetColsChecked(); + pTabViewShell->RemoveOutline( bColumn ); + } + pDlg->disposeOnce(); + } + ); + + bOk = false; + } + else if ( bColPoss ) + bColumns = true; + else if ( bRowPoss ) + bColumns = false; + else + bOk = false; + } + if (bOk) + { + pTabViewShell->RemoveOutline( bColumns ); + + if( ! rReq.IsAPI() ) + { + OUString aCol = bColumns ? OUString('C') : OUString('R'); + rReq.AppendItem( SfxStringItem( SID_OUTLINE_REMOVE, aCol ) ); + rReq.Done(); + } + } + } + break; + + // Clipboard + + case SID_COPY: // for graphs in DrawShell + { + weld::WaitObject aWait( GetViewData().GetDialogParent() ); + pTabViewShell->CopyToClip( nullptr, false, false, true ); + rReq.Done(); + GetViewData().SetPasteMode( ScPasteFlags::Mode | ScPasteFlags::Border ); + pTabViewShell->ShowCursor(); + pTabViewShell->UpdateCopySourceOverlay(); + } + break; + + case SID_CUT: // for graphs in DrawShell + { + weld::WaitObject aWait( GetViewData().GetDialogParent() ); + pTabViewShell->CutToClip(); + rReq.Done(); + GetViewData().SetPasteMode( ScPasteFlags::Mode | ScPasteFlags::Border ); + pTabViewShell->ShowCursor(); + pTabViewShell->UpdateCopySourceOverlay(); + } + break; + + case SID_PASTE: + { + ScClipUtil::PasteFromClipboard( GetViewData(), pTabViewShell, true ); + rReq.Done(); + } + break; + + case SID_CLIPBOARD_FORMAT_ITEMS: + { + weld::WaitObject aWait( GetViewData().GetDialogParent() ); + + SotClipboardFormatId nFormat = SotClipboardFormatId::NONE; + const SfxPoolItem* pItem; + if ( pReqArgs && pReqArgs->GetItemState(nSlot, true, &pItem) == SfxItemState::SET ) + if (auto pIntItem = dynamic_cast(pItem) ) + nFormat = static_cast(pIntItem->GetValue()); + + if ( nFormat != SotClipboardFormatId::NONE ) + { + css::uno::Reference xTransferable(ScTabViewShell::GetClipData(GetViewData().GetActiveWin())); + bool bCells = ( ScTransferObj::GetOwnClipboard(xTransferable) != nullptr ); + bool bDraw = ( ScDrawTransferObj::GetOwnClipboard(xTransferable) != nullptr ); + bool bOle = ( nFormat == SotClipboardFormatId::EMBED_SOURCE ); + + if ( bCells && bOle ) + pTabViewShell->PasteFromSystem(); + else if ( bDraw && bOle ) + pTabViewShell->PasteDraw(); + else + pTabViewShell->PasteFromSystem(nFormat); + } + //?else + //? pTabViewShell->PasteFromSystem(); + + rReq.Done(); + } + pTabViewShell->CellContentChanged(); + break; + + case FID_INS_CELL_CONTENTS: + { + ScDocument& rDoc = GetViewData().GetDocument(); + bool bOtherDoc = !rDoc.IsClipboardSource(); + // keep a reference in case the clipboard is changed during dialog or PasteFromClip + const ScTransferObj* pOwnClip = ScTransferObj::GetOwnClipboard(ScTabViewShell::GetClipData(GetViewData().GetActiveWin())); + if ( pOwnClip ) + { + InsertDeleteFlags nFlags = InsertDeleteFlags::NONE; + ScPasteFunc nFunction = ScPasteFunc::NONE; + InsCellCmd eMoveMode = INS_NONE; + bool bSkipEmpty = false; + bool bTranspose = false; + bool bAsLink = false; + + if ( pReqArgs!=nullptr && pTabViewShell->SelectionEditable() ) + { + const SfxPoolItem* pItem; + OUString aFlags('A'); + + if( pReqArgs->HasItem( FID_INS_CELL_CONTENTS, &pItem ) ) + aFlags = static_cast(pItem)->GetValue(); + + nFlags |= FlagsFromString(aFlags); + + const SfxUInt16Item* pFuncItem = rReq.GetArg(FN_PARAM_1); + const SfxBoolItem* pSkipItem = rReq.GetArg(FN_PARAM_2); + const SfxBoolItem* pTransposeItem = rReq.GetArg(FN_PARAM_3); + const SfxBoolItem* pLinkItem = rReq.GetArg(FN_PARAM_4); + const SfxInt16Item* pMoveItem = rReq.GetArg(FN_PARAM_5); + if ( pFuncItem ) + nFunction = static_cast(pFuncItem->GetValue()); + if ( pSkipItem ) + bSkipEmpty = pSkipItem->GetValue(); + if ( pTransposeItem ) + bTranspose = pTransposeItem->GetValue(); + if ( pLinkItem ) + bAsLink = pLinkItem->GetValue(); + if ( pMoveItem ) + eMoveMode = static_cast(pMoveItem->GetValue()); + } + else + { + ScEditableTester aTester( pTabViewShell ); + if (aTester.IsEditable()) + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr pDlg(pFact->CreateScInsertContentsDlg(pTabViewShell->GetFrameWeld())); + pDlg->SetOtherDoc( bOtherDoc ); + // if ChangeTrack MoveMode disable + pDlg->SetChangeTrack( rDoc.GetChangeTrack() != nullptr ); + // fdo#56098 disable shift if necessary + if (!bOtherDoc) + { + ScViewData& rData = GetViewData(); + if ( rData.GetMarkData().GetTableSelect( rData.GetTabNo() ) ) + { + SCCOL nStartX, nEndX, nClipStartX, nClipSizeX, nRangeSizeX; + SCROW nStartY, nEndY, nClipStartY, nClipSizeY, nRangeSizeY; + SCTAB nStartTab, nEndTab; + pOwnClip->GetDocument()->GetClipStart( nClipStartX, nClipStartY ); + pOwnClip->GetDocument()->GetClipArea( nClipSizeX, nClipSizeY, true ); + + if ( rData.GetSimpleArea( nStartX, nStartY, nStartTab, + nEndX, nEndY, nEndTab ) != SC_MARK_SIMPLE || + nStartTab != nEndTab ) + { + // the destination is not a simple range, + // assume the destination as the current cell + nStartX = nEndX = rData.GetCurX(); + nStartY = nEndY = rData.GetCurY(); + nStartTab = rData.GetTabNo(); + } + // we now have clip- and range dimensions + // the size of the destination area is the larger of the two + nRangeSizeX = nClipSizeX >= nEndX - nStartX ? nClipSizeX : nEndX - nStartX; + nRangeSizeY = nClipSizeY >= nEndY - nStartY ? nClipSizeY : nEndY - nStartY; + // When the source and destination areas intersect things may go wrong, + // especially if the area contains references. This may produce data loss + // (e.g. formulas that get wrong references), this scenario _must_ be avoided. + ScRange aSource( nClipStartX, nClipStartY, nStartTab, + nClipStartX + nClipSizeX, nClipStartY + nClipSizeY, nStartTab ); + ScRange aDest( nStartX, nStartY, nStartTab, + nStartX + nRangeSizeX, nStartY + nRangeSizeY, nStartTab ); + if ( pOwnClip->GetDocument()->IsCutMode() && aSource.Intersects( aDest ) ) + pDlg->SetCellShiftDisabled( CellShiftDisabledFlags::Down | CellShiftDisabledFlags::Right ); + else + { + //no conflict with intersecting ranges, + //check if paste plus shift will fit on sheet + //and disable shift-option if no fit + CellShiftDisabledFlags nDisableShiftX = CellShiftDisabledFlags::NONE; + CellShiftDisabledFlags nDisableShiftY = CellShiftDisabledFlags::NONE; + + //check if horizontal shift will fit + if ( !rData.GetDocument().IsBlockEmpty( + rDoc.MaxCol() - nRangeSizeX, nStartY, + rDoc.MaxCol(), nStartY + nRangeSizeY, + nStartTab ) ) + nDisableShiftX = CellShiftDisabledFlags::Right; + + //check if vertical shift will fit + if ( !rData.GetDocument().IsBlockEmpty( + nStartX, rDoc.MaxRow() - nRangeSizeY, + nStartX + nRangeSizeX, rDoc.MaxRow(), + nStartTab ) ) + nDisableShiftY = CellShiftDisabledFlags::Down; + + if ( nDisableShiftX != CellShiftDisabledFlags::NONE || nDisableShiftY != CellShiftDisabledFlags::NONE) + pDlg->SetCellShiftDisabled( nDisableShiftX | nDisableShiftY ); + } + } + } + if (pDlg->Execute() == RET_OK) + { + nFlags = pDlg->GetInsContentsCmdBits(); + nFunction = pDlg->GetFormulaCmdBits(); + bSkipEmpty = pDlg->IsSkipEmptyCells(); + bTranspose = pDlg->IsTranspose(); + bAsLink = pDlg->IsLink(); + eMoveMode = pDlg->GetMoveMode(); + } + } + else + pTabViewShell->ErrorMessage(aTester.GetMessageId()); + } + + if( nFlags != InsertDeleteFlags::NONE ) + { + { + weld::WaitObject aWait( GetViewData().GetDialogParent() ); + if ( bAsLink && bOtherDoc ) + pTabViewShell->PasteFromSystem(SotClipboardFormatId::LINK); // DDE insert + else + { + pTabViewShell->PasteFromClip( nFlags, pOwnClip->GetDocument(), + nFunction, bSkipEmpty, bTranspose, bAsLink, + eMoveMode, InsertDeleteFlags::NONE, true ); // allow warning dialog + } + } + + if( !pReqArgs ) + { + OUString aFlags = FlagsToString( nFlags ); + + rReq.AppendItem( SfxStringItem( FID_INS_CELL_CONTENTS, aFlags ) ); + rReq.AppendItem( SfxBoolItem( FN_PARAM_2, bSkipEmpty ) ); + rReq.AppendItem( SfxBoolItem( FN_PARAM_3, bTranspose ) ); + rReq.AppendItem( SfxBoolItem( FN_PARAM_4, bAsLink ) ); + rReq.AppendItem( SfxUInt16Item( FN_PARAM_1, static_cast(nFunction) ) ); + rReq.AppendItem( SfxInt16Item( FN_PARAM_5, static_cast(eMoveMode) ) ); + rReq.Done(); + } + } + } + } + pTabViewShell->CellContentChanged(); // => PasteFromXXX ??? + break; + case SID_PASTE_ONLY_VALUE: + case SID_PASTE_ONLY_TEXT: + case SID_PASTE_ONLY_FORMULA: + { + if ( ScTransferObj::GetOwnClipboard(ScTabViewShell::GetClipData(GetViewData().GetActiveWin())) ) // own cell data + { + rReq.SetSlot( FID_INS_CELL_CONTENTS ); + OUString aFlags; + if ( nSlot == SID_PASTE_ONLY_VALUE ) + aFlags = "V"; + else if ( nSlot == SID_PASTE_ONLY_TEXT ) + aFlags = "S"; + else + aFlags = "F"; + rReq.AppendItem( SfxStringItem( FID_INS_CELL_CONTENTS, aFlags ) ); + ExecuteSlot( rReq, GetInterface() ); + rReq.SetReturnValue(SfxInt16Item(nSlot, 1)); // 1 = success + pTabViewShell->CellContentChanged(); + } + else + rReq.SetReturnValue(SfxInt16Item(nSlot, 0)); // 0 = fail + break; + } + case SID_PASTE_TRANSPOSED: + { + if (ScTransferObj::GetOwnClipboard( + ScTabViewShell::GetClipData(GetViewData().GetActiveWin()))) // own cell data + { + rReq.SetSlot(FID_INS_CELL_CONTENTS); + // By default content (values/numbers, strings, formulas and dates), + // attributes and notes are pasted + rReq.AppendItem(SfxBoolItem(FN_PARAM_3, true)); // transpose + ExecuteSlot(rReq, GetInterface()); + rReq.SetReturnValue(SfxInt16Item(nSlot, 1)); // 1 = success + pTabViewShell->CellContentChanged(); + } + else + rReq.SetReturnValue(SfxInt16Item(nSlot, 0)); // 0 = fail + break; + } + case SID_PASTE_AS_LINK: + { + if (ScTransferObj::GetOwnClipboard( + ScTabViewShell::GetClipData(GetViewData().GetActiveWin()))) // own cell data + { + rReq.SetSlot(FID_INS_CELL_CONTENTS); + // paste links to values/numbers, strings, formulas and dates + // do not paste attributes, notes and objects + rReq.AppendItem(SfxStringItem(FID_INS_CELL_CONTENTS, "VSFD")); + rReq.AppendItem(SfxBoolItem(FN_PARAM_4, true)); // as link + ExecuteSlot(rReq, GetInterface()); + rReq.SetReturnValue(SfxInt16Item(nSlot, 1)); // 1 = success + pTabViewShell->CellContentChanged(); + } + else + rReq.SetReturnValue(SfxInt16Item(nSlot, 0)); // 0 = fail + break; + } + case SID_PASTE_TEXTIMPORT_DIALOG: + { + vcl::Window* pWin = GetViewData().GetActiveWin(); + TransferableDataHelper aDataHelper( + TransferableDataHelper::CreateFromSystemClipboard(pWin)); + const uno::Reference& xTransferable + = aDataHelper.GetTransferable(); + SotClipboardFormatId format = SotClipboardFormatId::STRING; + bool bSuccess = false; + if (xTransferable.is() && HasClipboardFormat(format)) + { + OUString sStrBuffer; + bSuccess = aDataHelper.GetString(format, sStrBuffer); + if (bSuccess) + { + auto pStrm = std::make_shared(sStrBuffer); + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + VclPtr pDlg(pFact->CreateScImportAsciiDlg( + pWin ? pWin->GetFrameWeld() : nullptr, OUString(), pStrm.get(), SC_PASTETEXT)); + ScRange aRange; + SCCOL nPosX = 0; + SCROW nPosY = 0; + if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE) + { + nPosX = aRange.aStart.Col(); + nPosY = aRange.aStart.Row(); + } + else + { + nPosX = GetViewData().GetCurX(); + nPosY = GetViewData().GetCurY(); + } + ScAddress aCellPos(nPosX, nPosY, GetViewData().GetTabNo()); + auto pObj = std::make_shared(GetViewData().GetDocument(), aCellPos); + pObj->SetOverwriting(true); + if (pDlg->Execute()) { + ScAsciiOptions aOptions; + pDlg->GetOptions(aOptions); + pDlg->SaveParameters(); + pObj->SetExtOptions(aOptions); + pObj->ImportString(sStrBuffer, format); + } + pDlg->disposeOnce(); + rReq.SetReturnValue(SfxInt16Item(nSlot, 1)); // 1 = success, 0 = fail + rReq.Done(); + } + } + if (!bSuccess) + { + rReq.SetReturnValue(SfxInt16Item(nSlot, 0)); // 0 = fail + rReq.Ignore(); + } + } + break; + case SID_PASTE_SPECIAL: + // differentiate between own cell data and draw objects/external data + // this makes FID_INS_CELL_CONTENTS superfluous + { + vcl::Window* pWin = GetViewData().GetActiveWin(); + css::uno::Reference xTransferable(ScTabViewShell::GetClipData(pWin)); + + // Clipboard-ID given as parameter? Basic "PasteSpecial(Format)" + const SfxPoolItem* pItem=nullptr; + if ( pReqArgs && + pReqArgs->GetItemState(nSlot, true, &pItem) == SfxItemState::SET && + dynamic_cast( pItem) != nullptr ) + { + SotClipboardFormatId nFormat = static_cast(static_cast(pItem)->GetValue()); + bool bRet=true; + { + weld::WaitObject aWait( GetViewData().GetDialogParent() ); + bool bDraw = ( ScDrawTransferObj::GetOwnClipboard(xTransferable) != nullptr ); + if ( bDraw && nFormat == SotClipboardFormatId::EMBED_SOURCE ) + pTabViewShell->PasteDraw(); + else + bRet = pTabViewShell->PasteFromSystem(nFormat, true); // TRUE: no error messages + } + + if ( bRet ) + { + rReq.SetReturnValue(SfxInt16Item(nSlot, 1)); // 1 = success, 0 = fail + rReq.Done(); + } + else + // if format is not available -> fallback to request without parameters + pItem = nullptr; + } + + if ( !pItem ) + { + if ( ScTransferObj::GetOwnClipboard(xTransferable) ) // own cell data + { + rReq.SetSlot( FID_INS_CELL_CONTENTS ); + ExecuteSlot( rReq, GetInterface() ); + rReq.SetReturnValue(SfxInt16Item(nSlot, 1)); // 1 = success + } + else // draw objects or external data + { + bool bDraw = ( ScDrawTransferObj::GetOwnClipboard(xTransferable) != nullptr ); + + SvxClipboardFormatItem aFormats( SID_CLIPBOARD_FORMAT_ITEMS ); + GetPossibleClipboardFormats( aFormats ); + + sal_uInt16 nFormatCount = aFormats.Count(); + if ( nFormatCount ) + { + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + ScopedVclPtr pDlg(pFact->CreatePasteDialog(pTabViewShell->GetFrameWeld())); + for (sal_uInt16 i=0; iInsert( nFormatId, aName ); + } + + SfxViewFrame& rViewFrame = pTabViewShell->GetViewFrame(); + auto xFrame = rViewFrame.GetFrame().GetFrameInterface(); + const OUString aModuleName(vcl::CommandInfoProvider::GetModuleIdentifier(xFrame)); + auto aProperties = vcl::CommandInfoProvider::GetCommandProperties(".uno:PasteTextImportDialog", aModuleName); + OUString sLabel(vcl::CommandInfoProvider::GetTooltipLabelForCommand(aProperties)); + pDlg->InsertUno(".uno:PasteTextImportDialog", sLabel); + + TransferableDataHelper aDataHelper( + TransferableDataHelper::CreateFromSystemClipboard( pWin ) ); + SotClipboardFormatId nFormat = pDlg->GetFormat( aDataHelper.GetTransferable() ); + if (nFormat != SotClipboardFormatId::NONE) + { + { + weld::WaitObject aWait( GetViewData().GetDialogParent() ); + if ( bDraw && nFormat == SotClipboardFormatId::EMBED_SOURCE ) + pTabViewShell->PasteDraw(); + else + pTabViewShell->PasteFromSystem(nFormat); + } + rReq.SetReturnValue(SfxInt16Item(nSlot, 1)); // 1 = success + rReq.AppendItem( SfxUInt32Item( nSlot, static_cast(nFormat) ) ); + rReq.Done(); + } + else + { + rReq.SetReturnValue(SfxInt16Item(nSlot, 0)); // 0 = fail + rReq.Ignore(); + } + } + else + rReq.SetReturnValue(SfxInt16Item(nSlot, 0)); // 0 = fail + } + } + } + pTabViewShell->CellContentChanged(); // => PasteFromSystem() ??? + break; + + case SID_PASTE_UNFORMATTED: + // differentiate between own cell data and draw objects/external data + // this makes FID_INS_CELL_CONTENTS superfluous + { + weld::WaitObject aWait( GetViewData().GetDialogParent() ); + + // we should differentiate between SotClipboardFormatId::STRING and SotClipboardFormatId::STRING_TSVC, + // and paste the SotClipboardFormatId::STRING_TSVC if it is available. + // Which makes a difference if the clipboard contains cells with embedded line breaks. + + SotClipboardFormatId nFormat = HasClipboardFormat( SotClipboardFormatId::STRING_TSVC) ? + SotClipboardFormatId::STRING_TSVC : SotClipboardFormatId::STRING; + + const bool bRet = pTabViewShell->PasteFromSystem(nFormat, true); // TRUE: no error messages + if ( bRet ) + { + rReq.SetReturnValue(SfxInt16Item(nSlot, 1)); // 1 = success + rReq.Done(); + } + else + { + rReq.SetReturnValue(SfxInt16Item(nSlot, 0)); // 0 = fail + } + + pTabViewShell->CellContentChanged(); // => PasteFromSystem() ??? + } + break; + + // other + + case FID_INS_ROWBRK: + pTabViewShell->InsertPageBreak( false ); + rReq.Done(); + break; + + case FID_INS_COLBRK: + pTabViewShell->InsertPageBreak( true ); + rReq.Done(); + break; + + case FID_DEL_ROWBRK: + pTabViewShell->DeletePageBreak( false ); + rReq.Done(); + break; + + case FID_DEL_COLBRK: + pTabViewShell->DeletePageBreak( true ); + rReq.Done(); + break; + + case SID_DETECTIVE_ADD_PRED: + pTabViewShell->DetectiveAddPred(); + rReq.Done(); + break; + + case SID_DETECTIVE_DEL_PRED: + pTabViewShell->DetectiveDelPred(); + rReq.Done(); + break; + + case SID_DETECTIVE_ADD_SUCC: + pTabViewShell->DetectiveAddSucc(); + rReq.Done(); + break; + + case SID_DETECTIVE_DEL_SUCC: + pTabViewShell->DetectiveDelSucc(); + rReq.Done(); + break; + + case SID_DETECTIVE_ADD_ERR: + pTabViewShell->DetectiveAddError(); + rReq.Done(); + break; + + case SID_DETECTIVE_INVALID: + pTabViewShell->DetectiveMarkInvalid(); + rReq.Done(); + break; + + case SID_DETECTIVE_REFRESH: + pTabViewShell->DetectiveRefresh(); + rReq.Done(); + break; + + case SID_DETECTIVE_MARK_PRED: + pTabViewShell->DetectiveMarkPred(); + break; + case SID_DETECTIVE_MARK_SUCC: + pTabViewShell->DetectiveMarkSucc(); + break; + case SID_INSERT_CURRENT_DATE: + pTabViewShell->InsertCurrentTime( + SvNumFormatType::DATE, ScResId(STR_UNDO_INSERT_CURRENT_DATE)); + break; + case SID_INSERT_CURRENT_TIME: + pTabViewShell->InsertCurrentTime( + SvNumFormatType::TIME, ScResId(STR_UNDO_INSERT_CURRENT_TIME)); + break; + + case SID_SPELL_DIALOG: + { + SfxViewFrame& rViewFrame = pTabViewShell->GetViewFrame(); + if( rReq.GetArgs() ) + rViewFrame.SetChildWindow( SID_SPELL_DIALOG, + static_cast< const SfxBoolItem& >( rReq.GetArgs()-> + Get( SID_SPELL_DIALOG ) ).GetValue() ); + else + rViewFrame.ToggleChildWindow( SID_SPELL_DIALOG ); + + rViewFrame.GetBindings().Invalidate( SID_SPELL_DIALOG ); + rReq.Ignore(); + } + break; + + case SID_HANGUL_HANJA_CONVERSION: + pTabViewShell->DoHangulHanjaConversion(); + break; + + case SID_CHINESE_CONVERSION: + { + //open ChineseTranslationDialog + Reference< XComponentContext > xContext( + ::cppu::defaultBootstrap_InitialComponentContext() ); //@todo get context from calc if that has one + if(xContext.is()) + { + Reference< lang::XMultiComponentFactory > xMCF( xContext->getServiceManager() ); + if(xMCF.is()) + { + Reference< ui::dialogs::XExecutableDialog > xDialog( + xMCF->createInstanceWithContext( + "com.sun.star.linguistic2.ChineseTranslationDialog" + , xContext), + UNO_QUERY); + Reference< lang::XInitialization > xInit( xDialog, UNO_QUERY ); + if( xInit.is() ) + { + // initialize dialog + uno::Sequence aSeq(comphelper::InitAnyPropertySequence( + { + {"ParentWindow", uno::Any(Reference< awt::XWindow >())} + })); + xInit->initialize( aSeq ); + + //execute dialog + sal_Int16 nDialogRet = xDialog->execute(); + if( RET_OK == nDialogRet ) + { + //get some parameters from the dialog + bool bToSimplified = true; + bool bUseVariants = true; + bool bCommonTerms = true; + Reference< beans::XPropertySet > xProp( xDialog, UNO_QUERY ); + if( xProp.is() ) + { + try + { + xProp->getPropertyValue("IsDirectionToSimplified") >>= bToSimplified; + xProp->getPropertyValue("IsUseCharacterVariants") >>= bUseVariants; + xProp->getPropertyValue("IsTranslateCommonTerms") >>= bCommonTerms; + } + catch( Exception& ) + { + } + } + + //execute translation + LanguageType eSourceLang = bToSimplified ? LANGUAGE_CHINESE_TRADITIONAL : LANGUAGE_CHINESE_SIMPLIFIED; + LanguageType eTargetLang = bToSimplified ? LANGUAGE_CHINESE_SIMPLIFIED : LANGUAGE_CHINESE_TRADITIONAL; + sal_Int32 nOptions = bUseVariants ? i18n::TextConversionOption::USE_CHARACTER_VARIANTS : 0; + if( !bCommonTerms ) + nOptions |= i18n::TextConversionOption::CHARACTER_BY_CHARACTER; + + vcl::Font aTargetFont = OutputDevice::GetDefaultFont( + DefaultFontType::CJK_SPREADSHEET, + eTargetLang, GetDefaultFontFlags::OnlyOne ); + ScConversionParam aConvParam( SC_CONVERSION_CHINESE_TRANSL, + eSourceLang, eTargetLang, std::move(aTargetFont), nOptions, false ); + pTabViewShell->DoSheetConversion( aConvParam ); + } + } + Reference< lang::XComponent > xComponent( xDialog, UNO_QUERY ); + if( xComponent.is() ) + xComponent->dispose(); + } + } + } + break; + + case SID_CONVERT_FORMULA_TO_VALUE: + { + pTabViewShell->ConvertFormulaToValue(); + } + break; + case SID_THESAURUS: + pTabViewShell->DoThesaurus(); + break; + + case SID_TOGGLE_REL: + pTabViewShell->DoRefConversion(); + break; + + case SID_DEC_INDENT: + pTabViewShell->ChangeIndent( false ); + break; + case SID_INC_INDENT: + pTabViewShell->ChangeIndent( true ); + break; + + case FID_USE_NAME: + { + CreateNameFlags nFlags = pTabViewShell->GetCreateNameFlags(); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr pDlg(pFact->CreateScNameCreateDlg(pTabViewShell->GetFrameWeld(), nFlags)); + + if( pDlg->Execute() ) + { + pTabViewShell->CreateNames(pDlg->GetFlags()); + rReq.Done(); + } + } + break; + + case SID_CONSOLIDATE: + { + const ScConsolidateItem* pItem; + if ( pReqArgs && (pItem = + pReqArgs->GetItemIfSet( SCITEM_CONSOLIDATEDATA )) ) + { + const ScConsolidateParam& rParam = pItem->GetData(); + + pTabViewShell->Consolidate( rParam ); + GetViewData().GetDocument().SetConsolidateDlgData( std::unique_ptr(new ScConsolidateParam(rParam)) ); + + rReq.Done(); + } +#if HAVE_FEATURE_SCRIPTING + else if (rReq.IsAPI()) + SbxBase::SetError(ERRCODE_BASIC_BAD_PARAMETER); +#endif + } + break; + + case SID_INS_FUNCTION: + { + const SfxBoolItem* pOkItem = static_cast(&pReqArgs->Get( SID_DLG_RETOK )); + + if ( pOkItem->GetValue() ) // OK + { + OUString aFormula; + const SfxStringItem* pSItem = &pReqArgs->Get( SCITEM_STRING ); + const SfxBoolItem* pMatrixItem = static_cast(&pReqArgs->Get( SID_DLG_MATRIX )); + + aFormula += pSItem->GetValue(); + pScMod->ActivateInputWindow( &aFormula, pMatrixItem->GetValue() ); + } + else // CANCEL + { + pScMod->ActivateInputWindow(); + } + rReq.Ignore(); // only SID_ENTER_STRING is recorded + } + break; + + case FID_DEFINE_NAME: + case FID_DEFINE_CURRENT_NAME: + if ( pReqArgs ) + { + const SfxPoolItem* pItem; + OUString aName, aSymbol, aAttrib; + + if( pReqArgs->HasItem( FID_DEFINE_NAME, &pItem ) ) + aName = static_cast(pItem)->GetValue(); + + if( pReqArgs->HasItem( FN_PARAM_1, &pItem ) ) + aSymbol = static_cast(pItem)->GetValue(); + + if( pReqArgs->HasItem( FN_PARAM_2, &pItem ) ) + aAttrib = static_cast(pItem)->GetValue(); + + if ( !aName.isEmpty() && !aSymbol.isEmpty() ) + { + if (pTabViewShell->InsertName( aName, aSymbol, aAttrib )) + rReq.Done(); +#if HAVE_FEATURE_SCRIPTING + else + SbxBase::SetError( ERRCODE_BASIC_BAD_PARAMETER ); // Basic-error +#endif + } + } + else + { + sal_uInt16 nId = ScNameDlgWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + break; + case FID_ADD_NAME: + { + sal_uInt16 nId = ScNameDefDlgWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + break; + + case SID_OPENDLG_CONDFRMT: + case SID_OPENDLG_CURRENTCONDFRMT: + case SID_OPENDLG_COLORSCALE: + case SID_OPENDLG_DATABAR: + case SID_OPENDLG_ICONSET: + case SID_OPENDLG_CONDDATE: + { + sal_uInt32 nIndex = sal_uInt32(-1); + bool bManaged = false; + + // Get the pool item stored by Conditional Format Manager Dialog. + auto itemsRange = pTabViewShell->GetPool().GetItemSurrogates(SCITEM_CONDFORMATDLGDATA); + if (itemsRange.begin() != itemsRange.end()) + { + const ScCondFormatDlgItem* pDlgItem = static_cast(*itemsRange.begin()); + nIndex = pDlgItem->GetIndex(); + bManaged = true; + } + + // Check if the Conditional Manager Dialog is editing or adding + // conditional format item. + if ( bManaged ) + { + sal_uInt16 nId = ScCondFormatDlgWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + break; + } + + ScRangeList aRangeList; + ScViewData& rData = GetViewData(); + rData.GetMarkData().FillRangeListWithMarks(&aRangeList, false); + + ScDocument& rDoc = GetViewData().GetDocument(); + if(rDoc.IsTabProtected(rData.GetTabNo())) + { + pTabViewShell->ErrorMessage( STR_ERR_CONDFORMAT_PROTECTED ); + break; + } + + ScAddress aPos(rData.GetCurX(), rData.GetCurY(), rData.GetTabNo()); + if(aRangeList.empty()) + { + aRangeList.push_back(ScRange(aPos)); + } + + // try to find an existing conditional format + const ScConditionalFormat* pCondFormat = nullptr; + const ScPatternAttr* pPattern = rDoc.GetPattern(aPos.Col(), aPos.Row(), aPos.Tab()); + ScConditionalFormatList* pList = rDoc.GetCondFormList(aPos.Tab()); + const ScCondFormatIndexes& rCondFormats = pPattern->GetItem(ATTR_CONDITIONAL).GetCondFormatData(); + bool bContainsCondFormat = !rCondFormats.empty(); + bool bCondFormatDlg = false; + bool bContainsExistingCondFormat = false; + if(bContainsCondFormat) + { + for (const auto& rCondFormat : rCondFormats) + { + // check if at least one existing conditional format has the same range + pCondFormat = pList->GetFormat(rCondFormat); + if(!pCondFormat) + continue; + + bContainsExistingCondFormat = true; + const ScRangeList& rCondFormatRange = pCondFormat->GetRange(); + if(rCondFormatRange == aRangeList) + { + // found a matching range, edit this conditional format + bCondFormatDlg = true; + nIndex = pCondFormat->GetKey(); + break; + } + } + } + + // do we have a parameter with the conditional formatting type? + const SfxInt16Item* pParam = rReq.GetArg(FN_PARAM_1); + if (pParam) + { + auto pFormat = std::make_unique(0, &rDoc); + pFormat->SetRange(aRangeList); + + if (nSlot == SID_OPENDLG_ICONSET) + { + ScIconSetType eIconSetType = limit_cast(pParam->GetValue(), IconSet_3Arrows, IconSet_5Boxes); + const int nSteps = ScIconSetFormat::getIconSetElements(eIconSetType); + + ScIconSetFormat* pEntry = new ScIconSetFormat(&rDoc); + ScIconSetFormatData* pIconSetFormatData = new ScIconSetFormatData(eIconSetType); + + pIconSetFormatData->m_Entries.emplace_back(new ScColorScaleEntry(0, COL_RED, COLORSCALE_PERCENT)); + pIconSetFormatData->m_Entries.emplace_back(new ScColorScaleEntry(round(100. / nSteps), COL_BROWN, COLORSCALE_PERCENT)); + pIconSetFormatData->m_Entries.emplace_back(new ScColorScaleEntry(round(200. / nSteps), COL_YELLOW, COLORSCALE_PERCENT)); + if (nSteps > 3) + pIconSetFormatData->m_Entries.emplace_back(new ScColorScaleEntry(round(300. / nSteps), COL_WHITE, COLORSCALE_PERCENT)); + if (nSteps > 4) + pIconSetFormatData->m_Entries.emplace_back(new ScColorScaleEntry(round(400. / nSteps), COL_GREEN, COLORSCALE_PERCENT)); + + pEntry->SetIconSetData(pIconSetFormatData); + pFormat->AddEntry(pEntry); + } + else if (nSlot == SID_OPENDLG_COLORSCALE) + { + typedef std::tuple ScaleEntry; + static std::vector> aScaleThemes = + { + { + { 0, Color(0xF8696B), COLORSCALE_MIN }, + { 0, Color(0x63BE7B), COLORSCALE_MAX }, + { 50, Color(0xFFEB84), COLORSCALE_PERCENTILE } + }, + { + { 0, Color(0x63BE7B), COLORSCALE_MIN }, + { 0, Color(0xF8696B), COLORSCALE_MAX }, + { 50, Color(0xFFEB84), COLORSCALE_PERCENTILE } + }, + { + { 0, Color(0xF8696B), COLORSCALE_MIN }, + { 0, Color(0x63BE7B), COLORSCALE_MAX }, + { 50, Color(0xFCFCFF), COLORSCALE_PERCENTILE } + }, + { + { 0, Color(0x63BE7B), COLORSCALE_MIN }, + { 0, Color(0xF8696B), COLORSCALE_MAX }, + { 50, Color(0xFCFCFF), COLORSCALE_PERCENTILE } + }, + { + { 0, Color(0xF8696B), COLORSCALE_MIN }, + { 0, Color(0x5A8AC6), COLORSCALE_MAX }, + { 50, Color(0xFCFCFF), COLORSCALE_PERCENTILE } + }, + { + { 0, Color(0x5A8AC6), COLORSCALE_MIN }, + { 0, Color(0xF8696B), COLORSCALE_MAX }, + { 50, Color(0xFCFCFF), COLORSCALE_PERCENTILE } + }, + { + { 0, Color(0xF8696B), COLORSCALE_MIN }, + { 0, Color(0xFCFCFF), COLORSCALE_MAX } + }, + { + { 0, Color(0xFCFCFF), COLORSCALE_MIN }, + { 0, Color(0xF8696B), COLORSCALE_MAX } + }, + { + { 0, Color(0x63BE7B), COLORSCALE_MIN }, + { 0, Color(0xFCFCFF), COLORSCALE_MAX } + }, + { + { 0, Color(0xFCFCFF), COLORSCALE_MIN }, + { 0, Color(0x63BE7B), COLORSCALE_MAX } + }, + { + { 0, Color(0x63BE7B), COLORSCALE_MIN }, + { 0, Color(0xFFEF9C), COLORSCALE_MAX } + }, + { + { 0, Color(0xFFEF9C), COLORSCALE_MIN }, + { 0, Color(0x63BE7B), COLORSCALE_MAX } + } + }; + + sal_uInt16 nTheme = pParam->GetValue(); + if (nTheme < aScaleThemes.size()) + { + ScColorScaleFormat* pFormatEntry = new ScColorScaleFormat(&rDoc); + + auto& aTheme = aScaleThemes[nTheme]; + + ScColorScaleEntry* pMin = new ScColorScaleEntry(std::get<0>(aTheme[0]), std::get<1>(aTheme[0]), std::get<2>(aTheme[0])); + ScColorScaleEntry* pMax = new ScColorScaleEntry(std::get<0>(aTheme[1]), std::get<1>(aTheme[1]), std::get<2>(aTheme[1])); + + pFormatEntry->AddEntry(pMin); + + // COLORSCALE_PERCENTILE has to be in the middle + if (aTheme.size() > 2) + { + ScColorScaleEntry* pPer = new ScColorScaleEntry(std::get<0>(aTheme[2]), std::get<1>(aTheme[2]), std::get<2>(aTheme[2])); + pFormatEntry->AddEntry(pPer); + } + + pFormatEntry->AddEntry(pMax); + + pFormat->AddEntry(pFormatEntry); + } + + } + else if (nSlot == SID_OPENDLG_DATABAR) + { + typedef std::tuple DatabarEntry; + static std::vector aDatabarThemes = + { + { Color(0x638EC6), true }, + { Color(0x63C384), true }, + { Color(0xFF555A), true }, + { Color(0xFFB628), true }, + { Color(0x008AEF), true }, + { Color(0xD6007B), true }, + { Color(0x638EC6), false }, + { Color(0x63C384), false }, + { Color(0xFF555A), false }, + { Color(0xFFB628), false }, + { Color(0x008AEF), false }, + { Color(0xD6007B), false } + }; + + sal_uInt16 nTheme = pParam->GetValue(); + if (nTheme < aDatabarThemes.size()) + { + ScDataBarFormat* pFormatEntry = new ScDataBarFormat(&rDoc); + + auto& aTheme = aDatabarThemes[nTheme]; + + ScDataBarFormatData* pData = new ScDataBarFormatData(); + pData->maPositiveColor = std::get<0>(aTheme); + pData->mbGradient = std::get<1>(aTheme); + pData->mxNegativeColor = Color(0xFF0000); + pData->mpLowerLimit.reset(new ScColorScaleEntry(0, 0, COLORSCALE_AUTO)); + pData->mpUpperLimit.reset(new ScColorScaleEntry(0, 0, COLORSCALE_AUTO)); + + pFormatEntry->SetDataBarData(pData); + + pFormat->AddEntry(pFormatEntry); + } + } + + // use the new conditional formatting + GetViewData().GetDocShell()->GetDocFunc().ReplaceConditionalFormat(nIndex, std::move(pFormat), aPos.Tab(), aRangeList); + + break; + } + + // if not found a conditional format ask whether we should edit one of the existing + // or should create a new overlapping conditional format + if(bContainsCondFormat && !bCondFormatDlg && bContainsExistingCondFormat) + { + std::unique_ptr xQueryBox(Application::CreateMessageDialog(pTabViewShell->GetFrameWeld(), + VclMessageType::Question, VclButtonsType::YesNo, + ScResId(STR_EDIT_EXISTING_COND_FORMATS))); + xQueryBox->set_default_response(RET_YES); + bool bEditExisting = xQueryBox->run() == RET_YES; + if (bEditExisting) + { + // differentiate between ranges where one conditional format is defined + // and several formats are defined + // if we have only one => open the cond format dlg to edit it + // otherwise open the manage cond format dlg + if (rCondFormats.size() == 1) + { + pCondFormat = pList->GetFormat(rCondFormats[0]); + assert(pCondFormat); + nIndex = pCondFormat->GetKey(); + bCondFormatDlg = true; + } + else + { + // Queue message to open Conditional Format Manager Dialog. + GetViewData().GetDispatcher().Execute( SID_OPENDLG_CONDFRMT_MANAGER, SfxCallMode::ASYNCHRON ); + break; + } + } + else + { + // define an overlapping conditional format + pCondFormat = pList->GetFormat(rCondFormats[0]); + assert(pCondFormat); + bCondFormatDlg = true; + } + } + + condformat::dialog::ScCondFormatDialogType eType = condformat::dialog::NONE; + switch(nSlot) + { + case SID_OPENDLG_CONDFRMT: + case SID_OPENDLG_CURRENTCONDFRMT: + eType = condformat::dialog::CONDITION; + break; + case SID_OPENDLG_COLORSCALE: + eType = condformat::dialog::COLORSCALE; + break; + case SID_OPENDLG_DATABAR: + eType = condformat::dialog::DATABAR; + break; + case SID_OPENDLG_ICONSET: + eType = condformat::dialog::ICONSET; + break; + case SID_OPENDLG_CONDDATE: + eType = condformat::dialog::DATE; + break; + default: + assert(false); + break; + } + + + if(bCondFormatDlg || !bContainsCondFormat) + { + // Put the xml string parameter to initialize the + // Conditional Format Dialog. + ScCondFormatDlgItem aDlgItem(nullptr, nIndex, false); + aDlgItem.SetDialogType(eType); + pTabViewShell->GetPool().DirectPutItemInPool(aDlgItem); + + sal_uInt16 nId = ScCondFormatDlgWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + } + break; + + case SID_DEFINE_COLROWNAMERANGES: + { + + sal_uInt16 nId = ScColRowNameRangesDlgWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + + } + break; + + case SID_UPDATECHART: + { + bool bAll = false; + + if( pReqArgs ) + { + const SfxPoolItem* pItem; + + if( pReqArgs->HasItem( SID_UPDATECHART, &pItem ) ) + bAll = static_cast(pItem)->GetValue(); + } + + pTabViewShell->UpdateCharts( bAll ); + + if( ! rReq.IsAPI() ) + { + rReq.AppendItem( SfxBoolItem( SID_UPDATECHART, bAll ) ); + rReq.Done(); + } + } + break; + + case SID_TABOP: + if (pReqArgs) + { + const ScTabOpItem& rItem = + static_cast( + pReqArgs->Get( SID_TABOP )); + + pTabViewShell->TabOp( rItem.GetData() ); + + rReq.Done( *pReqArgs ); + } + break; + + case SID_SOLVE: + if (pReqArgs) + { + const ScSolveItem& rItem = + pReqArgs->Get( SCITEM_SOLVEDATA ); + + pTabViewShell->Solve( rItem.GetData() ); + + rReq.Done( *pReqArgs ); + } + break; + + case FID_INSERT_NAME: + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr pDlg(pFact->CreateScNamePasteDlg(pTabViewShell->GetFrameWeld(), GetViewData().GetDocShell())); + switch( pDlg->Execute() ) + { + case BTN_PASTE_LIST: + pTabViewShell->InsertNameList(); + break; + case BTN_PASTE_NAME: + { + ScInputHandler* pHdl = pScMod->GetInputHdl( pTabViewShell ); + if (pHdl) + { + // "=" in KeyEvent, switches to input-mode + (void)pScMod->InputKeyEvent( KeyEvent('=', vcl::KeyCode()) ); + + std::vector aNames = pDlg->GetSelectedNames(); + if (!aNames.empty()) + { + OUStringBuffer aBuffer; + for (const auto& rName : aNames) + { + aBuffer.append(rName + " "); + } + pHdl->InsertFunction( aBuffer.makeStringAndClear(), false ); // without "()" + } + } + } + break; + } + } + break; + + case SID_RANGE_NOTETEXT: + if (pReqArgs) + { + const SfxStringItem& rTextItem = pReqArgs->Get( SID_RANGE_NOTETEXT ); + + // always cursor position + ScAddress aPos( GetViewData().GetCurX(), GetViewData().GetCurY(), GetViewData().GetTabNo() ); + pTabViewShell->SetNoteText( aPos, rTextItem.GetValue() ); + rReq.Done(); + } + break; + + case SID_INSERT_POSTIT: + case SID_EDIT_POSTIT: + { + const SvxPostItTextItem* pTextItem; + if ( pReqArgs && (pTextItem = pReqArgs->GetItemIfSet( SID_ATTR_POSTIT_TEXT )) ) + { + OUString aCellId; + // SID_ATTR_POSTIT_ID only argument for SID_EDIT_POSTIT + if (const SvxPostItIdItem* pCellId = pReqArgs->GetItemIfSet( SID_ATTR_POSTIT_ID )) + aCellId = pCellId->GetValue(); + + const SvxPostItAuthorItem* pAuthorItem = pReqArgs->GetItem( SID_ATTR_POSTIT_AUTHOR ); + const SvxPostItDateItem* pDateItem = pReqArgs->GetItem( SID_ATTR_POSTIT_DATE ); + + if (!aCellId.isEmpty()) + { + SetTabNoAndCursor( GetViewData(), aCellId ); + } + + ScAddress aPos( GetViewData().GetCurX(), GetViewData().GetCurY(), GetViewData().GetTabNo() ); + pTabViewShell->ReplaceNote( aPos, pTextItem->GetValue(), + pAuthorItem ? &pAuthorItem->GetValue() : nullptr, + pDateItem ? &pDateItem->GetValue() : nullptr ); + } + else if (!comphelper::LibreOfficeKit::isActive() || comphelper::LibreOfficeKit::isTiledAnnotations()) + { + pTabViewShell->EditNote(); // note object to edit + } + rReq.Done(); + } + break; + + case FID_NOTE_VISIBLE: + { + ScDocument& rDoc = GetViewData().GetDocument(); + ScAddress aPos( GetViewData().GetCurX(), GetViewData().GetCurY(), GetViewData().GetTabNo() ); + if( ScPostIt* pNote = rDoc.GetNote(aPos) ) + { + bool bShow; + const SfxPoolItem* pItem; + if ( pReqArgs && (pReqArgs->GetItemState( FID_NOTE_VISIBLE, true, &pItem ) == SfxItemState::SET) ) + bShow = static_cast(pItem)->GetValue(); + else + bShow = !pNote->IsCaptionShown(); + + pTabViewShell->ShowNote( bShow ); + + if (!pReqArgs) + rReq.AppendItem( SfxBoolItem( FID_NOTE_VISIBLE, bShow ) ); + + rReq.Done(); + rBindings.Invalidate( FID_NOTE_VISIBLE ); + } + else + rReq.Ignore(); + } + break; + + case FID_HIDE_NOTE: + case FID_SHOW_NOTE: + { + bool bShowNote = nSlot == FID_SHOW_NOTE; + ScViewData& rData = GetViewData(); + ScDocument& rDoc = rData.GetDocument(); + ScMarkData& rMark = rData.GetMarkData(); + + if (!rMark.IsMarked() && !rMark.IsMultiMarked()) + { + // Check current cell + ScAddress aPos( rData.GetCurX(), rData.GetCurY(), rData.GetTabNo() ); + if( rDoc.GetNote(aPos) ) + { + rData.GetDocShell()->GetDocFunc().ShowNote( aPos, bShowNote ); + } + } + else + { + // Check selection range + bool bDone = false; + ScRangeListRef aRangesRef; + rData.GetMultiArea(aRangesRef); + const ScRangeList aRanges = *aRangesRef; + + OUString aUndo = ScResId( bShowNote ? STR_UNDO_SHOWNOTE : STR_UNDO_HIDENOTE ); + rData.GetDocShell()->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, rData.GetViewShell()->GetViewShellId() ); + + for (auto const& rTab : rMark.GetSelectedTabs()) + { + // get notes + std::vector aNotes; + rDoc.GetAllNoteEntries(rTab, aNotes); + + for (const sc::NoteEntry& rNote : aNotes) + { + // check if note is in our selection range + const ScAddress& rAdr = rNote.maPos; + const ScRange* rRange = aRanges.Find(rAdr); + if (! rRange) + continue; + + // check if cell is editable + const SCTAB nRangeTab = rRange->aStart.Tab(); + if (rDoc.IsBlockEditable( nRangeTab, rAdr.Col(), rAdr.Row(), rAdr.Col(), rAdr.Row() )) + { + rData.GetDocShell()->GetDocFunc().ShowNote( rAdr, bShowNote ); + bDone = true; + } + } + } + + rData.GetDocShell()->GetUndoManager()->LeaveListAction(); + + if ( bDone ) + { + rReq.Done(); + rBindings.Invalidate( nSlot ); + } + else + rReq.Ignore(); + } + + } + break; + + case FID_SHOW_ALL_NOTES: + case FID_HIDE_ALL_NOTES: + { + bool bShowNote = nSlot == FID_SHOW_ALL_NOTES; + ScViewData& rData = GetViewData(); + ScMarkData& rMark = rData.GetMarkData(); + ScDocument& rDoc = rData.GetDocument(); + std::vector aNotes; + + OUString aUndo = ScResId( bShowNote ? STR_UNDO_SHOWALLNOTES : STR_UNDO_HIDEALLNOTES ); + rData.GetDocShell()->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, rData.GetViewShell()->GetViewShellId() ); + + for (auto const& rTab : rMark.GetSelectedTabs()) + { + rDoc.GetAllNoteEntries(rTab, aNotes); + } + + for (const sc::NoteEntry& rNote : aNotes) + { + const ScAddress& rAdr = rNote.maPos; + rData.GetDocShell()->GetDocFunc().ShowNote( rAdr, bShowNote ); + } + + rData.GetDocShell()->GetUndoManager()->LeaveListAction(); + } + break; + + case SID_TOGGLE_NOTES: + { + ScViewData& rData = GetViewData(); + ScMarkData& rMark = rData.GetMarkData(); + ScDocument& rDoc = rData.GetDocument(); + ScRangeList aRanges; + std::vector aNotes; + + for (auto const& rTab : rMark.GetSelectedTabs()) + aRanges.push_back(ScRange(0,0,rTab,rDoc.MaxCol(),rDoc.MaxRow(),rTab)); + + CommentCaptionState eState = rDoc.GetAllNoteCaptionsState( aRanges ); + rDoc.GetNotesInRange(aRanges, aNotes); + bool bShowNote = (eState == ALLHIDDEN || eState == MIXED); + + OUString aUndo = ScResId( bShowNote ? STR_UNDO_SHOWALLNOTES : STR_UNDO_HIDEALLNOTES ); + rData.GetDocShell()->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, rData.GetViewShell()->GetViewShellId() ); + + for(const auto& rNote : aNotes) + { + const ScAddress& rAdr = rNote.maPos; + rData.GetDocShell()->GetDocFunc().ShowNote( rAdr, bShowNote ); + } + + rData.GetDocShell()->GetUndoManager()->LeaveListAction(); + + if (!pReqArgs) + rReq.AppendItem( SfxBoolItem( SID_TOGGLE_NOTES, bShowNote ) ); + + rReq.Done(); + rBindings.Invalidate( SID_TOGGLE_NOTES ); + } + break; + + case SID_DELETE_NOTE: + { + const SvxPostItIdItem* pIdItem; + // If Id is mentioned, select the appropriate cell first + if ( pReqArgs && (pIdItem = pReqArgs->GetItemIfSet( SID_ATTR_POSTIT_ID )) ) + { + const OUString& aCellId = pIdItem->GetValue(); + if (!aCellId.isEmpty()) + { + SetTabNoAndCursor( GetViewData(), aCellId ); + } + } + + pTabViewShell->DeleteContents( InsertDeleteFlags::NOTE ); // delete all notes in selection + rReq.Done(); + } + break; + + case FID_DELETE_ALL_NOTES: + { + ScViewData& rData = GetViewData(); + ScMarkData& rMark = rData.GetMarkData(); + ScDocument& rDoc = rData.GetDocument(); + ScMarkData aNewMark(rDoc.GetSheetLimits()); + ScRangeList aRangeList; + + for (auto const& rTab : rMark.GetSelectedTabs()) + { + aRangeList.push_back(ScRange(0,0,rTab,rDoc.MaxCol(),rDoc.MaxRow(),rTab)); + } + + aNewMark.MarkFromRangeList( aRangeList, true ); + rData.GetDocShell()->GetDocFunc().DeleteContents(aNewMark, InsertDeleteFlags::NOTE, true, false ); + } + break; + + case SID_CHARMAP: + if( pReqArgs != nullptr ) + { + OUString aChars, aFontName; + const SfxItemSet *pArgs = rReq.GetArgs(); + const SfxPoolItem* pItem = nullptr; + if ( pArgs ) + pArgs->GetItemState(SID_CHARMAP, false, &pItem); + if ( pItem ) + { + const SfxStringItem* pStringItem = dynamic_cast( pItem ); + if ( pStringItem ) + aChars = pStringItem->GetValue(); + const SfxStringItem* pFontItem = + pArgs->GetItemIfSet( SID_ATTR_SPECIALCHAR, false); + if ( pFontItem ) + aFontName = pFontItem->GetValue(); + } + + if ( !aChars.isEmpty() ) + { + vcl::Font aFont; + pTabViewShell->GetSelectionPattern()->fillFontOnly(aFont, nullptr, nullptr, nullptr, + pTabViewShell->GetSelectionScriptType() ); + if ( !aFontName.isEmpty() ) + aFont = vcl::Font( aFontName, Size(1,1) ); + pTabViewShell->InsertSpecialChar( aChars, aFont ); + if( ! rReq.IsAPI() ) + rReq.Done(); + } + } + else + { + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + + // font color doesn't matter here + vcl::Font aCurFont; + pTabViewShell->GetSelectionPattern()->fillFontOnly(aCurFont, nullptr, nullptr, nullptr, + pTabViewShell->GetSelectionScriptType()); + + SfxAllItemSet aSet( GetPool() ); + aSet.Put( SfxBoolItem( FN_PARAM_1, false ) ); + aSet.Put( SvxFontItem( aCurFont.GetFamilyType(), aCurFont.GetFamilyName(), aCurFont.GetStyleName(), aCurFont.GetPitch(), aCurFont.GetCharSet(), GetPool().GetWhich(SID_ATTR_CHAR_FONT) ) ); + SfxViewFrame& rViewFrame = pTabViewShell->GetViewFrame(); + auto xFrame = rViewFrame.GetFrame().GetFrameInterface(); + ScopedVclPtr pDlg(pFact->CreateCharMapDialog(pTabViewShell->GetFrameWeld(), aSet, xFrame)); + pDlg->Execute(); + } + break; + + case SID_SELECT_SCENARIO: + { + // Testing + + if ( pReqArgs ) + { + const SfxStringItem& rItem = pReqArgs->Get(SID_SELECT_SCENARIO); + pTabViewShell->UseScenario(rItem.GetValue()); + //! why should the return value be valid?!?! + rReq.SetReturnValue(SfxStringItem(SID_SELECT_SCENARIO, rItem.GetValue())); + rReq.Done(); + } + } + break; + + case SID_HYPERLINK_SETLINK: + if( pReqArgs ) + { + const SfxPoolItem* pItem; + if( pReqArgs->HasItem( SID_HYPERLINK_SETLINK, &pItem ) ) + { + const SvxHyperlinkItem* pHyper = static_cast(pItem); + const OUString& rName = pHyper->GetName(); + const OUString& rURL = pHyper->GetURL(); + const OUString& rTarget = pHyper->GetTargetFrame(); + sal_uInt16 nType = static_cast(pHyper->GetInsertMode()); + + pTabViewShell->InsertURL( rName, rURL, rTarget, nType ); + rReq.Done(); + } + else + rReq.Ignore(); + } + break; + + case SID_OPENDLG_CONDFRMT_MANAGER: + case SID_OPENDLG_CURRENTCONDFRMT_MANAGER: + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScViewData& rData = GetViewData(); + ScDocument& rDoc = rData.GetDocument(); + + if (rDoc.IsTabProtected(rData.GetTabNo())) + { + pTabViewShell->ErrorMessage( STR_ERR_CONDFORMAT_PROTECTED ); + break; + } + + ScAddress aPos(rData.GetCurX(), rData.GetCurY(), rData.GetTabNo()); + + ScConditionalFormatList* pList = nullptr; + + const ScCondFormatDlgItem* pDlgItem = nullptr; + auto itemsRange = pTabViewShell->GetPool().GetItemSurrogates(SCITEM_CONDFORMATDLGDATA); + if (itemsRange.begin() != itemsRange.end()) + { + pDlgItem= static_cast(*itemsRange.begin()); + pList = const_cast(pDlgItem)->GetConditionalFormatList(); + } + + if (!pList) + pList = rDoc.GetCondFormList( aPos.Tab() ); + + VclPtr pDlg(pFact->CreateScCondFormatMgrDlg( + pTabViewShell->GetFrameWeld(), rDoc, pList)); + + if (pDlgItem) + pDlg->SetModified(); + + pDlg->StartExecuteAsync([this, pDlg, &rData, pTabViewShell, pDlgItem, aPos](sal_Int32 nRet){ + std::unique_ptr pCondFormatList = pDlg->GetConditionalFormatList(); + if(nRet == RET_OK && pDlg->CondFormatsChanged()) + { + rData.GetDocShell()->GetDocFunc().SetConditionalFormatList(pCondFormatList.release(), aPos.Tab()); + } + else if(nRet == DLG_RET_ADD) + { + // Put the xml string parameter to initialize the + // Conditional Format Dialog. ( add new ) + pTabViewShell->GetPool().DirectPutItemInPool(ScCondFormatDlgItem( + std::shared_ptr(pCondFormatList.release()), -1, true)); + // Queue message to open Conditional Format Dialog + GetViewData().GetDispatcher().Execute( SID_OPENDLG_CONDFRMT, SfxCallMode::ASYNCHRON ); + } + else if (nRet == DLG_RET_EDIT) + { + ScConditionalFormat* pFormat = pDlg->GetCondFormatSelected(); + sal_Int32 nIndex = pFormat ? pFormat->GetKey() : -1; + // Put the xml string parameter to initialize the + // Conditional Format Dialog. ( edit selected conditional format ) + pTabViewShell->GetPool().DirectPutItemInPool(ScCondFormatDlgItem( + std::shared_ptr(pCondFormatList.release()), nIndex, true)); + + // Queue message to open Conditional Format Dialog + GetViewData().GetDispatcher().Execute( SID_OPENDLG_CONDFRMT, SfxCallMode::ASYNCHRON ); + } + else + pCondFormatList.reset(); + + if (pDlgItem) + pTabViewShell->GetPool().DirectRemoveItemFromPool(*pDlgItem); + + pDlg->disposeOnce(); + }); + } + break; + + case SID_EXTERNAL_SOURCE: + { + const SfxStringItem* pFile = rReq.GetArg(SID_FILE_NAME); + const SfxStringItem* pSource = rReq.GetArg(FN_PARAM_1); + if ( pFile && pSource ) + { + OUString aFile; + OUString aFilter; + OUString aOptions; + OUString aSource; + sal_Int32 nRefreshDelaySeconds=0; + + aFile = pFile->GetValue(); + aSource = pSource->GetValue(); + const SfxStringItem* pFilter = rReq.GetArg(SID_FILTER_NAME); + if ( pFilter ) + aFilter = pFilter->GetValue(); + const SfxStringItem* pOptions = rReq.GetArg(SID_FILE_FILTEROPTIONS); + if ( pOptions ) + aOptions = pOptions->GetValue(); + const SfxUInt32Item* pRefresh = rReq.GetArg(FN_PARAM_2); + if ( pRefresh ) + nRefreshDelaySeconds = pRefresh->GetValue(); + + ExecuteExternalSource( aFile, aFilter, aOptions, aSource, nRefreshDelaySeconds, rReq ); + } + else + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + pImpl->m_pLinkedDlg.disposeAndClear(); + pImpl->m_pLinkedDlg = + pFact->CreateScLinkedAreaDlg(pTabViewShell->GetFrameWeld()); + delete pImpl->m_pRequest; + pImpl->m_pRequest = new SfxRequest( rReq ); + OUString sFile, sFilter, sOptions, sSource; + sal_Int32 nRefreshDelaySeconds = 0; + if (pImpl->m_pLinkedDlg->Execute() == RET_OK) + { + sFile = pImpl->m_pLinkedDlg->GetURL(); + sFilter = pImpl->m_pLinkedDlg->GetFilter(); + sOptions = pImpl->m_pLinkedDlg->GetOptions(); + sSource = pImpl->m_pLinkedDlg->GetSource(); + nRefreshDelaySeconds = pImpl->m_pLinkedDlg->GetRefreshDelaySeconds(); + if ( !sFile.isEmpty() ) + pImpl->m_pRequest->AppendItem( SfxStringItem( SID_FILE_NAME, sFile ) ); + if ( !sFilter.isEmpty() ) + pImpl->m_pRequest->AppendItem( SfxStringItem( SID_FILTER_NAME, sFilter ) ); + if ( !sOptions.isEmpty() ) + pImpl->m_pRequest->AppendItem( SfxStringItem( SID_FILE_FILTEROPTIONS, sOptions ) ); + if ( !sSource.isEmpty() ) + pImpl->m_pRequest->AppendItem( SfxStringItem( FN_PARAM_1, sSource ) ); + if ( nRefreshDelaySeconds ) + pImpl->m_pRequest->AppendItem( SfxUInt32Item( FN_PARAM_2, nRefreshDelaySeconds ) ); + } + + ExecuteExternalSource( sFile, sFilter, sOptions, sSource, nRefreshDelaySeconds, *(pImpl->m_pRequest) ); + } + } + break; + + case SID_AUTO_SUM: + { + const SfxItemSet *pArgs = rReq.GetArgs(); + const OUString sFunction = pArgs ? + static_cast( pArgs->Get( SID_AUTO_SUM ) ).GetValue() + : ""; + + OpCode eFunction = ocSum; + if (sFunction == "average") + eFunction = ocAverage; + else if (sFunction == "count") + eFunction = ocCount; + else if (sFunction == "min") + eFunction = ocMin; + if (sFunction == "max") + eFunction = ocMax; + + bool bSubTotal = false; + bool bRangeFinder = false; + const OUString aFormula = pTabViewShell->DoAutoSum( bRangeFinder, bSubTotal , eFunction ); + if ( !aFormula.isEmpty() ) + { + const sal_Int32 nPar = aFormula.indexOf( '(' ); + const sal_Int32 nLen = aFormula.getLength(); + ScInputHandler* pHdl = pScMod->GetInputHdl( pTabViewShell ); + + if ( pHdl && nPar != -1 ) + { + if ( !pScMod->IsEditMode() ) + { + pScMod->SetInputMode( SC_INPUT_TABLE ); + } + + EditView *pEditView=pHdl->GetActiveView(); + if ( pEditView ) + { + ESelection aTextSel = pEditView->GetSelection(); + aTextSel.nStartPos = 0; + aTextSel.nEndPos = EE_TEXTPOS_ALL; + pHdl->DataChanging(); + pEditView->SetSelection(aTextSel); + pEditView->InsertText(aFormula); + pEditView->SetSelection( bRangeFinder ? ESelection( 0, nPar + ( bSubTotal ? 3 : 1 ), 0, nLen - 1 ) : ESelection( 0, nLen - 1, 0, nLen - 1 ) ); + pHdl->DataChanged(); + + if ( bRangeFinder ) + { + pHdl->InitRangeFinder( aFormula ); + } + } + } + } + } + break; + + case SID_SELECT_UNPROTECTED_CELLS: + { + ScViewData& rData = GetViewData(); + SCTAB aTab = rData.GetTabNo(); + ScMarkData& rMark = rData.GetMarkData(); + ScDocument& rDoc = rData.GetDocument(); + ScRangeList rRangeList; + + rDoc.GetUnprotectedCells(rRangeList, aTab); + rMark.MarkFromRangeList(rRangeList, true); + pTabViewShell->SetMarkData(rMark); + } + break; + + case SID_SELECT_VISIBLE_ROWS: + { + ScViewData& rData = GetViewData(); + ScMarkData& rMark = rData.GetMarkData(); + ScDocument& rDoc = rData.GetDocument(); + + rMark.MarkToMulti(); + + const ScRange& aMultiArea = rMark.GetMultiMarkArea(); + SCCOL nStartCol = aMultiArea.aStart.Col(); + SCROW nStartRow = aMultiArea.aStart.Row(); + SCCOL nEndCol = aMultiArea.aEnd.Col(); + SCROW nEndRow = aMultiArea.aEnd.Row(); + + bool bChanged = false; + for (const SCTAB& nTab : rMark) + { + for (SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow) + { + SCROW nLastRow = nRow; + if (rDoc.RowHidden(nRow, nTab, nullptr, &nLastRow)) + { + rMark.SetMultiMarkArea( + ScRange(nStartCol, nRow, nTab, nEndCol, nLastRow, nTab), false); + bChanged = true; + nRow = nLastRow; + } + } + } + + if (bChanged && !rMark.HasAnyMultiMarks()) + rMark.ResetMark(); + + rMark.MarkToSimple(); + + pTabViewShell->SelectionChanged(); + } + break; + + case SID_SELECT_VISIBLE_COLUMNS: + { + ScViewData& rData = GetViewData(); + ScMarkData& rMark = rData.GetMarkData(); + ScDocument& rDoc = rData.GetDocument(); + + rMark.MarkToMulti(); + + const ScRange& aMultiArea = rMark.GetMultiMarkArea(); + SCCOL nStartCol = aMultiArea.aStart.Col(); + SCROW nStartRow = aMultiArea.aStart.Row(); + SCCOL nEndCol = aMultiArea.aEnd.Col(); + SCROW nEndRow = aMultiArea.aEnd.Row(); + + bool bChanged = false; + for (const SCTAB& nTab : rMark) + { + for (SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol) + { + SCCOL nLastCol = nCol; + if (rDoc.ColHidden(nCol, nTab, nullptr, &nLastCol)) + { + rMark.SetMultiMarkArea( + ScRange(nCol, nStartRow, nTab, nLastCol, nEndRow, nTab), false); + bChanged = true; + nCol = nLastCol; + } + } + } + + if (bChanged && !rMark.HasAnyMultiMarks()) + rMark.ResetMark(); + + rMark.MarkToSimple(); + + pTabViewShell->SelectionChanged(); + } + break; + + case SID_CURRENT_FORMULA_RANGE: + { + const SfxInt32Item* param1 = rReq.GetArg(FN_PARAM_1); + SCCOL colStart = param1 ? param1->GetValue() : 0; + + const SfxInt32Item* param2 = rReq.GetArg(FN_PARAM_2); + SCROW rowStart = param2 ? param2->GetValue() : 0; + + const SfxInt32Item* param3 = rReq.GetArg(FN_PARAM_3); + SCCOL colEnd = param3 ? param3->GetValue() : 0; + + const SfxInt32Item* param4 = rReq.GetArg(FN_PARAM_4); + SCROW rowEnd = param4 ? param4->GetValue() : 0; + + const SfxInt32Item* param5 = rReq.GetArg(FN_PARAM_5); + SCROW table = param5 ? param5->GetValue() : 0; + + ScInputHandler* pInputHdl = SC_MOD()->GetInputHdl(); + + if (param3 && param4 && pInputHdl) + { + ScViewData& rData = pTabViewShell->GetViewData(); + ScTabView* pTabView = rData.GetView(); + + if (param1 && param2) + rData.SetRefStart(colStart, rowStart, table); + + pTabView->UpdateRef( colEnd, rowEnd, table ); // setup the end & refresh formula + + ScRange aRef( + colStart, rowStart, rData.GetRefStartZ(), + colEnd, rowEnd, rData.GetRefEndZ() ); + SC_MOD()->SetReference( aRef, rData.GetDocument(), &rData.GetMarkData() ); + + pInputHdl->UpdateLokReferenceMarks(); + } + } + break; + + default: + OSL_FAIL("incorrect slot in ExecuteEdit"); + break; + } +} + +void ScCellShell::ExecuteTrans( SfxRequest& rReq ) +{ + TransliterationFlags nType = ScViewUtil::GetTransliterationType( rReq.GetSlot() ); + if ( nType != TransliterationFlags::NONE ) + { + GetViewData().GetView()->TransliterateText( nType ); + rReq.Done(); + } +} + +void ScCellShell::ExecuteRotateTrans( const SfxRequest& rReq ) +{ + if( rReq.GetSlot() == SID_TRANSLITERATE_ROTATE_CASE ) + GetViewData().GetView()->TransliterateText( m_aRotateCase.getNextMode() ); +} + +void ScCellShell::ExecuteExternalSource( + const OUString& _rFile, const OUString& _rFilter, const OUString& _rOptions, + const OUString& _rSource, sal_Int32 _nRefreshDelaySeconds, SfxRequest& _rRequest ) +{ + if ( !_rFile.isEmpty() && !_rSource.isEmpty() ) // filter may be empty + { + ScRange aLinkRange; + bool bMove = false; + + ScViewData& rData = GetViewData(); + ScMarkData& rMark = rData.GetMarkData(); + rMark.MarkToSimple(); + if ( rMark.IsMarked() ) + { + aLinkRange = rMark.GetMarkArea(); + bMove = true; // insert/delete cells to fit range + } + else + aLinkRange = ScRange( rData.GetCurX(), rData.GetCurY(), rData.GetTabNo() ); + + rData.GetDocFunc().InsertAreaLink( _rFile, _rFilter, _rOptions, _rSource, + aLinkRange, _nRefreshDelaySeconds, bMove, false ); + _rRequest.Done(); + } + else + _rRequest.Ignore(); +} + +namespace { + +bool isDPSourceValid(const ScDPObject& rDPObj) +{ + if (rDPObj.IsImportData()) + { + // If the data type is database, check if the database is still valid. + const ScImportSourceDesc* pDesc = rDPObj.GetImportSourceDesc(); + if (!pDesc) + return false; + + const ScDPSaveData* pSaveData = rDPObj.GetSaveData(); + const ScDPDimensionSaveData* pDimData = nullptr; + if (pSaveData) + pDimData = pSaveData->GetExistingDimensionData(); + + const ScDPCache* pCache = pDesc->CreateCache(pDimData); + if (!pCache) + // cache creation failed, probably due to invalid connection. + return false; + } + return true; +} + +void RunPivotLayoutDialog(ScModule* pScMod, + ScTabViewShell* pTabViewShell, + std::unique_ptr& pNewDPObject) +{ + bool bHadNewDPObject = pNewDPObject != nullptr; + pTabViewShell->SetDialogDPObject( std::move(pNewDPObject) ); + if ( bHadNewDPObject ) + { + // start layout dialog + + sal_uInt16 nId = ScPivotLayoutWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } +} + +void SetupRangeForPivotTableDialog(const ScRange& rRange, + ScAddress& rDestPos, + ScDocument* pDoc, + TranslateId pSrcErrorId, + std::unique_ptr& pNewDPObject) +{ + ScSheetSourceDesc aShtDesc(pDoc); + aShtDesc.SetSourceRange(rRange); + pSrcErrorId = aShtDesc.CheckSourceRange(); + if (!pSrcErrorId) + { + pNewDPObject.reset(new ScDPObject(pDoc)); + pNewDPObject->SetSheetDesc( aShtDesc ); + } + + // output below source data + if ( rRange.aEnd.Row()+2 <= pDoc->MaxRow() - 4 ) + rDestPos = ScAddress( rRange.aStart.Col(), + rRange.aEnd.Row()+2, + rRange.aStart.Tab() ); +} + +void ErrorOrRunPivotLayoutDialog(TranslateId pSrcErrorId, + const ScAddress& rDestPos, + ScModule* pScMod, + ScTabViewShell* pTabViewShell, + std::unique_ptr& pNewDPObject) +{ + if (pSrcErrorId) + { + // Error occurred during data creation. Launch an error and bail out. + std::unique_ptr xInfoBox(Application::CreateMessageDialog(pTabViewShell->GetFrameWeld(), + VclMessageType::Info, VclButtonsType::Ok, + ScResId(pSrcErrorId))); + xInfoBox->run(); + return; + } + + if ( pNewDPObject ) + pNewDPObject->SetOutRange( rDestPos ); + + RunPivotLayoutDialog(pScMod, pTabViewShell, pNewDPObject); +} + +} + +void ScCellShell::ExecuteDataPilotDialog() +{ + ScModule* pScMod = SC_MOD(); + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + ScViewData& rData = GetViewData(); + ScDocument& rDoc = rData.GetDocument(); + + // ScPivot is no longer used... + ScDPObject* pDPObj = rDoc.GetDPAtCursor( + rData.GetCurX(), rData.GetCurY(), + rData.GetTabNo() ); + if ( pDPObj ) // on an existing table? + { + std::unique_ptr pNewDPObject; + + if (isDPSourceValid(*pDPObj)) + pNewDPObject.reset(new ScDPObject(*pDPObj)); + + RunPivotLayoutDialog(pScMod, pTabViewShell, pNewDPObject); + } + else // create new table + { + // select database range or data + pTabViewShell->GetDBData( true, SC_DB_OLD ); + ScMarkData& rMark = GetViewData().GetMarkData(); + if ( !rMark.IsMarked() && !rMark.IsMultiMarked() ) + pTabViewShell->MarkDataArea( false ); + + // output to cursor position for non-sheet data + ScAddress aDestPos( rData.GetCurX(), rData.GetCurY(), + rData.GetTabNo() ); + + // first select type of source data + + bool bEnableExt = ScDPObject::HasRegisteredSources(); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + VclPtr pTypeDlg( + pFact->CreateScDataPilotSourceTypeDlg( + pTabViewShell->GetFrameWeld(), bEnableExt)); + + // Populate named ranges (if any). + ScRangeName* pRangeName = rDoc.GetRangeName(); + if (pRangeName) + { + ScRangeName::const_iterator itr = pRangeName->begin(), itrEnd = pRangeName->end(); + for (; itr != itrEnd; ++itr) + pTypeDlg->AppendNamedRange(itr->second->GetName()); + } + + pTypeDlg->StartExecuteAsync([this, pTypeDlg, pTabViewShell, + pScMod, pFact, &rDoc, &rMark, aDestPos](int nResult) mutable { + + if (nResult == RET_OK ) + { + if ( pTypeDlg->IsExternal() ) + { + std::vector aSources = ScDPObject::GetRegisteredSources(); + VclPtr pServDlg( + pFact->CreateScDataPilotServiceDlg( + pTabViewShell->GetFrameWeld(), aSources)); + + pServDlg->StartExecuteAsync([pServDlg, pScMod, pTabViewShell, + aDestPos, &rDoc](int nResult2) mutable { + if ( nResult2 == RET_OK ) + { + ScDPServiceDesc aServDesc( + pServDlg->GetServiceName(), + pServDlg->GetParSource(), + pServDlg->GetParName(), + pServDlg->GetParUser(), + pServDlg->GetParPass() ); + std::unique_ptr pNewDPObject(new ScDPObject(&rDoc)); + pNewDPObject->SetServiceData( aServDesc ); + pNewDPObject->SetOutRange(aDestPos); + + RunPivotLayoutDialog(pScMod, pTabViewShell, pNewDPObject); + } + + pServDlg->disposeOnce(); + }); + } + else if ( pTypeDlg->IsDatabase() ) + { + assert(pFact && "ScAbstractFactory create fail!"); + VclPtr pDataDlg( + pFact->CreateScDataPilotDatabaseDlg(pTabViewShell->GetFrameWeld())); + assert(pDataDlg && "Dialog create fail!"); + + pDataDlg->StartExecuteAsync([pDataDlg, pScMod, pTabViewShell, + aDestPos, &rDoc](int nResult2) mutable { + if ( nResult2 == RET_OK ) + { + ScImportSourceDesc aImpDesc(&rDoc); + pDataDlg->GetValues( aImpDesc ); + std::unique_ptr pNewDPObject(new ScDPObject(&rDoc)); + pNewDPObject->SetImportDesc( aImpDesc ); + pNewDPObject->SetOutRange(aDestPos); + + RunPivotLayoutDialog(pScMod, pTabViewShell, pNewDPObject); + } + + pDataDlg->disposeOnce(); + }); + } + else + { + TranslateId pSrcErrorId; + + if (pTypeDlg->IsNamedRange()) + { + std::unique_ptr pNewDPObject; + OUString aName = pTypeDlg->GetSelectedNamedRange(); + ScSheetSourceDesc aShtDesc(&rDoc); + aShtDesc.SetRangeName(aName); + pSrcErrorId = aShtDesc.CheckSourceRange(); + if (!pSrcErrorId) + { + pNewDPObject.reset(new ScDPObject(&rDoc)); + pNewDPObject->SetSheetDesc(aShtDesc); + } + + ErrorOrRunPivotLayoutDialog(pSrcErrorId, aDestPos, pScMod, pTabViewShell, pNewDPObject); + } + else // selection + { + //! use database ranges (select before type dialog?) + ScRange aRange; + ScMarkType eType = GetViewData().GetSimpleArea(aRange); + if ( (eType & SC_MARK_SIMPLE) == SC_MARK_SIMPLE ) + { + ScDocument* pDoc = &rDoc; + + // Shrink the range to the data area. + SCCOL nStartCol = aRange.aStart.Col(), nEndCol = aRange.aEnd.Col(); + SCROW nStartRow = aRange.aStart.Row(), nEndRow = aRange.aEnd.Row(); + if (rDoc.ShrinkToDataArea(aRange.aStart.Tab(), nStartCol, nStartRow, nEndCol, nEndRow)) + { + aRange.aStart.SetCol(nStartCol); + aRange.aStart.SetRow(nStartRow); + aRange.aEnd.SetCol(nEndCol); + aRange.aEnd.SetRow(nEndRow); + rMark.SetMarkArea(aRange); + pTabViewShell->MarkRange(aRange); + } + + if ( rDoc.HasSubTotalCells( aRange ) ) + { + // confirm selection if it contains SubTotal cells + std::shared_ptr xQueryBox(Application::CreateMessageDialog(pTabViewShell->GetFrameWeld(), + VclMessageType::Question, VclButtonsType::YesNo, + ScResId(STR_DATAPILOT_SUBTOTAL))); + xQueryBox->set_default_response(RET_YES); + xQueryBox->runAsync(xQueryBox, [aRange, pDoc, pTypeDlg, aDestPos, + pScMod, pTabViewShell, pSrcErrorId] (int nResult2) mutable { + if (nResult2 == RET_NO) + return; + + std::unique_ptr pNewDPObject; + SetupRangeForPivotTableDialog(aRange, aDestPos, pDoc, pSrcErrorId, pNewDPObject); + ErrorOrRunPivotLayoutDialog(pSrcErrorId, aDestPos, pScMod, pTabViewShell, pNewDPObject); + }); + + pTypeDlg->disposeOnce(); + return; + } + + std::unique_ptr pNewDPObject; + SetupRangeForPivotTableDialog(aRange, aDestPos, pDoc, pSrcErrorId, pNewDPObject); + ErrorOrRunPivotLayoutDialog(pSrcErrorId, aDestPos, pScMod, pTabViewShell, pNewDPObject); + } + } + } + } + + pTypeDlg->disposeOnce(); + }); + } +} + +void ScCellShell::ExecuteXMLSourceDialog() +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + if (!pTabViewShell) + return; + + ScModule* pScMod = SC_MOD(); + + sal_uInt16 nId = ScXMLSourceDlgWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrame = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrame.GetChildWindow(nId); + pScMod->SetRefDialog(nId, pWnd == nullptr); +} + +void ScCellShell::ExecuteSubtotals(SfxRequest& rReq) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + const SfxItemSet* pArgs = rReq.GetArgs(); + if ( pArgs ) + { + pTabViewShell->DoSubTotals( pArgs->Get( SCITEM_SUBTDATA ). + GetSubTotalData() ); + rReq.Done(); + return; + } + + ScopedVclPtr pDlg; + ScSubTotalParam aSubTotalParam; + SfxItemSetFixed aArgSet( GetPool() ); + + bool bAnonymous; + + // Only get existing named database range. + ScDBData* pDBData = pTabViewShell->GetDBData(true, SC_DB_OLD); + if (pDBData) + bAnonymous = false; + else + { + // No existing DB data at this position. Create an + // anonymous DB. + bAnonymous = true; + pDBData = pTabViewShell->GetAnonymousDBData(); + ScRange aDataRange; + pDBData->GetArea(aDataRange); + pTabViewShell->MarkRange(aDataRange, false); + } + + pDBData->GetSubTotalParam( aSubTotalParam ); + aSubTotalParam.bRemoveOnly = false; + if (bAnonymous) + { + // Preset sort formatting along with values and also create formula + // cells with "needs formatting". Subtotals on data of different types + // doesn't make much sense anyway. + aSubTotalParam.bIncludePattern = true; + } + + aArgSet.Put( ScSubTotalItem( SCITEM_SUBTDATA, &GetViewData(), &aSubTotalParam ) ); + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + pDlg.disposeAndReset(pFact->CreateScSubTotalDlg(pTabViewShell->GetFrameWeld(), aArgSet)); + pDlg->SetCurPageId("1stgroup"); + + short bResult = pDlg->Execute(); + + if ( (bResult == RET_OK) || (bResult == SCRET_REMOVE) ) + { + const SfxItemSet* pOutSet = nullptr; + + if ( bResult == RET_OK ) + { + pOutSet = pDlg->GetOutputItemSet(); + aSubTotalParam = + pOutSet->Get( SCITEM_SUBTDATA ).GetSubTotalData(); + } + else // if (bResult == SCRET_REMOVE) + { + pOutSet = &aArgSet; + aSubTotalParam.bRemoveOnly = true; + aSubTotalParam.bReplace = true; + aArgSet.Put( ScSubTotalItem( SCITEM_SUBTDATA, + &GetViewData(), + &aSubTotalParam ) ); + } + + pTabViewShell->DoSubTotals( aSubTotalParam ); + rReq.Done( *pOutSet ); + } + else + GetViewData().GetDocShell()->CancelAutoDBRange(); +} + +void ScCellShell::ExecuteFillSingleEdit() +{ + ScAddress aCurPos = GetViewData().GetCurPos(); + + OUString aInit; + + if (aCurPos.Row() > 0) + { + // Get the initial text value from the above cell. + + ScDocument& rDoc = GetViewData().GetDocument(); + ScAddress aPrevPos = aCurPos; + aPrevPos.IncRow(-1); + ScRefCellValue aCell(rDoc, aPrevPos); + + if (aCell.getType() == CELLTYPE_FORMULA) + { + aInit = "="; + const ScTokenArray* pCode = aCell.getFormula()->GetCode(); + sc::TokenStringContext aCxt(rDoc, rDoc.GetGrammar()); + aInit += pCode->CreateString(aCxt, aCurPos); + } + else + aInit = aCell.getString(&rDoc); + } + + SC_MOD()->SetInputMode(SC_INPUT_TABLE, &aInit); +} + +CellShell_Impl::CellShell_Impl() : + m_pRequest( nullptr ) {} + +CellShell_Impl::~CellShell_Impl() +{ +} +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/cellsh2.cxx b/sc/source/ui/view/cellsh2.cxx new file mode 100644 index 0000000000..71bcd6cac3 --- /dev/null +++ b/sc/source/ui/view/cellsh2.cxx @@ -0,0 +1,1256 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using namespace com::sun::star; + +static bool lcl_GetTextToColumnsRange( const ScViewData& rData, ScRange& rRange, bool bDoEmptyCheckOnly ) +{ + bool bRet = false; + const ScMarkData& rMark = rData.GetMarkData(); + + if ( rMark.IsMarked() ) + { + if ( !rMark.IsMultiMarked() ) + { + rRange = rMark.GetMarkArea(); + if ( rRange.aStart.Col() == rRange.aEnd.Col() ) + { + bRet = true; + } + } + } + else + { + const SCCOL nCol = rData.GetCurX(); + const SCROW nRow = rData.GetCurY(); + const SCTAB nTab = rData.GetTabNo(); + rRange = ScRange( nCol, nRow, nTab, nCol, nRow, nTab ); + bRet = true; + } + + const ScDocument& rDoc = rData.GetDocument(); + + if ( bDoEmptyCheckOnly ) + { + if ( bRet && rDoc.IsBlockEmpty( rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row(), + rRange.aStart.Tab() ) ) + { + bRet = false; + } + } + else if ( bRet ) + { + rRange.PutInOrder(); + SCCOL nStartCol = rRange.aStart.Col(), nEndCol = rRange.aEnd.Col(); + SCROW nStartRow = rRange.aStart.Row(), nEndRow = rRange.aEnd.Row(); + bool bShrunk = false; + rDoc.ShrinkToUsedDataArea( bShrunk, rRange.aStart.Tab(), nStartCol, nStartRow, + nEndCol, nEndRow, false, false, true ); + if ( bShrunk ) + { + rRange.aStart.SetRow( nStartRow ); + rRange.aEnd.SetRow( nEndRow ); + } + } + + return bRet; +} + +static bool lcl_GetSortParam( const ScViewData& rData, const ScSortParam& rSortParam ) +{ + ScTabViewShell* pTabViewShell = rData.GetViewShell(); + ScDBData* pDBData = pTabViewShell->GetDBData(); + ScDocument& rDoc = rData.GetDocument(); + SCTAB nTab = rData.GetTabNo(); + ScDirection eFillDir = DIR_TOP; + bool bSort = true; + ScRange aExternalRange; + + if( rSortParam.nCol1 != rSortParam.nCol2 ) + eFillDir = DIR_LEFT; + if( rSortParam.nRow1 != rSortParam.nRow2 ) + eFillDir = DIR_TOP; + + if( rSortParam.nRow2 == rDoc.MaxRow() ) + { + // Assume that user selected entire column(s), but cater for the + // possibility that the start row is not the first row. + SCSIZE nCount = rDoc.GetEmptyLinesInBlock( rSortParam.nCol1, rSortParam.nRow1, nTab, + rSortParam.nCol2, rSortParam.nRow2, nTab, eFillDir ); + aExternalRange = ScRange( rSortParam.nCol1, + ::std::min( rSortParam.nRow1 + sal::static_int_cast( nCount ), rDoc.MaxRow()), nTab, + rSortParam.nCol2, rSortParam.nRow2, nTab); + aExternalRange.PutInOrder(); + } + else if (rSortParam.nCol1 != rSortParam.nCol2 || rSortParam.nRow1 != rSortParam.nRow2) + { + // Preserve a preselected area. + aExternalRange = ScRange( rSortParam.nCol1, rSortParam.nRow1, nTab, rSortParam.nCol2, rSortParam.nRow2, nTab); + aExternalRange.PutInOrder(); + } + else + aExternalRange = ScRange( rData.GetCurX(), rData.GetCurY(), nTab ); + + SCROW nStartRow = aExternalRange.aStart.Row(); + SCCOL nStartCol = aExternalRange.aStart.Col(); + SCROW nEndRow = aExternalRange.aEnd.Row(); + SCCOL nEndCol = aExternalRange.aEnd.Col(); + rDoc.GetDataArea( aExternalRange.aStart.Tab(), nStartCol, nStartRow, nEndCol, nEndRow, false, false ); + aExternalRange.aStart.SetRow( nStartRow ); + aExternalRange.aStart.SetCol( nStartCol ); + aExternalRange.aEnd.SetRow( nEndRow ); + aExternalRange.aEnd.SetCol( nEndCol ); + + // with LibreOfficeKit, don't try to interact with the user + if (!comphelper::LibreOfficeKit::isActive() && + ((rSortParam.nCol1 == rSortParam.nCol2 && aExternalRange.aStart.Col() != aExternalRange.aEnd.Col()) || + (rSortParam.nRow1 == rSortParam.nRow2 && aExternalRange.aStart.Row() != aExternalRange.aEnd.Row()))) + { + pTabViewShell->AddHighlightRange( aExternalRange,COL_LIGHTBLUE ); + OUString aExtendStr( aExternalRange.Format(rDoc, ScRefFlags::VALID)); + + ScRange aCurrentRange( rSortParam.nCol1, rSortParam.nRow1, nTab, rSortParam.nCol2, rSortParam.nRow2, nTab ); + OUString aCurrentStr(aCurrentRange.Format(rDoc, ScRefFlags::VALID)); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr pWarningDlg(pFact->CreateScSortWarningDlg(pTabViewShell->GetFrameWeld(), aExtendStr, aCurrentStr)); + short bResult = pWarningDlg->Execute(); + if( bResult == BTN_EXTEND_RANGE || bResult == BTN_CURRENT_SELECTION ) + { + if( bResult == BTN_EXTEND_RANGE ) + { + pTabViewShell->MarkRange( aExternalRange, false ); + pDBData->SetArea( nTab, aExternalRange.aStart.Col(), aExternalRange.aStart.Row(), aExternalRange.aEnd.Col(), aExternalRange.aEnd.Row() ); + } + } + else + { + bSort = false; + rData.GetDocShell()->CancelAutoDBRange(); + } + + pTabViewShell->ClearHighlightRanges(); + } + return bSort; +} + +namespace +{ + // this registers the dialog which Find1RefWindow search for + class ScValidationRegisteredDlg + { + std::shared_ptr m_xDlg; + public: + ScValidationRegisteredDlg(weld::Window* pParent, std::shared_ptr xDlg) + : m_xDlg(std::move(xDlg)) + { + SC_MOD()->RegisterRefController(static_cast(ScValidationDlg::SLOTID), m_xDlg, pParent); + } + ~ScValidationRegisteredDlg() + { + m_xDlg->Close(); + SC_MOD()->UnregisterRefController(static_cast(ScValidationDlg::SLOTID), m_xDlg); + } + }; +} + +void ScCellShell::ExecuteDB( SfxRequest& rReq ) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + sal_uInt16 nSlotId = rReq.GetSlot(); + const SfxItemSet* pReqArgs = rReq.GetArgs(); + ScModule* pScMod = SC_MOD(); + + pTabViewShell->HideListBox(); // Autofilter-DropDown-Listbox + + if ( GetViewData().HasEditView( GetViewData().GetActivePart() ) ) + { + pScMod->InputEnterHandler(); + pTabViewShell->UpdateInputHandler(); + } + + switch ( nSlotId ) + { + case SID_VIEW_DATA_SOURCE_BROWSER: + { + // check if database beamer is open + + SfxViewFrame& rViewFrame = pTabViewShell->GetViewFrame(); + bool bWasOpen = false; + { + uno::Reference xFrame = rViewFrame.GetFrame().GetFrameInterface(); + uno::Reference xBeamerFrame = xFrame->findFrame( + "_beamer", + frame::FrameSearchFlag::CHILDREN); + if ( xBeamerFrame.is() ) + bWasOpen = true; + } + + if ( bWasOpen ) + { + // close database beamer: just forward to SfxViewFrame + + rViewFrame.ExecuteSlot( rReq ); + } + else + { + // show database beamer: SfxViewFrame call must be synchronous + + rViewFrame.ExecuteSlot( rReq, false ); // false = synchronous + + // select current database in database beamer + + ScImportParam aImportParam; + ScDBData* pDBData = pTabViewShell->GetDBData(true,SC_DB_OLD); // don't create if none found + if (pDBData) + pDBData->GetImportParam( aImportParam ); + + ScDBDocFunc::ShowInBeamer( aImportParam, &pTabViewShell->GetViewFrame() ); + } + rReq.Done(); // needed because it's a toggle slot + } + break; + + case SID_REIMPORT_DATA: + { + bool bOk = false; + ScDBData* pDBData = pTabViewShell->GetDBData(true,SC_DB_OLD); + if (pDBData) + { + ScImportParam aImportParam; + pDBData->GetImportParam( aImportParam ); + if (aImportParam.bImport && !pDBData->HasImportSelection()) + { + pTabViewShell->ImportData( aImportParam ); + pDBData->SetImportParam( aImportParam ); //! Undo ?? + bOk = true; + } + } + + if (!bOk && ! rReq.IsAPI() ) + pTabViewShell->ErrorMessage(STR_REIMPORT_EMPTY); + + if( bOk ) + rReq.Done(); + } + break; + + case SID_REFRESH_DBAREA: + { + ScDBData* pDBData = pTabViewShell->GetDBData(true,SC_DB_OLD); + if (pDBData) + { + // repeat import like SID_REIMPORT_DATA + + bool bContinue = true; + ScImportParam aImportParam; + pDBData->GetImportParam( aImportParam ); + if (aImportParam.bImport && !pDBData->HasImportSelection()) + { + bContinue = pTabViewShell->ImportData( aImportParam ); + pDBData->SetImportParam( aImportParam ); //! Undo ?? + + // mark (size may have been changed) + ScRange aNewRange; + pDBData->GetArea(aNewRange); + pTabViewShell->MarkRange(aNewRange); + } + + if ( bContinue ) // fail at import -> break + { + // internal operations, when any stored + + if ( pDBData->HasQueryParam() || pDBData->HasSortParam() || + pDBData->HasSubTotalParam() ) + pTabViewShell->RepeatDB(); + + // pivot tables that have the range as data source + + ScRange aRange; + pDBData->GetArea(aRange); + GetViewData().GetDocShell()->RefreshPivotTables(aRange); + } + } + rReq.Done(); + } + break; + + case SID_SBA_BRW_INSERT: + { + OSL_FAIL( "Deprecated Slot" ); + } + break; + + case SID_DATA_FORM: + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr pDlg(pFact->CreateScDataFormDlg( + pTabViewShell->GetFrameWeld(), pTabViewShell)); + + pDlg->Execute(); + + rReq.Done(); + } + break; + + case SID_SUBTOTALS: + ExecuteSubtotals(rReq); + break; + + case SID_SORT_DESCENDING: + case SID_SORT_ASCENDING: + { + //#i60401 ux-ctest: Calc does not support all users' strategies regarding sorting data + //the patch comes from maoyg + ScSortParam aSortParam; + ScDBData* pDBData = pTabViewShell->GetDBData(); + ScViewData& rData = GetViewData(); + + pDBData->GetSortParam( aSortParam ); + + if( lcl_GetSortParam( rData, aSortParam ) ) + { + SCCOL nCol = GetViewData().GetCurX(); + SCCOL nTab = GetViewData().GetTabNo(); + ScDocument& rDoc = GetViewData().GetDocument(); + + pDBData->GetSortParam( aSortParam ); + bool bHasHeader = rDoc.HasColHeader( aSortParam.nCol1, aSortParam.nRow1, aSortParam.nCol2, aSortParam.nRow2, nTab ); + + if( nCol < aSortParam.nCol1 ) + nCol = aSortParam.nCol1; + else if( nCol > aSortParam.nCol2 ) + nCol = aSortParam.nCol2; + + aSortParam.bHasHeader = bHasHeader; + aSortParam.bByRow = true; + aSortParam.bCaseSens = false; + aSortParam.bNaturalSort = false; + aSortParam.aDataAreaExtras.mbCellNotes = false; + aSortParam.aDataAreaExtras.mbCellDrawObjects = true; + aSortParam.aDataAreaExtras.mbCellFormats = true; + aSortParam.bInplace = true; + aSortParam.maKeyState[0].bDoSort = true; + aSortParam.maKeyState[0].nField = nCol; + aSortParam.maKeyState[0].bAscending = ( nSlotId == SID_SORT_ASCENDING ); + + for ( sal_uInt16 i=1; iUISort( aSortParam ); // subtotal when needed new + + rReq.Done(); + } + } + break; + + case SID_SORT: + { + const SfxItemSet* pArgs = rReq.GetArgs(); + + //#i60401 ux-ctest: Calc does not support all users' strategies regarding sorting data + //the patch comes from maoyg + + if ( pArgs ) // Basic + { + ScSortParam aSortParam; + ScDBData* pDBData = pTabViewShell->GetDBData(); + ScViewData& rData = GetViewData(); + + pDBData->GetSortParam( aSortParam ); + + if( lcl_GetSortParam( rData, aSortParam ) ) + { + ScDocument& rDoc = GetViewData().GetDocument(); + + pDBData->GetSortParam( aSortParam ); + bool bHasHeader = rDoc.HasColHeader( aSortParam.nCol1, aSortParam.nRow1, aSortParam.nCol2, aSortParam.nRow2, rData.GetTabNo() ); + if( bHasHeader ) + aSortParam.bHasHeader = bHasHeader; + + aSortParam.bInplace = true; // from Basic always + + if ( const SfxBoolItem* pItem = pArgs->GetItemIfSet( SID_SORT_BYROW ) ) + aSortParam.bByRow = pItem->GetValue(); + if ( const SfxBoolItem* pItem = pArgs->GetItemIfSet( SID_SORT_HASHEADER ) ) + aSortParam.bHasHeader = pItem->GetValue(); + if ( const SfxBoolItem* pItem = pArgs->GetItemIfSet( SID_SORT_CASESENS ) ) + aSortParam.bCaseSens = pItem->GetValue(); + if ( const SfxBoolItem* pItem = pArgs->GetItemIfSet( SID_SORT_NATURALSORT ) ) + aSortParam.bNaturalSort = pItem->GetValue(); + if ( const SfxBoolItem* pItem = pArgs->GetItemIfSet( SID_SORT_INCCOMMENTS ) ) + aSortParam.aDataAreaExtras.mbCellNotes = pItem->GetValue(); + if ( const SfxBoolItem* pItem = pArgs->GetItemIfSet( SID_SORT_INCIMAGES ) ) + aSortParam.aDataAreaExtras.mbCellDrawObjects = pItem->GetValue(); + if ( const SfxBoolItem* pItem = pArgs->GetItemIfSet( SID_SORT_ATTRIBS ) ) + aSortParam.aDataAreaExtras.mbCellFormats = pItem->GetValue(); + if ( const SfxUInt16Item* pItem = pArgs->GetItemIfSet( SID_SORT_USERDEF ) ) + { + sal_uInt16 nUserIndex = pItem->GetValue(); + aSortParam.bUserDef = ( nUserIndex != 0 ); + if ( nUserIndex ) + aSortParam.nUserIndex = nUserIndex - 1; // Basic: 1-based + } + + SCCOLROW nField0 = 0; + const SfxPoolItem* pItem = nullptr; + if ( pArgs->GetItemState( FN_PARAM_1, true, &pItem ) == SfxItemState::SET ) + nField0 = static_cast(pItem)->GetValue(); + aSortParam.maKeyState[0].bDoSort = ( nField0 != 0 ); + aSortParam.maKeyState[0].nField = nField0 > 0 ? (nField0-1) : 0; + if ( pArgs->GetItemState( FN_PARAM_2, true, &pItem ) == SfxItemState::SET ) + aSortParam.maKeyState[0].bAscending = static_cast(pItem)->GetValue(); + SCCOLROW nField1 = 0; + if ( pArgs->GetItemState( FN_PARAM_3, true, &pItem ) == SfxItemState::SET ) + nField1 = static_cast(pItem)->GetValue(); + aSortParam.maKeyState[1].bDoSort = ( nField1 != 0 ); + aSortParam.maKeyState[1].nField = nField1 > 0 ? (nField1-1) : 0; + if ( pArgs->GetItemState( FN_PARAM_4, true, &pItem ) == SfxItemState::SET ) + aSortParam.maKeyState[1].bAscending = static_cast(pItem)->GetValue(); + SCCOLROW nField2 = 0; + if ( pArgs->GetItemState( FN_PARAM_5, true, &pItem ) == SfxItemState::SET ) + nField2 = static_cast(pItem)->GetValue(); + aSortParam.maKeyState[2].bDoSort = ( nField2 != 0 ); + aSortParam.maKeyState[2].nField = nField2 > 0 ? (nField2-1) : 0; + if ( pArgs->GetItemState( FN_PARAM_6, true, &pItem ) == SfxItemState::SET ) + aSortParam.maKeyState[2].bAscending = static_cast(pItem)->GetValue(); + + // subtotal when needed new + pTabViewShell->UISort( aSortParam ); + rReq.Done(); + } + } + else + { + ScSortParam aSortParam; + ScDBData* pDBData = pTabViewShell->GetDBData(); + ScViewData& rData = GetViewData(); + + pDBData->GetSortParam( aSortParam ); + + if( lcl_GetSortParam( rData, aSortParam ) ) + { + ScDocument& rDoc = GetViewData().GetDocument(); + SfxItemSetFixed aArgSet( GetPool() ); + + pDBData->GetSortParam( aSortParam ); + bool bHasHeader = rDoc.HasColHeader( aSortParam.nCol1, aSortParam.nRow1, aSortParam.nCol2, aSortParam.nRow2, rData.GetTabNo() ); + if( bHasHeader ) + aSortParam.bHasHeader = bHasHeader; + + aArgSet.Put( ScSortItem( SCITEM_SORTDATA, &GetViewData(), &aSortParam ) ); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + std::shared_ptr pDlg(pFact->CreateScSortDlg(pTabViewShell->GetFrameWeld(), &aArgSet)); + pDlg->SetCurPageId("criteria"); // 1=sort field tab 2=sort options tab + + VclAbstractDialog::AsyncContext aContext; + aContext.maEndDialogFn = [pDlg, &rData, pTabViewShell](sal_Int32 nResult) + { + if ( nResult == RET_OK ) + { + const SfxItemSet* pOutSet = pDlg->GetOutputItemSet(); + const ScSortParam& rOutParam = + pOutSet->Get( SCITEM_SORTDATA ).GetSortData(); + + // subtotal when needed new + + pTabViewShell->UISort( rOutParam ); + + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxRequest aRequest(rViewFrm, SID_SORT); + + if ( rOutParam.bInplace ) + { + aRequest.AppendItem( SfxBoolItem( SID_SORT_BYROW, + rOutParam.bByRow ) ); + aRequest.AppendItem( SfxBoolItem( SID_SORT_HASHEADER, + rOutParam.bHasHeader ) ); + aRequest.AppendItem( SfxBoolItem( SID_SORT_CASESENS, + rOutParam.bCaseSens ) ); + aRequest.AppendItem( SfxBoolItem( SID_SORT_NATURALSORT, + rOutParam.bNaturalSort ) ); + aRequest.AppendItem( SfxBoolItem( SID_SORT_INCCOMMENTS, + rOutParam.aDataAreaExtras.mbCellNotes ) ); + aRequest.AppendItem( SfxBoolItem( SID_SORT_INCIMAGES, + rOutParam.aDataAreaExtras.mbCellDrawObjects ) ); + aRequest.AppendItem( SfxBoolItem( SID_SORT_ATTRIBS, + rOutParam.aDataAreaExtras.mbCellFormats ) ); + sal_uInt16 nUser = rOutParam.bUserDef ? ( rOutParam.nUserIndex + 1 ) : 0; + aRequest.AppendItem( SfxUInt16Item( SID_SORT_USERDEF, nUser ) ); + if ( rOutParam.maKeyState[0].bDoSort ) + { + aRequest.AppendItem( SfxInt32Item( TypedWhichId(FN_PARAM_1), + rOutParam.maKeyState[0].nField + 1 ) ); + aRequest.AppendItem( SfxBoolItem( FN_PARAM_2, + rOutParam.maKeyState[0].bAscending ) ); + } + if ( rOutParam.maKeyState[1].bDoSort ) + { + aRequest.AppendItem( SfxInt32Item( TypedWhichId(FN_PARAM_3), + rOutParam.maKeyState[1].nField + 1 ) ); + aRequest.AppendItem( SfxBoolItem( FN_PARAM_4, + rOutParam.maKeyState[1].bAscending ) ); + } + if ( rOutParam.maKeyState[2].bDoSort ) + { + aRequest.AppendItem( SfxInt32Item( TypedWhichId(FN_PARAM_5), + rOutParam.maKeyState[2].nField + 1 ) ); + aRequest.AppendItem( SfxBoolItem( FN_PARAM_6, + rOutParam.maKeyState[2].bAscending ) ); + } + } + + aRequest.Done(); + } + else + { + rData.GetDocShell()->CancelAutoDBRange(); + } + }; + + pDlg->StartExecuteAsync(aContext); + } + } + } + break; + + case SID_FILTER: + { + const SfxItemSet* pArgs = rReq.GetArgs(); + if ( pArgs ) + { + OSL_FAIL("SID_FILTER with arguments?"); + pTabViewShell->Query( + pArgs->Get( SCITEM_QUERYDATA ).GetQueryData(), nullptr, true ); + rReq.Done(); + } + else + { + sal_uInt16 nId = ScFilterDlgWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + } + break; + + case SID_SPECIAL_FILTER: + { + const SfxItemSet* pArgs = rReq.GetArgs(); + if ( pArgs ) + { + OSL_FAIL("SID_SPECIAL_FILTER with arguments?"); + pTabViewShell->Query( + pArgs->Get( SCITEM_QUERYDATA ).GetQueryData(), nullptr, true ); + rReq.Done(); + } + else + { + sal_uInt16 nId = ScSpecialFilterDlgWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + } + break; + + case FID_FILTER_OK: + { + const ScQueryItem* pQueryItem; + if ( pReqArgs && (pQueryItem = + pReqArgs->GetItemIfSet( SCITEM_QUERYDATA )) ) + { + SCTAB nCurTab = GetViewData().GetTabNo(); + SCTAB nRefTab = GetViewData().GetRefTabNo(); + + // If RefInput switched to a different sheet from the data sheet, + // switch back: + + if ( nCurTab != nRefTab ) + { + pTabViewShell->SetTabNo( nRefTab ); + pTabViewShell->PaintExtras(); + } + + ScRange aAdvSource; + if (pQueryItem->GetAdvancedQuerySource(aAdvSource)) + pTabViewShell->Query( pQueryItem->GetQueryData(), &aAdvSource, true ); + else + pTabViewShell->Query( pQueryItem->GetQueryData(), nullptr, true ); + rReq.Done( *pReqArgs ); + } + } + break; + + case SID_UNFILTER: + { + ScQueryParam aParam; + ScDBData* pDBData = pTabViewShell->GetDBData(); + + pDBData->GetQueryParam( aParam ); + SCSIZE nEC = aParam.GetEntryCount(); + for (SCSIZE i=0; iQuery( aParam, nullptr, true ); + rReq.Done(); + } + break; + + case SID_AUTO_FILTER: + pTabViewShell->ToggleAutoFilter(); + rReq.Done(); + break; + + case SID_AUTOFILTER_HIDE: + pTabViewShell->HideAutoFilter(); + rReq.Done(); + break; + + case SID_PIVOT_TABLE: + { + const ScPivotItem* pPItem; + if ( pReqArgs && (pPItem = + pReqArgs->GetItemIfSet( SCITEM_PIVOTDATA )) ) + { + SCTAB nCurTab = GetViewData().GetTabNo(); + SCTAB nRefTab = GetViewData().GetRefTabNo(); + + // If RefInput switched to a different sheet from the data sheet, + // switch back: + + if ( nCurTab != nRefTab ) + { + pTabViewShell->SetTabNo( nRefTab ); + pTabViewShell->PaintExtras(); + } + + const ScDPObject* pDPObject = pTabViewShell->GetDialogDPObject(); + if ( pDPObject ) + { + bool bSuccess = pTabViewShell->MakePivotTable( + pPItem->GetData(), pPItem->GetDestRange(), pPItem->IsNewSheet(), *pDPObject ); + SfxBoolItem aRet(0, bSuccess); + rReq.SetReturnValue(aRet); + } + rReq.Done(); + } +#if HAVE_FEATURE_SCRIPTING + else if (rReq.IsAPI()) + SbxBase::SetError(ERRCODE_BASIC_BAD_PARAMETER); +#endif + } + break; + + case SID_OPENDLG_PIVOTTABLE: + ExecuteDataPilotDialog(); + break; + case SID_DEFINE_DBNAME: + { + + sal_uInt16 nId = ScDbNameDlgWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + + } + break; + + case SID_SELECT_DB: + { + if ( pReqArgs ) + { + const SfxStringItem& rItem = pReqArgs->Get(SID_SELECT_DB); + pTabViewShell->GotoDBArea(rItem.GetValue()); + rReq.Done(); + } + else + { + ScDocument& rDoc = GetViewData().GetDocument(); + ScDBCollection* pDBCol = rDoc.GetDBCollection(); + + if ( pDBCol ) + { + std::vector aList; + const ScDBCollection::NamedDBs& rDBs = pDBCol->getNamedDBs(); + for (const auto& rxDB : rDBs) + aList.push_back(rxDB->GetName()); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr pDlg(pFact->CreateScSelEntryDlg(pTabViewShell->GetFrameWeld(), aList)); + if ( pDlg->Execute() == RET_OK ) + { + OUString aName = pDlg->GetSelectedEntry(); + pTabViewShell->GotoDBArea( aName ); + rReq.AppendItem( SfxStringItem( SID_SELECT_DB, aName ) ); + rReq.Done(); + } + } + } + } + break; + case SID_DATA_STREAMS: + { + sc::DataStreamDlg aDialog(GetViewData().GetDocShell(), pTabViewShell->GetFrameWeld()); + ScDocument& rDoc = GetViewData().GetDocument(); + sc::DocumentLinkManager& rMgr = rDoc.GetDocLinkManager(); + sc::DataStream* pStrm = rMgr.getDataStream(); + if (pStrm) + aDialog.Init(*pStrm); + + if (aDialog.run() == RET_OK) + aDialog.StartStream(); + } + break; + case SID_DATA_STREAMS_PLAY: + { + ScDocument& rDoc = GetViewData().GetDocument(); + sc::DocumentLinkManager& rMgr = rDoc.GetDocLinkManager(); + sc::DataStream* pStrm = rMgr.getDataStream(); + if (pStrm) + pStrm->StartImport(); + } + break; + case SID_DATA_STREAMS_STOP: + { + ScDocument& rDoc = GetViewData().GetDocument(); + sc::DocumentLinkManager& rMgr = rDoc.GetDocLinkManager(); + sc::DataStream* pStrm = rMgr.getDataStream(); + if (pStrm) + pStrm->StopImport(); + } + break; + case SID_DATA_PROVIDER: + { + auto xDoc = o3tl::make_shared(); + xDoc->InsertTab(0, "test"); + ScDocument& rDoc = GetViewData().GetDocument(); + ScDataProviderDlg aDialog(pTabViewShell->GetDialogParent(), xDoc, &rDoc); + if (aDialog.run() == RET_OK) + { + aDialog.import(rDoc); + } + } + break; + case SID_DATA_PROVIDER_REFRESH: + { + ScDocument& rDoc = GetViewData().GetDocument(); + auto& rDataMapper = rDoc.GetExternalDataMapper(); + for (auto& rDataSource : rDataMapper.getDataSources()) + { + rDataSource.refresh(&rDoc, false); + } + } + break; + case SID_MANAGE_XML_SOURCE: + ExecuteXMLSourceDialog(); + break; + case FID_VALIDATION: + case FID_CURRENTVALIDATION: + { + const SfxItemSet* pArgs = rReq.GetArgs(); + if ( pArgs ) + { + OSL_FAIL("later..."); + } + else + { + SfxItemSet aArgSet( GetPool(), ScTPValidationValue::GetRanges() ); + ScValidationMode eMode = SC_VALID_ANY; + ScConditionMode eOper = ScConditionMode::Equal; + OUString aExpr1, aExpr2; + bool bBlank = true; + sal_Int16 nListType = css::sheet::TableValidationVisibility::UNSORTED; + bool bShowHelp = false; + OUString aHelpTitle, aHelpText; + bool bShowError = false; + ScValidErrorStyle eErrStyle = SC_VALERR_STOP; + OUString aErrTitle, aErrText; + + ScDocument& rDoc = GetViewData().GetDocument(); + SCCOL nCurX = GetViewData().GetCurX(); + SCROW nCurY = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + ScAddress aCursorPos( nCurX, nCurY, nTab ); + sal_uInt32 nIndex = rDoc.GetAttr( + nCurX, nCurY, nTab, ATTR_VALIDDATA )->GetValue(); + if ( nIndex ) + { + const ScValidationData* pOldData = rDoc.GetValidationEntry( nIndex ); + if ( pOldData ) + { + eMode = pOldData->GetDataMode(); + eOper = pOldData->GetOperation(); + sal_uInt32 nNumFmt = 0; + if ( eMode == SC_VALID_DATE || eMode == SC_VALID_TIME ) + { + SvNumFormatType nType = ( eMode == SC_VALID_DATE ) ? SvNumFormatType::DATE + : SvNumFormatType::TIME; + nNumFmt = rDoc.GetFormatTable()->GetStandardFormat( + nType, ScGlobal::eLnge ); + } + aExpr1 = pOldData->GetExpression( aCursorPos, 0, nNumFmt ); + aExpr2 = pOldData->GetExpression( aCursorPos, 1, nNumFmt ); + bBlank = pOldData->IsIgnoreBlank(); + nListType = pOldData->GetListType(); + + bShowHelp = pOldData->GetInput( aHelpTitle, aHelpText ); + bShowError = pOldData->GetErrMsg( aErrTitle, aErrText, eErrStyle ); + + aArgSet.Put( SfxUInt16Item( FID_VALID_MODE, sal::static_int_cast(eMode) ) ); + aArgSet.Put( SfxUInt16Item( FID_VALID_CONDMODE, sal::static_int_cast(eOper) ) ); + aArgSet.Put( SfxStringItem( FID_VALID_VALUE1, aExpr1 ) ); + aArgSet.Put( SfxStringItem( FID_VALID_VALUE2, aExpr2 ) ); + aArgSet.Put( SfxBoolItem( FID_VALID_BLANK, bBlank ) ); + aArgSet.Put( SfxInt16Item( FID_VALID_LISTTYPE, nListType ) ); + aArgSet.Put( SfxBoolItem( FID_VALID_SHOWHELP, bShowHelp ) ); + aArgSet.Put( SfxStringItem( FID_VALID_HELPTITLE, aHelpTitle ) ); + aArgSet.Put( SfxStringItem( FID_VALID_HELPTEXT, aHelpText ) ); + aArgSet.Put( SfxBoolItem( FID_VALID_SHOWERR, bShowError ) ); + aArgSet.Put( SfxUInt16Item( FID_VALID_ERRSTYLE, sal::static_int_cast(eErrStyle) ) ); + aArgSet.Put( SfxStringItem( FID_VALID_ERRTITLE, aErrTitle ) ); + aArgSet.Put( SfxStringItem( FID_VALID_ERRTEXT, aErrText ) ); + } + } + + // cell range picker + vcl::Window* pWin = GetViewData().GetActiveWin(); + weld::Window* pParentWin = pWin ? pWin->GetFrameWeld() : nullptr; + auto xDlg = std::make_shared(pParentWin, &aArgSet, pTabViewShell); + ScValidationRegisteredDlg aRegisterThatDlgExists(pParentWin, xDlg); + + short nResult = xDlg->run(); + if ( nResult == RET_OK ) + { + const SfxItemSet* pOutSet = xDlg->GetOutputItemSet(); + + if ( const SfxUInt16Item* pItem = pOutSet->GetItemIfSet( FID_VALID_MODE ) ) + eMode = static_cast(pItem->GetValue()); + if ( const SfxUInt16Item* pItem = pOutSet->GetItemIfSet( FID_VALID_CONDMODE ) ) + eOper = static_cast(pItem->GetValue()); + if ( const SfxStringItem* pItem = pOutSet->GetItemIfSet( FID_VALID_VALUE1 ) ) + { + OUString aTemp1 = pItem->GetValue(); + if (eMode == SC_VALID_DATE || eMode == SC_VALID_TIME) + { + sal_uInt32 nNumIndex = 0; + double nVal; + if (rDoc.GetFormatTable()->IsNumberFormat(aTemp1, nNumIndex, nVal)) + aExpr1 = ::rtl::math::doubleToUString( nVal, + rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, + ScGlobal::getLocaleData().getNumDecimalSep()[0], true); + else + aExpr1 = aTemp1; + } + else + aExpr1 = aTemp1; + } + if ( const SfxStringItem* pItem = pOutSet->GetItemIfSet( FID_VALID_VALUE2 ) ) + { + OUString aTemp2 = pItem->GetValue(); + if (eMode == SC_VALID_DATE || eMode == SC_VALID_TIME) + { + sal_uInt32 nNumIndex = 0; + double nVal; + if (rDoc.GetFormatTable()->IsNumberFormat(aTemp2, nNumIndex, nVal)) + aExpr2 = ::rtl::math::doubleToUString( nVal, + rtl_math_StringFormat_Automatic, rtl_math_DecimalPlaces_Max, + ScGlobal::getLocaleData().getNumDecimalSep()[0], true); + else + aExpr2 = aTemp2; + if ( eMode == SC_VALID_TIME ) { + sal_Int32 wraparound = aExpr1.compareTo(aExpr2); + if (wraparound > 0) { + if (eOper == ScConditionMode::Between) { + eOper = ScConditionMode::NotBetween; + std::swap( aExpr1, aExpr2 ); + } + else if (eOper == ScConditionMode::NotBetween) { + eOper = ScConditionMode::Between; + std::swap( aExpr1, aExpr2 ); + } + } + } + } + else + aExpr2 = aTemp2; + } + if ( const SfxBoolItem* pItem = pOutSet->GetItemIfSet( FID_VALID_BLANK ) ) + bBlank = pItem->GetValue(); + if ( const SfxInt16Item* pItem = pOutSet->GetItemIfSet( FID_VALID_LISTTYPE ) ) + nListType = pItem->GetValue(); + + if ( const SfxBoolItem* pItem = pOutSet->GetItemIfSet( FID_VALID_SHOWHELP ) ) + bShowHelp = pItem->GetValue(); + if ( const SfxStringItem* pItem = pOutSet->GetItemIfSet( FID_VALID_HELPTITLE ) ) + aHelpTitle = pItem->GetValue(); + if ( const SfxStringItem* pItem = pOutSet->GetItemIfSet( FID_VALID_HELPTEXT ) ) + aHelpText = pItem->GetValue(); + + if ( const SfxBoolItem* pItem = pOutSet->GetItemIfSet( FID_VALID_SHOWERR ) ) + bShowError = pItem->GetValue(); + if ( const SfxUInt16Item* pItem = pOutSet->GetItemIfSet( FID_VALID_ERRSTYLE ) ) + eErrStyle = static_cast(pItem->GetValue()); + if ( const SfxStringItem* pItem = pOutSet->GetItemIfSet( FID_VALID_ERRTITLE ) ) + aErrTitle = pItem->GetValue(); + if ( const SfxStringItem* pItem = pOutSet->GetItemIfSet( FID_VALID_ERRTEXT ) ) + aErrText = pItem->GetValue(); + + ScValidationData aData( eMode, eOper, aExpr1, aExpr2, rDoc, aCursorPos ); + aData.SetIgnoreBlank( bBlank ); + aData.SetListType( nListType ); + + aData.SetInput(aHelpTitle, aHelpText); // sets bShowInput to TRUE + if (!bShowHelp) + aData.ResetInput(); // reset only bShowInput + + aData.SetError(aErrTitle, aErrText, eErrStyle); // sets bShowError to TRUE + if (!bShowError) + aData.ResetError(); // reset only bShowError + + pTabViewShell->SetValidation( aData ); + pTabViewShell->TestHintWindow(); + rReq.Done( *pOutSet ); + } + } + } + break; + + case SID_TEXT_TO_COLUMNS: + { + ScViewData& rData = GetViewData(); + ScRange aRange; + + if ( lcl_GetTextToColumnsRange( rData, aRange, false ) ) + { + ScDocument& rDoc = rData.GetDocument(); + + ScImportExport aExport( rDoc, aRange ); + aExport.SetExportTextOptions( ScExportTextOptions( ScExportTextOptions::None, 0, false ) ); + + // #i87703# text to columns fails with tab separator + aExport.SetDelimiter( u'\0' ); + + SvMemoryStream aStream; + aStream.SetStreamCharSet( RTL_TEXTENCODING_UNICODE ); + ScImportExport::SetNoEndianSwap( aStream ); + aExport.ExportStream( aStream, OUString(), SotClipboardFormatId::STRING ); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + ScopedVclPtr pDlg(pFact->CreateScImportAsciiDlg( + pTabViewShell->GetFrameWeld(), OUString(), &aStream, SC_TEXTTOCOLUMNS)); + + if ( pDlg->Execute() == RET_OK ) + { + ScDocShell* pDocSh = rData.GetDocShell(); + OSL_ENSURE( pDocSh, "ScCellShell::ExecuteDB: SID_TEXT_TO_COLUMNS - pDocSh is null!" ); + + OUString aUndo = ScResId( STR_UNDO_TEXTTOCOLUMNS ); + pDocSh->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, rData.GetViewShell()->GetViewShellId() ); + + ScImportExport aImport( rDoc, aRange.aStart ); + ScAsciiOptions aOptions; + pDlg->GetOptions( aOptions ); + pDlg->SaveParameters(); + aImport.SetExtOptions( aOptions ); + aImport.SetApi( false ); + aImport.SetImportBroadcast( true ); + aImport.SetOverwriting( true ); + aStream.Seek( 0 ); + aImport.ImportStream( aStream, OUString(), SotClipboardFormatId::STRING ); + + pDocSh->GetUndoManager()->LeaveListAction(); + } + } + } + break; + } +} + +void ScCellShell::GetDBState( SfxItemSet& rSet ) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + ScViewData& rData = GetViewData(); + ScDocShell* pDocSh = rData.GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + SCCOL nPosX = rData.GetCurX(); + SCROW nPosY = rData.GetCurY(); + SCTAB nTab = rData.GetTabNo(); + + bool bAutoFilter = false; + bool bAutoFilterTested = false; + + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + while (nWhich) + { + switch (nWhich) + { + case SID_REFRESH_DBAREA: + { + // imported data without selection + // or filter,sort,subtotal (also without import) + bool bOk = false; + ScDBData* pDBData = pTabViewShell->GetDBData(false,SC_DB_OLD); + if (pDBData && rDoc.GetChangeTrack() == nullptr) + { + if ( pDBData->HasImportParam() ) + bOk = !pDBData->HasImportSelection(); + else + { + bOk = pDBData->HasQueryParam() || + pDBData->HasSortParam() || + pDBData->HasSubTotalParam(); + } + } + if (!bOk) + rSet.DisableItem( nWhich ); + } + break; + + case SID_FILTER: + case SID_SPECIAL_FILTER: + { + ScRange aDummy; + ScMarkType eMarkType = GetViewData().GetSimpleArea( aDummy); + if (eMarkType != SC_MARK_SIMPLE && eMarkType != SC_MARK_SIMPLE_FILTERED) + { + rSet.DisableItem( nWhich ); + } + } + break; + + //in case of Redlining and multiselection disable + case SID_SORT_ASCENDING: + case SID_SORT_DESCENDING: + case SCITEM_SORTDATA: + case SCITEM_SUBTDATA: + case SID_OPENDLG_PIVOTTABLE: + { + //! move ReadOnly check to idl flags + + if ( pDocSh->IsReadOnly() || rDoc.GetChangeTrack()!=nullptr || + GetViewData().IsMultiMarked() ) + { + rSet.DisableItem( nWhich ); + } + } + break; + + case SID_REIMPORT_DATA: + { + // only imported data without selection + ScDBData* pDBData = pTabViewShell->GetDBData(false,SC_DB_OLD); + if (!pDBData || !pDBData->HasImportParam() || pDBData->HasImportSelection() || + rDoc.GetChangeTrack()!=nullptr) + { + rSet.DisableItem( nWhich ); + } + } + break; + + case SID_VIEW_DATA_SOURCE_BROWSER: + { + if (!SvtModuleOptions().IsModuleInstalled(SvtModuleOptions::EModule::DATABASE)) + rSet.Put(SfxVisibilityItem(nWhich, false)); + else + // get state (BoolItem) from SfxViewFrame + pTabViewShell->GetViewFrame().GetSlotState( nWhich, nullptr, &rSet ); + } + break; + case SID_SBA_BRW_INSERT: + { + // SBA wants a sal_Bool-item, enabled + + rSet.Put(SfxBoolItem(nWhich, true)); + } + break; + + case SID_AUTO_FILTER: + case SID_AUTOFILTER_HIDE: + { + if (!bAutoFilterTested) + { + bAutoFilter = rDoc.HasAutoFilter( nPosX, nPosY, nTab ); + bAutoFilterTested = true; + } + if ( nWhich == SID_AUTO_FILTER ) + { + ScRange aDummy; + ScMarkType eMarkType = GetViewData().GetSimpleArea( aDummy); + if (eMarkType != SC_MARK_SIMPLE && eMarkType != SC_MARK_SIMPLE_FILTERED) + { + rSet.DisableItem( nWhich ); + } + else if (rDoc.GetDPAtBlock(aDummy)) + { + rSet.DisableItem( nWhich ); + } + else + rSet.Put( SfxBoolItem( nWhich, bAutoFilter ) ); + } + else + if (!bAutoFilter) + rSet.DisableItem( nWhich ); + } + break; + + case SID_UNFILTER: + { + SCCOL nStartCol, nEndCol; + SCROW nStartRow, nEndRow; + SCTAB nStartTab, nEndTab; + bool bAnyQuery = false; + + bool bSelected = (GetViewData().GetSimpleArea( + nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab ) + == SC_MARK_SIMPLE); + + if ( bSelected ) + { + if (nStartCol==nEndCol && nStartRow==nEndRow) + bSelected = false; + } + else + { + nStartCol = GetViewData().GetCurX(); + nStartRow = GetViewData().GetCurY(); + nStartTab = GetViewData().GetTabNo(); + } + + ScDBData* pDBData = bSelected + ? rDoc.GetDBAtArea( nStartTab, nStartCol, nStartRow, nEndCol, nEndRow ) + : rDoc.GetDBAtCursor( nStartCol, nStartRow, nStartTab, ScDBDataPortion::AREA ); + + if ( pDBData ) + { + ScQueryParam aParam; + pDBData->GetQueryParam( aParam ); + if ( aParam.GetEntry(0).bDoQuery ) + bAnyQuery = true; + } + + if ( !bAnyQuery ) + rSet.DisableItem( nWhich ); + } + break; + + case SID_DEFINE_DBNAME: + { + if ( pDocSh->IsDocShared() ) + { + rSet.DisableItem( nWhich ); + } + } + break; + case SID_DATA_PROVIDER: + break; + case SID_DATA_PROVIDER_REFRESH: + { + ScDocument& rViewDoc = GetViewData().GetDocument(); + auto& rDataMapper = rViewDoc.GetExternalDataMapper(); + if (rDataMapper.getDataSources().empty()) + rSet.DisableItem(nWhich); + } + break; + case SID_DATA_STREAMS: + case SID_DATA_STREAMS_PLAY: + case SID_DATA_STREAMS_STOP: + { + if ( !officecfg::Office::Common::Misc::ExperimentalMode::get() ) + rSet.DisableItem( nWhich ); + } + break; + case SID_TEXT_TO_COLUMNS: + { + ScRange aRange; + if ( !lcl_GetTextToColumnsRange( rData, aRange, true ) ) + { + rSet.DisableItem( nWhich ); + } + } + break; + case SID_MANAGE_XML_SOURCE: + break; + } + nWhich = aIter.NextWhich(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/cellsh3.cxx b/sc/source/ui/view/cellsh3.cxx new file mode 100644 index 0000000000..e6c89b6a2b --- /dev/null +++ b/sc/source/ui/view/cellsh3.cxx @@ -0,0 +1,1096 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using sc::TwipsToEvenHMM; + +namespace +{ +/// Rid ourselves of unwanted " quoted json characters. +OString escapeJSON(const OUString &aStr) +{ + OUString aEscaped = aStr; + aEscaped = aEscaped.replaceAll("\n", " "); + aEscaped = aEscaped.replaceAll("\"", "'"); + return OUStringToOString(aEscaped, RTL_TEXTENCODING_UTF8); +} + +void lcl_lokGetWholeFunctionList() +{ + const SfxViewShell* pViewShell = SfxViewShell::Current(); + if (!(comphelper::LibreOfficeKit::isActive() + && pViewShell && pViewShell->isLOKMobilePhone())) + return; + + const ScFunctionList* pFuncList = ScGlobal::GetStarCalcFunctionList(); + sal_uInt32 nListCount = pFuncList->GetCount(); + std::set aFuncNameOrderedSet; + for(sal_uInt32 i = 0; i < nListCount; ++i) + { + const ScFuncDesc* pDesc = pFuncList->GetFunction( i ); + if ( pDesc->mxFuncName ) + { + aFuncNameOrderedSet.insert(*pDesc->mxFuncName); + } + } + ScFunctionMgr* pFuncManager = ScGlobal::GetStarCalcFunctionMgr(); + if (!(pFuncManager && aFuncNameOrderedSet.size())) + return; + + OStringBuffer aPayload( + "{ \"wholeList\": true, " + "\"categories\": [ "); + + formula::FormulaHelper aHelper(pFuncManager); + sal_uInt32 nCategoryCount = pFuncManager->getCount(); + for (sal_uInt32 i = 0; i < nCategoryCount; ++i) + { + OUString sCategoryName = ScFunctionMgr::GetCategoryName(i); + aPayload.append("{" + "\"name\": \"" + + escapeJSON(sCategoryName) + + "\"}, "); + } + sal_Int32 nLen = aPayload.getLength(); + aPayload[nLen - 2] = ' '; + aPayload[nLen - 1] = ']'; + aPayload.append(", "); + + OUString aDescFuncNameStr; + aPayload.append("\"functions\": [ "); + sal_uInt32 nCurIndex = 0; + for (const OUString& aFuncNameStr : aFuncNameOrderedSet) + { + aDescFuncNameStr = aFuncNameStr + "()"; + sal_Int32 nNextFStart = 0; + const formula::IFunctionDescription* ppFDesc; + ::std::vector< OUString > aArgs; + OUString eqPlusFuncName = "=" + aDescFuncNameStr; + if ( aHelper.GetNextFunc( eqPlusFuncName, false, nNextFStart, nullptr, &ppFDesc, &aArgs ) ) + { + if ( ppFDesc && !ppFDesc->getFunctionName().isEmpty() ) + { + if (ppFDesc->getCategory()) + { + aPayload.append("{" + "\"index\": " + + OString::number(static_cast(nCurIndex)) + + ", " + "\"category\": " + + OString::number(static_cast(ppFDesc->getCategory()->getNumber())) + + ", " + "\"signature\": \"" + + escapeJSON(ppFDesc->getSignature()) + + "\", " + "\"description\": \"" + + escapeJSON(ppFDesc->getDescription()) + + "\"}, "); + } + } + } + ++nCurIndex; + } + nLen = aPayload.getLength(); + aPayload[nLen - 2] = ' '; + aPayload[nLen - 1] = ']'; + aPayload.append(" }"); + + OString s = aPayload.makeStringAndClear(); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CALC_FUNCTION_LIST, s); +} + +} // end namespace + +void ScCellShell::Execute( SfxRequest& rReq ) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + SfxBindings& rBindings = pTabViewShell->GetViewFrame().GetBindings(); + ScModule* pScMod = SC_MOD(); + const SfxItemSet* pReqArgs = rReq.GetArgs(); + sal_uInt16 nSlot = rReq.GetSlot(); + + if (nSlot != SID_CURRENTCELL) // this comes with MouseButtonUp + pTabViewShell->HideListBox(); // Autofilter-DropDown-Listbox + + if ( GetViewData().HasEditView( GetViewData().GetActivePart() ) ) + { + switch ( nSlot ) + { + // when opening a reference-dialog the subshell may not be switched + // (on closing the dialog StopEditShell is called) + case SID_OPENDLG_FUNCTION: + // inplace leads to trouble with EditShell ... + //! cannot always be switched ???? + if (!pTabViewShell->GetViewFrame().GetFrame().IsInPlace()) + pTabViewShell->SetDontSwitch(true); // do not switch off EditShell + [[fallthrough]]; + + case FID_CELL_FORMAT: + case SID_ENABLE_HYPHENATION: + case SID_DATA_SELECT: + case SID_OPENDLG_CONSOLIDATE: + case SID_OPENDLG_SOLVE: + case SID_OPENDLG_OPTSOLVER: + + pScMod->InputEnterHandler(); + pTabViewShell->UpdateInputHandler(); + + pTabViewShell->SetDontSwitch(false); + + break; + + default: + break; + } + } + + switch ( nSlot ) + { + case SID_STATUS_SELMODE: + if ( pReqArgs ) + { + /* 0: STD Click cancels selection + * 1: ER Click extends selection + * 2: ERG Click defines further selection + */ + sal_uInt16 nMode = static_cast(pReqArgs->Get( nSlot )).GetValue(); + + switch ( nMode ) + { + case 1: nMode = KEY_SHIFT; break; + case 2: nMode = KEY_MOD1; break; // control-key + case 0: + default: + nMode = 0; + } + + pTabViewShell->LockModifiers( nMode ); + } + else + { + // no arguments (also executed by double click on the status bar controller): + // advance to next selection mode + + sal_uInt16 nModifiers = pTabViewShell->GetLockedModifiers(); + switch ( nModifiers ) + { + case KEY_SHIFT: nModifiers = KEY_MOD1; break; // EXT -> ADD + case KEY_MOD1: nModifiers = 0; break; // ADD -> STD + default: nModifiers = KEY_SHIFT; break; // STD -> EXT + } + pTabViewShell->LockModifiers( nModifiers ); + } + + rBindings.Invalidate( SID_STATUS_SELMODE ); + rReq.Done(); + break; + + // SID_STATUS_SELMODE_NORM is not used ??? + + case SID_STATUS_SELMODE_NORM: + pTabViewShell->LockModifiers( 0 ); + rBindings.Invalidate( SID_STATUS_SELMODE ); + break; + + // SID_STATUS_SELMODE_ERG / SID_STATUS_SELMODE_ERW as toggles: + + case SID_STATUS_SELMODE_ERG: + if ( pTabViewShell->GetLockedModifiers() & KEY_MOD1 ) + pTabViewShell->LockModifiers( 0 ); + else + pTabViewShell->LockModifiers( KEY_MOD1 ); + rBindings.Invalidate( SID_STATUS_SELMODE ); + break; + + case SID_STATUS_SELMODE_ERW: + if ( pTabViewShell->GetLockedModifiers() & KEY_SHIFT ) + pTabViewShell->LockModifiers( 0 ); + else + pTabViewShell->LockModifiers( KEY_SHIFT ); + rBindings.Invalidate( SID_STATUS_SELMODE ); + break; + + case SID_ENTER_STRING: + { + if ( pReqArgs ) + { + // In the LOK case, we want to set the document modified state + // right away at the start of the edit, so that the content is + // saved even when the user leaves the document before hitting + // Enter + // NOTE: This also means we want to set the modified state + // regardless of the DontCommit parameter's value. + if (comphelper::LibreOfficeKit::isActive() && !GetViewData().GetDocShell()->IsModified()) + { + GetViewData().GetDocShell()->SetModified(); + rBindings.Invalidate(SID_SAVEDOC); + rBindings.Invalidate(SID_DOC_MODIFIED); + } + + OUString aStr( pReqArgs->Get( SID_ENTER_STRING ).GetValue() ); + const SfxPoolItem* pDontCommitItem; + bool bCommit = true; + if (pReqArgs->HasItem(FN_PARAM_1, &pDontCommitItem)) + bCommit = !(static_cast(pDontCommitItem)->GetValue()); + + ScInputHandler* pHdl = SC_MOD()->GetInputHdl( pTabViewShell ); + if (bCommit) + { + pTabViewShell->EnterData( GetViewData().GetCurX(), + GetViewData().GetCurY(), + GetViewData().GetTabNo(), + aStr, nullptr, + true /*bMatrixExpand*/); + } + else if (pHdl) + { + SC_MOD()->SetInputMode(SC_INPUT_TABLE); + + EditView* pTableView = pHdl->GetActiveView(); + pHdl->DataChanging(); + if (pTableView) + pTableView->GetEditEngine()->SetText(aStr); + pHdl->DataChanged(); + + SC_MOD()->SetInputMode(SC_INPUT_NONE); + } + + if ( !pHdl || !pHdl->IsInEnterHandler() ) + { + // UpdateInputHandler is needed after the cell content + // has changed, but if called from EnterHandler, UpdateInputHandler + // will be called later when moving the cursor. + pTabViewShell->UpdateInputHandler(); + } + + rReq.Done(); + + // no GrabFocus here, as otherwise on a Mac the tab jumps before the + // sideview, when the input was not finished + // (GrabFocus is called in KillEditView) + } + } + break; + + case SID_INSERT_MATRIX: + { + if ( pReqArgs ) + { + OUString aStr = static_cast(pReqArgs-> + Get( SID_INSERT_MATRIX )).GetValue(); + ScDocument& rDoc = GetViewData().GetDocument(); + pTabViewShell->EnterMatrix( aStr, rDoc.GetGrammar() ); + rReq.Done(); + } + } + break; + + case FID_INPUTLINE_ENTER: + case FID_INPUTLINE_BLOCK: + case FID_INPUTLINE_MATRIX: + { + if( pReqArgs == nullptr ) //XXX temporary HACK to avoid GPF + break; + + const ScInputStatusItem* pStatusItem + = static_cast(&pReqArgs-> + Get( FID_INPUTLINE_STATUS )); + + const ScAddress& aCursorPos = pStatusItem->GetPos(); + const OUString& aString = pStatusItem->GetString(); + const EditTextObject* pData = pStatusItem->GetEditData(); + + if (pData) + { + if (nSlot == FID_INPUTLINE_BLOCK) + { + pTabViewShell->EnterBlock( aString, pData ); + } + else if ( !aString.isEmpty() && ( aString[0] == '=' || aString[0] == '+' || aString[0] == '-' ) ) + { + pTabViewShell->EnterData( aCursorPos.Col(), aCursorPos.Row(), aCursorPos.Tab(), + aString, pData, true /*bMatrixExpand*/); + } + else + { + pTabViewShell->EnterData(aCursorPos.Col(), aCursorPos.Row(), aCursorPos.Tab(), *pData); + } + } + else + { + if (nSlot == FID_INPUTLINE_ENTER) + { + if ( + aCursorPos.Col() == GetViewData().GetCurX() && + aCursorPos.Row() == GetViewData().GetCurY() && + aCursorPos.Tab() == GetViewData().GetTabNo() + ) + { + SfxStringItem aItem( SID_ENTER_STRING, aString ); + + const SfxPoolItem* aArgs[2]; + aArgs[0] = &aItem; + aArgs[1] = nullptr; + rBindings.Execute( SID_ENTER_STRING, aArgs ); + } + else + { + pTabViewShell->EnterData( aCursorPos.Col(), + aCursorPos.Row(), + aCursorPos.Tab(), + aString, nullptr, + true /*bMatrixExpand*/); + rReq.Done(); + } + } + else if (nSlot == FID_INPUTLINE_BLOCK) + { + pTabViewShell->EnterBlock( aString, nullptr ); + rReq.Done(); + } + else + { + ScDocument& rDoc = GetViewData().GetDocument(); + pTabViewShell->EnterMatrix( aString, rDoc.GetGrammar() ); + rReq.Done(); + } + } + + pTabViewShell->SetAutoSpellData( + aCursorPos.Col(), aCursorPos.Row(), pStatusItem->GetMisspellRanges()); + + // no GrabFocus here, as otherwise on a Mac the tab jumps before the + // sideview, when the input was not finished + // (GrabFocus is called in KillEditView) + } + break; + + case SID_OPENDLG_FUNCTION: + { + const SfxViewShell* pViewShell = SfxViewShell::Current(); + if (comphelper::LibreOfficeKit::isActive() + && pViewShell && pViewShell->isLOKMobilePhone()) + { + // not set the dialog id in the mobile case or we would + // not be able to get cell address pasted in the edit view + // by just tapping on them + lcl_lokGetWholeFunctionList(); + } + else + { + sal_uInt16 nId = SID_OPENDLG_FUNCTION; + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + bool bVis = comphelper::LibreOfficeKit::isActive() || pWnd == nullptr; + pScMod->SetRefDialog( nId, bVis ); + } + rReq.Ignore(); + } + break; + + case SID_OPENDLG_CONSOLIDATE: + { + sal_uInt16 nId = ScConsolidateDlgWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + break; + + case SID_EASY_CONDITIONAL_FORMAT_DIALOG: + { + if (pReqArgs != nullptr) + { + const SfxPoolItem* pFormat; + if (pReqArgs->HasItem( FN_PARAM_1, &pFormat)) + { + sal_Int16 nFormat = static_cast(pFormat)->GetValue(); + sal_uInt16 nId = sc::ConditionalFormatEasyDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrame = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWindow = rViewFrame.GetChildWindow( nId ); + GetViewData().GetDocument().SetEasyConditionalFormatDialogData(std::make_unique(static_cast(nFormat))); + + pScMod->SetRefDialog( nId, pWindow == nullptr ); + } + } + } + break; + + case FID_CELL_FORMAT: + { + if ( pReqArgs != nullptr ) + { + + // set cell attribute without dialog: + + SfxItemSetFixed aEmptySet( *pReqArgs->GetPool() ); + + SfxItemSetFixed aNewSet( *pReqArgs->GetPool() ); + + const SfxPoolItem* pAttr = nullptr; + sal_uInt16 nWhich = 0; + + for ( nWhich=ATTR_PATTERN_START; nWhich<=ATTR_PATTERN_END; nWhich++ ) + if ( pReqArgs->GetItemState( nWhich, true, &pAttr ) == SfxItemState::SET ) + aNewSet.Put( *pAttr ); + + pTabViewShell->ApplyAttributes( aNewSet, aEmptySet ); + + rReq.Done(); + } + else + { + pTabViewShell->ExecuteCellFormatDlg( rReq, "" ); + } + } + break; + + case SID_ENABLE_HYPHENATION: + pTabViewShell->ExecuteCellFormatDlg(rReq, "alignment"); + break; + + case SID_PROPERTY_PANEL_CELLTEXT_DLG: + pTabViewShell->ExecuteCellFormatDlg( rReq, "font" ); + break; + + case SID_CELL_FORMAT_BORDER: + pTabViewShell->ExecuteCellFormatDlg( rReq, "borders" ); + break; + + case SID_CHAR_DLG_EFFECT: + pTabViewShell->ExecuteCellFormatDlg( rReq, "fonteffects" ); + break; + + case SID_OPENDLG_SOLVE: + { + sal_uInt16 nId = ScSolverDlgWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + break; + + case SID_OPENDLG_OPTSOLVER: + { + sal_uInt16 nId = ScOptSolverDlgWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + break; + + case SID_OPENDLG_TABOP: + { + sal_uInt16 nId = ScTabOpDlgWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + break; + + case SID_SCENARIOS: + { + ScDocument& rDoc = GetViewData().GetDocument(); + ScMarkData& rMark = GetViewData().GetMarkData(); + SCTAB nTab = GetViewData().GetTabNo(); + + if ( rDoc.IsScenario(nTab) ) + { + rMark.MarkToMulti(); + if ( rMark.IsMultiMarked() ) + { + + bool bExtend = rReq.IsAPI(); + if (!bExtend) + { + std::unique_ptr xQueryBox(Application::CreateMessageDialog(pTabViewShell->GetFrameWeld(), + VclMessageType::Question, VclButtonsType::YesNo, + ScResId(STR_UPDATE_SCENARIO))); + xQueryBox->set_default_response(RET_YES); + bExtend = xQueryBox->run() == RET_YES; + } + + if (bExtend) + { + pTabViewShell->ExtendScenario(); + rReq.Done(); + } + } + else if( ! rReq.IsAPI() ) + { + std::unique_ptr xErrorBox(Application::CreateMessageDialog(pTabViewShell->GetFrameWeld(), + VclMessageType::Warning, VclButtonsType::Ok, + ScResId(STR_NOAREASELECTED))); + xErrorBox->run(); + } + } + else + { + rMark.MarkToMulti(); + if ( rMark.IsMultiMarked() ) + { + SCTAB i=1; + OUString aBaseName; + OUString aName; + Color aColor; + ScScenarioFlags nFlags; + + OUString aTmp; + rDoc.GetName(nTab, aTmp); + aBaseName = aTmp + "_" + ScResId(STR_SCENARIO) + "_"; + + // first test, if the prefix is recognised as valid, + // else avoid only doubles + bool bPrefix = ScDocument::ValidTabName( aBaseName ); + OSL_ENSURE(bPrefix, "invalid sheet name"); + + while ( rDoc.IsScenario(nTab+i) ) + i++; + + bool bValid; + SCTAB nDummy; + do + { + aName = aBaseName + OUString::number( i ); + if (bPrefix) + bValid = rDoc.ValidNewTabName( aName ); + else + bValid = !rDoc.GetTable( aName, nDummy ); + ++i; + } + while ( !bValid && i <= MAXTAB + 2 ); + + if ( pReqArgs != nullptr ) + { + OUString aArgName; + OUString aArgComment; + if ( const SfxStringItem* pItem = pReqArgs->GetItemIfSet( SID_SCENARIOS ) ) + aArgName = pItem->GetValue(); + if ( const SfxStringItem* pItem = pReqArgs->GetItemIfSet( SID_NEW_TABLENAME ) ) + aArgComment = pItem->GetValue(); + + aColor = COL_LIGHTGRAY; // Default + nFlags = ScScenarioFlags::NONE; // not TwoWay + + pTabViewShell->MakeScenario( aArgName, aArgComment, aColor, nFlags ); + if( ! rReq.IsAPI() ) + rReq.Done(); + } + else + { + bool bSheetProtected = rDoc.IsTabProtected(nTab); + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr pNewDlg(pFact->CreateScNewScenarioDlg(pTabViewShell->GetFrameWeld(), aName, false, bSheetProtected)); + if ( pNewDlg->Execute() == RET_OK ) + { + OUString aComment; + pNewDlg->GetScenarioData( aName, aComment, aColor, nFlags ); + pTabViewShell->MakeScenario( aName, aComment, aColor, nFlags ); + + rReq.AppendItem( SfxStringItem( SID_SCENARIOS, aName ) ); + rReq.AppendItem( SfxStringItem( SID_NEW_TABLENAME, aComment ) ); + rReq.Done(); + } + } + } + else if( ! rReq.IsAPI() ) + { + pTabViewShell->ErrorMessage(STR_ERR_NEWSCENARIO); + } + } + } + break; + + case SID_SELECTALL: + { + pTabViewShell->SelectAll(); + rReq.Done(); + } + break; + + case FID_ROW_HEIGHT: + { + const SfxPoolItem* pRow; + const SfxUInt16Item* pHeight; + sal_uInt16 nHeight; + + if ( pReqArgs && (pHeight = pReqArgs->GetItemIfSet( FID_ROW_HEIGHT )) && + pReqArgs->HasItem( FN_PARAM_1, &pRow ) ) + { + std::vector aRanges; + SCCOLROW nRow = static_cast(pRow)->GetValue() - 1; + nHeight = pHeight->GetValue(); + ScMarkData& rMark = GetViewData().GetMarkData(); + + if ( rMark.IsRowMarked( static_cast(nRow) ) ) + { + aRanges = rMark.GetMarkedRowSpans(); + } + else + { + aRanges.emplace_back(nRow, nRow); + } + + pTabViewShell->SetWidthOrHeight(false, aRanges, SC_SIZE_DIRECT, o3tl::toTwips(nHeight, o3tl::Length::mm100)); + } + else if ( pReqArgs && (pHeight = pReqArgs->GetItemIfSet( FID_ROW_HEIGHT )) ) + { + nHeight = pHeight->GetValue(); + + // #101390#; the value of the macro is in HMM so use convertMm100ToTwip to convert + pTabViewShell->SetMarkedWidthOrHeight( false, SC_SIZE_DIRECT, + o3tl::toTwips(nHeight, o3tl::Length::mm100)); + if( ! rReq.IsAPI() ) + rReq.Done(); + } + else + { + ScViewData& rData = GetViewData(); + FieldUnit eMetric = SC_MOD()->GetAppOptions().GetAppMetric(); + sal_uInt16 nCurHeight = rData.GetDocument(). + GetRowHeight( rData.GetCurY(), + rData.GetTabNo() ); + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + VclPtr pDlg(pFact->CreateScMetricInputDlg( + pTabViewShell->GetFrameWeld(), "RowHeightDialog", nCurHeight, + rData.GetDocument().GetSheetOptimalMinRowHeight(rData.GetTabNo()), + eMetric, 2, MAX_ROW_HEIGHT)); + + pDlg->StartExecuteAsync([pDlg, pTabViewShell](sal_Int32 nResult){ + if (nResult == RET_OK) + { + SfxRequest pRequest(pTabViewShell->GetViewFrame(), FID_ROW_HEIGHT); + tools::Long nVal = pDlg->GetInputValue(); + pTabViewShell->SetMarkedWidthOrHeight( false, SC_SIZE_DIRECT, static_cast(nVal) ); + + // #101390#; the value of the macro should be in HMM so use TwipsToEvenHMM to convert + pRequest.AppendItem( SfxUInt16Item( FID_ROW_HEIGHT, static_cast(TwipsToEvenHMM(nVal)) ) ); + pRequest.Done(); + } + pDlg->disposeOnce(); + }); + } + } + break; + + case FID_ROW_OPT_HEIGHT: + { + if ( pReqArgs ) + { + const SfxUInt16Item& rUInt16Item = pReqArgs->Get( FID_ROW_OPT_HEIGHT ); + + // #101390#; the value of the macro is in HMM so use convertMm100ToTwip to convert + pTabViewShell->SetMarkedWidthOrHeight( false, SC_SIZE_OPTIMAL, + o3tl::toTwips(rUInt16Item.GetValue(), o3tl::Length::mm100) ); + ScGlobal::nLastRowHeightExtra = rUInt16Item.GetValue(); + + if( ! rReq.IsAPI() ) + rReq.Done(); + } + else + { + FieldUnit eMetric = SC_MOD()->GetAppOptions().GetAppMetric(); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + VclPtr pDlg(pFact->CreateScMetricInputDlg( + pTabViewShell->GetFrameWeld(), "OptimalRowHeightDialog", + ScGlobal::nLastRowHeightExtra, 0, eMetric, 2, MAX_EXTRA_HEIGHT)); + + pDlg->StartExecuteAsync([pDlg, pTabViewShell](sal_Int32 nResult){ + if ( nResult == RET_OK ) + { + SfxRequest pRequest(pTabViewShell->GetViewFrame(), FID_ROW_OPT_HEIGHT); + tools::Long nVal = pDlg->GetInputValue(); + pTabViewShell->SetMarkedWidthOrHeight( false, SC_SIZE_OPTIMAL, static_cast(nVal) ); + ScGlobal::nLastRowHeightExtra = nVal; + + // #101390#; the value of the macro should be in HMM so use TwipsToEvenHMM to convert + pRequest.AppendItem( SfxUInt16Item( FID_ROW_OPT_HEIGHT, static_cast(TwipsToEvenHMM(nVal)) ) ); + pRequest.Done(); + } + pDlg->disposeOnce(); + }); + } + } + break; + + case FID_COL_WIDTH: + { + const SfxPoolItem* pColumn; + const SfxUInt16Item* pWidth; + sal_uInt16 nWidth; + + if ( pReqArgs && (pWidth = pReqArgs->GetItemIfSet( FID_COL_WIDTH )) && + pReqArgs->HasItem( FN_PARAM_1, &pColumn ) ) + { + std::vector aRanges; + SCCOLROW nColumn = static_cast(pColumn)->GetValue() - 1; + nWidth = pWidth->GetValue(); + ScMarkData& rMark = GetViewData().GetMarkData(); + + if ( rMark.IsColumnMarked( static_cast(nColumn) ) ) + { + aRanges = rMark.GetMarkedColSpans(); + } + else + { + aRanges.emplace_back(nColumn, nColumn); + } + + pTabViewShell->SetWidthOrHeight(true, aRanges, SC_SIZE_DIRECT, o3tl::toTwips(nWidth, o3tl::Length::mm100)); + } + else if ( pReqArgs && (pWidth = pReqArgs->GetItemIfSet( FID_COL_WIDTH )) ) + { + nWidth = pWidth->GetValue(); + + // #101390#; the value of the macro is in HMM so use convertMm100ToTwip to convert + pTabViewShell->SetMarkedWidthOrHeight( true, SC_SIZE_DIRECT, + o3tl::toTwips(nWidth, o3tl::Length::mm100)); + if( ! rReq.IsAPI() ) + rReq.Done(); + } + else + { + FieldUnit eMetric = SC_MOD()->GetAppOptions().GetAppMetric(); + ScViewData& rData = GetViewData(); + sal_uInt16 nCurHeight = rData.GetDocument(). + GetColWidth( rData.GetCurX(), + rData.GetTabNo() ); + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + VclPtr pDlg(pFact->CreateScMetricInputDlg( + pTabViewShell->GetFrameWeld(), "ColWidthDialog", nCurHeight, + STD_COL_WIDTH, eMetric, 2, MAX_COL_WIDTH)); + + pDlg->StartExecuteAsync([pDlg, pTabViewShell](sal_Int32 nResult){ + if ( nResult == RET_OK ) + { + SfxRequest pRequest(pTabViewShell->GetViewFrame(), FID_COL_WIDTH); + tools::Long nVal = pDlg->GetInputValue(); + pTabViewShell->SetMarkedWidthOrHeight( true, SC_SIZE_DIRECT, static_cast(nVal) ); + + // #101390#; the value of the macro should be in HMM so use TwipsToEvenHMM to convert + pRequest.AppendItem( SfxUInt16Item( FID_COL_WIDTH, static_cast(TwipsToEvenHMM(nVal))) ); + pRequest.Done(); + } + pDlg->disposeOnce(); + }); + } + } + break; + + case FID_COL_OPT_WIDTH: + { + if ( pReqArgs ) + { + const SfxUInt16Item& rUInt16Item = pReqArgs->Get( FID_COL_OPT_WIDTH ); + + // #101390#; the value of the macro is in HMM so use convertMm100ToTwip to convert + pTabViewShell->SetMarkedWidthOrHeight( true, SC_SIZE_OPTIMAL, + o3tl::toTwips(rUInt16Item.GetValue(), o3tl::Length::mm100) ); + ScGlobal::nLastColWidthExtra = rUInt16Item.GetValue(); + + if( ! rReq.IsAPI() ) + rReq.Done(); + } + else + { + FieldUnit eMetric = SC_MOD()->GetAppOptions().GetAppMetric(); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + VclPtr pDlg(pFact->CreateScMetricInputDlg( + pTabViewShell->GetFrameWeld(), "OptimalColWidthDialog", + ScGlobal::nLastColWidthExtra, STD_EXTRA_WIDTH, eMetric, 2, MAX_EXTRA_WIDTH)); + + pDlg->StartExecuteAsync([pDlg, pTabViewShell](sal_Int32 nResult){ + SfxRequest pRequest(pTabViewShell->GetViewFrame(), FID_COL_OPT_WIDTH); + if ( nResult == RET_OK ) + { + tools::Long nVal = pDlg->GetInputValue(); + pTabViewShell->SetMarkedWidthOrHeight( true, SC_SIZE_OPTIMAL, static_cast(nVal) ); + ScGlobal::nLastColWidthExtra = nVal; + + // #101390#; the value of the macro should be in HMM so use TwipsToEvenHMM to convert + pRequest.AppendItem( SfxUInt16Item( FID_COL_OPT_WIDTH, static_cast(TwipsToEvenHMM(nVal)) ) ); + pRequest.Done(); + } + pDlg->disposeOnce(); + }); + } + } + break; + + case FID_COL_OPT_DIRECT: + pTabViewShell->SetMarkedWidthOrHeight( true, SC_SIZE_OPTIMAL, STD_EXTRA_WIDTH ); + rReq.Done(); + break; + + case FID_ROW_HIDE: + pTabViewShell->SetMarkedWidthOrHeight( false, SC_SIZE_DIRECT, 0 ); + rReq.Done(); + break; + case FID_ROW_SHOW: + pTabViewShell->SetMarkedWidthOrHeight( false, SC_SIZE_SHOW, 0 ); + rReq.Done(); + break; + case FID_COL_HIDE: + pTabViewShell->SetMarkedWidthOrHeight( true, SC_SIZE_DIRECT, 0 ); + rReq.Done(); + break; + case FID_COL_SHOW: + pTabViewShell->SetMarkedWidthOrHeight( true, SC_SIZE_SHOW, 0 ); + rReq.Done(); + break; + + case SID_CELL_FORMAT_RESET: + { + pTabViewShell->DeleteContents( InsertDeleteFlags::HARDATTR | InsertDeleteFlags::EDITATTR ); + rReq.Done(); + } + break; + + case FID_MERGE_ON: + case FID_MERGE_OFF: + case FID_MERGE_TOGGLE: + { + if ( !GetViewData().GetDocument().GetChangeTrack() ) + { + // test whether to merge or to split + bool bMerge = false; + bool bCenter = false; + switch( nSlot ) + { + case FID_MERGE_ON: + bMerge = true; + break; + case FID_MERGE_OFF: + bMerge = false; + break; + case FID_MERGE_TOGGLE: + { + bCenter = true; + std::unique_ptr pItem; + if( rBindings.QueryState( nSlot, pItem ) >= SfxItemState::DEFAULT ) + bMerge = !static_cast< SfxBoolItem* >( pItem.get() )->GetValue(); + } + break; + } + + if( bMerge ) + { + // merge - check if to move contents of covered cells + bool bMoveContents = false; + bool bApi = rReq.IsAPI(); + const SfxPoolItem* pItem; + if ( pReqArgs && + pReqArgs->GetItemState(nSlot, true, &pItem) == SfxItemState::SET ) + { + assert(dynamic_cast( pItem) && "wrong item"); + bMoveContents = static_cast(pItem)->GetValue(); + } + + pTabViewShell->MergeCells( bApi, bMoveContents, bCenter, nSlot ); + } + else + { + // split cells + if (pTabViewShell->RemoveMerge()) + { + rBindings.Invalidate( nSlot ); + rReq.Done(); + } + } + break; + } + } + break; + + case SID_AUTOFORMAT: + { + weld::Window* pDlgParent = pTabViewShell->GetFrameWeld(); + SCCOL nStartCol; + SCROW nStartRow; + SCTAB nStartTab; + SCCOL nEndCol; + SCROW nEndRow; + SCTAB nEndTab; + + const ScMarkData& rMark = GetViewData().GetMarkData(); + if ( !rMark.IsMarked() && !rMark.IsMultiMarked() ) + pTabViewShell->MarkDataArea(); + + GetViewData().GetSimpleArea( nStartCol,nStartRow,nStartTab, + nEndCol,nEndRow,nEndTab ); + + if ( ( std::abs(nEndCol-nStartCol) > 1 ) + && ( std::abs(nEndRow-nStartRow) > 1 ) ) + { + if ( pReqArgs ) + { + const SfxStringItem& rNameItem = pReqArgs->Get( SID_AUTOFORMAT ); + ScAutoFormat* pFormat = ScGlobal::GetOrCreateAutoFormat(); + ScAutoFormat::const_iterator it = pFormat->find(rNameItem.GetValue()); + ScAutoFormat::const_iterator itBeg = pFormat->begin(); + size_t nIndex = std::distance(itBeg, it); + + pTabViewShell->AutoFormat( nIndex ); + + if( ! rReq.IsAPI() ) + rReq.Done(); + } + else + { + ScGlobal::ClearAutoFormat(); + std::unique_ptr pNewEntry(pTabViewShell->CreateAutoFormatData()); + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr pDlg(pFact->CreateScAutoFormatDlg(pDlgParent, ScGlobal::GetOrCreateAutoFormat(), pNewEntry.get(), GetViewData())); + + if ( pDlg->Execute() == RET_OK ) + { + ScEditableTester aTester( pTabViewShell ); + if ( !aTester.IsEditable() ) + { + pTabViewShell->ErrorMessage(aTester.GetMessageId()); + } + else + { + pTabViewShell->AutoFormat( pDlg->GetIndex() ); + + rReq.AppendItem( SfxStringItem( SID_AUTOFORMAT, pDlg->GetCurrFormatName() ) ); + rReq.Done(); + } + } + } + } + else + { + std::unique_ptr xErrorBox(Application::CreateMessageDialog(pDlgParent, + VclMessageType::Warning, VclButtonsType::Ok, + ScResId(STR_INVALID_AFAREA))); + xErrorBox->run(); + } + } + break; + + case SID_CANCEL: + { + if (GetViewData().HasEditView(GetViewData().GetActivePart())) + pScMod->InputCancelHandler(); + else if (pTabViewShell->HasPaintBrush()) + pTabViewShell->ResetBrushDocument(); // abort format paint brush + else if (pTabViewShell->HasHintWindow()) + pTabViewShell->RemoveHintWindow(); + else if( ScViewUtil::IsFullScreen( *pTabViewShell ) ) + ScViewUtil::SetFullScreen( *pTabViewShell, false ); + else + { + // TODO/LATER: when is this code executed? + pTabViewShell->Escape(); + } + } + break; + + case SID_ACCEPT_FORMULA: + { + if (GetViewData().HasEditView(GetViewData().GetActivePart())) + pScMod->InputEnterHandler(); + } + break; + + case SID_START_FORMULA: + { + ScInputHandler* pInputHandler = pScMod->GetInputHdl(); + if (pInputHandler && pInputHandler->GetInputWindow()) + pInputHandler->GetInputWindow()->StartFormula(); + } + break; + + case SID_DATA_SELECT: + pTabViewShell->StartDataSelect(); + break; + + case SID_DETECTIVE_FILLMODE: + { + bool bOldMode = pTabViewShell->IsAuditShell(); + pTabViewShell->SetAuditShell( !bOldMode ); + pTabViewShell->Invalidate( nSlot ); + } + break; + + case FID_INPUTLINE_STATUS: + OSL_FAIL("Execute from InputLine status"); + break; + + case SID_STATUS_DOCPOS: + // Launch navigator. + GetViewData().GetDispatcher().Execute( + SID_NAVIGATOR, SfxCallMode::SYNCHRON|SfxCallMode::RECORD ); + break; + + case SID_MARKAREA: + // called from Basic at the hidden view to select a range in the visible view + OSL_FAIL("old slot SID_MARKAREA"); + break; + + default: + OSL_FAIL("ScCellShell::Execute: unknown slot"); + break; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/cellsh4.cxx b/sc/source/ui/view/cellsh4.cxx new file mode 100644 index 0000000000..bacbf2b98f --- /dev/null +++ b/sc/source/ui/view/cellsh4.cxx @@ -0,0 +1,522 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +void ScCellShell::ExecuteCursor( SfxRequest& rReq ) +{ + ScViewData& rData = GetViewData(); + ScTabViewShell* pTabViewShell = rData.GetViewShell(); + const SfxItemSet* pReqArgs = rReq.GetArgs(); + sal_uInt16 nSlotId = rReq.GetSlot(); + SCCOLROW nRepeat = 1; + bool bSel = false; + bool bKeep = false; + + if ( pReqArgs != nullptr ) + { + const SfxPoolItem* pItem; + if (pReqArgs->HasItem(FN_PARAM_1, &pItem)) + nRepeat = static_cast(static_cast(pItem)->GetValue()); + if (pReqArgs->HasItem(FN_PARAM_2, &pItem)) + bSel = static_cast(pItem)->GetValue(); + } + else + { + // evaluate locked selection mode + + sal_uInt16 nLocked = pTabViewShell->GetLockedModifiers(); + if ( nLocked & KEY_SHIFT ) + bSel = true; // EXT + else if ( nLocked & KEY_MOD1 ) + { + // ADD mode: keep the selection, start a new block when marking with shift again + bKeep = true; + } + } + + if (bSel) + { + switch (nSlotId) + { + case SID_CURSORDOWN: + rReq.SetSlot(SID_CURSORDOWN_SEL); + break; + case SID_CURSORUP: + rReq.SetSlot(SID_CURSORUP_SEL); + break; + case SID_CURSORRIGHT: + rReq.SetSlot(SID_CURSORRIGHT_SEL); + break; + case SID_CURSORLEFT: + rReq.SetSlot(SID_CURSORLEFT_SEL); + break; + case SID_CURSORPAGEDOWN: + rReq.SetSlot(SID_CURSORPAGEDOWN_SEL); + break; + case SID_CURSORPAGEUP: + rReq.SetSlot(SID_CURSORPAGEUP_SEL); + break; + case SID_CURSORBLKDOWN: + rReq.SetSlot(SID_CURSORBLKDOWN_SEL); + break; + case SID_CURSORBLKUP: + rReq.SetSlot(SID_CURSORBLKUP_SEL); + break; + case SID_CURSORBLKRIGHT: + rReq.SetSlot(SID_CURSORBLKRIGHT_SEL); + break; + case SID_CURSORBLKLEFT: + rReq.SetSlot(SID_CURSORBLKLEFT_SEL); + break; + default: + ; + } + ExecuteCursorSel(rReq); + return; + } + + SCCOLROW nRTLSign = 1; + if ( rData.GetDocument().IsLayoutRTL( rData.GetTabNo() ) ) + { + //! evaluate cursor movement option? + nRTLSign = -1; + } + + // once extra, so that the cursor will not be painted too often with ExecuteInputDirect: + pTabViewShell->HideAllCursors(); + + // #i123629# + if( pTabViewShell->GetCurObjectSelectionType() == OST_Editing ) + pTabViewShell->SetForceFocusOnCurCell(true); + else + pTabViewShell->SetForceFocusOnCurCell(false); + + // If ScrollLock key is active, cell cursor stays on the current cell while + // scrolling the grid. + bool bScrollLock = false; + // tdf#112876 - allow to disable for special keyboards + if (officecfg::Office::Calc::Input::UseScrollLock::get()) + { + KeyIndicatorState eState = pFrameWin->GetIndicatorState(); + if (eState & KeyIndicatorState::SCROLLLOCK) + bScrollLock = true; + } + //OS: once for all should do, however! + pTabViewShell->ExecuteInputDirect(); + switch ( nSlotId ) + { + case SID_CURSORDOWN: + if (bScrollLock) + pTabViewShell->ScrollY( nRepeat, SC_SPLIT_BOTTOM ); + else + pTabViewShell->MoveCursorRel( 0, nRepeat, SC_FOLLOW_LINE, bSel, bKeep ); + break; + + case SID_CURSORBLKDOWN: + pTabViewShell->MoveCursorArea( 0, nRepeat, SC_FOLLOW_JUMP, bSel, bKeep, !rReq.IsAPI() ); + break; + + case SID_CURSORUP: + if (bScrollLock) + pTabViewShell->ScrollY( -nRepeat, SC_SPLIT_BOTTOM); + else + pTabViewShell->MoveCursorRel( 0, -nRepeat, SC_FOLLOW_LINE, bSel, bKeep ); + break; + + case SID_CURSORBLKUP: + pTabViewShell->MoveCursorArea( 0, -nRepeat, SC_FOLLOW_JUMP, bSel, bKeep, !rReq.IsAPI() ); + break; + + case SID_CURSORLEFT: + if (bScrollLock) + pTabViewShell->ScrollX( static_cast(-nRepeat * nRTLSign), SC_SPLIT_LEFT); + else + pTabViewShell->MoveCursorRel( static_cast(-nRepeat * nRTLSign), 0, SC_FOLLOW_LINE, bSel, bKeep ); + break; + + case SID_CURSORBLKLEFT: + pTabViewShell->MoveCursorArea( static_cast(-nRepeat * nRTLSign), 0, SC_FOLLOW_JUMP, bSel, bKeep, !rReq.IsAPI() ); + break; + + case SID_CURSORRIGHT: + if (bScrollLock) + pTabViewShell->ScrollX( static_cast(nRepeat * nRTLSign), SC_SPLIT_LEFT); + else + pTabViewShell->MoveCursorRel( static_cast(nRepeat * nRTLSign), 0, SC_FOLLOW_LINE, bSel, bKeep ); + break; + + case SID_CURSORBLKRIGHT: + pTabViewShell->MoveCursorArea( static_cast(nRepeat * nRTLSign), 0, SC_FOLLOW_JUMP, bSel, bKeep, !rReq.IsAPI() ); + break; + + case SID_CURSORPAGEDOWN: + if (bScrollLock) + { + SCCOL nPageX; + SCROW nPageY; + pTabViewShell->GetPageMoveEndPosition( 0, nRepeat, nPageX, nPageY); + pTabViewShell->ScrollY( nPageY, SC_SPLIT_BOTTOM); + } + else + pTabViewShell->MoveCursorPage( 0, nRepeat, SC_FOLLOW_FIX, bSel, bKeep ); + break; + + case SID_CURSORPAGEUP: + if (bScrollLock) + { + SCCOL nPageX; + SCROW nPageY; + pTabViewShell->GetPageMoveEndPosition( 0, nRepeat, nPageX, nPageY); + pTabViewShell->ScrollY( -nPageY, SC_SPLIT_BOTTOM); + } + else + pTabViewShell->MoveCursorPage( 0, -nRepeat, SC_FOLLOW_FIX, bSel, bKeep ); + break; + + case SID_CURSORPAGERIGHT_: //XXX !!! + if (bScrollLock) + { + SCCOL nPageX; + SCROW nPageY; + pTabViewShell->GetPageMoveEndPosition( static_cast(nRepeat), 0, nPageX, nPageY); + pTabViewShell->ScrollX( nPageX, SC_SPLIT_LEFT); + } + else + pTabViewShell->MoveCursorPage( static_cast(nRepeat), 0, SC_FOLLOW_FIX, bSel, bKeep ); + break; + + case SID_CURSORPAGELEFT_: //XXX !!! + if (bScrollLock) + { + SCCOL nPageX; + SCROW nPageY; + pTabViewShell->GetPageMoveEndPosition( static_cast(nRepeat), 0, nPageX, nPageY); + pTabViewShell->ScrollX( -nPageX, SC_SPLIT_LEFT); + } + else + pTabViewShell->MoveCursorPage( static_cast(-nRepeat), 0, SC_FOLLOW_FIX, bSel, bKeep ); + break; + + default: + OSL_FAIL("Unknown message in ViewShell (Cursor)"); + return; + } + + pTabViewShell->ShowAllCursors(); + + rReq.AppendItem( SfxInt16Item(FN_PARAM_1, static_cast(nRepeat)) ); + rReq.AppendItem( SfxBoolItem(FN_PARAM_2, bSel) ); + rReq.Done(); +} + +void ScCellShell::GetStateCursor( SAL_UNUSED_PARAMETER SfxItemSet& /* rSet */ ) +{ +} + +void ScCellShell::ExecuteCursorSel( SfxRequest& rReq ) +{ + sal_uInt16 nSlotId = rReq.GetSlot(); + ScTabViewShell* pViewShell = GetViewData().GetViewShell(); + ScInputHandler* pInputHdl = pViewShell->GetInputHandler(); + pViewShell->HideAllCursors(); + if (pInputHdl && pInputHdl->IsInputMode()) + { + // the current cell is in edit mode. Commit the text before moving on. + pViewShell->ExecuteInputDirect(); + } + + SCCOLROW nRepeat = 1; + const SfxItemSet* pReqArgs = rReq.GetArgs(); + // get repetition + if ( pReqArgs != nullptr ) + { + const SfxPoolItem* pItem; + if (pReqArgs->HasItem(FN_PARAM_1, &pItem)) + nRepeat = static_cast(static_cast(pItem)->GetValue()); + } + + SCROW nMovY = nRepeat; + // Horizontal direction depends on whether or not the UI language is RTL. + SCCOL nMovX = nRepeat; + if (GetViewData().GetDocument().IsLayoutRTL(GetViewData().GetTabNo())) + { + // mirror horizontal movement for right-to-left mode. + nMovX = -nRepeat; + } + + switch (nSlotId) + { + case SID_CURSORDOWN_SEL: + pViewShell->ExpandBlock(0, nMovY, SC_FOLLOW_LINE); + break; + case SID_CURSORUP_SEL: + pViewShell->ExpandBlock(0, -nMovY, SC_FOLLOW_LINE); + break; + case SID_CURSORRIGHT_SEL: + pViewShell->ExpandBlock(nMovX, 0, SC_FOLLOW_LINE); + break; + case SID_CURSORLEFT_SEL: + pViewShell->ExpandBlock(-nMovX, 0, SC_FOLLOW_LINE); + break; + case SID_CURSORPAGEUP_SEL: + pViewShell->ExpandBlockPage(0, -nMovY); + break; + case SID_CURSORPAGEDOWN_SEL: + pViewShell->ExpandBlockPage(0, nMovY); + break; + case SID_CURSORPAGERIGHT_SEL: + pViewShell->ExpandBlockPage(nMovX, 0); + break; + case SID_CURSORPAGELEFT_SEL: + pViewShell->ExpandBlockPage(-nMovX, 0); + break; + case SID_CURSORBLKDOWN_SEL: + pViewShell->ExpandBlockArea(0, nMovY); + break; + case SID_CURSORBLKUP_SEL: + pViewShell->ExpandBlockArea(0, -nMovY); + break; + case SID_CURSORBLKRIGHT_SEL: + pViewShell->ExpandBlockArea(nMovX , 0); + break; + case SID_CURSORBLKLEFT_SEL: + pViewShell->ExpandBlockArea(-nMovX, 0); + break; + default: + ; + } + pViewShell->ShowAllCursors(); + + rReq.AppendItem( SfxInt16Item(FN_PARAM_1,static_cast(nRepeat)) ); + rReq.Done(); +} + +void ScCellShell::ExecuteMove( SfxRequest& rReq ) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + sal_uInt16 nSlotId = rReq.GetSlot(); + const SfxItemSet* pReqArgs = rReq.GetArgs(); + + if(nSlotId != SID_CURSORTOPOFSCREEN && nSlotId != SID_CURSORENDOFSCREEN) + pTabViewShell->ExecuteInputDirect(); + switch ( nSlotId ) + { + case SID_NEXT_TABLE: + case SID_NEXT_TABLE_SEL: + pTabViewShell->SelectNextTab( 1, (nSlotId == SID_NEXT_TABLE_SEL) ); + break; + + case SID_PREV_TABLE: + case SID_PREV_TABLE_SEL: + pTabViewShell->SelectNextTab( -1, (nSlotId == SID_PREV_TABLE_SEL) ); + break; + + // cursor movements in range do not originate from Basic, + // because the ScSbxRange-object changes the marking at input + + case SID_NEXT_UNPROTECT: + pTabViewShell->FindNextUnprot( false, !rReq.IsAPI() ); + break; + + case SID_PREV_UNPROTECT: + pTabViewShell->FindNextUnprot( true, !rReq.IsAPI() ); + break; + + case SID_CURSORENTERUP: + if (rReq.IsAPI()) + pTabViewShell->MoveCursorRel( 0, -1, SC_FOLLOW_LINE, false ); + else + pTabViewShell->MoveCursorEnter( true ); + break; + + case SID_CURSORENTERDOWN: + if (rReq.IsAPI()) + pTabViewShell->MoveCursorRel( 0, 1, SC_FOLLOW_LINE, false ); + else + pTabViewShell->MoveCursorEnter( false ); + break; + + case SID_SELECT_COL: + { + const SfxPoolItem* pColItem; + const SfxPoolItem* pModifierItem; + if ( pReqArgs && pReqArgs->HasItem( FN_PARAM_1, &pColItem ) && + pReqArgs->HasItem( FN_PARAM_2, &pModifierItem ) ) + { + SCCOL nCol = static_cast(static_cast(pColItem)->GetValue()); + sal_Int16 nModifier = static_cast(pModifierItem)->GetValue(); + + pTabViewShell->MarkColumns( nCol, nModifier ); + } + else + pTabViewShell->MarkColumns(); + } + break; + + case SID_SELECT_ROW: + { + const SfxPoolItem* pRowItem; + const SfxPoolItem* pModifierItem; + if ( pReqArgs && pReqArgs->HasItem( FN_PARAM_1, &pRowItem ) && + pReqArgs->HasItem( FN_PARAM_2, &pModifierItem ) ) + { + SCROW nRow = static_cast(static_cast(pRowItem)->GetValue()); + sal_Int16 nModifier = static_cast(pModifierItem)->GetValue(); + + pTabViewShell->MarkRows( nRow, nModifier ); + } + else + pTabViewShell->MarkRows(); + } + break; + + case SID_SELECT_NONE: + pTabViewShell->Unmark(); + break; + + case SID_ALIGNCURSOR: + pTabViewShell->AlignToCursor( GetViewData().GetCurX(), GetViewData().GetCurY(), SC_FOLLOW_JUMP ); + break; + + case SID_MARKDATAAREA: + pTabViewShell->MarkDataArea(); + break; + + case SID_MARKARRAYFORMULA: + pTabViewShell->MarkMatrixFormula(); + break; + + case SID_SETINPUTMODE: + SC_MOD()->SetInputMode( SC_INPUT_TABLE ); + break; + + case SID_FOCUS_INPUTLINE: + { + ScInputHandler* pHdl = SC_MOD()->GetInputHdl( pTabViewShell ); + if (pHdl) + { + ScInputWindow* pWin = pHdl->GetInputWindow(); + if (pWin) + pWin->SwitchToTextWin(); + } + } + break; + + case SID_CURSORTOPOFSCREEN: + pTabViewShell->MoveCursorScreen( 0, -1, SC_FOLLOW_LINE, false ); + break; + + case SID_CURSORENDOFSCREEN: + pTabViewShell->MoveCursorScreen( 0, 1, SC_FOLLOW_LINE, false ); + break; + + default: + OSL_FAIL("Unknown message in ViewShell (Cursor)"); + return; + } + + rReq.Done(); +} + +void ScCellShell::ExecutePageSel( SfxRequest& rReq ) +{ + sal_uInt16 nSlotId = rReq.GetSlot(); + switch ( nSlotId ) + { + case SID_CURSORHOME_SEL: rReq.SetSlot( SID_CURSORHOME ); break; + case SID_CURSOREND_SEL: rReq.SetSlot( SID_CURSOREND ); break; + case SID_CURSORTOPOFFILE_SEL: rReq.SetSlot( SID_CURSORTOPOFFILE ); break; + case SID_CURSORENDOFFILE_SEL: rReq.SetSlot( SID_CURSORENDOFFILE ); break; + default: + OSL_FAIL("Unknown message in ViewShell (ExecutePageSel)"); + return; + } + rReq.AppendItem( SfxBoolItem(FN_PARAM_2, true) ); + ExecuteSlot( rReq, GetInterface() ); +} + +void ScCellShell::ExecutePage( SfxRequest& rReq ) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + const SfxItemSet* pReqArgs = rReq.GetArgs(); + sal_uInt16 nSlotId = rReq.GetSlot(); + bool bSel = false; + bool bKeep = false; + + if ( pReqArgs != nullptr ) + { + const SfxPoolItem* pItem; + if (pReqArgs->HasItem(FN_PARAM_2, &pItem)) + bSel = static_cast(pItem)->GetValue(); + } + else + { + // evaluate locked selection mode + + sal_uInt16 nLocked = pTabViewShell->GetLockedModifiers(); + if ( nLocked & KEY_SHIFT ) + bSel = true; // EXT + else if ( nLocked & KEY_MOD1 ) + { + // ADD mode: keep the selection, start a new block when marking with shift again + bKeep = true; + } + } + + pTabViewShell->ExecuteInputDirect(); + switch ( nSlotId ) + { + case SID_CURSORHOME: + pTabViewShell->MoveCursorEnd( -1, 0, SC_FOLLOW_LINE, bSel, bKeep ); + break; + + case SID_CURSOREND: + pTabViewShell->MoveCursorEnd( 1, 0, SC_FOLLOW_JUMP, bSel, bKeep ); + break; + + case SID_CURSORTOPOFFILE: + pTabViewShell->MoveCursorEnd( -1, -1, SC_FOLLOW_LINE, bSel, bKeep ); + break; + + case SID_CURSORENDOFFILE: + pTabViewShell->MoveCursorEnd( 1, 1, SC_FOLLOW_JUMP_END, bSel, bKeep ); + break; + + default: + OSL_FAIL("Unknown message in ViewShell (ExecutePage)"); + return; + } + + rReq.AppendItem( SfxBoolItem(FN_PARAM_2, bSel) ); + rReq.Done(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/cliputil.cxx b/sc/source/ui/view/cliputil.cxx new file mode 100644 index 0000000000..9c7d25db10 --- /dev/null +++ b/sc/source/ui/view/cliputil.cxx @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace +{ + +/// Paste only if SfxClassificationHelper recommends so. +bool lcl_checkClassification(ScDocument* pSourceDoc, const ScDocument& rDestinationDoc) +{ + if (!pSourceDoc) + return true; + + ScClipOptions* pSourceOptions = pSourceDoc->GetClipOptions(); + ScDocShell* pDestinationShell = rDestinationDoc.GetDocumentShell(); + if (!pSourceOptions || !pDestinationShell) + return true; + + SfxClassificationCheckPasteResult eResult = SfxClassificationHelper::CheckPaste(pSourceOptions->m_xDocumentProperties, pDestinationShell->getDocProperties()); + return SfxClassificationHelper::ShowPasteInfo(eResult); +} + +} + +void ScClipUtil::PasteFromClipboard( ScViewData& rViewData, ScTabViewShell* pTabViewShell, bool bShowDialog ) +{ + const ScTransferObj* pOwnClip = ScTransferObj::GetOwnClipboard(ScTabViewShell::GetClipData(rViewData.GetActiveWin())); + ScDocument& rThisDoc = rViewData.GetDocument(); + SCCOL nThisCol = rViewData.GetCurX(); + SCROW nThisRow = rViewData.GetCurY(); + SCTAB nThisTab = rViewData.GetTabNo(); + ScDPObject* pDPObj = rThisDoc.GetDPAtCursor( nThisCol, nThisRow, nThisTab ); + + if ( pOwnClip && pDPObj ) + { + // paste from Calc into DataPilot table: sort (similar to drag & drop) + + ScDocument* pClipDoc = pOwnClip->GetDocument(); + SCTAB nSourceTab = pOwnClip->GetVisibleTab(); + + SCCOL nClipStartX; + SCROW nClipStartY; + SCCOL nClipEndX; + SCROW nClipEndY; + pClipDoc->GetClipStart( nClipStartX, nClipStartY ); + pClipDoc->GetClipArea( nClipEndX, nClipEndY, true ); + nClipEndX = nClipEndX + nClipStartX; + nClipEndY = nClipEndY + nClipStartY; // GetClipArea returns the difference + + ScRange aSource( nClipStartX, nClipStartY, nSourceTab, nClipEndX, nClipEndY, nSourceTab ); + bool bDone = pTabViewShell->DataPilotMove( aSource, rViewData.GetCurPos() ); + if ( !bDone ) + pTabViewShell->ErrorMessage( STR_ERR_DATAPILOT_INPUT ); + } + else + { + // normal paste + weld::WaitObject aWait( rViewData.GetDialogParent() ); + if (!pOwnClip) + { + pTabViewShell->PasteFromSystem(); + // Anchor To Cell rather than To Page + ScDrawView* pDrawView = pTabViewShell->GetScDrawView(); + if(pDrawView && 1 == pDrawView->GetMarkedObjectCount()) + { + SdrObject* pPickObj = pDrawView->GetMarkedObjectByIndex(0); + if(pPickObj) + { + ScDrawLayer::SetCellAnchoredFromPosition( *pPickObj, rThisDoc, nThisTab, false ); + } + } + } + else + { + ScDocument* pClipDoc = pOwnClip->GetDocument(); + InsertDeleteFlags nFlags = InsertDeleteFlags::ALL; + if (pClipDoc->GetClipParam().isMultiRange()) + // For multi-range paste, we paste values by default. + nFlags &= ~InsertDeleteFlags::FORMULA; + + if (lcl_checkClassification(pClipDoc, rThisDoc)) + pTabViewShell->PasteFromClip( nFlags, pClipDoc, + ScPasteFunc::NONE, false, false, false, INS_NONE, InsertDeleteFlags::NONE, + bShowDialog ); // allow warning dialog + } + } + if (comphelper::LibreOfficeKit::isActive()) + { + bool entireColumnOrRowSelected = false; + if (pOwnClip) + { + ScClipParam clipParam = pOwnClip->GetDocument()->GetClipParam(); + if (clipParam.maRanges.size() > 0) + { + if (clipParam.maRanges[0].aEnd.Col() == pOwnClip->GetDocument()->MaxCol() + || clipParam.maRanges[0].aEnd.Row() == pOwnClip->GetDocument()->MaxRow()) + { + entireColumnOrRowSelected = true; + } + } + } + const SfxBoolItem* pItem = rThisDoc.GetAttr(nThisCol, nThisRow, nThisTab, ATTR_LINEBREAK); + if (pItem->GetValue() || entireColumnOrRowSelected) + { + ScTabViewShell::notifyAllViewsSheetGeomInvalidation( + pTabViewShell, true /* bColumns */, true /* bRows */, true /* bSizes*/, + true /* bHidden */, true /* bFiltered */, true /* bGroups */, nThisTab); + } + } + pTabViewShell->CellContentChanged(); // => PasteFromSystem() ??? +} + +bool ScClipUtil::CheckDestRanges( + const ScDocument& rDoc, SCCOL nSrcCols, SCROW nSrcRows, const ScMarkData& rMark, const ScRangeList& rDest) +{ + for (size_t i = 0, n = rDest.size(); i < n; ++i) + { + ScRange aTest = rDest[i]; + // Check for filtered rows in all selected sheets. + for (const auto& rTab : rMark) + { + aTest.aStart.SetTab(rTab); + aTest.aEnd.SetTab(rTab); + if (ScViewUtil::HasFiltered(aTest, rDoc)) + { + // I don't know how to handle pasting into filtered rows yet. + return false; + } + } + + // Destination range must be an exact multiple of the source range. + SCROW nRows = aTest.aEnd.Row() - aTest.aStart.Row() + 1; + SCCOL nCols = aTest.aEnd.Col() - aTest.aStart.Col() + 1; + SCROW nRowTest = (nRows / nSrcRows) * nSrcRows; + SCCOL nColTest = (nCols / nSrcCols) * nSrcCols; + if ( rDest.size() > 1 && ( nRows != nRowTest || nCols != nColTest ) ) + { + // Destination range is not a multiple of the source range. Bail out. + return false; + } + } + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/colrowba.cxx b/sc/source/ui/view/colrowba.cxx new file mode 100644 index 0000000000..ab9e82282e --- /dev/null +++ b/sc/source/ui/view/colrowba.cxx @@ -0,0 +1,383 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static OUString lcl_MetricString( tools::Long nTwips, std::u16string_view rText ) +{ + if ( nTwips <= 0 ) + return ScResId(STR_TIP_HIDE); + else + { + FieldUnit eUserMet = SC_MOD()->GetAppOptions().GetAppMetric(); + + sal_Int64 nUserVal = vcl::ConvertValue( nTwips*100, 1, 2, FieldUnit::TWIP, eUserMet ); + + OUString aStr = OUString::Concat(rText) + " " + + ScGlobal::getLocaleData().getNum( nUserVal, 2 ) + + " " + SdrFormatter::GetUnitStr(eUserMet); + return aStr; + } +} + +ScColBar::ScColBar( vcl::Window* pParent, ScHSplitPos eWhich, + ScHeaderFunctionSet* pFuncSet, ScHeaderSelectionEngine* pEng, + ScTabView* pTab ) : + ScHeaderControl( pParent, pEng, pTab->GetViewData().GetDocument().MaxCol()+1, false, pTab ), + meWhich( eWhich ), + mpFuncSet( pFuncSet ) +{ + Show(); +} + +ScColBar::~ScColBar() +{ +} + +SCCOLROW ScColBar::GetPos() const +{ + return pTabView->GetViewData().GetPosX(meWhich); +} + +sal_uInt16 ScColBar::GetEntrySize( SCCOLROW nEntryNo ) const +{ + const ScViewData& rViewData = pTabView->GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + SCTAB nTab = rViewData.GetTabNo(); + if (rDoc.ColHidden(static_cast(nEntryNo), nTab)) + return 0; + else + return static_cast(ScViewData::ToPixel( rDoc.GetColWidth( static_cast(nEntryNo), nTab ), rViewData.GetPPTX() )); +} + +OUString ScColBar::GetEntryText( SCCOLROW nEntryNo ) const +{ + return pTabView->GetViewData().GetDocument().GetAddressConvention() == formula::FormulaGrammar::CONV_XL_R1C1 + ? OUString::number(nEntryNo + 1) + : ScColToAlpha( static_cast(nEntryNo) ); +} + +void ScColBar::SetEntrySize( SCCOLROW nPos, sal_uInt16 nNewSize ) +{ + const ScViewData& rViewData = pTabView->GetViewData(); + sal_uInt16 nSizeTwips; + ScSizeMode eMode = SC_SIZE_DIRECT; + if (nNewSize < 10) nNewSize = 10; // pixels + + if ( nNewSize == HDR_SIZE_OPTIMUM ) + { + nSizeTwips = STD_EXTRA_WIDTH; + eMode = SC_SIZE_OPTIMAL; + } + else + nSizeTwips = static_cast( nNewSize / rViewData.GetPPTX() ); + + const ScMarkData& rMark = rViewData.GetMarkData(); + + std::vector aRanges; + if ( rMark.IsColumnMarked( static_cast(nPos) ) ) + { + ScDocument& rDoc = rViewData.GetDocument(); + SCCOL nStart = 0; + while (nStart<=rDoc.MaxCol()) + { + while (nStartSetWidthOrHeight(true, aRanges, eMode, nSizeTwips); +} + +void ScColBar::HideEntries( SCCOLROW nStart, SCCOLROW nEnd ) +{ + std::vector aRanges(1, sc::ColRowSpan(nStart,nEnd)); + pTabView->GetViewData().GetView()->SetWidthOrHeight(true, aRanges, SC_SIZE_DIRECT, 0); +} + +void ScColBar::SetMarking( bool bSet ) +{ + pTabView->GetViewData().GetMarkData().SetMarking( bSet ); + if (!bSet) + { + pTabView->GetViewData().GetView()->UpdateAutoFillMark(); + } +} + +void ScColBar::SelectWindow() +{ + const ScViewData& rViewData = pTabView->GetViewData(); + ScTabViewShell* pViewSh = rViewData.GetViewShell(); + + pViewSh->SetActive(); // Appear and SetViewFrame + pViewSh->DrawDeselectAll(); + + ScSplitPos eActive = rViewData.GetActivePart(); + if (meWhich==SC_SPLIT_LEFT) + { + if (eActive==SC_SPLIT_TOPRIGHT) eActive=SC_SPLIT_TOPLEFT; + if (eActive==SC_SPLIT_BOTTOMRIGHT) eActive=SC_SPLIT_BOTTOMLEFT; + } + else + { + if (eActive==SC_SPLIT_TOPLEFT) eActive=SC_SPLIT_TOPRIGHT; + if (eActive==SC_SPLIT_BOTTOMLEFT) eActive=SC_SPLIT_BOTTOMRIGHT; + } + pViewSh->ActivatePart( eActive ); + + mpFuncSet->SetColumn( true ); + mpFuncSet->SetWhich( eActive ); + + pViewSh->ActiveGrabFocus(); +} + +bool ScColBar::IsDisabled() const +{ + ScModule* pScMod = SC_MOD(); + return pScMod->IsModalMode(); +} + +bool ScColBar::ResizeAllowed() const +{ + const ScViewData& rViewData = pTabView->GetViewData(); + return !rViewData.HasEditView( rViewData.GetActivePart() ); +} + +void ScColBar::DrawInvert( tools::Long nDragPosP ) +{ + tools::Rectangle aRect( nDragPosP,0, nDragPosP+HDR_SLIDERSIZE-1,GetOutputSizePixel().Width()-1 ); + PaintImmediately(); + GetOutDev()->Invert(aRect); + + pTabView->GetViewData().GetView()->InvertVertical(meWhich,nDragPosP); +} + +OUString ScColBar::GetDragHelp( tools::Long nVal ) +{ + tools::Long nTwips = static_cast( nVal / pTabView->GetViewData().GetPPTX() ); + return lcl_MetricString( nTwips, ScResId(STR_TIP_WIDTH) ); +} + +bool ScColBar::IsLayoutRTL() const // override only for columns +{ + const ScViewData& rViewData = pTabView->GetViewData(); + return rViewData.GetDocument().IsLayoutRTL( rViewData.GetTabNo() ); +} + +ScRowBar::ScRowBar( vcl::Window* pParent, ScVSplitPos eWhich, + ScHeaderFunctionSet* pFuncSet, ScHeaderSelectionEngine* pEng, + ScTabView* pTab ) : + ScHeaderControl( pParent, pEng, pTab->GetViewData().GetDocument().MaxRow()+1, true, pTab ), + meWhich( eWhich ), + mpFuncSet( pFuncSet ) +{ + Show(); +} + +ScRowBar::~ScRowBar() +{ +} + +SCCOLROW ScRowBar::GetPos() const +{ + return pTabView->GetViewData().GetPosY(meWhich); +} + +sal_uInt16 ScRowBar::GetEntrySize( SCCOLROW nEntryNo ) const +{ + const ScViewData& rViewData = pTabView->GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + SCTAB nTab = rViewData.GetTabNo(); + SCROW nLastRow = -1; + if (rDoc.RowHidden(nEntryNo, nTab, nullptr, &nLastRow)) + return 0; + else + return static_cast(ScViewData::ToPixel( rDoc.GetOriginalHeight( nEntryNo, + nTab ), rViewData.GetPPTY() )); +} + +OUString ScRowBar::GetEntryText( SCCOLROW nEntryNo ) const +{ + return OUString::number( nEntryNo + 1 ); +} + +void ScRowBar::SetEntrySize( SCCOLROW nPos, sal_uInt16 nNewSize ) +{ + const ScViewData& rViewData = pTabView->GetViewData(); + sal_uInt16 nSizeTwips; + ScSizeMode eMode = SC_SIZE_DIRECT; + if (nNewSize < 10) nNewSize = 10; // pixels + + if ( nNewSize == HDR_SIZE_OPTIMUM ) + { + nSizeTwips = 0; + eMode = SC_SIZE_OPTIMAL; + } + else + nSizeTwips = static_cast( nNewSize / rViewData.GetPPTY() ); + + const ScMarkData& rMark = rViewData.GetMarkData(); + + std::vector aRanges; + if ( rMark.IsRowMarked( nPos ) ) + { + ScDocument& rDoc = rViewData.GetDocument(); + SCROW nStart = 0; + while (nStart<=rDoc.MaxRow()) + { + while (nStartSetWidthOrHeight(false, aRanges, eMode, nSizeTwips); +} + +void ScRowBar::HideEntries( SCCOLROW nStart, SCCOLROW nEnd ) +{ + std::vector aRange(1, sc::ColRowSpan(nStart,nEnd)); + pTabView->GetViewData().GetView()->SetWidthOrHeight(false, aRange, SC_SIZE_DIRECT, 0); +} + +void ScRowBar::SetMarking( bool bSet ) +{ + pTabView->GetViewData().GetMarkData().SetMarking( bSet ); + if (!bSet) + { + pTabView->GetViewData().GetView()->UpdateAutoFillMark(); + } +} + +void ScRowBar::SelectWindow() +{ + const ScViewData& rViewData = pTabView->GetViewData(); + ScTabViewShell* pViewSh = rViewData.GetViewShell(); + + pViewSh->SetActive(); // Appear and SetViewFrame + pViewSh->DrawDeselectAll(); + + ScSplitPos eActive = rViewData.GetActivePart(); + if (meWhich==SC_SPLIT_TOP) + { + if (eActive==SC_SPLIT_BOTTOMLEFT) eActive=SC_SPLIT_TOPLEFT; + if (eActive==SC_SPLIT_BOTTOMRIGHT) eActive=SC_SPLIT_TOPRIGHT; + } + else + { + if (eActive==SC_SPLIT_TOPLEFT) eActive=SC_SPLIT_BOTTOMLEFT; + if (eActive==SC_SPLIT_TOPRIGHT) eActive=SC_SPLIT_BOTTOMRIGHT; + } + pViewSh->ActivatePart( eActive ); + + mpFuncSet->SetColumn( false ); + mpFuncSet->SetWhich( eActive ); + + pViewSh->ActiveGrabFocus(); +} + +bool ScRowBar::IsDisabled() const +{ + ScModule* pScMod = SC_MOD(); + return pScMod->IsModalMode(); +} + +bool ScRowBar::ResizeAllowed() const +{ + const ScViewData& rViewData = pTabView->GetViewData(); + return !rViewData.HasEditView( rViewData.GetActivePart() ); +} + +void ScRowBar::DrawInvert( tools::Long nDragPosP ) +{ + tools::Rectangle aRect( 0,nDragPosP, GetOutputSizePixel().Width()-1,nDragPosP+HDR_SLIDERSIZE-1 ); + PaintImmediately(); + GetOutDev()->Invert(aRect); + + pTabView->GetViewData().GetView()->InvertHorizontal(meWhich,nDragPosP); +} + +OUString ScRowBar::GetDragHelp( tools::Long nVal ) +{ + tools::Long nTwips = static_cast( nVal / pTabView->GetViewData().GetPPTY() ); + return lcl_MetricString( nTwips, ScResId(STR_TIP_HEIGHT) ); +} + +SCCOLROW ScRowBar::GetHiddenCount( SCCOLROW nEntryNo ) const // override only for rows +{ + const ScViewData& rViewData = pTabView->GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + SCTAB nTab = rViewData.GetTabNo(); + return rDoc.GetHiddenRowCount( nEntryNo, nTab ); +} + +bool ScRowBar::IsMirrored() const // override only for rows +{ + const ScViewData& rViewData = pTabView->GetViewData(); + return rViewData.GetDocument().IsLayoutRTL( rViewData.GetTabNo() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/dbfunc.cxx b/sc/source/ui/view/dbfunc.cxx new file mode 100644 index 0000000000..8f1b9e8fc5 --- /dev/null +++ b/sc/source/ui/view/dbfunc.cxx @@ -0,0 +1,466 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ScDBFunc::ScDBFunc( vcl::Window* pParent, ScDocShell& rDocSh, ScTabViewShell* pViewShell ) : + ScViewFunc( pParent, rDocSh, pViewShell ) +{ +} + +ScDBFunc::~ScDBFunc() +{ +} + +// auxiliary functions + +void ScDBFunc::GotoDBArea( const OUString& rDBName ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + ScDBCollection* pDBCol = rDoc.GetDBCollection(); + ScDBData* pData = pDBCol->getNamedDBs().findByUpperName(ScGlobal::getCharClass().uppercase(rDBName)); + if (!pData) + return; + + SCTAB nTab = 0; + SCCOL nStartCol = 0; + SCROW nStartRow = 0; + SCCOL nEndCol = 0; + SCROW nEndRow = 0; + + pData->GetArea( nTab, nStartCol, nStartRow, nEndCol, nEndRow ); + SetTabNo( nTab ); + + MoveCursorAbs( nStartCol, nStartRow, SC_FOLLOW_JUMP, + false, false ); // bShift,bControl + DoneBlockMode(); + InitBlockMode( nStartCol, nStartRow, nTab ); + MarkCursor( nEndCol, nEndRow, nTab ); + SelectionChanged(); +} + +// search current datarange for sort / filter + +ScDBData* ScDBFunc::GetDBData( bool bMark, ScGetDBMode eMode, ScGetDBSelection eSel ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDBData* pData = nullptr; + ScRange aRange; + ScMarkType eMarkType = GetViewData().GetSimpleArea(aRange); + if ( eMarkType == SC_MARK_SIMPLE || eMarkType == SC_MARK_SIMPLE_FILTERED ) + { + bool bShrinkColumnsOnly = false; + if (eSel == ScGetDBSelection::RowDown) + { + // Don't alter row range, additional rows may have been selected on + // purpose to append data, or to have a fake header row. + bShrinkColumnsOnly = true; + // Select further rows only if only one row or a portion thereof is + // selected. + if (aRange.aStart.Row() != aRange.aEnd.Row()) + { + // If an area is selected shrink that to the actual used + // columns, don't draw filter buttons for empty columns. + eSel = ScGetDBSelection::ShrinkToUsedData; + } + else if (aRange.aStart.Col() == aRange.aEnd.Col()) + { + // One cell only, if it is not marked obtain entire used data + // area. + const ScMarkData& rMarkData = GetViewData().GetMarkData(); + if (!(rMarkData.IsMarked() || rMarkData.IsMultiMarked())) + eSel = ScGetDBSelection::Keep; + } + } + switch (eSel) + { + case ScGetDBSelection::ShrinkToUsedData: + case ScGetDBSelection::RowDown: + { + // Shrink the selection to actual used area. + ScDocument& rDoc = pDocSh->GetDocument(); + SCCOL nCol1 = aRange.aStart.Col(), nCol2 = aRange.aEnd.Col(); + SCROW nRow1 = aRange.aStart.Row(), nRow2 = aRange.aEnd.Row(); + bool bShrunk; + rDoc.ShrinkToUsedDataArea( bShrunk, aRange.aStart.Tab(), + nCol1, nRow1, nCol2, nRow2, bShrinkColumnsOnly); + if (bShrunk) + { + aRange.aStart.SetCol(nCol1); + aRange.aEnd.SetCol(nCol2); + aRange.aStart.SetRow(nRow1); + aRange.aEnd.SetRow(nRow2); + } + } + break; + default: + ; // nothing + } + pData = pDocSh->GetDBData( aRange, eMode, eSel ); + } + else if ( eMode != SC_DB_OLD ) + pData = pDocSh->GetDBData( + ScRange( GetViewData().GetCurX(), GetViewData().GetCurY(), + GetViewData().GetTabNo() ), + eMode, ScGetDBSelection::Keep ); + + if (!pData) + return nullptr; + + if (bMark) + { + ScRange aFound; + pData->GetArea(aFound); + MarkRange( aFound, false ); + } + return pData; +} + +ScDBData* ScDBFunc::GetAnonymousDBData() +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScRange aRange; + ScMarkType eMarkType = GetViewData().GetSimpleArea(aRange); + if (eMarkType != SC_MARK_SIMPLE && eMarkType != SC_MARK_SIMPLE_FILTERED) + return nullptr; + + // Expand to used data area if not explicitly marked. + const ScMarkData& rMarkData = GetViewData().GetMarkData(); + if (!rMarkData.IsMarked() && !rMarkData.IsMultiMarked()) + { + SCCOL nCol1 = aRange.aStart.Col(); + SCCOL nCol2 = aRange.aEnd.Col(); + SCROW nRow1 = aRange.aStart.Row(); + SCROW nRow2 = aRange.aEnd.Row(); + pDocSh->GetDocument().GetDataArea(aRange.aStart.Tab(), nCol1, nRow1, nCol2, nRow2, false, false); + aRange.aStart.SetCol(nCol1); + aRange.aStart.SetRow(nRow1); + aRange.aEnd.SetCol(nCol2); + aRange.aEnd.SetRow(nRow2); + } + + return pDocSh->GetAnonymousDBData(aRange); +} + +// main functions + +// Sort + +void ScDBFunc::UISort( const ScSortParam& rSortParam ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + ScDBData* pDBData = rDoc.GetDBAtArea( nTab, rSortParam.nCol1, rSortParam.nRow1, + rSortParam.nCol2, rSortParam.nRow2 ); + if (!pDBData) + { + OSL_FAIL( "Sort: no DBData" ); + return; + } + + ScSubTotalParam aSubTotalParam; + pDBData->GetSubTotalParam( aSubTotalParam ); + if (aSubTotalParam.bGroupActive[0] && !aSubTotalParam.bRemoveOnly) + { + // repeat subtotals, with new sortorder + + DoSubTotals( aSubTotalParam, true/*bRecord*/, &rSortParam ); + } + else + { + Sort( rSortParam ); // just sort + } +} + +void ScDBFunc::Sort( const ScSortParam& rSortParam, bool bRecord, bool bPaint ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + SCTAB nTab = GetViewData().GetTabNo(); + ScDBDocFunc aDBDocFunc( *pDocSh ); + bool bSuccess = aDBDocFunc.Sort( nTab, rSortParam, bRecord, bPaint, false ); + if ( bSuccess && !rSortParam.bInplace ) + { + // mark target + ScRange aDestRange( rSortParam.nDestCol, rSortParam.nDestRow, rSortParam.nDestTab, + rSortParam.nDestCol + rSortParam.nCol2 - rSortParam.nCol1, + rSortParam.nDestRow + rSortParam.nRow2 - rSortParam.nRow1, + rSortParam.nDestTab ); + MarkRange( aDestRange ); + } + + ResetAutoSpellForContentChange(); +} + +// filters + +void ScDBFunc::Query( const ScQueryParam& rQueryParam, const ScRange* pAdvSource, bool bRecord ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + SCTAB nTab = GetViewData().GetTabNo(); + ScDBDocFunc aDBDocFunc( *pDocSh ); + bool bSuccess = aDBDocFunc.Query( nTab, rQueryParam, pAdvSource, bRecord, false ); + + if (!bSuccess) + return; + + bool bCopy = !rQueryParam.bInplace; + if (bCopy) + { + // mark target range (data base range has been set up if applicable) + ScDocument& rDoc = pDocSh->GetDocument(); + ScDBData* pDestData = rDoc.GetDBAtCursor( + rQueryParam.nDestCol, rQueryParam.nDestRow, + rQueryParam.nDestTab, ScDBDataPortion::TOP_LEFT ); + if (pDestData) + { + ScRange aDestRange; + pDestData->GetArea(aDestRange); + MarkRange( aDestRange ); + } + } + + if (!bCopy) + { + ScTabViewShell::notifyAllViewsSheetGeomInvalidation( + GetViewData().GetViewShell(), + false /* bColumns */, true /* bRows */, + false /* bSizes*/, true /* bHidden */, true /* bFiltered */, + false /* bGroups */, nTab); + UpdateScrollBars(ROW_HEADER); + SelectionChanged(); // for attribute states (filtered rows are ignored) + } + + GetViewData().GetBindings().Invalidate( SID_UNFILTER ); +} + +// autofilter-buttons show / hide + +void ScDBFunc::ToggleAutoFilter() +{ + ScViewData* pViewData = &GetViewData(); + ScDocShell* pDocSh = pViewData->GetDocShell(); + + ScQueryParam aParam; + ScDocument& rDoc = pViewData->GetDocument(); + ScDBData* pDBData = GetDBData(false, SC_DB_AUTOFILTER, ScGetDBSelection::RowDown); + + pDBData->SetByRow( true ); //! undo, retrieve beforehand ?? + pDBData->GetQueryParam( aParam ); + + SCCOL nCol; + SCROW nRow = aParam.nRow1; + SCTAB nTab = pViewData->GetTabNo(); + ScMF nFlag; + bool bHasAuto = true; + bool bHeader = pDBData->HasHeader(); + + //! instead retrieve from DB-range? + + for (nCol=aParam.nCol1; nCol<=aParam.nCol2 && bHasAuto; nCol++) + { + nFlag = rDoc.GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG )->GetValue(); + + if ( !(nFlag & ScMF::Auto) ) + bHasAuto = false; + } + + if (bHasAuto) // remove + { + // hide filter buttons + + for (nCol=aParam.nCol1; nCol<=aParam.nCol2; nCol++) + { + nFlag = rDoc.GetAttr( nCol, nRow, nTab, ATTR_MERGE_FLAG )->GetValue(); + rDoc.ApplyAttr( nCol, nRow, nTab, ScMergeFlagAttr( nFlag & ~ScMF::Auto ) ); + } + + // use a list action for the AutoFilter buttons (ScUndoAutoFilter) and the filter operation + + OUString aUndo = ScResId( STR_UNDO_QUERY ); + pDocSh->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, pViewData->GetViewShell()->GetViewShellId() ); + + ScRange aRange; + pDBData->GetArea( aRange ); + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique( pDocSh, aRange, pDBData->GetName(), false ) ); + + pDBData->SetAutoFilter(false); + + // remove filter (incl. Paint / Undo) + + SCSIZE nEC = aParam.GetEntryCount(); + for (SCSIZE i=0; iGetUndoManager()->LeaveListAction(); + + ScDBFunc::ModifiedAutoFilter(pDocSh); + } + else // show filter buttons + { + if ( !rDoc.IsBlockEmpty( aParam.nCol1, aParam.nRow1, + aParam.nCol2, aParam.nRow2, nTab ) ) + { + if (!bHeader) + { + std::shared_ptr xBox(Application::CreateMessageDialog(pViewData->GetDialogParent(), + VclMessageType::Question, + VclButtonsType::YesNo, ScResId(STR_MSSG_MAKEAUTOFILTER_0))); // header from first row? + xBox->set_title(ScResId(STR_MSSG_DOSUBTOTALS_0)); // "StarCalc" + xBox->set_default_response(RET_YES); + xBox->SetInstallLOKNotifierHdl(LINK(this, ScDBFunc, InstallLOKNotifierHdl)); + xBox->runAsync(xBox, [pDocSh, pViewData, pDBData, nCol, nRow, nTab, aParam] (sal_Int32 nResult) { + if (nResult == RET_YES) + { + pDBData->SetHeader( true ); //! Undo ?? + } + + ApplyAutoFilter(pDocSh, pViewData, pDBData, nCol, nRow, nTab, aParam); + }); + } + else + ApplyAutoFilter(pDocSh, pViewData, pDBData, nCol, nRow, nTab, aParam); + } + else + { + std::shared_ptr xErrorBox(Application::CreateMessageDialog(pViewData->GetDialogParent(), + VclMessageType::Warning, VclButtonsType::Ok, + ScResId(STR_ERR_AUTOFILTER))); + xErrorBox->SetInstallLOKNotifierHdl(LINK(this, ScDBFunc, InstallLOKNotifierHdl)); + xErrorBox->runAsync(xErrorBox, [] (sal_Int32) {}); + } + } +} + +IMPL_STATIC_LINK_NOARG(ScDBFunc, InstallLOKNotifierHdl, void*, vcl::ILibreOfficeKitNotifier*) +{ + return GetpApp(); +} + +void ScDBFunc::ApplyAutoFilter(ScDocShell* pDocSh, ScViewData* pViewData, ScDBData* pDBData, + SCCOL nCol, SCROW nRow, SCTAB nTab, ScQueryParam aParam) +{ + ScDocument& rDoc = pViewData->GetDocument(); + ScRange aRange; + pDBData->GetArea(aRange); + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique(pDocSh, aRange, pDBData->GetName(), true)); + + pDBData->SetAutoFilter(true); + + for (nCol=aParam.nCol1; nCol<=aParam.nCol2; nCol++) + { + ScMF nFlag = rDoc.GetAttr(nCol, nRow, nTab, ATTR_MERGE_FLAG)->GetValue(); + rDoc.ApplyAttr(nCol, nRow, nTab, ScMergeFlagAttr(nFlag | ScMF::Auto)); + } + pDocSh->PostPaint(ScRange(aParam.nCol1, nRow, nTab, aParam.nCol2, nRow, nTab), + PaintPartFlags::Grid); + + ScDBFunc::ModifiedAutoFilter(pDocSh); +} + +void ScDBFunc::ModifiedAutoFilter(ScDocShell* pDocSh) +{ + ScDocShellModificator aModificator(*pDocSh); + aModificator.SetDocumentModified(); + + SfxBindings* pBindings = pDocSh->GetViewBindings(); + pBindings->Invalidate(SID_AUTO_FILTER); + pBindings->Invalidate(SID_AUTOFILTER_HIDE); +} + +// just hide, no data change + +void ScDBFunc::HideAutoFilter() +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocShellModificator aModificator( *pDocSh ); + + ScDocument& rDoc = pDocSh->GetDocument(); + + ScDBData* pDBData = GetDBData( false ); + + SCTAB nTab; + SCCOL nCol1, nCol2; + SCROW nRow1, nRow2; + pDBData->GetArea(nTab, nCol1, nRow1, nCol2, nRow2); + + for (SCCOL nCol=nCol1; nCol<=nCol2; nCol++) + { + ScMF nFlag = rDoc.GetAttr( nCol, nRow1, nTab, ATTR_MERGE_FLAG )->GetValue(); + rDoc.ApplyAttr( nCol, nRow1, nTab, ScMergeFlagAttr( nFlag & ~ScMF::Auto ) ); + } + + ScRange aRange; + pDBData->GetArea( aRange ); + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique( pDocSh, aRange, pDBData->GetName(), false ) ); + + pDBData->SetAutoFilter(false); + + pDocSh->PostPaint(ScRange(nCol1, nRow1, nTab, nCol2, nRow1, nTab), PaintPartFlags::Grid ); + aModificator.SetDocumentModified(); + + SfxBindings& rBindings = GetViewData().GetBindings(); + rBindings.Invalidate( SID_AUTO_FILTER ); + rBindings.Invalidate( SID_AUTOFILTER_HIDE ); +} + +// Re-Import + +bool ScDBFunc::ImportData( const ScImportParam& rParam ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + ScEditableTester aTester( rDoc, GetViewData().GetTabNo(), rParam.nCol1,rParam.nRow1, + rParam.nCol2,rParam.nRow2 ); + if ( !aTester.IsEditable() ) + { + ErrorMessage(aTester.GetMessageId()); + return false; + } + + ScDBDocFunc aDBDocFunc( *GetViewData().GetDocShell() ); + return aDBDocFunc.DoImport( GetViewData().GetTabNo(), rParam, nullptr ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/dbfunc2.cxx b/sc/source/ui/view/dbfunc2.cxx new file mode 100644 index 0000000000..fffa16909f --- /dev/null +++ b/sc/source/ui/view/dbfunc2.cxx @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include + +void ScDBFunc::UpdateCharts( bool bAllCharts ) +{ + sal_uInt16 nFound = 0; + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + + if ( rDoc.GetDrawLayer() ) + nFound = DoUpdateCharts( ScAddress( rViewData.GetCurX(), + rViewData.GetCurY(), + rViewData.GetTabNo()), + rDoc, + bAllCharts ); + + if ( !nFound && !bAllCharts ) + ErrorMessage(STR_NOCHARTATCURSOR); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/dbfunc3.cxx b/sc/source/ui/view/dbfunc3.cxx new file mode 100644 index 0000000000..9720bf7f4f --- /dev/null +++ b/sc/source/ui/view/dbfunc3.cxx @@ -0,0 +1,2312 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace com::sun::star; +using ::com::sun::star::uno::Any; +using ::com::sun::star::uno::Sequence; +using ::com::sun::star::uno::Reference; +using ::com::sun::star::uno::UNO_QUERY; +using ::com::sun::star::beans::XPropertySet; +using ::com::sun::star::container::XNameAccess; +using ::com::sun::star::sheet::XDimensionsSupplier; +using ::std::vector; + +// outliner + +// create outline grouping + +void ScDBFunc::MakeOutline( bool bColumns, bool bRecord ) +{ + ScRange aRange; + if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE) + { + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScOutlineDocFunc aFunc(*pDocSh); + aFunc.MakeOutline( aRange, bColumns, bRecord, false ); + + ScTabViewShell::notifyAllViewsHeaderInvalidation(GetViewData().GetViewShell(), bColumns ? COLUMN_HEADER : ROW_HEADER, GetViewData().GetTabNo()); + ScTabViewShell::notifyAllViewsSheetGeomInvalidation(GetViewData().GetViewShell(), + bColumns, !bColumns, false /* bSizes*/, + false /* bHidden */, false /* bFiltered */, + true /* bGroups */, GetViewData().GetTabNo()); + } + else + ErrorMessage(STR_NOMULTISELECT); +} + +// delete outline grouping + +void ScDBFunc::RemoveOutline( bool bColumns, bool bRecord ) +{ + ScRange aRange; + if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE) + { + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScOutlineDocFunc aFunc(*pDocSh); + aFunc.RemoveOutline( aRange, bColumns, bRecord, false ); + + ScTabViewShell::notifyAllViewsHeaderInvalidation(GetViewData().GetViewShell(), bColumns ? COLUMN_HEADER : ROW_HEADER, GetViewData().GetTabNo()); + ScTabViewShell::notifyAllViewsSheetGeomInvalidation(GetViewData().GetViewShell(), + bColumns, !bColumns, false /* bSizes*/, + true /* bHidden */, true /* bFiltered */, + true /* bGroups */, GetViewData().GetTabNo()); + } + else + ErrorMessage(STR_NOMULTISELECT); +} + +// menu status: delete outlines + +void ScDBFunc::TestRemoveOutline( bool& rCol, bool& rRow ) +{ + bool bColFound = false; + bool bRowFound = false; + + SCCOL nStartCol, nEndCol; + SCROW nStartRow, nEndRow; + SCTAB nStartTab, nEndTab; + if (GetViewData().GetSimpleArea(nStartCol,nStartRow,nStartTab,nEndCol,nEndRow,nEndTab) == SC_MARK_SIMPLE) + { + SCTAB nTab = nStartTab; + ScDocument& rDoc = GetViewData().GetDocument(); + ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab ); + if (pTable) + { + ScOutlineEntry* pEntry; + SCCOLROW nStart; + SCCOLROW nEnd; + bool bColMarked = ( nStartRow == 0 && nEndRow == rDoc.MaxRow() ); + bool bRowMarked = ( nStartCol == 0 && nEndCol == rDoc.MaxCol() ); + + // columns + + if ( !bRowMarked || bColMarked ) // not when entire rows are marked + { + ScOutlineArray& rArray = pTable->GetColArray(); + ScSubOutlineIterator aColIter( &rArray ); + while (!bColFound) + { + pEntry=aColIter.GetNext(); + if (!pEntry) + break; + nStart = pEntry->GetStart(); + nEnd = pEntry->GetEnd(); + if ( nStartCol<=static_cast(nEnd) && nEndCol>=static_cast(nStart) ) + bColFound = true; + } + } + + // rows + + if ( !bColMarked || bRowMarked ) // not when entire columns are marked + { + ScOutlineArray& rArray = pTable->GetRowArray(); + ScSubOutlineIterator aRowIter( &rArray ); + while (!bRowFound) + { + pEntry=aRowIter.GetNext(); + if (!pEntry) + break; + nStart = pEntry->GetStart(); + nEnd = pEntry->GetEnd(); + if ( nStartRow<=nEnd && nEndRow>=nStart ) + bRowFound = true; + } + } + } + } + + rCol = bColFound; + rRow = bRowFound; +} + +void ScDBFunc::RemoveAllOutlines( bool bRecord ) +{ + SCTAB nTab = GetViewData().GetTabNo(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScOutlineDocFunc aFunc(*pDocSh); + + bool bOk = aFunc.RemoveAllOutlines( nTab, bRecord ); + + if (bOk) + { + ScTabViewShell::notifyAllViewsSheetGeomInvalidation(GetViewData().GetViewShell(), + true /* bColumns */, true /* bRows */, false /* bSizes*/, + true /* bHidden */, true /* bFiltered */, + true /* bGroups */, nTab); + UpdateScrollBars(BOTH_HEADERS); + } +} + +// auto outlines + +void ScDBFunc::AutoOutline( ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + ScRange aRange( 0,0,nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab ); // the complete sheet, if nothing is marked + ScMarkData& rMark = GetViewData().GetMarkData(); + if ( rMark.IsMarked() || rMark.IsMultiMarked() ) + { + rMark.MarkToMulti(); + aRange = rMark.GetMultiMarkArea(); + } + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScOutlineDocFunc aFunc(*pDocSh); + aFunc.AutoOutline( aRange, true ); +} + +// select outline level + +void ScDBFunc::SelectLevel( bool bColumns, sal_uInt16 nLevel, bool bRecord ) +{ + SCTAB nTab = GetViewData().GetTabNo(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScOutlineDocFunc aFunc(*pDocSh); + + bool bOk = aFunc.SelectLevel( nTab, bColumns, nLevel, bRecord, true/*bPaint*/ ); + + if (bOk) + { + ScTabViewShell::notifyAllViewsSheetGeomInvalidation(GetViewData().GetViewShell(), + bColumns, !bColumns, false /* bSizes*/, + true /* bHidden */, true /* bFiltered */, + true /* bGroups */, nTab); + UpdateScrollBars(bColumns ? COLUMN_HEADER : ROW_HEADER); + } +} + +// show individual outline groups + +void ScDBFunc::SetOutlineState( bool bColumns, sal_uInt16 nLevel, sal_uInt16 nEntry, bool bHidden) +{ + const sal_uInt16 nHeadEntry = static_cast< sal_uInt16 >( -1 ); + if ( nEntry == nHeadEntry) + SelectLevel( bColumns, sal::static_int_cast(nLevel) ); + else + { + if ( !bHidden ) + ShowOutline( bColumns, sal::static_int_cast(nLevel), sal::static_int_cast(nEntry) ); + else + HideOutline( bColumns, sal::static_int_cast(nLevel), sal::static_int_cast(nEntry) ); + } +} + +void ScDBFunc::ShowOutline( bool bColumns, sal_uInt16 nLevel, sal_uInt16 nEntry, bool bRecord, bool bPaint ) +{ + SCTAB nTab = GetViewData().GetTabNo(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScOutlineDocFunc aFunc(*pDocSh); + + aFunc.ShowOutline( nTab, bColumns, nLevel, nEntry, bRecord, bPaint ); + + if ( bPaint ) + { + ScTabViewShell::notifyAllViewsSheetGeomInvalidation(GetViewData().GetViewShell(), + bColumns, !bColumns, false /* bSizes*/, + true /* bHidden */, true /* bFiltered */, + true /* bGroups */, nTab); + UpdateScrollBars(bColumns ? COLUMN_HEADER : ROW_HEADER); + } +} + +// hide individual outline groups + +void ScDBFunc::HideOutline( bool bColumns, sal_uInt16 nLevel, sal_uInt16 nEntry, bool bRecord, bool bPaint ) +{ + SCTAB nTab = GetViewData().GetTabNo(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScOutlineDocFunc aFunc(*pDocSh); + + bool bOk = aFunc.HideOutline( nTab, bColumns, nLevel, nEntry, bRecord, bPaint ); + + if ( bOk && bPaint ) + { + ScTabViewShell::notifyAllViewsSheetGeomInvalidation(GetViewData().GetViewShell(), + bColumns, !bColumns, false /* bSizes*/, + true /* bHidden */, true /* bFiltered */, + true /* bGroups */, nTab); + UpdateScrollBars(bColumns ? COLUMN_HEADER : ROW_HEADER); + } +} + +// menu status: show/hide marked range + +bool ScDBFunc::OutlinePossible(bool bHide) +{ + bool bEnable = false; + + SCCOL nStartCol; + SCROW nStartRow; + SCTAB nStartTab; + SCCOL nEndCol; + SCROW nEndRow; + SCTAB nEndTab; + + if (GetViewData().GetSimpleArea(nStartCol,nStartRow,nStartTab,nEndCol,nEndRow,nEndTab) == SC_MARK_SIMPLE) + { + ScDocument& rDoc = GetViewData().GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab ); + if (pTable) + { + SCCOLROW nStart; + SCCOLROW nEnd; + + // columns + + ScOutlineArray& rColArray = pTable->GetColArray(); + ScSubOutlineIterator aColIter( &rColArray ); + while (!bEnable) + { + ScOutlineEntry* pEntry = aColIter.GetNext(); + if (!pEntry) + break; + nStart = pEntry->GetStart(); + nEnd = pEntry->GetEnd(); + if ( bHide ) + { + if ( nStartCol<=static_cast(nEnd) && nEndCol>=static_cast(nStart) ) + if (!pEntry->IsHidden()) + bEnable = true; + } + else + { + if ( nStart>=nStartCol && nEnd<=nEndCol ) + if (pEntry->IsHidden()) + bEnable = true; + } + } + + // rows + + ScOutlineArray& rRowArray = pTable->GetRowArray(); + ScSubOutlineIterator aRowIter( &rRowArray ); + for (;;) + { + ScOutlineEntry* pEntry = aRowIter.GetNext(); + if (!pEntry) + break; + nStart = pEntry->GetStart(); + nEnd = pEntry->GetEnd(); + if ( bHide ) + { + if ( nStartRow<=nEnd && nEndRow>=nStart ) + if (!pEntry->IsHidden()) + bEnable = true; + } + else + { + if ( nStart>=nStartRow && nEnd<=nEndRow ) + if (pEntry->IsHidden()) + bEnable = true; + } + } + } + } + + return bEnable; +} + +// show marked range + +void ScDBFunc::ShowMarkedOutlines( bool bRecord ) +{ + ScRange aRange; + if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE) + { + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScOutlineDocFunc aFunc(*pDocSh); + bool bDone = aFunc.ShowMarkedOutlines( aRange, bRecord ); + if (bDone) + { + ScTabViewShell::notifyAllViewsSheetGeomInvalidation( + GetViewData().GetViewShell(), true, true, + false /* bSizes*/, true /* bHidden */, true /* bFiltered */, + true /* bGroups */, GetViewData().GetTabNo()); + UpdateScrollBars(); + } + } + else + ErrorMessage(STR_NOMULTISELECT); +} + +// hide marked range + +void ScDBFunc::HideMarkedOutlines( bool bRecord ) +{ + ScRange aRange; + if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE) + { + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScOutlineDocFunc aFunc(*pDocSh); + bool bDone = aFunc.HideMarkedOutlines( aRange, bRecord ); + if (bDone) + { + ScTabViewShell::notifyAllViewsSheetGeomInvalidation( + GetViewData().GetViewShell(), true, true, + false /* bSizes*/, true /* bHidden */, true /* bFiltered */, + true /* bGroups */, GetViewData().GetTabNo()); + UpdateScrollBars(); + } + } + else + ErrorMessage(STR_NOMULTISELECT); +} + +// sub totals + +void ScDBFunc::DoSubTotals( const ScSubTotalParam& rParam, bool bRecord, + const ScSortParam* pForceNewSort ) +{ + bool bDo = !rParam.bRemoveOnly; // sal_False = only delete + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScMarkData& rMark = GetViewData().GetMarkData(); + SCTAB nTab = GetViewData().GetTabNo(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + ScDBData* pDBData = rDoc.GetDBAtArea( nTab, rParam.nCol1, rParam.nRow1, + rParam.nCol2, rParam.nRow2 ); + if (!pDBData) + { + OSL_FAIL( "SubTotals: no DBData" ); + return; + } + + ScEditableTester aTester( rDoc, nTab, 0,rParam.nRow1+1, rDoc.MaxCol(),rDoc.MaxRow() ); + if (!aTester.IsEditable()) + { + ErrorMessage(aTester.GetMessageId()); + return; + } + + if (rDoc.HasAttrib( rParam.nCol1, rParam.nRow1+1, nTab, + rParam.nCol2, rParam.nRow2, nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped )) + { + ErrorMessage(STR_MSSG_INSERTCELLS_0); // do not insert into merged + return; + } + + weld::WaitObject aWait(GetViewData().GetDialogParent()); + bool bOk = true; + if (rParam.bReplace) + { + if (rDoc.TestRemoveSubTotals( nTab, rParam )) + { + std::unique_ptr xBox(Application::CreateMessageDialog(GetViewData().GetDialogParent(), + VclMessageType::Question, VclButtonsType::YesNo, + ScResId(STR_MSSG_DOSUBTOTALS_1))); // "delete data?" + xBox->set_title(ScResId(STR_MSSG_DOSUBTOTALS_0)); // "StarCalc" + xBox->set_default_response(RET_YES); + bOk = xBox->run() == RET_YES; + } + } + + if (!bOk) + return; + + ScDocShellModificator aModificator( *pDocSh ); + + ScSubTotalParam aNewParam( rParam ); // change end of range + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr pUndoTab; + std::unique_ptr pUndoRange; + std::unique_ptr pUndoDB; + + if (bRecord) // record old data + { + bool bOldFilter = bDo && rParam.bDoSort; + SCTAB nTabCount = rDoc.GetTableCount(); + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab ); + if (pTable) + { + pUndoTab.reset(new ScOutlineTable( *pTable )); + + SCCOLROW nOutStartCol; // row/column status + SCCOLROW nOutStartRow; + SCCOLROW nOutEndCol; + SCCOLROW nOutEndRow; + pTable->GetColArray().GetRange( nOutStartCol, nOutEndCol ); + pTable->GetRowArray().GetRange( nOutStartRow, nOutEndRow ); + + pUndoDoc->InitUndo( rDoc, nTab, nTab, true, true ); + rDoc.CopyToDocument( static_cast(nOutStartCol), 0, nTab, static_cast(nOutEndCol), rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, false, *pUndoDoc ); + rDoc.CopyToDocument( 0, nOutStartRow, nTab, rDoc.MaxCol(), nOutEndRow, nTab, InsertDeleteFlags::NONE, false, *pUndoDoc ); + } + else + pUndoDoc->InitUndo( rDoc, nTab, nTab, false, bOldFilter ); + + // record data range - including filter results + rDoc.CopyToDocument( 0,rParam.nRow1+1,nTab, rDoc.MaxCol(),rParam.nRow2,nTab, + InsertDeleteFlags::ALL, false, *pUndoDoc ); + + // all formulas for reference + rDoc.CopyToDocument( 0,0,0, rDoc.MaxCol(),rDoc.MaxRow(),nTabCount-1, + InsertDeleteFlags::FORMULA, false, *pUndoDoc ); + + // database and other ranges + ScRangeName* pDocRange = rDoc.GetRangeName(); + if (!pDocRange->empty()) + pUndoRange.reset(new ScRangeName( *pDocRange )); + ScDBCollection* pDocDB = rDoc.GetDBCollection(); + if (!pDocDB->empty()) + pUndoDB.reset(new ScDBCollection( *pDocDB )); + } + + ScOutlineTable* pOut = rDoc.GetOutlineTable( nTab ); + if (pOut) + { + // Remove all existing outlines in the specified range. + ScOutlineArray& rRowArray = pOut->GetRowArray(); + sal_uInt16 nDepth = rRowArray.GetDepth(); + for (sal_uInt16 i = 0; i < nDepth; ++i) + { + bool bSize; + rRowArray.Remove(aNewParam.nRow1, aNewParam.nRow2, bSize); + } + } + + if (rParam.bReplace) + rDoc.RemoveSubTotals( nTab, aNewParam ); + bool bSuccess = true; + if (bDo) + { + // Sort + if ( rParam.bDoSort || pForceNewSort ) + { + pDBData->SetArea( nTab, aNewParam.nCol1,aNewParam.nRow1, aNewParam.nCol2,aNewParam.nRow2 ); + + // set subtotal fields before sorting + // (duplicate values are dropped, so that they can be called again) + + ScSortParam aOldSort; + pDBData->GetSortParam( aOldSort ); + ScSortParam aSortParam( aNewParam, pForceNewSort ? *pForceNewSort : aOldSort ); + Sort( aSortParam, false, false ); + } + + bSuccess = rDoc.DoSubTotals( nTab, aNewParam ); + } + ScRange aDirtyRange( aNewParam.nCol1, aNewParam.nRow1, nTab, + aNewParam.nCol2, aNewParam.nRow2, nTab ); + rDoc.SetDirty( aDirtyRange, true ); + + if (bRecord) + { + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique( pDocSh, nTab, + rParam, aNewParam.nRow2, + std::move(pUndoDoc), std::move(pUndoTab), // pUndoDBData, + std::move(pUndoRange), std::move(pUndoDB) ) ); + } + + if (!bSuccess) + { + // "Can not insert any rows" + ErrorMessage(STR_MSSG_DOSUBTOTALS_2); + } + + // store + pDBData->SetSubTotalParam( aNewParam ); + pDBData->SetArea( nTab, aNewParam.nCol1,aNewParam.nRow1, aNewParam.nCol2,aNewParam.nRow2 ); + rDoc.CompileDBFormula(); + + const ScRange aMarkRange( aNewParam.nCol1, aNewParam.nRow1, nTab, aNewParam.nCol2, aNewParam.nRow2, nTab); + DoneBlockMode(); + InitOwnBlockMode( aMarkRange ); + rMark.SetMarkArea( aMarkRange ); + MarkDataChanged(); + + pDocSh->PostPaint(ScRange(0, 0, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab), + PaintPartFlags::Grid | PaintPartFlags::Left | PaintPartFlags::Top | PaintPartFlags::Size); + + aModificator.SetDocumentModified(); + + SelectionChanged(); +} + +// consolidate + +void ScDBFunc::Consolidate( const ScConsolidateParam& rParam ) +{ + ScDocShell* pDocShell = GetViewData().GetDocShell(); + pDocShell->DoConsolidate( rParam ); + SetTabNo( rParam.nTab, true ); +} + +// pivot + +static OUString lcl_MakePivotTabName( std::u16string_view rPrefix, SCTAB nNumber ) +{ + OUString aName = rPrefix + OUString::number( nNumber ); + return aName; +} + +bool ScDBFunc::MakePivotTable( + const ScDPSaveData& rData, const ScRange& rDest, bool bNewTable, + const ScDPObject& rSource ) +{ + // error message if no fields are set + // this must be removed when drag&drop of fields from a toolbox is available + + if ( rData.IsEmpty() ) + { + ErrorMessage(STR_PIVOT_NODATA); + return false; + } + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = GetViewData().GetDocument(); + bool bUndo = rDoc.IsUndoEnabled(); + + ScRange aDestRange = rDest; + if ( bNewTable ) + { + SCTAB nSrcTab = GetViewData().GetTabNo(); + + OUString aName( ScResId(STR_PIVOT_TABLE) ); + OUString aStr; + + rDoc.GetName( nSrcTab, aStr ); + aName += "_" + aStr + "_"; + + SCTAB nNewTab = nSrcTab+1; + + SCTAB i=1; + while ( !rDoc.InsertTab( nNewTab, lcl_MakePivotTabName( aName, i ) ) && i <= MAXTAB ) + i++; + + bool bAppend = ( nNewTab+1 == rDoc.GetTableCount() ); + if (bUndo) + { + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique( pDocSh, nNewTab, bAppend, lcl_MakePivotTabName( aName, i ) )); + } + + GetViewData().InsertTab( nNewTab ); + SetTabNo(nNewTab, true); + + aDestRange = ScRange( 0, 0, nNewTab ); + } + + ScDPObject* pDPObj = rDoc.GetDPAtCursor( + aDestRange.aStart.Col(), aDestRange.aStart.Row(), aDestRange.aStart.Tab() ); + + ScDPObject aObj( rSource ); + aObj.SetOutRange( aDestRange ); + if ( pDPObj && !rData.GetExistingDimensionData() ) + { + // copy dimension data from old object - lost in the dialog + //! change the dialog to keep the dimension data + + ScDPSaveData aNewData( rData ); + const ScDPSaveData* pOldData = pDPObj->GetSaveData(); + if ( pOldData ) + { + const ScDPDimensionSaveData* pDimSave = pOldData->GetExistingDimensionData(); + aNewData.SetDimensionData( pDimSave ); + } + aObj.SetSaveData( aNewData ); + } + else + aObj.SetSaveData( rData ); + + bool bAllowMove = (pDPObj != nullptr); // allow re-positioning when editing existing table + + ScDBDocFunc aFunc( *pDocSh ); + bool bSuccess = aFunc.DataPilotUpdate(pDPObj, &aObj, true, false, bAllowMove); + + CursorPosChanged(); // shells may be switched + + if ( bNewTable ) + { + pDocSh->PostPaintExtras(); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); + } + + return bSuccess; +} + +void ScDBFunc::DeletePivotTable() +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScDPObject* pDPObj = rDoc.GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), + GetViewData().GetTabNo() ); + if ( pDPObj ) + { + ScDBDocFunc aFunc( *pDocSh ); + aFunc.RemovePivotTable(*pDPObj, true, false); + CursorPosChanged(); // shells may be switched + } + else + ErrorMessage(STR_PIVOT_NOTFOUND); +} + +void ScDBFunc::RecalcPivotTable() +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = GetViewData().GetDocument(); + + ScDPObject* pDPObj = rDoc.GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), + GetViewData().GetTabNo() ); + if (pDPObj) + { + // Remove existing data cache for the data that this datapilot uses, + // to force re-build data cache. + ScDBDocFunc aFunc(*pDocSh); + aFunc.RefreshPivotTables(pDPObj, false); + + CursorPosChanged(); // shells may be switched + } + else + ErrorMessage(STR_PIVOT_NOTFOUND); +} + +void ScDBFunc::GetSelectedMemberList(ScDPUniqueStringSet& rEntries, tools::Long& rDimension) +{ + ScDPObject* pDPObj = GetViewData().GetDocument().GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ); + if ( !pDPObj ) + return; + + tools::Long nStartDimension = -1; + tools::Long nStartHierarchy = -1; + tools::Long nStartLevel = -1; + + ScRangeListRef xRanges; + GetViewData().GetMultiArea( xRanges ); // incl. cursor if nothing is selected + size_t nRangeCount = xRanges->size(); + bool bContinue = true; + + for (size_t nRangePos=0; nRangePos < nRangeCount && bContinue; nRangePos++) + { + ScRange const & rRange = (*xRanges)[nRangePos]; + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + SCCOL nEndCol = rRange.aEnd.Col(); + SCROW nEndRow = rRange.aEnd.Row(); + SCTAB nTab = rRange.aStart.Tab(); + + for (SCROW nRow=nStartRow; nRow<=nEndRow && bContinue; nRow++) + for (SCCOL nCol=nStartCol; nCol<=nEndCol && bContinue; nCol++) + { + sheet::DataPilotTableHeaderData aData; + pDPObj->GetHeaderPositionData(ScAddress(nCol, nRow, nTab), aData); + if ( aData.Dimension < 0 ) + bContinue = false; // not part of any dimension + else + { + if ( nStartDimension < 0 ) // first member? + { + nStartDimension = aData.Dimension; + nStartHierarchy = aData.Hierarchy; + nStartLevel = aData.Level; + } + if ( aData.Dimension != nStartDimension || + aData.Hierarchy != nStartHierarchy || + aData.Level != nStartLevel ) + { + bContinue = false; // cannot mix dimensions + } + } + if ( bContinue ) + { + // accept any part of a member description, also subtotals, + // but don't stop if empty parts are contained + if ( aData.Flags & sheet::MemberResultFlags::HASMEMBER ) + rEntries.insert(aData.MemberName); + } + } + } + + rDimension = nStartDimension; // dimension from which the found members came + if (!bContinue) + rEntries.clear(); // remove all if not valid +} + +bool ScDBFunc::HasSelectionForDateGroup( ScDPNumGroupInfo& rOldInfo, sal_Int32& rParts ) +{ + // determine if the date group dialog has to be shown for the current selection + + bool bFound = false; + + SCCOL nCurX = GetViewData().GetCurX(); + SCROW nCurY = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + ScDocument& rDoc = GetViewData().GetDocument(); + + ScDPObject* pDPObj = rDoc.GetDPAtCursor( nCurX, nCurY, nTab ); + if ( pDPObj ) + { + ScDPUniqueStringSet aEntries; + tools::Long nSelectDimension = -1; + GetSelectedMemberList( aEntries, nSelectDimension ); + + if (!aEntries.empty()) + { + bool bIsDataLayout; + OUString aDimName = pDPObj->GetDimName( nSelectDimension, bIsDataLayout ); + OUString aBaseDimName( aDimName ); + + bool bInGroupDim = false; + bool bFoundParts = false; + + ScDPDimensionSaveData* pDimData = + const_cast( pDPObj->GetSaveData()->GetExistingDimensionData() ); + if ( pDimData ) + { + const ScDPSaveNumGroupDimension* pNumGroupDim = pDimData->GetNumGroupDim( aDimName ); + const ScDPSaveGroupDimension* pGroupDim = pDimData->GetNamedGroupDim( aDimName ); + if ( pNumGroupDim ) + { + // existing num group dimension + + if ( pNumGroupDim->GetDatePart() != 0 ) + { + // dimension has date info -> edit settings of this dimension + // (parts are collected below) + + rOldInfo = pNumGroupDim->GetDateInfo(); + bFound = true; + } + else if ( pNumGroupDim->GetInfo().mbDateValues ) + { + // Numerical grouping with DateValues flag is used for grouping + // of days with a "Number of days" value. + + rOldInfo = pNumGroupDim->GetInfo(); + rParts = css::sheet::DataPilotFieldGroupBy::DAYS; // not found in CollectDateParts + bFoundParts = true; + bFound = true; + } + bInGroupDim = true; + } + else if ( pGroupDim ) + { + // existing additional group dimension + + if ( pGroupDim->GetDatePart() != 0 ) + { + // dimension has date info -> edit settings of this dimension + // (parts are collected below) + + rOldInfo = pGroupDim->GetDateInfo(); + aBaseDimName = pGroupDim->GetSourceDimName(); + bFound = true; + } + bInGroupDim = true; + } + } + if ( bFound && !bFoundParts ) + { + // collect date parts from all group dimensions + rParts = pDimData->CollectDateParts( aBaseDimName ); + } + if ( !bFound && !bInGroupDim ) + { + // create new date group dimensions if the selection is a single cell + // in a normal dimension with date content + + ScRange aSelRange; + if ( (GetViewData().GetSimpleArea( aSelRange ) == SC_MARK_SIMPLE) && + aSelRange.aStart == aSelRange.aEnd ) + { + SCCOL nSelCol = aSelRange.aStart.Col(); + SCROW nSelRow = aSelRange.aStart.Row(); + SCTAB nSelTab = aSelRange.aStart.Tab(); + if ( rDoc.HasValueData( nSelCol, nSelRow, nSelTab ) ) + { + sal_uLong nIndex = rDoc.GetAttr( + nSelCol, nSelRow, nSelTab, ATTR_VALUE_FORMAT)->GetValue(); + SvNumFormatType nType = rDoc.GetFormatTable()->GetType(nIndex); + if ( nType == SvNumFormatType::DATE || nType == SvNumFormatType::TIME || nType == SvNumFormatType::DATETIME ) + { + bFound = true; + // use currently selected value for automatic limits + if( rOldInfo.mbAutoStart ) + rOldInfo.mfStart = rDoc.GetValue( aSelRange.aStart ); + if( rOldInfo.mbAutoEnd ) + rOldInfo.mfEnd = rDoc.GetValue( aSelRange.aStart ); + } + } + } + } + } + } + + return bFound; +} + +bool ScDBFunc::HasSelectionForNumGroup( ScDPNumGroupInfo& rOldInfo ) +{ + // determine if the numeric group dialog has to be shown for the current selection + + bool bFound = false; + + SCCOL nCurX = GetViewData().GetCurX(); + SCROW nCurY = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + ScDocument& rDoc = GetViewData().GetDocument(); + + ScDPObject* pDPObj = rDoc.GetDPAtCursor( nCurX, nCurY, nTab ); + if ( pDPObj ) + { + ScDPUniqueStringSet aEntries; + tools::Long nSelectDimension = -1; + GetSelectedMemberList( aEntries, nSelectDimension ); + + if (!aEntries.empty()) + { + bool bIsDataLayout; + OUString aDimName = pDPObj->GetDimName( nSelectDimension, bIsDataLayout ); + + bool bInGroupDim = false; + + ScDPDimensionSaveData* pDimData = + const_cast( pDPObj->GetSaveData()->GetExistingDimensionData() ); + if ( pDimData ) + { + const ScDPSaveNumGroupDimension* pNumGroupDim = pDimData->GetNumGroupDim( aDimName ); + if ( pNumGroupDim ) + { + // existing num group dimension + // -> edit settings of this dimension + + rOldInfo = pNumGroupDim->GetInfo(); + bFound = true; + } + else if ( pDimData->GetNamedGroupDim( aDimName ) ) + bInGroupDim = true; // in a group dimension + } + if ( !bFound && !bInGroupDim ) + { + // create a new num group dimension if the selection is a single cell + // in a normal dimension with numeric content + + ScRange aSelRange; + if ( (GetViewData().GetSimpleArea( aSelRange ) == SC_MARK_SIMPLE) && + aSelRange.aStart == aSelRange.aEnd ) + { + if ( rDoc.HasValueData( aSelRange.aStart.Col(), aSelRange.aStart.Row(), + aSelRange.aStart.Tab() ) ) + { + bFound = true; + // use currently selected value for automatic limits + if( rOldInfo.mbAutoStart ) + rOldInfo.mfStart = rDoc.GetValue( aSelRange.aStart ); + if( rOldInfo.mbAutoEnd ) + rOldInfo.mfEnd = rDoc.GetValue( aSelRange.aStart ); + } + } + } + } + } + + return bFound; +} + +void ScDBFunc::DateGroupDataPilot( const ScDPNumGroupInfo& rInfo, sal_Int32 nParts ) +{ + ScDPObject* pDPObj = GetViewData().GetDocument().GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ); + if (!pDPObj) + return; + + ScDPUniqueStringSet aEntries; + tools::Long nSelectDimension = -1; + GetSelectedMemberList( aEntries, nSelectDimension ); + + if (aEntries.empty()) + return; + + std::vector aDeletedNames; + bool bIsDataLayout; + OUString aDimName = pDPObj->GetDimName( nSelectDimension, bIsDataLayout ); + + ScDPSaveData aData( *pDPObj->GetSaveData() ); + ScDPDimensionSaveData* pDimData = aData.GetDimensionData(); // created if not there + + // find the source dimension name. + OUString aBaseDimName = aDimName; + if( const ScDPSaveGroupDimension* pBaseGroupDim = pDimData->GetNamedGroupDim( aDimName ) ) + aBaseDimName = pBaseGroupDim->GetSourceDimName(); + + // Remove all group dimensions associated with this source dimension. For + // date grouping, we need to remove all existing groups for the affected + // source dimension and build new one(s) from scratch. Keep the deleted + // names so that they can be reused during re-construction. + aData.RemoveAllGroupDimensions(aBaseDimName, &aDeletedNames); + + if ( nParts ) + { + // create date group dimensions + + bool bFirst = true; + sal_Int32 nMask = 1; + for (sal_uInt16 nBit=0; nBit<32; nBit++) + { + if ( nParts & nMask ) + { + if ( bFirst ) + { + // innermost part: create NumGroupDimension (replacing original values) + // Dimension name is left unchanged + + if ( (nParts == sheet::DataPilotFieldGroupBy::DAYS) && (rInfo.mfStep >= 1.0) ) + { + // only days, and a step value specified: use numerical grouping + // with DateValues flag, not date grouping + + ScDPNumGroupInfo aNumInfo( rInfo ); + aNumInfo.mbDateValues = true; + + ScDPSaveNumGroupDimension aNumGroupDim( aBaseDimName, aNumInfo ); + pDimData->AddNumGroupDimension( aNumGroupDim ); + } + else + { + ScDPSaveNumGroupDimension aNumGroupDim( aBaseDimName, rInfo, nMask ); + pDimData->AddNumGroupDimension( aNumGroupDim ); + } + + bFirst = false; + } + else + { + // additional parts: create GroupDimension (shown as additional dimensions) + OUString aGroupDimName = + pDimData->CreateDateGroupDimName(nMask, *pDPObj, true, &aDeletedNames); + ScDPSaveGroupDimension aGroupDim( aBaseDimName, aGroupDimName ); + aGroupDim.SetDateInfo( rInfo, nMask ); + pDimData->AddGroupDimension( aGroupDim ); + + // set orientation + ScDPSaveDimension* pSaveDimension = aData.GetDimensionByName( aGroupDimName ); + if ( pSaveDimension->GetOrientation() == sheet::DataPilotFieldOrientation_HIDDEN ) + { + ScDPSaveDimension* pOldDimension = aData.GetDimensionByName( aBaseDimName ); + pSaveDimension->SetOrientation( pOldDimension->GetOrientation() ); + aData.SetPosition( pSaveDimension, 0 ); //! before (immediate) base + } + } + } + nMask *= 2; + } + } + + // apply changes + ScDBDocFunc aFunc( *GetViewData().GetDocShell() ); + pDPObj->SetSaveData( aData ); + aFunc.RefreshPivotTableGroups(pDPObj); + + // unmark cell selection + Unmark(); +} + +void ScDBFunc::NumGroupDataPilot( const ScDPNumGroupInfo& rInfo ) +{ + ScDPObject* pDPObj = GetViewData().GetDocument().GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ); + if (!pDPObj) + return; + + ScDPUniqueStringSet aEntries; + tools::Long nSelectDimension = -1; + GetSelectedMemberList( aEntries, nSelectDimension ); + + if (aEntries.empty()) + return; + + bool bIsDataLayout; + OUString aDimName = pDPObj->GetDimName( nSelectDimension, bIsDataLayout ); + + ScDPSaveData aData( *pDPObj->GetSaveData() ); + ScDPDimensionSaveData* pDimData = aData.GetDimensionData(); // created if not there + + ScDPSaveNumGroupDimension* pExisting = pDimData->GetNumGroupDimAcc( aDimName ); + if ( pExisting ) + { + // modify existing group dimension + pExisting->SetGroupInfo( rInfo ); + } + else + { + // create new group dimension + ScDPSaveNumGroupDimension aNumGroupDim( aDimName, rInfo ); + pDimData->AddNumGroupDimension( aNumGroupDim ); + } + + // apply changes + ScDBDocFunc aFunc( *GetViewData().GetDocShell() ); + pDPObj->SetSaveData( aData ); + aFunc.RefreshPivotTableGroups(pDPObj); + + // unmark cell selection + Unmark(); +} + +void ScDBFunc::GroupDataPilot() +{ + ScDPObject* pDPObj = GetViewData().GetDocument().GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ); + if (!pDPObj) + return; + + ScDPUniqueStringSet aEntries; + tools::Long nSelectDimension = -1; + GetSelectedMemberList( aEntries, nSelectDimension ); + + if (aEntries.empty()) + return; + + bool bIsDataLayout; + OUString aDimName = pDPObj->GetDimName( nSelectDimension, bIsDataLayout ); + + ScDPSaveData aData( *pDPObj->GetSaveData() ); + ScDPDimensionSaveData* pDimData = aData.GetDimensionData(); // created if not there + + // find original base + OUString aBaseDimName = aDimName; + const ScDPSaveGroupDimension* pBaseGroupDim = pDimData->GetNamedGroupDim( aDimName ); + if ( pBaseGroupDim ) + { + // any entry's SourceDimName is the original base + aBaseDimName = pBaseGroupDim->GetSourceDimName(); + } + + // find existing group dimension + // (using the selected dim, can be intermediate group dim) + ScDPSaveGroupDimension* pGroupDimension = pDimData->GetGroupDimAccForBase( aDimName ); + + // remove the selected items from their groups + // (empty groups are removed, too) + if ( pGroupDimension ) + { + for (const OUString& aEntryName : aEntries) + { + if ( pBaseGroupDim ) + { + // for each selected (intermediate) group, remove all its items + // (same logic as for adding, below) + const ScDPSaveGroupItem* pBaseGroup = pBaseGroupDim->GetNamedGroup( aEntryName ); + if ( pBaseGroup ) + pBaseGroup->RemoveElementsFromGroups( *pGroupDimension ); // remove all elements + else + pGroupDimension->RemoveFromGroups( aEntryName ); + } + else + pGroupDimension->RemoveFromGroups( aEntryName ); + } + } + + std::unique_ptr pNewGroupDim; + if ( !pGroupDimension ) + { + // create a new group dimension + OUString aGroupDimName = + pDimData->CreateGroupDimName(aBaseDimName, *pDPObj, false, nullptr); + pNewGroupDim.reset(new ScDPSaveGroupDimension( aBaseDimName, aGroupDimName )); + + pGroupDimension = pNewGroupDim.get(); // make changes to the new dim if none existed + + if ( pBaseGroupDim ) + { + // If it's a higher-order group dimension, pre-allocate groups for all + // non-selected original groups, so the individual base members aren't + // used for automatic groups (this would make the original groups hard + // to find). + //! Also do this when removing groups? + //! Handle this case dynamically with automatic groups? + + tools::Long nGroupCount = pBaseGroupDim->GetGroupCount(); + for ( tools::Long nGroup = 0; nGroup < nGroupCount; nGroup++ ) + { + const ScDPSaveGroupItem& rBaseGroup = pBaseGroupDim->GetGroupByIndex( nGroup ); + + if (!aEntries.count(rBaseGroup.GetGroupName())) + { + // add an additional group for each item that is not in the selection + ScDPSaveGroupItem aGroup( rBaseGroup.GetGroupName() ); + aGroup.AddElementsFromGroup( rBaseGroup ); + pGroupDimension->AddGroupItem( aGroup ); + } + } + } + } + OUString aGroupDimName = pGroupDimension->GetGroupDimName(); + + OUString aGroupName = pGroupDimension->CreateGroupName(ScResId(STR_PIVOT_GROUP)); + ScDPSaveGroupItem aGroup( aGroupName ); + for (const OUString& aEntryName : aEntries) + { + if ( pBaseGroupDim ) + { + // for each selected (intermediate) group, add all its items + const ScDPSaveGroupItem* pBaseGroup = pBaseGroupDim->GetNamedGroup( aEntryName ); + if ( pBaseGroup ) + aGroup.AddElementsFromGroup( *pBaseGroup ); + else + aGroup.AddElement( aEntryName ); // no group found -> automatic group, add the item itself + } + else + aGroup.AddElement( aEntryName ); // no group dimension, add all items directly + } + + pGroupDimension->AddGroupItem( aGroup ); + + if ( pNewGroupDim ) + { + pDimData->AddGroupDimension( *pNewGroupDim ); + pNewGroupDim.reset(); // AddGroupDimension copies the object + // don't access pGroupDimension after here + } + pGroupDimension = nullptr; + + // set orientation + ScDPSaveDimension* pSaveDimension = aData.GetDimensionByName( aGroupDimName ); + if ( pSaveDimension->GetOrientation() == sheet::DataPilotFieldOrientation_HIDDEN ) + { + ScDPSaveDimension* pOldDimension = aData.GetDimensionByName( aDimName ); + pSaveDimension->SetOrientation( pOldDimension->GetOrientation() ); + aData.SetPosition( pSaveDimension, 0 ); //! before (immediate) base + } + + // apply changes + ScDBDocFunc aFunc( *GetViewData().GetDocShell() ); + pDPObj->SetSaveData( aData ); + aFunc.RefreshPivotTableGroups(pDPObj); + + // unmark cell selection + Unmark(); +} + +void ScDBFunc::UngroupDataPilot() +{ + ScDPObject* pDPObj = GetViewData().GetDocument().GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ); + if (!pDPObj) + return; + + ScDPUniqueStringSet aEntries; + tools::Long nSelectDimension = -1; + GetSelectedMemberList( aEntries, nSelectDimension ); + + if (aEntries.empty()) + return; + + bool bIsDataLayout; + OUString aDimName = pDPObj->GetDimName( nSelectDimension, bIsDataLayout ); + + ScDPSaveData aData( *pDPObj->GetSaveData() ); + if (!aData.GetExistingDimensionData()) + // There is nothing to ungroup. + return; + + ScDPDimensionSaveData* pDimData = aData.GetDimensionData(); + + ScDPSaveGroupDimension* pGroupDim = pDimData->GetNamedGroupDimAcc( aDimName ); + const ScDPSaveNumGroupDimension* pNumGroupDim = pDimData->GetNumGroupDim( aDimName ); + if ( ( pGroupDim && pGroupDim->GetDatePart() != 0 ) || + ( pNumGroupDim && pNumGroupDim->GetDatePart() != 0 ) ) + { + // Date grouping: need to remove all affected group dimensions. + // This is done using DateGroupDataPilot with nParts=0. + + DateGroupDataPilot( ScDPNumGroupInfo(), 0 ); + return; + } + + if ( pGroupDim ) + { + for (const auto& rEntry : aEntries) + pGroupDim->RemoveGroup(rEntry); + + // remove group dimension if empty + bool bEmptyDim = pGroupDim->IsEmpty(); + if ( !bEmptyDim ) + { + // If all remaining groups in the dimension aren't shown, remove + // the dimension too, as if it was completely empty. + ScDPUniqueStringSet aVisibleEntries; + pDPObj->GetMemberResultNames( aVisibleEntries, nSelectDimension ); + bEmptyDim = pGroupDim->HasOnlyHidden( aVisibleEntries ); + } + if ( bEmptyDim ) + { + pDimData->RemoveGroupDimension( aDimName ); // pGroupDim is deleted + + // also remove SaveData settings for the dimension that no longer exists + aData.RemoveDimensionByName( aDimName ); + } + } + else if ( pNumGroupDim ) + { + // remove the numerical grouping + pDimData->RemoveNumGroupDimension( aDimName ); + // SaveData settings can remain unchanged - the same dimension still exists + } + + // apply changes + ScDBDocFunc aFunc( *GetViewData().GetDocShell() ); + pDPObj->SetSaveData( aData ); + aFunc.RefreshPivotTableGroups(pDPObj); + + // unmark cell selection + Unmark(); +} + +static OUString lcl_replaceMemberNameInSubtotal(const OUString& rSubtotal, std::u16string_view rMemberName) +{ + sal_Int32 n = rSubtotal.getLength(); + const sal_Unicode* p = rSubtotal.getStr(); + OUStringBuffer aBuf, aWordBuf; + for (sal_Int32 i = 0; i < n; ++i) + { + sal_Unicode c = p[i]; + if (c == ' ') + { + OUString aWord = aWordBuf.makeStringAndClear(); + if (aWord == rMemberName) + aBuf.append('?'); + else + aBuf.append(aWord); + aBuf.append(c); + } + else if (c == '\\') + { + // Escape a backslash character. + aWordBuf.append(OUStringChar(c) + OUStringChar(c)); + } + else if (c == '?') + { + // A literal '?' must be escaped with a backslash ('\'); + aWordBuf.append("\\" + OUStringChar(c)); + } + else + aWordBuf.append(c); + } + + if (!aWordBuf.isEmpty()) + { + OUString aWord = aWordBuf.makeStringAndClear(); + if (aWord == rMemberName) + aBuf.append('?'); + else + aBuf.append(aWord); + } + + return aBuf.makeStringAndClear(); +} + +void ScDBFunc::DataPilotInput( const ScAddress& rPos, const OUString& rString ) +{ + using namespace ::com::sun::star::sheet; + + ScDocument& rDoc = GetViewData().GetDocument(); + ScDPObject* pDPObj = rDoc.GetDPAtCursor( rPos.Col(), rPos.Row(), rPos.Tab() ); + if (!pDPObj) + return; + + OUString aOldText = rDoc.GetString(rPos.Col(), rPos.Row(), rPos.Tab()); + + if ( aOldText == rString ) + { + // nothing to do: silently exit + return; + } + + TranslateId pErrorId; + + pDPObj->BuildAllDimensionMembers(); + ScDPSaveData aData( *pDPObj->GetSaveData() ); + bool bChange = false; + bool bNeedReloadGroups = false; + + DataPilotFieldOrientation nOrient = DataPilotFieldOrientation_HIDDEN; + tools::Long nField = pDPObj->GetHeaderDim( rPos, nOrient ); + if ( nField >= 0 ) + { + // changing a field title + if ( aData.GetExistingDimensionData() ) + { + // only group dimensions can be renamed + + ScDPDimensionSaveData* pDimData = aData.GetDimensionData(); + ScDPSaveGroupDimension* pGroupDim = pDimData->GetNamedGroupDimAcc( aOldText ); + if ( pGroupDim ) + { + // valid name: not empty, no existing dimension (group or other) + if (!rString.isEmpty() && !pDPObj->IsDimNameInUse(rString)) + { + pGroupDim->Rename( rString ); + + // also rename in SaveData to preserve the field settings + ScDPSaveDimension* pSaveDim = aData.GetDimensionByName( aOldText ); + pSaveDim->SetName( rString ); + + bChange = true; + } + else + pErrorId = STR_INVALIDNAME; + } + } + else if (nOrient == DataPilotFieldOrientation_COLUMN || nOrient == DataPilotFieldOrientation_ROW) + { + bool bDataLayout = false; + OUString aDimName = pDPObj->GetDimName(nField, bDataLayout); + ScDPSaveDimension* pDim = bDataLayout ? aData.GetDataLayoutDimension() : aData.GetDimensionByName(aDimName); + if (pDim) + { + if (!rString.isEmpty()) + { + if (rString.equalsIgnoreAsciiCase(aDimName)) + { + pDim->RemoveLayoutName(); + bChange = true; + } + else if (!pDPObj->IsDimNameInUse(rString)) + { + pDim->SetLayoutName(rString); + bChange = true; + } + else + pErrorId = STR_INVALIDNAME; + } + else + pErrorId = STR_INVALIDNAME; + } + } + } + else if (pDPObj->IsDataDescriptionCell(rPos)) + { + // There is only one data dimension. + ScDPSaveDimension* pDim = aData.GetFirstDimension(sheet::DataPilotFieldOrientation_DATA); + if (pDim) + { + if (!rString.isEmpty()) + { + if (pDim->GetName().equalsIgnoreAsciiCase(rString)) + { + pDim->RemoveLayoutName(); + bChange = true; + } + else if (!pDPObj->IsDimNameInUse(rString)) + { + pDim->SetLayoutName(rString); + bChange = true; + } + else + pErrorId = STR_INVALIDNAME; + } + else + pErrorId = STR_INVALIDNAME; + } + } + else + { + // This is not a field header. + sheet::DataPilotTableHeaderData aPosData; + pDPObj->GetHeaderPositionData(rPos, aPosData); + + if ((aPosData.Flags & MemberResultFlags::HASMEMBER) && !aOldText.isEmpty()) + { + if ( aData.GetExistingDimensionData() && !(aPosData.Flags & MemberResultFlags::SUBTOTAL)) + { + bool bIsDataLayout; + OUString aDimName = pDPObj->GetDimName( aPosData.Dimension, bIsDataLayout ); + + ScDPDimensionSaveData* pDimData = aData.GetDimensionData(); + ScDPSaveGroupDimension* pGroupDim = pDimData->GetNamedGroupDimAcc( aDimName ); + if ( pGroupDim ) + { + // valid name: not empty, no existing group in this dimension + //! ignore case? + if (!rString.isEmpty() && !pGroupDim->GetNamedGroup(rString)) + { + ScDPSaveGroupItem* pGroup = pGroupDim->GetNamedGroupAcc( aOldText ); + if ( pGroup ) + pGroup->Rename( rString ); // rename the existing group + else + { + // create a new group to replace the automatic group + ScDPSaveGroupItem aGroup( rString ); + aGroup.AddElement( aOldText ); + pGroupDim->AddGroupItem( aGroup ); + } + + // in both cases also adjust savedata, to preserve member settings (show details) + ScDPSaveDimension* pSaveDim = aData.GetDimensionByName( aDimName ); + ScDPSaveMember* pSaveMember = pSaveDim->GetExistingMemberByName( aOldText ); + if ( pSaveMember ) + pSaveMember->SetName( rString ); + + bChange = true; + bNeedReloadGroups = true; + } + else + pErrorId = STR_INVALIDNAME; + } + } + else if (aPosData.Flags & MemberResultFlags::GRANDTOTAL) + { + aData.SetGrandTotalName(rString); + bChange = true; + } + else if (aPosData.Dimension >= 0 && !aPosData.MemberName.isEmpty()) + { + bool bDataLayout = false; + OUString aDimName = pDPObj->GetDimName(static_cast(aPosData.Dimension), bDataLayout); + if (bDataLayout) + { + // data dimension + do + { + if (aPosData.Flags & MemberResultFlags::SUBTOTAL) + break; + + ScDPSaveDimension* pDim = aData.GetDimensionByName(aPosData.MemberName); + if (!pDim) + break; + + if (rString.isEmpty()) + { + pErrorId = STR_INVALIDNAME; + break; + } + + if (aPosData.MemberName.equalsIgnoreAsciiCase(rString)) + { + pDim->RemoveLayoutName(); + bChange = true; + } + else if (!pDPObj->IsDimNameInUse(rString)) + { + pDim->SetLayoutName(rString); + bChange = true; + } + else + pErrorId = STR_INVALIDNAME; + } + while (false); + } + else + { + // field member + do + { + ScDPSaveDimension* pDim = aData.GetDimensionByName(aDimName); + if (!pDim) + break; + + ScDPSaveMember* pMem = pDim->GetExistingMemberByName(aPosData.MemberName); + if (!pMem) + break; + + if (aPosData.Flags & MemberResultFlags::SUBTOTAL) + { + // Change subtotal only when the table has one data dimension. + if (aData.GetDataDimensionCount() > 1) + break; + + // display name for subtotal is allowed only if the subtotal type is 'Automatic'. + if (pDim->GetSubTotalsCount() != 1) + break; + + if (pDim->GetSubTotalFunc(0) != ScGeneralFunction::AUTO) + break; + + const std::optional & pLayoutName = pMem->GetLayoutName(); + OUString aMemberName; + if (pLayoutName) + aMemberName = *pLayoutName; + else + aMemberName = aPosData.MemberName; + + OUString aNew = lcl_replaceMemberNameInSubtotal(rString, aMemberName); + pDim->SetSubtotalName(aNew); + bChange = true; + } + else + { + // Check to make sure the member name isn't + // already used. + if (!rString.isEmpty()) + { + if (rString.equalsIgnoreAsciiCase(pMem->GetName())) + { + pMem->RemoveLayoutName(); + bChange = true; + } + else if (!pDim->IsMemberNameInUse(rString)) + { + pMem->SetLayoutName(rString); + bChange = true; + } + else + pErrorId = STR_INVALIDNAME; + } + else + pErrorId = STR_INVALIDNAME; + } + } + while (false); + } + } + } + } + + if ( bChange ) + { + // apply changes + ScDBDocFunc aFunc( *GetViewData().GetDocShell() ); + pDPObj->SetSaveData( aData ); + if (bNeedReloadGroups) + { + ScDPCollection* pDPs = rDoc.GetDPCollection(); + if (pDPs) + { + o3tl::sorted_vector aRefs; + // tdf#111305: Reload groups in cache after modifications. + pDPs->ReloadGroupsInCache(pDPObj, aRefs); + } // pDPs + } // bNeedReloadGroups + aFunc.UpdatePivotTable(*pDPObj, true, false); + } + else + { + if (!pErrorId) + pErrorId = STR_ERR_DATAPILOT_INPUT; + ErrorMessage(pErrorId); + } +} + +static void lcl_MoveToEnd( ScDPSaveDimension& rDim, const OUString& rItemName ) +{ + std::unique_ptr pNewMember; + const ScDPSaveMember* pOldMember = rDim.GetExistingMemberByName( rItemName ); + if ( pOldMember ) + pNewMember.reset(new ScDPSaveMember( *pOldMember )); + else + pNewMember.reset(new ScDPSaveMember( rItemName )); + rDim.AddMember( std::move(pNewMember) ); + // AddMember takes ownership of the new pointer, + // puts it to the end of the list even if it was in the list before. +} + +namespace { + +struct ScOUStringCollate +{ + CollatorWrapper* mpCollator; + + explicit ScOUStringCollate(CollatorWrapper* pColl) : mpCollator(pColl) {} + + bool operator()(const OUString& rStr1, const OUString& rStr2) const + { + return ( mpCollator->compareString(rStr1, rStr2) < 0 ); + } +}; + +} + +void ScDBFunc::DataPilotSort(ScDPObject* pDPObj, tools::Long nDimIndex, bool bAscending, const sal_uInt16* pUserListId) +{ + if (!pDPObj) + return; + + // We need to run this to get all members later. + if ( pUserListId ) + pDPObj->BuildAllDimensionMembers(); + + if (nDimIndex < 0) + // Invalid dimension index. Bail out. + return; + + ScDPSaveData* pSaveData = pDPObj->GetSaveData(); + if (!pSaveData) + return; + + ScDPSaveData aNewSaveData(*pSaveData); + bool bDataLayout; + OUString aDimName = pDPObj->GetDimName(nDimIndex, bDataLayout); + ScDPSaveDimension* pSaveDim = aNewSaveData.GetDimensionByName(aDimName); + if (!pSaveDim) + return; + + // manual evaluation of sort order is only needed if a user list id is given + if ( pUserListId ) + { + typedef ScDPSaveDimension::MemberList MemList; + const MemList& rDimMembers = pSaveDim->GetMembers(); + vector aMembers; + std::unordered_set aMemberSet; + size_t nMemberCount = 0; + for (ScDPSaveMember* pMem : rDimMembers) + { + aMembers.push_back(pMem->GetName()); + aMemberSet.insert(pMem->GetName()); + ++nMemberCount; + } + + // Sort the member list in ascending order. + ScOUStringCollate aCollate( &ScGlobal::GetCollator() ); + std::stable_sort(aMembers.begin(), aMembers.end(), aCollate); + + // Collect and rank those custom sort strings that also exist in the member name list. + + typedef std::unordered_map UserSortMap; + UserSortMap aSubStrs; + sal_uInt16 nSubCount = 0; + ScUserList* pUserList = ScGlobal::GetUserList(); + if (!pUserList) + return; + + { + size_t n = pUserList->size(); + if (!n || *pUserListId >= static_cast(n)) + return; + } + + const ScUserListData& rData = (*pUserList)[*pUserListId]; + sal_uInt16 n = rData.GetSubCount(); + for (sal_uInt16 i = 0; i < n; ++i) + { + OUString aSub = rData.GetSubStr(i); + if (!aMemberSet.count(aSub)) + // This string doesn't exist in the member name set. Don't add this. + continue; + + aSubStrs.emplace(aSub, nSubCount++); + } + + // Rank all members. + + vector aRankedNames(nMemberCount); + sal_uInt16 nCurStrId = 0; + for (auto const& aMemberName : aMembers) + { + sal_uInt16 nRank = 0; + UserSortMap::const_iterator itrSub = aSubStrs.find(aMemberName); + if (itrSub == aSubStrs.end()) + nRank = nSubCount + nCurStrId++; + else + nRank = itrSub->second; + + if (!bAscending) + nRank = static_cast< sal_uInt16 >( nMemberCount - nRank - 1 ); + + aRankedNames[nRank] = aMemberName; + } + + // Re-order ScDPSaveMember instances with the new ranks. + for (auto const& aRankedName : aRankedNames) + { + const ScDPSaveMember* pOldMem = pSaveDim->GetExistingMemberByName(aRankedName); + if (!pOldMem) + // All members are supposed to be present. + continue; + + pSaveDim->AddMember(std::unique_ptr(new ScDPSaveMember(*pOldMem))); + } + + // Set the sorting mode to manual for now. We may introduce a new sorting + // mode later on. + + sheet::DataPilotFieldSortInfo aSortInfo; + aSortInfo.Mode = sheet::DataPilotFieldSortMode::MANUAL; + pSaveDim->SetSortInfo(&aSortInfo); + } + else + { + // without user list id, just apply sorting mode + + sheet::DataPilotFieldSortInfo aSortInfo; + aSortInfo.Mode = sheet::DataPilotFieldSortMode::NAME; + aSortInfo.IsAscending = bAscending; + pSaveDim->SetSortInfo(&aSortInfo); + } + + // Update the datapilot with the newly sorted field members. + + std::unique_ptr pNewObj(new ScDPObject(*pDPObj)); + pNewObj->SetSaveData(aNewSaveData); + ScDBDocFunc aFunc(*GetViewData().GetDocShell()); + + aFunc.DataPilotUpdate(pDPObj, pNewObj.get(), true, false); +} + +bool ScDBFunc::DataPilotMove( const ScRange& rSource, const ScAddress& rDest ) +{ + bool bRet = false; + ScDocument& rDoc = GetViewData().GetDocument(); + ScDPObject* pDPObj = rDoc.GetDPAtCursor( rSource.aStart.Col(), rSource.aStart.Row(), rSource.aStart.Tab() ); + if ( pDPObj && pDPObj == rDoc.GetDPAtCursor( rDest.Col(), rDest.Row(), rDest.Tab() ) ) + { + sheet::DataPilotTableHeaderData aDestData; + pDPObj->GetHeaderPositionData( rDest, aDestData ); + bool bValid = ( aDestData.Dimension >= 0 ); // dropping onto a field + + // look through the source range + std::unordered_set< OUString > aMembersSet; // for lookup + std::vector< OUString > aMembersVector; // members in original order, for inserting + aMembersVector.reserve( std::max( static_cast( rSource.aEnd.Col() - rSource.aStart.Col() + 1 ), + static_cast( rSource.aEnd.Row() - rSource.aStart.Row() + 1 ) ) ); + for (SCROW nRow = rSource.aStart.Row(); bValid && nRow <= rSource.aEnd.Row(); ++nRow ) + for (SCCOL nCol = rSource.aStart.Col(); bValid && nCol <= rSource.aEnd.Col(); ++nCol ) + { + sheet::DataPilotTableHeaderData aSourceData; + pDPObj->GetHeaderPositionData( ScAddress( nCol, nRow, rSource.aStart.Tab() ), aSourceData ); + if ( aSourceData.Dimension == aDestData.Dimension && !aSourceData.MemberName.isEmpty() ) + { + if ( aMembersSet.insert( aSourceData.MemberName ).second ) + { + aMembersVector.push_back( aSourceData.MemberName ); + } + // duplicates are ignored + } + else + bValid = false; // empty (subtotal) or different field + } + + if ( bValid ) + { + bool bIsDataLayout; + OUString aDimName = pDPObj->GetDimName( aDestData.Dimension, bIsDataLayout ); + if ( !bIsDataLayout ) + { + ScDPSaveData aData( *pDPObj->GetSaveData() ); + ScDPSaveDimension* pDim = aData.GetDimensionByName( aDimName ); + + // get all member names in source order + uno::Sequence aMemberNames; + pDPObj->GetMemberNames( aDestData.Dimension, aMemberNames ); + + bool bInserted = false; + + for (const OUString& aMemberStr : std::as_const(aMemberNames)) + { + if ( !bInserted && aMemberStr == aDestData.MemberName ) + { + // insert dragged items before this item + for ( const auto& rMember : aMembersVector ) + lcl_MoveToEnd( *pDim, rMember ); + bInserted = true; + } + + if ( aMembersSet.find( aMemberStr ) == aMembersSet.end() ) // skip dragged items + lcl_MoveToEnd( *pDim, aMemberStr ); + } + // insert dragged item at end if dest wasn't found (for example, empty) + if ( !bInserted ) + for ( const auto& rMember : aMembersVector ) + lcl_MoveToEnd( *pDim, rMember ); + + // Items that were in SaveData, but not in the source, end up at the start of the list. + + // set flag for manual sorting + sheet::DataPilotFieldSortInfo aSortInfo; + aSortInfo.Mode = sheet::DataPilotFieldSortMode::MANUAL; + pDim->SetSortInfo( &aSortInfo ); + + // apply changes + ScDBDocFunc aFunc( *GetViewData().GetDocShell() ); + std::unique_ptr pNewObj(new ScDPObject( *pDPObj )); + pNewObj->SetSaveData( aData ); + aFunc.DataPilotUpdate( pDPObj, pNewObj.get(), true, false ); //! bApi for drag&drop? + pNewObj.reset(); + + Unmark(); // entry was moved - no use in leaving the old cell selected + + bRet = true; + } + } + } + + return bRet; +} + +bool ScDBFunc::HasSelectionForDrillDown( css::sheet::DataPilotFieldOrientation& rOrientation ) +{ + bool bRet = false; + + ScDPObject* pDPObj = GetViewData().GetDocument().GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ); + if ( pDPObj ) + { + ScDPUniqueStringSet aEntries; + tools::Long nSelectDimension = -1; + GetSelectedMemberList( aEntries, nSelectDimension ); + + if (!aEntries.empty()) + { + bool bIsDataLayout; + OUString aDimName = pDPObj->GetDimName( nSelectDimension, bIsDataLayout ); + if ( !bIsDataLayout ) + { + ScDPSaveData* pSaveData = pDPObj->GetSaveData(); + ScDPSaveDimension* pDim = pSaveData->GetExistingDimensionByName( aDimName ); + if ( pDim ) + { + css::sheet::DataPilotFieldOrientation nDimOrient = pDim->GetOrientation(); + ScDPSaveDimension* pInner = pSaveData->GetInnermostDimension( nDimOrient ); + if ( pDim == pInner ) + { + rOrientation = nDimOrient; + bRet = true; + } + } + } + } + } + + return bRet; +} + +void ScDBFunc::SetDataPilotDetails(bool bShow, const OUString* pNewDimensionName) +{ + ScDPObject* pDPObj = GetViewData().GetDocument().GetDPAtCursor( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ); + if ( !pDPObj ) + return; + + ScDPUniqueStringSet aEntries; + tools::Long nSelectDimension = -1; + GetSelectedMemberList( aEntries, nSelectDimension ); + + if (aEntries.empty()) + return; + + bool bIsDataLayout; + OUString aDimName = pDPObj->GetDimName( nSelectDimension, bIsDataLayout ); + if ( bIsDataLayout ) + return; + + ScDPSaveData aData( *pDPObj->GetSaveData() ); + ScDPSaveDimension* pDim = aData.GetDimensionByName( aDimName ); + + if ( bShow && pNewDimensionName ) + { + // add the new dimension with the same orientation, at the end + + ScDPSaveDimension* pNewDim = aData.GetDimensionByName( *pNewDimensionName ); + ScDPSaveDimension* pDuplicated = nullptr; + if ( pNewDim->GetOrientation() == sheet::DataPilotFieldOrientation_DATA ) + { + // Need to duplicate the dimension, create column/row in addition to data: + // The duplicated dimension inherits the existing settings, pNewDim is modified below. + pDuplicated = aData.DuplicateDimension( *pNewDimensionName ); + } + + css::sheet::DataPilotFieldOrientation nOrientation = pDim->GetOrientation(); + pNewDim->SetOrientation( nOrientation ); + + tools::Long nPosition = LONG_MAX; + aData.SetPosition( pNewDim, nPosition ); + + ScDPSaveDimension* pDataLayout = aData.GetDataLayoutDimension(); + if ( pDataLayout->GetOrientation() == nOrientation && + aData.GetDataDimensionCount() <= 1 ) + { + // If there is only one data dimension, the data layout dimension + // must still be the last one in its orientation. + aData.SetPosition( pDataLayout, nPosition ); + } + + if ( pDuplicated ) + { + // The duplicated (data) dimension needs to be behind the original dimension + aData.SetPosition( pDuplicated, nPosition ); + } + + // Hide details for all visible members (selected are changed below). + //! Use all members from source level instead (including non-visible)? + + ScDPUniqueStringSet aVisibleEntries; + pDPObj->GetMemberResultNames( aVisibleEntries, nSelectDimension ); + + for (const OUString& aVisName : aVisibleEntries) + { + ScDPSaveMember* pMember = pDim->GetMemberByName( aVisName ); + pMember->SetShowDetails( false ); + } + } + + for (const auto& rEntry : aEntries) + { + ScDPSaveMember* pMember = pDim->GetMemberByName(rEntry); + pMember->SetShowDetails( bShow ); + } + + // apply changes + ScDBDocFunc aFunc( *GetViewData().GetDocShell() ); + std::unique_ptr pNewObj(new ScDPObject( *pDPObj )); + pNewObj->SetSaveData( aData ); + aFunc.DataPilotUpdate( pDPObj, pNewObj.get(), true, false ); + pNewObj.reset(); + + // unmark cell selection + Unmark(); +} + +void ScDBFunc::ShowDataPilotSourceData( ScDPObject& rDPObj, const Sequence& rFilters ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + if (rDoc.GetDocumentShell()->IsReadOnly()) + { + ErrorMessage(STR_READONLYERR); + return; + } + + Reference xDimSupplier = rDPObj.GetSource(); + Reference xDims = xDimSupplier->getDimensions(); + Reference xDDSupplier(xDimSupplier, UNO_QUERY); + if (!xDDSupplier.is()) + return; + + Sequence< Sequence > aTabData = xDDSupplier->getDrillDownData(rFilters); + sal_Int32 nRowSize = aTabData.getLength(); + if (nRowSize <= 1) + // There is no data to show. Bail out. + return; + + SCCOL nColSize = aTabData[0].getLength(); + + SCTAB nNewTab = GetViewData().GetTabNo(); + + ScDocumentUniquePtr pInsDoc(new ScDocument(SCDOCMODE_CLIP)); + pInsDoc->ResetClip( &rDoc, nNewTab ); + for (SCROW nRow = 0; nRow < nRowSize; ++nRow) + { + for (SCCOL nCol = 0; nCol < nColSize; ++nCol) + { + const Any& rAny = aTabData[nRow][nCol]; + OUString aStr; + double fVal; + if (rAny >>= aStr) + { + pInsDoc->SetString(ScAddress(nCol,nRow,nNewTab), aStr); + } + else if (rAny >>= fVal) + pInsDoc->SetValue(nCol, nRow, nNewTab, fVal); + } + } + + // set number format (important for dates) + for (SCCOL nCol = 0; nCol < nColSize; ++nCol) + { + OUString aStr; + if (!(aTabData[0][nCol] >>= aStr)) + continue; + + Reference xPropSet(xDims->getByName(aStr), UNO_QUERY); + if (!xPropSet.is()) + continue; + + Any any = xPropSet->getPropertyValue( SC_UNO_DP_NUMBERFO ); + sal_Int32 nNumFmt = 0; + if (!(any >>= nNumFmt)) + continue; + + ScPatternAttr aPattern( pInsDoc->GetPool() ); + aPattern.GetItemSet().Put( SfxUInt32Item(ATTR_VALUE_FORMAT, static_cast(nNumFmt)) ); + pInsDoc->ApplyPatternAreaTab(nCol, 1, nCol, nRowSize-1, nNewTab, aPattern); + } + + SCCOL nEndCol = 0; + SCROW nEndRow = 0; + pInsDoc->GetCellArea( nNewTab, nEndCol, nEndRow ); + pInsDoc->SetClipArea( ScRange( 0, 0, nNewTab, nEndCol, nEndRow, nNewTab ) ); + + SfxUndoManager* pMgr = GetViewData().GetDocShell()->GetUndoManager(); + OUString aUndo = ScResId( STR_UNDO_DOOUTLINE ); + pMgr->EnterListAction( aUndo, aUndo, 0, GetViewData().GetViewShell()->GetViewShellId() ); + + OUString aNewTabName; + rDoc.CreateValidTabName(aNewTabName); + if ( InsertTable(aNewTabName, nNewTab) ) + PasteFromClip( InsertDeleteFlags::ALL, pInsDoc.get() ); + + pMgr->LeaveListAction(); +} + +// repeat data base operations (sorting, filtering, subtotals) + +void ScDBFunc::RepeatDB( bool bRecord ) +{ + SCCOL nCurX = GetViewData().GetCurX(); + SCROW nCurY = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + ScDocument& rDoc = GetViewData().GetDocument(); + ScDBData* pDBData = GetDBData(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + ScQueryParam aQueryParam; + pDBData->GetQueryParam( aQueryParam ); + bool bQuery = aQueryParam.GetEntry(0).bDoQuery; + + ScSortParam aSortParam; + pDBData->GetSortParam( aSortParam ); + bool bSort = aSortParam.maKeyState[0].bDoSort; + + ScSubTotalParam aSubTotalParam; + pDBData->GetSubTotalParam( aSubTotalParam ); + bool bSubTotal = aSubTotalParam.bGroupActive[0] && !aSubTotalParam.bRemoveOnly; + + if ( bQuery || bSort || bSubTotal ) + { + bool bQuerySize = false; + ScRange aOldQuery; + ScRange aNewQuery; + if (bQuery && !aQueryParam.bInplace) + { + ScDBData* pDest = rDoc.GetDBAtCursor( aQueryParam.nDestCol, aQueryParam.nDestRow, + aQueryParam.nDestTab, ScDBDataPortion::TOP_LEFT ); + if (pDest && pDest->IsDoSize()) + { + pDest->GetArea( aOldQuery ); + bQuerySize = true; + } + } + + SCTAB nDummy; + SCCOL nStartCol; + SCROW nStartRow; + SCCOL nEndCol; + SCROW nEndRow; + pDBData->GetArea( nDummy, nStartCol, nStartRow, nEndCol, nEndRow ); + + //! undo only needed data ? + + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr pUndoTab; + std::unique_ptr pUndoRange; + std::unique_ptr pUndoDB; + + if (bRecord) + { + SCTAB nTabCount = rDoc.GetTableCount(); + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + ScOutlineTable* pTable = rDoc.GetOutlineTable( nTab ); + if (pTable) + { + pUndoTab.reset(new ScOutlineTable( *pTable )); + + SCCOLROW nOutStartCol; // row/column status + SCCOLROW nOutStartRow; + SCCOLROW nOutEndCol; + SCCOLROW nOutEndRow; + pTable->GetColArray().GetRange( nOutStartCol, nOutEndCol ); + pTable->GetRowArray().GetRange( nOutStartRow, nOutEndRow ); + + pUndoDoc->InitUndo( rDoc, nTab, nTab, true, true ); + rDoc.CopyToDocument( static_cast(nOutStartCol), 0, nTab, static_cast(nOutEndCol), rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, false, *pUndoDoc ); + rDoc.CopyToDocument( 0, nOutStartRow, nTab, rDoc.MaxCol(), nOutEndRow, nTab, InsertDeleteFlags::NONE, false, *pUndoDoc ); + } + else + pUndoDoc->InitUndo( rDoc, nTab, nTab, false, true ); + + // Record data range - including filter results + rDoc.CopyToDocument( 0,nStartRow,nTab, rDoc.MaxCol(),nEndRow,nTab, InsertDeleteFlags::ALL, false, *pUndoDoc ); + + // all formulas for reference + rDoc.CopyToDocument( 0,0,0, rDoc.MaxCol(),rDoc.MaxRow(),nTabCount-1, InsertDeleteFlags::FORMULA, false, *pUndoDoc ); + + // data base and other ranges + ScRangeName* pDocRange = rDoc.GetRangeName(); + if (!pDocRange->empty()) + pUndoRange.reset(new ScRangeName( *pDocRange )); + ScDBCollection* pDocDB = rDoc.GetDBCollection(); + if (!pDocDB->empty()) + pUndoDB.reset(new ScDBCollection( *pDocDB )); + } + + if (bSort && bSubTotal) + { + // sort without subtotals + + aSubTotalParam.bRemoveOnly = true; // is reset below + DoSubTotals( aSubTotalParam, false ); + } + + if (bSort) + { + pDBData->GetSortParam( aSortParam ); // range may have changed + Sort( aSortParam, false, false); + } + if (bQuery) + { + pDBData->GetQueryParam( aQueryParam ); // range may have changed + ScRange aAdvSource; + if (pDBData->GetAdvancedQuerySource(aAdvSource)) + { + rDoc.CreateQueryParam(aAdvSource, aQueryParam); + Query( aQueryParam, &aAdvSource, false ); + } + else + Query( aQueryParam, nullptr, false ); + + // if not inplace the sheet may have changed + if ( !aQueryParam.bInplace && aQueryParam.nDestTab != nTab ) + SetTabNo( nTab ); + } + if (bSubTotal) + { + pDBData->GetSubTotalParam( aSubTotalParam ); // range may have changed + aSubTotalParam.bRemoveOnly = false; + DoSubTotals( aSubTotalParam, false ); + } + + if (bRecord) + { + SCTAB nDummyTab; + SCCOL nDummyCol; + SCROW nDummyRow, nNewEndRow; + pDBData->GetArea( nDummyTab, nDummyCol,nDummyRow, nDummyCol,nNewEndRow ); + + const ScRange* pOld = nullptr; + const ScRange* pNew = nullptr; + if (bQuerySize) + { + ScDBData* pDest = rDoc.GetDBAtCursor( aQueryParam.nDestCol, aQueryParam.nDestRow, + aQueryParam.nDestTab, ScDBDataPortion::TOP_LEFT ); + if (pDest) + { + pDest->GetArea( aNewQuery ); + pOld = &aOldQuery; + pNew = &aNewQuery; + } + } + + GetViewData().GetDocShell()->GetUndoManager()->AddUndoAction( + std::make_unique( GetViewData().GetDocShell(), nTab, + nStartCol, nStartRow, nEndCol, nEndRow, + nNewEndRow, + nCurX, nCurY, + std::move(pUndoDoc), std::move(pUndoTab), + std::move(pUndoRange), std::move(pUndoDB), + pOld, pNew ) ); + } + + GetViewData().GetDocShell()->PostPaint( + ScRange(0, 0, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab), + PaintPartFlags::Grid | PaintPartFlags::Left | PaintPartFlags::Top | PaintPartFlags::Size); + } + else // "no not execute any operations" + ErrorMessage(STR_MSSG_REPEATDB_0); +} + +void ScDBFunc::OnLOKShowHideColRow(bool bColumns, SCCOLROW nStart) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + SCTAB nCurrentTabIndex = GetViewData().GetTabNo(); + SfxViewShell* pThisViewShell = GetViewData().GetViewShell(); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + ScTabViewShell* pTabViewShell = dynamic_cast(pViewShell); + if (pTabViewShell && pTabViewShell->GetDocId() == pThisViewShell->GetDocId()) + { + if (bColumns) + { + if (ScPositionHelper* pPosHelper = pTabViewShell->GetViewData().GetLOKWidthHelper(nCurrentTabIndex)) + pPosHelper->invalidateByIndex(nStart); + } + else + { + if (ScPositionHelper* pPosHelper = pTabViewShell->GetViewData().GetLOKHeightHelper(nCurrentTabIndex)) + pPosHelper->invalidateByIndex(nStart); + } + + if (pTabViewShell->getPart() == nCurrentTabIndex) + { + pTabViewShell->ShowCursor(); + pTabViewShell->MarkDataChanged(); + } + } + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/dbfunc4.cxx b/sc/source/ui/view/dbfunc4.cxx new file mode 100644 index 0000000000..f13035b291 --- /dev/null +++ b/sc/source/ui/view/dbfunc4.cxx @@ -0,0 +1,73 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include + +#include +#include +#include + +using namespace com::sun::star; + +sal_uInt16 ScDBFunc::DoUpdateCharts(const ScAddress& rPos, ScDocument& rDoc, bool bAllCharts) +{ + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (!pModel) + return 0; + + sal_uInt16 nFound = 0; + + sal_uInt16 nPageCount = pModel->GetPageCount(); + for (sal_uInt16 nPageNo = 0; nPageNo < nPageCount; nPageNo++) + { + SdrPage* pPage = pModel->GetPage(nPageNo); + OSL_ENSURE(pPage, "Page ?"); + + SdrObjListIter aIter(pPage, SdrIterMode::DeepNoGroups); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if (pObject->GetObjIdentifier() == SdrObjKind::OLE2 && ScDocument::IsChart(pObject)) + { + OUString aName = static_cast(pObject)->GetPersistName(); + bool bHit = true; + if (!bAllCharts) + { + ScRangeList aRanges; + bool bColHeaders = false; + bool bRowHeaders = false; + rDoc.GetOldChartParameters(aName, aRanges, bColHeaders, bRowHeaders); + bHit = aRanges.Contains(rPos); + } + if (bHit) + { + rDoc.UpdateChart(aName); + ++nFound; + } + } + pObject = aIter.Next(); + } + } + return nFound; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/drawutil.cxx b/sc/source/ui/view/drawutil.cxx new file mode 100644 index 0000000000..9658fa7ff2 --- /dev/null +++ b/sc/source/ui/view/drawutil.cxx @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include + +#include +#include +#include + +void ScDrawUtil::CalcScale( const ScDocument& rDoc, SCTAB nTab, + SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, + const OutputDevice* pDev, + const Fraction& rZoomX, const Fraction& rZoomY, + double nPPTX, double nPPTY, + Fraction& rScaleX, Fraction& rScaleY ) +{ + tools::Long nPixelX = 0; + tools::Long nTwipsX = 0; + tools::Long nPixelY = 0; + tools::Long nTwipsY = 0; + for (SCCOL i=nStartCol; i(nWidth); + nPixelX += ScViewData::ToPixel( nWidth, nPPTX ); + } + + for (SCROW nRow = nStartRow; nRow <= nEndRow-1; ++nRow) + { + SCROW nLastRow = nRow; + if (rDoc.RowHidden(nRow, nTab, nullptr, &nLastRow)) + { + nRow = nLastRow; + continue; + } + + sal_uInt16 nHeight = rDoc.GetRowHeight(nRow, nTab); + nTwipsY += static_cast(nHeight); + nPixelY += ScViewData::ToPixel(nHeight, nPPTY); + } + + MapMode aHMMMode( MapUnit::Map100thMM, Point(), rZoomX, rZoomY ); + Point aPixelLog = pDev->PixelToLogic( Point( nPixelX,nPixelY ), aHMMMode ); + + // Fraction(double) ctor can be used here (and avoid overflows of PixelLog * Zoom) + // because ReduceInaccurate is called later anyway. + + if ( aPixelLog.X() && nTwipsX ) + rScaleX = Fraction( static_cast(aPixelLog.X()) * + static_cast(rZoomX.GetNumerator()) / + o3tl::convert(nTwipsX, o3tl::Length::twip, o3tl::Length::mm100) / + static_cast(rZoomX.GetDenominator()) ); + else + rScaleX = Fraction( 1, 1 ); + + if ( aPixelLog.Y() && nTwipsY ) + rScaleY = Fraction( static_cast(aPixelLog.Y()) * + static_cast(rZoomY.GetNumerator()) / + o3tl::convert(nTwipsY, o3tl::Length::twip, o3tl::Length::mm100) / + static_cast(rZoomY.GetDenominator()) ); + else + rScaleY = Fraction( 1, 1 ); + + // 25 bits of accuracy are needed to always hit the right part of + // cells in the last rows (was 17 before 1M rows). + rScaleX.ReduceInaccurate( 25 ); + rScaleY.ReduceInaccurate( 25 ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/drawvie3.cxx b/sc/source/ui/view/drawvie3.cxx new file mode 100644 index 0000000000..6561423ab2 --- /dev/null +++ b/sc/source/ui/view/drawvie3.cxx @@ -0,0 +1,259 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include "imapwrap.hxx" +#include +#include +#include +#include +#include + +ScDrawView::ScDrawView( + OutputDevice* pOut, + ScViewData* pData ) +: FmFormView(*pData->GetDocument().GetDrawLayer(), pOut), + pViewData( pData ), + pDev( pOut ), + rDoc( pData->GetDocument() ), + nTab( pData->GetTabNo() ), + pDropMarkObj( nullptr ), + bInConstruct( true ) +{ + SetNegativeX(comphelper::LibreOfficeKit::isActive() && rDoc.IsLayoutRTL(nTab)); + // #i73602# Use default from the configuration + SetBufferedOverlayAllowed(SvtOptionsDrawinglayer::IsOverlayBuffer_Calc()); + + // #i74769#, #i75172# Use default from the configuration + SetBufferedOutputAllowed(SvtOptionsDrawinglayer::IsPaintBuffer_Calc()); + + Construct(); +} + +// set anchor + +void ScDrawView::SetPageAnchored() +{ + if( !AreObjectsMarked() ) + return; + + const SdrMarkList* pMark = &GetMarkedObjectList(); + const size_t nCount = pMark->GetMarkCount(); + + BegUndo(ScResId(SCSTR_UNDO_PAGE_ANCHOR)); + for( size_t i=0; iGetMark(i)->GetMarkedSdrObj(); + AddUndo (std::make_unique( pObj, &rDoc, nTab )); + ScDrawLayer::SetPageAnchored( *pObj ); + } + EndUndo(); + + if ( pViewData ) + pViewData->GetDocShell()->SetDrawModified(); + + // Remove the anchor object. + maHdlList.RemoveAllByKind(SdrHdlKind::Anchor); + maHdlList.RemoveAllByKind(SdrHdlKind::Anchor_TR); +} + +void ScDrawView::SetCellAnchored(bool bResizeWithCell) +{ + if( !AreObjectsMarked() ) + return; + + const SdrMarkList* pMark = &GetMarkedObjectList(); + const size_t nCount = pMark->GetMarkCount(); + + BegUndo(ScResId(SCSTR_UNDO_CELL_ANCHOR)); + for( size_t i=0; iGetMark(i)->GetMarkedSdrObj(); + AddUndo (std::make_unique( pObj, &rDoc, nTab )); + ScDrawLayer::SetCellAnchoredFromPosition(*pObj, rDoc, nTab, bResizeWithCell); + } + EndUndo(); + + if ( pViewData ) + { + pViewData->GetDocShell()->SetDrawModified(); + + // Set the anchor object. + AddCustomHdl(); + } +} + +ScAnchorType ScDrawView::GetAnchorType() const +{ + bool bPage = false; + bool bCell = false; + bool bCellResize = false; + if( AreObjectsMarked() ) + { + const SdrMarkList* pMark = &GetMarkedObjectList(); + const size_t nCount = pMark->GetMarkCount(); + for( size_t i=0; iGetMark(i)->GetMarkedSdrObj(); + const ScAnchorType aAnchorType = ScDrawLayer::GetAnchorType( *pObj ); + if( aAnchorType == SCA_CELL ) + bCell =true; + else if (aAnchorType == SCA_CELL_RESIZE) + bCellResize = true; + else + bPage = true; + } + } + if( bPage && !bCell && !bCellResize ) + return SCA_PAGE; + if( !bPage && bCell && !bCellResize ) + return SCA_CELL; + if( !bPage && !bCell && bCellResize ) + return SCA_CELL_RESIZE; + return SCA_DONTKNOW; +} + +namespace { + +bool lcl_AreRectanglesApproxEqual(const tools::Rectangle& rRectA, const tools::Rectangle& rRectB) +{ + // Twips <-> Hmm conversions introduce +-1 differences although the rectangles should actually + // be equal. Therefore test with == is not appropriate in some cases. + if (std::abs(rRectA.Left() - rRectB.Left()) > 1) + return false; + if (std::abs(rRectA.Top() - rRectB.Top()) > 1) + return false; + if (std::abs(rRectA.Right() - rRectB.Right()) > 1) + return false; + if (std::abs(rRectA.Bottom() - rRectB.Bottom()) > 1) + return false; + return true; +} + +/** + * Updated the anchors of any non-note object that is cell anchored which + * has been moved since the last anchors for its position was calculated. + */ +void adjustAnchoredPosition(const SdrHint& rHint, const ScDocument& rDoc, SCTAB nTab) +{ + if (rHint.GetKind() != SdrHintKind::ObjectChange && rHint.GetKind() != SdrHintKind::ObjectInserted) + return; + + SdrObject* pObj = const_cast(rHint.GetObject()); + if (!pObj) + return; + + ScDrawObjData *pAnchor = ScDrawLayer::GetObjData(pObj); + if (!pAnchor) + return; + + if (pAnchor->meType == ScDrawObjData::CellNote) + return; + + // SetCellAnchoredFromPosition has to be called only if shape geometry has been changed, and not + // if only shape visibility has been changed. It is not enough to test shape rect, because e.g. a + // 180deg rotation changes only the logic rect (tdf#139583). + ScDrawObjData& rNoRotatedAnchor = *ScDrawLayer::GetNonRotatedObjData(pObj, true /*bCreate*/); + if (lcl_AreRectanglesApproxEqual(pAnchor->getShapeRect(), pObj->GetSnapRect()) + && lcl_AreRectanglesApproxEqual(rNoRotatedAnchor.getShapeRect(), pObj->GetLogicRect())) + return; + + if (pAnchor->maStart.Tab() != nTab) + // The object is not anchored on the current sheet. Skip it. + // TODO: In the future, we may want to adjust objects that are + // anchored on all selected sheets. + return; + + ScDrawLayer::SetCellAnchoredFromPosition(*pObj, rDoc, pAnchor->maStart.Tab(), pAnchor->mbResizeWithCell); +} + +} + +void ScDrawView::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint) + { + const SdrHint* pSdrHint = static_cast( &rHint ); + adjustAnchoredPosition(*pSdrHint, rDoc, nTab); + FmFormView::Notify( rBC,rHint ); + } + else if (auto pDeletedHint = dynamic_cast(&rHint)) // Sheet has been deleted + { + SCTAB nDelTab = pDeletedHint->GetTab(); + if (ValidTab(nDelTab)) + { + // used to be: HidePagePgNum(nDelTab) - hide only if the deleted sheet is shown here + if ( nDelTab == nTab ) + HideSdrPage(); + } + } + else if (auto pChangedHint = dynamic_cast(&rHint)) // Size has been changed + { + if ( nTab == pChangedHint->GetTab() ) + UpdateWorkArea(); + } + else + FmFormView::Notify( rBC,rHint ); +} + +void ScDrawView::UpdateIMap( SdrObject* pObj ) +{ + if ( !(pViewData && + pViewData->GetViewShell()->GetViewFrame().HasChildWindow( ScIMapChildWindowId() ) && + pObj && ( dynamic_cast( pObj) != nullptr || dynamic_cast( pObj) != nullptr )) ) + return; + + Graphic aGraphic; + TargetList aTargetList; + SvxIMapInfo* pIMapInfo = SvxIMapInfo::GetIMapInfo( pObj ); + const ImageMap* pImageMap = nullptr; + if ( pIMapInfo ) + pImageMap = &pIMapInfo->GetImageMap(); + + // handle target list + SfxViewFrame::GetTargetList( aTargetList ); + + // handle graphics from object + if ( auto pGrafObj = dynamic_cast( pObj) ) + aGraphic = pGrafObj->GetGraphic(); + else + { + const Graphic* pGraphic = static_cast(pObj)->GetGraphic(); + if ( pGraphic ) + aGraphic = *pGraphic; + } + + ScIMapDlgSet( aGraphic, pImageMap, &aTargetList, pObj ); // from imapwrap +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/drawvie4.cxx b/sc/source/ui/view/drawvie4.cxx new file mode 100644 index 0000000000..2bd3290982 --- /dev/null +++ b/sc/source/ui/view/drawvie4.cxx @@ -0,0 +1,572 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace com::sun::star; + +Point aDragStartDiff; + +void ScDrawView::BeginDrag( vcl::Window* pWindow, const Point& rStartPos ) +{ + if ( !AreObjectsMarked() ) + return; + + BrkAction(); + + tools::Rectangle aMarkedRect = GetAllMarkedRect(); + + aDragStartDiff = rStartPos - aMarkedRect.TopLeft(); + + bool bAnyOle, bOneOle; + const SdrMarkList& rMarkList = GetMarkedObjectList(); + CheckOle( rMarkList, bAnyOle, bOneOle ); + + ScDocShellRef aDragShellRef; + if (bAnyOle) + { + aDragShellRef = new ScDocShell; // DocShell needs a Ref immediately + aDragShellRef->DoInitNew(); + } + ScDrawLayer::SetGlobalDrawPersist( aDragShellRef.get() ); + std::unique_ptr pModel(CreateMarkedObjModel()); + ScDrawLayer::SetGlobalDrawPersist(nullptr); + + // Charts now always copy their data in addition to the source reference, so + // there's no need to call SchDLL::Update for the charts in the clipboard doc. + // Update with the data (including NumberFormatter) from the live document would + // also store the NumberFormatter in the clipboard chart (#88749#) + + ScDocShell* pDocSh = pViewData->GetDocShell(); + + TransferableObjectDescriptor aObjDesc; + pDocSh->FillTransferableObjectDescriptor( aObjDesc ); + aObjDesc.maDisplayName = pDocSh->GetMedium()->GetURLObject().GetURLNoPass(); + // maSize is set in ScDrawTransferObj ctor + + rtl::Reference pTransferObj = new ScDrawTransferObj( std::move(pModel), pDocSh, std::move(aObjDesc) ); + + pTransferObj->SetDrawPersist( aDragShellRef.get() ); // keep persist for ole objects alive + pTransferObj->SetDragSource( this ); // copies selection + + SC_MOD()->SetDragObject( nullptr, pTransferObj.get() ); // for internal D&D + pTransferObj->StartDrag( pWindow, DND_ACTION_COPYMOVE | DND_ACTION_LINK ); +} + +namespace { + +void getRangeFromDataSource( uno::Reference< chart2::data::XDataSource > const & xDataSource, std::vector& rRangeRep) +{ + const uno::Sequence > xSeqs = xDataSource->getDataSequences(); + for (const uno::Reference& xLS : xSeqs) + { + uno::Reference xSeq = xLS->getValues(); + if (xSeq.is()) + { + OUString aRep = xSeq->getSourceRangeRepresentation(); + rRangeRep.push_back(aRep); + } + xSeq = xLS->getLabel(); + if (xSeq.is()) + { + OUString aRep = xSeq->getSourceRangeRepresentation(); + rRangeRep.push_back(aRep); + } + } +} + +void getRangeFromErrorBar(const uno::Reference< chart2::XChartDocument >& rChartDoc, std::vector& rRangeRep) +{ + uno::Reference xDiagram = rChartDoc->getFirstDiagram(); + if(!xDiagram.is()) + return; + + uno::Reference< chart2::XCoordinateSystemContainer > xCooSysContainer( xDiagram, uno::UNO_QUERY); + if(!xCooSysContainer.is()) + return; + + const uno::Sequence< uno::Reference< chart2::XCoordinateSystem > > xCooSysSequence( xCooSysContainer->getCoordinateSystems()); + for(const auto& rCooSys : xCooSysSequence) + { + uno::Reference< chart2::XChartTypeContainer > xChartTypeContainer( rCooSys, uno::UNO_QUERY); + if(!xChartTypeContainer.is()) + continue; + + const uno::Sequence< uno::Reference< chart2::XChartType > > xChartTypeSequence( xChartTypeContainer->getChartTypes() ); + for(const auto& rChartType : xChartTypeSequence) + { + uno::Reference< chart2::XDataSeriesContainer > xDataSequenceContainer( rChartType, uno::UNO_QUERY); + if(!xDataSequenceContainer.is()) + continue; + + const uno::Sequence< uno::Reference< chart2::XDataSeries > > xSeriesSequence( xDataSequenceContainer->getDataSeries() ); + for(const uno::Reference& xSeries : xSeriesSequence) + { + uno::Reference< beans::XPropertySet > xPropSet( xSeries, uno::UNO_QUERY); + uno::Reference< chart2::data::XDataSource > xErrorBarY; + xPropSet->getPropertyValue("ErrorBarY") >>= xErrorBarY; + if(xErrorBarY.is()) + getRangeFromDataSource(xErrorBarY, rRangeRep); + uno::Reference< chart2::data::XDataSource > xErrorBarX; + xPropSet->getPropertyValue("ErrorBarX") >>= xErrorBarX; + if(xErrorBarX.is()) + getRangeFromDataSource(xErrorBarX, rRangeRep); + } + } + } +} + +void getRangeFromOle2Object(const SdrOle2Obj& rObj, std::vector& rRangeRep) +{ + if (!rObj.IsChart()) + // not a chart object. + return; + + const uno::Reference& xObj = rObj.GetObjRef(); + if (!xObj.is()) + return; + + uno::Reference xChartDoc(xObj->getComponent(), uno::UNO_QUERY); + if (!xChartDoc.is()) + return; + + if(xChartDoc->hasInternalDataProvider()) + return; + + getRangeFromErrorBar(xChartDoc, rRangeRep); + + uno::Reference xDataSource(xChartDoc, uno::UNO_QUERY); + if (!xDataSource.is()) + return; + + // Get all data sources used in this chart. + getRangeFromDataSource(xDataSource, rRangeRep); + + return; +} + +// Get all cell ranges that are referenced by the selected chart objects. +void getOleSourceRanges(const SdrMarkList& rMarkList, bool& rAnyOle, bool& rOneOle, std::vector* pRanges = nullptr, const ScDocument* pDoc = nullptr ) +{ + bool bCalcSourceRanges = pRanges && pDoc; + std::vector aRangeReps; + rAnyOle = rOneOle = false; + const size_t nCount = rMarkList.GetMarkCount(); + for (size_t i=0; iGetMarkedSdrObj(); + if ( !pObj ) + continue; + + SdrObjKind nSdrObjKind = pObj->GetObjIdentifier(); + if (nSdrObjKind == SdrObjKind::OLE2) + { + rAnyOle = true; + rOneOle = (nCount == 1); + if ( bCalcSourceRanges ) + getRangeFromOle2Object( static_cast( *pObj ), aRangeReps ); + else + break; + } + else if ( dynamic_cast( pObj) != nullptr ) + { + SdrObjListIter aIter( *pObj, SdrIterMode::DeepNoGroups ); + SdrObject* pSubObj = aIter.Next(); + while (pSubObj) + { + if ( pSubObj->GetObjIdentifier() == SdrObjKind::OLE2 ) + { + rAnyOle = true; + // rOneOle remains false - a group isn't treated like a single OLE object + if ( !bCalcSourceRanges ) + return; + + getRangeFromOle2Object( static_cast( *pSubObj ), aRangeReps ); + } + pSubObj = aIter.Next(); + } + } + } + + if (!bCalcSourceRanges) + return; + + // Compile all range representation strings into ranges. + for (const auto& rRangeRep : aRangeReps) + { + ScRangeList aRange; + ScAddress aAddr; + if (aRange.Parse(rRangeRep, *pDoc, pDoc->GetAddressConvention()) & ScRefFlags::VALID) + { + pRanges->insert(pRanges->end(), aRange.begin(), aRange.end()); + } + else if (aAddr.Parse(rRangeRep, *pDoc, pDoc->GetAddressConvention()) & ScRefFlags::VALID) + pRanges->push_back(aAddr); + } + + return; +} + +class InsertTabIndex +{ + std::vector& mrTabs; +public: + explicit InsertTabIndex(std::vector& rTabs) : mrTabs(rTabs) {} + void operator() (const ScRange& rRange) + { + mrTabs.push_back(rRange.aStart.Tab()); + } +}; + +class CopyRangeData +{ + ScDocument& mrSrc; + ScDocument& mrDest; +public: + CopyRangeData(ScDocument& rSrc, ScDocument& rDest) : mrSrc(rSrc), mrDest(rDest) {} + + void operator() (const ScRange& rRange) + { + OUString aTabName; + mrSrc.GetName(rRange.aStart.Tab(), aTabName); + + SCTAB nTab; + if (!mrDest.GetTable(aTabName, nTab)) + // Sheet by this name doesn't exist. + return; + + mrSrc.CopyStaticToDocument(rRange, nTab, mrDest); + } +}; + +void copyChartRefDataToClipDoc(ScDocument& rSrcDoc, ScDocument& rClipDoc, const std::vector& rRanges) +{ + // Get a list of referenced table indices. + std::vector aTabs; + std::for_each(rRanges.begin(), rRanges.end(), InsertTabIndex(aTabs)); + std::sort(aTabs.begin(), aTabs.end()); + aTabs.erase(std::unique(aTabs.begin(), aTabs.end()), aTabs.end()); + + // Get table names. + if (aTabs.empty()) + return; + + // Create sheets only for referenced source sheets. + OUString aName; + std::vector::const_iterator it = aTabs.begin(), itEnd = aTabs.end(); + if (!rSrcDoc.GetName(*it, aName)) + return; + + rClipDoc.SetTabNameOnLoad(0, aName); // document initially has one sheet. + + for (++it; it != itEnd; ++it) + { + if (!rSrcDoc.GetName(*it, aName)) + return; + + rClipDoc.AppendTabOnLoad(aName); + } + + std::for_each(rRanges.begin(), rRanges.end(), CopyRangeData(rSrcDoc, rClipDoc)); +} + +} + +void ScDrawView::CheckOle( const SdrMarkList& rMarkList, bool& rAnyOle, bool& rOneOle ) +{ + getOleSourceRanges( rMarkList, rAnyOle, rOneOle ); +} + +void ScDrawView::DoCopy() +{ + const SdrMarkList& rMarkList = GetMarkedObjectList(); + std::vector aRanges; + bool bAnyOle = false, bOneOle = false; + getOleSourceRanges( rMarkList, bAnyOle, bOneOle, &aRanges, &rDoc ); + + // update ScGlobal::xDrawClipDocShellRef + ScDrawLayer::SetGlobalDrawPersist( ScTransferObj::SetDrawClipDoc( bAnyOle ) ); + if (ScGlobal::xDrawClipDocShellRef.is() && !aRanges.empty()) + { + // Copy data referenced by the chart objects to the draw clip + // document. We need to do this before CreateMarkedObjModel() below. + ScDocShellRef xDocSh = ScGlobal::xDrawClipDocShellRef; + ScDocument& rClipDoc = xDocSh->GetDocument(); + copyChartRefDataToClipDoc(rDoc, rClipDoc, aRanges); + } + std::unique_ptr pModel(CreateMarkedObjModel()); + ScDrawLayer::SetGlobalDrawPersist(nullptr); + + // Charts now always copy their data in addition to the source reference, so + // there's no need to call SchDLL::Update for the charts in the clipboard doc. + // Update with the data (including NumberFormatter) from the live document would + // also store the NumberFormatter in the clipboard chart (#88749#) + + ScDocShell* pDocSh = pViewData->GetDocShell(); + + TransferableObjectDescriptor aObjDesc; + pDocSh->FillTransferableObjectDescriptor( aObjDesc ); + aObjDesc.maDisplayName = pDocSh->GetMedium()->GetURLObject().GetURLNoPass(); + // maSize is set in ScDrawTransferObj ctor + + rtl::Reference pTransferObj(new ScDrawTransferObj( std::move(pModel), pDocSh, std::move(aObjDesc) )); + + if ( ScGlobal::xDrawClipDocShellRef.is() ) + { + pTransferObj->SetDrawPersist( ScGlobal::xDrawClipDocShellRef.get() ); // keep persist for ole objects alive + } + + pTransferObj->CopyToClipboard( pViewData->GetActiveWin() ); // system clipboard +} + +uno::Reference ScDrawView::CopyToTransferable() +{ + bool bAnyOle, bOneOle; + const SdrMarkList& rMarkList = GetMarkedObjectList(); + CheckOle( rMarkList, bAnyOle, bOneOle ); + + // update ScGlobal::xDrawClipDocShellRef + ScDrawLayer::SetGlobalDrawPersist( ScTransferObj::SetDrawClipDoc( bAnyOle ) ); + std::unique_ptr pModel( CreateMarkedObjModel() ); + ScDrawLayer::SetGlobalDrawPersist(nullptr); + + // Charts now always copy their data in addition to the source reference, so + // there's no need to call SchDLL::Update for the charts in the clipboard doc. + // Update with the data (including NumberFormatter) from the live document would + // also store the NumberFormatter in the clipboard chart (#88749#) + // lcl_RefreshChartData( pModel, pViewData->GetDocument() ); + + ScDocShell* pDocSh = pViewData->GetDocShell(); + + TransferableObjectDescriptor aObjDesc; + pDocSh->FillTransferableObjectDescriptor( aObjDesc ); + aObjDesc.maDisplayName = pDocSh->GetMedium()->GetURLObject().GetURLNoPass(); + // maSize is set in ScDrawTransferObj ctor + + rtl::Reference pTransferObj = new ScDrawTransferObj( std::move(pModel), pDocSh, std::move(aObjDesc) ); + + if ( ScGlobal::xDrawClipDocShellRef.is() ) + { + pTransferObj->SetDrawPersist( ScGlobal::xDrawClipDocShellRef.get() ); // keep persist for ole objects alive + } + + return pTransferObj; +} + +// Calculate correction for 100%, regardless of current settings + +void ScDrawView::CalcNormScale( Fraction& rFractX, Fraction& rFractY ) const +{ + double nPPTX = ScGlobal::nScreenPPTX; + double nPPTY = ScGlobal::nScreenPPTY; + + if (pViewData) + nPPTX /= pViewData->GetDocShell()->GetOutputFactor(); + + SCCOL nEndCol = 0; + SCROW nEndRow = 0; + rDoc.GetTableArea( nTab, nEndCol, nEndRow ); + if (nEndCol<20) + nEndCol = 20; + if (nEndRow<20) + nEndRow = 1000; + + Fraction aZoom(1,1); + ScDrawUtil::CalcScale( rDoc, nTab, 0,0, nEndCol,nEndRow, pDev, aZoom,aZoom, + nPPTX, nPPTY, rFractX,rFractY ); +} + +void ScDrawView::SetMarkedOriginalSize() +{ + std::unique_ptr pUndoGroup(new SdrUndoGroup(GetModel())); + + const SdrMarkList& rMarkList = GetMarkedObjectList(); + tools::Long nDone = 0; + const size_t nCount = rMarkList.GetMarkCount(); + for (size_t i=0; iGetMarkedSdrObj(); + SdrObjKind nIdent = pObj->GetObjIdentifier(); + bool bDo = false; + Size aOriginalSize; + if (nIdent == SdrObjKind::OLE2) + { + // TODO/LEAN: working with visual area can switch object to running state + uno::Reference < embed::XEmbeddedObject > xObj = static_cast(pObj)->GetObjRef(); + if ( xObj.is() ) // NULL for an invalid object that couldn't be loaded + { + sal_Int64 nAspect = static_cast(pObj)->GetAspect(); + + if ( nAspect == embed::Aspects::MSOLE_ICON ) + { + MapMode aMapMode( MapUnit::Map100thMM ); + aOriginalSize = static_cast(pObj)->GetOrigObjSize( &aMapMode ); + bDo = true; + } + else + { + MapUnit aUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( xObj->getMapUnit( static_cast(pObj)->GetAspect() ) ); + try + { + awt::Size aSz = xObj->getVisualAreaSize( static_cast(pObj)->GetAspect() ); + aOriginalSize = OutputDevice::LogicToLogic( + Size( aSz.Width, aSz.Height ), + MapMode(aUnit), + MapMode(MapUnit::Map100thMM)); + bDo = true; + } catch( embed::NoVisualAreaSizeException& ) + { + TOOLS_WARN_EXCEPTION("sc.ui", "Can't get the original size of the object!" ); + } + } + } + } + else if (nIdent == SdrObjKind::Graphic) + { + const SdrGrafObj* pSdrGrafObj = static_cast(pObj); + + MapMode aSourceMap = pSdrGrafObj->GetGraphic().GetPrefMapMode(); + MapMode aDestMap( MapUnit::Map100thMM ); + if (aSourceMap.GetMapUnit() == MapUnit::MapPixel) + { + // consider pixel correction, so that the bitmap is correct on the screen + Fraction aNormScaleX, aNormScaleY; + CalcNormScale( aNormScaleX, aNormScaleY ); + aDestMap.SetScaleX(aNormScaleX); + aDestMap.SetScaleY(aNormScaleY); + } + aOriginalSize = pSdrGrafObj->getOriginalSize(); + bDo = true; + } + + if ( bDo ) + { + tools::Rectangle aDrawRect = pObj->GetLogicRect(); + + pUndoGroup->AddAction( std::make_unique( *pObj ) ); + pObj->Resize( aDrawRect.TopLeft(), Fraction( aOriginalSize.Width(), aDrawRect.GetWidth() ), + Fraction( aOriginalSize.Height(), aDrawRect.GetHeight() ) ); + ++nDone; + } + } + + if (nDone && pViewData) + { + pUndoGroup->SetComment(ScResId( STR_UNDO_ORIGINALSIZE )); + ScDocShell* pDocSh = pViewData->GetDocShell(); + pDocSh->GetUndoManager()->AddUndoAction(std::move(pUndoGroup)); + pDocSh->SetDrawModified(); + } +} + +void ScDrawView::FitToCellSize() +{ + const SdrMarkList& rMarkList = GetMarkedObjectList(); + + if (rMarkList.GetMarkCount() != 1) + { + SAL_WARN("sc.ui", "Fit to cell only works with one graphic!"); + return; + } + + SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj(); + + ScAnchorType aAnchorType = ScDrawLayer::GetAnchorType(*pObj); + if (aAnchorType != SCA_CELL && aAnchorType != SCA_CELL_RESIZE) + { + SAL_WARN("sc.ui", "Fit to cell only works with cell anchored graphics!"); + return; + } + + ScDrawObjData* pObjData = ScDrawLayer::GetObjData(pObj); + if (!pObjData) + { + SAL_WARN("sc.ui", "Missing ScDrawObjData!"); + return; + } + + std::unique_ptr pUndoGroup(new SdrUndoGroup(GetModel())); + tools::Rectangle aGraphicRect = pObj->GetSnapRect(); + tools::Rectangle aCellRect = ScDrawLayer::GetCellRect( rDoc, pObjData->maStart, true); + + // For graphic objects, we want to keep the aspect ratio + if (pObj->shouldKeepAspectRatio()) + { + tools::Long nWidth = aGraphicRect.GetWidth(); + assert(nWidth && "div-by-zero"); + double fScaleX = static_cast(aCellRect.GetWidth()) / static_cast(nWidth); + tools::Long nHeight = aGraphicRect.GetHeight(); + assert(nHeight && "div-by-zero"); + double fScaleY = static_cast(aCellRect.GetHeight()) / static_cast(nHeight); + double fScaleMin = std::min(fScaleX, fScaleY); + + aCellRect.setWidth(static_cast(aGraphicRect.GetWidth()) * fScaleMin); + aCellRect.setHeight(static_cast(aGraphicRect.GetHeight()) * fScaleMin); + } + + pUndoGroup->AddAction( std::make_unique( *pObj ) ); + if (pObj->GetObjIdentifier() == SdrObjKind::CustomShape) + pObj->AdjustToMaxRect(aCellRect); + else + pObj->SetSnapRect(aCellRect); + + pUndoGroup->SetComment(ScResId( STR_UNDO_FITCELLSIZE )); + ScDocShell* pDocSh = pViewData->GetDocShell(); + pDocSh->GetUndoManager()->AddUndoAction(std::move(pUndoGroup)); + +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/drawview.cxx b/sc/source/ui/view/drawview.cxx new file mode 100644 index 0000000000..c1a48dc6df --- /dev/null +++ b/sc/source/ui/view/drawview.cxx @@ -0,0 +1,1251 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +using namespace com::sun::star; + +#define SC_HANDLESIZE_BIG 9 + +void ScDrawView::Construct() +{ + EnableExtendedKeyInputDispatcher(false); + EnableExtendedMouseEventDispatcher(false); + + SetFrameDragSingles(); + + SetMinMoveDistancePixel( 2 ); + SetHitTolerancePixel( 2 ); + + if (pViewData) + { + SCTAB nViewTab = pViewData->GetTabNo(); + ShowSdrPage(GetModel().GetPage(nViewTab)); + + bool bEx = pViewData->GetViewShell()->IsDrawSelMode(); + bool bProt = rDoc.IsTabProtected( nViewTab ) || + pViewData->GetSfxDocShell()->IsReadOnly(); + + SdrLayer* pLayer; + SdrLayerAdmin& rAdmin = GetModel().GetLayerAdmin(); + pLayer = rAdmin.GetLayerPerID(SC_LAYER_BACK); + if (pLayer) + SetLayerLocked( pLayer->GetName(), bProt || !bEx ); + pLayer = rAdmin.GetLayerPerID(SC_LAYER_INTERN); + if (pLayer) + SetLayerLocked( pLayer->GetName() ); + pLayer = rAdmin.GetLayerPerID(SC_LAYER_FRONT); + if (pLayer) + { + SetLayerLocked( pLayer->GetName(), bProt ); + SetActiveLayer( pLayer->GetName() ); // set active layer to FRONT + } + pLayer = rAdmin.GetLayerPerID(SC_LAYER_CONTROLS); + if (pLayer) + SetLayerLocked( pLayer->GetName(), bProt ); + pLayer = rAdmin.GetLayerPerID(SC_LAYER_HIDDEN); + if (pLayer) + { + SetLayerLocked( pLayer->GetName(), bProt ); + SetLayerVisible( pLayer->GetName(), false); + } + + SetSwapAsynchron(); + } + else + { + ShowSdrPage(GetModel().GetPage(nTab)); + } + + UpdateUserViewOptions(); + RecalcScale(); + UpdateWorkArea(); + + bInConstruct = false; +} + +void ScDrawView::ImplClearCalcDropMarker() +{ + pDropMarker.reset(); +} + +ScDrawView::~ScDrawView() +{ + ImplClearCalcDropMarker(); +} + +void ScDrawView::AddCustomHdl() +{ + const SdrMarkList &rMrkList = GetMarkedObjectList(); + const size_t nCount = rMrkList.GetMarkCount(); + for(size_t nPos=0; nPosGetMarkedSdrObj(); + if (ScDrawObjData *pAnchor = ScDrawLayer::GetObjDataTab(pObj, nTab)) + { + if (ScTabView* pView = pViewData->GetView()) + pView->CreateAnchorHandles(maHdlList, pAnchor->maStart); + } + } +} + +void ScDrawView::InvalidateAttribs() +{ + if (!pViewData) return; + SfxBindings& rBindings = pViewData->GetBindings(); + + // true status values: + rBindings.InvalidateAll( true ); +} + +void ScDrawView::InvalidateDrawTextAttrs() +{ + if (!pViewData) return; + SfxBindings& rBindings = pViewData->GetBindings(); + + // cjk/ctl font items have no configured slots, + // need no invalidate + + rBindings.Invalidate( SID_ATTR_CHAR_FONT ); + rBindings.Invalidate( SID_ATTR_CHAR_FONTHEIGHT ); + rBindings.Invalidate( SID_ATTR_CHAR_WEIGHT ); + rBindings.Invalidate( SID_ATTR_CHAR_POSTURE ); + rBindings.Invalidate( SID_ATTR_CHAR_UNDERLINE ); + rBindings.Invalidate( SID_ULINE_VAL_NONE ); + rBindings.Invalidate( SID_ULINE_VAL_SINGLE ); + rBindings.Invalidate( SID_ULINE_VAL_DOUBLE ); + rBindings.Invalidate( SID_ULINE_VAL_DOTTED ); + rBindings.Invalidate( SID_ATTR_CHAR_OVERLINE ); + rBindings.Invalidate( SID_ATTR_CHAR_COLOR ); + rBindings.Invalidate( SID_ATTR_CHAR_BACK_COLOR ); + rBindings.Invalidate( SID_ATTR_PARA_ADJUST_LEFT ); + rBindings.Invalidate( SID_ATTR_PARA_ADJUST_RIGHT ); + rBindings.Invalidate( SID_ATTR_PARA_ADJUST_BLOCK ); + rBindings.Invalidate( SID_ATTR_PARA_ADJUST_CENTER); + rBindings.Invalidate( SID_ALIGNLEFT ); + rBindings.Invalidate( SID_ALIGNCENTERHOR ); + rBindings.Invalidate( SID_ALIGNRIGHT ); + rBindings.Invalidate( SID_ALIGNBLOCK ); + rBindings.Invalidate( SID_ATTR_PARA_LINESPACE_10 ); + rBindings.Invalidate( SID_ATTR_PARA_LINESPACE_15 ); + rBindings.Invalidate( SID_ATTR_PARA_LINESPACE_20 ); + rBindings.Invalidate( SID_SET_SUPER_SCRIPT ); + rBindings.Invalidate( SID_SET_SUB_SCRIPT ); + rBindings.Invalidate( SID_ATTR_CHAR_KERNING ); + rBindings.Invalidate( SID_ATTR_CHAR_STRIKEOUT ); + rBindings.Invalidate( SID_ATTR_CHAR_SHADOWED ); + rBindings.Invalidate( SID_TEXTDIRECTION_LEFT_TO_RIGHT ); + rBindings.Invalidate( SID_TEXTDIRECTION_TOP_TO_BOTTOM ); + rBindings.Invalidate( SID_ATTR_PARA_LEFT_TO_RIGHT ); + rBindings.Invalidate( SID_ATTR_PARA_RIGHT_TO_LEFT ); + rBindings.Invalidate( SID_TABLE_VERT_NONE ); + rBindings.Invalidate( SID_TABLE_VERT_CENTER ); + rBindings.Invalidate( SID_TABLE_VERT_BOTTOM ); + // pseudo slots for Format menu + rBindings.Invalidate( SID_ALIGN_ANY_LEFT ); + rBindings.Invalidate( SID_ALIGN_ANY_HCENTER ); + rBindings.Invalidate( SID_ALIGN_ANY_RIGHT ); + rBindings.Invalidate( SID_ALIGN_ANY_JUSTIFIED ); +} + +void ScDrawView::SetMarkedToLayer( SdrLayerID nLayerNo ) +{ + if (!AreObjectsMarked()) + return; + + // #i11702# use SdrUndoObjectLayerChange for undo + // STR_UNDO_SELATTR is "Attributes" - should use a different text later + BegUndo( ScResId( STR_UNDO_SELATTR ) ); + + const SdrMarkList& rMark = GetMarkedObjectList(); + const size_t nCount = rMark.GetMarkCount(); + for (size_t i=0; iGetMarkedSdrObj(); + if ( dynamic_cast( pObj) == nullptr && (pObj->GetLayer() != SC_LAYER_INTERN) ) + { + AddUndo( std::make_unique( *pObj, pObj->GetLayer(), nLayerNo) ); + pObj->SetLayer( nLayerNo ); + } + } + + EndUndo(); + + // repaint is done in SetLayer + + pViewData->GetDocShell()->SetDrawModified(); + + // check mark list now instead of later in a timer + CheckMarked(); + MarkListHasChanged(); +} + +bool ScDrawView::HasMarkedControl() const +{ + SdrObjListIter aIter( GetMarkedObjectList() ); + for( SdrObject* pObj = aIter.Next(); pObj; pObj = aIter.Next() ) + if( dynamic_cast( pObj) != nullptr ) + return true; + return false; +} + +bool ScDrawView::HasMarkedInternal() const +{ + // internal objects should not be inside a group, but who knows... + SdrObjListIter aIter( GetMarkedObjectList() ); + for( SdrObject* pObj = aIter.Next(); pObj; pObj = aIter.Next() ) + if( pObj->GetLayer() == SC_LAYER_INTERN ) + return true; + return false; +} + +void ScDrawView::UpdateWorkArea() +{ + SdrPage* pPage = GetModel().GetPage(static_cast(nTab)); + if (pPage) + { + Size aPageSize( pPage->GetSize() ); + tools::Rectangle aNewArea( Point(), aPageSize ); + if ( aPageSize.Width() < 0 ) + { + // RTL: from max.negative (left) to zero (right) + aNewArea.SetRight( 0 ); + aNewArea.SetLeft( aPageSize.Width() + 1 ); + } + SetWorkArea( aNewArea ); + } + else + { + OSL_FAIL("Page not found"); + } +} + +void ScDrawView::DoCut() +{ + DoCopy(); + BegUndo( ScResId( STR_UNDO_CUT ) ); + DeleteMarked(); // In this View - not affected by 505f change + EndUndo(); +} + +void ScDrawView::GetScale( Fraction& rFractX, Fraction& rFractY ) const +{ + rFractX = aScaleX; + rFractY = aScaleY; +} + +void ScDrawView::RecalcScale() +{ + double nPPTX; + double nPPTY; + Fraction aZoomX(1,1); + Fraction aZoomY(1,1); + + if (pViewData) + { + nTab = pViewData->GetTabNo(); + nPPTX = pViewData->GetPPTX(); + nPPTY = pViewData->GetPPTY(); + aZoomX = pViewData->GetZoomX(); + aZoomY = pViewData->GetZoomY(); + } + else + { + Point aLogic = pDev->LogicToPixel(Point(1000,1000), MapMode(MapUnit::MapTwip)); + nPPTX = aLogic.X() / 1000.0; + nPPTY = aLogic.Y() / 1000.0; + //! Zoom, handed over ??? + } + + SCCOL nEndCol = 0; + SCROW nEndRow = 0; + rDoc.GetTableArea( nTab, nEndCol, nEndRow ); + if (nEndCol<20) + nEndCol = 20; + if (nEndRow<20) + nEndRow = 20; + + ScDrawUtil::CalcScale( + rDoc, nTab, 0, 0, nEndCol, nEndRow, pDev, aZoomX, aZoomY, nPPTX, nPPTY, + aScaleX, aScaleY); + + // clear all evtl existing GridOffset vectors + resetGridOffsetsForAllSdrPageViews(); + + SdrPageView* pPV = GetSdrPageView(); + if ( !(pViewData && pPV) ) + return; + + if ( SdrPage* pPage = pPV->GetPage() ) + { + for (const rtl::Reference& pObj : *pPage) + // Align objects to nearest grid position + SyncForGrid( pObj.get() ); + } +} + +void ScDrawView::DoConnect(SdrOle2Obj* pOleObj) +{ + if ( pViewData ) + pViewData->GetViewShell()->ConnectObject( pOleObj ); +} + +void ScDrawView::MarkListHasChanged() +{ + FmFormView::MarkListHasChanged(); + + ScTabViewShell* pViewSh = pViewData->GetViewShell(); + + // #i110829# remove the cell selection only if drawing objects are selected + if ( !bInConstruct && GetMarkedObjectList().GetMarkCount() ) + { + pViewSh->Unmark(); // remove cell selection + + // end cell edit mode if drawing objects are selected + SC_MOD()->InputEnterHandler(); + } + + // deactivate IP + + ScModule* pScMod = SC_MOD(); + bool bUnoRefDialog = pScMod->IsRefDialogOpen() && pScMod->GetCurRefDlgId() == WID_SIMPLE_REF; + + ScClient* pClient = static_cast( pViewSh->GetIPClient() ); + if ( pClient && pClient->IsObjectInPlaceActive() && !bUnoRefDialog ) + { + // do not display the handles for ViewShell::Activate from the Reset2Open + pClient->DeactivateObject(); + // replacing image ole graphics is now done in ScClient::UIActivate + } + + // Select Ole object? + + SdrOle2Obj* pOle2Obj = nullptr; + SdrGrafObj* pGrafObj = nullptr; + + const SdrMarkList& rMarkList = GetMarkedObjectList(); + const size_t nMarkCount = rMarkList.GetMarkCount(); + + if ( nMarkCount == 0 && !pViewData->GetViewShell()->IsDrawSelMode() && !bInConstruct ) + { + // relock layers that may have been unlocked before + LockBackgroundLayer(true); + LockInternalLayer(); + } + + bool bSubShellSet = false; + if (nMarkCount == 1) + { + SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj(); + if (pObj->GetObjIdentifier() == SdrObjKind::OLE2) + { + pOle2Obj = static_cast(pObj); + if (!ScDocument::IsChart(pObj) ) + pViewSh->SetOleObjectShell(true); + else + pViewSh->SetChartShell(true); + bSubShellSet = true; + } + else if (pObj->GetObjIdentifier() == SdrObjKind::Graphic) + { + pGrafObj = static_cast(pObj); + pViewSh->SetGraphicShell(true); + bSubShellSet = true; + } + else if (pObj->GetObjIdentifier() == SdrObjKind::Media) + { + pViewSh->SetMediaShell(true); + bSubShellSet = true; + } + else if (pObj->GetObjIdentifier() != SdrObjKind::Text // prevent switching to the drawing shell + || !pViewSh->IsDrawTextShell()) // when creating a text object @#70206# + { + pViewSh->SetDrawShell(true); + } + } + + if ( nMarkCount && !bSubShellSet ) + { + bool bOnlyControls = true; + bool bOnlyGraf = true; + for (size_t i=0; iGetMarkedSdrObj(); + if ( auto pObjGroup = dynamic_cast( pObj) ) + { + const SdrObjList *pLst = pObjGroup->GetSubList(); + const size_t nListCount = pLst->GetObjCount(); + if ( nListCount == 0 ) + { + // An empty group (may occur during Undo) is no control or graphics object. + // Creating the form shell during undo would lead to problems with the undo manager. + bOnlyControls = false; + bOnlyGraf = false; + } + for ( size_t j = 0; j < nListCount; ++j ) + { + SdrObject *pSubObj = pLst->GetObj( j ); + + if (dynamic_cast( pSubObj) == nullptr) + bOnlyControls = false; + if (pSubObj->GetObjIdentifier() != SdrObjKind::Graphic) + bOnlyGraf = false; + + if ( !bOnlyControls && !bOnlyGraf ) break; + } + } + else + { + if (dynamic_cast( pObj) == nullptr) + bOnlyControls = false; + if (pObj->GetObjIdentifier() != SdrObjKind::Graphic) + bOnlyGraf = false; + } + + if ( !bOnlyControls && !bOnlyGraf ) break; + } + + if(bOnlyControls) + { + pViewSh->SetDrawFormShell(true); // now UNO controls + } + else if(bOnlyGraf) + { + pViewSh->SetGraphicShell(true); + } + else if(nMarkCount>1) + { + pViewSh->SetDrawShell(true); + } + } + + // adjust verbs + + SfxViewFrame& rViewFrame = pViewSh->GetViewFrame(); + bool bOle = pViewSh->GetViewFrame().GetFrame().IsInPlace(); + uno::Sequence< embed::VerbDescriptor > aVerbs; + if ( pOle2Obj && !bOle ) + { + const uno::Reference < embed::XEmbeddedObject >& xObj = pOle2Obj->GetObjRef(); + OSL_ENSURE( xObj.is(), "SdrOle2Obj without ObjRef" ); + if (xObj.is()) + aVerbs = xObj->getSupportedVerbs(); + } + pViewSh->SetVerbs( aVerbs ); + + // image map editor + + if ( pOle2Obj ) + UpdateIMap( pOle2Obj ); + else if ( pGrafObj ) + UpdateIMap( pGrafObj ); + + InvalidateAttribs(); // after the image map editor update + InvalidateDrawTextAttrs(); + + for(sal_uInt32 a(0); a < PaintWindowCount(); a++) + { + SdrPaintWindow* pPaintWindow = GetPaintWindow(a); + OutputDevice& rOutDev = pPaintWindow->GetOutputDevice(); + + if(OUTDEV_WINDOW == rOutDev.GetOutDevType()) + { + rOutDev.GetOwnerWindow()->PaintImmediately(); + } + } + + // uno object for view returns drawing objects as selection, + // so it must notify its SelectionChangeListeners + + SfxFrame& rFrame = rViewFrame.GetFrame(); + uno::Reference xController = rFrame.GetController(); + if (xController.is()) + { + ScTabViewObj* pImp = dynamic_cast( xController.get() ); + if (pImp) + pImp->SelectionChanged(); + } + + // update selection transfer object + + pViewSh->CheckSelectionTransfer(); + +} + +bool ScDrawView::SdrBeginTextEdit( + SdrObject* pObj, + SdrPageView* pPV, + vcl::Window* pWinL, + bool bIsNewObj, + SdrOutliner* pGivenOutliner, + OutlinerView* pGivenOutlinerView, + bool bDontDeleteOutliner, + bool bOnlyOneView, + bool bGrabFocus ) +{ + const bool bRet = FmFormView::SdrBeginTextEdit( + pObj, pPV, pWinL, bIsNewObj, + pGivenOutliner, pGivenOutlinerView, bDontDeleteOutliner, + bOnlyOneView, bGrabFocus ); + + ScTabViewShell* pViewSh = pViewData->GetViewShell(); + + if (comphelper::LibreOfficeKit::isActive()) + { + if (OutlinerView* pView = GetTextEditOutlinerView()) + { + tools::Rectangle aRectangle = pView->GetOutputArea(); + if (pWinL && pWinL->GetMapMode().GetMapUnit() == MapUnit::Map100thMM) + { + aRectangle = o3tl::convert(aRectangle, o3tl::Length::mm100, o3tl::Length::twip); + } + OString sRectangle = aRectangle.toString(); + SfxLokHelper::notifyOtherViews(pViewSh, LOK_CALLBACK_VIEW_LOCK, "rectangle", sRectangle); + } + } + + SfxFrame& rFrame = pViewSh->GetViewFrame().GetFrame(); + uno::Reference< frame::XController > xController = rFrame.GetController(); + if (xController.is()) + { + ScTabViewObj* pImp = dynamic_cast( xController.get() ); + if (pImp) + pImp->SelectionChanged(); + } + + return bRet; +} + +SdrEndTextEditKind ScDrawView::SdrEndTextEdit( bool bDontDeleteReally ) +{ + const SdrEndTextEditKind eRet = FmFormView::SdrEndTextEdit( bDontDeleteReally ); + + ScTabViewShell* pViewSh = pViewData->GetViewShell(); + + if (comphelper::LibreOfficeKit::isActive()) + SfxLokHelper::notifyOtherViews(pViewSh, LOK_CALLBACK_VIEW_LOCK, "rectangle", "EMPTY"_ostr); + + SfxFrame& rFrame = pViewSh->GetViewFrame().GetFrame(); + uno::Reference< frame::XController > xController = rFrame.GetController(); + if (xController.is()) + { + ScTabViewObj* pImp = dynamic_cast( xController.get() ); + if (pImp) + pImp->SelectionChanged(); + } + + return eRet; +} + +void ScDrawView::ModelHasChanged() +{ + SdrObject* pEditObj = GetTextEditObject(); + if ( pEditObj && !pEditObj->IsInserted() && pViewData ) + { + // SdrObjEditView::ModelHasChanged will end text edit in this case, + // so make sure the EditEngine's undo manager is no longer used. + pViewData->GetViewShell()->SetDrawTextUndo(nullptr); + SetCreateMode(); // don't leave FuText in a funny state + } + + FmFormView::ModelHasChanged(); +} + +void ScDrawView::UpdateUserViewOptions() +{ + if (!pViewData) + return; + + const ScViewOptions& rOpt = pViewData->GetOptions(); + const ScGridOptions& rGrid = rOpt.GetGridOptions(); + + SetDragStripes( rOpt.GetOption( VOPT_HELPLINES ) ); + SetMarkHdlSizePixel( SC_HANDLESIZE_BIG ); + + SetGridVisible( rGrid.GetGridVisible() ); + SetSnapEnabled( rGrid.GetUseGridSnap() ); + SetGridSnap( rGrid.GetUseGridSnap() ); + + Fraction aFractX( rGrid.GetFieldDrawX(), rGrid.GetFieldDivisionX() + 1 ); + Fraction aFractY( rGrid.GetFieldDrawY(), rGrid.GetFieldDivisionY() + 1 ); + SetSnapGridWidth( aFractX, aFractY ); + + SetGridCoarse( Size( rGrid.GetFieldDrawX(), rGrid.GetFieldDrawY() ) ); + SetGridFine( Size( rGrid.GetFieldDrawX() / (rGrid.GetFieldDivisionX() + 1), + rGrid.GetFieldDrawY() / (rGrid.GetFieldDivisionY() + 1) ) ); +} + +SdrObject* ScDrawView::GetObjectByName(std::u16string_view rName) +{ + ScDocShell* pShell = rDoc.GetDocumentShell(); + if (pShell) + { + SdrModel& rDrawLayer = GetModel(); + sal_uInt16 nTabCount = rDoc.GetTableCount(); + for (sal_uInt16 i=0; iGetView(); + if ( nObjectTab != nTab ) // switch sheet + pView->SetTabNo( nObjectTab ); + DBG_ASSERT( nTab == nObjectTab, "Switching sheets did not work" ); + pView->ScrollToObject( pFound ); + if ( pFound->GetLayer() == SC_LAYER_BACK && + !pViewData->GetViewShell()->IsDrawSelMode() && + !rDoc.IsTabProtected( nTab ) && + !pViewData->GetSfxDocShell()->IsReadOnly() ) + { + SdrLayer* pLayer = GetModel().GetLayerAdmin().GetLayerPerID(SC_LAYER_BACK); + if (pLayer) + SetLayerLocked( pLayer->GetName(), false ); + } + SdrPageView* pPV = GetSdrPageView(); + const bool bUnMark = IsObjMarked(pFound); + MarkObj( pFound, pPV, bUnMark); +} + +bool ScDrawView::SelectObject( std::u16string_view rName ) +{ + UnmarkAll(); + + SCTAB nObjectTab = 0; + SdrObject* pFound = nullptr; + + ScDocShell* pShell = rDoc.GetDocumentShell(); + if (pShell) + { + SdrModel& rDrawLayer = GetModel(); + SCTAB nTabCount = rDoc.GetTableCount(); + for (SCTAB i=0; i(i)); + OSL_ENSURE(pPage,"Page ?"); + if (pPage) + { + SdrObjListIter aIter( pPage, SdrIterMode::DeepWithGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject && !pFound) + { + if ( ScDrawLayer::GetVisibleName( pObject ) == rName ) + { + pFound = pObject; + nObjectTab = i; + } + pObject = aIter.Next(); + } + } + } + } + + if ( pFound ) + { + ScTabView* pView = pViewData->GetView(); + if ( nObjectTab != nTab ) // switch sheet + pView->SetTabNo( nObjectTab ); + + OSL_ENSURE( nTab == nObjectTab, "Switching sheets did not work" ); + + pView->ScrollToObject( pFound ); + + /* To select an object on the background layer, the layer has to + be unlocked even if exclusive drawing selection mode is not active + (this is reversed in MarkListHasChanged when nothing is selected) */ + if ( pFound->GetLayer() == SC_LAYER_BACK && + !pViewData->GetViewShell()->IsDrawSelMode() && + !rDoc.IsTabProtected( nTab ) && + !pViewData->GetSfxDocShell()->IsReadOnly() ) + { + LockBackgroundLayer(false); + } + + SdrPageView* pPV = GetSdrPageView(); + MarkObj( pFound, pPV ); + } + + return ( pFound != nullptr ); +} + +//If object is marked , return true , else return false . +bool ScDrawView::GetObjectIsMarked( const SdrObject* pObject ) +{ + bool bisMarked = false; + if (pObject ) + { + bisMarked = IsObjMarked(pObject); + } + return bisMarked; +} + +bool ScDrawView::InsertObjectSafe(SdrObject* pObj, SdrPageView& rPV) +{ + SdrInsertFlags nOptions=SdrInsertFlags::NONE; + // Do not change marks when the ole object is active + // (for Drop from ole object would otherwise be deactivated in the middle of ExecuteDrag!) + + if (pViewData) + { + SfxInPlaceClient* pClient = pViewData->GetViewShell()->GetIPClient(); + if ( pClient && pClient->IsObjectInPlaceActive() ) + nOptions |= SdrInsertFlags::DONTMARK; + } + + return InsertObjectAtView(pObj, rPV, nOptions); +} + +SdrObject* ScDrawView::GetMarkedNoteCaption( ScDrawObjData** ppCaptData ) +{ + const SdrMarkList& rMarkList = GetMarkedObjectList(); + if( pViewData && (rMarkList.GetMarkCount() == 1) ) + { + SdrObject* pObj = rMarkList.GetMark( 0 )->GetMarkedSdrObj(); + if( ScDrawObjData* pCaptData = ScDrawLayer::GetNoteCaptionData( pObj, pViewData->GetTabNo() ) ) + { + if( ppCaptData ) *ppCaptData = pCaptData; + return pObj; + } + } + return nullptr; +} + +void ScDrawView::LockCalcLayer( SdrLayerID nLayer, bool bLock ) +{ + SdrLayer* pLockLayer = GetModel().GetLayerAdmin().GetLayerPerID( nLayer ); + if( pLockLayer && (IsLayerLocked( pLockLayer->GetName() ) != bLock) ) + SetLayerLocked( pLockLayer->GetName(), bLock ); +} + +void ScDrawView::MakeVisible( const tools::Rectangle& rRect, vcl::Window& rWin ) +{ + //! Evaluate rWin properly + //! change zoom if necessary + + if ( pViewData && pViewData->GetActiveWin() == &rWin ) + pViewData->GetView()->MakeVisible( rRect ); +} + +SfxViewShell* ScDrawView::GetSfxViewShell() const +{ + return pViewData->GetViewShell(); +} + +void ScDrawView::DeleteMarked() +{ + // try to delete a note caption object with its cell note in the Calc document + ScDrawObjData* pCaptData = nullptr; + if( SdrObject* pCaptObj = GetMarkedNoteCaption( &pCaptData ) ) + { + ScDrawLayer* pDrawLayer = rDoc.GetDrawLayer(); + ScDocShell* pDocShell = pViewData ? pViewData->GetDocShell() : nullptr; + SfxUndoManager* pUndoMgr = pDocShell ? pDocShell->GetUndoManager() : nullptr; + bool bUndo = pDrawLayer && pDocShell && pUndoMgr && rDoc.IsUndoEnabled(); + + // remove the cell note from document, we are its owner now + std::unique_ptr pNote = rDoc.ReleaseNote( pCaptData->maStart ); + OSL_ENSURE( pNote, "ScDrawView::DeleteMarked - cell note missing in document" ); + if( pNote ) + { + // rescue note data for undo (with pointer to caption object) + ScNoteData aNoteData = pNote->GetNoteData(); + OSL_ENSURE( aNoteData.mxCaption.get() == pCaptObj, "ScDrawView::DeleteMarked - caption object does not match" ); + // collect the drawing undo action created while deleting the note + if( bUndo ) + pDrawLayer->BeginCalcUndo(false); + // delete the note (already removed from document above) + pNote.reset(); + // add the undo action for the note + if( bUndo ) + pUndoMgr->AddUndoAction( std::make_unique( *pDocShell, pCaptData->maStart, aNoteData, false, pDrawLayer->GetCalcUndo() ) ); + // repaint the cell to get rid of the note marker + if( pDocShell ) + pDocShell->PostPaintCell( pCaptData->maStart ); + // done, return now to skip call of FmFormView::DeleteMarked() + return; + } + } + + FmFormView::DeleteMarked(); +} + +SdrEndTextEditKind ScDrawView::ScEndTextEdit() +{ + bool bIsTextEdit = IsTextEdit(); + SdrEndTextEditKind eKind = SdrEndTextEdit(); + + if (bIsTextEdit) + pViewData->GetViewShell()->SetDrawTextUndo(nullptr); // the "normal" undo manager + + return eKind; +} + +void ScDrawView::MarkDropObj( SdrObject* pObj ) +{ + if ( pDropMarkObj != pObj ) + { + pDropMarkObj = pObj; + ImplClearCalcDropMarker(); + + if(pDropMarkObj) + { + pDropMarker.reset( new SdrDropMarkerOverlay(*this, *pDropMarkObj) ); + } + } +} + +// In order to counteract the effects of rounding due to the nature of how the +// grid positions are calculated and drawn we calculate the offset needed at the +// current zoom to be applied to an SrdObject when it is drawn in order to make +// sure that it's position relative to the nearest cell anchor doesn't change. +// Of course not all shape(s)/control(s) are cell anchored, if the +// object doesn't have a cell anchor we synthesise a temporary anchor. +void ScDrawView::SyncForGrid( SdrObject* pObj ) +{ + // process members of a group shape separately + if ( auto pObjGroup = dynamic_cast( pObj) ) + { + SdrObjList *pLst = pObjGroup->GetSubList(); + for (const rtl::Reference& pChild : *pLst) + SyncForGrid( pChild.get() ); + } + + ScSplitPos eWhich = pViewData->GetActivePart(); + ScGridWindow* pGridWin = pViewData->GetActiveWin(); + ScDrawObjData* pData = ScDrawLayer::GetObjData( pObj ); + if ( !pGridWin ) + return; + + ScAddress aOldStt; + if( pData && pData->maStart.IsValid()) + { + aOldStt = pData->maStart; + } + else + { + // Page anchored object so... + // synthesise an anchor ( but don't attach it to + // the object as we want to maintain page anchoring ) + ScDrawObjData aAnchor; + const tools::Rectangle aObjRect(pObj->GetLogicRect()); + ScDrawLayer::GetCellAnchorFromPosition( + aObjRect, + aAnchor, + rDoc, + GetTab()); + aOldStt = aAnchor.maStart; + } + MapMode aDrawMode = pGridWin->GetDrawMapMode(); + // find pos anchor position + Point aOldPos( rDoc.GetColOffset( aOldStt.Col(), aOldStt.Tab() ), rDoc.GetRowOffset( aOldStt.Row(), aOldStt.Tab() ) ); + aOldPos.setX(convertTwipToMm100(aOldPos.X())); + aOldPos.setY(convertTwipToMm100(aOldPos.Y())); + // find position of same point on the screen ( e.g. grid ) + Point aCurPos = pViewData->GetScrPos( aOldStt.Col(), aOldStt.Row(), eWhich, true ); + Point aCurPosHmm = pGridWin->PixelToLogic(aCurPos, aDrawMode ); + Point aGridOff = aCurPosHmm - aOldPos; + // fdo#63878 Fix the X position for RTL Sheet + if( rDoc.IsNegativePage( GetTab() ) && !comphelper::LibreOfficeKit::isActive() ) + aGridOff.setX( aCurPosHmm.getX() + aOldPos.getX() ); +} + +void ScDrawView::resetGridOffsetsForAllSdrPageViews() +{ + SdrPageView* pPageView(GetSdrPageView()); + + if(nullptr == pPageView) + return; + + for(sal_uInt32 a(0); a < pPageView->PageWindowCount(); a++) + { + SdrPageWindow* pPageWindow(pPageView->GetPageWindow(a)); + assert(pPageWindow && "SdrView::SetMasterPagePaintCaching: Corrupt SdrPageWindow list (!)"); + + if(nullptr != pPageWindow) + { + sdr::contact::ObjectContact& rObjectContact(pPageWindow->GetObjectContact()); + + if(rObjectContact.supportsGridOffsets()) + { + rObjectContact.resetAllGridOffsets(); + } + } + } +} + +bool ScDrawView::calculateGridOffsetForSdrObject( + SdrObject& rSdrObject, + basegfx::B2DVector& rTarget) const +{ + if (comphelper::LibreOfficeKit::isActive() && + !comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs)) + return false; + + ScGridWindow* pGridWin(pViewData->GetActiveWin()); + + if(nullptr == pGridWin) + { + return false; + } + + ScDrawObjData* pData(ScDrawLayer::GetObjData(&rSdrObject)); + ScAddress aOldStt; + + if(nullptr != pData && pData->maStart.IsValid()) + { + aOldStt = pData->maStart; + } + else + { + // Page anchored object so... + // synthesise an anchor ( but don't attach it to + // the object as we want to maintain page anchoring ) + ScDrawObjData aAnchor; + const tools::Rectangle aObjRect(rSdrObject.GetLogicRect()); + ScDrawLayer::GetCellAnchorFromPosition( + aObjRect, + aAnchor, + rDoc, + GetTab()); + aOldStt = aAnchor.maStart; + } + + MapMode aDrawMode = pGridWin->GetDrawMapMode(); + + // find pos anchor position + Point aOldPos(rDoc.GetColOffset(aOldStt.Col(), aOldStt.Tab()), rDoc.GetRowOffset(aOldStt.Row(), aOldStt.Tab())); + aOldPos.setX(convertTwipToMm100(aOldPos.X())); + aOldPos.setY(convertTwipToMm100(aOldPos.Y())); + + // find position of same point on the screen ( e.g. grid ) + ScSplitPos eWhich(pViewData->GetActivePart()); + Point aCurPos(pViewData->GetScrPos(aOldStt.Col(), aOldStt.Row(), eWhich, true)); + Point aCurPosHmm(pGridWin->PixelToLogic(aCurPos, aDrawMode)); + Point aGridOff(aCurPosHmm - aOldPos); + + bool bLOKActive = comphelper::LibreOfficeKit::isActive(); + bool bNegativePage = rDoc.IsNegativePage(GetTab()); + + // fdo#63878 Fix the X position for RTL Sheet + if(bNegativePage && !bLOKActive) + { + aGridOff.setX(aCurPosHmm.getX() + aOldPos.getX()); + } + + rTarget.setX(bNegativePage && bLOKActive ? -aGridOff.X() : aGridOff.X()); + rTarget.setY(aGridOff.Y()); + return true; +} + +bool ScDrawView::calculateGridOffsetForB2DRange( + const basegfx::B2DRange& rB2DRange, + basegfx::B2DVector& rTarget) const +{ + ScGridWindow* pGridWin(pViewData->GetActiveWin()); + + if(nullptr == pGridWin || rB2DRange.isEmpty()) + { + return false; + } + + // No SdrObject, so synthesise an anchor ( but don't attach it to + // the object as we want to maintain page anchoring ) + ScDrawObjData aAnchor; + const tools::Rectangle aRectangle( + basegfx::fround(rB2DRange.getMinX()), basegfx::fround(rB2DRange.getMinY()), + basegfx::fround(rB2DRange.getMaxX()), basegfx::fround(rB2DRange.getMaxY())); + ScDrawLayer::GetCellAnchorFromPosition( + aRectangle, + aAnchor, + rDoc, + GetTab()); + ScAddress aOldStt(aAnchor.maStart); + + MapMode aDrawMode = pGridWin->GetDrawMapMode(); + + // find pos anchor position + Point aOldPos(rDoc.GetColOffset(aOldStt.Col(), aOldStt.Tab()), rDoc.GetRowOffset(aOldStt.Row(), aOldStt.Tab())); + aOldPos.setX(convertTwipToMm100(aOldPos.X())); + aOldPos.setY(convertTwipToMm100(aOldPos.Y())); + + // find position of same point on the screen ( e.g. grid ) + ScSplitPos eWhich(pViewData->GetActivePart()); + Point aCurPos(pViewData->GetScrPos(aOldStt.Col(), aOldStt.Row(), eWhich, true)); + Point aCurPosHmm(pGridWin->PixelToLogic(aCurPos, aDrawMode)); + Point aGridOff(aCurPosHmm - aOldPos); + + bool bLOKActive = comphelper::LibreOfficeKit::isActive(); + bool bNegativePage = rDoc.IsNegativePage(GetTab()); + + // fdo#63878 Fix the X position for RTL Sheet + if(bNegativePage && !bLOKActive) + { + aGridOff.setX(aCurPosHmm.getX() + aOldPos.getX()); + } + + rTarget.setX(bLOKActive && bNegativePage ? -aGridOff.X() : aGridOff.X()); + rTarget.setY(aGridOff.Y()); + return true; +} + +// Create a new view-local UndoManager manager for Calc +std::unique_ptr ScDrawView::createLocalTextUndoManager() +{ + std::unique_ptr pUndoManager(new SdrUndoManager); + ScDocShell* pDocShell = pViewData ? pViewData->GetDocShell() : nullptr; + pUndoManager->SetDocShell(pDocShell); + return pUndoManager; +} + +// #i123922# helper to apply a Graphic to an existing SdrObject +SdrObject* ScDrawView::ApplyGraphicToObject( + SdrObject& rHitObject, + const Graphic& rGraphic, + const OUString& rBeginUndoText, + const OUString& rFile) +{ + if(auto pGrafHitObj = dynamic_cast< SdrGrafObj* >(&rHitObject)) + { + rtl::Reference pNewGrafObj = SdrObject::Clone(*pGrafHitObj, rHitObject.getSdrModelFromSdrObject()); + + pNewGrafObj->SetGraphic(rGraphic); + BegUndo(rBeginUndoText); + ReplaceObjectAtView(&rHitObject, *GetSdrPageView(), pNewGrafObj.get()); + + // set in all cases - the Clone() will have copied an existing link (!) + pNewGrafObj->SetGraphicLink( rFile ); + + EndUndo(); + return pNewGrafObj.get(); + } + else if(rHitObject.IsClosedObj() && !dynamic_cast< SdrOle2Obj* >(&rHitObject)) + { + AddUndo(std::make_unique(rHitObject)); + + SfxItemSetFixed aSet(GetModel().GetItemPool()); + + aSet.Put(XFillStyleItem(drawing::FillStyle_BITMAP)); + aSet.Put(XFillBitmapItem(OUString(), rGraphic)); + rHitObject.SetMergedItemSetAndBroadcast(aSet); + return &rHitObject; + } + + return nullptr; +} + +// Own derivation of ObjectContact to allow on-demand calculation of +// GridOffset for non-linear ViewToDevice transformation (calc) +namespace sdr::contact +{ + namespace { + + class ObjectContactOfScDrawView final : public ObjectContactOfPageView + { + private: + // The ScDrawView to work on + const ScDrawView& mrScDrawView; + + public: + explicit ObjectContactOfScDrawView( + const ScDrawView& rScDrawView, + SdrPageWindow& rPageWindow, + const char* pDebugName); + + virtual bool supportsGridOffsets() const override; + virtual void calculateGridOffsetForViewObjectContact( + basegfx::B2DVector& rTarget, + const ViewObjectContact& rClient) const override; + virtual void calculateGridOffsetForB2DRange( + basegfx::B2DVector& rTarget, + const basegfx::B2DRange& rB2DRange) const override; + }; + + } + + ObjectContactOfScDrawView::ObjectContactOfScDrawView( + const ScDrawView& rScDrawView, + SdrPageWindow& rPageWindow, + const char* pDebugName) + : ObjectContactOfPageView(rPageWindow, pDebugName), + mrScDrawView(rScDrawView) + { + } + + bool ObjectContactOfScDrawView::supportsGridOffsets() const + { + // Except when scPrintTwipsMsgs flag is active, + // Calc in LOK mode directly sets pixel-aligned logical coordinates for draw-objects. + if (comphelper::LibreOfficeKit::isActive() && + !comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs)) + return false; + + // no GridOffset support for printer + if(isOutputToPrinter()) + { + return false; + } + + // no GridOffset support for PDF export + if(isOutputToPDFFile()) + { + return false; + } + + // yes - we support it + return true; + } + + void ObjectContactOfScDrawView::calculateGridOffsetForViewObjectContact( + basegfx::B2DVector& rTarget, + const ViewObjectContact& rClient) const + { + // Here the on-demand calculation happens. Try to access the SdrObject involved + SdrObject* pTargetSdrObject(rClient.GetViewContact().TryToGetSdrObject()); + + if(nullptr != pTargetSdrObject) + { + mrScDrawView.calculateGridOffsetForSdrObject( + *pTargetSdrObject, + rTarget); + } + } + + void ObjectContactOfScDrawView::calculateGridOffsetForB2DRange( + basegfx::B2DVector& rTarget, + const basegfx::B2DRange& rB2DRange) const + { + // Here the on-demand calculation happens. Try to access the SdrObject involved + if(!rB2DRange.isEmpty()) + { + mrScDrawView.calculateGridOffsetForB2DRange( + rB2DRange, + rTarget); + } + } +} + +// Create own derivation of ObjectContact for calc +sdr::contact::ObjectContact* ScDrawView::createViewSpecificObjectContact( + SdrPageWindow& rPageWindow, + const char* pDebugName) const +{ + return new sdr::contact::ObjectContactOfScDrawView( + *this, + rPageWindow, + pDebugName); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/editsh.cxx b/sc/source/ui/view/editsh.cxx new file mode 100644 index 0000000000..c392f111e2 --- /dev/null +++ b/sc/source/ui/view/editsh.cxx @@ -0,0 +1,1427 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ShellClass_ScEditShell +#include + +#include +#include +#include + +using namespace ::com::sun::star; + + +SFX_IMPL_INTERFACE(ScEditShell, SfxShell) + +void ScEditShell::InitInterface_Impl() +{ + GetStaticInterface()->RegisterPopupMenu("celledit"); +} + +ScEditShell::ScEditShell(EditView* pView, ScViewData& rData) : + pEditView (pView), + rViewData (rData), + bPastePossible (false), + bIsInsertMode (true) +{ + SetPool( pEditView->GetEditEngine()->GetEmptyItemSet().GetPool() ); + SetUndoManager( &pEditView->GetEditEngine()->GetUndoManager() ); + SetName("EditCell"); + SfxShell::SetContextName(vcl::EnumContext::GetContextName(vcl::EnumContext::Context::EditCell)); +} + +ScEditShell::~ScEditShell() +{ + if ( mxClipEvtLstnr.is() ) + { + mxClipEvtLstnr->RemoveListener( rViewData.GetActiveWin() ); + + // The listener may just now be waiting for the SolarMutex and call the link + // afterwards, in spite of RemoveListener. So the link has to be reset, too. + mxClipEvtLstnr->ClearCallbackLink(); + } +} + +ScInputHandler* ScEditShell::GetMyInputHdl() +{ + return SC_MOD()->GetInputHdl( rViewData.GetViewShell() ); +} + +void ScEditShell::SetEditView(EditView* pView) +{ + pEditView = pView; + pEditView->SetInsertMode( bIsInsertMode ); + SetPool( pEditView->GetEditEngine()->GetEmptyItemSet().GetPool() ); + SetUndoManager( &pEditView->GetEditEngine()->GetUndoManager() ); +} + +static void lcl_RemoveAttribs( EditView& rEditView ) +{ + ScEditEngineDefaulter* pEngine = static_cast(rEditView.GetEditEngine()); + + bool bOld = pEngine->SetUpdateLayout(false); + + OUString aName = ScResId( STR_UNDO_DELETECONTENTS ); + ViewShellId nViewShellId(-1); + if (ScTabViewShell* pViewSh = ScTabViewShell::GetActiveViewShell()) + nViewShellId = pViewSh->GetViewShellId(); + pEngine->GetUndoManager().EnterListAction( aName, aName, 0, nViewShellId ); + + rEditView.RemoveAttribs(true); + pEngine->RepeatDefaults(); // paragraph attributes from cell formats must be preserved + + pEngine->GetUndoManager().LeaveListAction(); + + pEngine->SetUpdateLayout(bOld); +} + +static void lclInsertCharacter( EditView* pTableView, EditView* pTopView, sal_Unicode cChar ) +{ + OUString aString( cChar ); + if( pTableView ) + pTableView->InsertText( aString ); + if( pTopView ) + pTopView->InsertText( aString ); +} + +void ScEditShell::Execute( SfxRequest& rReq ) +{ + const SfxItemSet* pReqArgs = rReq.GetArgs(); + sal_uInt16 nSlot = rReq.GetSlot(); + SfxBindings& rBindings = rViewData.GetBindings(); + + ScInputHandler* pHdl = GetMyInputHdl(); + OSL_ENSURE(pHdl,"no ScInputHandler"); + + EditView* pTopView = pHdl->GetTopView(); // Has thee input cell the focus? + EditView* pTableView = pHdl->GetTableView(); + + OSL_ENSURE(pTableView,"no EditView :-("); + /* #i91683# No EditView if spell-check dialog is active and positioned on + * an error and user immediately (without double click or F2) selected a + * text portion of that cell with the mouse and wanted to modify it. */ + /* FIXME: Bailing out only cures the symptom and prevents a crash, no edit + * action is possible. A real fix somehow would need to create a valid + * EditView from the spell-check view. */ + if (!pTableView) + return; + + EditEngine* pEngine = pTableView->GetEditEngine(); + + pHdl->DataChanging(); + bool bSetSelIsRef = false; + bool bSetModified = true; + + switch ( nSlot ) + { + case SID_ATTR_INSERT: + case FID_INS_CELL_CONTENTS: // Insert taste, while defined as Acc + bIsInsertMode = !pTableView->IsInsertMode(); + pTableView->SetInsertMode( bIsInsertMode ); + if (pTopView) + pTopView->SetInsertMode( bIsInsertMode ); + rBindings.Invalidate( SID_ATTR_INSERT ); + break; + + case SID_THES: + { + OUString aReplaceText; + const SfxStringItem* pItem2 = rReq.GetArg(FN_PARAM_THES_WORD_REPLACE); + if (pItem2) + aReplaceText = pItem2->GetValue(); + if (!aReplaceText.isEmpty()) + ReplaceTextWithSynonym( *pEditView, aReplaceText ); + } + break; + + case SID_COPY: + pTableView->Copy(); + bSetModified = false; + break; + + case SID_CUT: + pTableView->Cut(); + if (pTopView) + pTopView->DeleteSelected(); + break; + + case SID_PASTE: + { + EVControlBits nControl = pTableView->GetControlWord(); + if (pTopView) + { + pTopView->Paste(); + pTableView->SetControlWord(nControl | EVControlBits::SINGLELINEPASTE); + } + + pTableView->PasteSpecial(); + pTableView->SetControlWord(nControl); + } + break; + + case SID_DELETE: + pTableView->DeleteSelected(); + if (pTopView) + pTopView->DeleteSelected(); + break; + + case SID_CELL_FORMAT_RESET: // "Standard" + lcl_RemoveAttribs( *pTableView ); + if ( pTopView ) + lcl_RemoveAttribs( *pTopView ); + break; + + case SID_CLIPBOARD_FORMAT_ITEMS: + { + SotClipboardFormatId nFormat = SotClipboardFormatId::NONE; + const SfxPoolItem* pItem; + if ( pReqArgs && pReqArgs->GetItemState(nSlot, true, &pItem) == SfxItemState::SET ) + if (auto pIntItem = dynamic_cast( pItem)) + nFormat = static_cast(pIntItem->GetValue()); + + if ( nFormat != SotClipboardFormatId::NONE ) + { + if (SotClipboardFormatId::STRING == nFormat) + pTableView->Paste(); + else + pTableView->PasteSpecial(); + + if (pTopView) + pTopView->Paste(); + } + } + break; + + case SID_PASTE_SPECIAL: + { + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + ScopedVclPtr pDlg(pFact->CreatePasteDialog(rViewData.GetDialogParent())); + SotClipboardFormatId nFormat = SotClipboardFormatId::NONE; + pDlg->Insert( SotClipboardFormatId::STRING, OUString() ); + pDlg->Insert( SotClipboardFormatId::RTF, OUString() ); + pDlg->Insert( SotClipboardFormatId::RICHTEXT, OUString() ); + // Do not offer SotClipboardFormatId::STRING_TSVC for + // in-cell paste. + + TransferableDataHelper aDataHelper( + TransferableDataHelper::CreateFromSystemClipboard( rViewData.GetActiveWin() ) ); + + nFormat = pDlg->GetFormat( aDataHelper.GetTransferable() ); + pDlg.disposeAndClear(); + + // while the dialog was open, edit mode may have been stopped + if (!SC_MOD()->IsInputMode()) + return; + + if (nFormat != SotClipboardFormatId::NONE) + { + if (SotClipboardFormatId::STRING == nFormat) + pTableView->Paste(); + else + pTableView->PasteSpecial(); + + if (pTopView) + pTopView->Paste(); + } + + if (vcl::Window* pViewWindow = pTopView ? pTopView->GetWindow() : nullptr) + pViewWindow->GrabFocus(); + } + break; + + case SID_PASTE_UNFORMATTED: + { + pTableView->Paste(); + + if (pTopView) + { + pTopView->Paste(); + if (vcl::Window* pViewWindow = pTopView->GetWindow()) + pViewWindow->GrabFocus(); + } + } + break; + + case SID_SELECTALL: + { + sal_Int32 nPar = pEngine->GetParagraphCount(); + if (nPar) + { + sal_Int32 nLen = pEngine->GetTextLen(nPar-1); + pTableView->SetSelection(ESelection(0,0,nPar-1,nLen)); + if (pTopView) + pTopView->SetSelection(ESelection(0,0,nPar-1,nLen)); + rBindings.Invalidate( SID_ATTR_CHAR_FONT ); + rBindings.Invalidate( SID_ATTR_CHAR_FONTHEIGHT ); + rBindings.Invalidate( SID_ATTR_CHAR_WEIGHT ); + rBindings.Invalidate( SID_ATTR_CHAR_POSTURE ); + rBindings.Invalidate( SID_ATTR_CHAR_UNDERLINE ); + rBindings.Invalidate( SID_ATTR_CHAR_STRIKEOUT ); + rBindings.Invalidate( SID_ATTR_CHAR_SHADOWED ); + rBindings.Invalidate( SID_ATTR_CHAR_KERNING ); + rBindings.Invalidate( SID_ATTR_CHAR_COLOR ); + rBindings.Invalidate( SID_SET_SUPER_SCRIPT ); + rBindings.Invalidate( SID_SET_SUB_SCRIPT ); + } + } + return; + case SID_UNICODE_NOTATION_TOGGLE: + { + EditView* pActiveView = pHdl->GetActiveView(); + if( pActiveView ) + { + OUString sInput = pEngine->GetText(); + ESelection aSel( pActiveView->GetSelection() ); + if( aSel.HasRange() ) + sInput = pActiveView->GetSelected(); + + if( aSel.nStartPos > aSel.nEndPos ) + aSel.nEndPos = aSel.nStartPos; + + //calculate a valid end-position by reading logical characters + sal_Int32 nUtf16Pos=0; + while( (nUtf16Pos < sInput.getLength()) && (nUtf16Pos < aSel.nEndPos) ) + { + sInput.iterateCodePoints(&nUtf16Pos); + if( nUtf16Pos > aSel.nEndPos ) + aSel.nEndPos = nUtf16Pos; + } + + ToggleUnicodeCodepoint aToggle; + while( nUtf16Pos && aToggle.AllowMoreInput( sInput[nUtf16Pos-1]) ) + --nUtf16Pos; + OUString sReplacement = aToggle.ReplacementString(); + if( !sReplacement.isEmpty() ) + { + aSel.nStartPos = aSel.nEndPos - aToggle.StringToReplace().getLength(); + pTableView->SetSelection( aSel ); + pTableView->InsertText(sReplacement, true); + if( pTopView ) + { + pTopView->SetSelection( aSel ); + pTopView->InsertText(sReplacement, true); + } + } + } + } + break; + + case SID_CHARMAP: + { + SvtScriptType nScript = pTableView->GetSelectedScriptType(); + sal_uInt16 nFontWhich = ( nScript == SvtScriptType::ASIAN ) ? EE_CHAR_FONTINFO_CJK : + ( ( nScript == SvtScriptType::COMPLEX ) ? EE_CHAR_FONTINFO_CTL : + EE_CHAR_FONTINFO ); + auto const attribs = pTableView->GetAttribs(); + const SvxFontItem& rItem = static_cast( + attribs.Get(nFontWhich)); + + OUString aString; + std::shared_ptr aNewItem(std::make_shared(EE_CHAR_FONTINFO)); + + const SfxItemSet *pArgs = rReq.GetArgs(); + const SfxPoolItem* pItem = nullptr; + if( pArgs ) + pArgs->GetItemState(SID_CHARMAP, false, &pItem); + + if ( pItem ) + { + aString = static_cast(pItem)->GetValue(); + const SfxStringItem* pFontItem = pArgs->GetItemIfSet( SID_ATTR_SPECIALCHAR, false); + if ( pFontItem ) + { + const OUString& aFontName(pFontItem->GetValue()); + vcl::Font aFont(aFontName, Size(1,1)); // Size just because CTOR + // tdf#125054 see comment in drtxob.cxx, same ID + aNewItem = std::make_shared( + aFont.GetFamilyType(), aFont.GetFamilyName(), + aFont.GetStyleName(), aFont.GetPitch(), + aFont.GetCharSet(), ATTR_FONT); + } + else + { + aNewItem.reset(rItem.Clone()); + } + + // tdf#125054 force Item to correct intended ID + aNewItem->SetWhich(EE_CHAR_FONTINFO); + } + else + { + ScViewUtil::ExecuteCharMap(rItem, *rViewData.GetViewShell()); + + // while the dialog was open, edit mode may have been stopped + if (!SC_MOD()->IsInputMode()) + return; + } + + if ( !aString.isEmpty() ) + { + // if string contains WEAK characters, set all fonts + SvtScriptType nSetScript; + ScDocument& rDoc = rViewData.GetDocument(); + if ( rDoc.HasStringWeakCharacters( aString ) ) + nSetScript = SvtScriptType::LATIN | SvtScriptType::ASIAN | SvtScriptType::COMPLEX; + else + nSetScript = rDoc.GetStringScriptType( aString ); + + SfxItemSet aSet( pTableView->GetEmptyItemSet() ); + SvxScriptSetItem aSetItem( SID_ATTR_CHAR_FONT, GetPool() ); + aSetItem.PutItemForScriptType( nSetScript, *aNewItem ); + aSet.Put( aSetItem.GetItemSet(), false ); + + // SetAttribs on the View selects a word, when nothing is selected + pTableView->GetEditEngine()->QuickSetAttribs( aSet, pTableView->GetSelection() ); + pTableView->InsertText(aString); + if (pTopView) + pTopView->InsertText(aString); + + SfxStringItem aStringItem( SID_CHARMAP, aString ); + SfxStringItem aFontItem( SID_ATTR_SPECIALCHAR, aNewItem->GetFamilyName() ); + rReq.AppendItem( aFontItem ); + rReq.AppendItem( aStringItem ); + rReq.Done(); + + } + + if (vcl::Window* pViewWindow = pTopView ? pTopView->GetWindow() : nullptr) + pViewWindow->GrabFocus(); + } + break; + + case FID_INSERT_NAME: + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr pDlg(pFact->CreateScNamePasteDlg(rViewData.GetDialogParent(), rViewData.GetDocShell())); + short nRet = pDlg->Execute(); + // pDlg is needed below + + // while the dialog was open, edit mode may have been stopped + if (!SC_MOD()->IsInputMode()) + return; + + if ( nRet == BTN_PASTE_NAME ) + { + std::vector aNames = pDlg->GetSelectedNames(); + if (!aNames.empty()) + { + OUStringBuffer aBuffer; + for (const auto& rName : aNames) + { + aBuffer.append(rName + " "); + } + const OUString s = aBuffer.makeStringAndClear(); + pTableView->InsertText(s); + if (pTopView) + pTopView->InsertText(s); + } + } + pDlg.disposeAndClear(); + + if (vcl::Window* pViewWindow = pTopView ? pTopView->GetWindow() : nullptr) + pViewWindow->GrabFocus(); + } + break; + + case SID_CHAR_DLG_EFFECT: + case SID_CHAR_DLG: + { + SfxItemSet aAttrs( pTableView->GetAttribs() ); + + SfxObjectShell* pObjSh = rViewData.GetSfxDocShell(); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr pDlg(pFact->CreateScCharDlg( + rViewData.GetDialogParent(), &aAttrs, pObjSh, false)); + if (nSlot == SID_CHAR_DLG_EFFECT) + { + pDlg->SetCurPageId("fonteffects"); + } + short nRet = pDlg->Execute(); + // pDlg is needed below + + // while the dialog was open, edit mode may have been stopped + if (!SC_MOD()->IsInputMode()) + return; + + if ( nRet == RET_OK ) + { + const SfxItemSet* pOut = pDlg->GetOutputItemSet(); + pTableView->SetAttribs( *pOut ); + } + } + break; + + case SID_TOGGLE_REL: + { + /* TODO: MLFORMULA: this should work also with multi-line formulas. */ + if (pEngine->GetParagraphCount() == 1) + { + OUString aText = pEngine->GetText(); + ESelection aSel = pEditView->GetSelection(); // current View + + ScDocument& rDoc = rViewData.GetDocument(); + ScRefFinder aFinder(aText, rViewData.GetCurPos(), rDoc, rDoc.GetAddressConvention()); + aFinder.ToggleRel( aSel.nStartPos, aSel.nEndPos ); + if (aFinder.GetFound()) + { + const OUString& aNew = aFinder.GetText(); + ESelection aNewSel( 0,aFinder.GetSelStart(), 0,aFinder.GetSelEnd() ); + pEngine->SetText( aNew ); + pTableView->SetSelection( aNewSel ); + if ( pTopView ) + { + pTopView->GetEditEngine()->SetText( aNew ); + pTopView->SetSelection( aNewSel ); + } + + // reference is being selected -> do not overwrite when typing + bSetSelIsRef = true; + } + } + } + break; + + case SID_HYPERLINK_SETLINK: + if( pReqArgs ) + { + const SfxPoolItem* pItem; + if ( pReqArgs->GetItemState( SID_HYPERLINK_SETLINK, true, &pItem ) == SfxItemState::SET ) + { + const SvxHyperlinkItem* pHyper = static_cast(pItem); + const OUString& rName = pHyper->GetName(); + const OUString& rURL = pHyper->GetURL(); + const OUString& rTarget = pHyper->GetTargetFrame(); + SvxLinkInsertMode eMode = pHyper->GetInsertMode(); + + bool bCellLinksOnly + = (SC_MOD()->GetAppOptions().GetLinksInsertedLikeMSExcel() + && rViewData.GetSfxDocShell()->GetMedium()->GetFilter()->IsMSOFormat()) + || comphelper::LibreOfficeKit::isActive(); + + bool bDone = false; + if ( (eMode == HLINK_DEFAULT || eMode == HLINK_FIELD) && !bCellLinksOnly ) + { + std::unique_ptr aSvxFieldDataPtr(GetURLField()); + const SvxURLField* pURLField(static_cast(aSvxFieldDataPtr.get())); + if ( pURLField ) + { + // select old field + + ESelection aSel = pTableView->GetSelection(); + aSel.Adjust(); + aSel.nEndPara = aSel.nStartPara; + aSel.nEndPos = aSel.nStartPos + 1; + pTableView->SetSelection( aSel ); + + // insert new field + + SvxURLField aURLField( rURL, rName, SvxURLFormat::Repr ); + aURLField.SetTargetFrame( rTarget ); + SvxFieldItem aURLItem( aURLField, EE_FEATURE_FIELD ); + pTableView->InsertField( aURLItem ); + pTableView->SetSelection( aSel ); // select inserted field + + // now also fields in the Top-View + + if ( pTopView ) + { + aSel = pTopView->GetSelection(); + aSel.nEndPara = aSel.nStartPara; + aSel.nEndPos = aSel.nStartPos + 1; + pTopView->SetSelection( aSel ); + pTopView->InsertField( aURLItem ); + pTopView->SetSelection( aSel ); // select inserted field + } + + bDone = true; + } + } + + if (!bDone) + { + if (bCellLinksOnly) + { + sal_Int32 nPar = pEngine->GetParagraphCount(); + if (nPar) + { + sal_Int32 nLen = pEngine->GetTextLen(nPar - 1); + pTableView->SetSelection(ESelection(0, 0, nPar - 1, nLen)); + if (pTopView) + pTopView->SetSelection(ESelection(0, 0, nPar - 1, nLen)); + } + } + rViewData.GetViewShell()-> + InsertURL( rName, rURL, rTarget, static_cast(eMode) ); + + // when "Button", the InsertURL in ViewShell turns the EditShell off + // thus the immediate return statement + return; + } + } + } + break; + case SID_OPEN_HYPERLINK: + { + const SvxFieldItem* pFieldItem + = pEditView->GetFieldAtSelection(/*AlsoCheckBeforeCursor=*/true); + const SvxFieldData* pField = pFieldItem ? pFieldItem->GetField() : nullptr; + if (const SvxURLField* pURLField = dynamic_cast(pField)) + ScGlobal::OpenURL( pURLField->GetURL(), pURLField->GetTargetFrame(), true ); + return; + } + case SID_EDIT_HYPERLINK: + { + // Ensure the field is selected first + pEditView->SelectFieldAtCursor(); + rViewData.GetViewShell()->GetViewFrame().GetDispatcher()->Execute( + SID_HYPERLINK_DIALOG); + } + break; + case SID_COPY_HYPERLINK_LOCATION: + { + const SvxFieldItem* pFieldItem + = pEditView->GetFieldAtSelection(/*AlsoCheckBeforeCursor=*/true); + const SvxFieldData* pField = pFieldItem ? pFieldItem->GetField() : nullptr; + if (const SvxURLField* pURLField = dynamic_cast(pField)) + { + uno::Reference xClipboard + = pEditView->GetClipboard(); + vcl::unohelper::TextDataObject::CopyStringTo(pURLField->GetURL(), xClipboard, SfxViewShell::Current()); + } + } + break; + case SID_REMOVE_HYPERLINK: + { + URLFieldHelper::RemoveURLField(*pEditView); + } + break; + + case FN_INSERT_SOFT_HYPHEN: + lclInsertCharacter( pTableView, pTopView, CHAR_SHY ); + break; + case FN_INSERT_HARDHYPHEN: + lclInsertCharacter( pTableView, pTopView, CHAR_NBHY ); + break; + case FN_INSERT_HARD_SPACE: + lclInsertCharacter( pTableView, pTopView, CHAR_NBSP ); + break; + case FN_INSERT_NNBSP: + lclInsertCharacter( pTableView, pTopView, CHAR_NNBSP ); + break; + case SID_INSERT_RLM: + lclInsertCharacter( pTableView, pTopView, CHAR_RLM ); + break; + case SID_INSERT_LRM: + lclInsertCharacter( pTableView, pTopView, CHAR_LRM ); + break; + case SID_INSERT_ZWSP: + lclInsertCharacter( pTableView, pTopView, CHAR_ZWSP ); + break; + case SID_INSERT_WJ: + lclInsertCharacter( pTableView, pTopView, CHAR_WJ ); + break; + case SID_INSERT_FIELD_SHEET: + { + SvxTableField aField(rViewData.GetTabNo()); + SvxFieldItem aItem(aField, EE_FEATURE_FIELD); + pTableView->InsertField(aItem); + } + break; + case SID_INSERT_FIELD_TITLE: + { + SvxFileField aField; + SvxFieldItem aItem(aField, EE_FEATURE_FIELD); + pTableView->InsertField(aItem); + } + break; + case SID_INSERT_FIELD_DATE_VAR: + { + SvxDateField aField; + SvxFieldItem aItem(aField, EE_FEATURE_FIELD); + pTableView->InsertField(aItem); + } + break; + } + + pHdl->DataChanged(false, bSetModified); + if (bSetSelIsRef) + pHdl->SetSelIsRef(true); +} + +static void lcl_DisableAll( SfxItemSet& rSet ) // disable all slots +{ + SfxWhichIter aIter( rSet ); + sal_uInt16 nWhich = aIter.FirstWhich(); + while (nWhich) + { + rSet.DisableItem( nWhich ); + nWhich = aIter.NextWhich(); + } +} + +void ScEditShell::GetState( SfxItemSet& rSet ) +{ + // When deactivating the view, edit mode is stopped, but the EditShell is left active + // (a shell can't be removed from within Deactivate). In that state, the EditView isn't inserted + // into the EditEngine, so it can have an invalid selection and must not be used. + ScInputHandler* pHdl = GetMyInputHdl(); + if ( !rViewData.HasEditView( rViewData.GetActivePart() ) ) + { + lcl_DisableAll( rSet ); + + // Some items are actually useful and still applicable when in formula building mode: enable + if (pHdl && pHdl->IsFormulaMode()) + { + rSet.ClearItem(SID_TOGGLE_REL); // F4 Cycle Cell Reference Types + rSet.ClearItem(SID_CHARMAP); // Insert Special Characters + } + return; + } + + EditView* pActiveView = pHdl ? pHdl->GetActiveView() : pEditView; + + SfxWhichIter aIter( rSet ); + sal_uInt16 nWhich = aIter.FirstWhich(); + while (nWhich) + { + switch (nWhich) + { + case SID_ATTR_INSERT: // Status row + { + if ( pActiveView ) + rSet.Put( SfxBoolItem( nWhich, pActiveView->IsInsertMode() ) ); + else + { + // Here the code used to pass the value 42 and it used + // to "work" without warnings because the SfxBoolItem + // was based on 'sal_Bool', which is actually 'unsigned + // char'. But now it uses actual 'bool', and passing 42 + // for a 'bool' parameter causes a warning at least with + // MSVC. So use 'true'. I really really hope there is + // not code somewhere that retrieves this "boolean" item + // and checks it value for the magic value 42... + rSet.Put( SfxBoolItem( nWhich, true) ); + } + } + break; + + case SID_HYPERLINK_GETLINK: + { + SvxHyperlinkItem aHLinkItem; + bool bCellLinksOnly + = (SC_MOD()->GetAppOptions().GetLinksInsertedLikeMSExcel() + && rViewData.GetSfxDocShell()->GetMedium()->GetFilter()->IsMSOFormat()) + || comphelper::LibreOfficeKit::isActive(); + std::unique_ptr aSvxFieldDataPtr(GetURLField()); + const SvxURLField* pURLField(static_cast(aSvxFieldDataPtr.get())); + if (!bCellLinksOnly) + { + if (pURLField) + { + aHLinkItem.SetName(pURLField->GetRepresentation()); + aHLinkItem.SetURL(pURLField->GetURL()); + aHLinkItem.SetTargetFrame(pURLField->GetTargetFrame()); + } + else if (pActiveView) + { + // use selected text as name for urls + OUString sReturn = pActiveView->GetSelected(); + sReturn = sReturn.copy( + 0, std::min(sReturn.getLength(), static_cast(255))); + aHLinkItem.SetName(comphelper::string::stripEnd(sReturn, ' ')); + } + } + else + { + if (!pURLField) + { + aSvxFieldDataPtr = GetFirstURLFieldFromCell(); + pURLField = static_cast(aSvxFieldDataPtr.get()); + } + if (pURLField) + { + aHLinkItem.SetURL(pURLField->GetURL()); + aHLinkItem.SetTargetFrame(pURLField->GetTargetFrame()); + } + ScDocument& rDoc = rViewData.GetDocument(); + SCCOL nPosX = rViewData.GetCurX(); + SCROW nPosY = rViewData.GetCurY(); + SCTAB nTab = rViewData.GetTabNo(); + aHLinkItem.SetName(rDoc.GetString(nPosX, nPosY, nTab)); + } + rSet.Put(aHLinkItem); + } + break; + + case SID_OPEN_HYPERLINK: + case SID_EDIT_HYPERLINK: + case SID_COPY_HYPERLINK_LOCATION: + case SID_REMOVE_HYPERLINK: + { + if (!URLFieldHelper::IsCursorAtURLField(*pEditView, + /*AlsoCheckBeforeCursor=*/true)) + rSet.DisableItem (nWhich); + } + break; + + case SID_TRANSLITERATE_HALFWIDTH: + case SID_TRANSLITERATE_FULLWIDTH: + case SID_TRANSLITERATE_HIRAGANA: + case SID_TRANSLITERATE_KATAKANA: + case SID_INSERT_RLM: + case SID_INSERT_LRM: + ScViewUtil::HideDisabledSlot( rSet, rViewData.GetBindings(), nWhich ); + break; + + case SID_THES: + { + OUString aStatusVal; + LanguageType nLang = LANGUAGE_NONE; + bool bIsLookUpWord = pActiveView && + GetStatusValueForThesaurusFromContext(aStatusVal, nLang, *pActiveView); + rSet.Put( SfxStringItem( SID_THES, aStatusVal ) ); + + // disable thesaurus context menu entry if there is nothing to look up + bool bCanDoThesaurus = ScModule::HasThesaurusLanguage( nLang ); + if (!bIsLookUpWord || !bCanDoThesaurus) + rSet.DisableItem( SID_THES ); + } + break; + case SID_INSERT_FIELD_SHEET: + case SID_INSERT_FIELD_TITLE: + case SID_INSERT_FIELD_DATE_VAR: + break; + case SID_COPY: + case SID_CUT: + if (GetObjectShell() && GetObjectShell()->isContentExtractionLocked()) + { + rSet.DisableItem(SID_COPY); + rSet.DisableItem(SID_CUT); + } + break; + + } + nWhich = aIter.NextWhich(); + } +} + +std::unique_ptr ScEditShell::GetURLField() +{ + ScInputHandler* pHdl = GetMyInputHdl(); + EditView* pActiveView = pHdl ? pHdl->GetActiveView() : pEditView; + if (!pActiveView) + return std::unique_ptr(); + + const SvxFieldData* pField = pActiveView->GetFieldUnderMouseOrInSelectionOrAtCursor(); + if (auto pURLField = dynamic_cast(pField)) + return pURLField->Clone(); + + return std::unique_ptr(); +} + +std::unique_ptr ScEditShell::GetFirstURLFieldFromCell() +{ + EditEngine* pEE = GetEditView()->GetEditEngine(); + sal_Int32 nParaCount = pEE->GetParagraphCount(); + for (sal_Int32 nPara = 0; nPara < nParaCount; ++nPara) + { + ESelection aSel(nPara, 0); + std::vector aPosList; + pEE->GetPortions(nPara, aPosList); + for (const auto& rPos : aPosList) + { + aSel.nEndPos = rPos; + + SfxItemSet aEditSet(pEE->GetAttribs(aSel)); + if (aSel.nStartPos + 1 == aSel.nEndPos) + { + // test if the character is a text field + if (const SvxFieldItem* pItem = aEditSet.GetItemIfSet(EE_FEATURE_FIELD, false)) + { + const SvxFieldData* pField = pItem->GetField(); + if (const SvxURLField* pUrlField = dynamic_cast(pField)) + { + return pUrlField->Clone(); + } + } + } + aSel.nStartPos = aSel.nEndPos; + } + } + + return std::unique_ptr(); +} + +IMPL_LINK( ScEditShell, ClipboardChanged, TransferableDataHelper*, pDataHelper, void ) +{ + bPastePossible = ( pDataHelper->HasFormat( SotClipboardFormatId::STRING ) + || pDataHelper->HasFormat( SotClipboardFormatId::RTF ) + || pDataHelper->HasFormat( SotClipboardFormatId::RICHTEXT )); + + SfxBindings& rBindings = rViewData.GetBindings(); + rBindings.Invalidate( SID_PASTE ); + rBindings.Invalidate( SID_PASTE_SPECIAL ); + rBindings.Invalidate( SID_PASTE_UNFORMATTED ); + rBindings.Invalidate( SID_CLIPBOARD_FORMAT_ITEMS ); +} + +void ScEditShell::GetClipState( SfxItemSet& rSet ) +{ + // Do not offer SotClipboardFormatId::STRING_TSVC for in-cell paste. + + if ( !mxClipEvtLstnr.is() ) + { + // create listener + mxClipEvtLstnr = new TransferableClipboardListener( LINK( this, ScEditShell, ClipboardChanged ) ); + vcl::Window* pWin = rViewData.GetActiveWin(); + mxClipEvtLstnr->AddListener( pWin ); + + // get initial state + TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromSystemClipboard( rViewData.GetActiveWin() ) ); + bPastePossible = ( aDataHelper.HasFormat( SotClipboardFormatId::STRING ) + || aDataHelper.HasFormat( SotClipboardFormatId::RTF ) + || aDataHelper.HasFormat( SotClipboardFormatId::RICHTEXT ) ); + } + + SfxWhichIter aIter( rSet ); + sal_uInt16 nWhich = aIter.FirstWhich(); + while (nWhich) + { + switch (nWhich) + { + case SID_PASTE: + case SID_PASTE_SPECIAL: + case SID_PASTE_UNFORMATTED: + if( !bPastePossible ) + rSet.DisableItem( nWhich ); + break; + case SID_CLIPBOARD_FORMAT_ITEMS: + if( bPastePossible ) + { + SvxClipboardFormatItem aFormats( SID_CLIPBOARD_FORMAT_ITEMS ); + TransferableDataHelper aDataHelper( + TransferableDataHelper::CreateFromSystemClipboard( rViewData.GetActiveWin() ) ); + + if ( aDataHelper.HasFormat( SotClipboardFormatId::STRING ) ) + aFormats.AddClipbrdFormat( SotClipboardFormatId::STRING ); + if ( aDataHelper.HasFormat( SotClipboardFormatId::RTF ) ) + aFormats.AddClipbrdFormat( SotClipboardFormatId::RTF ); + + rSet.Put( aFormats ); + } + else + rSet.DisableItem( nWhich ); + break; + } + nWhich = aIter.NextWhich(); + } +} + +static void lcl_InvalidateUnder( SfxBindings& rBindings ) +{ + rBindings.Invalidate( SID_ATTR_CHAR_UNDERLINE ); + rBindings.Invalidate( SID_ULINE_VAL_NONE ); + rBindings.Invalidate( SID_ULINE_VAL_SINGLE ); + rBindings.Invalidate( SID_ULINE_VAL_DOUBLE ); + rBindings.Invalidate( SID_ULINE_VAL_DOTTED ); +} + +void ScEditShell::ExecuteAttr(SfxRequest& rReq) +{ + SfxItemSet aSet( pEditView->GetEmptyItemSet() ); + SfxBindings& rBindings = rViewData.GetBindings(); + const SfxItemSet* pArgs = rReq.GetArgs(); + sal_uInt16 nSlot = rReq.GetSlot(); + + switch ( nSlot ) + { + case SID_ATTR_CHAR_FONTHEIGHT: + case SID_ATTR_CHAR_FONT: + { + if (pArgs) + { + // #i78017 establish the same behaviour as in Writer + SvtScriptType nScript = SvtScriptType::LATIN | SvtScriptType::ASIAN | SvtScriptType::COMPLEX; + if (nSlot == SID_ATTR_CHAR_FONT) + { + nScript = pEditView->GetSelectedScriptType(); + if (nScript == SvtScriptType::NONE) nScript = ScGlobal::GetDefaultScriptType(); + } + + SfxItemPool& rPool = GetPool(); + SvxScriptSetItem aSetItem( nSlot, rPool ); + sal_uInt16 nWhich = rPool.GetWhich( nSlot ); + aSetItem.PutItemForScriptType( nScript, pArgs->Get( nWhich ) ); + + aSet.Put( aSetItem.GetItemSet(), false ); + } + } + break; + + case SID_ATTR_CHAR_COLOR: + { + if (pArgs) + { + aSet.Put( pArgs->Get( pArgs->GetPool()->GetWhich( nSlot ) ) ); + rBindings.Invalidate( nSlot ); + } + } + break; + + // Toggles + + case SID_ATTR_CHAR_WEIGHT: + { + // #i78017 establish the same behaviour as in Writer + SvtScriptType nScript = SvtScriptType::LATIN | SvtScriptType::ASIAN | SvtScriptType::COMPLEX; + + SfxItemPool& rPool = GetPool(); + + bool bOld = false; + SvxScriptSetItem aOldSetItem( nSlot, rPool ); + aOldSetItem.GetItemSet().Put( pEditView->GetAttribs(), false ); + const SfxPoolItem* pCore = aOldSetItem.GetItemOfScript( nScript ); + if ( pCore && static_cast(pCore)->GetWeight() > WEIGHT_NORMAL ) + bOld = true; + + SvxScriptSetItem aSetItem( nSlot, rPool ); + aSetItem.PutItemForScriptType( nScript, + SvxWeightItem( bOld ? WEIGHT_NORMAL : WEIGHT_BOLD, EE_CHAR_WEIGHT ) ); + aSet.Put( aSetItem.GetItemSet(), false ); + + rBindings.Invalidate( nSlot ); + } + break; + + case SID_ATTR_CHAR_POSTURE: + { + // #i78017 establish the same behaviour as in Writer + SvtScriptType nScript = SvtScriptType::LATIN | SvtScriptType::ASIAN | SvtScriptType::COMPLEX; + + SfxItemPool& rPool = GetPool(); + + bool bOld = false; + SvxScriptSetItem aOldSetItem( nSlot, rPool ); + aOldSetItem.GetItemSet().Put( pEditView->GetAttribs(), false ); + const SfxPoolItem* pCore = aOldSetItem.GetItemOfScript( nScript ); + if ( pCore && static_cast(pCore)->GetValue() != ITALIC_NONE ) + bOld = true; + + SvxScriptSetItem aSetItem( nSlot, rPool ); + aSetItem.PutItemForScriptType( nScript, + SvxPostureItem( bOld ? ITALIC_NONE : ITALIC_NORMAL, EE_CHAR_ITALIC ) ); + aSet.Put( aSetItem.GetItemSet(), false ); + + rBindings.Invalidate( nSlot ); + } + break; + + case SID_ULINE_VAL_NONE: + aSet.Put( SvxUnderlineItem( LINESTYLE_NONE, EE_CHAR_UNDERLINE ) ); + lcl_InvalidateUnder( rBindings ); + break; + + case SID_ATTR_CHAR_UNDERLINE: + case SID_ULINE_VAL_SINGLE: + case SID_ULINE_VAL_DOUBLE: + case SID_ULINE_VAL_DOTTED: + { + FontLineStyle eOld = pEditView->GetAttribs().Get(EE_CHAR_UNDERLINE).GetLineStyle(); + FontLineStyle eNew = eOld; + switch (nSlot) + { + case SID_ATTR_CHAR_UNDERLINE: + if ( pArgs ) + { + const SvxTextLineItem& rTextLineItem = static_cast< const SvxTextLineItem& >( pArgs->Get( pArgs->GetPool()->GetWhich(nSlot) ) ); + eNew = rTextLineItem.GetLineStyle(); + } + else + { + eNew = ( eOld != LINESTYLE_NONE ) ? LINESTYLE_NONE : LINESTYLE_SINGLE; + } + break; + case SID_ULINE_VAL_SINGLE: + eNew = ( eOld == LINESTYLE_SINGLE ) ? LINESTYLE_NONE : LINESTYLE_SINGLE; + break; + case SID_ULINE_VAL_DOUBLE: + eNew = ( eOld == LINESTYLE_DOUBLE ) ? LINESTYLE_NONE : LINESTYLE_DOUBLE; + break; + case SID_ULINE_VAL_DOTTED: + eNew = ( eOld == LINESTYLE_DOTTED ) ? LINESTYLE_NONE : LINESTYLE_DOTTED; + break; + } + aSet.Put( SvxUnderlineItem( eNew, EE_CHAR_UNDERLINE ) ); + lcl_InvalidateUnder( rBindings ); + } + break; + + case SID_ATTR_CHAR_OVERLINE: + { + FontLineStyle eOld = pEditView->GetAttribs().Get(EE_CHAR_OVERLINE).GetLineStyle(); + FontLineStyle eNew = ( eOld != LINESTYLE_NONE ) ? LINESTYLE_NONE : LINESTYLE_SINGLE; + aSet.Put( SvxOverlineItem( eNew, EE_CHAR_OVERLINE ) ); + rBindings.Invalidate( nSlot ); + } + break; + + case SID_ATTR_CHAR_STRIKEOUT: + { + bool bOld = pEditView->GetAttribs().Get(EE_CHAR_STRIKEOUT).GetValue() != STRIKEOUT_NONE; + aSet.Put( SvxCrossedOutItem( bOld ? STRIKEOUT_NONE : STRIKEOUT_SINGLE, EE_CHAR_STRIKEOUT ) ); + rBindings.Invalidate( nSlot ); + } + break; + + case SID_ATTR_CHAR_SHADOWED: + { + bool bOld = pEditView->GetAttribs().Get(EE_CHAR_SHADOW).GetValue(); + aSet.Put( SvxShadowedItem( !bOld, EE_CHAR_SHADOW ) ); + rBindings.Invalidate( nSlot ); + } + break; + + case SID_ATTR_CHAR_CONTOUR: + { + bool bOld = pEditView->GetAttribs().Get(EE_CHAR_OUTLINE).GetValue(); + aSet.Put( SvxContourItem( !bOld, EE_CHAR_OUTLINE ) ); + rBindings.Invalidate( nSlot ); + } + break; + + case SID_SET_SUPER_SCRIPT: + { + SvxEscapement eOld = static_cast(pEditView->GetAttribs().Get(EE_CHAR_ESCAPEMENT).GetEnumValue()); + SvxEscapement eNew = (eOld == SvxEscapement::Superscript) ? + SvxEscapement::Off : SvxEscapement::Superscript; + aSet.Put( SvxEscapementItem( eNew, EE_CHAR_ESCAPEMENT ) ); + rBindings.Invalidate( nSlot ); + } + break; + case SID_SET_SUB_SCRIPT: + { + SvxEscapement eOld = static_cast(pEditView->GetAttribs().Get(EE_CHAR_ESCAPEMENT).GetEnumValue()); + SvxEscapement eNew = (eOld == SvxEscapement::Subscript) ? + SvxEscapement::Off : SvxEscapement::Subscript; + aSet.Put( SvxEscapementItem( eNew, EE_CHAR_ESCAPEMENT ) ); + rBindings.Invalidate( nSlot ); + } + break; + case SID_ATTR_CHAR_KERNING: + { + if(pArgs) + { + aSet.Put ( pArgs->Get(pArgs->GetPool()->GetWhich(nSlot))); + rBindings.Invalidate( nSlot ); + } + } + break; + + case SID_GROW_FONT_SIZE: + case SID_SHRINK_FONT_SIZE: + { + SfxObjectShell* pObjSh = SfxObjectShell::Current(); + const SvxFontListItem* pFontListItem = static_cast + (pObjSh ? pObjSh->GetItem(SID_ATTR_CHAR_FONTLIST) : nullptr); + const FontList* pFontList = pFontListItem ? pFontListItem->GetFontList() : nullptr; + pEditView->ChangeFontSize( nSlot == SID_GROW_FONT_SIZE, pFontList ); + rBindings.Invalidate( SID_ATTR_CHAR_FONTHEIGHT ); + } + break; + } + + // apply + + EditEngine* pEngine = pEditView->GetEditEngine(); + bool bOld = pEngine->SetUpdateLayout(false); + + pEditView->SetAttribs( aSet ); + + pEngine->SetUpdateLayout(bOld); + pEditView->Invalidate(); + + ScInputHandler* pHdl = GetMyInputHdl(); + pHdl->SetModified(); + + rReq.Done(); +} + +void ScEditShell::GetAttrState(SfxItemSet &rSet) +{ + if ( !rViewData.HasEditView( rViewData.GetActivePart() ) ) + { + lcl_DisableAll( rSet ); + return; + } + + SfxItemSet aAttribs = pEditView->GetAttribs(); + rSet.Put( aAttribs ); + + // choose font info according to selection script type + + SvtScriptType nScript = pEditView->GetSelectedScriptType(); + if (nScript == SvtScriptType::NONE) nScript = ScGlobal::GetDefaultScriptType(); + + // #i55929# input-language-dependent script type (depends on input language if nothing selected) + SvtScriptType nInputScript = nScript; + if ( !pEditView->GetSelection().HasRange() ) + { + LanguageType nInputLang = rViewData.GetActiveWin()->GetInputLanguage(); + if (nInputLang != LANGUAGE_DONTKNOW && nInputLang != LANGUAGE_SYSTEM) + nInputScript = SvtLanguageOptions::GetScriptTypeOfLanguage( nInputLang ); + } + + // #i55929# according to spec, nInputScript is used for font and font height only + if ( rSet.GetItemState( EE_CHAR_FONTINFO ) != SfxItemState::UNKNOWN ) + ScViewUtil::PutItemScript( rSet, aAttribs, EE_CHAR_FONTINFO, nInputScript ); + if ( rSet.GetItemState( EE_CHAR_FONTHEIGHT ) != SfxItemState::UNKNOWN ) + ScViewUtil::PutItemScript( rSet, aAttribs, EE_CHAR_FONTHEIGHT, nInputScript ); + if ( rSet.GetItemState( EE_CHAR_WEIGHT ) != SfxItemState::UNKNOWN ) + ScViewUtil::PutItemScript( rSet, aAttribs, EE_CHAR_WEIGHT, nScript ); + if ( rSet.GetItemState( EE_CHAR_ITALIC ) != SfxItemState::UNKNOWN ) + ScViewUtil::PutItemScript( rSet, aAttribs, EE_CHAR_ITALIC, nScript ); + + // underline + SfxItemState eState = aAttribs.GetItemState( EE_CHAR_UNDERLINE ); + if ( eState == SfxItemState::DONTCARE ) + { + rSet.InvalidateItem( SID_ULINE_VAL_NONE ); + rSet.InvalidateItem( SID_ULINE_VAL_SINGLE ); + rSet.InvalidateItem( SID_ULINE_VAL_DOUBLE ); + rSet.InvalidateItem( SID_ULINE_VAL_DOTTED ); + } + else + { + FontLineStyle eUnderline = aAttribs.Get(EE_CHAR_UNDERLINE).GetLineStyle(); + rSet.Put(SfxBoolItem(SID_ULINE_VAL_SINGLE, eUnderline == LINESTYLE_SINGLE)); + rSet.Put(SfxBoolItem(SID_ULINE_VAL_DOUBLE, eUnderline == LINESTYLE_DOUBLE)); + rSet.Put(SfxBoolItem(SID_ULINE_VAL_DOTTED, eUnderline == LINESTYLE_DOTTED)); + rSet.Put(SfxBoolItem(SID_ULINE_VAL_NONE, eUnderline == LINESTYLE_NONE)); + } + + //! Testing whether brace highlighting is active !!!! + ScInputHandler* pHdl = GetMyInputHdl(); + if ( pHdl && pHdl->IsFormulaMode() ) + rSet.ClearItem( EE_CHAR_WEIGHT ); // Highlighted brace not here + + SvxEscapement eEsc = static_cast(aAttribs.Get( EE_CHAR_ESCAPEMENT ).GetEnumValue()); + rSet.Put(SfxBoolItem(SID_SET_SUPER_SCRIPT, eEsc == SvxEscapement::Superscript)); + rSet.Put(SfxBoolItem(SID_SET_SUB_SCRIPT, eEsc == SvxEscapement::Subscript)); + rViewData.GetBindings().Invalidate( SID_SET_SUPER_SCRIPT ); + rViewData.GetBindings().Invalidate( SID_SET_SUB_SCRIPT ); + + eState = aAttribs.GetItemState( EE_CHAR_KERNING ); + rViewData.GetBindings().Invalidate( SID_ATTR_CHAR_KERNING ); + if ( eState == SfxItemState::DONTCARE ) + { + rSet.InvalidateItem(EE_CHAR_KERNING); + } +} + +OUString ScEditShell::GetSelectionText( bool bWholeWord ) +{ + OUString aStrSelection; + + if ( rViewData.HasEditView( rViewData.GetActivePart() ) ) + { + if ( bWholeWord ) + { + EditEngine* pEngine = pEditView->GetEditEngine(); + ESelection aSel = pEditView->GetSelection(); + OUString aStrCurrentDelimiters = pEngine->GetWordDelimiters(); + + pEngine->SetWordDelimiters(" .,;\"'"); + aStrSelection = pEngine->GetWord( aSel.nEndPara, aSel.nEndPos ); + pEngine->SetWordDelimiters( aStrCurrentDelimiters ); + } + else + { + aStrSelection = pEditView->GetSelected(); + } + } + + return aStrSelection; +} + +void ScEditShell::ExecuteUndo(const SfxRequest& rReq) +{ + // Undo must be handled here because it's called for both EditViews + + ScInputHandler* pHdl = GetMyInputHdl(); + OSL_ENSURE(pHdl,"no ScInputHandler"); + EditView* pTopView = pHdl->GetTopView(); + EditView* pTableView = pHdl->GetTableView(); + OSL_ENSURE(pTableView,"no EditView"); + + pHdl->DataChanging(); + + const SfxItemSet* pReqArgs = rReq.GetArgs(); + sal_uInt16 nSlot = rReq.GetSlot(); + switch ( nSlot ) + { + case SID_UNDO: + case SID_REDO: + { + bool bIsUndo = ( nSlot == SID_UNDO ); + + sal_uInt16 nCount = 1; + const SfxPoolItem* pItem; + if ( pReqArgs && pReqArgs->GetItemState( nSlot, true, &pItem ) == SfxItemState::SET ) + nCount = static_cast(pItem)->GetValue(); + + for (sal_uInt16 i=0; iUndo(); + if (pTopView) + pTopView->Undo(); + } + else + { + pTableView->Redo(); + if (pTopView) + pTopView->Redo(); + } + } + } + break; + } + rViewData.GetBindings().InvalidateAll(false); + + pHdl->DataChanged(); +} + +void ScEditShell::GetUndoState(SfxItemSet &rSet) +{ + // Undo state is taken from normal ViewFrame state function + + SfxViewFrame& rViewFrm = rViewData.GetViewShell()->GetViewFrame(); + if ( GetUndoManager() ) + { + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + while( nWhich ) + { + rViewFrm.GetSlotState( nWhich, nullptr, &rSet ); + nWhich = aIter.NextWhich(); + } + } + + // disable if no action in input line EditView + + ScInputHandler* pHdl = GetMyInputHdl(); + OSL_ENSURE(pHdl,"no ScInputHandler"); + EditView* pTopView = pHdl->GetTopView(); + if (pTopView) + { + SfxUndoManager& rTopMgr = pTopView->GetEditEngine()->GetUndoManager(); + if ( rTopMgr.GetUndoActionCount() == 0 ) + rSet.DisableItem( SID_UNDO ); + if ( rTopMgr.GetRedoActionCount() == 0 ) + rSet.DisableItem( SID_REDO ); + } +} + +void ScEditShell::ExecuteTrans( const SfxRequest& rReq ) +{ + TransliterationFlags nType = ScViewUtil::GetTransliterationType( rReq.GetSlot() ); + if ( nType == TransliterationFlags::NONE ) + return; + + ScInputHandler* pHdl = GetMyInputHdl(); + assert(pHdl && "no ScInputHandler"); + + EditView* pTopView = pHdl->GetTopView(); + EditView* pTableView = pHdl->GetTableView(); + assert(pTableView && "no EditView"); + + pHdl->DataChanging(); + + pTableView->TransliterateText( nType ); + if (pTopView) + pTopView->TransliterateText( nType ); + + pHdl->DataChanged(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/formatsh.cxx b/sc/source/ui/view/formatsh.cxx new file mode 100644 index 0000000000..93a456e46b --- /dev/null +++ b/sc/source/ui/view/formatsh.cxx @@ -0,0 +1,2108 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ShellClass_ScFormatShell +#define ShellClass_TableFont +#define ShellClass_FormatForSelection +#include + +#include +#include + +#include + +using namespace ::com::sun::star; + +namespace { + +SvxCellHorJustify lclConvertSlotToHAlign( sal_uInt16 nSlot ) +{ + SvxCellHorJustify eHJustify = SvxCellHorJustify::Standard; + switch( nSlot ) + { + case SID_ALIGN_ANY_HDEFAULT: eHJustify = SvxCellHorJustify::Standard; break; + case SID_ALIGN_ANY_LEFT: eHJustify = SvxCellHorJustify::Left; break; + case SID_ALIGN_ANY_HCENTER: eHJustify = SvxCellHorJustify::Center; break; + case SID_ALIGN_ANY_RIGHT: eHJustify = SvxCellHorJustify::Right; break; + case SID_ALIGN_ANY_JUSTIFIED: eHJustify = SvxCellHorJustify::Block; break; + default: OSL_FAIL( "lclConvertSlotToHAlign - invalid slot" ); + } + return eHJustify; +} + +SvxCellVerJustify lclConvertSlotToVAlign( sal_uInt16 nSlot ) +{ + SvxCellVerJustify eVJustify = SvxCellVerJustify::Standard; + switch( nSlot ) + { + case SID_ALIGN_ANY_VDEFAULT: eVJustify = SvxCellVerJustify::Standard; break; + case SID_ALIGN_ANY_TOP: eVJustify = SvxCellVerJustify::Top; break; + case SID_ALIGN_ANY_VCENTER: eVJustify = SvxCellVerJustify::Center; break; + case SID_ALIGN_ANY_BOTTOM: eVJustify = SvxCellVerJustify::Bottom; break; + default: OSL_FAIL( "lclConvertSlotToVAlign - invalid slot" ); + } + return eVJustify; +} + +} // namespace + + +SFX_IMPL_INTERFACE(ScFormatShell, SfxShell) + +void ScFormatShell::InitInterface_Impl() +{ + GetStaticInterface()->RegisterObjectBar(SFX_OBJECTBAR_OBJECT, + SfxVisibilityFlags::Standard | SfxVisibilityFlags::Server, + ToolbarId::Objectbar_Format); +} + +ScFormatShell::ScFormatShell(ScViewData& rData) : + SfxShell(rData.GetViewShell()), + rViewData(rData) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + + SetPool( &pTabViewShell->GetPool() ); + SfxUndoManager* pMgr = rViewData.GetSfxDocShell()->GetUndoManager(); + SetUndoManager( pMgr ); + if (pMgr && !rViewData.GetDocument().IsUndoEnabled()) + { + pMgr->SetMaxUndoActionCount( 0 ); + } + SetName("Format"); +} + +ScFormatShell::~ScFormatShell() +{ +} + +void ScFormatShell::ExecuteStyle( SfxRequest& rReq ) +{ + const SfxItemSet* pArgs = rReq.GetArgs(); + const sal_uInt16 nSlotId = rReq.GetSlot(); + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScTabViewShell* pTabViewShell= GetViewData().GetViewShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + SfxStyleSheetBasePool* pStylePool = rDoc.GetStyleSheetPool(); + + if ( (nSlotId == SID_STYLE_PREVIEW) + || (nSlotId == SID_STYLE_END_PREVIEW) ) + { + if (nSlotId == SID_STYLE_PREVIEW) + { + SfxStyleFamily eFamily = SfxStyleFamily::Para; + const SfxUInt16Item* pFamItem; + if ( pArgs && (pFamItem = pArgs->GetItemIfSet( SID_STYLE_FAMILY )) ) + eFamily = static_cast(pFamItem->GetValue()); + const SfxPoolItem* pNameItem; + OUString aStyleName; + if (pArgs && SfxItemState::SET == pArgs->GetItemState( nSlotId, true, &pNameItem )) + aStyleName = static_cast(pNameItem)->GetValue(); + if ( eFamily == SfxStyleFamily::Para ) // CellStyles + { + ScMarkData aFuncMark( rViewData.GetMarkData() ); + ScViewUtil::UnmarkFiltered( aFuncMark, rDoc ); + aFuncMark.MarkToMulti(); + + if ( !aFuncMark.IsMarked() && !aFuncMark.IsMultiMarked() ) + { + SCCOL nCol = rViewData.GetCurX(); + SCROW nRow = rViewData.GetCurY(); + SCTAB nTab = rViewData.GetTabNo(); + ScRange aRange( nCol, nRow, nTab ); + aFuncMark.SetMarkArea( aRange ); + } + rDoc.SetPreviewSelection( aFuncMark ); + ScStyleSheet* pPreviewStyle = static_cast( pStylePool->Find( aStyleName, eFamily ) ); + rDoc.SetPreviewCellStyle( pPreviewStyle ); + ScPatternAttr aAttr( *rDoc.GetSelectionPattern( aFuncMark ) ); + aAttr.SetStyleSheet( pPreviewStyle ); + + SfxItemSet aItemSet( GetPool() ); + + ScPatternAttr aNewAttrs( GetViewData().GetDocument().GetPool() ); + SfxItemSet& rNewSet = aNewAttrs.GetItemSet(); + rNewSet.Put( aItemSet, false ); + + rDoc.ApplySelectionPattern( aNewAttrs, rDoc.GetPreviewSelection() ); + pTabViewShell->UpdateSelectionArea( aFuncMark, &aAttr ); + } + } + else + { + // No mark at all happens when creating a new document, in which + // case the selection pattern obtained would be empty (created of + // GetPool()) anyway and nothing needs to be applied. + ScMarkData aPreviewMark( rDoc.GetPreviewSelection()); + if (aPreviewMark.IsMarked() || aPreviewMark.IsMultiMarked()) + { + ScPatternAttr aAttr( *rDoc.GetSelectionPattern( aPreviewMark ) ); + if ( ScStyleSheet* pPreviewStyle = rDoc.GetPreviewCellStyle() ) + aAttr.SetStyleSheet( pPreviewStyle ); + rDoc.SetPreviewCellStyle(nullptr); + + SfxItemSet aItemSet( GetPool() ); + + ScPatternAttr aNewAttrs( GetViewData().GetDocument().GetPool() ); + SfxItemSet& rNewSet = aNewAttrs.GetItemSet(); + rNewSet.Put( aItemSet, false ); + rDoc.ApplySelectionPattern( aNewAttrs, aPreviewMark ); + pTabViewShell->UpdateSelectionArea( aPreviewMark, &aAttr ); + } + } + } + else if (nSlotId == SID_CLASSIFICATION_APPLY) + { + const SfxPoolItem* pItem = nullptr; + if (pArgs && pArgs->GetItemState(nSlotId, false, &pItem) == SfxItemState::SET) + { + const OUString& rName = static_cast(pItem)->GetValue(); + SfxClassificationHelper aHelper(pDocSh->getDocProperties()); + auto eType = SfxClassificationPolicyType::IntellectualProperty; + if (const SfxStringItem* pNameItem = pArgs->GetItemIfSet(SID_TYPE_NAME, false)) + { + const OUString& rType = pNameItem->GetValue(); + eType = SfxClassificationHelper::stringToPolicyType(rType); + } + aHelper.SetBACName(rName, eType); + } + else + SAL_WARN("sc.ui", "missing parameter for SID_CLASSIFICATION_APPLY"); + } + else + { + OSL_FAIL( "Unknown slot (ScViewShell::ExecuteStyle)" ); + } +} + +void ScFormatShell::ExecuteNumFormat( SfxRequest& rReq ) +{ + ScModule* pScMod = SC_MOD(); + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + const SfxItemSet* pReqArgs = rReq.GetArgs(); + sal_uInt16 nSlot = rReq.GetSlot(); + SfxBindings& rBindings = pTabViewShell->GetViewFrame().GetBindings(); + + pTabViewShell->HideListBox(); // Autofilter-DropDown-Listbox + + // End input + if ( GetViewData().HasEditView( GetViewData().GetActivePart() ) ) + { + switch ( nSlot ) + { + case SID_NUMBER_TYPE_FORMAT: + case SID_NUMBER_TWODEC: + case SID_NUMBER_SCIENTIFIC: + case SID_NUMBER_DATE: + case SID_NUMBER_CURRENCY: + case SID_NUMBER_PERCENT: + case SID_NUMBER_STANDARD: + case SID_NUMBER_FORMAT: + case SID_NUMBER_INCDEC: + case SID_NUMBER_DECDEC: + case SID_NUMBER_THOUSANDS: + case FID_DEFINE_NAME: + case FID_ADD_NAME: + case FID_USE_NAME: + case FID_INSERT_NAME: + case SID_SPELL_DIALOG: + case SID_HANGUL_HANJA_CONVERSION: + + pScMod->InputEnterHandler(); + pTabViewShell->UpdateInputHandler(); + break; + + default: + break; + } + } + + SvNumFormatType nType = GetCurrentNumberFormatType(); + switch ( nSlot ) + { + case SID_NUMBER_TWODEC: + { + const SfxItemSet& rAttrSet = pTabViewShell->GetSelectionPattern()->GetItemSet(); + sal_uInt32 nNumberFormat = rAttrSet.Get(ATTR_VALUE_FORMAT).GetValue(); + + if ((nType & SvNumFormatType::NUMBER) && nNumberFormat == 4) + pTabViewShell->SetNumberFormat( SvNumFormatType::NUMBER ); + else + pTabViewShell->SetNumberFormat( SvNumFormatType::NUMBER, 4 ); + rBindings.Invalidate( nSlot ); + rReq.Done(); + } + break; + case SID_NUMBER_SCIENTIFIC: + if (nType & SvNumFormatType::SCIENTIFIC) + pTabViewShell->SetNumberFormat( SvNumFormatType::NUMBER ); + else + pTabViewShell->SetNumberFormat( SvNumFormatType::SCIENTIFIC ); + rBindings.Invalidate( nSlot ); + rReq.Done(); + break; + case SID_NUMBER_DATE: + if (nType & SvNumFormatType::DATE) + pTabViewShell->SetNumberFormat( SvNumFormatType::NUMBER ); + else + pTabViewShell->SetNumberFormat( SvNumFormatType::DATE ); + rBindings.Invalidate( nSlot ); + rReq.Done(); + break; + case SID_NUMBER_TIME: + if (nType & SvNumFormatType::TIME) + pTabViewShell->SetNumberFormat( SvNumFormatType::NUMBER ); + else + pTabViewShell->SetNumberFormat( SvNumFormatType::TIME ); + rBindings.Invalidate( nSlot ); + rReq.Done(); + break; + case SID_NUMBER_CURRENCY: + if(pReqArgs) + { + const SfxPoolItem* pItem; + if ( pReqArgs->HasItem( SID_NUMBER_CURRENCY, &pItem ) ) + { + sal_uInt32 nNewFormat = static_cast(pItem)->GetValue(); + ScDocument& rDoc = rViewData.GetDocument(); + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + const SfxItemSet& rOldSet = pTabViewShell->GetSelectionPattern()->GetItemSet(); + + LanguageType eOldLang = rOldSet.Get( ATTR_LANGUAGE_FORMAT ).GetLanguage(); + sal_uInt32 nOldFormat = rOldSet.Get( ATTR_VALUE_FORMAT ).GetValue(); + + if ( nOldFormat != nNewFormat ) + { + const SvNumberformat* pNewEntry = pFormatter->GetEntry( nNewFormat ); + ScPatternAttr aNewAttrs( rDoc.GetPool() ); + SfxItemSet& rSet = aNewAttrs.GetItemSet(); + LanguageType eNewLang = pNewEntry ? pNewEntry->GetLanguage() : LANGUAGE_DONTKNOW; + if ( eNewLang != eOldLang && eNewLang != LANGUAGE_DONTKNOW ) + rSet.Put( SvxLanguageItem( eNewLang, ATTR_LANGUAGE_FORMAT ) ); + rSet.Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nNewFormat ) ); + pTabViewShell->ApplySelectionPattern( aNewAttrs ); + } + else + pTabViewShell->SetNumberFormat( SvNumFormatType::NUMBER ); + } + } + else + { + if ( nType & SvNumFormatType::CURRENCY ) + pTabViewShell->SetNumberFormat( SvNumFormatType::NUMBER ); + else + pTabViewShell->SetNumberFormat( SvNumFormatType::CURRENCY ); + } + rBindings.Invalidate( nSlot ); + rReq.Done(); + break; + case SID_NUMBER_PERCENT: + if (nType & SvNumFormatType::PERCENT) + pTabViewShell->SetNumberFormat( SvNumFormatType::NUMBER ); + else + pTabViewShell->SetNumberFormat( SvNumFormatType::PERCENT ); + rBindings.Invalidate( nSlot ); + rReq.Done(); + break; + case SID_NUMBER_STANDARD: + pTabViewShell->SetNumberFormat( SvNumFormatType::NUMBER ); + rReq.Done(); + break; + case SID_NUMBER_INCDEC: + pTabViewShell->ChangeNumFmtDecimals( true ); + rReq.Done(); + break; + case SID_NUMBER_DECDEC: + pTabViewShell->ChangeNumFmtDecimals( false ); + rReq.Done(); + break; + case SID_NUMBER_THOUSANDS: + { + ScDocument& rDoc = rViewData.GetDocument(); + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + bool bThousand(false); + bool bNegRed(false); + sal_uInt16 nPrecision(0); + sal_uInt16 nLeadZeroes(0); + LanguageType eLanguage = ScGlobal::eLnge; + + sal_uInt32 nCurrentNumberFormat = rDoc.GetNumberFormat( + rViewData.GetCurX(), rViewData.GetCurY(), rViewData.GetTabNo()); + const SvNumberformat* pEntry = pFormatter->GetEntry(nCurrentNumberFormat); + + if (pEntry) + eLanguage = pEntry->GetLanguage(); + + pFormatter->GetFormatSpecialInfo(nCurrentNumberFormat, bThousand, bNegRed, nPrecision, nLeadZeroes); + bThousand = !bThousand; + OUString aCode = pFormatter->GenerateFormat( + nCurrentNumberFormat, + eLanguage, + bThousand, + bNegRed, + nPrecision, + nLeadZeroes); + pTabViewShell->SetNumFmtByStr(aCode); + + rBindings.Invalidate(nSlot); + rReq.Done(); + } + break; + case SID_NUMBER_FORMAT: + // symphony version with format interpretation + if(pReqArgs) + { + const SfxPoolItem* pItem; + ScDocument& rDoc = rViewData.GetDocument(); + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + + sal_uInt32 nCurrentNumberFormat = rDoc.GetNumberFormat( + rViewData.GetCurX(), rViewData.GetCurY(), rViewData.GetTabNo()); + const SvNumberformat* pEntry = pFormatter->GetEntry(nCurrentNumberFormat); + + if(!pEntry) + break; + + LanguageType eLanguage = pEntry->GetLanguage(); + SvNumFormatType eType = pEntry->GetMaskedType(); + + //Just use eType to judge whether the command is fired for NUMBER/PERCENT/CURRENCY/SCIENTIFIC/FRACTION/TIME + //In sidebar, users can fire SID_NUMBER_FORMAT command by operating the related UI controls before they are disable + if(!(eType == SvNumFormatType::ALL + || eType == SvNumFormatType::NUMBER + || eType == SvNumFormatType::PERCENT + || eType == SvNumFormatType::CURRENCY + || eType == SvNumFormatType::SCIENTIFIC + || eType == SvNumFormatType::TIME + || eType == SvNumFormatType::FRACTION)) + pEntry = nullptr; + + if(SfxItemState::SET == pReqArgs->GetItemState(nSlot, true, &pItem) && pEntry) + { + OUString aCode = static_cast(pItem)->GetValue(); + sal_uInt16 aLen = aCode.getLength(); + std::unique_ptr sFormat( new OUString[4] ); + OUStringBuffer sTmpStr; + sal_uInt16 nCount(0); + sal_uInt16 nStrCount(0); + + while(nCount < aLen) + { + sal_Unicode cChar = aCode[nCount]; + + if(cChar == ',') + { + sFormat[nStrCount] = sTmpStr.makeStringAndClear(); + nStrCount++; + } + else + { + sTmpStr.append(cChar); + } + + nCount++; + + if(nStrCount > 3) + break; + } + + const bool bThousand = static_cast(sFormat[0].toInt32()); + const bool bNegRed = static_cast(sFormat[1].toInt32()); + const sal_uInt16 nPrecision = static_cast(sFormat[2].toInt32()); + const sal_uInt16 nLeadZeroes = static_cast(sFormat[3].toInt32()); + + aCode = pFormatter->GenerateFormat( + nCurrentNumberFormat,//modify + eLanguage, + bThousand, + bNegRed, + nPrecision, + nLeadZeroes); + pTabViewShell->SetNumFmtByStr(aCode); + } + } + break; + + case SID_ATTR_NUMBERFORMAT_VALUE: + if ( pReqArgs ) + { + if ( const SfxUInt32Item* pItem = pReqArgs->GetItemIfSet( ATTR_VALUE_FORMAT ) ) + { + // We have to accomplish this using ApplyAttributes() + // because we also need the language information to be + // considered. + const SfxItemSet& rOldSet = + pTabViewShell->GetSelectionPattern()->GetItemSet(); + SfxItemPool* pDocPool = GetViewData().GetDocument().GetPool(); + SfxItemSetFixed aNewSet( *pDocPool ); + aNewSet.Put( *pItem ); + pTabViewShell->ApplyAttributes( aNewSet, rOldSet ); + } + } + break; + + case SID_NUMBER_TYPE_FORMAT: + if ( pReqArgs ) + { + const SfxPoolItem* pItem; + if ( pReqArgs->GetItemState( nSlot, true, &pItem ) == SfxItemState::SET ) + { + sal_uInt16 nFormat = static_cast(pItem)->GetValue(); + switch(nFormat) + { + case 0: + pTabViewShell->SetNumberFormat( SvNumFormatType::NUMBER); //Modify + break; + case 1: + pTabViewShell->SetNumberFormat( SvNumFormatType::NUMBER, 2 ); //Modify + break; + case 2: + pTabViewShell->SetNumberFormat( SvNumFormatType::PERCENT ); + break; + case 3: + pTabViewShell->SetNumberFormat( SvNumFormatType::CURRENCY ); + break; + case 4: + pTabViewShell->SetNumberFormat( SvNumFormatType::DATE ); + break; + case 5: + pTabViewShell->SetNumberFormat( SvNumFormatType::TIME ); + break; + case 6: + pTabViewShell->SetNumberFormat( SvNumFormatType::SCIENTIFIC ); + break; + case 7: + pTabViewShell->SetNumberFormat( SvNumFormatType::FRACTION ); + break; + case 8: + pTabViewShell->SetNumberFormat( SvNumFormatType::LOGICAL ); + break; + case 9: + pTabViewShell->SetNumberFormat( SvNumFormatType::TEXT ); + break; + default: + ; + } + rReq.Done(); + } + } + break; + + default: + OSL_FAIL("ExecuteEdit: invalid slot"); + break; + } +} + +void ScFormatShell::ExecuteAlignment( SfxRequest& rReq ) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + SfxBindings& rBindings = rViewData.GetBindings(); + const SfxItemSet* pSet = rReq.GetArgs(); + sal_uInt16 nSlot = rReq.GetSlot(); + + pTabViewShell->HideListBox(); // Autofilter-DropDown-Listbox + + switch( nSlot ) + { + // pseudo slots for Format menu + case SID_ALIGN_ANY_HDEFAULT: + case SID_ALIGN_ANY_LEFT: + case SID_ALIGN_ANY_HCENTER: + case SID_ALIGN_ANY_RIGHT: + case SID_ALIGN_ANY_JUSTIFIED: + pTabViewShell->ApplyAttr( SvxHorJustifyItem( lclConvertSlotToHAlign( nSlot ), ATTR_HOR_JUSTIFY ) ); + break; + case SID_ALIGN_ANY_VDEFAULT: + case SID_ALIGN_ANY_TOP: + case SID_ALIGN_ANY_VCENTER: + case SID_ALIGN_ANY_BOTTOM: + pTabViewShell->ApplyAttr( SvxVerJustifyItem( lclConvertSlotToVAlign( nSlot ), ATTR_VER_JUSTIFY ) ); + break; + + default: + if( pSet ) + { + const SfxPoolItem* pItem = nullptr; + if( pSet->GetItemState(GetPool().GetWhich(nSlot), true, &pItem ) == SfxItemState::SET ) + { + + switch ( nSlot ) + { + case SID_ATTR_ALIGN_HOR_JUSTIFY: + case SID_ATTR_ALIGN_VER_JUSTIFY: + case SID_ATTR_ALIGN_INDENT: + case SID_ATTR_ALIGN_HYPHENATION: + case SID_ATTR_ALIGN_DEGREES: + case SID_ATTR_ALIGN_LOCKPOS: + case SID_ATTR_ALIGN_MARGIN: + case SID_ATTR_ALIGN_STACKED: + pTabViewShell->ApplyAttr( *pItem ); + break; + + case SID_H_ALIGNCELL: + { + SvxCellHorJustify eJust = static_cast(pItem)->GetValue(); + // #i78476# update alignment of text in cell edit mode + pTabViewShell->UpdateInputHandlerCellAdjust( eJust ); + pTabViewShell->ApplyAttr( SvxHorJustifyItem( eJust, ATTR_HOR_JUSTIFY ) ); + } + break; + case SID_V_ALIGNCELL: + pTabViewShell->ApplyAttr( SvxVerJustifyItem( static_cast(pItem)->GetValue(), ATTR_VER_JUSTIFY ) ); + break; + default: + OSL_FAIL( "ExecuteAlignment: invalid slot" ); + return; + } + } + } + } + rBindings.Invalidate( SID_ATTR_PARA_ADJUST_LEFT ); + rBindings.Invalidate( SID_ATTR_PARA_ADJUST_RIGHT ); + rBindings.Invalidate( SID_ATTR_PARA_ADJUST_BLOCK ); + rBindings.Invalidate( SID_ATTR_PARA_ADJUST_CENTER); + rBindings.Invalidate( SID_ALIGNLEFT ); + rBindings.Invalidate( SID_ALIGNRIGHT ); + rBindings.Invalidate( SID_ALIGNCENTERHOR ); + rBindings.Invalidate( SID_ALIGNBLOCK ); + rBindings.Invalidate( SID_ALIGNTOP ); + rBindings.Invalidate( SID_ALIGNBOTTOM ); + rBindings.Invalidate( SID_ALIGNCENTERVER ); + rBindings.Invalidate( SID_V_ALIGNCELL ); + rBindings.Invalidate( SID_H_ALIGNCELL ); + // pseudo slots for Format menu + rBindings.Invalidate( SID_ALIGN_ANY_HDEFAULT ); + rBindings.Invalidate( SID_ALIGN_ANY_LEFT ); + rBindings.Invalidate( SID_ALIGN_ANY_HCENTER ); + rBindings.Invalidate( SID_ALIGN_ANY_RIGHT ); + rBindings.Invalidate( SID_ALIGN_ANY_JUSTIFIED ); + rBindings.Invalidate( SID_ALIGN_ANY_VDEFAULT ); + rBindings.Invalidate( SID_ALIGN_ANY_TOP ); + rBindings.Invalidate( SID_ALIGN_ANY_VCENTER ); + rBindings.Invalidate( SID_ALIGN_ANY_BOTTOM ); + rBindings.Update(); + + if( ! rReq.IsAPI() ) + rReq.Done(); +} + +void ScFormatShell::ExecuteTextAttr( SfxRequest& rReq ) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + SfxBindings& rBindings = rViewData.GetBindings(); + const ScPatternAttr* pAttrs = pTabViewShell->GetSelectionPattern(); + const SfxItemSet* pSet = rReq.GetArgs(); + sal_uInt16 nSlot = rReq.GetSlot(); + std::optional pNewSet; + + pTabViewShell->HideListBox(); // Autofilter-DropDown-Listbox + + if ( (nSlot == SID_ATTR_CHAR_WEIGHT) + ||(nSlot == SID_ATTR_CHAR_POSTURE) + ||(nSlot == SID_ATTR_CHAR_UNDERLINE) + ||(nSlot == SID_ULINE_VAL_NONE) + ||(nSlot == SID_ULINE_VAL_SINGLE) + ||(nSlot == SID_ULINE_VAL_DOUBLE) + ||(nSlot == SID_ULINE_VAL_DOTTED) ) + { + pNewSet.emplace( GetPool() ); + + switch ( nSlot ) + { + case SID_ATTR_CHAR_WEIGHT: + { + // #i78017 establish the same behaviour as in Writer + SvtScriptType nScript = SvtScriptType::LATIN | SvtScriptType::ASIAN | SvtScriptType::COMPLEX; + + SfxItemPool& rPool = GetPool(); + SvxScriptSetItem aSetItem( nSlot, rPool ); + if ( pSet ) + aSetItem.PutItemForScriptType( nScript, pSet->Get( ATTR_FONT_WEIGHT ) ); + else + { + // toggle manually + + FontWeight eWeight = WEIGHT_BOLD; + SvxScriptSetItem aOldSetItem( nSlot, rPool ); + aOldSetItem.GetItemSet().Put( pAttrs->GetItemSet(), false ); + const SfxPoolItem* pCore = aOldSetItem.GetItemOfScript( nScript ); + if ( pCore && static_cast(pCore)->GetWeight() == WEIGHT_BOLD ) + eWeight = WEIGHT_NORMAL; + + aSetItem.PutItemForScriptType( nScript, SvxWeightItem( eWeight, ATTR_FONT_WEIGHT ) ); + } + pTabViewShell->ApplyUserItemSet( aSetItem.GetItemSet() ); + pNewSet->Put( aSetItem.GetItemSet(), false ); + } + break; + + case SID_ATTR_CHAR_POSTURE: + { + // #i78017 establish the same behaviour as in Writer + SvtScriptType nScript = SvtScriptType::LATIN | SvtScriptType::ASIAN | SvtScriptType::COMPLEX; + + SfxItemPool& rPool = GetPool(); + SvxScriptSetItem aSetItem( nSlot, rPool ); + if ( pSet ) + aSetItem.PutItemForScriptType( nScript, pSet->Get( ATTR_FONT_POSTURE ) ); + else + { + // toggle manually + + FontItalic eItalic = ITALIC_NORMAL; + SvxScriptSetItem aOldSetItem( nSlot, rPool ); + aOldSetItem.GetItemSet().Put( pAttrs->GetItemSet(), false ); + const SfxPoolItem* pCore = aOldSetItem.GetItemOfScript( nScript ); + if ( pCore && static_cast(pCore)->GetPosture() == ITALIC_NORMAL ) + eItalic = ITALIC_NONE; + + aSetItem.PutItemForScriptType( nScript, SvxPostureItem( eItalic, ATTR_FONT_POSTURE ) ); + } + pTabViewShell->ApplyUserItemSet( aSetItem.GetItemSet() ); + pNewSet->Put( aSetItem.GetItemSet(), false ); + } + break; + + case SID_ATTR_CHAR_UNDERLINE: + { + if( pSet ) + { + const SfxPoolItem& rUnderline = pSet->Get( ATTR_FONT_UNDERLINE ); + + if( dynamic_cast( &rUnderline) != nullptr ) + { + pTabViewShell->ApplyAttr( rUnderline ); + pNewSet->Put( rUnderline,rUnderline.Which() ); + } + else if ( auto pTextLineItem = dynamic_cast( &rUnderline) ) + { + // #i106580# also allow SvxTextLineItem (base class of SvxUnderlineItem) + SvxUnderlineItem aNewItem( pTextLineItem->GetLineStyle(), pTextLineItem->Which() ); + aNewItem.SetColor( pTextLineItem->GetColor() ); + pTabViewShell->ApplyAttr( aNewItem ); + pNewSet->Put( aNewItem, aNewItem.Which() ); + } + } + else + { + SvxUnderlineItem aUnderline( pAttrs->GetItem( ATTR_FONT_UNDERLINE ) ); + FontLineStyle eUnderline = (LINESTYLE_NONE != aUnderline.GetLineStyle()) + ? LINESTYLE_NONE + : LINESTYLE_SINGLE; + aUnderline.SetLineStyle( eUnderline ); + pTabViewShell->ApplyAttr( aUnderline ); + pNewSet->Put( aUnderline,aUnderline.Which() ); + } + } + break; + + case SID_ULINE_VAL_NONE: + pTabViewShell->ApplyAttr( SvxUnderlineItem( LINESTYLE_NONE, ATTR_FONT_UNDERLINE ) ); + break; + case SID_ULINE_VAL_SINGLE: // Toggles + case SID_ULINE_VAL_DOUBLE: + case SID_ULINE_VAL_DOTTED: + { + FontLineStyle eOld = pAttrs->GetItem(ATTR_FONT_UNDERLINE).GetLineStyle(); + FontLineStyle eNew = eOld; + switch (nSlot) + { + case SID_ULINE_VAL_SINGLE: + eNew = ( eOld == LINESTYLE_SINGLE ) ? LINESTYLE_NONE : LINESTYLE_SINGLE; + break; + case SID_ULINE_VAL_DOUBLE: + eNew = ( eOld == LINESTYLE_DOUBLE ) ? LINESTYLE_NONE : LINESTYLE_DOUBLE; + break; + case SID_ULINE_VAL_DOTTED: + eNew = ( eOld == LINESTYLE_DOTTED ) ? LINESTYLE_NONE : LINESTYLE_DOTTED; + break; + } + pTabViewShell->ApplyAttr( SvxUnderlineItem( eNew, ATTR_FONT_UNDERLINE ) ); + } + break; + + default: + break; + } + rBindings.Invalidate( nSlot ); + } + else + { + /* + * "Self-made" functionality of radio buttons + * At the toggle the default state is used, this means + * no button was clicked. + */ + + const SfxItemSet& rAttrSet = pTabViewShell->GetSelectionPattern()->GetItemSet(); + const SvxHorJustifyItem* pHorJustify = rAttrSet.GetItemIfSet(ATTR_HOR_JUSTIFY); + const SvxVerJustifyItem* pVerJustify = rAttrSet.GetItemIfSet(ATTR_VER_JUSTIFY ); + SvxCellHorJustify eHorJustify = SvxCellHorJustify::Standard; + SvxCellVerJustify eVerJustify = SvxCellVerJustify::Standard; + + if (pHorJustify) + { + eHorJustify = pHorJustify->GetValue(); + } + if (pVerJustify) + { + eVerJustify = pVerJustify->GetValue(); + } + + switch ( nSlot ) + { + case SID_ALIGNLEFT: + rReq.SetSlot( SID_H_ALIGNCELL ); + rReq.AppendItem( SvxHorJustifyItem( + !pHorJustify || (eHorJustify != SvxCellHorJustify::Left) ? + SvxCellHorJustify::Left : SvxCellHorJustify::Standard, SID_H_ALIGNCELL ) ); + ExecuteSlot( rReq, GetInterface() ); + return; + + case SID_ALIGNRIGHT: + rReq.SetSlot( SID_H_ALIGNCELL ); + rReq.AppendItem( SvxHorJustifyItem( + !pHorJustify || (eHorJustify != SvxCellHorJustify::Right) ? + SvxCellHorJustify::Right : SvxCellHorJustify::Standard, SID_H_ALIGNCELL ) ); + ExecuteSlot( rReq, GetInterface() ); + return; + + case SID_ALIGNCENTERHOR: + rReq.SetSlot( SID_H_ALIGNCELL ); + rReq.AppendItem( SvxHorJustifyItem( + !pHorJustify || (eHorJustify != SvxCellHorJustify::Center) ? + SvxCellHorJustify::Center : SvxCellHorJustify::Standard, SID_H_ALIGNCELL ) ); + ExecuteSlot( rReq, GetInterface() ); + return; + + case SID_ALIGNBLOCK: + rReq.SetSlot( SID_H_ALIGNCELL ); + rReq.AppendItem( SvxHorJustifyItem( + !pHorJustify || (eHorJustify != SvxCellHorJustify::Block) ? + SvxCellHorJustify::Block : SvxCellHorJustify::Standard, SID_H_ALIGNCELL ) ); + ExecuteSlot( rReq, GetInterface() ); + return; + + case SID_ALIGNTOP: + rReq.SetSlot( SID_V_ALIGNCELL ); + rReq.AppendItem( SvxVerJustifyItem( + !pVerJustify || (eVerJustify != SvxCellVerJustify::Top) ? + SvxCellVerJustify::Top : SvxCellVerJustify::Standard, SID_V_ALIGNCELL ) ); + ExecuteSlot( rReq, GetInterface() ); + return; + + case SID_ALIGNBOTTOM: + rReq.SetSlot( SID_V_ALIGNCELL ); + rReq.AppendItem( SvxVerJustifyItem( + !pVerJustify || (eVerJustify != SvxCellVerJustify::Bottom) ? + SvxCellVerJustify::Bottom : SvxCellVerJustify::Standard, SID_V_ALIGNCELL ) ); + ExecuteSlot( rReq, GetInterface() ); + return; + + case SID_ALIGNCENTERVER: + rReq.SetSlot( SID_V_ALIGNCELL ); + rReq.AppendItem( SvxVerJustifyItem( + !pVerJustify || (eVerJustify != SvxCellVerJustify::Center) ? + SvxCellVerJustify::Center : SvxCellVerJustify::Standard, SID_V_ALIGNCELL ) ); + ExecuteSlot( rReq, GetInterface() ); + return; + + default: + break; + } + + } + + rBindings.Update(); + + if( pNewSet ) + { + rReq.Done( *pNewSet ); + pNewSet.reset(); + } + else + { + rReq.Done(); + } + +} + +void ScFormatShell::ExecuteAttr( SfxRequest& rReq ) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + SfxBindings& rBindings = rViewData.GetBindings(); + const SfxItemSet* pNewAttrs = rReq.GetArgs(); + sal_uInt16 nSlot = rReq.GetSlot(); + + pTabViewShell->HideListBox(); // Autofilter-DropDown-Listbox + ScDocument& rDoc = GetViewData().GetDocument(); + if ( !pNewAttrs ) + { + switch ( nSlot ) + { + case SID_GROW_FONT_SIZE: + case SID_SHRINK_FONT_SIZE: + { + SfxItemPool& rPool = GetPool(); + SvxScriptSetItem aSetItem( SID_ATTR_CHAR_FONTHEIGHT, rPool ); + aSetItem.GetItemSet().Put( pTabViewShell->GetSelectionPattern()->GetItemSet(), false ); + + SvtScriptType nScriptTypes = pTabViewShell->GetSelectionScriptType(); + const SvxFontHeightItem* pSize( static_cast( aSetItem.GetItemOfScript( nScriptTypes ) ) ); + + if ( pSize ) + { + SvxFontHeightItem aSize( *pSize ); + sal_uInt32 nSize = aSize.GetHeight(); + + const sal_uInt32 nFontInc = 40; // 2pt + const sal_uInt32 nFontMaxSz = 19998; // 999.9pt + if ( nSlot == SID_GROW_FONT_SIZE ) + nSize = std::min< sal_uInt32 >( nSize + nFontInc, nFontMaxSz ); + else + nSize = std::max< sal_Int32 >( nSize - nFontInc, nFontInc ); + + aSize.SetHeight( nSize ); + aSetItem.PutItemForScriptType( nScriptTypes, aSize ); + pTabViewShell->ApplyUserItemSet( aSetItem.GetItemSet() ); + } + rBindings.Invalidate( SID_ATTR_CHAR_FONTHEIGHT ); + } + break; + + case SID_ATTR_CHAR_ENDPREVIEW_FONT: + { + rDoc.SetPreviewFont(nullptr); + pTabViewShell->UpdateSelectionArea( rDoc.GetPreviewSelection() ); + break; + } + case SID_ATTR_CHAR_COLOR: + case SID_ATTR_CHAR_FONT: + case SID_ATTR_CHAR_FONTHEIGHT: + pTabViewShell->ExecuteCellFormatDlg(rReq, "font"); // when ToolBar is vertical + break; + + case SID_BACKGROUND_COLOR: + { + SvxBrushItem aBrushItem( + pTabViewShell->GetSelectionPattern()->GetItem( ATTR_BACKGROUND ) ); + aBrushItem.SetColor( COL_TRANSPARENT ); + pTabViewShell->ApplyAttr( aBrushItem, false ); + } + break; + + case SID_ATTR_ALIGN_LINEBREAK: // without parameter as toggle + { + const ScPatternAttr* pAttrs = pTabViewShell->GetSelectionPattern(); + bool bOld = pAttrs->GetItem(ATTR_LINEBREAK).GetValue(); + ScLineBreakCell aBreakItem(!bOld); + pTabViewShell->ApplyAttr( aBreakItem ); + + SfxAllItemSet aNewSet( GetPool() ); + aNewSet.Put( aBreakItem,aBreakItem.Which() ); + rReq.Done( aNewSet ); + + rBindings.Invalidate( nSlot ); + } + break; + + case SID_SCATTR_CELLPROTECTION: // without parameter as toggle + { + const ScPatternAttr* pAttrs = pTabViewShell->GetSelectionPattern(); + bool bProtect = pAttrs->GetItem(ATTR_PROTECTION).GetProtection(); + bool bHideFormula = pAttrs->GetItem(ATTR_PROTECTION).GetHideFormula(); + bool bHideCell = pAttrs->GetItem(ATTR_PROTECTION).GetHideCell(); + bool bHidePrint = pAttrs->GetItem(ATTR_PROTECTION).GetHidePrint(); + + ScProtectionAttr aProtectionItem( !bProtect, bHideFormula, bHideCell, bHidePrint ); + pTabViewShell->ApplyAttr( aProtectionItem ); + + SfxAllItemSet aNewSet( GetPool() ); + aNewSet.Put( aProtectionItem, aProtectionItem.Which()); + aNewSet.Put( SfxBoolItem( SID_SCATTR_CELLPROTECTION, !bProtect ) ); + rReq.Done( aNewSet ); + + rBindings.Invalidate( nSlot ); + } + break; + } + } + else + { + switch ( nSlot ) + { + case SID_ATTR_CHAR_PREVIEW_FONT: + { + SfxItemPool& rPool = GetPool(); + sal_uInt16 nWhich = rPool.GetWhich( nSlot ); + const SvxFontItem& rFont = static_cast(pNewAttrs->Get( nWhich )); + SvxScriptSetItem aSetItem( SID_ATTR_CHAR_FONT, rPool ); + SvtScriptType nScript = pTabViewShell->GetSelectionScriptType(); + aSetItem.PutItemForScriptType( nScript, rFont ); + + ScMarkData aFuncMark( rViewData.GetMarkData() ); + ScViewUtil::UnmarkFiltered( aFuncMark, rDoc ); + rDoc.SetPreviewFont( aSetItem.GetItemSet().Clone() ); + aFuncMark.MarkToMulti(); + + if ( !aFuncMark.IsMarked() && !aFuncMark.IsMultiMarked() ) + { + SCCOL nCol = rViewData.GetCurX(); + SCROW nRow = rViewData.GetCurY(); + SCTAB nTab = rViewData.GetTabNo(); + ScRange aRange( nCol, nRow, nTab ); + aFuncMark.SetMarkArea( aRange ); + } + rDoc.SetPreviewSelection( aFuncMark ); + pTabViewShell->UpdateSelectionArea( aFuncMark ); + break; + } + case SID_ATTR_CHAR_OVERLINE: + case SID_ATTR_CHAR_STRIKEOUT: + case SID_ATTR_ALIGN_LINEBREAK: + case SID_ATTR_CHAR_CONTOUR: + case SID_ATTR_CHAR_SHADOWED: + case SID_ATTR_CHAR_RELIEF: + pTabViewShell->ApplyAttr( pNewAttrs->Get( pNewAttrs->GetPool()->GetWhich( nSlot ) ) ); + rBindings.Invalidate( nSlot ); + rBindings.Update( nSlot ); + break; + case SID_ATTR_CHAR_COLOR: + case SID_SCATTR_PROTECTION : + { + pTabViewShell->ApplyAttr( pNewAttrs->Get( pNewAttrs->GetPool()->GetWhich( nSlot) ), false); + + rBindings.Invalidate( nSlot ); + rBindings.Update( nSlot ); + } + + break; + + case SID_ATTR_CHAR_FONT: + case SID_ATTR_CHAR_FONTHEIGHT: + { + // #i78017 establish the same behaviour as in Writer + SvtScriptType nScript = SvtScriptType::LATIN | SvtScriptType::ASIAN | SvtScriptType::COMPLEX; + if (nSlot == SID_ATTR_CHAR_FONT) + nScript = pTabViewShell->GetSelectionScriptType(); + + SfxItemPool& rPool = GetPool(); + SvxScriptSetItem aSetItem( nSlot, rPool ); + sal_uInt16 nWhich = rPool.GetWhich( nSlot ); + aSetItem.PutItemForScriptType( nScript, pNewAttrs->Get( nWhich ) ); + + pTabViewShell->ApplyUserItemSet( aSetItem.GetItemSet() ); + + rBindings.Invalidate( nSlot ); + rBindings.Update( nSlot ); + } + break; + + case SID_FRAME_LINESTYLE: + { + // Update default line + const ::editeng::SvxBorderLine* pLine = + pNewAttrs->Get( SID_FRAME_LINESTYLE ). + GetLine(); + + if ( pLine ) + { + ::editeng::SvxBorderLine* pDefLine = pTabViewShell->GetDefaultFrameLine(); + + if ( pDefLine ) + { + pDefLine->SetBorderLineStyle( + pLine->GetBorderLineStyle()); + pDefLine->SetWidth( pLine->GetWidth( ) ); + pTabViewShell->SetSelectionFrameLines( pDefLine, false ); + } + else + { + pTabViewShell->SetDefaultFrameLine( pLine ); + pTabViewShell->GetDefaultFrameLine()->SetColor( COL_BLACK ); + pTabViewShell->SetSelectionFrameLines( pLine, false ); + } + } + else + { + Color aColorBlack( COL_BLACK ); + ::editeng::SvxBorderLine aDefLine( &aColorBlack, 20, + SvxBorderLineStyle::SOLID ); + pTabViewShell->SetDefaultFrameLine( &aDefLine ); + pTabViewShell->SetSelectionFrameLines( nullptr, false ); + } + } + break; + + case SID_FRAME_LINECOLOR: + { + ::editeng::SvxBorderLine* pDefLine = pTabViewShell->GetDefaultFrameLine(); + + Color aColor = pNewAttrs->Get( SID_FRAME_LINECOLOR ).GetValue(); + + // Update default line + if ( pDefLine ) + { + pDefLine->SetColor( aColor ); + pTabViewShell->SetSelectionFrameLines( pDefLine, true ); + } + else + { + ::editeng::SvxBorderLine aDefLine( &aColor, 20, SvxBorderLineStyle::SOLID ); + pTabViewShell->SetDefaultFrameLine( &aDefLine ); + pTabViewShell->SetSelectionFrameLines( &aDefLine, false ); + } + } + break; + + case SID_ATTR_BORDER_OUTER: + case SID_ATTR_BORDER: + { + ::editeng::SvxBorderLine* pDefLine = pTabViewShell->GetDefaultFrameLine(); + const ScPatternAttr* pOldAttrs = pTabViewShell->GetSelectionPattern(); + SfxItemSetFixed aOldSet( *rDoc.GetPool() ); + SfxItemSetFixed aNewSet( *rDoc.GetPool() ); + const SfxPoolItem& rBorderAttr = + pOldAttrs->GetItemSet(). + Get( ATTR_BORDER ); + + // Evaluate border items from controller: + + if ( const SvxBoxItem* pBoxItem = pNewAttrs->GetItemIfSet( ATTR_BORDER ) ) + { + // The SvxFrameToolBoxControl toolbox controller uses a default + // SvxBorderLine (all widths 0) to mark the lines that should be set. + // Macro recording uses a SvxBoxItem with the real values (OutWidth > 0) + // or NULL pointers for no lines. + // -> Substitute existing lines with pDefLine only if widths are 0. + SvxBoxItem aBoxItem ( *pBoxItem ); + if ( aBoxItem.GetTop() && aBoxItem.GetTop()->GetOutWidth() == 0 ) + aBoxItem.SetLine( pDefLine, SvxBoxItemLine::TOP ); + if ( aBoxItem.GetBottom() && aBoxItem.GetBottom()->GetOutWidth() == 0 ) + aBoxItem.SetLine( pDefLine, SvxBoxItemLine::BOTTOM ); + if ( aBoxItem.GetLeft() && aBoxItem.GetLeft()->GetOutWidth() == 0 ) + aBoxItem.SetLine( pDefLine, SvxBoxItemLine::LEFT ); + if ( aBoxItem.GetRight() && aBoxItem.GetRight()->GetOutWidth() == 0 ) + aBoxItem.SetLine( pDefLine, SvxBoxItemLine::RIGHT ); + aNewSet.Put( aBoxItem ); + rReq.AppendItem( aBoxItem ); + } + + if ( const SvxBoxInfoItem* pBoxInfoItem = pNewAttrs->GetItemIfSet( ATTR_BORDER_INNER ) ) + { + SvxBoxInfoItem aBoxInfoItem( *pBoxInfoItem ); + if ( aBoxInfoItem.GetHori() && aBoxInfoItem.GetHori()->GetOutWidth() == 0 ) + aBoxInfoItem.SetLine( pDefLine, SvxBoxInfoItemLine::HORI ); + if ( aBoxInfoItem.GetVert() && aBoxInfoItem.GetVert()->GetOutWidth() == 0 ) + aBoxInfoItem.SetLine( pDefLine, SvxBoxInfoItemLine::VERT ); + aNewSet.Put( aBoxInfoItem ); + rReq.AppendItem( aBoxInfoItem ); + } + else + { + SvxBoxInfoItem aBoxInfoItem( ATTR_BORDER_INNER ); + aBoxInfoItem.SetLine( nullptr, SvxBoxInfoItemLine::HORI ); + aBoxInfoItem.SetLine( nullptr, SvxBoxInfoItemLine::VERT ); + aNewSet.Put( aBoxInfoItem ); + } + + aOldSet.Put( rBorderAttr ); + pTabViewShell->ApplyAttributes( aNewSet, aOldSet ); + } + break; + + case SID_ATTR_BORDER_DIAG_TLBR: + case SID_ATTR_BORDER_DIAG_BLTR: + { + const ScPatternAttr* pOldAttrs = pTabViewShell->GetSelectionPattern(); + SfxItemSet aOldSet(pOldAttrs->GetItemSet()); + SfxItemSet aNewSet(pOldAttrs->GetItemSet()); + + if(SID_ATTR_BORDER_DIAG_TLBR == nSlot) + { + if(SfxItemState::SET == pNewAttrs->GetItemState(ATTR_BORDER_TLBR)) + { + SvxLineItem aItem(ATTR_BORDER_TLBR); + aItem.SetLine(pNewAttrs->Get(ATTR_BORDER_TLBR).GetLine()); + aNewSet.Put(aItem); + rReq.AppendItem(aItem); + pTabViewShell->ApplyAttributes(aNewSet, aOldSet); + } + } + else // if( nSlot == SID_ATTR_BORDER_DIAG_BLTR ) + { + if(SfxItemState::SET == pNewAttrs->GetItemState(ATTR_BORDER_BLTR )) + { + SvxLineItem aItem(ATTR_BORDER_BLTR); + aItem.SetLine(pNewAttrs->Get(ATTR_BORDER_BLTR).GetLine()); + aNewSet.Put(aItem); + rReq.AppendItem(aItem); + pTabViewShell->ApplyAttributes(aNewSet, aOldSet); + } + } + + rBindings.Invalidate(nSlot); + } + break; + + // ATTR_BACKGROUND (=SID_ATTR_BRUSH) has to be set to two IDs: + case SID_BACKGROUND_COLOR: + { + const SvxColorItem& rNewColorItem = pNewAttrs->Get( SID_BACKGROUND_COLOR ); + Color aColor = rNewColorItem.GetValue(); + + SvxBrushItem aBrushItem( + pTabViewShell->GetSelectionPattern()->GetItem( ATTR_BACKGROUND ) ); + aBrushItem.SetColor(aColor); + aBrushItem.setComplexColor(rNewColorItem.getComplexColor()); + + pTabViewShell->ApplyAttr( aBrushItem, false ); + } + break; + + case SID_ATTR_BRUSH: + { + SvxBrushItem aBrushItem( pTabViewShell->GetSelectionPattern()-> + GetItem( ATTR_BACKGROUND ) ); + const SvxBrushItem& rNewBrushItem = static_cast( + pNewAttrs->Get( GetPool().GetWhich(nSlot) ) ); + aBrushItem.SetColor(rNewBrushItem.GetColor()); + aBrushItem.setComplexColor(rNewBrushItem.getComplexColor()); + pTabViewShell->ApplyAttr( aBrushItem ); + } + break; + + case SID_ATTR_BORDER_SHADOW: + { + const SvxShadowItem& rNewShadowItem = + pNewAttrs->Get( ATTR_SHADOW ); + pTabViewShell->ApplyAttr( rNewShadowItem ); + } + break; + + default: + break; + } + + if( ! rReq.IsAPI() && ! rReq.IsDone() ) + rReq.Done(); + } +} + +void ScFormatShell::GetAttrState( SfxItemSet& rSet ) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + const SfxItemSet& rAttrSet = pTabViewShell->GetSelectionPattern()->GetItemSet(); + const SvxBrushItem& rBrushItem = rAttrSet.Get( ATTR_BACKGROUND ); + SfxWhichIter aIter( rSet ); + sal_uInt16 nWhich = aIter.FirstWhich(); + + rSet.Put( rAttrSet, false ); + + // choose font info according to selection script type + SvtScriptType nScript = SvtScriptType::NONE; // GetSelectionScriptType never returns 0 + if ( rSet.GetItemState( ATTR_FONT ) != SfxItemState::UNKNOWN ) + { + nScript = pTabViewShell->GetSelectionScriptType(); + ScViewUtil::PutItemScript( rSet, rAttrSet, ATTR_FONT, nScript ); + } + if ( rSet.GetItemState( ATTR_FONT_HEIGHT ) != SfxItemState::UNKNOWN ) + { + if (nScript == SvtScriptType::NONE) nScript = pTabViewShell->GetSelectionScriptType(); + ScViewUtil::PutItemScript( rSet, rAttrSet, ATTR_FONT_HEIGHT, nScript ); + } + + while ( nWhich ) + { + switch(nWhich) + { + case SID_BACKGROUND_COLOR: + { + rSet.Put( SvxColorItem( rBrushItem.GetColor(), SID_BACKGROUND_COLOR ) ); + if(SfxItemState::DONTCARE == rAttrSet.GetItemState(ATTR_BACKGROUND)) + { + rSet.InvalidateItem(SID_BACKGROUND_COLOR); + } + } + break; + case SID_FRAME_LINESTYLE: + case SID_FRAME_LINECOLOR: + { + // handled together because both need the cell border information for decisions + Color aCol; + editeng::SvxBorderLine aLine(nullptr,0,SvxBorderLineStyle::SOLID); + bool bCol = false; + bool bColDisable = false, bStyleDisable = false; + std::shared_ptr aBoxItem(std::make_shared(ATTR_BORDER)); + std::shared_ptr aInfoItem(std::make_shared(ATTR_BORDER_INNER)); + + pTabViewShell->GetSelectionFrame(aBoxItem, aInfoItem); + + if( aBoxItem->GetTop() ) + { + bCol = true; + aCol = aBoxItem->GetTop()->GetColor() ; + aLine.SetColor(aCol); + aLine.SetWidth( aBoxItem->GetTop()->GetWidth()); + aLine.SetBorderLineStyle( aBoxItem->GetTop()->GetBorderLineStyle()); + } + + if( aBoxItem->GetBottom() ) + { + if(!bCol) + { + bCol = true; + aCol = aBoxItem->GetBottom()->GetColor() ; + aLine.SetColor(aCol); + aLine.SetWidth( aBoxItem->GetBottom()->GetWidth()); + aLine.SetBorderLineStyle( aBoxItem->GetBottom()->GetBorderLineStyle()); + } + else + { + if(aCol != aBoxItem->GetBottom()->GetColor() ) + bColDisable = true; + if( aLine != *aBoxItem->GetBottom() ) + bStyleDisable = true; + } + } + + if( aBoxItem->GetLeft() ) + { + if(!bCol) + { + bCol = true; + aCol = aBoxItem->GetLeft()->GetColor() ; + aLine.SetColor(aCol); + aLine.SetWidth( aBoxItem->GetLeft()->GetWidth()); + aLine.SetBorderLineStyle( aBoxItem->GetLeft()->GetBorderLineStyle()); + } + else + { + if(aCol != aBoxItem->GetLeft()->GetColor() ) + bColDisable = true; + if( aLine != *aBoxItem->GetLeft() ) + bStyleDisable = true; + } + } + + if( aBoxItem->GetRight() ) + { + if(!bCol) + { + bCol = true; + aCol = aBoxItem->GetRight()->GetColor() ; + aLine.SetColor(aCol); + aLine.SetWidth( aBoxItem->GetRight()->GetWidth()); + aLine.SetBorderLineStyle( aBoxItem->GetRight()->GetBorderLineStyle()); + } + else + { + if(aCol != aBoxItem->GetRight()->GetColor() ) + bColDisable = true; + if( aLine != *aBoxItem->GetRight() ) + bStyleDisable = true; + } + } + + if( aInfoItem->GetVert()) + { + if(!bCol) + { + bCol = true; + aCol = aInfoItem->GetVert()->GetColor() ; + aLine.SetColor(aCol); + aLine.SetWidth( aInfoItem->GetVert()->GetWidth()); + aLine.SetBorderLineStyle( aInfoItem->GetVert()->GetBorderLineStyle()); + } + else + { + if(aCol != aInfoItem->GetVert()->GetColor() ) + bColDisable = true; + if( aLine != *aInfoItem->GetVert() ) + bStyleDisable = true; + } + } + + if( aInfoItem->GetHori()) + { + if(!bCol) + { + bCol = true; + aCol = aInfoItem->GetHori()->GetColor() ; + aLine.SetColor(aCol); + aLine.SetWidth( aInfoItem->GetHori()->GetWidth()); + aLine.SetBorderLineStyle( aInfoItem->GetHori()->GetBorderLineStyle()); + } + else + { + if(aCol != aInfoItem->GetHori()->GetColor() ) + bColDisable = true; + if( aLine != *aInfoItem->GetHori() ) + bStyleDisable = true; + } + } + + if( !aInfoItem->IsValid( SvxBoxInfoItemValidFlags::VERT ) + || !aInfoItem->IsValid( SvxBoxInfoItemValidFlags::HORI ) + || !aInfoItem->IsValid( SvxBoxInfoItemValidFlags::LEFT ) + || !aInfoItem->IsValid( SvxBoxInfoItemValidFlags::RIGHT ) + || !aInfoItem->IsValid( SvxBoxInfoItemValidFlags::TOP ) + || !aInfoItem->IsValid( SvxBoxInfoItemValidFlags::BOTTOM ) ) + { + bColDisable = true; + bStyleDisable = true; + } + + if(SID_FRAME_LINECOLOR == nWhich) + { + if(bColDisable) // if different lines have different colors + { + aCol = COL_TRANSPARENT; + rSet.Put( SvxColorItem(aCol, SID_FRAME_LINECOLOR ) ); + rSet.InvalidateItem(SID_FRAME_LINECOLOR); + } + else if (!bCol) // if no line available + { + aCol = COL_AUTO; + rSet.Put( SvxColorItem(aCol, SID_FRAME_LINECOLOR ) ); + } + else + rSet.Put( SvxColorItem(aCol, SID_FRAME_LINECOLOR ) ); + } + else // if( nWhich == SID_FRAME_LINESTYLE) + { + if(bStyleDisable) // if have several lines but don't have same style + { + aLine.SetWidth( 1 ); + SvxLineItem aItem(SID_FRAME_LINESTYLE); + aItem.SetLine(&aLine); + rSet.Put( aItem ); + rSet.InvalidateItem(SID_FRAME_LINESTYLE); + } + else // all the lines have same style or no line available, use initial value (0,0,0,0) + { + SvxLineItem aItem(SID_FRAME_LINESTYLE); + aItem.SetLine(&aLine); + rSet.Put( aItem ); + } + } + } + break; + case SID_ATTR_BRUSH: + { + rSet.Put( rBrushItem.CloneSetWhich(GetPool().GetWhich(nWhich)) ); + } + break; + case SID_SCATTR_CELLPROTECTION: + { + bool bProtect = rAttrSet.Get( ATTR_PROTECTION ).GetProtection(); + rSet.Put( SfxBoolItem(SID_SCATTR_CELLPROTECTION, bProtect) ); + } + break; + } + nWhich = aIter.NextWhich(); + } + + // stuff for sidebar panels + Invalidate(SID_ATTR_ALIGN_DEGREES); + Invalidate(SID_ATTR_ALIGN_LOCKPOS); + Invalidate(SID_ATTR_ALIGN_STACKED); +} + +void ScFormatShell::GetTextAttrState( SfxItemSet& rSet ) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + const SfxItemSet& rAttrSet = pTabViewShell->GetSelectionPattern()->GetItemSet(); + rSet.Put( rAttrSet, false ); // Include ItemStates in copy + + // choose font info according to selection script type + SvtScriptType nScript = SvtScriptType::NONE; // GetSelectionScriptType never returns 0 + if ( rSet.GetItemState( ATTR_FONT_WEIGHT ) != SfxItemState::UNKNOWN ) + { + nScript = pTabViewShell->GetSelectionScriptType(); + ScViewUtil::PutItemScript( rSet, rAttrSet, ATTR_FONT_WEIGHT, nScript ); + } + if ( rSet.GetItemState( ATTR_FONT_POSTURE ) != SfxItemState::UNKNOWN ) + { + if (nScript == SvtScriptType::NONE) nScript = pTabViewShell->GetSelectionScriptType(); + ScViewUtil::PutItemScript( rSet, rAttrSet, ATTR_FONT_POSTURE, nScript ); + } + + SfxItemState eState; + + // own control on radio button functionality: + + // underline + + eState = rAttrSet.GetItemState( ATTR_FONT_UNDERLINE ); + if ( eState == SfxItemState::DONTCARE ) + { + rSet.InvalidateItem( SID_ULINE_VAL_NONE ); + rSet.InvalidateItem( SID_ULINE_VAL_SINGLE ); + rSet.InvalidateItem( SID_ULINE_VAL_DOUBLE ); + rSet.InvalidateItem( SID_ULINE_VAL_DOTTED ); + } + else + { + FontLineStyle eUnderline = + rAttrSet.Get(ATTR_FONT_UNDERLINE).GetLineStyle(); + rSet.Put(SfxBoolItem(SID_ULINE_VAL_SINGLE, eUnderline == LINESTYLE_SINGLE)); + rSet.Put(SfxBoolItem(SID_ULINE_VAL_DOUBLE, eUnderline == LINESTYLE_DOUBLE)); + rSet.Put(SfxBoolItem(SID_ULINE_VAL_DOTTED, eUnderline == LINESTYLE_DOTTED)); + rSet.Put(SfxBoolItem(SID_ULINE_VAL_NONE, eUnderline == LINESTYLE_NONE)); + } + + // horizontal alignment + + const SvxHorJustifyItem* pHorJustify = nullptr; + const SvxVerJustifyItem* pVerJustify = nullptr; + SvxCellVerJustify eVerJustify = SvxCellVerJustify::Standard; + sal_uInt16 nWhich = 0; + bool bJustifyStd = false; + SfxBoolItem aBoolItem ( 0, true ); + + eState = rAttrSet.GetItemState( ATTR_HOR_JUSTIFY, true, + reinterpret_cast(&pHorJustify) ); + switch ( eState ) + { + case SfxItemState::SET: + { + switch ( pHorJustify->GetValue() ) + { + case SvxCellHorJustify::Standard: + break; + + case SvxCellHorJustify::Left: + nWhich = SID_ALIGNLEFT; + break; + + case SvxCellHorJustify::Right: + nWhich = SID_ALIGNRIGHT; + break; + + case SvxCellHorJustify::Center: + nWhich = SID_ALIGNCENTERHOR; + break; + + case SvxCellHorJustify::Block: + nWhich = SID_ALIGNBLOCK; + break; + + case SvxCellHorJustify::Repeat: + default: + bJustifyStd = true; + break; + } + } + break; + + case SfxItemState::DONTCARE: + rSet.InvalidateItem( SID_ALIGNLEFT ); + rSet.InvalidateItem( SID_ALIGNRIGHT ); + rSet.InvalidateItem( SID_ALIGNCENTERHOR ); + rSet.InvalidateItem( SID_ALIGNBLOCK ); + break; + + default: + bJustifyStd = true; + break; + } + + if ( nWhich ) + { + aBoolItem.SetWhich( nWhich ); + rSet.Put( aBoolItem ); + } + else if ( bJustifyStd ) + { + aBoolItem.SetValue( false ); + aBoolItem.SetWhich( SID_ALIGNLEFT ); rSet.Put( aBoolItem ); + aBoolItem.SetWhich( SID_ALIGNRIGHT ); rSet.Put( aBoolItem ); + aBoolItem.SetWhich( SID_ALIGNCENTERHOR ); rSet.Put( aBoolItem ); + aBoolItem.SetWhich( SID_ALIGNBLOCK ); rSet.Put( aBoolItem ); + bJustifyStd = false; + } + + // vertical alignment + + nWhich = 0; + aBoolItem.SetValue( true ); + + eState = rAttrSet.GetItemState( ATTR_VER_JUSTIFY, true, + reinterpret_cast(&pVerJustify) ); + + switch ( eState ) + { + case SfxItemState::SET: + { + eVerJustify = pVerJustify->GetValue(); + + switch ( eVerJustify ) + { + case SvxCellVerJustify::Top: + nWhich = SID_ALIGNTOP; + break; + + case SvxCellVerJustify::Bottom: + nWhich = SID_ALIGNBOTTOM; + break; + + case SvxCellVerJustify::Center: + nWhich = SID_ALIGNCENTERVER; + break; + + case SvxCellVerJustify::Standard: + default: + bJustifyStd = true; + break; + } + } + break; + + case SfxItemState::DONTCARE: + rSet.InvalidateItem( SID_ALIGNTOP ); + rSet.InvalidateItem( SID_ALIGNBOTTOM ); + rSet.InvalidateItem( SID_ALIGNCENTERVER ); + break; + + default: + bJustifyStd = true; + break; + } + + if ( nWhich ) + { + aBoolItem.SetWhich( nWhich ); + rSet.Put( aBoolItem ); + } + else if ( bJustifyStd ) + { + aBoolItem.SetValue( false ); + aBoolItem.SetWhich( SID_ALIGNTOP ); rSet.Put( aBoolItem ); + aBoolItem.SetWhich( SID_ALIGNBOTTOM ); rSet.Put( aBoolItem ); + aBoolItem.SetWhich( SID_ALIGNCENTERVER ); rSet.Put( aBoolItem ); + } +} + +void ScFormatShell::GetBorderState( SfxItemSet& rSet ) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + std::shared_ptr aBoxItem(std::make_shared(ATTR_BORDER)); + std::shared_ptr aInfoItem(std::make_shared(ATTR_BORDER_INNER)); + + pTabViewShell->GetSelectionFrame( aBoxItem, aInfoItem ); + + if ( rSet.GetItemState( ATTR_BORDER ) != SfxItemState::UNKNOWN ) + rSet.Put( *aBoxItem ); + if ( rSet.GetItemState( ATTR_BORDER_INNER ) != SfxItemState::UNKNOWN ) + rSet.Put( *aInfoItem ); +} + +void ScFormatShell::GetAlignState( SfxItemSet& rSet ) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + const SfxItemSet& rAttrSet = pTabViewShell->GetSelectionPattern()->GetItemSet(); + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + + SvxCellHorJustify eHAlign = SvxCellHorJustify::Standard; + bool bHasHAlign = rAttrSet.GetItemState( ATTR_HOR_JUSTIFY ) != SfxItemState::DONTCARE; + if( bHasHAlign ) + eHAlign = rAttrSet.Get( ATTR_HOR_JUSTIFY ).GetValue(); + + SvxCellVerJustify eVAlign = SvxCellVerJustify::Standard; + bool bHasVAlign = rAttrSet.GetItemState( ATTR_VER_JUSTIFY ) != SfxItemState::DONTCARE; + if( bHasVAlign ) + eVAlign = rAttrSet.Get( ATTR_VER_JUSTIFY ).GetValue(); + + while ( nWhich ) + { + switch ( nWhich ) + { + case SID_H_ALIGNCELL: + if ( bHasHAlign ) + rSet.Put( SvxHorJustifyItem( eHAlign, nWhich )); + break; + case SID_V_ALIGNCELL: + if ( bHasVAlign ) + rSet.Put( SvxVerJustifyItem( eVAlign, nWhich )); + break; + + // pseudo slots for Format menu + case SID_ALIGN_ANY_HDEFAULT: + case SID_ALIGN_ANY_LEFT: + case SID_ALIGN_ANY_HCENTER: + case SID_ALIGN_ANY_RIGHT: + case SID_ALIGN_ANY_JUSTIFIED: + rSet.Put( SfxBoolItem( nWhich, bHasHAlign && (eHAlign == lclConvertSlotToHAlign( nWhich )) ) ); + break; + case SID_ALIGN_ANY_VDEFAULT: + case SID_ALIGN_ANY_TOP: + case SID_ALIGN_ANY_VCENTER: + case SID_ALIGN_ANY_BOTTOM: + rSet.Put( SfxBoolItem( nWhich, bHasVAlign && (eVAlign == lclConvertSlotToVAlign( nWhich )) ) ); + break; + } + nWhich = aIter.NextWhich(); + } +} + +void ScFormatShell::GetNumFormatState( SfxItemSet& rSet ) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + ScDocument& rDoc = rViewData.GetDocument(); + const SfxItemSet& rAttrSet = pTabViewShell->GetSelectionPattern()->GetItemSet(); + const SfxItemState eItemState = rAttrSet.GetItemState( ATTR_VALUE_FORMAT ); + sal_uInt32 nNumberFormat = rAttrSet.Get(ATTR_VALUE_FORMAT).GetValue(); + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + // If item state is default or set it + // indicates one number format so we + // don't have to iterate over all + // selected cells' attribute ranges to + // determine selected types. + // Does *NOT* include the + // SvNumFormatType::DEFINED bit. + const SvNumFormatType nType = (eItemState >= SfxItemState::DEFAULT ? pFormatter->GetType( nNumberFormat) : + GetCurrentNumberFormatType()); + NfIndexTableOffset nOffset = pFormatter->GetIndexTableOffset(nNumberFormat); + + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + + while ( nWhich ) + { + switch ( nWhich ) + { + case SID_NUMBER_THOUSANDS: + { + bool bEnable = (SfxItemState::DONTCARE != eItemState); + if (bEnable) + { + bEnable = ((nType != SvNumFormatType::ALL) && (nType & + (SvNumFormatType::NUMBER | + SvNumFormatType::PERCENT | + SvNumFormatType::CURRENCY | + SvNumFormatType::FRACTION))); + if (bEnable) + { + bool bThousand( false ); + bool bNegRed( false ); + sal_uInt16 nPrecision( 0 ); + sal_uInt16 nLeadZeroes( 0 ); + pFormatter->GetFormatSpecialInfo( nNumberFormat, bThousand, bNegRed, nPrecision, nLeadZeroes); + rSet.Put( SfxBoolItem( nWhich, bThousand)); + } + } + if (!bEnable) + { + rSet.DisableItem( nWhich ); + } + } + break; + case SID_NUMBER_FORMAT: + // symphony version with format interpretation + { + if(SfxItemState::DONTCARE != eItemState) + { + bool bThousand(false); + bool bNegRed(false); + sal_uInt16 nPrecision(0); + sal_uInt16 nLeadZeroes(0); + + pFormatter->GetFormatSpecialInfo(nNumberFormat,bThousand, bNegRed, nPrecision, nLeadZeroes); + + const SvNumberformat* pFormatEntry = pFormatter->GetEntry( nNumberFormat ); + if (pFormatEntry && (pFormatEntry->GetType() & SvNumFormatType::SCIENTIFIC)) + { + // if scientific, bThousand is used for engineering notation + const sal_uInt16 nIntegerDigits = pFormatEntry->GetFormatIntegerDigits(); + bThousand = nIntegerDigits > 0 && ((nIntegerDigits % 3) == 0); + } + OUString aFormat; + static constexpr OUString sBreak = u","_ustr; + const OUString sThousand = OUString::number(static_cast(bThousand)); + const OUString sNegRed = OUString::number(static_cast(bNegRed)); + const OUString sPrecision = OUString::number(nPrecision); + const OUString sLeadZeroes = OUString::number(nLeadZeroes); + const OUString sNatNum12 = OUString::number( static_cast< sal_Int32 >( pFormatter->IsNatNum12( nNumberFormat ) ) ); + + aFormat += sThousand + + sBreak + + sNegRed + + sBreak + + sPrecision + + sBreak + + sLeadZeroes + + sBreak + + sNatNum12 + + sBreak; + + rSet.Put(SfxStringItem(nWhich, aFormat)); + + if (comphelper::LibreOfficeKit::isActive()) + { + OUString sPayload = ".uno:NumberFormat=" + aFormat; + GetViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_STATE_CHANGED, + OUStringToOString(sPayload, RTL_TEXTENCODING_ASCII_US)); + } + } + else + { + rSet.InvalidateItem( nWhich ); + } + } + break; + + case SID_NUMBER_TYPE_FORMAT: + { + sal_Int16 nFormatCategory = -1; + if ( eItemState >= SfxItemState::DEFAULT ) //Modify for more robust + { + switch(nType) + { + case SvNumFormatType::NUMBER: + // Determine if General format. + if ((nNumberFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0) + nFormatCategory = 0; + else + nFormatCategory = 1; + break; + case SvNumFormatType::PERCENT: + nFormatCategory = 2; + break; + case SvNumFormatType::CURRENCY: + nFormatCategory = 3; + break; + case SvNumFormatType::DATE: + //Add + case SvNumFormatType::DATETIME: + nFormatCategory = 4; + break; + case SvNumFormatType::TIME: + nFormatCategory = 5; + break; + case SvNumFormatType::SCIENTIFIC: + nFormatCategory = 6; + break; + case SvNumFormatType::FRACTION: + nFormatCategory = 7; + break; + case SvNumFormatType::LOGICAL: + nFormatCategory = 8; + break; + case SvNumFormatType::TEXT: + nFormatCategory = 9; + break; + default: + nFormatCategory = -1; //for more robust + } + if( nFormatCategory == -1 ) + rSet.InvalidateItem( nWhich ); + else + rSet.Put( SfxUInt16Item( nWhich, nFormatCategory ) ); + } + else + { + rSet.InvalidateItem( nWhich ); + } + + } + break; + case SID_NUMBER_CURRENCY: + rSet.Put( SfxBoolItem(nWhich, bool(nType & SvNumFormatType::CURRENCY)) ); + break; + case SID_NUMBER_SCIENTIFIC: + rSet.Put( SfxBoolItem(nWhich, bool(nType & SvNumFormatType::SCIENTIFIC)) ); + break; + case SID_NUMBER_DATE: + rSet.Put( SfxBoolItem(nWhich, bool(nType & SvNumFormatType::DATE)) ); + break; + case SID_NUMBER_PERCENT: + rSet.Put( SfxBoolItem(nWhich, bool(nType & SvNumFormatType::PERCENT)) ); + break; + case SID_NUMBER_TIME: + rSet.Put( SfxBoolItem(nWhich, bool(nType & SvNumFormatType::TIME)) ); + break; + case SID_NUMBER_TWODEC: + rSet.Put( SfxBoolItem(nWhich, (nType & SvNumFormatType::NUMBER) && nOffset == NF_NUMBER_1000DEC2 ) ); + break; + case SID_NUMBER_STANDARD: + rSet.Put( SfxBoolItem(nWhich, (nType & SvNumFormatType::NUMBER) && (nNumberFormat % SV_COUNTRY_LANGUAGE_OFFSET) == 0) ); + break; + } + nWhich = aIter.NextWhich(); + } +} + +void ScFormatShell::ExecuteTextDirection( const SfxRequest& rReq ) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + pTabViewShell->HideListBox(); // Autofilter-DropDown-Listbox + bool bEditMode = false; + if ( GetViewData().HasEditView( GetViewData().GetActivePart() ) ) + { + bEditMode=true; + SC_MOD()->InputEnterHandler(); + pTabViewShell->UpdateInputHandler(); + } + sal_uInt16 nSlot = rReq.GetSlot(); + switch( nSlot ) + { + case SID_TEXTDIRECTION_LEFT_TO_RIGHT: + case SID_TEXTDIRECTION_TOP_TO_BOTTOM: + { + bool bVert = (nSlot == SID_TEXTDIRECTION_TOP_TO_BOTTOM); + ScPatternAttr aAttr( GetViewData().GetDocument().GetPool() ); + SfxItemSet& rItemSet = aAttr.GetItemSet(); + rItemSet.Put( ScVerticalStackCell( bVert ) ); + rItemSet.Put( SfxBoolItem( ATTR_VERTICAL_ASIAN, bVert ) ); + pTabViewShell->ApplySelectionPattern( aAttr ); + pTabViewShell->AdjustBlockHeight(); + } + break; + + case SID_ATTR_PARA_LEFT_TO_RIGHT: + case SID_ATTR_PARA_RIGHT_TO_LEFT: + { + SvxFrameDirection eDirection = ( nSlot == SID_ATTR_PARA_LEFT_TO_RIGHT ) ? + SvxFrameDirection::Horizontal_LR_TB : SvxFrameDirection::Horizontal_RL_TB; + pTabViewShell->ApplyAttr( SvxFrameDirectionItem( eDirection, ATTR_WRITINGDIR ) ); + } + break; + } + if (bEditMode) + SC_MOD()->SetInputMode( SC_INPUT_TABLE ); +} + +void ScFormatShell::GetTextDirectionState( SfxItemSet& rSet ) +{ + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + const SfxItemSet& rAttrSet = pTabViewShell->GetSelectionPattern()->GetItemSet(); + + bool bVertDontCare = + (rAttrSet.GetItemState( ATTR_VERTICAL_ASIAN ) == SfxItemState::DONTCARE) || + (rAttrSet.GetItemState( ATTR_STACKED ) == SfxItemState::DONTCARE); + bool bLeftRight = !bVertDontCare && + !rAttrSet.Get( ATTR_STACKED ).GetValue(); + bool bTopBottom = !bVertDontCare && !bLeftRight && + rAttrSet.Get( ATTR_VERTICAL_ASIAN ).GetValue(); + + bool bBidiDontCare = (rAttrSet.GetItemState( ATTR_WRITINGDIR ) == SfxItemState::DONTCARE); + EEHorizontalTextDirection eBidiDir = EEHorizontalTextDirection::Default; + if ( !bBidiDontCare ) + { + SvxFrameDirection eCellDir = rAttrSet.Get( ATTR_WRITINGDIR ).GetValue(); + if ( eCellDir == SvxFrameDirection::Environment ) + eBidiDir = GetViewData().GetDocument(). + GetEditTextDirection( GetViewData().GetTabNo() ); + else if ( eCellDir == SvxFrameDirection::Horizontal_RL_TB ) + eBidiDir = EEHorizontalTextDirection::R2L; + else + eBidiDir = EEHorizontalTextDirection::L2R; + } + + bool bDisableCTLFont = !SvtCTLOptions::IsCTLFontEnabled(); + bool bDisableVerticalText = !SvtCJKOptions::IsVerticalTextEnabled(); + + SfxWhichIter aIter( rSet ); + sal_uInt16 nWhich = aIter.FirstWhich(); + while( nWhich ) + { + switch( nWhich ) + { + case SID_TEXTDIRECTION_LEFT_TO_RIGHT: + case SID_TEXTDIRECTION_TOP_TO_BOTTOM: + if ( bDisableVerticalText ) + rSet.DisableItem( nWhich ); + else + { + if( bVertDontCare ) + rSet.InvalidateItem( nWhich ); + else if ( nWhich == SID_TEXTDIRECTION_LEFT_TO_RIGHT ) + rSet.Put( SfxBoolItem( nWhich, bLeftRight ) ); + else + rSet.Put( SfxBoolItem( nWhich, bTopBottom ) ); + } + break; + + case SID_ATTR_PARA_LEFT_TO_RIGHT: + case SID_ATTR_PARA_RIGHT_TO_LEFT: + if ( bDisableCTLFont ) + rSet.DisableItem( nWhich ); + else + { + if ( bTopBottom ) + rSet.DisableItem( nWhich ); + else if ( bBidiDontCare ) + rSet.InvalidateItem( nWhich ); + else if ( nWhich == SID_ATTR_PARA_LEFT_TO_RIGHT ) + rSet.Put( SfxBoolItem( nWhich, eBidiDir == EEHorizontalTextDirection::L2R ) ); + else + rSet.Put( SfxBoolItem( nWhich, eBidiDir == EEHorizontalTextDirection::R2L ) ); + } + } + nWhich = aIter.NextWhich(); + } +} + +void ScFormatShell::ExecFormatPaintbrush( const SfxRequest& rReq ) +{ + ScViewFunc* pView = rViewData.GetView(); + if ( pView->HasPaintBrush() ) + { + // cancel paintbrush mode + pView->ResetBrushDocument(); + } + else + { + bool bLock = false; + const SfxItemSet *pArgs = rReq.GetArgs(); + if( pArgs && pArgs->Count() >= 1 ) + bLock = pArgs->Get(SID_FORMATPAINTBRUSH).GetValue(); + + // in case of multi selection, deselect all and use the cursor position + ScRange aDummy; + if ( rViewData.GetSimpleArea(aDummy) != SC_MARK_SIMPLE ) + pView->Unmark(); + + ScDocumentUniquePtr pBrushDoc(new ScDocument( SCDOCMODE_CLIP )); + pView->CopyToClip( pBrushDoc.get(), false, true ); + pView->SetBrushDocument( std::move(pBrushDoc), bLock ); + } +} + +void ScFormatShell::StateFormatPaintbrush( SfxItemSet& rSet ) +{ + if ( rViewData.HasEditView( rViewData.GetActivePart() ) ) + rSet.DisableItem( SID_FORMATPAINTBRUSH ); + else + rSet.Put( SfxBoolItem( SID_FORMATPAINTBRUSH, rViewData.GetView()->HasPaintBrush() ) ); +} + +SvNumFormatType ScFormatShell::GetCurrentNumberFormatType() +{ + SvNumFormatType nType = SvNumFormatType::ALL; + ScDocument& rDoc = GetViewData().GetDocument(); + ScMarkData aMark(GetViewData().GetMarkData()); + const SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + if (!pFormatter) + return nType; + + // TODO: Find out how to get a selected table range in case multiple tables + // are selected. Currently we only check for the current active table. + + if ( aMark.IsMarked() || aMark.IsMultiMarked() ) + { + aMark.MarkToMulti(); + const ScRange& aRange = aMark.GetMultiMarkArea(); + const ScMultiSel& rMultiSel = aMark.GetMultiSelData(); + + SvNumFormatType nComboType = SvNumFormatType::ALL; + bool bFirstItem = true; + for (SCCOL nCol = aRange.aStart.Col(); nCol <= aRange.aEnd.Col(); ++nCol) + { + if (!rMultiSel.HasMarks(nCol)) + continue; + + SCROW nRow1, nRow2; + ScMultiSelIter aMultiIter(rMultiSel, nCol); + while (aMultiIter.Next(nRow1, nRow2)) + { + ScRange aColRange(nCol, nRow1, aRange.aStart.Tab()); + aColRange.aEnd.SetRow(nRow2); + sal_uInt32 nNumFmt = rDoc.GetNumberFormat(aColRange); + SvNumFormatType nThisType = pFormatter->GetType(nNumFmt); + if (bFirstItem) + { + bFirstItem = false; + nComboType = nThisType; + } + else if (nComboType != nThisType) + // mixed number format type. + return SvNumFormatType::ALL; + } + } + nType = nComboType; + } + else + { + sal_uInt32 nNumFmt = rDoc.GetNumberFormat( rViewData.GetCurX(), rViewData.GetCurY(), + rViewData.GetTabNo()); + nType = pFormatter->GetType( nNumFmt ); + } + return nType; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/gridmerg.cxx b/sc/source/ui/view/gridmerg.cxx new file mode 100644 index 0000000000..117b3e1ad7 --- /dev/null +++ b/sc/source/ui/view/gridmerg.cxx @@ -0,0 +1,225 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include + +#include + +#define PAGEBREAK_LINE_DISTANCE_PIXEL 5 +#define PAGEBREAK_LINE_DASH_LEN_PIXEL 5 +#define PAGEBREAK_LINE_DASH_COUNT 1 + +ScGridMerger::ScGridMerger( OutputDevice* pOutDev, tools::Long nOnePixelX, tools::Long nOnePixelY ) + : pDev(pOutDev) + , nOneX(nOnePixelX) + , nOneY(nOnePixelY) + , nFixStart(0) + , nFixEnd(0) + , nVarStart(0) + , nVarDiff(0) + , nCount(0) + , bVertical(false) +{ + // optimize (DrawGrid) only for pixel MapMode, + // to avoid rounding errors + + bOptimize = ( pDev->GetMapMode().GetMapUnit() == MapUnit::MapPixel ); +} + +ScGridMerger::~ScGridMerger() +{ + Flush(); +} + +void ScGridMerger::AddLine( tools::Long nStart, tools::Long nEnd, tools::Long nPos ) +{ + if ( nCount ) + { + // not first line - test fix position + // more than one previous line - test distance + + if ( nStart != nFixStart || nEnd != nFixEnd ) + { + if ( nCount == 1 && nPos == nVarStart && + ( nStart == nFixEnd || + nStart == nFixEnd + ( bVertical ? nOneY : nOneX ) ) ) + { + // additional optimization: extend connected lines + // keep nCount at 1 + nFixEnd = nEnd; + } + else + Flush(); + } + else if ( nCount == 1 ) + { + nVarDiff = nPos - nVarStart; + ++nCount; + } + else if ( nPos != nVarStart + nCount * nVarDiff ) //! keep VarEnd? + Flush(); + else + ++nCount; + } + + if ( !nCount ) + { + // first line (or flushed above) - just store + + nFixStart = nStart; + nFixEnd = nEnd; + nVarStart = nPos; + nVarDiff = 0; + nCount = 1; + } +} + +void ScGridMerger::AddHorLine(bool bWorksInPixels, tools::Long nX1, tools::Long nX2, tools::Long nY, bool bDashed) +{ + if ( bWorksInPixels ) + { + Point aPoint(pDev->PixelToLogic(Point(nX1, nY))); + nX1 = aPoint.X(); + nY = aPoint.Y(); + nX2 = pDev->PixelToLogic(Point(nX2, 0)).X(); + } + + if ( bDashed ) + { + // If there are some unflushed lines they must be flushed since + // new line is of different style + if (bOptimize) { + Flush(); + bVertical = false; + } + + LineInfo aLineInfo(LineStyle::Dash, 1); + aLineInfo.SetDashCount( PAGEBREAK_LINE_DASH_COUNT ); + + // Calculating logic values of DashLen and Distance from fixed pixel values + Size aDashDistanceLen( pDev->PixelToLogic( Size( PAGEBREAK_LINE_DISTANCE_PIXEL, + PAGEBREAK_LINE_DASH_LEN_PIXEL ))); + + aLineInfo.SetDistance( aDashDistanceLen.Width() ); + aLineInfo.SetDashLen( aDashDistanceLen.Height() ); + + pDev->DrawLine( Point( nX1, nY ), Point( nX2, nY ), aLineInfo ); + } + else if ( bOptimize ) + { + if ( bVertical ) + { + Flush(); + bVertical = false; + } + AddLine( nX1, nX2, nY ); + } + else + pDev->DrawLine( Point( nX1, nY ), Point( nX2, nY ) ); +} + +void ScGridMerger::AddVerLine(bool bWorksInPixels, tools::Long nX, tools::Long nY1, tools::Long nY2, bool bDashed) +{ + if (bWorksInPixels) + { + Point aPoint(pDev->PixelToLogic(Point(nX, nY1))); + nX = aPoint.X(); + nY1 = aPoint.Y(); + nY2 = pDev->PixelToLogic(Point(0, nY2)).Y(); + } + + if ( bDashed ) + { + // If there are some unflushed lines they must be flushed since + // new line is of different style + if (bOptimize) { + Flush(); + bVertical = false; + } + + LineInfo aLineInfo(LineStyle::Dash, 1); + aLineInfo.SetDashCount( PAGEBREAK_LINE_DASH_COUNT ); + + // Calculating logic values of DashLen and Distance from fixed pixel values + Size aDashDistanceLen( pDev->PixelToLogic( Size( PAGEBREAK_LINE_DISTANCE_PIXEL, + PAGEBREAK_LINE_DASH_LEN_PIXEL ))); + + aLineInfo.SetDistance( aDashDistanceLen.Width() ); + aLineInfo.SetDashLen( aDashDistanceLen.Height() ); + + pDev->DrawLine( Point( nX, nY1 ), Point( nX, nY2 ), aLineInfo); + } + else if ( bOptimize ) + { + if ( !bVertical ) + { + Flush(); + bVertical = true; + } + AddLine( nY1, nY2, nX ); + } + else + pDev->DrawLine( Point( nX, nY1 ), Point( nX, nY2 ) ); +} + +void ScGridMerger::Flush() +{ + if (!nCount) + return; + + if (bVertical) + { + if ( nCount == 1 ) + pDev->DrawLine( Point( nVarStart, nFixStart ), Point( nVarStart, nFixEnd ) ); + else + { + tools::Long nVarEnd = nVarStart + ( nCount - 1 ) * nVarDiff; + if ( nVarDiff < 0 ) + { + // nVarDiff is negative in RTL layout mode + // Change the positions so DrawGrid is called with a positive distance + // (nVarStart / nVarDiff can be modified, aren't used after Flush) + + nVarDiff = -nVarDiff; + std::swap( nVarStart, nVarEnd ); + } + pDev->DrawGrid( tools::Rectangle( nVarStart, nFixStart, nVarEnd, nFixEnd ), + Size( nVarDiff, nFixEnd - nFixStart ), + DrawGridFlags::VertLines ); + } + } + else + { + if ( nCount == 1 ) + pDev->DrawLine( Point( nFixStart, nVarStart ), Point( nFixEnd, nVarStart ) ); + else + { + tools::Long nVarEnd = nVarStart + ( nCount - 1 ) * nVarDiff; + pDev->DrawGrid( tools::Rectangle( nFixStart, nVarStart, nFixEnd, nVarEnd ), + Size( nFixEnd - nFixStart, nVarDiff ), + DrawGridFlags::HorzLines ); + } + } + nCount = 0; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/gridwin.cxx b/sc/source/ui/view/gridwin.cxx new file mode 100644 index 0000000000..3f4f6b219c --- /dev/null +++ b/sc/source/ui/view/gridwin.cxx @@ -0,0 +1,7218 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include + +using namespace css; +using namespace css::uno; + +struct ScGridWindow::MouseEventState +{ + bool mbActivatePart; + + MouseEventState() : + mbActivatePart(false) + {} +}; + +#define SC_FILTERLISTBOX_LINES 12 + +ScGridWindow::VisibleRange::VisibleRange(const ScDocument& rDoc) + : mnCol1(0) + , mnCol2(rDoc.MaxCol()) + , mnRow1(0) + , mnRow2(rDoc.MaxRow()) +{ +} + +bool ScGridWindow::VisibleRange::isInside(SCCOL nCol, SCROW nRow) const +{ + return mnCol1 <= nCol && nCol <= mnCol2 && mnRow1 <= nRow && nRow <= mnRow2; +} + +bool ScGridWindow::VisibleRange::set(SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2) +{ + bool bChanged = mnCol1 != nCol1 || mnRow1 != nRow1 || mnCol2 != nCol2 || mnRow2 != nRow2; + + mnCol1 = nCol1; + mnRow1 = nRow1; + mnCol2 = nCol2; + mnRow2 = nRow2; + + return bChanged; +} + +// ListBox in a FloatingWindow (pParent) +ScFilterListBox::ScFilterListBox(weld::Window* pParent, ScGridWindow* pGrid, + SCCOL nNewCol, SCROW nNewRow, ScFilterBoxMode eNewMode) + : xBuilder(Application::CreateBuilder(pParent, "modules/scalc/ui/filterlist.ui")) + , xPopover(xBuilder->weld_popover("FilterList")) + , xTreeView(xBuilder->weld_tree_view("list")) + , pGridWin(pGrid) + , nCol(nNewCol) + , nRow(nNewRow) + , bInit(true) + , bCancelled(false) + , bGridHadMouseCaptured(pGrid->IsMouseCaptured()) + , nSel(0) + , eMode(eNewMode) + , nAsyncSelectHdl(nullptr) +{ + xTreeView->connect_row_activated(LINK(this, ScFilterListBox, SelectHdl)); + xTreeView->connect_key_press(LINK(this, ScFilterListBox, KeyInputHdl)); +} + +ScFilterListBox::~ScFilterListBox() +{ + if (nAsyncSelectHdl) + { + Application::RemoveUserEvent(nAsyncSelectHdl); + nAsyncSelectHdl = nullptr; + } +} + +void ScFilterListBox::EndInit() +{ + sal_Int32 nPos = xTreeView->get_selected_index(); + if (nPos == -1) + nSel = 0; + else + nSel = nPos; + + bInit = false; +} + +IMPL_LINK(ScFilterListBox, KeyInputHdl, const KeyEvent&, rKeyEvent, bool) +{ + bool bDone = false; + + vcl::KeyCode aCode = rKeyEvent.GetKeyCode(); + // esc with no modifiers + if (!aCode.GetModifier() && aCode.GetCode() == KEY_ESCAPE) + { + pGridWin->ClickExtern(); // clears the listbox + bDone = true; + } + + // nowhere to tab to + if (aCode.GetCode() == KEY_TAB) + bDone = true; + + return bDone; +} + +IMPL_LINK_NOARG(ScFilterListBox, SelectHdl, weld::TreeView&, bool) +{ + if (!bInit && !bCancelled && !nAsyncSelectHdl) + { + int nPos = xTreeView->get_selected_index(); + if (nPos != -1) + { + nSel = nPos; + // #i81298# launch async so the box isn't deleted from modifications within FilterSelect + nAsyncSelectHdl = Application::PostUserEvent(LINK(this, ScFilterListBox, AsyncSelectHdl)); + } + } + return true; +} + +IMPL_LINK_NOARG(ScFilterListBox, AsyncSelectHdl, void*, void) +{ + nAsyncSelectHdl = nullptr; + + //tdf#133971 hold self-ref until we return + auto xThis(shared_from_this()); + pGridWin->FilterSelect(nSel); + if (xThis.use_count() == 1) + { + // tdf#133855 we got disposed by FilterSelect + return; + } + pGridWin->ClickExtern(); +} + +static bool lcl_IsEditableMatrix( ScDocument& rDoc, const ScRange& rRange ) +{ + // If it is an editable range and if there is a Matrix cell at the bottom right with an + // origin top left then the range will be set to contain the exact matrix. + //! Extract the MatrixEdges functions directly from the column ??? + if ( !rDoc.IsBlockEditable( rRange.aStart.Tab(), rRange.aStart.Col(),rRange.aStart.Row(), + rRange.aEnd.Col(),rRange.aEnd.Row() ) ) + return false; + + ScRefCellValue aCell(rDoc, rRange.aEnd); + ScAddress aPos; + return (aCell.getType() == CELLTYPE_FORMULA && aCell.getFormula()->GetMatrixOrigin(rDoc, aPos) && aPos == rRange.aStart); +} + +static void lcl_UnLockComment( ScDrawView* pView, const Point& rPos, const ScViewData& rViewData ) +{ + if (!pView) + return; + + ScDocument& rDoc = rViewData.GetDocument(); + ScAddress aCellPos( rViewData.GetCurX(), rViewData.GetCurY(), rViewData.GetTabNo() ); + ScPostIt* pNote = rDoc.GetNote( aCellPos ); + SdrObject* pObj = pNote ? pNote->GetCaption() : nullptr; + if( pObj && pObj->GetLogicRect().Contains( rPos ) && ScDrawLayer::IsNoteCaption( pObj ) ) + { + const ScProtectionAttr* pProtAttr = rDoc.GetAttr( aCellPos, ATTR_PROTECTION ); + bool bProtectAttr = pProtAttr->GetProtection() || pProtAttr->GetHideCell() ; + bool bProtectDoc = rDoc.IsTabProtected( aCellPos.Tab() ) || rViewData.GetSfxDocShell()->IsReadOnly() ; + // unlock internal layer (if not protected), will be relocked in ScDrawView::MarkListHasChanged() + pView->LockInternalLayer( bProtectDoc && bProtectAttr ); + } +} + +static bool lcl_GetHyperlinkCell( + ScDocument& rDoc, SCCOL& rPosX, SCROW nPosY, SCTAB nTab, ScRefCellValue& rCell, OUString& rURL ) +{ + bool bFound = false; + do + { + ScAddress aPos(rPosX, nPosY, nTab); + rCell.assign(rDoc, aPos); + if (rCell.isEmpty()) + { + if ( rPosX <= 0 ) + return false; // everything empty to the links + else + --rPosX; // continue search + } + else + { + const ScPatternAttr* pPattern = rDoc.GetPattern(aPos); + if ( !pPattern->GetItem(ATTR_HYPERLINK).GetValue().isEmpty() ) + { + rURL = pPattern->GetItem(ATTR_HYPERLINK).GetValue(); + bFound = true; + } + else if (rCell.getType() == CELLTYPE_EDIT) + bFound = true; + else if (rCell.getType() == CELLTYPE_FORMULA && rCell.getFormula()->IsHyperLinkCell()) + bFound = true; + else + return false; // other cell + } + } + while ( !bFound ); + + return bFound; +} + +static void lcl_GetMirror(Point& rPoint, tools::Rectangle& rRect, const tools::Long nWidth) +{ + tools::Long nLeft = rRect.Left(); + tools::Long nRight = rRect.Right(); + tools::Long nMirrorPX = o3tl::convert(nWidth, o3tl::Length::twip, o3tl::Length::px); + tools::Long nMirrorMM = o3tl::convert(nWidth, o3tl::Length::twip, o3tl::Length::mm100); + + rPoint.setX(nMirrorPX - rPoint.X()); + rRect.SetLeft(nMirrorMM - nRight); + rRect.SetRight(nMirrorMM - nLeft); +} + +// WB_DIALOGCONTROL needed for UNO-Controls +ScGridWindow::ScGridWindow( vcl::Window* pParent, ScViewData& rData, ScSplitPos eWhichPos ) +: DocWindow( pParent, WB_CLIPCHILDREN | WB_DIALOGCONTROL ), + DropTargetHelper( this ), + DragSourceHelper( this ), + maVisibleRange(rData.GetDocument()), + mrViewData( rData ), + eWhich( eWhichPos ), + nCursorHideCount( 0 ), + nButtonDown( 0 ), + nMouseStatus( SC_GM_NONE ), + nNestedButtonState( ScNestedButtonState::NONE ), + nDPField( 0 ), + pDragDPObj( nullptr ), + nRFIndex( 0 ), + nRFAddX( 0 ), + nRFAddY( 0 ), + nPagebreakMouse( SC_PD_NONE ), + nPagebreakBreak( 0 ), + nPagebreakPrev( 0 ), + nPageScript( SvtScriptType::NONE ), + nDragStartX( -1 ), + nDragStartY( -1 ), + nDragEndX( -1 ), + nDragEndY( -1 ), + meDragInsertMode( INS_NONE ), + aComboButton( GetOutDev() ), + aCurMousePos( 0,0 ), + nPaintCount( 0 ), + aRFSelectedCorned( NONE ), + maShowPageBreaksTimer("ScGridWindow maShowPageBreaksTimer"), + bEEMouse( false ), + bDPMouse( false ), + bRFMouse( false ), + bRFSize( false ), + bPagebreakDrawn( false ), + bDragRect( false ), + bIsInPaint( false ), + bNeedsRepaint( false ), + bAutoMarkVisible( false ), + bListValButton( false ), + m_nDownPosX( -1 ), + m_nDownPosY( -1 ) +{ + set_id("grid_window"); + switch(eWhich) + { + case SC_SPLIT_TOPLEFT: + eHWhich = SC_SPLIT_LEFT; + eVWhich = SC_SPLIT_TOP; + break; + case SC_SPLIT_TOPRIGHT: + eHWhich = SC_SPLIT_RIGHT; + eVWhich = SC_SPLIT_TOP; + break; + case SC_SPLIT_BOTTOMLEFT: + eHWhich = SC_SPLIT_LEFT; + eVWhich = SC_SPLIT_BOTTOM; + break; + case SC_SPLIT_BOTTOMRIGHT: + eHWhich = SC_SPLIT_RIGHT; + eVWhich = SC_SPLIT_BOTTOM; + break; + default: + OSL_FAIL("GridWindow: wrong position"); + } + + SetUseFrameData(comphelper::LibreOfficeKit::isActive()); + SetBackground(); + + SetMapMode(mrViewData.GetLogicMode(eWhich)); + EnableChildTransparentMode(); + SetDialogControlFlags( DialogControlFlags::Return | DialogControlFlags::WantFocus ); + + SetHelpId( HID_SC_WIN_GRIDWIN ); + + GetOutDev()->SetDigitLanguage( ScModule::GetOptDigitLanguage() ); + EnableRTL( false ); + + bInitialPageBreaks = true; + maShowPageBreaksTimer.SetInvokeHandler(LINK(this, ScGridWindow, InitiatePageBreaksTimer)); + maShowPageBreaksTimer.SetTimeout(1); +} + +ScGridWindow::~ScGridWindow() +{ + disposeOnce(); +} + +void ScGridWindow::dispose() +{ + maShowPageBreaksTimer.Stop(); + + ImpDestroyOverlayObjects(); + + mpFilterBox.reset(); + mpNoteMarker.reset(); + mpAutoFilterPopup.reset(); + mpDPFieldPopup.reset(); + aComboButton.SetOutputDevice(nullptr); + + if (mpSpellCheckCxt) + mpSpellCheckCxt->reset(); + mpSpellCheckCxt.reset(); + + vcl::Window::dispose(); +} + +void ScGridWindow::ClickExtern() +{ + do + { + // #i84277# when initializing the filter box, a Basic error can deactivate the view + if (mpFilterBox && mpFilterBox->IsInInit()) + break; + mpFilterBox.reset(); + } + while (false); + + if (mpDPFieldPopup) + { + mpDPFieldPopup->close(false); + mpDPFieldPopup.reset(); + } +} + +IMPL_LINK_NOARG(ScGridWindow, PopupModeEndHdl, weld::Popover&, void) +{ + if (mpFilterBox) + { + bool bMouseWasCaptured = mpFilterBox->MouseWasCaptured(); + mpFilterBox->SetCancelled(); // cancel select + // restore the mouse capture state of the GridWindow to + // what it was at initial popup time + SAL_WARN_IF(bMouseWasCaptured, "sc.ui", "Is there a scenario where the mouse was captured before mouse down?"); + if (bMouseWasCaptured) + CaptureMouse(); + } + GrabFocus(); +} + +IMPL_LINK( ScGridWindow, PopupSpellingHdl, SpellCallbackInfo&, rInfo, void ) +{ + if( rInfo.nCommand == SpellCallbackCommand::STARTSPELLDLG ) + mrViewData.GetDispatcher().Execute( SID_SPELL_DIALOG, SfxCallMode::ASYNCHRON ); + else if (rInfo.nCommand == SpellCallbackCommand::AUTOCORRECT_OPTIONS) + mrViewData.GetDispatcher().Execute( SID_AUTO_CORRECT_DLG, SfxCallMode::ASYNCHRON ); + else //IGNOREWORD, ADDTODICTIONARY, WORDLANGUAGE, PARALANGUAGE + { + // The spelling status of the word has changed. Close the cell to reset the caches + ScInputHandler* pHdl = SC_MOD()->GetInputHdl(mrViewData.GetViewShell()); + if (pHdl) + pHdl->EnterHandler(); + } +} + +namespace { + +struct AutoFilterData : public ScCheckListMenuControl::ExtendedData +{ + ScAddress maPos; + ScDBData* mpData; +}; + +class AutoFilterAction : public ScCheckListMenuControl::Action +{ +protected: + VclPtr mpWindow; + ScGridWindow::AutoFilterMode meMode; +public: + AutoFilterAction(ScGridWindow* p, ScGridWindow::AutoFilterMode eMode) : + mpWindow(p), meMode(eMode) {} + virtual bool execute() override + { + mpWindow->UpdateAutoFilterFromMenu(meMode); + // UpdateAutoFilterFromMenu manually closes the popup so return + // false to not attempt a second close + return false; + } +}; + +class AutoFilterPopupEndAction : public ScCheckListMenuControl::Action +{ + VclPtr mpWindow; + ScAddress maPos; +public: + AutoFilterPopupEndAction(ScGridWindow* p, const ScAddress& rPos) : + mpWindow(p), maPos(rPos) {} + virtual bool execute() override + { + mpWindow->RefreshAutoFilterButton(maPos); + mpWindow->GrabFocus(); + return false; // this is called after the popup has been closed + } +}; + +class AutoFilterSubMenuAction : public AutoFilterAction +{ +protected: + ScListSubMenuControl* m_pSubMenu; + +public: + AutoFilterSubMenuAction(ScGridWindow* p, ScListSubMenuControl* pSubMenu, ScGridWindow::AutoFilterMode eMode) + : AutoFilterAction(p, eMode) + , m_pSubMenu(pSubMenu) + { + } +}; + +class AutoFilterColorAction : public AutoFilterSubMenuAction +{ +private: + Color m_aColor; + +public: + AutoFilterColorAction(ScGridWindow* p, ScListSubMenuControl* pSubMenu, ScGridWindow::AutoFilterMode eMode, const Color& rColor) + : AutoFilterSubMenuAction(p, pSubMenu, eMode) + , m_aColor(rColor) + { + } + + virtual bool execute() override + { + const AutoFilterData* pData = + static_cast(m_pSubMenu->getExtendedData()); + + if (!pData) + return false; + + ScDBData* pDBData = pData->mpData; + if (!pDBData) + return false; + + const ScAddress& rPos = pData->maPos; + + ScViewData& rViewData = m_pSubMenu->GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + + ScQueryParam aParam; + pDBData->GetQueryParam(aParam); + + // Try to use the existing entry for the column (if one exists). + ScQueryEntry* pEntry = aParam.FindEntryByField(rPos.Col(), true); + + if (!pEntry) + { + // Something went terribly wrong! + return false; + } + + if (ScTabViewShell::isAnyEditViewInRange(rViewData.GetViewShell(), /*bColumns*/ false, aParam.nRow1, aParam.nRow2)) + return false; + + pEntry->bDoQuery = true; + pEntry->nField = rPos.Col(); + pEntry->eConnect = SC_AND; + + ScFilterEntries aFilterEntries; + rDoc.GetFilterEntries(rPos.Col(), rPos.Row(), rPos.Tab(), aFilterEntries); + + bool bActive = false; + auto aItem = pEntry->GetQueryItem(); + if (aItem.maColor == m_aColor + && ((meMode == ScGridWindow::AutoFilterMode::TextColor + && aItem.meType == ScQueryEntry::ByTextColor) + || (meMode == ScGridWindow::AutoFilterMode::BackgroundColor + && aItem.meType == ScQueryEntry::ByBackgroundColor))) + { + bActive = true; + } + + // Disable color filter when active color was selected + if (bActive) + { + aParam.RemoveAllEntriesByField(rPos.Col()); + pEntry = nullptr; // invalidated by RemoveAllEntriesByField call + + // tdf#46184 reset filter options to default values + aParam.eSearchType = utl::SearchParam::SearchType::Normal; + aParam.bCaseSens = false; + aParam.bDuplicate = true; + aParam.bInplace = true; + } + else + { + if (meMode == ScGridWindow::AutoFilterMode::TextColor) + pEntry->SetQueryByTextColor(m_aColor); + else + pEntry->SetQueryByBackgroundColor(m_aColor); + } + + rViewData.GetView()->Query(aParam, nullptr, true); + pDBData->SetQueryParam(aParam); + + return true; + } +}; + +class AutoFilterSortColorAction : public AutoFilterSubMenuAction +{ +private: + Color m_aColor; + ScViewData& m_rViewData; + +public: + AutoFilterSortColorAction(ScGridWindow* p, ScListSubMenuControl* pSubMenu, ScGridWindow::AutoFilterMode eMode, const Color& rColor, ScViewData& rViewData) + : AutoFilterSubMenuAction(p, pSubMenu, eMode) + , m_aColor(rColor) + , m_rViewData(rViewData) + { + } + + virtual bool execute() override + { + const AutoFilterData* pData = + static_cast(m_pSubMenu->getExtendedData()); + + if (!pData) + return false; + + ScDBData* pDBData = pData->mpData; + if (!pDBData) + return false; + + const ScAddress& rPos = pData->maPos; + SCCOL nCol = rPos.Col(); + ScSortParam aSortParam; + pDBData->GetSortParam(aSortParam); + if (nCol < aSortParam.nCol1 || nCol > aSortParam.nCol2) + // out of bound + return false; + + bool bHasHeader = pDBData->HasHeader(); + + aSortParam.bHasHeader = bHasHeader; + aSortParam.bByRow = true; + aSortParam.bCaseSens = false; + aSortParam.bNaturalSort = false; + aSortParam.aDataAreaExtras.mbCellNotes = false; + aSortParam.aDataAreaExtras.mbCellDrawObjects = true; + aSortParam.aDataAreaExtras.mbCellFormats = true; + aSortParam.bInplace = true; + aSortParam.maKeyState[0].bDoSort = true; + aSortParam.maKeyState[0].nField = nCol; + aSortParam.maKeyState[0].bAscending = true; + aSortParam.maKeyState[0].aColorSortMode = meMode == ScGridWindow::AutoFilterMode::TextColor + ? ScColorSortMode::TextColor + : ScColorSortMode::BackgroundColor; + aSortParam.maKeyState[0].aColorSortColor = m_aColor; + + for (size_t i = 1; i < aSortParam.GetSortKeyCount(); ++i) + aSortParam.maKeyState[i].bDoSort = false; + + m_rViewData.GetViewShell()->UISort(aSortParam); + + return true; + } +}; + +class AutoFilterColorPopupStartAction : public AutoFilterSubMenuAction +{ +private: + bool mbIsFilter; +public: + AutoFilterColorPopupStartAction(ScGridWindow* p, ScListSubMenuControl* pSubMenu, bool bIsFilter) + : AutoFilterSubMenuAction(p, pSubMenu, ScGridWindow::AutoFilterMode::Normal), + mbIsFilter(bIsFilter) + { + } + + virtual bool execute() override + { + const AutoFilterData* pData = + static_cast(m_pSubMenu->getExtendedData()); + + if (!pData) + return false; + + ScDBData* pDBData = pData->mpData; + if (!pDBData) + return false; + + ScViewData& rViewData = m_pSubMenu->GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + const ScAddress& rPos = pData->maPos; + + ScFilterEntries aFilterEntries; + rDoc.GetFilterEntries(rPos.Col(), rPos.Row(), rPos.Tab(), aFilterEntries); + + m_pSubMenu->clearMenuItems(); + + XColorListRef xUserColorList; + + OUString aPaletteName(officecfg::Office::Common::UserColors::PaletteName::get()); + PaletteManager aPaletteManager; + std::vector aPaletteNames = aPaletteManager.GetPaletteList(); + for (size_t i = 0, nLen = aPaletteNames.size(); i < nLen; ++i) + { + if (aPaletteName == aPaletteNames[i]) + { + aPaletteManager.SetPalette(i); + xUserColorList = XPropertyList::AsColorList( + XPropertyList::CreatePropertyListFromURL( + XPropertyListType::Color, aPaletteManager.GetSelectedPalettePath())); + if (!xUserColorList->Load()) + xUserColorList = nullptr; + break; + } + } + + ScQueryParam aParam; + pDBData->GetQueryParam(aParam); + ScQueryEntry* pEntry = aParam.FindEntryByField(rPos.Col(), true); + + int nMenu = 0; + for (auto eMode : {ScGridWindow::AutoFilterMode::BackgroundColor, ScGridWindow::AutoFilterMode::TextColor}) + { + std::set aColors = eMode == ScGridWindow::AutoFilterMode::TextColor + ? aFilterEntries.getTextColors() + : aFilterEntries.getBackgroundColors(); + + for (auto& rColor : aColors) + { + bool bActive = false; + + if (pEntry) + { + auto aItem = pEntry->GetQueryItem(); + if (aItem.maColor == rColor + && ((eMode == ScGridWindow::AutoFilterMode::TextColor + && aItem.meType == ScQueryEntry::ByTextColor) + || (eMode == ScGridWindow::AutoFilterMode::BackgroundColor + && aItem.meType == ScQueryEntry::ByBackgroundColor))) + { + bActive = true; + } + } + + const bool bAutoColor = rColor == COL_AUTO; + + // ColorListBox::ShowPreview is similar + ScopedVclPtr xDev(m_pSubMenu->create_virtual_device()); + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + Size aImageSize(rStyleSettings.GetListBoxPreviewDefaultPixelSize()); + xDev->SetOutputSize(aImageSize); + const tools::Rectangle aRect(Point(0, 0), aImageSize); + + if (bAutoColor) + { + const Color aW(COL_WHITE); + const Color aG(0xef, 0xef, 0xef); + int nMinDim = std::min(aImageSize.Width(), aImageSize.Height()) + 1; + int nCheckSize = nMinDim / 3; + xDev->DrawCheckered(aRect.TopLeft(), aRect.GetSize(), std::min(nCheckSize, 8), aW, aG); + xDev->SetFillColor(); + } + else + xDev->SetFillColor(rColor); + + xDev->SetLineColor(rStyleSettings.GetDisableColor()); + xDev->DrawRect(aRect); + + if (bAutoColor) + { + OUString sText = eMode == ScGridWindow::AutoFilterMode::TextColor + ? ScResId(SCSTR_FILTER_AUTOMATIC_COLOR) + : ScResId(SCSTR_FILTER_NO_FILL); + if (mbIsFilter) + { + m_pSubMenu->addMenuColorItem( + sText, bActive, *xDev, nMenu, + new AutoFilterColorAction(mpWindow, m_pSubMenu, eMode, rColor)); + } + else + { + m_pSubMenu->addMenuColorItem( + sText, bActive, *xDev, nMenu, + new AutoFilterSortColorAction(mpWindow, m_pSubMenu, eMode, rColor, rViewData)); + } + } + else + { + OUString sName; + + bool bFoundColorName = false; + if (xUserColorList) + { + sal_Int32 nPos = xUserColorList->GetIndexOfColor(rColor); + if (nPos != -1) + { + XColorEntry* pColorEntry = xUserColorList->GetColor(nPos); + sName = pColorEntry->GetName(); + bFoundColorName = true; + } + } + if (!bFoundColorName) + sName = "#" + rColor.AsRGBHexString().toAsciiUpperCase(); + + if (mbIsFilter) + { + m_pSubMenu->addMenuColorItem( + sName, bActive, *xDev, nMenu, + new AutoFilterColorAction(mpWindow, m_pSubMenu, eMode, rColor)); + } + else + { + m_pSubMenu->addMenuColorItem( + sName, bActive, *xDev, nMenu, + new AutoFilterSortColorAction(mpWindow, m_pSubMenu, eMode, rColor, + rViewData)); + } + } + } + + ++nMenu; + } + + m_pSubMenu->resizeToFitMenuItems(); + + return false; + } +}; + +class AddItemToEntry +{ + ScQueryEntry::QueryItemsType& mrItems; + svl::SharedStringPool& mrPool; +public: + AddItemToEntry(ScQueryEntry::QueryItemsType& rItems, svl::SharedStringPool& rPool) : + mrItems(rItems), mrPool(rPool) {} + void operator() (const ScCheckListMenuControl::ResultEntry& rEntry) + { + if (rEntry.bValid) + { + ScQueryEntry::Item aNew; + aNew.maString = mrPool.intern(rEntry.aName); + // set the filter type to ByValue, if the filter condition is value + aNew.meType = rEntry.bDate ? ScQueryEntry::ByDate : rEntry.bValue ? ScQueryEntry::ByValue : ScQueryEntry::ByString; + aNew.mfVal = rEntry.nValue; + mrItems.push_back(aNew); + } + } +}; + +class AddSelectedItemString +{ + std::unordered_set& mrSetString; + std::unordered_set& mrSetValue; +public: + explicit AddSelectedItemString(std::unordered_set& rString, std::unordered_set& rValue) : + mrSetString(rString), mrSetValue(rValue) {} + + void operator() (const ScQueryEntry::Item& rItem) + { + if( rItem.meType == ScQueryEntry::QueryType::ByValue ) + mrSetValue.insert(rItem.mfVal); + else + mrSetString.insert(rItem.maString.getString()); + } +}; + +void collectUIInformation(const OUString& aRow, const OUString& aCol , const OUString& aevent) +{ + EventDescription aDescription; + aDescription.aAction = "LAUNCH"; + aDescription.aID = "grid_window"; + aDescription.aParameters = {{aevent, ""}, + {"ROW", aRow}, {"COL", aCol}}; + aDescription.aParent = "MainWindow"; + aDescription.aKeyWord = "ScGridWinUIObject"; + + UITestLogger::getInstance().logEvent(aDescription); +} + +} + +void ScGridWindow::LaunchAutoFilterMenu(SCCOL nCol, SCROW nRow) +{ + SCTAB nTab = mrViewData.GetTabNo(); + ScDocument& rDoc = mrViewData.GetDocument(); + bool bLOKActive = comphelper::LibreOfficeKit::isActive(); + + mpAutoFilterPopup.reset(); + + // Estimate the width (in pixels) of the longest text in the list + ScFilterEntries aFilterEntries; + rDoc.GetFilterEntries(nCol, nRow, nTab, aFilterEntries); + + weld::Window* pPopupParent = GetFrameWeld(); + int nColWidth = ScViewData::ToPixel(rDoc.GetColWidth(nCol, nTab), mrViewData.GetPPTX()); + mpAutoFilterPopup.reset(new ScCheckListMenuControl(pPopupParent, mrViewData, + aFilterEntries.mbHasDates, nColWidth)); + + int nMaxTextWidth = 0; + if (aFilterEntries.size() <= 10) + { + // do pixel calculation for all elements of short lists + for (const auto& rEntry : aFilterEntries) + { + const OUString& aText = rEntry.GetString(); + nMaxTextWidth = std::max(nMaxTextWidth, mpAutoFilterPopup->GetTextWidth(aText) + aText.getLength() * 2); + } + } + else + { + // find the longest string, probably it will be the longest rendered text, too + // (performance optimization for long lists) + auto itMax = aFilterEntries.begin(); + for (auto it = itMax; it != aFilterEntries.end(); ++it) + { + int nTextWidth = it->GetString().getLength(); + if (nMaxTextWidth < nTextWidth) + { + nMaxTextWidth = nTextWidth; + itMax = it; + } + } + nMaxTextWidth = mpAutoFilterPopup->GetTextWidth(itMax->GetString()) + nMaxTextWidth * 2; + } + + // window should be at least as wide as the column, or the longest text + checkbox, scrollbar ... (it is estimated with 70 pixel now) + // window should be maximum 1024 pixel wide. + int nWindowWidth = std::min(1024, nMaxTextWidth + 70); + nWindowWidth = mpAutoFilterPopup->IncreaseWindowWidthToFitText(nWindowWidth); + nMaxTextWidth = std::max(nMaxTextWidth, nWindowWidth - 70); + + mpAutoFilterPopup->setOKAction(new AutoFilterAction(this, AutoFilterMode::Normal)); + mpAutoFilterPopup->setPopupEndAction( + new AutoFilterPopupEndAction(this, ScAddress(nCol, nRow, nTab))); + std::unique_ptr pData(new AutoFilterData); + pData->maPos = ScAddress(nCol, nRow, nTab); + + Point aPos = mrViewData.GetScrPos(nCol, nRow, eWhich); + tools::Long nSizeX = 0; + tools::Long nSizeY = 0; + mrViewData.GetMergeSizePixel(nCol, nRow, nSizeX, nSizeY); + if (bLOKActive) + { + // Reverse the zoom factor from aPos and nSize[X|Y] + // before letting the autofilter window convert the to twips + // with no zoom information. + double fZoomX(mrViewData.GetZoomX()); + double fZoomY(mrViewData.GetZoomY()); + aPos.setX(aPos.getX() / fZoomX); + aPos.setY(aPos.getY() / fZoomY); + nSizeX = nSizeX / fZoomX; + nSizeY = nSizeY / fZoomY; + } + tools::Rectangle aCellRect(bLOKActive ? aPos : OutputToScreenPixel(aPos), Size(nSizeX, nSizeY)); + + ScDBData* pDBData = rDoc.GetDBAtCursor(nCol, nRow, nTab, ScDBDataPortion::AREA); + if (!pDBData) + return; + + pDBData->ExtendBackColorArea(rDoc); + pData->mpData = pDBData; + mpAutoFilterPopup->setExtendedData(std::move(pData)); + + ScQueryParam aParam; + pDBData->GetQueryParam(aParam); + std::vector aEntries = aParam.FindAllEntriesByField(nCol); + std::unordered_set aSelectedString; + std::unordered_set aSelectedValue; + bool bQueryByNonEmpty = aEntries.size() == 1 && aEntries[0]->IsQueryByNonEmpty(); + + if (!bQueryByNonEmpty) + { + for (ScQueryEntry* pEntry : aEntries) + { + if (pEntry && pEntry->eOp == SC_EQUAL) + { + ScQueryEntry::QueryItemsType& rItems = pEntry->GetQueryItems(); + std::for_each(rItems.begin(), rItems.end(), AddSelectedItemString(aSelectedString, aSelectedValue)); + } + } + } + + // Populate the check box list. + mpAutoFilterPopup->setMemberSize(aFilterEntries.size()); + for (auto it = aFilterEntries.begin(); it != aFilterEntries.end(); ++it) + { + // tdf#140745 show (empty) entry on top of the checkbox list + if (it->GetString().isEmpty()) + { + const OUString& aStringVal = it->GetString(); + const double aDoubleVal = it->GetValue(); + bool bSelected = true; + if (!aSelectedValue.empty() || !aSelectedString.empty()) + bSelected = aSelectedString.count(aStringVal) > 0; + else if (bQueryByNonEmpty) + bSelected = false; + mpAutoFilterPopup->addMember(aStringVal, aDoubleVal, bSelected, it->IsHiddenByFilter()); + aFilterEntries.maStrData.erase(it); + break; + } + } + for (const auto& rEntry : aFilterEntries) + { + const OUString& aStringVal = rEntry.GetString(); + const double aDoubleVal = rEntry.GetValue(); + const double aRDoubleVal = rEntry.GetRoundedValue(); + bool bSelected = !rEntry.IsHiddenByFilter(); + + if (!aSelectedValue.empty() || !aSelectedString.empty()) + { + if (rEntry.GetStringType() == ScTypedStrData::Value) + { + if (aDoubleVal == aRDoubleVal) + bSelected = aSelectedValue.count(aDoubleVal) > 0 + || aSelectedString.count(aStringVal) > 0; + else + bSelected = aSelectedValue.count(aDoubleVal) > 0 + || aSelectedValue.count(aRDoubleVal) > 0 + || aSelectedString.count(aStringVal) > 0; + } + else + bSelected = aSelectedString.count(aStringVal) > 0; + } + + if ( rEntry.IsDate() ) + mpAutoFilterPopup->addDateMember( aStringVal, rEntry.GetValue(), bSelected, rEntry.IsHiddenByFilter()); + else + mpAutoFilterPopup->addMember( aStringVal, aRDoubleVal, bSelected, rEntry.IsHiddenByFilter(), + rEntry.GetStringType() == ScTypedStrData::Value ); + } + + // Populate the menu. + mpAutoFilterPopup->addMenuItem( + ScResId(STR_MENU_SORT_ASC), + new AutoFilterAction(this, AutoFilterMode::SortAscending)); + mpAutoFilterPopup->addMenuItem( + ScResId(STR_MENU_SORT_DESC), + new AutoFilterAction(this, AutoFilterMode::SortDescending)); + if (ScListSubMenuControl* pSubMenu = mpAutoFilterPopup->addSubMenuItem(ScResId(SCSTR_SORT_COLOR), true, true)) + pSubMenu->setPopupStartAction(new AutoFilterColorPopupStartAction(this, pSubMenu, false)); + mpAutoFilterPopup->addSeparator(); + if (ScListSubMenuControl* pSubMenu = mpAutoFilterPopup->addSubMenuItem(ScResId(SCSTR_FILTER_COLOR), true, true)) + pSubMenu->setPopupStartAction(new AutoFilterColorPopupStartAction(this, pSubMenu, true)); + if (ScListSubMenuControl* pSubMenu = mpAutoFilterPopup->addSubMenuItem(ScResId(SCSTR_FILTER_CONDITION), true, false)) + { + pSubMenu->addMenuItem( + ScResId(SCSTR_FILTER_EMPTY), new AutoFilterAction(this, AutoFilterMode::Empty)); + pSubMenu->addMenuItem( + ScResId(SCSTR_FILTER_NOTEMPTY), new AutoFilterAction(this, AutoFilterMode::NonEmpty)); + pSubMenu->addMenuItem( + ScResId(SCSTR_TOP10FILTER), new AutoFilterAction(this, AutoFilterMode::Top10)); + pSubMenu->addMenuItem( + ScResId(SCSTR_BOTTOM10FILTER), new AutoFilterAction(this, AutoFilterMode::Bottom10)); + pSubMenu->addSeparator(); + pSubMenu->addMenuItem( + ScResId(SCSTR_STDFILTER), new AutoFilterAction(this, AutoFilterMode::Custom)); + pSubMenu->resizeToFitMenuItems(); + } + if (aEntries.size()) + mpAutoFilterPopup->addMenuItem( + ScResId(SCSTR_CLEAR_FILTER), new AutoFilterAction(this, AutoFilterMode::Clear)); + + mpAutoFilterPopup->initMembers(nMaxTextWidth + 20); // 20 pixel estimated for the checkbox + + ScCheckListMenuControl::Config aConfig; + aConfig.mbAllowEmptySet = false; + aConfig.mbRTL = mrViewData.GetDocument().IsLayoutRTL(mrViewData.GetTabNo()); + mpAutoFilterPopup->setConfig(aConfig); + if (IsMouseCaptured()) + ReleaseMouse(); + mpAutoFilterPopup->launch(pPopupParent, aCellRect); + + // remember filter rules before modification + mpAutoFilterPopup->getResult(aSaveAutoFilterResult); + + collectUIInformation(OUString::number(nRow), OUString::number(nCol),"AUTOFILTER"); +} + +void ScGridWindow::RefreshAutoFilterButton(const ScAddress& rPos) +{ + if (mpFilterButton) + { + bool bFilterActive = IsAutoFilterActive(rPos.Col(), rPos.Row(), rPos.Tab()); + mpFilterButton->setHasHiddenMember(bFilterActive); + mpFilterButton->setPopupPressed(false); + mpFilterButton->draw(); + } +} + +void ScGridWindow::UpdateAutoFilterFromMenu(AutoFilterMode eMode) +{ + // Terminate autofilter popup now when there is no further user input needed + bool bColorMode = eMode == AutoFilterMode::TextColor || eMode == AutoFilterMode::BackgroundColor; + if (!bColorMode) + mpAutoFilterPopup->terminateAllPopupMenus(); + + const AutoFilterData* pData = + static_cast(mpAutoFilterPopup->getExtendedData()); + + if (!pData) + return; + + const ScAddress& rPos = pData->maPos; + ScDBData* pDBData = pData->mpData; + if (!pDBData) + return; + + ScDocument& rDoc = mrViewData.GetDocument(); + svl::SharedStringPool& rPool = rDoc.GetSharedStringPool(); + switch (eMode) + { + case AutoFilterMode::SortAscending: + case AutoFilterMode::SortDescending: + { + SCCOL nCol = rPos.Col(); + ScSortParam aSortParam; + pDBData->GetSortParam(aSortParam); + if (nCol < aSortParam.nCol1 || nCol > aSortParam.nCol2) + // out of bound + return; + + bool bHasHeader = pDBData->HasHeader(); + + aSortParam.bHasHeader = bHasHeader; + aSortParam.bByRow = true; + aSortParam.bCaseSens = false; + aSortParam.bNaturalSort = false; + aSortParam.aDataAreaExtras.mbCellNotes = false; + aSortParam.aDataAreaExtras.mbCellDrawObjects = true; + aSortParam.aDataAreaExtras.mbCellFormats = true; + aSortParam.bInplace = true; + aSortParam.maKeyState[0].bDoSort = true; + aSortParam.maKeyState[0].nField = nCol; + aSortParam.maKeyState[0].bAscending = (eMode == AutoFilterMode::SortAscending); + aSortParam.maKeyState[0].aColorSortMode = ScColorSortMode::None; + + for (size_t i = 1; i < aSortParam.GetSortKeyCount(); ++i) + aSortParam.maKeyState[i].bDoSort = false; + + mrViewData.GetViewShell()->UISort(aSortParam); + return; + } + case AutoFilterMode::Custom: + { + ScRange aRange; + pDBData->GetArea(aRange); + mrViewData.GetView()->MarkRange(aRange); + mrViewData.GetView()->SetCursor(rPos.Col(), rPos.Row()); + mrViewData.GetDispatcher().Execute(SID_FILTER, SfxCallMode::SLOT | SfxCallMode::RECORD); + return; + } + default: + ; + } + + ScQueryParam aParam; + pDBData->GetQueryParam(aParam); + + if (eMode == AutoFilterMode::Normal) + { + // Do not recreate autofilter rules if there are no changes from the user + ScCheckListMenuControl::ResultType aResult; + mpAutoFilterPopup->getResult(aResult); + + if (aResult == aSaveAutoFilterResult) + { + SAL_INFO("sc.ui", "Apply autofilter to data when entries are the same"); + + if (!mpAutoFilterPopup->isAllSelected()) + { + // Apply autofilter to data + ScQueryEntry* pEntry = aParam.FindEntryByField(rPos.Col(), true); + pEntry->bDoQuery = true; + pEntry->nField = rPos.Col(); + pEntry->eConnect = SC_AND; + pEntry->eOp = SC_EQUAL; + mrViewData.GetView()->Query(aParam, nullptr, true); + } + + return; + } + } + + // Remove old entries in auto-filter rules + if (!bColorMode) + { + aParam.RemoveAllEntriesByField(rPos.Col()); + + // tdf#46184 reset filter options to default values + aParam.eSearchType = utl::SearchParam::SearchType::Normal; + aParam.bCaseSens = false; + aParam.bDuplicate = true; + aParam.bInplace = true; + } + + if (eMode != AutoFilterMode::Clear + && !(eMode == AutoFilterMode::Normal && mpAutoFilterPopup->isAllSelected())) + { + // Try to use the existing entry for the column (if one exists). + ScQueryEntry* pEntry = aParam.FindEntryByField(rPos.Col(), true); + + if (!pEntry) + // Something went terribly wrong! + return; + + if (ScTabViewShell::isAnyEditViewInRange(mrViewData.GetViewShell(), /*bColumns*/ false, aParam.nRow1, aParam.nRow2)) + return; + + pEntry->bDoQuery = true; + pEntry->nField = rPos.Col(); + pEntry->eConnect = SC_AND; + + switch (eMode) + { + case AutoFilterMode::Normal: + { + pEntry->eOp = SC_EQUAL; + + ScCheckListMenuControl::ResultType aResult; + mpAutoFilterPopup->getResult(aResult); + + ScQueryEntry::QueryItemsType& rItems = pEntry->GetQueryItems(); + rItems.clear(); + std::for_each(aResult.begin(), aResult.end(), AddItemToEntry(rItems, rPool)); + } + break; + case AutoFilterMode::Top10: + pEntry->eOp = SC_TOPVAL; + pEntry->GetQueryItem().meType = ScQueryEntry::ByString; + pEntry->GetQueryItem().maString = rPool.intern("10"); + break; + case AutoFilterMode::Bottom10: + pEntry->eOp = SC_BOTVAL; + pEntry->GetQueryItem().meType = ScQueryEntry::ByString; + pEntry->GetQueryItem().maString = rPool.intern("10"); + break; + case AutoFilterMode::Empty: + pEntry->SetQueryByEmpty(); + break; + case AutoFilterMode::NonEmpty: + pEntry->SetQueryByNonEmpty(); + break; + case AutoFilterMode::TextColor: + case AutoFilterMode::BackgroundColor: + assert(false && "should be handled by AutoFilterColorAction::execute"); + break; + break; + default: + // We don't know how to handle this! + return; + } + } + + mrViewData.GetView()->Query(aParam, nullptr, true); + pDBData->SetQueryParam(aParam); +} + +namespace { + +void getCellGeometry(Point& rScrPos, Size& rScrSize, const ScViewData& rViewData, SCCOL nCol, SCROW nRow, ScSplitPos eWhich) +{ + // Get the screen position of the cell. + rScrPos = rViewData.GetScrPos(nCol, nRow, eWhich); + + // Get the screen size of the cell. + tools::Long nSizeX, nSizeY; + rViewData.GetMergeSizePixel(nCol, nRow, nSizeX, nSizeY); + rScrSize = Size(nSizeX-1, nSizeY-1); +} + +} + +void ScGridWindow::LaunchPageFieldMenu( SCCOL nCol, SCROW nRow ) +{ + if (nCol == 0) + // We assume that the page field button is located in cell to the immediate left. + return; + + SCTAB nTab = mrViewData.GetTabNo(); + ScDPObject* pDPObj = mrViewData.GetDocument().GetDPAtCursor(nCol, nRow, nTab); + if (!pDPObj) + return; + + Point aScrPos; + Size aScrSize; + getCellGeometry(aScrPos, aScrSize, mrViewData, nCol, nRow, eWhich); + bool bLOK = comphelper::LibreOfficeKit::isActive(); + DPLaunchFieldPopupMenu(bLOK ? aScrPos : OutputToScreenPixel(aScrPos), aScrSize, ScAddress(nCol-1, nRow, nTab), pDPObj); +} + +void ScGridWindow::LaunchDPFieldMenu( SCCOL nCol, SCROW nRow ) +{ + SCTAB nTab = mrViewData.GetTabNo(); + ScDPObject* pDPObj = mrViewData.GetDocument().GetDPAtCursor(nCol, nRow, nTab); + if (!pDPObj) + return; + + Point aScrPos; + Size aScrSize; + getCellGeometry(aScrPos, aScrSize, mrViewData, nCol, nRow, eWhich); + bool bLOK = comphelper::LibreOfficeKit::isActive(); + DPLaunchFieldPopupMenu(bLOK ? aScrPos : OutputToScreenPixel(aScrPos), aScrSize, ScAddress(nCol, nRow, nTab), pDPObj); +} + +void ScGridWindow::ShowFilterMenu(weld::Window* pParent, const tools::Rectangle& rCellRect, bool bLayoutRTL) +{ + auto nSizeX = rCellRect.GetWidth(); + + // minimum width in pixel + if (comphelper::LibreOfficeKit::isActive()) + { + const tools::Long nMinLOKWinWidth = o3tl::convert(STD_COL_WIDTH * 13 / 10, o3tl::Length::twip, o3tl::Length::px); + if (nSizeX < nMinLOKWinWidth) + nSizeX = nMinLOKWinWidth; + } + + weld::TreeView& rFilterBox = mpFilterBox->get_widget(); + int nEntryCount = rFilterBox.n_children(); + if (nEntryCount > SC_FILTERLISTBOX_LINES) + nEntryCount = SC_FILTERLISTBOX_LINES; + auto nHeight = rFilterBox.get_height_rows(nEntryCount); + rFilterBox.set_size_request(-1, nHeight); + Size aSize(rFilterBox.get_preferred_size()); + auto nMaxToExpandTo = std::min(nSizeX, static_cast(300)); // do not over do it (Pixel) + if (aSize.Width() < nMaxToExpandTo) + aSize.setWidth(nMaxToExpandTo); + + aSize.AdjustWidth(4); // add a little margin + nSizeX += 4; + aSize.AdjustHeight(4); + + tools::Rectangle aCellRect(rCellRect); + aCellRect.AdjustLeft(-2); // offset the little margin above + + if (!bLayoutRTL && aSize.Width() > nSizeX) + { + // move popup position + tools::Long nDiff = aSize.Width() - nSizeX; + tools::Long nNewX = aCellRect.Left() - nDiff; + if ( nNewX < 0 ) + nNewX = 0; + aCellRect.SetLeft( nNewX ); + } + + rFilterBox.set_size_request(aSize.Width(), aSize.Height()); + + if (IsMouseCaptured()) + ReleaseMouse(); + mpFilterBox->popup_at_rect(pParent, aCellRect); +} + +void ScGridWindow::DoScenarioMenu( const ScRange& rScenRange ) +{ + bool bMenuAtTop = true; + + ScDocument& rDoc = mrViewData.GetDocument(); + mpFilterBox.reset(); + + SCCOL nCol = rScenRange.aEnd.Col(); // Cell is below the Buttons + SCROW nRow = rScenRange.aStart.Row(); + if (nRow == 0) + { + nRow = rScenRange.aEnd.Row() + 1; // Range at very the top -> Button below + if (nRow>rDoc.MaxRow()) nRow = rDoc.MaxRow(); + bMenuAtTop = false; + } + + SCTAB nTab = mrViewData.GetTabNo(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + + tools::Long nSizeX = 0; + tools::Long nSizeY = 0; + mrViewData.GetMergeSizePixel( nCol, nRow, nSizeX, nSizeY ); + // The button height should not use the merged cell height, should still use single row height + nSizeY = ScViewData::ToPixel(rDoc.GetRowHeight(nRow, nTab), mrViewData.GetPPTY()); + Point aPos = mrViewData.GetScrPos( nCol, nRow, eWhich ); + if ( bLayoutRTL ) + aPos.AdjustX( -nSizeX ); + tools::Rectangle aCellRect(aPos, Size(nSizeX, nSizeY)); + aCellRect.AdjustTop( -nSizeY ); + aCellRect.AdjustBottom( -(nSizeY - 1) ); + if (!bMenuAtTop) + { + Size aButSize = mrViewData.GetScenButSize(); + aCellRect.AdjustBottom(aButSize.Height()); + } + + // Place the ListBox directly below the black line of the cell grid + // (It looks odd if the line gets hidden...) + + weld::Window* pParent = weld::GetPopupParent(*this, aCellRect); + mpFilterBox = std::make_shared(pParent, this, nCol, nRow, ScFilterBoxMode::Scenario); + mpFilterBox->connect_closed(LINK(this, ScGridWindow, PopupModeEndHdl)); + weld::TreeView& rFilterBox = mpFilterBox->get_widget(); + rFilterBox.set_direction(bLayoutRTL); // Fix for bug fdo#44925 use sheet direction for widget RTL/LTR + + // Listbox fill + rFilterBox.freeze(); + OUString aCurrent; + OUString aTabName; + SCTAB nTabCount = rDoc.GetTableCount(); + for (SCTAB i=nTab+1; i 0 ) + { + nPos = 0; + } + if (nPos != -1) + { + rFilterBox.set_cursor(nPos); + rFilterBox.select(nPos); + } + mpFilterBox->EndInit(); +} + +void ScGridWindow::LaunchDataSelectMenu(const SCCOL nCol, const SCROW nRow) +{ + mpFilterBox.reset(); + + ScDocument& rDoc = mrViewData.GetDocument(); + const SCTAB nTab = mrViewData.GetTabNo(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + + tools::Long nSizeX = 0; + tools::Long nSizeY = 0; + mrViewData.GetMergeSizePixel( nCol, nRow, nSizeX, nSizeY ); + Point aPos = mrViewData.GetScrPos( nCol, nRow, eWhich ); + bool bLOKActive = comphelper::LibreOfficeKit::isActive(); + + if (bLOKActive) + { + // aPos is now view-zoom adjusted and in pixels an more importantly this is pixel aligned to the view-zoom, + // but once we use this to set the position of the floating window, it has no information of view-zoom level + // so if we don't reverse the zoom now, a simple PixelToLogic(aPos, MapMode(MapUnit::MapTwip)) employed in + // FloatingWindow::ImplCalcPos will produce a 'scaled' twips position which will again get zoom scaled in the + // client (effective double scaling) causing wrong positioning/size. + double fZoomX(mrViewData.GetZoomX()); + double fZoomY(mrViewData.GetZoomY()); + aPos.setX(aPos.getX() / fZoomX); + aPos.setY(aPos.getY() / fZoomY); + nSizeX = nSizeX / fZoomX; + nSizeY = nSizeY / fZoomY; + } + + if ( bLayoutRTL ) + aPos.AdjustX( -nSizeX ); + tools::Rectangle aCellRect(aPos, Size(nSizeX, nSizeY)); + + weld::Window* pParent = comphelper::LibreOfficeKit::isActive() ? GetFrameWeld() : weld::GetPopupParent(*this, aCellRect); + mpFilterBox = std::make_shared(pParent, this, nCol, nRow, ScFilterBoxMode::DataSelect); + mpFilterBox->connect_closed(LINK(this, ScGridWindow, PopupModeEndHdl)); + weld::TreeView& rFilterBox = mpFilterBox->get_widget(); + rFilterBox.set_direction(bLayoutRTL); // Fix for bug fdo#44925 use sheet direction for widget RTL/LTR + + // SetSize later + + const sal_uInt32 nIndex = rDoc.GetAttr(nCol, nRow, nTab, ATTR_VALIDDATA)->GetValue(); + const ScValidationData* pData = nIndex ? rDoc.GetValidationEntry(nIndex) : nullptr; + + bool bEmpty = false; + std::vector aStrings; // case sensitive + // Fill List + rDoc.GetDataEntries(nCol, nRow, nTab, aStrings, true /* bValidation */); + + // IsIgnoreBlank allows blank values. Don't add empty string unless "Allow Empty Cells" + if (pData && !pData->IsIgnoreBlank()) + { + auto lambda = [](const ScTypedStrData& rStr) { return rStr.GetString().isEmpty(); }; + std::erase_if(aStrings, lambda); + } + + if (aStrings.empty()) + bEmpty = true; + + if (!bEmpty) + { + rFilterBox.freeze(); + + // Fill Listbox + bool bWait = aStrings.size() > 100; + + if (bWait) + EnterWait(); + + for (const auto& rString : aStrings) + { + const OUString& rFilterString = rString.GetString(); + rFilterBox.append_text(rFilterString); + } + + if (bWait) + LeaveWait(); + + rFilterBox.thaw(); + + ShowFilterMenu(pParent, aCellRect, bLayoutRTL); + } + + sal_Int32 nSelPos = -1; + + if ( nIndex ) + { + if (pData) + { + std::unique_ptr pNew; + OUString aDocStr = rDoc.GetString(nCol, nRow, nTab); + if ( rDoc.HasValueData( nCol, nRow, nTab ) ) + { + double fVal = rDoc.GetValue(ScAddress(nCol, nRow, nTab)); + pNew.reset(new ScTypedStrData(aDocStr, fVal, fVal, ScTypedStrData::Value)); + } + else + pNew.reset(new ScTypedStrData(aDocStr, 0.0, 0.0, ScTypedStrData::Standard)); + + if (pData->GetListType() == css::sheet::TableValidationVisibility::SORTEDASCENDING) + { + auto it = std::lower_bound(aStrings.begin(), aStrings.end(), *pNew, ScTypedStrData::LessCaseSensitive()); + if (it != aStrings.end() && ScTypedStrData::EqualCaseSensitive()(*it, *pNew)) + nSelPos = static_cast(std::distance(aStrings.begin(), it)); + } + else + { + auto it = std::find_if(aStrings.begin(), aStrings.end(), FindTypedStrData(*pNew, true)); + if (it != aStrings.end()) + nSelPos = static_cast(std::distance(aStrings.begin(), it)); + } + } + } + + // Do not show an empty selection List: + + if ( bEmpty ) + { + mpFilterBox.reset(); + } + else + { + rFilterBox.grab_focus(); + + if (rFilterBox.n_children()) + { + if (nSelPos != -1) + rFilterBox.set_cursor(nSelPos); + else + rFilterBox.set_cursor(0); + } + // Select only after GrabFocus, so that the focus rectangle gets correct + if (nSelPos != -1) + rFilterBox.select(nSelPos); + else + rFilterBox.unselect_all(); + + mpFilterBox->EndInit(); + } + collectUIInformation(OUString::number(nRow), OUString::number(nCol),"SELECTMENU"); +} + +void ScGridWindow::FilterSelect( sal_uLong nSel ) +{ + weld::TreeView& rFilterBox = mpFilterBox->get_widget(); + OUString aString = rFilterBox.get_text(static_cast(nSel)); + + SCCOL nCol = mpFilterBox->GetCol(); + SCROW nRow = mpFilterBox->GetRow(); + switch (mpFilterBox->GetMode()) + { + case ScFilterBoxMode::DataSelect: + ExecDataSelect(nCol, nRow, aString); + break; + case ScFilterBoxMode::Scenario: + mrViewData.GetView()->UseScenario(aString); + break; + } + + // coverity[check_after_deref] - could be destroyed by ExecDataSelect + if (mpFilterBox) + mpFilterBox->popdown(); + + GrabFocus(); // Otherwise the focus would be wrong on OS/2 +} + +void ScGridWindow::ExecDataSelect( SCCOL nCol, SCROW nRow, const OUString& rStr ) +{ + ScModule* pScMod = SC_MOD(); + ScInputHandler* pViewHdl = pScMod->GetInputHdl(mrViewData.GetViewShell()); + if (pViewHdl && mrViewData.HasEditView(mrViewData.GetActivePart())) + pViewHdl->CancelHandler(); + + SCTAB nTab = mrViewData.GetTabNo(); + ScViewFunc* pView = mrViewData.GetView(); + pView->EnterData( nCol, nRow, nTab, rStr ); + + // #i52307# CellContentChanged is not in EnterData so it isn't called twice + // if the cursor is moved afterwards. + pView->CellContentChanged(); +} + +void ScGridWindow::MoveMouseStatus( ScGridWindow& rDestWin ) +{ + if (nButtonDown) + { + rDestWin.nButtonDown = nButtonDown; + rDestWin.nMouseStatus = nMouseStatus; + } + + if (bRFMouse) + { + rDestWin.bRFMouse = bRFMouse; + rDestWin.bRFSize = bRFSize; + rDestWin.nRFIndex = nRFIndex; + rDestWin.nRFAddX = nRFAddX; + rDestWin.nRFAddY = nRFAddY; + bRFMouse = false; + } + + if (nPagebreakMouse) + { + rDestWin.nPagebreakMouse = nPagebreakMouse; + rDestWin.nPagebreakBreak = nPagebreakBreak; + rDestWin.nPagebreakPrev = nPagebreakPrev; + rDestWin.aPagebreakSource = aPagebreakSource; + rDestWin.aPagebreakDrag = aPagebreakDrag; + nPagebreakMouse = SC_PD_NONE; + } +} + +bool ScGridWindow::TestMouse( const MouseEvent& rMEvt, bool bAction ) +{ + // MouseEvent buttons must only be checked if bAction==TRUE + // to allow changing the mouse pointer in MouseMove, + // but not start AutoFill with right button (#74229#). + // with bAction==sal_True, SetFillMode / SetDragMode is called + + if ( bAction && !rMEvt.IsLeft() ) + return false; + + bool bNewPointer = false; + + SfxInPlaceClient* pClient = mrViewData.GetViewShell()->GetIPClient(); + bool bOleActive = ( pClient && pClient->IsObjectInPlaceActive() ); + + if ( mrViewData.IsActive() && !bOleActive ) + { + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + + // Auto-Fill + + ScRange aMarkRange; + if (mrViewData.GetSimpleArea( aMarkRange ) == SC_MARK_SIMPLE) + { + if (aMarkRange.aStart.Tab() == mrViewData.GetTabNo() && mpAutoFillRect) + { + Point aMousePos = rMEvt.GetPosPixel(); + if (mpAutoFillRect->Contains(aMousePos)) + { + SetPointer( PointerStyle::Cross ); //! bold cross ? + if (bAction) + { + SCCOL nX = aMarkRange.aEnd.Col(); + SCROW nY = aMarkRange.aEnd.Row(); + + if ( lcl_IsEditableMatrix( mrViewData.GetDocument(), aMarkRange ) ) + mrViewData.SetDragMode( + aMarkRange.aStart.Col(), aMarkRange.aStart.Row(), nX, nY, ScFillMode::MATRIX ); + else + mrViewData.SetFillMode( + aMarkRange.aStart.Col(), aMarkRange.aStart.Row(), nX, nY ); + + // The simple selection must also be recognized when dragging, + // where the Marking flag is set and MarkToSimple won't work anymore. + mrViewData.GetMarkData().MarkToSimple(); + } + bNewPointer = true; + } + } + } + + // Embedded rectangle + + if (rDoc.IsEmbedded()) + { + ScRange aRange; + rDoc.GetEmbedded( aRange ); + if ( mrViewData.GetTabNo() == aRange.aStart.Tab() ) + { + Point aStartPos = mrViewData.GetScrPos( aRange.aStart.Col(), aRange.aStart.Row(), eWhich ); + Point aEndPos = mrViewData.GetScrPos( aRange.aEnd.Col()+1, aRange.aEnd.Row()+1, eWhich ); + Point aMousePos = rMEvt.GetPosPixel(); + if ( bLayoutRTL ) + { + aStartPos.AdjustX(2 ); + aEndPos.AdjustX(2 ); + } + bool bTop = ( aMousePos.X() >= aStartPos.X()-3 && aMousePos.X() <= aStartPos.X()+1 && + aMousePos.Y() >= aStartPos.Y()-3 && aMousePos.Y() <= aStartPos.Y()+1 ); + bool bBottom = ( aMousePos.X() >= aEndPos.X()-3 && aMousePos.X() <= aEndPos.X()+1 && + aMousePos.Y() >= aEndPos.Y()-3 && aMousePos.Y() <= aEndPos.Y()+1 ); + if ( bTop || bBottom ) + { + SetPointer( PointerStyle::Cross ); + if (bAction) + { + ScFillMode nMode = bTop ? ScFillMode::EMBED_LT : ScFillMode::EMBED_RB; + mrViewData.SetDragMode( + aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), nMode ); + } + bNewPointer = true; + } + } + } + } + + if (!bNewPointer && bAction) + { + mrViewData.ResetFillMode(); + } + + return bNewPointer; +} + +void ScGridWindow::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if (SfxLokHelper::getDeviceFormFactor() == LOKDeviceFormFactor::MOBILE) + { + ScViewFunc* pView = mrViewData.GetView(); + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + bool bRefMode = pViewShell && pViewShell->IsRefInputMode(); + + Point aPos(rMEvt.GetPosPixel()); + SCCOL nPosX; + SCROW nPosY; + mrViewData.GetPosFromPixel(aPos.X(), aPos.Y(), eWhich, nPosX, nPosY); + + if (bRefMode && pView->GetFunctionSet().CheckRefBounds(nPosX, nPosY)) + return; + } + + nNestedButtonState = ScNestedButtonState::Down; + + MouseEventState aState; + HandleMouseButtonDown(rMEvt, aState); + if (aState.mbActivatePart) + mrViewData.GetView()->ActivatePart(eWhich); + + if ( nNestedButtonState == ScNestedButtonState::Up ) + { + // #i41690# If an object is deactivated from MouseButtonDown, it might reschedule, + // so MouseButtonUp comes before the MouseButtonDown call is finished. In this case, + // simulate another MouseButtonUp call, so the selection state is consistent. + + nButtonDown = rMEvt.GetButtons(); + FakeButtonUp(); + + if ( IsTracking() ) + EndTracking(); // normally done in VCL as part of MouseButtonUp handling + } + nNestedButtonState = ScNestedButtonState::NONE; +} + +void ScGridWindow::HandleMouseButtonDown( const MouseEvent& rMEvt, MouseEventState& rState ) +{ + // We have to check if a context menu is shown and we have an UI + // active inplace client. In that case we have to ignore the event. + // Otherwise we would crash (context menu has been + // opened by inplace client and we would deactivate the inplace client, + // the context menu is closed by VCL asynchronously which in the end + // would work on deleted objects or the context menu has no parent anymore) + SfxViewShell* pViewSh = mrViewData.GetViewShell(); + SfxInPlaceClient* pClient = pViewSh->GetIPClient(); + if ( pClient && + pClient->IsObjectInPlaceActive() && + vcl::IsInPopupMenuExecute() ) + return; + + aCurMousePos = rMEvt.GetPosPixel(); + + // Filter popup is ended with its own mouse click, not when clicking into the Grid Window, + // so the following query is no longer necessary: + ClickExtern(); // deletes FilterBox when available + + HideNoteMarker(); + + bEEMouse = false; + + ScModule* pScMod = SC_MOD(); + if (pScMod->IsModalMode(mrViewData.GetSfxDocShell())) + return; + + const bool bWasMouseCaptured = IsMouseCaptured(); + SAL_WARN_IF(bWasMouseCaptured, "sc.ui", "Is there a scenario where the mouse is captured before mouse down?"); + + pScActiveViewShell = mrViewData.GetViewShell(); // if left is clicked + nScClickMouseModifier = rMEvt.GetModifier(); // to always catch a control click + + bool bDetective = mrViewData.GetViewShell()->IsAuditShell(); + bool bRefMode = mrViewData.IsRefMode(); // Start reference + bool bFormulaMode = pScMod->IsFormulaMode(); // next click -> reference + bool bEditMode = mrViewData.HasEditView(eWhich); // also in Mode==SC_INPUT_TYPE + bool bDouble = (rMEvt.GetClicks() == 2); + ScDocument& rDoc = mrViewData.GetDocument(); + bool bIsTiledRendering = comphelper::LibreOfficeKit::isActive(); + + // DeactivateIP does only happen when MarkListHasChanged + + // An error message can show up during GrabFocus call + // (for instance when renaming tables per sheet title) + + if ( !nButtonDown || !bDouble ) // single (first) click is always valid + nButtonDown = rMEvt.GetButtons(); // set nButtonDown first, so StopMarking works + + if ( ( bEditMode && mrViewData.GetActivePart() == eWhich ) || !bFormulaMode ) + GrabFocus(); + + // #i31846# need to cancel a double click if the first click has set the "ignore" state, + // but a single (first) click is always valid + if ( nMouseStatus == SC_GM_IGNORE && bDouble ) + { + nButtonDown = 0; + nMouseStatus = SC_GM_NONE; + return; + } + + if ( bDetective ) // Detectiv fill mode + { + if ( rMEvt.IsLeft() && !rMEvt.GetModifier() ) + { + Point aPos = rMEvt.GetPosPixel(); + SCCOL nPosX; + SCROW nPosY; + mrViewData.GetPosFromPixel( aPos.X(), aPos.Y(), eWhich, nPosX, nPosY ); + + SfxInt16Item aPosXItem( SID_RANGE_COL, nPosX ); + SfxInt32Item aPosYItem( SID_RANGE_ROW, nPosY ); + mrViewData.GetDispatcher().ExecuteList(SID_FILL_SELECT, + SfxCallMode::SLOT | SfxCallMode::RECORD, + { &aPosXItem, &aPosYItem }); + + } + nButtonDown = 0; + nMouseStatus = SC_GM_NONE; + return; + } + + if (!bDouble) + nMouseStatus = SC_GM_NONE; + + rState.mbActivatePart = !bFormulaMode; // Don't activate when in formula mode. + + if (bFormulaMode) + { + ScViewSelectionEngine* pSelEng = mrViewData.GetView()->GetSelEngine(); + pSelEng->SetWindow(this); + pSelEng->SetWhich(eWhich); + pSelEng->SetVisibleArea( tools::Rectangle(Point(), GetOutputSizePixel()) ); + } + + if (bEditMode && (mrViewData.GetRefTabNo() == mrViewData.GetTabNo())) + { + Point aPos = rMEvt.GetPosPixel(); + SCCOL nPosX; + SCROW nPosY; + mrViewData.GetPosFromPixel( aPos.X(), aPos.Y(), eWhich, nPosX, nPosY ); + + EditView* pEditView; + SCCOL nEditCol; + SCROW nEditRow; + mrViewData.GetEditView( eWhich, pEditView, nEditCol, nEditRow ); + SCCOL nEndCol = mrViewData.GetEditEndCol(); + SCROW nEndRow = mrViewData.GetEditEndRow(); + SCCOL nStartCol = mrViewData.GetEditStartCol(); + + if ( nPosX >= nStartCol && nPosX <= nEndCol && + nPosY >= nEditRow && nPosY <= nEndRow ) + { + // when clicking in the table EditView, always reset the focus + if (bFormulaMode) // otherwise this has already happen above + GrabFocus(); + + pScMod->SetInputMode( SC_INPUT_TABLE ); + bEEMouse = true; + + if (comphelper::LibreOfficeKit::isActive() && rDoc.IsLayoutRTL(mrViewData.GetTabNo())) + { + Point aMouse = rMEvt.GetPosPixel(); + tools::Rectangle aOutputArea = pEditView->GetOutputArea(); + comphelper::ScopeGuard aOutputGuard( + [pEditView, aOutputArea] { + pEditView->SetOutputArea(aOutputArea); + }); + + lcl_GetMirror(aMouse, aOutputArea, mrViewData.getLOKVisibleArea().GetWidth()); + pEditView->SetOutputArea(aOutputArea); + + MouseEvent aEvent(aMouse, rMEvt.GetClicks(), rMEvt.GetMode(), + rMEvt.GetButtons(), rMEvt.GetModifier()); + pEditView->MouseButtonDown( aEvent ); + } + else + pEditView->MouseButtonDown( rMEvt ); + return; + } + } + + if (pScMod->GetIsWaterCan()) + { + //! what's up with the Mac ??? + if ( rMEvt.GetModifier() + rMEvt.GetButtons() == MOUSE_RIGHT ) + { + nMouseStatus = SC_GM_WATERUNDO; + return; + } + } + + // Order that matches the displayed Cursor: + // RangeFinder, AutoFill, PageBreak, Drawing + + RfCorner rCorner = NONE; + bool bFound = HitRangeFinder(rMEvt.GetPosPixel(), rCorner, &nRFIndex, &nRFAddX, &nRFAddY); + bRFSize = (rCorner != NONE); + aRFSelectedCorned = rCorner; + + if (bFound) + { + bRFMouse = true; // the other variables are initialized above + + rState.mbActivatePart = true; // always activate ? + StartTracking(); + return; + } + + bool bCrossPointer = TestMouse( rMEvt, true ); + if ( bCrossPointer ) + { + if ( bDouble ) + mrViewData.GetView()->FillCrossDblClick(); + else + pScMod->InputEnterHandler(); // Autofill etc. + } + + if ( !bCrossPointer ) + { + nPagebreakMouse = HitPageBreak( rMEvt.GetPosPixel(), &aPagebreakSource, + &nPagebreakBreak, &nPagebreakPrev ); + if (nPagebreakMouse) + { + bPagebreakDrawn = false; + StartTracking(); + PagebreakMove( rMEvt, false ); + return; + } + } + + // in the tiled rendering case, single clicks into drawing objects take + // precedence over bEditMode + if (((!bFormulaMode && !bEditMode) || bIsTiledRendering) && rMEvt.IsLeft()) + { + if ( !bCrossPointer && DrawMouseButtonDown(rMEvt) ) + { + return; + } + + mrViewData.GetViewShell()->SetDrawShell( false ); // no Draw-object selected + + // TestMouse has already happened above + } + + Point aPos = rMEvt.GetPosPixel(); + SCCOL nPosX; + SCROW nPosY; + mrViewData.GetPosFromPixel( aPos.X(), aPos.Y(), eWhich, nPosX, nPosY ); + SCTAB nTab = mrViewData.GetTabNo(); + m_nDownPosX = nPosX; + m_nDownPosY = nPosY; + + // FIXME: this is to limit the number of rows handled in the Online + // to 1000; this will be removed again when the performance + // bottlenecks are sorted out + if ( comphelper::LibreOfficeKit::isActive() && nPosY > MAXTILEDROW - 1 ) + { + nButtonDown = 0; + nMouseStatus = SC_GM_NONE; + return; + } + + // Auto filter / pivot table / data select popup. This shouldn't activate the part. + + if ( !bDouble && !bFormulaMode && rMEvt.IsLeft() ) + { + SCCOL nRealPosX; + SCROW nRealPosY; + mrViewData.GetPosFromPixel( aPos.X(), aPos.Y(), eWhich, nRealPosX, nRealPosY, false );//the real row/col + + // show in the merged cells the filter of the first cell (nPosX instead of nRealPosX) + const ScMergeFlagAttr* pRealPosAttr = rDoc.GetAttr(nPosX, nRealPosY, nTab, ATTR_MERGE_FLAG); + if( pRealPosAttr->HasAutoFilter() ) + { + SC_MOD()->InputEnterHandler(); + if (DoAutoFilterButton(nPosX, nRealPosY, rMEvt)) + return; + } + + const ScMergeFlagAttr* pAttr = rDoc.GetAttr(nPosX, nPosY, nTab, ATTR_MERGE_FLAG); + if (pAttr->HasAutoFilter()) + { + if (DoAutoFilterButton(nPosX, nPosY, rMEvt)) + { + rState.mbActivatePart = false; + return; + } + } + + if (pAttr->HasPivotButton() || pAttr->HasPivotPopupButton() || pAttr->HasPivotMultiFieldPopupButton()) + { + DoPushPivotButton(nPosX, nPosY, rMEvt, pAttr->HasPivotButton(), + pAttr->HasPivotPopupButton(), pAttr->HasPivotMultiFieldPopupButton()); + rState.mbActivatePart = false; + return; + } + + if (pAttr->HasPivotToggle()) + { + DoPushPivotToggle(nPosX, nPosY, rMEvt); + rState.mbActivatePart = false; + } + + // List Validity drop-down button + + if ( bListValButton ) + { + tools::Rectangle aButtonRect = GetListValButtonRect( aListValPos ); + if ( aButtonRect.Contains( aPos ) ) + { + // tdf#149609 if we captured the mouse in the course of this function + // release it before showing the data select menu to undo any unhelpful + // seleng capture + if (!bWasMouseCaptured && IsMouseCaptured()) + ReleaseMouse(); + + LaunchDataSelectMenu( aListValPos.Col(), aListValPos.Row() ); + + nMouseStatus = SC_GM_FILTER; // not set in DoAutoFilterMenue for bDataSelect + rState.mbActivatePart = false; + return; + } + } + } + + // scenario selection + + ScRange aScenRange; + if ( rMEvt.IsLeft() && HasScenarioButton( aPos, aScenRange ) ) + { + // tdf#149609 if we captured the mouse in the course of this function + // release it before showing the data scenario menu to undo any unhelpful + // seleng capture + if (!bWasMouseCaptured && IsMouseCaptured()) + ReleaseMouse(); + + DoScenarioMenu( aScenRange ); + + // Scenario selection comes from MouseButtonDown: + // The next MouseMove on the FilterBox is like a ButtonDown + nMouseStatus = SC_GM_FILTER; + return; + } + + // double click started ? + + // StopMarking can be called from DrawMouseButtonDown + + if ( nMouseStatus != SC_GM_IGNORE && !bRefMode ) + { + if ( bDouble && !bCrossPointer ) + { + if (nMouseStatus == SC_GM_TABDOWN) + nMouseStatus = SC_GM_DBLDOWN; + } + else + nMouseStatus = SC_GM_TABDOWN; + } + + // links in the edit cell + + bool bAlt = rMEvt.IsMod2(); + if ( !bAlt && rMEvt.IsLeft() && ScGlobal::ShouldOpenURL() && + GetEditUrl(rMEvt.GetPosPixel()) ) // click on link: do not move cursor + { + SetPointer( PointerStyle::RefHand ); + nMouseStatus = SC_GM_URLDOWN; // also only execute when ButtonUp + return; + } + + // Gridwin - Selection Engine + + if ( !rMEvt.IsLeft() ) + return; + + ScViewSelectionEngine* pSelEng = mrViewData.GetView()->GetSelEngine(); + pSelEng->SetWindow(this); + pSelEng->SetWhich(eWhich); + pSelEng->SetVisibleArea( tools::Rectangle(Point(), GetOutputSizePixel()) ); + + // SelMouseButtonDown on the View is still setting the bMoveIsShift flag + if ( mrViewData.GetView()->SelMouseButtonDown( rMEvt ) ) + { + if (IsMouseCaptured()) + { + // Tracking instead of CaptureMouse, so it can be canceled cleanly + //! Someday SelectionEngine should call StartTracking on its own!?! + ReleaseMouse(); + StartTracking(); + } + mrViewData.GetMarkData().SetMarking(true); + return; + } +} + +void ScGridWindow::MouseButtonUp( const MouseEvent& rMEvt ) +{ + aCurMousePos = rMEvt.GetPosPixel(); + ScDocument& rDoc = mrViewData.GetDocument(); + ScMarkData& rMark = mrViewData.GetMarkData(); + // #i41690# detect a MouseButtonUp call from within MouseButtonDown + // (possible through Reschedule from storing an OLE object that is deselected) + + if ( nNestedButtonState == ScNestedButtonState::Down ) + nNestedButtonState = ScNestedButtonState::Up; + + if (nButtonDown != rMEvt.GetButtons()) + nMouseStatus = SC_GM_IGNORE; // reset and return + + nButtonDown = 0; + + if (nMouseStatus == SC_GM_IGNORE) + { + nMouseStatus = SC_GM_NONE; + // Selection engine: cancel selection + mrViewData.GetView()->GetSelEngine()->Reset(); + rMark.SetMarking(false); + if (mrViewData.IsAnyFillMode()) + { + mrViewData.GetView()->StopRefMode(); + mrViewData.ResetFillMode(); + } + StopMarking(); + DrawEndAction(); // cancel selection/moving in drawing layer + ReleaseMouse(); + return; + } + + if (nMouseStatus == SC_GM_FILTER) + { + nMouseStatus = SC_GM_NONE; + ReleaseMouse(); + return; // nothing more should happen here + } + + ScModule* pScMod = SC_MOD(); + if (pScMod->IsModalMode(mrViewData.GetSfxDocShell())) + return; + + SfxBindings& rBindings = mrViewData.GetBindings(); + if (bEEMouse && mrViewData.HasEditView( eWhich )) + { + EditView* pEditView; + SCCOL nEditCol; + SCROW nEditRow; + mrViewData.GetEditView( eWhich, pEditView, nEditCol, nEditRow ); + + if (comphelper::LibreOfficeKit::isActive() && rDoc.IsLayoutRTL(mrViewData.GetTabNo())) + { + Point aMouse = rMEvt.GetPosPixel(); + tools::Rectangle aOutputArea = pEditView->GetOutputArea(); + comphelper::ScopeGuard aOutputGuard( + [pEditView, aOutputArea] { + pEditView->SetOutputArea(aOutputArea); + }); + + lcl_GetMirror(aMouse, aOutputArea, mrViewData.getLOKVisibleArea().GetWidth()); + pEditView->SetOutputArea(aOutputArea); + + MouseEvent aEvent(aMouse, rMEvt.GetClicks(), rMEvt.GetMode(), + rMEvt.GetButtons(), rMEvt.GetModifier()); + pEditView->MouseButtonUp( aEvent ); + } + else + pEditView->MouseButtonUp( rMEvt ); + + if ( rMEvt.IsMiddle() && + GetSettings().GetMouseSettings().GetMiddleButtonAction() == MouseMiddleButtonAction::PasteSelection ) + { + // EditView may have pasted from selection + pScMod->InputChanged( pEditView ); + } + else + pScMod->InputSelection( pEditView ); // parentheses etc. + + mrViewData.GetView()->InvalidateAttribs(); + rBindings.Invalidate( SID_HYPERLINK_GETLINK ); + bEEMouse = false; + return; + } + + if (bDPMouse) + { + DPMouseButtonUp( rMEvt ); // resets bDPMouse + return; + } + + if (bRFMouse) + { + RFMouseMove( rMEvt, true ); // Again the proper range + bRFMouse = false; + SetPointer( PointerStyle::Arrow ); + ReleaseMouse(); + return; + } + + if (nPagebreakMouse) + { + PagebreakMove( rMEvt, true ); + nPagebreakMouse = SC_PD_NONE; + SetPointer( PointerStyle::Arrow ); + ReleaseMouse(); + return; + } + + if (nMouseStatus == SC_GM_WATERUNDO) // Undo in format paintbrush mode + { + SfxUndoManager* pMgr = mrViewData.GetDocShell()->GetUndoManager(); + if ( pMgr->GetUndoActionCount() && dynamic_cast(pMgr->GetUndoAction()) ) + pMgr->Undo(); + return; + } + + if (DrawMouseButtonUp(rMEvt)) // includes format paint brush handling for drawing objects + { + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + SfxBindings& rFrmBindings=pViewShell->GetViewFrame().GetBindings(); + rFrmBindings.Invalidate(SID_ATTR_TRANSFORM_WIDTH); + rFrmBindings.Invalidate(SID_ATTR_TRANSFORM_HEIGHT); + rFrmBindings.Invalidate(SID_ATTR_TRANSFORM_POS_X); + rFrmBindings.Invalidate(SID_ATTR_TRANSFORM_POS_Y); + rFrmBindings.Invalidate(SID_ATTR_TRANSFORM_ANGLE); + rFrmBindings.Invalidate(SID_ATTR_TRANSFORM_ROT_X); + rFrmBindings.Invalidate(SID_ATTR_TRANSFORM_ROT_Y); + rFrmBindings.Invalidate(SID_ATTR_TRANSFORM_AUTOWIDTH); + rFrmBindings.Invalidate(SID_ATTR_TRANSFORM_AUTOHEIGHT); + return; + } + + rMark.SetMarking(false); + + SetPointer( mrViewData.IsThemedCursor() ? PointerStyle::FatCross : PointerStyle::Arrow ); + + if (mrViewData.IsFillMode() || + ( mrViewData.GetFillMode() == ScFillMode::MATRIX && rMEvt.IsMod1() )) + { + nScFillModeMouseModifier = rMEvt.GetModifier(); + SCCOL nStartCol; + SCROW nStartRow; + SCCOL nEndCol; + SCROW nEndRow; + mrViewData.GetFillData( nStartCol, nStartRow, nEndCol, nEndRow ); + ScRange aDelRange; + bool bIsDel = mrViewData.GetDelMark( aDelRange ); + + ScViewFunc* pView = mrViewData.GetView(); + pView->StopRefMode(); + mrViewData.ResetFillMode(); + pView->GetFunctionSet().SetAnchorFlag( false ); // #i5819# don't use AutoFill anchor flag for selection + + if ( bIsDel ) + { + pView->MarkRange( aDelRange, false ); + pView->DeleteContents( InsertDeleteFlags::CONTENTS ); + SCTAB nTab = mrViewData.GetTabNo(); + ScRange aBlockRange( nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab ); + if ( aBlockRange != aDelRange ) + { + if ( aDelRange.aStart.Row() == nStartRow ) + aBlockRange.aEnd.SetCol( aDelRange.aStart.Col() - 1 ); + else + aBlockRange.aEnd.SetRow( aDelRange.aStart.Row() - 1 ); + pView->MarkRange( aBlockRange, false ); + } + } + else + mrViewData.GetDispatcher().Execute( FID_FILL_AUTO, SfxCallMode::SLOT | SfxCallMode::RECORD ); + } + else if (mrViewData.GetFillMode() == ScFillMode::MATRIX) + { + SCTAB nTab = mrViewData.GetTabNo(); + SCCOL nStartCol; + SCROW nStartRow; + SCCOL nEndCol; + SCROW nEndRow; + mrViewData.GetFillData( nStartCol, nStartRow, nEndCol, nEndRow ); + ScRange aBlockRange( nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab ); + SCCOL nFillCol = mrViewData.GetRefEndX(); + SCROW nFillRow = mrViewData.GetRefEndY(); + ScAddress aEndPos( nFillCol, nFillRow, nTab ); + + ScTabView* pView = mrViewData.GetView(); + pView->StopRefMode(); + mrViewData.ResetFillMode(); + pView->GetFunctionSet().SetAnchorFlag( false ); + + if ( aEndPos != aBlockRange.aEnd ) + { + mrViewData.GetDocShell()->GetDocFunc().ResizeMatrix( aBlockRange, aEndPos ); + mrViewData.GetView()->MarkRange( ScRange( aBlockRange.aStart, aEndPos ) ); + } + } + else if (mrViewData.IsAnyFillMode()) + { + // Embedded area has been changed + ScTabView* pView = mrViewData.GetView(); + pView->StopRefMode(); + mrViewData.ResetFillMode(); + pView->GetFunctionSet().SetAnchorFlag( false ); + mrViewData.GetDocShell()->UpdateOle(mrViewData); + } + + bool bRefMode = mrViewData.IsRefMode(); + if (bRefMode) + pScMod->EndReference(); + + // Format paintbrush mode (Switch) + + if (pScMod->GetIsWaterCan()) + { + // Check on undo already done above + + ScStyleSheetPool* pStylePool = mrViewData.GetDocument(). + GetStyleSheetPool(); + if ( pStylePool ) + { + SfxStyleSheet* pStyleSheet = static_cast( + pStylePool->GetActualStyleSheet()); + + if ( pStyleSheet ) + { + SfxStyleFamily eFamily = pStyleSheet->GetFamily(); + + switch ( eFamily ) + { + case SfxStyleFamily::Para: + mrViewData.GetView()->SetStyleSheetToMarked( pStyleSheet ); + mrViewData.GetView()->DoneBlockMode(); + break; + + case SfxStyleFamily::Page: + mrViewData.GetDocument().SetPageStyle( mrViewData.GetTabNo(), + pStyleSheet->GetName() ); + + ScPrintFunc( mrViewData.GetDocShell(), + mrViewData.GetViewShell()->GetPrinter(true), + mrViewData.GetTabNo() ).UpdatePages(); + + rBindings.Invalidate( SID_STATUS_PAGESTYLE ); + break; + + default: + break; + } + } + } + } + + ScDBFunc* pView = mrViewData.GetView(); + ScDocument* pBrushDoc = pView->GetBrushDocument(); + if ( pBrushDoc ) + { + pView->PasteFromClip( InsertDeleteFlags::ATTRIB, pBrushDoc ); + if ( !pView->IsPaintBrushLocked() ) + pView->ResetBrushDocument(); // invalidates pBrushDoc pointer + } + + Point aPos = rMEvt.GetPosPixel(); + SCCOL nPosX; + SCROW nPosY; + SCTAB nTab = mrViewData.GetTabNo(); + mrViewData.GetPosFromPixel( aPos.X(), aPos.Y(), eWhich, nPosX, nPosY ); + ScDPObject* pDPObj = rDoc.GetDPAtCursor( nPosX, nPosY, nTab ); + + // double click (only left button) + + bool bIsTiledRendering = comphelper::LibreOfficeKit::isActive(); + bool bDouble = ( rMEvt.GetClicks() == 2 && rMEvt.IsLeft() ); + if ( bDouble + && !bRefMode + && (nMouseStatus == SC_GM_DBLDOWN || (bIsTiledRendering && nMouseStatus != SC_GM_URLDOWN)) + && !pScMod->IsRefDialogOpen()) + { + // data pilot table + if ( pDPObj && pDPObj->GetSaveData()->GetDrillDown() ) + { + ScAddress aCellPos( nPosX, nPosY, mrViewData.GetTabNo() ); + + // Check for header drill-down first. + sheet::DataPilotTableHeaderData aData; + pDPObj->GetHeaderPositionData(aCellPos, aData); + + if ( ( aData.Flags & sheet::MemberResultFlags::HASMEMBER ) && + ! ( aData.Flags & sheet::MemberResultFlags::SUBTOTAL ) ) + { + css::sheet::DataPilotFieldOrientation nDummy; + if ( pView->HasSelectionForDrillDown( nDummy ) ) + { + // execute slot to show dialog + mrViewData.GetDispatcher().Execute( SID_OUTLINE_SHOW, SfxCallMode::SLOT | SfxCallMode::RECORD ); + } + else + { + // toggle single entry + ScDPObject aNewObj( *pDPObj ); + pDPObj->ToggleDetails( aData, &aNewObj ); + ScDBDocFunc aFunc( *mrViewData.GetDocShell() ); + aFunc.DataPilotUpdate( pDPObj, &aNewObj, true, false ); + mrViewData.GetView()->CursorPosChanged(); // shells may be switched + } + } + else + { + // Check if the data area is double-clicked. + + Sequence aFilters; + if ( pDPObj->GetDataFieldPositionData(aCellPos, aFilters) ) + mrViewData.GetView()->ShowDataPilotSourceData( *pDPObj, aFilters ); + } + + return; + } + + // Check for cell protection attribute. + const ScTableProtection* pProtect = rDoc.GetTabProtection(nTab); + bool bEditAllowed = true; + if ( pProtect && pProtect->isProtected() ) + { + bool bCellProtected = rDoc.HasAttrib(nPosX, nPosY, nTab, nPosX, nPosY, nTab, HasAttrFlags::Protected); + bool bSkipProtected = !pProtect->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS); + bool bSkipUnprotected = !pProtect->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS); + + if ( bSkipProtected && bSkipUnprotected ) + bEditAllowed = false; + else if ( (bCellProtected && bSkipProtected) || (!bCellProtected && bSkipUnprotected) ) + bEditAllowed = false; + } + + if ( bEditAllowed ) + { + // edit cell contents + mrViewData.GetViewShell()->UpdateInputHandler(); + pScMod->SetInputMode( SC_INPUT_TABLE ); + if (mrViewData.HasEditView(eWhich)) + { + // Set text cursor where clicked + EditView* pEditView = mrViewData.GetEditView( eWhich ); + MouseEvent aEditEvt( rMEvt.GetPosPixel(), 1, MouseEventModifiers::SYNTHETIC, MOUSE_LEFT, 0 ); + pEditView->MouseButtonDown( aEditEvt ); + pEditView->MouseButtonUp( aEditEvt ); + } + } + + if ( bIsTiledRendering && rMEvt.IsLeft() && mrViewData.GetView()->GetSelEngine()->SelMouseButtonUp( rMEvt ) ) + { + mrViewData.GetView()->SelectionChanged(); + } + + if ( bDouble ) + return; + } + + // Links in edit cells + + bool bAlt = rMEvt.IsMod2(); + if ( !bAlt && !bRefMode && !bDouble && nMouseStatus == SC_GM_URLDOWN ) + { + // Only execute on ButtonUp, if ButtonDown also was done on a URL + + OUString aName, aUrl, aTarget; + if ( GetEditUrl( rMEvt.GetPosPixel(), &aName, &aUrl, &aTarget ) ) + { + nMouseStatus = SC_GM_NONE; // Ignore double-click + bool isTiledRendering = comphelper::LibreOfficeKit::isActive(); + // ScGlobal::OpenURL() only understands Calc A1 style syntax. + // Convert it to Calc A1 before calling OpenURL(). + if (rDoc.GetAddressConvention() == formula::FormulaGrammar::CONV_OOO) + { + if (aUrl.startsWith("#")) { + ScGlobal::OpenURL(aUrl, aTarget, isTiledRendering); + return; + } + // On a mobile device view there is no ctrl+click and for hyperlink popup + // the cell coordinates must be sent along with click position for elegance + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + if (isTiledRendering && pViewShell && + (pViewShell->isLOKMobilePhone() || pViewShell->isLOKTablet())) + { + aPos = rMEvt.GetPosPixel(); + mrViewData.GetPosFromPixel( aPos.X(), aPos.Y(), eWhich, nPosX, nPosY ); + OString aCursor = pViewShell->GetViewData().describeCellCursorAt(nPosX, nPosY); + double fPPTX = pViewShell->GetViewData().GetPPTX(); + int mouseX = aPos.X() / fPPTX; + OString aMsg(aUrl.toUtf8() + " coordinates: " + aCursor + ", " + OString::number(mouseX)); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_HYPERLINK_CLICKED, aMsg); + } else + ScGlobal::OpenURL(aUrl, aTarget); + } + else + { + ScAddress aTempAddr; + ScAddress::ExternalInfo aExtInfo; + ScRefFlags nRes = aTempAddr.Parse(aUrl, rDoc, rDoc.GetAddressConvention(), &aExtInfo); + if (!(nRes & ScRefFlags::VALID)) + { + // Not a reference string. Pass it through unmodified. + ScGlobal::OpenURL(aUrl, aTarget); + return; + } + + OUStringBuffer aBuf; + if (aExtInfo.mbExternal) + { + // External reference. + ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + const OUString* pStr = pRefMgr->getExternalFileName(aExtInfo.mnFileId); + if (pStr) + aBuf.append(*pStr); + + OUString aRefCalcA1(aTempAddr.Format(ScRefFlags::ADDR_ABS, nullptr, formula::FormulaGrammar::CONV_OOO)); + aBuf.append("#" + aExtInfo.maTabName + "." + aRefCalcA1); + ScGlobal::OpenURL(aBuf.makeStringAndClear(), aTarget); + } + else + { + // Internal reference. + OUString aUrlCalcA1(aTempAddr.Format(ScRefFlags::ADDR_ABS_3D, &rDoc, formula::FormulaGrammar::CONV_OOO)); + aBuf.append("#" + aUrlCalcA1); + ScGlobal::OpenURL(aBuf.makeStringAndClear(), aTarget, isTiledRendering); + } + } + + // fire worksheet_followhyperlink event + uno::Reference< script::vba::XVBAEventProcessor > xVbaEvents = rDoc.GetVbaEventProcessor(); + if( xVbaEvents.is() ) try + { + aPos = rMEvt.GetPosPixel(); + nTab = mrViewData.GetTabNo(); + mrViewData.GetPosFromPixel( aPos.X(), aPos.Y(), eWhich, nPosX, nPosY ); + OUString sURL; + ScRefCellValue aCell; + if (lcl_GetHyperlinkCell(rDoc, nPosX, nPosY, nTab, aCell, sURL)) + { + ScAddress aCellPos( nPosX, nPosY, nTab ); + uno::Reference< table::XCell > xCell( new ScCellObj( mrViewData.GetDocShell(), aCellPos ) ); + uno::Sequence< uno::Any > aArgs{ uno::Any(xCell) }; + xVbaEvents->processVbaEvent( script::vba::VBAEventId::WORKSHEET_FOLLOWHYPERLINK, aArgs ); + } + } + catch( uno::Exception& ) + { + } + + return; + } + } + + // Gridwin - SelectionEngine + + // SelMouseButtonDown is called only for left button, but SelMouseButtonUp would return + // sal_True for any call, so IsLeft must be checked here, too. + + if ( !(rMEvt.IsLeft() && mrViewData.GetView()->GetSelEngine()->SelMouseButtonUp( rMEvt )) ) + return; + + mrViewData.GetView()->SelectionChanged(); + + SfxDispatcher* pDisp = mrViewData.GetViewShell()->GetDispatcher(); + bool bFormulaMode = pScMod->IsFormulaMode(); + OSL_ENSURE( pDisp || bFormulaMode, "Cursor moved on inactive View ?" ); + + // #i14927# execute SID_CURRENTCELL (for macro recording) only if there is no + // multiple selection, so the argument string completely describes the selection, + // and executing the slot won't change the existing selection (executing the slot + // here and from a recorded macro is treated equally) + if ( pDisp && !bFormulaMode && !rMark.IsMultiMarked() ) + { + OUString aAddr; // CurrentCell + if( rMark.IsMarked() ) + { + const ScRange& aScRange = rMark.GetMarkArea(); + aAddr = aScRange.Format(rDoc, ScRefFlags::RANGE_ABS); + if ( aScRange.aStart == aScRange.aEnd ) + { + // make sure there is a range selection string even for a single cell + aAddr += ":" + aAddr; + } + + //! SID_MARKAREA does not exist anymore ??? + //! What happens when selecting with the cursor ??? + } + else // only move cursor + { + ScAddress aScAddress( mrViewData.GetCurX(), mrViewData.GetCurY(), 0 ); + aAddr = aScAddress.Format(ScRefFlags::ADDR_ABS); + } + + SfxStringItem aPosItem( SID_CURRENTCELL, aAddr ); + // We don't want to align to the cursor position because if the + // cell cursor isn't visible after making selection, it would jump + // back to the origin of the selection where the cell cursor is. + SfxBoolItem aAlignCursorItem( FN_PARAM_2, false ); + pDisp->ExecuteList(SID_CURRENTCELL, + SfxCallMode::SLOT | SfxCallMode::RECORD, + { &aPosItem, &aAlignCursorItem }); + + mrViewData.GetView()->InvalidateAttribs(); + + } + mrViewData.GetViewShell()->SelectionChanged(); + + if (bIsTiledRendering && !bRefMode && !bDouble) + { + OUString aName, aUrl, aTarget; + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + if (pViewShell && nPosX == m_nDownPosX && nPosY == m_nDownPosY + && GetEditUrl(aPos, &aName, &aUrl, &aTarget)) + { + OString aMsg(aUrl.toUtf8() + " coordinates: " + + pViewShell->GetViewData().describeCellCursorAt(nPosX, nPosY) + ", " + + OString::number(aPos.X() / pViewShell->GetViewData().GetPPTX())); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_HYPERLINK_CLICKED, aMsg); + } + } + + m_nDownPosX = m_nDownPosY = -1; + + return; +} + +void ScGridWindow::FakeButtonUp() +{ + if ( nButtonDown ) + { + MouseEvent aEvent( aCurMousePos ); // nButtons = 0 -> ignore + MouseButtonUp( aEvent ); + } +} + +void ScGridWindow::MouseMove( const MouseEvent& rMEvt ) +{ + aCurMousePos = rMEvt.GetPosPixel(); + + if (rMEvt.IsLeaveWindow() && mpNoteMarker && !mpNoteMarker->IsByKeyboard()) + HideNoteMarker(); + + ScModule* pScMod = SC_MOD(); + if (pScMod->IsModalMode(mrViewData.GetSfxDocShell())) + return; + + // If the Drag&Drop is started in the edit mode then sadly nothing else is kept + if (bEEMouse && nButtonDown && !rMEvt.GetButtons()) + { + bEEMouse = false; + nButtonDown = 0; + nMouseStatus = SC_GM_NONE; + return; + } + + if (nMouseStatus == SC_GM_IGNORE) + return; + + if (nMouseStatus == SC_GM_WATERUNDO) // Undo in format paintbrush mode -> only what for Up + return; + + if ( mrViewData.GetViewShell()->IsAuditShell() ) // Detective Fill Mode + { + SetPointer( PointerStyle::Fill ); + return; + } + + bool bFormulaMode = pScMod->IsFormulaMode(); // next click -> reference + + if (bEEMouse && mrViewData.HasEditView( eWhich )) + { + EditView* pEditView; + SCCOL nEditCol; + SCROW nEditRow; + mrViewData.GetEditView( eWhich, pEditView, nEditCol, nEditRow ); + + if (comphelper::LibreOfficeKit::isActive() && mrViewData.GetDocument().IsLayoutRTL(mrViewData.GetTabNo())) + { + Point aMouse = rMEvt.GetPosPixel(); + tools::Rectangle aOutputArea = pEditView->GetOutputArea(); + comphelper::ScopeGuard aOutputGuard( + [pEditView, aOutputArea] { + pEditView->SetOutputArea(aOutputArea); + }); + + lcl_GetMirror(aMouse, aOutputArea, mrViewData.getLOKVisibleArea().GetWidth()); + pEditView->SetOutputArea(aOutputArea); + + MouseEvent aEvent(aMouse, rMEvt.GetClicks(), rMEvt.GetMode(), + rMEvt.GetButtons(), rMEvt.GetModifier()); + + pEditView->MouseMove( aEvent ); + } + else + pEditView->MouseMove( rMEvt ); + return; + } + + if (bDPMouse) + { + DPMouseMove( rMEvt ); + return; + } + + if (bRFMouse) + { + RFMouseMove( rMEvt, false ); + return; + } + + if (nPagebreakMouse) + { + PagebreakMove( rMEvt, false ); + return; + } + + // Show other mouse pointer? + + bool bEditMode = mrViewData.HasEditView(eWhich); + + //! Test if refMode dragging !!! + if ( bEditMode && (mrViewData.GetRefTabNo() == mrViewData.GetTabNo()) ) + { + Point aPos = rMEvt.GetPosPixel(); + SCCOL nPosX; + SCROW nPosY; + mrViewData.GetPosFromPixel( aPos.X(), aPos.Y(), eWhich, nPosX, nPosY ); + + EditView* pEditView; + SCCOL nEditCol; + SCROW nEditRow; + mrViewData.GetEditView( eWhich, pEditView, nEditCol, nEditRow ); + SCCOL nEndCol = mrViewData.GetEditEndCol(); + SCROW nEndRow = mrViewData.GetEditEndRow(); + + if ( nPosX >= nEditCol && nPosX <= nEndCol && + nPosY >= nEditRow && nPosY <= nEndRow ) + { + if ( !pEditView ) + { + SetPointer( PointerStyle::Text ); + return; + } + + const SvxFieldItem* pFld; + if ( comphelper::LibreOfficeKit::isActive() ) + { + Point aLogicClick = pEditView->GetOutputDevice().PixelToLogic(aPos); + pFld = pEditView->GetField( aLogicClick ); + } + else + { + pFld = pEditView->GetFieldUnderMousePointer(); + } + // Field can only be URL field + bool bAlt = rMEvt.IsMod2(); + if ( !bAlt && !nButtonDown && ScGlobal::ShouldOpenURL() && pFld ) + SetPointer( PointerStyle::RefHand ); + else if ( pEditView->GetEditEngine()->IsEffectivelyVertical() ) + SetPointer( PointerStyle::TextVertical ); + else + SetPointer( PointerStyle::Text ); + return; + } + } + + bool bWater = SC_MOD()->GetIsWaterCan() || mrViewData.GetView()->HasPaintBrush(); + if (bWater) + SetPointer( PointerStyle::Fill ); + + if (!bWater) + { + bool bCross = false; + + // range finder + + RfCorner rCorner = NONE; + if ( HitRangeFinder( rMEvt.GetPosPixel(), rCorner, nullptr, nullptr, nullptr ) ) + { + if (rCorner != NONE) + SetPointer( PointerStyle::Cross ); + else + SetPointer( PointerStyle::Hand ); + bCross = true; + } + + // Page-Break-Mode + + if ( !nButtonDown && mrViewData.IsPagebreakMode() ) + { + sal_uInt16 nBreakType = HitPageBreak( rMEvt.GetPosPixel(), nullptr, nullptr, nullptr ); + if (nBreakType != 0 ) + { + PointerStyle eNew = PointerStyle::Arrow; + switch ( nBreakType ) + { + case SC_PD_RANGE_L: + case SC_PD_RANGE_R: + case SC_PD_BREAK_H: + eNew = PointerStyle::ESize; + break; + case SC_PD_RANGE_T: + case SC_PD_RANGE_B: + case SC_PD_BREAK_V: + eNew = PointerStyle::SSize; + break; + case SC_PD_RANGE_TL: + case SC_PD_RANGE_BR: + eNew = PointerStyle::SESize; + break; + case SC_PD_RANGE_TR: + case SC_PD_RANGE_BL: + eNew = PointerStyle::NESize; + break; + } + SetPointer( eNew ); + bCross = true; + } + } + + // Show fill cursor? + + if ( !bFormulaMode && !nButtonDown ) + if (TestMouse( rMEvt, false )) + bCross = true; + + if ( nButtonDown && mrViewData.IsAnyFillMode() ) + { + SetPointer( PointerStyle::Cross ); + bCross = true; + nScFillModeMouseModifier = rMEvt.GetModifier(); // evaluated for AutoFill and Matrix + } + + if (!bCross) + { + bool bAlt = rMEvt.IsMod2(); + + if (bEditMode) // First has to be in edit mode! + SetPointer( mrViewData.IsThemedCursor() ? PointerStyle::FatCross : PointerStyle::Arrow ); + else if ( !bAlt && !nButtonDown && ScGlobal::ShouldOpenURL() && + GetEditUrl(rMEvt.GetPosPixel()) ) + SetPointer( PointerStyle::RefHand ); + else if ( DrawMouseMove(rMEvt) ) // Reset pointer + return; + } + } + + // In LOK case, avoid spurious "leavingwindow" mouse move events which has negative coordinates. + // Such events occur for some reason when a user is selecting a range, (even when not leaving the view area) + // with one or more other viewers in that sheet. + bool bSkipSelectionUpdate = comphelper::LibreOfficeKit::isActive() && + rMEvt.IsLeaveWindow() && (aCurMousePos.X() < 0 || aCurMousePos.Y() < 0); + + if (!bSkipSelectionUpdate) + mrViewData.GetView()->GetSelEngine()->SelMouseMove( rMEvt ); +} + +static void lcl_InitMouseEvent(css::awt::MouseEvent& rEvent, const MouseEvent& rEvt) +{ + rEvent.Modifiers = 0; + if ( rEvt.IsShift() ) + rEvent.Modifiers |= css::awt::KeyModifier::SHIFT; + if ( rEvt.IsMod1() ) + rEvent.Modifiers |= css::awt::KeyModifier::MOD1; + if ( rEvt.IsMod2() ) + rEvent.Modifiers |= css::awt::KeyModifier::MOD2; + if ( rEvt.IsMod3() ) + rEvent.Modifiers |= css::awt::KeyModifier::MOD3; + + rEvent.Buttons = 0; + if ( rEvt.IsLeft() ) + rEvent.Buttons |= css::awt::MouseButton::LEFT; + if ( rEvt.IsRight() ) + rEvent.Buttons |= css::awt::MouseButton::RIGHT; + if ( rEvt.IsMiddle() ) + rEvent.Buttons |= css::awt::MouseButton::MIDDLE; + + rEvent.X = rEvt.GetPosPixel().X(); + rEvent.Y = rEvt.GetPosPixel().Y(); + rEvent.ClickCount = rEvt.GetClicks(); + rEvent.PopupTrigger = false; +} + +bool ScGridWindow::PreNotify( NotifyEvent& rNEvt ) +{ + bool bDone = false; + NotifyEventType nType = rNEvt.GetType(); + if ( nType == NotifyEventType::MOUSEBUTTONUP || nType == NotifyEventType::MOUSEBUTTONDOWN ) + { + vcl::Window* pWindow = rNEvt.GetWindow(); + if (pWindow == this) + { + SfxViewFrame& rViewFrame = mrViewData.GetViewShell()->GetViewFrame(); + css::uno::Reference xController = rViewFrame.GetFrame().GetController(); + if (xController.is()) + { + ScTabViewObj* pImp = dynamic_cast( xController.get() ); + if (pImp && pImp->IsMouseListening()) + { + css::awt::MouseEvent aEvent; + lcl_InitMouseEvent( aEvent, *rNEvt.GetMouseEvent() ); + if ( rNEvt.GetWindow() ) + aEvent.Source = rNEvt.GetWindow()->GetComponentInterface(); + if ( nType == NotifyEventType::MOUSEBUTTONDOWN) + bDone = pImp->MousePressed( aEvent ); + else + bDone = pImp->MouseReleased( aEvent ); + } + } + } + } + if (bDone) // event consumed by a listener + { + if ( nType == NotifyEventType::MOUSEBUTTONDOWN ) + { + const MouseEvent* pMouseEvent = rNEvt.GetMouseEvent(); + if ( pMouseEvent->IsRight() && pMouseEvent->GetClicks() == 1 ) + { + // If a listener returned true for a right-click call, also prevent opening the context menu + // (this works only if the context menu is opened on mouse-down) + nMouseStatus = SC_GM_IGNORE; + } + } + + return true; + } + else + return Window::PreNotify( rNEvt ); +} + +void ScGridWindow::Tracking( const TrackingEvent& rTEvt ) +{ + // Since the SelectionEngine does not track, the events have to be + // handed to the different MouseHandler... + + const MouseEvent& rMEvt = rTEvt.GetMouseEvent(); + + if ( rTEvt.IsTrackingCanceled() ) // Cancel everything... + { + if (!mrViewData.GetView()->IsInActivatePart() && !SC_MOD()->IsRefDialogOpen()) + { + if (bDPMouse) + bDPMouse = false; // Paint for each bDragRect + if (bDragRect) + { + bDragRect = false; + UpdateDragRectOverlay(); + } + if (bRFMouse) + { + RFMouseMove( rMEvt, true ); // Not possible to cancel properly... + bRFMouse = false; + } + if (nPagebreakMouse) + { + bPagebreakDrawn = false; + UpdateDragRectOverlay(); + nPagebreakMouse = SC_PD_NONE; + } + + SetPointer( PointerStyle::Arrow ); + StopMarking(); + MouseButtonUp( rMEvt ); // With status SC_GM_IGNORE from StopMarking + + bool bRefMode = mrViewData.IsRefMode(); + if (bRefMode) + SC_MOD()->EndReference(); // Do not let the Dialog remain minimized + } + } + else if ( rTEvt.IsTrackingEnded() ) + { + if (!comphelper::LibreOfficeKit::isActive()) + { + // MouseButtonUp always with matching buttons (eg for test tool, # 63148 #) + // The tracking event will indicate if it was completed and not canceled. + MouseEvent aUpEvt( rMEvt.GetPosPixel(), rMEvt.GetClicks(), + rMEvt.GetMode(), nButtonDown, rMEvt.GetModifier() ); + MouseButtonUp( aUpEvt ); + } + } + else + MouseMove( rMEvt ); +} + +void ScGridWindow::StartDrag( sal_Int8 /* nAction */, const Point& rPosPixel ) +{ + if (mpFilterBox || nPagebreakMouse) + return; + + HideNoteMarker(); + + CommandEvent aDragEvent( rPosPixel, CommandEventId::StartDrag, true ); + + if (bEEMouse && mrViewData.HasEditView( eWhich )) + { + EditView* pEditView; + SCCOL nEditCol; + SCROW nEditRow; + mrViewData.GetEditView( eWhich, pEditView, nEditCol, nEditRow ); + + // don't remove the edit view while switching views + ScModule* pScMod = SC_MOD(); + pScMod->SetInEditCommand( true ); + + pEditView->Command( aDragEvent ); + + ScInputHandler* pHdl = pScMod->GetInputHdl(); + if (pHdl) + pHdl->DataChanged(); + + pScMod->SetInEditCommand( false ); + if (!mrViewData.IsActive()) // dropped to different view? + { + ScInputHandler* pViewHdl = pScMod->GetInputHdl( mrViewData.GetViewShell() ); + if ( pViewHdl && mrViewData.HasEditView( eWhich ) ) + { + pViewHdl->CancelHandler(); + ShowCursor(); // missing from KillEditView + } + } + } + else + if ( !DrawCommand(aDragEvent) ) + mrViewData.GetView()->GetSelEngine()->Command( aDragEvent ); +} + +static void lcl_SetTextCursorPos( ScViewData& rViewData, ScSplitPos eWhich, vcl::Window* pWin ) +{ + SCCOL nCol = rViewData.GetCurX(); + SCROW nRow = rViewData.GetCurY(); + tools::Rectangle aEditArea = rViewData.GetEditArea( eWhich, nCol, nRow, pWin, nullptr, true ); + aEditArea.SetRight( aEditArea.Left() ); + aEditArea = pWin->PixelToLogic( aEditArea ); + pWin->SetCursorRect( &aEditArea ); +} + +void ScGridWindow::Command( const CommandEvent& rCEvt ) +{ + // The command event is send to the window after a possible context + // menu from an inplace client is closed. Now we have the chance to + // deactivate the inplace client without any problem regarding parent + // windows and code on the stack. + CommandEventId nCmd = rCEvt.GetCommand(); + ScTabViewShell* pTabViewSh = mrViewData.GetViewShell(); + SfxInPlaceClient* pClient = pTabViewSh->GetIPClient(); + if ( pClient && + pClient->IsObjectInPlaceActive() && + nCmd == CommandEventId::ContextMenu ) + { + pTabViewSh->DeactivateOle(); + return; + } + + ScModule* pScMod = SC_MOD(); + OSL_ENSURE( nCmd != CommandEventId::StartDrag, "ScGridWindow::Command called with CommandEventId::StartDrag" ); + + if (nCmd == CommandEventId::ModKeyChange) + { + Window::Command(rCEvt); + return; + } + + if ( nCmd == CommandEventId::StartExtTextInput || + nCmd == CommandEventId::EndExtTextInput || + nCmd == CommandEventId::ExtTextInput || + nCmd == CommandEventId::CursorPos || + nCmd == CommandEventId::QueryCharPosition ) + { + bool bEditView = mrViewData.HasEditView( eWhich ); + if (!bEditView) + { + // only if no cell editview is active, look at drawview + SdrView* pSdrView = mrViewData.GetView()->GetScDrawView(); + if ( pSdrView ) + { + OutlinerView* pOlView = pSdrView->GetTextEditOutlinerView(); + if ( pOlView && pOlView->GetWindow() == this ) + { + pOlView->Command( rCEvt ); + return; // done + } + } + } + + if ( nCmd == CommandEventId::CursorPos && !bEditView ) + { + // CURSORPOS may be called without following text input, + // to set the input method window position + // -> input mode must not be started, + // manually calculate text insert position if not in input mode + + lcl_SetTextCursorPos( mrViewData, eWhich, this ); + return; + } + + ScInputHandler* pHdl = pScMod->GetInputHdl( mrViewData.GetViewShell() ); + if ( pHdl ) + { + pHdl->InputCommand( rCEvt ); + return; // done + } + + Window::Command( rCEvt ); + return; + } + + if ( nCmd == CommandEventId::PasteSelection ) + { + if ( bEEMouse ) + { + // EditEngine handles selection in MouseButtonUp - no action + // needed in command handler + } + else + { + PasteSelection( rCEvt.GetMousePosPixel() ); + } + return; + } + + if ( nCmd == CommandEventId::InputLanguageChange ) + { + // #i55929# Font and font size state depends on input language if nothing is selected, + // so the slots have to be invalidated when the input language is changed. + + SfxBindings& rBindings = mrViewData.GetBindings(); + rBindings.Invalidate( SID_ATTR_CHAR_FONT ); + rBindings.Invalidate( SID_ATTR_CHAR_FONTHEIGHT ); + return; + } + + if ( nCmd == CommandEventId::Wheel || nCmd == CommandEventId::StartAutoScroll || nCmd == CommandEventId::AutoScroll ) + { + bool bDone = mrViewData.GetView()->ScrollCommand( rCEvt, eWhich ); + if (!bDone) + Window::Command(rCEvt); + return; + } + + if (nCmd == CommandEventId::GestureZoom) + { + bool bDone = mrViewData.GetView()->GestureZoomCommand(rCEvt); + if (!bDone) + Window::Command(rCEvt); + return; + } + + // #i7560# FormulaMode check is below scrolling - scrolling is allowed during formula input + bool bDisable = pScMod->IsFormulaMode() || + pScMod->IsModalMode(mrViewData.GetSfxDocShell()); + if (bDisable) + return; + + if (nCmd != CommandEventId::ContextMenu || SC_MOD()->GetIsWaterCan()) + return; + + bool bMouse = rCEvt.IsMouseEvent(); + if ( bMouse && nMouseStatus == SC_GM_IGNORE ) + return; + + if (mrViewData.IsAnyFillMode()) + { + mrViewData.GetView()->StopRefMode(); + mrViewData.ResetFillMode(); + } + ReleaseMouse(); + StopMarking(); + + Point aPosPixel = rCEvt.GetMousePosPixel(); + Point aMenuPos = aPosPixel; + + bool bPosIsInEditView = mrViewData.HasEditView(eWhich); + SCCOL nCellX = -1; + SCROW nCellY = -1; + mrViewData.GetPosFromPixel(aPosPixel.X(), aPosPixel.Y(), eWhich, nCellX, nCellY); + // GetPosFromPixel ignores the fact that when editing a cell, the cell might grow to cover + // other rows/columns. In addition, the mouse might now be outside the edited cell. + if (bPosIsInEditView) + { + if (nCellX >= mrViewData.GetEditViewCol() && nCellX <= mrViewData.GetEditEndCol()) + nCellX = mrViewData.GetEditViewCol(); + else + bPosIsInEditView = false; + + if (nCellY >= mrViewData.GetEditViewRow() && nCellY <= mrViewData.GetEditEndRow()) + nCellY = mrViewData.GetEditViewRow(); + else + bPosIsInEditView = false; + + if (!bPosIsInEditView) + { + // Close the edit view when moving outside of the edited cell + // to avoid showing the edit popup, or providing the wrong EditView to spellcheck. + ScInputHandler* pHdl = pScMod->GetInputHdl(); + if (pHdl) + pHdl->EnterHandler(); + } + } + + bool bSpellError = false; + SCCOL nColSpellError = nCellX; + + if ( bMouse ) + { + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + const ScTableProtection* pProtect = rDoc.GetTabProtection(nTab); + bool bSelectAllowed = true; + if ( pProtect && pProtect->isProtected() ) + { + // This sheet is protected. Check if a context menu is allowed on this cell. + bool bCellProtected = rDoc.HasAttrib(nCellX, nCellY, nTab, nCellX, nCellY, nTab, HasAttrFlags::Protected); + bool bSelProtected = pProtect->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS); + bool bSelUnprotected = pProtect->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS); + + if (bCellProtected) + bSelectAllowed = bSelProtected; + else + bSelectAllowed = bSelUnprotected; + } + if (!bSelectAllowed) + // Selecting this cell is not allowed, neither is context menu. + return; + + if (mpSpellCheckCxt) + { + // Find the first string to the left for spell checking in case the current cell is empty. + ScAddress aPos(nCellX, nCellY, nTab); + ScRefCellValue aSpellCheckCell(rDoc, aPos); + while (!bPosIsInEditView && aSpellCheckCell.getType() == CELLTYPE_NONE) + { + // Loop until we get the first non-empty cell in the row. + aPos.IncCol(-1); + if (aPos.Col() < 0) + break; + + aSpellCheckCell.assign(rDoc, aPos); + } + + if (aPos.Col() >= 0 && (aSpellCheckCell.getType() == CELLTYPE_STRING || aSpellCheckCell.getType() == CELLTYPE_EDIT)) + nColSpellError = aPos.Col(); + + // Is there possibly a misspelled word somewhere in the cell? + // A "yes" does not mean that the word under the mouse pointer is wrong though. + bSpellError = (mpSpellCheckCxt->isMisspelled(nColSpellError, nCellY)); + // Avoid situations where selecting the cell-with-wrong-spelling would be bad + if (bSpellError) + { + // When the mouse is over an empty cell, text with spelling errors + // potentially could have overflowed underneath the mouse pointer + if (nColSpellError != nCellX) + { + // If the mouse is over a selected cell, only consider spell-checking + // if the cell with the misspelling is also selected. tdf#157038 + if (mrViewData.GetMarkData().IsCellMarked(nCellX, nCellY)) + bSpellError = mrViewData.GetMarkData().IsCellMarked(nColSpellError, nCellY); + } + } + } + + // #i18735# First select the item under the mouse pointer. + // This can change the selection, and the view state (edit mode, etc). + SelectForContextMenu(aPosPixel, bSpellError ? nColSpellError : nCellX, nCellY); + } + + bool bDone = false; + bool bEdit = mrViewData.HasEditView(eWhich); + + if ( !bEdit ) + { + // Edit cell with spelling errors? + // tdf#127341 the formerly used GetEditUrl(aPosPixel) additionally + // to bSpellError activated EditMode here for right-click on URL + // which prevents the regular context-menu from appearing. Since this + // is more expected than the context-menu for editing an URL, I removed + // this. If this was wanted and can be argued it might be re-activated. + // For now, reduce to spelling errors - as the original comment above + // suggests. + if (bMouse && bSpellError) + { + // GetEditUrlOrError has already moved the Cursor + + pScMod->SetInputMode( SC_INPUT_TABLE ); + bEdit = mrViewData.HasEditView(eWhich); // Did it work? + + OSL_ENSURE( bEdit, "Can not be switched in edit mode" ); + } + } + if ( bEdit ) + { + EditView* pEditView = mrViewData.GetEditView( eWhich ); // is then not 0 + + if ( !bMouse ) + { + vcl::Cursor* pCur = pEditView->GetCursor(); + if ( pCur ) + { + Point aLogicPos = pCur->GetPos(); + // use the position right of the cursor (spell popup is opened if + // the cursor is before the word, but not if behind it) + aLogicPos.AdjustX(pCur->GetWidth() ); + aLogicPos.AdjustY(pCur->GetHeight() / 2 ); // center vertically + aMenuPos = LogicToPixel( aLogicPos ); + } + } + + // if edit mode was just started above, online spelling may be incomplete + pEditView->GetEditEngine()->CompleteOnlineSpelling(); + + // IsCursorAtWrongSpelledWord could be used for !bMouse + // if there was a corresponding ExecuteSpellPopup call + + if (bSpellError) + { + // On OS/2 when clicking next to the Popup menu, the MouseButtonDown + // comes before the end of menu execute, thus the SetModified has to + // be done prior to this (Bug #40968#) + ScInputHandler* pHdl = pScMod->GetInputHdl(); + if (pHdl) + pHdl->SetModified(); + + const OUString sOldText = pHdl ? pHdl->GetEditString() : ""; + + // Only done/shown if a misspelled word is actually under the mouse pointer. + Link aLink = LINK( this, ScGridWindow, PopupSpellingHdl ); + bDone = pEditView->ExecuteSpellPopup(aMenuPos, aLink); + + // If the spelling is corrected, stop editing to flush any cached spelling info. + // Or, if no misspelled word at this position, and it wasn't initially in edit mode, + // then exit the edit mode in order to get the full context popup (not edit popup). + if (pHdl && (pHdl->GetEditString() != sOldText + || (!bDone && !bPosIsInEditView))) + { + pHdl->EnterHandler(); + } + + if (!bDone && nColSpellError != nCellX) + { + // NOTE: This call can change the selection, and the view state (edit mode, etc). + SelectForContextMenu(aPosPixel, nCellX, nCellY); + } + } + } + else if ( !bMouse ) + { + // non-edit menu by keyboard -> use lower right of cell cursor position + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTabNo = mrViewData.GetTabNo(); + bool bLayoutIsRTL = rDoc.IsLayoutRTL(nTabNo); + + SCCOL nCurX = mrViewData.GetCurX(); + SCROW nCurY = mrViewData.GetCurY(); + aMenuPos = mrViewData.GetScrPos( nCurX, nCurY, eWhich, true ); + tools::Long nSizeXPix; + tools::Long nSizeYPix; + mrViewData.GetMergeSizePixel( nCurX, nCurY, nSizeXPix, nSizeYPix ); + // fdo#55432 take the correct position for RTL sheet + aMenuPos.AdjustX(bLayoutIsRTL ? -nSizeXPix : nSizeXPix ); + aMenuPos.AdjustY(nSizeYPix ); + + ScTabViewShell* pViewSh = mrViewData.GetViewShell(); + if (pViewSh) + { + // Is a draw object selected? + + SdrView* pDrawView = pViewSh->GetScDrawView(); + if (pDrawView && pDrawView->AreObjectsMarked()) + { + // #100442#; the context menu should open in the middle of the selected objects + tools::Rectangle aSelectRect(LogicToPixel(pDrawView->GetAllMarkedBoundRect())); + aMenuPos = aSelectRect.Center(); + } + } + } + + if (bDone) + return; + + SfxDispatcher::ExecutePopup( this, &aMenuPos ); +} + +void ScGridWindow::SelectForContextMenu( const Point& rPosPixel, SCCOL nCellX, SCROW nCellY ) +{ + // #i18735# if the click was outside of the current selection, + // the cursor is moved or an object at the click position selected. + // (see SwEditWin::SelectMenuPosition in Writer) + + ScTabView* pView = mrViewData.GetView(); + ScDrawView* pDrawView = pView->GetScDrawView(); + + // check cell edit mode + + if ( mrViewData.HasEditView(eWhich) ) + { + ScModule* pScMod = SC_MOD(); + SCCOL nEditStartCol = mrViewData.GetEditViewCol(); //! change to GetEditStartCol after calcrtl is integrated + SCROW nEditStartRow = mrViewData.GetEditViewRow(); + SCCOL nEditEndCol = mrViewData.GetEditEndCol(); + SCROW nEditEndRow = mrViewData.GetEditEndRow(); + + if ( nCellX >= nEditStartCol && nCellX <= nEditEndCol && + nCellY >= nEditStartRow && nCellY <= nEditEndRow ) + { + // handle selection within the EditView + + EditView* pEditView = mrViewData.GetEditView( eWhich ); // not NULL (HasEditView) + EditEngine* pEditEngine = pEditView->GetEditEngine(); + tools::Rectangle aOutputArea = pEditView->GetOutputArea(); + tools::Rectangle aVisArea = pEditView->GetVisArea(); + + Point aTextPos = PixelToLogic( rPosPixel ); + if ( pEditEngine->IsEffectivelyVertical() ) // have to manually transform position + { + aTextPos -= aOutputArea.TopRight(); + tools::Long nTemp = -aTextPos.X(); + aTextPos.setX( aTextPos.Y() ); + aTextPos.setY( nTemp ); + } + else + aTextPos -= aOutputArea.TopLeft(); + aTextPos += aVisArea.TopLeft(); // position in the edit document + + EPosition aDocPosition = pEditEngine->FindDocPosition(aTextPos); + ESelection aCompare(aDocPosition.nPara, aDocPosition.nIndex); + ESelection aSelection = pEditView->GetSelection(); + aSelection.Adjust(); // needed for IsLess/IsGreater + if ( aCompare < aSelection || aCompare > aSelection ) + { + // clicked outside the selected text - deselect and move text cursor + MouseEvent aEvent( rPosPixel ); + pEditView->MouseButtonDown( aEvent ); + pEditView->MouseButtonUp( aEvent ); + pScMod->InputSelection( pEditView ); + } + + return; // clicked within the edit view - keep edit mode + } + else + { + // outside of the edit view - end edit mode, regardless of cell selection, then continue + pScMod->InputEnterHandler(); + } + } + + // check draw text edit mode + + Point aLogicPos = PixelToLogic( rPosPixel ); // after cell edit mode is ended + if ( pDrawView && pDrawView->GetTextEditObject() && pDrawView->GetTextEditOutlinerView() ) + { + OutlinerView* pOlView = pDrawView->GetTextEditOutlinerView(); + tools::Rectangle aOutputArea = pOlView->GetOutputArea(); + if ( aOutputArea.Contains( aLogicPos ) ) + { + // handle selection within the OutlinerView + + Outliner* pOutliner = pOlView->GetOutliner(); + const EditEngine& rEditEngine = pOutliner->GetEditEngine(); + tools::Rectangle aVisArea = pOlView->GetVisArea(); + + Point aTextPos = aLogicPos; + if ( pOutliner->IsVertical() ) // have to manually transform position + { + aTextPos -= aOutputArea.TopRight(); + tools::Long nTemp = -aTextPos.X(); + aTextPos.setX( aTextPos.Y() ); + aTextPos.setY( nTemp ); + } + else + aTextPos -= aOutputArea.TopLeft(); + aTextPos += aVisArea.TopLeft(); // position in the edit document + + EPosition aDocPosition = rEditEngine.FindDocPosition(aTextPos); + ESelection aCompare(aDocPosition.nPara, aDocPosition.nIndex); + ESelection aSelection = pOlView->GetSelection(); + aSelection.Adjust(); // needed for IsLess/IsGreater + if ( aCompare < aSelection || aCompare > aSelection ) + { + // clicked outside the selected text - deselect and move text cursor + // use DrawView to allow extra handling there (none currently) + MouseEvent aEvent( rPosPixel ); + pDrawView->MouseButtonDown( aEvent, GetOutDev() ); + pDrawView->MouseButtonUp( aEvent, GetOutDev() ); + } + + return; // clicked within the edit area - keep edit mode + } + else + { + // Outside of the edit area - end text edit mode, then continue. + // DrawDeselectAll also ends text edit mode and updates the shells. + // If the click was on the edited object, it will be selected again below. + pView->DrawDeselectAll(); + } + } + + // look for existing selection + + bool bHitSelected = false; + if ( pDrawView && pDrawView->IsMarkedObjHit( aLogicPos ) ) + { + // clicked on selected object -> don't change anything + bHitSelected = true; + } + else if ( mrViewData.GetMarkData().IsCellMarked(nCellX, nCellY) ) + { + // clicked on selected cell -> don't change anything + bHitSelected = true; + } + + // select drawing object or move cell cursor + + if ( bHitSelected ) + return; + + bool bWasDraw = ( pDrawView && pDrawView->AreObjectsMarked() ); + bool bHitDraw = false; + if ( pDrawView ) + { + pDrawView->UnmarkAllObj(); + // Unlock the Internal Layer in order to activate the context menu. + // re-lock in ScDrawView::MarkListHasChanged() + lcl_UnLockComment(pDrawView, aLogicPos, mrViewData); + bHitDraw = pDrawView->MarkObj( aLogicPos ); + // draw shell is activated in MarkListHasChanged + } + if ( !bHitDraw ) + { + pView->Unmark(); + pView->SetCursor(nCellX, nCellY); + if ( bWasDraw ) + mrViewData.GetViewShell()->SetDrawShell( false ); // switch shells + } +} + +void ScGridWindow::KeyInput(const KeyEvent& rKEvt) +{ + // Cursor control for ref input dialog + const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); + +#ifdef DBG_UTIL + + if (rKeyCode.IsMod1() && rKeyCode.IsShift()) + { + if (rKeyCode.GetCode() == KEY_F12) + { + dumpColumnInformationPixel(); + } + else if (rKeyCode.GetCode() == KEY_F11) + { + dumpGraphicInformation(); + } + else if (rKeyCode.GetCode() == KEY_F10) + { + dumpColumnInformationHmm(); + } + else if (rKeyCode.GetCode() == KEY_F6) + { + dumpCellProperties(); + } + else if (rKeyCode.GetCode() == KEY_F8) + { + dumpColumnCellStorage(); + } + else if (rKeyCode.GetCode() == KEY_F7) + { + ScDocument& rDoc = mrViewData.GetDocument(); + auto& rMapper = rDoc.GetExternalDataMapper(); + for (auto& itr : rMapper.getDataSources()) + { + itr.refresh(&rDoc); + } + return; + } + } + +#endif + + if( SC_MOD()->IsRefDialogOpen() ) + { + if( !rKeyCode.GetModifier() && (rKeyCode.GetCode() == KEY_F2) ) + { + SC_MOD()->EndReference(); + } + else if( mrViewData.GetViewShell()->MoveCursorKeyInput( rKEvt ) ) + { + ScRange aRef( + mrViewData.GetRefStartX(), mrViewData.GetRefStartY(), mrViewData.GetRefStartZ(), + mrViewData.GetRefEndX(), mrViewData.GetRefEndY(), mrViewData.GetRefEndZ() ); + SC_MOD()->SetReference( aRef, mrViewData.GetDocument() ); + } + mrViewData.GetViewShell()->SelectionChanged(); + return ; + } + else if( rKeyCode.GetCode() == KEY_RETURN && mrViewData.IsPasteMode() + && SC_MOD()->GetInputOptions().GetEnterPasteMode() ) + { + ScTabViewShell* pTabViewShell = mrViewData.GetViewShell(); + ScClipUtil::PasteFromClipboard( mrViewData, pTabViewShell, true ); + + // Clear clipboard content. + uno::Reference xSystemClipboard = + GetClipboard(); + if (xSystemClipboard.is()) + { + xSystemClipboard->setContents( + uno::Reference(), + uno::Reference()); + } + + // hide the border around the copy source + mrViewData.SetPasteMode( ScPasteFlags::NONE ); + // Clear CopySourceOverlay in each window of a split/frozen tabview + mrViewData.GetView()->UpdateCopySourceOverlay(); + return; + } + // if semi-modeless SfxChildWindow dialog above, then no KeyInputs: + else if( !mrViewData.IsAnyFillMode() ) + { + if (rKeyCode.GetCode() == KEY_ESCAPE) + { + mrViewData.SetPasteMode( ScPasteFlags::NONE ); + // Clear CopySourceOverlay in each window of a split/frozen tabview + mrViewData.GetView()->UpdateCopySourceOverlay(); + } + // query for existing note marker before calling ViewShell's keyboard handling + // which may remove the marker + bool bHadKeyMarker = mpNoteMarker && mpNoteMarker->IsByKeyboard(); + ScTabViewShell* pViewSh = mrViewData.GetViewShell(); + + if (mrViewData.GetDocShell()->GetProgress()) + return; + + if (DrawKeyInput(rKEvt, this)) + { + const vcl::KeyCode& rLclKeyCode = rKEvt.GetKeyCode(); + if (rLclKeyCode.GetCode() == KEY_DOWN + || rLclKeyCode.GetCode() == KEY_UP + || rLclKeyCode.GetCode() == KEY_LEFT + || rLclKeyCode.GetCode() == KEY_RIGHT) + { + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + SfxBindings& rBindings = pViewShell->GetViewFrame().GetBindings(); + rBindings.Invalidate(SID_ATTR_TRANSFORM_POS_X); + rBindings.Invalidate(SID_ATTR_TRANSFORM_POS_Y); + } + return; + } + + if (!mrViewData.GetView()->IsDrawSelMode() && !DrawHasMarkedObj()) // No entries in draw mode + { //! check DrawShell !!! + if (pViewSh->TabKeyInput(rKEvt)) + return; + } + else + if (pViewSh->SfxViewShell::KeyInput(rKEvt)) // from SfxViewShell + return; + + vcl::KeyCode aCode = rKEvt.GetKeyCode(); + if ( aCode.GetCode() == KEY_ESCAPE && aCode.GetModifier() == 0 ) + { + if ( bHadKeyMarker ) + HideNoteMarker(); + else + pViewSh->Escape(); + return; + } + if ( aCode.GetCode() == KEY_F1 && aCode.GetModifier() == KEY_MOD1 ) + { + // ctrl-F1 shows or hides the note or redlining info for the cursor position + // (hard-coded because F1 can't be configured) + + if ( bHadKeyMarker ) + HideNoteMarker(); // hide when previously visible + else + ShowNoteMarker( mrViewData.GetCurX(), mrViewData.GetCurY(), true ); + return; + } + if (aCode.GetCode() == KEY_BRACKETLEFT && aCode.GetModifier() == KEY_MOD1) + { + pViewSh->DetectiveMarkPred(); + return; + } + if (aCode.GetCode() == KEY_BRACKETRIGHT && aCode.GetModifier() == KEY_MOD1) + { + pViewSh->DetectiveMarkSucc(); + return; + } + + } + + Window::KeyInput(rKEvt); +} + +OUString ScGridWindow::GetSurroundingText() const +{ + bool bEditView = mrViewData.HasEditView(eWhich); + if (bEditView) + { + ScModule* pScMod = SC_MOD(); + ScInputHandler* pHdl = pScMod->GetInputHdl(mrViewData.GetViewShell()); + if (pHdl) + return pHdl->GetSurroundingText(); + } + else if (SdrView* pSdrView = mrViewData.GetView()->GetScDrawView()) + { + // if no cell editview is active, look at drawview + OutlinerView* pOlView = pSdrView->GetTextEditOutlinerView(); + if (pOlView && pOlView->GetWindow() == this) + return pOlView->GetSurroundingText(); + } + + return Window::GetSurroundingText(); +} + +Selection ScGridWindow::GetSurroundingTextSelection() const +{ + bool bEditView = mrViewData.HasEditView(eWhich); + if (bEditView) + { + ScModule* pScMod = SC_MOD(); + ScInputHandler* pHdl = pScMod->GetInputHdl(mrViewData.GetViewShell()); + if (pHdl) + return pHdl->GetSurroundingTextSelection(); + } + else if (SdrView* pSdrView = mrViewData.GetView()->GetScDrawView()) + { + // if no cell editview is active, look at drawview + OutlinerView* pOlView = pSdrView->GetTextEditOutlinerView(); + if (pOlView && pOlView->GetWindow() == this) + return pOlView->GetSurroundingTextSelection(); + } + + return Window::GetSurroundingTextSelection(); +} + +bool ScGridWindow::DeleteSurroundingText(const Selection& rSelection) +{ + bool bEditView = mrViewData.HasEditView(eWhich); + if (bEditView) + { + ScModule* pScMod = SC_MOD(); + ScInputHandler* pHdl = pScMod->GetInputHdl(mrViewData.GetViewShell()); + if (pHdl) + return pHdl->DeleteSurroundingText(rSelection); + } + else if (SdrView* pSdrView = mrViewData.GetView()->GetScDrawView()) + { + // if no cell editview is active, look at drawview + OutlinerView* pOlView = pSdrView->GetTextEditOutlinerView(); + if (pOlView && pOlView->GetWindow() == this) + return pOlView->DeleteSurroundingText(rSelection); + } + + return Window::DeleteSurroundingText(rSelection); +} + +void ScGridWindow::StopMarking() +{ + DrawEndAction(); // Cancel Select/move on Drawing-Layer + + if (nButtonDown) + { + mrViewData.GetMarkData().SetMarking(false); + nMouseStatus = SC_GM_IGNORE; + } +} + +void ScGridWindow::UpdateInputContext() +{ + bool bReadOnly = mrViewData.GetDocShell()->IsReadOnly(); + InputContextFlags nOptions = bReadOnly ? InputContextFlags::NONE : ( InputContextFlags::Text | InputContextFlags::ExtText ); + + // when font from InputContext is used, + // it must be taken from the cursor position's cell attributes + + InputContext aContext; + aContext.SetOptions( nOptions ); + SetInputContext( aContext ); +} + + // sensitive range (Pixel) +#define SCROLL_SENSITIVE 20 + +void ScGridWindow::DropScroll( const Point& rMousePos ) +{ + ScDocument& rDoc = mrViewData.GetDocument(); + SCCOL nDx = 0; + SCROW nDy = 0; + Size aSize = GetOutputSizePixel(); + + if (aSize.Width() > SCROLL_SENSITIVE * 3) + { + if ( rMousePos.X() < SCROLL_SENSITIVE && mrViewData.GetPosX(WhichH(eWhich)) > 0 ) + nDx = -1; + if ( rMousePos.X() >= aSize.Width() - SCROLL_SENSITIVE + && mrViewData.GetPosX(WhichH(eWhich)) < rDoc.MaxCol() ) + nDx = 1; + } + if (aSize.Height() > SCROLL_SENSITIVE * 3) + { + if ( rMousePos.Y() < SCROLL_SENSITIVE && mrViewData.GetPosY(WhichV(eWhich)) > 0 ) + nDy = -1; + if ( rMousePos.Y() >= aSize.Height() - SCROLL_SENSITIVE + && mrViewData.GetPosY(WhichV(eWhich)) < rDoc.MaxRow() ) + nDy = 1; + } + + if ( nDx != 0 || nDy != 0 ) + { + if ( nDx != 0 ) + mrViewData.GetView()->ScrollX( nDx, WhichH(eWhich) ); + if ( nDy != 0 ) + mrViewData.GetView()->ScrollY( nDy, WhichV(eWhich) ); + } +} + +static bool lcl_TestScenarioRedliningDrop( const ScDocument* pDoc, const ScRange& aDragRange) +{ + // Test, if a scenario is affected by a drop when turing on RedLining, + bool bReturn = false; + SCTAB nTab = aDragRange.aStart.Tab(); + SCTAB nTabCount = pDoc->GetTableCount(); + + if(pDoc->GetChangeTrack()!=nullptr) + { + if( pDoc->IsScenario(nTab) && pDoc->HasScenarioRange(nTab, aDragRange)) + { + bReturn = true; + } + else + { + for(SCTAB i=nTab+1; iIsScenario(i); i++) + { + if(pDoc->HasScenarioRange(i, aDragRange)) + { + bReturn = true; + break; + } + } + } + } + return bReturn; +} + +static ScRange lcl_MakeDropRange( const ScDocument& rDoc, SCCOL nPosX, SCROW nPosY, SCTAB nTab, const ScRange& rSource ) +{ + SCCOL nCol1 = nPosX; + SCCOL nCol2 = nCol1 + ( rSource.aEnd.Col() - rSource.aStart.Col() ); + if ( nCol2 > rDoc.MaxCol() ) + { + nCol1 -= nCol2 - rDoc.MaxCol(); + nCol2 = rDoc.MaxCol(); + } + SCROW nRow1 = nPosY; + SCROW nRow2 = nRow1 + ( rSource.aEnd.Row() - rSource.aStart.Row() ); + if ( nRow2 > rDoc.MaxRow() ) + { + nRow1 -= nRow2 - rDoc.MaxRow(); + nRow2 = rDoc.MaxRow(); + } + + return ScRange( nCol1, nRow1, nTab, nCol2, nRow2, nTab ); +} + +sal_Int8 ScGridWindow::AcceptPrivateDrop( const AcceptDropEvent& rEvt, const ScDragData& rData ) +{ + if ( rEvt.mbLeaving ) + { + bDragRect = false; + UpdateDragRectOverlay(); + return rEvt.mnAction; + } + + if ( rData.pCellTransfer ) + { + // Don't move source that would include filtered rows. + if ((rEvt.mnAction & DND_ACTION_MOVE) && rData.pCellTransfer->HasFilteredRows()) + { + if (bDragRect) + { + bDragRect = false; + UpdateDragRectOverlay(); + } + return DND_ACTION_NONE; + } + + Point aPos = rEvt.maPosPixel; + + ScDocument* pSourceDoc = rData.pCellTransfer->GetSourceDocument(); + ScDocument& rThisDoc = mrViewData.GetDocument(); + if (pSourceDoc == &rThisDoc) + { + OUString aName; + if ( rThisDoc.HasChartAtPoint(mrViewData.GetTabNo(), PixelToLogic(aPos), aName )) + { + if (bDragRect) // Remove rectangle + { + bDragRect = false; + UpdateDragRectOverlay(); + } + + //! highlight chart? (selection border?) + + sal_Int8 nRet = rEvt.mnAction; + return nRet; + } + } + + if (rData.pCellTransfer->GetDragSourceFlags() & ScDragSrc::Table) // whole sheet? + { + bool bOk = rThisDoc.IsDocEditable(); + return bOk ? rEvt.mnAction : 0; // don't draw selection frame + } + + SCCOL nPosX; + SCROW nPosY; + mrViewData.GetPosFromPixel( aPos.X(), aPos.Y(), eWhich, nPosX, nPosY ); + + ScRange aSourceRange = rData.pCellTransfer->GetRange(); + SCCOL nSourceStartX = aSourceRange.aStart.Col(); + SCROW nSourceStartY = aSourceRange.aStart.Row(); + SCCOL nSourceEndX = aSourceRange.aEnd.Col(); + SCROW nSourceEndY = aSourceRange.aEnd.Row(); + SCCOL nSizeX = nSourceEndX - nSourceStartX + 1; + SCROW nSizeY = nSourceEndY - nSourceStartY + 1; + + if ( rEvt.mnAction != DND_ACTION_MOVE ) + nSizeY = rData.pCellTransfer->GetNonFilteredRows(); // copy/link: no filtered rows + + SCCOL nNewDragX = nPosX - rData.pCellTransfer->GetDragHandleX(); + if (nNewDragX<0) nNewDragX=0; + if (nNewDragX+(nSizeX-1) > rThisDoc.MaxCol()) + nNewDragX = rThisDoc.MaxCol()-(nSizeX-1); + SCROW nNewDragY = nPosY - rData.pCellTransfer->GetDragHandleY(); + if (nNewDragY<0) nNewDragY=0; + if (nNewDragY+(nSizeY-1) > rThisDoc.MaxRow()) + nNewDragY = rThisDoc.MaxRow()-(nSizeY-1); + + // don't break scenario ranges, don't drop on filtered + SCTAB nTab = mrViewData.GetTabNo(); + ScRange aDropRange = lcl_MakeDropRange( rThisDoc, nNewDragX, nNewDragY, nTab, aSourceRange ); + if ( lcl_TestScenarioRedliningDrop( &rThisDoc, aDropRange ) || + lcl_TestScenarioRedliningDrop( pSourceDoc, aSourceRange ) || + ScViewUtil::HasFiltered( aDropRange, rThisDoc) ) + { + if (bDragRect) + { + bDragRect = false; + UpdateDragRectOverlay(); + } + return DND_ACTION_NONE; + } + + InsCellCmd eDragInsertMode = INS_NONE; + Window::PointerState aState = GetPointerState(); + + // check for datapilot item sorting + ScDPObject* pDPObj = nullptr; + if ( &rThisDoc == pSourceDoc && ( pDPObj = rThisDoc.GetDPAtCursor( nNewDragX, nNewDragY, nTab ) ) != nullptr ) + { + // drop on DataPilot table: sort or nothing + + bool bDPSort = false; + if ( rThisDoc.GetDPAtCursor( nSourceStartX, nSourceStartY, aSourceRange.aStart.Tab() ) == pDPObj ) + { + sheet::DataPilotTableHeaderData aDestData; + pDPObj->GetHeaderPositionData( ScAddress(nNewDragX, nNewDragY, nTab), aDestData ); + bool bValid = ( aDestData.Dimension >= 0 ); // dropping onto a field + + // look through the source range + for (SCROW nRow = aSourceRange.aStart.Row(); bValid && nRow <= aSourceRange.aEnd.Row(); ++nRow ) + for (SCCOL nCol = aSourceRange.aStart.Col(); bValid && nCol <= aSourceRange.aEnd.Col(); ++nCol ) + { + sheet::DataPilotTableHeaderData aSourceData; + pDPObj->GetHeaderPositionData( ScAddress( nCol, nRow, aSourceRange.aStart.Tab() ), aSourceData ); + if ( aSourceData.Dimension != aDestData.Dimension || aSourceData.MemberName.isEmpty() ) + bValid = false; // empty (subtotal) or different field + } + + if ( bValid ) + { + bool bIsDataLayout; + OUString aDimName = pDPObj->GetDimName( aDestData.Dimension, bIsDataLayout ); + const ScDPSaveDimension* pDim = pDPObj->GetSaveData()->GetExistingDimensionByName( aDimName ); + if ( pDim ) + { + ScRange aOutRange = pDPObj->GetOutRange(); + + sheet::DataPilotFieldOrientation nOrient = pDim->GetOrientation(); + if ( nOrient == sheet::DataPilotFieldOrientation_COLUMN ) + { + eDragInsertMode = INS_CELLSRIGHT; + nSizeY = aOutRange.aEnd.Row() - nNewDragY + 1; + bDPSort = true; + } + else if ( nOrient == sheet::DataPilotFieldOrientation_ROW ) + { + eDragInsertMode = INS_CELLSDOWN; + nSizeX = aOutRange.aEnd.Col() - nNewDragX + 1; + bDPSort = true; + } + } + } + } + + if ( !bDPSort ) + { + // no valid sorting in a DataPilot table -> disallow + if ( bDragRect ) + { + bDragRect = false; + UpdateDragRectOverlay(); + } + return DND_ACTION_NONE; + } + } + else if ( aState.mnState & KEY_MOD2 ) + { + if ( &rThisDoc == pSourceDoc && nTab == aSourceRange.aStart.Tab() ) + { + tools::Long nDeltaX = std::abs( static_cast< tools::Long >( nNewDragX - nSourceStartX ) ); + tools::Long nDeltaY = std::abs( static_cast< tools::Long >( nNewDragY - nSourceStartY ) ); + if ( nDeltaX <= nDeltaY ) + { + eDragInsertMode = INS_CELLSDOWN; + } + else + { + eDragInsertMode = INS_CELLSRIGHT; + } + + if ( ( eDragInsertMode == INS_CELLSDOWN && nNewDragY <= nSourceEndY && + ( nNewDragX + nSizeX - 1 ) >= nSourceStartX && nNewDragX <= nSourceEndX && + ( nNewDragX != nSourceStartX || nNewDragY >= nSourceStartY ) ) || + ( eDragInsertMode == INS_CELLSRIGHT && nNewDragX <= nSourceEndX && + ( nNewDragY + nSizeY - 1 ) >= nSourceStartY && nNewDragY <= nSourceEndY && + ( nNewDragY != nSourceStartY || nNewDragX >= nSourceStartX ) ) ) + { + if ( bDragRect ) + { + bDragRect = false; + UpdateDragRectOverlay(); + } + return DND_ACTION_NONE; + } + } + else + { + if ( static_cast< tools::Long >( nSizeX ) >= static_cast< tools::Long >( nSizeY ) ) + { + eDragInsertMode = INS_CELLSDOWN; + + } + else + { + eDragInsertMode = INS_CELLSRIGHT; + } + } + } + + if ( nNewDragX != nDragStartX || nNewDragY != nDragStartY || + nDragStartX+nSizeX-1 != nDragEndX || nDragStartY+nSizeY-1 != nDragEndY || + !bDragRect || eDragInsertMode != meDragInsertMode ) + { + nDragStartX = nNewDragX; + nDragStartY = nNewDragY; + nDragEndX = nDragStartX+nSizeX-1; + nDragEndY = nDragStartY+nSizeY-1; + bDragRect = true; + meDragInsertMode = eDragInsertMode; + + UpdateDragRectOverlay(); + } + } + + return rEvt.mnAction; +} + +sal_Int8 ScGridWindow::AcceptDrop( const AcceptDropEvent& rEvt ) +{ + const ScDragData& rData = SC_MOD()->GetDragData(); + if ( rEvt.mbLeaving ) + { + DrawMarkDropObj( nullptr ); + if ( rData.pCellTransfer ) + return AcceptPrivateDrop( rEvt, rData ); // hide drop marker for internal D&D + else + return rEvt.mnAction; + } + + if ( mrViewData.GetDocShell()->IsReadOnly() ) + return DND_ACTION_NONE; + + ScDocument& rThisDoc = mrViewData.GetDocument(); + sal_Int8 nRet = DND_ACTION_NONE; + + if (rData.pCellTransfer) + { + ScRange aSource = rData.pCellTransfer->GetRange(); + if ( aSource.aStart.Col() != 0 || aSource.aEnd.Col() != rThisDoc.MaxCol() || + aSource.aStart.Row() != 0 || aSource.aEnd.Row() != rThisDoc.MaxRow() ) + DropScroll( rEvt.maPosPixel ); + + nRet = AcceptPrivateDrop( rEvt, rData ); + } + else + { + if ( !rData.aLinkDoc.isEmpty() ) + { + OUString aThisName; + ScDocShell* pDocSh = mrViewData.GetDocShell(); + if (pDocSh && pDocSh->HasName()) + aThisName = pDocSh->GetMedium()->GetName(); + + if ( rData.aLinkDoc != aThisName ) + nRet = rEvt.mnAction; + } + else if (!rData.aJumpTarget.isEmpty()) + { + // internal bookmarks (from Navigator) + // local jumps from an unnamed document are possible only within a document + + if ( !rData.pJumpLocalDoc || rData.pJumpLocalDoc == &mrViewData.GetDocument() ) + nRet = rEvt.mnAction; + } + else + { + sal_Int8 nMyAction = rEvt.mnAction; + + // clear DND_ACTION_LINK when other actions are set. The usage below cannot handle + // multiple set values + if((nMyAction & DND_ACTION_LINK) && (nMyAction & DND_ACTION_COPYMOVE)) + { + nMyAction &= ~DND_ACTION_LINK; + } + + if ( !rData.pDrawTransfer || + !IsMyModel(rData.pDrawTransfer->GetDragSourceView()) ) // drawing within the document + if ( rEvt.mbDefault && nMyAction == DND_ACTION_MOVE ) + nMyAction = DND_ACTION_COPY; + + SdrObject* pHitObj = rThisDoc.GetObjectAtPoint( + mrViewData.GetTabNo(), PixelToLogic(rEvt.maPosPixel) ); + if ( pHitObj && nMyAction == DND_ACTION_LINK ) + { + if ( IsDropFormatSupported(SotClipboardFormatId::SVXB) + || IsDropFormatSupported(SotClipboardFormatId::GDIMETAFILE) + || IsDropFormatSupported(SotClipboardFormatId::PNG) + || IsDropFormatSupported(SotClipboardFormatId::BITMAP) ) + { + // graphic dragged onto drawing object + DrawMarkDropObj( pHitObj ); + nRet = nMyAction; + } + } + if (!nRet) + { + DrawMarkDropObj(nullptr); + + switch ( nMyAction ) + { + case DND_ACTION_COPY: + case DND_ACTION_MOVE: + case DND_ACTION_COPYMOVE: + { + bool bMove = ( nMyAction == DND_ACTION_MOVE ); + if ( IsDropFormatSupported( SotClipboardFormatId::EMBED_SOURCE ) || + IsDropFormatSupported( SotClipboardFormatId::LINK_SOURCE ) || + IsDropFormatSupported( SotClipboardFormatId::EMBED_SOURCE_OLE ) || + IsDropFormatSupported( SotClipboardFormatId::LINK_SOURCE_OLE ) || + IsDropFormatSupported( SotClipboardFormatId::EMBEDDED_OBJ_OLE ) || + IsDropFormatSupported( SotClipboardFormatId::STRING ) || + IsDropFormatSupported( SotClipboardFormatId::STRING_TSVC ) || + IsDropFormatSupported( SotClipboardFormatId::SYLK ) || + IsDropFormatSupported( SotClipboardFormatId::LINK ) || + IsDropFormatSupported( SotClipboardFormatId::HTML ) || + IsDropFormatSupported( SotClipboardFormatId::HTML_SIMPLE ) || + IsDropFormatSupported( SotClipboardFormatId::DIF ) || + IsDropFormatSupported( SotClipboardFormatId::DRAWING ) || + IsDropFormatSupported( SotClipboardFormatId::SVXB ) || + IsDropFormatSupported( SotClipboardFormatId::RTF ) || + IsDropFormatSupported( SotClipboardFormatId::RICHTEXT ) || + IsDropFormatSupported( SotClipboardFormatId::GDIMETAFILE ) || + IsDropFormatSupported( SotClipboardFormatId::PNG ) || + IsDropFormatSupported( SotClipboardFormatId::BITMAP ) || + IsDropFormatSupported( SotClipboardFormatId::SBA_DATAEXCHANGE ) || + IsDropFormatSupported( SotClipboardFormatId::SBA_FIELDDATAEXCHANGE ) || + ( !bMove && ( + IsDropFormatSupported( SotClipboardFormatId::FILE_LIST ) || + IsDropFormatSupported( SotClipboardFormatId::SIMPLE_FILE ) || + IsDropFormatSupported( SotClipboardFormatId::SOLK ) || + IsDropFormatSupported( SotClipboardFormatId::UNIFORMRESOURCELOCATOR ) || + IsDropFormatSupported( SotClipboardFormatId::NETSCAPE_BOOKMARK ) || + IsDropFormatSupported( SotClipboardFormatId::FILEGRPDESCRIPTOR ) ) ) ) + { + nRet = nMyAction; + } + } + break; + case DND_ACTION_LINK: + if ( IsDropFormatSupported( SotClipboardFormatId::LINK_SOURCE ) || + IsDropFormatSupported( SotClipboardFormatId::LINK_SOURCE_OLE ) || + IsDropFormatSupported( SotClipboardFormatId::LINK ) || + IsDropFormatSupported( SotClipboardFormatId::FILE_LIST ) || + IsDropFormatSupported( SotClipboardFormatId::SIMPLE_FILE ) || + IsDropFormatSupported( SotClipboardFormatId::SOLK ) || + IsDropFormatSupported( SotClipboardFormatId::UNIFORMRESOURCELOCATOR ) || + IsDropFormatSupported( SotClipboardFormatId::NETSCAPE_BOOKMARK ) || + IsDropFormatSupported( SotClipboardFormatId::FILEGRPDESCRIPTOR ) ) + { + nRet = nMyAction; + } + break; + } + + if ( nRet ) + { + // Simple check for protection: It's not known here if the drop will result + // in cells or drawing objects (some formats can be both) and how many cells + // the result will be. But if IsFormatEditable for the drop cell position + // is sal_False (ignores matrix formulas), nothing can be pasted, so the drop + // can already be rejected here. + + Point aPos = rEvt.maPosPixel; + SCCOL nPosX; + SCROW nPosY; + mrViewData.GetPosFromPixel( aPos.X(), aPos.Y(), eWhich, nPosX, nPosY ); + SCTAB nTab = mrViewData.GetTabNo(); + ScDocument& rDoc = mrViewData.GetDocument(); + + ScEditableTester aTester( rDoc, nTab, nPosX,nPosY, nPosX,nPosY ); + if ( !aTester.IsFormatEditable() ) + nRet = DND_ACTION_NONE; // forbidden + } + } + } + + // scroll only for accepted formats + if (nRet) + DropScroll( rEvt.maPosPixel ); + } + + return nRet; +} + +static SotClipboardFormatId lcl_GetDropFormatId( const uno::Reference& xTransfer, bool bPreferText ) +{ + TransferableDataHelper aDataHelper( xTransfer ); + + if ( !aDataHelper.HasFormat( SotClipboardFormatId::SBA_DATAEXCHANGE ) ) + { + // use bookmark formats if no sba is present + + if ( aDataHelper.HasFormat( SotClipboardFormatId::SOLK ) ) + return SotClipboardFormatId::SOLK; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::UNIFORMRESOURCELOCATOR ) ) + return SotClipboardFormatId::UNIFORMRESOURCELOCATOR; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::NETSCAPE_BOOKMARK ) ) + return SotClipboardFormatId::NETSCAPE_BOOKMARK; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::FILEGRPDESCRIPTOR ) ) + return SotClipboardFormatId::FILEGRPDESCRIPTOR; + } + + SotClipboardFormatId nFormatId = SotClipboardFormatId::NONE; + if ( aDataHelper.HasFormat( SotClipboardFormatId::DRAWING ) ) + nFormatId = SotClipboardFormatId::DRAWING; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::SVXB ) ) + nFormatId = SotClipboardFormatId::SVXB; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::EMBED_SOURCE ) ) + { + // If it's a Writer object, insert RTF instead of OLE + + bool bDoRtf = false; + tools::SvRef xStm; + TransferableObjectDescriptor aObjDesc; + if( aDataHelper.GetTransferableObjectDescriptor( SotClipboardFormatId::OBJECTDESCRIPTOR, aObjDesc ) && + aDataHelper.GetSotStorageStream( SotClipboardFormatId::EMBED_SOURCE, xStm ) ) + { + bDoRtf = ( ( aObjDesc.maClassName == SvGlobalName( SO3_SW_CLASSID ) || + aObjDesc.maClassName == SvGlobalName( SO3_SWWEB_CLASSID ) ) + && ( aDataHelper.HasFormat( SotClipboardFormatId::RTF ) || aDataHelper.HasFormat( SotClipboardFormatId::RICHTEXT ) ) ); + } + if ( bDoRtf ) + nFormatId = aDataHelper.HasFormat( SotClipboardFormatId::RTF ) ? SotClipboardFormatId::RTF : SotClipboardFormatId::RICHTEXT; + else + nFormatId = SotClipboardFormatId::EMBED_SOURCE; + } + else if ( aDataHelper.HasFormat( SotClipboardFormatId::LINK_SOURCE ) ) + nFormatId = SotClipboardFormatId::LINK_SOURCE; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::SBA_DATAEXCHANGE ) ) + nFormatId = SotClipboardFormatId::SBA_DATAEXCHANGE; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::SBA_FIELDDATAEXCHANGE ) ) + nFormatId = SotClipboardFormatId::SBA_FIELDDATAEXCHANGE; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::BIFF_8 ) ) + nFormatId = SotClipboardFormatId::BIFF_8; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::BIFF_5 ) ) + nFormatId = SotClipboardFormatId::BIFF_5; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::EMBED_SOURCE_OLE ) ) + nFormatId = SotClipboardFormatId::EMBED_SOURCE_OLE; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::EMBEDDED_OBJ_OLE ) ) + nFormatId = SotClipboardFormatId::EMBEDDED_OBJ_OLE; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::LINK_SOURCE_OLE ) ) + nFormatId = SotClipboardFormatId::LINK_SOURCE_OLE; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::RTF ) ) + nFormatId = SotClipboardFormatId::RTF; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::RICHTEXT ) ) + nFormatId = SotClipboardFormatId::RICHTEXT; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::HTML ) ) + nFormatId = SotClipboardFormatId::HTML; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::HTML_SIMPLE ) ) + nFormatId = SotClipboardFormatId::HTML_SIMPLE; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::SYLK ) ) + nFormatId = SotClipboardFormatId::SYLK; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::LINK ) ) + nFormatId = SotClipboardFormatId::LINK; + else if ( bPreferText && aDataHelper.HasFormat( SotClipboardFormatId::STRING ) ) // #i86734# the behaviour introduced in #i62773# is wrong when pasting + nFormatId = SotClipboardFormatId::STRING; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::FILE_LIST ) ) + nFormatId = SotClipboardFormatId::FILE_LIST; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::SIMPLE_FILE ) ) // #i62773# FILE_LIST/FILE before STRING (Unix file managers) + nFormatId = SotClipboardFormatId::SIMPLE_FILE; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::STRING_TSVC ) ) + nFormatId = SotClipboardFormatId::STRING_TSVC; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::STRING ) ) + nFormatId = SotClipboardFormatId::STRING; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::GDIMETAFILE ) ) + nFormatId = SotClipboardFormatId::GDIMETAFILE; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::PNG ) ) + nFormatId = SotClipboardFormatId::PNG; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::BITMAP ) ) + nFormatId = SotClipboardFormatId::BITMAP; + + return nFormatId; +} + +static SotClipboardFormatId lcl_GetDropLinkId( const uno::Reference& xTransfer ) +{ + TransferableDataHelper aDataHelper( xTransfer ); + + SotClipboardFormatId nFormatId = SotClipboardFormatId::NONE; + if ( aDataHelper.HasFormat( SotClipboardFormatId::LINK_SOURCE ) ) + nFormatId = SotClipboardFormatId::LINK_SOURCE; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::LINK_SOURCE_OLE ) ) + nFormatId = SotClipboardFormatId::LINK_SOURCE_OLE; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::LINK ) ) + nFormatId = SotClipboardFormatId::LINK; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::FILE_LIST ) ) + nFormatId = SotClipboardFormatId::FILE_LIST; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::SIMPLE_FILE ) ) + nFormatId = SotClipboardFormatId::SIMPLE_FILE; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::SOLK ) ) + nFormatId = SotClipboardFormatId::SOLK; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::UNIFORMRESOURCELOCATOR ) ) + nFormatId = SotClipboardFormatId::UNIFORMRESOURCELOCATOR; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::NETSCAPE_BOOKMARK ) ) + nFormatId = SotClipboardFormatId::NETSCAPE_BOOKMARK; + else if ( aDataHelper.HasFormat( SotClipboardFormatId::FILEGRPDESCRIPTOR ) ) + nFormatId = SotClipboardFormatId::FILEGRPDESCRIPTOR; + + return nFormatId; +} + +sal_Int8 ScGridWindow::ExecutePrivateDrop( const ExecuteDropEvent& rEvt, const ScDragData& rData ) +{ + // hide drop marker + bDragRect = false; + UpdateDragRectOverlay(); + + return DropTransferObj( rData.pCellTransfer, nDragStartX, nDragStartY, + PixelToLogic(rEvt.maPosPixel), rEvt.mnAction ); +} + +sal_Int8 ScGridWindow::DropTransferObj( ScTransferObj* pTransObj, SCCOL nDestPosX, SCROW nDestPosY, + const Point& rLogicPos, sal_Int8 nDndAction ) +{ + if ( !pTransObj ) + return 0; + + ScDocument* pSourceDoc = pTransObj->GetSourceDocument(); + ScDocShell* pDocSh = mrViewData.GetDocShell(); + ScDocument& rThisDoc = mrViewData.GetDocument(); + ScViewFunc* pView = mrViewData.GetView(); + SCTAB nThisTab = mrViewData.GetTabNo(); + ScDragSrc nFlags = pTransObj->GetDragSourceFlags(); + + bool bIsNavi = (nFlags & ScDragSrc::Navigator) == ScDragSrc::Navigator; + bool bIsMove = ( nDndAction == DND_ACTION_MOVE && !bIsNavi ); + + // workaround for wrong nDndAction on Windows when pressing solely + // the Alt key during drag and drop; + // can be removed after #i79215# has been fixed + if ( meDragInsertMode != INS_NONE ) + { + bIsMove = ( nDndAction & DND_ACTION_MOVE && !bIsNavi ); + } + + bool bIsLink = ( nDndAction == DND_ACTION_LINK ); + + ScRange aSource = pTransObj->GetRange(); + + // only use visible tab from source range - when dragging within one table, + // all selected tables at the time of dropping are used (handled in MoveBlockTo) + SCTAB nSourceTab = pTransObj->GetVisibleTab(); + aSource.aStart.SetTab( nSourceTab ); + aSource.aEnd.SetTab( nSourceTab ); + + SCCOL nSizeX = aSource.aEnd.Col() - aSource.aStart.Col() + 1; + SCROW nSizeY = (bIsMove ? (aSource.aEnd.Row() - aSource.aStart.Row() + 1) : + pTransObj->GetNonFilteredRows()); // copy/link: no filtered rows + ScRange aDest( nDestPosX, nDestPosY, nThisTab, + nDestPosX + nSizeX - 1, nDestPosY + nSizeY - 1, nThisTab ); + + /* NOTE: AcceptPrivateDrop() already checked for filtered conditions during + * dragging and adapted drawing of the selection frame. We check here + * (again) because this may actually also be called from PasteSelection(), + * we would have to duplicate determination of flags and destination range + * and would lose the context of the "filtered destination is OK" cases + * below, which is already awkward enough as is. */ + + // Don't move filtered source. + bool bFiltered = (bIsMove && pTransObj->HasFilteredRows()); + if (!bFiltered) + { + if (pSourceDoc != &rThisDoc && ((nFlags & ScDragSrc::Table) || + (!bIsLink && meDragInsertMode == INS_NONE))) + { + // Nothing. Either entire sheet to be dropped, or the one case + // where PasteFromClip() is to be called that handles a filtered + // destination itself. Drag-copy from another document without + // inserting cells. + } + else + // Don't copy or move to filtered destination. + bFiltered = ScViewUtil::HasFiltered(aDest, rThisDoc); + } + + bool bDone = false; + + if (!bFiltered && pSourceDoc == &rThisDoc) + { + if (nFlags & ScDragSrc::Table) // whole sheet? + { + if ( rThisDoc.IsDocEditable() ) + { + SCTAB nSrcTab = aSource.aStart.Tab(); + mrViewData.GetDocShell()->MoveTable( nSrcTab, nThisTab, !bIsMove, true ); // with Undo + pView->SetTabNo( nThisTab, true ); + bDone = true; + } + } + else // move/copy block + { + OUString aChartName; + if (rThisDoc.HasChartAtPoint( nThisTab, rLogicPos, aChartName )) + { + OUString aRangeName(aSource.Format(rThisDoc, ScRefFlags::RANGE_ABS_3D, + rThisDoc.GetAddressConvention())); + SfxStringItem aNameItem( SID_CHART_NAME, aChartName ); + SfxStringItem aRangeItem( SID_CHART_SOURCE, aRangeName ); + sal_uInt16 nId = bIsMove ? SID_CHART_SOURCE : SID_CHART_ADDSOURCE; + mrViewData.GetDispatcher().ExecuteList(nId, + SfxCallMode::ASYNCHRON | SfxCallMode::RECORD, + { &aRangeItem, &aNameItem }); + bDone = true; + } + else if ( rThisDoc.GetDPAtCursor( nDestPosX, nDestPosY, nThisTab ) ) + { + // drop on DataPilot table: try to sort, fail if that isn't possible + + ScAddress aDestPos( nDestPosX, nDestPosY, nThisTab ); + if ( aDestPos != aSource.aStart ) + bDone = mrViewData.GetView()->DataPilotMove( aSource, aDestPos ); + else + bDone = true; // same position: nothing + } + else if ( nDestPosX != aSource.aStart.Col() || nDestPosY != aSource.aStart.Row() || + nSourceTab != nThisTab ) + { + OUString aUndo = ScResId( bIsMove ? STR_UNDO_MOVE : STR_UNDO_COPY ); + pDocSh->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, mrViewData.GetViewShell()->GetViewShellId() ); + + SCCOL nCorrectCursorPosCol = 0; + SCROW nCorrectCursorPosRow = 0; + + bDone = true; + if ( meDragInsertMode != INS_NONE ) + { + // call with bApi = sal_True to avoid error messages in drop handler + bDone = pDocSh->GetDocFunc().InsertCells( aDest, nullptr, meDragInsertMode, true /*bRecord*/, true /*bApi*/, true /*bPartOfPaste*/ ); + if ( bDone ) + { + if ( nThisTab == nSourceTab ) + { + if ( meDragInsertMode == INS_CELLSDOWN && + nDestPosX == aSource.aStart.Col() && nDestPosY < aSource.aStart.Row() ) + { + ScRange aErrorRange( ScAddress::UNINITIALIZED ); + bDone = aSource.Move( 0, nSizeY, 0, aErrorRange, *pSourceDoc ); + nCorrectCursorPosRow = nSizeY; + } + else if ( meDragInsertMode == INS_CELLSRIGHT && + nDestPosY == aSource.aStart.Row() && nDestPosX < aSource.aStart.Col() ) + { + ScRange aErrorRange( ScAddress::UNINITIALIZED ); + bDone = aSource.Move( nSizeX, 0, 0, aErrorRange, *pSourceDoc ); + nCorrectCursorPosCol = nSizeX; + } + } + pDocSh->UpdateOle(mrViewData); + pView->CellContentChanged(); + } + } + + if ( bDone ) + { + if ( bIsLink ) + { + bDone = pView->LinkBlock( aSource, aDest.aStart ); + } + else + { + bDone = pView->MoveBlockTo( aSource, aDest.aStart, bIsMove ); + } + } + + if ( bDone && meDragInsertMode != INS_NONE && bIsMove && nThisTab == nSourceTab ) + { + DelCellCmd eCmd = DelCellCmd::NONE; + if ( meDragInsertMode == INS_CELLSDOWN ) + { + eCmd = DelCellCmd::CellsUp; + } + else if ( meDragInsertMode == INS_CELLSRIGHT ) + { + eCmd = DelCellCmd::CellsLeft; + } + + if ( ( eCmd == DelCellCmd::CellsUp && nDestPosX == aSource.aStart.Col() ) || + ( eCmd == DelCellCmd::CellsLeft && nDestPosY == aSource.aStart.Row() ) ) + { + // call with bApi = sal_True to avoid error messages in drop handler + bDone = pDocSh->GetDocFunc().DeleteCells( aSource, nullptr, eCmd, true /*bApi*/ ); + if ( bDone ) + { + if ( eCmd == DelCellCmd::CellsUp && nDestPosY > aSource.aEnd.Row() ) + { + ScRange aErrorRange( ScAddress::UNINITIALIZED ); + bDone = aDest.Move( 0, -nSizeY, 0, aErrorRange, rThisDoc ); + } + else if ( eCmd == DelCellCmd::CellsLeft && nDestPosX > aSource.aEnd.Col() ) + { + ScRange aErrorRange( ScAddress::UNINITIALIZED ); + bDone = aDest.Move( -nSizeX, 0, 0, aErrorRange, rThisDoc ); + } + pDocSh->UpdateOle(mrViewData); + pView->CellContentChanged(); + } + } + } + + if ( bDone ) + { + pView->MarkRange( aDest, false ); + + SCCOL nDCol; + SCROW nDRow; + if (pTransObj->WasSourceCursorInSelection()) + { + nDCol = pTransObj->GetSourceCursorX() - aSource.aStart.Col() + nCorrectCursorPosCol; + nDRow = pTransObj->GetSourceCursorY() - aSource.aStart.Row() + nCorrectCursorPosRow; + } + else + { + nDCol = 0; + nDRow = 0; + } + pView->SetCursor( aDest.aStart.Col() + nDCol, aDest.aStart.Row() + nDRow ); + } + + pDocSh->GetUndoManager()->LeaveListAction(); + + } + else + bDone = true; // nothing to do + } + + if (bDone) + pTransObj->SetDragWasInternal(); // don't delete source in DragFinished + } + else if ( !bFiltered && pSourceDoc ) // between documents + { + if (nFlags & ScDragSrc::Table) // copy/link sheets between documents + { + if ( rThisDoc.IsDocEditable() ) + { + ScDocShell* pSrcShell = pTransObj->GetSourceDocShell(); + + std::vector nTabs; + + ScMarkData aMark = pTransObj->GetSourceMarkData(); + SCTAB nTabCount = pSourceDoc->GetTableCount(); + + for(SCTAB i=0; iIsVisible(j))&&(pSourceDoc->IsScenario(j))) + { + nTabs.push_back( j ); + i=j; + } + else break; + } + } + } + + pView->ImportTables( pSrcShell,static_cast(nTabs.size()), nTabs.data(), bIsLink, nThisTab ); + bDone = true; + } + } + else if ( bIsLink ) + { + // as in PasteDDE + // (external references might be used instead?) + + ScDocShell* pSourceSh = pSourceDoc->GetDocumentShell(); + OSL_ENSURE(pSourceSh, "drag document has no shell"); + if (pSourceSh) + { + OUString aUndo = ScResId( STR_UNDO_COPY ); + pDocSh->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, mrViewData.GetViewShell()->GetViewShellId() ); + + bDone = true; + if ( meDragInsertMode != INS_NONE ) + { + // call with bApi = sal_True to avoid error messages in drop handler + bDone = pDocSh->GetDocFunc().InsertCells( aDest, nullptr, meDragInsertMode, true /*bRecord*/, true /*bApi*/, true /*bPartOfPaste*/ ); + if ( bDone ) + { + pDocSh->UpdateOle(mrViewData); + pView->CellContentChanged(); + } + } + + if ( bDone ) + { + OUString aApp = Application::GetAppName(); + OUString aTopic = pSourceSh->GetTitle( SFX_TITLE_FULLNAME ); + OUString aItem(aSource.Format(*pSourceDoc, ScRefFlags::VALID | ScRefFlags::TAB_3D)); + + // TODO: we could define ocQuote for " + const OUString aQuote('"'); + const OUString& sSep = ScCompiler::GetNativeSymbol( ocSep); + OUString aFormula = + "=" + + ScCompiler::GetNativeSymbol(ocDde) + + ScCompiler::GetNativeSymbol(ocOpen) + + aQuote + + aApp + + aQuote + + sSep + + aQuote + + aTopic + + aQuote + + sSep + + aQuote + + aItem + + aQuote + + ScCompiler::GetNativeSymbol(ocClose); + + pView->DoneBlockMode(); + pView->InitBlockMode( nDestPosX, nDestPosY, nThisTab ); + pView->MarkCursor( nDestPosX + nSizeX - 1, + nDestPosY + nSizeY - 1, nThisTab ); + + pView->EnterMatrix( aFormula, ::formula::FormulaGrammar::GRAM_NATIVE ); + + pView->MarkRange( aDest, false ); + pView->SetCursor( aDest.aStart.Col(), aDest.aStart.Row() ); + } + + pDocSh->GetUndoManager()->LeaveListAction(); + } + } + else + { + //! HasSelectedBlockMatrixFragment without selected sheet? + //! or don't start dragging on a part of a matrix + + OUString aUndo = ScResId( bIsMove ? STR_UNDO_MOVE : STR_UNDO_COPY ); + pDocSh->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, mrViewData.GetViewShell()->GetViewShellId() ); + + bDone = true; + if ( meDragInsertMode != INS_NONE ) + { + // call with bApi = sal_True to avoid error messages in drop handler + bDone = pDocSh->GetDocFunc().InsertCells( aDest, nullptr, meDragInsertMode, true /*bRecord*/, true /*bApi*/, true /*bPartOfPaste*/ ); + if ( bDone ) + { + pDocSh->UpdateOle(mrViewData); + pView->CellContentChanged(); + } + } + + if ( bDone ) + { + pView->Unmark(); // before SetCursor, so CheckSelectionTransfer isn't called with a selection + pView->SetCursor( nDestPosX, nDestPosY ); + bDone = pView->PasteFromClip( InsertDeleteFlags::ALL, pTransObj->GetDocument() ); // clip-doc + if ( bDone ) + { + pView->MarkRange( aDest, false ); + pView->SetCursor( aDest.aStart.Col(), aDest.aStart.Row() ); + } + } + + pDocSh->GetUndoManager()->LeaveListAction(); + + // no longer call ResetMark here - the inserted block has been selected + // and may have been copied to primary selection + } + } + + sal_Int8 nRet = bDone ? nDndAction : DND_ACTION_NONE; + return nRet; +} + +sal_Int8 ScGridWindow::ExecuteDrop( const ExecuteDropEvent& rEvt ) +{ + DrawMarkDropObj( nullptr ); // drawing layer + + ScModule* pScMod = SC_MOD(); + const ScDragData& rData = pScMod->GetDragData(); + if (rData.pCellTransfer) + return ExecutePrivateDrop( rEvt, rData ); + + Point aPos = rEvt.maPosPixel; + + if ( !rData.aLinkDoc.isEmpty() ) + { + // try to insert a link + + bool bOk = true; + OUString aThisName; + ScDocShell* pDocSh = mrViewData.GetDocShell(); + if (pDocSh && pDocSh->HasName()) + aThisName = pDocSh->GetMedium()->GetName(); + + if ( rData.aLinkDoc == aThisName ) // error - no link within a document + bOk = false; + else + { + ScViewFunc* pView = mrViewData.GetView(); + if ( !rData.aLinkTable.isEmpty() ) + pView->InsertTableLink( rData.aLinkDoc, OUString(), OUString(), + rData.aLinkTable ); + else if ( !rData.aLinkArea.isEmpty() ) + { + SCCOL nPosX; + SCROW nPosY; + mrViewData.GetPosFromPixel( aPos.X(), aPos.Y(), eWhich, nPosX, nPosY ); + pView->MoveCursorAbs( nPosX, nPosY, SC_FOLLOW_NONE, false, false ); + + pView->InsertAreaLink( rData.aLinkDoc, OUString(), OUString(), + rData.aLinkArea ); + } + else + { + OSL_FAIL("drop with link: no sheet nor area"); + bOk = false; + } + } + + return bOk ? rEvt.mnAction : DND_ACTION_NONE; // don't try anything else + } + + Point aLogicPos = PixelToLogic(aPos); + bool bIsLink = ( rEvt.mnAction == DND_ACTION_LINK ); + + if (!bIsLink && rData.pDrawTransfer) + { + ScDragSrc nFlags = rData.pDrawTransfer->GetDragSourceFlags(); + + bool bIsNavi = (nFlags & ScDragSrc::Navigator) == ScDragSrc::Navigator; + bool bIsMove = ( rEvt.mnAction == DND_ACTION_MOVE && !bIsNavi ); + + bPasteIsMove = bIsMove; + + mrViewData.GetView()->PasteDraw( + aLogicPos, rData.pDrawTransfer->GetModel(), false, u"A", u"B"); + + if (bPasteIsMove) + rData.pDrawTransfer->SetDragWasInternal(); + bPasteIsMove = false; + + return rEvt.mnAction; + } + + SCCOL nPosX; + SCROW nPosY; + mrViewData.GetPosFromPixel( aPos.X(), aPos.Y(), eWhich, nPosX, nPosY ); + + if (!rData.aJumpTarget.isEmpty()) + { + // internal bookmark (from Navigator) + // bookmark clipboard formats are in PasteScDataObject + + if ( !rData.pJumpLocalDoc || rData.pJumpLocalDoc == &mrViewData.GetDocument() ) + { + mrViewData.GetViewShell()->InsertBookmark( rData.aJumpText, rData.aJumpTarget, + nPosX, nPosY ); + return rEvt.mnAction; + } + } + + ScDocument& rThisDoc = mrViewData.GetDocument(); + SdrObject* pHitObj = rThisDoc.GetObjectAtPoint( mrViewData.GetTabNo(), PixelToLogic(aPos) ); + if ( pHitObj && bIsLink ) + { + // dropped on drawing object + // PasteOnDrawObjectLinked checks for valid formats + if ( mrViewData.GetView()->PasteOnDrawObjectLinked( rEvt.maDropEvent.Transferable, *pHitObj ) ) + return rEvt.mnAction; + } + + bool bDone = false; + + SotClipboardFormatId nFormatId = bIsLink ? + lcl_GetDropLinkId( rEvt.maDropEvent.Transferable ) : + lcl_GetDropFormatId( rEvt.maDropEvent.Transferable, false ); + if ( nFormatId != SotClipboardFormatId::NONE ) + { + pScMod->SetInExecuteDrop( true ); // #i28468# prevent error messages from PasteDataFormat + bDone = mrViewData.GetView()->PasteDataFormat( + nFormatId, rEvt.maDropEvent.Transferable, nPosX, nPosY, &aLogicPos, bIsLink ); + pScMod->SetInExecuteDrop( false ); + } + + sal_Int8 nRet = bDone ? rEvt.mnAction : DND_ACTION_NONE; + return nRet; +} + +void ScGridWindow::PasteSelection( const Point& rPosPixel ) +{ + Point aLogicPos = PixelToLogic( rPosPixel ); + + SCCOL nPosX; + SCROW nPosY; + mrViewData.GetPosFromPixel( rPosPixel.X(), rPosPixel.Y(), eWhich, nPosX, nPosY ); + + // If the mouse down was inside a visible note window, ignore it and + // leave it up to the ScPostIt to handle it + SdrView* pDrawView = mrViewData.GetViewShell()->GetScDrawView(); + if (pDrawView) + { + const size_t nCount = pDrawView->GetMarkedObjectCount(); + for (size_t i = 0; i < nCount; ++i) + { + SdrObject* pObj = pDrawView->GetMarkedObjectByIndex(i); + if (pObj && pObj->GetLogicRect().Contains(aLogicPos)) + { + // Inside an active drawing object. Bail out. + return; + } + } + } + + ScSelectionTransferObj* pOwnSelection = SC_MOD()->GetSelectionTransfer(); + if ( pOwnSelection ) + { + // within Calc + + // keep a reference to the data in case the selection is changed during paste + rtl::Reference pCellTransfer = pOwnSelection->GetCellData(); + if ( pCellTransfer ) + { + DropTransferObj( pCellTransfer.get(), nPosX, nPosY, aLogicPos, DND_ACTION_COPY ); + } + else + { + // keep a reference to the data in case the selection is changed during paste + rtl::Reference pDrawTransfer = pOwnSelection->GetDrawData(); + if ( pDrawTransfer ) + { + // bSameDocClipboard argument for PasteDraw is needed + // because only DragData is checked directly inside PasteDraw + mrViewData.GetView()->PasteDraw( + aLogicPos, pDrawTransfer->GetModel(), false, + pDrawTransfer->GetShellID(), SfxObjectShell::CreateShellID(mrViewData.GetDocShell())); + } + } + } + else + { + // get selection from system + TransferableDataHelper aDataHelper(TransferableDataHelper::CreateFromPrimarySelection()); + const uno::Reference& xTransferable = aDataHelper.GetTransferable(); + if ( xTransferable.is() ) + { + SotClipboardFormatId nFormatId = lcl_GetDropFormatId( xTransferable, true ); + if ( nFormatId != SotClipboardFormatId::NONE ) + mrViewData.GetView()->PasteDataFormat( nFormatId, xTransferable, nPosX, nPosY, &aLogicPos ); + } + } +} + +void ScGridWindow::UpdateEditViewPos() +{ + if (!mrViewData.HasEditView(eWhich)) + return; + + EditView* pView; + SCCOL nCol; + SCROW nRow; + mrViewData.GetEditView( eWhich, pView, nCol, nRow ); + SCCOL nEndCol = mrViewData.GetEditEndCol(); + SCROW nEndRow = mrViewData.GetEditEndRow(); + + // hide EditView? + + bool bHide = ( nEndColIsFormulaMode() ) + if ( mrViewData.GetTabNo() != mrViewData.GetRefTabNo() ) + bHide = true; + + if (bHide) + { + tools::Rectangle aRect = pView->GetOutputArea(); + tools::Long nHeight = aRect.Bottom() - aRect.Top(); + aRect.SetTop( PixelToLogic(GetOutputSizePixel(), mrViewData.GetLogicMode()). + Height() * 2 ); + aRect.SetBottom( aRect.Top() + nHeight ); + pView->SetOutputArea( aRect ); + pView->HideCursor(); + } + else + { + // bForceToTop = sal_True for editing + tools::Rectangle aPixRect = mrViewData.GetEditArea( eWhich, nCol, nRow, this, nullptr, true ); + + if (comphelper::LibreOfficeKit::isActive() && + comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs)) + { + tools::Rectangle aPTwipsRect = mrViewData.GetEditArea(eWhich, nCol, nRow, this, nullptr, + true, true /* bInPrintTwips */); + tools::Rectangle aOutputAreaPTwips = pView->GetLOKSpecialOutputArea(); + aOutputAreaPTwips.SetPos(aPTwipsRect.TopLeft()); + pView->SetLOKSpecialOutputArea(aOutputAreaPTwips); + } + + Point aScrPos = PixelToLogic( aPixRect.TopLeft(), mrViewData.GetLogicMode() ); + + tools::Rectangle aRect = pView->GetOutputArea(); + aRect.SetPos( aScrPos ); + pView->SetOutputArea( aRect ); + pView->ShowCursor(); + } +} + +void ScGridWindow::ScrollPixel( tools::Long nDifX, tools::Long nDifY ) +{ + ClickExtern(); + HideNoteMarker(); + + SetMapMode(MapMode(MapUnit::MapPixel)); + Scroll( nDifX, nDifY, ScrollFlags::Children ); + SetMapMode( GetDrawMapMode() ); // generated shifted MapMode + + UpdateEditViewPos(); + + DrawAfterScroll(); +} + +// Update Formulas ------------------------------------------------------ + +void ScGridWindow::UpdateFormulas(SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2) +{ + if (mrViewData.GetView()->IsMinimized()) + return; + + if ( nPaintCount ) + { + // Do not start, switched to paint + // (then at least the MapMode would no longer be right) + + bNeedsRepaint = true; // -> at end of paint run Invalidate on all + aRepaintPixel = tools::Rectangle(); // All + return; + } + + if ( comphelper::LibreOfficeKit::isActive() ) + { + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + if (nX1 < 0) + nX1 = pViewShell->GetLOKStartHeaderCol() + 1; + if (nY1 < 0) + nY1 = pViewShell->GetLOKStartHeaderRow() + 1; + if (nX2 < 0) + nX2 = pViewShell->GetLOKEndHeaderCol(); + if (nY2 < 0) + nY2 = pViewShell->GetLOKEndHeaderRow(); + + if (nX1 < 0 || nY1 < 0) return; + } + else + { + nX1 = mrViewData.GetPosX( eHWhich ); + nY1 = mrViewData.GetPosY( eVWhich ); + nX2 = nX1 + mrViewData.VisibleCellsX( eHWhich ); + nY2 = nY1 + mrViewData.VisibleCellsY( eVWhich ); + } + + if (nX2 < nX1) nX2 = nX1; + if (nY2 < nY1) nY2 = nY1; + + ScDocument& rDoc = mrViewData.GetDocument(); + + if (nX2 > rDoc.MaxCol()) nX2 = rDoc.MaxCol(); + if (nY2 > rDoc.MaxRow()) nY2 = rDoc.MaxRow(); + + // Draw( nX1, nY1, nX2, nY2, SC_UPDATE_CHANGED ); + + // don't draw directly - instead use OutputData to find changed area and invalidate + + SCROW nPosY = nY1; + + SCTAB nTab = mrViewData.GetTabNo(); + + if ( !comphelper::LibreOfficeKit::isActive() ) + { + rDoc.ExtendHidden( nX1, nY1, nX2, nY2, nTab ); + } + + Point aScrPos = mrViewData.GetScrPos( nX1, nY1, eWhich ); + tools::Long nMirrorWidth = GetSizePixel().Width(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + if ( bLayoutRTL ) + { + tools::Long nEndPixel = mrViewData.GetScrPos( nX2+1, nPosY, eWhich ).X(); + nMirrorWidth = aScrPos.X() - nEndPixel; + aScrPos.setX( nEndPixel + 1 ); + } + + tools::Long nScrX = aScrPos.X(); + tools::Long nScrY = aScrPos.Y(); + + double nPPTX = mrViewData.GetPPTX(); + double nPPTY = mrViewData.GetPPTY(); + + ScTableInfo aTabInfo; + rDoc.FillInfo( aTabInfo, nX1, nY1, nX2, nY2, nTab, nPPTX, nPPTY, false, false ); + + Fraction aZoomX = mrViewData.GetZoomX(); + Fraction aZoomY = mrViewData.GetZoomY(); + ScOutputData aOutputData( GetOutDev(), OUTTYPE_WINDOW, aTabInfo, &rDoc, nTab, + nScrX, nScrY, nX1, nY1, nX2, nY2, nPPTX, nPPTY, + &aZoomX, &aZoomY ); + aOutputData.SetMirrorWidth( nMirrorWidth ); + + aOutputData.FindChanged(); + + // #i122149# do not use old GetChangedArea() which used polygon-based Regions, but use + // the region-band based new version; anyways, only rectangles are added + vcl::Region aChangedRegion( aOutputData.GetChangedAreaRegion() ); // logic (PixelToLogic) + if(!aChangedRegion.IsEmpty()) + { + Invalidate(aChangedRegion); + } + + CheckNeedsRepaint(); // #i90362# used to be called via Draw() - still needed here +} + +void ScGridWindow::UpdateAutoFillMark(bool bMarked, const ScRange& rMarkRange) +{ + if ( bMarked != bAutoMarkVisible || ( bMarked && rMarkRange.aEnd != aAutoMarkPos ) ) + { + bAutoMarkVisible = bMarked; + if ( bMarked ) + aAutoMarkPos = rMarkRange.aEnd; + + UpdateAutoFillOverlay(); + } +} + +void ScGridWindow::updateLOKInputHelp(const OUString& title, const OUString& content) const +{ + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + + boost::property_tree::ptree aTree; + aTree.put("title", title); + aTree.put("content", content); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_VALIDITY_INPUT_HELP, OString(aStream.str())); +} + +void ScGridWindow::updateLOKValListButton( bool bVisible, const ScAddress& rPos ) const +{ + SCCOL nX = rPos.Col(); + SCROW nY = rPos.Row(); + std::stringstream ss; + ss << nX << ", " << nY << ", " << static_cast(bVisible); + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_VALIDITY_LIST_BUTTON, OString(ss.str())); +} + +void ScGridWindow::notifyKitCellFollowJump( ) const +{ + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_SC_FOLLOW_JUMP, getCellCursor()); +} + +void ScGridWindow::UpdateListValPos( bool bVisible, const ScAddress& rPos ) +{ + bool bOldButton = bListValButton; + ScAddress aOldPos = aListValPos; + + bListValButton = bVisible; + aListValPos = rPos; + + if ( bListValButton ) + { + if ( !bOldButton || aListValPos != aOldPos ) + { + // paint area of new button + if ( comphelper::LibreOfficeKit::isActive() ) + { + updateLOKValListButton( true, aListValPos ); + } + else + { + Invalidate( PixelToLogic( GetListValButtonRect( aListValPos ) ) ); + } + } + } + if ( !bOldButton ) + return; + + if ( !bListValButton || aListValPos != aOldPos ) + { + // paint area of old button + if ( comphelper::LibreOfficeKit::isActive() ) + { + updateLOKValListButton( false, aOldPos ); + } + else + { + Invalidate( PixelToLogic( GetListValButtonRect( aOldPos ) ) ); + } + } +} + +void ScGridWindow::HideCursor() +{ + ++nCursorHideCount; +} + +void ScGridWindow::ShowCursor() +{ + --nCursorHideCount; +} + +void ScGridWindow::GetFocus() +{ + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + pViewShell->SetFormShellAtTop( false ); // focus in GridWindow -> FormShell no longer on top + + if (pViewShell->HasAccessibilityObjects()) + pViewShell->BroadcastAccessibility(ScAccGridWinFocusGotHint(eWhich)); + + if ( !SC_MOD()->IsFormulaMode() ) + { + pViewShell->UpdateInputHandler(); +// StopMarking(); // If Dialog (error), because then no ButtonUp + // MO: only when not in RefInput mode + // -> GetFocus/MouseButtonDown order on Mac + } + + mrViewData.GetDocShell()->CheckConfigOptions(); + Window::GetFocus(); +} + +void ScGridWindow::LoseFocus() +{ + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + + if (pViewShell && pViewShell->HasAccessibilityObjects()) + pViewShell->BroadcastAccessibility(ScAccGridWinFocusLostHint(eWhich)); + + Window::LoseFocus(); +} + +bool ScGridWindow::HitRangeFinder( const Point& rMouse, RfCorner& rCorner, + sal_uInt16* pIndex, SCCOL* pAddX, SCROW* pAddY) +{ + bool bFound = false; + ScInputHandler* pHdl = SC_MOD()->GetInputHdl( mrViewData.GetViewShell() ); + if (pHdl) + { + ScRangeFindList* pRangeFinder = pHdl->GetRangeFindList(); + if ( pRangeFinder && !pRangeFinder->IsHidden() && + pRangeFinder->GetDocName() == mrViewData.GetDocShell()->GetTitle() ) + { + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + SCCOL nPosX; + SCROW nPosY; + mrViewData.GetPosFromPixel( rMouse.X(), rMouse.Y(), eWhich, nPosX, nPosY ); + // merged (single/Range) ??? + ScAddress aAddr( nPosX, nPosY, nTab ); + + Point aCellStart = mrViewData.GetScrPos( nPosX, nPosY, eWhich, true ); + Point aCellEnd = aCellStart; + tools::Long nSizeXPix; + tools::Long nSizeYPix; + mrViewData.GetMergeSizePixel( nPosX, nPosY, nSizeXPix, nSizeYPix ); + + aCellEnd.AdjustX(nSizeXPix * nLayoutSign ); + aCellEnd.AdjustY(nSizeYPix ); + + bool bCornerHorizontalRight; + bool bCornerHorizontalLeft; + if ( bLayoutRTL ) + { + bCornerHorizontalRight = ( rMouse.X() >= aCellEnd.X() && rMouse.X() <= aCellEnd.X() + 8 ); + bCornerHorizontalLeft = ( rMouse.X() >= aCellStart.X() - 8 && rMouse.X() <= aCellStart.X() ); + } + else + { + bCornerHorizontalRight = ( rMouse.X() >= aCellEnd.X() - 8 && rMouse.X() <= aCellEnd.X() ); + bCornerHorizontalLeft = ( rMouse.X() >= aCellStart.X() && rMouse.X() <= aCellStart.X() + 8 ); + } + + bool bCornerVerticalDown = rMouse.Y() >= aCellEnd.Y() - 8 && rMouse.Y() <= aCellEnd.Y(); + bool bCornerVerticalUp = rMouse.Y() >= aCellStart.Y() && rMouse.Y() <= aCellStart.Y() + 8; + + // corner is hit only if the mouse is within the cell + sal_uInt16 nCount = static_cast(pRangeFinder->Count()); + for (sal_uInt16 i=nCount; i;) + { + // search backwards so that the last repainted frame is found + --i; + ScRangeFindData& rData = pRangeFinder->GetObject(i); + if ( rData.aRef.Contains(aAddr) ) + { + if (pIndex) + *pIndex = i; + if (pAddX) + *pAddX = nPosX - rData.aRef.aStart.Col(); + if (pAddY) + *pAddY = nPosY - rData.aRef.aStart.Row(); + + bFound = true; + + rCorner = NONE; + + ScAddress aEnd = rData.aRef.aEnd; + ScAddress aStart = rData.aRef.aStart; + + if ( bCornerHorizontalLeft && bCornerVerticalUp && + aAddr == aStart) + { + rCorner = LEFT_UP; + } + else if (bCornerHorizontalRight && bCornerVerticalDown && + aAddr == aEnd) + { + rCorner = RIGHT_DOWN; + } + else if (bCornerHorizontalRight && bCornerVerticalUp && + aAddr == ScAddress(aEnd.Col(), aStart.Row(), aStart.Tab())) + { + rCorner = RIGHT_UP; + } + else if (bCornerHorizontalLeft && bCornerVerticalDown && + aAddr == ScAddress(aStart.Col(), aEnd.Row(), aStart.Tab())) + { + rCorner = LEFT_DOWN; + } + break; + } + } + } + } + return bFound; +} + +#define SCE_TOP 1 +#define SCE_BOTTOM 2 +#define SCE_LEFT 4 +#define SCE_RIGHT 8 +#define SCE_ALL 15 + +static void lcl_PaintOneRange( ScDocShell* pDocSh, const ScRange& rRange, sal_uInt16 nEdges ) +{ + // the range is always properly oriented + + SCCOL nCol1 = rRange.aStart.Col(); + SCROW nRow1 = rRange.aStart.Row(); + SCTAB nTab1 = rRange.aStart.Tab(); + SCCOL nCol2 = rRange.aEnd.Col(); + SCROW nRow2 = rRange.aEnd.Row(); + SCTAB nTab2 = rRange.aEnd.Tab(); + bool bHiddenEdge = false; + SCROW nTmp; + + ScDocument& rDoc = pDocSh->GetDocument(); + while ( nCol1 > 0 && rDoc.ColHidden(nCol1, nTab1) ) + { + --nCol1; + bHiddenEdge = true; + } + while ( nCol2 < rDoc.MaxCol() && rDoc.ColHidden(nCol2, nTab1) ) + { + ++nCol2; + bHiddenEdge = true; + } + nTmp = rDoc.FirstVisibleRow(0, nRow1, nTab1); + if (!rDoc.ValidRow(nTmp)) + nTmp = 0; + if (nTmp < nRow1) + { + nRow1 = nTmp; + bHiddenEdge = true; + } + nTmp = rDoc.FirstVisibleRow(nRow2, rDoc.MaxRow(), nTab1); + if (!rDoc.ValidRow(nTmp)) + nTmp = rDoc.MaxRow(); + if (nTmp > nRow2) + { + nRow2 = nTmp; + bHiddenEdge = true; + } + + if ( nCol2 > nCol1 + 1 && nRow2 > nRow1 + 1 && !bHiddenEdge ) + { + // Only along the edges (The corners are hit twice) + if ( nEdges & SCE_TOP ) + pDocSh->PostPaint( nCol1, nRow1, nTab1, nCol2, nRow1, nTab2, PaintPartFlags::Marks ); + if ( nEdges & SCE_LEFT ) + pDocSh->PostPaint( nCol1, nRow1, nTab1, nCol1, nRow2, nTab2, PaintPartFlags::Marks ); + if ( nEdges & SCE_RIGHT ) + pDocSh->PostPaint( nCol2, nRow1, nTab1, nCol2, nRow2, nTab2, PaintPartFlags::Marks ); + if ( nEdges & SCE_BOTTOM ) + pDocSh->PostPaint( nCol1, nRow2, nTab1, nCol2, nRow2, nTab2, PaintPartFlags::Marks ); + } + else // everything in one call + pDocSh->PostPaint( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2, PaintPartFlags::Marks ); +} + +static void lcl_PaintRefChanged( ScDocShell* pDocSh, const ScRange& rOldUn, const ScRange& rNewUn ) +{ + // Repaint for the parts of the frame in old, which in are no more in New + + ScRange aOld = rOldUn; + ScRange aNew = rNewUn; + aOld.PutInOrder(); + aNew.PutInOrder(); + + if ( aOld.aStart == aOld.aEnd ) //! Ignore sheet ? + pDocSh->GetDocument().ExtendMerge(aOld); + if ( aNew.aStart == aNew.aEnd ) //! Ignore sheet ? + pDocSh->GetDocument().ExtendMerge(aNew); + + SCCOL nOldCol1 = aOld.aStart.Col(); + SCROW nOldRow1 = aOld.aStart.Row(); + SCCOL nOldCol2 = aOld.aEnd.Col(); + SCROW nOldRow2 = aOld.aEnd.Row(); + SCCOL nNewCol1 = aNew.aStart.Col(); + SCROW nNewRow1 = aNew.aStart.Row(); + SCCOL nNewCol2 = aNew.aEnd.Col(); + SCROW nNewRow2 = aNew.aEnd.Row(); + SCTAB nTab1 = aOld.aStart.Tab(); // sheet is not changed + SCTAB nTab2 = aOld.aEnd.Tab(); + + if ( nNewRow2 < nOldRow1 || nNewRow1 > nOldRow2 || + nNewCol2 < nOldCol1 || nNewCol1 > nOldCol2 || + ( nNewCol1 != nOldCol1 && nNewRow1 != nOldRow1 && + nNewCol2 != nOldCol2 && nNewRow2 != nOldRow2 ) ) + { + // Completely removed or changed all sides + // (check <= instead of < goes wrong for single rows/columns) + + lcl_PaintOneRange( pDocSh, aOld, SCE_ALL ); + } + else // Test all four corners separately + { + // upper part + if ( nNewRow1 < nOldRow1 ) // only delete upper line + lcl_PaintOneRange( pDocSh, ScRange( + nOldCol1, nOldRow1, nTab1, nOldCol2, nOldRow1, nTab2 ), SCE_ALL ); + else if ( nNewRow1 > nOldRow1 ) // the upper part which is will be removed + lcl_PaintOneRange( pDocSh, ScRange( + nOldCol1, nOldRow1, nTab1, nOldCol2, nNewRow1-1, nTab2 ), + SCE_ALL &~ SCE_BOTTOM ); + + // bottom part + if ( nNewRow2 > nOldRow2 ) // only delete bottom line + lcl_PaintOneRange( pDocSh, ScRange( + nOldCol1, nOldRow2, nTab1, nOldCol2, nOldRow2, nTab2 ), SCE_ALL ); + else if ( nNewRow2 < nOldRow2 ) // the bottom part which is will be removed + lcl_PaintOneRange( pDocSh, ScRange( + nOldCol1, nNewRow2+1, nTab1, nOldCol2, nOldRow2, nTab2 ), + SCE_ALL &~ SCE_TOP ); + + // left part + if ( nNewCol1 < nOldCol1 ) // only delete left line + lcl_PaintOneRange( pDocSh, ScRange( + nOldCol1, nOldRow1, nTab1, nOldCol1, nOldRow2, nTab2 ), SCE_ALL ); + else if ( nNewCol1 > nOldCol1 ) // the left part which is will be removed + lcl_PaintOneRange( pDocSh, ScRange( + nOldCol1, nOldRow1, nTab1, nNewCol1-1, nOldRow2, nTab2 ), + SCE_ALL &~ SCE_RIGHT ); + + // right part + if ( nNewCol2 > nOldCol2 ) // only delete right line + lcl_PaintOneRange( pDocSh, ScRange( + nOldCol2, nOldRow1, nTab1, nOldCol2, nOldRow2, nTab2 ), SCE_ALL ); + else if ( nNewCol2 < nOldCol2 ) // the right part which is will be removed + lcl_PaintOneRange( pDocSh, ScRange( + nNewCol2+1, nOldRow1, nTab1, nOldCol2, nOldRow2, nTab2 ), + SCE_ALL &~ SCE_LEFT ); + } +} + +void ScGridWindow::RFMouseMove( const MouseEvent& rMEvt, bool bUp ) +{ + ScInputHandler* pHdl = SC_MOD()->GetInputHdl( mrViewData.GetViewShell() ); + if (!pHdl) + return; + ScRangeFindList* pRangeFinder = pHdl->GetRangeFindList(); + if (!pRangeFinder || nRFIndex >= pRangeFinder->Count()) + return; + ScRangeFindData& rData = pRangeFinder->GetObject( nRFIndex ); + + // Mouse pointer + + if (bRFSize) + SetPointer( PointerStyle::Cross ); + else + SetPointer( PointerStyle::Hand ); + + // Scrolling + + bool bTimer = false; + Point aPos = rMEvt.GetPosPixel(); + SCCOL nDx = 0; + SCROW nDy = 0; + if ( aPos.X() < 0 ) nDx = -1; + if ( aPos.Y() < 0 ) nDy = -1; + Size aSize = GetOutputSizePixel(); + if ( aPos.X() >= aSize.Width() ) + nDx = 1; + if ( aPos.Y() >= aSize.Height() ) + nDy = 1; + if ( nDx != 0 || nDy != 0 ) + { + if ( nDx != 0) mrViewData.GetView()->ScrollX( nDx, WhichH(eWhich) ); + if ( nDy != 0 ) mrViewData.GetView()->ScrollY( nDy, WhichV(eWhich) ); + bTimer = true; + } + + // Switching when fixating (so Scrolling works) + + if ( eWhich == mrViewData.GetActivePart() ) //?? + { + if ( mrViewData.GetHSplitMode() == SC_SPLIT_FIX ) + if ( nDx > 0 ) + { + if ( eWhich == SC_SPLIT_TOPLEFT ) + mrViewData.GetView()->ActivatePart( SC_SPLIT_TOPRIGHT ); + else if ( eWhich == SC_SPLIT_BOTTOMLEFT ) + mrViewData.GetView()->ActivatePart( SC_SPLIT_BOTTOMRIGHT ); + } + + if ( mrViewData.GetVSplitMode() == SC_SPLIT_FIX ) + if ( nDy > 0 ) + { + if ( eWhich == SC_SPLIT_TOPLEFT ) + mrViewData.GetView()->ActivatePart( SC_SPLIT_BOTTOMLEFT ); + else if ( eWhich == SC_SPLIT_TOPRIGHT ) + mrViewData.GetView()->ActivatePart( SC_SPLIT_BOTTOMRIGHT ); + } + } + + // Move + + SCCOL nPosX; + SCROW nPosY; + mrViewData.GetPosFromPixel( aPos.X(), aPos.Y(), eWhich, nPosX, nPosY ); + + ScRange aOld = rData.aRef; + ScRange aNew = aOld; + if ( bRFSize ) + { + switch (aRFSelectedCorned) + { + case LEFT_UP: + aNew.aStart.SetCol(nPosX); + aNew.aStart.SetRow(nPosY); + break; + case LEFT_DOWN: + aNew.aStart.SetCol(nPosX); + aNew.aEnd.SetRow(nPosY); + break; + case RIGHT_UP: + aNew.aEnd.SetCol(nPosX); + aNew.aStart.SetRow(nPosY); + break; + case RIGHT_DOWN: + aNew.aEnd.SetCol(nPosX); + aNew.aEnd.SetRow(nPosY); + break; + default: + break; + } + } + else + { + ScDocument& rDoc = mrViewData.GetDocument(); + tools::Long nStartX = nPosX - nRFAddX; + if ( nStartX < 0 ) nStartX = 0; + tools::Long nStartY = nPosY - nRFAddY; + if ( nStartY < 0 ) nStartY = 0; + tools::Long nEndX = nStartX + aOld.aEnd.Col() - aOld.aStart.Col(); + if ( nEndX > rDoc.MaxCol() ) + { + nStartX -= ( nEndX - rDoc.MaxRow() ); + nEndX = rDoc.MaxCol(); + } + tools::Long nEndY = nStartY + aOld.aEnd.Row() - aOld.aStart.Row(); + if ( nEndY > rDoc.MaxRow() ) + { + nStartY -= ( nEndY - rDoc.MaxRow() ); + nEndY = rDoc.MaxRow(); + } + + aNew.aStart.SetCol(static_cast(nStartX)); + aNew.aStart.SetRow(static_cast(nStartY)); + aNew.aEnd.SetCol(static_cast(nEndX)); + aNew.aEnd.SetRow(static_cast(nEndY)); + } + + if ( bUp ) + aNew.PutInOrder(); // For ButtonUp again in the proper order + + if ( aNew != aOld ) + { + pHdl->UpdateRange( nRFIndex, aNew ); + + ScDocShell* pDocSh = mrViewData.GetDocShell(); + + pHdl->UpdateLokReferenceMarks(); + + // only redrawing what has been changed... + lcl_PaintRefChanged( pDocSh, aOld, aNew ); + + // only redraw new frame (synchronously) + pDocSh->Broadcast( ScIndexHint( SfxHintId::ScShowRangeFinder, nRFIndex ) ); + + PaintImmediately(); // what you move, will be seen immediately + } + + // Timer for Scrolling + + if (bTimer) + mrViewData.GetView()->SetTimer( this, rMEvt ); // repeat event + else + mrViewData.GetView()->ResetTimer(); +} + +namespace { + +SvxAdjust toSvxAdjust( const ScPatternAttr& rPat ) +{ + SvxCellHorJustify eHorJust = + rPat.GetItem(ATTR_HOR_JUSTIFY).GetValue(); + + SvxAdjust eSvxAdjust = SvxAdjust::Left; + switch (eHorJust) + { + case SvxCellHorJustify::Left: + case SvxCellHorJustify::Repeat: // not implemented + case SvxCellHorJustify::Standard: // always Text if an EditCell type + eSvxAdjust = SvxAdjust::Left; + break; + case SvxCellHorJustify::Right: + eSvxAdjust = SvxAdjust::Right; + break; + case SvxCellHorJustify::Center: + eSvxAdjust = SvxAdjust::Center; + break; + case SvxCellHorJustify::Block: + eSvxAdjust = SvxAdjust::Block; + break; + } + + return eSvxAdjust; +} + +std::shared_ptr createEditEngine( ScDocShell* pDocSh, const ScPatternAttr& rPat ) +{ + ScDocument& rDoc = pDocSh->GetDocument(); + + auto pEngine = std::make_shared(&rDoc, rDoc.GetEditPool()); + ScSizeDeviceProvider aProv(pDocSh); + pEngine->SetRefDevice(aProv.GetDevice()); + pEngine->SetRefMapMode(MapMode(MapUnit::Map100thMM)); + SfxItemSet aDefault = pEngine->GetEmptyItemSet(); + rPat.FillEditItemSet(&aDefault); + aDefault.Put( SvxAdjustItem(toSvxAdjust(rPat), EE_PARA_JUST) ); + pEngine->SetDefaults(aDefault); + + return pEngine; +} + +bool extractURLInfo( const SvxFieldItem* pFieldItem, OUString* pName, OUString* pUrl, OUString* pTarget ) +{ + if (!pFieldItem) + return false; + + const SvxFieldData* pField = pFieldItem->GetField(); + if (pField->GetClassId() != text::textfield::Type::URL) + return false; + + const SvxURLField* pURLField = static_cast(pField); + + if (pName) + *pName = pURLField->GetRepresentation(); + if (pUrl) + *pUrl = pURLField->GetURL(); + if (pTarget) + *pTarget = pURLField->GetTargetFrame(); + + return true; +} + +} + +bool ScGridWindow::GetEditUrl( const Point& rPos, + OUString* pName, OUString* pUrl, OUString* pTarget ) +{ + ScTabViewShell* pViewSh = mrViewData.GetViewShell(); + ScInputHandler* pInputHdl = nullptr; + if (pViewSh) + pInputHdl = pViewSh->GetInputHandler(); + EditView* pView = (pInputHdl && pInputHdl->IsInputMode()) ? pInputHdl->GetTableView() : nullptr; + if (pView) + return extractURLInfo(pView->GetFieldUnderMousePointer(), pName, pUrl, pTarget); + + //! Pass on nPosX/Y? + SCCOL nPosX; + SCROW nPosY; + mrViewData.GetPosFromPixel( rPos.X(), rPos.Y(), eWhich, nPosX, nPosY ); + + SCTAB nTab = mrViewData.GetTabNo(); + ScDocShell* pDocSh = mrViewData.GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + OUString sURL; + ScRefCellValue aCell; + bool bFound = lcl_GetHyperlinkCell(rDoc, nPosX, nPosY, nTab, aCell, sURL); + if( !bFound ) + return false; + + const ScPatternAttr* pPattern = rDoc.GetPattern( nPosX, nPosY, nTab ); + // bForceToTop = sal_False, use the cell's real position + tools::Rectangle aEditRect = mrViewData.GetEditArea( eWhich, nPosX, nPosY, this, pPattern, false ); + if (rPos.Y() < aEditRect.Top()) + return false; + + // vertical can not (yet) be clicked: + + if (pPattern->GetCellOrientation() != SvxCellOrientation::Standard) + return false; + + bool bBreak = pPattern->GetItem(ATTR_LINEBREAK).GetValue() || + (pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue() == SvxCellHorJustify::Block); + SvxCellHorJustify eHorJust = pPattern->GetItem(ATTR_HOR_JUSTIFY).GetValue(); + + // EditEngine + + std::shared_ptr pEngine = createEditEngine(pDocSh, *pPattern); + + MapMode aEditMode = mrViewData.GetLogicMode(eWhich); // without draw scaling + tools::Rectangle aLogicEdit = PixelToLogic( aEditRect, aEditMode ); + tools::Long nThisColLogic = aLogicEdit.Right() - aLogicEdit.Left() + 1; + Size aPaperSize( 1000000, 1000000 ); + if (aCell.getType() == CELLTYPE_FORMULA) + { + tools::Long nSizeX = 0; + tools::Long nSizeY = 0; + mrViewData.GetMergeSizePixel( nPosX, nPosY, nSizeX, nSizeY ); + aPaperSize = Size(nSizeX, nSizeY ); + aPaperSize = PixelToLogic(aPaperSize); + } + + if (bBreak) + aPaperSize.setWidth( nThisColLogic ); + pEngine->SetPaperSize( aPaperSize ); + + std::unique_ptr pTextObj; + if (aCell.getType() == CELLTYPE_EDIT) + { + if (aCell.getEditText()) + pEngine->SetTextCurrentDefaults(*aCell.getEditText()); + } + else // Not an Edit cell and is a formula cell with 'Hyperlink' + // function if we have no URL, otherwise it could be a formula + // cell ( or other type ? ) with a hyperlink associated with it. + { + if (sURL.isEmpty()) + pTextObj = aCell.getFormula()->CreateURLObject(); + else + { + OUString aRepres = sURL; + + // TODO: text content of formatted numbers can be different + if (aCell.hasNumeric()) + aRepres = OUString::number(aCell.getValue()); + else if (aCell.getType() == CELLTYPE_FORMULA) + aRepres = aCell.getFormula()->GetString().getString(); + + pTextObj = ScEditUtil::CreateURLObjectFromURL(rDoc, sURL, aRepres); + } + + if (pTextObj) + pEngine->SetTextCurrentDefaults(*pTextObj); + } + + tools::Long nStartX = aLogicEdit.Left(); + + tools::Long nTextWidth = pEngine->CalcTextWidth(); + tools::Long nTextHeight = pEngine->GetTextHeight(); + if ( nTextWidth < nThisColLogic ) + { + if (eHorJust == SvxCellHorJustify::Right) + nStartX += nThisColLogic - nTextWidth; + else if (eHorJust == SvxCellHorJustify::Center) + nStartX += (nThisColLogic - nTextWidth) / 2; + } + + aLogicEdit.SetLeft( nStartX ); + if (!bBreak) + aLogicEdit.SetRight( nStartX + nTextWidth ); + + // There is one glitch when dealing with a hyperlink cell and + // the cell content is NUMERIC. This defaults to right aligned and + // we need to adjust accordingly. + if (aCell.hasNumeric() && eHorJust == SvxCellHorJustify::Standard) + { + aLogicEdit.SetRight( aLogicEdit.Left() + nThisColLogic - 1 ); + aLogicEdit.SetLeft( aLogicEdit.Right() - nTextWidth ); + } + aLogicEdit.SetBottom( aLogicEdit.Top() + nTextHeight ); + + Point aLogicClick = PixelToLogic(rPos,aEditMode); + if ( aLogicEdit.Contains(aLogicClick) ) + { + EditView aTempView(pEngine.get(), this); + aTempView.SetOutputArea( aLogicEdit ); + + bool bRet; + if (comphelper::LibreOfficeKit::isActive()) + { + bRet = extractURLInfo(aTempView.GetField(aLogicClick), pName, pUrl, pTarget); + } + else + { + MapMode aOld = GetMapMode(); + SetMapMode(aEditMode); // no return anymore + bRet = extractURLInfo(aTempView.GetFieldUnderMousePointer(), pName, pUrl, pTarget); + SetMapMode(aOld); + } + return bRet; + } + return false; +} + +bool ScGridWindow::HasScenarioButton( const Point& rPosPixel, ScRange& rScenRange ) +{ + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + SCTAB nTabCount = rDoc.GetTableCount(); + if ( nTab+1 there is none + tools::Long nBHeight = aButSize.Height(); + tools::Long nHSpace = static_cast( SC_SCENARIO_HSPACE * mrViewData.GetPPTX() ); + + //! cache the Ranges in Table!!!! + + ScMarkData aMarks(rDoc.GetSheetLimits()); + for (SCTAB i=nTab+1; i &ctx ) +{ + mpSpellCheckCxt = ctx; +} + +void ScGridWindow::ResetAutoSpell() +{ + if (mpSpellCheckCxt) + mpSpellCheckCxt->reset(); +} + +void ScGridWindow::ResetAutoSpellForContentChange() +{ + if (mpSpellCheckCxt) + mpSpellCheckCxt->resetForContentChange(); +} + +void ScGridWindow::SetAutoSpellData( SCCOL nPosX, SCROW nPosY, const std::vector* pRanges ) +{ + if (!mpSpellCheckCxt) + return; + + mpSpellCheckCxt->setMisspellRanges(nPosX, nPosY, pRanges); +} + +const std::vector* ScGridWindow::GetAutoSpellData( SCCOL nPosX, SCROW nPosY ) +{ + if (!mpSpellCheckCxt) + return nullptr; + + if (!maVisibleRange.isInside(nPosX, nPosY)) + return nullptr; + + return mpSpellCheckCxt->getMisspellRanges(nPosX, nPosY); +} + +bool ScGridWindow::InsideVisibleRange( SCCOL nPosX, SCROW nPosY ) +{ + return maVisibleRange.isInside(nPosX, nPosY); +} + +OString ScGridWindow::getCellCursor() const +{ + // GridWindow stores a shown cell cursor in mpOOCursors, hence + // we can use that to determine whether we would want to be showing + // one (client-side) for tiled rendering too. + if (!mpOOCursors) + return "EMPTY"_ostr; + + if (comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs)) + return mrViewData.describeCellCursorInPrintTwips(); + + return mrViewData.describeCellCursor(); +} + +void ScGridWindow::notifyKitCellCursor() const +{ + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_CURSOR, getCellCursor()); + if (bListValButton && aListValPos == mrViewData.GetCurPos()) + updateLOKValListButton(true, aListValPos); + std::vector aRects; + GetSelectionRects(aRects); + if (aRects.empty() || !mrViewData.IsActive()) + { + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, ""_ostr); + SfxLokHelper::notifyOtherViews(pViewShell, LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", "EMPTY"_ostr); + } +} + +void ScGridWindow::notifyKitCellViewCursor(const SfxViewShell* pForShell) const +{ + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + + if (pViewShell->GetDocId() != pForShell->GetDocId()) + return; + + OString aCursor("EMPTY"_ostr); + if (mpOOCursors) // cf. getCellCursor above + { + auto pForTabView = dynamic_cast(pForShell); + if (!pForTabView) + return; + + if (comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs)) + aCursor = mrViewData.describeCellCursorInPrintTwips(); + else + aCursor = pForTabView->GetViewData().describeCellCursorAt( + mrViewData.GetCurX(), mrViewData.GetCurY()); // our position. + } + SfxLokHelper::notifyOtherView(pViewShell, pForShell, LOK_CALLBACK_CELL_VIEW_CURSOR, "rectangle", aCursor); +} + +// Send our cursor details to a view described by @pForShell, or all views +// if @pForShell is null. In each case send the current view a cell-cursor +// event, and others a cell_view_cursor event. +// +// NB. we need to re-construct the cursor details for each other view in their +// own zoomed co-ordinate system (but not in scPrintTwipsMsgs mode). +void ScGridWindow::updateKitCellCursor(const SfxViewShell* pForShell) const +{ + if (comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs)) + { + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + // Generate the cursor info string just once and directly send to all. + // Calling notifyKitCellViewCursor() would regenerate the + // cursor-string unnecessarily. + OString aCursor = getCellCursor(); + + if (pForShell) + { + SfxLokHelper::notifyOtherView(pViewShell, pForShell, + LOK_CALLBACK_CELL_VIEW_CURSOR, "rectangle", aCursor); + } + else + { + notifyKitCellCursor(); + SfxLokHelper::notifyOtherViews(pViewShell, + LOK_CALLBACK_CELL_VIEW_CURSOR, "rectangle", aCursor); + } + + return; + } + + if (!pForShell) + { + for (SfxViewShell* it = SfxViewShell::GetFirst(); it; + it = SfxViewShell::GetNext(*it)) + updateKitCellCursor(it); + return; + } + + if (pForShell == mrViewData.GetViewShell()) + notifyKitCellCursor(); + else + notifyKitCellViewCursor(pForShell); +} + +void ScGridWindow::updateKitOtherCursors() const +{ + for (SfxViewShell* it = SfxViewShell::GetFirst(); it; + it = SfxViewShell::GetNext(*it)) + { + auto pOther = dynamic_cast(it); + if (!pOther) + continue; + const ScGridWindow *pGrid = pOther->GetViewData().GetActiveWin(); + assert(pGrid); + if (pGrid == this) + notifyKitCellCursor(); + else + pGrid->notifyKitCellViewCursor(mrViewData.GetViewShell()); + } +} + +void ScGridWindow::CursorChanged() +{ + // here the created OverlayObjects may be transformed in later versions. For + // now, just re-create them + + UpdateCursorOverlay(); + UpdateSparklineGroupOverlay(); +} + +void ScGridWindow::ImpCreateOverlayObjects() +{ + UpdateCursorOverlay(); + UpdateCopySourceOverlay(); + UpdateSelectionOverlay(); + UpdateHighlightOverlay(); + UpdateAutoFillOverlay(); + UpdateDragRectOverlay(); + UpdateHeaderOverlay(); + UpdateShrinkOverlay(); + UpdateSparklineGroupOverlay(); +} + +void ScGridWindow::ImpDestroyOverlayObjects() +{ + DeleteCursorOverlay(); + DeleteCopySourceOverlay(); + DeleteSelectionOverlay(); + mpOOHighlight.reset(); // DeleteHighlightOverlay + DeleteAutoFillOverlay(); + DeleteDragRectOverlay(); + DeleteHeaderOverlay(); + DeleteShrinkOverlay(); + DeleteSparklineGroupOverlay(); +} + +void ScGridWindow::UpdateAllOverlays() +{ + // delete and re-allocate all overlay objects + + ImpDestroyOverlayObjects(); + ImpCreateOverlayObjects(); +} + +void ScGridWindow::DeleteCursorOverlay() +{ + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_CURSOR, "EMPTY"_ostr); + SfxLokHelper::notifyOtherViews(pViewShell, LOK_CALLBACK_CELL_VIEW_CURSOR, "rectangle", "EMPTY"_ostr); + mpOOCursors.reset(); +} + +void ScGridWindow::DeleteCopySourceOverlay() +{ + mpOOSelectionBorder.reset(); +} + +void ScGridWindow::UpdateCopySourceOverlay() +{ + MapMode aDrawMode = GetDrawMapMode(); + MapMode aOldMode = GetMapMode(); + if ( aOldMode != aDrawMode ) + SetMapMode( aDrawMode ); + + DeleteCopySourceOverlay(); + + if (comphelper::LibreOfficeKit::isActive()) + return; + if (!mrViewData.ShowPasteSource()) + return; + if (!SC_MOD()->GetInputOptions().GetEnterPasteMode()) + return; + rtl::Reference xOverlayManager = getOverlayManager(); + if (!xOverlayManager.is()) + return; + const ScTransferObj* pTransObj = ScTransferObj::GetOwnClipboard(ScTabViewShell::GetClipData(mrViewData.GetActiveWin())); + if (!pTransObj) + return; + ScDocument* pClipDoc = pTransObj->GetDocument(); + if (!pClipDoc) + return; + + SCTAB nCurTab = mrViewData.GetCurPos().Tab(); + + ScClipParam& rClipParam = pClipDoc->GetClipParam(); + mpOOSelectionBorder.reset(new sdr::overlay::OverlayObjectList); + for ( size_t i = 0; i < rClipParam.maRanges.size(); ++i ) + { + ScRange const & r = rClipParam.maRanges[i]; + if (r.aStart.Tab() != nCurTab) + continue; + + SCCOL nClipStartX = r.aStart.Col(); + SCROW nClipStartY = r.aStart.Row(); + SCCOL nClipEndX = r.aEnd.Col(); + SCROW nClipEndY = r.aEnd.Row(); + + Point aClipStartScrPos = mrViewData.GetScrPos( nClipStartX, nClipStartY, eWhich ); + Point aClipEndScrPos = mrViewData.GetScrPos( nClipEndX + 1, nClipEndY + 1, eWhich ); + aClipStartScrPos -= Point(1, 1); + tools::Long nSizeXPix = aClipEndScrPos.X() - aClipStartScrPos.X(); + tools::Long nSizeYPix = aClipEndScrPos.Y() - aClipStartScrPos.Y(); + + tools::Rectangle aRect( aClipStartScrPos, Size(nSizeXPix, nSizeYPix) ); + + Color aHighlight = GetSettings().GetStyleSettings().GetHighlightColor(); + + tools::Rectangle aLogic = PixelToLogic(aRect, aDrawMode); + ::basegfx::B2DRange aRange = vcl::unotools::b2DRectangleFromRectangle(aLogic); + std::unique_ptr pDashedBorder(new ScOverlayDashedBorder(aRange, aHighlight)); + xOverlayManager->add(*pDashedBorder); + mpOOSelectionBorder->append(std::move(pDashedBorder)); + } + + if ( aOldMode != aDrawMode ) + SetMapMode( aOldMode ); +} + +static std::vector convertPixelToLogical( + const ScViewData& rViewData, + const std::vector& rRectangles, + tools::Rectangle &rBoundingBox) +{ + std::vector aLogicRects; + + double nPPTX = rViewData.GetPPTX(); + double nPPTY = rViewData.GetPPTY(); + + for (const auto& rRectangle : rRectangles) + { + // We explicitly create a copy, since we need to expand + // the rectangle before coordinate conversion + tools::Rectangle aRectangle(rRectangle); + aRectangle.AdjustRight(1 ); + aRectangle.AdjustBottom(1 ); + + tools::Rectangle aRect(aRectangle.Left() / nPPTX, aRectangle.Top() / nPPTY, + aRectangle.Right() / nPPTX, aRectangle.Bottom() / nPPTY); + + rBoundingBox.Union(aRect); + aLogicRects.push_back(aRect); + } + return aLogicRects; +} + +static OString rectanglesToString(const std::vector &rLogicRects) +{ + bool bFirst = true; + OStringBuffer aRects; + for (const auto &rRect : rLogicRects) + { + if (!bFirst) + aRects.append("; "); + bFirst = false; + aRects.append(rRect.toString()); + } + return aRects.makeStringAndClear(); +} + +/** + * Turn the selection ranges rRectangles into the LibreOfficeKit selection, and send to other views. + * + * @param pLogicRects - if set then don't invoke the callback, just collect the rectangles in the pointed vector. + */ +void ScGridWindow::UpdateKitSelection(const std::vector& rRectangles, std::vector* pLogicRects) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + // If this is true, rRectangles should already in print twips. + // If false, rRectangles are in pixels. + bool bInPrintTwips = comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs); + + tools::Rectangle aBoundingBox; + std::vector aConvertedRects; + + if (bInPrintTwips) + std::for_each(rRectangles.begin(), rRectangles.end(), + [&aBoundingBox](const tools::Rectangle& rRect) { aBoundingBox.Union(rRect); }); + else + aConvertedRects = convertPixelToLogical(mrViewData, rRectangles, aBoundingBox); + + const std::vector& rLogicRects = bInPrintTwips ? rRectangles : aConvertedRects; + if (pLogicRects) + { + *pLogicRects = rLogicRects; + return; + } + + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + pViewShell->UpdateInputHandler(); + OString sBoundingBoxString = "EMPTY"_ostr; + if (!aBoundingBox.IsEmpty()) + sBoundingBoxString = aBoundingBox.toString(); + OString aRectListString = rectanglesToString(rLogicRects); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_SELECTION_AREA, sBoundingBoxString); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, aRectListString); + + if (bInPrintTwips) + { + SfxLokHelper::notifyOtherViews(pViewShell, LOK_CALLBACK_TEXT_VIEW_SELECTION, + "selection", aRectListString); + return; + } + + for (SfxViewShell* it = SfxViewShell::GetFirst(); it; + it = SfxViewShell::GetNext(*it)) + { + if (it == pViewShell) + continue; + auto pOther = dynamic_cast(it); + if (!pOther) + return; + + const ScGridWindow *pGrid = pOther->GetViewData().GetActiveWin(); + assert(pGrid); + + // Fetch pixels & convert for each view separately. + tools::Rectangle aDummyBBox; + std::vector aPixelRects; + pGrid->GetPixelRectsFor(mrViewData.GetMarkData() /* ours */, aPixelRects); + auto aOtherLogicRects = convertPixelToLogical(pOther->GetViewData(), aPixelRects, aDummyBBox); + SfxLokHelper::notifyOtherView(pViewShell, pOther, LOK_CALLBACK_TEXT_VIEW_SELECTION, + "selection", rectanglesToString(aOtherLogicRects)); + } +} + +/** + * Fetch the selection ranges for other views into the LibreOfficeKit selection, + * map them into our view co-ordinates and send to our view. + */ +void ScGridWindow::updateOtherKitSelections() const +{ + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + bool bInPrintTwips = comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs); + + for (SfxViewShell* it = SfxViewShell::GetFirst(); it; + it = SfxViewShell::GetNext(*it)) + { + auto pOther = dynamic_cast(it); + if (!pOther) + return; + + // Fetch pixels & convert for each view separately. + tools::Rectangle aBoundingBox; + std::vector aRects; + OString aRectsString; + GetRectsAnyFor(pOther->GetViewData().GetMarkData() /* theirs */, aRects, bInPrintTwips); + if (bInPrintTwips) + { + std::for_each(aRects.begin(), aRects.end(), + [&aBoundingBox](const tools::Rectangle& rRect) { aBoundingBox.Union(rRect); }); + aRectsString = rectanglesToString(aRects); + } + else + aRectsString = rectanglesToString( + convertPixelToLogical(pViewShell->GetViewData(), aRects, aBoundingBox)); + + if (it == pViewShell) + { + OString sBoundingBoxString = "EMPTY"_ostr; + if (!aBoundingBox.IsEmpty()) + sBoundingBoxString = aBoundingBox.toString(); + + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_SELECTION_AREA, sBoundingBoxString); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, aRectsString); + } + else + SfxLokHelper::notifyOtherView(it, pViewShell, LOK_CALLBACK_TEXT_VIEW_SELECTION, + "selection", aRectsString); + } +} + +namespace +{ + +void updateLibreOfficeKitAutoFill(const ScViewData& rViewData, tools::Rectangle const & rRectangle) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + double nPPTX = rViewData.GetPPTX(); + double nPPTY = rViewData.GetPPTY(); + + OString sRectangleString = "EMPTY"_ostr; + if (!rRectangle.IsEmpty()) + { + // selection start handle + tools::Rectangle aLogicRectangle( + rRectangle.Left() / nPPTX, rRectangle.Top() / nPPTY, + rRectangle.Right() / nPPTX, rRectangle.Bottom() / nPPTY); + sRectangleString = aLogicRectangle.toString(); + } + + ScTabViewShell* pViewShell = rViewData.GetViewShell(); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_AUTO_FILL_AREA, sRectangleString); +} + +} //end anonymous namespace + +void ScGridWindow::UpdateCursorOverlay() +{ + ScDocument& rDoc = mrViewData.GetDocument(); + + MapMode aDrawMode = GetDrawMapMode(); + MapMode aOldMode = GetMapMode(); + if ( aOldMode != aDrawMode ) + SetMapMode( aDrawMode ); + + // Existing OverlayObjects may be transformed in later versions. + // For now, just re-create them. + + DeleteCursorOverlay(); + + std::vector aPixelRects; + + // determine the cursor rectangles in pixels (moved from ScGridWindow::DrawCursor) + + SCTAB nTab = mrViewData.GetTabNo(); + SCCOL nX = mrViewData.GetCurX(); + SCROW nY = mrViewData.GetCurY(); + + const ScPatternAttr* pPattern = rDoc.GetPattern(nX,nY,nTab); + + if (!comphelper::LibreOfficeKit::isActive() && !maVisibleRange.isInside(nX, nY)) + { + if (maVisibleRange.mnCol2 < nX || maVisibleRange.mnRow2 < nY) + return; // no further check needed, nothing visible + + // fdo#87382 Also display the cell cursor for the visible part of + // merged cells if the view position is part of merged cells. + const ScMergeAttr& rMerge = pPattern->GetItem(ATTR_MERGE); + if (rMerge.GetColMerge() <= 1 && rMerge.GetRowMerge() <= 1) + return; // not merged and invisible + + SCCOL nX2 = nX + rMerge.GetColMerge() - 1; + SCROW nY2 = nY + rMerge.GetRowMerge() - 1; + // Check if the middle or tail of the merged range is visible. + if (maVisibleRange.mnCol1 > nX2 || maVisibleRange.mnRow1 > nY2) + return; // no visible part + } + + // don't show the cursor in overlapped cells + const ScMergeFlagAttr& rMergeFlag = pPattern->GetItem(ATTR_MERGE_FLAG); + bool bOverlapped = rMergeFlag.IsOverlapped(); + + // left or above of the screen? + bool bVis = comphelper::LibreOfficeKit::isActive() || ( nX>=mrViewData.GetPosX(eHWhich) && nY>=mrViewData.GetPosY(eVWhich) ); + if (!bVis) + { + SCCOL nEndX = nX; + SCROW nEndY = nY; + const ScMergeAttr& rMerge = pPattern->GetItem(ATTR_MERGE); + if (rMerge.GetColMerge() > 1) + nEndX += rMerge.GetColMerge()-1; + if (rMerge.GetRowMerge() > 1) + nEndY += rMerge.GetRowMerge()-1; + bVis = ( nEndX>=mrViewData.GetPosX(eHWhich) && nEndY>=mrViewData.GetPosY(eVWhich) ); + } + + if ( bVis && !bOverlapped && !mrViewData.HasEditView(eWhich) && mrViewData.IsActive() ) + { + Point aScrPos = mrViewData.GetScrPos( nX, nY, eWhich, true ); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + + // completely right of/below the screen? + // (test with logical start position in aScrPos) + bool bMaybeVisible; + if ( bLayoutRTL ) + bMaybeVisible = ( aScrPos.X() >= -2 && aScrPos.Y() >= -2 ); + else + { + Size aOutSize = GetOutputSizePixel(); + bMaybeVisible = ( aScrPos.X() <= aOutSize.Width() + 2 && aScrPos.Y() <= aOutSize.Height() + 2 ); + } + + // in the tiled rendering case, don't limit to the screen size + if (bMaybeVisible || comphelper::LibreOfficeKit::isActive()) + { + tools::Long nSizeXPix; + tools::Long nSizeYPix; + mrViewData.GetMergeSizePixel( nX, nY, nSizeXPix, nSizeYPix ); + + if (bLayoutRTL) + aScrPos.AdjustX( -(nSizeXPix - 2) ); // move instead of mirroring + + // show the cursor as 4 (thin) rectangles + tools::Rectangle aRect(aScrPos, Size(nSizeXPix - 1, nSizeYPix - 1)); + + float fScaleFactor = GetDPIScaleFactor(); + + tools::Long aCursorWidth = 1 * fScaleFactor; + + tools::Rectangle aLeft = aRect; + aLeft.AdjustTop( -aCursorWidth ); + aLeft.AdjustBottom(aCursorWidth ); + aLeft.SetRight( aLeft.Left() ); + aLeft.AdjustLeft( -aCursorWidth ); + + tools::Rectangle aRight = aRect; + aRight.AdjustTop( -aCursorWidth ); + aRight.AdjustBottom(aCursorWidth ); + aRight.SetLeft( aRight.Right() ); + aRight.AdjustRight(aCursorWidth ); + + tools::Rectangle aTop = aRect; + aTop.SetBottom( aTop.Top() ); + aTop.AdjustTop( -aCursorWidth ); + + tools::Rectangle aBottom = aRect; + aBottom.SetTop( aBottom.Bottom() ); + aBottom.AdjustBottom(aCursorWidth ); + + aPixelRects.push_back(aLeft); + aPixelRects.push_back(aRight); + aPixelRects.push_back(aTop); + aPixelRects.push_back(aBottom); + } + } + + if ( !aPixelRects.empty() ) + { + if (comphelper::LibreOfficeKit::isActive()) + { + mpOOCursors.reset(new sdr::overlay::OverlayObjectList); + updateKitCellCursor(nullptr); + } + else + { + // #i70788# get the OverlayManager safely + rtl::Reference xOverlayManager = getOverlayManager(); + + if (xOverlayManager.is()) + { + Color aCursorColor = GetSettings().GetStyleSettings().GetAccentColor(); + if (mrViewData.GetActivePart() != eWhich) + // non-active pane uses a different color. + aCursorColor = SC_MOD()->GetColorConfig().GetColorValue(svtools::CALCPAGEBREAKAUTOMATIC).nColor; + std::vector< basegfx::B2DRange > aRanges; + const basegfx::B2DHomMatrix aTransform(GetOutDev()->GetInverseViewTransformation()); + + // tdf#143733, tdf#145080 - improve border visibility + // constants picked for maximum consistency at 100% and adequate response on zoom + // line width = 1.5 at 100% (0.75 left +/- 0.75 right), 50% = 1, 200% = 1.25, 400% = 2.25 + const double MinSize = 0.25 * GetDPIScaleFactor(); + double fZoom(mrViewData.GetZoomX() * 0.5); + for(const tools::Rectangle & rRA : aPixelRects) + { + basegfx::B2DRange aRB(rRA.Left() - MinSize - fZoom, rRA.Top() - MinSize - fZoom, + rRA.Right() + MinSize + fZoom, rRA.Bottom() + MinSize + fZoom); + aRB.transform(aTransform); + aRanges.push_back(aRB); + } + + std::unique_ptr pOverlay(new sdr::overlay::OverlaySelection( + sdr::overlay::OverlayType::Solid, + aCursorColor, + std::move(aRanges), + false)); + + xOverlayManager->add(*pOverlay); + mpOOCursors.reset(new sdr::overlay::OverlayObjectList); + mpOOCursors->append(std::move(pOverlay)); + } + } + } + + if ( aOldMode != aDrawMode ) + SetMapMode( aOldMode ); +} + +void ScGridWindow::GetCellSelection(std::vector& rLogicRects) +{ + std::vector aRects; + if (comphelper::LibreOfficeKit::isActive() && + comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs)) + GetSelectionRectsPrintTwips(aRects); + else + GetSelectionRects(aRects); + UpdateKitSelection(aRects, &rLogicRects); +} + +void ScGridWindow::DeleteSelectionOverlay() +{ + mpOOSelection.reset(); +} + +void ScGridWindow::UpdateSelectionOverlay() +{ + MapMode aDrawMode = GetDrawMapMode(); + MapMode aOldMode = GetMapMode(); + if ( aOldMode != aDrawMode ) + SetMapMode( aDrawMode ); + + DeleteSelectionOverlay(); + std::vector aRects; + if (comphelper::LibreOfficeKit::isActive() && + comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs)) + GetSelectionRectsPrintTwips(aRects); + else + GetSelectionRects(aRects); + + if (!aRects.empty() && mrViewData.IsActive()) + { + // #i70788# get the OverlayManager safely + rtl::Reference xOverlayManager = getOverlayManager(); + if (comphelper::LibreOfficeKit::isActive()) + { + // notify the LibreOfficeKit too + UpdateKitSelection(aRects); + } + else if (xOverlayManager.is()) + { + std::vector< basegfx::B2DRange > aRanges; + const basegfx::B2DHomMatrix aTransform(GetOutDev()->GetInverseViewTransformation()); + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + + for(const tools::Rectangle & rRA : aRects) + { + if (bLayoutRTL) + { + basegfx::B2DRange aRB(rRA.Left(), rRA.Top() - 1, rRA.Right() + 1, rRA.Bottom()); + aRB.transform(aTransform); + aRanges.push_back(aRB); + } + else + { + basegfx::B2DRange aRB(rRA.Left() - 1, rRA.Top() - 1, rRA.Right(), rRA.Bottom()); + aRB.transform(aTransform); + aRanges.push_back(aRB); + } + } + + // get the system's highlight color + const Color aHighlight(SvtOptionsDrawinglayer::getHilightColor()); + + std::unique_ptr pOverlay(new sdr::overlay::OverlaySelection( + sdr::overlay::OverlayType::Transparent, + aHighlight, + std::move(aRanges), + true)); + + xOverlayManager->add(*pOverlay); + mpOOSelection.reset(new sdr::overlay::OverlayObjectList); + mpOOSelection->append(std::move(pOverlay)); + } + } + else + { + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, "EMPTY"_ostr); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_SELECTION_AREA, "EMPTY"_ostr); + SfxLokHelper::notifyOtherViews(pViewShell, LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", "EMPTY"_ostr); + + ScInputHandler* pViewHdl = SC_MOD()->GetInputHdl(pViewShell); + if (!pViewHdl || !pViewHdl->IsEditMode()) + { + std::vector aReferenceMarks; + ScInputHandler::SendReferenceMarks(pViewShell, aReferenceMarks); + } + } + + if ( aOldMode != aDrawMode ) + SetMapMode( aOldMode ); +} + +void ScGridWindow::UpdateHighlightOverlay() +{ + mpOOHighlight.reset(); // DeleteHighlightOverlay + std::vector aRects; + if (comphelper::LibreOfficeKit::isActive() && + comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs)) + GetRectsAnyFor(mrViewData.GetHighlightData(), aRects, true); + else + GetPixelRectsFor(mrViewData.GetHighlightData(), aRects); + + if (!aRects.empty() && mrViewData.IsActive()) + { + // #i70788# get the OverlayManager safely + if (rtl::Reference xOverlayManager = getOverlayManager()) + { + std::vector< basegfx::B2DRange > aRanges; + const basegfx::B2DHomMatrix aTransform(GetOutDev()->GetInverseViewTransformation()); + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + + for(const tools::Rectangle & rRA : aRects) + { + if (bLayoutRTL) + { + basegfx::B2DRange aRB(rRA.Left(), rRA.Top() - 1, rRA.Right() + 1, rRA.Bottom()); + aRB.transform(aTransform); + aRanges.push_back(aRB); + } + else + { + basegfx::B2DRange aRB(rRA.Left() - 1, rRA.Top() - 1, rRA.Right(), rRA.Bottom()); + aRB.transform(aTransform); + aRanges.push_back(aRB); + } + } + + const Color aBackgroundColor = SC_MOD()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor; + Color aHighlightColor = Application::GetSettings().GetStyleSettings().GetAccentColor(); + aHighlightColor.Merge(aBackgroundColor, 100); + + std::unique_ptr pOverlay(new sdr::overlay::OverlaySelection( + sdr::overlay::OverlayType::Transparent, + aHighlightColor, + std::move(aRanges), + false)); + + xOverlayManager->add(*pOverlay); + mpOOHighlight.reset(new sdr::overlay::OverlayObjectList); + mpOOHighlight->append(std::move(pOverlay)); + } + } +} + +void ScGridWindow::DeleteAutoFillOverlay() +{ + mpOOAutoFill.reset(); + mpAutoFillRect.reset(); +} + +void ScGridWindow::UpdateAutoFillOverlay() +{ + MapMode aDrawMode = GetDrawMapMode(); + MapMode aOldMode = GetMapMode(); + if ( aOldMode != aDrawMode ) + SetMapMode( aDrawMode ); + + DeleteAutoFillOverlay(); + + // get the AutoFill handle rectangle in pixels + + if ( !(bAutoMarkVisible && aAutoMarkPos.Tab() == mrViewData.GetTabNo() && + !mrViewData.HasEditView(eWhich) && mrViewData.IsActive()) ) + return; + + SCCOL nX = aAutoMarkPos.Col(); + SCROW nY = aAutoMarkPos.Row(); + + if (!maVisibleRange.isInside(nX, nY) && !comphelper::LibreOfficeKit::isActive()) + { + // Autofill mark is not visible. Bail out. + return; + } + + SCTAB nTab = mrViewData.GetTabNo(); + ScDocument& rDoc = mrViewData.GetDocument(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + + // tdf#143733 tdf#145080 - improve border visibility + // constants picked for maximum consistency at 100% + // size = 6 at 100% (as before), 50% = 4.5, 200% = 9, 400% = 15 + const float fScaleFactor = 3 * GetDPIScaleFactor(); + const double fZoom(3 * mrViewData.GetZoomX()); + // Size should be even + Size aFillHandleSize(fZoom + fScaleFactor, fZoom + fScaleFactor); + + Point aFillPos = mrViewData.GetScrPos( nX, nY, eWhich, true ); + tools::Long nSizeXPix; + tools::Long nSizeYPix; + mrViewData.GetMergeSizePixel( nX, nY, nSizeXPix, nSizeYPix ); + + if (bLayoutRTL && !comphelper::LibreOfficeKit::isActive()) + aFillPos.AdjustX( -(nSizeXPix - 2 + (aFillHandleSize.Width() / 2)) ); + else + aFillPos.AdjustX(nSizeXPix - (aFillHandleSize.Width() / 2) ); + + aFillPos.AdjustY(nSizeYPix ); + aFillPos.AdjustY( -(aFillHandleSize.Height() / 2) ); + + tools::Rectangle aFillRect(aFillPos, aFillHandleSize); + + // expand rect to increase hit area + mpAutoFillRect = aFillRect; + mpAutoFillRect->expand(fScaleFactor); + + // #i70788# get the OverlayManager safely + rtl::Reference xOverlayManager = getOverlayManager(); + if (comphelper::LibreOfficeKit::isActive()) // notify the LibreOfficeKit + { + updateLibreOfficeKitAutoFill(mrViewData, aFillRect); + } + else if (xOverlayManager.is()) + { + Color aHandleColor = GetSettings().GetStyleSettings().GetHighlightColor(); + if (mrViewData.GetActivePart() != eWhich) + // non-active pane uses a different color. + aHandleColor = SC_MOD()->GetColorConfig().GetColorValue(svtools::CALCPAGEBREAKAUTOMATIC).nColor; + std::vector< basegfx::B2DRange > aRanges; + const basegfx::B2DHomMatrix aTransform(GetOutDev()->GetInverseViewTransformation()); + basegfx::B2DRange aRB = vcl::unotools::b2DRectangleFromRectangle(aFillRect); + + aRB.transform(aTransform); + aRanges.push_back(aRB); + + std::unique_ptr pOverlay(new sdr::overlay::OverlaySelection( + sdr::overlay::OverlayType::Solid, + aHandleColor, + std::move(aRanges), + false)); + + xOverlayManager->add(*pOverlay); + mpOOAutoFill.reset(new sdr::overlay::OverlayObjectList); + mpOOAutoFill->append(std::move(pOverlay)); + } + + if ( aOldMode != aDrawMode ) + SetMapMode( aOldMode ); +} + +void ScGridWindow::DeleteDragRectOverlay() +{ + mpOODragRect.reset(); +} + +void ScGridWindow::UpdateDragRectOverlay() +{ + bool bInPrintTwips = comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs); + + MapMode aDrawMode = GetDrawMapMode(); + MapMode aOldMode = GetMapMode(); + if ( aOldMode != aDrawMode ) + SetMapMode( aDrawMode ); + + DeleteDragRectOverlay(); + + // get the rectangles in pixels (moved from DrawDragRect) + + if ( bDragRect || bPagebreakDrawn ) + { + std::vector aPixelRects; + + SCCOL nX1 = bDragRect ? nDragStartX : aPagebreakDrag.aStart.Col(); + SCROW nY1 = bDragRect ? nDragStartY : aPagebreakDrag.aStart.Row(); + SCCOL nX2 = bDragRect ? nDragEndX : aPagebreakDrag.aEnd.Col(); + SCROW nY2 = bDragRect ? nDragEndY : aPagebreakDrag.aEnd.Row(); + + SCTAB nTab = mrViewData.GetTabNo(); + + SCCOL nPosX = mrViewData.GetPosX(WhichH(eWhich)); + SCROW nPosY = mrViewData.GetPosY(WhichV(eWhich)); + if (nX1 < nPosX) nX1 = nPosX; + if (nX2 < nPosX) nX2 = nPosX; + if (nY1 < nPosY) nY1 = nPosY; + if (nY2 < nPosY) nY2 = nPosY; + + Point aScrPos(bInPrintTwips ? mrViewData.GetPrintTwipsPos( nX1, nY1 ) + : mrViewData.GetScrPos( nX1, nY1, eWhich ) ); + + tools::Long nSizeXPix=0; + tools::Long nSizeYPix=0; + ScDocument& rDoc = mrViewData.GetDocument(); + double nPPTX = mrViewData.GetPPTX(); + double nPPTY = mrViewData.GetPPTY(); + SCCOLROW i; + + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + tools::Long nAdjust = comphelper::LibreOfficeKit::isActive() ? 0 : 2; + + if (rDoc.ValidCol(nX2) && nX2>=nX1) + for (i=nX1; i<=nX2; i++) + { + tools::Long nWidth = rDoc.GetColWidth( static_cast(i), nTab ); + nSizeXPix += bInPrintTwips ? nWidth : ScViewData::ToPixel( nWidth, nPPTX ); + } + else + { + aScrPos.AdjustX( -nLayoutSign ); + nSizeXPix += nAdjust; + } + + if (rDoc.ValidRow(nY2) && nY2>=nY1) + for (i=nY1; i<=nY2; i++) + { + tools::Long nHeight = rDoc.GetRowHeight( i, nTab ); + nSizeYPix += bInPrintTwips ? nHeight : ScViewData::ToPixel( nHeight, nPPTY ); + } + else + { + aScrPos.AdjustY( -1 ); + nSizeYPix += nAdjust; + } + + if (comphelper::LibreOfficeKit::isActive()) + { + nSizeXPix -= 2; + nSizeYPix -= 2; + } + + aScrPos.AdjustX( -(nAdjust * nLayoutSign) ); + aScrPos.AdjustY( -1 * nAdjust ); + tools::Rectangle aRect( aScrPos.X(), aScrPos.Y(), + aScrPos.X() + ( nSizeXPix + nAdjust ) * nLayoutSign, aScrPos.Y() + nSizeYPix + nAdjust ); + if ( bLayoutRTL ) + { + aRect.SetLeft( aRect.Right() ); // end position is left + aRect.SetRight( aScrPos.X() ); + } + + if ( meDragInsertMode == INS_CELLSDOWN ) + { + aPixelRects.emplace_back( aRect.Left()+1, aRect.Top()+3, aRect.Left()+1, aRect.Bottom()-2 ); + aPixelRects.emplace_back( aRect.Right()-1, aRect.Top()+3, aRect.Right()-1, aRect.Bottom()-2 ); + aPixelRects.emplace_back( aRect.Left()+1, aRect.Top(), aRect.Right()-1, aRect.Top()+2 ); + aPixelRects.emplace_back( aRect.Left()+1, aRect.Bottom()-1, aRect.Right()-1, aRect.Bottom()-1 ); + } + else if ( meDragInsertMode == INS_CELLSRIGHT ) + { + aPixelRects.emplace_back( aRect.Left(), aRect.Top()+1, aRect.Left()+2, aRect.Bottom()-1 ); + aPixelRects.emplace_back( aRect.Right()-1, aRect.Top()+1, aRect.Right()-1, aRect.Bottom()-1 ); + aPixelRects.emplace_back( aRect.Left()+3, aRect.Top()+1, aRect.Right()-2, aRect.Top()+1 ); + aPixelRects.emplace_back( aRect.Left()+3, aRect.Bottom()-1, aRect.Right()-2, aRect.Bottom()-1 ); + } + else + { + aPixelRects.emplace_back( aRect.Left(), aRect.Top(), aRect.Left()+2, aRect.Bottom() ); + aPixelRects.emplace_back( aRect.Right()-2, aRect.Top(), aRect.Right(), aRect.Bottom() ); + aPixelRects.emplace_back( aRect.Left()+3, aRect.Top(), aRect.Right()-3, aRect.Top()+2 ); + aPixelRects.emplace_back( aRect.Left()+3, aRect.Bottom()-2, aRect.Right()-3, aRect.Bottom() ); + } + + // #i70788# get the OverlayManager safely + rtl::Reference xOverlayManager = getOverlayManager(); + + if (xOverlayManager.is() && !comphelper::LibreOfficeKit::isActive()) + { + std::vector< basegfx::B2DRange > aRanges; + const basegfx::B2DHomMatrix aTransform(GetOutDev()->GetInverseViewTransformation()); + + for(const tools::Rectangle & rRA : aPixelRects) + { + basegfx::B2DRange aRB(rRA.Left(), rRA.Top(), rRA.Right() + 1, rRA.Bottom() + 1); + aRB.transform(aTransform); + aRanges.push_back(aRB); + } + + std::unique_ptr pOverlay(new sdr::overlay::OverlaySelection( + sdr::overlay::OverlayType::Invert, + COL_BLACK, + std::move(aRanges), + false)); + + xOverlayManager->add(*pOverlay); + mpOODragRect.reset(new sdr::overlay::OverlayObjectList); + mpOODragRect->append(std::move(pOverlay)); + } + + ScTabViewShell* pViewShell = ScTabViewShell::GetActiveViewShell(); + if (comphelper::LibreOfficeKit::isActive() && pViewShell) + { + OString aRectsString; + tools::Rectangle aBoundingBox; + + std::vector aRectangles; + aRectangles.push_back(aRect); + + if (bInPrintTwips) + { + aBoundingBox = aRect; + aRectsString = rectanglesToString(aRectangles); + } + else + { + aRectsString = rectanglesToString(convertPixelToLogical(pViewShell->GetViewData(), aRectangles, aBoundingBox)); + } + + OString sBoundingBoxString = "EMPTY"_ostr; + if (!aBoundingBox.IsEmpty()) + sBoundingBoxString = aBoundingBox.toString(); + + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_CELL_SELECTION_AREA, sBoundingBoxString); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_TEXT_SELECTION, aRectsString); + } + } + + if ( aOldMode != aDrawMode ) + SetMapMode( aOldMode ); +} + +void ScGridWindow::DeleteHeaderOverlay() +{ + mpOOHeader.reset(); +} + +void ScGridWindow::UpdateHeaderOverlay() +{ + MapMode aDrawMode = GetDrawMapMode(); + MapMode aOldMode = GetMapMode(); + if ( aOldMode != aDrawMode ) + SetMapMode( aDrawMode ); + + DeleteHeaderOverlay(); + + // Pixel rectangle is in aInvertRect + if ( !aInvertRect.IsEmpty() ) + { + // #i70788# get the OverlayManager safely + rtl::Reference xOverlayManager = getOverlayManager(); + + if (xOverlayManager.is() && !comphelper::LibreOfficeKit::isActive()) + { + // Color aHighlight = GetSettings().GetStyleSettings().GetHighlightColor(); + std::vector< basegfx::B2DRange > aRanges; + const basegfx::B2DHomMatrix aTransform(GetOutDev()->GetInverseViewTransformation()); + basegfx::B2DRange aRB(aInvertRect.Left(), aInvertRect.Top(), aInvertRect.Right() + 1, aInvertRect.Bottom() + 1); + + aRB.transform(aTransform); + aRanges.push_back(aRB); + + std::unique_ptr pOverlay(new sdr::overlay::OverlaySelection( + sdr::overlay::OverlayType::Invert, + COL_BLACK, + std::move(aRanges), + false)); + + xOverlayManager->add(*pOverlay); + mpOOHeader.reset(new sdr::overlay::OverlayObjectList); + mpOOHeader->append(std::move(pOverlay)); + } + } + + if ( aOldMode != aDrawMode ) + SetMapMode( aOldMode ); +} + +void ScGridWindow::DeleteShrinkOverlay() +{ + mpOOShrink.reset(); +} + +void ScGridWindow::UpdateShrinkOverlay() +{ + MapMode aDrawMode = GetDrawMapMode(); + MapMode aOldMode = GetMapMode(); + if ( aOldMode != aDrawMode ) + SetMapMode( aDrawMode ); + + DeleteShrinkOverlay(); + + // get the rectangle in pixels + + tools::Rectangle aPixRect; + ScRange aRange; + SCTAB nTab = mrViewData.GetTabNo(); + if ( mrViewData.IsRefMode() && nTab >= mrViewData.GetRefStartZ() && nTab <= mrViewData.GetRefEndZ() && + mrViewData.GetDelMark( aRange ) ) + { + //! limit to visible area + if ( aRange.aStart.Col() <= aRange.aEnd.Col() && + aRange.aStart.Row() <= aRange.aEnd.Row() ) + { + Point aStart = mrViewData.GetScrPos( aRange.aStart.Col(), + aRange.aStart.Row(), eWhich ); + Point aEnd = mrViewData.GetScrPos( aRange.aEnd.Col()+1, + aRange.aEnd.Row()+1, eWhich ); + aEnd.AdjustX( -1 ); + aEnd.AdjustY( -1 ); + + aPixRect = tools::Rectangle( aStart,aEnd ); + } + } + + if ( !aPixRect.IsEmpty() ) + { + // #i70788# get the OverlayManager safely + rtl::Reference xOverlayManager = getOverlayManager(); + + if (xOverlayManager.is() && !comphelper::LibreOfficeKit::isActive()) + { + std::vector< basegfx::B2DRange > aRanges; + const basegfx::B2DHomMatrix aTransform(GetOutDev()->GetInverseViewTransformation()); + basegfx::B2DRange aRB(aPixRect.Left(), aPixRect.Top(), aPixRect.Right() + 1, aPixRect.Bottom() + 1); + + aRB.transform(aTransform); + aRanges.push_back(aRB); + + std::unique_ptr pOverlay(new sdr::overlay::OverlaySelection( + sdr::overlay::OverlayType::Invert, + COL_BLACK, + std::move(aRanges), + false)); + + xOverlayManager->add(*pOverlay); + mpOOShrink.reset(new sdr::overlay::OverlayObjectList); + mpOOShrink->append(std::move(pOverlay)); + } + } + + if ( aOldMode != aDrawMode ) + SetMapMode( aOldMode ); +} + +void ScGridWindow::DeleteSparklineGroupOverlay() +{ + mpOOSparklineGroup.reset(); +} + +void ScGridWindow::UpdateSparklineGroupOverlay() +{ + MapMode aDrawMode = GetDrawMapMode(); + + MapMode aOldMode = GetMapMode(); + if (aOldMode != aDrawMode) + SetMapMode(aDrawMode); + + DeleteSparklineGroupOverlay(); + + ScAddress aCurrentAddress = mrViewData.GetCurPos(); + + ScDocument& rDocument = mrViewData.GetDocument(); + if (auto pSparkline = rDocument.GetSparkline(aCurrentAddress)) + { + mpOOSparklineGroup.reset(new sdr::overlay::OverlayObjectList); + + rtl::Reference xOverlayManager = getOverlayManager(); + if (xOverlayManager.is()) + { + auto* pList = rDocument.GetSparklineList(aCurrentAddress.Tab()); + if (pList) + { + auto const& pSparklines = pList->getSparklinesFor(pSparkline->getSparklineGroup()); + + Color aColor = SvtOptionsDrawinglayer::getHilightColor(); + + std::vector aRanges; + const basegfx::B2DHomMatrix aTransform(GetOutDev()->GetInverseViewTransformation()); + + for (auto const& pCurrentSparkline : pSparklines) + { + SCCOL nColumn = pCurrentSparkline->getColumn(); + SCROW nRow = pCurrentSparkline->getRow(); + + Point aStart = mrViewData.GetScrPos(nColumn, nRow, eWhich); + Point aEnd = mrViewData.GetScrPos(nColumn + 1, nRow + 1, eWhich); + + basegfx::B2DRange aRange(aStart.X(), aStart.Y(), aEnd.X(), aEnd.Y()); + + aRange.transform(aTransform); + aRanges.push_back(aRange); + } + + std::unique_ptr pOverlay(new sdr::overlay::OverlaySelection( + sdr::overlay::OverlayType::Transparent, + aColor, std::move(aRanges), true)); + + xOverlayManager->add(*pOverlay); + mpOOSparklineGroup->append(std::move(pOverlay)); + } + } + } + + if (aOldMode != aDrawMode) + SetMapMode(aOldMode); +} + +// #i70788# central method to get the OverlayManager safely +rtl::Reference ScGridWindow::getOverlayManager() const +{ + SdrPageView* pPV = mrViewData.GetView()->GetScDrawView()->GetSdrPageView(); + + if(pPV) + { + SdrPageWindow* pPageWin = pPV->FindPageWindow( *GetOutDev() ); + + if ( pPageWin ) + { + return pPageWin->GetOverlayManager(); + } + } + + return rtl::Reference(); +} + +void ScGridWindow::flushOverlayManager() +{ + // #i70788# get the OverlayManager safely + rtl::Reference xOverlayManager = getOverlayManager(); + + if (xOverlayManager.is()) + xOverlayManager->flush(); +} + +ScViewData& ScGridWindow::getViewData() +{ + return mrViewData; +} + +FactoryFunction ScGridWindow::GetUITestFactory() const +{ + return ScGridWinUIObject::create; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/gridwin2.cxx b/sc/source/ui/view/gridwin2.cxx new file mode 100644 index 0000000000..01f5f39dd7 --- /dev/null +++ b/sc/source/ui/view/gridwin2.cxx @@ -0,0 +1,1312 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +using namespace css; +using namespace css::sheet; +using css::sheet::DataPilotFieldOrientation; +using std::vector; + +DataPilotFieldOrientation ScGridWindow::GetDPFieldOrientation( SCCOL nCol, SCROW nRow ) const +{ + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + ScDPObject* pDPObj = rDoc.GetDPAtCursor(nCol, nRow, nTab); + if (!pDPObj) + return DataPilotFieldOrientation_HIDDEN; + + DataPilotFieldOrientation nOrient = DataPilotFieldOrientation_HIDDEN; + + // Check for page field first. + if (nCol > 0) + { + // look for the dimension header left of the drop-down arrow + tools::Long nField = pDPObj->GetHeaderDim( ScAddress( nCol-1, nRow, nTab ), nOrient ); + if ( nField >= 0 && nOrient == DataPilotFieldOrientation_PAGE ) + { + bool bIsDataLayout = false; + OUString aFieldName = pDPObj->GetDimName( nField, bIsDataLayout ); + if ( !aFieldName.isEmpty() && !bIsDataLayout ) + return DataPilotFieldOrientation_PAGE; + } + } + + nOrient = DataPilotFieldOrientation_HIDDEN; + + // Now, check for row/column field. + tools::Long nField = pDPObj->GetHeaderDim(ScAddress(nCol, nRow, nTab), nOrient); + if (nField >= 0 && (nOrient == DataPilotFieldOrientation_COLUMN || nOrient == DataPilotFieldOrientation_ROW) ) + { + bool bIsDataLayout = false; + OUString aFieldName = pDPObj->GetDimName(nField, bIsDataLayout); + if (!aFieldName.isEmpty() && !bIsDataLayout) + return nOrient; + } + + return DataPilotFieldOrientation_HIDDEN; +} + +// private method for mouse button handling +bool ScGridWindow::DoPageFieldSelection( SCCOL nCol, SCROW nRow ) +{ + if (GetDPFieldOrientation( nCol, nRow ) == DataPilotFieldOrientation_PAGE) + { + LaunchPageFieldMenu( nCol, nRow ); + return true; + } + return false; +} + +bool ScGridWindow::DoAutoFilterButton( SCCOL nCol, SCROW nRow, const MouseEvent& rMEvt ) +{ + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + Point aScrPos = mrViewData.GetScrPos(nCol, nRow, eWhich); + Point aDiffPix = rMEvt.GetPosPixel(); + + aDiffPix -= aScrPos; + bool bLOKActive = comphelper::LibreOfficeKit::isActive(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + if ( bLayoutRTL && !bLOKActive ) + aDiffPix.setX( -aDiffPix.X() ); + + tools::Long nSizeX, nSizeY; + mrViewData.GetMergeSizePixel( nCol, nRow, nSizeX, nSizeY ); + // The button height should not use the merged cell height, should still use single row height + nSizeY = ScViewData::ToPixel(rDoc.GetRowHeight(nRow, nTab), mrViewData.GetPPTY()); + Size aScrSize(nSizeX-1, nSizeY-1); + + // Check if the mouse cursor is clicking on the popup arrow box. + mpFilterButton.reset(new ScDPFieldButton(GetOutDev(), &GetSettings().GetStyleSettings(), &mrViewData.GetZoomY(), &rDoc)); + mpFilterButton->setBoundingBox(aScrPos, aScrSize, bLayoutRTL && !bLOKActive); + mpFilterButton->setPopupLeft(bLayoutRTL && bLOKActive ? false : bLayoutRTL); // #i114944# AutoFilter button is left-aligned in RTL + Point aPopupPos; + Size aPopupSize; + mpFilterButton->getPopupBoundingBox(aPopupPos, aPopupSize); + tools::Rectangle aRect(aPopupPos, aPopupSize); + if (aRect.Contains(rMEvt.GetPosPixel())) + { + if ( DoPageFieldSelection( nCol, nRow ) ) + return true; + + bool bFilterActive = IsAutoFilterActive(nCol, nRow, nTab); + mpFilterButton->setHasHiddenMember(bFilterActive); + mpFilterButton->setDrawBaseButton(false); + mpFilterButton->setDrawPopupButton(true); + mpFilterButton->setPopupPressed(true); + mpFilterButton->draw(); + LaunchAutoFilterMenu(nCol, nRow); + return true; + } + + return false; +} + +void ScGridWindow::DoPushPivotButton( SCCOL nCol, SCROW nRow, const MouseEvent& rMEvt, bool bButton, bool bPopup, bool bMultiField ) +{ + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + + ScDPObject* pDPObj = rDoc.GetDPAtCursor(nCol, nRow, nTab); + + if (pDPObj) + { + DataPilotFieldOrientation nOrient = DataPilotFieldOrientation_HIDDEN; + ScAddress aPos( nCol, nRow, nTab ); + ScAddress aDimPos = aPos; + if (!bButton && bPopup && aDimPos.Col() > 0) + // For page field selection cell, the real field position is to the left. + aDimPos.IncCol(-1); + + if (bMultiField && DPTestMultiFieldPopupArrow(rMEvt, aPos, pDPObj)) + { + // Multi-field pop up menu has been launched. Don't activate + // field move or regular popup. + return; + } + + tools::Long nField = pDPObj->GetHeaderDim(aDimPos, nOrient); + if ( nField >= 0 ) + { + bDPMouse = false; + nDPField = nField; + pDragDPObj = pDPObj; + + if (bPopup && DPTestFieldPopupArrow(rMEvt, aPos, aDimPos, pDPObj)) + { + // field name pop up menu has been launched. Don't activate + // field move. + return; + } + + if (bButton) + { + bDPMouse = true; + DPTestMouse( rMEvt, true ); + StartTracking(); + } + } + else if ( pDPObj->IsFilterButton(aPos) ) + { + ReleaseMouse(); // may have been captured in ButtonDown + + ScQueryParam aQueryParam; + SCTAB nSrcTab = 0; + const ScSheetSourceDesc* pDesc = pDPObj->GetSheetDesc(); + OSL_ENSURE(pDesc, "no sheet source for filter button"); + if (pDesc) + { + aQueryParam = pDesc->GetQueryParam(); + nSrcTab = pDesc->GetSourceRange().aStart.Tab(); + } + + SfxItemSetFixed aArgSet( mrViewData.GetViewShell()->GetPool() ); + aArgSet.Put( ScQueryItem( SCITEM_QUERYDATA, &mrViewData, &aQueryParam ) ); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr pDlg( + pFact->CreateScPivotFilterDlg( + mrViewData.GetViewShell()->GetFrameWeld(), aArgSet, nSrcTab)); + if ( pDlg->Execute() == RET_OK ) + { + ScSheetSourceDesc aNewDesc(&rDoc); + if (pDesc) + aNewDesc = *pDesc; + + const ScQueryItem& rQueryItem = pDlg->GetOutputItem(); + aNewDesc.SetQueryParam(rQueryItem.GetQueryData()); + + ScDPObject aNewObj( *pDPObj ); + aNewObj.SetSheetDesc( aNewDesc ); + ScDBDocFunc aFunc( *mrViewData.GetDocShell() ); + aFunc.DataPilotUpdate( pDPObj, &aNewObj, true, false ); + mrViewData.GetView()->CursorPosChanged(); // shells may be switched + } + } + } + else + { + OSL_FAIL("Nothing here"); + } +} + +void ScGridWindow::DoPushPivotToggle( SCCOL nCol, SCROW nRow, const MouseEvent& rMEvt ) +{ + bool bLayoutRTL = mrViewData.GetDocument().IsLayoutRTL( mrViewData.GetTabNo() ); + + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + + ScDPObject* pDPObj = rDoc.GetDPAtCursor(nCol, nRow, nTab); + if (!pDPObj) + return; + + if (!pDPObj->GetSaveData()->GetDrillDown()) + return; + + // Get the geometry of the cell. + Point aScrPos = mrViewData.GetScrPos(nCol, nRow, eWhich); + tools::Long nSizeX, nSizeY; + mrViewData.GetMergeSizePixel(nCol, nRow, nSizeX, nSizeY); + Size aScrSize(nSizeX - 1, nSizeY - 1); + + sal_uInt16 nIndent = 0; + if (const ScIndentItem* pIndentItem = rDoc.GetAttr(nCol, nRow, nTab, ATTR_INDENT)) + nIndent = pIndentItem->GetValue(); + + // Check if the mouse cursor is clicking on the toggle +/- box. + ScDPFieldButton aBtn(GetOutDev(), &GetSettings().GetStyleSettings(), &GetMapMode().GetScaleY()); + aBtn.setBoundingBox(aScrPos, aScrSize, bLayoutRTL); + aBtn.setDrawToggleButton(true, true, nIndent); + Point aPopupPos; + Size aPopupSize; + aBtn.getToggleBoundingBox(aPopupPos, aPopupSize); + tools::Rectangle aRect(aPopupPos, aPopupSize); + if (aRect.Contains(rMEvt.GetPosPixel())) + { + // Mouse cursor inside the toggle +/- box. + sheet::DataPilotTableHeaderData aData; + ScAddress aCellPos(nCol, nRow, nTab); + pDPObj->GetHeaderPositionData(aCellPos, aData); + ScDPObject aNewObj(*pDPObj); + pDPObj->ToggleDetails(aData, &aNewObj); + ScDBDocFunc aFunc(*mrViewData.GetDocShell()); + aFunc.DataPilotUpdate(pDPObj, &aNewObj, true, false); + } +} + +// Data Pilot interaction + +void ScGridWindow::DPTestMouse( const MouseEvent& rMEvt, bool bMove ) +{ + OSL_ENSURE(pDragDPObj, "pDragDPObj missing"); + + // scroll window if at edges + //! move this to separate method + + bool bTimer = false; + Point aPixel = rMEvt.GetPosPixel(); + + SCCOL nDx = 0; + SCROW nDy = 0; + if ( aPixel.X() < 0 ) + nDx = -1; + if ( aPixel.Y() < 0 ) + nDy = -1; + Size aSize = GetOutputSizePixel(); + if ( aPixel.X() >= aSize.Width() ) + nDx = 1; + if ( aPixel.Y() >= aSize.Height() ) + nDy = 1; + if ( nDx != 0 || nDy != 0 ) + { + UpdateDragRect( false, tools::Rectangle() ); + + if ( nDx != 0) + mrViewData.GetView()->ScrollX( nDx, WhichH(eWhich) ); + if ( nDy != 0 ) + mrViewData.GetView()->ScrollY( nDy, WhichV(eWhich) ); + + bTimer = true; + } + + SCCOL nPosX; + SCROW nPosY; + mrViewData.GetPosFromPixel( aPixel.X(), aPixel.Y(), eWhich, nPosX, nPosY ); + bool bMouseLeft; + bool bMouseTop; + mrViewData.GetMouseQuadrant( aPixel, eWhich, nPosX, nPosY, bMouseLeft, bMouseTop ); + + ScAddress aPos( nPosX, nPosY, mrViewData.GetTabNo() ); + + tools::Rectangle aPosRect; + DataPilotFieldOrientation nOrient; + tools::Long nDimPos; + bool bHasRange = pDragDPObj->GetHeaderDrag( aPos, bMouseLeft, bMouseTop, nDPField, + aPosRect, nOrient, nDimPos ); + UpdateDragRect( bHasRange && bMove, aPosRect ); + + bool bIsDataLayout; + sal_Int32 nDimFlags = 0; + OUString aDimName = pDragDPObj->GetDimName( nDPField, bIsDataLayout, &nDimFlags ); + bool bAllowed = !bHasRange || ScDPObject::IsOrientationAllowed( nOrient, nDimFlags ); + + if (bMove) // set mouse pointer + { + PointerStyle ePointer = PointerStyle::PivotDelete; + if ( !bAllowed ) + ePointer = PointerStyle::NotAllowed; + else if ( bHasRange ) + switch (nOrient) + { + case DataPilotFieldOrientation_COLUMN: ePointer = PointerStyle::PivotCol; break; + case DataPilotFieldOrientation_ROW: ePointer = PointerStyle::PivotRow; break; + case DataPilotFieldOrientation_PAGE: + case DataPilotFieldOrientation_DATA: ePointer = PointerStyle::PivotField; break; + default: break; + } + SetPointer( ePointer ); + } + else // execute change + { + if (!bHasRange) + nOrient = DataPilotFieldOrientation_HIDDEN; + + if ( bIsDataLayout && ( nOrient != DataPilotFieldOrientation_COLUMN && + nOrient != DataPilotFieldOrientation_ROW ) ) + { + // removing data layout is not allowed + mrViewData.GetView()->ErrorMessage(STR_PIVOT_MOVENOTALLOWED); + } + else if ( bAllowed ) + { + ScDPSaveData aSaveData( *pDragDPObj->GetSaveData() ); + + ScDPSaveDimension* pDim; + if ( bIsDataLayout ) + pDim = aSaveData.GetDataLayoutDimension(); + else + pDim = aSaveData.GetDimensionByName(aDimName); + pDim->SetOrientation( nOrient ); + aSaveData.SetPosition( pDim, nDimPos ); + + //! docfunc method with ScDPSaveData as argument? + + ScDPObject aNewObj( *pDragDPObj ); + aNewObj.SetSaveData( aSaveData ); + ScDBDocFunc aFunc( *mrViewData.GetDocShell() ); + // when dragging fields, allow re-positioning (bAllowMove) + aFunc.DataPilotUpdate( pDragDPObj, &aNewObj, true, false, true ); + mrViewData.GetView()->CursorPosChanged(); // shells may be switched + } + } + + if (bTimer && bMove) + mrViewData.GetView()->SetTimer( this, rMEvt ); // repeat event + else + mrViewData.GetView()->ResetTimer(); +} + +bool ScGridWindow::DPTestFieldPopupArrow( + const MouseEvent& rMEvt, const ScAddress& rPos, const ScAddress& rDimPos, ScDPObject* pDPObj) +{ + bool bLayoutRTL = mrViewData.GetDocument().IsLayoutRTL( mrViewData.GetTabNo() ); + bool bLOK = comphelper::LibreOfficeKit::isActive(); + + // Get the geometry of the cell. + Point aScrPos = mrViewData.GetScrPos(rPos.Col(), rPos.Row(), eWhich); + tools::Long nSizeX, nSizeY; + mrViewData.GetMergeSizePixel(rPos.Col(), rPos.Row(), nSizeX, nSizeY); + Size aScrSize(nSizeX-1, nSizeY-1); + + // Check if the mouse cursor is clicking on the popup arrow box. + ScDPFieldButton aBtn(GetOutDev(), &GetSettings().GetStyleSettings(), &GetMapMode().GetScaleY()); + aBtn.setBoundingBox(aScrPos, aScrSize, bLayoutRTL); + aBtn.setPopupLeft(false); // DataPilot popup is always right-aligned for now + Point aPopupPos; + Size aPopupSize; + aBtn.getPopupBoundingBox(aPopupPos, aPopupSize); + tools::Rectangle aRect(aPopupPos, aPopupSize); + if (aRect.Contains(rMEvt.GetPosPixel())) + { + // Mouse cursor inside the popup arrow box. Launch the field menu. + DPLaunchFieldPopupMenu(bLOK ? aScrPos : OutputToScreenPixel(aScrPos), aScrSize, rDimPos, pDPObj); + return true; + } + + return false; +} + +bool ScGridWindow::DPTestMultiFieldPopupArrow( + const MouseEvent& rMEvt, const ScAddress& rPos, ScDPObject* pDPObj) +{ + bool bLayoutRTL = mrViewData.GetDocument().IsLayoutRTL( mrViewData.GetTabNo() ); + bool bLOK = comphelper::LibreOfficeKit::isActive(); + + // Get the geometry of the cell. + Point aScrPos = mrViewData.GetScrPos(rPos.Col(), rPos.Row(), eWhich); + tools::Long nSizeX, nSizeY; + mrViewData.GetMergeSizePixel(rPos.Col(), rPos.Row(), nSizeX, nSizeY); + Size aScrSize(nSizeX - 1, nSizeY - 1); + + // Check if the mouse cursor is clicking on the popup arrow box. + ScDPFieldButton aBtn(GetOutDev(), &GetSettings().GetStyleSettings(), &GetMapMode().GetScaleY()); + aBtn.setBoundingBox(aScrPos, aScrSize, bLayoutRTL); + aBtn.setPopupLeft(false); // DataPilot popup is always right-aligned for now + aBtn.setDrawPopupButtonMulti(true); + Point aPopupPos; + Size aPopupSize; + aBtn.getPopupBoundingBox(aPopupPos, aPopupSize); + tools::Rectangle aRect(aPopupPos, aPopupSize); + if (aRect.Contains(rMEvt.GetPosPixel())) + { + DataPilotFieldOrientation nOrient; + pDPObj->GetHeaderDim(rPos, nOrient); + // Mouse cursor inside the popup arrow box. Launch the multi-field menu. + DPLaunchMultiFieldPopupMenu(bLOK ? aScrPos : OutputToScreenPixel(aScrPos), aScrSize, pDPObj, nOrient); + return true; + } + + return false; +} + +namespace { + +struct DPFieldPopupData : public ScCheckListMenuControl::ExtendedData +{ + ScDPLabelData maLabels; + ScDPObject* mpDPObj; + tools::Long mnDim; +}; + +struct DPMultiFieldPopupData : public DPFieldPopupData +{ + std::vector maFieldIndices; + std::vector maFieldNames; +}; + +class DPFieldPopupOKAction : public ScCheckListMenuControl::Action +{ +public: + explicit DPFieldPopupOKAction(ScGridWindow* p) : + mpGridWindow(p) {} + + virtual bool execute() override + { + mpGridWindow->UpdateDPFromFieldPopupMenu(); + return true; + } +private: + VclPtr mpGridWindow; +}; + +class DPFieldChangedAction : public ScCheckListMenuControl::Action +{ +public: + explicit DPFieldChangedAction(ScGridWindow* p) : + mpGridWindow(p) {} + + virtual bool execute() override + { + mpGridWindow->UpdateDPPopupMenuForFieldChange(); + return true; + } +private: + VclPtr mpGridWindow; +}; + +class PopupSortAction : public ScCheckListMenuControl::Action +{ +public: + enum SortType { ASCENDING, DESCENDING, CUSTOM }; + + explicit PopupSortAction(ScDPObject* pDPObject, tools::Long nDimIndex, SortType eType, + sal_uInt16 nUserListIndex, ScTabViewShell* pViewShell) + : mpDPObject(pDPObject) + , mnDimIndex(nDimIndex) + , meType(eType) + , mnUserListIndex(nUserListIndex) + , mpViewShell(pViewShell) + {} + + virtual bool execute() override + { + switch (meType) + { + case ASCENDING: + mpViewShell->DataPilotSort(mpDPObject, mnDimIndex, true); + break; + case DESCENDING: + mpViewShell->DataPilotSort(mpDPObject, mnDimIndex, false); + break; + case CUSTOM: + mpViewShell->DataPilotSort(mpDPObject, mnDimIndex, true, &mnUserListIndex); + break; + default: + ; + } + return true; + } + +private: + ScDPObject* mpDPObject; + tools::Long mnDimIndex; + SortType meType; + sal_uInt16 mnUserListIndex; + ScTabViewShell* mpViewShell; +}; + +} + +void ScGridWindow::DPLaunchFieldPopupMenu(const Point& rScreenPosition, const Size& rScreenSize, + const ScAddress& rAddress, ScDPObject* pDPObject) +{ + DataPilotFieldOrientation nOrient; + tools::Long nDimIndex = pDPObject->GetHeaderDim(rAddress, nOrient); + + DPLaunchFieldPopupMenu(rScreenPosition, rScreenSize, nDimIndex, pDPObject); +} + +bool lcl_FillDPFieldPopupData(tools::Long nDimIndex, ScDPObject* pDPObj, + DPFieldPopupData& rDPData, bool& bDimOrientNotPage) +{ + if (!pDPObj) + return false; + + rDPData.mnDim = nDimIndex; + pDPObj->GetSource(); + + bool bIsDataLayout; + OUString aDimName = pDPObj->GetDimName(rDPData.mnDim, bIsDataLayout); + pDPObj->BuildAllDimensionMembers(); + const ScDPSaveData* pSaveData = pDPObj->GetSaveData(); + const ScDPSaveDimension* pDim = pSaveData->GetExistingDimensionByName(aDimName); + if (!pDim) + // This should never happen. + return false; + + bDimOrientNotPage = pDim->GetOrientation() != DataPilotFieldOrientation_PAGE; + + // We need to get the list of field members. + pDPObj->FillLabelData(rDPData.mnDim, rDPData.maLabels); + rDPData.mpDPObj = pDPObj; + + return true; +} + +void ScGridWindow::DPLaunchMultiFieldPopupMenu(const Point& rScreenPosition, const Size& rScreenSize, + ScDPObject* pDPObj, DataPilotFieldOrientation nOrient) +{ + if (!pDPObj) + return; + + pDPObj->GetSource(); + + std::unique_ptr pDPData(new DPMultiFieldPopupData); + pDPObj->GetFieldIdsNames(nOrient, pDPData->maFieldIndices, pDPData->maFieldNames); + + if (pDPData->maFieldIndices.empty()) + return; + + tools::Long nDimIndex = pDPData->maFieldIndices[0]; + + bool bDimOrientNotPage = true; + if (!lcl_FillDPFieldPopupData(nDimIndex, pDPObj, *pDPData, bDimOrientNotPage)) + return; + + mpDPFieldPopup.reset(); + + weld::Window* pPopupParent = GetFrameWeld(); + mpDPFieldPopup.reset(new ScCheckListMenuControl(pPopupParent, mrViewData, + false, -1, true)); + + mpDPFieldPopup->addFields(pDPData->maFieldNames); + DPSetupFieldPopup(std::move(pDPData), bDimOrientNotPage, pDPObj, true); + + DPConfigFieldPopup(); + + if (IsMouseCaptured()) + ReleaseMouse(); + + tools::Rectangle aCellRect(rScreenPosition, rScreenSize); + mpDPFieldPopup->launch(pPopupParent, aCellRect); +} + +void ScGridWindow::DPPopulateFieldMembers(const ScDPLabelData& rLabelData) +{ + // Populate field members. + size_t n = rLabelData.maMembers.size(); + mpDPFieldPopup->setMemberSize(n); + for (size_t i = 0; i < n; ++i) + { + const ScDPLabelData::Member& rMem = rLabelData.maMembers[i]; + OUString aName = rMem.getDisplayName(); + if (aName.isEmpty()) + // Use special string for an empty name. + mpDPFieldPopup->addMember(ScResId(STR_EMPTYDATA), 0.0, rMem.mbVisible, false); + else + mpDPFieldPopup->addMember(rMem.getDisplayName(), 0.0, rMem.mbVisible, false); + } +} + +void ScGridWindow::DPSetupFieldPopup(std::unique_ptr pDPData, + bool bDimOrientNotPage, ScDPObject* pDPObj, + bool bMultiField) +{ + if (!mpDPFieldPopup || !pDPObj) + return; + + const ScDPLabelData& rLabelData = static_cast(pDPData.get())->maLabels; + const tools::Long nDimIndex = static_cast(pDPData.get())->mnDim; + mpDPFieldPopup->setExtendedData(std::move(pDPData)); + + if (bMultiField) + mpDPFieldPopup->setFieldChangedAction(new DPFieldChangedAction(this)); + + mpDPFieldPopup->setOKAction(new DPFieldPopupOKAction(this)); + DPPopulateFieldMembers(rLabelData); + + if (bDimOrientNotPage) + { + vector aUserSortNames; + ScUserList* pUserList = ScGlobal::GetUserList(); + if (pUserList) + { + size_t n = pUserList->size(); + aUserSortNames.reserve(n); + for (size_t i = 0; i < n; ++i) + { + const ScUserListData& rData = (*pUserList)[i]; + aUserSortNames.push_back(rData.GetString()); + } + } + + // Populate the menus. + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + mpDPFieldPopup->addMenuItem( + ScResId(STR_MENU_SORT_ASC), + new PopupSortAction(pDPObj, nDimIndex, PopupSortAction::ASCENDING, 0, pViewShell)); + mpDPFieldPopup->addMenuItem( + ScResId(STR_MENU_SORT_DESC), + new PopupSortAction(pDPObj, nDimIndex, PopupSortAction::DESCENDING, 0, pViewShell)); + + ScListSubMenuControl* pSubMenu = mpDPFieldPopup->addSubMenuItem(ScResId(STR_MENU_SORT_CUSTOM), !aUserSortNames.empty(), false); + if (pSubMenu) + { + size_t n = aUserSortNames.size(); + for (size_t i = 0; i < n; ++i) + { + pSubMenu->addMenuItem(aUserSortNames[i], + new PopupSortAction(pDPObj, nDimIndex, PopupSortAction::CUSTOM, sal_uInt16(i), pViewShell)); + } + pSubMenu->resizeToFitMenuItems(); + } + } + + mpDPFieldPopup->initMembers(); +} + +void ScGridWindow::DPConfigFieldPopup() +{ + if (!mpDPFieldPopup) + return; + + ScCheckListMenuControl::Config aConfig; + aConfig.mbAllowEmptySet = false; + aConfig.mbRTL = mrViewData.GetDocument().IsLayoutRTL(mrViewData.GetTabNo()); + mpDPFieldPopup->setConfig(aConfig); +} + +void ScGridWindow::DPLaunchFieldPopupMenu(const Point& rScrPos, const Size& rScrSize, + tools::Long nDimIndex, ScDPObject* pDPObj) +{ + std::unique_ptr pDPData(new DPFieldPopupData); + bool bDimOrientNotPage = true; + if (!lcl_FillDPFieldPopupData(nDimIndex, pDPObj, *pDPData, bDimOrientNotPage)) + return; + + mpDPFieldPopup.reset(); + + vcl::ILibreOfficeKitNotifier* pNotifier = nullptr; + if (comphelper::LibreOfficeKit::isActive()) + pNotifier = SfxViewShell::Current(); + + weld::Window* pPopupParent = GetFrameWeld(); + mpDPFieldPopup.reset(new ScCheckListMenuControl(pPopupParent, mrViewData, + false, -1, pNotifier)); + + DPSetupFieldPopup(std::move(pDPData), bDimOrientNotPage, pDPObj); + + DPConfigFieldPopup(); + + if (IsMouseCaptured()) + ReleaseMouse(); + + tools::Rectangle aCellRect(rScrPos, rScrSize); + mpDPFieldPopup->launch(pPopupParent, aCellRect); +} + +void ScGridWindow::UpdateDPPopupMenuForFieldChange() +{ + if (!mpDPFieldPopup) + return; + + DPMultiFieldPopupData* pDPData = static_cast(mpDPFieldPopup->getExtendedData()); + if (!pDPData) + return; + + if (pDPData->maFieldIndices.empty()) + return; + + tools::Long nIndex = mpDPFieldPopup->getField(); + if (nIndex < 0) + return; + + tools::Long nDimIndex = pDPData->maFieldIndices[nIndex]; + if (nDimIndex == pDPData->mnDim) + return; + + bool bDimOrientNotPage = true; + if (!lcl_FillDPFieldPopupData(nDimIndex, pDPData->mpDPObj, *pDPData, bDimOrientNotPage)) + return; + + mpDPFieldPopup->clearMembers(); + + DPPopulateFieldMembers(pDPData->maLabels); + + mpDPFieldPopup->initMembers(); +} + +void ScGridWindow::UpdateDPFromFieldPopupMenu() +{ + typedef std::unordered_map MemNameMapType; + + if (!mpDPFieldPopup) + return; + + DPFieldPopupData* pDPData = static_cast(mpDPFieldPopup->getExtendedData()); + if (!pDPData) + return; + + ScDPObject* pDPObj = pDPData->mpDPObj; + ScDPSaveData* pSaveData = pDPObj->GetSaveData(); + + bool bIsDataLayout; + OUString aDimName = pDPObj->GetDimName(pDPData->mnDim, bIsDataLayout); + ScDPSaveDimension* pDim = pSaveData->GetDimensionByName(aDimName); + if (!pDim) + return; + + // Build a map of layout names to original names. + const ScDPLabelData& rLabelData = pDPData->maLabels; + MemNameMapType aMemNameMap; + for (const auto& rMember : rLabelData.maMembers) + aMemNameMap.emplace(rMember.maLayoutName, rMember.maName); + + // The raw result may contain a mixture of layout names and original names. + ScCheckListMenuControl::ResultType aRawResult; + mpDPFieldPopup->getResult(aRawResult); + + std::unordered_map aResult; + for (const auto& rItem : aRawResult) + { + MemNameMapType::const_iterator itrNameMap = aMemNameMap.find(rItem.aName); + if (itrNameMap == aMemNameMap.end()) + { + // This is an original member name. Use it as-is. + OUString aName = rItem.aName; + if (aName == ScResId(STR_EMPTYDATA)) + // Translate the special empty name into an empty string. + aName.clear(); + + aResult.emplace(aName, rItem.bValid); + } + else + { + // This is a layout name. Get the original member name and use it. + aResult.emplace(itrNameMap->second, rItem.bValid); + } + } + pDim->UpdateMemberVisibility(aResult); + + ScDBDocFunc aFunc(*mrViewData.GetDocShell()); + aFunc.UpdatePivotTable(*pDPObj, true, false); +} + +namespace { + +template +inline +T lcl_getValidValue(T value, T defvalue) +{ + return (value <0) ? defvalue : value; +} + +} // anonymous namespace + +bool ScGridWindow::UpdateVisibleRange() +{ + ScDocument const& rDoc = mrViewData.GetDocument(); + SCCOL nPosX = 0; + SCROW nPosY = 0; + SCCOL nXRight = rDoc.MaxCol(); + SCROW nYBottom = rDoc.MaxRow(); + + if (comphelper::LibreOfficeKit::isActive()) + { + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + nPosX = lcl_getValidValue(pViewShell->GetLOKStartHeaderCol(), nPosX); + nPosY = lcl_getValidValue(pViewShell->GetLOKStartHeaderRow(), nPosY); + nXRight = lcl_getValidValue(pViewShell->GetLOKEndHeaderCol(), nXRight); + nYBottom = lcl_getValidValue(pViewShell->GetLOKEndHeaderRow(), nYBottom); + } + else + { + nPosX = mrViewData.GetPosX(eHWhich); + nPosY = mrViewData.GetPosY(eVWhich); + nXRight = nPosX + mrViewData.VisibleCellsX(eHWhich); + if (nXRight > rDoc.MaxCol()) + nXRight = rDoc.MaxCol(); + nYBottom = nPosY + mrViewData.VisibleCellsY(eVWhich); + if (nYBottom > rDoc.MaxRow()) + nYBottom = rDoc.MaxRow(); + } + + // Store the current visible range. + return maVisibleRange.set(nPosX, nPosY, nXRight, nYBottom); +} + +void ScGridWindow::DPMouseMove( const MouseEvent& rMEvt ) +{ + DPTestMouse( rMEvt, true ); +} + +void ScGridWindow::DPMouseButtonUp( const MouseEvent& rMEvt ) +{ + bDPMouse = false; + ReleaseMouse(); + + DPTestMouse( rMEvt, false ); + SetPointer( PointerStyle::Arrow ); +} + +void ScGridWindow::UpdateDragRect( bool bShowRange, const tools::Rectangle& rPosRect ) +{ + SCCOL nStartX = ( rPosRect.Left() >= 0 ) ? static_cast(rPosRect.Left()) : SCCOL_MAX; + SCROW nStartY = ( rPosRect.Top() >= 0 ) ? static_cast(rPosRect.Top()) : SCROW_MAX; + SCCOL nEndX = ( rPosRect.Right() >= 0 ) ? static_cast(rPosRect.Right()) : SCCOL_MAX; + SCROW nEndY = ( rPosRect.Bottom() >= 0 ) ? static_cast(rPosRect.Bottom()) : SCROW_MAX; + + if ( bShowRange == bDragRect && nDragStartX == nStartX && nDragEndX == nEndX && + nDragStartY == nStartY && nDragEndY == nEndY ) + { + return; // everything unchanged + } + + if ( bShowRange ) + { + nDragStartX = nStartX; + nDragStartY = nStartY; + nDragEndX = nEndX; + nDragEndY = nEndY; + bDragRect = true; + } + else + bDragRect = false; + + UpdateDragRectOverlay(); +} + +// Page-Break Mode + +sal_uInt16 ScGridWindow::HitPageBreak( const Point& rMouse, ScRange* pSource, + SCCOLROW* pBreak, SCCOLROW* pPrev ) +{ + sal_uInt16 nFound = SC_PD_NONE; // 0 + ScRange aSource; + SCCOLROW nBreak = 0; + SCCOLROW nPrev = 0; + + ScPageBreakData* pPageData = mrViewData.GetView()->GetPageBreakData(); + if ( pPageData ) + { + bool bHori = false; + bool bVert = false; + SCCOL nHitX = 0; + SCROW nHitY = 0; + + tools::Long nMouseX = rMouse.X(); + tools::Long nMouseY = rMouse.Y(); + SCCOL nPosX; + SCROW nPosY; + mrViewData.GetPosFromPixel( nMouseX, nMouseY, eWhich, nPosX, nPosY ); + Point aTL = mrViewData.GetScrPos( nPosX, nPosY, eWhich ); + Point aBR = mrViewData.GetScrPos( nPosX+1, nPosY+1, eWhich ); + + // Horizontal more tolerances as for vertical, because there is more space + if ( nMouseX <= aTL.X() + 4 ) + { + bHori = true; + nHitX = nPosX; + } + else if ( nMouseX >= aBR.X() - 6 ) + { + bHori = true; + nHitX = nPosX+1; // left edge of the next cell + } + if ( nMouseY <= aTL.Y() + 2 ) + { + bVert = true; + nHitY = nPosY; + } + else if ( nMouseY >= aBR.Y() - 4 ) + { + bVert = true; + nHitY = nPosY+1; // upper edge of the next cell + } + + if ( bHori || bVert ) + { + sal_uInt16 nCount = sal::static_int_cast( pPageData->GetCount() ); + for (sal_uInt16 nPos=0; nPosGetData(nPos); + ScRange aRange = rData.GetPrintRange(); + bool bLHit = ( bHori && nHitX == aRange.aStart.Col() ); + bool bRHit = ( bHori && nHitX == aRange.aEnd.Col() + 1 ); + bool bTHit = ( bVert && nHitY == aRange.aStart.Row() ); + bool bBHit = ( bVert && nHitY == aRange.aEnd.Row() + 1 ); + bool bInsideH = ( nPosX >= aRange.aStart.Col() && nPosX <= aRange.aEnd.Col() ); + bool bInsideV = ( nPosY >= aRange.aStart.Row() && nPosY <= aRange.aEnd.Row() ); + + if ( bLHit ) + { + if ( bTHit ) + nFound = SC_PD_RANGE_TL; + else if ( bBHit ) + nFound = SC_PD_RANGE_BL; + else if ( bInsideV ) + nFound = SC_PD_RANGE_L; + } + else if ( bRHit ) + { + if ( bTHit ) + nFound = SC_PD_RANGE_TR; + else if ( bBHit ) + nFound = SC_PD_RANGE_BR; + else if ( bInsideV ) + nFound = SC_PD_RANGE_R; + } + else if ( bTHit && bInsideH ) + nFound = SC_PD_RANGE_T; + else if ( bBHit && bInsideH ) + nFound = SC_PD_RANGE_B; + if (nFound) + aSource = aRange; + + // breaks + + if ( bVert && bInsideH && !nFound ) + { + size_t nRowCount = rData.GetPagesY(); + const SCROW* pRowEnd = rData.GetPageEndY(); + for (size_t nRowPos=0; nRowPos+1= aSize.Width() ) + nDx = 1; + if ( aPos.Y() >= aSize.Height() ) + nDy = 1; + if ( nDx != 0 || nDy != 0 ) + { + if ( bPagebreakDrawn ) // invert + { + bPagebreakDrawn = false; + UpdateDragRectOverlay(); + } + + if ( nDx != 0 ) mrViewData.GetView()->ScrollX( nDx, WhichH(eWhich) ); + if ( nDy != 0 ) mrViewData.GetView()->ScrollY( nDy, WhichV(eWhich) ); + bTimer = true; + } + + // Switching when fixating (so Scrolling works) + + if ( eWhich == mrViewData.GetActivePart() ) //?? + { + if ( mrViewData.GetHSplitMode() == SC_SPLIT_FIX ) + if ( nDx > 0 ) + { + if ( eWhich == SC_SPLIT_TOPLEFT ) + mrViewData.GetView()->ActivatePart( SC_SPLIT_TOPRIGHT ); + else if ( eWhich == SC_SPLIT_BOTTOMLEFT ) + mrViewData.GetView()->ActivatePart( SC_SPLIT_BOTTOMRIGHT ); + } + + if ( mrViewData.GetVSplitMode() == SC_SPLIT_FIX ) + if ( nDy > 0 ) + { + if ( eWhich == SC_SPLIT_TOPLEFT ) + mrViewData.GetView()->ActivatePart( SC_SPLIT_BOTTOMLEFT ); + else if ( eWhich == SC_SPLIT_TOPRIGHT ) + mrViewData.GetView()->ActivatePart( SC_SPLIT_BOTTOMRIGHT ); + } + } + + // from here new + + // Searching for a position between the cells (before nPosX / nPosY) + SCCOL nPosX; + SCROW nPosY; + mrViewData.GetPosFromPixel( aPos.X(), aPos.Y(), eWhich, nPosX, nPosY ); + bool bLeft, bTop; + mrViewData.GetMouseQuadrant( aPos, eWhich, nPosX, nPosY, bLeft, bTop ); + if ( !bLeft ) ++nPosX; + if ( !bTop ) ++nPosY; + + bool bBreak = ( nPagebreakMouse == SC_PD_BREAK_H || nPagebreakMouse == SC_PD_BREAK_V ); + bool bHide = false; + bool bToEnd = false; + ScRange aDrawRange = aPagebreakSource; + if ( bBreak ) + { + if ( nPagebreakMouse == SC_PD_BREAK_H ) + { + if ( nPosX > aPagebreakSource.aStart.Col() && + nPosX <= aPagebreakSource.aEnd.Col() + 1 ) // to the end is also allowed + { + bToEnd = ( nPosX == aPagebreakSource.aEnd.Col() + 1 ); + aDrawRange.aStart.SetCol( nPosX ); + aDrawRange.aEnd.SetCol( nPosX - 1 ); + } + else + bHide = true; + } + else + { + if ( nPosY > aPagebreakSource.aStart.Row() && + nPosY <= aPagebreakSource.aEnd.Row() + 1 ) // to the end is also allowed + { + bToEnd = ( nPosY == aPagebreakSource.aEnd.Row() + 1 ); + aDrawRange.aStart.SetRow( nPosY ); + aDrawRange.aEnd.SetRow( nPosY - 1 ); + } + else + bHide = true; + } + } + else + { + if ( nPagebreakMouse & SC_PD_RANGE_L ) + aDrawRange.aStart.SetCol( nPosX ); + if ( nPagebreakMouse & SC_PD_RANGE_T ) + aDrawRange.aStart.SetRow( nPosY ); + if ( nPagebreakMouse & SC_PD_RANGE_R ) + { + if ( nPosX > 0 ) + aDrawRange.aEnd.SetCol( nPosX-1 ); + else + bHide = true; + } + if ( nPagebreakMouse & SC_PD_RANGE_B ) + { + if ( nPosY > 0 ) + aDrawRange.aEnd.SetRow( nPosY-1 ); + else + bHide = true; + } + if ( aDrawRange.aStart.Col() > aDrawRange.aEnd.Col() || + aDrawRange.aStart.Row() > aDrawRange.aEnd.Row() ) + bHide = true; + } + + if ( !bPagebreakDrawn || bUp || aDrawRange != aPagebreakDrag ) + { + // draw... + + if ( bPagebreakDrawn ) + { + // invert + bPagebreakDrawn = false; + } + aPagebreakDrag = aDrawRange; + if ( !bUp && !bHide ) + { + // revert + bPagebreakDrawn = true; + } + UpdateDragRectOverlay(); + } + + // when ButtonUp execute the changes + + if ( bUp ) + { + ScViewFunc* pViewFunc = mrViewData.GetView(); + ScDocShell* pDocSh = mrViewData.GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + bool bUndo (rDoc.IsUndoEnabled()); + + if ( bBreak ) + { + bool bColumn = ( nPagebreakMouse == SC_PD_BREAK_H ); + SCCOLROW nNew = bColumn ? static_cast(nPosX) : static_cast(nPosY); + if ( nNew != nPagebreakBreak ) + { + if (bUndo) + { + OUString aUndo = ScResId( STR_UNDO_DRAG_BREAK ); + pDocSh->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, mrViewData.GetViewShell()->GetViewShellId() ); + } + + bool bGrow = !bHide && nNew > nPagebreakBreak; + if ( bColumn ) + { + if (rDoc.HasColBreak(static_cast(nPagebreakBreak), nTab) & ScBreakType::Manual) + { + ScAddress aOldAddr( static_cast(nPagebreakBreak), nPosY, nTab ); + pViewFunc->DeletePageBreak( true, true, &aOldAddr, false ); + } + if ( !bHide && !bToEnd ) // not at the end + { + ScAddress aNewAddr( static_cast(nNew), nPosY, nTab ); + pViewFunc->InsertPageBreak( true, true, &aNewAddr, false ); + } + if ( bGrow ) + { + // change last break to hard, and change scaling + bool bManualBreak(rDoc.HasColBreak(static_cast(nPagebreakPrev), nTab) & ScBreakType::Manual); + if ( static_cast(nPagebreakPrev) > aPagebreakSource.aStart.Col() && !bManualBreak ) + { + ScAddress aPrev( static_cast(nPagebreakPrev), nPosY, nTab ); + pViewFunc->InsertPageBreak( true, true, &aPrev, false ); + } + + if (!pDocSh->AdjustPrintZoom( ScRange( + static_cast(nPagebreakPrev),0,nTab, static_cast(nNew-1),0,nTab ) )) + bGrow = false; + } + } + else + { + if (rDoc.HasRowBreak(nPagebreakBreak, nTab) & ScBreakType::Manual) + { + ScAddress aOldAddr( nPosX, nPagebreakBreak, nTab ); + pViewFunc->DeletePageBreak( false, true, &aOldAddr, false ); + } + if ( !bHide && !bToEnd ) // not at the end + { + ScAddress aNewAddr( nPosX, nNew, nTab ); + pViewFunc->InsertPageBreak( false, true, &aNewAddr, false ); + } + if ( bGrow ) + { + // change last break to hard, and change scaling + bool bManualBreak(rDoc.HasRowBreak(nPagebreakPrev, nTab) & ScBreakType::Manual); + if ( nPagebreakPrev > aPagebreakSource.aStart.Row() && !bManualBreak ) + { + ScAddress aPrev( nPosX, nPagebreakPrev, nTab ); + pViewFunc->InsertPageBreak( false, true, &aPrev, false ); + } + + if (!pDocSh->AdjustPrintZoom( ScRange( + 0,nPagebreakPrev,nTab, 0,nNew-1,nTab ) )) + bGrow = false; + } + } + + if (bUndo) + { + pDocSh->GetUndoManager()->LeaveListAction(); + } + + if (!bGrow) // otherwise has already happened in AdjustPrintZoom + { + pViewFunc->UpdatePageBreakData( true ); + pDocSh->SetDocumentModified(); + } + } + } + else if ( bHide || aPagebreakDrag != aPagebreakSource ) + { + // set print range + + OUString aNewRanges; + sal_uInt16 nOldCount = rDoc.GetPrintRangeCount( nTab ); + if ( nOldCount ) + { + for (sal_uInt16 nPos=0; nPosFormat(rDoc, ScRefFlags::VALID); + else if ( !bHide ) + aTemp = aPagebreakDrag.Format(rDoc, ScRefFlags::VALID); + if (!aTemp.isEmpty()) + { + if ( !aNewRanges.isEmpty() ) + aNewRanges += ";"; + aNewRanges += aTemp; + } + } + } + } + else if (!bHide) + aNewRanges = aPagebreakDrag.Format(rDoc, ScRefFlags::VALID); + + pViewFunc->SetPrintRanges( rDoc.IsPrintEntireSheet( nTab ), &aNewRanges, nullptr, nullptr, false ); + } + } + + // Timer for Scrolling + + if (bTimer && !bUp) + mrViewData.GetView()->SetTimer( this, rMEvt ); // repeat event + else + mrViewData.GetView()->ResetTimer(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/gridwin3.cxx b/sc/source/ui/view/gridwin3.cxx new file mode 100644 index 0000000000..e550447da8 --- /dev/null +++ b/sc/source/ui/view/gridwin3.cxx @@ -0,0 +1,399 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +bool ScGridWindow::DrawMouseButtonDown(const MouseEvent& rMEvt) +{ + bool bRet = false; + FuPoor* pDraw = mrViewData.GetView()->GetDrawFuncPtr(); + if (pDraw && !mrViewData.IsRefMode()) + { + MapMode aDrawMode = GetDrawMapMode(); + MapMode aOldMode = GetMapMode(); + if ( comphelper::LibreOfficeKit::isActive() && aOldMode != aDrawMode ) + SetMapMode( aDrawMode ); + + pDraw->SetWindow( this ); + Point aLogicPos = PixelToLogic(rMEvt.GetPosPixel()); + if ( pDraw->IsDetectiveHit( aLogicPos ) ) + { + // nothing on detective arrows (double click is evaluated on ButtonUp) + bRet = true; + } + else + { + bRet = pDraw->MouseButtonDown( rMEvt ); + if ( bRet ) + UpdateStatusPosSize(); + } + + if ( comphelper::LibreOfficeKit::isActive() && aOldMode != aDrawMode ) + SetMapMode( aOldMode ); + } + + // cancel draw with right key + ScDrawView* pDrView = mrViewData.GetScDrawView(); + if ( pDrView && !rMEvt.IsLeft() && !bRet ) + { + pDrView->BrkAction(); + bRet = true; + } + return bRet; +} + +bool ScGridWindow::DrawMouseButtonUp(const MouseEvent& rMEvt) +{ + ScViewFunc* pView = mrViewData.GetView(); + bool bRet = false; + FuPoor* pDraw = pView->GetDrawFuncPtr(); + if (pDraw && !mrViewData.IsRefMode()) + { + MapMode aDrawMode = GetDrawMapMode(); + MapMode aOldMode = GetMapMode(); + if ( comphelper::LibreOfficeKit::isActive() && aOldMode != aDrawMode ) + SetMapMode( aDrawMode ); + + pDraw->SetWindow( this ); + bRet = pDraw->MouseButtonUp( rMEvt ); + + // execute "format paint brush" for drawing objects + SfxItemSet* pDrawBrush = pView->GetDrawBrushSet(); + if ( pDrawBrush ) + { + ScDrawView* pDrView = mrViewData.GetScDrawView(); + if ( pDrView ) + { + pDrView->SetAttrToMarked(*pDrawBrush, true/*bReplaceAll*/); + } + + if ( !pView->IsPaintBrushLocked() ) + pView->ResetBrushDocument(); // end paint brush mode if not locked + } + + if ( comphelper::LibreOfficeKit::isActive() && aOldMode != aDrawMode ) + SetMapMode( aOldMode ); + } + + return bRet; +} + +bool ScGridWindow::DrawMouseMove(const MouseEvent& rMEvt) +{ + FuPoor* pDraw = mrViewData.GetView()->GetDrawFuncPtr(); + if (pDraw && !mrViewData.IsRefMode()) + { + MapMode aDrawMode = GetDrawMapMode(); + MapMode aOldMode = GetMapMode(); + if ( comphelper::LibreOfficeKit::isActive() && aOldMode != aDrawMode ) + SetMapMode( aDrawMode ); + + pDraw->SetWindow( this ); + bool bRet = pDraw->MouseMove( rMEvt ); + if ( bRet ) + UpdateStatusPosSize(); + + if ( comphelper::LibreOfficeKit::isActive() && aOldMode != aDrawMode ) + SetMapMode( aOldMode ); + + return bRet; + } + else + { + SetPointer( PointerStyle::Arrow ); + return false; + } +} + +void ScGridWindow::DrawEndAction() +{ + ScDrawView* pDrView = mrViewData.GetScDrawView(); + if ( pDrView && pDrView->IsAction() ) + pDrView->BrkAction(); + + FuPoor* pDraw = mrViewData.GetView()->GetDrawFuncPtr(); + if (pDraw) + pDraw->StopDragTimer(); + + // ReleaseMouse on call +} + +bool ScGridWindow::DrawCommand(const CommandEvent& rCEvt) +{ + ScDrawView* pDrView = mrViewData.GetScDrawView(); + FuPoor* pDraw = mrViewData.GetView()->GetDrawFuncPtr(); + if (pDrView && pDraw && !mrViewData.IsRefMode()) + { + pDraw->SetWindow( this ); + sal_uInt8 nUsed = pDraw->Command( rCEvt ); + if( nUsed == SC_CMD_USED ) + nButtonDown = 0; // MouseButtonUp is swallowed... + if( nUsed || pDrView->IsAction() ) + return true; + } + + return false; +} + +bool ScGridWindow::DrawKeyInput(const KeyEvent& rKEvt, vcl::Window* pWin) +{ + ScDrawView* pDrView = mrViewData.GetScDrawView(); + FuPoor* pDraw = mrViewData.GetView()->GetDrawFuncPtr(); + + + if (pDrView && pDrView->KeyInput(rKEvt, pWin)) + return true; + + if (pDrView && pDraw && !mrViewData.IsRefMode()) + { + pDraw->SetWindow( this ); + bool bOldMarked = pDrView->AreObjectsMarked(); + if (pDraw->KeyInput( rKEvt )) + { + bool bLeaveDraw = false; + bool bUsed = true; + bool bNewMarked = pDrView->AreObjectsMarked(); + if ( !mrViewData.GetView()->IsDrawSelMode() ) + if ( !bNewMarked ) + { + mrViewData.GetViewShell()->SetDrawShell( false ); + bLeaveDraw = true; + if ( !bOldMarked && + rKEvt.GetKeyCode().GetCode() == KEY_DELETE ) + bUsed = false; // nothing deleted + if(bOldMarked) + GetFocus(); + } + if (!bLeaveDraw) + UpdateStatusPosSize(); // for moving/resizing etc. by keyboard + return bUsed; + } + } + + return false; +} + +void ScGridWindow::DrawRedraw( ScOutputData& rOutputData, SdrLayerID nLayer ) +{ + const ScViewOptions& rOpts = mrViewData.GetOptions(); + + // use new flags at SdrPaintView for hiding objects + const bool bDrawOle(VOBJ_MODE_SHOW == rOpts.GetObjMode(VOBJ_TYPE_OLE)); + const bool bDrawChart(VOBJ_MODE_SHOW == rOpts.GetObjMode(VOBJ_TYPE_CHART)); + const bool bDrawDraw(VOBJ_MODE_SHOW == rOpts.GetObjMode(VOBJ_TYPE_DRAW)); + + if(!(bDrawOle || bDrawChart || bDrawDraw)) + return; + + ScDrawView* pDrView = mrViewData.GetView()->GetScDrawView(); + + if(pDrView) + { + pDrView->setHideOle(!bDrawOle); + pDrView->setHideChart(!bDrawChart); + pDrView->setHideDraw(!bDrawDraw); + pDrView->setHideFormControl(!bDrawDraw); + } + + rOutputData.DrawSelectiveObjects(nLayer); +} + +void ScGridWindow::DrawSdrGrid( const tools::Rectangle& rDrawingRect, OutputDevice* pContentDev ) +{ + // Draw grid lines + + ScDrawView* pDrView = mrViewData.GetView()->GetScDrawView(); + if ( pDrView && pDrView->IsGridVisible() ) + { + SdrPageView* pPV = pDrView->GetSdrPageView(); + OSL_ENSURE(pPV, "PageView not available"); + if (pPV) + { + pContentDev->SetLineColor(COL_GRAY); + + pPV->DrawPageViewGrid( *pContentDev, rDrawingRect ); + } + } +} + +MapMode ScGridWindow::GetDrawMapMode( bool bForce ) +{ + ScDocument& rDoc = mrViewData.GetDocument(); + + // FIXME this shouldn't be necessary once we change the entire Calc to + // work in the logic coordinates (ideally 100ths of mm - so that it is + // the same as editeng and drawinglayer), and get rid of all the + // SetMapMode's and other unnecessary fun we have with pixels + if (comphelper::LibreOfficeKit::isActive()) + { + return mrViewData.GetLogicMode(); + } + + SCTAB nTab = mrViewData.GetTabNo(); + bool bNegativePage = rDoc.IsNegativePage( nTab ); + + MapMode aDrawMode = mrViewData.GetLogicMode(); + + ScDrawView* pDrView = mrViewData.GetView()->GetScDrawView(); + if ( pDrView || bForce ) + { + Fraction aScaleX; + Fraction aScaleY; + if (pDrView) + pDrView->GetScale( aScaleX, aScaleY ); + else + { + SCCOL nEndCol = 0; + SCROW nEndRow = 0; + rDoc.GetTableArea( nTab, nEndCol, nEndRow ); + if (nEndCol<20) nEndCol = 20; + if (nEndRow<20) nEndRow = 1000; + ScDrawUtil::CalcScale( rDoc, nTab, 0,0, nEndCol,nEndRow, GetOutDev(), + mrViewData.GetZoomX(),mrViewData.GetZoomY(), + mrViewData.GetPPTX(),mrViewData.GetPPTY(), + aScaleX,aScaleY ); + } + aDrawMode.SetScaleX(aScaleX); + aDrawMode.SetScaleY(aScaleY); + } + aDrawMode.SetOrigin(Point()); + Point aStartPos = mrViewData.GetPixPos(eWhich); + if ( bNegativePage ) + { + // RTL uses negative positions for drawing objects + aStartPos.setX( -aStartPos.X() + GetOutputSizePixel().Width() - 1 ); + } + aDrawMode.SetOrigin( PixelToLogic( aStartPos, aDrawMode ) ); + + return aDrawMode; +} + +void ScGridWindow::DrawAfterScroll() +{ + PaintImmediately(); // always, so the behaviour with and without DrawingLayer is the same + + ScDrawView* pDrView = mrViewData.GetView()->GetScDrawView(); + if (pDrView) + { + OutlinerView* pOlView = pDrView->GetTextEditOutlinerView(); + if (pOlView && pOlView->GetWindow() == this) + pOlView->ShowCursor(false); // was removed at scrolling + } +} + +void ScGridWindow::CreateAnchorHandle(SdrHdlList& rHdl, const ScAddress& rAddress) +{ + ScDrawView* pDrView = mrViewData.GetView()->GetScDrawView(); + if (pDrView) + { + const ScViewOptions& rOpts = mrViewData.GetOptions(); + if(rOpts.GetOption( VOPT_ANCHOR )) + { + bool bNegativePage = mrViewData.GetDocument().IsNegativePage( mrViewData.GetTabNo() ); + Point aPos = mrViewData.GetScrPos( rAddress.Col(), rAddress.Row(), eWhich, true ); + aPos = PixelToLogic(aPos); + rHdl.AddHdl(std::make_unique(aPos, bNegativePage ? SdrHdlKind::Anchor_TR : SdrHdlKind::Anchor)); + } + } +} + +void ScGridWindow::UpdateStatusPosSize() +{ + ScDrawView* pDrView = mrViewData.GetView()->GetScDrawView(); + if (!pDrView) + return; // shouldn't be called in that case + + SdrPageView* pPV = pDrView->GetSdrPageView(); + if (!pPV) + return; // shouldn't be called in that case either + + SfxItemSetFixed aSet(mrViewData.GetViewShell()->GetPool()); + + // Fill items for position and size: + // show action rectangle during action, + // position and size of selected object(s) if something is selected, + // mouse position otherwise + + bool bActionItem = false; + if ( pDrView->IsAction() ) // action rectangle + { + tools::Rectangle aRect; + pDrView->TakeActionRect( aRect ); + if ( !aRect.IsEmpty() ) + { + pPV->LogicToPagePos(aRect); + aSet.Put( SfxPointItem( SID_ATTR_POSITION, aRect.TopLeft() ) ); + aSet.Put( SvxSizeItem( SID_ATTR_SIZE, + Size( aRect.Right() - aRect.Left(), aRect.Bottom() - aRect.Top() ) ) ); + bActionItem = true; + } + } + if ( !bActionItem ) + { + if ( pDrView->AreObjectsMarked() ) // selected objects + { + tools::Rectangle aRect = pDrView->GetAllMarkedRect(); + pPV->LogicToPagePos(aRect); + aSet.Put( SfxPointItem( SID_ATTR_POSITION, aRect.TopLeft() ) ); + aSet.Put( SvxSizeItem( SID_ATTR_SIZE, + Size( aRect.Right() - aRect.Left(), aRect.Bottom() - aRect.Top() ) ) ); + } + else // mouse position + { + Point aPos = PixelToLogic(aCurMousePos); + pPV->LogicToPagePos(aPos); + aSet.Put( SfxPointItem( SID_ATTR_POSITION, aPos ) ); + aSet.Put( SvxSizeItem( SID_ATTR_SIZE, Size( 0, 0 ) ) ); + } + } + + mrViewData.GetBindings().SetState(aSet); +} + +bool ScGridWindow::DrawHasMarkedObj() +{ + ScDrawView* p = mrViewData.GetScDrawView(); + return p && p->AreObjectsMarked(); +} + +void ScGridWindow::DrawMarkDropObj( SdrObject* pObj ) +{ + ScDrawView* pDrView = mrViewData.GetView()->GetScDrawView(); + if (pDrView) + pDrView->MarkDropObj(pObj); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/gridwin4.cxx b/sc/source/ui/view/gridwin4.cxx new file mode 100644 index 0000000000..3639e82876 --- /dev/null +++ b/sc/source/ui/view/gridwin4.cxx @@ -0,0 +1,2695 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static void lcl_LimitRect( tools::Rectangle& rRect, const tools::Rectangle& rVisible ) +{ + if ( rRect.Top() < rVisible.Top()-1 ) rRect.SetTop( rVisible.Top()-1 ); + if ( rRect.Bottom() > rVisible.Bottom()+1 ) rRect.SetBottom( rVisible.Bottom()+1 ); + + // The header row must be drawn also when the inner rectangle is not visible, + // that is why there is no return value anymore. + // When it is far away, then lcl_DrawOneFrame is not even called. +} + +static void lcl_DrawOneFrame( vcl::RenderContext* pDev, const tools::Rectangle& rInnerPixel, + const OUString& rTitle, const Color& rColor, bool bTextBelow, + double nPPTX, double nPPTY, const Fraction& rZoomY, + ScDocument& rDoc, ScViewData& rButtonViewData, bool bLayoutRTL ) +{ + // rButtonViewData is only used to set the button size, + + tools::Rectangle aInner = rInnerPixel; + if ( bLayoutRTL ) + { + aInner.SetLeft( rInnerPixel.Right() ); + aInner.SetRight( rInnerPixel.Left() ); + } + + tools::Rectangle aVisible( Point(0,0), pDev->GetOutputSizePixel() ); + lcl_LimitRect( aInner, aVisible ); + + tools::Rectangle aOuter = aInner; + tools::Long nHor = static_cast( SC_SCENARIO_HSPACE * nPPTX ); + tools::Long nVer = static_cast( SC_SCENARIO_VSPACE * nPPTY ); + aOuter.AdjustLeft( -nHor ); + aOuter.AdjustRight(nHor ); + aOuter.AdjustTop( -nVer ); + aOuter.AdjustBottom(nVer ); + + // use ScPatternAttr::GetFont only for font size + vcl::Font aAttrFont; + rDoc.GetPool()->GetDefaultItem(ATTR_PATTERN). + fillFontOnly(aAttrFont, pDev, &rZoomY); + + // everything else from application font + vcl::Font aAppFont = pDev->GetSettings().GetStyleSettings().GetAppFont(); + aAppFont.SetFontSize( aAttrFont.GetFontSize() ); + + aAppFont.SetAlignment( ALIGN_TOP ); + pDev->SetFont( aAppFont ); + + Size aTextSize( pDev->GetTextWidth( rTitle ), pDev->GetTextHeight() ); + + if ( bTextBelow ) + aOuter.AdjustBottom(aTextSize.Height() ); + else + aOuter.AdjustTop( -(aTextSize.Height()) ); + + pDev->SetLineColor(); + pDev->SetFillColor( rColor ); + // left, top, right, bottom + pDev->DrawRect( tools::Rectangle( aOuter.Left(), aOuter.Top(), aInner.Left(), aOuter.Bottom() ) ); + pDev->DrawRect( tools::Rectangle( aOuter.Left(), aOuter.Top(), aOuter.Right(), aInner.Top() ) ); + pDev->DrawRect( tools::Rectangle( aInner.Right(), aOuter.Top(), aOuter.Right(), aOuter.Bottom() ) ); + pDev->DrawRect( tools::Rectangle( aOuter.Left(), aInner.Bottom(), aOuter.Right(), aOuter.Bottom() ) ); + + tools::Long nButtonY = bTextBelow ? aInner.Bottom() : aOuter.Top(); + + ScDDComboBoxButton aComboButton(pDev); + aComboButton.SetOptSizePixel(); + tools::Long nBWidth = tools::Long(aComboButton.GetSizePixel().Width() * rZoomY); + tools::Long nBHeight = nVer + aTextSize.Height() + 1; + Size aButSize( nBWidth, nBHeight ); + tools::Long nButtonPos = bLayoutRTL ? aOuter.Left() : aOuter.Right()-nBWidth+1; + aComboButton.Draw( Point(nButtonPos, nButtonY), aButSize ); + rButtonViewData.SetScenButSize( aButSize ); + + tools::Long nTextStart = bLayoutRTL ? aInner.Right() - aTextSize.Width() + 1 : aInner.Left(); + + bool bWasClip = false; + vcl::Region aOldClip; + bool bClip = ( aTextSize.Width() > aOuter.Right() - nBWidth - aInner.Left() ); + if ( bClip ) + { + if (pDev->IsClipRegion()) + { + bWasClip = true; + aOldClip = pDev->GetActiveClipRegion(); + } + tools::Long nClipStartX = bLayoutRTL ? aOuter.Left() + nBWidth : aInner.Left(); + tools::Long nClipEndX = bLayoutRTL ? aInner.Right() : aOuter.Right() - nBWidth; + pDev->SetClipRegion( vcl::Region(tools::Rectangle( nClipStartX, nButtonY + nVer/2, + nClipEndX, nButtonY + nVer/2 + aTextSize.Height())) ); + } + + pDev->DrawText( Point( nTextStart, nButtonY + nVer/2 ), rTitle ); + + if ( bClip ) + { + if ( bWasClip ) + pDev->SetClipRegion(aOldClip); + else + pDev->SetClipRegion(); + } + + pDev->SetFillColor(); + pDev->SetLineColor( COL_BLACK ); + pDev->DrawRect( aInner ); + pDev->DrawRect( aOuter ); +} + +static void lcl_DrawScenarioFrames( OutputDevice* pDev, ScViewData& rViewData, ScSplitPos eWhich, + SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2 ) +{ + ScDocument& rDoc = rViewData.GetDocument(); + SCTAB nTab = rViewData.GetTabNo(); + SCTAB nTabCount = rDoc.GetTableCount(); + if ( nTab+1 >= nTabCount || !rDoc.IsScenario(nTab+1) || rDoc.IsScenario(nTab) ) + return; + + if ( nX1 > 0 ) --nX1; + if ( nY1>=2 ) nY1 -= 2; // Hack: Header row affects two cells + else if ( nY1 > 0 ) --nY1; + if ( nX2 < rDoc.MaxCol() ) ++nX2; + if ( nY2 < rDoc.MaxRow()-1 ) nY2 += 2; // Hack: Header row affects two cells + else if ( nY2 < rDoc.MaxRow() ) ++nY2; + ScRange aViewRange( nX1,nY1,nTab, nX2,nY2,nTab ); + + //! cache the ranges in table!!!! + + ScMarkData aMarks(rDoc.GetSheetLimits()); + for (SCTAB i=nTab+1; isize(); j < n; ++j) + { + ScRange aRange = (*xRanges)[j]; + // Always extend scenario frame to merged cells where no new non-covered cells + // are framed + rDoc.ExtendTotalMerge( aRange ); + + //! -> Extend repaint when merging !!! + + if ( aRange.Intersects( aViewRange ) ) //! Space for Text/Button? + { + Point aStartPos = rViewData.GetScrPos( + aRange.aStart.Col(), aRange.aStart.Row(), eWhich, true ); + Point aEndPos = rViewData.GetScrPos( + aRange.aEnd.Col()+1, aRange.aEnd.Row()+1, eWhich, true ); + // on the grid: + aStartPos.AdjustX( -nLayoutSign ); + aStartPos.AdjustY( -1 ); + aEndPos.AdjustX( -nLayoutSign ); + aEndPos.AdjustY( -1 ); + + bool bTextBelow = ( aRange.aStart.Row() == 0 ); + + OUString aCurrent; + Color aColor( COL_LIGHTGRAY ); + for (SCTAB nAct=nTab+1; nAct& rHighlightRanges ) +{ + SCTAB nTab = rViewData.GetTabNo(); + for ( const auto& rHighlightRange : rHighlightRanges) + { + ScRange aRange = rHighlightRange.aRef; + if ( nTab >= aRange.aStart.Tab() && nTab <= aRange.aEnd.Tab() ) + { + rOutputData.DrawRefMark( + aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), + rHighlightRange.aColor, false ); + } + } +} + +// Calculates top-left offset to be applied based on margins and indent. +static void lcl_GetEditAreaTLOffset(tools::Long& nOffsetX, tools::Long& nOffsetY, const ScAddress& rAddr, + const ScViewData& rViewData, ScDocument& rDoc) +{ + tools::Long nLeftMargin = 0; + tools::Long nTopMargin = 0; + tools::Long nIndent = 0; + tools::Long nDummy = 0; + ScEditUtil aEUtil(&rDoc, rAddr.Col(), rAddr.Row(), rAddr.Tab(), + Point(0, 0), nullptr, rViewData.GetPPTX(), + rViewData.GetPPTY(), Fraction(1.0), Fraction(1.0), + false /* bPrintTwips */); + const ScPatternAttr* pPattern = rDoc.GetPattern(rAddr); + if (!rDoc.IsLayoutRTL(rAddr.Tab())) + nIndent = aEUtil.GetIndent(pPattern); + aEUtil.GetMargins(pPattern, nLeftMargin, nTopMargin, nDummy, nDummy); + nOffsetX = nIndent + nLeftMargin; + nOffsetY = nTopMargin; +} + +void ScGridWindow::DoInvertRect( const tools::Rectangle& rPixel ) +{ + if ( rPixel == aInvertRect ) + aInvertRect = tools::Rectangle(); // Cancel + else + { + OSL_ENSURE( aInvertRect.IsEmpty(), "DoInvertRect no pairs" ); + + aInvertRect = rPixel; // Mark new rectangle + } + + UpdateHeaderOverlay(); // uses aInvertRect +} + +void ScGridWindow::PrePaint(vcl::RenderContext& /*rRenderContext*/) +{ + // forward PrePaint to DrawingLayer + ScTabViewShell* pTabViewShell = mrViewData.GetViewShell(); + + if(pTabViewShell) + { + SdrView* pDrawView = pTabViewShell->GetScDrawView(); + + if (pDrawView) + { + pDrawView->PrePaint(); + } + } +} + +bool ScGridWindow::NeedLOKCursorInvalidation(const tools::Rectangle& rCursorRect, + const Fraction aScaleX, const Fraction aScaleY) +{ + // Don't see the need for a map as there will be only a few zoom levels + // and as of now X and Y zooms in online are the same. + for (auto& rEntry : maLOKLastCursor) + { + if (aScaleX == rEntry.aScaleX && aScaleY == rEntry.aScaleY) + { + if (rCursorRect == rEntry.aRect) + return false; // No change + + // Update and allow invalidate. + rEntry.aRect = rCursorRect; + return true; + } + } + + maLOKLastCursor.push_back(LOKCursorEntry{aScaleX, aScaleY, rCursorRect}); + return true; +} + +void ScGridWindow::InvalidateLOKViewCursor(const tools::Rectangle& rCursorRect, + const Fraction aScaleX, const Fraction aScaleY) +{ + if (!NeedLOKCursorInvalidation(rCursorRect, aScaleX, aScaleY)) + return; + + ScTabViewShell* pThisViewShell = mrViewData.GetViewShell(); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + + while (pViewShell) + { + if (pViewShell != pThisViewShell && pViewShell->GetDocId() == pThisViewShell->GetDocId()) + { + ScTabViewShell* pOtherViewShell = dynamic_cast(pViewShell); + if (pOtherViewShell) + { + ScViewData& rOtherViewData = pOtherViewShell->GetViewData(); + Fraction aZoomX = rOtherViewData.GetZoomX(); + Fraction aZoomY = rOtherViewData.GetZoomY(); + if (aZoomX == aScaleX && aZoomY == aScaleY) + { + SfxLokHelper::notifyOtherView(pThisViewShell, pOtherViewShell, + LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, "rectangle", rCursorRect.toString()); + } + } + } + + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +void ScGridWindow::Paint( vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& rRect ) +{ + ScDocument& rDoc = mrViewData.GetDocument(); + if ( rDoc.IsInInterpreter() ) + { + // Via Reschedule, interpreted cells do not trigger Invalidate again, + // otherwise for instance an error box would never appear (bug 36381). + // Later, through bNeedsRepaint everything is painted again. + if ( bNeedsRepaint ) + { + //! Merge Rectangle? + aRepaintPixel = tools::Rectangle(); // multiple -> paint all + } + else + { + bNeedsRepaint = true; + aRepaintPixel = LogicToPixel(rRect); // only affected ranges + } + return; + } + + // #i117893# If GetSizePixel needs to call the resize handler, the resulting nested Paint call + // (possibly for a larger rectangle) has to be allowed. Call GetSizePixel before setting bIsInPaint. + GetSizePixel(); + + if (bIsInPaint) + return; + + bIsInPaint = true; + + tools::Rectangle aPixRect = LogicToPixel( rRect ); + + SCCOL nX1 = mrViewData.GetPosX(eHWhich); + SCROW nY1 = mrViewData.GetPosY(eVWhich); + + SCTAB nTab = mrViewData.GetTabNo(); + + double nPPTX = mrViewData.GetPPTX(); + double nPPTY = mrViewData.GetPPTY(); + + tools::Rectangle aMirroredPixel = aPixRect; + if ( rDoc.IsLayoutRTL( nTab ) ) + { + // mirror and swap + tools::Long nWidth = GetSizePixel().Width(); + aMirroredPixel.SetLeft( nWidth - 1 - aPixRect.Right() ); + aMirroredPixel.SetRight( nWidth - 1 - aPixRect.Left() ); + } + + tools::Long nScrX = ScViewData::ToPixel( rDoc.GetColWidth( nX1, nTab ), nPPTX ); + while ( nScrX <= aMirroredPixel.Left() && nX1 < rDoc.MaxCol() ) + { + ++nX1; + nScrX += ScViewData::ToPixel( rDoc.GetColWidth( nX1, nTab ), nPPTX ); + } + SCCOL nX2 = nX1; + while ( nScrX <= aMirroredPixel.Right() && nX2 < rDoc.MaxCol() ) + { + ++nX2; + nScrX += ScViewData::ToPixel( rDoc.GetColWidth( nX2, nTab ), nPPTX ); + } + + tools::Long nScrY = 0; + ScViewData::AddPixelsWhile( nScrY, aPixRect.Top(), nY1, rDoc.MaxRow(), nPPTY, &rDoc, nTab); + SCROW nY2 = nY1; + if (nScrY <= aPixRect.Bottom() && nY2 < rDoc.MaxRow()) + { + ++nY2; + ScViewData::AddPixelsWhile( nScrY, aPixRect.Bottom(), nY2, rDoc.MaxRow(), nPPTY, &rDoc, nTab); + } + + Draw( nX1,nY1,nX2,nY2, ScUpdateMode::Marks ); // don't continue with painting + + bIsInPaint = false; +} + +void ScGridWindow::Draw( SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2, ScUpdateMode eMode ) +{ + ScDocument& rDoc = mrViewData.GetDocument(); + + // let's ignore the normal Draw() attempts when doing the tiled rendering, + // all the rendering should go through PaintTile() in that case. + // TODO revisit if we can actually turn this into an assert(), and clean + // up the callers + if (comphelper::LibreOfficeKit::isActive()) + return; + + ScModule* pScMod = SC_MOD(); + bool bTextWysiwyg = pScMod->GetInputOptions().GetTextWysiwyg(); + + if (mrViewData.IsMinimized()) + return; + + PutInOrder( nX1, nX2 ); + PutInOrder( nY1, nY2 ); + + OSL_ENSURE( rDoc.ValidCol(nX2) && rDoc.ValidRow(nY2), "GridWin Draw area too big" ); + + UpdateVisibleRange(); + + if (nX2 < maVisibleRange.mnCol1 || nY2 < maVisibleRange.mnRow1) + return; + // invisible + if (nX1 < maVisibleRange.mnCol1) + nX1 = maVisibleRange.mnCol1; + if (nY1 < maVisibleRange.mnRow1) + nY1 = maVisibleRange.mnRow1; + + if (nX1 > maVisibleRange.mnCol2 || nY1 > maVisibleRange.mnRow2) + return; + + if (nX2 > maVisibleRange.mnCol2) + nX2 = maVisibleRange.mnCol2; + if (nY2 > maVisibleRange.mnRow2) + nY2 = maVisibleRange.mnRow2; + + if ( eMode != ScUpdateMode::Marks && nX2 < maVisibleRange.mnCol2) + nX2 = maVisibleRange.mnCol2; // to continue painting + + // point of no return + + ++nPaintCount; // mark that painting is in progress + + SCTAB nTab = mrViewData.GetTabNo(); + rDoc.ExtendHidden( nX1, nY1, nX2, nY2, nTab ); + + Point aScrPos = mrViewData.GetScrPos( nX1, nY1, eWhich ); + tools::Long nMirrorWidth = GetSizePixel().Width(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + if ( bLayoutRTL ) + { + tools::Long nEndPixel = mrViewData.GetScrPos( nX2+1, maVisibleRange.mnRow1, eWhich ).X(); + nMirrorWidth = aScrPos.X() - nEndPixel; + aScrPos.setX( nEndPixel + 1 ); + } + + tools::Long nScrX = aScrPos.X(); + tools::Long nScrY = aScrPos.Y(); + + SCCOL nCurX = mrViewData.GetCurX(); + SCROW nCurY = mrViewData.GetCurY(); + SCCOL nCurEndX = nCurX; + SCROW nCurEndY = nCurY; + rDoc.ExtendMerge( nCurX, nCurY, nCurEndX, nCurEndY, nTab ); + bool bCurVis = nCursorHideCount==0 && + ( nCurEndX+1 >= nX1 && nCurX <= nX2+1 && nCurEndY+1 >= nY1 && nCurY <= nY2+1 ); + + // AutoFill Handles + if ( !bCurVis && nCursorHideCount==0 && bAutoMarkVisible && aAutoMarkPos.Tab() == nTab && + ( aAutoMarkPos.Col() != nCurX || aAutoMarkPos.Row() != nCurY ) ) + { + SCCOL nHdlX = aAutoMarkPos.Col(); + SCROW nHdlY = aAutoMarkPos.Row(); + rDoc.ExtendMerge( nHdlX, nHdlY, nHdlX, nHdlY, nTab ); + // left and top is unaffected + + //! Paint AutoFill handles alone (without Cursor) ??? + } + + double nPPTX = mrViewData.GetPPTX(); + double nPPTY = mrViewData.GetPPTY(); + + const ScViewOptions& rOpts = mrViewData.GetOptions(); + + // data block + + ScTableInfo aTabInfo; + rDoc.FillInfo( aTabInfo, nX1, nY1, nX2, nY2, nTab, + nPPTX, nPPTY, false, rOpts.GetOption(VOPT_FORMULAS), + &mrViewData.GetMarkData() ); + + Fraction aZoomX = mrViewData.GetZoomX(); + Fraction aZoomY = mrViewData.GetZoomY(); + ScOutputData aOutputData( GetOutDev(), OUTTYPE_WINDOW, aTabInfo, &rDoc, nTab, + nScrX, nScrY, nX1, nY1, nX2, nY2, nPPTX, nPPTY, + &aZoomX, &aZoomY ); + + aOutputData.SetMirrorWidth( nMirrorWidth ); // needed for RTL + aOutputData.SetSpellCheckContext(mpSpellCheckCxt.get()); + + ScopedVclPtr< VirtualDevice > xFmtVirtDev; + bool bLogicText = bTextWysiwyg; // call DrawStrings in logic MapMode? + + if ( bTextWysiwyg ) + { + // use printer for text formatting + + OutputDevice* pFmtDev = rDoc.GetPrinter(); + pFmtDev->SetMapMode( mrViewData.GetLogicMode(eWhich) ); + aOutputData.SetFmtDevice( pFmtDev ); + } + else if ( aZoomX != aZoomY && mrViewData.IsOle() ) + { + // #i45033# For OLE inplace editing with different zoom factors, + // use a virtual device with 1/100th mm as text formatting reference + + xFmtVirtDev.disposeAndReset( VclPtr::Create() ); + xFmtVirtDev->SetMapMode(MapMode(MapUnit::Map100thMM)); + aOutputData.SetFmtDevice( xFmtVirtDev.get() ); + + bLogicText = true; // use logic MapMode + } + + DrawContent(*GetOutDev(), aTabInfo, aOutputData, bLogicText); + + // If something was inverted during the Paint (selection changed from Basic Macro) + // then this is now mixed up and has to be repainted + OSL_ENSURE(nPaintCount, "Wrong nPaintCount"); + --nPaintCount; + if (!nPaintCount) + CheckNeedsRepaint(); + + // Flag drawn formula cells "unchanged". + rDoc.ResetChanged(ScRange(nX1, nY1, nTab, nX2, nY2, nTab)); + rDoc.PrepareFormulaCalc(); +} + +namespace { + +class SuppressEditViewMessagesGuard +{ +public: + SuppressEditViewMessagesGuard(EditView& rEditView) : + mrEditView(rEditView), + mbOrigSuppressFlag(rEditView.IsSuppressLOKMessages()) + { + if (!mbOrigSuppressFlag) + mrEditView.SuppressLOKMessages(true); + } + + ~SuppressEditViewMessagesGuard() + { + if (mrEditView.IsSuppressLOKMessages() != mbOrigSuppressFlag) + mrEditView.SuppressLOKMessages(mbOrigSuppressFlag); + } + +private: + EditView& mrEditView; + const bool mbOrigSuppressFlag; +}; + +} + +/** + * Used to store the necessary information about the (combined-)tile + * area relevant to coordinate transformations in RTL mode. + */ +class ScLokRTLContext +{ +public: + ScLokRTLContext(const ScOutputData& rOutputData, const tools::Long nTileDeviceOriginPixelX): + mrOutputData(rOutputData), + mnTileDevOriginX(nTileDeviceOriginPixelX) + {} + + /** + * Converts from document x pixel position to the + * corresponding pixel position w.r.t the tile device origin. + */ + tools::Long docToTilePos(tools::Long nPosX) const + { + tools::Long nMirrorX = (-2 * mnTileDevOriginX) + mrOutputData.GetScrW(); + return nMirrorX - 1 - nPosX; + } + + +private: + const ScOutputData& mrOutputData; + const tools::Long mnTileDevOriginX; +}; + +namespace +{ +int lcl_GetMultiLineHeight(EditEngine* pEditEngine) +{ + int nHeight = 0; + int nParagraphs = pEditEngine->GetParagraphCount(); + if (nParagraphs > 1 || (nParagraphs > 0 && pEditEngine->GetLineCount(0) > 1)) + { + for (int nPara = 0; nPara < nParagraphs; nPara++) + { + nHeight += pEditEngine->GetLineCount(nPara) * pEditEngine->GetLineHeight(nPara); + } + } + + return nHeight; +} +} + +void ScGridWindow::DrawContent(OutputDevice &rDevice, const ScTableInfo& rTableInfo, ScOutputData& aOutputData, + bool bLogicText) +{ + ScModule* pScMod = SC_MOD(); + ScDocument& rDoc = mrViewData.GetDocument(); + const ScViewOptions& rOpts = mrViewData.GetOptions(); + bool bIsTiledRendering = comphelper::LibreOfficeKit::isActive(); + bool bNoBackgroundAndGrid = bIsTiledRendering + && comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scNoGridBackground); + + SCTAB nTab = aOutputData.nTab; + SCCOL nX1 = aOutputData.nX1; + SCROW nY1 = aOutputData.nY1; + SCCOL nX2 = aOutputData.nX2; + SCROW nY2 = aOutputData.nY2; + tools::Long nScrX = aOutputData.nScrX; + tools::Long nScrY = aOutputData.nScrY; + + const svtools::ColorConfig& rColorCfg = pScMod->GetColorConfig(); + Color aGridColor( rColorCfg.GetColorValue( svtools::CALCGRID ).nColor ); + if ( aGridColor == COL_TRANSPARENT ) + { + // use view options' grid color only if color config has "automatic" color + aGridColor = rOpts.GetGridColor(); + } + + aOutputData.SetSyntaxMode ( mrViewData.IsSyntaxMode() ); + aOutputData.SetGridColor ( aGridColor ); + aOutputData.SetShowNullValues ( rOpts.GetOption( VOPT_NULLVALS ) ); + aOutputData.SetShowFormulas ( rOpts.GetOption( VOPT_FORMULAS ) ); + aOutputData.SetShowSpellErrors ( rDoc.GetDocOptions().IsAutoSpell() ); + aOutputData.SetMarkClipped ( SC_MOD()->GetColorConfig().GetColorValue(svtools::CALCTEXTOVERFLOW).bIsVisible ); + + aOutputData.SetUseStyleColor( true ); // always set in table view + + aOutputData.SetViewShell( mrViewData.GetViewShell() ); + + bool bGrid = rOpts.GetOption( VOPT_GRID ) && mrViewData.GetShowGrid(); + bool bGridFirst = !rOpts.GetOption( VOPT_GRID_ONTOP ); + + bool bPage = rOpts.GetOption( VOPT_PAGEBREAKS ) && !bIsTiledRendering; + + bool bPageMode = mrViewData.IsPagebreakMode(); + if (bPageMode) // after FindChanged + { + // SetPagebreakMode also initializes bPrinted Flags + aOutputData.SetPagebreakMode( mrViewData.GetView()->GetPageBreakData() ); + } + + EditView* pEditView = nullptr; + bool bEditMode = mrViewData.HasEditView(eWhich); + if ( bEditMode && mrViewData.GetRefTabNo() == nTab ) + { + SCCOL nEditCol; + SCROW nEditRow; + mrViewData.GetEditView( eWhich, pEditView, nEditCol, nEditRow ); + SCCOL nEditEndCol = mrViewData.GetEditEndCol(); + SCROW nEditEndRow = mrViewData.GetEditEndRow(); + + if ( nEditEndCol >= nX1 && nEditCol <= nX2 && nEditEndRow >= nY1 && nEditRow <= nY2 ) + aOutputData.SetEditCell( nEditCol, nEditRow ); + else + bEditMode = false; + } + + const MapMode aOriginalMode = rDevice.GetMapMode(); + + // define drawing layer map mode and paint rectangle + MapMode aDrawMode = GetDrawMapMode(); + if (bIsTiledRendering) + { + // FIXME this shouldn't be necessary once we change the entire Calc to + // work in the logic coordinates (ideally 100ths of mm - so that it is + // the same as editeng and drawinglayer), and get rid of all the + // SetMapMode's and other unnecessary fun we have with pixels + // See also ScGridWindow::GetDrawMapMode() for the rest of this hack + aDrawMode.SetOrigin(PixelToLogic(Point(nScrX, nScrY), aDrawMode)); + } + tools::Rectangle aDrawingRectLogic; + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + bool bLokRTL = bLayoutRTL && bIsTiledRendering; + std::unique_ptr pLokRTLCtxt( + bLokRTL ? + new ScLokRTLContext(aOutputData, o3tl::convert(aOriginalMode.GetOrigin().X(), o3tl::Length::twip, o3tl::Length::px)) : + nullptr); + + { + // get drawing pixel rect + tools::Rectangle aDrawingRectPixel( + bLokRTL ? Point(-(nScrX + aOutputData.GetScrW()), nScrY) : Point(nScrX, nScrY), + Size(aOutputData.GetScrW(), aOutputData.GetScrH())); + + // correct for border (left/right) + if(rDoc.MaxCol() == nX2 && !bLokRTL) + { + if(bLayoutRTL) + { + aDrawingRectPixel.SetLeft( 0 ); + } + else + { + aDrawingRectPixel.SetRight( GetOutputSizePixel().getWidth() ); + } + } + + // correct for border (bottom) + if(rDoc.MaxRow() == nY2) + { + aDrawingRectPixel.SetBottom( GetOutputSizePixel().getHeight() ); + } + + // get logic positions + aDrawingRectLogic = PixelToLogic(aDrawingRectPixel, aDrawMode); + } + + bool bInPlaceEditing = bEditMode && (mrViewData.GetRefTabNo() == mrViewData.GetTabNo()); + vcl::Cursor* pInPlaceCrsr = nullptr; + bool bInPlaceVisCursor = false; + if (bInPlaceEditing) + { + // toggle the cursor off if it's on to ensure the cursor invert + // background logic remains valid after the background is cleared on + // the next cursor flash + pInPlaceCrsr = pEditView->GetCursor(); + bInPlaceVisCursor = pInPlaceCrsr && pInPlaceCrsr->IsVisible(); + if (bInPlaceVisCursor) + pInPlaceCrsr->Hide(); + } + + OutputDevice* pContentDev = &rDevice; // device for document content, used by overlay manager + SdrPaintWindow* pTargetPaintWindow = nullptr; // #i74769# work with SdrPaintWindow directly + + { + // init redraw + ScTabViewShell* pTabViewShell = mrViewData.GetViewShell(); + + if(pTabViewShell) + { + MapMode aCurrentMapMode(pContentDev->GetMapMode()); + pContentDev->SetMapMode(aDrawMode); + SdrView* pDrawView = pTabViewShell->GetScDrawView(); + + if(pDrawView) + { + // #i74769# Use new BeginDrawLayers() interface + vcl::Region aDrawingRegion(aDrawingRectLogic); + pTargetPaintWindow = pDrawView->BeginDrawLayers(pContentDev, aDrawingRegion); + OSL_ENSURE(pTargetPaintWindow, "BeginDrawLayers: Got no SdrPaintWindow (!)"); + + if (!bIsTiledRendering) + { + // #i74769# get target device from SdrPaintWindow, this may be the prerender + // device now, too. + pContentDev = &(pTargetPaintWindow->GetTargetOutputDevice()); + aOutputData.SetContentDevice(pContentDev); + } + } + + pContentDev->SetMapMode(aCurrentMapMode); + } + } + + // app-background / document edge (area) (Pixel) + if ( !bIsTiledRendering && ( nX2 == rDoc.MaxCol() || nY2 == rDoc.MaxRow() ) ) + { + // save MapMode and set to pixel + MapMode aCurrentMapMode(pContentDev->GetMapMode()); + pContentDev->SetMapMode(MapMode(MapUnit::MapPixel)); + + tools::Rectangle aPixRect( Point(), GetOutputSizePixel() ); + pContentDev->SetFillColor( rColorCfg.GetColorValue(svtools::APPBACKGROUND).nColor ); + pContentDev->SetLineColor(); + if ( nX2==rDoc.MaxCol() ) + { + tools::Rectangle aDrawRect( aPixRect ); + if ( bLayoutRTL ) + aDrawRect.SetRight( nScrX - 1 ); + else + aDrawRect.SetLeft( nScrX + aOutputData.GetScrW() ); + if (aDrawRect.Right() >= aDrawRect.Left()) + pContentDev->DrawRect( aDrawRect ); + } + if ( nY2==rDoc.MaxRow() ) + { + tools::Rectangle aDrawRect( aPixRect ); + aDrawRect.SetTop( nScrY + aOutputData.GetScrH() ); + if ( nX2==rDoc.MaxCol() ) + { + // no double painting of the corner + if ( bLayoutRTL ) + aDrawRect.SetLeft( nScrX ); + else + aDrawRect.SetRight( nScrX + aOutputData.GetScrW() - 1 ); + } + if (aDrawRect.Bottom() >= aDrawRect.Top()) + pContentDev->DrawRect( aDrawRect ); + } + + // restore MapMode + pContentDev->SetMapMode(aCurrentMapMode); + } + + if ( rDoc.HasBackgroundDraw( nTab, aDrawingRectLogic ) ) + { + pContentDev->SetMapMode(MapMode(MapUnit::MapPixel)); + aOutputData.DrawClear(); + + // drawing background + + pContentDev->SetMapMode(aDrawMode); + DrawRedraw( aOutputData, SC_LAYER_BACK ); + } + else + aOutputData.SetSolidBackground(!bNoBackgroundAndGrid); + + aOutputData.DrawDocumentBackground(); + + if (bGridFirst && (bGrid || bPage)) + { + // Draw lines in background color cover over lok client grid lines in merged cell areas if bNoBackgroundAndGrid is set. + if (bNoBackgroundAndGrid) + aOutputData.DrawGrid(*pContentDev, false /* bGrid */, false /* bPage */, true /* bMergeCover */); + else + aOutputData.DrawGrid(*pContentDev, bGrid, bPage); + } + + aOutputData.DrawBackground(*pContentDev); + + if (!bGridFirst && (bGrid || bPage) && !bNoBackgroundAndGrid) + aOutputData.DrawGrid(*pContentDev, bGrid, bPage); + + pContentDev->SetMapMode(MapMode(MapUnit::MapPixel)); + + //tdf#128258 - draw a dotted line before hidden columns/rows + DrawHiddenIndicator(nX1,nY1,nX2,nY2, *pContentDev); + + if ( bPageMode ) + { + // DrawPagePreview draws complete lines/page numbers, must always be clipped + if ( aOutputData.SetChangedClip() ) + { + DrawPagePreview(nX1,nY1,nX2,nY2, *pContentDev); + pContentDev->SetClipRegion(); + } + } + + aOutputData.DrawShadow(); + aOutputData.DrawFrame(*pContentDev); + + aOutputData.DrawSparklines(*pContentDev); + + // Show Note Mark + if ( rOpts.GetOption( VOPT_NOTES ) ) + aOutputData.DrawNoteMarks(*pContentDev); + + if ( rOpts.GetOption( VOPT_FORMULAS_MARKS ) ) + aOutputData.DrawFormulaMarks(*pContentDev); + + if ( !bLogicText ) + aOutputData.DrawStrings(); // in pixel MapMode + + // edit cells and printer-metrics text must be before the buttons + // (DataPilot buttons contain labels in UI font) + + pContentDev->SetMapMode(mrViewData.GetLogicMode(eWhich)); + if ( bLogicText ) + aOutputData.DrawStrings(true); // in logic MapMode if bLogicText is set + aOutputData.DrawEdit(true); + + // the buttons are painted in absolute coordinates + if (bIsTiledRendering) + { + // Tiled offset nScrX, nScrY + MapMode aMap( MapUnit::MapPixel ); + Point aOrigin = aOriginalMode.GetOrigin(); + aOrigin.setX(o3tl::convert(aOrigin.getX(), o3tl::Length::twip, o3tl::Length::px) + nScrX); + aOrigin.setY(o3tl::convert(aOrigin.getY(), o3tl::Length::twip, o3tl::Length::px) + nScrY); + aMap.SetOrigin(aOrigin); + pContentDev->SetMapMode(aMap); + } + else + pContentDev->SetMapMode(MapMode(MapUnit::MapPixel)); + + // Autofilter- and Pivot-Buttons + DrawButtons(nX1, nX2, rTableInfo, pContentDev, pLokRTLCtxt.get()); // Pixel + + pContentDev->SetMapMode(MapMode(MapUnit::MapPixel)); + + aOutputData.DrawClipMarks(); + + // In any case, Scenario / ChangeTracking must happen after DrawGrid, also for !bGridFirst + + //! test if ChangeTrack display is active + //! Disable scenario frame via view option? + + SCTAB nTabCount = rDoc.GetTableCount(); + const std::vector &rHigh = mrViewData.GetView()->GetHighlightRanges(); + bool bHasScenario = ( nTab+1SetMapMode(aDrawMode); + + // Bitmaps and buttons are in absolute pixel coordinates. + const MapMode aOrig = pContentDev->GetMapMode(); + if (bIsTiledRendering) + { + Point aOrigin = aOriginalMode.GetOrigin(); + tools::Long nXOffset = bLayoutRTL ? + (-o3tl::convert(aOrigin.getX(), o3tl::Length::twip, o3tl::Length::px) + aOutputData.GetScrW()) : + o3tl::convert(aOrigin.getX(), o3tl::Length::twip, o3tl::Length::px); + Size aPixelOffset(nXOffset, o3tl::convert(aOrigin.getY(), o3tl::Length::twip, o3tl::Length::px)); + pContentDev->SetPixelOffset(aPixelOffset); + comphelper::LibreOfficeKit::setLocalRendering(); + } + + DrawRedraw( aOutputData, SC_LAYER_FRONT ); + DrawRedraw( aOutputData, SC_LAYER_INTERN ); + DrawSdrGrid( aDrawingRectLogic, pContentDev ); + + if (bIsTiledRendering) + { + pContentDev->SetPixelOffset(Size()); + pContentDev->SetMapMode(aOrig); + } + + pContentDev->SetMapMode(MapMode(MapUnit::MapPixel)); + + if ( mrViewData.IsRefMode() && nTab >= mrViewData.GetRefStartZ() && nTab <= mrViewData.GetRefEndZ() ) + { + Color aRefColor( rColorCfg.GetColorValue(svtools::CALCREFERENCE).nColor ); + aOutputData.DrawRefMark( mrViewData.GetRefStartX(), mrViewData.GetRefStartY(), + mrViewData.GetRefEndX(), mrViewData.GetRefEndY(), + aRefColor, false ); + } + + // range finder + + ScInputHandler* pHdl = pScMod->GetInputHdl( mrViewData.GetViewShell() ); + if (pHdl) + { + ScDocShell* pDocSh = mrViewData.GetDocShell(); + ScRangeFindList* pRangeFinder = pHdl->GetRangeFindList(); + if ( pRangeFinder && !pRangeFinder->IsHidden() && + pRangeFinder->GetDocName() == pDocSh->GetTitle() ) + { + sal_uInt16 nCount = static_cast(pRangeFinder->Count()); + for (sal_uInt16 i=0; iGetObject(i); + + ScRange aRef = rData.aRef; + aRef.PutInOrder(); + if ( aRef.aStart.Tab() >= nTab && aRef.aEnd.Tab() <= nTab ) + aOutputData.DrawRefMark( aRef.aStart.Col(), aRef.aStart.Row(), + aRef.aEnd.Col(), aRef.aEnd.Row(), + rData.nColor, + true ); + } + } + } + + { + // end redraw + ScTabViewShell* pTabViewShell = mrViewData.GetViewShell(); + + if(pTabViewShell) + { + MapMode aCurrentMapMode(pContentDev->GetMapMode()); + pContentDev->SetMapMode(aDrawMode); + + if (bIsTiledRendering) + { + Point aOrigin = aOriginalMode.GetOrigin(); + if (bLayoutRTL) + aOrigin.setX(-o3tl::convert(aOrigin.getX(), o3tl::Length::twip, o3tl::Length::px) + + aOutputData.nScrX + aOutputData.GetScrW()); + else + aOrigin.setX(o3tl::convert(aOrigin.getX(), o3tl::Length::twip, o3tl::Length::px) + + aOutputData.nScrX); + + aOrigin.setY(o3tl::convert(aOrigin.getY(), o3tl::Length::twip, o3tl::Length::px) + + aOutputData.nScrY); + const double twipFactor = 15 * 1.76388889; // 26.45833335 + aOrigin = Point(aOrigin.getX() * twipFactor, + aOrigin.getY() * twipFactor); + MapMode aNew = rDevice.GetMapMode(); + aNew.SetOrigin(aOrigin); + rDevice.SetMapMode(aNew); + } + + SdrView* pDrawView = pTabViewShell->GetScDrawView(); + + if(pDrawView) + { + // #i74769# work with SdrPaintWindow directly + pDrawView->EndDrawLayers(*pTargetPaintWindow, true); + } + + pContentDev->SetMapMode(aCurrentMapMode); + } + } + + // paint in-place editing + if (bIsTiledRendering) + { + ScTabViewShell* pThisViewShell = mrViewData.GetViewShell(); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + + while (pViewShell) + { + bool bEnterLoop = bIsTiledRendering || pViewShell != pThisViewShell; + if (bEnterLoop && pViewShell->GetDocId() == pThisViewShell->GetDocId()) + { + ScTabViewShell* pTabViewShell = dynamic_cast(pViewShell); + if (pTabViewShell) + { + ScViewData& rOtherViewData = pTabViewShell->GetViewData(); + ScSplitPos eOtherWhich = rOtherViewData.GetEditActivePart(); + + bool bOtherEditMode = rOtherViewData.HasEditView(eOtherWhich); + SCCOL nCol1 = rOtherViewData.GetEditStartCol(); + SCROW nRow1 = rOtherViewData.GetEditStartRow(); + SCCOL nCol2 = rOtherViewData.GetEditEndCol(); + SCROW nRow2 = rOtherViewData.GetEditEndRow(); + bOtherEditMode = bOtherEditMode + && ( nCol2 >= nX1 && nCol1 <= nX2 && nRow2 >= nY1 && nRow1 <= nY2 ); + if (bOtherEditMode && rOtherViewData.GetRefTabNo() == nTab) + { + EditView* pOtherEditView = rOtherViewData.GetEditView(eOtherWhich); + if (pOtherEditView) + { + tools::Long nScreenX = aOutputData.nScrX; + tools::Long nScreenY = aOutputData.nScrY; + + rDevice.SetLineColor(); + SfxViewShell* pSfxViewShell = SfxViewShell::Current(); + ScTabViewShell* pCurrentViewShell = dynamic_cast(pSfxViewShell); + if (pCurrentViewShell) + { + const ScViewData& pViewData = pCurrentViewShell->GetViewData(); + const ScViewOptions& aViewOptions = pViewData.GetOptions(); + const ScPatternAttr* pPattern = rDoc.GetPattern( nCol1, nRow1, nTab ); + Color aCellColor = pPattern->GetItem(ATTR_BACKGROUND).GetColor(); + if (aCellColor.IsTransparent()) + { + aCellColor = aViewOptions.GetDocColor(); + } + rDevice.SetFillColor(aCellColor); + pOtherEditView->SetBackgroundColor(aCellColor); + } + Point aStart = mrViewData.GetScrPos( nCol1, nRow1, eOtherWhich ); + Point aEnd = mrViewData.GetScrPos( nCol2+1, nRow2+1, eOtherWhich ); + + if (bIsTiledRendering) + { + EditEngine* pEditEngine = pOtherEditView->GetEditEngine(); + if (pEditEngine) + aEnd.AdjustY(lcl_GetMultiLineHeight(pEditEngine)); + } + + if (bLokRTL) + { + // Transform the cell range X coordinates such that the edit cell area is + // horizontally mirrored w.r.t the (combined-)tile. + aStart.setX(pLokRTLCtxt->docToTilePos(aStart.X())); + aEnd.setX(pLokRTLCtxt->docToTilePos(aEnd.X())); + } + + // don't overwrite grid + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + aEnd.AdjustX( -(2 * nLayoutSign) ); + aEnd.AdjustY( -2 ); + + tools::Rectangle aBackground(aStart, aEnd); + if (bLokRTL) + aBackground.Normalize(); + + // Need to draw the background in absolute coords. + Point aOrigin = aOriginalMode.GetOrigin(); + aOrigin.setX( + o3tl::convert(aOrigin.getX(), o3tl::Length::twip, o3tl::Length::px) + + nScreenX); + aOrigin.setY( + o3tl::convert(aOrigin.getY(), o3tl::Length::twip, o3tl::Length::px) + + nScreenY); + aBackground += aOrigin; + rDevice.SetMapMode(aDrawMode); + + static const double twipFactor = 15 * 1.76388889; // 26.45833335 + // keep into account the zoom factor + aOrigin = Point((aOrigin.getX() * twipFactor) / static_cast(aDrawMode.GetScaleX()), + (aOrigin.getY() * twipFactor) / static_cast(aDrawMode.GetScaleY())); + + MapMode aNew = rDevice.GetMapMode(); + aNew.SetOrigin(aOrigin); + rDevice.SetMapMode(aNew); + + // paint the background + rDevice.DrawRect(rDevice.PixelToLogic(aBackground)); + tools::Rectangle aBGAbs(aBackground); + + tools::Rectangle aEditRect(aBackground); + tools::Long nOffsetX = 0, nOffsetY = 0; + // Get top-left offset because of margin and indent. + lcl_GetEditAreaTLOffset(nOffsetX, nOffsetY, ScAddress(nCol1, nRow1, nTab), mrViewData, rDoc); + aEditRect.AdjustLeft(nOffsetX + 1); + aEditRect.AdjustRight(1); + aEditRect.AdjustTop(nOffsetY + 1); + aEditRect.AdjustBottom(1); + + // EditView has an 'output area' which is used to clip the 'paint area' we provide below. + // So they need to be in the same coordinates/units. This is tied to the mapmode of the gridwin + // attached to the EditView, so we have to change its mapmode too (temporarily). We save the + // original mapmode and 'output area' and roll them back when we finish painting to rDevice. + OutputDevice& rOtherWin = pOtherEditView->GetOutputDevice(); + const tools::Rectangle aOrigOutputArea(pOtherEditView->GetOutputArea()); // Not in pixels. + const MapMode aOrigMapMode = rOtherWin.GetMapMode(); + rOtherWin.SetMapMode(rDevice.GetMapMode()); + + // Avoid sending wrong cursor/selection messages by the 'other' view, as the output-area is going + // to be tweaked temporarily to match the current view's zoom. + SuppressEditViewMessagesGuard aGuard(*pOtherEditView); + comphelper::ScopeGuard aOutputGuard( + [pOtherEditView, aOrigOutputArea, bLokRTL] { + if (bLokRTL && aOrigOutputArea != pOtherEditView->GetOutputArea()) + pOtherEditView->SetOutputArea(aOrigOutputArea); + }); + + aEditRect = rDevice.PixelToLogic(aEditRect); + if (bIsTiledRendering) + pOtherEditView->SetOutputArea(aEditRect); + else + aEditRect.Intersection(pOtherEditView->GetOutputArea()); + pOtherEditView->Paint(aEditRect, &rDevice); + + // EditView will do the cursor notifications correctly if we're in + // print-twips messaging mode. + if (bIsTiledRendering && !comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs)) + { + // Now we need to get relative cursor position within the editview. + // This is for sending the pixel-aligned twips position of the cursor to the specific views with + // the same given zoom level. + tools::Rectangle aCursorRect = pOtherEditView->GetEditCursor(); + Point aCursPos = OutputDevice::LogicToLogic(aCursorRect.TopLeft(), + MapMode(MapUnit::Map100thMM), MapMode(MapUnit::MapTwip)); + + const MapMode& rDevMM = rDevice.GetMapMode(); + MapMode aMM(MapUnit::MapTwip); + aMM.SetScaleX(rDevMM.GetScaleX()); + aMM.SetScaleY(rDevMM.GetScaleY()); + + aBGAbs.AdjustLeft(1); + aBGAbs.AdjustTop(1); + aCursorRect = GetOutDev()->PixelToLogic(aBGAbs, aMM); + aCursorRect.setWidth(0); + aCursorRect.Move(aCursPos.getX(), 0); + // Sends view cursor position to views of all matching zooms if needed (avoids duplicates). + InvalidateLOKViewCursor(aCursorRect, aMM.GetScaleX(), aMM.GetScaleY()); + } + + // Rollback the mapmode and 'output area'. + rOtherWin.SetMapMode(aOrigMapMode); + if (!bIsTiledRendering) + pOtherEditView->SetOutputArea(aOrigOutputArea); + rDevice.SetMapMode(MapMode(MapUnit::MapPixel)); + } + } + } + } + + pViewShell = SfxViewShell::GetNext(*pViewShell); + } + + } + + // In-place editing - when the user is typing, we need to paint the text + // using the editeng. + // It's being done after EndDrawLayers() to get it outside the overlay + // buffer and on top of everything. + if (bInPlaceEditing) + { + // get the coordinates of the area we need to clear (overpaint by + // the background) + SCCOL nCol1 = mrViewData.GetEditStartCol(); + SCROW nRow1 = mrViewData.GetEditStartRow(); + SCCOL nCol2 = mrViewData.GetEditEndCol(); + SCROW nRow2 = mrViewData.GetEditEndRow(); + rDevice.SetLineColor(); + rDevice.SetFillColor(pEditView->GetBackgroundColor()); + Point aStart = mrViewData.GetScrPos( nCol1, nRow1, eWhich ); + Point aEnd = mrViewData.GetScrPos( nCol2+1, nRow2+1, eWhich ); + + if (bLokRTL) + { + // Transform the cell range X coordinates such that the edit cell area is + // horizontally mirrored w.r.t the (combined-)tile. + aStart.setX(pLokRTLCtxt->docToTilePos(aStart.X())); + aEnd.setX(pLokRTLCtxt->docToTilePos(aEnd.X())); + } + + // don't overwrite grid + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + aEnd.AdjustX( -(2 * nLayoutSign) ); + aEnd.AdjustY( -2 ); + + // set the correct mapmode + tools::Rectangle aBackground(aStart, aEnd); + if (bLokRTL) + aBackground.Normalize(); + tools::Rectangle aBGAbs(aBackground); + + if (bIsTiledRendering) + { + // Need to draw the background in absolute coords. + Point aOrigin = aOriginalMode.GetOrigin(); + aOrigin.setX(o3tl::convert(aOrigin.getX(), o3tl::Length::twip, o3tl::Length::px) + + nScrX); + aOrigin.setY(o3tl::convert(aOrigin.getY(), o3tl::Length::twip, o3tl::Length::px) + + nScrY); + aBackground += aOrigin; + rDevice.SetMapMode(aDrawMode); + } + else + rDevice.SetMapMode(mrViewData.GetLogicMode()); + + if (bIsTiledRendering) + { + Point aOrigin = aOriginalMode.GetOrigin(); + aOrigin.setX(o3tl::convert(aOrigin.getX(), o3tl::Length::twip, o3tl::Length::px) + + nScrX); + aOrigin.setY(o3tl::convert(aOrigin.getY(), o3tl::Length::twip, o3tl::Length::px) + + nScrY); + static const double twipFactor = 15 * 1.76388889; // 26.45833335 + // keep into account the zoom factor + aOrigin = Point((aOrigin.getX() * twipFactor) / static_cast(aDrawMode.GetScaleX()), + (aOrigin.getY() * twipFactor) / static_cast(aDrawMode.GetScaleY())); + MapMode aNew = rDevice.GetMapMode(); + aNew.SetOrigin(aOrigin); + rDevice.SetMapMode(aNew); + } + + // paint the editeng text + if (bIsTiledRendering) + { + // EditView has an 'output area' which is used to clip the paint area we provide below. + // So they need to be in the same coordinates/units. This is tied to the mapmode of the gridwin + // attached to the EditView, so we have to change its mapmode too (temporarily). We save the + // original mapmode and 'output area' and roll them back when we finish painting to rDevice. + const MapMode aOrigMapMode = GetMapMode(); + SetMapMode(rDevice.GetMapMode()); + + // Avoid sending wrong cursor/selection messages by the current view, as the output-area is going + // to be tweaked temporarily to match other view's zoom. (This does not affect the manual + // cursor-messaging done in the non print-twips mode) + SuppressEditViewMessagesGuard aGuard(*pEditView); + + // EditView will do the cursor notifications correctly if we're in + // print-twips messaging mode. + if (!comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs)) + { + // Now we need to get relative cursor position within the editview. + // This is for sending the pixel-aligned twips position of the cursor to the specific views with + // the same given zoom level. + tools::Rectangle aCursorRect = pEditView->GetEditCursor(); + Point aCursPos = o3tl::toTwips(aCursorRect.TopLeft(), o3tl::Length::mm100); + + const MapMode& rDevMM = rDevice.GetMapMode(); + MapMode aMM(MapUnit::MapTwip); + aMM.SetScaleX(rDevMM.GetScaleX()); + aMM.SetScaleY(rDevMM.GetScaleY()); + + aBGAbs.AdjustLeft(1); + aBGAbs.AdjustTop(1); + aCursorRect = GetOutDev()->PixelToLogic(aBGAbs, aMM); + aCursorRect.setWidth(0); + aCursorRect.Move(aCursPos.getX(), 0); + // Sends view cursor position to views of all matching zooms if needed (avoids duplicates). + InvalidateLOKViewCursor(aCursorRect, aMM.GetScaleX(), aMM.GetScaleY()); + } + + // Rollback the mapmode and 'output area'. + SetMapMode(aOrigMapMode); + } + else + { + // paint the background + tools::Rectangle aLogicRect(rDevice.PixelToLogic(aBackground)); + //tdf#100925, rhbz#1283420, Draw some text here, to get + //X11CairoTextRender::getCairoContext called, so that the forced read + //from the underlying X Drawable gets it to sync. + rDevice.DrawText(aLogicRect.BottomLeft(), " "); + rDevice.DrawRect(aLogicRect); + + tools::Rectangle aEditRect(Point(nScrX, nScrY), Size(aOutputData.GetScrW(), aOutputData.GetScrH())); + pEditView->Paint(rDevice.PixelToLogic(aEditRect), &rDevice); + } + + rDevice.SetMapMode(MapMode(MapUnit::MapPixel)); + + // restore the cursor it was originally visible + if (bInPlaceVisCursor) + pInPlaceCrsr->Show(); + } + + if (mrViewData.HasEditView(eWhich)) + { + // flush OverlayManager before changing the MapMode + flushOverlayManager(); + + // set MapMode for text edit + rDevice.SetMapMode(mrViewData.GetLogicMode()); + } + else + rDevice.SetMapMode(aDrawMode); + + if (mpNoteMarker) + mpNoteMarker->Draw(); // Above the cursor, in drawing map mode + + if (bPage && bInitialPageBreaks) + SetupInitialPageBreaks(rDoc, nTab); +} + + +void ScGridWindow::SetupInitialPageBreaks(const ScDocument& rDoc, SCTAB nTab) +{ + // tdf#124983, if option LibreOfficeDev Calc/View/Visual Aids/Page breaks + // is enabled, breaks should be visible. If the document is opened the first + // time, the breaks are not calculated yet, so for this initialization + // a timer will be triggered here. + std::set aColBreaks; + std::set aRowBreaks; + rDoc.GetAllColBreaks(aColBreaks, nTab, true, false); + rDoc.GetAllRowBreaks(aRowBreaks, nTab, true, false); + if (aColBreaks.size() == 0 || aRowBreaks.size() == 0) + { + maShowPageBreaksTimer.SetPriority(TaskPriority::DEFAULT_IDLE); + maShowPageBreaksTimer.Start(); + } + bInitialPageBreaks = false; +} + +namespace +{ + template + void lcl_getBoundingRowColumnforTile(ScViewData& rViewData, + tools::Long nTileStartPosPx, tools::Long nTileEndPosPx, + sal_Int32& nTopLeftTileOffset, sal_Int32& nTopLeftTileOrigin, + sal_Int32& nTopLeftTileIndex, sal_Int32& nBottomRightTileIndex) + { + const bool bColumnHeader = std::is_same::value; + + SCTAB nTab = rViewData.GetTabNo(); + + IndexType nStartIndex = -1; + IndexType nEndIndex = -1; + tools::Long nStartPosPx = 0; + tools::Long nEndPosPx = 0; + + ScPositionHelper& rPositionHelper = + bColumnHeader ? rViewData.GetLOKWidthHelper() : rViewData.GetLOKHeightHelper(); + const auto& rStartNearest = rPositionHelper.getNearestByPosition(nTileStartPosPx); + const auto& rEndNearest = rPositionHelper.getNearestByPosition(nTileEndPosPx); + + ScBoundsProvider aBoundsProvider(rViewData, nTab, bColumnHeader); + aBoundsProvider.Compute(rStartNearest, rEndNearest, nTileStartPosPx, nTileEndPosPx); + aBoundsProvider.GetStartIndexAndPosition(nStartIndex, nStartPosPx); ++nStartIndex; + aBoundsProvider.GetEndIndexAndPosition(nEndIndex, nEndPosPx); + + nTopLeftTileOffset = nTileStartPosPx - nStartPosPx; + nTopLeftTileOrigin = nStartPosPx; + nTopLeftTileIndex = nStartIndex; + nBottomRightTileIndex = nEndIndex; + } + + void lcl_RTLAdjustTileColOffset(ScViewData& rViewData, sal_Int32& nTileColOffset, + tools::Long nTileEndPx, sal_Int32 nEndCol, SCTAB nTab, + const ScDocument& rDoc, double fPPTX) + { + auto GetColWidthPx = [&rDoc, nTab, fPPTX](SCCOL nCol) { + const sal_uInt16 nSize = rDoc.GetColWidth(nCol, nTab); + const tools::Long nSizePx = ScViewData::ToPixel(nSize, fPPTX); + return nSizePx; + }; + + ScPositionHelper rHelper = rViewData.GetLOKWidthHelper(); + tools::Long nEndColPos = rHelper.computePosition(nEndCol, GetColWidthPx); + + nTileColOffset += (nEndColPos - nTileEndPx - nTileColOffset); + } + + class ScLOKProxyObjectContact final : public sdr::contact::ObjectContactOfPageView + { + private: + ScDrawView* mpScDrawView; + + public: + explicit ScLOKProxyObjectContact( + ScDrawView* pDrawView, + SdrPageWindow& rPageWindow, + const char* pDebugName) : + ObjectContactOfPageView(rPageWindow, pDebugName), + mpScDrawView(pDrawView) + { + } + + virtual bool supportsGridOffsets() const override { return true; } + + virtual void calculateGridOffsetForViewObjectContact( + basegfx::B2DVector& rTarget, + const sdr::contact::ViewObjectContact& rClient) const override + { + if (!mpScDrawView) + return; + + SdrPageView* pPageView(mpScDrawView->GetSdrPageView()); + if (!pPageView) + return; + + SdrPageWindow* pSdrPageWindow = nullptr; + if (pPageView->PageWindowCount() > 0) + pSdrPageWindow = pPageView->GetPageWindow(0); + if (!pSdrPageWindow) + return; + + sdr::contact::ObjectContact& rObjContact(pSdrPageWindow->GetObjectContact()); + + SdrObject* pTargetSdrObject(rClient.GetViewContact().TryToGetSdrObject()); + if (pTargetSdrObject) + rTarget = pTargetSdrObject->GetViewContact().GetViewObjectContact(rObjContact).getGridOffset(); + } + }; + + class ScLOKDrawView : public FmFormView + { + public: + ScLOKDrawView(OutputDevice* pOut, ScViewData& rData) : + FmFormView(*rData.GetDocument().GetDrawLayer(), pOut), + mpScDrawView(rData.GetScDrawView()) + { + } + + virtual sdr::contact::ObjectContact* createViewSpecificObjectContact( + SdrPageWindow& rPageWindow, const char* pDebugName) const override + { + if (!mpScDrawView) + return SdrView::createViewSpecificObjectContact(rPageWindow, pDebugName); + + return new ScLOKProxyObjectContact(mpScDrawView, rPageWindow, pDebugName); + } + + private: + ScDrawView* mpScDrawView; + }; +} // anonymous namespace + +void ScGridWindow::PaintTile( VirtualDevice& rDevice, + int nOutputWidth, int nOutputHeight, + int nTilePosX, int nTilePosY, + tools::Long nTileWidth, tools::Long nTileHeight, + SCCOL nTiledRenderingAreaEndCol, SCROW nTiledRenderingAreaEndRow ) +{ + Fraction origZoomX = mrViewData.GetZoomX(); + Fraction origZoomY = mrViewData.GetZoomY(); + + // Output size is in pixels while tile position and size are in logical units (twips). + + // Assumption: always paint the whole sheet i.e. "visible" range is always + // from (0,0) to last data position. + + // Tile geometry is independent of the zoom level, but the output size is + // dependent of the zoom level. Determine the correct zoom level before + // we start. + + // FIXME the painting works using a mixture of drawing with coordinates in + // pixels and in logic coordinates; it should be cleaned up to use logic + // coords only, and avoid all the SetMapMode()'s. + // Similarly to Writer, we should set the mapmode once on the rDevice, and + // not care about any zoom settings. + + Fraction aFracX(o3tl::convert(nOutputWidth, o3tl::Length::px, o3tl::Length::twip), nTileWidth); + Fraction aFracY(o3tl::convert(nOutputHeight, o3tl::Length::px, o3tl::Length::twip), nTileHeight); + + const bool bChangeZoom = (aFracX != origZoomX || aFracY != origZoomY); + + // page break zoom, and aLogicMode in ScViewData + // FIXME: there are issues when SetZoom is called conditionally. + mrViewData.SetZoom(aFracX, aFracY, true); + if (bChangeZoom) + { + if (ScDrawView* pDrawView = mrViewData.GetScDrawView()) + pDrawView->resetGridOffsetsForAllSdrPageViews(); + } + + const double fTilePosXPixel = static_cast(nTilePosX) * nOutputWidth / nTileWidth; + const double fTilePosYPixel = static_cast(nTilePosY) * nOutputHeight / nTileHeight; + const double fTileBottomPixel = static_cast(nTilePosY + nTileHeight) * nOutputHeight / nTileHeight; + const double fTileRightPixel = static_cast(nTilePosX + nTileWidth) * nOutputWidth / nTileWidth; + + SCTAB nTab = mrViewData.GetTabNo(); + ScDocument& rDoc = mrViewData.GetDocument(); + + const double fPPTX = mrViewData.GetPPTX(); + const double fPPTY = mrViewData.GetPPTY(); + + // find approximate col/row offsets of nearby. + sal_Int32 nTopLeftTileRowOffset = 0; + sal_Int32 nTopLeftTileColOffset = 0; + sal_Int32 nTopLeftTileRowOrigin = 0; + sal_Int32 nTopLeftTileColOrigin = 0; + + sal_Int32 nTopLeftTileRow = 0; + sal_Int32 nTopLeftTileCol = 0; + sal_Int32 nBottomRightTileRow = 0; + sal_Int32 nBottomRightTileCol = 0; + + lcl_getBoundingRowColumnforTile(mrViewData, + fTilePosYPixel, fTileBottomPixel, + nTopLeftTileRowOffset, nTopLeftTileRowOrigin, + nTopLeftTileRow, nBottomRightTileRow); + + lcl_getBoundingRowColumnforTile(mrViewData, + fTilePosXPixel, fTileRightPixel, + nTopLeftTileColOffset, nTopLeftTileColOrigin, + nTopLeftTileCol, nBottomRightTileCol); + + // Enlarge + nBottomRightTileCol++; + nBottomRightTileRow++; + + if (nBottomRightTileCol > rDoc.MaxCol()) + nBottomRightTileCol = rDoc.MaxCol(); + + if (nBottomRightTileRow > MAXTILEDROW) + nBottomRightTileRow = MAXTILEDROW; + + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + + if (bLayoutRTL) + { + lcl_RTLAdjustTileColOffset(mrViewData, nTopLeftTileColOffset, + fTileRightPixel, nBottomRightTileCol, nTab, + rDoc, fPPTX); + } + + // size of the document including drawings, charts, etc. + SCCOL nEndCol = nTiledRenderingAreaEndCol; + SCROW nEndRow = nTiledRenderingAreaEndRow; + + if (nEndCol < nBottomRightTileCol) + nEndCol = nBottomRightTileCol; + + if (nEndRow < nBottomRightTileRow) + nEndRow = nBottomRightTileRow; + + nTopLeftTileCol = std::max(nTopLeftTileCol, 0); + nTopLeftTileRow = std::max(nTopLeftTileRow, 0); + nTopLeftTileColOrigin = o3tl::convert(nTopLeftTileColOrigin, o3tl::Length::px, o3tl::Length::twip); + nTopLeftTileRowOrigin = o3tl::convert(nTopLeftTileRowOrigin, o3tl::Length::px, o3tl::Length::twip); + + // Checkout -> 'rDoc.ExtendMerge' ... if we miss merged cells. + + // Origin must be the offset of the first col and row + // containing our top-left pixel. + const MapMode aOriginalMode = rDevice.GetMapMode(); + MapMode aAbsMode = aOriginalMode; + const Point aOrigin(-nTopLeftTileColOrigin, -nTopLeftTileRowOrigin); + aAbsMode.SetOrigin(aOrigin); + rDevice.SetMapMode(aAbsMode); + + ScTableInfo aTabInfo(nEndRow + 3); + rDoc.FillInfo(aTabInfo, nTopLeftTileCol, nTopLeftTileRow, + nBottomRightTileCol, nBottomRightTileRow, + nTab, fPPTX, fPPTY, false, false); + +// FIXME: is this called some +// Point aScrPos = mrViewData.GetScrPos( nX1, nY1, eWhich ); + + ScOutputData aOutputData(&rDevice, OUTTYPE_WINDOW, aTabInfo, &rDoc, nTab, + -nTopLeftTileColOffset, + -nTopLeftTileRowOffset, + nTopLeftTileCol, nTopLeftTileRow, + nBottomRightTileCol, nBottomRightTileRow, + fPPTX, fPPTY, nullptr, nullptr); + + // setup the SdrPage so that drawinglayer works correctly + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (pModel) + { + bool bPrintTwipsMsgs = comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs); + if (!mpLOKDrawView) + { + mpLOKDrawView.reset(bPrintTwipsMsgs ? + new ScLOKDrawView( + &rDevice, + mrViewData) : + new FmFormView( + *pModel, + &rDevice)); + } + + mpLOKDrawView->SetNegativeX(bLayoutRTL); + mpLOKDrawView->ShowSdrPage(mpLOKDrawView->GetModel().GetPage(nTab)); + aOutputData.SetDrawView(mpLOKDrawView.get()); + aOutputData.SetSpellCheckContext(mpSpellCheckCxt.get()); + } + + // draw the content + DrawContent(rDevice, aTabInfo, aOutputData, true); + rDevice.SetMapMode(aOriginalMode); + + // Paint the chart(s) in edit mode. + LokChartHelper::PaintAllChartsOnTile(rDevice, nOutputWidth, nOutputHeight, + nTilePosX, nTilePosY, nTileWidth, nTileHeight, bLayoutRTL); + + rDevice.SetMapMode(aOriginalMode); + + // Flag drawn formula cells "unchanged". + rDoc.ResetChanged(ScRange(nTopLeftTileCol, nTopLeftTileRow, nTab, nBottomRightTileCol, nBottomRightTileRow, nTab)); + rDoc.PrepareFormulaCalc(); + + mrViewData.SetZoom(origZoomX, origZoomY, true); + if (bChangeZoom) + { + if (ScDrawView* pDrawView = mrViewData.GetScDrawView()) + pDrawView->resetGridOffsetsForAllSdrPageViews(); + } + + if (bLayoutRTL) + { + Bitmap aCellBMP = rDevice.GetBitmap(Point(0, 0), Size(nOutputWidth, nOutputHeight)); + aCellBMP.Mirror(BmpMirrorFlags::Horizontal); + rDevice.DrawBitmap(Point(0, 0), Size(nOutputWidth, nOutputHeight), aCellBMP); + } +} + +void ScGridWindow::LogicInvalidatePart(const tools::Rectangle* pRectangle, int nPart) +{ + tools::Rectangle aRectangle; + tools::Rectangle* pResultRectangle; + if (!pRectangle) + pResultRectangle = nullptr; + else + { + aRectangle = *pRectangle; + // When dragging shapes the map mode is disabled. + if (IsMapModeEnabled()) + { + if (GetMapMode().GetMapUnit() == MapUnit::Map100thMM) + { + aRectangle = o3tl::convert(aRectangle, o3tl::Length::mm100, o3tl::Length::twip); + } + } + else + aRectangle = PixelToLogic(aRectangle, MapMode(MapUnit::MapTwip)); + pResultRectangle = &aRectangle; + } + + // Trim invalidation rectangle overlapping negative X region in RTL mode. + if (pResultRectangle && pResultRectangle->Left() < 0 + && mrViewData.GetDocument().IsLayoutRTL(mrViewData.GetTabNo())) + { + pResultRectangle->SetLeft(0); + if (pResultRectangle->Right() < 0) + pResultRectangle->SetRight(0); + } + + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + SfxLokHelper::notifyInvalidation(pViewShell, nPart, pResultRectangle); +} + +void ScGridWindow::LogicInvalidate(const tools::Rectangle* pRectangle) +{ + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + LogicInvalidatePart(pRectangle, pViewShell->getPart()); +} + +void ScGridWindow::SetCellSelectionPixel(int nType, int nPixelX, int nPixelY) +{ + ScTabView* pTabView = mrViewData.GetView(); + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + ScInputHandler* pInputHandler = SC_MOD()->GetInputHdl(pViewShell); + + if (pInputHandler && pInputHandler->IsInputMode()) + { + // we need to switch off the editeng + ScTabView::UpdateInputLine(); + pViewShell->UpdateInputHandler(); + } + + if (nType == LOK_SETTEXTSELECTION_RESET) + { + pTabView->DoneBlockMode(); + return; + } + + // obtain the current selection + ScRangeList aRangeList = mrViewData.GetMarkData().GetMarkedRanges(); + + SCCOL nCol1, nCol2; + SCROW nRow1, nRow2; + SCTAB nTab1, nTab2; + + bool bWasEmpty = false; + if (aRangeList.empty()) + { + nCol1 = nCol2 = mrViewData.GetCurX(); + nRow1 = nRow2 = mrViewData.GetCurY(); + bWasEmpty = true; + } + else + aRangeList.Combine().GetVars(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + + // convert the coordinates to column/row + SCCOL nNewPosX; + SCROW nNewPosY; + SCTAB nTab = mrViewData.GetTabNo(); + mrViewData.GetPosFromPixel(nPixelX, nPixelY, eWhich, nNewPosX, nNewPosY); + + // change the selection + switch (nType) + { + case LOK_SETTEXTSELECTION_START: + if (nNewPosX != nCol1 || nNewPosY != nRow1 || bWasEmpty) + { + pTabView->SetCursor(nNewPosX, nNewPosY); + pTabView->DoneBlockMode(); + pTabView->InitBlockMode(nNewPosX, nNewPosY, nTab, true); + pTabView->MarkCursor(nCol2, nRow2, nTab); + } + break; + case LOK_SETTEXTSELECTION_END: + if (nNewPosX != nCol2 || nNewPosY != nRow2 || bWasEmpty) + { + pTabView->SetCursor(nCol1, nRow1); + pTabView->DoneBlockMode(); + pTabView->InitBlockMode(nCol1, nRow1, nTab, true); + pTabView->MarkCursor(nNewPosX, nNewPosY, nTab); + } + break; + default: + assert(false); + break; + } +} + +void ScGridWindow::CheckNeedsRepaint() +{ + // called at the end of painting, and from timer after background text width calculation + + if (!bNeedsRepaint) + return; + + bNeedsRepaint = false; + if (aRepaintPixel.IsEmpty()) + Invalidate(); + else + Invalidate(PixelToLogic(aRepaintPixel)); + aRepaintPixel = tools::Rectangle(); + + // selection function in status bar might also be invalid + SfxBindings& rBindings = mrViewData.GetBindings(); + rBindings.Invalidate( SID_STATUS_SUM ); + rBindings.Invalidate( SID_ATTR_SIZE ); + rBindings.Invalidate( SID_TABLE_CELL ); +} + +void ScGridWindow::DrawHiddenIndicator( SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2, vcl::RenderContext& rRenderContext) +{ + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + const svtools::ColorConfig& rColorCfg = SC_MOD()->GetColorConfig(); + const svtools::ColorConfigValue aColorValue = rColorCfg.GetColorValue(svtools::CALCHIDDENROWCOL); + if (aColorValue.bIsVisible) { + rRenderContext.SetLineColor(aColorValue.nColor); + LineInfo aLineInfo(LineStyle::Dash, 2); + aLineInfo.SetDashCount(0); + aLineInfo.SetDotCount(1); + aLineInfo.SetDistance(15); + // round caps except when running VCL_PLUGIN=gen due to a performance issue + // https://bugs.documentfoundation.org/show_bug.cgi?id=128258#c14 + if (mrViewData.GetActiveWin()->GetSystemData()->toolkit != SystemEnvData::Toolkit::Gen) + aLineInfo.SetLineCap(css::drawing::LineCap_ROUND); + aLineInfo.SetDotLen(1); + for (int i=nX1; iGetPageBreakData(); + if (!pPageData) + return; + + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + Size aWinSize = GetOutputSizePixel(); + const svtools::ColorConfig& rColorCfg = SC_MOD()->GetColorConfig(); + Color aManual( rColorCfg.GetColorValue(svtools::CALCPAGEBREAKMANUAL).nColor ); + Color aAutomatic( rColorCfg.GetColorValue(svtools::CALCPAGEBREAK).nColor ); + + OUString aPageStr = ScResId( STR_PGNUM ); + if ( nPageScript == SvtScriptType::NONE ) + { + // get script type of translated "Page" string only once + nPageScript = rDoc.GetStringScriptType( aPageStr ); + if (nPageScript == SvtScriptType::NONE) + nPageScript = ScGlobal::GetDefaultScriptType(); + } + + vcl::Font aFont; + std::unique_ptr pEditEng; + const ScPatternAttr& rDefPattern = rDoc.GetPool()->GetDefaultItem(ATTR_PATTERN); + if ( nPageScript == SvtScriptType::LATIN ) + { + // use single font and call DrawText directly + rDefPattern.fillFontOnly(aFont); + aFont.SetColor(COL_LIGHTGRAY); + // font size is set as needed + } + else + { + // use EditEngine to draw mixed-script string + pEditEng.reset(new ScEditEngineDefaulter( EditEngine::CreatePool().get(), true )); + pEditEng->SetRefMapMode(rRenderContext.GetMapMode()); + auto pEditDefaults = std::make_unique( pEditEng->GetEmptyItemSet() ); + rDefPattern.FillEditItemSet( pEditDefaults.get() ); + pEditDefaults->Put( SvxColorItem( COL_LIGHTGRAY, EE_CHAR_COLOR ) ); + pEditEng->SetDefaults( std::move(pEditDefaults) ); + } + + sal_uInt16 nCount = sal::static_int_cast( pPageData->GetCount() ); + for (sal_uInt16 nPos=0; nPosGetData(nPos); + ScRange aRange = rData.GetPrintRange(); + if ( aRange.aStart.Col() <= nX2+1 && aRange.aEnd.Col()+1 >= nX1 && + aRange.aStart.Row() <= nY2+1 && aRange.aEnd.Row()+1 >= nY1 ) + { + // 3 pixel frame around the print area + // (middle pixel on the grid lines) + + rRenderContext.SetLineColor(); + if (rData.IsAutomatic()) + rRenderContext.SetFillColor( aAutomatic ); + else + rRenderContext.SetFillColor( aManual ); + + Point aStart = mrViewData.GetScrPos( + aRange.aStart.Col(), aRange.aStart.Row(), eWhich, true ); + Point aEnd = mrViewData.GetScrPos( + aRange.aEnd.Col() + 1, aRange.aEnd.Row() + 1, eWhich, true ); + aStart.AdjustX( -2 ); + aStart.AdjustY( -2 ); + + // Prevent overflows: + if ( aStart.X() < -10 ) aStart.setX( -10 ); + if ( aStart.Y() < -10 ) aStart.setY( -10 ); + if ( aEnd.X() > aWinSize.Width() + 10 ) + aEnd.setX( aWinSize.Width() + 10 ); + if ( aEnd.Y() > aWinSize.Height() + 10 ) + aEnd.setY( aWinSize.Height() + 10 ); + + rRenderContext.DrawRect( tools::Rectangle( aStart, Point(aEnd.X(),aStart.Y()+2) ) ); + rRenderContext.DrawRect( tools::Rectangle( aStart, Point(aStart.X()+2,aEnd.Y()) ) ); + rRenderContext.DrawRect( tools::Rectangle( Point(aStart.X(),aEnd.Y()-2), aEnd ) ); + rRenderContext.DrawRect( tools::Rectangle( Point(aEnd.X()-2,aStart.Y()), aEnd ) ); + + // Page breaks + //! Display differently (dashed ????) + + size_t nColBreaks = rData.GetPagesX(); + const SCCOL* pColEnd = rData.GetPageEndX(); + size_t nColPos; + for (nColPos=0; nColPos+1= nX1 && nBreak <= nX2+1 ) + { + //! Search for hidden + if (rDoc.HasColBreak(nBreak, nTab) & ScBreakType::Manual) + rRenderContext.SetFillColor( aManual ); + else + rRenderContext.SetFillColor( aAutomatic ); + Point aBreak = mrViewData.GetScrPos( + nBreak, aRange.aStart.Row(), eWhich, true ); + rRenderContext.DrawRect( tools::Rectangle( aBreak.X()-1, aStart.Y(), aBreak.X(), aEnd.Y() ) ); + } + } + + size_t nRowBreaks = rData.GetPagesY(); + const SCROW* pRowEnd = rData.GetPageEndY(); + size_t nRowPos; + for (nRowPos=0; nRowPos+1= nY1 && nBreak <= nY2+1 ) + { + //! Search for hidden + if (rDoc.HasRowBreak(nBreak, nTab) & ScBreakType::Manual) + rRenderContext.SetFillColor( aManual ); + else + rRenderContext.SetFillColor( aAutomatic ); + Point aBreak = mrViewData.GetScrPos( + aRange.aStart.Col(), nBreak, eWhich, true ); + rRenderContext.DrawRect( tools::Rectangle( aStart.X(), aBreak.Y()-1, aEnd.X(), aBreak.Y() ) ); + } + } + + // Page numbers + + SCROW nPrStartY = aRange.aStart.Row(); + for (nRowPos=0; nRowPos= nY1 && nPrStartY <= nY2 ) + { + SCCOL nPrStartX = aRange.aStart.Col(); + for (nColPos=0; nColPos= nX1 && nPrStartX <= nX2 ) + { + Point aPageStart = mrViewData.GetScrPos( + nPrStartX, nPrStartY, eWhich, true ); + Point aPageEnd = mrViewData.GetScrPos( + nPrEndX+1,nPrEndY+1, eWhich, true ); + + tools::Long nPageNo = rData.GetFirstPage(); + if ( rData.IsTopDown() ) + nPageNo += static_cast(nColPos)*nRowBreaks+nRowPos; + else + nPageNo += static_cast(nRowPos)*nColBreaks+nColPos; + + OUString aThisPageStr = aPageStr.replaceFirst("%1", OUString::number(nPageNo)); + + if ( pEditEng ) + { + // find right font size with EditEngine + tools::Long nHeight = 100; + pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT ) ); + pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT_CJK ) ); + pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT_CTL ) ); + pEditEng->SetTextCurrentDefaults( aThisPageStr ); + Size aSize100( pEditEng->CalcTextWidth(), pEditEng->GetTextHeight() ); + + // 40% of width or 60% of height + tools::Long nSizeX = 40 * ( aPageEnd.X() - aPageStart.X() ) / aSize100.Width(); + tools::Long nSizeY = 60 * ( aPageEnd.Y() - aPageStart.Y() ) / aSize100.Height(); + nHeight = std::min(nSizeX,nSizeY); + pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT ) ); + pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT_CJK ) ); + pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT_CTL ) ); + + // centered output with EditEngine + Size aTextSize( pEditEng->CalcTextWidth(), pEditEng->GetTextHeight() ); + Point aPos( (aPageStart.X()+aPageEnd.X()-aTextSize.Width())/2, + (aPageStart.Y()+aPageEnd.Y()-aTextSize.Height())/2 ); + pEditEng->Draw(rRenderContext, aPos); + } + else + { + // find right font size for DrawText + aFont.SetFontSize( Size( 0,100 ) ); + rRenderContext.SetFont( aFont ); + + Size aSize100(rRenderContext.GetTextWidth( aThisPageStr ), rRenderContext.GetTextHeight() ); + if (aSize100.Width() && aSize100.Height()) + { + // 40% of width or 60% of height + tools::Long nSizeX = 40 * ( aPageEnd.X() - aPageStart.X() ) / aSize100.Width(); + tools::Long nSizeY = 60 * ( aPageEnd.Y() - aPageStart.Y() ) / aSize100.Height(); + aFont.SetFontSize( Size( 0,std::min(nSizeX,nSizeY) ) ); + rRenderContext.SetFont( aFont ); + } + + // centered output with DrawText + Size aTextSize(rRenderContext.GetTextWidth( aThisPageStr ), rRenderContext.GetTextHeight() ); + Point aPos( (aPageStart.X()+aPageEnd.X()-aTextSize.Width())/2, + (aPageStart.Y()+aPageEnd.Y()-aTextSize.Height())/2 ); + rRenderContext.DrawText( aPos, aThisPageStr ); + } + } + nPrStartX = nPrEndX + 1; + } + } + nPrStartY = nPrEndY + 1; + } + } + } +} + +void ScGridWindow::DrawButtons(SCCOL nX1, SCCOL nX2, const ScTableInfo& rTabInfo, OutputDevice* pContentDev, const ScLokRTLContext* pLokRTLContext) +{ + aComboButton.SetOutputDevice( pContentDev ); + + ScDocument& rDoc = mrViewData.GetDocument(); + ScDPFieldButton aCellBtn(pContentDev, &GetSettings().GetStyleSettings(), &mrViewData.GetZoomY(), &rDoc); + + SCCOL nCol; + SCROW nRow; + SCSIZE nArrY; + SCSIZE nQuery; + SCTAB nTab = mrViewData.GetTabNo(); + ScDBData* pDBData = nullptr; + std::unique_ptr pQueryParam; + + RowInfo* pRowInfo = rTabInfo.mpRowInfo.get(); + sal_uInt16 nArrCount = rTabInfo.mnArrCount; + + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + + Point aOldPos = aComboButton.GetPosPixel(); // store state for MouseDown/Up + Size aOldSize = aComboButton.GetSizePixel(); + + for (nArrY=1; nArrY+1nRowNo; + + for (nCol=nX1; nCol<=nX2; nCol++) + { + ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nCol); + //if several columns merged on a row, there should be only one auto button at the end of the columns. + //if several rows merged on a column, the button may be in the middle, so "!pInfo->bVOverlapped" should not be used + if ( pInfo->bAutoFilter && !pInfo->bHOverlapped ) + { + if (!pQueryParam) + pQueryParam.reset(new ScQueryParam); + + bool bNewData = true; + if (pDBData) + { + SCCOL nStartCol; + SCROW nStartRow; + SCCOL nEndCol; + SCROW nEndRow; + SCTAB nAreaTab; + pDBData->GetArea( nAreaTab, nStartCol, nStartRow, nEndCol, nEndRow ); + if ( nCol >= nStartCol && nCol <= nEndCol && + nRow >= nStartRow && nRow <= nEndRow ) + bNewData = false; + } + if (bNewData) + { + pDBData = rDoc.GetDBAtCursor( nCol, nRow, nTab, ScDBDataPortion::AREA ); + if (pDBData) + pDBData->GetQueryParam( *pQueryParam ); + else + { + // can also be part of DataPilot table + } + } + + // pQueryParam can only include MAXQUERY entries + + bool bArrowState = false; + if (pQueryParam->bInplace) + { + SCSIZE nCount = pQueryParam->GetEntryCount(); + for (nQuery = 0; nQuery < nCount; ++nQuery) + { + // Do no restrict to EQUAL here + // (Column head should become blue also when ">1") + const ScQueryEntry& rEntry = pQueryParam->GetEntry(nQuery); + if (rEntry.bDoQuery && rEntry.nField == nCol) + { + bArrowState = true; + break; + } + } + } + + tools::Long nSizeX; + tools::Long nSizeY; + SCCOL nStartCol= nCol; + SCROW nStartRow = nRow; + //if address(nCol,nRow) is not the start pos of the merge area, the value of the nSizeX will be incorrect, it will be the length of the cell. + //should first get the start pos of the merge area, then get the nSizeX through the start pos. + rDoc.ExtendOverlapped(nStartCol, nStartRow,nCol, nRow, nTab);//get nStartCol,nStartRow + mrViewData.GetMergeSizePixel( nStartCol, nStartRow, nSizeX, nSizeY );//get nSizeX + nSizeY = ScViewData::ToPixel(rDoc.GetRowHeight(nRow, nTab), mrViewData.GetPPTY()); + Point aScrPos = mrViewData.GetScrPos( nCol, nRow, eWhich ); + if (pLokRTLContext) + aScrPos.setX(pLokRTLContext->docToTilePos(aScrPos.X())); + + aCellBtn.setBoundingBox(aScrPos, Size(nSizeX-1, nSizeY-1), bLayoutRTL); + aCellBtn.setPopupLeft(bLayoutRTL); // #i114944# AutoFilter button is left-aligned in RTL + aCellBtn.setDrawBaseButton(false); + aCellBtn.setDrawPopupButton(true); + aCellBtn.setHasHiddenMember(bArrowState); + aCellBtn.draw(); + } + } + } + + if ( (pRowInfo[nArrY].bPivotToggle || pRowInfo[nArrY].bPivotButton) && pRowInfo[nArrY].bChanged ) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + nRow = pThisRowInfo->nRowNo; + for (nCol=nX1; nCol<=nX2; nCol++) + { + ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nCol); + if (pInfo->bHOverlapped || pInfo->bVOverlapped) + continue; + + Point aScrPos = mrViewData.GetScrPos( nCol, nRow, eWhich ); + tools::Long nSizeX; + tools::Long nSizeY; + mrViewData.GetMergeSizePixel( nCol, nRow, nSizeX, nSizeY ); + tools::Long nPosX = aScrPos.X(); + tools::Long nPosY = aScrPos.Y(); + // bLayoutRTL is handled in setBoundingBox + + bool bDrawToggle = pInfo->bPivotCollapseButton || pInfo->bPivotExpandButton; + if (!bDrawToggle) + { + OUString aStr = rDoc.GetString(nCol, nRow, nTab); + aCellBtn.setText(aStr); + } + + sal_uInt16 nIndent = 0; + if (const ScIndentItem* pIndentItem = rDoc.GetAttr(nCol, nRow, nTab, ATTR_INDENT)) + nIndent = pIndentItem->GetValue(); + aCellBtn.setBoundingBox(Point(nPosX, nPosY), Size(nSizeX-1, nSizeY-1), bLayoutRTL); + aCellBtn.setPopupLeft(false); // DataPilot popup is always right-aligned for now + aCellBtn.setDrawBaseButton(pInfo->bPivotButton); + aCellBtn.setDrawPopupButton(pInfo->bPivotPopupButton); + aCellBtn.setDrawPopupButtonMulti(pInfo->bPivotPopupButtonMulti); + aCellBtn.setDrawToggleButton(bDrawToggle, pInfo->bPivotCollapseButton, nIndent); + aCellBtn.setHasHiddenMember(pInfo->bFilterActive); + aCellBtn.draw(); + } + } + + if ( !comphelper::LibreOfficeKit::isActive() && bListValButton && pRowInfo[nArrY].nRowNo == aListValPos.Row() && pRowInfo[nArrY].bChanged ) + { + tools::Rectangle aRect = GetListValButtonRect( aListValPos ); + aComboButton.SetPosPixel( aRect.TopLeft() ); + aComboButton.SetSizePixel( aRect.GetSize() ); + pContentDev->SetClipRegion(vcl::Region(aRect)); + aComboButton.Draw(); + pContentDev->SetClipRegion(); // always called from Draw() without clip region + aComboButton.SetPosPixel( aOldPos ); // restore old state + aComboButton.SetSizePixel( aOldSize ); // for MouseUp/Down (AutoFilter) + } + } + + pQueryParam.reset(); + aComboButton.SetOutputDevice( GetOutDev() ); +} + +tools::Rectangle ScGridWindow::GetListValButtonRect( const ScAddress& rButtonPos ) +{ + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + ScDDComboBoxButton aButton( GetOutDev() ); // for optimal size + Size aBtnSize = aButton.GetSizePixel(); + + SCCOL nCol = rButtonPos.Col(); + SCROW nRow = rButtonPos.Row(); + + tools::Long nCellSizeX; // width of this cell, including merged + tools::Long nDummy; + mrViewData.GetMergeSizePixel( nCol, nRow, nCellSizeX, nDummy ); + + // for height, only the cell's row is used, excluding merged cells + tools::Long nCellSizeY = ScViewData::ToPixel( rDoc.GetRowHeight( nRow, nTab ), mrViewData.GetPPTY() ); + tools::Long nAvailable = nCellSizeX; + + // left edge of next cell if there is a non-hidden next column + SCCOL nNextCol = nCol + 1; + const ScMergeAttr* pMerge = rDoc.GetAttr( nCol,nRow,nTab, ATTR_MERGE ); + if ( pMerge->GetColMerge() > 1 ) + nNextCol = nCol + pMerge->GetColMerge(); // next cell after the merged area + while ( nNextCol <= rDoc.MaxCol() && rDoc.ColHidden(nNextCol, nTab) ) + ++nNextCol; + bool bNextCell = ( nNextCol <= rDoc.MaxCol() ); + if ( bNextCell ) + nAvailable = ScViewData::ToPixel( rDoc.GetColWidth( nNextCol, nTab ), mrViewData.GetPPTX() ); + + if ( nAvailable < aBtnSize.Width() ) + aBtnSize.setWidth( nAvailable ); + if ( nCellSizeY < aBtnSize.Height() ) + aBtnSize.setHeight( nCellSizeY ); + + Point aPos = mrViewData.GetScrPos( nCol, nRow, eWhich, true ); + aPos.AdjustX(nCellSizeX * nLayoutSign ); // start of next cell + if (!bNextCell) + aPos.AdjustX( -(aBtnSize.Width() * nLayoutSign) ); // right edge of cell if next cell not available + aPos.AdjustY(nCellSizeY - aBtnSize.Height() ); + // X remains at the left edge + + if ( bLayoutRTL ) + aPos.AdjustX( -(aBtnSize.Width()-1) ); // align right edge of button with cell border + + return tools::Rectangle( aPos, aBtnSize ); +} + +bool ScGridWindow::IsAutoFilterActive( SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + ScDocument& rDoc = mrViewData.GetDocument(); + ScDBData* pDBData = rDoc.GetDBAtCursor( nCol, nRow, nTab, ScDBDataPortion::AREA ); + ScQueryParam aQueryParam; + + if ( pDBData ) + pDBData->GetQueryParam( aQueryParam ); + else + { + OSL_FAIL("Auto filter button without DBData"); + } + + bool bSimpleQuery = true; + bool bColumnFound = false; + SCSIZE nQuery; + + if ( !aQueryParam.bInplace ) + bSimpleQuery = false; + + // aQueryParam can only include MAXQUERY entries + + SCSIZE nCount = aQueryParam.GetEntryCount(); + for (nQuery = 0; nQuery < nCount && bSimpleQuery; ++nQuery) + if ( aQueryParam.GetEntry(nQuery).bDoQuery ) + { + if (aQueryParam.GetEntry(nQuery).nField == nCol) + bColumnFound = true; + + if (nQuery > 0) + if (aQueryParam.GetEntry(nQuery).eConnect != SC_AND) + bSimpleQuery = false; + } + + return ( bSimpleQuery && bColumnFound ); +} + +void ScGridWindow::GetSelectionRects( ::std::vector< tools::Rectangle >& rPixelRects ) const +{ + GetPixelRectsFor( mrViewData.GetMarkData(), rPixelRects ); +} + +void ScGridWindow::GetSelectionRectsPrintTwips(::std::vector< tools::Rectangle >& rRects) const +{ + GetRectsAnyFor(mrViewData.GetMarkData(), rRects, true); +} + +/// convert rMarkData into pixel rectangles for this view +void ScGridWindow::GetPixelRectsFor( const ScMarkData &rMarkData, + ::std::vector< tools::Rectangle >& rPixelRects ) const +{ + GetRectsAnyFor(rMarkData, rPixelRects, false); +} + +void ScGridWindow::GetRectsAnyFor(const ScMarkData &rMarkData, + ::std::vector< tools::Rectangle >& rRects, + bool bInPrintTwips) const +{ + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + double nPPTX = mrViewData.GetPPTX(); + double nPPTY = mrViewData.GetPPTY(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + // LOK clients needs exact document coordinates, so don't horizontally mirror them. + tools::Long nLayoutSign = (!comphelper::LibreOfficeKit::isActive() && bLayoutRTL) ? -1 : 1; + + ScMarkData aMultiMark( rMarkData ); + aMultiMark.SetMarking( false ); + + if (!aMultiMark.IsMultiMarked()) + { + // simple range case - simplify calculation + const ScRange& aSimpleRange = aMultiMark.GetMarkArea(); + + aMultiMark.MarkToMulti(); + if ( !aMultiMark.IsMultiMarked() ) + return; + + SCCOL nX1 = aSimpleRange.aStart.Col(); + SCROW nY1 = aSimpleRange.aStart.Row(); + SCCOL nX2 = aSimpleRange.aEnd.Col(); + SCROW nY2 = aSimpleRange.aEnd.Row(); + + PutInOrder( nX1, nX2 ); + PutInOrder( nY1, nY2 ); + + SCCOL nPosX = mrViewData.GetPosX( eHWhich ); + SCROW nPosY = mrViewData.GetPosY( eVWhich ); + // is the selection visible at all? + if (nX2 < nPosX || nY2 < nPosY) + return; + + Point aScrStartPos = bInPrintTwips ? mrViewData.GetPrintTwipsPos(nX1, nY1) : + mrViewData.GetScrPos(nX1, nY1, eWhich); + + tools::Long nStartX = aScrStartPos.X(); + tools::Long nStartY = aScrStartPos.Y(); + + Point aScrEndPos = bInPrintTwips ? mrViewData.GetPrintTwipsPos(nX2, nY2) : + mrViewData.GetScrPos(nX2, nY2, eWhich); + + tools::Long nWidthTwips = rDoc.GetColWidth(nX2, nTab); + const tools::Long nWidth = bInPrintTwips ? + nWidthTwips : ScViewData::ToPixel(nWidthTwips, nPPTX); + tools::Long nEndX = aScrEndPos.X() + (nWidth - 1) * nLayoutSign; + + sal_uInt16 nHeightTwips = rDoc.GetRowHeight( nY2, nTab ); + const tools::Long nHeight = bInPrintTwips ? + nHeightTwips : ScViewData::ToPixel(nHeightTwips, nPPTY); + tools::Long nEndY = aScrEndPos.Y() + nHeight - 1; + + ScInvertMerger aInvert( &rRects ); + aInvert.AddRect( tools::Rectangle( nStartX, nStartY, nEndX, nEndY ) ); + + return; + } + + aMultiMark.MarkToMulti(); + if ( !aMultiMark.IsMultiMarked() ) + return; + const ScRange& aMultiRange = aMultiMark.GetMultiMarkArea(); + SCCOL nX1 = aMultiRange.aStart.Col(); + SCROW nY1 = aMultiRange.aStart.Row(); + SCCOL nX2 = aMultiRange.aEnd.Col(); + SCROW nY2 = aMultiRange.aEnd.Row(); + + PutInOrder( nX1, nX2 ); + PutInOrder( nY1, nY2 ); + + SCCOL nTestX2 = nX2; + SCROW nTestY2 = nY2; + + rDoc.ExtendMerge( nX1,nY1, nTestX2,nTestY2, nTab ); + + SCCOL nPosX = mrViewData.GetPosX( eHWhich ); + SCROW nPosY = mrViewData.GetPosY( eVWhich ); + // is the selection visible at all? + if (nTestX2 < nPosX || nTestY2 < nPosY) + return; + SCCOL nRealX1 = nX1; + if (nX1 < nPosX) + nX1 = nPosX; + if (nY1 < nPosY) + nY1 = nPosY; + + if (!comphelper::LibreOfficeKit::isActive()) + { + // limit the selection to only what is visible on the screen + SCCOL nXRight = nPosX + mrViewData.VisibleCellsX(eHWhich); + if (nXRight > rDoc.MaxCol()) + nXRight = rDoc.MaxCol(); + + SCROW nYBottom = nPosY + mrViewData.VisibleCellsY(eVWhich); + if (nYBottom > rDoc.MaxRow()) + nYBottom = rDoc.MaxRow(); + + // is the selection visible at all? + if (nX1 > nXRight || nY1 > nYBottom) + return; + + if (nX2 > nXRight) + nX2 = nXRight; + if (nY2 > nYBottom) + nY2 = nYBottom; + } + else + { + SCCOL nMaxTiledCol; + SCROW nMaxTiledRow; + rDoc.GetTiledRenderingArea(nTab, nMaxTiledCol, nMaxTiledRow); + + if (nX2 > nMaxTiledCol) + nX2 = nMaxTiledCol; + if (nY2 > nMaxTiledRow) + nY2 = nMaxTiledRow; + } + + ScInvertMerger aInvert( &rRects ); + + Point aScrPos = bInPrintTwips ? mrViewData.GetPrintTwipsPos(nX1, nY1) : + mrViewData.GetScrPos(nX1, nY1, eWhich); + tools::Long nScrY = aScrPos.Y(); + bool bWasHidden = false; + for (SCROW nY=nY1; nY<=nY2; nY++) + { + bool bFirstRow = ( nY == nPosY ); // first visible row? + bool bDoHidden = false; // repeat hidden ? + sal_uInt16 nHeightTwips = rDoc.GetRowHeight( nY,nTab ); + bool bDoRow = ( nHeightTwips != 0 ); + if (bDoRow) + { + if (bWasHidden) // test hidden merge + { + bDoHidden = true; + bDoRow = true; + } + + bWasHidden = false; + } + else + { + bWasHidden = true; + if (nY==nY2) + bDoRow = true; // last cell of the block + } + + if ( bDoRow ) + { + SCCOL nLoopEndX = nX2; + if (nX2 < nX1) // the rest of the merge + { + SCCOL nStartX = nX1; + while ( rDoc.GetAttr(nStartX,nY,nTab,ATTR_MERGE_FLAG)->IsHorOverlapped() ) + --nStartX; + if (nStartX <= nX2) + nLoopEndX = nX1; + } + + const tools::Long nHeight = bInPrintTwips ? + nHeightTwips : ScViewData::ToPixel(nHeightTwips, nPPTY); + tools::Long nEndY = nScrY + nHeight - 1; + tools::Long nScrX = aScrPos.X(); + for (SCCOL nX=nX1; nX<=nLoopEndX; nX++) + { + tools::Long nWidth = rDoc.GetColWidth(nX, nTab); + if (!bInPrintTwips) + nWidth = ScViewData::ToPixel(nWidth, nPPTX); + + if ( nWidth > 0 ) + { + tools::Long nEndX = nScrX + ( nWidth - 1 ) * nLayoutSign; + + SCROW nThisY = nY; + const ScPatternAttr* pPattern = rDoc.GetPattern( nX, nY, nTab ); + const ScMergeFlagAttr* pMergeFlag = &pPattern->GetItem(ATTR_MERGE_FLAG); + if ( pMergeFlag->IsVerOverlapped() && ( bDoHidden || bFirstRow ) ) + { + while ( pMergeFlag->IsVerOverlapped() && nThisY > 0 && + (rDoc.RowHidden(nThisY-1, nTab) || bFirstRow) ) + { + --nThisY; + pPattern = rDoc.GetPattern( nX, nThisY, nTab ); + pMergeFlag = &pPattern->GetItem(ATTR_MERGE_FLAG); + } + } + + // only the rest of the merged is seen ? + SCCOL nThisX = nX; + if ( pMergeFlag->IsHorOverlapped() && nX == nPosX && nX > nRealX1 ) + { + while ( pMergeFlag->IsHorOverlapped() ) + { + --nThisX; + pPattern = rDoc.GetPattern( nThisX, nThisY, nTab ); + pMergeFlag = &pPattern->GetItem(ATTR_MERGE_FLAG); + } + } + + if ( aMultiMark.IsCellMarked( nThisX, nThisY, true ) ) + { + if ( !pMergeFlag->IsOverlapped() ) + { + const ScMergeAttr* pMerge = &pPattern->GetItem(ATTR_MERGE); + if (pMerge->GetColMerge() > 0 || pMerge->GetRowMerge() > 0) + { + const SCCOL nEndColMerge = nThisX + pMerge->GetColMerge(); + const SCROW nEndRowMerge = nThisY + pMerge->GetRowMerge(); + Point aEndPos = bInPrintTwips ? + mrViewData.GetPrintTwipsPos(nEndColMerge, nEndRowMerge) : + mrViewData.GetScrPos(nEndColMerge, nEndRowMerge, eWhich); + if ( aEndPos.X() * nLayoutSign > nScrX * nLayoutSign && aEndPos.Y() > nScrY ) + { + aInvert.AddRect( tools::Rectangle( nScrX,nScrY, + aEndPos.X()-nLayoutSign,aEndPos.Y()-1 ) ); + } + } + else if ( nEndX * nLayoutSign >= nScrX * nLayoutSign && nEndY >= nScrY ) + { + aInvert.AddRect( tools::Rectangle( nScrX,nScrY,nEndX,nEndY ) ); + } + } + } + + nScrX = nEndX + nLayoutSign; + } + } + nScrY = nEndY + 1; + } + } +} + +void ScGridWindow::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged(rDCEvt); + + if ( !((rDCEvt.GetType() == DataChangedEventType::PRINTER) || + (rDCEvt.GetType() == DataChangedEventType::DISPLAY) || + (rDCEvt.GetType() == DataChangedEventType::FONTS) || + (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) || + ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))) ) + return; + + if ( rDCEvt.GetType() == DataChangedEventType::FONTS && eWhich == mrViewData.GetActivePart() ) + mrViewData.GetDocShell()->UpdateFontList(); + + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + { + if ( eWhich == mrViewData.GetActivePart() ) // only once for the view + { + ScTabView* pView = mrViewData.GetView(); + + pView->RecalcPPT(); + + // RepeatResize in case scroll bar sizes have changed + pView->RepeatResize(); + pView->UpdateAllOverlays(); + + // invalidate cell attribs in input handler, in case the + // EditEngine BackgroundColor has to be changed + if ( mrViewData.IsActive() ) + { + ScInputHandler* pHdl = SC_MOD()->GetInputHdl(); + if (pHdl) + pHdl->ForgetLastPattern(); + } + } + } + + Invalidate(); +} + +void ScGridWindow::initiatePageBreaks() +{ + bInitialPageBreaks = true; +} + +IMPL_LINK(ScGridWindow, InitiatePageBreaksTimer, Timer*, pTimer, void) +{ + if (pTimer != &maShowPageBreaksTimer) + return; + + const ScViewOptions& rOpts = mrViewData.GetOptions(); + const bool bPage = rOpts.GetOption(VOPT_PAGEBREAKS); + // tdf#124983, if option LibreOfficeDev Calc/View/Visual Aids/Page + // breaks is enabled, breaks should be visible. If the document is + // opened the first time or a tab is activated the first time, the + // breaks are not calculated yet, so this initialization is done here. + if (bPage) + { + const SCTAB nCurrentTab = mrViewData.GetTabNo(); + ScDocument& rDoc = mrViewData.GetDocument(); + const Size aPageSize = rDoc.GetPageSize(nCurrentTab); + // Do not attempt to calculate a page size here if it is empty if + // that involves counting pages. + // An earlier implementation did + // ScPrintFunc(pDocSh, pDocSh->GetPrinter(), nCurrentTab); + // rDoc.SetPageSize(nCurrentTab, rDoc.GetPageSize(nCurrentTab)); + // which resulted in tremendous waiting times after having loaded + // larger documents i.e. imported from CSV, in which UI is entirely + // blocked. All time is spent under ScPrintFunc::CountPages() in + // ScTable::ExtendPrintArea() in the loop that calls + // MaybeAddExtraColumn() to do stuff for each text string content + // cell (each row in each column). Maybe that can be optimized, or + // obtaining page size without that overhead would be possible, but + // as is calling that from here is a no-no so this is a quick + // disable things. + if (!aPageSize.IsEmpty()) + { + ScDocShell* pDocSh = mrViewData.GetDocShell(); + const bool bModified = pDocSh->IsModified(); + // Even setting the same size sets page size valid, so + // UpdatePageBreaks() actually does something. + rDoc.SetPageSize( nCurrentTab, aPageSize); + rDoc.UpdatePageBreaks(nCurrentTab); + pDocSh->PostPaint(0, 0, nCurrentTab, rDoc.MaxCol(), rDoc.MaxRow(), nCurrentTab, PaintPartFlags::Grid); + pDocSh->SetModified(bModified); + } + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/gridwin5.cxx b/sc/source/ui/view/gridwin5.cxx new file mode 100644 index 0000000000..206b53843e --- /dev/null +++ b/sc/source/ui/view/gridwin5.cxx @@ -0,0 +1,420 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +bool ScGridWindow::ShowNoteMarker( SCCOL nPosX, SCROW nPosY, bool bKeyboard ) +{ + bool bDone = false; + + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + ScAddress aCellPos( nPosX, nPosY, nTab ); + + OUString aTrackText; + bool bLeftEdge = false; + + // change tracking + + ScChangeTrack* pTrack = rDoc.GetChangeTrack(); + ScChangeViewSettings* pSettings = rDoc.GetChangeViewSettings(); + if ( pTrack && pTrack->GetFirst() && pSettings && pSettings->ShowChanges()) + { + const ScChangeAction* pFound = nullptr; + const ScChangeAction* pFoundContent = nullptr; + const ScChangeAction* pFoundMove = nullptr; + const ScChangeAction* pAction = pTrack->GetFirst(); + while (pAction) + { + if ( pAction->IsVisible() && + ScViewUtil::IsActionShown( *pAction, *pSettings, rDoc ) ) + { + ScChangeActionType eType = pAction->GetType(); + const ScBigRange& rBig = pAction->GetBigRange(); + if ( rBig.aStart.Tab() == nTab ) + { + ScRange aRange = rBig.MakeRange( rDoc ); + + if ( eType == SC_CAT_DELETE_ROWS ) + aRange.aEnd.SetRow( aRange.aStart.Row() ); + else if ( eType == SC_CAT_DELETE_COLS ) + aRange.aEnd.SetCol( aRange.aStart.Col() ); + + if ( aRange.Contains( aCellPos ) ) + { + pFound = pAction; // the last one wins + switch ( eType ) + { + case SC_CAT_CONTENT : + pFoundContent = pAction; + break; + case SC_CAT_MOVE : + pFoundMove = pAction; + break; + default: + { + // added to avoid warnings + } + } + } + } + if ( eType == SC_CAT_MOVE ) + { + ScRange aRange = + static_cast(pAction)-> + GetFromRange().MakeRange( rDoc ); + if ( aRange.Contains( aCellPos ) ) + { + pFound = pAction; + } + } + } + pAction = pAction->GetNext(); + } + + if ( pFound ) + { + if ( pFoundContent && pFound->GetType() != SC_CAT_CONTENT ) + pFound = pFoundContent; // content wins + if ( pFoundMove && pFound->GetType() != SC_CAT_MOVE && + pFoundMove->GetActionNumber() > + pFound->GetActionNumber() ) + pFound = pFoundMove; // move wins + + // for deleted columns: Arrow on the left side of the cell + if ( pFound->GetType() == SC_CAT_DELETE_COLS ) + bLeftEdge = true; + + DateTime aDT = pFound->GetDateTime(); + aTrackText = pFound->GetUser() + + ", " + + ScGlobal::getLocaleData().getDate(aDT) + + " " + + ScGlobal::getLocaleData().getTime(aDT) + + ":\n"; + OUString aComStr=pFound->GetComment(); + if(!aComStr.isEmpty()) + { + aTrackText += aComStr + "\n( "; + } + OUString aTmp = pFound->GetDescription(rDoc); + aTrackText += aTmp; + if(!aComStr.isEmpty()) + { + aTrackText += ")"; + } + } + } + + // Note, only if it is not already displayed on the Drawing Layer: + const ScPostIt* pNote = rDoc.GetNote( aCellPos ); + if ( (!aTrackText.isEmpty()) || (pNote && !pNote->IsCaptionShown()) ) + { + bool bNew = true; + bool bFast = false; + if (mpNoteMarker) // A note already shown + { + if (mpNoteMarker->GetDocPos() == aCellPos) + bNew = false; // then stop + else + bFast = true; // otherwise, at once + + // marker which was shown for ctrl-F1 isn't removed by mouse events + if (mpNoteMarker->IsByKeyboard() && !bKeyboard) + bNew = false; + } + if (bNew) + { + if (bKeyboard) + bFast = true; // keyboard also shows the marker immediately + + mpNoteMarker.reset(); + + bool bHSplit = mrViewData.GetHSplitMode() != SC_SPLIT_NONE; + bool bVSplit = mrViewData.GetVSplitMode() != SC_SPLIT_NONE; + + vcl::Window* pLeft = mrViewData.GetView()->GetWindowByPos( bVSplit ? SC_SPLIT_TOPLEFT : SC_SPLIT_BOTTOMLEFT ); + vcl::Window* pRight = bHSplit ? mrViewData.GetView()->GetWindowByPos( bVSplit ? SC_SPLIT_TOPRIGHT : SC_SPLIT_BOTTOMRIGHT ) : nullptr; + vcl::Window* pBottom = bVSplit ? mrViewData.GetView()->GetWindowByPos( SC_SPLIT_BOTTOMLEFT ) : nullptr; + vcl::Window* pDiagonal = (bHSplit && bVSplit) ? mrViewData.GetView()->GetWindowByPos( SC_SPLIT_BOTTOMRIGHT ) : nullptr; + OSL_ENSURE( pLeft, "ScGridWindow::ShowNoteMarker - missing top-left grid window" ); + + /* If caption is shown from right or bottom windows, adjust + mapmode to include size of top-left window. */ + MapMode aMapMode = GetDrawMapMode( true ); + Size aLeftSize = pLeft->PixelToLogic( pLeft->GetOutputSizePixel(), aMapMode ); + Point aOrigin = aMapMode.GetOrigin(); + if( (this == pRight) || (this == pDiagonal) ) + aOrigin.AdjustX(aLeftSize.Width() ); + if( (this == pBottom) || (this == pDiagonal) ) + aOrigin.AdjustY(aLeftSize.Height() ); + aMapMode.SetOrigin( aOrigin ); + + mpNoteMarker.reset(new ScNoteMarker(pLeft, pRight, pBottom, pDiagonal, + &rDoc, aCellPos, aTrackText, + aMapMode, bLeftEdge, bFast, bKeyboard)); + } + + bDone = true; // something is shown (old or new) + } + + return bDone; +} + +void ScGridWindow::RequestHelp(const HelpEvent& rHEvt) +{ + bool bDone = false; + OUString aFormulaText; + tools::Rectangle aFormulaPixRect; + bool bHelpEnabled = bool(rHEvt.GetMode() & ( HelpEventMode::BALLOON | HelpEventMode::QUICK )); + SdrView* pDrView = mrViewData.GetScDrawView(); + bool bDrawTextEdit = false; + if (pDrView) + bDrawTextEdit = pDrView->IsTextEdit(); + // notes or change tracking + if ( bHelpEnabled && !bDrawTextEdit ) + { + Point aPosPixel = ScreenToOutputPixel( rHEvt.GetMousePosPixel() ); + SCCOL nPosX; + SCROW nPosY; + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + const ScViewOptions& rOpts = mrViewData.GetOptions(); + mrViewData.GetPosFromPixel( aPosPixel.X(), aPosPixel.Y(), eWhich, nPosX, nPosY ); + + if ( ShowNoteMarker( nPosX, nPosY, false ) ) + { + Window::RequestHelp( rHEvt ); // turn off old Tip/Balloon + bDone = true; + } + + if ( rOpts.GetOption( VOPT_FORMULAS_MARKS ) ) + { + aFormulaText = rDoc.GetFormula( nPosX, nPosY, nTab ); + if ( !aFormulaText.isEmpty() ) { + const ScPatternAttr* pPattern = rDoc.GetPattern( nPosX, nPosY, nTab ); + aFormulaPixRect = mrViewData.GetEditArea( eWhich, nPosX, nPosY, this, pPattern, true ); + } + } + } + + if (!bDone && mpNoteMarker) + { + if (mpNoteMarker->IsByKeyboard()) + { + // marker which was shown for ctrl-F1 isn't removed by mouse events + } + else + { + mpNoteMarker.reset(); + } + } + + if ( !aFormulaText.isEmpty() ) + { + tools::Rectangle aScreenRect(OutputToScreenPixel(aFormulaPixRect.TopLeft()), + OutputToScreenPixel(aFormulaPixRect.BottomRight())); + if ( rHEvt.GetMode() & HelpEventMode::BALLOON ) + Help::ShowBalloon(this, rHEvt.GetMousePosPixel(), aScreenRect, aFormulaText); + else if ( rHEvt.GetMode() & HelpEventMode::QUICK ) + Help::ShowQuickHelp(this, aScreenRect, aFormulaText); + bDone = true; + } + + // Image-Map / Text-URL + + if ( bHelpEnabled && !bDone && !nButtonDown ) // only without pressed button + { + OUString aHelpText; + tools::Rectangle aPixRect; + Point aPosPixel = ScreenToOutputPixel( rHEvt.GetMousePosPixel() ); + + if ( pDrView ) // URL / Image-Map + { + SdrViewEvent aVEvt; + MouseEvent aMEvt( aPosPixel, 1, MouseEventModifiers::NONE, MOUSE_LEFT ); + SdrHitKind eHit = pDrView->PickAnything( aMEvt, SdrMouseEventKind::BUTTONDOWN, aVEvt ); + + if ( eHit != SdrHitKind::NONE && aVEvt.mpObj != nullptr ) + { + // URL for IMapObject below Pointer is help text + if (SvxIMapInfo::GetIMapInfo(aVEvt.mpObj)) + { + Point aLogicPos = PixelToLogic( aPosPixel ); + IMapObject* pIMapObj = SvxIMapInfo::GetHitIMapObject( + aVEvt.mpObj, aLogicPos, GetOutDev() ); + + if ( pIMapObj ) + { + // For image maps show the description, if available + aHelpText = pIMapObj->GetAltText(); + if (aHelpText.isEmpty()) + aHelpText = SfxHelp::GetURLHelpText(pIMapObj->GetURL()); + aPixRect = LogicToPixel(aVEvt.mpObj->GetLogicRect()); + } + } + // URL in shape text or at shape itself (URL in text overrides object URL) + if ( aHelpText.isEmpty() ) + { + if( aVEvt.meEvent == SdrEventKind::ExecuteUrl ) + { + if (aVEvt.mpURLField) + { + aHelpText = SfxHelp::GetURLHelpText(aVEvt.mpURLField->GetURL()); + aPixRect = LogicToPixel(aVEvt.mpObj->GetLogicRect()); + } + } + else + { + SdrPageView* pPV = nullptr; + Point aMDPos = PixelToLogic( aPosPixel ); + SdrObject* pObj = pDrView->PickObj(aMDPos, pDrView->getHitTolLog(), pPV, SdrSearchOptions::ALSOONMASTER); + if (pObj) + { + if ( pObj->IsGroupObject() ) + { + SdrObject* pHit = pDrView->PickObj(aMDPos, pDrView->getHitTolLog(), pPV, SdrSearchOptions::DEEP); + if (pHit) + pObj = pHit; + } + // Fragments pointing into the current document need no tooltip + // describing the ctrl-click functionality. + if ( !pObj->getHyperlink().isEmpty() && !pObj->getHyperlink().startsWith("#") ) + { + aPixRect = LogicToPixel(aVEvt.mpObj->GetLogicRect()); + aHelpText = SfxHelp::GetURLHelpText(pObj->getHyperlink()); + } + } + } + } + } + } + + if ( aHelpText.isEmpty() ) // Text-URL + { + OUString aUrl; + if ( GetEditUrl( aPosPixel, nullptr, &aUrl ) ) + { + aHelpText = SfxHelp::GetURLHelpText( + INetURLObject::decode(aUrl, INetURLObject::DecodeMechanism::Unambiguous)); + + ScDocument& rDoc = mrViewData.GetDocument(); + SCCOL nPosX; + SCROW nPosY; + SCTAB nTab = mrViewData.GetTabNo(); + mrViewData.GetPosFromPixel( aPosPixel.X(), aPosPixel.Y(), eWhich, nPosX, nPosY ); + const ScPatternAttr* pPattern = rDoc.GetPattern( nPosX, nPosY, nTab ); + + // bForceToTop = sal_False, use the cell's real position + aPixRect = mrViewData.GetEditArea( eWhich, nPosX, nPosY, this, pPattern, false ); + } + } + + if ( !aHelpText.isEmpty() ) + { + tools::Rectangle aScreenRect(OutputToScreenPixel(aPixRect.TopLeft()), + OutputToScreenPixel(aPixRect.BottomRight())); + + if ( rHEvt.GetMode() & HelpEventMode::BALLOON ) + Help::ShowBalloon(this,rHEvt.GetMousePosPixel(), aScreenRect, aHelpText); + else if ( rHEvt.GetMode() & HelpEventMode::QUICK ) + Help::ShowQuickHelp(this,aScreenRect, aHelpText); + + bDone = true; + } + } + + // basic controls + + if ( pDrView && bHelpEnabled && !bDone ) + { + SdrPageView* pPV = pDrView->GetSdrPageView(); + OSL_ENSURE( pPV, "SdrPageView* is NULL" ); + if (pPV) + bDone = FmFormPage::RequestHelp( this, pDrView, rHEvt ); + } + + // If QuickHelp for AutoFill is shown, do not allow it to be removed + + if ( nMouseStatus == SC_GM_TABDOWN && mrViewData.GetRefType() == SC_REFTYPE_FILL && + Help::IsQuickHelpEnabled() ) + bDone = true; + + if (!bDone) + Window::RequestHelp( rHEvt ); +} + +bool ScGridWindow::IsMyModel(const SdrEditView* pSdrView) +{ + return pSdrView && + &pSdrView->GetModel() == mrViewData.GetDocument().GetDrawLayer(); +} + +void ScGridWindow::HideNoteMarker() +{ + mpNoteMarker.reset(); +} + +css::uno::Reference< css::accessibility::XAccessible > + ScGridWindow::CreateAccessible() +{ + css::uno::Reference< css::accessibility::XAccessible > xAcc= GetAccessible(false); + if (xAcc.is()) + { + return xAcc; + } + + rtl::Reference pAccessibleDocument = + new ScAccessibleDocument(GetAccessibleParentWindow()->GetAccessible(), + mrViewData.GetViewShell(), eWhich); + pAccessibleDocument->PreInit(); + + xAcc = pAccessibleDocument; + SetAccessible(xAcc); + + pAccessibleDocument->Init(); + + return xAcc; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/gridwin_dbgutil.cxx b/sc/source/ui/view/gridwin_dbgutil.cxx new file mode 100644 index 0000000000..b141bddd76 --- /dev/null +++ b/sc/source/ui/view/gridwin_dbgutil.cxx @@ -0,0 +1,171 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +std::ostream& operator<<(std::ostream& rStrm, const ScAddress& rAddr) +{ + rStrm << "Col: " << rAddr.Col() << ", Row: " << rAddr.Row() << ", Tab: " << rAddr.Tab(); + return rStrm; +} + +void dumpScDrawObjData(const ScGridWindow& rWindow, const ScDrawObjData& rData, MapUnit eMapUnit) +{ + const Point& rStartOffset = rData.maStartOffset; + Point aStartOffsetPixel = rWindow.LogicToPixel(rStartOffset, MapMode(eMapUnit)); + std::cout << " Start: " << rData.maStart << ", Offset: " << aStartOffsetPixel << std::endl; + + const Point& rEndOffset = rData.maEndOffset; + Point aEndOffsetPixel = rWindow.LogicToPixel(rEndOffset, MapMode(eMapUnit)); + std::cout << " End: : " << rData.maEnd << ", Offset: " << aEndOffsetPixel << std::endl; +} + +} + +void ScGridWindow::dumpColumnInformationPixel() +{ + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + for (SCCOL nCol = 0; nCol <= 20; ++nCol) + { + sal_uInt16 nWidth = rDoc.GetColWidth(nCol, nTab); + tools::Long nPixel = LogicToPixel(Point(nWidth, 0), MapMode(MapUnit::MapTwip)).getX(); + std::cout << "Column: " << nCol << ", Width: " << nPixel << "px" << std::endl; + } +} + +void ScGridWindow::dumpColumnInformationHmm() +{ + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + for (SCCOL nCol = 0; nCol <= 20; ++nCol) + { + sal_uInt16 nWidth = rDoc.GetColWidth(nCol, nTab); + tools::Long nPixel = o3tl::convert(nWidth, o3tl::Length::twip, o3tl::Length::mm100); + std::cout << "Column: " << nCol << ", Width: " << nPixel << "hmm" << std::endl; + } +} + +void ScGridWindow::dumpCellProperties() +{ + ScDocument& rDoc = mrViewData.GetDocument(); + const ScMarkData& rMark = mrViewData.GetMarkData(); + SCTAB nTab = mrViewData.GetTabNo(); + + ScRangeList aList; + if (rMark.IsMultiMarked()) + { + aList = rMark.GetMarkedRangesForTab(nTab); + } + else if (rMark.IsMarked()) + { + aList.Join(rMark.GetMarkArea()); + } + else + { + SCCOL nCol = mrViewData.GetCurX(); + SCROW nRow = mrViewData.GetCurY(); + + ScRange aRange(nCol, nRow, nTab); + aList.Join(aRange, false); + } + + xmlTextWriterPtr writer = xmlNewTextWriterFilename( "dump.xml", 0 ); + xmlTextWriterSetIndent(writer,1); + (void)xmlTextWriterSetIndentString(writer, BAD_CAST(" ")); + + (void)xmlTextWriterStartDocument( writer, nullptr, nullptr, nullptr ); + + (void)xmlTextWriterStartElement(writer, BAD_CAST("selection")); + + for (size_t i = 0, n = aList.size(); i < n; ++i) + { + ScRange const & rRange = aList[i]; + + for (SCCOL nCol = rRange.aStart.Col(); nCol <= rRange.aEnd.Col(); ++nCol) + { + for (SCROW nRow = rRange.aStart.Row(); nRow <= rRange.aEnd.Row(); ++nRow) + { + const ScPatternAttr* pPatternAttr = rDoc.GetPattern(nCol, nRow, nTab); + (void)xmlTextWriterStartElement(writer, BAD_CAST("cell")); + (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("column"), BAD_CAST(OString::number(nCol).getStr())); + (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("row"), BAD_CAST(OString::number(nRow).getStr())); + (void)xmlTextWriterWriteAttribute(writer, BAD_CAST("tab"), BAD_CAST(OString::number(nTab).getStr())); + + pPatternAttr->GetItemSet().dumpAsXml(writer); + + (void)xmlTextWriterEndElement(writer); + } + } + } + + (void)xmlTextWriterEndElement(writer); + + (void)xmlTextWriterEndDocument( writer ); + xmlFreeTextWriter (writer); +} + +void ScGridWindow::dumpGraphicInformation() +{ + ScDocument& rDoc = mrViewData.GetDocument(); + ScDrawLayer* pDrawLayer = rDoc.GetDrawLayer(); + if (!pDrawLayer) + return; + + sal_uInt16 nPageCount = pDrawLayer->GetPageCount(); + for (sal_uInt16 nPage = 0; nPage < nPageCount; ++nPage) + { + SdrPage* pPage = pDrawLayer->GetPage(nPage); + size_t nObjCount = pPage->GetObjCount(); + for (size_t nObj = 0; nObj < nObjCount; ++nObj) + { + SdrObject* pObj = pPage->GetObj(nObj); + std::cout << "Graphic Object" << std::endl; + ScDrawObjData* pObjData = ScDrawLayer::GetObjData(pObj); + if (pObjData) + dumpScDrawObjData(*this, *pObjData, pDrawLayer->GetScaleUnit()); + + const tools::Rectangle& rRect = pObj->GetSnapRect(); + tools::Rectangle aRect = LogicToPixel(rRect, MapMode(pDrawLayer->GetScaleUnit())); + std::cout << "Snap Rectangle (in pixel): " << aRect << std::endl; + } + } +} + +void ScGridWindow::dumpColumnCellStorage() +{ + // Get the current cursor position. + ScAddress aCurPos = mrViewData.GetCurPos(); + + ScDocument& rDoc = mrViewData.GetDocument(); + const ScDPObject* pDP = rDoc.GetDPAtCursor(aCurPos.Col(), aCurPos.Row(), aCurPos.Tab()); + if (pDP) + { + // Dump the pivot table info if the cursor is over a pivot table. + pDP->Dump(); + pDP->DumpCache(); + return; + } + + // Dump the column cell storage info. + rDoc.DumpColumnStorage(aCurPos.Tab(), aCurPos.Col()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/hdrcont.cxx b/sc/source/ui/view/hdrcont.cxx new file mode 100644 index 0000000000..c6688ea115 --- /dev/null +++ b/sc/source/ui/view/hdrcont.cxx @@ -0,0 +1,1124 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SC_DRAG_MIN 2 + +// passes in paint +// (selection left/right must be first because the continuous lines +// are partly overwritten later) + +#define SC_HDRPAINT_SEL_BOTTOM 4 +#define SC_HDRPAINT_BOTTOM 5 +#define SC_HDRPAINT_TEXT 6 +#define SC_HDRPAINT_COUNT 7 + +ScHeaderControl::ScHeaderControl( vcl::Window* pParent, SelectionEngine* pSelectionEngine, + SCCOLROW nNewSize, bool bNewVertical, ScTabView* pTab ) : + Window ( pParent ), + pSelEngine ( pSelectionEngine ), + aShowHelpTimer("sc HeaderControl Popover Timer"), + bVertical ( bNewVertical ), + nSize ( nNewSize ), + nMarkStart ( 0 ), + nMarkEnd ( 0 ), + bMarkRange ( false ), + bDragging ( false ), + nDragNo ( 0 ), + nDragStart ( 0 ), + nDragPos ( 0 ), + nTipVisible ( nullptr ), + bDragMoved ( false ), + bIgnoreMove ( false ), + bInRefMode ( false ), + pTabView ( pTab ) +{ + // RTL: no default mirroring for this window, the spreadsheet itself + // is also not mirrored + // mirror the vertical window for correct border drawing + // table layout depends on sheet format, not UI setting, so the + // borders of the vertical window have to be handled manually, too. + EnableRTL( false ); + + aNormFont = GetFont(); + aNormFont.SetTransparent( true ); //! hard-set WEIGHT_NORMAL ??? + aBoldFont = aNormFont; + aBoldFont.SetWeight( WEIGHT_BOLD ); + aAutoFilterFont = aNormFont; + + SetFont(aBoldFont); + bBoldSet = true; + bAutoFilterSet = false; + + Size aSize = LogicToPixel( Size( + GetTextWidth("8888"), + GetTextHeight() ) ); + aSize.AdjustWidth(4 ); // place for highlight border + aSize.AdjustHeight(3 ); + SetSizePixel( aSize ); + + nWidth = nSmallWidth = aSize.Width(); + nBigWidth = LogicToPixel( Size( GetTextWidth("8888888"), 0 ) ).Width() + 5; + + aShowHelpTimer.SetInvokeHandler(LINK(this, ScHeaderControl, ShowDragHelpHdl)); + aShowHelpTimer.SetTimeout(GetSettings().GetMouseSettings().GetDoubleClickTime()); + + SetBackground(); +} + +void ScHeaderControl::dispose() +{ + aShowHelpTimer.Stop(); + vcl::Window::dispose(); +} + +void ScHeaderControl::SetWidth( tools::Long nNew ) +{ + OSL_ENSURE( bVertical, "SetWidth works only on row headers" ); + if ( nNew != nWidth ) + { + Size aSize( nNew, GetSizePixel().Height() ); + SetSizePixel( aSize ); + + nWidth = nNew; + + Invalidate(); + } +} + +ScHeaderControl::~ScHeaderControl() +{ +} + +void ScHeaderControl::DoPaint( SCCOLROW nStart, SCCOLROW nEnd ) +{ + bool bLayoutRTL = IsLayoutRTL(); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + tools::Rectangle aRect( Point(0,0), GetOutputSizePixel() ); + if ( bVertical ) + { + aRect.SetTop( GetScrPos( nStart )-nLayoutSign ); // extra pixel for line at top of selection + aRect.SetBottom( GetScrPos( nEnd+1 )-nLayoutSign ); + } + else + { + aRect.SetLeft( GetScrPos( nStart )-nLayoutSign ); // extra pixel for line left of selection + aRect.SetRight( GetScrPos( nEnd+1 )-nLayoutSign ); + } + Invalidate(aRect); +} + +void ScHeaderControl::SetMark( bool bNewSet, SCCOLROW nNewStart, SCCOLROW nNewEnd ) +{ + bool bEnabled = SC_MOD()->GetInputOptions().GetMarkHeader(); //! cache? + if (!bEnabled) + bNewSet = false; + + bool bOldSet = bMarkRange; + SCCOLROW nOldStart = nMarkStart; + SCCOLROW nOldEnd = nMarkEnd; + PutInOrder( nNewStart, nNewEnd ); + bMarkRange = bNewSet; + nMarkStart = nNewStart; + nMarkEnd = nNewEnd; + + // Paint + + if ( bNewSet ) + { + if ( bOldSet ) + { + if ( nNewStart == nOldStart ) + { + if ( nNewEnd != nOldEnd ) + DoPaint( std::min( nNewEnd, nOldEnd ) + 1, std::max( nNewEnd, nOldEnd ) ); + } + else if ( nNewEnd == nOldEnd ) + DoPaint( std::min( nNewStart, nOldStart ), std::max( nNewStart, nOldStart ) - 1 ); + else if ( nNewStart > nOldEnd || nNewEnd < nOldStart ) + { + // two areas + DoPaint( nOldStart, nOldEnd ); + DoPaint( nNewStart, nNewEnd ); + } + else // somehow overlapping... (it is not often) + DoPaint( std::min( nNewStart, nOldStart ), std::max( nNewEnd, nOldEnd ) ); + } + else + DoPaint( nNewStart, nNewEnd ); // completely new selection + } + else if ( bOldSet ) + DoPaint( nOldStart, nOldEnd ); // cancel selection +} + +tools::Long ScHeaderControl::GetScrPos( SCCOLROW nEntryNo ) const +{ + tools::Long nScrPos; + + tools::Long nMax = ( bVertical ? GetOutputSizePixel().Height() : GetOutputSizePixel().Width() ) + 1; + if (nEntryNo >= nSize) + nScrPos = nMax; + else + { + nScrPos = 0; + for (SCCOLROW i=GetPos(); i 0) + i += nHidden - 1; + } + } + } + + if ( IsLayoutRTL() ) + nScrPos = nMax - nScrPos - 2; + + return nScrPos; +} + +void ScHeaderControl::Paint( vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& rRect ) +{ + // It is important for VCL to have few calls, that is why the outer lines are + // grouped together + + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + bool bHighContrast = rStyleSettings.GetHighContrastMode(); + bool bDark = rStyleSettings.GetFaceColor().IsDark(); + // Use the same distinction for bDark as in Window::DrawSelectionBackground + + Color aTextColor = rStyleSettings.GetButtonTextColor(); + Color aSelTextColor = rStyleSettings.GetHighlightTextColor(); + Color aAFilterTextColor = rStyleSettings.GetButtonTextColor(); + aAFilterTextColor.Merge(COL_LIGHTBLUE, bDark ? 150 : 10); // color of filtered row numbers + aNormFont.SetColor( aTextColor ); + aAutoFilterFont.SetColor(aAFilterTextColor); + if ( bHighContrast ) + aBoldFont.SetColor( aTextColor ); + else + aBoldFont.SetColor( aSelTextColor ); + + if (bAutoFilterSet) + SetTextColor(aAFilterTextColor); + else + SetTextColor((bBoldSet && !bHighContrast) ? aSelTextColor : aTextColor); + + Color aSelLineColor = rStyleSettings.GetHighlightColor(); + aSelLineColor.Merge( COL_BLACK, 0xe0 ); // darken just a little bit + + bool bLayoutRTL = IsLayoutRTL(); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + bool bMirrored = IsMirrored(); + + OUString aString; + sal_uInt16 nBarSize; + Point aScrPos; + Size aTextSize; + + if (bVertical) + nBarSize = static_cast(GetSizePixel().Width()); + else + nBarSize = static_cast(GetSizePixel().Height()); + + SCCOLROW nPos = GetPos(); + + tools::Long nPStart = bVertical ? rRect.Top() : rRect.Left(); + tools::Long nPEnd = bVertical ? rRect.Bottom() : rRect.Right(); + + tools::Long nTransStart = nPEnd + 1; + tools::Long nTransEnd = 0; + + tools::Long nInitScrPos = 0; + if ( bLayoutRTL ) + { + std::swap(nPStart, nPEnd); + std::swap(nTransStart, nTransEnd); + if ( bVertical ) // start loops from the end + nInitScrPos = GetSizePixel().Height() - 1; + else + nInitScrPos = GetSizePixel().Width() - 1; + } + + // complete the painting of the outer lines + // first find the end of the last cell + + tools::Long nLineEnd = nInitScrPos - nLayoutSign; + + for (SCCOLROW i=nPos; i= nMarkStart && i <= nMarkEnd ) + { + tools::Long nLineStart = nLineEnd - ( nSizePix - 1 ) * nLayoutSign; + if ( nLineStart * nLayoutSign < nTransStart * nLayoutSign ) + nTransStart = nLineStart; + if ( nLineEnd * nLayoutSign > nTransEnd * nLayoutSign ) + nTransEnd = nLineEnd; + } + + if ( nLineEnd * nLayoutSign > nPEnd * nLayoutSign ) + { + nLineEnd = nPEnd; + break; + } + } + else + { + SCCOLROW nHidden = GetHiddenCount(i); + if (nHidden > 0) + i += nHidden - 1; + } + } + + // background is different for entry area and behind the entries + + tools::Rectangle aFillRect; + GetOutDev()->SetLineColor(); + + if ( nLineEnd * nLayoutSign >= nInitScrPos * nLayoutSign ) + { + GetOutDev()->SetFillColor( rStyleSettings.GetFaceColor() ); + if ( bVertical ) + aFillRect = tools::Rectangle( 0, nInitScrPos, nBarSize-1, nLineEnd ); + else + aFillRect = tools::Rectangle( nInitScrPos, 0, nLineEnd, nBarSize-1 ); + GetOutDev()->DrawRect( aFillRect ); + } + + if ( nLineEnd * nLayoutSign < nPEnd * nLayoutSign ) + { + GetOutDev()->SetFillColor( SC_MOD()->GetColorConfig().GetColorValue(svtools::APPBACKGROUND).nColor ); + if ( bVertical ) + aFillRect = tools::Rectangle( 0, nLineEnd+nLayoutSign, nBarSize-1, nPEnd ); + else + aFillRect = tools::Rectangle( nLineEnd+nLayoutSign, 0, nPEnd, nBarSize-1 ); + GetOutDev()->DrawRect( aFillRect ); + } + + if ( nLineEnd * nLayoutSign >= nPStart * nLayoutSign ) + { + if ( nTransEnd * nLayoutSign >= nTransStart * nLayoutSign ) + { + if (bVertical) + aFillRect = tools::Rectangle( 0, nTransStart, nBarSize-1, nTransEnd ); + else + aFillRect = tools::Rectangle( nTransStart, 0, nTransEnd, nBarSize-1 ); + + if ( bHighContrast ) + { + if ( bDark ) + { + // solid grey background for dark face color is drawn before lines + GetOutDev()->SetLineColor(); + GetOutDev()->SetFillColor( COL_LIGHTGRAY ); + GetOutDev()->DrawRect( aFillRect ); + } + } + else + { + // background for selection + GetOutDev()->SetLineColor(); + Color aColor( rStyleSettings.GetAccentColor() ); +// merging the highlightcolor (which is used if accent does not exist) with the background +// fails in many cases such as Breeze Dark (highlight is too close to background) and +// Breeze Light (font color is white and not readable anymore) +#ifdef MACOSX + aColor.Merge( rStyleSettings.GetFaceColor(), 80 ); +#endif + GetOutDev()->SetFillColor( aColor ); + GetOutDev()->DrawRect( aFillRect ); + } + } + + GetOutDev()->SetLineColor( rStyleSettings.GetDarkShadowColor() ); + if (bVertical) + { + tools::Long nDarkPos = bMirrored ? 0 : nBarSize-1; + GetOutDev()->DrawLine( Point( nDarkPos, nPStart ), Point( nDarkPos, nLineEnd ) ); + } + else + GetOutDev()->DrawLine( Point( nPStart, nBarSize-1 ), Point( nLineEnd, nBarSize-1 ) ); + + // line in different color for selection + if ( nTransEnd * nLayoutSign >= nTransStart * nLayoutSign && !bHighContrast ) + { + GetOutDev()->SetLineColor( aSelLineColor ); + if (bVertical) + { + tools::Long nDarkPos = bMirrored ? 0 : nBarSize-1; + GetOutDev()->DrawLine( Point( nDarkPos, nTransStart ), Point( nDarkPos, nTransEnd ) ); + } + else + GetOutDev()->DrawLine( Point( nTransStart, nBarSize-1 ), Point( nTransEnd, nBarSize-1 ) ); + } + } + + // tdf#89841 Use blue row numbers when Autofilter selected + std::vector aSpans; + if (bVertical && pTabView) + { + SCTAB nTab = pTabView->GetViewData().GetTabNo(); + ScDocument& rDoc = pTabView->GetViewData().GetDocument(); + + ScDBData* pDBData = rDoc.GetAnonymousDBData(nTab); + if (pDBData && pDBData->HasAutoFilter()) + { + SCSIZE nSelected = 0; + SCSIZE nTotal = 0; + pDBData->GetFilterSelCount(nSelected, nTotal); + if (nTotal > nSelected) + { + ScRange aRange; + pDBData->GetArea(aRange); + SCCOLROW nStartRow = static_cast(aRange.aStart.Row()); + SCCOLROW nEndRow = static_cast(aRange.aEnd.Row()); + if (pDBData->HasHeader()) + nStartRow++; + aSpans.push_back(sc::ColRowSpan(nStartRow, nEndRow)); + } + } + + ScDBCollection* pDocColl = rDoc.GetDBCollection(); + if (!pDocColl->empty()) + { + ScDBCollection::NamedDBs& rDBs = pDocColl->getNamedDBs(); + for (const auto& rxDB : rDBs) + { + if (rxDB->GetTab() == nTab && rxDB->HasAutoFilter()) + { + SCSIZE nSelected = 0; + SCSIZE nTotal = 0; + rxDB->GetFilterSelCount(nSelected, nTotal); + if (nTotal > nSelected) + { + ScRange aRange; + rxDB->GetArea(aRange); + SCCOLROW nStartRow = static_cast(aRange.aStart.Row()); + SCCOLROW nEndRow = static_cast(aRange.aEnd.Row()); + if (rxDB->HasHeader()) + nStartRow++; + aSpans.push_back(sc::ColRowSpan(nStartRow, nEndRow)); + } + } + } + } + } + + // loop through entries several times to avoid changing the line color too often + // and to allow merging of lines + + ScGridMerger aGrid( GetOutDev(), 1, 1 ); + + // start at SC_HDRPAINT_BOTTOM instead of 0 - selection doesn't get different + // borders, light border at top isn't used anymore + // use SC_HDRPAINT_SEL_BOTTOM for different color + + for (sal_uInt16 nPass = SC_HDRPAINT_SEL_BOTTOM; nPass < SC_HDRPAINT_COUNT; nPass++) + { + // set line color etc. before entry loop + switch ( nPass ) + { + case SC_HDRPAINT_SEL_BOTTOM: + // same as non-selected for high contrast + GetOutDev()->SetLineColor( bHighContrast ? rStyleSettings.GetDarkShadowColor() : aSelLineColor ); + break; + case SC_HDRPAINT_BOTTOM: + GetOutDev()->SetLineColor( rStyleSettings.GetDarkShadowColor() ); + break; + case SC_HDRPAINT_TEXT: + // DrawSelectionBackground is used only for high contrast on light background + if ( nTransEnd * nLayoutSign >= nTransStart * nLayoutSign && bHighContrast && !bDark ) + { + // Transparent selection background is drawn after lines, before text. + // Use DrawSelectionBackground to make sure there is a visible + // difference. The case of a dark face color, where DrawSelectionBackground + // would just paint over the lines, is handled separately (bDark). + // Otherwise, GetHighlightColor is used with 80% transparency. + // The window's background color (SetBackground) has to be the background + // of the cell area, for the contrast comparison in DrawSelectionBackground. + + tools::Rectangle aTransRect; + if (bVertical) + aTransRect = tools::Rectangle( 0, nTransStart, nBarSize-1, nTransEnd ); + else + aTransRect = tools::Rectangle( nTransStart, 0, nTransEnd, nBarSize-1 ); + SetBackground( rStyleSettings.GetFaceColor() ); + DrawSelectionBackground( aTransRect, 0, true, false ); + SetBackground(); + } + break; + } + + SCCOLROW nCount=0; + tools::Long nScrPos=nInitScrPos; + do + { + if (bVertical) + aScrPos = Point( 0, nScrPos ); + else + aScrPos = Point( nScrPos, 0 ); + + SCCOLROW nEntryNo = nCount + nPos; + if ( nEntryNo >= nSize ) // rDoc.MaxCol()/rDoc.MaxRow() + nScrPos = nPEnd + nLayoutSign; // beyond nPEnd -> stop + else + { + sal_uInt16 nSizePix = GetEntrySize( nEntryNo ); + + if (nSizePix == 0) + { + SCCOLROW nHidden = GetHiddenCount(nEntryNo); + if (nHidden > 0) + nCount += nHidden - 1; + } + else if ((nScrPos+nSizePix*nLayoutSign)*nLayoutSign >= nPStart*nLayoutSign) + { + Point aEndPos(aScrPos); + if (bVertical) + aEndPos = Point( aScrPos.X()+nBarSize-1, aScrPos.Y()+(nSizePix-1)*nLayoutSign ); + else + aEndPos = Point( aScrPos.X()+(nSizePix-1)*nLayoutSign, aScrPos.Y()+nBarSize-1 ); + + bool bMark = bMarkRange && nEntryNo >= nMarkStart && nEntryNo <= nMarkEnd; + bool bNextToMark = bMarkRange && nEntryNo + 1 >= nMarkStart && nEntryNo <= nMarkEnd; + + switch ( nPass ) + { + case SC_HDRPAINT_SEL_BOTTOM: + case SC_HDRPAINT_BOTTOM: + if ( nPass == ( bNextToMark ? SC_HDRPAINT_SEL_BOTTOM : SC_HDRPAINT_BOTTOM ) ) + { + if (bVertical) + aGrid.AddHorLine(/* here we work in pixels */ true, aScrPos.X(), aEndPos.X(), aEndPos.Y()); + else + aGrid.AddVerLine(/* here we work in pixels */ true, aEndPos.X(), aScrPos.Y(), aEndPos.Y()); + + // thick bottom for hidden rows + // (drawn directly, without aGrid) + if ( nEntryNo+1 < nSize ) + if ( GetEntrySize(nEntryNo+1)==0 ) + { + if (bVertical) + GetOutDev()->DrawLine( Point(aScrPos.X(),aEndPos.Y()-nLayoutSign), + Point(aEndPos.X(),aEndPos.Y()-nLayoutSign) ); + else + GetOutDev()->DrawLine( Point(aEndPos.X()-nLayoutSign,aScrPos.Y()), + Point(aEndPos.X()-nLayoutSign,aEndPos.Y()) ); + } + } + break; + + case SC_HDRPAINT_TEXT: + if ( nSizePix > 1 ) // minimal check for small columns/rows + { + if (bVertical) + { + bool bAutoFilterPos = false; + for (const auto& rSpan : aSpans) + { + if (nEntryNo >= rSpan.mnStart && nEntryNo <= rSpan.mnEnd) + { + bAutoFilterPos = true; + break; + } + } + + if (bMark != bBoldSet || bAutoFilterPos != bAutoFilterSet) + { + if (bMark) + SetFont(aBoldFont); + else if (bAutoFilterPos) + SetFont(aAutoFilterFont); + else + SetFont(aNormFont); + bBoldSet = bMark; + bAutoFilterSet = bAutoFilterPos && !bMark; + } + } + else + { + if (bMark != bBoldSet) + { + if (bMark) + SetFont(aBoldFont); + else + SetFont(aNormFont); + bBoldSet = bMark; + } + } + + aString = GetEntryText( nEntryNo ); + aTextSize.setWidth( GetTextWidth( aString ) ); + aTextSize.setHeight( GetTextHeight() ); + + Point aTxtPos(aScrPos); + if (bVertical) + { + aTxtPos.AdjustX((nBarSize-aTextSize.Width())/2 ); + aTxtPos.AdjustY((nSizePix*nLayoutSign-aTextSize.Height())/2 ); + if ( bMirrored ) + aTxtPos.AdjustX(1 ); // dark border is left instead of right + } + else + { + aTxtPos.AdjustX((nSizePix*nLayoutSign-aTextSize.Width()+1)/2 ); + aTxtPos.AdjustY((nBarSize-aTextSize.Height())/2 ); + } + GetOutDev()->DrawText( aTxtPos, aString ); + } + break; + } + + // when selecting the complete row/column: + // InvertRect( Rectangle( aScrPos, aEndPos ) ); + } + nScrPos += nSizePix * nLayoutSign; // also if before the visible area + } + ++nCount; + } + while ( nScrPos * nLayoutSign <= nPEnd * nLayoutSign ); + + aGrid.Flush(); + } +} + +SCCOLROW ScHeaderControl::GetMousePos(const Point& rPos, bool& rBorder) const +{ + bool bFound = false; + SCCOLROW nPos = GetPos(); + SCCOLROW nHitNo = nPos; + SCCOLROW nEntryNo = 1 + nPos; + tools::Long nScrPos; + tools::Long nMousePos = bVertical ? rPos.Y() : rPos.X(); + tools::Long nDif; + Size aSize = GetOutputSizePixel(); + tools::Long nWinSize = bVertical ? aSize.Height() : aSize.Width(); + + bool bLayoutRTL = IsLayoutRTL(); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + tools::Long nEndPos = bLayoutRTL ? -1 : nWinSize; + + nScrPos = GetScrPos( nPos ) - nLayoutSign; + do + { + if (nEntryNo > nSize) + nScrPos = nEndPos + nLayoutSign; + else + nScrPos += GetEntrySize( nEntryNo - 1 ) * nLayoutSign; //! GetHiddenCount() ?? + + nDif = nMousePos - nScrPos; + if (nDif >= -2 && nDif <= 2) + { + bFound = true; + nHitNo=nEntryNo-1; + } + else if (nDif * nLayoutSign >= 0 && nEntryNo < nSize) + nHitNo = nEntryNo; + ++nEntryNo; + } + while ( nScrPos * nLayoutSign < nEndPos * nLayoutSign && nDif * nLayoutSign > 0 ); + + rBorder = bFound; + return nHitNo; +} + +bool ScHeaderControl::IsSelectionAllowed(SCCOLROW nPos) const +{ + ScTabViewShell* pViewSh = dynamic_cast(SfxViewShell::Current()); + if (!pViewSh) + return false; + + ScViewData& rViewData = pViewSh->GetViewData(); + sal_uInt16 nTab = rViewData.GetTabNo(); + ScDocument& rDoc = rViewData.GetDocument(); + const ScTableProtection* pProtect = rDoc.GetTabProtection(nTab); + bool bSelectAllowed = true; + if ( pProtect && pProtect->isProtected() ) + { + // This sheet is protected. Check if a context menu is allowed on this cell. + bool bCellsProtected = false; + if (bVertical) + { + // row header + SCROW nRPos = static_cast(nPos); + bCellsProtected = rDoc.HasAttrib(0, nRPos, nTab, rDoc.MaxCol(), nRPos, nTab, HasAttrFlags::Protected); + } + else + { + // column header + SCCOL nCPos = static_cast(nPos); + bCellsProtected = rDoc.HasAttrib(nCPos, 0, nTab, nCPos, rDoc.MaxRow(), nTab, HasAttrFlags::Protected); + } + + bool bSelProtected = pProtect->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS); + bool bSelUnprotected = pProtect->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS); + + if (bCellsProtected) + bSelectAllowed = bSelProtected; + else + bSelectAllowed = bSelUnprotected; + } + return bSelectAllowed; +} + +void ScHeaderControl::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if (IsDisabled()) + return; + + bIgnoreMove = false; + SelectWindow(); + + bool bIsBorder; + SCCOLROW nHitNo = GetMousePos(rMEvt.GetPosPixel(), bIsBorder); + if (!IsSelectionAllowed(nHitNo)) + return; + if ( ! rMEvt.IsLeft() ) + return; + if ( SC_MOD()->IsFormulaMode() ) + { + if( !pTabView ) + return; + SCTAB nTab = pTabView->GetViewData().GetTabNo(); + if( !rMEvt.IsShift() ) + pTabView->DoneRefMode( rMEvt.IsMod1() ); + ScDocument& rDoc = pTabView->GetViewData().GetDocument(); + if( !bVertical ) + { + pTabView->InitRefMode( nHitNo, 0, nTab, SC_REFTYPE_REF ); + pTabView->UpdateRef( nHitNo, rDoc.MaxRow(), nTab ); + } + else + { + pTabView->InitRefMode( 0, nHitNo, nTab, SC_REFTYPE_REF ); + pTabView->UpdateRef( rDoc.MaxCol(), nHitNo, nTab ); + } + bInRefMode = true; + return; + } + if ( bIsBorder && ResizeAllowed() ) + { + nDragNo = nHitNo; + sal_uInt16 nClicks = rMEvt.GetClicks(); + if ( nClicks && nClicks%2==0 ) + { + SetEntrySize( nDragNo, HDR_SIZE_OPTIMUM ); + SetPointer( PointerStyle::Arrow ); + } + else + { + if (bVertical) + nDragStart = rMEvt.GetPosPixel().Y(); + else + nDragStart = rMEvt.GetPosPixel().X(); + nDragPos = nDragStart; + // tdf#140833 launch help tip to show after the double click time has expired + // so under gtk the popover isn't active when the double click is processed + // by gtk because under load on wayland the double click is getting handled + // by something else and getting sent to the window underneath our window + aShowHelpTimer.Start(); + DrawInvert( nDragPos ); + + StartTracking(); + bDragging = true; + bDragMoved = false; + } + } + else + { + pSelEngine->SetWindow( this ); + tools::Rectangle aVis( Point(), GetOutputSizePixel() ); + if (bVertical) + { + aVis.SetLeft( LONG_MIN ); + aVis.SetRight( LONG_MAX ); + } + else + { + aVis.SetTop( LONG_MIN ); + aVis.SetBottom( LONG_MAX ); + } + pSelEngine->SetVisibleArea( aVis ); + + SetMarking( true ); // must precede SelMouseButtonDown + pSelEngine->SelMouseButtonDown( rMEvt ); + + // In column/row headers a simple click already is a selection. + // -> Call SelMouseMove to ensure CreateAnchor is called (and DestroyAnchor + // if the next click is somewhere else with Control key). + pSelEngine->SelMouseMove( rMEvt ); + + if (IsMouseCaptured()) + { + // tracking instead of CaptureMouse, so it can be cancelled cleanly + //! someday SelectionEngine itself should call StartTracking!?! + ReleaseMouse(); + StartTracking(); + } + } +} + +void ScHeaderControl::MouseButtonUp( const MouseEvent& rMEvt ) +{ + if ( IsDisabled() ) + return; + + if ( SC_MOD()->IsFormulaMode() ) + { + SC_MOD()->EndReference(); + bInRefMode = false; + return; + } + + SetMarking( false ); + bIgnoreMove = false; + + if ( bDragging ) + { + DrawInvert( nDragPos ); + ReleaseMouse(); + HideDragHelp(); + bDragging = false; + + tools::Long nScrPos = GetScrPos( nDragNo ); + tools::Long nMousePos = bVertical ? rMEvt.GetPosPixel().Y() : rMEvt.GetPosPixel().X(); + bool bLayoutRTL = IsLayoutRTL(); + tools::Long nNewWidth = bLayoutRTL ? ( nScrPos - nMousePos + 1 ) + : ( nMousePos + 2 - nScrPos ); + + if ( nNewWidth < 0 /* && !IsSelected(nDragNo) */ ) + { + SCCOLROW nStart = 0; + SCCOLROW nEnd = nDragNo; + while (nNewWidth < 0) + { + nStart = nDragNo; + if (nDragNo>0) + { + --nDragNo; + nNewWidth += GetEntrySize( nDragNo ); //! GetHiddenCount() ??? + } + else + nNewWidth = 0; + } + HideEntries( nStart, nEnd ); + } + else + { + if (bDragMoved) + SetEntrySize( nDragNo, static_cast(nNewWidth) ); + } + } + else + { + pSelEngine->SelMouseButtonUp( rMEvt ); + ReleaseMouse(); + } +} + +void ScHeaderControl::MouseMove( const MouseEvent& rMEvt ) +{ + if ( IsDisabled() ) + { + SetPointer( PointerStyle::Arrow ); + return; + } + + if ( bInRefMode && rMEvt.IsLeft() && SC_MOD()->IsFormulaMode() ) + { + if( !pTabView ) + return; + bool bTmp; + SCCOLROW nHitNo = GetMousePos(rMEvt.GetPosPixel(), bTmp); + SCTAB nTab = pTabView->GetViewData().GetTabNo(); + ScDocument& rDoc = pTabView->GetViewData().GetDocument(); + if( !bVertical ) + pTabView->UpdateRef( nHitNo, rDoc.MaxRow(), nTab ); + else + pTabView->UpdateRef( rDoc.MaxCol(), nHitNo, nTab ); + + return; + } + + if ( bDragging ) + { + tools::Long nNewPos = bVertical ? rMEvt.GetPosPixel().Y() : rMEvt.GetPosPixel().X(); + if ( nNewPos != nDragPos ) + { + DrawInvert( nDragPos ); + nDragPos = nNewPos; + ShowDragHelp(); + DrawInvert( nDragPos ); + + if (nDragPos <= nDragStart-SC_DRAG_MIN || nDragPos >= nDragStart+SC_DRAG_MIN) + bDragMoved = true; + } + } + else + { + bool bIsBorder; + (void)GetMousePos(rMEvt.GetPosPixel(), bIsBorder); + + if ( bIsBorder && rMEvt.GetButtons()==0 && ResizeAllowed() ) + SetPointer( bVertical ? PointerStyle::VSizeBar : PointerStyle::HSizeBar ); + else + SetPointer( PointerStyle::Arrow ); + + if (!bIgnoreMove) + pSelEngine->SelMouseMove( rMEvt ); + } +} + +void ScHeaderControl::Tracking( const TrackingEvent& rTEvt ) +{ + // Distribute the tracking events to the various MouseEvents, because + // SelectionEngine does not know anything about Tracking + + if ( rTEvt.IsTrackingCanceled() ) + StopMarking(); + else if ( rTEvt.IsTrackingEnded() ) + MouseButtonUp( rTEvt.GetMouseEvent() ); + else + MouseMove( rTEvt.GetMouseEvent() ); +} + +void ScHeaderControl::Command( const CommandEvent& rCEvt ) +{ + CommandEventId nCmd = rCEvt.GetCommand(); + if ( nCmd == CommandEventId::ContextMenu ) + { + StopMarking(); // finish selection / dragging + + // execute popup menu + + ScTabViewShell* pViewSh = dynamic_cast< ScTabViewShell *>( SfxViewShell::Current() ); + if ( pViewSh ) + { + if ( rCEvt.IsMouseEvent() ) + { + // #i18735# select the column/row under the mouse pointer + ScViewData& rViewData = pViewSh->GetViewData(); + + SelectWindow(); // also deselects drawing objects, stops draw text edit + if ( rViewData.HasEditView( rViewData.GetActivePart() ) ) + SC_MOD()->InputEnterHandler(); // always end edit mode + + bool bBorder; + SCCOLROW nPos = GetMousePos(rCEvt.GetMousePosPixel(), bBorder ); + if (!IsSelectionAllowed(nPos)) + // Selecting this cell is not allowed, neither is context menu. + return; + + SCTAB nTab = rViewData.GetTabNo(); + ScDocument& rDoc = pViewSh->GetViewData().GetDocument(); + ScRange aNewRange; + if ( bVertical ) + aNewRange = ScRange( 0, sal::static_int_cast(nPos), nTab, + rDoc.MaxCol(), sal::static_int_cast(nPos), nTab ); + else + aNewRange = ScRange( sal::static_int_cast(nPos), 0, nTab, + sal::static_int_cast(nPos), rDoc.MaxRow(), nTab ); + + // see if any part of the range is already selected + ScRangeList aRanges; + rViewData.GetMarkData().FillRangeListWithMarks( &aRanges, false ); + bool bSelected = aRanges.Intersects(aNewRange); + + // select the range if no part of it was selected + if ( !bSelected ) + pViewSh->MarkRange( aNewRange ); + } + + pViewSh->GetDispatcher()->ExecutePopup( bVertical ? OUString( "rowheader" ) : OUString( "colheader" ) ); + } + } + else if ( nCmd == CommandEventId::StartDrag ) + { + pSelEngine->Command( rCEvt ); + } +} + +void ScHeaderControl::StopMarking() +{ + if ( bDragging ) + { + DrawInvert( nDragPos ); + HideDragHelp(); + bDragging = false; + } + + SetMarking( false ); + bIgnoreMove = true; + + // don't call pSelEngine->Reset, so selection across the parts of + // a split/frozen view is possible + if (IsMouseCaptured()) + ReleaseMouse(); +} + +IMPL_LINK_NOARG(ScHeaderControl, ShowDragHelpHdl, Timer*, void) +{ + ShowDragHelp(); +} + +void ScHeaderControl::ShowDragHelp() +{ + aShowHelpTimer.Stop(); + if (!Help::IsQuickHelpEnabled()) + return; + + tools::Long nScrPos = GetScrPos( nDragNo ); + bool bLayoutRTL = IsLayoutRTL(); + tools::Long nVal = bLayoutRTL ? ( nScrPos - nDragPos + 1 ) + : ( nDragPos + 2 - nScrPos ); + + OUString aHelpStr = GetDragHelp( nVal ); + Point aPos = OutputToScreenPixel( Point(0,0) ); + Size aSize = GetSizePixel(); + + Point aMousePos = OutputToScreenPixel(GetPointerPosPixel()); + + tools::Rectangle aRect; + QuickHelpFlags nAlign; + if (!bVertical) + { + // above + aRect.SetLeft( aMousePos.X() ); + aRect.SetTop( aPos.Y() - 4 ); + nAlign = QuickHelpFlags::Bottom|QuickHelpFlags::Center; + } + else + { + // top right + aRect.SetLeft( aPos.X() + aSize.Width() + 8 ); + aRect.SetTop( aMousePos.Y() - 2 ); + nAlign = QuickHelpFlags::Left|QuickHelpFlags::Bottom; + } + + aRect.SetRight( aRect.Left() ); + aRect.SetBottom( aRect.Top() ); + + if (nTipVisible) + Help::HidePopover(this, nTipVisible); + nTipVisible = Help::ShowPopover(this, aRect, aHelpStr, nAlign); +} + +void ScHeaderControl::HideDragHelp() +{ + aShowHelpTimer.Stop(); + if (nTipVisible) + { + Help::HidePopover(this, nTipVisible); + nTipVisible = nullptr; + } +} + +void ScHeaderControl::RequestHelp( const HelpEvent& rHEvt ) +{ + // If the own QuickHelp is displayed, don't let RequestHelp remove it + + bool bOwn = bDragging && Help::IsQuickHelpEnabled(); + if (!bOwn) + Window::RequestHelp(rHEvt); +} + +// dummies for virtual methods + +SCCOLROW ScHeaderControl::GetHiddenCount( SCCOLROW nEntryNo ) const +{ + SCCOLROW nHidden = 0; + while ( nEntryNo < nSize && GetEntrySize( nEntryNo ) == 0 ) + { + ++nEntryNo; + ++nHidden; + } + return nHidden; +} + +bool ScHeaderControl::IsLayoutRTL() const +{ + return false; +} + +bool ScHeaderControl::IsMirrored() const +{ + return false; +} + +bool ScHeaderControl::IsDisabled() const +{ + return false; +} + +bool ScHeaderControl::ResizeAllowed() const +{ + return true; +} + +void ScHeaderControl::SelectWindow() +{ +} + +void ScHeaderControl::DrawInvert( tools::Long /* nDragPos */ ) +{ +} + +OUString ScHeaderControl::GetDragHelp( tools::Long /* nVal */ ) +{ + return OUString(); +} + +void ScHeaderControl::SetMarking( bool /* bSet */ ) +{ +} + +void ScHeaderControl::GetMarkRange(SCCOLROW& rStart, SCCOLROW& rEnd) const +{ + rStart = nMarkStart; + rEnd = nMarkEnd; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/hintwin.cxx b/sc/source/ui/view/hintwin.cxx new file mode 100644 index 0000000000..d57d8e0b78 --- /dev/null +++ b/sc/source/ui/view/hintwin.cxx @@ -0,0 +1,182 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define HINT_LINESPACE 2 +#define HINT_INDENT 3 +#define HINT_MARGIN 4 + +ScOverlayHint::ScOverlayHint(OUString aTit, const OUString& rMsg, const Color& rColor, vcl::Font aFont) + : OverlayObject(rColor) + , m_aTitle(std::move(aTit)) + , m_aMessage(convertLineEnd(rMsg, LINEEND_CR)) + , m_aTextFont(std::move(aFont)) + , m_aMapMode(MapUnit::MapPixel) + , m_nLeft(0) + , m_nTop(0) +{ +} + +drawinglayer::primitive2d::Primitive2DContainer ScOverlayHint::createOverlaySequence(sal_Int32 nLeft, sal_Int32 nTop, + const MapMode &rMapMode, + basegfx::B2DRange &rRange) const +{ + OutputDevice* pDefaultDev = Application::GetDefaultDevice(); + MapMode aOld = pDefaultDev->GetMapMode(); + pDefaultDev->SetMapMode(rMapMode); + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + const Color& rColor = rStyleSettings.GetLabelTextColor(); + vcl::Font aTextFont = m_aTextFont; + aTextFont.SetFontSize(pDefaultDev->PixelToLogic(aTextFont.GetFontSize(), rMapMode)); + vcl::Font aHeadFont = aTextFont; + aHeadFont.SetWeight(WEIGHT_BOLD); + + // Create the text primitive for the title + basegfx::B2DVector aFontSize; + drawinglayer::attribute::FontAttribute aFontAttr = + drawinglayer::primitive2d::getFontAttributeFromVclFont(aFontSize, aHeadFont, false, false); + + FontMetric aFontMetric = pDefaultDev->GetFontMetric(aHeadFont); + Size aHintMargin = pDefaultDev->PixelToLogic(Size(HINT_MARGIN, HINT_MARGIN), rMapMode); + Size aIndent = pDefaultDev->PixelToLogic(Size(HINT_INDENT, HINT_LINESPACE), rMapMode); + double nTextOffsetY = nTop + aHintMargin.Height() + aFontMetric.GetAscent(); + Point aTextPos(nLeft + aHintMargin.Width() , nTextOffsetY); + rRange = basegfx::B2DRange(nLeft, nTop, nLeft + aHintMargin.Width(), nTop + aHintMargin.Height()); + + basegfx::B2DHomMatrix aTextMatrix(basegfx::utils::createScaleTranslateB2DHomMatrix( + aFontSize.getX(), aFontSize.getY(), + aTextPos.X(), aTextPos.Y())); + + rtl::Reference pTitle = + new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + aTextMatrix, m_aTitle, 0, m_aTitle.getLength(), + std::vector(), {}, std::move(aFontAttr), css::lang::Locale(), + rColor.getBColor()); + + Point aTextStart(nLeft + aHintMargin.Width() + aIndent.Width(), + nTop + aHintMargin.Height() + aFontMetric.GetLineHeight() + aIndent.Height()); + + drawinglayer::geometry::ViewInformation2D aDummy; + rRange.expand(pTitle->getB2DRange(aDummy)); + + drawinglayer::primitive2d::Primitive2DContainer aSeq { pTitle }; + + aFontMetric = pDefaultDev->GetFontMetric(aTextFont); + pDefaultDev->SetMapMode(aOld); + + nTextOffsetY = aFontMetric.GetAscent(); + sal_Int32 nLineHeight = aFontMetric.GetLineHeight(); + + aFontAttr = drawinglayer::primitive2d::getFontAttributeFromVclFont(aFontSize, aTextFont, false, false); + + sal_Int32 nIndex = 0; + Point aLineStart = aTextStart; + sal_Int32 nLineCount = 0; + while (nIndex != -1) + { + OUString aLine = m_aMessage.getToken( 0, '\r', nIndex ); + if (aLine.getLength() > 255) + { + // prevent silliness higher up from hanging up the program + SAL_WARN("sc", "ridiculously long line, truncating, len=" << aLine.getLength()); + aLine = aLine.copy(0,255); + } + + aTextMatrix = basegfx::utils::createScaleTranslateB2DHomMatrix( + aFontSize.getX(), aFontSize.getY(), + aLineStart.X(), aLineStart.Y() + nTextOffsetY); + + // Create the text primitive for each line of text + rtl::Reference pMessage = + new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + aTextMatrix, aLine, 0, aLine.getLength(), + std::vector(), {}, aFontAttr, css::lang::Locale(), + rColor.getBColor()); + + rRange.expand(pMessage->getB2DRange(aDummy)); + + aSeq.push_back(pMessage); + + aLineStart.AdjustY(nLineHeight ); + nLineCount++; + if (nLineCount > 50) + { + // prevent silliness higher up from hanging up the program + SAL_WARN("sc", "ridiculously long message, bailing out"); + break; + } + } + + rRange.expand(basegfx::B2DTuple(rRange.getMaxX() + aHintMargin.Width(), + rRange.getMaxY() + aHintMargin.Height())); + + basegfx::B2DPolygon aPoly(basegfx::utils::createPolygonFromRect(rRange)); + + const drawinglayer::primitive2d::Primitive2DReference aBg( + new drawinglayer::primitive2d::PolyPolygonColorPrimitive2D(basegfx::B2DPolyPolygon(aPoly), getBaseColor().getBColor())); + + basegfx::BColor aBorderColor(0.5, 0.5, 0.5); + const drawinglayer::primitive2d::Primitive2DReference aBorder( + new drawinglayer::primitive2d::PolygonHairlinePrimitive2D( + std::move(aPoly), aBorderColor)); + + aSeq.insert(aSeq.begin(), aBorder); + aSeq.insert(aSeq.begin(), aBg); + + return aSeq; +} + +drawinglayer::primitive2d::Primitive2DContainer ScOverlayHint::createOverlayObjectPrimitive2DSequence() +{ + basegfx::B2DRange aRange; + return createOverlaySequence(m_nLeft, m_nTop, m_aMapMode, aRange); +} + +Size ScOverlayHint::GetSizePixel() const +{ + basegfx::B2DRange aRange; + createOverlaySequence(0, 0, MapMode(MapUnit::MapPixel), aRange); + return Size(aRange.getWidth(), aRange.getHeight()); +} + +void ScOverlayHint::SetPos(const Point& rPos, const MapMode& rMode) +{ + m_nLeft = rPos.X(); + m_nTop = rPos.Y(); + m_aMapMode = rMode; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/imapwrap.cxx b/sc/source/ui/view/imapwrap.cxx new file mode 100644 index 0000000000..72d1373393 --- /dev/null +++ b/sc/source/ui/view/imapwrap.cxx @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include "imapwrap.hxx" + +sal_uInt16 ScIMapChildWindowId() +{ + return SvxIMapDlgChildWindow::GetChildWindowId(); +} + +void ScIMapDlgSet( const Graphic& rGraphic, const ImageMap* pImageMap, + const TargetList* pTargetList, void* pEditingObj ) +{ + SvxIMapDlgChildWindow::UpdateIMapDlg( rGraphic, pImageMap, pTargetList, pEditingObj ); +} + +const void* ScIMapDlgGetObj( const SvxIMapDlg* pDlg ) +{ + if ( pDlg ) + return pDlg->GetEditingObject(); + else + return nullptr; +} + +const ImageMap& ScIMapDlgGetMap( const SvxIMapDlg* pDlg ) +{ + return pDlg->GetImageMap(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/imapwrap.hxx b/sc/source/ui/view/imapwrap.hxx new file mode 100644 index 0000000000..9b46b1bc83 --- /dev/null +++ b/sc/source/ui/view/imapwrap.hxx @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#pragma once + + +#include +#include + +class Graphic; +class ImageMap; +class SvxIMapDlg; + +sal_uInt16 ScIMapChildWindowId(); + +ImageMap const & ScIMapDlgGetMap(const SvxIMapDlg * pDlg); + +void const * ScIMapDlgGetObj(const SvxIMapDlg * pDlg); + +void ScIMapDlgSet( + Graphic const & rGraphic, ImageMap const * pImageMap, + TargetList const * pTargetList, void * pEditingObj); + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/invmerge.cxx b/sc/source/ui/view/invmerge.cxx new file mode 100644 index 0000000000..a082221977 --- /dev/null +++ b/sc/source/ui/view/invmerge.cxx @@ -0,0 +1,162 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include + +ScInvertMerger::ScInvertMerger( ::std::vector< tools::Rectangle >* pRectangles ) : + pRects( pRectangles ) +{ + // collect rectangles instead of inverting +} + +ScInvertMerger::~ScInvertMerger() +{ + Flush(); +} + +void ScInvertMerger::Flush() +{ + FlushLine(); + FlushTotal(); + + OSL_ENSURE( aLineRect.IsEmpty() && aTotalRect.IsEmpty(), "Flush: not empty" ); + + if ( !pRects ) + return; + + // also join vertically if there are non-adjacent columns involved + + size_t nComparePos = 0; + while ( nComparePos < pRects->size() ) + { + tools::Rectangle aCompRect = (*pRects)[nComparePos]; + sal_Int32 nBottom = aCompRect.Bottom(); + size_t nOtherPos = nComparePos + 1; + + while ( nOtherPos < pRects->size() ) + { + tools::Rectangle aOtherRect = (*pRects)[nOtherPos]; + if ( aOtherRect.Top() > nBottom + 1 ) + { + // rectangles are sorted, so we can stop searching + break; + } + if ( aOtherRect.Top() == nBottom + 1 && + aOtherRect.Left() == aCompRect.Left() && + aOtherRect.Right() == aCompRect.Right() ) + { + // extend first rectangle + nBottom = aOtherRect.Bottom(); + aCompRect.SetBottom( nBottom ); + (*pRects)[nComparePos].SetBottom( nBottom ); + + // remove second rectangle + pRects->erase( pRects->begin() + nOtherPos ); + + // continue at unmodified nOtherPos + } + else + ++nOtherPos; + } + + ++nComparePos; + } +} + +void ScInvertMerger::FlushTotal() +{ + if( aTotalRect.IsEmpty() ) + return; // nothing to do + + if ( pRects ) + pRects->push_back( aTotalRect ); + + aTotalRect.SetEmpty(); +} + +void ScInvertMerger::FlushLine() +{ + if( aLineRect.IsEmpty() ) + return; // nothing to do + + if ( aTotalRect.IsEmpty() ) + { + aTotalRect = aLineRect; // start new total rect + } + else + { + if ( aLineRect.Left() == aTotalRect.Left() && + aLineRect.Right() == aTotalRect.Right() && + aLineRect.Top() == aTotalRect.Bottom() + 1 ) + { + // extend total rect + aTotalRect.SetBottom( aLineRect.Bottom() ); + } + else + { + FlushTotal(); // draw old total rect + aTotalRect = aLineRect; // and start new one + } + } + + aLineRect.SetEmpty(); +} + +void ScInvertMerger::AddRect( const tools::Rectangle& rRect ) +{ + tools::Rectangle aJustified = rRect; + if ( rRect.Left() > rRect.Right() ) // switch for RTL layout + { + aJustified.SetLeft( rRect.Right() ); + aJustified.SetRight( rRect.Left() ); + } + + if ( aLineRect.IsEmpty() ) + { + aLineRect = aJustified; // start new line rect + } + else + { + bool bDone = false; + if ( aJustified.Top() == aLineRect.Top() && + aJustified.Bottom() == aLineRect.Bottom() ) + { + // try to extend line rect + if ( aJustified.Left() == aLineRect.Right() + 1 ) + { + aLineRect.SetRight( aJustified.Right() ); + bDone = true; + } + else if ( aJustified.Right() + 1 == aLineRect.Left() ) // for RTL layout + { + aLineRect.SetLeft( aJustified.Left() ); + bDone = true; + } + } + if (!bDone) + { + FlushLine(); // use old line rect for total rect + aLineRect = aJustified; // and start new one + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/notemark.cxx b/sc/source/ui/view/notemark.cxx new file mode 100644 index 0000000000..2824a453e7 --- /dev/null +++ b/sc/source/ui/view/notemark.cxx @@ -0,0 +1,203 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#define SC_NOTEMARK_TIME 800 +#define SC_NOTEMARK_SHORT 70 + +ScNoteMarker::ScNoteMarker( vcl::Window* pWin, vcl::Window* pRight, vcl::Window* pBottom, vcl::Window* pDiagonal, + ScDocument* pD, const ScAddress& aPos, OUString aUser, + const MapMode& rMap, bool bLeftEdge, bool bForce, bool bKeyboard) : + m_pWindow( pWin ), + m_pRightWin( pRight ), + m_pBottomWin( pBottom ), + m_pDiagWin( pDiagonal ), + m_pDoc( pD ), + m_aDocPos( aPos ), + m_aUserText(std::move( aUser )), + m_aTimer("ScNoteMarker m_aTimer"), + m_aMapMode( rMap ), + m_bLeft( bLeftEdge ), + m_bByKeyboard( bKeyboard ), + m_bVisible( false ) +{ + Size aSizePixel = m_pWindow->GetOutputSizePixel(); + if( m_pRightWin ) + aSizePixel.AdjustWidth(m_pRightWin->GetOutputSizePixel().Width() ); + if( m_pBottomWin ) + aSizePixel.AdjustHeight(m_pBottomWin->GetOutputSizePixel().Height() ); + tools::Rectangle aVisPixel( Point( 0, 0 ), aSizePixel ); + m_aVisRect = m_pWindow->PixelToLogic( aVisPixel, m_aMapMode ); + + m_aTimer.SetInvokeHandler( LINK( this, ScNoteMarker, TimeHdl ) ); + m_aTimer.SetTimeout( bForce ? SC_NOTEMARK_SHORT : SC_NOTEMARK_TIME ); + m_aTimer.Start(); +} + +ScNoteMarker::~ScNoteMarker() +{ + m_xObject.clear(); + + InvalidateWin(); + + m_pModel.reset(); +} + +IMPL_LINK_NOARG(ScNoteMarker, TimeHdl, Timer *, void) +{ + if (!m_bVisible) + { + m_pModel.reset( new SdrModel() ); + m_pModel->SetScaleUnit(MapUnit::Map100thMM); + SfxItemPool& rPool = m_pModel->GetItemPool(); + rPool.SetDefaultMetric(MapUnit::Map100thMM); + rPool.FreezeIdRanges(); + + OutputDevice* pPrinter = m_pDoc->GetRefDevice(); + if (pPrinter) + { + // On the outliner of the draw model also the printer is set as RefDevice, + // and it should look uniform. + Outliner& rOutliner = m_pModel->GetDrawOutliner(); + rOutliner.SetRefDevice(pPrinter); + } + + if( rtl::Reference pPage = m_pModel->AllocPage( false ) ) + + { + m_xObject = ScNoteUtil::CreateTempCaption( *m_pDoc, m_aDocPos, *pPage, m_aUserText, m_aVisRect, m_bLeft ); + if( m_xObject ) + { + // Here, SyncForGrid and GetGridOffset was used with the comment: + // // Need to include grid offset: GetCurrentBoundRect is removing it + // // but we need to know actual rect position + // This is no longer true - SdrObject::RecalcBoundRect() uses the + // GetViewContact().getViewIndependentPrimitive2DContainer()) call + // that now by default adds the eventually needed GridOffset. Thus + // I have removed that adaptation stuff. + m_aRect = m_xObject->GetCurrentBoundRect(); + } + + // Insert page so that the model recognise it and also deleted + m_pModel->InsertPage( pPage.get() ); + + } + m_bVisible = true; + } + + Draw(); +} + +static void lcl_DrawWin( const SdrObject* pObject, vcl::RenderContext* pWindow, const MapMode& rMap ) +{ + MapMode aOld = pWindow->GetMapMode(); + pWindow->SetMapMode( rMap ); + + DrawModeFlags nOldDrawMode = pWindow->GetDrawMode(); + if ( Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + pWindow->SetDrawMode( nOldDrawMode | DrawModeFlags::SettingsLine | DrawModeFlags::SettingsFill | + DrawModeFlags::SettingsText | DrawModeFlags::SettingsGradient ); + } + + pObject->SingleObjectPainter( *pWindow ); // #110094#-17 + + pWindow->SetDrawMode( nOldDrawMode ); + pWindow->SetMapMode( aOld ); +} + +static MapMode lcl_MoveMapMode( const MapMode& rMap, const Size& rMove ) +{ + MapMode aNew = rMap; + Point aOrigin = aNew.GetOrigin(); + aOrigin.AdjustX( -(rMove.Width()) ); + aOrigin.AdjustY( -rMove.Height() ); + aNew.SetOrigin(aOrigin); + return aNew; +} + +void ScNoteMarker::Draw() +{ + if ( !(m_xObject && m_bVisible) ) + return; + + lcl_DrawWin( m_xObject.get(), m_pWindow->GetOutDev(), m_aMapMode ); + + if ( m_pRightWin || m_pBottomWin ) + { + Size aWinSize = m_pWindow->PixelToLogic( m_pWindow->GetOutputSizePixel(), m_aMapMode ); + if ( m_pRightWin ) + lcl_DrawWin( m_xObject.get(), m_pRightWin->GetOutDev(), + lcl_MoveMapMode( m_aMapMode, Size( aWinSize.Width(), 0 ) ) ); + if ( m_pBottomWin ) + lcl_DrawWin( m_xObject.get(), m_pBottomWin->GetOutDev(), + lcl_MoveMapMode( m_aMapMode, Size( 0, aWinSize.Height() ) ) ); + if ( m_pDiagWin ) + lcl_DrawWin( m_xObject.get(), m_pDiagWin->GetOutDev(), lcl_MoveMapMode( m_aMapMode, aWinSize ) ); + } +} + +void ScNoteMarker::InvalidateWin() +{ + if (!m_bVisible) + return; + + // Extend the invalidated rectangle by 1 pixel in each direction in case AA would slightly + // paint outside the nominal area. + tools::Rectangle aRect(m_aRect); + const Size aPixelSize = m_pWindow->PixelToLogic(Size(1, 1)); + aRect.AdjustLeft(-aPixelSize.getWidth()); + aRect.AdjustTop(-aPixelSize.getHeight()); + aRect.AdjustRight(aPixelSize.getWidth()); + aRect.AdjustBottom(aPixelSize.getHeight()); + + m_pWindow->Invalidate( OutputDevice::LogicToLogic(aRect, m_aMapMode, m_pWindow->GetMapMode()) ); + + if ( !(m_pRightWin || m_pBottomWin) ) + return; + + Size aWinSize = m_pWindow->PixelToLogic( m_pWindow->GetOutputSizePixel(), m_aMapMode ); + if ( m_pRightWin ) + m_pRightWin->Invalidate( OutputDevice::LogicToLogic(aRect, + lcl_MoveMapMode( m_aMapMode, Size( aWinSize.Width(), 0 ) ), + m_pRightWin->GetMapMode()) ); + if ( m_pBottomWin ) + m_pBottomWin->Invalidate( OutputDevice::LogicToLogic(aRect, + lcl_MoveMapMode( m_aMapMode, Size( 0, aWinSize.Height() ) ), + m_pBottomWin->GetMapMode()) ); + if ( m_pDiagWin ) + m_pDiagWin->Invalidate( OutputDevice::LogicToLogic(aRect, + lcl_MoveMapMode( m_aMapMode, aWinSize ), + m_pDiagWin->GetMapMode()) ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/olinewin.cxx b/sc/source/ui/view/olinewin.cxx new file mode 100644 index 0000000000..1cc55a9f3f --- /dev/null +++ b/sc/source/ui/view/olinewin.cxx @@ -0,0 +1,1040 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +const tools::Long SC_OL_BITMAPSIZE = 12; +const tools::Long SC_OL_POSOFFSET = 2; + +const size_t SC_OL_NOLEVEL = static_cast< size_t >( -1 ); +const size_t SC_OL_HEADERENTRY = static_cast< size_t >( -1 ); + +ScOutlineWindow::ScOutlineWindow( vcl::Window* pParent, ScOutlineMode eMode, ScViewData* pViewData, ScSplitPos eWhich ) : + Window( pParent ), + mrViewData( *pViewData ), + meWhich( eWhich ), + mbHoriz( eMode == SC_OUTLINE_HOR ), + mbMirrorEntries( false ), // updated in SetHeaderSize + mbMirrorLevels( false ), // updated in SetHeaderSize + maLineColor( COL_BLACK ), + mnHeaderSize( 0 ), + mnHeaderPos( 0 ), + mnMainFirstPos( 0 ), + mnMainLastPos( 0 ), + mbMTActive( false ), + mbMTPressed( false ), + mnFocusLevel( 0 ), + mnFocusEntry( SC_OL_HEADERENTRY ), + mbDontDrawFocus( false ) +{ + EnableRTL( false ); // mirroring is done manually + + InitSettings(); + maFocusRect.SetEmpty(); + SetHeaderSize( 0 ); + + // insert the window into task pane list for "F6 cycling" + if( SystemWindow* pSysWin = GetSystemWindow() ) + if( TaskPaneList* pTaskPaneList = pSysWin->GetTaskPaneList() ) + pTaskPaneList->AddWindow( this ); +} + +ScOutlineWindow::~ScOutlineWindow() +{ + disposeOnce(); +} + +void ScOutlineWindow::dispose() +{ + // remove the window from task pane list + if( SystemWindow* pSysWin = GetSystemWindow() ) + if( TaskPaneList* pTaskPaneList = pSysWin->GetTaskPaneList() ) + pTaskPaneList->RemoveWindow( this ); + vcl::Window::dispose(); +} + +void ScOutlineWindow::SetHeaderSize( tools::Long nNewSize ) +{ + bool bLayoutRTL = GetDoc().IsLayoutRTL( GetTab() ); + mbMirrorEntries = bLayoutRTL && mbHoriz; + mbMirrorLevels = bLayoutRTL && !mbHoriz; + + bool bNew = (nNewSize != mnHeaderSize); + mnHeaderSize = nNewSize; + mnHeaderPos = mbMirrorEntries ? (GetOutputSizeEntry() - mnHeaderSize) : 0; + mnMainFirstPos = mbMirrorEntries ? 0 : mnHeaderSize; + mnMainLastPos = GetOutputSizeEntry() - (mbMirrorEntries ? mnHeaderSize : 0) - 1; + if ( bNew ) + Invalidate(); +} + +tools::Long ScOutlineWindow::GetDepthSize() const +{ + tools::Long nSize = GetLevelCount() * SC_OL_BITMAPSIZE; + if ( nSize > 0 ) + nSize += 2 * SC_OL_POSOFFSET + 1; + return nSize; +} + +void ScOutlineWindow::ScrollPixel( tools::Long nDiff ) +{ + HideFocus(); + mbDontDrawFocus = true; + + tools::Long nStart = mnMainFirstPos; + tools::Long nEnd = mnMainLastPos; + + tools::Long nInvStart, nInvEnd; + if (nDiff < 0) + { + nStart -= nDiff; + nInvStart = nEnd + nDiff; + nInvEnd = nEnd; + } + else + { + nEnd -= nDiff; + nInvStart = nStart; + nInvEnd = nStart + nDiff; + } + + ScrollRel( nDiff, nStart, nEnd ); + Invalidate( GetRectangle( 0, nInvStart, GetOutputSizeLevel() - 1, nInvEnd ) ); + + // if focus becomes invisible, move it to next visible button + ImplMoveFocusToVisible( nDiff < 0 ); + + mbDontDrawFocus = false; + ShowFocus(); +} + +void ScOutlineWindow::ScrollRel( tools::Long nEntryDiff, tools::Long nEntryStart, tools::Long nEntryEnd ) +{ + tools::Rectangle aRect( GetRectangle( 0, nEntryStart, GetOutputSizeLevel() - 1, nEntryEnd ) ); + if ( mbHoriz ) + Scroll( nEntryDiff, 0, aRect ); + else + Scroll( 0, nEntryDiff, aRect ); +} + +// internal ------------------------------------------------------------------- + +void ScOutlineWindow::InitSettings() +{ + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + SetBackground( rStyleSettings.GetFaceColor() ); + maLineColor = rStyleSettings.GetButtonTextColor(); + Invalidate(); +} + +const ScOutlineArray* ScOutlineWindow::GetOutlineArray() const +{ + const ScOutlineTable* pTable = GetDoc().GetOutlineTable( GetTab() ); + if ( !pTable ) return nullptr; + return mbHoriz ? &pTable->GetColArray() : &pTable->GetRowArray(); +} + +const ScOutlineEntry* ScOutlineWindow::GetOutlineEntry( size_t nLevel, size_t nEntry ) const +{ + const ScOutlineArray* pArray = GetOutlineArray(); + return pArray ? pArray->GetEntry( sal::static_int_cast(nLevel), sal::static_int_cast(nEntry) ) : nullptr; +} + +bool ScOutlineWindow::IsHidden( SCCOLROW nColRowIndex ) const +{ + return mbHoriz ? + GetDoc().ColHidden(static_cast(nColRowIndex), GetTab()) : + GetDoc().RowHidden(static_cast(nColRowIndex), GetTab()); +} + +bool ScOutlineWindow::IsFiltered( SCCOLROW nColRowIndex ) const +{ + // columns cannot be filtered + return !mbHoriz && GetDoc().RowFiltered( static_cast(nColRowIndex), GetTab() ); +} + +bool ScOutlineWindow::IsFirstVisible( SCCOLROW nColRowIndex ) const +{ + bool bAllHidden = true; + for ( SCCOLROW nPos = 0; (nPos < nColRowIndex) && bAllHidden; ++nPos ) + bAllHidden = IsHidden( nPos ); + return bAllHidden; +} + +void ScOutlineWindow::GetVisibleRange( SCCOLROW& rnColRowStart, SCCOLROW& rnColRowEnd ) const +{ + if ( mbHoriz ) + { + rnColRowStart = mrViewData.GetPosX( WhichH( meWhich ) ); + rnColRowEnd = rnColRowStart + mrViewData.VisibleCellsX( WhichH( meWhich ) ); + } + else + { + rnColRowStart = mrViewData.GetPosY( WhichV( meWhich ) ); + rnColRowEnd = rnColRowStart + mrViewData.VisibleCellsY( WhichV( meWhich ) ); + } + + // include collapsed columns/rows in front of visible range + while ( (rnColRowStart > 0) && IsHidden( rnColRowStart - 1 ) ) + --rnColRowStart; +} + +Point ScOutlineWindow::GetPoint( tools::Long nLevelPos, tools::Long nEntryPos ) const +{ + return mbHoriz ? Point( nEntryPos, nLevelPos ) : Point( nLevelPos, nEntryPos ); +} + +tools::Rectangle ScOutlineWindow::GetRectangle( + tools::Long nLevelStart, tools::Long nEntryStart, tools::Long nLevelEnd, tools::Long nEntryEnd ) const +{ + return tools::Rectangle( GetPoint( nLevelStart, nEntryStart ), GetPoint( nLevelEnd, nEntryEnd ) ); +} + +tools::Long ScOutlineWindow::GetOutputSizeLevel() const +{ + Size aSize( GetOutputSizePixel() ); + return mbHoriz ? aSize.Height() : aSize.Width(); +} + +tools::Long ScOutlineWindow::GetOutputSizeEntry() const +{ + Size aSize( GetOutputSizePixel() ); + return mbHoriz ? aSize.Width() : aSize.Height(); +} + +size_t ScOutlineWindow::GetLevelCount() const +{ + const ScOutlineArray* pArray = GetOutlineArray(); + size_t nLevelCount = pArray ? pArray->GetDepth() : 0; + return nLevelCount ? (nLevelCount + 1) : 0; +} + +tools::Long ScOutlineWindow::GetLevelPos( size_t nLevel ) const +{ + // #i51970# must always return the *left* edge of the area used by a level + tools::Long nPos = static_cast< tools::Long >( SC_OL_POSOFFSET + nLevel * SC_OL_BITMAPSIZE ); + return mbMirrorLevels ? (GetOutputSizeLevel() - nPos - SC_OL_BITMAPSIZE) : nPos; +} + +size_t ScOutlineWindow::GetLevelFromPos( tools::Long nLevelPos ) const +{ + if( mbMirrorLevels ) nLevelPos = GetOutputSizeLevel() - nLevelPos - 1; + tools::Long nStart = SC_OL_POSOFFSET; + if ( nLevelPos < nStart ) return SC_OL_NOLEVEL; + size_t nLevel = static_cast< size_t >( (nLevelPos - nStart) / SC_OL_BITMAPSIZE ); + return (nLevel < GetLevelCount()) ? nLevel : SC_OL_NOLEVEL; +} + +tools::Long ScOutlineWindow::GetColRowPos( SCCOLROW nColRowIndex ) const +{ + tools::Long nDocPos = mbHoriz ? + mrViewData.GetScrPos( static_cast(nColRowIndex), 0, meWhich, true ).X() : + mrViewData.GetScrPos( 0, static_cast(nColRowIndex), meWhich, true ).Y(); + return mnMainFirstPos + nDocPos; +} + +tools::Long ScOutlineWindow::GetHeaderEntryPos() const +{ + return mnHeaderPos + (mnHeaderSize - SC_OL_BITMAPSIZE) / 2; +} + +bool ScOutlineWindow::GetEntryPos( + size_t nLevel, size_t nEntry, + tools::Long& rnStartPos, tools::Long& rnEndPos, tools::Long& rnImagePos ) const +{ + const ScOutlineEntry* pEntry = GetOutlineEntry( nLevel, nEntry ); + if ( !pEntry || !pEntry->IsVisible() ) + return false; + + SCCOLROW nStart = pEntry->GetStart(); + SCCOLROW nEnd = pEntry->GetEnd(); + + tools::Long nEntriesSign = mbMirrorEntries ? -1 : 1; + + // --- common calculation --- + + rnStartPos = GetColRowPos( nStart ); + rnEndPos = GetColRowPos( nEnd + 1 ); + + bool bHidden = IsHidden( nStart ); + rnImagePos = bHidden ? + (rnStartPos - ( SC_OL_BITMAPSIZE / 2 ) * nEntriesSign) : + rnStartPos + nEntriesSign; + tools::Long nCenter = (rnStartPos + rnEndPos - SC_OL_BITMAPSIZE * nEntriesSign + + ( mbMirrorEntries ? 1 : 0 )) / 2; + rnImagePos = mbMirrorEntries ? std::max( rnImagePos, nCenter ) : std::min( rnImagePos, nCenter ); + + // --- refinements --- + + // do not cut leftmost/topmost image + if ( bHidden && IsFirstVisible( nStart ) ) + rnImagePos = rnStartPos; + + // do not cover previous collapsed image + bool bDoNoCover = !bHidden && nEntry; + const ScOutlineEntry* pPrevEntry = bDoNoCover ? GetOutlineEntry(nLevel, nEntry - 1) : nullptr; + if (pPrevEntry) + { + SCCOLROW nPrevEnd = pPrevEntry->GetEnd(); + if ( (nPrevEnd + 1 == nStart) && IsHidden( nPrevEnd ) ) + { + if ( IsFirstVisible( pPrevEntry->GetStart() ) ) + rnStartPos += SC_OL_BITMAPSIZE * nEntriesSign; + else + rnStartPos += ( SC_OL_BITMAPSIZE / 2 ) * nEntriesSign; + rnImagePos = rnStartPos; + } + } + + // restrict rnStartPos...rnEndPos to valid area + rnStartPos = std::max( rnStartPos, mnMainFirstPos ); + rnEndPos = std::max( rnEndPos, mnMainFirstPos ); + + if ( mbMirrorEntries ) + rnImagePos -= SC_OL_BITMAPSIZE - 1; // start pos aligns with right edge of bitmap + + // --- all rows filtered? --- + + bool bVisible = true; + if ( !mbHoriz ) + { + bVisible = false; + for ( SCCOLROW nRow = nStart; (nRow <= nEnd) && !bVisible; ++nRow ) + bVisible = !IsFiltered( nRow ); + } + return bVisible; +} + +bool ScOutlineWindow::GetImagePos( size_t nLevel, size_t nEntry, Point& rPos ) const +{ + bool bRet = nLevel < GetLevelCount(); + if ( bRet ) + { + tools::Long nLevelPos = GetLevelPos( nLevel ); + if ( nEntry == SC_OL_HEADERENTRY ) + rPos = GetPoint( nLevelPos, GetHeaderEntryPos() ); + else + { + tools::Long nStartPos, nEndPos, nImagePos; + bRet = GetEntryPos( nLevel, nEntry, nStartPos, nEndPos, nImagePos ); + rPos = GetPoint( nLevelPos, nImagePos ); + } + } + return bRet; +} + +bool ScOutlineWindow::IsButtonVisible( size_t nLevel, size_t nEntry ) const +{ + bool bRet = false; + if ( nEntry == SC_OL_HEADERENTRY ) + bRet = (mnHeaderSize > 0) && (nLevel < GetLevelCount()); + else + { + const ScOutlineEntry* pEntry = GetOutlineEntry( nLevel, nEntry ); + if ( pEntry && pEntry->IsVisible() ) + { + SCCOLROW nStart, nEnd; + GetVisibleRange( nStart, nEnd ); + bRet = (nStart <= pEntry->GetStart()) && (pEntry->GetStart() <= nEnd); + } + } + return bRet; +} + +bool ScOutlineWindow::ItemHit( const Point& rPos, size_t& rnLevel, size_t& rnEntry, bool& rbButton ) const +{ + const ScOutlineArray* pArray = GetOutlineArray(); + if ( !pArray ) return false; + + SCCOLROW nStartIndex, nEndIndex; + GetVisibleRange( nStartIndex, nEndIndex ); + + size_t nLevel = GetLevelFromPos( mbHoriz ? rPos.Y() : rPos.X() ); + if ( nLevel == SC_OL_NOLEVEL ) + return false; + + tools::Long nEntryMousePos = mbHoriz ? rPos.X() : rPos.Y(); + + // --- level buttons --- + + if ( mnHeaderSize > 0 ) + { + tools::Long nImagePos = GetHeaderEntryPos(); + if ( (nImagePos <= nEntryMousePos) && (nEntryMousePos < nImagePos + SC_OL_BITMAPSIZE) ) + { + rnLevel = nLevel; + rnEntry = SC_OL_HEADERENTRY; + rbButton = true; + return true; + } + } + + // --- expand/collapse buttons and expanded lines --- + + // search outline entries backwards + size_t nEntry = pArray->GetCount( sal::static_int_cast(nLevel) ); + while ( nEntry ) + { + --nEntry; + + const ScOutlineEntry* pEntry = pArray->GetEntry( sal::static_int_cast(nLevel), + sal::static_int_cast(nEntry) ); + SCCOLROW nStart = pEntry->GetStart(); + SCCOLROW nEnd = pEntry->GetEnd(); + + if ( (nEnd >= nStartIndex) && (nStart <= nEndIndex) ) + { + tools::Long nStartPos, nEndPos, nImagePos; + if ( GetEntryPos( nLevel, nEntry, nStartPos, nEndPos, nImagePos ) ) + { + rnLevel = nLevel; + rnEntry = nEntry; + + // button? + if ( (nStart >= nStartIndex) && (nImagePos <= nEntryMousePos) && (nEntryMousePos < nImagePos + SC_OL_BITMAPSIZE) ) + { + rbButton = true; + return true; + } + + // line? + if ( mbMirrorEntries ) + ::std::swap( nStartPos, nEndPos ); // in RTL mode, nStartPos is the larger value + if ( (nStartPos <= nEntryMousePos) && (nEntryMousePos <= nEndPos) ) + { + rbButton = false; + return true; + } + } + } + } + + return false; +} + +bool ScOutlineWindow::ButtonHit( const Point& rPos, size_t& rnLevel, size_t& rnEntry ) const +{ + bool bButton; + bool bRet = ItemHit( rPos, rnLevel, rnEntry, bButton ); + return bRet && bButton; +} + +bool ScOutlineWindow::LineHit( const Point& rPos, size_t& rnLevel, size_t& rnEntry ) const +{ + bool bButton; + bool bRet = ItemHit( rPos, rnLevel, rnEntry, bButton ); + return bRet && !bButton; +} + +void ScOutlineWindow::DoFunction( size_t nLevel, size_t nEntry ) const +{ + ScDBFunc& rFunc = *mrViewData.GetView(); + if ( nEntry == SC_OL_HEADERENTRY ) + rFunc.SelectLevel( mbHoriz, sal::static_int_cast(nLevel) ); + else + { + const ScOutlineEntry* pEntry = GetOutlineEntry( nLevel, nEntry ); + if ( pEntry ) + { + if ( pEntry->IsHidden() ) + rFunc.ShowOutline( mbHoriz, sal::static_int_cast(nLevel), sal::static_int_cast(nEntry) ); + else + rFunc.HideOutline( mbHoriz, sal::static_int_cast(nLevel), sal::static_int_cast(nEntry) ); + } + } +} + +void ScOutlineWindow::DoExpand( size_t nLevel, size_t nEntry ) const +{ + const ScOutlineEntry* pEntry = GetOutlineEntry( nLevel, nEntry ); + if ( pEntry && pEntry->IsHidden() ) + DoFunction( nLevel, nEntry ); +} + +void ScOutlineWindow::DoCollapse( size_t nLevel, size_t nEntry ) const +{ + const ScOutlineEntry* pEntry = GetOutlineEntry( nLevel, nEntry ); + if ( pEntry && !pEntry->IsHidden() ) + DoFunction( nLevel, nEntry ); +} + +void ScOutlineWindow::Resize() +{ + Window::Resize(); + SetHeaderSize( mnHeaderSize ); // recalculates header/group positions + if ( !IsFocusButtonVisible() ) + { + HideFocus(); + ShowFocus(); // calculates valid position + } +} + +void ScOutlineWindow::DataChanged( const DataChangedEvent& rDCEvt ) +{ + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + { + InitSettings(); + Invalidate(); + } + Window::DataChanged( rDCEvt ); +} + +// drawing -------------------------------------------------------------------- + +void ScOutlineWindow::SetEntryAreaClipRegion() +{ + GetOutDev()->SetClipRegion( vcl::Region(tools::Rectangle( + GetPoint( 0, mnMainFirstPos ), + GetPoint( GetOutputSizeLevel() - 1, mnMainLastPos )))); +} + +void ScOutlineWindow::DrawLineRel( + tools::Long nLevelStart, tools::Long nEntryStart, tools::Long nLevelEnd, tools::Long nEntryEnd ) +{ + GetOutDev()->DrawLine( GetPoint( nLevelStart, nEntryStart ), GetPoint( nLevelEnd, nEntryEnd ) ); +} + +void ScOutlineWindow::DrawRectRel( + tools::Long nLevelStart, tools::Long nEntryStart, tools::Long nLevelEnd, tools::Long nEntryEnd ) +{ + GetOutDev()->DrawRect( GetRectangle( nLevelStart, nEntryStart, nLevelEnd, nEntryEnd ) ); +} + +namespace +{ + Image GetImage(const OUString& rId) + { + return Image(StockImage::Yes, rId); + } +} + +void ScOutlineWindow::DrawImageRel(tools::Long nLevelPos, tools::Long nEntryPos, const OUString& rId) +{ + const Image& rImage = GetImage(rId); + GetOutDev()->SetLineColor(); + GetOutDev()->SetFillColor( GetBackground().GetColor() ); + Point aPos( GetPoint( nLevelPos, nEntryPos ) ); + GetOutDev()->DrawRect( tools::Rectangle( aPos, rImage.GetSizePixel() ) ); + GetOutDev()->DrawImage( aPos, rImage ); +} + +void ScOutlineWindow::DrawBorderRel( size_t nLevel, size_t nEntry, bool bPressed ) +{ + Point aPos; + if ( GetImagePos( nLevel, nEntry, aPos ) ) + { + OUString sId = bPressed ? RID_BMP_PRESSED : RID_BMP_NOTPRESSED; + bool bClip = (nEntry != SC_OL_HEADERENTRY); + if ( bClip ) + SetEntryAreaClipRegion(); + GetOutDev()->DrawImage(aPos, GetImage(sId)); + if ( bClip ) + GetOutDev()->SetClipRegion(); + } + mbMTPressed = bPressed; +} + +void ScOutlineWindow::ShowFocus() +{ + if ( !HasFocus() ) + return; + + // first move to a visible position + ImplMoveFocusToVisible( true ); + + if ( !IsFocusButtonVisible() ) + return; + + Point aPos; + if ( GetImagePos( mnFocusLevel, mnFocusEntry, aPos ) ) + { + aPos += Point( 1, 1 ); + maFocusRect = tools::Rectangle( aPos, Size( SC_OL_BITMAPSIZE - 2, SC_OL_BITMAPSIZE - 2 ) ); + bool bClip = (mnFocusEntry != SC_OL_HEADERENTRY); + if ( bClip ) + SetEntryAreaClipRegion(); + InvertTracking( maFocusRect, ShowTrackFlags::Small | ShowTrackFlags::TrackWindow ); + if ( bClip ) + GetOutDev()->SetClipRegion(); + } +} + +void ScOutlineWindow::HideFocus() +{ + if ( !maFocusRect.IsEmpty() ) + { + bool bClip = (mnFocusEntry != SC_OL_HEADERENTRY); + if ( bClip ) + SetEntryAreaClipRegion(); + InvertTracking( maFocusRect, ShowTrackFlags::Small | ShowTrackFlags::TrackWindow ); + if ( bClip ) + GetOutDev()->SetClipRegion(); + maFocusRect.SetEmpty(); + } +} + +constexpr OUString aLevelBmps[]= +{ + RID_BMP_LEVEL1, + RID_BMP_LEVEL2, + RID_BMP_LEVEL3, + RID_BMP_LEVEL4, + RID_BMP_LEVEL5, + RID_BMP_LEVEL6, + RID_BMP_LEVEL7, + RID_BMP_LEVEL8 +}; + +void ScOutlineWindow::Paint( vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& /* rRect */ ) +{ + tools::Long nEntriesSign = mbMirrorEntries ? -1 : 1; + tools::Long nLevelsSign = mbMirrorLevels ? -1 : 1; + + Size aSize = GetOutputSizePixel(); + tools::Long nLevelEnd = (mbHoriz ? aSize.Height() : aSize.Width()) - 1; + tools::Long nEntryEnd = (mbHoriz ? aSize.Width() : aSize.Height()) - 1; + + GetOutDev()->SetLineColor( maLineColor ); + tools::Long nBorderPos = mbMirrorLevels ? 0 : nLevelEnd; + DrawLineRel( nBorderPos, 0, nBorderPos, nEntryEnd ); + + const ScOutlineArray* pArray = GetOutlineArray(); + if ( !pArray ) return; + + size_t nLevelCount = GetLevelCount(); + + // --- draw header images --- + + if ( mnHeaderSize > 0 ) + { + tools::Long nEntryPos = GetHeaderEntryPos(); + for ( size_t nLevel = 0; nLevel < nLevelCount; ++nLevel ) + DrawImageRel(GetLevelPos(nLevel), nEntryPos, aLevelBmps[nLevel]); + + GetOutDev()->SetLineColor( maLineColor ); + tools::Long nLinePos = mnHeaderPos + (mbMirrorEntries ? 0 : (mnHeaderSize - 1)); + DrawLineRel( 0, nLinePos, nLevelEnd, nLinePos ); + } + + // --- draw lines & collapse/expand images --- + + SetEntryAreaClipRegion(); + + SCCOLROW nStartIndex, nEndIndex; + GetVisibleRange( nStartIndex, nEndIndex ); + + for ( size_t nLevel = 0; nLevel + 1 < nLevelCount; ++nLevel ) + { + tools::Long nLevelPos = GetLevelPos( nLevel ); + tools::Long nEntryPos1 = 0, nEntryPos2 = 0, nImagePos = 0; + + size_t nEntryCount = pArray->GetCount( sal::static_int_cast(nLevel) ); + size_t nEntry; + + // first draw all lines in the current level + GetOutDev()->SetLineColor(); + GetOutDev()->SetFillColor( maLineColor ); + for ( nEntry = 0; nEntry < nEntryCount; ++nEntry ) + { + const ScOutlineEntry* pEntry = pArray->GetEntry( sal::static_int_cast(nLevel), + sal::static_int_cast(nEntry) ); + SCCOLROW nStart = pEntry->GetStart(); + SCCOLROW nEnd = pEntry->GetEnd(); + + // visible range? + bool bDraw = (nEnd >= nStartIndex) && (nStart <= nEndIndex); + // find output coordinates + if ( bDraw ) + bDraw = GetEntryPos( nLevel, nEntry, nEntryPos1, nEntryPos2, nImagePos ); + // draw, if not collapsed + if ( bDraw && !pEntry->IsHidden() ) + { + if ( nStart >= nStartIndex ) + nEntryPos1 += nEntriesSign; + nEntryPos2 -= 2 * nEntriesSign; + tools::Long nLinePos = nLevelPos; + if ( mbMirrorLevels ) + nLinePos += SC_OL_BITMAPSIZE - 1; // align with right edge of bitmap + DrawRectRel( nLinePos, nEntryPos1, nLinePos + nLevelsSign, nEntryPos2 ); + + if ( nEnd <= nEndIndex ) + DrawRectRel( nLinePos, nEntryPos2 - nEntriesSign, + nLinePos + ( SC_OL_BITMAPSIZE / 3 ) * nLevelsSign, nEntryPos2 ); + } + } + + // draw all images in the level from last to first + nEntry = nEntryCount; + while ( nEntry ) + { + --nEntry; + + const ScOutlineEntry* pEntry = pArray->GetEntry( sal::static_int_cast(nLevel), + sal::static_int_cast(nEntry) ); + SCCOLROW nStart = pEntry->GetStart(); + + // visible range? + bool bDraw = (nStartIndex <= nStart) && (nStart <= nEndIndex + 1); + // find output coordinates + if ( bDraw ) + bDraw = GetEntryPos( nLevel, nEntry, nEntryPos1, nEntryPos2, nImagePos ); + // draw, if not hidden by higher levels + if ( bDraw ) + { + OUString sImageId = pEntry->IsHidden() ? RID_BMP_PLUS : RID_BMP_MINUS; + DrawImageRel(nLevelPos, nImagePos, sImageId); + } + } + } + + GetOutDev()->SetClipRegion(); + + if ( !mbDontDrawFocus ) + ShowFocus(); +} + +// focus ---------------------------------------------------------------------- + +/** Increments or decrements a value and wraps at the specified limits. + @return true = value wrapped. */ +static bool lcl_RotateValue( size_t& rnValue, size_t nMin, size_t nMax, bool bForward ) +{ + OSL_ENSURE( nMin <= nMax, "lcl_RotateValue - invalid range" ); + OSL_ENSURE( nMax < static_cast< size_t >( -1 ), "lcl_RotateValue - range overflow" ); + bool bWrap = false; + if ( bForward ) + { + if ( rnValue < nMax ) + ++rnValue; + else + { + rnValue = nMin; + bWrap = true; + } + } + else + { + if ( rnValue > nMin ) + --rnValue; + else + { + rnValue = nMax; + bWrap = true; + } + } + return bWrap; +} + +bool ScOutlineWindow::IsFocusButtonVisible() const +{ + return IsButtonVisible( mnFocusLevel, mnFocusEntry ); +} + +bool ScOutlineWindow::ImplMoveFocusByEntry( bool bForward, bool bFindVisible ) +{ + const ScOutlineArray* pArray = GetOutlineArray(); + if ( !pArray ) + return false; + + bool bWrapped = false; + size_t nEntryCount = pArray->GetCount( sal::static_int_cast(mnFocusLevel) ); + // #i29530# entry count may be decreased after changing active sheet + if( mnFocusEntry >= nEntryCount ) + mnFocusEntry = SC_OL_HEADERENTRY; + size_t nOldEntry = mnFocusEntry; + + do + { + if ( mnFocusEntry == SC_OL_HEADERENTRY ) + { + // move from header to first or last entry + if ( nEntryCount > 0 ) + mnFocusEntry = bForward ? 0 : (nEntryCount - 1); + /* wrapped, if forward from right header to first entry, + or if backward from left header to last entry */ + // Header and entries are now always in consistent order, + // so there's no need to check for mirroring here. + if ( !nEntryCount || !bForward ) + bWrapped = true; + } + else if ( lcl_RotateValue( mnFocusEntry, 0, nEntryCount - 1, bForward ) ) + { + // lcl_RotateValue returns true -> wrapped the entry range -> move to header + mnFocusEntry = SC_OL_HEADERENTRY; + /* wrapped, if forward from last entry to left header, + or if backward from first entry to right header */ + if ( bForward ) + bWrapped = true; + } + } + while ( bFindVisible && !IsFocusButtonVisible() && (nOldEntry != mnFocusEntry) ); + + return bWrapped; +} + +bool ScOutlineWindow::ImplMoveFocusByLevel( bool bForward ) +{ + const ScOutlineArray* pArray = GetOutlineArray(); + if ( !pArray ) + return false; + + bool bWrapped = false; + size_t nLevelCount = GetLevelCount(); + + if ( mnFocusEntry == SC_OL_HEADERENTRY ) + { + if ( nLevelCount > 0 ) + bWrapped = lcl_RotateValue( mnFocusLevel, 0, nLevelCount - 1, bForward ); + } + else + { + const ScOutlineEntry* pEntry = pArray->GetEntry( + mnFocusLevel, mnFocusEntry); + + if ( pEntry ) + { + SCCOLROW nStart = pEntry->GetStart(); + SCCOLROW nEnd = pEntry->GetEnd(); + size_t nNewLevel = mnFocusLevel; + size_t nNewEntry = 0; + + bool bFound = false; + if ( bForward && (mnFocusLevel + 2 < nLevelCount) ) + { + // next level -> find first child entry + nNewLevel = mnFocusLevel + 1; + bFound = pArray->GetEntryIndexInRange(nNewLevel, nStart, nEnd, nNewEntry); + } + else if ( !bForward && (mnFocusLevel > 0) ) + { + // previous level -> find parent entry + nNewLevel = mnFocusLevel - 1; + bFound = pArray->GetEntryIndex(nNewLevel, nStart, nNewEntry); + } + + if ( bFound && IsButtonVisible( nNewLevel, nNewEntry ) ) + { + mnFocusLevel = nNewLevel; + mnFocusEntry = nNewEntry; + } + } + } + + return bWrapped; +} + +bool ScOutlineWindow::ImplMoveFocusByTabOrder( bool bForward ) +{ + bool bRet = false; + size_t nOldLevel = mnFocusLevel; + size_t nOldEntry = mnFocusEntry; + + do + { + /* one level up, if backward from left header, + or one level down, if forward from right header */ + if ( (!bForward) && (mnFocusEntry == SC_OL_HEADERENTRY) ) + bRet |= ImplMoveFocusByLevel( bForward ); + // move to next/previous entry + bool bWrapInLevel = ImplMoveFocusByEntry( bForward, false ); + bRet |= bWrapInLevel; + /* one level up, if wrapped backward to right header, + or one level down, if wrapped forward to right header */ + if ( bForward && bWrapInLevel ) + bRet |= ImplMoveFocusByLevel( bForward ); + } + while ( !IsFocusButtonVisible() && ((nOldLevel != mnFocusLevel) || (nOldEntry != mnFocusEntry)) ); + + return bRet; +} + +void ScOutlineWindow::ImplMoveFocusToVisible( bool bForward ) +{ + // first try to find an entry in the same level + if ( !IsFocusButtonVisible() ) + ImplMoveFocusByEntry( bForward, true ); + // then try to find any other entry + if ( !IsFocusButtonVisible() ) + ImplMoveFocusByTabOrder( bForward ); +} + +void ScOutlineWindow::MoveFocusByEntry( bool bForward ) +{ + HideFocus(); + ImplMoveFocusByEntry( bForward, true ); + ShowFocus(); +} + +void ScOutlineWindow::MoveFocusByLevel( bool bForward ) +{ + HideFocus(); + ImplMoveFocusByLevel( bForward ); + ShowFocus(); +} + +void ScOutlineWindow::MoveFocusByTabOrder( bool bForward ) +{ + HideFocus(); + ImplMoveFocusByTabOrder( bForward ); + ShowFocus(); +} + +void ScOutlineWindow::GetFocus() +{ + Window::GetFocus(); + ShowFocus(); +} + +void ScOutlineWindow::LoseFocus() +{ + HideFocus(); + Window::LoseFocus(); +} + +// mouse ---------------------------------------------------------------------- + +void ScOutlineWindow::StartMouseTracking( size_t nLevel, size_t nEntry ) +{ + mbMTActive = true; + mnMTLevel = nLevel; + mnMTEntry = nEntry; + DrawBorderRel( nLevel, nEntry, true ); +} + +void ScOutlineWindow::EndMouseTracking() +{ + if ( mbMTPressed ) + DrawBorderRel( mnMTLevel, mnMTEntry, false ); + mbMTActive = false; +} + +void ScOutlineWindow::MouseMove( const MouseEvent& rMEvt ) +{ + if ( IsMouseTracking() ) + { + size_t nLevel, nEntry; + bool bHit = false; + + if ( ButtonHit( rMEvt.GetPosPixel(), nLevel, nEntry ) ) + bHit = (nLevel == mnMTLevel) && (nEntry == mnMTEntry); + + if ( bHit != mbMTPressed ) + DrawBorderRel( mnMTLevel, mnMTEntry, bHit ); + } +} + +void ScOutlineWindow::MouseButtonUp( const MouseEvent& rMEvt ) +{ + if ( IsMouseTracking() ) + { + EndMouseTracking(); + + size_t nLevel, nEntry; + if ( ButtonHit( rMEvt.GetPosPixel(), nLevel, nEntry ) ) + if ( (nLevel == mnMTLevel) && (nEntry == mnMTEntry) ) + DoFunction( nLevel, nEntry ); + } +} + +void ScOutlineWindow::MouseButtonDown( const MouseEvent& rMEvt ) +{ + size_t nLevel, nEntry; + bool bHit = ButtonHit( rMEvt.GetPosPixel(), nLevel, nEntry ); + if ( bHit ) + StartMouseTracking( nLevel, nEntry ); + else if ( rMEvt.GetClicks() == 2 ) + { + bHit = LineHit( rMEvt.GetPosPixel(), nLevel, nEntry ); + if ( bHit ) + DoFunction( nLevel, nEntry ); + } + + // if an item has been hit and window is focused, move focus to this item + if ( bHit && HasFocus() ) + { + HideFocus(); + mnFocusLevel = nLevel; + mnFocusEntry = nEntry; + ShowFocus(); + } +} + +// keyboard ------------------------------------------------------------------- + +void ScOutlineWindow::KeyInput( const KeyEvent& rKEvt ) +{ + const vcl::KeyCode& rKCode = rKEvt.GetKeyCode(); + bool bNoMod = !rKCode.GetModifier(); + bool bShift = (rKCode.GetModifier() == KEY_SHIFT); + bool bCtrl = (rKCode.GetModifier() == KEY_MOD1); + + sal_uInt16 nCode = rKCode.GetCode(); + bool bUpDownKey = (nCode == KEY_UP) || (nCode == KEY_DOWN); + bool bLeftRightKey = (nCode == KEY_LEFT) || (nCode == KEY_RIGHT); + + // TAB key + if ( (nCode == KEY_TAB) && (bNoMod || bShift) ) + // move forward without SHIFT key + MoveFocusByTabOrder( bNoMod ); // TAB uses logical order, regardless of mirroring + + // LEFT/RIGHT/UP/DOWN keys + else if ( bNoMod && (bUpDownKey || bLeftRightKey) ) + { + bool bForward = (nCode == KEY_DOWN) || (nCode == KEY_RIGHT); + if ( mbHoriz == bLeftRightKey ) + // move inside level with LEFT/RIGHT in horizontal and with UP/DOWN in vertical + MoveFocusByEntry( bForward != mbMirrorEntries ); + else + // move to next/prev level with LEFT/RIGHT in vertical and with UP/DOWN in horizontal + MoveFocusByLevel( bForward != mbMirrorLevels ); + } + + // CTRL + number + else if ( bCtrl && (nCode >= KEY_1) && (nCode <= KEY_9) ) + { + size_t nLevel = static_cast< size_t >( nCode - KEY_1 ); + if ( nLevel < GetLevelCount() ) + DoFunction( nLevel, SC_OL_HEADERENTRY ); + } + + // other key codes + else switch ( rKCode.GetFullCode() ) + { + case KEY_ADD: DoExpand( mnFocusLevel, mnFocusEntry ); break; + case KEY_SUBTRACT: DoCollapse( mnFocusLevel, mnFocusEntry ); break; + case KEY_SPACE: + case KEY_RETURN: DoFunction( mnFocusLevel, mnFocusEntry ); break; + default: Window::KeyInput( rKEvt ); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/output.cxx b/sc/source/ui/view/output.cxx new file mode 100644 index 0000000000..9d0fe14305 --- /dev/null +++ b/sc/source/ui/view/output.cxx @@ -0,0 +1,2773 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +using namespace com::sun::star; + +// Static Data + +// color for ChangeTracking "by author" as in the writer (swmodul1.cxx) + +#define SC_AUTHORCOLORCOUNT 9 + +const Color nAuthorColor[ SC_AUTHORCOLORCOUNT ] = { + COL_LIGHTRED, COL_LIGHTBLUE, COL_LIGHTMAGENTA, + COL_GREEN, COL_RED, COL_BLUE, + COL_BROWN, COL_MAGENTA, COL_CYAN }; + +// Helper class for color assignment to avoid repeated lookups for the same user + +ScActionColorChanger::ScActionColorChanger( const ScChangeTrack& rTrack ) : + rOpt( SC_MOD()->GetAppOptions() ), + rUsers( rTrack.GetUserCollection() ), + nLastUserIndex( 0 ), + nColor( COL_BLACK ) +{ +} + +void ScActionColorChanger::Update( const ScChangeAction& rAction ) +{ + Color nSetColor; + switch (rAction.GetType()) + { + case SC_CAT_INSERT_COLS: + case SC_CAT_INSERT_ROWS: + case SC_CAT_INSERT_TABS: + nSetColor = rOpt.GetTrackInsertColor(); + break; + case SC_CAT_DELETE_COLS: + case SC_CAT_DELETE_ROWS: + case SC_CAT_DELETE_TABS: + nSetColor = rOpt.GetTrackDeleteColor(); + break; + case SC_CAT_MOVE: + nSetColor = rOpt.GetTrackMoveColor(); + break; + default: + nSetColor = rOpt.GetTrackContentColor(); + break; + } + if ( nSetColor != COL_TRANSPARENT ) // color assigned + nColor = nSetColor; + else // by author + { + if (aLastUserName != rAction.GetUser()) + { + aLastUserName = rAction.GetUser(); + std::set::const_iterator it = rUsers.find(aLastUserName); + if (it == rUsers.end()) + { + // empty string is possible if a name wasn't found while saving a 5.0 file + SAL_INFO_IF( aLastUserName.isEmpty(), "sc.ui", "Author not found" ); + nLastUserIndex = 0; + } + else + { + size_t nPos = std::distance(rUsers.begin(), it); + nLastUserIndex = nPos % SC_AUTHORCOLORCOUNT; + } + } + nColor = nAuthorColor[nLastUserIndex]; + } +} + +ScOutputData::ScOutputData( OutputDevice* pNewDev, ScOutputType eNewType, + ScTableInfo& rTabInfo, ScDocument* pNewDoc, + SCTAB nNewTab, tools::Long nNewScrX, tools::Long nNewScrY, + SCCOL nNewX1, SCROW nNewY1, SCCOL nNewX2, SCROW nNewY2, + double nPixelPerTwipsX, double nPixelPerTwipsY, + const Fraction* pZoomX, const Fraction* pZoomY ) : + mpDev( pNewDev ), + mpRefDevice( pNewDev ), // default is output device + pFmtDevice( pNewDev ), // default is output device + mrTabInfo( rTabInfo ), + pRowInfo( rTabInfo.mpRowInfo.get() ), + nArrCount( rTabInfo.mnArrCount ), + mpDoc( pNewDoc ), + nTab( nNewTab ), + nScrX( nNewScrX ), + nScrY( nNewScrY ), + nX1( nNewX1 ), + nY1( nNewY1 ), + nX2( nNewX2 ), + nY2( nNewY2 ), + eType( eNewType ), + mnPPTX( nPixelPerTwipsX ), + mnPPTY( nPixelPerTwipsY ), + pViewShell( nullptr ), + pDrawView( nullptr ), + bEditMode( false ), + nEditCol( 0 ), + nEditRow( 0 ), + bMetaFile( false ), + bPagebreakMode( false ), + bSolidBackground( false ), + mbUseStyleColor( false ), + mbForceAutoColor( SvtAccessibilityOptions::GetIsAutomaticFontColor() ), + mbSyntaxMode( false ), + aGridColor( COL_BLACK ), + mbShowNullValues( true ), + mbShowFormulas( false ), + bShowSpellErrors( false ), + bMarkClipped( false ), // sal_False for printer/metafile etc. + bSnapPixel( false ), + bAnyClipped( false ), + bVertical(false), + mpTargetPaintWindow(nullptr), // #i74769# use SdrPaintWindow direct + mpSpellCheckCxt(nullptr) +{ + if (pZoomX) + aZoomX = *pZoomX; + else + aZoomX = Fraction(1,1); + if (pZoomY) + aZoomY = *pZoomY; + else + aZoomY = Fraction(1,1); + + nVisX1 = nX1; + nVisY1 = nY1; + nVisX2 = nX2; + nVisY2 = nY2; + mpDoc->StripHidden( nVisX1, nVisY1, nVisX2, nVisY2, nTab ); + + nScrW = 0; + for (SCCOL nX=nVisX1; nX<=nVisX2; nX++) + nScrW += pRowInfo[0].basicCellInfo(nX).nWidth; + + nMirrorW = nScrW; + + nScrH = 0; + for (SCSIZE nArrY=1; nArrY+1IsTabProtected( nTab ); + bLayoutRTL = mpDoc->IsLayoutRTL( nTab ); + + // always needed, so call at the end of the constructor + SetCellRotations(); + InitOutputEditEngine(); +} + +ScOutputData::~ScOutputData() +{ +} + +void ScOutputData::SetSpellCheckContext( const sc::SpellCheckContext* pCxt ) +{ + mpSpellCheckCxt = pCxt; +} + +void ScOutputData::SetContentDevice( OutputDevice* pContentDev ) +{ + // use pContentDev instead of pDev where used + + if ( mpRefDevice == mpDev ) + mpRefDevice = pContentDev; + if ( pFmtDevice == mpDev ) + pFmtDevice = pContentDev; + mpDev = pContentDev; +} + +void ScOutputData::SetMirrorWidth( tools::Long nNew ) +{ + nMirrorW = nNew; +} + +void ScOutputData::SetGridColor( const Color& rColor ) +{ + aGridColor = rColor; +} + +void ScOutputData::SetMarkClipped( bool bSet ) +{ + bMarkClipped = bSet; +} + +void ScOutputData::SetShowNullValues( bool bSet ) +{ + mbShowNullValues = bSet; +} + +void ScOutputData::SetShowFormulas( bool bSet ) +{ + mbShowFormulas = bSet; +} + +void ScOutputData::SetShowSpellErrors( bool bSet ) +{ + bShowSpellErrors = bSet; + // reset EditEngine because it depends on bShowSpellErrors + mxOutputEditEngine.reset(); +} + +void ScOutputData::SetSnapPixel() +{ + bSnapPixel = true; +} + +void ScOutputData::SetEditCell( SCCOL nCol, SCROW nRow ) +{ + nEditCol = nCol; + nEditRow = nRow; + bEditMode = true; +} + +void ScOutputData::SetMetaFileMode( bool bNewMode ) +{ + bMetaFile = bNewMode; +} + +void ScOutputData::SetSyntaxMode( bool bNewMode ) +{ + mbSyntaxMode = bNewMode; + if ( bNewMode && !mxValueColor ) + { + const svtools::ColorConfig& rColorCfg = SC_MOD()->GetColorConfig(); + mxValueColor = rColorCfg.GetColorValue( svtools::CALCVALUE ).nColor; + mxTextColor = rColorCfg.GetColorValue( svtools::CALCTEXT ).nColor; + mxFormulaColor = rColorCfg.GetColorValue( svtools::CALCFORMULA ).nColor; + } +} + +void ScOutputData::DrawGrid(vcl::RenderContext& rRenderContext, bool bGrid, bool bPage, bool bMergeCover) +{ + // bMergeCover : Draw lines in sheet bgcolor to cover lok client grid lines in merged cell areas. + // When scNoGridBackground is set in lok mode, bMergeCover is set to true and bGrid to false. + + SCCOL nX; + SCROW nY; + tools::Long nPosX; + tools::Long nPosY; + SCSIZE nArrY; + ScBreakType nBreak = ScBreakType::NONE; + ScBreakType nBreakOld = ScBreakType::NONE; + + bool bSingle; + bool bDashed = false; + Color aPageColor; + Color aManualColor; + + if (bPagebreakMode) + bPage = false; // no "normal" breaks over the whole width/height + + // It is a big mess to distinguish when we are using pixels and when logic + // units for drawing. Ultimately we want to work only in the logic units, + // but until that happens, we need to special-case: + // + // * metafile + // * drawing to the screen - everything is internally counted in pixels there + // + // 'Internally' in the above means the pCellInfo[...].nWidth and + // pRowInfo[...]->nHeight: + // + // * when bWorksInPixels is true: these are in pixels + // * when bWorksInPixels is false: these are in the logic units + // + // This is where all the confusion comes from, ultimately we want them + // always in the logic units (100th of millimeters), but we need to get + // there gradually (get rid of setting MapUnit::MapPixel first), otherwise we'd + // break all the drawing by one change. + // So until that happens, we need to special case. + bool bWorksInPixels = bMetaFile; + const svtools::ColorConfig& rColorCfg = SC_MOD()->GetColorConfig(); + Color aSheetBGColor = rColorCfg.GetColorValue(::svtools::DOCCOLOR).nColor; + + if ( eType == OUTTYPE_WINDOW ) + { + bWorksInPixels = true; + aPageColor = rColorCfg.GetColorValue(svtools::CALCPAGEBREAKAUTOMATIC).nColor; + aManualColor = rColorCfg.GetColorValue(svtools::CALCPAGEBREAKMANUAL).nColor; + } + else + { + aPageColor = aGridColor; + aManualColor = aGridColor; + } + + tools::Long nOneX = 1; + tools::Long nOneY = 1; + if (!bWorksInPixels) + { + Size aOnePixel = rRenderContext.PixelToLogic(Size(1,1)); + nOneX = aOnePixel.Width(); + nOneY = aOnePixel.Height(); + } + + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + tools::Long nSignedOneX = nOneX * nLayoutSign; + + rRenderContext.SetLineColor(bMergeCover ? aSheetBGColor : aGridColor); + + ScGridMerger aGrid(&rRenderContext, nOneX, nOneY); + + // vertical lines + + nPosX = nScrX; + if ( bLayoutRTL ) + nPosX += nMirrorW - nOneX; + + for (nX=nX1; nX<=nX2; nX++) + { + sal_uInt16 nWidth = pRowInfo[0].basicCellInfo(nX).nWidth; + if (nWidth) + { + nPosX += nWidth * nLayoutSign; + + if ( bPage ) + { + // Search also in hidden part for page breaks + SCCOL nCol = nX + 1; + while (nCol <= mpDoc->MaxCol()) + { + nBreak = mpDoc->HasColBreak(nCol, nTab); + bool bHidden = mpDoc->ColHidden(nCol, nTab); + + if ( nBreak != ScBreakType::NONE || !bHidden ) + break; + ++nCol; + } + + if (nBreak != nBreakOld) + { + aGrid.Flush(); + + if (static_cast(nBreak)) + { + rRenderContext.SetLineColor( (nBreak & ScBreakType::Manual) ? aManualColor : + aPageColor ); + bDashed = true; + } + else + { + rRenderContext.SetLineColor(bMergeCover ? aSheetBGColor : aGridColor); + bDashed = false; + } + + nBreakOld = nBreak; + } + } + + bool bDraw = bGrid || nBreakOld != ScBreakType::NONE || bMergeCover; // simple grid only if set that way + + sal_uInt16 nWidthXplus1 = pRowInfo[0].basicCellInfo(nX+1).nWidth; + bSingle = false; //! get into Fillinfo !!!!! + if ( nXMaxCol() && !bSingle ) + { + bSingle = ( nWidthXplus1 == 0 ); + for (nArrY=1; nArrY+1MaxCol() && bSingle ) + { + SCCOL nVisX = nX + 1; + while ( nVisX < mpDoc->MaxCol() && !mpDoc->GetColWidth(nVisX,nTab) ) + ++nVisX; + + nPosY = nScrY; + for (nArrY=1; nArrY+1nHeight; + + bool bHOver = pThisRowInfo->cellInfo(nX).bHideGrid; + if (!bHOver) + { + if (nWidthXplus1) + bHOver = pThisRowInfo->cellInfo(nX+1).bHOverlapped; + else + { + if (nVisX <= nX2) + bHOver = pThisRowInfo->cellInfo(nVisX).bHOverlapped; + else + bHOver = mpDoc->GetAttr( + nVisX,pThisRowInfo->nRowNo,nTab,ATTR_MERGE_FLAG) + ->IsHorOverlapped(); + if (bHOver) + bHOver = mpDoc->GetAttr( + nX + 1,pThisRowInfo->nRowNo,nTab,ATTR_MERGE_FLAG) + ->IsHorOverlapped(); + } + } + + if ((pThisRowInfo->bChanged && !bHOver && !bMergeCover) || (bHOver && bMergeCover)) + { + aGrid.AddVerLine(bWorksInPixels, nPosX-nSignedOneX, nPosY, nNextY-nOneY, bDashed); + } + nPosY = nNextY; + } + } + else if (!bMergeCover) + { + aGrid.AddVerLine(bWorksInPixels, nPosX-nSignedOneX, nScrY, nScrY+nScrH-nOneY, bDashed); + } + } + } + } + + // horizontal lines + + bool bHiddenRow = true; + SCROW nHiddenEndRow = -1; + nPosY = nScrY; + for (nArrY=1; nArrY+1MaxRow(); ++i) + { + if (i > nHiddenEndRow) + bHiddenRow = mpDoc->RowHidden(i, nTab, nullptr, &nHiddenEndRow); + /* TODO: optimize the row break thing for large hidden + * segments where HasRowBreak() has to be called + * nevertheless for each row, as a row break is drawn also + * for hidden rows, above them. This needed to be done only + * once per hidden segment, maybe giving manual breaks + * priority. Something like GetNextRowBreak() and + * GetNextManualRowBreak(). */ + nBreak = mpDoc->HasRowBreak(i, nTab); + if (!bHiddenRow || nBreak != ScBreakType::NONE) + break; + } + + if (nBreakOld != nBreak) + { + aGrid.Flush(); + + if (static_cast(nBreak)) + { + rRenderContext.SetLineColor( (nBreak & ScBreakType::Manual) ? aManualColor : + aPageColor ); + bDashed = true; + } + else + { + rRenderContext.SetLineColor(bMergeCover ? aSheetBGColor : aGridColor); + bDashed = false; + } + + nBreakOld = nBreak; + } + } + + bool bDraw = bGrid || nBreakOld != ScBreakType::NONE || bMergeCover; // simple grid only if set so + + bool bNextYisNextRow = (pRowInfo[nArrYplus1].nRowNo == nYplus1); + bSingle = !bNextYisNextRow; // Hidden + for (SCCOL i=nX1; i<=nX2 && !bSingle; i++) + { + if (pRowInfo[nArrYplus1].cellInfo(i).bVOverlapped) + bSingle = true; + } + + if (bDraw) + { + if ( bSingle && nYMaxRow() ) + { + SCROW nVisY = pRowInfo[nArrYplus1].nRowNo; + + nPosX = nScrX; + if ( bLayoutRTL ) + nPosX += nMirrorW - nOneX; + + for (SCCOL i=nX1; i<=nX2; i++) + { + const tools::Long nNextX = nPosX + pRowInfo[0].basicCellInfo(i).nWidth * nLayoutSign; + if (nNextX != nPosX) // visible + { + bool bVOver; + if ( bNextYisNextRow ) + bVOver = pRowInfo[nArrYplus1].cellInfo(i).bVOverlapped; + else + { + bVOver = mpDoc->GetAttr( + i,nYplus1,nTab,ATTR_MERGE_FLAG) + ->IsVerOverlapped() + && mpDoc->GetAttr( + i,nVisY,nTab,ATTR_MERGE_FLAG) + ->IsVerOverlapped(); + //! nVisY from Array ?? + } + + if ((!bVOver && !bMergeCover) || (bVOver && bMergeCover)) + { + aGrid.AddHorLine(bWorksInPixels, nPosX, nNextX-nSignedOneX, nPosY-nOneY, bDashed); + } + } + nPosX = nNextX; + } + } + else if (!bMergeCover) + { + aGrid.AddHorLine(bWorksInPixels, nScrX, nScrX+nScrW-nOneX, nPosY-nOneY, bDashed); + } + } + } + } +} + +void ScOutputData::SetPagebreakMode( ScPageBreakData* pPageData ) +{ + bPagebreakMode = true; + if (!pPageData) + return; // not yet initialized -> everything "not printed" + + // mark printed range + // (everything in FillInfo is already initialized to sal_False) + + sal_uInt16 nRangeCount = sal::static_int_cast(pPageData->GetCount()); + for (sal_uInt16 nPos=0; nPosGetData( nPos ).GetPrintRange(); + + SCCOL nStartX = std::max( aRange.aStart.Col(), nX1 ); + SCCOL nEndX = std::min( aRange.aEnd.Col(), nX2 ); + SCROW nStartY = std::max( aRange.aStart.Row(), nY1 ); + SCROW nEndY = std::min( aRange.aEnd.Row(), nY2 ); + + for (SCSIZE nArrY=1; nArrY+1bChanged && pThisRowInfo->nRowNo >= nStartY && + pThisRowInfo->nRowNo <= nEndY ) + { + for (SCCOL nX=nStartX; nX<=nEndX; nX++) + pThisRowInfo->cellInfo(nX).bPrinted = true; + } + } + } +} + +void ScOutputData::SetCellRotations() +{ + //! save nRotMax + SCCOL nRotMax = nX2; + for (SCSIZE nRotY=0; nRotY nRotMax) + nRotMax = pRowInfo[nRotY].nRotMaxCol; + + for (SCSIZE nArrY=1; nArrYnRotMaxCol != SC_ROTMAX_NONE && + ( pThisRowInfo->bChanged || pRowInfo[nArrY-1].bChanged || + ( nArrY+1nRowNo; + + for (SCCOL nX=0; nX<=nRotMax; nX++) + { + ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nX); + const ScPatternAttr* pPattern = pInfo->pPatternAttr; + const SfxItemSet* pCondSet = pInfo->pConditionSet; + + if ( !pPattern && !mpDoc->ColHidden(nX, nTab) ) + { + pPattern = mpDoc->GetPattern( nX, nY, nTab ); + pCondSet = mpDoc->GetCondResult( nX, nY, nTab ); + } + + if ( pPattern ) // column isn't hidden + { + ScRotateDir nDir = pPattern->GetRotateDir( pCondSet ); + if (nDir != ScRotateDir::NONE) + { + // Needed for ScCellInfo internal decisions (bg fill, ...) + pInfo->nRotateDir = nDir; + + // create target coordinates + const SCCOL nTargetX(nX - nVisX1 + 1); + const SCROW nTargetY(nY - nVisY1 + 1); + + // Check for values - below in SetCellRotation these will + // be converted to size_t and thus may not be negative + if(nTargetX >= 0 && nTargetY >= 0) + { + // add rotation info to Array information + const Degree100 nAttrRotate(pPattern->GetRotateVal(pCondSet)); + const SvxRotateMode eRotMode(pPattern->GetItem(ATTR_ROTATE_MODE, pCondSet).GetValue()); + const double fOrient((bLayoutRTL ? -1.0 : 1.0) * toRadians(nAttrRotate)); // 1/100th degrees -> [0..2PI] + svx::frame::Array& rArray = mrTabInfo.maArray; + + rArray.SetCellRotation(nTargetX, nTargetY, eRotMode, fOrient); + } + } + } + } + } + } +} + +static ScRotateDir lcl_GetRotateDir( const ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + const ScPatternAttr* pPattern = pDoc->GetPattern( nCol, nRow, nTab ); + const SfxItemSet* pCondSet = pDoc->GetCondResult( nCol, nRow, nTab ); + + ScRotateDir nRet = ScRotateDir::NONE; + + Degree100 nAttrRotate = pPattern->GetRotateVal( pCondSet ); + if ( nAttrRotate ) + { + SvxRotateMode eRotMode = + pPattern->GetItem(ATTR_ROTATE_MODE, pCondSet).GetValue(); + + if ( eRotMode == SVX_ROTATE_MODE_STANDARD ) + nRet = ScRotateDir::Standard; + else if ( eRotMode == SVX_ROTATE_MODE_CENTER ) + nRet = ScRotateDir::Center; + else if ( eRotMode == SVX_ROTATE_MODE_TOP || eRotMode == SVX_ROTATE_MODE_BOTTOM ) + { + tools::Long nRot180 = nAttrRotate.get() % 18000; // 1/100 degree + if ( nRot180 == 9000 ) + nRet = ScRotateDir::Center; + else if ( ( eRotMode == SVX_ROTATE_MODE_TOP && nRot180 < 9000 ) || + ( eRotMode == SVX_ROTATE_MODE_BOTTOM && nRot180 > 9000 ) ) + nRet = ScRotateDir::Left; + else + nRet = ScRotateDir::Right; + } + } + + return nRet; +} + +static const SvxBrushItem* lcl_FindBackground( const ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + const ScPatternAttr* pPattern = pDoc->GetPattern( nCol, nRow, nTab ); + const SfxItemSet* pCondSet = pDoc->GetCondResult( nCol, nRow, nTab ); + const SvxBrushItem* pBackground = + &pPattern->GetItem( ATTR_BACKGROUND, pCondSet ); + + ScRotateDir nDir = lcl_GetRotateDir( pDoc, nCol, nRow, nTab ); + + // treat CENTER like RIGHT + if ( nDir == ScRotateDir::Right || nDir == ScRotateDir::Center ) + { + // text goes to the right -> take background from the left + while ( nCol > 0 && lcl_GetRotateDir( pDoc, nCol, nRow, nTab ) == nDir && + pBackground->GetColor().GetAlpha() != 0 ) + { + --nCol; + pPattern = pDoc->GetPattern( nCol, nRow, nTab ); + pCondSet = pDoc->GetCondResult( nCol, nRow, nTab ); + pBackground = &pPattern->GetItem( ATTR_BACKGROUND, pCondSet ); + } + } + else if ( nDir == ScRotateDir::Left ) + { + // text goes to the left -> take background from the right + while ( nCol < pDoc->MaxCol() && lcl_GetRotateDir( pDoc, nCol, nRow, nTab ) == nDir && + pBackground->GetColor().GetAlpha() != 0 ) + { + ++nCol; + pPattern = pDoc->GetPattern( nCol, nRow, nTab ); + pCondSet = pDoc->GetCondResult( nCol, nRow, nTab ); + pBackground = &pPattern->GetItem( ATTR_BACKGROUND, pCondSet ); + } + } + + return pBackground; +} + +static bool lcl_EqualBack( const RowInfo& rFirst, const RowInfo& rOther, + SCCOL nX1, SCCOL nX2, bool bShowProt, bool bPagebreakMode ) +{ + if ( rFirst.bChanged != rOther.bChanged || + rFirst.bEmptyBack != rOther.bEmptyBack ) + return false; + + SCCOL nX; + if ( bShowProt ) + { + for ( nX=nX1; nX<=nX2; nX++ ) + { + const ScPatternAttr* pPat1 = rFirst.cellInfo(nX).pPatternAttr; + const ScPatternAttr* pPat2 = rOther.cellInfo(nX).pPatternAttr; + if ( !pPat1 || !pPat2 || + !SfxPoolItem::areSame(pPat1->GetItem(ATTR_PROTECTION), pPat2->GetItem(ATTR_PROTECTION) ) ) + return false; + } + } + else + { + for ( nX=nX1; nX<=nX2; nX++ ) + if ( !SfxPoolItem::areSame(rFirst.cellInfo(nX).pBackground, rOther.cellInfo(nX).pBackground ) ) + return false; + } + + if ( rFirst.nRotMaxCol != SC_ROTMAX_NONE || rOther.nRotMaxCol != SC_ROTMAX_NONE ) + for ( nX=nX1; nX<=nX2; nX++ ) + if ( rFirst.cellInfo(nX).nRotateDir != rOther.cellInfo(nX).nRotateDir ) + return false; + + if ( bPagebreakMode ) + for ( nX=nX1; nX<=nX2; nX++ ) + if ( rFirst.cellInfo(nX).bPrinted != rOther.cellInfo(nX).bPrinted ) + return false; + + for ( nX=nX1; nX<=nX2; nX++ ) + { + std::optional const & pCol1 = rFirst.cellInfo(nX).mxColorScale; + std::optional const & pCol2 = rOther.cellInfo(nX).mxColorScale; + if( (pCol1 && !pCol2) || (!pCol1 && pCol2) ) + return false; + + if (pCol1 && (*pCol1 != *pCol2)) + return false; + + const ScDataBarInfo* pInfo1 = rFirst.cellInfo(nX).pDataBar; + const ScDataBarInfo* pInfo2 = rOther.cellInfo(nX).pDataBar; + + if( (pInfo1 && !pInfo2) || (!pInfo1 && pInfo2) ) + return false; + + if (pInfo1 && (*pInfo1 != *pInfo2)) + return false; + + // each cell with an icon set should be painted the same way + const ScIconSetInfo* pIconSet1 = rFirst.cellInfo(nX).pIconSet; + const ScIconSetInfo* pIconSet2 = rOther.cellInfo(nX).pIconSet; + + if(pIconSet1 || pIconSet2) + return false; + } + + return true; +} + +void ScOutputData::DrawDocumentBackground() +{ + if ( !bSolidBackground ) + return; + + Color aBgColor( SC_MOD()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor ); + mpDev->SetLineColor(aBgColor); + mpDev->SetFillColor(aBgColor); + + Point aScreenPos = mpDev->PixelToLogic(Point(nScrX, nScrY)); + Size aScreenSize = mpDev->PixelToLogic(Size(nScrW - 1,nScrH - 1)); + + mpDev->DrawRect(tools::Rectangle(aScreenPos, aScreenSize)); +} + +namespace { + +const double lclCornerRectTransparency = 40.0; + +void drawDataBars(vcl::RenderContext& rRenderContext, const ScDataBarInfo* pOldDataBarInfo, const tools::Rectangle& rRect, tools::Long nOneX, tools::Long nOneY) +{ + tools::Long nPosZero = 0; + tools::Rectangle aPaintRect = rRect; + aPaintRect.AdjustTop(2 * nOneY ); + aPaintRect.AdjustBottom( -(2 * nOneY) ); + aPaintRect.AdjustLeft( 2 * nOneX ); + aPaintRect.AdjustRight( -(2 * nOneX) ); + if(pOldDataBarInfo->mnZero) + { + // need to calculate null point in cell + tools::Long nLength = aPaintRect.Right() - aPaintRect.Left(); + nPosZero = static_cast(aPaintRect.Left() + nLength*pOldDataBarInfo->mnZero/100.0); + } + else + { + nPosZero = aPaintRect.Left(); + } + + if(pOldDataBarInfo->mnLength < 0) + { + aPaintRect.SetRight( nPosZero ); + tools::Long nLength = nPosZero - aPaintRect.Left(); + aPaintRect.SetLeft( nPosZero + static_cast(nLength * pOldDataBarInfo->mnLength/100.0) ); + } + else if(pOldDataBarInfo->mnLength > 0) + { + aPaintRect.SetLeft( nPosZero ); + tools::Long nLength = aPaintRect.Right() - nPosZero; + aPaintRect.SetRight( nPosZero + static_cast(nLength * pOldDataBarInfo->mnLength/100.0) ); + } + else + return; + + if(pOldDataBarInfo->mbGradient) + { + rRenderContext.SetLineColor(pOldDataBarInfo->maColor); + Gradient aGradient(css::awt::GradientStyle_LINEAR, pOldDataBarInfo->maColor, COL_TRANSPARENT); + aGradient.SetSteps(255); + + if(pOldDataBarInfo->mnLength < 0) + aGradient.SetAngle(2700_deg10); + else + aGradient.SetAngle(900_deg10); + + rRenderContext.DrawGradient(aPaintRect, aGradient); + + rRenderContext.SetLineColor(); + } + else + { + rRenderContext.SetFillColor(pOldDataBarInfo->maColor); + rRenderContext.DrawRect(aPaintRect); + } + + //draw axis + if(!(pOldDataBarInfo->mnZero && pOldDataBarInfo->mnZero != 100)) + return; + + Point aPoint1(nPosZero, rRect.Top()); + Point aPoint2(nPosZero, rRect.Bottom()); + LineInfo aLineInfo(LineStyle::Dash, 1); + aLineInfo.SetDashCount( 4 ); + aLineInfo.SetDistance( 3 ); + aLineInfo.SetDashLen( 3 ); + rRenderContext.SetFillColor(pOldDataBarInfo->maAxisColor); + rRenderContext.SetLineColor(pOldDataBarInfo->maAxisColor); + rRenderContext.DrawLine(aPoint1, aPoint2, aLineInfo); + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(); +} + +const BitmapEx& getIcon(sc::IconSetBitmapMap & rIconSetBitmapMap, ScIconSetType eType, sal_Int32 nIndex) +{ + return ScIconSetFormat::getBitmap(rIconSetBitmapMap, eType, nIndex); +} + +void drawIconSets(vcl::RenderContext& rRenderContext, const ScIconSetInfo* pOldIconSetInfo, const tools::Rectangle& rRect, tools::Long nOneX, tools::Long nOneY, + sc::IconSetBitmapMap & rIconSetBitmapMap) +{ + ScIconSetType eType = pOldIconSetInfo->eIconSetType; + sal_Int32 nIndex = pOldIconSetInfo->nIconIndex; + const BitmapEx& rIcon = getIcon(rIconSetBitmapMap, eType, nIndex); + + tools::Long aHeight = o3tl::convert(10, o3tl::Length::pt, o3tl::Length::mm100); + + if (pOldIconSetInfo->mnHeight) + { + if (comphelper::LibreOfficeKit::isActive()) + { + aHeight = rRenderContext.LogicToPixel(Size(0, pOldIconSetInfo->mnHeight), MapMode(MapUnit::MapTwip)).Height(); + aHeight *= comphelper::LibreOfficeKit::getDPIScale(); + } + else + { + aHeight = o3tl::convert(pOldIconSetInfo->mnHeight, o3tl::Length::twip, o3tl::Length::mm100); + } + } + + Size aSize = rIcon.GetSizePixel(); + double fRatio = static_cast(aSize.Width()) / aSize.Height(); + tools::Long aWidth = fRatio * aHeight; + + rRenderContext.Push(); + rRenderContext.SetClipRegion(vcl::Region(rRect)); + rRenderContext.DrawBitmapEx(Point(rRect.Left() + 2 * nOneX, rRect.Bottom() - 2 * nOneY - aHeight), Size(aWidth, aHeight), rIcon); + rRenderContext.Pop(); +} + +void drawCells(vcl::RenderContext& rRenderContext, std::optional const & pColor, const SvxBrushItem* pBackground, std::optional& pOldColor, const SvxBrushItem*& pOldBackground, + tools::Rectangle& rRect, tools::Long nPosX, tools::Long nLayoutSign, tools::Long nOneX, tools::Long nOneY, const ScDataBarInfo* pDataBarInfo, const ScDataBarInfo*& pOldDataBarInfo, + const ScIconSetInfo* pIconSetInfo, const ScIconSetInfo*& pOldIconSetInfo, + sc::IconSetBitmapMap & rIconSetBitmapMap) +{ + tools::Long nSignedOneX = nOneX * nLayoutSign; + // need to paint if old color scale has been used and now + // we have a different color or a style based background + // we can here fall back to pointer comparison + if (pOldColor && (pBackground || pOldColor != pColor || pOldDataBarInfo || pDataBarInfo || pIconSetInfo || pOldIconSetInfo)) + { + rRect.SetRight( nPosX-nSignedOneX ); + if( !pOldColor->IsTransparent() ) + { + rRenderContext.SetFillColor( *pOldColor ); + rRenderContext.DrawRect( rRect ); + } + if( pOldDataBarInfo ) + drawDataBars(rRenderContext, pOldDataBarInfo, rRect, nOneX, nOneY); + if( pOldIconSetInfo ) + drawIconSets(rRenderContext, pOldIconSetInfo, rRect, nOneX, nOneY, rIconSetBitmapMap); + + rRect.SetLeft( nPosX - nSignedOneX ); + } + + if ( pOldBackground && (pColor || !SfxPoolItem::areSame(pBackground, pOldBackground) || pOldDataBarInfo || pDataBarInfo || pIconSetInfo || pOldIconSetInfo) ) + { + rRect.SetRight( nPosX-nSignedOneX ); + if (pOldBackground) // ==0 if hidden + { + Color aBackCol = pOldBackground->GetColor(); + if ( !aBackCol.IsTransparent() ) //! partial transparency? + { + rRenderContext.SetFillColor( aBackCol ); + rRenderContext.DrawRect( rRect ); + } + } + if( pOldDataBarInfo ) + drawDataBars(rRenderContext, pOldDataBarInfo, rRect, nOneX, nOneY); + if( pOldIconSetInfo ) + drawIconSets(rRenderContext, pOldIconSetInfo, rRect, nOneX, nOneY, rIconSetBitmapMap); + + rRect.SetLeft( nPosX - nSignedOneX ); + } + + if (!pOldBackground && !pOldColor && (pDataBarInfo || pIconSetInfo)) + { + rRect.SetRight( nPosX -nSignedOneX ); + rRect.SetLeft( nPosX - nSignedOneX ); + } + + if(pColor) + { + // only update pOldColor if the colors changed + if (!pOldColor || *pOldColor != *pColor) + pOldColor = pColor; + + pOldBackground = nullptr; + } + else if(pBackground) + { + pOldBackground = pBackground; + pOldColor.reset(); + } + + if(pDataBarInfo) + pOldDataBarInfo = pDataBarInfo; + else + pOldDataBarInfo = nullptr; + + if(pIconSetInfo) + pOldIconSetInfo = pIconSetInfo; + else + pOldIconSetInfo = nullptr; +} + +} + +void ScOutputData::DrawBackground(vcl::RenderContext& rRenderContext) +{ + Size aOnePixel = rRenderContext.PixelToLogic(Size(1,1)); + tools::Long nOneXLogic = aOnePixel.Width(); + tools::Long nOneYLogic = aOnePixel.Height(); + + // See more about bWorksInPixels in ScOutputData::DrawGrid + bool bWorksInPixels = false; + if (eType == OUTTYPE_WINDOW) + bWorksInPixels = true; + + tools::Long nOneX = 1; + tools::Long nOneY = 1; + if (!bWorksInPixels) + { + nOneX = nOneXLogic; + nOneY = nOneYLogic; + } + + tools::Rectangle aRect; + + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + rRenderContext.SetLineColor(); + + bool bShowProt = mbSyntaxMode && mpDoc->IsTabProtected(nTab); + bool bDoAll = bShowProt || bPagebreakMode || bSolidBackground; + + bool bCellContrast = mbUseStyleColor && + Application::GetSettings().GetStyleSettings().GetHighContrastMode(); + + tools::Long nPosY = nScrY; + + const svtools::ColorConfig& rColorCfg = SC_MOD()->GetColorConfig(); + Color aProtectedColor( rColorCfg.GetColorValue( svtools::CALCPROTECTEDBACKGROUND ).nColor ); + auto pProtectedBackground = std::make_shared( aProtectedColor, ATTR_BACKGROUND ); + + // iterate through the rows to show + for (SCSIZE nArrY=1; nArrY+1nHeight; + + if ( pThisRowInfo->bChanged ) + { + if ( ( ( pThisRowInfo->bEmptyBack ) || mbSyntaxMode ) && !bDoAll ) + { + // nothing + } + else + { + // scan for rows with the same background: + SCSIZE nSkip = 0; + while ( nArrY+nSkip+2 pOldColor; + const ScDataBarInfo* pOldDataBarInfo = nullptr; + const ScIconSetInfo* pOldIconSetInfo = nullptr; + SCCOL nMergedCols = 1; + SCCOL nOldMerged = 0; + + for (SCCOL nX=nX1; nX + nMergedCols <= nX2 + 1; nX += nOldMerged) + { + ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nX-1+nMergedCols); + + nOldMerged = nMergedCols; + + tools::Long nNewPosX = nPosX; + // extend for all merged cells + nMergedCols = 1; + if (pInfo->bMerged && pInfo->pPatternAttr) + { + const ScMergeAttr* pMerge = + &pInfo->pPatternAttr->GetItem(ATTR_MERGE); + nMergedCols = std::max(1, pMerge->GetColMerge()); + } + + for (SCCOL nMerged = 0; nMerged < nMergedCols; ++nMerged) + { + SCCOL nCol = nX+nOldMerged+nMerged; + if (nCol > nX2+2) + break; + nNewPosX += pRowInfo[0].basicCellInfo(nCol-1).nWidth * nLayoutSign; + } + + if (nNewPosX == nPosX) + continue; // Zero width, no need to draw. + + if (bCellContrast) + { + // high contrast for cell borders and backgrounds -> empty background + pBackground = ScGlobal::GetEmptyBrushItem(); + } + else if (bShowProt) // show cell protection in syntax mode + { + const ScPatternAttr* pP = pInfo->pPatternAttr; + if (pP) + { + const ScProtectionAttr& rProt = pP->GetItem(ATTR_PROTECTION); + if (rProt.GetProtection() || rProt.GetHideCell()) + pBackground = pProtectedBackground.get(); + else + pBackground = ScGlobal::GetEmptyBrushItem(); + } + else + pBackground = nullptr; + } + else + pBackground = pInfo->pBackground; + + if ( bPagebreakMode && !pInfo->bPrinted ) + pBackground = pProtectedBackground.get(); + + if ( pInfo->nRotateDir > ScRotateDir::Standard && + !pBackground->GetColor().IsFullyTransparent() && + !bCellContrast ) + { + SCROW nY = pRowInfo[nArrY].nRowNo; + pBackground = lcl_FindBackground( mpDoc, nX, nY, nTab ); + } + + std::optional const & pColor = pInfo->mxColorScale; + const ScDataBarInfo* pDataBarInfo = pInfo->pDataBar; + const ScIconSetInfo* pIconSetInfo = pInfo->pIconSet; + + tools::Long nPosXLogic = nPosX; + if (bWorksInPixels) + nPosXLogic = rRenderContext.PixelToLogic(Point(nPosX, 0)).X(); + + drawCells(rRenderContext, pColor, pBackground, pOldColor, pOldBackground, aRect, nPosXLogic, nLayoutSign, nOneXLogic, nOneYLogic, pDataBarInfo, pOldDataBarInfo, pIconSetInfo, pOldIconSetInfo, mpDoc->GetIconSetBitmapMap()); + + nPosX = nNewPosX; + } + + tools::Long nPosXLogic = nPosX; + if (bWorksInPixels) + nPosXLogic = rRenderContext.PixelToLogic(Point(nPosX, 0)).X(); + + drawCells(rRenderContext, std::optional(), nullptr, pOldColor, pOldBackground, aRect, nPosXLogic, nLayoutSign, nOneXLogic, nOneYLogic, nullptr, pOldDataBarInfo, nullptr, pOldIconSetInfo, mpDoc->GetIconSetBitmapMap()); + + nArrY += nSkip; + } + } + nPosY += nRowHeight; + } +} + +void ScOutputData::DrawShadow() +{ + DrawExtraShadow( false, false, false, false ); +} + +void ScOutputData::DrawExtraShadow(bool bLeft, bool bTop, bool bRight, bool bBottom) +{ + mpDev->SetLineColor(); + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + bool bCellContrast = mbUseStyleColor && rStyleSettings.GetHighContrastMode(); + Color aAutoTextColor; + if ( bCellContrast ) + aAutoTextColor = SC_MOD()->GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor; + + tools::Long nInitPosX = nScrX; + if ( bLayoutRTL ) + { + Size aOnePixel = mpDev->PixelToLogic(Size(1,1)); + tools::Long nOneX = aOnePixel.Width(); + nInitPosX += nMirrorW - nOneX; + } + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + tools::Long nPosY = nScrY - pRowInfo[0].nHeight; + for (SCSIZE nArrY=0; nArrYnHeight; + + if ( pThisRowInfo->bChanged && !bSkipY ) + { + tools::Long nPosX = nInitPosX - pRowInfo[0].basicCellInfo(nX1-1).nWidth * nLayoutSign; + for (SCCOL nCol=nX1-1; nCol<=nX2+1; nCol++) + { + bool bCornerX = ( nCol==nX1-1 || nCol==nX2+1 ); + bool bSkipX = ( nCol==nX1-1 && !bLeft ) || ( nCol==nX2+1 && !bRight ); + + for (sal_uInt16 nPass=0; nPass<2; nPass++) // horizontal / vertical + { + const SvxShadowItem* pAttr = nPass ? + pThisRowInfo->cellInfo(nCol).pVShadowOrigin : + pThisRowInfo->cellInfo(nCol).pHShadowOrigin; + if ( pAttr && !bSkipX ) + { + ScShadowPart ePart = nPass ? + pThisRowInfo->cellInfo(nCol).eVShadowPart : + pThisRowInfo->cellInfo(nCol).eHShadowPart; + + bool bDo = true; + if ( (nPass==0 && bCornerX) || (nPass==1 && bCornerY) ) + if ( ePart != SC_SHADOW_CORNER ) + bDo = false; + + if (bDo) + { + tools::Long nThisWidth = pRowInfo[0].basicCellInfo(nCol).nWidth; + tools::Long nMaxWidth = nThisWidth; + if (!nMaxWidth) + { + //! direction must depend on shadow location + SCCOL nWx = nCol+1; + while (nWxGetWidth(); + tools::Long nSizeX = static_cast(nSize*mnPPTX); + if (nSizeX >= nMaxWidth) nSizeX = nMaxWidth-1; + tools::Long nSizeY = static_cast(nSize*mnPPTY); + if (nSizeY >= nRowHeight) nSizeY = nRowHeight-1; + + nSizeX *= nLayoutSign; // used only to add to rectangle values + + SvxShadowLocation eLoc = pAttr->GetLocation(); + if ( bLayoutRTL ) + { + // Shadow location is specified as "visual" (right is always right), + // so the attribute's location value is mirrored here and in FillInfo. + switch (eLoc) + { + case SvxShadowLocation::BottomRight: eLoc = SvxShadowLocation::BottomLeft; break; + case SvxShadowLocation::BottomLeft: eLoc = SvxShadowLocation::BottomRight; break; + case SvxShadowLocation::TopRight: eLoc = SvxShadowLocation::TopLeft; break; + case SvxShadowLocation::TopLeft: eLoc = SvxShadowLocation::TopRight; break; + default: + { + // added to avoid warnings + } + } + } + + if (ePart == SC_SHADOW_HORIZ || ePart == SC_SHADOW_HSTART || + ePart == SC_SHADOW_CORNER) + { + if (eLoc == SvxShadowLocation::TopLeft || eLoc == SvxShadowLocation::TopRight) + aRect.SetTop( aRect.Bottom() - nSizeY ); + else + aRect.SetBottom( aRect.Top() + nSizeY ); + } + if (ePart == SC_SHADOW_VERT || ePart == SC_SHADOW_VSTART || + ePart == SC_SHADOW_CORNER) + { + if (eLoc == SvxShadowLocation::TopLeft || eLoc == SvxShadowLocation::BottomLeft) + aRect.SetLeft( aRect.Right() - nSizeX ); + else + aRect.SetRight( aRect.Left() + nSizeX ); + } + if (ePart == SC_SHADOW_HSTART) + { + if (eLoc == SvxShadowLocation::TopLeft || eLoc == SvxShadowLocation::BottomLeft) + aRect.AdjustRight( -nSizeX ); + else + aRect.AdjustLeft(nSizeX ); + } + if (ePart == SC_SHADOW_VSTART) + { + if (eLoc == SvxShadowLocation::TopLeft || eLoc == SvxShadowLocation::TopRight) + aRect.AdjustBottom( -nSizeY ); + else + aRect.AdjustTop(nSizeY ); + } + + //! merge rectangles? + mpDev->SetFillColor( bCellContrast ? aAutoTextColor : pAttr->GetColor() ); + mpDev->DrawRect( aRect ); + } + } + } + + nPosX += pRowInfo[0].basicCellInfo(nCol).nWidth * nLayoutSign; + } + } + nPosY += nRowHeight; + } +} + +void ScOutputData::DrawClear() +{ + tools::Rectangle aRect; + Size aOnePixel = mpDev->PixelToLogic(Size(1,1)); + tools::Long nOneX = aOnePixel.Width(); + tools::Long nOneY = aOnePixel.Height(); + + // (called only for ScGridWindow) + Color aBgColor( SC_MOD()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor ); + + if (bMetaFile) + nOneX = nOneY = 0; + + mpDev->SetLineColor(); + + mpDev->SetFillColor( aBgColor ); + + tools::Long nPosY = nScrY; + for (SCSIZE nArrY=1; nArrY+1nHeight; + + if ( pThisRowInfo->bChanged ) + { + // scan for more rows which must be painted: + SCSIZE nSkip = 0; + while ( nArrY+nSkip+2DrawRect( aRect ); + + nArrY += nSkip; + } + nPosY += nRowHeight; + } +} + +// Lines + +static tools::Long lclGetSnappedX( const OutputDevice& rDev, tools::Long nPosX, bool bSnapPixel ) +{ + return (bSnapPixel && nPosX) ? rDev.PixelToLogic( rDev.LogicToPixel( Size( nPosX, 0 ) ) ).Width() : nPosX; +} + +static tools::Long lclGetSnappedY( const OutputDevice& rDev, tools::Long nPosY, bool bSnapPixel ) +{ + return (bSnapPixel && nPosY) ? rDev.PixelToLogic( rDev.LogicToPixel( Size( 0, nPosY ) ) ).Height() : nPosY; +} + +void ScOutputData::DrawFrame(vcl::RenderContext& rRenderContext) +{ + DrawModeFlags nOldDrawMode = rRenderContext.GetDrawMode(); + + Color aSingleColor; + bool bUseSingleColor = false; + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + bool bCellContrast = mbUseStyleColor && rStyleSettings.GetHighContrastMode(); + + // if a Calc OLE object is embedded in Draw/Impress, the VCL DrawMode is used + // for display mode / B&W printing. The VCL DrawMode handling doesn't work for lines + // that are drawn with DrawRect, so if the line/background bits are set, the DrawMode + // must be reset and the border colors handled here. + + if ( ( nOldDrawMode & DrawModeFlags::WhiteFill ) && ( nOldDrawMode & DrawModeFlags::BlackLine ) ) + { + rRenderContext.SetDrawMode( nOldDrawMode & (~DrawModeFlags::WhiteFill) ); + aSingleColor = COL_BLACK; + bUseSingleColor = true; + } + else if ( ( nOldDrawMode & DrawModeFlags::SettingsFill ) && ( nOldDrawMode & DrawModeFlags::SettingsLine ) ) + { + rRenderContext.SetDrawMode( nOldDrawMode & (~DrawModeFlags::SettingsFill) ); + aSingleColor = rStyleSettings.GetWindowTextColor(); // same as used in VCL for DrawModeFlags::SettingsLine + bUseSingleColor = true; + } + else if ( bCellContrast ) + { + aSingleColor = SC_MOD()->GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor; + bUseSingleColor = true; + } + + const Color* pForceColor = bUseSingleColor ? &aSingleColor : nullptr; + + if (mrTabInfo.maArray.HasCellRotation()) + { + DrawRotatedFrame(rRenderContext); // removes the lines that must not be painted here + } + + tools::Long nInitPosX = nScrX; + if ( bLayoutRTL ) + { + Size aOnePixel = rRenderContext.PixelToLogic(Size(1,1)); + tools::Long nOneX = aOnePixel.Width(); + nInitPosX += nMirrorW - nOneX; + } + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + // *** set column and row sizes of the frame border array *** + + svx::frame::Array& rArray = mrTabInfo.maArray; + size_t nColCount = rArray.GetColCount(); + size_t nRowCount = rArray.GetRowCount(); + + // row heights + + // row 0 is not visible (dummy for borders from top) - subtract its height from initial position + // subtract 1 unit more, because position 0 is first *in* cell, grid line is one unit before + tools::Long nOldPosY = nScrY - 1 - pRowInfo[ 0 ].nHeight; + tools::Long nOldSnapY = lclGetSnappedY( rRenderContext, nOldPosY, bSnapPixel ); + rArray.SetYOffset( nOldSnapY ); + for( size_t nRow = 0; nRow < nRowCount; ++nRow ) + { + tools::Long nNewPosY = nOldPosY + pRowInfo[ nRow ].nHeight; + tools::Long nNewSnapY = lclGetSnappedY( rRenderContext, nNewPosY, bSnapPixel ); + rArray.SetRowHeight( nRow, nNewSnapY - nOldSnapY ); + nOldPosY = nNewPosY; + nOldSnapY = nNewSnapY; + } + + // column widths + + // column nX1-1 is not visible (dummy for borders from left) - subtract its width from initial position + // subtract 1 unit more, because position 0 is first *in* cell, grid line is one unit above + tools::Long nOldPosX = nInitPosX - nLayoutSign * (1 + pRowInfo[ 0 ].basicCellInfo( nX1 - 1 ).nWidth); + tools::Long nOldSnapX = lclGetSnappedX( rRenderContext, nOldPosX, bSnapPixel ); + // set X offset for left-to-right sheets; for right-to-left sheets this is done after for() loop + if( !bLayoutRTL ) + rArray.SetXOffset( nOldSnapX ); + for( SCCOL nCol = nX1 - 1; nCol <= nX2 + 1; ++nCol ) + { + size_t nArrCol = bLayoutRTL ? nX2 + 1 - nCol : nCol - (nX1 - 1); + tools::Long nNewPosX = nOldPosX + pRowInfo[ 0 ].basicCellInfo( nCol ).nWidth * nLayoutSign; + tools::Long nNewSnapX = lclGetSnappedX( rRenderContext, nNewPosX, bSnapPixel ); + rArray.SetColWidth( nArrCol, std::abs( nNewSnapX - nOldSnapX ) ); + nOldPosX = nNewPosX; + nOldSnapX = nNewSnapX; + } + if( bLayoutRTL ) + rArray.SetXOffset( nOldSnapX ); + + // *** draw the array *** + + size_t nFirstCol = 1; + size_t nFirstRow = 1; + size_t nLastCol = nColCount - 2; + size_t nLastRow = nRowCount - 2; + + if( mrTabInfo.mbPageMode ) + rArray.SetClipRange( nFirstCol, nFirstRow, nLastCol, nLastRow ); + + // draw only rows with set RowInfo::bChanged flag + size_t nRow1 = nFirstRow; + std::unique_ptr pProcessor(CreateProcessor2D()); + if (!pProcessor) + return; + while( nRow1 <= nLastRow ) + { + while( (nRow1 <= nLastRow) && !pRowInfo[ nRow1 ].bChanged ) ++nRow1; + if( nRow1 <= nLastRow ) + { + size_t nRow2 = nRow1; + while( (nRow2 + 1 <= nLastRow) && pRowInfo[ nRow2 + 1 ].bChanged ) ++nRow2; + auto xPrimitive = rArray.CreateB2DPrimitiveRange( + nFirstCol, nRow1, nLastCol, nRow2, pForceColor ); + pProcessor->process(xPrimitive); + nRow1 = nRow2 + 1; + } + } + pProcessor.reset(); + + rRenderContext.SetDrawMode(nOldDrawMode); +} + +void ScOutputData::DrawRotatedFrame(vcl::RenderContext& rRenderContext) +{ + //! save nRotMax + SCCOL nRotMax = nX2; + for (SCSIZE nRotY=0; nRotY nRotMax) + nRotMax = pRowInfo[nRotY].nRotMaxCol; + + const ScPatternAttr* pPattern; + const SfxItemSet* pCondSet; + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + bool bCellContrast = mbUseStyleColor && rStyleSettings.GetHighContrastMode(); + + tools::Long nInitPosX = nScrX; + if ( bLayoutRTL ) + { + Size aOnePixel = rRenderContext.PixelToLogic(Size(1,1)); + tools::Long nOneX = aOnePixel.Width(); + nInitPosX += nMirrorW - nOneX; + } + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + tools::Rectangle aClipRect( Point(nScrX, nScrY), Size(nScrW, nScrH) ); + if (bMetaFile) + { + rRenderContext.Push(); + rRenderContext.IntersectClipRegion( aClipRect ); + } + else + rRenderContext.SetClipRegion( vcl::Region( aClipRect ) ); + + std::unique_ptr pProcessor(CreateProcessor2D( )); + tools::Long nPosY = nScrY; + for (SCSIZE nArrY=1; nArrYnRotateDir > ScRotateDir::Standard && + !pInfo->bHOverlapped && !pInfo->bVOverlapped ) + { + pPattern = pInfo->pPatternAttr; + pCondSet = pInfo->pConditionSet; + if (!pPattern) + { + pPattern = mpDoc->GetPattern( nX, nY, nTab ); + pInfo->pPatternAttr = pPattern; + pCondSet = mpDoc->GetCondResult( nX, nY, nTab ); + pInfo->pConditionSet = pCondSet; + } + + //! LastPattern etc. + + Degree100 nAttrRotate = pPattern->GetRotateVal( pCondSet ); + SvxRotateMode eRotMode = + pPattern->GetItem(ATTR_ROTATE_MODE, pCondSet).GetValue(); + + if (nAttrRotate) + { + if (nX < nX1) // compute negative position + { + nPosX = nInitPosX; + SCCOL nCol = nX1; + while (nCol > nX) + { + --nCol; + nPosX -= nLayoutSign * static_cast(pRowInfo[0].basicCellInfo(nCol).nWidth); + } + } + + // start position minus 1 so rotated backgrounds suit the border + // (border is on the grid) + + tools::Long nTop = nPosY - 1; + tools::Long nBottom = nPosY + nRowHeight - 1; + tools::Long nTopLeft = nPosX - nLayoutSign; + tools::Long nTopRight = nPosX + (nColWidth - 1) * nLayoutSign; + tools::Long nBotLeft = nTopLeft; + tools::Long nBotRight = nTopRight; + + // inclusion of the sign here hasn't been decided yet + // (if not, the extension of the non-rotated background must also be changed) + double nRealOrient = nLayoutSign * toRadians(nAttrRotate); // 1/100th degrees + double nCos = cos(nRealOrient); + double nSin = sin(nRealOrient); + //! restrict !!! + tools::Long nSkew = static_cast(nRowHeight * nCos / nSin); + + switch (eRotMode) + { + case SVX_ROTATE_MODE_BOTTOM: + nTopLeft += nSkew; + nTopRight += nSkew; + break; + case SVX_ROTATE_MODE_CENTER: + nSkew /= 2; + nTopLeft += nSkew; + nTopRight += nSkew; + nBotLeft -= nSkew; + nBotRight -= nSkew; + break; + case SVX_ROTATE_MODE_TOP: + nBotLeft -= nSkew; + nBotRight -= nSkew; + break; + default: + { + // added to avoid warnings + } + } + + Point aPoints[4]; + aPoints[0] = Point(nTopLeft, nTop); + aPoints[1] = Point(nTopRight, nTop); + aPoints[2] = Point(nBotRight, nBottom); + aPoints[3] = Point(nBotLeft, nBottom); + + const SvxBrushItem* pBackground = pInfo->pBackground; + if (!pBackground) + pBackground = &pPattern->GetItem(ATTR_BACKGROUND, pCondSet); + if (bCellContrast) + { + // high contrast for cell borders and backgrounds -> empty background + pBackground = ScGlobal::GetEmptyBrushItem(); + } + if (!pInfo->mxColorScale) + { + const Color& rColor = pBackground->GetColor(); + if (rColor.GetAlpha() != 0) + { + // draw background only for the changed row itself + // (background doesn't extend into other cells). + // For the borders (rotated and normal), clipping should be + // set if the row isn't changed, but at least the borders + // don't cover the cell contents. + if (rThisRowInfo.bChanged) + { + tools::Polygon aPoly(4, aPoints); + + // for DrawPolygon, without Pen one pixel is left out + // to the right and below... + if (!rColor.IsTransparent()) + rRenderContext.SetLineColor(rColor); + else + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(rColor); + rRenderContext.DrawPolygon(aPoly); + } + } + } + else + { + tools::Polygon aPoly(4, aPoints); + std::optional const & pColor = pInfo->mxColorScale; + + // for DrawPolygon, without Pen one pixel is left out + // to the right and below... + if (!pColor->IsTransparent()) + rRenderContext.SetLineColor(*pColor); + else + rRenderContext.SetLineColor(); + rRenderContext.SetFillColor(*pColor); + rRenderContext.DrawPolygon(aPoly); + + } + } + } + nPosX += nColWidth * nLayoutSign; + } + } + nPosY += nRowHeight; + } + + pProcessor.reset(); + + if (bMetaFile) + rRenderContext.Pop(); + else + rRenderContext.SetClipRegion(); +} + +std::unique_ptr ScOutputData::CreateProcessor2D( ) +{ + mpDoc->InitDrawLayer(mpDoc->GetDocumentShell()); + ScDrawLayer* pDrawLayer = mpDoc->GetDrawLayer(); + if (!pDrawLayer) + return nullptr; + + basegfx::B2DRange aViewRange; + SdrPage *pDrawPage = pDrawLayer->GetPage( static_cast< sal_uInt16 >( nTab ) ); + drawinglayer::geometry::ViewInformation2D aNewViewInfos; + aNewViewInfos.setViewTransformation(mpDev->GetViewTransformation()); + aNewViewInfos.setViewport(aViewRange); + aNewViewInfos.setVisualizedPage(GetXDrawPageForSdrPage( pDrawPage )); + + return drawinglayer::processor2d::createProcessor2DFromOutputDevice( + *mpDev, aNewViewInfos ); +} + +// Printer + +vcl::Region ScOutputData::GetChangedAreaRegion() +{ + vcl::Region aRegion; + tools::Rectangle aDrawingRect; + bool bHad(false); + tools::Long nPosY = nScrY; + SCSIZE nArrY; + + aDrawingRect.SetLeft( nScrX ); + aDrawingRect.SetRight( nScrX+nScrW-1 ); + + for(nArrY=1; nArrY+1bChanged) + { + if(!bHad) + { + aDrawingRect.SetTop( nPosY ); + bHad = true; + } + + aDrawingRect.SetBottom( nPosY + pRowInfo[nArrY].nHeight - 1 ); + } + else if(bHad) + { + aRegion.Union(mpDev->PixelToLogic(aDrawingRect)); + bHad = false; + } + + nPosY += pRowInfo[nArrY].nHeight; + } + + if(bHad) + { + aRegion.Union(mpDev->PixelToLogic(aDrawingRect)); + } + + return aRegion; +} + +bool ScOutputData::SetChangedClip() +{ + tools::PolyPolygon aPoly; + + tools::Rectangle aDrawingRect; + aDrawingRect.SetLeft( nScrX ); + aDrawingRect.SetRight( nScrX+nScrW-1 ); + + bool bHad = false; + tools::Long nPosY = nScrY; + SCSIZE nArrY; + for (nArrY=1; nArrY+1bChanged ) + { + if (!bHad) + { + aDrawingRect.SetTop( nPosY ); + bHad = true; + } + aDrawingRect.SetBottom( nPosY + pRowInfo[nArrY].nHeight - 1 ); + } + else if (bHad) + { + aPoly.Insert( tools::Polygon( mpDev->PixelToLogic(aDrawingRect) ) ); + bHad = false; + } + nPosY += pRowInfo[nArrY].nHeight; + } + + if (bHad) + aPoly.Insert( tools::Polygon( mpDev->PixelToLogic(aDrawingRect) ) ); + + bool bRet = (aPoly.Count() != 0); + if (bRet) + mpDev->SetClipRegion(vcl::Region(aPoly)); + return bRet; +} + +void ScOutputData::FindChanged() +{ + SCCOL nX; + SCSIZE nArrY; + + bool bWasIdleEnabled = mpDoc->IsIdleEnabled(); + mpDoc->EnableIdle(false); + for (nArrY=0; nArrYMaxCol(), nCol2 = 0; + SCROW nRow1 = mpDoc->MaxRow(), nRow2 = 0; + bool bAnyDirty = false; + bool bAnyChanged = false; + + for (nArrY=0; nArrYcellInfo(nX).maCell; + + if (rCell.getType() != CELLTYPE_FORMULA) + continue; + + ScFormulaCell* pFCell = rCell.getFormula(); + if (pFCell->IsRunning()) + // still being interpreted. Skip it. + continue; + + bool bDirty = pFCell->GetDirty(); + bAnyChanged = bAnyChanged || pFCell->IsChanged(); + + if (bDirty) + { + if (!bAnyDirty) + { + ScProgress::CreateInterpretProgress(mpDoc); + bAnyDirty = true; + } + + ScAddress& rPos(pFCell->aPos); + nCol1 = std::min(rPos.Col(), nCol1); + nCol2 = std::max(rPos.Col(), nCol2); + nRow1 = std::min(rPos.Row(), nRow1); + nRow2 = std::max(rPos.Row(), nRow2); + + const SfxUInt32Item* pItem = mpDoc->GetAttr(rPos, ATTR_VALIDDATA); + const ScValidationData* pData = mpDoc->GetValidationEntry(pItem->GetValue()); + if (pData) + { + ScRefCellValue aCell(*mpDoc, rPos); + if (pData->IsDataValid(aCell, rPos)) + ScDetectiveFunc(*mpDoc, rPos.Tab()).DeleteCirclesAt(rPos.Col(), rPos.Row()); + } + } + } + } + + if (bAnyDirty || bAnyChanged) + { + if (bAnyDirty) + mpDoc->EnsureFormulaCellResults(ScRange(nCol1, nRow1, nTab, nCol2, nRow2, nTab), true); + + for (nArrY=0; nArrYcellInfo(nX).maCell; + + if (rCell.getType() != CELLTYPE_FORMULA) + continue; + + ScFormulaCell* pFCell = rCell.getFormula(); + if (pFCell->IsRunning()) + // still being interpreted. Skip it. + continue; + + if (!pFCell->IsChanged()) + // the result hasn't changed. Skip it. + continue; + + pThisRowInfo->bChanged = true; + if ( pThisRowInfo->cellInfo(nX).bMerged ) + { + SCSIZE nOverY = nArrY + 1; + while ( nOverYEnableIdle(bWasIdleEnabled); +} + +ReferenceMark ScOutputData::FillReferenceMark( SCCOL nRefStartX, SCROW nRefStartY, + SCCOL nRefEndX, SCROW nRefEndY, const Color& rColor) +{ + ReferenceMark aResult; + + PutInOrder( nRefStartX, nRefEndX ); + PutInOrder( nRefStartY, nRefEndY ); + + if ( nRefStartX == nRefEndX && nRefStartY == nRefEndY ) + mpDoc->ExtendMerge( nRefStartX, nRefStartY, nRefEndX, nRefEndY, nTab ); + + if ( nRefStartX <= nVisX2 && nRefEndX >= nVisX1 && + nRefStartY <= nVisY2 && nRefEndY >= nVisY1 ) + { + tools::Long nMinX = nScrX; + tools::Long nMinY = nScrY; + tools::Long nMaxX = nScrX + nScrW - 1; + tools::Long nMaxY = nScrY + nScrH - 1; + if ( bLayoutRTL ) + std::swap( nMinX, nMaxX ); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + bool bTop = false; + bool bBottom = false; + bool bLeft = false; + bool bRight = false; + + tools::Long nPosY = nScrY; + bool bNoStartY = ( nY1 < nRefStartY ); + bool bNoEndY = false; + for (SCSIZE nArrY=1; nArrYnRefStartY && bNoStartY) ) + { + nMinY = nPosY; + bTop = true; + } + if ( nY==nRefEndY ) + { + nMaxY = nPosY + pRowInfo[nArrY].nHeight - 2; + bBottom = true; + } + if ( nY>nRefEndY && bNoEndY ) + { + nMaxY = nPosY-2; + bBottom = true; + } + bNoStartY = ( nY < nRefStartY ); + bNoEndY = ( nY < nRefEndY ); + nPosY += pRowInfo[nArrY].nHeight; + } + + tools::Long nPosX = nScrX; + if ( bLayoutRTL ) + nPosX += nMirrorW - 1; // always in pixels + + for (SCCOL nX=nX1; nX<=nX2; nX++) + { + if ( nX==nRefStartX ) + { + nMinX = nPosX; + bLeft = true; + } + if ( nX==nRefEndX ) + { + nMaxX = nPosX + ( pRowInfo[0].basicCellInfo(nX).nWidth - 2 ) * nLayoutSign; + bRight = true; + } + nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign; + } + + if (bTop && bBottom && bLeft && bRight) + { + // mnPPT[XY] already has the factor aZoom[XY] in it. + aResult = ReferenceMark( nMinX / mnPPTX, + nMinY / mnPPTY, + ( nMaxX - nMinX ) / mnPPTX, + ( nMaxY - nMinY ) / mnPPTY, + nTab, + rColor ); + } + } + + return aResult; +} + +void ScOutputData::DrawRefMark( SCCOL nRefStartX, SCROW nRefStartY, + SCCOL nRefEndX, SCROW nRefEndY, + const Color& rColor, bool bHandle ) +{ + PutInOrder( nRefStartX, nRefEndX ); + PutInOrder( nRefStartY, nRefEndY ); + + if ( nRefStartX == nRefEndX && nRefStartY == nRefEndY ) + mpDoc->ExtendMerge( nRefStartX, nRefStartY, nRefEndX, nRefEndY, nTab ); + else if (mpDoc->HasAttrib(nRefEndX, nRefEndY, nTab, HasAttrFlags::Merged)) + mpDoc->ExtendMerge(nRefEndX, nRefEndY, nRefEndX, nRefEndY, nTab); + + if ( !(nRefStartX <= nVisX2 && nRefEndX >= nVisX1 && + nRefStartY <= nVisY2 && nRefEndY >= nVisY1) ) + return; + + tools::Long nMinX = nScrX; + tools::Long nMinY = nScrY; + tools::Long nMaxX = nScrX + nScrW - 1; + tools::Long nMaxY = nScrY + nScrH - 1; + if ( bLayoutRTL ) + std::swap( nMinX, nMaxX ); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + bool bTop = false; + bool bBottom = false; + bool bLeft = false; + bool bRight = false; + + tools::Long nPosY = nScrY; + bool bNoStartY = ( nY1 < nRefStartY ); + bool bNoEndY = false; + for (SCSIZE nArrY=1; nArrYnRefStartY && bNoStartY) ) + { + nMinY = nPosY; + bTop = true; + } + if ( nY==nRefEndY ) + { + nMaxY = nPosY + pRowInfo[nArrY].nHeight - 2; + bBottom = true; + } + if ( nY>nRefEndY && bNoEndY ) + { + nMaxY = nPosY-2; + bBottom = true; + } + bNoStartY = ( nY < nRefStartY ); + bNoEndY = ( nY < nRefEndY ); + nPosY += pRowInfo[nArrY].nHeight; + } + + tools::Long nPosX = nScrX; + if ( bLayoutRTL ) + nPosX += nMirrorW - 1; // always in pixels + + for (SCCOL nX=nX1; nX<=nX2; nX++) + { + if ( nX==nRefStartX ) + { + nMinX = nPosX; + bLeft = true; + } + if ( nX==nRefEndX ) + { + nMaxX = nPosX + ( pRowInfo[0].basicCellInfo(nX).nWidth - 2 ) * nLayoutSign; + bRight = true; + } + nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign; + } + + if ( nMaxX * nLayoutSign < nMinX * nLayoutSign || nMaxY < nMinY ) + return; + + mpDev->SetLineColor( rColor ); + if (bTop && bBottom && bLeft && bRight && !comphelper::LibreOfficeKit::isActive() ) + { + mpDev->SetFillColor(); + mpDev->DrawRect( tools::Rectangle( nMinX, nMinY, nMaxX, nMaxY ) ); + } + else if ( !comphelper::LibreOfficeKit::isActive() ) + { + if (bTop) + mpDev->DrawLine( Point( nMinX, nMinY ), Point( nMaxX, nMinY ) ); + if (bBottom) + mpDev->DrawLine( Point( nMinX, nMaxY ), Point( nMaxX, nMaxY ) ); + if (bLeft) + mpDev->DrawLine( Point( nMinX, nMinY ), Point( nMinX, nMaxY ) ); + if (bRight) + mpDev->DrawLine( Point( nMaxX, nMinY ), Point( nMaxX, nMaxY ) ); + } + if ( !bHandle || !bRight || !bBottom || comphelper::LibreOfficeKit::isActive() ) + return; + + mpDev->SetLineColor( rColor ); + mpDev->SetFillColor( rColor ); + + const sal_Int32 aRadius = 4; + + sal_Int32 aRectMaxX1 = nMaxX - nLayoutSign * aRadius; + sal_Int32 aRectMaxX2 = nMaxX + nLayoutSign; + sal_Int32 aRectMinX1 = nMinX - nLayoutSign; + sal_Int32 aRectMinX2 = nMinX + nLayoutSign * aRadius; + + sal_Int32 aRectMaxY1 = nMaxY - aRadius; + sal_Int32 aRectMaxY2 = nMaxY + 1; + sal_Int32 aRectMinY1 = nMinY - 1; + sal_Int32 aRectMinY2 = nMinY + aRadius; + + // Draw corner rectangles + tools::Rectangle aLowerRight( aRectMaxX1, aRectMaxY1, aRectMaxX2, aRectMaxY2 ); + tools::Rectangle aUpperLeft ( aRectMinX1, aRectMinY1, aRectMinX2, aRectMinY2 ); + tools::Rectangle aLowerLeft ( aRectMinX1, aRectMaxY1, aRectMinX2, aRectMaxY2 ); + tools::Rectangle aUpperRight( aRectMaxX1, aRectMinY1, aRectMaxX2, aRectMinY2 ); + + mpDev->DrawTransparent( tools::PolyPolygon( tools::Polygon( aLowerRight ) ), lclCornerRectTransparency ); + mpDev->DrawTransparent( tools::PolyPolygon( tools::Polygon( aUpperLeft ) ), lclCornerRectTransparency ); + mpDev->DrawTransparent( tools::PolyPolygon( tools::Polygon( aLowerLeft ) ), lclCornerRectTransparency ); + mpDev->DrawTransparent( tools::PolyPolygon( tools::Polygon( aUpperRight ) ), lclCornerRectTransparency ); +} + +void ScOutputData::DrawOneChange( SCCOL nRefStartX, SCROW nRefStartY, + SCCOL nRefEndX, SCROW nRefEndY, + const Color& rColor, sal_uInt16 nType ) +{ + PutInOrder( nRefStartX, nRefEndX ); + PutInOrder( nRefStartY, nRefEndY ); + + if ( nRefStartX == nRefEndX && nRefStartY == nRefEndY ) + mpDoc->ExtendMerge( nRefStartX, nRefStartY, nRefEndX, nRefEndY, nTab ); + + if ( !(nRefStartX <= nVisX2 + 1 && nRefEndX >= nVisX1 && + nRefStartY <= nVisY2 + 1 && nRefEndY >= nVisY1) ) // +1 because it touches next cells left/top + return; + + tools::Long nMinX = nScrX; + tools::Long nMinY = nScrY; + tools::Long nMaxX = nScrX+nScrW-1; + tools::Long nMaxY = nScrY+nScrH-1; + if ( bLayoutRTL ) + std::swap( nMinX, nMaxX ); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + bool bTop = false; + bool bBottom = false; + bool bLeft = false; + bool bRight = false; + + tools::Long nPosY = nScrY; + bool bNoStartY = ( nY1 < nRefStartY ); + bool bNoEndY = false; + for (SCSIZE nArrY=1; nArrYnRefStartY && bNoStartY) ) + { + nMinY = nPosY - 1; + bTop = true; + } + if ( nY==nRefEndY ) + { + nMaxY = nPosY + pRowInfo[nArrY].nHeight - 1; + bBottom = true; + } + if ( nY>nRefEndY && bNoEndY ) + { + nMaxY = nPosY - 1; + bBottom = true; + } + bNoStartY = ( nY < nRefStartY ); + bNoEndY = ( nY < nRefEndY ); + nPosY += pRowInfo[nArrY].nHeight; + } + + tools::Long nPosX = nScrX; + if ( bLayoutRTL ) + nPosX += nMirrorW - 1; // always in pixels + + for (SCCOL nX=nX1; nX<=nX2+1; nX++) + { + if ( nX==nRefStartX ) + { + nMinX = nPosX - nLayoutSign; + bLeft = true; + } + if ( nX==nRefEndX ) + { + nMaxX = nPosX + ( pRowInfo[0].basicCellInfo(nX).nWidth - 1 ) * nLayoutSign; + bRight = true; + } + nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign; + } + + if ( nMaxX * nLayoutSign < nMinX * nLayoutSign || nMaxY < nMinY ) + return; + + if ( nType == SC_CAT_DELETE_ROWS ) + bLeft = bRight = bBottom = false; //! thick lines??? + else if ( nType == SC_CAT_DELETE_COLS ) + bTop = bBottom = bRight = false; //! thick lines??? + + mpDev->SetLineColor( rColor ); + if (bTop && bBottom && bLeft && bRight) + { + mpDev->SetFillColor(); + mpDev->DrawRect( tools::Rectangle( nMinX, nMinY, nMaxX, nMaxY ) ); + } + else + { + if (bTop) + { + mpDev->DrawLine( Point( nMinX,nMinY ), Point( nMaxX,nMinY ) ); + if ( nType == SC_CAT_DELETE_ROWS ) + mpDev->DrawLine( Point( nMinX,nMinY+1 ), Point( nMaxX,nMinY+1 ) ); + } + if (bBottom) + mpDev->DrawLine( Point( nMinX,nMaxY ), Point( nMaxX,nMaxY ) ); + if (bLeft) + { + mpDev->DrawLine( Point( nMinX,nMinY ), Point( nMinX,nMaxY ) ); + if ( nType == SC_CAT_DELETE_COLS ) + mpDev->DrawLine( Point( nMinX+nLayoutSign,nMinY ), Point( nMinX+nLayoutSign,nMaxY ) ); + } + if (bRight) + mpDev->DrawLine( Point( nMaxX,nMinY ), Point( nMaxX,nMaxY ) ); + } + if ( bLeft && bTop ) + { + mpDev->SetLineColor(); + mpDev->SetFillColor( rColor ); + mpDev->DrawRect( tools::Rectangle( nMinX+nLayoutSign, nMinY+1, nMinX+3*nLayoutSign, nMinY+3 ) ); + } +} + +void ScOutputData::DrawChangeTrack() +{ + ScChangeTrack* pTrack = mpDoc->GetChangeTrack(); + ScChangeViewSettings* pSettings = mpDoc->GetChangeViewSettings(); + if ( !pTrack || !pTrack->GetFirst() || !pSettings || !pSettings->ShowChanges() ) + return; // nothing there or hidden + + ScActionColorChanger aColorChanger(*pTrack); + + // clipping happens from the outside + //! without clipping, only paint affected cells ??!??!? + + SCCOL nEndX = nX2; + SCROW nEndY = nY2; + if ( nEndX < mpDoc->MaxCol() ) ++nEndX; // also from the next cell since the mark + if ( nEndY < mpDoc->MaxRow() ) ++nEndY; // protrudes from the preceding cell + ScRange aViewRange( nX1, nY1, nTab, nEndX, nEndY, nTab ); + const ScChangeAction* pAction = pTrack->GetFirst(); + while (pAction) + { + if ( pAction->IsVisible() ) + { + ScChangeActionType eActionType = pAction->GetType(); + const ScBigRange& rBig = pAction->GetBigRange(); + if ( rBig.aStart.Tab() == nTab ) + { + ScRange aRange = rBig.MakeRange( *mpDoc ); + + if ( eActionType == SC_CAT_DELETE_ROWS ) + aRange.aEnd.SetRow( aRange.aStart.Row() ); + else if ( eActionType == SC_CAT_DELETE_COLS ) + aRange.aEnd.SetCol( aRange.aStart.Col() ); + + if ( aRange.Intersects( aViewRange ) && + ScViewUtil::IsActionShown( *pAction, *pSettings, *mpDoc ) ) + { + aColorChanger.Update( *pAction ); + Color aColor( aColorChanger.GetColor() ); + DrawOneChange( aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), aColor, sal::static_int_cast(eActionType) ); + + } + } + if ( eActionType == SC_CAT_MOVE && + static_cast(pAction)-> + GetFromRange().aStart.Tab() == nTab ) + { + ScRange aRange = static_cast(pAction)-> + GetFromRange().MakeRange( *mpDoc ); + if ( aRange.Intersects( aViewRange ) && + ScViewUtil::IsActionShown( *pAction, *pSettings, *mpDoc ) ) + { + aColorChanger.Update( *pAction ); + Color aColor( aColorChanger.GetColor() ); + DrawOneChange( aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), aColor, sal::static_int_cast(eActionType) ); + } + } + } + + pAction = pAction->GetNext(); + } +} + +void ScOutputData::DrawSparklines(vcl::RenderContext& rRenderContext) +{ + Size aOnePixel = rRenderContext.PixelToLogic(Size(1,1)); + tools::Long nOneXLogic = aOnePixel.Width(); + tools::Long nOneYLogic = aOnePixel.Height(); + + // See more about bWorksInPixels in ScOutputData::DrawGrid + bool bWorksInPixels = false; + if (eType == OUTTYPE_WINDOW) + bWorksInPixels = true; + + tools::Long nOneX = 1; + tools::Long nOneY = 1; + if (!bWorksInPixels) + { + nOneX = nOneXLogic; + nOneY = nOneYLogic; + } + + tools::Long nInitPosX = nScrX; + if ( bLayoutRTL ) + nInitPosX += nMirrorW - 1; // always in pixels + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + tools::Long nPosY = nScrY; + for (SCSIZE nArrY=1; nArrY+1bChanged ) + { + tools::Long nPosX = nInitPosX; + for (SCCOL nX=nX1; nX<=nX2; nX++) + { + ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nX); + bool bIsMerged = false; + + if ( nX==nX1 && pInfo->bHOverlapped && !pInfo->bVOverlapped ) + { + // find start of merged cell + bIsMerged = true; + SCROW nY = pRowInfo[nArrY].nRowNo; + SCCOL nMergeX = nX; + SCROW nMergeY = nY; + mpDoc->ExtendOverlapped( nMergeX, nMergeY, nX, nY, nTab ); + } + + std::shared_ptr pSparkline; + ScAddress aCurrentAddress(nX, pRowInfo[nArrY].nRowNo, nTab); + + if (!mpDoc->ColHidden(nX, nTab) && (pSparkline = mpDoc->GetSparkline(aCurrentAddress)) + && (bIsMerged || (!pInfo->bHOverlapped && !pInfo->bVOverlapped))) + { + const tools::Long nWidth = pRowInfo[0].basicCellInfo(nX).nWidth; + const tools::Long nHeight = pThisRowInfo->nHeight; + + Point aPoint(nPosX, nPosY); + Size aSize(nWidth, nHeight); + + sc::SparklineRenderer renderer(*mpDoc); + renderer.render(pSparkline, rRenderContext, tools::Rectangle(aPoint, aSize), nOneX, nOneY, double(aZoomX), double(aZoomY)); + } + + nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign; + } + } + nPosY += pThisRowInfo->nHeight; + } + +} + +//TODO: moggi Need to check if this can't be written simpler +void ScOutputData::DrawNoteMarks(vcl::RenderContext& rRenderContext) +{ + // cool#6911 draw the note indicator browser-side instead + if (comphelper::LibreOfficeKit::isActive()) + return; + + tools::Long nInitPosX = nScrX; + if ( bLayoutRTL ) + nInitPosX += nMirrorW - 1; // always in pixels + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + tools::Long nPosY = nScrY; + for (SCSIZE nArrY=1; nArrY+1bChanged ) + { + tools::Long nPosX = nInitPosX; + for (SCCOL nX=nX1; nX<=nX2; nX++) + { + ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nX); + bool bIsMerged = false; + + if ( nX==nX1 && pInfo->bHOverlapped && !pInfo->bVOverlapped ) + { + // find start of merged cell + bIsMerged = true; + SCROW nY = pRowInfo[nArrY].nRowNo; + SCCOL nMergeX = nX; + SCROW nMergeY = nY; + mpDoc->ExtendOverlapped( nMergeX, nMergeY, nX, nY, nTab ); + } + + if (!mpDoc->ColHidden(nX, nTab) && mpDoc->GetNote(nX, pRowInfo[nArrY].nRowNo, nTab) + && (bIsMerged || (!pInfo->bHOverlapped && !pInfo->bVOverlapped))) + { + + const bool bIsDarkBackground = SC_MOD()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor.IsDark(); + const Color aColor = pInfo->pBackground->GetColor(); + if ( aColor == COL_AUTO ? bIsDarkBackground : aColor.IsDark() ) + rRenderContext.SetLineColor(COL_WHITE); + else + rRenderContext.SetLineColor(COL_BLACK); + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + if ( mbUseStyleColor && rStyleSettings.GetHighContrastMode() ) + rRenderContext.SetFillColor( SC_MOD()->GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor ); + else + rRenderContext.SetFillColor( SC_MOD()->GetColorConfig().GetColorValue(svtools::CALCCOMMENTS).nColor ); + + tools::Long nMarkX = nPosX + ( pRowInfo[0].basicCellInfo(nX).nWidth - 2 ) * nLayoutSign; + if ( bIsMerged || pInfo->bMerged ) + { + // if merged, add widths of all cells + SCCOL nNextX = nX + 1; + while ( nNextX <= nX2 + 1 && pThisRowInfo->cellInfo(nNextX).bHOverlapped ) + { + nMarkX += pRowInfo[0].basicCellInfo(nNextX).nWidth * nLayoutSign; + ++nNextX; + } + } + // DPI/ZOOM 100/100 => 10, 100/50 => 7, 100/150 => 13 + // DPI/ZOOM 150/100 => 13, 150/50 => 8.5, 150/150 => 17.5 + const double nSize( rRenderContext.GetDPIScaleFactor() * aZoomX * 6 + 4); + Point aPoints[3]; + aPoints[0] = Point(nMarkX, nPosY); + aPoints[0].setX( bLayoutRTL ? aPoints[0].X() + nSize : aPoints[0].X() - nSize ); + aPoints[1] = Point(nMarkX, nPosY); + aPoints[2] = Point(nMarkX, nPosY + nSize); + tools::Polygon aPoly(3, aPoints); + + if ( bLayoutRTL ? ( nMarkX >= 0 ) : ( nMarkX < nScrX+nScrW ) ) + rRenderContext.DrawPolygon(aPoly); + } + + nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign; + } + } + nPosY += pThisRowInfo->nHeight; + } +} + +void ScOutputData::DrawFormulaMarks(vcl::RenderContext& rRenderContext) +{ + bool bFirst = true; + + tools::Long nInitPosX = nScrX; + if ( bLayoutRTL ) + nInitPosX += nMirrorW - 1; // always in pixels + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + tools::Long nPosY = nScrY; + for (SCSIZE nArrY=1; nArrY+1bChanged ) + { + tools::Long nPosX = nInitPosX; + for (SCCOL nX=nX1; nX<=nX2; nX++) + { + ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nX); + if (!mpDoc->ColHidden(nX, nTab) && !mpDoc->GetFormula(nX, pRowInfo[nArrY].nRowNo, nTab).isEmpty() + && (!pInfo->bHOverlapped && !pInfo->bVOverlapped)) + { + if (bFirst) + { + rRenderContext.SetLineColor(COL_WHITE); + + const StyleSettings& rStyleSettings = Application::GetSettings().GetStyleSettings(); + if ( mbUseStyleColor && rStyleSettings.GetHighContrastMode() ) + rRenderContext.SetFillColor( SC_MOD()->GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor ); + else + rRenderContext.SetFillColor(COL_LIGHTBLUE); + + bFirst = false; + } + + tools::Long nMarkX = nPosX; + tools::Long nMarkY = nPosY + pThisRowInfo->nHeight - 2; + if ( pInfo->bMerged ) + { + for (SCSIZE nNextY=nArrY+1; nNextY+1GetAttr(nX,nNextY,nTab,ATTR_MERGE_FLAG)->IsVerOverlapped(); + } + if (!bVOver) break; + nMarkY += pRowInfo[nNextY].nHeight; + } + } + // DPI/ZOOM 100/100 => 10, 100/50 => 7, 100/150 => 13 + // DPI/ZOOM 150/100 => 13, 150/50 => 8.5, 150/150 => 17.5 + const double nSize( rRenderContext.GetDPIScaleFactor() * aZoomX * 6 + 4); + Point aPoints[3]; + aPoints[0] = Point(nMarkX, nMarkY); + aPoints[0].setX( bLayoutRTL ? aPoints[0].X() - nSize : aPoints[0].X() + nSize ); + aPoints[1] = Point(nMarkX, nMarkY); + aPoints[2] = Point(nMarkX, nMarkY - nSize); + tools::Polygon aPoly(3, aPoints); + + if ( bLayoutRTL ? ( nMarkX >= 0 ) : ( nMarkX < nScrX+nScrW ) ) + rRenderContext.DrawPolygon(aPoly); + } + + nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign; + } + } + nPosY += pThisRowInfo->nHeight; + } +} + +void ScOutputData::AddPDFNotes() +{ + vcl::PDFExtOutDevData* pPDFData = dynamic_cast< vcl::PDFExtOutDevData* >( mpDev->GetExtOutDevData() ); + if ( !pPDFData || !pPDFData->GetIsExportNotes() ) + return; + + tools::Long nInitPosX = nScrX; + if ( bLayoutRTL ) + { + Size aOnePixel = mpDev->PixelToLogic(Size(1,1)); + tools::Long nOneX = aOnePixel.Width(); + nInitPosX += nMirrorW - nOneX; + } + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + tools::Long nPosY = nScrY; + for (SCSIZE nArrY=1; nArrY+1bChanged ) + { + tools::Long nPosX = nInitPosX; + for (SCCOL nX=nX1; nX<=nX2; nX++) + { + ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nX); + bool bIsMerged = false; + SCROW nY = pRowInfo[nArrY].nRowNo; + SCCOL nMergeX = nX; + SCROW nMergeY = nY; + + if ( nX==nX1 && pInfo->bHOverlapped && !pInfo->bVOverlapped ) + { + // find start of merged cell + bIsMerged = true; + mpDoc->ExtendOverlapped( nMergeX, nMergeY, nX, nY, nTab ); + // use origin's pCell for NotePtr test below + } + + if ( mpDoc->GetNote(nMergeX, nMergeY, nTab) && ( bIsMerged || + ( !pInfo->bHOverlapped && !pInfo->bVOverlapped ) ) ) + { + tools::Long nNoteWidth = static_cast( SC_CLIPMARK_SIZE * mnPPTX ); + tools::Long nNoteHeight = static_cast( SC_CLIPMARK_SIZE * mnPPTY ); + + tools::Long nMarkX = nPosX + ( pRowInfo[0].basicCellInfo(nX).nWidth - nNoteWidth ) * nLayoutSign; + if ( bIsMerged || pInfo->bMerged ) + { + // if merged, add widths of all cells + SCCOL nNextX = nX + 1; + while ( nNextX <= nX2 + 1 && pThisRowInfo->cellInfo(nNextX).bHOverlapped ) + { + nMarkX += pRowInfo[0].basicCellInfo(nNextX).nWidth * nLayoutSign; + ++nNextX; + } + } + if ( bLayoutRTL ? ( nMarkX >= 0 ) : ( nMarkX < nScrX+nScrW ) ) + { + tools::Rectangle aNoteRect( nMarkX, nPosY, nMarkX+nNoteWidth*nLayoutSign, nPosY+nNoteHeight ); + const ScPostIt* pNote = mpDoc->GetNote(nMergeX, nMergeY, nTab); + + // Note title is the cell address (as on printed note pages) + ScAddress aAddress( nMergeX, nMergeY, nTab ); + OUString aTitle(aAddress.Format(ScRefFlags::VALID, mpDoc, mpDoc->GetAddressConvention())); + + // Content has to be a simple string without line breaks + OUString aContent = pNote->GetText(); + aContent = aContent.replaceAll("\n", " "); + + vcl::PDFNote aNote; + aNote.Title = aTitle; + aNote.Contents = aContent; + pPDFData->CreateNote( aNoteRect, aNote ); + } + } + + nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign; + } + } + nPosY += pThisRowInfo->nHeight; + } +} + +void ScOutputData::DrawClipMarks() +{ + if (!bAnyClipped) + return; + + Color aArrowFillCol( SC_MOD()->GetColorConfig().GetColorValue(svtools::CALCTEXTOVERFLOW).nColor ); + const bool bIsDarkBackground = SC_MOD()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor.IsDark(); + + DrawModeFlags nOldDrawMode = mpDev->GetDrawMode(); + + tools::Long nInitPosX = nScrX; + if ( bLayoutRTL ) + nInitPosX += nMirrorW - 1; // always in pixels + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + tools::Rectangle aCellRect; + tools::Long nPosY = nScrY; + for (SCSIZE nArrY=1; nArrY+1bChanged ) + { + SCROW nY = pThisRowInfo->nRowNo; + tools::Long nPosX = nInitPosX; + for (SCCOL nX=nX1; nX<=nX2; nX++) + { + ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nX); + if (pInfo->nClipMark != ScClipMark::NONE) + { + if (pInfo->bHOverlapped || pInfo->bVOverlapped) + { + // merge origin may be outside of visible area - use document functions + + SCCOL nOverX = nX; + SCROW nOverY = nY; + tools::Long nStartPosX = nPosX; + tools::Long nStartPosY = nPosY; + + while ( nOverX > 0 && ( mpDoc->GetAttr( + nOverX, nOverY, nTab, ATTR_MERGE_FLAG )->GetValue() & ScMF::Hor ) ) + { + --nOverX; + nStartPosX -= nLayoutSign * static_cast( mpDoc->GetColWidth(nOverX,nTab) * mnPPTX ); + } + + while ( nOverY > 0 && ( mpDoc->GetAttr( + nOverX, nOverY, nTab, ATTR_MERGE_FLAG )->GetValue() & ScMF::Ver ) ) + { + --nOverY; + nStartPosY -= nLayoutSign * static_cast( mpDoc->GetRowHeight(nOverY,nTab) * mnPPTY ); + } + + tools::Long nOutWidth = static_cast( mpDoc->GetColWidth(nOverX,nTab) * mnPPTX ); + tools::Long nOutHeight = static_cast( mpDoc->GetRowHeight(nOverY,nTab) * mnPPTY ); + + const ScMergeAttr* pMerge = mpDoc->GetAttr( nOverX, nOverY, nTab, ATTR_MERGE ); + SCCOL nCountX = pMerge->GetColMerge(); + for (SCCOL i=1; iGetColWidth(nOverX+i,nTab) * mnPPTX; + SCROW nCountY = pMerge->GetRowMerge(); + nOutHeight += mpDoc->GetScaledRowHeight( nOverY+1, nOverY+nCountY-1, nTab, mnPPTY); + + if ( bLayoutRTL ) + nStartPosX -= nOutWidth - 1; + aCellRect = tools::Rectangle( Point( nStartPosX, nStartPosY ), Size( nOutWidth, nOutHeight ) ); + } + else + { + tools::Long nOutWidth = pRowInfo[0].basicCellInfo(nX).nWidth; + tools::Long nOutHeight = pThisRowInfo->nHeight; + + if ( pInfo->bMerged && pInfo->pPatternAttr ) + { + SCCOL nOverX = nX; + SCROW nOverY = nY; + const ScMergeAttr* pMerge = + &pInfo->pPatternAttr->GetItem(ATTR_MERGE); + SCCOL nCountX = pMerge->GetColMerge(); + for (SCCOL i=1; iGetColWidth(nOverX+i,nTab) * mnPPTX; + SCROW nCountY = pMerge->GetRowMerge(); + nOutHeight += mpDoc->GetScaledRowHeight( nOverY+1, nOverY+nCountY-1, nTab, mnPPTY); + } + + tools::Long nStartPosX = nPosX; + if ( bLayoutRTL ) + nStartPosX -= nOutWidth - 1; + // #i80447# create aCellRect from two points in case nOutWidth is 0 + aCellRect = tools::Rectangle( Point( nStartPosX, nPosY ), + Point( nStartPosX+nOutWidth-1, nPosY+nOutHeight-1 ) ); + } + + aCellRect.AdjustBottom( -1 ); // don't paint over the cell grid + if ( bLayoutRTL ) + aCellRect.AdjustLeft(1 ); + else + aCellRect.AdjustRight( -1 ); + + tools::Long nMarkPixel = static_cast( SC_CLIPMARK_SIZE * mnPPTX ); + Size aMarkSize( nMarkPixel, (nMarkPixel-1)*2 ); + + const Color aColor = pInfo->pBackground ? pInfo->pBackground->GetColor() : COL_AUTO; + if ( aColor == COL_AUTO ? bIsDarkBackground : aColor.IsDark() ) + mpDev->SetDrawMode( nOldDrawMode | DrawModeFlags::WhiteLine ); + else + mpDev->SetDrawMode( nOldDrawMode | DrawModeFlags::BlackLine ); + + if (bVertical) + { + if (pInfo->nClipMark & (bLayoutRTL ? ScClipMark::Bottom : ScClipMark::Top)) + { + // visually top + tools::Rectangle aMarkRect = aCellRect; + aMarkRect.SetBottom(aCellRect.Top() + nMarkPixel - 1); + SvxFont::DrawArrow(*mpDev, aMarkRect, aMarkSize, aArrowFillCol, true, true); + } + if (pInfo->nClipMark & (bLayoutRTL ? ScClipMark::Top : ScClipMark::Bottom)) + { + // visually bottom + tools::Rectangle aMarkRect = aCellRect; + aMarkRect.SetTop(aCellRect.Bottom() + nMarkPixel + 1); + SvxFont::DrawArrow(*mpDev, aMarkRect, aMarkSize, aArrowFillCol, false, + true); + } + } + else + { + if (pInfo->nClipMark & (bLayoutRTL ? ScClipMark::Right : ScClipMark::Left)) + { + // visually left + tools::Rectangle aMarkRect = aCellRect; + aMarkRect.SetRight(aCellRect.Left() + nMarkPixel - 1); + SvxFont::DrawArrow(*mpDev, aMarkRect, aMarkSize, aArrowFillCol, true, + false); + } + if (pInfo->nClipMark & (bLayoutRTL ? ScClipMark::Left : ScClipMark::Right)) + { + // visually right + tools::Rectangle aMarkRect = aCellRect; + aMarkRect.SetLeft(aCellRect.Right() - nMarkPixel + 1); + SvxFont::DrawArrow(*mpDev, aMarkRect, aMarkSize, aArrowFillCol, false, + false); + } + } + } + nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign; + } + } + nPosY += pThisRowInfo->nHeight; + } + + mpDev->SetDrawMode(nOldDrawMode); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/output2.cxx b/sc/source/ui/view/output2.cxx new file mode 100644 index 0000000000..d419981d8e --- /dev/null +++ b/sc/source/ui/view/output2.cxx @@ -0,0 +1,5248 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +#include + +using namespace com::sun::star; + +//! Merge Autofilter width with column.cxx +#define DROPDOWN_BITMAP_SIZE 18 + +#define DRAWTEXT_MAX 32767 + +const sal_uInt16 SC_SHRINKAGAIN_MAX = 7; +constexpr auto HMM_PER_TWIPS = o3tl::convert(1.0, o3tl::Length::twip, o3tl::Length::mm100); + +class ScDrawStringsVars +{ + ScOutputData* pOutput; // connection + + const ScPatternAttr* pPattern; // attribute + const SfxItemSet* pCondSet; // from conditional formatting + + vcl::Font aFont; // created from attributes + FontMetric aMetric; + tools::Long nAscentPixel; // always pixels + SvxCellOrientation eAttrOrient; + SvxCellHorJustify eAttrHorJust; + SvxCellVerJustify eAttrVerJust; + SvxCellJustifyMethod eAttrHorJustMethod; + const SvxMarginItem* pMargin; + sal_uInt16 nIndent; + bool bRotated; + + OUString aString; // contents + Size aTextSize; + tools::Long nOriginalWidth; + tools::Long nMaxDigitWidth; + tools::Long nSignWidth; + tools::Long nDotWidth; + tools::Long nExpWidth; + + ScRefCellValue maLastCell; + sal_uLong nValueFormat; + bool bLineBreak; + bool bRepeat; + bool bShrink; + + bool bPixelToLogic; + bool bCellContrast; + + Color aBackConfigColor; // used for ScPatternAttr::GetFont calls + Color aTextConfigColor; + sal_Int32 nRepeatPos; + sal_Unicode nRepeatChar; + +public: + ScDrawStringsVars(ScOutputData* pData, bool bPTL); + + // SetPattern = ex-SetVars + // SetPatternSimple: without Font + + void SetPattern( + const ScPatternAttr* pNew, const SfxItemSet* pSet, const ScRefCellValue& rCell, + SvtScriptType nScript ); + + void SetPatternSimple( const ScPatternAttr* pNew, const SfxItemSet* pSet ); + + bool SetText( const ScRefCellValue& rCell ); // TRUE -> drop pOldPattern + void SetHashText(); + bool SetTextToWidthOrHash( ScRefCellValue& rCell, tools::Long nWidth ); + void SetAutoText( const OUString& rAutoText ); + + SvxCellOrientation GetOrient() const { return eAttrOrient; } + SvxCellHorJustify GetHorJust() const { return eAttrHorJust; } + SvxCellVerJustify GetVerJust() const { return eAttrVerJust; } + SvxCellJustifyMethod GetHorJustMethod() const { return eAttrHorJustMethod; } + const SvxMarginItem* GetMargin() const { return pMargin; } + + sal_uInt16 GetLeftTotal() const { return pMargin->GetLeftMargin() + nIndent; } + sal_uInt16 GetRightTotal() const { return pMargin->GetRightMargin() + nIndent; } + + const OUString& GetString() const { return aString; } + const Size& GetTextSize() const { return aTextSize; } + tools::Long GetOriginalWidth() const { return nOriginalWidth; } + tools::Long GetFmtTextWidth(const OUString& rString); + + // Get the effective number format, including formula result types. + // This assumes that a formula cell has already been calculated. + sal_uLong GetResultValueFormat() const { return nValueFormat;} + + bool GetLineBreak() const { return bLineBreak; } + bool IsRepeat() const { return bRepeat; } + bool IsShrink() const { return bShrink; } + void RepeatToFill( tools::Long nColWidth ); + + tools::Long GetAscent() const { return nAscentPixel; } + bool IsRotated() const { return bRotated; } + + void SetShrinkScale( tools::Long nScale, SvtScriptType nScript ); + + bool HasCondHeight() const { return pCondSet && SfxItemState::SET == + pCondSet->GetItemState( ATTR_FONT_HEIGHT ); } + + bool HasEditCharacters() const; + + // ScOutputData::LayoutStrings() usually triggers a number of calls that require + // to lay out the text, which is relatively slow, so cache that operation. + const SalLayoutGlyphs* GetLayoutGlyphs(const OUString& rString) const + { + return SalLayoutGlyphsCache::self()->GetLayoutGlyphs(pOutput->pFmtDevice, rString); + } + +private: + tools::Long GetMaxDigitWidth(); // in logic units + tools::Long GetSignWidth(); + tools::Long GetDotWidth(); + tools::Long GetExpWidth(); + void TextChanged(); +}; + +ScDrawStringsVars::ScDrawStringsVars(ScOutputData* pData, bool bPTL) : + pOutput ( pData ), + pPattern ( nullptr ), + pCondSet ( nullptr ), + nAscentPixel(0), + eAttrOrient ( SvxCellOrientation::Standard ), + eAttrHorJust( SvxCellHorJustify::Standard ), + eAttrVerJust( SvxCellVerJustify::Bottom ), + eAttrHorJustMethod( SvxCellJustifyMethod::Auto ), + pMargin ( nullptr ), + nIndent ( 0 ), + bRotated ( false ), + nOriginalWidth( 0 ), + nMaxDigitWidth( 0 ), + nSignWidth( 0 ), + nDotWidth( 0 ), + nExpWidth( 0 ), + nValueFormat( 0 ), + bLineBreak ( false ), + bRepeat ( false ), + bShrink ( false ), + bPixelToLogic( bPTL ), + nRepeatPos( -1 ), + nRepeatChar( 0x0 ) +{ + ScModule* pScMod = SC_MOD(); + bCellContrast = pOutput->mbUseStyleColor && + Application::GetSettings().GetStyleSettings().GetHighContrastMode(); + + const svtools::ColorConfig& rColorConfig = pScMod->GetColorConfig(); + aBackConfigColor = rColorConfig.GetColorValue(svtools::DOCCOLOR).nColor; + aTextConfigColor = rColorConfig.GetColorValue(svtools::FONTCOLOR).nColor; +} + +void ScDrawStringsVars::SetShrinkScale( tools::Long nScale, SvtScriptType nScript ) +{ + // text remains valid, size is updated + + OutputDevice* pDev = pOutput->mpDev; + OutputDevice* pRefDevice = pOutput->mpRefDevice; + OutputDevice* pFmtDevice = pOutput->pFmtDevice; + + // call GetFont with a modified fraction, use only the height + + Fraction aFraction( nScale, 100 ); + if ( !bPixelToLogic ) + aFraction *= pOutput->aZoomY; + vcl::Font aTmpFont; + pPattern->fillFontOnly(aTmpFont, pFmtDevice, &aFraction, pCondSet, nScript); + // only need font height + tools::Long nNewHeight = aTmpFont.GetFontHeight(); + if ( nNewHeight > 0 ) + aFont.SetFontHeight( nNewHeight ); + + // set font and dependent variables as in SetPattern + + pDev->SetFont( aFont ); + if ( pFmtDevice != pDev ) + pFmtDevice->SetFont( aFont ); + + aMetric = pFmtDevice->GetFontMetric(); + if ( pFmtDevice->GetOutDevType() == OUTDEV_PRINTER && aMetric.GetInternalLeading() == 0 ) + { + OutputDevice* pDefaultDev = Application::GetDefaultDevice(); + MapMode aOld = pDefaultDev->GetMapMode(); + pDefaultDev->SetMapMode( pFmtDevice->GetMapMode() ); + aMetric = pDefaultDev->GetFontMetric( aFont ); + pDefaultDev->SetMapMode( aOld ); + } + + nAscentPixel = aMetric.GetAscent(); + if ( bPixelToLogic ) + nAscentPixel = pRefDevice->LogicToPixel( Size( 0, nAscentPixel ) ).Height(); + + SetAutoText( aString ); // same text again, to get text size +} + +namespace { + +template +EnumType lcl_GetValue(const ScPatternAttr& rPattern, sal_uInt16 nWhich, const SfxItemSet* pCondSet) +{ + const ItemType& rItem = static_cast(rPattern.GetItem(nWhich, pCondSet)); + return static_cast(rItem.GetValue()); +} + +bool lcl_GetBoolValue(const ScPatternAttr& rPattern, sal_uInt16 nWhich, const SfxItemSet* pCondSet) +{ + return lcl_GetValue(rPattern, nWhich, pCondSet); +} + +} + +static bool lcl_isNumberFormatText(const ScDocument* pDoc, SCCOL nCellX, SCROW nCellY, SCTAB nTab ) +{ + sal_uInt32 nCurrentNumberFormat = pDoc->GetNumberFormat( nCellX, nCellY, nTab ); + SvNumberFormatter* pNumberFormatter = pDoc->GetFormatTable(); + return pNumberFormatter->GetType( nCurrentNumberFormat ) == SvNumFormatType::TEXT; +} + +void ScDrawStringsVars::SetPattern( + const ScPatternAttr* pNew, const SfxItemSet* pSet, const ScRefCellValue& rCell, + SvtScriptType nScript ) +{ + nMaxDigitWidth = 0; + nSignWidth = 0; + nDotWidth = 0; + nExpWidth = 0; + + pPattern = pNew; + pCondSet = pSet; + + // evaluate pPattern + + OutputDevice* pDev = pOutput->mpDev; + OutputDevice* pRefDevice = pOutput->mpRefDevice; + OutputDevice* pFmtDevice = pOutput->pFmtDevice; + + // font + + ScAutoFontColorMode eColorMode; + if ( pOutput->mbUseStyleColor ) + { + if ( pOutput->mbForceAutoColor ) + eColorMode = bCellContrast ? ScAutoFontColorMode::IgnoreAll : ScAutoFontColorMode::IgnoreFont; + else + eColorMode = bCellContrast ? ScAutoFontColorMode::IgnoreBack : ScAutoFontColorMode::Display; + } + else + eColorMode = ScAutoFontColorMode::Print; + + if (bPixelToLogic) + pPattern->fillFont(aFont, eColorMode, pFmtDevice, nullptr, pCondSet, nScript, &aBackConfigColor, &aTextConfigColor); + else + pPattern->fillFont(aFont, eColorMode, pFmtDevice, &pOutput->aZoomY, pCondSet, nScript, &aBackConfigColor, &aTextConfigColor ); + + aFont.SetAlignment(ALIGN_BASELINE); + + // orientation + + eAttrOrient = pPattern->GetCellOrientation( pCondSet ); + + // alignment + + eAttrHorJust = pPattern->GetItem( ATTR_HOR_JUSTIFY, pCondSet ).GetValue(); + + eAttrVerJust = pPattern->GetItem( ATTR_VER_JUSTIFY, pCondSet ).GetValue(); + if ( eAttrVerJust == SvxCellVerJustify::Standard ) + eAttrVerJust = SvxCellVerJustify::Bottom; + + // justification method + + eAttrHorJustMethod = lcl_GetValue(*pPattern, ATTR_HOR_JUSTIFY_METHOD, pCondSet); + + // line break + + bLineBreak = pPattern->GetItem( ATTR_LINEBREAK, pCondSet ).GetValue(); + + // handle "repeat" alignment + + bRepeat = ( eAttrHorJust == SvxCellHorJustify::Repeat ); + if ( bRepeat ) + { + // "repeat" disables rotation (before constructing the font) + eAttrOrient = SvxCellOrientation::Standard; + + // #i31843# "repeat" with "line breaks" is treated as default alignment (but rotation is still disabled) + if ( bLineBreak ) + eAttrHorJust = SvxCellHorJustify::Standard; + } + + sal_Int16 nRot; + switch (eAttrOrient) + { + case SvxCellOrientation::Standard: + nRot = 0; + bRotated = pPattern->GetItem( ATTR_ROTATE_VALUE, pCondSet ).GetValue() != 0_deg100 && + !bRepeat; + break; + case SvxCellOrientation::Stacked: + nRot = 0; + bRotated = false; + break; + case SvxCellOrientation::TopBottom: + nRot = 2700; + bRotated = false; + break; + case SvxCellOrientation::BottomUp: + nRot = 900; + bRotated = false; + break; + default: + OSL_FAIL("Invalid SvxCellOrientation value"); + nRot = 0; + bRotated = false; + break; + } + aFont.SetOrientation( Degree10(nRot) ); + + // syntax mode + + if (pOutput->mbSyntaxMode) + pOutput->SetSyntaxColor(&aFont, rCell); + + // There is no cell attribute for kerning, default is kerning OFF, all + // kerning is stored at an EditText object that is drawn using EditEngine. + // See also matching kerning cases in ScColumn::GetNeededSize and + // ScColumn::GetOptimalColWidth. + aFont.SetKerning(FontKerning::NONE); + + pDev->SetFont( aFont ); + if ( pFmtDevice != pDev ) + pFmtDevice->SetFont( aFont ); + + aMetric = pFmtDevice->GetFontMetric(); + + // if there is the leading 0 on a printer device, we have problems + // -> take metric from the screen (as for EditEngine!) + if ( pFmtDevice->GetOutDevType() == OUTDEV_PRINTER && aMetric.GetInternalLeading() == 0 ) + { + OutputDevice* pDefaultDev = Application::GetDefaultDevice(); + MapMode aOld = pDefaultDev->GetMapMode(); + pDefaultDev->SetMapMode( pFmtDevice->GetMapMode() ); + aMetric = pDefaultDev->GetFontMetric( aFont ); + pDefaultDev->SetMapMode( aOld ); + } + + nAscentPixel = aMetric.GetAscent(); + if ( bPixelToLogic ) + nAscentPixel = pRefDevice->LogicToPixel( Size( 0, nAscentPixel ) ).Height(); + + Color aULineColor( pPattern->GetItem( ATTR_FONT_UNDERLINE, pCondSet ).GetColor() ); + pDev->SetTextLineColor( aULineColor ); + + Color aOLineColor( pPattern->GetItem( ATTR_FONT_OVERLINE, pCondSet ).GetColor() ); + pDev->SetOverlineColor( aOLineColor ); + + // number format + + nValueFormat = pPattern->GetNumberFormat( pOutput->mpDoc->GetFormatTable(), pCondSet ); + + // margins + pMargin = &pPattern->GetItem( ATTR_MARGIN, pCondSet ); + if ( eAttrHorJust == SvxCellHorJustify::Left || eAttrHorJust == SvxCellHorJustify::Right ) + nIndent = pPattern->GetItem( ATTR_INDENT, pCondSet ).GetValue(); + else + nIndent = 0; + + // "Shrink to fit" + + bShrink = pPattern->GetItem( ATTR_SHRINKTOFIT, pCondSet ).GetValue(); + + // at least the text size needs to be retrieved again + //! differentiate and do not get the text again from the number format? + maLastCell.clear(); +} + +void ScDrawStringsVars::SetPatternSimple( const ScPatternAttr* pNew, const SfxItemSet* pSet ) +{ + nMaxDigitWidth = 0; + nSignWidth = 0; + nDotWidth = 0; + nExpWidth = 0; + + // Is called, when the font variables do not change (!StringDiffer) + + pPattern = pNew; + pCondSet = pSet; //! is this needed ??? + + // number format + + sal_uLong nOld = nValueFormat; + nValueFormat = pPattern->GetNumberFormat( pOutput->mpDoc->GetFormatTable(), pCondSet ); + + if (nValueFormat != nOld) + maLastCell.clear(); // always reformat + + // margins + + pMargin = &pPattern->GetItem( ATTR_MARGIN, pCondSet ); + + if ( eAttrHorJust == SvxCellHorJustify::Left ) + nIndent = pPattern->GetItem( ATTR_INDENT, pCondSet ).GetValue(); + else + nIndent = 0; + + // "Shrink to fit" + + bShrink = pPattern->GetItem( ATTR_SHRINKTOFIT, pCondSet ).GetValue(); +} + +static bool SameValue( const ScRefCellValue& rCell, const ScRefCellValue& rOldCell ) +{ + return rOldCell.getType() == CELLTYPE_VALUE && rCell.getType() == CELLTYPE_VALUE && + rCell.getDouble() == rOldCell.getDouble(); +} + +bool ScDrawStringsVars::SetText( const ScRefCellValue& rCell ) +{ + bool bChanged = false; + + if (!rCell.isEmpty()) + { + if (!SameValue(rCell, maLastCell)) + { + maLastCell = rCell; // store cell + + const Color* pColor; + sal_uLong nFormat = nValueFormat; + aString = ScCellFormat::GetString( rCell, + nFormat, &pColor, + *pOutput->mpDoc->GetFormatTable(), + *pOutput->mpDoc, + pOutput->mbShowNullValues, + pOutput->mbShowFormulas, + true ); + if ( nFormat ) + { + nRepeatPos = aString.indexOf( 0x1B ); + if ( nRepeatPos != -1 ) + { + if (nRepeatPos + 1 == aString.getLength()) + nRepeatPos = -1; + else + { + nRepeatChar = aString[ nRepeatPos + 1 ]; + // delete placeholder and char to repeat + aString = aString.replaceAt( nRepeatPos, 2, u"" ); + // Do not cache/reuse a repeat-filled string, column + // widths or fonts or sizes may differ. + maLastCell.clear(); + } + } + } + else + { + nRepeatPos = -1; + nRepeatChar = 0x0; + } + if (aString.getLength() > DRAWTEXT_MAX) + aString = aString.copy(0, DRAWTEXT_MAX); + + if ( pColor && !pOutput->mbSyntaxMode && !( pOutput->mbUseStyleColor && pOutput->mbForceAutoColor ) ) + { + OutputDevice* pDev = pOutput->mpDev; + aFont.SetColor(*pColor); + pDev->SetFont( aFont ); // only for output + bChanged = true; + maLastCell.clear(); // next time return here again + } + + TextChanged(); + } + // otherwise keep string/size + } + else + { + aString.clear(); + maLastCell.clear(); + aTextSize = Size(0,0); + nOriginalWidth = 0; + } + + return bChanged; +} + +void ScDrawStringsVars::SetHashText() +{ + SetAutoText("###"); +} + +void ScDrawStringsVars::RepeatToFill( tools::Long nColWidth ) +{ + if ( nRepeatPos == -1 || nRepeatPos > aString.getLength() ) + return; + + tools::Long nCharWidth = GetFmtTextWidth(OUString(nRepeatChar)); + + if ( nCharWidth < 1 || (bPixelToLogic && nCharWidth < pOutput->mpRefDevice->PixelToLogic(Size(1,0)).Width()) ) + return; + + // Are there restrictions on the cell type we should filter out here ? + tools::Long nTextWidth = aTextSize.Width(); + if ( bPixelToLogic ) + { + nColWidth = pOutput->mpRefDevice->PixelToLogic(Size(nColWidth,0)).Width(); + nTextWidth = pOutput->mpRefDevice->PixelToLogic(Size(nTextWidth,0)).Width(); + } + + tools::Long nSpaceToFill = nColWidth - nTextWidth; + if ( nSpaceToFill <= nCharWidth ) + return; + + sal_Int32 nCharsToInsert = nSpaceToFill / nCharWidth; + OUStringBuffer aFill(nCharsToInsert); + comphelper::string::padToLength(aFill, nCharsToInsert, nRepeatChar); + aString = aString.replaceAt( nRepeatPos, 0, aFill ); + TextChanged(); +} + +bool ScDrawStringsVars::SetTextToWidthOrHash( ScRefCellValue& rCell, tools::Long nWidth ) +{ + // #i113045# do the single-character width calculations in logic units + if (bPixelToLogic) + nWidth = pOutput->mpRefDevice->PixelToLogic(Size(nWidth,0)).Width(); + + CellType eType = rCell.getType(); + if (eType != CELLTYPE_VALUE && eType != CELLTYPE_FORMULA) + // must be a value or formula cell. + return false; + + if (eType == CELLTYPE_FORMULA) + { + ScFormulaCell* pFCell = rCell.getFormula(); + if (pFCell->GetErrCode() != FormulaError::NONE || pOutput->mbShowFormulas) + { + SetHashText(); // If the error string doesn't fit, always use "###". Also for "display formulas" (#i116691#) + return true; + } + // If it's formula, the result must be a value. + if (!pFCell->IsValue()) + return false; + } + + sal_uLong nFormat = GetResultValueFormat(); + if ((nFormat % SV_COUNTRY_LANGUAGE_OFFSET) != 0) + { + // Not 'General' number format. Set hash text and bail out. + SetHashText(); + return true; + } + + double fVal = rCell.getValue(); + + const SvNumberformat* pNumFormat = pOutput->mpDoc->GetFormatTable()->GetEntry(nFormat); + if (!pNumFormat) + return false; + + tools::Long nMaxDigit = GetMaxDigitWidth(); + if (!nMaxDigit) + return false; + + sal_uInt16 nNumDigits = static_cast(nWidth / nMaxDigit); + { + OUString sTempOut(aString); + if (!pNumFormat->GetOutputString(fVal, nNumDigits, sTempOut)) + { + aString = sTempOut; + // Failed to get output string. Bail out. + return false; + } + aString = sTempOut; + } + sal_uInt8 nSignCount = 0, nDecimalCount = 0, nExpCount = 0; + sal_Int32 nLen = aString.getLength(); + sal_Unicode cDecSep = ScGlobal::getLocaleData().getLocaleItem().decimalSeparator[0]; + for( sal_Int32 i = 0; i < nLen; ++i ) + { + sal_Unicode c = aString[i]; + if (c == '-') + ++nSignCount; + else if (c == cDecSep) + ++nDecimalCount; + else if (c == 'E') + ++nExpCount; + } + + // #i112250# A small value might be formatted as "0" when only counting the digits, + // but fit into the column when considering the smaller width of the decimal separator. + if (aString == "0" && fVal != 0.0) + nDecimalCount = 1; + + if (nDecimalCount) + nWidth += (nMaxDigit - GetDotWidth()) * nDecimalCount; + if (nSignCount) + nWidth += (nMaxDigit - GetSignWidth()) * nSignCount; + if (nExpCount) + nWidth += (nMaxDigit - GetExpWidth()) * nExpCount; + + if (nDecimalCount || nSignCount || nExpCount) + { + // Re-calculate. + nNumDigits = static_cast(nWidth / nMaxDigit); + OUString sTempOut(aString); + if (!pNumFormat->GetOutputString(fVal, nNumDigits, sTempOut)) + { + aString = sTempOut; + // Failed to get output string. Bail out. + return false; + } + aString = sTempOut; + } + + tools::Long nActualTextWidth = GetFmtTextWidth(aString); + if (nActualTextWidth > nWidth) + { + // Even after the decimal adjustment the text doesn't fit. Give up. + SetHashText(); + return true; + } + + TextChanged(); + maLastCell.clear(); // #i113022# equal cell and format in another column may give different string + return false; +} + +void ScDrawStringsVars::SetAutoText( const OUString& rAutoText ) +{ + aString = rAutoText; + + OutputDevice* pRefDevice = pOutput->mpRefDevice; + OutputDevice* pFmtDevice = pOutput->pFmtDevice; + aTextSize.setWidth( GetFmtTextWidth( aString ) ); + aTextSize.setHeight( pFmtDevice->GetTextHeight() ); + + if ( !pRefDevice->GetConnectMetaFile() || pRefDevice->GetOutDevType() == OUTDEV_PRINTER ) + { + double fMul = pOutput->GetStretch(); + aTextSize.setWidth( static_cast(aTextSize.Width() / fMul + 0.5) ); + } + + aTextSize.setHeight( aMetric.GetAscent() + aMetric.GetDescent() ); + if ( GetOrient() != SvxCellOrientation::Standard ) + { + tools::Long nTemp = aTextSize.Height(); + aTextSize.setHeight( aTextSize.Width() ); + aTextSize.setWidth( nTemp ); + } + + nOriginalWidth = aTextSize.Width(); + if ( bPixelToLogic ) + aTextSize = pRefDevice->LogicToPixel( aTextSize ); + + maLastCell.clear(); // the same text may fit in the next cell +} + +tools::Long ScDrawStringsVars::GetMaxDigitWidth() +{ + if (nMaxDigitWidth > 0) + return nMaxDigitWidth; + + for (char i = 0; i < 10; ++i) + { + char cDigit = '0' + i; + // Do not cache this with GetFmtTextWidth(), nMaxDigitWidth is already cached. + tools::Long n = pOutput->pFmtDevice->GetTextWidth(OUString(cDigit)); + nMaxDigitWidth = ::std::max(nMaxDigitWidth, n); + } + return nMaxDigitWidth; +} + +tools::Long ScDrawStringsVars::GetSignWidth() +{ + if (nSignWidth > 0) + return nSignWidth; + + nSignWidth = pOutput->pFmtDevice->GetTextWidth(OUString('-')); + return nSignWidth; +} + +tools::Long ScDrawStringsVars::GetDotWidth() +{ + if (nDotWidth > 0) + return nDotWidth; + + const OUString& sep = ScGlobal::getLocaleData().getLocaleItem().decimalSeparator; + nDotWidth = pOutput->pFmtDevice->GetTextWidth(sep); + return nDotWidth; +} + +tools::Long ScDrawStringsVars::GetExpWidth() +{ + if (nExpWidth > 0) + return nExpWidth; + + nExpWidth = pOutput->pFmtDevice->GetTextWidth(OUString('E')); + return nExpWidth; +} + +tools::Long ScDrawStringsVars::GetFmtTextWidth( const OUString& rString ) +{ + return pOutput->pFmtDevice->GetTextWidth( rString, 0, -1, nullptr, GetLayoutGlyphs( rString )); +} + +void ScDrawStringsVars::TextChanged() +{ + OutputDevice* pRefDevice = pOutput->mpRefDevice; + OutputDevice* pFmtDevice = pOutput->pFmtDevice; + aTextSize.setWidth( GetFmtTextWidth( aString ) ); + aTextSize.setHeight( pFmtDevice->GetTextHeight() ); + + if ( !pRefDevice->GetConnectMetaFile() || pRefDevice->GetOutDevType() == OUTDEV_PRINTER ) + { + double fMul = pOutput->GetStretch(); + aTextSize.setWidth( static_cast(aTextSize.Width() / fMul + 0.5) ); + } + + aTextSize.setHeight( aMetric.GetAscent() + aMetric.GetDescent() ); + if ( GetOrient() != SvxCellOrientation::Standard ) + { + tools::Long nTemp = aTextSize.Height(); + aTextSize.setHeight( aTextSize.Width() ); + aTextSize.setWidth( nTemp ); + } + + nOriginalWidth = aTextSize.Width(); + if ( bPixelToLogic ) + aTextSize = pRefDevice->LogicToPixel( aTextSize ); +} + +bool ScDrawStringsVars::HasEditCharacters() const +{ + for (sal_Int32 nIdx = 0; nIdx < aString.getLength(); ++nIdx) + { + switch(aString[nIdx]) + { + case CHAR_NBSP: + // tdf#122676: Ignore CHAR_NBSP (this is thousand separator in any number) + // if repeat character is set + if (nRepeatPos < 0) + return true; + break; + case CHAR_SHY: + case CHAR_ZWSP: + case CHAR_LRM: + case CHAR_RLM: + case CHAR_NBHY: + case CHAR_WJ: + return true; + default: + break; + } + } + + return false; +} + +double ScOutputData::GetStretch() const +{ + if ( mpRefDevice->IsMapModeEnabled() ) + { + // If a non-trivial MapMode is set, its scale is now already + // taken into account in the OutputDevice's font handling + // (OutputDevice::ImplNewFont, see #95414#). + // The old handling below is only needed for pixel output. + return 1.0; + } + + // calculation in double is faster than Fraction multiplication + // and doesn't overflow + + if ( mpRefDevice == pFmtDevice ) + { + MapMode aOld = mpRefDevice->GetMapMode(); + return static_cast(aOld.GetScaleY()) / static_cast(aOld.GetScaleX()) * static_cast(aZoomY) / static_cast(aZoomX); + } + else + { + // when formatting for printer, device map mode has already been taken care of + return static_cast(aZoomY) / static_cast(aZoomX); + } +} + +// output strings + +static void lcl_DoHyperlinkResult( const OutputDevice* pDev, const tools::Rectangle& rRect, ScRefCellValue& rCell ) +{ + vcl::PDFExtOutDevData* pPDFData = dynamic_cast< vcl::PDFExtOutDevData* >( pDev->GetExtOutDevData() ); + + OUString aURL; + OUString aCellText; + if (rCell.getType() == CELLTYPE_FORMULA) + { + ScFormulaCell* pFCell = rCell.getFormula(); + if ( pFCell->IsHyperLinkCell() ) + pFCell->GetURLResult( aURL, aCellText ); + } + + if ( !aURL.isEmpty() && pPDFData ) + { + vcl::PDFExtOutDevBookmarkEntry aBookmark; + aBookmark.nLinkId = pPDFData->CreateLink(rRect, aCellText); + aBookmark.aBookmark = aURL; + std::vector< vcl::PDFExtOutDevBookmarkEntry >& rBookmarks = pPDFData->GetBookmarks(); + rBookmarks.push_back( aBookmark ); + } +} + +void ScOutputData::SetSyntaxColor( vcl::Font* pFont, const ScRefCellValue& rCell ) +{ + switch (rCell.getType()) + { + case CELLTYPE_VALUE: + pFont->SetColor(*mxValueColor); + break; + case CELLTYPE_STRING: + pFont->SetColor(*mxTextColor); + break; + case CELLTYPE_FORMULA: + pFont->SetColor(*mxFormulaColor); + break; + default: + { + // added to avoid warnings + } + } +} + +static void lcl_SetEditColor( EditEngine& rEngine, const Color& rColor ) +{ + ESelection aSel( 0, 0, rEngine.GetParagraphCount(), 0 ); + SfxItemSet aSet( rEngine.GetEmptyItemSet() ); + aSet.Put( SvxColorItem( rColor, EE_CHAR_COLOR ) ); + rEngine.QuickSetAttribs( aSet, aSel ); + // function is called with update mode set to FALSE +} + +void ScOutputData::SetEditSyntaxColor( EditEngine& rEngine, const ScRefCellValue& rCell ) +{ + Color aColor; + switch (rCell.getType()) + { + case CELLTYPE_VALUE: + aColor = *mxValueColor; + break; + case CELLTYPE_STRING: + aColor = *mxTextColor; + break; + case CELLTYPE_FORMULA: + aColor = *mxFormulaColor; + break; + default: + { + // added to avoid warnings + } + } + lcl_SetEditColor( rEngine, aColor ); +} + +bool ScOutputData::GetMergeOrigin( SCCOL nX, SCROW nY, SCSIZE nArrY, + SCCOL& rOverX, SCROW& rOverY, + bool bVisRowChanged ) +{ + bool bDoMerge = false; + bool bIsLeft = ( nX == nVisX1 ); + bool bIsTop = ( nY == nVisY1 ) || bVisRowChanged; + + bool bHOver; + bool bVOver; + bool bHidden; + + if (!mpDoc->ColHidden(nX, nTab) && nX >= nX1 && nX <= nX2 + && !mpDoc->RowHidden(nY, nTab) && nY >= nY1 && nY <= nY2) + { + ScCellInfo* pInfo = &pRowInfo[nArrY].cellInfo(nX); + bHOver = pInfo->bHOverlapped; + bVOver = pInfo->bVOverlapped; + } + else + { + ScMF nOverlap2 = mpDoc->GetAttr(nX, nY, nTab, ATTR_MERGE_FLAG)->GetValue(); + bHOver = bool(nOverlap2 & ScMF::Hor); + bVOver = bool(nOverlap2 & ScMF::Ver); + } + + if ( bHOver && bVOver ) + bDoMerge = bIsLeft && bIsTop; + else if ( bHOver ) + bDoMerge = bIsLeft; + else if ( bVOver ) + bDoMerge = bIsTop; + + rOverX = nX; + rOverY = nY; + + while (bHOver) // nY constant + { + --rOverX; + bHidden = mpDoc->ColHidden(rOverX, nTab); + if ( !bDoMerge && !bHidden ) + return false; + + if (rOverX >= nX1 && !bHidden) + { + bHOver = pRowInfo[nArrY].cellInfo(rOverX).bHOverlapped; + bVOver = pRowInfo[nArrY].cellInfo(rOverX).bVOverlapped; + } + else + { + ScMF nOverlap = mpDoc->GetAttr(rOverX, rOverY, nTab, ATTR_MERGE_FLAG)->GetValue(); + bHOver = bool(nOverlap & ScMF::Hor); + bVOver = bool(nOverlap & ScMF::Ver); + } + } + + while (bVOver) + { + --rOverY; + bHidden = mpDoc->RowHidden(rOverY, nTab); + if ( !bDoMerge && !bHidden ) + return false; + + if (nArrY>0) + --nArrY; // local copy ! + + if (rOverX >= nX1 && rOverY >= nY1 && + !mpDoc->ColHidden(rOverX, nTab) && + !mpDoc->RowHidden(rOverY, nTab) && + pRowInfo[nArrY].nRowNo == rOverY) + { + bVOver = pRowInfo[nArrY].cellInfo(rOverX).bVOverlapped; + } + else + { + ScMF nOverlap = mpDoc->GetAttr( rOverX, rOverY, nTab, ATTR_MERGE_FLAG )->GetValue(); + bVOver = bool(nOverlap & ScMF::Ver); + } + } + + return true; +} + +static bool StringDiffer( const ScPatternAttr*& rpOldPattern, const ScPatternAttr* pNewPattern ) +{ + OSL_ENSURE( pNewPattern, "pNewPattern" ); + + if ( SfxPoolItem::areSame( pNewPattern, rpOldPattern ) ) + return false; + else if ( !rpOldPattern ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_FONT ), rpOldPattern->GetItem( ATTR_FONT ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_CJK_FONT ), rpOldPattern->GetItem( ATTR_CJK_FONT ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_CTL_FONT ), rpOldPattern->GetItem( ATTR_CTL_FONT ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_FONT_HEIGHT ), rpOldPattern->GetItem( ATTR_FONT_HEIGHT ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_CJK_FONT_HEIGHT ), rpOldPattern->GetItem( ATTR_CJK_FONT_HEIGHT ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_CTL_FONT_HEIGHT ), rpOldPattern->GetItem( ATTR_CTL_FONT_HEIGHT ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_FONT_WEIGHT ), rpOldPattern->GetItem( ATTR_FONT_WEIGHT ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_CJK_FONT_WEIGHT ), rpOldPattern->GetItem( ATTR_CJK_FONT_WEIGHT ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_CTL_FONT_WEIGHT ), rpOldPattern->GetItem( ATTR_CTL_FONT_WEIGHT ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_FONT_POSTURE ), rpOldPattern->GetItem( ATTR_FONT_POSTURE ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_CJK_FONT_POSTURE ), rpOldPattern->GetItem( ATTR_CJK_FONT_POSTURE ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_CTL_FONT_POSTURE ), rpOldPattern->GetItem( ATTR_CTL_FONT_POSTURE ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_FONT_UNDERLINE ), rpOldPattern->GetItem( ATTR_FONT_UNDERLINE ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_FONT_OVERLINE ), rpOldPattern->GetItem( ATTR_FONT_OVERLINE ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_FONT_WORDLINE ), rpOldPattern->GetItem( ATTR_FONT_WORDLINE ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_FONT_CROSSEDOUT ), rpOldPattern->GetItem( ATTR_FONT_CROSSEDOUT ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_FONT_CONTOUR ), rpOldPattern->GetItem( ATTR_FONT_CONTOUR ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_FONT_SHADOWED ), rpOldPattern->GetItem( ATTR_FONT_SHADOWED ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_FONT_COLOR ), rpOldPattern->GetItem( ATTR_FONT_COLOR ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_HOR_JUSTIFY ), rpOldPattern->GetItem( ATTR_HOR_JUSTIFY ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_HOR_JUSTIFY_METHOD ), rpOldPattern->GetItem( ATTR_HOR_JUSTIFY_METHOD ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_VER_JUSTIFY ), rpOldPattern->GetItem( ATTR_VER_JUSTIFY ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_VER_JUSTIFY_METHOD ), rpOldPattern->GetItem( ATTR_VER_JUSTIFY_METHOD ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_STACKED ), rpOldPattern->GetItem( ATTR_STACKED ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_LINEBREAK ), rpOldPattern->GetItem( ATTR_LINEBREAK ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_MARGIN ), rpOldPattern->GetItem( ATTR_MARGIN ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_ROTATE_VALUE ), rpOldPattern->GetItem( ATTR_ROTATE_VALUE ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_FORBIDDEN_RULES ), rpOldPattern->GetItem( ATTR_FORBIDDEN_RULES ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_FONT_EMPHASISMARK ), rpOldPattern->GetItem( ATTR_FONT_EMPHASISMARK ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_FONT_RELIEF ), rpOldPattern->GetItem( ATTR_FONT_RELIEF ) ) ) + return true; + else if ( !SfxPoolItem::areSame( pNewPattern->GetItem( ATTR_BACKGROUND ), rpOldPattern->GetItem( ATTR_BACKGROUND ) ) ) + return true; // needed with automatic text color + else + { + rpOldPattern = pNewPattern; + return false; + } +} + +static void lcl_CreateInterpretProgress( bool& bProgress, ScDocument* pDoc, + const ScFormulaCell* pFCell ) +{ + if ( !bProgress && pFCell->GetDirty() ) + { + ScProgress::CreateInterpretProgress( pDoc ); + bProgress = true; + } +} + +static bool IsAmbiguousScript( SvtScriptType nScript ) +{ + return ( nScript != SvtScriptType::LATIN && + nScript != SvtScriptType::ASIAN && + nScript != SvtScriptType::COMPLEX ); +} + +bool ScOutputData::IsEmptyCellText( const RowInfo* pThisRowInfo, SCCOL nX, SCROW nY ) +{ + // pThisRowInfo may be NULL + + bool bEmpty; + if ( pThisRowInfo && nX <= nX2 ) + bEmpty = pThisRowInfo->basicCellInfo(nX).bEmptyCellText; + else + { + ScRefCellValue aCell(*mpDoc, ScAddress(nX, nY, nTab)); + bEmpty = aCell.isEmpty(); + } + + if ( !bEmpty && ( nX < nX1 || nX > nX2 || !pThisRowInfo ) ) + { + // for the range nX1..nX2 in RowInfo, cell protection attribute is already evaluated + // into bEmptyCellText in ScDocument::FillInfo / lcl_HidePrint (printfun) + + bool bIsPrint = ( eType == OUTTYPE_PRINTER ); + + if ( bIsPrint || bTabProtected ) + { + const ScProtectionAttr* pAttr = + mpDoc->GetEffItem( nX, nY, nTab, ATTR_PROTECTION ); + if ( bIsPrint && pAttr->GetHidePrint() ) + bEmpty = true; + else if ( bTabProtected ) + { + if ( pAttr->GetHideCell() ) + bEmpty = true; + else if ( mbShowFormulas && pAttr->GetHideFormula() ) + { + if (mpDoc->GetCellType(ScAddress(nX, nY, nTab)) == CELLTYPE_FORMULA) + bEmpty = true; + } + } + } + } + return bEmpty; +} + +void ScOutputData::GetVisibleCell( SCCOL nCol, SCROW nRow, SCTAB nTabP, ScRefCellValue& rCell ) +{ + rCell.assign(*mpDoc, ScAddress(nCol, nRow, nTabP)); + if (!rCell.isEmpty() && IsEmptyCellText(nullptr, nCol, nRow)) + rCell.clear(); +} + +bool ScOutputData::IsAvailable( SCCOL nX, SCROW nY ) +{ + // apply the same logic here as in DrawStrings/DrawEdit: + // Stop at non-empty or merged or overlapped cell, + // where a note is empty as well as a cell that's hidden by protection settings + + ScRefCellValue aCell(*mpDoc, ScAddress(nX, nY, nTab)); + if (!aCell.isEmpty() && !IsEmptyCellText(nullptr, nX, nY)) + return false; + + const ScPatternAttr* pPattern = mpDoc->GetPattern( nX, nY, nTab ); + return !(pPattern->GetItem(ATTR_MERGE).IsMerged() || + pPattern->GetItem(ATTR_MERGE_FLAG).IsOverlapped()); +} + +// nX, nArrY: loop variables from DrawStrings / DrawEdit +// nPosX, nPosY: corresponding positions for nX, nArrY +// nCellX, nCellY: position of the cell that contains the text +// nNeeded: Text width, including margin +// rPattern: cell format at nCellX, nCellY +// nHorJustify: horizontal alignment (visual) to determine which cells to use for long strings +// bCellIsValue: if set, don't extend into empty cells +// bBreak: if set, don't extend, and don't set clip marks (but rLeftClip/rRightClip is set) +// bOverwrite: if set, also extend into non-empty cells (for rotated text) +// rParam output: various area parameters. + +void ScOutputData::GetOutputArea( SCCOL nX, SCSIZE nArrY, tools::Long nPosX, tools::Long nPosY, + SCCOL nCellX, SCROW nCellY, tools::Long nNeeded, + const ScPatternAttr& rPattern, + sal_uInt16 nHorJustify, bool bCellIsValue, + bool bBreak, bool bOverwrite, + OutputAreaParam& rParam ) +{ + // rThisRowInfo may be for a different row than nCellY, is still used for clip marks + RowInfo& rThisRowInfo = pRowInfo[nArrY]; + + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + tools::Long nCellPosX = nPosX; // find nCellX position, starting at nX/nPosX + SCCOL nCompCol = nX; + while ( nCellX > nCompCol ) + { + //! extra member function for width? + tools::Long nColWidth = ( nCompCol <= nX2 ) ? + pRowInfo[0].basicCellInfo(nCompCol).nWidth : + static_cast( mpDoc->GetColWidth( nCompCol, nTab ) * mnPPTX ); + nCellPosX += nColWidth * nLayoutSign; + ++nCompCol; + } + while ( nCellX < nCompCol ) + { + --nCompCol; + tools::Long nColWidth = ( nCompCol <= nX2 ) ? + pRowInfo[0].basicCellInfo(nCompCol).nWidth : + static_cast( mpDoc->GetColWidth( nCompCol, nTab ) * mnPPTX ); + nCellPosX -= nColWidth * nLayoutSign; + } + + tools::Long nCellPosY = nPosY; // find nCellY position, starting at nArrY/nPosY + SCSIZE nCompArr = nArrY; + SCROW nCompRow = pRowInfo[nCompArr].nRowNo; + while ( nCellY > nCompRow ) + { + if ( nCompArr + 1 < nArrCount ) + { + nCellPosY += pRowInfo[nCompArr].nHeight; + ++nCompArr; + nCompRow = pRowInfo[nCompArr].nRowNo; + } + else + { + sal_uInt16 nDocHeight = mpDoc->GetRowHeight( nCompRow, nTab ); + if ( nDocHeight ) + nCellPosY += static_cast( nDocHeight * mnPPTY ); + ++nCompRow; + } + } + nCellPosY -= mpDoc->GetScaledRowHeight( nCellY, nCompRow-1, nTab, mnPPTY ); + + const ScMergeAttr* pMerge = &rPattern.GetItem( ATTR_MERGE ); + bool bMerged = pMerge->IsMerged(); + tools::Long nMergeCols = pMerge->GetColMerge(); + if ( nMergeCols == 0 ) + nMergeCols = 1; + tools::Long nMergeRows = pMerge->GetRowMerge(); + if ( nMergeRows == 0 ) + nMergeRows = 1; + + tools::Long nMergeSizeX = 0; + for ( tools::Long i=0; i( mpDoc->GetColWidth( sal::static_int_cast(nCellX+i), nTab ) * mnPPTX ); + nMergeSizeX += nColWidth; + } + tools::Long nMergeSizeY = 0; + short nDirect = 0; + if ( rThisRowInfo.nRowNo == nCellY ) + { + // take first row's height from row info + nMergeSizeY += rThisRowInfo.nHeight; + nDirect = 1; // skip in loop + } + // following rows always from document + nMergeSizeY += mpDoc->GetScaledRowHeight( nCellY+nDirect, nCellY+nMergeRows-1, nTab, mnPPTY); + + --nMergeSizeX; // leave out the grid horizontally, also for alignment (align between grid lines) + + rParam.mnColWidth = nMergeSizeX; // store the actual column width. + rParam.mnLeftClipLength = rParam.mnRightClipLength = 0; + + // construct the rectangles using logical left/right values (justify is called at the end) + + // rAlignRect is the single cell or merged area, used for alignment. + + rParam.maAlignRect.SetLeft( nCellPosX ); + rParam.maAlignRect.SetRight( nCellPosX + ( nMergeSizeX - 1 ) * nLayoutSign ); + rParam.maAlignRect.SetTop( nCellPosY ); + rParam.maAlignRect.SetBottom( nCellPosY + nMergeSizeY - 1 ); + + // rClipRect is all cells that are used for output. + // For merged cells this is the same as rAlignRect, otherwise neighboring cells can also be used. + + rParam.maClipRect = rParam.maAlignRect; + if ( nNeeded > nMergeSizeX ) + { + SvxCellHorJustify eHorJust = static_cast(nHorJustify); + + tools::Long nMissing = nNeeded - nMergeSizeX; + tools::Long nLeftMissing = 0; + tools::Long nRightMissing = 0; + switch ( eHorJust ) + { + case SvxCellHorJustify::Left: + nRightMissing = nMissing; + break; + case SvxCellHorJustify::Right: + nLeftMissing = nMissing; + break; + case SvxCellHorJustify::Center: + nLeftMissing = nMissing / 2; + nRightMissing = nMissing - nLeftMissing; + break; + default: + { + // added to avoid warnings + } + } + + // nLeftMissing, nRightMissing are logical, eHorJust values are visual + if ( bLayoutRTL ) + ::std::swap( nLeftMissing, nRightMissing ); + + SCCOL nRightX = nCellX; + SCCOL nLeftX = nCellX; + if ( !bMerged && !bCellIsValue && !bBreak ) + { + // look for empty cells into which the text can be extended + + while ( nRightMissing > 0 && nRightX < mpDoc->MaxCol() && ( bOverwrite || IsAvailable( nRightX+1, nCellY ) ) ) + { + ++nRightX; + tools::Long nAdd = static_cast( mpDoc->GetColWidth( nRightX, nTab ) * mnPPTX ); + nRightMissing -= nAdd; + rParam.maClipRect.AdjustRight(nAdd * nLayoutSign ); + + if ( rThisRowInfo.nRowNo == nCellY && nRightX >= nX1 && nRightX <= nX2 ) + rThisRowInfo.cellInfo(nRightX-1).bHideGrid = true; + } + + while ( nLeftMissing > 0 && nLeftX > 0 && ( bOverwrite || IsAvailable( nLeftX-1, nCellY ) ) ) + { + if ( rThisRowInfo.nRowNo == nCellY && nLeftX >= nX1 && nLeftX <= nX2 ) + rThisRowInfo.cellInfo(nLeftX-1).bHideGrid = true; + + --nLeftX; + tools::Long nAdd = static_cast( mpDoc->GetColWidth( nLeftX, nTab ) * mnPPTX ); + nLeftMissing -= nAdd; + rParam.maClipRect.AdjustLeft( -(nAdd * nLayoutSign) ); + } + } + + // Set flag and reserve space for clipping mark triangle, + // even if rThisRowInfo isn't for nCellY (merged cells). + if ( nRightMissing > 0 && bMarkClipped && nRightX >= nX1 && nRightX <= nX2 && !bBreak && !bCellIsValue ) + { + rThisRowInfo.cellInfo(nRightX).nClipMark |= ScClipMark::Right; + bAnyClipped = true; + tools::Long nMarkPixel = static_cast( SC_CLIPMARK_SIZE * mnPPTX ); + rParam.maClipRect.AdjustRight( -(nMarkPixel * nLayoutSign) ); + } + if ( nLeftMissing > 0 && bMarkClipped && nLeftX >= nX1 && nLeftX <= nX2 && !bBreak && !bCellIsValue ) + { + rThisRowInfo.cellInfo(nLeftX).nClipMark |= ScClipMark::Left; + bAnyClipped = true; + tools::Long nMarkPixel = static_cast( SC_CLIPMARK_SIZE * mnPPTX ); + rParam.maClipRect.AdjustLeft(nMarkPixel * nLayoutSign ); + } + + rParam.mbLeftClip = ( nLeftMissing > 0 ); + rParam.mbRightClip = ( nRightMissing > 0 ); + rParam.mnLeftClipLength = nLeftMissing; + rParam.mnRightClipLength = nRightMissing; + } + else + { + rParam.mbLeftClip = rParam.mbRightClip = false; + + // leave space for AutoFilter on screen + // (for automatic line break: only if not formatting for printer, as in ScColumn::GetNeededSize) + + if ( eType==OUTTYPE_WINDOW && + ( rPattern.GetItem(ATTR_MERGE_FLAG).GetValue() & (ScMF::Auto|ScMF::Button|ScMF::ButtonPopup) ) && + ( !bBreak || mpRefDevice == pFmtDevice ) ) + { + // filter drop-down width depends on row height + double fZoom = mpRefDevice ? static_cast(mpRefDevice->GetMapMode().GetScaleY()) : 1.0; + fZoom = fZoom > 1.0 ? fZoom : 1.0; + const tools::Long nFilter = fZoom * DROPDOWN_BITMAP_SIZE; + bool bFit = ( nNeeded + nFilter <= nMergeSizeX ); + if ( bFit ) + { + // content fits even in the remaining area without the filter button + // -> align within that remaining area + + rParam.maAlignRect.AdjustRight( -(nFilter * nLayoutSign) ); + rParam.maClipRect.AdjustRight( -(nFilter * nLayoutSign) ); + } + } + } + + // justify both rectangles for alignment calculation, use with DrawText etc. + + rParam.maAlignRect.Normalize(); + rParam.maClipRect.Normalize(); +} + +namespace { + +bool beginsWithRTLCharacter(const OUString& rStr) +{ + if (rStr.isEmpty()) + return false; + + switch (ScGlobal::getCharClass().getCharacterDirection(rStr, 0)) + { + case i18n::DirectionProperty_RIGHT_TO_LEFT: + case i18n::DirectionProperty_RIGHT_TO_LEFT_ARABIC: + case i18n::DirectionProperty_RIGHT_TO_LEFT_EMBEDDING: + case i18n::DirectionProperty_RIGHT_TO_LEFT_OVERRIDE: + return true; + default: + ; + } + + return false; +} + +} + +/** Get left, right or centered alignment from RTL context. + + Does not return standard, block or repeat, for these the contextual left or + right alignment is returned. + */ +static SvxCellHorJustify getAlignmentFromContext( SvxCellHorJustify eInHorJust, + bool bCellIsValue, const OUString& rText, + const ScPatternAttr& rPattern, const SfxItemSet* pCondSet, + const ScDocument* pDoc, SCTAB nTab, const bool bNumberFormatIsText ) +{ + SvxCellHorJustify eHorJustContext = eInHorJust; + bool bUseWritingDirection = false; + if (eInHorJust == SvxCellHorJustify::Standard) + { + // fdo#32530: Default alignment depends on value vs + // string, and the direction of the 1st letter. + if (beginsWithRTLCharacter( rText)) //If language is RTL + { + if (bCellIsValue) + eHorJustContext = bNumberFormatIsText ? SvxCellHorJustify::Right : SvxCellHorJustify::Left; + else + eHorJustContext = SvxCellHorJustify::Right; + } + else if (bCellIsValue) //If language is not RTL + eHorJustContext = bNumberFormatIsText ? SvxCellHorJustify::Left : SvxCellHorJustify::Right; + else + bUseWritingDirection = true; + } + + if (bUseWritingDirection || + eInHorJust == SvxCellHorJustify::Block || eInHorJust == SvxCellHorJustify::Repeat) + { + SvxFrameDirection nDirection = lcl_GetValue(rPattern, ATTR_WRITINGDIR, pCondSet); + if (nDirection == SvxFrameDirection::Horizontal_LR_TB || nDirection == SvxFrameDirection::Vertical_LR_TB) + eHorJustContext = SvxCellHorJustify::Left; + else if (nDirection == SvxFrameDirection::Environment) + { + SAL_WARN_IF( !pDoc, "sc.ui", "getAlignmentFromContext - pDoc==NULL"); + // fdo#73588: The content of the cell must also + // begin with a RTL character to be right + // aligned; otherwise, it should be left aligned. + eHorJustContext = (pDoc && pDoc->IsLayoutRTL(nTab) && (beginsWithRTLCharacter( rText))) ? SvxCellHorJustify::Right : SvxCellHorJustify::Left; + } + else + eHorJustContext = SvxCellHorJustify::Right; + } + return eHorJustContext; +} + +void ScOutputData::DrawStrings( bool bPixelToLogic ) +{ + LayoutStrings(bPixelToLogic); +} + +void ScOutputData::LayoutStrings(bool bPixelToLogic) +{ + bool bOrigIsInLayoutStrings = mpDoc->IsInLayoutStrings(); + mpDoc->SetLayoutStrings(true); + OSL_ENSURE( mpDev == mpRefDevice || + mpDev->GetMapMode().GetMapUnit() == mpRefDevice->GetMapMode().GetMapUnit(), + "LayoutStrings: different MapUnits ?!?!" ); + vcl::text::ComplexTextLayoutFlags eTextLayout = mpDev->GetLayoutMode(); + mpDev->SetLayoutMode(vcl::text::ComplexTextLayoutFlags::Default); + + comphelper::ScopeGuard g([this, bOrigIsInLayoutStrings, eTextLayout] { + mpDoc->SetLayoutStrings(bOrigIsInLayoutStrings); + mpDev->SetLayoutMode(eTextLayout); + }); + + sc::IdleSwitch aIdleSwitch(*mpDoc, false); + + // Try to limit interpreting to only visible cells. Calling e.g. IsValue() + // on a formula cell that needs interpreting would call Interpret() + // for the entire formula group, which could be large. + mpDoc->InterpretCellsIfNeeded( ScRange( nX1, nY1, nTab, nX2, nY2, nTab )); + + vcl::PDFExtOutDevData* pPDFData = dynamic_cast< vcl::PDFExtOutDevData* >(mpDev->GetExtOutDevData() ); + + ScDrawStringsVars aVars( this, bPixelToLogic ); + + bool bProgress = false; + + tools::Long nInitPosX = nScrX; + if ( bLayoutRTL ) + nInitPosX += nMirrorW - 1; // pixels + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + SCCOL nLastContentCol = mpDoc->MaxCol(); + if ( nX2 < mpDoc->MaxCol() ) + { + SCROW nEndRow; + mpDoc->GetCellArea(nTab, nLastContentCol, nEndRow); + } + + SCCOL nLoopStartX = nX1; + if ( nX1 > 0 ) + --nLoopStartX; // start before nX1 for rest of long text to the left + + // variables for GetOutputArea + OutputAreaParam aAreaParam; + bool bCellIsValue = false; + tools::Long nNeededWidth = 0; + const ScPatternAttr* pPattern = nullptr; + const SfxItemSet* pCondSet = nullptr; + const ScPatternAttr* pOldPattern = nullptr; + const SfxItemSet* pOldCondSet = nullptr; + SvtScriptType nOldScript = SvtScriptType::NONE; + + // alternative pattern instances in case we need to modify the pattern + // before processing the cell value. + std::vector > aAltPatterns; + + KernArray aDX; + tools::Long nPosY = nScrY; + for (SCSIZE nArrY=1; nArrY+1nRowNo; + if (pThisRowInfo->bChanged) + { + tools::Long nPosX = nInitPosX; + if ( nLoopStartX < nX1 ) + nPosX -= pRowInfo[0].basicCellInfo(nLoopStartX).nWidth * nLayoutSign; + for (SCCOL nX=nLoopStartX; nX<=nX2; nX++) + { + bool bMergeEmpty = false; + const ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nX); + bool bEmpty = nX < nX1 || pThisRowInfo->basicCellInfo(nX).bEmptyCellText; + + SCCOL nCellX = nX; // position where the cell really starts + SCROW nCellY = nY; + bool bDoCell = false; + bool bUseEditEngine = false; + + // Part of a merged cell? + + bool bOverlapped = (pInfo->bHOverlapped || pInfo->bVOverlapped); + if ( bOverlapped ) + { + bEmpty = true; + + SCCOL nOverX; // start of the merged cells + SCROW nOverY; + bool bVisChanged = !pRowInfo[nArrY-1].bChanged; + if (GetMergeOrigin( nX,nY, nArrY, nOverX,nOverY, bVisChanged )) + { + nCellX = nOverX; + nCellY = nOverY; + bDoCell = true; + } + else + bMergeEmpty = true; + } + + // Rest of a long text further to the left? + + if ( bEmpty && !bMergeEmpty && nX < nX1 && !bOverlapped ) + { + SCCOL nTempX=nX1; + while (nTempX > 0 && IsEmptyCellText( pThisRowInfo, nTempX, nY )) + --nTempX; + + if ( nTempX < nX1 && + !IsEmptyCellText( pThisRowInfo, nTempX, nY ) && + !mpDoc->HasAttrib( nTempX,nY,nTab, nX1,nY,nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped ) ) + { + nCellX = nTempX; + bDoCell = true; + } + } + + // Rest of a long text further to the right? + + if ( bEmpty && !bMergeEmpty && nX == nX2 && !bOverlapped ) + { + // don't have to look further than nLastContentCol + + SCCOL nTempX=nX; + while (nTempX < nLastContentCol && IsEmptyCellText( pThisRowInfo, nTempX, nY )) + ++nTempX; + + if ( nTempX > nX && + !IsEmptyCellText( pThisRowInfo, nTempX, nY ) && + !mpDoc->HasAttrib( nTempX,nY,nTab, nX,nY,nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped ) ) + { + nCellX = nTempX; + bDoCell = true; + } + } + + // normal visible cell + + if (!bEmpty) + bDoCell = true; + + // don't output the cell that's being edited + + if ( bDoCell && bEditMode && nCellX == nEditCol && nCellY == nEditRow ) + bDoCell = false; + + // skip text in cell if data bar/icon set is set and only value selected + if ( bDoCell ) + { + if(pInfo->pDataBar && !pInfo->pDataBar->mbShowValue) + bDoCell = false; + if(pInfo->pIconSet && !pInfo->pIconSet->mbShowValue) + bDoCell = false; + } + + // output the cell text + + ScRefCellValue aCell; + if (bDoCell) + { + if ( nCellY == nY && nCellX == nX && nCellX >= nX1 && nCellX <= nX2 ) + aCell = pThisRowInfo->cellInfo(nCellX).maCell; + else + GetVisibleCell( nCellX, nCellY, nTab, aCell ); // get from document + if (aCell.isEmpty()) + bDoCell = false; + else if (aCell.getType() == CELLTYPE_EDIT) + bUseEditEngine = true; + } + + // Check if this cell is mis-spelled. + if (bDoCell && !bUseEditEngine && aCell.getType() == CELLTYPE_STRING) + { + if (mpSpellCheckCxt && mpSpellCheckCxt->isMisspelled(nCellX, nCellY)) + bUseEditEngine = true; + } + + if (bDoCell && !bUseEditEngine) + { + if ( nCellY == nY && nCellX >= nX1 && nCellX <= nX2 ) + { + ScCellInfo& rCellInfo = pThisRowInfo->cellInfo(nCellX); + pPattern = rCellInfo.pPatternAttr; + pCondSet = rCellInfo.pConditionSet; + + if ( !pPattern ) + { + // #i68085# pattern from cell info for hidden columns is null, + // test for null is quicker than using column flags + pPattern = mpDoc->GetPattern( nCellX, nCellY, nTab ); + pCondSet = mpDoc->GetCondResult( nCellX, nCellY, nTab ); + } + } + else // get from document + { + pPattern = mpDoc->GetPattern( nCellX, nCellY, nTab ); + pCondSet = mpDoc->GetCondResult( nCellX, nCellY, nTab ); + } + if ( mpDoc->GetPreviewFont() || mpDoc->GetPreviewCellStyle() ) + { + aAltPatterns.push_back(std::make_unique(*pPattern)); + ScPatternAttr* pAltPattern = aAltPatterns.back().get(); + if ( ScStyleSheet* pPreviewStyle = mpDoc->GetPreviewCellStyle( nCellX, nCellY, nTab ) ) + { + pAltPattern->SetStyleSheet(pPreviewStyle); + } + else if ( SfxItemSet* pFontSet = mpDoc->GetPreviewFont( nCellX, nCellY, nTab ) ) + { + if ( const SvxFontItem* pItem = pFontSet->GetItemIfSet( ATTR_FONT ) ) + pAltPattern->GetItemSet().Put( *pItem ); + if ( const SvxFontItem* pItem = pFontSet->GetItemIfSet( ATTR_CJK_FONT ) ) + pAltPattern->GetItemSet().Put( *pItem ); + if ( const SvxFontItem* pItem = pFontSet->GetItemIfSet( ATTR_CTL_FONT ) ) + pAltPattern->GetItemSet().Put( *pItem ); + } + pPattern = pAltPattern; + } + + if (aCell.hasNumeric() && + pPattern->GetItem(ATTR_LINEBREAK, pCondSet).GetValue()) + { + // Disable line break when the cell content is numeric. + aAltPatterns.push_back(std::make_unique(*pPattern)); + ScPatternAttr* pAltPattern = aAltPatterns.back().get(); + ScLineBreakCell aLineBreak(false); + pAltPattern->GetItemSet().Put(aLineBreak); + pPattern = pAltPattern; + } + + SvtScriptType nScript = mpDoc->GetCellScriptType( + ScAddress(nCellX, nCellY, nTab), + pPattern->GetNumberFormat(mpDoc->GetFormatTable(), pCondSet)); + + if (nScript == SvtScriptType::NONE) + nScript = ScGlobal::GetDefaultScriptType(); + + if ( !SfxPoolItem::areSame(pPattern, pOldPattern) || pCondSet != pOldCondSet || + nScript != nOldScript || mbSyntaxMode ) + { + if ( StringDiffer(pOldPattern,pPattern) || + pCondSet != pOldCondSet || nScript != nOldScript || mbSyntaxMode ) + { + aVars.SetPattern(pPattern, pCondSet, aCell, nScript); + } + else + aVars.SetPatternSimple( pPattern, pCondSet ); + pOldPattern = pPattern; + pOldCondSet = pCondSet; + nOldScript = nScript; + } + + // use edit engine for rotated, stacked or mixed-script text + if ( aVars.GetOrient() == SvxCellOrientation::Stacked || + aVars.IsRotated() || IsAmbiguousScript(nScript) ) + bUseEditEngine = true; + } + if (bDoCell && !bUseEditEngine) + { + bool bFormulaCell = (aCell.getType() == CELLTYPE_FORMULA); + if ( bFormulaCell ) + lcl_CreateInterpretProgress(bProgress, mpDoc, aCell.getFormula()); + if ( aVars.SetText(aCell) ) + pOldPattern = nullptr; + bUseEditEngine = aVars.HasEditCharacters() || (bFormulaCell && aCell.getFormula()->IsMultilineResult()); + } + tools::Long nTotalMargin = 0; + SvxCellHorJustify eOutHorJust = SvxCellHorJustify::Standard; + if (bDoCell && !bUseEditEngine) + { + CellType eCellType = aCell.getType(); + bCellIsValue = ( eCellType == CELLTYPE_VALUE ); + if ( eCellType == CELLTYPE_FORMULA ) + { + ScFormulaCell* pFCell = aCell.getFormula(); + bCellIsValue = pFCell->IsRunning() || pFCell->IsValue(); + } + + const bool bNumberFormatIsText = lcl_isNumberFormatText( mpDoc, nCellX, nCellY, nTab ); + eOutHorJust = getAlignmentFromContext( aVars.GetHorJust(), bCellIsValue, aVars.GetString(), + *pPattern, pCondSet, mpDoc, nTab, bNumberFormatIsText ); + + bool bBreak = ( aVars.GetLineBreak() || aVars.GetHorJust() == SvxCellHorJustify::Block ); + // #i111387# #o11817313# tdf#121040 disable automatic line breaks for all number formats + // Must be synchronized with ScColumn::GetNeededSize() + SvNumberFormatter* pFormatter = mpDoc->GetFormatTable(); + if (bBreak && bCellIsValue && (pFormatter->GetType(aVars.GetResultValueFormat()) == SvNumFormatType::NUMBER)) + bBreak = false; + + bool bRepeat = aVars.IsRepeat() && !bBreak; + bool bShrink = aVars.IsShrink() && !bBreak && !bRepeat; + + nTotalMargin = + static_cast(aVars.GetLeftTotal() * mnPPTX) + + static_cast(aVars.GetMargin()->GetRightMargin() * mnPPTX); + + nNeededWidth = aVars.GetTextSize().Width() + nTotalMargin; + + // GetOutputArea gives justified rectangles + GetOutputArea( nX, nArrY, nPosX, nPosY, nCellX, nCellY, nNeededWidth, + *pPattern, sal::static_int_cast(eOutHorJust), + bCellIsValue || bRepeat || bShrink, bBreak, false, + aAreaParam ); + + aVars.RepeatToFill( aAreaParam.mnColWidth - nTotalMargin ); + if ( bShrink ) + { + if ( aVars.GetOrient() != SvxCellOrientation::Standard ) + { + // Only horizontal scaling is handled here. + // DrawEdit is used to vertically scale 90 deg rotated text. + bUseEditEngine = true; + } + else if ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) // horizontal + { + tools::Long nAvailable = aAreaParam.maAlignRect.GetWidth() - nTotalMargin; + tools::Long nScaleSize = aVars.GetTextSize().Width(); // without margin + + if ( nAvailable > 0 && nScaleSize > 0 ) // 0 if the text is empty (formulas, number formats) + { + tools::Long nScale = ( nAvailable * 100 ) / nScaleSize; + + aVars.SetShrinkScale( nScale, nOldScript ); + tools::Long nNewSize = aVars.GetTextSize().Width(); + + sal_uInt16 nShrinkAgain = 0; + while ( nNewSize > nAvailable && nShrinkAgain < SC_SHRINKAGAIN_MAX ) + { + // If the text is still too large, reduce the scale again by 10%, until it fits, + // at most 7 times (it's less than 50% of the calculated scale then). + + nScale = ( nScale * 9 ) / 10; + aVars.SetShrinkScale( nScale, nOldScript ); + nNewSize = aVars.GetTextSize().Width(); + ++nShrinkAgain; + } + // If even at half the size the font still isn't rendered smaller, + // fall back to normal clipping (showing ### for numbers). + if ( nNewSize <= nAvailable ) + { + // Reset relevant parameters. + aAreaParam.mbLeftClip = aAreaParam.mbRightClip = false; + aAreaParam.mnLeftClipLength = aAreaParam.mnRightClipLength = 0; + } + + pOldPattern = nullptr; + } + } + } + + if ( bRepeat && !aAreaParam.mbLeftClip && !aAreaParam.mbRightClip ) + { + tools::Long nAvailable = aAreaParam.maAlignRect.GetWidth() - nTotalMargin; + tools::Long nRepeatSize = aVars.GetTextSize().Width(); // without margin + // When formatting for the printer, the text sizes don't always add up. + // Round down (too few repetitions) rather than exceeding the cell size then: + if ( pFmtDevice != mpRefDevice ) + ++nRepeatSize; + if ( nRepeatSize > 0 ) + { + tools::Long nRepeatCount = nAvailable / nRepeatSize; + if ( nRepeatCount > 1 ) + { + OUString aCellStr = aVars.GetString(); + OUStringBuffer aRepeated(aCellStr); + for ( tools::Long nRepeat = 1; nRepeat < nRepeatCount; nRepeat++ ) + aRepeated.append(aCellStr); + aVars.SetAutoText( aRepeated.makeStringAndClear() ); + } + } + } + + // use edit engine if automatic line breaks are needed + if ( bBreak ) + { + if ( aVars.GetOrient() == SvxCellOrientation::Standard ) + bUseEditEngine = ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ); + else + { + tools::Long nHeight = aVars.GetTextSize().Height() + + static_cast(aVars.GetMargin()->GetTopMargin()*mnPPTY) + + static_cast(aVars.GetMargin()->GetBottomMargin()*mnPPTY); + bUseEditEngine = ( nHeight > aAreaParam.maClipRect.GetHeight() ); + } + } + if (!bUseEditEngine) + { + bUseEditEngine = + aVars.GetHorJust() == SvxCellHorJustify::Block && + aVars.GetHorJustMethod() == SvxCellJustifyMethod::Distribute; + } + } + if (bUseEditEngine) + { + // mark the cell in ScCellInfo to be drawn in DrawEdit: + // Cells to the left are marked directly, cells to the + // right are handled by the flag for nX2 + SCCOL nMarkX = ( nCellX <= nX2 ) ? nCellX : nX2; + RowInfo* pMarkRowInfo = ( nCellY == nY ) ? pThisRowInfo : &pRowInfo[0]; + pMarkRowInfo->basicCellInfo(nMarkX).bEditEngine = true; + bDoCell = false; // don't draw here + } + if ( bDoCell ) + { + if ( bCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) ) + { + bool bHasHashText = false; + if (mbShowFormulas) + { + aVars.SetHashText(); + bHasHashText = true; + } + else + // Adjust the decimals to fit the available column width. + bHasHashText = aVars.SetTextToWidthOrHash( aCell, aAreaParam.mnColWidth - nTotalMargin ); + + if ( bHasHashText ) + { + tools::Long nMarkPixel = SC_CLIPMARK_SIZE * mnPPTX; + + if ( eOutHorJust == SvxCellHorJustify::Left ) + { + if ( nCellY == nY && nCellX >= nX1 && nCellX <= nX2 ) + pRowInfo[nArrY].cellInfo(nCellX).nClipMark |= ScClipMark::Right; + bAnyClipped = true; + aAreaParam.maClipRect.AdjustRight( -(nMarkPixel * nLayoutSign) ); + } + else if ( eOutHorJust == SvxCellHorJustify::Right ) + { + if ( nCellY == nY && nCellX >= nX1 && nCellX <= nX2 ) + pRowInfo[nArrY].cellInfo(nCellX).nClipMark |= ScClipMark::Left; + bAnyClipped = true; + aAreaParam.maClipRect.AdjustLeft(nMarkPixel * nLayoutSign); + } + else + { + if ( nCellY == nY && nCellX >= nX1 && nCellX <= nX2 ) + { + pRowInfo[nArrY].cellInfo(nCellX).nClipMark |= ScClipMark::Right; + pRowInfo[nArrY].cellInfo(nCellX).nClipMark |= ScClipMark::Left; + } + bAnyClipped = true; + aAreaParam.maClipRect.AdjustRight( -(nMarkPixel * nLayoutSign) ); + aAreaParam.maClipRect.AdjustLeft(nMarkPixel * nLayoutSign); + } + } + + nNeededWidth = aVars.GetTextSize().Width() + + static_cast( aVars.GetLeftTotal() * mnPPTX ) + + static_cast( aVars.GetMargin()->GetRightMargin() * mnPPTX ); + if ( nNeededWidth <= aAreaParam.maClipRect.GetWidth() ) + { + // Cell value is no longer clipped. Reset relevant parameters. + aAreaParam.mbLeftClip = aAreaParam.mbRightClip = false; + aAreaParam.mnLeftClipLength = aAreaParam.mnRightClipLength = 0; + } + } + + tools::Long nJustPosX = aAreaParam.maAlignRect.Left(); // "justified" - effect of alignment will be added + tools::Long nJustPosY = aAreaParam.maAlignRect.Top(); + tools::Long nAvailWidth = aAreaParam.maAlignRect.GetWidth(); + tools::Long nOutHeight = aAreaParam.maAlignRect.GetHeight(); + + bool bOutside = ( aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW ); + // Take adjusted values of aAreaParam.mbLeftClip and aAreaParam.mbRightClip + bool bVClip = AdjustAreaParamClipRect(aAreaParam); + bool bHClip = aAreaParam.mbLeftClip || aAreaParam.mbRightClip; + + // check horizontal space + + if ( !bOutside ) + { + bool bRightAdjusted = false; // to correct text width calculation later + switch (eOutHorJust) + { + case SvxCellHorJustify::Left: + nJustPosX += static_cast( aVars.GetLeftTotal() * mnPPTX ); + break; + case SvxCellHorJustify::Right: + nJustPosX += nAvailWidth - aVars.GetTextSize().Width() - + static_cast( aVars.GetRightTotal() * mnPPTX ); + bRightAdjusted = true; + break; + case SvxCellHorJustify::Center: + nJustPosX += ( nAvailWidth - aVars.GetTextSize().Width() + + static_cast( aVars.GetLeftTotal() * mnPPTX ) - + static_cast( aVars.GetMargin()->GetRightMargin() * mnPPTX ) ) / 2; + break; + default: + { + // added to avoid warnings + } + } + + tools::Long nTestClipHeight = aVars.GetTextSize().Height(); + switch (aVars.GetVerJust()) + { + case SvxCellVerJustify::Top: + case SvxCellVerJustify::Block: + { + tools::Long nTop = static_cast( aVars.GetMargin()->GetTopMargin() * mnPPTY ); + nJustPosY += nTop; + nTestClipHeight += nTop; + } + break; + case SvxCellVerJustify::Bottom: + { + tools::Long nBot = static_cast( aVars.GetMargin()->GetBottomMargin() * mnPPTY ); + nJustPosY += nOutHeight - aVars.GetTextSize().Height() - nBot; + nTestClipHeight += nBot; + } + break; + case SvxCellVerJustify::Center: + { + tools::Long nTop = static_cast( aVars.GetMargin()->GetTopMargin() * mnPPTY ); + tools::Long nBot = static_cast( aVars.GetMargin()->GetBottomMargin() * mnPPTY ); + nJustPosY += ( nOutHeight + nTop - + aVars.GetTextSize().Height() - nBot ) / 2; + nTestClipHeight += std::abs( nTop - nBot ); + } + break; + default: + { + // added to avoid warnings + } + } + + if ( nTestClipHeight > nOutHeight ) + { + // no vertical clipping when printing cells with optimal height, + // except when font size is from conditional formatting. + if ( eType != OUTTYPE_PRINTER || + ( mpDoc->GetRowFlags( nCellY, nTab ) & CRFlags::ManualSize ) || + ( aVars.HasCondHeight() ) ) + bVClip = true; + } + + if ( bHClip || bVClip ) + { + // only clip the affected dimension so that not all right-aligned + // columns are cut off when performing a non-proportional resize + if (!bHClip) + { + aAreaParam.maClipRect.SetLeft( nScrX ); + aAreaParam.maClipRect.SetRight( nScrX+nScrW ); + } + if (!bVClip) + { + aAreaParam.maClipRect.SetTop( nScrY ); + aAreaParam.maClipRect.SetBottom( nScrY+nScrH ); + } + + // aClipRect is not used after SetClipRegion/IntersectClipRegion, + // so it can be modified here + if (bPixelToLogic) + aAreaParam.maClipRect = mpRefDevice->PixelToLogic( aAreaParam.maClipRect ); + + if (bMetaFile) + { + mpDev->Push(); + mpDev->IntersectClipRegion( aAreaParam.maClipRect ); + } + else + mpDev->SetClipRegion( vcl::Region( aAreaParam.maClipRect ) ); + } + + Point aURLStart( nJustPosX, nJustPosY ); // copy before modifying for orientation + + switch (aVars.GetOrient()) + { + case SvxCellOrientation::Standard: + nJustPosY += aVars.GetAscent(); + break; + case SvxCellOrientation::TopBottom: + nJustPosX += aVars.GetTextSize().Width() - aVars.GetAscent(); + break; + case SvxCellOrientation::BottomUp: + nJustPosY += aVars.GetTextSize().Height(); + nJustPosX += aVars.GetAscent(); + break; + default: + { + // added to avoid warnings + } + } + + // When clipping, the visible part is now completely defined by the alignment, + // there's no more special handling to show the right part of RTL text. + + Point aDrawTextPos( nJustPosX, nJustPosY ); + if ( bPixelToLogic ) + { + // undo text width adjustment in pixels + if (bRightAdjusted) + aDrawTextPos.AdjustX(aVars.GetTextSize().Width() ); + + aDrawTextPos = mpRefDevice->PixelToLogic( aDrawTextPos ); + + // redo text width adjustment in logic units + if (bRightAdjusted) + aDrawTextPos.AdjustX( -(aVars.GetOriginalWidth()) ); + } + + // in Metafiles always use DrawTextArray to ensure that positions are + // recorded (for non-proportional resize): + + const OUString& aString = aVars.GetString(); + if (!aString.isEmpty()) + { + // If the string is clipped, make it shorter for + // better performance since drawing by HarfBuzz is + // quite expensive especially for long string. + + OUString aShort = aString; + + // But never fiddle with numeric values. + // (Which was the cause of tdf#86024). + // The General automatic format output takes + // care of this, or fixed width numbers either fit + // or display as ###. + if (!bCellIsValue) + { + double fVisibleRatio = 1.0; + double fTextWidth = aVars.GetTextSize().Width(); + sal_Int32 nTextLen = aString.getLength(); + if (eOutHorJust == SvxCellHorJustify::Left && aAreaParam.mnRightClipLength > 0) + { + fVisibleRatio = (fTextWidth - aAreaParam.mnRightClipLength) / fTextWidth; + if (0.0 < fVisibleRatio && fVisibleRatio < 1.0) + { + // Only show the left-end segment. + sal_Int32 nShortLen = fVisibleRatio*nTextLen + 1; + aShort = aShort.copy(0, nShortLen); + } + } + else if (eOutHorJust == SvxCellHorJustify::Right && aAreaParam.mnLeftClipLength > 0) + { + fVisibleRatio = (fTextWidth - aAreaParam.mnLeftClipLength) / fTextWidth; + if (0.0 < fVisibleRatio && fVisibleRatio < 1.0) + { + // Only show the right-end segment. + sal_Int32 nShortLen = fVisibleRatio*nTextLen + 1; + aShort = aShort.copy(nTextLen-nShortLen); + + // Adjust the text position after shortening of the string. + double fShortWidth = aVars.GetFmtTextWidth(aShort); + double fOffset = fTextWidth - fShortWidth; + aDrawTextPos.Move(fOffset, 0); + } + } + } + + if (bMetaFile || pFmtDevice != mpDev || aZoomX != aZoomY) + { + size_t nLen = aShort.getLength(); + if (aDX.size() < nLen) + aDX.resize(nLen, 0); + + pFmtDevice->GetTextArray(aShort, &aDX); + + if ( !mpRefDevice->GetConnectMetaFile() || + mpRefDevice->GetOutDevType() == OUTDEV_PRINTER ) + { + double fMul = GetStretch(); + for (size_t i = 0; i < nLen; ++i) + aDX.set(i, static_cast(aDX[i] / fMul + 0.5)); + } + + mpDev->DrawTextArray(aDrawTextPos, aShort, aDX, {}, 0, nLen); + } + else + { + mpDev->DrawText(aDrawTextPos, aShort, 0, -1, nullptr, nullptr, + aVars.GetLayoutGlyphs(aShort)); + } + } + + if ( bHClip || bVClip ) + { + if (bMetaFile) + mpDev->Pop(); + else + mpDev->SetClipRegion(); + } + + // PDF: whole-cell hyperlink from formula? + bool bHasURL = pPDFData && aCell.getType() == CELLTYPE_FORMULA && aCell.getFormula()->IsHyperLinkCell(); + if (bHasURL) + { + tools::Rectangle aURLRect( aURLStart, aVars.GetTextSize() ); + lcl_DoHyperlinkResult(mpDev, aURLRect, aCell); + } + } + } + nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign; + } + } + nPosY += pRowInfo[nArrY].nHeight; + } + if ( bProgress ) + ScProgress::DeleteInterpretProgress(); +} + +void ScOutputData::SetRefDevice( OutputDevice* pRDev ) +{ + mpRefDevice = pFmtDevice = pRDev; + // reset EditEngine because it depends on pFmtDevice and mpRefDevice + mxOutputEditEngine.reset(); +} + +void ScOutputData::SetFmtDevice( OutputDevice* pRDev ) +{ + pFmtDevice = pRDev; + // reset EditEngine because it depends on pFmtDevice + mxOutputEditEngine.reset(); +} + +void ScOutputData::SetUseStyleColor( bool bSet ) +{ + mbUseStyleColor = bSet; + // reset EditEngine because it depends on mbUseStyleColor + mxOutputEditEngine.reset(); +} + +void ScOutputData::InitOutputEditEngine() +{ + if (!mxOutputEditEngine) + { + mxOutputEditEngine = std::make_unique(mpDoc, mpDoc->GetEnginePool()); + mxOutputEditEngine->SetUpdateLayout( false ); + mxOutputEditEngine->EnableUndo( false ); // don't need undo for painting purposes + // a RefDevice always has to be set, otherwise EditEngine would create a VirtualDevice + mxOutputEditEngine->SetRefDevice( pFmtDevice ); + EEControlBits nCtrl = mxOutputEditEngine->GetControlWord(); + if ( bShowSpellErrors ) + nCtrl |= EEControlBits::ONLINESPELLING; + if ( eType == OUTTYPE_PRINTER ) + nCtrl &= ~EEControlBits::MARKFIELDS; + else + nCtrl &= ~EEControlBits::MARKURLFIELDS; // URLs not shaded for output + if ( eType == OUTTYPE_WINDOW && mpRefDevice == pFmtDevice ) + nCtrl &= ~EEControlBits::FORMAT100; // use the actual MapMode + mxOutputEditEngine->SetControlWord( nCtrl ); + mxOutputEditEngine->EnableAutoColor( mbUseStyleColor ); + } + else + { + // just in case someone turned it on during the last paint cycle + mxOutputEditEngine->SetUpdateLayout( false ); + } + // we don't track changes to these settings, so we have to apply them every time + mpDoc->ApplyAsianEditSettings( *mxOutputEditEngine ); + mxOutputEditEngine->SetDefaultHorizontalTextDirection( mpDoc->GetEditTextDirection( nTab ) ); +} + +static void lcl_ClearEdit( EditEngine& rEngine ) // text and attributes +{ + rEngine.SetUpdateLayout( false ); + + rEngine.SetText(OUString()); + // do not keep any para-attributes + const SfxItemSet& rPara = rEngine.GetParaAttribs(0); + if (rPara.Count()) + rEngine.SetParaAttribs( 0, + SfxItemSet( *rPara.GetPool(), rPara.GetRanges() ) ); + rEngine.EnableSkipOutsideFormat(false); +} + +static bool lcl_SafeIsValue( ScRefCellValue& rCell ) +{ + switch (rCell.getType()) + { + case CELLTYPE_VALUE: + return true; + case CELLTYPE_FORMULA: + { + ScFormulaCell* pFCell = rCell.getFormula(); + if (pFCell->IsRunning() || pFCell->IsValue()) + return true; + } + break; + default: + { + // added to avoid warnings + } + } + return false; +} + +static void lcl_ScaleFonts( EditEngine& rEngine, tools::Long nPercent ) +{ + bool bUpdateMode = rEngine.SetUpdateLayout( false ); + + sal_Int32 nParCount = rEngine.GetParagraphCount(); + for (sal_Int32 nPar=0; nPar aPortions; + rEngine.GetPortions( nPar, aPortions ); + + sal_Int32 nStart = 0; + for ( const sal_Int32 nEnd : aPortions ) + { + ESelection aSel( nPar, nStart, nPar, nEnd ); + SfxItemSet aAttribs = rEngine.GetAttribs( aSel ); + + tools::Long nWestern = aAttribs.Get(EE_CHAR_FONTHEIGHT).GetHeight(); + tools::Long nCJK = aAttribs.Get(EE_CHAR_FONTHEIGHT_CJK).GetHeight(); + tools::Long nCTL = aAttribs.Get(EE_CHAR_FONTHEIGHT_CTL).GetHeight(); + + nWestern = ( nWestern * nPercent ) / 100; + nCJK = ( nCJK * nPercent ) / 100; + nCTL = ( nCTL * nPercent ) / 100; + + aAttribs.Put( SvxFontHeightItem( nWestern, 100, EE_CHAR_FONTHEIGHT ) ); + aAttribs.Put( SvxFontHeightItem( nCJK, 100, EE_CHAR_FONTHEIGHT_CJK ) ); + aAttribs.Put( SvxFontHeightItem( nCTL, 100, EE_CHAR_FONTHEIGHT_CTL ) ); + + rEngine.QuickSetAttribs( aAttribs, aSel ); //! remove paragraph attributes from aAttribs? + + nStart = nEnd; + } + } + + if ( bUpdateMode ) + rEngine.SetUpdateLayout( true ); +} + +static tools::Long lcl_GetEditSize( EditEngine& rEngine, bool bWidth, bool bSwap, Degree100 nAttrRotate ) +{ + if ( bSwap ) + bWidth = !bWidth; + + if ( nAttrRotate ) + { + tools::Long nRealWidth = static_cast(rEngine.CalcTextWidth()); + tools::Long nRealHeight = rEngine.GetTextHeight(); + + // assuming standard mode, otherwise width isn't used + + double nRealOrient = toRadians(nAttrRotate); // 1/100th degrees + double nAbsCos = fabs( cos( nRealOrient ) ); + double nAbsSin = fabs( sin( nRealOrient ) ); + if ( bWidth ) + return static_cast( nRealWidth * nAbsCos + nRealHeight * nAbsSin ); + else + return static_cast( nRealHeight * nAbsCos + nRealWidth * nAbsSin ); + } + else if ( bWidth ) + return static_cast(rEngine.CalcTextWidth()); + else + return rEngine.GetTextHeight(); +} + +void ScOutputData::ShrinkEditEngine( EditEngine& rEngine, const tools::Rectangle& rAlignRect, + tools::Long nLeftM, tools::Long nTopM, tools::Long nRightM, tools::Long nBottomM, + bool bWidth, SvxCellOrientation nOrient, Degree100 nAttrRotate, bool bPixelToLogic, + tools::Long& rEngineWidth, tools::Long& rEngineHeight, tools::Long& rNeededPixel, bool& rLeftClip, bool& rRightClip ) +{ + if ( !bWidth ) + { + // vertical + + tools::Long nScaleSize = bPixelToLogic ? + mpRefDevice->LogicToPixel(Size(0,rEngineHeight)).Height() : rEngineHeight; + + // Don't scale if it fits already. + // Allowing to extend into the margin, to avoid scaling at optimal height. + if ( nScaleSize <= rAlignRect.GetHeight() ) + return; + + bool bSwap = ( nOrient == SvxCellOrientation::TopBottom || nOrient == SvxCellOrientation::BottomUp ); + tools::Long nAvailable = rAlignRect.GetHeight() - nTopM - nBottomM; + tools::Long nScale = ( nAvailable * 100 ) / nScaleSize; + + lcl_ScaleFonts( rEngine, nScale ); + rEngineHeight = lcl_GetEditSize( rEngine, false, bSwap, nAttrRotate ); + tools::Long nNewSize = bPixelToLogic ? + mpRefDevice->LogicToPixel(Size(0,rEngineHeight)).Height() : rEngineHeight; + + sal_uInt16 nShrinkAgain = 0; + while ( nNewSize > nAvailable && nShrinkAgain < SC_SHRINKAGAIN_MAX ) + { + // further reduce, like in DrawStrings + lcl_ScaleFonts( rEngine, 90 ); // reduce by 10% + rEngineHeight = lcl_GetEditSize( rEngine, false, bSwap, nAttrRotate ); + nNewSize = bPixelToLogic ? + mpRefDevice->LogicToPixel(Size(0,rEngineHeight)).Height() : rEngineHeight; + ++nShrinkAgain; + } + + // sizes for further processing (alignment etc): + rEngineWidth = lcl_GetEditSize( rEngine, true, bSwap, nAttrRotate ); + tools::Long nPixelWidth = bPixelToLogic ? + mpRefDevice->LogicToPixel(Size(rEngineWidth,0)).Width() : rEngineWidth; + rNeededPixel = nPixelWidth + nLeftM + nRightM; + } + else if ( rLeftClip || rRightClip ) + { + // horizontal + + tools::Long nAvailable = rAlignRect.GetWidth() - nLeftM - nRightM; + tools::Long nScaleSize = rNeededPixel - nLeftM - nRightM; // without margin + + if ( nScaleSize <= nAvailable ) + return; + + tools::Long nScale = ( nAvailable * 100 ) / nScaleSize; + + lcl_ScaleFonts( rEngine, nScale ); + rEngineWidth = lcl_GetEditSize( rEngine, true, false, nAttrRotate ); + tools::Long nNewSize = bPixelToLogic ? + mpRefDevice->LogicToPixel(Size(rEngineWidth,0)).Width() : rEngineWidth; + + sal_uInt16 nShrinkAgain = 0; + while ( nNewSize > nAvailable && nShrinkAgain < SC_SHRINKAGAIN_MAX ) + { + // further reduce, like in DrawStrings + lcl_ScaleFonts( rEngine, 90 ); // reduce by 10% + rEngineWidth = lcl_GetEditSize( rEngine, true, false, nAttrRotate ); + nNewSize = bPixelToLogic ? + mpRefDevice->LogicToPixel(Size(rEngineWidth,0)).Width() : rEngineWidth; + ++nShrinkAgain; + } + if ( nNewSize <= nAvailable ) + rLeftClip = rRightClip = false; + + // sizes for further processing (alignment etc): + rNeededPixel = nNewSize + nLeftM + nRightM; + rEngineHeight = lcl_GetEditSize( rEngine, false, false, nAttrRotate ); + } +} + +ScOutputData::DrawEditParam::DrawEditParam(const ScPatternAttr* pPattern, const SfxItemSet* pCondSet, bool bCellIsValue) : + meHorJustAttr( lcl_GetValue(*pPattern, ATTR_HOR_JUSTIFY, pCondSet) ), + meHorJustContext( meHorJustAttr ), + meHorJustResult( meHorJustAttr ), + meVerJust( lcl_GetValue(*pPattern, ATTR_VER_JUSTIFY, pCondSet) ), + meHorJustMethod( lcl_GetValue(*pPattern, ATTR_HOR_JUSTIFY_METHOD, pCondSet) ), + meVerJustMethod( lcl_GetValue(*pPattern, ATTR_VER_JUSTIFY_METHOD, pCondSet) ), + meOrient( pPattern->GetCellOrientation(pCondSet) ), + mnArrY(0), + mnX(0), mnCellX(0), mnCellY(0), + mnPosX(0), mnPosY(0), mnInitPosX(0), + mbBreak( (meHorJustAttr == SvxCellHorJustify::Block) || lcl_GetBoolValue(*pPattern, ATTR_LINEBREAK, pCondSet) ), + mbCellIsValue(bCellIsValue), + mbAsianVertical(false), + mbPixelToLogic(false), + mbHyphenatorSet(false), + mpEngine(nullptr), + mpPattern(pPattern), + mpCondSet(pCondSet), + mpPreviewFontSet(nullptr), + mpOldPattern(nullptr), + mpOldCondSet(nullptr), + mpOldPreviewFontSet(nullptr), + mpThisRowInfo(nullptr), + mpMisspellRanges(nullptr) +{} + +bool ScOutputData::DrawEditParam::readCellContent( + const ScDocument* pDoc, bool bShowNullValues, bool bShowFormulas, bool bSyntaxMode, bool bUseStyleColor, bool bForceAutoColor, bool& rWrapFields) +{ + if (maCell.getType() == CELLTYPE_EDIT) + { + const EditTextObject* pData = maCell.getEditText(); + if (pData) + { + mpEngine->SetTextCurrentDefaults(*pData); + + if ( mbBreak && !mbAsianVertical && pData->HasField() ) + { + // Fields aren't wrapped, so clipping is enabled to prevent + // a field from being drawn beyond the cell size + + rWrapFields = true; + } + } + else + { + OSL_FAIL("pData == 0"); + return false; + } + } + else + { + sal_uInt32 nFormat = mpPattern->GetNumberFormat( + pDoc->GetFormatTable(), mpCondSet ); + const Color* pColor; + OUString aString = ScCellFormat::GetString( maCell, + nFormat, &pColor, + *pDoc->GetFormatTable(), + *pDoc, + bShowNullValues, + bShowFormulas); + + mpEngine->SetTextCurrentDefaults(aString); + if ( pColor && !bSyntaxMode && !( bUseStyleColor && bForceAutoColor ) ) + lcl_SetEditColor( *mpEngine, *pColor ); + } + + if (mpMisspellRanges) + mpEngine->SetAllMisspellRanges(*mpMisspellRanges); + + return true; +} + +void ScOutputData::DrawEditParam::setPatternToEngine(bool bUseStyleColor) +{ + // syntax highlighting mode is ignored here + // StringDiffer doesn't look at hyphenate, language items + + if (SfxPoolItem::areSame(mpPattern, mpOldPattern) && mpCondSet == mpOldCondSet && mpPreviewFontSet == mpOldPreviewFontSet ) + return; + + Color nConfBackColor = SC_MOD()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor; + bool bCellContrast = bUseStyleColor && + Application::GetSettings().GetStyleSettings().GetHighContrastMode(); + + auto pSet = std::make_unique( mpEngine->GetEmptyItemSet() ); + mpPattern->FillEditItemSet( pSet.get(), mpCondSet ); + if ( mpPreviewFontSet ) + { + if ( const SvxFontItem* pItem = mpPreviewFontSet->GetItemIfSet( ATTR_FONT ) ) + { + // tdf#125054 adapt WhichID + pSet->Put(*pItem, EE_CHAR_FONTINFO); + } + if ( const SvxFontItem* pItem = mpPreviewFontSet->GetItemIfSet( ATTR_CJK_FONT ) ) + { + // tdf#125054 adapt WhichID + pSet->Put(*pItem, EE_CHAR_FONTINFO_CJK); + } + if ( const SvxFontItem* pItem = mpPreviewFontSet->GetItemIfSet( ATTR_CTL_FONT ) ) + { + // tdf#125054 adapt WhichID + pSet->Put(*pItem, EE_CHAR_FONTINFO_CTL); + } + } + bool bParaHyphenate = pSet->Get(EE_PARA_HYPHENATE).GetValue(); + mpEngine->SetDefaults( std::move(pSet) ); + mpOldPattern = mpPattern; + mpOldCondSet = mpCondSet; + mpOldPreviewFontSet = mpPreviewFontSet; + + EEControlBits nControl = mpEngine->GetControlWord(); + if (meOrient == SvxCellOrientation::Stacked) + nControl |= EEControlBits::ONECHARPERLINE; + else + nControl &= ~EEControlBits::ONECHARPERLINE; + mpEngine->SetControlWord( nControl ); + + if ( !mbHyphenatorSet && bParaHyphenate ) + { + // set hyphenator the first time it is needed + css::uno::Reference xXHyphenator( LinguMgr::GetHyphenator() ); + mpEngine->SetHyphenator( xXHyphenator ); + mbHyphenatorSet = true; + } + + Color aBackCol = mpPattern->GetItem( ATTR_BACKGROUND, mpCondSet ).GetColor(); + if ( bUseStyleColor && ( aBackCol.IsTransparent() || bCellContrast ) ) + aBackCol = nConfBackColor; + mpEngine->SetBackgroundColor( aBackCol ); +} + +void ScOutputData::DrawEditParam::calcMargins(tools::Long& rTopM, tools::Long& rLeftM, tools::Long& rBottomM, tools::Long& rRightM, double nPPTX, double nPPTY) const +{ + const SvxMarginItem& rMargin = mpPattern->GetItem(ATTR_MARGIN, mpCondSet); + + sal_uInt16 nIndent = 0; + if (meHorJustAttr == SvxCellHorJustify::Left || meHorJustAttr == SvxCellHorJustify::Right) + nIndent = lcl_GetValue(*mpPattern, ATTR_INDENT, mpCondSet); + + rLeftM = static_cast(((rMargin.GetLeftMargin() + nIndent) * nPPTX)); + rTopM = static_cast((rMargin.GetTopMargin() * nPPTY)); + rRightM = static_cast((rMargin.GetRightMargin() * nPPTX)); + rBottomM = static_cast((rMargin.GetBottomMargin() * nPPTY)); + if(meHorJustAttr == SvxCellHorJustify::Right) + { + rLeftM = static_cast((rMargin.GetLeftMargin() * nPPTX)); + rRightM = static_cast(((rMargin.GetRightMargin() + nIndent) * nPPTX)); + } +} + +void ScOutputData::DrawEditParam::calcPaperSize( + Size& rPaperSize, const tools::Rectangle& rAlignRect, double nPPTX, double nPPTY) const +{ + tools::Long nTopM, nLeftM, nBottomM, nRightM; + calcMargins(nTopM, nLeftM, nBottomM, nRightM, nPPTX, nPPTY); + + if (isVerticallyOriented()) + { + rPaperSize.setWidth( rAlignRect.GetHeight() - nTopM - nBottomM ); + rPaperSize.setHeight( rAlignRect.GetWidth() - nLeftM - nRightM ); + } + else + { + rPaperSize.setWidth( rAlignRect.GetWidth() - nLeftM - nRightM ); + rPaperSize.setHeight( rAlignRect.GetHeight() - nTopM - nBottomM ); + } + + if (mbAsianVertical) + { + rPaperSize.setHeight( rAlignRect.GetHeight() - nTopM - nBottomM ); + // Subtract some extra value from the height or else the text would go + // outside the cell area. The value of 5 is arbitrary, and is based + // entirely on heuristics. + rPaperSize.AdjustHeight( -5 ); + } +} + +void ScOutputData::DrawEditParam::getEngineSize(ScFieldEditEngine* pEngine, tools::Long& rWidth, tools::Long& rHeight) const +{ + tools::Long nEngineWidth = 0; + if (!mbBreak || meOrient == SvxCellOrientation::Stacked || mbAsianVertical) + nEngineWidth = static_cast(pEngine->CalcTextWidth()); + + tools::Long nEngineHeight = pEngine->GetTextHeight(); + + if (isVerticallyOriented()) + std::swap( nEngineWidth, nEngineHeight ); + + if (meOrient == SvxCellOrientation::Stacked) + nEngineWidth = nEngineWidth * 11 / 10; + + rWidth = nEngineWidth; + rHeight = nEngineHeight; +} + +bool ScOutputData::DrawEditParam::hasLineBreak() const +{ + return (mbBreak || (meOrient == SvxCellOrientation::Stacked) || mbAsianVertical); +} + +bool ScOutputData::DrawEditParam::isHyperlinkCell() const +{ + if (maCell.getType() != CELLTYPE_FORMULA) + return false; + + return maCell.getFormula()->IsHyperLinkCell(); +} + +bool ScOutputData::DrawEditParam::isVerticallyOriented() const +{ + return (meOrient == SvxCellOrientation::TopBottom || meOrient == SvxCellOrientation::BottomUp); +} + +void ScOutputData::DrawEditParam::calcStartPosForVertical( + Point& rLogicStart, tools::Long nCellWidth, tools::Long nEngineWidth, tools::Long nTopM, const OutputDevice* pRefDevice) +{ + OSL_ENSURE(isVerticallyOriented(), "Use this only for vertically oriented cell!"); + + if (mbPixelToLogic) + rLogicStart = pRefDevice->PixelToLogic(rLogicStart); + + if (!mbBreak) + return; + + // vertical adjustment is within the EditEngine + if (mbPixelToLogic) + rLogicStart.AdjustY(pRefDevice->PixelToLogic(Size(0,nTopM)).Height() ); + else + rLogicStart.AdjustY(nTopM ); + + switch (meHorJustResult) + { + case SvxCellHorJustify::Center: + rLogicStart.AdjustX((nCellWidth - nEngineWidth) / 2 ); + break; + case SvxCellHorJustify::Right: + rLogicStart.AdjustX(nCellWidth - nEngineWidth ); + break; + default: + ; // do nothing + } +} + +void ScOutputData::DrawEditParam::setAlignmentToEngine() +{ + if (isVerticallyOriented() || mbAsianVertical) + { + SvxAdjust eSvxAdjust = SvxAdjust::Left; + switch (meVerJust) + { + case SvxCellVerJustify::Top: + eSvxAdjust = (meOrient == SvxCellOrientation::TopBottom || mbAsianVertical) ? + SvxAdjust::Left : SvxAdjust::Right; + break; + case SvxCellVerJustify::Center: + eSvxAdjust = SvxAdjust::Center; + break; + case SvxCellVerJustify::Bottom: + case SvxCellVerJustify::Standard: + eSvxAdjust = (meOrient == SvxCellOrientation::TopBottom || mbAsianVertical) ? + SvxAdjust::Right : SvxAdjust::Left; + break; + case SvxCellVerJustify::Block: + eSvxAdjust = SvxAdjust::Block; + break; + } + + mpEngine->SetDefaultItem( SvxAdjustItem(eSvxAdjust, EE_PARA_JUST) ); + mpEngine->SetDefaultItem( SvxJustifyMethodItem(meVerJustMethod, EE_PARA_JUST_METHOD) ); + + if (meHorJustResult == SvxCellHorJustify::Block) + mpEngine->SetDefaultItem( SvxVerJustifyItem(SvxCellVerJustify::Block, EE_PARA_VER_JUST) ); + } + else + { + // horizontal alignment now may depend on cell content + // (for values with number formats with mixed script types) + // -> always set adjustment + + SvxAdjust eSvxAdjust = SvxAdjust::Left; + if (meOrient == SvxCellOrientation::Stacked) + eSvxAdjust = SvxAdjust::Center; + else if (mbBreak) + { + if (meOrient == SvxCellOrientation::Standard) + switch (meHorJustResult) + { + case SvxCellHorJustify::Repeat: // repeat is not yet implemented + case SvxCellHorJustify::Standard: + SAL_WARN("sc.ui","meHorJustResult does not match getAlignmentFromContext()"); + [[fallthrough]]; + case SvxCellHorJustify::Left: + eSvxAdjust = SvxAdjust::Left; + break; + case SvxCellHorJustify::Center: + eSvxAdjust = SvxAdjust::Center; + break; + case SvxCellHorJustify::Right: + eSvxAdjust = SvxAdjust::Right; + break; + case SvxCellHorJustify::Block: + eSvxAdjust = SvxAdjust::Block; + break; + } + else + switch (meVerJust) + { + case SvxCellVerJustify::Top: + eSvxAdjust = SvxAdjust::Right; + break; + case SvxCellVerJustify::Center: + eSvxAdjust = SvxAdjust::Center; + break; + case SvxCellVerJustify::Bottom: + case SvxCellVerJustify::Standard: + eSvxAdjust = SvxAdjust::Left; + break; + case SvxCellVerJustify::Block: + eSvxAdjust = SvxAdjust::Block; + break; + } + } + + mpEngine->SetDefaultItem( SvxAdjustItem(eSvxAdjust, EE_PARA_JUST) ); + + if (mbAsianVertical) + { + mpEngine->SetDefaultItem( SvxJustifyMethodItem(meVerJustMethod, EE_PARA_JUST_METHOD) ); + if (meHorJustResult == SvxCellHorJustify::Block) + mpEngine->SetDefaultItem( SvxVerJustifyItem(SvxCellVerJustify::Block, EE_PARA_VER_JUST) ); + } + else + { + mpEngine->SetDefaultItem( SvxJustifyMethodItem(meHorJustMethod, EE_PARA_JUST_METHOD) ); + if (meVerJust == SvxCellVerJustify::Block) + mpEngine->SetDefaultItem( SvxVerJustifyItem(SvxCellVerJustify::Block, EE_PARA_VER_JUST) ); + } + } + + mpEngine->SetVertical(mbAsianVertical); + if (maCell.getType() == CELLTYPE_EDIT) + { + // We need to synchronize the vertical mode in the EditTextObject + // instance too. No idea why we keep this state in two separate + // instances. + const EditTextObject* pData = maCell.getEditText(); + if (pData) + const_cast(pData)->SetVertical(mbAsianVertical); + } +} + +bool ScOutputData::DrawEditParam::adjustHorAlignment(ScFieldEditEngine* pEngine) +{ + if (meHorJustResult == SvxCellHorJustify::Right || meHorJustResult == SvxCellHorJustify::Center) + { + SvxAdjust eEditAdjust = (meHorJustResult == SvxCellHorJustify::Center) ? + SvxAdjust::Center : SvxAdjust::Right; + + const bool bPrevUpdateLayout = pEngine->SetUpdateLayout(false); + pEngine->SetDefaultItem( SvxAdjustItem(eEditAdjust, EE_PARA_JUST) ); + pEngine->SetUpdateLayout(bPrevUpdateLayout); + return true; + } + return false; +} + +void ScOutputData::DrawEditParam::adjustForHyperlinkInPDF(Point aURLStart, const OutputDevice* pDev) +{ + // PDF: whole-cell hyperlink from formula? + vcl::PDFExtOutDevData* pPDFData = dynamic_cast( pDev->GetExtOutDevData() ); + bool bHasURL = pPDFData && isHyperlinkCell(); + if (!bHasURL) + return; + + tools::Long nURLWidth = static_cast(mpEngine->CalcTextWidth()); + tools::Long nURLHeight = mpEngine->GetTextHeight(); + if (mbBreak) + { + Size aPaper = mpEngine->GetPaperSize(); + if ( mbAsianVertical ) + nURLHeight = aPaper.Height(); + else + nURLWidth = aPaper.Width(); + } + if (isVerticallyOriented()) + std::swap( nURLWidth, nURLHeight ); + else if (mbAsianVertical) + aURLStart.AdjustX( -nURLWidth ); + + tools::Rectangle aURLRect( aURLStart, Size( nURLWidth, nURLHeight ) ); + lcl_DoHyperlinkResult(pDev, aURLRect, maCell); +} + +// Returns true if the rect is clipped vertically +bool ScOutputData::AdjustAreaParamClipRect(OutputAreaParam& rAreaParam) +{ + if( rAreaParam.maClipRect.Left() < nScrX ) + { + rAreaParam.maClipRect.SetLeft( nScrX ); + rAreaParam.mbLeftClip = true; + } + if( rAreaParam.maClipRect.Right() > nScrX + nScrW ) + { + rAreaParam.maClipRect.SetRight( nScrX + nScrW ); //! minus one? + rAreaParam.mbRightClip = true; + } + + bool bVClip = false; + + if( rAreaParam.maClipRect.Top() < nScrY ) + { + rAreaParam.maClipRect.SetTop( nScrY ); + bVClip = true; + } + if( rAreaParam.maClipRect.Bottom() > nScrY + nScrH ) + { + rAreaParam.maClipRect.SetBottom( nScrY + nScrH ); //! minus one? + bVClip = true; + } + return bVClip; +} + +// Doesn't handle clip marks - should be handled in advance using GetOutputArea +class ClearableClipRegion +{ +public: + ClearableClipRegion( const tools::Rectangle& rRect, bool bClip, bool bSimClip, + const VclPtr& pDev, bool bMetaFile ) + :mbMetaFile(bMetaFile) + { + if (!(bClip || bSimClip)) + return; + + maRect = rRect; + if (bClip) // for bSimClip only initialize aClipRect + { + mpDev.reset(pDev); + if (mbMetaFile) + { + mpDev->Push(); + mpDev->IntersectClipRegion(maRect); + } + else + mpDev->SetClipRegion(vcl::Region(maRect)); + } + } + + ~ClearableClipRegion() COVERITY_NOEXCEPT_FALSE + { + // Pop() or SetClipRegion() must only be called in case bClip was true + // in the ctor, and only then mpDev is set. + if (mpDev) + { + if (mbMetaFile) + mpDev->Pop(); + else + mpDev->SetClipRegion(); + } + } + + const tools::Rectangle& getRect() const { return maRect; } + +private: + tools::Rectangle maRect; + VclPtr mpDev; + bool mbMetaFile; +}; + +// Returns needed width in current units; sets rNeededPixel to needed width in pixels +tools::Long ScOutputData::SetEngineTextAndGetWidth( DrawEditParam& rParam, const OUString& rSetString, + tools::Long& rNeededPixel, tools::Long nAddWidthPixels ) +{ + rParam.mpEngine->SetTextCurrentDefaults( rSetString ); + tools::Long nEngineWidth = static_cast( rParam.mpEngine->CalcTextWidth() ); + if ( rParam.mbPixelToLogic ) + rNeededPixel = mpRefDevice->LogicToPixel( Size( nEngineWidth, 0 ) ).Width(); + else + rNeededPixel = nEngineWidth; + + rNeededPixel += nAddWidthPixels; + + return nEngineWidth; +} + +void ScOutputData::DrawEditStandard(DrawEditParam& rParam) +{ + OSL_ASSERT(rParam.meOrient == SvxCellOrientation::Standard); + OSL_ASSERT(!rParam.mbAsianVertical); + + Size aRefOne = mpRefDevice->PixelToLogic(Size(1,1)); + + bool bRepeat = (rParam.meHorJustAttr == SvxCellHorJustify::Repeat && !rParam.mbBreak); + bool bShrink = !rParam.mbBreak && !bRepeat && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet); + Degree100 nAttrRotate = lcl_GetValue(*rParam.mpPattern, ATTR_ROTATE_VALUE, rParam.mpCondSet); + + if ( rParam.meHorJustAttr == SvxCellHorJustify::Repeat ) + { + // ignore orientation/rotation if "repeat" is active + rParam.meOrient = SvxCellOrientation::Standard; + nAttrRotate = 0_deg100; + + // #i31843# "repeat" with "line breaks" is treated as default alignment + // (but rotation is still disabled). + // Default again leads to context dependent alignment instead of + // SvxCellHorJustify::Standard. + if ( rParam.mbBreak ) + rParam.meHorJustResult = rParam.meHorJustContext; + } + + if (nAttrRotate) + { + //! set flag to find the cell in DrawRotated again ? + //! (or flag already set during DrawBackground, then no query here) + return; // rotated is outputted separately + } + + SvxCellHorJustify eOutHorJust = rParam.meHorJustContext; + + //! mirror margin values for RTL? + //! move margin down to after final GetOutputArea call + tools::Long nTopM, nLeftM, nBottomM, nRightM; + rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY); + + SCCOL nXForPos = rParam.mnX; + if ( nXForPos < nX1 ) + { + nXForPos = nX1; + rParam.mnPosX = rParam.mnInitPosX; + } + SCSIZE nArrYForPos = rParam.mnArrY; + if ( nArrYForPos < 1 ) + { + nArrYForPos = 1; + rParam.mnPosY = nScrY; + } + + OutputAreaParam aAreaParam; + + // Initial page size - large for normal text, cell size for automatic line breaks + + Size aPaperSize( 1000000, 1000000 ); + if (rParam.mbBreak) + { + // call GetOutputArea with nNeeded=0, to get only the cell width + + //! handle nArrY == 0 + GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0, + *rParam.mpPattern, sal::static_int_cast(eOutHorJust), + rParam.mbCellIsValue, true, false, aAreaParam ); + + //! special ScEditUtil handling if formatting for printer + rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY); + } + if (rParam.mbPixelToLogic) + { + Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize); + if ( rParam.mbBreak && !rParam.mbAsianVertical && mpRefDevice != pFmtDevice ) + { + // #i85342# screen display and formatting for printer, + // use same GetEditArea call as in ScViewData::SetEditEngine + + Fraction aFract(1,1); + tools::Rectangle aUtilRect = ScEditUtil( mpDoc, rParam.mnCellX, rParam.mnCellY, nTab, Point(0,0), pFmtDevice, + HMM_PER_TWIPS, HMM_PER_TWIPS, aFract, aFract ).GetEditArea( rParam.mpPattern, false ); + aLogicSize.setWidth( aUtilRect.GetWidth() ); + } + rParam.mpEngine->SetPaperSize(aLogicSize); + } + else + rParam.mpEngine->SetPaperSize(aPaperSize); + + // Fill the EditEngine (cell attributes and text) + + // default alignment for asian vertical mode is top-right + if ( rParam.mbAsianVertical && rParam.meVerJust == SvxCellVerJustify::Standard ) + rParam.meVerJust = SvxCellVerJustify::Top; + + rParam.setPatternToEngine(mbUseStyleColor); + rParam.setAlignmentToEngine(); + // Don't format unnecessary parts if the text will be drawn from top (Standard will + // act that way if text doesn't fit, see below). + rParam.mpEngine->EnableSkipOutsideFormat(rParam.meVerJust==SvxCellVerJustify::Top + || rParam.meVerJust==SvxCellVerJustify::Standard); + + // Read content from cell + + bool bWrapFields = false; + if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields)) + // Failed to read cell content. Bail out. + return; + + if ( mbSyntaxMode ) + SetEditSyntaxColor(*rParam.mpEngine, rParam.maCell); + else if ( mbUseStyleColor && mbForceAutoColor ) + lcl_SetEditColor( *rParam.mpEngine, COL_AUTO ); //! or have a flag at EditEngine + + rParam.mpEngine->SetUpdateLayout( true ); // after SetText, before CalcTextWidth/GetTextHeight + + // Get final output area using the calculated width + + tools::Long nEngineWidth, nEngineHeight; + rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight); + + tools::Long nNeededPixel = nEngineWidth; + if (rParam.mbPixelToLogic) + nNeededPixel = mpRefDevice->LogicToPixel(Size(nNeededPixel,0)).Width(); + nNeededPixel += nLeftM + nRightM; + + if (!rParam.mbBreak || bShrink) + { + // for break, the first GetOutputArea call is sufficient + GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, nNeededPixel, + *rParam.mpPattern, sal::static_int_cast(eOutHorJust), + rParam.mbCellIsValue || bRepeat || bShrink, false, false, aAreaParam ); + + if ( bShrink ) + { + ShrinkEditEngine( *rParam.mpEngine, aAreaParam.maAlignRect, + nLeftM, nTopM, nRightM, nBottomM, true, + rParam.meOrient, 0_deg100, rParam.mbPixelToLogic, + nEngineWidth, nEngineHeight, nNeededPixel, + aAreaParam.mbLeftClip, aAreaParam.mbRightClip ); + } + if ( bRepeat && !aAreaParam.mbLeftClip && !aAreaParam.mbRightClip && rParam.mpEngine->GetParagraphCount() == 1 ) + { + // First check if twice the space for the formatted text is available + // (otherwise just keep it unchanged). + + tools::Long nFormatted = nNeededPixel - nLeftM - nRightM; // without margin + tools::Long nAvailable = aAreaParam.maAlignRect.GetWidth() - nLeftM - nRightM; + if ( nAvailable >= 2 * nFormatted ) + { + // "repeat" is handled with unformatted text (for performance reasons) + OUString aCellStr = rParam.mpEngine->GetText(); + + tools::Long nRepeatSize = 0; + SetEngineTextAndGetWidth( rParam, aCellStr, nRepeatSize, 0 ); + if ( pFmtDevice != mpRefDevice ) + ++nRepeatSize; + if ( nRepeatSize > 0 ) + { + tools::Long nRepeatCount = nAvailable / nRepeatSize; + if ( nRepeatCount > 1 ) + { + OUStringBuffer aRepeated(aCellStr); + for ( tools::Long nRepeat = 1; nRepeat < nRepeatCount; nRepeat++ ) + aRepeated.append(aCellStr); + + SetEngineTextAndGetWidth( rParam, aRepeated.makeStringAndClear(), + nNeededPixel, (nLeftM + nRightM ) ); + + nEngineHeight = rParam.mpEngine->GetTextHeight(); + } + } + } + } + + + if ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) ) + { + SetEngineTextAndGetWidth( rParam, "###", nNeededPixel, ( nLeftM + nRightM ) ); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + ScCellInfo* pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX); + SetClipMarks( aAreaParam, pClipMarkCell, eOutHorJust, nLayoutSign ); + } + + if (eOutHorJust != SvxCellHorJustify::Left) + { + aPaperSize.setWidth( nNeededPixel + 1 ); + if (rParam.mbPixelToLogic) + rParam.mpEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize)); + else + rParam.mpEngine->SetPaperSize(aPaperSize); + } + } + + tools::Long nStartX = aAreaParam.maAlignRect.Left(); + tools::Long nStartY = aAreaParam.maAlignRect.Top(); + tools::Long nCellWidth = aAreaParam.maAlignRect.GetWidth(); + tools::Long nOutWidth = nCellWidth - 1 - nLeftM - nRightM; + tools::Long nOutHeight = aAreaParam.maAlignRect.GetHeight() - nTopM - nBottomM; + + if (rParam.mbBreak) + { + // text with automatic breaks is aligned only within the + // edit engine's paper size, the output of the whole area + // is always left-aligned + + nStartX += nLeftM; + } + else + { + if ( eOutHorJust == SvxCellHorJustify::Right ) + nStartX -= nNeededPixel - nCellWidth + nRightM + 1; + else if ( eOutHorJust == SvxCellHorJustify::Center ) + nStartX -= ( nNeededPixel - nCellWidth + nRightM + 1 - nLeftM ) / 2; + else + nStartX += nLeftM; + } + + bool bOutside = (aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW); + if (bOutside) + return; + + // Also take fields in a cell with automatic breaks into account: clip to cell width + bool bClip = AdjustAreaParamClipRect(aAreaParam) || aAreaParam.mbLeftClip || aAreaParam.mbRightClip || bWrapFields; + bool bSimClip = false; + + Size aCellSize; // output area, excluding margins, in logical units + if (rParam.mbPixelToLogic) + aCellSize = mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) ); + else + aCellSize = Size( nOutWidth, nOutHeight ); + + if ( nEngineHeight >= aCellSize.Height() + aRefOne.Height() ) + { + const ScMergeAttr* pMerge = &rParam.mpPattern->GetItem(ATTR_MERGE); + bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1; + + // Don't clip for text height when printing rows with optimal height, + // except when font size is from conditional formatting. + //! Allow clipping when vertically merged? + if ( eType != OUTTYPE_PRINTER || + ( mpDoc->GetRowFlags( rParam.mnCellY, nTab ) & CRFlags::ManualSize ) || + ( rParam.mpCondSet && SfxItemState::SET == + rParam.mpCondSet->GetItemState(ATTR_FONT_HEIGHT) ) ) + bClip = true; + else + bSimClip = true; + + // Show clip marks if height is at least 5pt too small and + // there are several lines of text. + // Not for asian vertical text, because that would interfere + // with the default right position of the text. + // Only with automatic line breaks, to avoid having to find + // the cells with the horizontal end of the text again. + if ( nEngineHeight - aCellSize.Height() > 100 && + rParam.mbBreak && bMarkClipped && + ( rParam.mpEngine->GetParagraphCount() > 1 || rParam.mpEngine->GetLineCount(0) > 1 ) ) + { + ScCellInfo* pClipMarkCell = nullptr; + if ( bMerged ) + { + // anywhere in the merged area... + SCCOL nClipX = ( rParam.mnX < nX1 ) ? nX1 : rParam.mnX; + pClipMarkCell = &pRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].cellInfo(nClipX); + } + else + pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX); + + pClipMarkCell->nClipMark |= ScClipMark::Right; //! also allow left? + bAnyClipped = true; + + tools::Long nMarkPixel = static_cast( SC_CLIPMARK_SIZE * mnPPTX ); + if ( aAreaParam.maClipRect.Right() - nMarkPixel > aAreaParam.maClipRect.Left() ) + aAreaParam.maClipRect.AdjustRight( -nMarkPixel ); + + // Standard is normally treated as Bottom, but if text height is clipped, then + // Top looks better and also allows using EditEngine::EnableSkipOutsideFormat(). + if (rParam.meVerJust==SvxCellVerJustify::Standard) + rParam.meVerJust=SvxCellVerJustify::Top; + } + } + + Point aURLStart; + + { // Clip marks are already handled in GetOutputArea + ClearableClipRegion aClip(rParam.mbPixelToLogic ? mpRefDevice->PixelToLogic(aAreaParam.maClipRect) + : aAreaParam.maClipRect, bClip, bSimClip, mpDev, bMetaFile); + + Point aLogicStart; + if (rParam.mbPixelToLogic) + aLogicStart = mpRefDevice->PixelToLogic( Point(nStartX,nStartY) ); + else + aLogicStart = Point(nStartX, nStartY); + + if (!rParam.mbBreak) + { + // horizontal alignment + if (rParam.adjustHorAlignment(rParam.mpEngine)) + // reset adjustment for the next cell + rParam.mpOldPattern = nullptr; + } + + if (rParam.meVerJust==SvxCellVerJustify::Bottom || + rParam.meVerJust==SvxCellVerJustify::Standard) + { + //! if pRefDevice != pFmtDevice, keep heights in logic units, + //! only converting margin? + + if (rParam.mbPixelToLogic) + aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, nTopM + + mpRefDevice->LogicToPixel(aCellSize).Height() - + mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height() + )).Height() ); + else + aLogicStart.AdjustY(nTopM + aCellSize.Height() - nEngineHeight ); + } + else if (rParam.meVerJust==SvxCellVerJustify::Center) + { + if (rParam.mbPixelToLogic) + aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, nTopM + ( + mpRefDevice->LogicToPixel(aCellSize).Height() - + mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height() ) + / 2)).Height() ); + else + aLogicStart.AdjustY(nTopM + (aCellSize.Height() - nEngineHeight) / 2 ); + } + else // top + { + if (rParam.mbPixelToLogic) + aLogicStart.AdjustY(mpRefDevice->PixelToLogic(Size(0,nTopM)).Height() ); + else + aLogicStart.AdjustY(nTopM ); + } + + aURLStart = aLogicStart; // copy before modifying for orientation + + // bMoveClipped handling has been replaced by complete alignment + // handling (also extending to the left). + + if (bSimClip) + { + // no hard clip, only draw the affected rows + Point aDocStart = aClip.getRect().TopLeft(); + aDocStart -= aLogicStart; + rParam.mpEngine->Draw(*mpDev, aClip.getRect(), aDocStart, false); + } + else + { + rParam.mpEngine->Draw(*mpDev, aLogicStart); + } + } + + rParam.adjustForHyperlinkInPDF(aURLStart, mpDev); +} + +void ScOutputData::SetClipMarks( OutputAreaParam &aAreaParam, ScCellInfo* pClipMarkCell, + SvxCellHorJustify eOutHorJust, + tools::Long nLayoutSign ) +{ + tools::Long nMarkPixel = SC_CLIPMARK_SIZE * mnPPTX; + + if ( eOutHorJust == SvxCellHorJustify::Left ) + { + pClipMarkCell->nClipMark |= ScClipMark::Right; + bAnyClipped = true; + aAreaParam.maClipRect.AdjustRight( -( nMarkPixel * nLayoutSign ) ); + } + else if ( eOutHorJust == SvxCellHorJustify::Right ) + { + pClipMarkCell->nClipMark |= ScClipMark::Left; + bAnyClipped = true; + aAreaParam.maClipRect.AdjustLeft( nMarkPixel * nLayoutSign ); + } + else + { + pClipMarkCell->nClipMark |= ScClipMark::Right; + pClipMarkCell->nClipMark |= ScClipMark::Left; + bAnyClipped = true; + aAreaParam.maClipRect.AdjustRight( -( nMarkPixel * nLayoutSign ) ); + aAreaParam.maClipRect.AdjustLeft( nMarkPixel * nLayoutSign ); + } + +} + +void ScOutputData::ShowClipMarks( DrawEditParam& rParam, tools::Long nEngineWidth, const Size& aCellSize, + bool bMerged, OutputAreaParam& aAreaParam, bool bTop) +{ + // Show clip marks if width is at least 5pt too small and + // there are several lines of text. + // Not for asian vertical text, because that would interfere + // with the default right position of the text. + // Only with automatic line breaks, to avoid having to find + // the cells with the horizontal end of the text again. + if (nEngineWidth - aCellSize.Width() <= 100 || !rParam.mbBreak || !bMarkClipped + || (rParam.mpEngine->GetParagraphCount() <= 1 && rParam.mpEngine->GetLineCount(0) <= 1)) + return; + + ScCellInfo* pClipMarkCell = nullptr; + if (bMerged) + { + // anywhere in the merged area... + SCCOL nClipX = (rParam.mnX < nX1) ? nX1 : rParam.mnX; + pClipMarkCell = &pRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].cellInfo(nClipX); + } + else + pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX); + + bAnyClipped = true; + bVertical = true; + const tools::Long nMarkPixel = static_cast(SC_CLIPMARK_SIZE * mnPPTX); + if (bTop) + { + pClipMarkCell->nClipMark |= ScClipMark::Top; + if (aAreaParam.maClipRect.Top() - nMarkPixel < aAreaParam.maClipRect.Bottom()) + aAreaParam.maClipRect.AdjustTop(+nMarkPixel); + } + else + { + pClipMarkCell->nClipMark |= ScClipMark::Bottom; + if (aAreaParam.maClipRect.Top() - nMarkPixel < aAreaParam.maClipRect.Bottom()) + aAreaParam.maClipRect.AdjustBottom(-nMarkPixel); + } +} + +ClearableClipRegionPtr ScOutputData::Clip( DrawEditParam& rParam, const Size& aCellSize, + OutputAreaParam& aAreaParam, tools::Long nEngineWidth, + bool bWrapFields, bool bTop) +{ + // Also take fields in a cell with automatic breaks into account: clip to cell width + bool bClip = AdjustAreaParamClipRect(aAreaParam) || aAreaParam.mbLeftClip || aAreaParam.mbRightClip || bWrapFields; + bool bSimClip = false; + + const Size& aRefOne = mpRefDevice->PixelToLogic(Size(1,1)); + if ( nEngineWidth >= aCellSize.Width() + aRefOne.Width() ) + { + const ScMergeAttr* pMerge = &rParam.mpPattern->GetItem(ATTR_MERGE); + const bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1; + + // Don't clip for text height when printing rows with optimal height, + // except when font size is from conditional formatting. + //! Allow clipping when vertically merged? + if ( eType != OUTTYPE_PRINTER || + ( mpDoc->GetRowFlags( rParam.mnCellY, nTab ) & CRFlags::ManualSize ) || + ( rParam.mpCondSet && SfxItemState::SET == + rParam.mpCondSet->GetItemState(ATTR_FONT_HEIGHT) ) ) + bClip = true; + else + bSimClip = true; + + ShowClipMarks( rParam, nEngineWidth, aCellSize, bMerged, aAreaParam, bTop); + } + + // Clip marks are already handled in GetOutputArea + return ClearableClipRegionPtr(new ClearableClipRegion(rParam.mbPixelToLogic ? + mpRefDevice->PixelToLogic(aAreaParam.maClipRect) + : aAreaParam.maClipRect, bClip, bSimClip, mpDev, bMetaFile)); +} + +void ScOutputData::DrawEditBottomTop(DrawEditParam& rParam) +{ + OSL_ASSERT(rParam.meHorJustAttr != SvxCellHorJustify::Repeat); + + const bool bRepeat = (rParam.meHorJustAttr == SvxCellHorJustify::Repeat && !rParam.mbBreak); + const bool bShrink = !rParam.mbBreak && !bRepeat && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet); + + SvxCellHorJustify eOutHorJust = rParam.meHorJustContext; + + //! mirror margin values for RTL? + //! move margin down to after final GetOutputArea call + tools::Long nTopM, nLeftM, nBottomM, nRightM; + rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY); + + SCCOL nXForPos = rParam.mnX; + if ( nXForPos < nX1 ) + { + nXForPos = nX1; + rParam.mnPosX = rParam.mnInitPosX; + } + SCSIZE nArrYForPos = rParam.mnArrY; + if ( nArrYForPos < 1 ) + { + nArrYForPos = 1; + rParam.mnPosY = nScrY; + } + + OutputAreaParam aAreaParam; + + // Initial page size - large for normal text, cell size for automatic line breaks + + Size aPaperSize( 1000000, 1000000 ); + if (rParam.mbBreak) + { + // call GetOutputArea with nNeeded=0, to get only the cell width + + //! handle nArrY == 0 + GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0, + *rParam.mpPattern, sal::static_int_cast(eOutHorJust), + rParam.mbCellIsValue, true, false, aAreaParam ); + + //! special ScEditUtil handling if formatting for printer + rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY); + } + if (rParam.mbPixelToLogic) + { + Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize); + rParam.mpEngine->SetPaperSize(aLogicSize); + } + else + rParam.mpEngine->SetPaperSize(aPaperSize); + + // Fill the EditEngine (cell attributes and text) + + rParam.setPatternToEngine(mbUseStyleColor); + rParam.setAlignmentToEngine(); + + // Read content from cell + + bool bWrapFields = false; + if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields)) + // Failed to read cell content. Bail out. + return; + + if ( mbSyntaxMode ) + SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell ); + else if ( mbUseStyleColor && mbForceAutoColor ) + lcl_SetEditColor( *rParam.mpEngine, COL_AUTO ); //! or have a flag at EditEngine + + rParam.mpEngine->SetUpdateLayout( true ); // after SetText, before CalcTextWidth/GetTextHeight + + // Get final output area using the calculated width + + tools::Long nEngineWidth, nEngineHeight; + rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight); + + tools::Long nNeededPixel = nEngineWidth; + if (rParam.mbPixelToLogic) + nNeededPixel = mpRefDevice->LogicToPixel(Size(nNeededPixel,0)).Width(); + nNeededPixel += nLeftM + nRightM; + + if (!rParam.mbBreak || bShrink) + { + // for break, the first GetOutputArea call is sufficient + GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, nNeededPixel, + *rParam.mpPattern, sal::static_int_cast(eOutHorJust), + rParam.mbCellIsValue || bRepeat || bShrink, false, false, aAreaParam ); + + if ( bShrink ) + { + ShrinkEditEngine( *rParam.mpEngine, aAreaParam.maAlignRect, + nLeftM, nTopM, nRightM, nBottomM, false, + (rParam.meOrient), 0_deg100, rParam.mbPixelToLogic, + nEngineWidth, nEngineHeight, nNeededPixel, + aAreaParam.mbLeftClip, aAreaParam.mbRightClip ); + } + if ( bRepeat && !aAreaParam.mbLeftClip && !aAreaParam.mbRightClip && rParam.mpEngine->GetParagraphCount() == 1 ) + { + // First check if twice the space for the formatted text is available + // (otherwise just keep it unchanged). + + const tools::Long nFormatted = nNeededPixel - nLeftM - nRightM; // without margin + const tools::Long nAvailable = aAreaParam.maAlignRect.GetWidth() - nLeftM - nRightM; + if ( nAvailable >= 2 * nFormatted ) + { + // "repeat" is handled with unformatted text (for performance reasons) + OUString aCellStr = rParam.mpEngine->GetText(); + + tools::Long nRepeatSize = 0; + SetEngineTextAndGetWidth( rParam, aCellStr, nRepeatSize, 0 ); + if ( pFmtDevice != mpRefDevice ) + ++nRepeatSize; + if ( nRepeatSize > 0 ) + { + const tools::Long nRepeatCount = nAvailable / nRepeatSize; + if ( nRepeatCount > 1 ) + { + OUStringBuffer aRepeated(aCellStr); + for ( tools::Long nRepeat = 1; nRepeat < nRepeatCount; nRepeat++ ) + aRepeated.append(aCellStr); + + nEngineWidth = SetEngineTextAndGetWidth( rParam, aRepeated.makeStringAndClear(), + nNeededPixel, (nLeftM + nRightM ) ); + + nEngineHeight = rParam.mpEngine->GetTextHeight(); + } + } + } + } + if ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) ) + { + nEngineWidth = SetEngineTextAndGetWidth( rParam, "###", nNeededPixel, ( nLeftM + nRightM ) ); + + // No clip marks if "###" doesn't fit (same as in DrawStrings) + } + } + + tools::Long nStartX = aAreaParam.maAlignRect.Left(); + const tools::Long nStartY = aAreaParam.maAlignRect.Top(); + const tools::Long nCellWidth = aAreaParam.maAlignRect.GetWidth(); + const tools::Long nOutWidth = nCellWidth - 1 - nLeftM - nRightM; + const tools::Long nOutHeight = aAreaParam.maAlignRect.GetHeight() - nTopM - nBottomM; + + if (rParam.mbBreak) + { + // text with automatic breaks is aligned only within the + // edit engine's paper size, the output of the whole area + // is always left-aligned + + nStartX += nLeftM; + } + else + { + if ( eOutHorJust == SvxCellHorJustify::Right ) + nStartX -= nNeededPixel - nCellWidth + nRightM + 1; + else if ( eOutHorJust == SvxCellHorJustify::Center ) + nStartX -= ( nNeededPixel - nCellWidth + nRightM + 1 - nLeftM ) / 2; + else + nStartX += nLeftM; + } + + const bool bOutside = (aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW); + if (bOutside) + return; + + // output area, excluding margins, in logical units + const Size& aCellSize = rParam.mbPixelToLogic + ? mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) ) + : Size( nOutWidth, nOutHeight ); + + Point aURLStart; + + { + const auto pClipRegion = Clip( rParam, aCellSize, aAreaParam, nEngineWidth, bWrapFields, true ); + + Point aLogicStart(nStartX, nStartY); + rParam.calcStartPosForVertical(aLogicStart, aCellSize.Width(), nEngineWidth, nTopM, mpRefDevice); + + aURLStart = aLogicStart; // copy before modifying for orientation + + if (rParam.meHorJustResult == SvxCellHorJustify::Block || rParam.mbBreak) + { + Size aPSize = rParam.mpEngine->GetPaperSize(); + aPSize.setWidth( aCellSize.Height() ); + rParam.mpEngine->SetPaperSize(aPSize); + aLogicStart.AdjustY( + rParam.mbBreak ? aPSize.Width() : nEngineHeight ); + } + else + { + // Note that the "paper" is rotated 90 degrees to the left, so + // paper's width is in vertical direction. Also, the whole text + // is on a single line, as text wrap is not in effect. + + // Set the paper width to be the width of the text. + Size aPSize = rParam.mpEngine->GetPaperSize(); + aPSize.setWidth( rParam.mpEngine->CalcTextWidth() ); + rParam.mpEngine->SetPaperSize(aPSize); + + tools::Long nGap = 0; + tools::Long nTopOffset = 0; + if (rParam.mbPixelToLogic) + { + nGap = mpRefDevice->LogicToPixel(aCellSize).Height() - mpRefDevice->LogicToPixel(aPSize).Width(); + nGap = mpRefDevice->PixelToLogic(Size(0, nGap)).Height(); + nTopOffset = mpRefDevice->PixelToLogic(Size(0,nTopM)).Height(); + } + else + { + nGap = aCellSize.Height() - aPSize.Width(); + nTopOffset = nTopM; + } + + // First, align text to bottom. + aLogicStart.AdjustY(aCellSize.Height() ); + aLogicStart.AdjustY(nTopOffset ); + + switch (rParam.meVerJust) + { + case SvxCellVerJustify::Standard: + case SvxCellVerJustify::Bottom: + // align to bottom (do nothing). + break; + case SvxCellVerJustify::Center: + // center it. + aLogicStart.AdjustY( -(nGap / 2) ); + break; + case SvxCellVerJustify::Block: + case SvxCellVerJustify::Top: + // align to top + aLogicStart.AdjustY( -nGap ); + break; + default: + ; + } + } + + rParam.mpEngine->Draw(*mpDev, aLogicStart, 900_deg10); + } + + rParam.adjustForHyperlinkInPDF(aURLStart, mpDev); +} + +void ScOutputData::DrawEditTopBottom(DrawEditParam& rParam) +{ + OSL_ASSERT(rParam.meHorJustAttr != SvxCellHorJustify::Repeat); + + const bool bRepeat = (rParam.meHorJustAttr == SvxCellHorJustify::Repeat && !rParam.mbBreak); + const bool bShrink = !rParam.mbBreak && !bRepeat && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet); + + SvxCellHorJustify eOutHorJust = rParam.meHorJustContext; + + //! mirror margin values for RTL? + //! move margin down to after final GetOutputArea call + tools::Long nTopM, nLeftM, nBottomM, nRightM; + rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY); + + SCCOL nXForPos = rParam.mnX; + if ( nXForPos < nX1 ) + { + nXForPos = nX1; + rParam.mnPosX = rParam.mnInitPosX; + } + SCSIZE nArrYForPos = rParam.mnArrY; + if ( nArrYForPos < 1 ) + { + nArrYForPos = 1; + rParam.mnPosY = nScrY; + } + + OutputAreaParam aAreaParam; + + // Initial page size - large for normal text, cell size for automatic line breaks + + Size aPaperSize( 1000000, 1000000 ); + if (rParam.hasLineBreak()) + { + // call GetOutputArea with nNeeded=0, to get only the cell width + + //! handle nArrY == 0 + GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0, + *rParam.mpPattern, sal::static_int_cast(eOutHorJust), + rParam.mbCellIsValue, true, false, aAreaParam ); + + //! special ScEditUtil handling if formatting for printer + rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY); + } + if (rParam.mbPixelToLogic) + { + Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize); + rParam.mpEngine->SetPaperSize(aLogicSize); + } + else + rParam.mpEngine->SetPaperSize(aPaperSize); + + // Fill the EditEngine (cell attributes and text) + + rParam.setPatternToEngine(mbUseStyleColor); + rParam.setAlignmentToEngine(); + + // Read content from cell + + bool bWrapFields = false; + if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields)) + // Failed to read cell content. Bail out. + return; + + if ( mbSyntaxMode ) + SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell ); + else if ( mbUseStyleColor && mbForceAutoColor ) + lcl_SetEditColor( *rParam.mpEngine, COL_AUTO ); //! or have a flag at EditEngine + + rParam.mpEngine->SetUpdateLayout( true ); // after SetText, before CalcTextWidth/GetTextHeight + + // Get final output area using the calculated width + + tools::Long nEngineWidth, nEngineHeight; + rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight); + + tools::Long nNeededPixel = nEngineWidth; + if (rParam.mbPixelToLogic) + nNeededPixel = mpRefDevice->LogicToPixel(Size(nNeededPixel,0)).Width(); + nNeededPixel += nLeftM + nRightM; + + if (!rParam.mbBreak || bShrink) + { + // for break, the first GetOutputArea call is sufficient + GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, nNeededPixel, + *rParam.mpPattern, sal::static_int_cast(eOutHorJust), + rParam.mbCellIsValue || bRepeat || bShrink, false, false, aAreaParam ); + + if ( bShrink ) + { + ShrinkEditEngine( *rParam.mpEngine, aAreaParam.maAlignRect, + nLeftM, nTopM, nRightM, nBottomM, false, + rParam.meOrient, 0_deg100, rParam.mbPixelToLogic, + nEngineWidth, nEngineHeight, nNeededPixel, + aAreaParam.mbLeftClip, aAreaParam.mbRightClip ); + } + if ( bRepeat && !aAreaParam.mbLeftClip && !aAreaParam.mbRightClip && rParam.mpEngine->GetParagraphCount() == 1 ) + { + // First check if twice the space for the formatted text is available + // (otherwise just keep it unchanged). + + const tools::Long nFormatted = nNeededPixel - nLeftM - nRightM; // without margin + const tools::Long nAvailable = aAreaParam.maAlignRect.GetWidth() - nLeftM - nRightM; + if ( nAvailable >= 2 * nFormatted ) + { + // "repeat" is handled with unformatted text (for performance reasons) + OUString aCellStr = rParam.mpEngine->GetText(); + + tools::Long nRepeatSize = 0; + SetEngineTextAndGetWidth( rParam, aCellStr, nRepeatSize, 0 ); + + if ( pFmtDevice != mpRefDevice ) + ++nRepeatSize; + if ( nRepeatSize > 0 ) + { + const tools::Long nRepeatCount = nAvailable / nRepeatSize; + if ( nRepeatCount > 1 ) + { + OUStringBuffer aRepeated(aCellStr); + for ( tools::Long nRepeat = 1; nRepeat < nRepeatCount; nRepeat++ ) + aRepeated.append(aCellStr); + + nEngineWidth = SetEngineTextAndGetWidth( rParam, aRepeated.makeStringAndClear(), + nNeededPixel, (nLeftM + nRightM ) ); + + nEngineHeight = rParam.mpEngine->GetTextHeight(); + } + } + } + } + if ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) ) + { + nEngineWidth = SetEngineTextAndGetWidth( rParam, "###", nNeededPixel, ( nLeftM + nRightM ) ); + + // No clip marks if "###" doesn't fit (same as in DrawStrings) + } + } + + tools::Long nStartX = aAreaParam.maAlignRect.Left(); + const tools::Long nStartY = aAreaParam.maAlignRect.Top(); + const tools::Long nCellWidth = aAreaParam.maAlignRect.GetWidth(); + const tools::Long nOutWidth = nCellWidth - 1 - nLeftM - nRightM; + const tools::Long nOutHeight = aAreaParam.maAlignRect.GetHeight() - nTopM - nBottomM; + + if (rParam.mbBreak) + { + // text with automatic breaks is aligned only within the + // edit engine's paper size, the output of the whole area + // is always left-aligned + + nStartX += nLeftM; + if (rParam.meHorJustResult == SvxCellHorJustify::Block) + nStartX += aPaperSize.Height(); + } + else + { + if ( eOutHorJust == SvxCellHorJustify::Right ) + nStartX -= nNeededPixel - nCellWidth + nRightM + 1; + else if ( eOutHorJust == SvxCellHorJustify::Center ) + nStartX -= ( nNeededPixel - nCellWidth + nRightM + 1 - nLeftM ) / 2; + else + nStartX += nLeftM; + } + + const bool bOutside = (aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW); + if (bOutside) + return; + + // output area, excluding margins, in logical units + const Size& aCellSize = rParam.mbPixelToLogic + ? mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) ) + : Size( nOutWidth, nOutHeight ); + + Point aURLStart; + + { + const auto pClipRegion = Clip( rParam, aCellSize, aAreaParam, nEngineWidth, bWrapFields, false ); + + Point aLogicStart(nStartX, nStartY); + rParam.calcStartPosForVertical(aLogicStart, aCellSize.Width(), nEngineWidth, nTopM, mpRefDevice); + + aURLStart = aLogicStart; // copy before modifying for orientation + + if (rParam.meHorJustResult != SvxCellHorJustify::Block) + { + aLogicStart.AdjustX(nEngineWidth ); + if (!rParam.mbBreak) + { + // Set the paper width to text size. + Size aPSize = rParam.mpEngine->GetPaperSize(); + aPSize.setWidth( rParam.mpEngine->CalcTextWidth() ); + rParam.mpEngine->SetPaperSize(aPSize); + + tools::Long nGap = 0; + tools::Long nTopOffset = 0; // offset by top margin + if (rParam.mbPixelToLogic) + { + nGap = mpRefDevice->LogicToPixel(aPSize).Width() - mpRefDevice->LogicToPixel(aCellSize).Height(); + nGap = mpRefDevice->PixelToLogic(Size(0, nGap)).Height(); + nTopOffset = mpRefDevice->PixelToLogic(Size(0,nTopM)).Height(); + } + else + { + nGap = aPSize.Width() - aCellSize.Height(); + nTopOffset = nTopM; + } + aLogicStart.AdjustY(nTopOffset ); + + switch (rParam.meVerJust) + { + case SvxCellVerJustify::Standard: + case SvxCellVerJustify::Bottom: + // align to bottom + aLogicStart.AdjustY( -nGap ); + break; + case SvxCellVerJustify::Center: + // center it. + aLogicStart.AdjustY( -(nGap / 2) ); + break; + case SvxCellVerJustify::Block: + case SvxCellVerJustify::Top: + // align to top (do nothing) + default: + ; + } + } + } + + // bMoveClipped handling has been replaced by complete alignment + // handling (also extending to the left). + + rParam.mpEngine->Draw(*mpDev, aLogicStart, 2700_deg10); + } + + rParam.adjustForHyperlinkInPDF(aURLStart, mpDev); +} + +void ScOutputData::DrawEditStacked(DrawEditParam& rParam) +{ + OSL_ASSERT(rParam.meHorJustAttr != SvxCellHorJustify::Repeat); + Size aRefOne = mpRefDevice->PixelToLogic(Size(1,1)); + + bool bRepeat = (rParam.meHorJustAttr == SvxCellHorJustify::Repeat && !rParam.mbBreak); + bool bShrink = !rParam.mbBreak && !bRepeat && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet); + + rParam.mbAsianVertical = + lcl_GetBoolValue(*rParam.mpPattern, ATTR_VERTICAL_ASIAN, rParam.mpCondSet); + + if ( rParam.mbAsianVertical ) + { + // in asian mode, use EditEngine::SetVertical instead of EEControlBits::ONECHARPERLINE + rParam.meOrient = SvxCellOrientation::Standard; + DrawEditAsianVertical(rParam); + return; + } + + SvxCellHorJustify eOutHorJust = rParam.meHorJustContext; + + //! mirror margin values for RTL? + //! move margin down to after final GetOutputArea call + tools::Long nTopM, nLeftM, nBottomM, nRightM; + rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY); + + SCCOL nXForPos = rParam.mnX; + if ( nXForPos < nX1 ) + { + nXForPos = nX1; + rParam.mnPosX = rParam.mnInitPosX; + } + SCSIZE nArrYForPos = rParam.mnArrY; + if ( nArrYForPos < 1 ) + { + nArrYForPos = 1; + rParam.mnPosY = nScrY; + } + + OutputAreaParam aAreaParam; + + // Initial page size - large for normal text, cell size for automatic line breaks + + Size aPaperSize( 1000000, 1000000 ); + // call GetOutputArea with nNeeded=0, to get only the cell width + + //! handle nArrY == 0 + GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0, + *rParam.mpPattern, sal::static_int_cast(eOutHorJust), + rParam.mbCellIsValue, true, false, aAreaParam ); + + //! special ScEditUtil handling if formatting for printer + rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY); + + if (rParam.mbPixelToLogic) + { + Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize); + if ( rParam.mbBreak && mpRefDevice != pFmtDevice ) + { + // #i85342# screen display and formatting for printer, + // use same GetEditArea call as in ScViewData::SetEditEngine + + Fraction aFract(1,1); + tools::Rectangle aUtilRect = ScEditUtil( mpDoc, rParam.mnCellX, rParam.mnCellY, nTab, Point(0,0), pFmtDevice, + HMM_PER_TWIPS, HMM_PER_TWIPS, aFract, aFract ).GetEditArea( rParam.mpPattern, false ); + aLogicSize.setWidth( aUtilRect.GetWidth() ); + } + rParam.mpEngine->SetPaperSize(aLogicSize); + } + else + rParam.mpEngine->SetPaperSize(aPaperSize); + + // Fill the EditEngine (cell attributes and text) + + rParam.setPatternToEngine(mbUseStyleColor); + rParam.setAlignmentToEngine(); + + // Read content from cell + + bool bWrapFields = false; + if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields)) + // Failed to read cell content. Bail out. + return; + + if ( mbSyntaxMode ) + SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell ); + else if ( mbUseStyleColor && mbForceAutoColor ) + lcl_SetEditColor( *rParam.mpEngine, COL_AUTO ); //! or have a flag at EditEngine + + rParam.mpEngine->SetUpdateLayout( true ); // after SetText, before CalcTextWidth/GetTextHeight + + // Get final output area using the calculated width + + tools::Long nEngineWidth, nEngineHeight; + rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight); + + tools::Long nNeededPixel = nEngineWidth; + if (rParam.mbPixelToLogic) + nNeededPixel = mpRefDevice->LogicToPixel(Size(nNeededPixel,0)).Width(); + nNeededPixel += nLeftM + nRightM; + + if (bShrink) + { + // for break, the first GetOutputArea call is sufficient + GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, nNeededPixel, + *rParam.mpPattern, sal::static_int_cast(eOutHorJust), + true, false, false, aAreaParam ); + + ShrinkEditEngine( *rParam.mpEngine, aAreaParam.maAlignRect, + nLeftM, nTopM, nRightM, nBottomM, true, + rParam.meOrient, 0_deg100, rParam.mbPixelToLogic, + nEngineWidth, nEngineHeight, nNeededPixel, + aAreaParam.mbLeftClip, aAreaParam.mbRightClip ); + + if ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) ) + { + nEngineWidth = SetEngineTextAndGetWidth( rParam, "###", nNeededPixel, ( nLeftM + nRightM ) ); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + ScCellInfo* pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX); + SetClipMarks( aAreaParam, pClipMarkCell, eOutHorJust, nLayoutSign ); + } + + if ( eOutHorJust != SvxCellHorJustify::Left ) + { + aPaperSize.setWidth( nNeededPixel + 1 ); + if (rParam.mbPixelToLogic) + rParam.mpEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize)); + else + rParam.mpEngine->SetPaperSize(aPaperSize); + } + } + + tools::Long nStartX = aAreaParam.maAlignRect.Left(); + tools::Long nStartY = aAreaParam.maAlignRect.Top(); + tools::Long nCellWidth = aAreaParam.maAlignRect.GetWidth(); + tools::Long nOutWidth = nCellWidth - 1 - nLeftM - nRightM; + tools::Long nOutHeight = aAreaParam.maAlignRect.GetHeight() - nTopM - nBottomM; + + if (rParam.mbBreak) + { + // text with automatic breaks is aligned only within the + // edit engine's paper size, the output of the whole area + // is always left-aligned + + nStartX += nLeftM; + } + else + { + if ( eOutHorJust == SvxCellHorJustify::Right ) + nStartX -= nNeededPixel - nCellWidth + nRightM + 1; + else if ( eOutHorJust == SvxCellHorJustify::Center ) + nStartX -= ( nNeededPixel - nCellWidth + nRightM + 1 - nLeftM ) / 2; + else + nStartX += nLeftM; + } + + bool bOutside = (aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW); + if (bOutside) + return; + + // Also take fields in a cell with automatic breaks into account: clip to cell width + bool bClip = AdjustAreaParamClipRect(aAreaParam) || aAreaParam.mbLeftClip || aAreaParam.mbRightClip || bWrapFields; + bool bSimClip = false; + + Size aCellSize; // output area, excluding margins, in logical units + if (rParam.mbPixelToLogic) + aCellSize = mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) ); + else + aCellSize = Size( nOutWidth, nOutHeight ); + + if ( nEngineHeight >= aCellSize.Height() + aRefOne.Height() ) + { + const ScMergeAttr* pMerge = &rParam.mpPattern->GetItem(ATTR_MERGE); + bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1; + + // Don't clip for text height when printing rows with optimal height, + // except when font size is from conditional formatting. + //! Allow clipping when vertically merged? + if ( eType != OUTTYPE_PRINTER || + ( mpDoc->GetRowFlags( rParam.mnCellY, nTab ) & CRFlags::ManualSize ) || + ( rParam.mpCondSet && SfxItemState::SET == + rParam.mpCondSet->GetItemState(ATTR_FONT_HEIGHT) ) ) + bClip = true; + else + bSimClip = true; + + // Show clip marks if height is at least 5pt too small and + // there are several lines of text. + // Not for asian vertical text, because that would interfere + // with the default right position of the text. + // Only with automatic line breaks, to avoid having to find + // the cells with the horizontal end of the text again. + if ( nEngineHeight - aCellSize.Height() > 100 && + rParam.mbBreak && bMarkClipped && + ( rParam.mpEngine->GetParagraphCount() > 1 || rParam.mpEngine->GetLineCount(0) > 1 ) ) + { + ScCellInfo* pClipMarkCell = nullptr; + if ( bMerged ) + { + // anywhere in the merged area... + SCCOL nClipX = ( rParam.mnX < nX1 ) ? nX1 : rParam.mnX; + pClipMarkCell = &pRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].cellInfo(nClipX); + } + else + pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX); + + pClipMarkCell->nClipMark |= ScClipMark::Right; //! also allow left? + bAnyClipped = true; + + tools::Long nMarkPixel = static_cast( SC_CLIPMARK_SIZE * mnPPTX ); + if ( aAreaParam.maClipRect.Right() - nMarkPixel > aAreaParam.maClipRect.Left() ) + aAreaParam.maClipRect.AdjustRight( -nMarkPixel ); + } + } + + Point aURLStart; + + { // Clip marks are already handled in GetOutputArea + ClearableClipRegion aClip(rParam.mbPixelToLogic ? mpRefDevice->PixelToLogic(aAreaParam.maClipRect) + : aAreaParam.maClipRect, bClip, bSimClip, mpDev, bMetaFile); + + Point aLogicStart; + if (rParam.mbPixelToLogic) + aLogicStart = mpRefDevice->PixelToLogic( Point(nStartX,nStartY) ); + else + aLogicStart = Point(nStartX, nStartY); + + if (rParam.meVerJust==SvxCellVerJustify::Bottom || + rParam.meVerJust==SvxCellVerJustify::Standard) + { + //! if pRefDevice != pFmtDevice, keep heights in logic units, + //! only converting margin? + + if (rParam.mbPixelToLogic) + aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, nTopM + + mpRefDevice->LogicToPixel(aCellSize).Height() - + mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height() + )).Height() ); + else + aLogicStart.AdjustY(nTopM + aCellSize.Height() - nEngineHeight ); + } + else if (rParam.meVerJust==SvxCellVerJustify::Center) + { + if (rParam.mbPixelToLogic) + aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, nTopM + ( + mpRefDevice->LogicToPixel(aCellSize).Height() - + mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height() ) + / 2)).Height() ); + else + aLogicStart.AdjustY(nTopM + (aCellSize.Height() - nEngineHeight) / 2 ); + } + else // top + { + if (rParam.mbPixelToLogic) + aLogicStart.AdjustY(mpRefDevice->PixelToLogic(Size(0,nTopM)).Height() ); + else + aLogicStart.AdjustY(nTopM ); + } + + aURLStart = aLogicStart; // copy before modifying for orientation + + Size aPaperLogic = rParam.mpEngine->GetPaperSize(); + aPaperLogic.setWidth( nEngineWidth ); + rParam.mpEngine->SetPaperSize(aPaperLogic); + + // bMoveClipped handling has been replaced by complete alignment + // handling (also extending to the left). + + if (bSimClip) + { + // no hard clip, only draw the affected rows + Point aDocStart = aClip.getRect().TopLeft(); + aDocStart -= aLogicStart; + rParam.mpEngine->Draw(*mpDev, aClip.getRect(), aDocStart, false); + } + else + { + rParam.mpEngine->Draw(*mpDev, aLogicStart); + } + } + + rParam.adjustForHyperlinkInPDF(aURLStart, mpDev); +} + +void ScOutputData::DrawEditAsianVertical(DrawEditParam& rParam) +{ + // When in asian vertical orientation, the orientation value is STANDARD, + // and the asian vertical boolean is true. + OSL_ASSERT(rParam.meOrient == SvxCellOrientation::Standard); + OSL_ASSERT(rParam.mbAsianVertical); + OSL_ASSERT(rParam.meHorJustAttr != SvxCellHorJustify::Repeat); + + Size aRefOne = mpRefDevice->PixelToLogic(Size(1,1)); + + bool bHidden = false; + bool bShrink = !rParam.mbBreak && lcl_GetBoolValue(*rParam.mpPattern, ATTR_SHRINKTOFIT, rParam.mpCondSet); + Degree100 nAttrRotate = lcl_GetValue(*rParam.mpPattern, ATTR_ROTATE_VALUE, rParam.mpCondSet); + + if (nAttrRotate) + { + //! set flag to find the cell in DrawRotated again ? + //! (or flag already set during DrawBackground, then no query here) + bHidden = true; // rotated is outputted separately + } + + // default alignment for asian vertical mode is top-right + /* TODO: is setting meHorJustContext and meHorJustResult unconditionally to + * SvxCellHorJustify::Right really wanted? Seems this was done all the time, + * also before context was introduced and everything was attr only. */ + if ( rParam.meHorJustAttr == SvxCellHorJustify::Standard ) + rParam.meHorJustResult = rParam.meHorJustContext = SvxCellHorJustify::Right; + + if (bHidden) + return; + + SvxCellHorJustify eOutHorJust = rParam.meHorJustContext; + + //! mirror margin values for RTL? + //! move margin down to after final GetOutputArea call + tools::Long nTopM, nLeftM, nBottomM, nRightM; + rParam.calcMargins(nTopM, nLeftM, nBottomM, nRightM, mnPPTX, mnPPTY); + + SCCOL nXForPos = rParam.mnX; + if ( nXForPos < nX1 ) + { + nXForPos = nX1; + rParam.mnPosX = rParam.mnInitPosX; + } + SCSIZE nArrYForPos = rParam.mnArrY; + if ( nArrYForPos < 1 ) + { + nArrYForPos = 1; + rParam.mnPosY = nScrY; + } + + OutputAreaParam aAreaParam; + + // Initial page size - large for normal text, cell size for automatic line breaks + + Size aPaperSize( 1000000, 1000000 ); + // call GetOutputArea with nNeeded=0, to get only the cell width + + //! handle nArrY == 0 + GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, 0, + *rParam.mpPattern, sal::static_int_cast(eOutHorJust), + rParam.mbCellIsValue, true, false, aAreaParam ); + + //! special ScEditUtil handling if formatting for printer + rParam.calcPaperSize(aPaperSize, aAreaParam.maAlignRect, mnPPTX, mnPPTY); + + if (rParam.mbPixelToLogic) + { + Size aLogicSize = mpRefDevice->PixelToLogic(aPaperSize); + if ( rParam.mbBreak && !rParam.mbAsianVertical && mpRefDevice != pFmtDevice ) + { + // #i85342# screen display and formatting for printer, + // use same GetEditArea call as in ScViewData::SetEditEngine + + Fraction aFract(1,1); + tools::Rectangle aUtilRect = ScEditUtil( mpDoc, rParam.mnCellX, rParam.mnCellY, nTab, Point(0,0), pFmtDevice, + HMM_PER_TWIPS, HMM_PER_TWIPS, aFract, aFract ).GetEditArea( rParam.mpPattern, false ); + aLogicSize.setWidth( aUtilRect.GetWidth() ); + } + rParam.mpEngine->SetPaperSize(aLogicSize); + } + else + rParam.mpEngine->SetPaperSize(aPaperSize); + + // Fill the EditEngine (cell attributes and text) + + // default alignment for asian vertical mode is top-right + if ( rParam.meVerJust == SvxCellVerJustify::Standard ) + rParam.meVerJust = SvxCellVerJustify::Top; + + rParam.setPatternToEngine(mbUseStyleColor); + rParam.setAlignmentToEngine(); + + // Read content from cell + + bool bWrapFields = false; + if (!rParam.readCellContent(mpDoc, mbShowNullValues, mbShowFormulas, mbSyntaxMode, mbUseStyleColor, mbForceAutoColor, bWrapFields)) + // Failed to read cell content. Bail out. + return; + + if ( mbSyntaxMode ) + SetEditSyntaxColor( *rParam.mpEngine, rParam.maCell ); + else if ( mbUseStyleColor && mbForceAutoColor ) + lcl_SetEditColor( *rParam.mpEngine, COL_AUTO ); //! or have a flag at EditEngine + + rParam.mpEngine->SetUpdateLayout( true ); // after SetText, before CalcTextWidth/GetTextHeight + + // Get final output area using the calculated width + + tools::Long nEngineWidth, nEngineHeight; + rParam.getEngineSize(rParam.mpEngine, nEngineWidth, nEngineHeight); + + tools::Long nNeededPixel = nEngineWidth; + if (rParam.mbPixelToLogic) + nNeededPixel = mpRefDevice->LogicToPixel(Size(nNeededPixel,0)).Width(); + nNeededPixel += nLeftM + nRightM; + + // for break, the first GetOutputArea call is sufficient + GetOutputArea( nXForPos, nArrYForPos, rParam.mnPosX, rParam.mnPosY, rParam.mnCellX, rParam.mnCellY, nNeededPixel, + *rParam.mpPattern, sal::static_int_cast(eOutHorJust), + rParam.mbCellIsValue || bShrink, false, false, aAreaParam ); + + if ( bShrink ) + { + ShrinkEditEngine( *rParam.mpEngine, aAreaParam.maAlignRect, + nLeftM, nTopM, nRightM, nBottomM, false, + rParam.meOrient, 0_deg100, rParam.mbPixelToLogic, + nEngineWidth, nEngineHeight, nNeededPixel, + aAreaParam.mbLeftClip, aAreaParam.mbRightClip ); + } + if ( rParam.mbCellIsValue && ( aAreaParam.mbLeftClip || aAreaParam.mbRightClip ) ) + { + nEngineWidth = SetEngineTextAndGetWidth( rParam, "###", nNeededPixel, ( nLeftM + nRightM ) ); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + ScCellInfo* pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX); + SetClipMarks( aAreaParam, pClipMarkCell, eOutHorJust, nLayoutSign ); + } + + if (eOutHorJust != SvxCellHorJustify::Left) + { + aPaperSize.setWidth( nNeededPixel + 1 ); + if (rParam.mbPixelToLogic) + rParam.mpEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize)); + else + rParam.mpEngine->SetPaperSize(aPaperSize); + } + + tools::Long nStartX = aAreaParam.maAlignRect.Left(); + tools::Long nStartY = aAreaParam.maAlignRect.Top(); + tools::Long nCellWidth = aAreaParam.maAlignRect.GetWidth(); + tools::Long nOutWidth = nCellWidth - 1 - nLeftM - nRightM; + tools::Long nOutHeight = aAreaParam.maAlignRect.GetHeight() - nTopM - nBottomM; + + // text with automatic breaks is aligned only within the + // edit engine's paper size, the output of the whole area + // is always left-aligned + + nStartX += nLeftM; + + bool bOutside = (aAreaParam.maClipRect.Right() < nScrX || aAreaParam.maClipRect.Left() >= nScrX + nScrW); + if (bOutside) + return; + + // Also take fields in a cell with automatic breaks into account: clip to cell width + bool bClip = AdjustAreaParamClipRect(aAreaParam) || aAreaParam.mbLeftClip || aAreaParam.mbRightClip || bWrapFields; + bool bSimClip = false; + + Size aCellSize; // output area, excluding margins, in logical units + if (rParam.mbPixelToLogic) + aCellSize = mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) ); + else + aCellSize = Size( nOutWidth, nOutHeight ); + + if ( nEngineHeight >= aCellSize.Height() + aRefOne.Height() ) + { + const ScMergeAttr* pMerge = &rParam.mpPattern->GetItem(ATTR_MERGE); + bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1; + + // Don't clip for text height when printing rows with optimal height, + // except when font size is from conditional formatting. + //! Allow clipping when vertically merged? + if ( eType != OUTTYPE_PRINTER || + ( mpDoc->GetRowFlags( rParam.mnCellY, nTab ) & CRFlags::ManualSize ) || + ( rParam.mpCondSet && SfxItemState::SET == + rParam.mpCondSet->GetItemState(ATTR_FONT_HEIGHT) ) ) + bClip = true; + else + bSimClip = true; + + // Show clip marks if height is at least 5pt too small and + // there are several lines of text. + // Not for asian vertical text, because that would interfere + // with the default right position of the text. + // Only with automatic line breaks, to avoid having to find + // the cells with the horizontal end of the text again. + if ( nEngineHeight - aCellSize.Height() > 100 && + ( rParam.mbBreak || rParam.meOrient == SvxCellOrientation::Stacked ) && + !rParam.mbAsianVertical && bMarkClipped && + ( rParam.mpEngine->GetParagraphCount() > 1 || rParam.mpEngine->GetLineCount(0) > 1 ) ) + { + ScCellInfo* pClipMarkCell = nullptr; + if ( bMerged ) + { + // anywhere in the merged area... + SCCOL nClipX = ( rParam.mnX < nX1 ) ? nX1 : rParam.mnX; + pClipMarkCell = &pRowInfo[(rParam.mnArrY != 0) ? rParam.mnArrY : 1].cellInfo(nClipX); + } + else + pClipMarkCell = &rParam.mpThisRowInfo->cellInfo(rParam.mnX); + + pClipMarkCell->nClipMark |= ScClipMark::Right; //! also allow left? + bAnyClipped = true; + + tools::Long nMarkPixel = static_cast( SC_CLIPMARK_SIZE * mnPPTX ); + if ( aAreaParam.maClipRect.Right() - nMarkPixel > aAreaParam.maClipRect.Left() ) + aAreaParam.maClipRect.AdjustRight( -nMarkPixel ); + } + } + + Point aURLStart; + + { // Clip marks are already handled in GetOutputArea + ClearableClipRegion aClip(rParam.mbPixelToLogic ? mpRefDevice->PixelToLogic(aAreaParam.maClipRect) + : aAreaParam.maClipRect, bClip, bSimClip, mpDev, bMetaFile); + + Point aLogicStart; + if (rParam.mbPixelToLogic) + aLogicStart = mpRefDevice->PixelToLogic( Point(nStartX,nStartY) ); + else + aLogicStart = Point(nStartX, nStartY); + + tools::Long nAvailWidth = aCellSize.Width(); + // space for AutoFilter is already handled in GetOutputArea + + // horizontal alignment + + if (rParam.meHorJustResult==SvxCellHorJustify::Right) + aLogicStart.AdjustX(nAvailWidth - nEngineWidth ); + else if (rParam.meHorJustResult==SvxCellHorJustify::Center) + aLogicStart.AdjustX((nAvailWidth - nEngineWidth) / 2 ); + + // paper size is subtracted below + aLogicStart.AdjustX(nEngineWidth ); + + // vertical adjustment is within the EditEngine + if (rParam.mbPixelToLogic) + aLogicStart.AdjustY(mpRefDevice->PixelToLogic(Size(0,nTopM)).Height() ); + else + aLogicStart.AdjustY(nTopM ); + + aURLStart = aLogicStart; // copy before modifying for orientation + + // bMoveClipped handling has been replaced by complete alignment + // handling (also extending to the left). + + // with SetVertical, the start position is top left of + // the whole output area, not the text itself + aLogicStart.AdjustX( -(rParam.mpEngine->GetPaperSize().Width()) ); + + rParam.mpEngine->Draw(*mpDev, aLogicStart); + } + + rParam.adjustForHyperlinkInPDF(aURLStart, mpDev); +} + +void ScOutputData::DrawEdit(bool bPixelToLogic) +{ + InitOutputEditEngine(); + + bool bHyphenatorSet = false; + const ScPatternAttr* pOldPattern = nullptr; + const SfxItemSet* pOldCondSet = nullptr; + const SfxItemSet* pOldPreviewFontSet = nullptr; + ScRefCellValue aCell; + + tools::Long nInitPosX = nScrX; + if ( bLayoutRTL ) + { + nInitPosX += nMirrorW - 1; + } + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + SCCOL nLastContentCol = mpDoc->MaxCol(); + if ( nX2 < mpDoc->MaxCol() ) + { + SCROW nEndRow; + mpDoc->GetCellArea(nTab, nLastContentCol, nEndRow); + } + + tools::Long nRowPosY = nScrY; + for (SCSIZE nArrY=0; nArrY+1bChanged || nArrY==0 ) + { + tools::Long nPosX = 0; + for (SCCOL nX=0; nX<=nX2; nX++) // due to overflow + { + std::unique_ptr< ScPatternAttr > pPreviewPattr; + if (nX==nX1) nPosX = nInitPosX; // positions before nX1 are calculated individually + + if (pThisRowInfo->basicCellInfo(nX).bEditEngine) + { + SCROW nY = pThisRowInfo->nRowNo; + + SCCOL nCellX = nX; // position where the cell really starts + SCROW nCellY = nY; + bool bDoCell = false; + + tools::Long nPosY = nRowPosY; + if ( nArrY == 0 ) + { + nPosY = nScrY; + nY = pRowInfo[1].nRowNo; + SCCOL nOverX; // start of the merged cells + SCROW nOverY; + if (GetMergeOrigin( nX,nY, 1, nOverX,nOverY, true )) + { + nCellX = nOverX; + nCellY = nOverY; + bDoCell = true; + } + } + else if ( nX == nX2 && pThisRowInfo->cellInfo(nX).maCell.isEmpty() ) + { + // Rest of a long text further to the right? + + SCCOL nTempX=nX; + while (nTempX < nLastContentCol && IsEmptyCellText( pThisRowInfo, nTempX, nY )) + ++nTempX; + + if ( nTempX > nX && + !IsEmptyCellText( pThisRowInfo, nTempX, nY ) && + !mpDoc->HasAttrib( nTempX,nY,nTab, nX,nY,nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped ) ) + { + nCellX = nTempX; + bDoCell = true; + } + } + else + { + bDoCell = true; + } + + if ( bDoCell && bEditMode && nCellX == nEditCol && nCellY == nEditRow ) + bDoCell = false; + + const ScPatternAttr* pPattern = nullptr; + const SfxItemSet* pCondSet = nullptr; + if (bDoCell) + { + if ( nCellY == nY && nCellX >= nX1 && nCellX <= nX2 && + !mpDoc->ColHidden(nCellX, nTab) ) + { + ScCellInfo& rCellInfo = pThisRowInfo->cellInfo(nCellX); + pPattern = rCellInfo.pPatternAttr; + pCondSet = rCellInfo.pConditionSet; + aCell = rCellInfo.maCell; + } + else // get from document + { + pPattern = mpDoc->GetPattern( nCellX, nCellY, nTab ); + pCondSet = mpDoc->GetCondResult( nCellX, nCellY, nTab ); + GetVisibleCell( nCellX, nCellY, nTab, aCell ); + } + if (aCell.isEmpty()) + bDoCell = false; + } + if (bDoCell) + { + if ( mpDoc->GetPreviewCellStyle() ) + { + if ( ScStyleSheet* pPreviewStyle = mpDoc->GetPreviewCellStyle( nCellX, nCellY, nTab ) ) + { + pPreviewPattr.reset( new ScPatternAttr(*pPattern) ); + pPreviewPattr->SetStyleSheet(pPreviewStyle); + pPattern = pPreviewPattr.get(); + } + } + SfxItemSet* pPreviewFontSet = mpDoc->GetPreviewFont( nCellX, nCellY, nTab ); + lcl_ClearEdit( *mxOutputEditEngine ); // also calls SetUpdateMode(sal_False) + + // fdo#32530: Check if the first character is RTL. + OUString aStr = mpDoc->GetString(nCellX, nCellY, nTab); + + DrawEditParam aParam(pPattern, pCondSet, lcl_SafeIsValue(aCell)); + const bool bNumberFormatIsText = lcl_isNumberFormatText( mpDoc, nCellX, nCellY, nTab ); + aParam.meHorJustContext = getAlignmentFromContext( aParam.meHorJustAttr, + aParam.mbCellIsValue, aStr, *pPattern, pCondSet, mpDoc, nTab, bNumberFormatIsText); + aParam.meHorJustResult = (aParam.meHorJustAttr == SvxCellHorJustify::Block) ? + SvxCellHorJustify::Block : aParam.meHorJustContext; + aParam.mbPixelToLogic = bPixelToLogic; + aParam.mbHyphenatorSet = bHyphenatorSet; + aParam.mpEngine = mxOutputEditEngine.get(); + aParam.maCell = aCell; + aParam.mnArrY = nArrY; + aParam.mnX = nX; + aParam.mnCellX = nCellX; + aParam.mnCellY = nCellY; + aParam.mnPosX = nPosX; + aParam.mnPosY = nPosY; + aParam.mnInitPosX = nInitPosX; + aParam.mpPreviewFontSet = pPreviewFontSet; + aParam.mpOldPattern = pOldPattern; + aParam.mpOldCondSet = pOldCondSet; + aParam.mpOldPreviewFontSet = pOldPreviewFontSet; + aParam.mpThisRowInfo = pThisRowInfo; + if (mpSpellCheckCxt) + aParam.mpMisspellRanges = mpSpellCheckCxt->getMisspellRanges(nCellX, nCellY); + + if (aParam.meHorJustAttr == SvxCellHorJustify::Repeat) + { + // ignore orientation/rotation if "repeat" is active + aParam.meOrient = SvxCellOrientation::Standard; + } + switch (aParam.meOrient) + { + case SvxCellOrientation::BottomUp: + DrawEditBottomTop(aParam); + break; + case SvxCellOrientation::TopBottom: + DrawEditTopBottom(aParam); + break; + case SvxCellOrientation::Stacked: + // this can be vertically stacked or asian vertical. + DrawEditStacked(aParam); + break; + default: + DrawEditStandard(aParam); + } + + // Retrieve parameters for next iteration. + pOldPattern = aParam.mpOldPattern; + pOldCondSet = aParam.mpOldCondSet; + pOldPreviewFontSet = aParam.mpOldPreviewFontSet; + bHyphenatorSet = aParam.mbHyphenatorSet; + } + } + nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign; + } + } + nRowPosY += pRowInfo[nArrY].nHeight; + } + + if (mrTabInfo.maArray.HasCellRotation()) + { + DrawRotated(bPixelToLogic); //! call from outside ? + } +} + +void ScOutputData::DrawRotated(bool bPixelToLogic) +{ + InitOutputEditEngine(); + //! store nRotMax + SCCOL nRotMax = nX2; + for (SCSIZE nRotY=0; nRotY nRotMax) + nRotMax = pRowInfo[nRotY].nRotMaxCol; + + ScModule* pScMod = SC_MOD(); + Color nConfBackColor = pScMod->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor; + bool bCellContrast = mbUseStyleColor && + Application::GetSettings().GetStyleSettings().GetHighContrastMode(); + + bool bHyphenatorSet = false; + const ScPatternAttr* pPattern; + const SfxItemSet* pCondSet; + const ScPatternAttr* pOldPattern = nullptr; + const SfxItemSet* pOldCondSet = nullptr; + ScRefCellValue aCell; + + tools::Long nInitPosX = nScrX; + if ( bLayoutRTL ) + { + nInitPosX += nMirrorW - 1; + } + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + tools::Long nRowPosY = nScrY; + for (SCSIZE nArrY=0; nArrY+1(pThisRowInfo->nHeight); + if (nArrY==1) nRowPosY = nScrY; // positions before are calculated individually + + if ( ( pThisRowInfo->bChanged || nArrY==0 ) && pThisRowInfo->nRotMaxCol != SC_ROTMAX_NONE ) + { + tools::Long nPosX = 0; + for (SCCOL nX=0; nX<=nRotMax; nX++) + { + if (nX==nX1) nPosX = nInitPosX; // positions before nX1 are calculated individually + + const ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nX); + if ( pInfo->nRotateDir != ScRotateDir::NONE ) + { + SCROW nY = pThisRowInfo->nRowNo; + + bool bHidden = false; + if (bEditMode) + if ( nX == nEditCol && nY == nEditRow ) + bHidden = true; + + if (!bHidden) + { + lcl_ClearEdit( *mxOutputEditEngine ); // also calls SetUpdateMode(sal_False) + + tools::Long nPosY = nRowPosY; + + //! rest from merged cells further up do not work! + + bool bFromDoc = false; + pPattern = pInfo->pPatternAttr; + pCondSet = pInfo->pConditionSet; + if (!pPattern) + { + pPattern = mpDoc->GetPattern( nX, nY, nTab ); + bFromDoc = true; + } + aCell = pInfo->maCell; + if (bFromDoc) + pCondSet = mpDoc->GetCondResult( nX, nY, nTab ); + + if (aCell.isEmpty() && nX>nX2) + GetVisibleCell( nX, nY, nTab, aCell ); + + if (aCell.isEmpty() || IsEmptyCellText(pThisRowInfo, nX, nY)) + bHidden = true; // nRotateDir is also set without a cell + + tools::Long nCellWidth = static_cast(pRowInfo[0].basicCellInfo(nX).nWidth); + + SvxCellHorJustify eHorJust = + pPattern->GetItem(ATTR_HOR_JUSTIFY, pCondSet).GetValue(); + bool bBreak = ( eHorJust == SvxCellHorJustify::Block ) || + pPattern->GetItem(ATTR_LINEBREAK, pCondSet).GetValue(); + bool bRepeat = ( eHorJust == SvxCellHorJustify::Repeat && !bBreak ); + bool bShrink = !bBreak && !bRepeat && + pPattern->GetItem( ATTR_SHRINKTOFIT, pCondSet ).GetValue(); + SvxCellOrientation eOrient = pPattern->GetCellOrientation( pCondSet ); + + const ScMergeAttr* pMerge = &pPattern->GetItem(ATTR_MERGE); + bool bMerged = pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1; + + tools::Long nStartX = nPosX; + tools::Long nStartY = nPosY; + if (nX nX) + { + --nCol; + nStartX -= nLayoutSign * static_cast(pRowInfo[0].basicCellInfo(nCol).nWidth); + } + } + } + tools::Long nCellStartX = nStartX; + + // omit substitute representation of small text + + if (!bHidden) + { + tools::Long nOutWidth = nCellWidth - 1; + tools::Long nOutHeight = nCellHeight; + + if ( bMerged ) + { + SCCOL nCountX = pMerge->GetColMerge(); + for (SCCOL i=1; iGetColWidth(nX+i,nTab) * mnPPTX; + SCROW nCountY = pMerge->GetRowMerge(); + nOutHeight += mpDoc->GetScaledRowHeight( nY+1, nY+nCountY-1, nTab, mnPPTY); + } + + SvxCellVerJustify eVerJust = + pPattern->GetItem(ATTR_VER_JUSTIFY, pCondSet).GetValue(); + + // syntax mode is ignored here... + + // StringDiffer doesn't look at hyphenate, language items + if ( !SfxPoolItem::areSame(pPattern, pOldPattern) || pCondSet != pOldCondSet ) + { + auto pSet = std::make_unique( mxOutputEditEngine->GetEmptyItemSet() ); + pPattern->FillEditItemSet( pSet.get(), pCondSet ); + + // adjustment for EditEngine + SvxAdjust eSvxAdjust = SvxAdjust::Left; + if (eOrient==SvxCellOrientation::Stacked) + eSvxAdjust = SvxAdjust::Center; + // adjustment for bBreak is omitted here + pSet->Put( SvxAdjustItem( eSvxAdjust, EE_PARA_JUST ) ); + + bool bParaHyphenate = pSet->Get(EE_PARA_HYPHENATE).GetValue(); + mxOutputEditEngine->SetDefaults( std::move(pSet) ); + pOldPattern = pPattern; + pOldCondSet = pCondSet; + + EEControlBits nControl = mxOutputEditEngine->GetControlWord(); + if (eOrient==SvxCellOrientation::Stacked) + nControl |= EEControlBits::ONECHARPERLINE; + else + nControl &= ~EEControlBits::ONECHARPERLINE; + mxOutputEditEngine->SetControlWord( nControl ); + + if ( !bHyphenatorSet && bParaHyphenate ) + { + // set hyphenator the first time it is needed + css::uno::Reference xXHyphenator( LinguMgr::GetHyphenator() ); + mxOutputEditEngine->SetHyphenator( xXHyphenator ); + bHyphenatorSet = true; + } + + Color aBackCol = + pPattern->GetItem( ATTR_BACKGROUND, pCondSet ).GetColor(); + if ( mbUseStyleColor && ( aBackCol.IsTransparent() || bCellContrast ) ) + aBackCol = nConfBackColor; + mxOutputEditEngine->SetBackgroundColor( aBackCol ); + } + + // margins + + //! change position and paper size to EditUtil !!! + + const SvxMarginItem* pMargin = + &pPattern->GetItem(ATTR_MARGIN, pCondSet); + sal_uInt16 nIndent = 0; + if ( eHorJust == SvxCellHorJustify::Left ) + nIndent = pPattern->GetItem(ATTR_INDENT, pCondSet).GetValue(); + + tools::Long nTotalHeight = nOutHeight; // without subtracting the margin + if ( bPixelToLogic ) + nTotalHeight = mpRefDevice->PixelToLogic(Size(0,nTotalHeight)).Height(); + + tools::Long nLeftM = static_cast( (pMargin->GetLeftMargin() + nIndent) * mnPPTX ); + tools::Long nTopM = static_cast( pMargin->GetTopMargin() * mnPPTY ); + tools::Long nRightM = static_cast( pMargin->GetRightMargin() * mnPPTX ); + tools::Long nBottomM = static_cast( pMargin->GetBottomMargin() * mnPPTY ); + nStartX += nLeftM; + nStartY += nTopM; + nOutWidth -= nLeftM + nRightM; + nOutHeight -= nTopM + nBottomM; + + // rotate here already, to adjust paper size for page breaks + Degree100 nAttrRotate; + double nSin = 0.0; + double nCos = 1.0; + SvxRotateMode eRotMode = SVX_ROTATE_MODE_STANDARD; + if ( eOrient == SvxCellOrientation::Standard ) + { + nAttrRotate = pPattern-> + GetItem(ATTR_ROTATE_VALUE, pCondSet).GetValue(); + if ( nAttrRotate ) + { + eRotMode = pPattern->GetItem(ATTR_ROTATE_MODE, pCondSet).GetValue(); + + // tdf#143377 To use the same limits to avoid too big Skew + // with TextOrientation in Calc, use 1/2 degree here, too. + // This equals '50' in the notation here (100th degree) + static const sal_Int32 nMinRad(50); + + // bring nAttrRotate to the range [0..36000[ + nAttrRotate = Degree100(((nAttrRotate.get() % 36000) + 36000) % 36000); + + // check for to be avoided extreme values and correct + if (nAttrRotate < Degree100(nMinRad)) + { + // range [0..50] + nAttrRotate = Degree100(nMinRad); + eRotMode = SVX_ROTATE_MODE_STANDARD; // no overflow + } + else if (nAttrRotate > Degree100(36000 - nMinRad)) + { + // range [35950..36000[ + nAttrRotate = Degree100(36000 - nMinRad); + eRotMode = SVX_ROTATE_MODE_STANDARD; // no overflow + } + else if (nAttrRotate > Degree100(18000 - nMinRad) && (nAttrRotate < Degree100(18000 + nMinRad))) + { + // range 50 around 18000, [17950..18050] + nAttrRotate = (nAttrRotate > Degree100(18000)) + ? Degree100(18000 + nMinRad) + : Degree100(18000 - nMinRad); + eRotMode = SVX_ROTATE_MODE_STANDARD; // no overflow + } + + if ( bLayoutRTL ) + { + // keep in range [0..36000[ + nAttrRotate = Degree100(36000 - nAttrRotate.get()); + } + + double nRealOrient = toRadians(nAttrRotate); // 1/100 degree + nCos = cos( nRealOrient ); + + // tdf#143377 new strategy: instead of using zero for nSin, which + // would be the *correct* value, continue with the corrected maximum + // allowed value which is then *not* zero. This is similar to + // the behaviour before where (just due to numerical unprecisions) + // nSin was also not zero (pure coincidence), but very close to it. + // I checked and tried to make safe all places below that use + // nSin and divide by it, but there is too much going on and that + // would not be safe, so rely on the same values as before, but + // now numerically limited to not get the Skew go havoc + nSin = sin( nRealOrient ); + } + } + + Size aPaperSize( 1000000, 1000000 ); + if (eOrient==SvxCellOrientation::Stacked) + aPaperSize.setWidth( nOutWidth ); // to center + else if (bBreak) + { + if (nAttrRotate) + { + //! the correct paper size for break depends on the number + //! of rows, as long as the rows can not be outputted individually + //! offsetted -> therefore unlimited, so no wrapping. + //! With offset rows the following would be correct: + aPaperSize.setWidth( static_cast(nOutHeight / fabs(nSin)) ); + } + else if (eOrient == SvxCellOrientation::Standard) + aPaperSize.setWidth( nOutWidth ); + else + aPaperSize.setWidth( nOutHeight - 1 ); + } + if (bPixelToLogic) + mxOutputEditEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize)); + else + mxOutputEditEngine->SetPaperSize(aPaperSize); // scale is always 1 + + // read data from cell + + if (aCell.getType() == CELLTYPE_EDIT) + { + if (aCell.getEditText()) + mxOutputEditEngine->SetTextCurrentDefaults(*aCell.getEditText()); + else + { + OSL_FAIL("pData == 0"); + } + } + else + { + sal_uInt32 nFormat = pPattern->GetNumberFormat( + mpDoc->GetFormatTable(), pCondSet ); + const Color* pColor; + OUString aString = ScCellFormat::GetString( aCell, + nFormat, &pColor, + *mpDoc->GetFormatTable(), + *mpDoc, + mbShowNullValues, + mbShowFormulas); + + mxOutputEditEngine->SetTextCurrentDefaults(aString); + if ( pColor && !mbSyntaxMode && !( mbUseStyleColor && mbForceAutoColor ) ) + lcl_SetEditColor( *mxOutputEditEngine, *pColor ); + } + + if ( mbSyntaxMode ) + { + SetEditSyntaxColor(*mxOutputEditEngine, aCell); + } + else if ( mbUseStyleColor && mbForceAutoColor ) + lcl_SetEditColor( *mxOutputEditEngine, COL_AUTO ); //! or have a flag at EditEngine + + mxOutputEditEngine->SetUpdateLayout( true ); // after SetText, before CalcTextWidth/GetTextHeight + + tools::Long nEngineWidth = static_cast(mxOutputEditEngine->CalcTextWidth()); + tools::Long nEngineHeight = mxOutputEditEngine->GetTextHeight(); + + if (nAttrRotate && bBreak) + { + double nAbsCos = fabs( nCos ); + double nAbsSin = fabs( nSin ); + + // adjust width of papersize for height of text + int nSteps = 5; + while (nSteps > 0) + { + // everything is in pixels + tools::Long nEnginePixel = mpRefDevice->LogicToPixel( + Size(0,nEngineHeight)).Height(); + tools::Long nEffHeight = nOutHeight - static_cast(nEnginePixel * nAbsCos) + 2; + tools::Long nNewWidth = static_cast(nEffHeight / nAbsSin) + 2; + bool bFits = ( nNewWidth >= aPaperSize.Width() ); + if ( bFits ) + nSteps = 0; + else + { + if ( nNewWidth < 4 ) + { + // can't fit -> fall back to using half height + nEffHeight = nOutHeight / 2; + nNewWidth = static_cast(nEffHeight / nAbsSin) + 2; + nSteps = 0; + } + else + --nSteps; + + // set paper width and get new text height + aPaperSize.setWidth( nNewWidth ); + if (bPixelToLogic) + mxOutputEditEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize)); + else + mxOutputEditEngine->SetPaperSize(aPaperSize); // Scale is always 1 + //mxOutputEditEngine->QuickFormatDoc( sal_True ); + + nEngineWidth = static_cast(mxOutputEditEngine->CalcTextWidth()); + nEngineHeight = mxOutputEditEngine->GetTextHeight(); + } + } + } + + tools::Long nRealWidth = nEngineWidth; + tools::Long nRealHeight = nEngineHeight; + + // when rotated, adjust size + if (nAttrRotate) + { + double nAbsCos = fabs( nCos ); + double nAbsSin = fabs( nSin ); + + if ( eRotMode == SVX_ROTATE_MODE_STANDARD ) + nEngineWidth = static_cast( nRealWidth * nAbsCos + + nRealHeight * nAbsSin ); + else + nEngineWidth = static_cast( nRealHeight / nAbsSin ); + //! limit !!! + + nEngineHeight = static_cast( nRealHeight * nAbsCos + + nRealWidth * nAbsSin ); + } + + if (!nAttrRotate) // only rotated text here + bHidden = true; //! check first !!! + + //! omit which doesn't stick out + + if (!bHidden) + { + Size aClipSize( nScrX+nScrW-nStartX, nScrY+nScrH-nStartY ); + + // go on writing + + Size aCellSize; + if (bPixelToLogic) + aCellSize = mpRefDevice->PixelToLogic( Size( nOutWidth, nOutHeight ) ); + else + aCellSize = Size( nOutWidth, nOutHeight ); // scale is one + + tools::Long nGridWidth = nEngineWidth; + bool bNegative = false; + if ( eRotMode != SVX_ROTATE_MODE_STANDARD ) + { + nGridWidth = aCellSize.Width() + + std::abs(static_cast( aCellSize.Height() * nCos / nSin )); + bNegative = ( pInfo->nRotateDir == ScRotateDir::Left ); + if ( bLayoutRTL ) + bNegative = !bNegative; + } + + // use GetOutputArea to hide the grid + // (clip region is done manually below) + OutputAreaParam aAreaParam; + + SCCOL nCellX = nX; + SCROW nCellY = nY; + SvxCellHorJustify eOutHorJust = eHorJust; + if ( eRotMode != SVX_ROTATE_MODE_STANDARD ) + eOutHorJust = bNegative ? SvxCellHorJustify::Right : SvxCellHorJustify::Left; + tools::Long nNeededWidth = nGridWidth; // in pixel for GetOutputArea + if ( bPixelToLogic ) + nNeededWidth = mpRefDevice->LogicToPixel(Size(nNeededWidth,0)).Width(); + + GetOutputArea( nX, nArrY, nCellStartX, nPosY, nCellX, nCellY, nNeededWidth, + *pPattern, sal::static_int_cast(eOutHorJust), + false, false, true, aAreaParam ); + + if ( bShrink ) + { + tools::Long nPixelWidth = bPixelToLogic ? + mpRefDevice->LogicToPixel(Size(nEngineWidth,0)).Width() : nEngineWidth; + tools::Long nNeededPixel = nPixelWidth + nLeftM + nRightM; + + aAreaParam.mbLeftClip = aAreaParam.mbRightClip = true; + + // always do height + ShrinkEditEngine( *mxOutputEditEngine, aAreaParam.maAlignRect, nLeftM, nTopM, nRightM, nBottomM, + false, eOrient, nAttrRotate, bPixelToLogic, + nEngineWidth, nEngineHeight, nNeededPixel, aAreaParam.mbLeftClip, aAreaParam.mbRightClip ); + + if ( eRotMode == SVX_ROTATE_MODE_STANDARD ) + { + // do width only if rotating within the cell (standard mode) + ShrinkEditEngine( *mxOutputEditEngine, aAreaParam.maAlignRect, nLeftM, nTopM, nRightM, nBottomM, + true, eOrient, nAttrRotate, bPixelToLogic, + nEngineWidth, nEngineHeight, nNeededPixel, aAreaParam.mbLeftClip, aAreaParam.mbRightClip ); + } + + // nEngineWidth/nEngineHeight is updated in ShrinkEditEngine + // (but width is only valid for standard mode) + nRealWidth = static_cast(mxOutputEditEngine->CalcTextWidth()); + nRealHeight = mxOutputEditEngine->GetTextHeight(); + + if ( eRotMode != SVX_ROTATE_MODE_STANDARD ) + nEngineWidth = static_cast( nRealHeight / fabs( nSin ) ); + } + + tools::Long nClipStartX = nStartX; + if (nXPixelToLogic( tools::Rectangle( + Point(nClipStartX,nClipStartY), aClipSize ) ); + else + aAreaParam.maClipRect = tools::Rectangle(Point(nClipStartX, nClipStartY), + aClipSize ); // Scale = 1 + + if (bMetaFile) + { + mpDev->Push(); + mpDev->IntersectClipRegion( aAreaParam.maClipRect ); + } + else + mpDev->SetClipRegion( vcl::Region( aAreaParam.maClipRect ) ); + + Point aLogicStart; + if (bPixelToLogic) + aLogicStart = mpRefDevice->PixelToLogic( Point(nStartX,nStartY) ); + else + aLogicStart = Point(nStartX, nStartY); + if ( eOrient!=SvxCellOrientation::Standard || !bBreak ) + { + tools::Long nAvailWidth = aCellSize.Width(); + if (eType==OUTTYPE_WINDOW && + eOrient!=SvxCellOrientation::Stacked && + pInfo->bAutoFilter) + { + // filter drop-down width depends on row height + double fZoom = mpRefDevice ? static_cast(mpRefDevice->GetMapMode().GetScaleY()) : 1.0; + fZoom = fZoom > 1.0 ? fZoom : 1.0; + if (bPixelToLogic) + nAvailWidth -= mpRefDevice->PixelToLogic(Size(0,fZoom * DROPDOWN_BITMAP_SIZE)).Height(); + else + nAvailWidth -= fZoom * DROPDOWN_BITMAP_SIZE; + tools::Long nComp = nEngineWidth; + if (nAvailWidthSetUpdateLayout( false ); + + SvxAdjust eSvxAdjust = + (eHorJust==SvxCellHorJustify::Right) ? + SvxAdjust::Right : SvxAdjust::Center; + mxOutputEditEngine->SetDefaultItem( + SvxAdjustItem( eSvxAdjust, EE_PARA_JUST ) ); + + aPaperSize.setWidth( nOutWidth ); + if (bPixelToLogic) + mxOutputEditEngine->SetPaperSize(mpRefDevice->PixelToLogic(aPaperSize)); + else + mxOutputEditEngine->SetPaperSize(aPaperSize); + + mxOutputEditEngine->SetUpdateLayout( true ); + } + } + else + { + // rotated text is centered by default + if (eHorJust==SvxCellHorJustify::Right) + aLogicStart.AdjustX(nAvailWidth - nEngineWidth ); + else if (eHorJust==SvxCellHorJustify::Center || + eHorJust==SvxCellHorJustify::Standard) + aLogicStart.AdjustX((nAvailWidth - nEngineWidth) / 2 ); + } + } + + if ( bLayoutRTL ) + { + if (bPixelToLogic) + aLogicStart.AdjustX( -(mpRefDevice->PixelToLogic( + Size( nCellWidth, 0 ) ).Width()) ); + else + aLogicStart.AdjustX( -nCellWidth ); + } + + if ( eOrient==SvxCellOrientation::Standard || + eOrient==SvxCellOrientation::Stacked || !bBreak ) + { + if (eVerJust==SvxCellVerJustify::Bottom || + eVerJust==SvxCellVerJustify::Standard) + { + if (bPixelToLogic) + aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0, + mpRefDevice->LogicToPixel(aCellSize).Height() - + mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height() + )).Height() ); + else + aLogicStart.AdjustY(aCellSize.Height() - nEngineHeight ); + } + + else if (eVerJust==SvxCellVerJustify::Center) + { + if (bPixelToLogic) + aLogicStart.AdjustY(mpRefDevice->PixelToLogic( Size(0,( + mpRefDevice->LogicToPixel(aCellSize).Height() - + mpRefDevice->LogicToPixel(Size(0,nEngineHeight)).Height()) + / 2)).Height() ); + else + aLogicStart.AdjustY((aCellSize.Height() - nEngineHeight) / 2 ); + } + } + + // TOPBOTTOM and BOTTOMTOP are handled in DrawStrings/DrawEdit + OSL_ENSURE( eOrient == SvxCellOrientation::Standard && nAttrRotate, + "DrawRotated: no rotation" ); + + Degree10 nOriVal = 0_deg10; + if ( nAttrRotate ) + { + // attribute is 1/100, Font 1/10 degrees + nOriVal = to(nAttrRotate); + + double nAddX = 0.0; + double nAddY = 0.0; + if ( nCos > 0.0 && eRotMode != SVX_ROTATE_MODE_STANDARD ) + { + //! limit !!! + double nH = nRealHeight * nCos; + nAddX += nH * ( nCos / fabs(nSin) ); + } + if ( nCos < 0.0 && eRotMode == SVX_ROTATE_MODE_STANDARD ) + nAddX -= nRealWidth * nCos; + if ( nSin < 0.0 ) + nAddX -= nRealHeight * nSin; + if ( nSin > 0.0 ) + nAddY += nRealWidth * nSin; + if ( nCos < 0.0 ) + nAddY -= nRealHeight * nCos; + + if ( eRotMode != SVX_ROTATE_MODE_STANDARD ) + { + //! limit !!! + double nSkew = nTotalHeight * nCos / fabs(nSin); + if ( eRotMode == SVX_ROTATE_MODE_CENTER ) + nAddX -= nSkew * 0.5; + if ( ( eRotMode == SVX_ROTATE_MODE_TOP && nSin > 0.0 ) || + ( eRotMode == SVX_ROTATE_MODE_BOTTOM && nSin < 0.0 ) ) + nAddX -= nSkew; + + tools::Long nUp = 0; + if ( eVerJust == SvxCellVerJustify::Center ) + nUp = ( aCellSize.Height() - nEngineHeight ) / 2; + else if ( eVerJust == SvxCellVerJustify::Top ) + { + if ( nSin > 0.0 ) + nUp = aCellSize.Height() - nEngineHeight; + } + else // BOTTOM / STANDARD + { + if ( nSin < 0.0 ) + nUp = aCellSize.Height() - nEngineHeight; + } + if ( nUp ) + nAddX += ( nUp * nCos / fabs(nSin) ); + } + + aLogicStart.AdjustX(static_cast(nAddX) ); + aLogicStart.AdjustY(static_cast(nAddY) ); + } + + // bSimClip is not used here (because nOriVal is set) + + mxOutputEditEngine->Draw(*mpDev, aLogicStart, nOriVal); + + if (bMetaFile) + mpDev->Pop(); + else + mpDev->SetClipRegion(); + } + } + } + } + nPosX += pRowInfo[0].basicCellInfo(nX).nWidth * nLayoutSign; + } + } + nRowPosY += pRowInfo[nArrY].nHeight; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/output3.cxx b/sc/source/ui/view/output3.cxx new file mode 100644 index 0000000000..bc6efec654 --- /dev/null +++ b/sc/source/ui/view/output3.cxx @@ -0,0 +1,267 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +// #i72502# +Point ScOutputData::PrePrintDrawingLayer(tools::Long nLogStX, tools::Long nLogStY ) +{ + tools::Rectangle aRect; + SCCOL nCol; + Point aOffset; + tools::Long nLayoutSign(bLayoutRTL ? -1 : 1); + + for (nCol=0; nColGetColWidth( nCol, nTab ) * nLayoutSign) ); + aOffset.AdjustY( -sal_Int32(mpDoc->GetRowHeight( 0, nY1-1, nTab )) ); + + tools::Long nDataWidth = 0; + for (nCol=nX1; nCol<=nX2; nCol++) + nDataWidth += mpDoc->GetColWidth( nCol, nTab ); + + if ( bLayoutRTL ) + aOffset.AdjustX(nDataWidth ); + + aRect.SetLeft( -aOffset.X() ); + aRect.SetRight( -aOffset.X() ); + aRect.SetTop( -aOffset.Y() ); + aRect.SetBottom( -aOffset.Y() ); + + Point aMMOffset( aOffset ); + aMMOffset.setX(o3tl::convert(aMMOffset.X(), o3tl::Length::twip, o3tl::Length::mm100)); + aMMOffset.setY(o3tl::convert(aMMOffset.Y(), o3tl::Length::twip, o3tl::Length::mm100)); + + if (!bMetaFile) + aMMOffset += Point( nLogStX, nLogStY ); + + for (nCol=nX1; nCol<=nX2; nCol++) + aRect.AdjustRight(mpDoc->GetColWidth( nCol, nTab ) ); + aRect.AdjustBottom(mpDoc->GetRowHeight( nY1, nY2, nTab ) ); + + aRect.SetLeft(o3tl::convert(aRect.Left(), o3tl::Length::twip, o3tl::Length::mm100)); + aRect.SetTop(o3tl::convert(aRect.Top(), o3tl::Length::twip, o3tl::Length::mm100)); + aRect.SetRight(o3tl::convert(aRect.Right(), o3tl::Length::twip, o3tl::Length::mm100)); + aRect.SetBottom(o3tl::convert(aRect.Bottom(), o3tl::Length::twip, o3tl::Length::mm100)); + + if(pViewShell || pDrawView) + { + SdrView* pLocalDrawView = pDrawView ? pDrawView : pViewShell->GetScDrawView(); + + if(pLocalDrawView) + { + // #i76114# MapMode has to be set because BeginDrawLayers uses GetPaintRegion + MapMode aOldMode = mpDev->GetMapMode(); + if (!bMetaFile) + mpDev->SetMapMode( MapMode( MapUnit::Map100thMM, aMMOffset, aOldMode.GetScaleX(), aOldMode.GetScaleY() ) ); + + // #i74769# work with SdrPaintWindow directly + // #i76114# pass bDisableIntersect = true, because the intersection of the table area + // with the Window's paint region can be empty + vcl::Region aRectRegion(aRect); + mpTargetPaintWindow = pLocalDrawView->BeginDrawLayers(mpDev, aRectRegion, true); + OSL_ENSURE(mpTargetPaintWindow, "BeginDrawLayers: Got no SdrPaintWindow (!)"); + + if (!bMetaFile) + mpDev->SetMapMode( aOldMode ); + } + } + + return aMMOffset; +} + +// #i72502# +void ScOutputData::PostPrintDrawingLayer(const Point& rMMOffset) // #i74768# +{ + // #i74768# just use offset as in PrintDrawingLayer() to also get the form controls + // painted with offset + MapMode aOldMode = mpDev->GetMapMode(); + + if (!bMetaFile) + { + mpDev->SetMapMode( MapMode( MapUnit::Map100thMM, rMMOffset, aOldMode.GetScaleX(), aOldMode.GetScaleY() ) ); + } + + if(pViewShell || pDrawView) + { + SdrView* pLocalDrawView = pDrawView ? pDrawView : pViewShell->GetScDrawView(); + + if(pLocalDrawView) + { + // #i74769# work with SdrPaintWindow directly + pLocalDrawView->EndDrawLayers(*mpTargetPaintWindow, true); + mpTargetPaintWindow = nullptr; + } + } + + // #i74768# + if (!bMetaFile) + { + mpDev->SetMapMode( aOldMode ); + } +} + +// #i72502# +void ScOutputData::PrintDrawingLayer(SdrLayerID nLayer, const Point& rMMOffset) +{ + bool bHideAllDrawingLayer(false); + + if(pViewShell || pDrawView) + { + SdrView* pLocalDrawView = pDrawView ? pDrawView : pViewShell->GetScDrawView(); + + if(pLocalDrawView) + { + bHideAllDrawingLayer = pLocalDrawView->getHideOle() && pLocalDrawView->getHideChart() + && pLocalDrawView->getHideDraw() && pLocalDrawView->getHideFormControl(); + } + } + + if(bHideAllDrawingLayer || (!mpDoc->GetDrawLayer())) + { + return; + } + + MapMode aOldMode = mpDev->GetMapMode(); + + if (!bMetaFile) + { + mpDev->SetMapMode( MapMode( MapUnit::Map100thMM, rMMOffset, aOldMode.GetScaleX(), aOldMode.GetScaleY() ) ); + } + + DrawSelectiveObjects( nLayer ); + + if (!bMetaFile) + { + mpDev->SetMapMode( aOldMode ); + } +} + +void ScOutputData::DrawSelectiveObjects(SdrLayerID nLayer) +{ + ScDrawLayer* pModel = mpDoc->GetDrawLayer(); + if (!pModel) + return; + + // #i46362# high contrast mode (and default text direction) must be handled + // by the application, so it's still needed when using DrawLayer(). + + SdrOutliner& rOutl = pModel->GetDrawOutliner(); + rOutl.EnableAutoColor( mbUseStyleColor ); + rOutl.SetDefaultHorizontalTextDirection( + mpDoc->GetEditTextDirection( nTab ) ); + + // #i69767# The hyphenator must be set (used to be before drawing a text shape with hyphenation). + // LinguMgr::GetHyphenator (EditEngine) uses a wrapper now that creates the real hyphenator on demand, + // so it's not a performance problem to call UseHyphenator even when it's not needed. + + pModel->UseHyphenator(); + + DrawModeFlags nOldDrawMode = mpDev->GetDrawMode(); + if ( mbUseStyleColor && Application::GetSettings().GetStyleSettings().GetHighContrastMode() ) + { + mpDev->SetDrawMode( nOldDrawMode | DrawModeFlags::SettingsLine | DrawModeFlags::SettingsFill | + DrawModeFlags::SettingsText | DrawModeFlags::SettingsGradient ); + } + + if(pViewShell || pDrawView) + { + SdrView* pLocalDrawView = pDrawView ? pDrawView : pViewShell->GetScDrawView(); + + if(pLocalDrawView) + { + SdrPageView* pPageView = pLocalDrawView->GetSdrPageView(); + + if(pPageView) + { + if (nullptr != pPageView->FindPageWindow(*mpDev)) + { + // Target OutputDevice is registered for this view + // (as it should be), we can just render + pPageView->DrawLayer(sal::static_int_cast(nLayer), mpDev); + } + else if (0 != pPageView->PageWindowCount()) + { + // We need to temporarily make the target OutputDevice being + // 'known/registered' in the paint mechanism so that + // SdrPageView::DrawLayer can find it. + // This situation can occur when someone interprets the + // OutputDevice parameter that gets handed over to DrawLayer + // (or other SdrPageView repaint methods) to be there to + // define a new render target. + // This is *not* the case: This parameter is used to + // *identify* an already registered target-OutputDevice. + // The default is even to call with a nullptr -> that triggers + // the repaint for *all* registered OutputDevices/Windows. + // Since this is a common and known misinterpretation it + // is good to offer workarounds in the code - there are some + // already. + // For now - use an already existing 'patch mechanism' and + // 'smuggle' the unknown/temporary OutputDevice as a + // temporary SdrPaintWindow to the SdrPageWindow, that is + // not very expensive. + // NOTE: Just using the 1st SdrPageWindow here will be OK + // in most cases, the splitting of a view is only used + // in calc nowadays and should have identical zoom. + // Still, trigger a warning... + OSL_ENSURE(1 == pPageView->PageWindowCount(), + "ScOutputData::DrawSelectiveObjects: More than one SdrPageView, still using 1st one (!)"); + SdrPageWindow* patchedPageWindow(pPageView->GetPageWindow(0)); + assert(nullptr != patchedPageWindow && "SdrPageWindow *must* exist when 0 != PageWindowCount()"); + SdrPaintWindow temporaryPaintWindow(*pLocalDrawView, *mpDev); + SdrPaintWindow* previousPaintWindow(patchedPageWindow->patchPaintWindow(temporaryPaintWindow)); + pPageView->DrawLayer(sal::static_int_cast(nLayer), mpDev); + patchedPageWindow->unpatchPaintWindow(previousPaintWindow); + } + else + { + // There does not even exist a SdrPageWindow. Still call the + // paint to get the paint done, but be aware that this will create + // temporary SdrPaintWindow and SdrPageWindow and - due to the + // former - will not be able to use the decomposition buffering + // in the VC/VOC/OC mechanism. For that reason this might be + // somewhat 'expensive'. + // You will also get a warning about it (see OSL_FAIL in + // SdrPageView::DrawLayer) + pPageView->DrawLayer(sal::static_int_cast(nLayer), mpDev); + } + } + } + } + + mpDev->SetDrawMode(nOldDrawMode); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/overlayobject.cxx b/sc/source/ui/view/overlayobject.cxx new file mode 100644 index 0000000000..a564265a95 --- /dev/null +++ b/sc/source/ui/view/overlayobject.cxx @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +using sdr::overlay::OverlayObject; +using sdr::overlay::OverlayManager; + +#define DASH_UPDATE_INTERVAL 500 // in msec + +ScOverlayDashedBorder::ScOverlayDashedBorder(const ::basegfx::B2DRange& rRange, const Color& rColor) : + OverlayObject(rColor), + mbToggle(true) +{ + // tdf#155414 include system "reduce animation" preferences + // Allow the system's "reduce animation" preferences to disable the + // Calc animated border when copying a selection of cells. + mbAllowsAnimation = (officecfg::Office::Common::VCL::AnimationsEnabled::get() && !MiscSettings::GetUseReducedAnimation()); + maRange = rRange; +} + +ScOverlayDashedBorder::~ScOverlayDashedBorder() +{ +} + +void ScOverlayDashedBorder::Trigger(sal_uInt32 nTime) +{ + OverlayManager* pMgr = getOverlayManager(); + if (pMgr) + { + SetTime(nTime + DASH_UPDATE_INTERVAL); + mbToggle = !mbToggle; + pMgr->InsertEvent(*this); + objectChange(); + } +} + +void ScOverlayDashedBorder::stripeDefinitionHasChanged() +{ + objectChange(); +} + +drawinglayer::primitive2d::Primitive2DContainer ScOverlayDashedBorder::createOverlayObjectPrimitive2DSequence() +{ + using ::basegfx::B2DPolygon; + using ::basegfx::B2DPolyPolygon; + + OverlayManager* pMgr = getOverlayManager(); + if (!pMgr) + return drawinglayer::primitive2d::Primitive2DContainer(); + + basegfx::BColor aColorA = pMgr->getStripeColorA().getBColor(); + basegfx::BColor aColorB = pMgr->getStripeColorB().getBColor(); + if (!mbToggle) + ::std::swap(aColorA, aColorB); + + const basegfx::B2DPolygon aPoly = basegfx::utils::createPolygonFromRect(maRange); + B2DPolyPolygon aPolygon(aPoly); + const drawinglayer::primitive2d::Primitive2DReference aReference( + new drawinglayer::primitive2d::PolyPolygonMarkerPrimitive2D( + std::move(aPolygon), aColorA, aColorB, pMgr->getStripeLengthPixel())); + + return drawinglayer::primitive2d::Primitive2DContainer { aReference }; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/pfuncache.cxx b/sc/source/ui/view/pfuncache.cxx new file mode 100644 index 0000000000..fe563ba961 --- /dev/null +++ b/sc/source/ui/view/pfuncache.cxx @@ -0,0 +1,191 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +ScPrintFuncCache::ScPrintFuncCache( ScDocShell* pD, const ScMarkData& rMark, + ScPrintSelectionStatus aStatus ) : + aSelection(std::move( aStatus )), + pDocSh( pD ), + nTotalPages( 0 ), + bLocInitialized( false ) +{ + // page count uses the stored cell widths for the printer anyway, + // so ScPrintFunc with the document's printer can be used to count + + SfxPrinter* pPrinter = pDocSh->GetPrinter(); + + ScRange aRange; + const ScRange* pSelRange = nullptr; + if ( rMark.IsMarked() ) + { + aRange = rMark.GetMarkArea(); + pSelRange = &aRange; + } + + ScDocument& rDoc = pDocSh->GetDocument(); + SCTAB nTabCount = rDoc.GetTableCount(); + + // avoid repeated progress bars if row heights for all sheets are needed + if ( nTabCount > 1 && rMark.GetSelectCount() == nTabCount ) + pDocSh->UpdatePendingRowHeights( nTabCount-1, true ); + + SCTAB nTab; + for ( nTab=0; nTab 0 ? nFirstAttr[nTab-1] : 1; + + tools::Long nThisTab = 0; + if ( rMark.GetTableSelect( nTab ) ) + { + ScPrintFunc aFunc( pDocSh, pPrinter, nTab, nAttrPage, 0, pSelRange, &aSelection.GetOptions() ); + nThisTab = aFunc.GetTotalPages(); + nFirstAttr.push_back( aFunc.GetFirstPageNo() ); // from page style or previous sheet + } + else + nFirstAttr.push_back( nAttrPage ); + + nPages.push_back( nThisTab ); + nTotalPages += nThisTab; + } +} + +ScPrintFuncCache::~ScPrintFuncCache() +{ +} + +void ScPrintFuncCache::InitLocations( const ScMarkData& rMark, OutputDevice* pDev ) +{ + if ( bLocInitialized ) + return; // initialize only once + + ScRange aRange; + const ScRange* pSelRange = nullptr; + if ( rMark.IsMarked() ) + { + aRange = rMark.GetMarkArea(); + pSelRange = &aRange; + } + + tools::Long nRenderer = 0; // 0-based physical page number across sheets + tools::Long nTabStart = 0; + + ScDocument& rDoc = pDocSh->GetDocument(); + SCTAB nTabCount = rDoc.GetTableCount(); + for (SCTAB nTab : rMark) + { + if (nTab >= nTabCount) + break; + ScPrintFunc aFunc( pDev, pDocSh, nTab, nFirstAttr[nTab], nTotalPages, pSelRange, &aSelection.GetOptions() ); + aFunc.SetRenderFlag( true ); + + tools::Long nDisplayStart = GetDisplayStart( nTab ); + + for ( tools::Long nPage=0; nPageGetDocument(); + SCTAB nTabCount = rDoc.GetTableCount(); + SCTAB nTab = 0; + while ( nTab < nTabCount && nPage >= nPages[nTab] ) + nPage -= nPages[nTab++]; + if (nTab >= nTabCount) + nTab = nTabCount - 1; + return nTab; +} + +tools::Long ScPrintFuncCache::GetTabStart( SCTAB nTab ) const +{ + tools::Long nRet = 0; + const SCTAB maxIndex = std::min(nTab, static_cast(nPages.size())); + for ( SCTAB i=0; iGetDocument(); + for (SCTAB i=0; i(nPages.size()) ) + nDisplayStart += nPages[i]; + else + OSL_FAIL("nPages out of bounds, FIX IT!"); + } + } + return nDisplayStart; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/pgbrksh.cxx b/sc/source/ui/view/pgbrksh.cxx new file mode 100644 index 0000000000..a0cdb21be6 --- /dev/null +++ b/sc/source/ui/view/pgbrksh.cxx @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include + +#define ShellClass_ScPageBreakShell +#include + +SFX_IMPL_INTERFACE(ScPageBreakShell, SfxShell) + +void ScPageBreakShell::InitInterface_Impl() +{ + GetStaticInterface()->RegisterPopupMenu("pagebreak"); +} + +ScPageBreakShell::ScPageBreakShell(ScTabViewShell* pViewSh) + : SfxShell(pViewSh) +{ + SetPool(&pViewSh->GetPool()); + ScViewData& rViewData = pViewSh->GetViewData(); + SfxUndoManager* pMgr = rViewData.GetSfxDocShell()->GetUndoManager(); + SetUndoManager(pMgr); + if (!rViewData.GetDocument().IsUndoEnabled()) + { + pMgr->SetMaxUndoActionCount(0); + } + SetName("PageBreak"); +} + +ScPageBreakShell::~ScPageBreakShell() {} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/pivotsh.cxx b/sc/source/ui/view/pivotsh.cxx new file mode 100644 index 0000000000..b4ef807ffa --- /dev/null +++ b/sc/source/ui/view/pivotsh.cxx @@ -0,0 +1,167 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ShellClass_ScPivotShell +#include + + +SFX_IMPL_INTERFACE(ScPivotShell, SfxShell) + +void ScPivotShell::InitInterface_Impl() +{ + GetStaticInterface()->RegisterPopupMenu("pivot"); +} + +ScPivotShell::ScPivotShell( ScTabViewShell* pViewSh ) : + SfxShell(pViewSh), + pViewShell( pViewSh ) +{ + SetPool( &pViewSh->GetPool() ); + ScViewData& rViewData = pViewSh->GetViewData(); + SfxUndoManager* pMgr = rViewData.GetSfxDocShell()->GetUndoManager(); + SetUndoManager( pMgr ); + if ( !rViewData.GetDocument().IsUndoEnabled() ) + { + pMgr->SetMaxUndoActionCount( 0 ); + } + SetName("Pivot"); + SfxShell::SetContextName(vcl::EnumContext::GetContextName(vcl::EnumContext::Context::Pivot)); +} + +ScPivotShell::~ScPivotShell() +{ +} + +void ScPivotShell::Execute( const SfxRequest& rReq ) +{ + switch ( rReq.GetSlot() ) + { + case SID_PIVOT_RECALC: + pViewShell->RecalcPivotTable(); + break; + + case SID_PIVOT_KILL: + pViewShell->DeletePivotTable(); + break; + + case SID_DP_FILTER: + { + ScDPObject* pDPObj = GetCurrDPObject(); + if( pDPObj ) + { + ScQueryParam aQueryParam; + SCTAB nSrcTab = 0; + const ScSheetSourceDesc* pDesc = pDPObj->GetSheetDesc(); + OSL_ENSURE( pDesc, "no sheet source for DP filter dialog" ); + if( pDesc ) + { + aQueryParam = pDesc->GetQueryParam(); + nSrcTab = pDesc->GetSourceRange().aStart.Tab(); + } + + ScViewData& rViewData = pViewShell->GetViewData(); + SfxItemSetFixed aArgSet( pViewShell->GetPool() ); + aArgSet.Put( ScQueryItem( SCITEM_QUERYDATA, &rViewData, &aQueryParam ) ); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr pDlg(pFact->CreateScPivotFilterDlg( + pViewShell->GetFrameWeld(), aArgSet, nSrcTab)); + + if( pDlg->Execute() == RET_OK ) + { + ScSheetSourceDesc aNewDesc(&rViewData.GetDocument()); + if( pDesc ) + aNewDesc = *pDesc; + + const ScQueryItem& rQueryItem = pDlg->GetOutputItem(); + aNewDesc.SetQueryParam(rQueryItem.GetQueryData()); + + ScDPObject aNewObj( *pDPObj ); + aNewObj.SetSheetDesc( aNewDesc ); + ScDBDocFunc aFunc( *rViewData.GetDocShell() ); + aFunc.DataPilotUpdate( pDPObj, &aNewObj, true, false ); + rViewData.GetView()->CursorPosChanged(); // shells may be switched + } + } + } + break; + } +} + +void ScPivotShell::GetState( SfxItemSet& rSet ) +{ + ScDocShell* pDocSh = pViewShell->GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + bool bDisable = pDocSh->IsReadOnly() || rDoc.GetChangeTrack(); + + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + while (nWhich) + { + switch (nWhich) + { + case SID_PIVOT_RECALC: + case SID_PIVOT_KILL: + { + //! move ReadOnly check to idl flags + if ( bDisable ) + { + rSet.DisableItem( nWhich ); + } + } + break; + case SID_DP_FILTER: + { + ScDPObject* pDPObj = GetCurrDPObject(); + if( bDisable || !pDPObj || !pDPObj->IsSheetData() ) + rSet.DisableItem( nWhich ); + } + break; + } + nWhich = aIter.NextWhich(); + } +} + +ScDPObject* ScPivotShell::GetCurrDPObject() +{ + const ScViewData& rViewData = pViewShell->GetViewData(); + return rViewData.GetDocument().GetDPAtCursor( + rViewData.GetCurX(), rViewData.GetCurY(), rViewData.GetTabNo() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/preview.cxx b/sc/source/ui/view/preview.cxx new file mode 100644 index 0000000000..33430883da --- /dev/null +++ b/sc/source/ui/view/preview.cxx @@ -0,0 +1,1575 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#define SC_PREVIEW_SHADOWSIZE 2 + +static tools::Long lcl_GetDisplayStart( SCTAB nTab, const ScDocument* pDoc, std::vector& nPages ) +{ + tools::Long nDisplayStart = 0; + for (SCTAB i=0; iNeedPageResetAfterTab(i) ) + nDisplayStart = 0; + else + nDisplayStart += nPages[i]; + } + return nDisplayStart; +} + +ScPreview::ScPreview( vcl::Window* pParent, ScDocShell* pDocSh, ScPreviewShell* pViewSh ) : + Window( pParent ), + nPageNo( 0 ), + nZoom( 100 ), + nTabCount( 0 ), + nTabsTested( 0 ), + nTab( 0 ), + nTabPage( 0 ), + nTabStart( 0 ), + nDisplayStart( 0 ), + aDateTime( DateTime::SYSTEM ), + nTotalPages( 0 ), + pDocShell( pDocSh ), + pViewShell( pViewSh ), + bInGetState( false ), + bValid( false ), + bStateValid( false ), + bLocationValid( false ), + bInPaint( false ), + bInSetZoom( false ), + bLeftRulerMove( false ), + bRightRulerMove( false ), + bTopRulerMove( false ), + bBottomRulerMove( false ), + bHeaderRulerMove( false ), + bFooterRulerMove( false ), + bLeftRulerChange( false ), + bRightRulerChange( false ), + bTopRulerChange( false ), + bBottomRulerChange( false ), + bHeaderRulerChange( false ), + bFooterRulerChange( false ), + bPageMargin ( false ), + bColRulerMove( false ), + mbHasEmptyRangeTable(false), + nLeftPosition( 0 ), + mnScale( 0 ), + nColNumberButtonDown( 0 ), + nHeaderHeight ( 0 ), + nFooterHeight ( 0 ) +{ + GetOutDev()->SetOutDevViewType( OutDevViewType::PrintPreview ); + SetBackground(); + + SetHelpId( HID_SC_WIN_PREVIEW ); + + GetOutDev()->SetDigitLanguage( ScModule::GetOptDigitLanguage() ); +} + +ScPreview::~ScPreview() +{ + disposeOnce(); +} + +void ScPreview::dispose() +{ + pDrawView.reset(); + pLocationData.reset(); + vcl::Window::dispose(); +} + +void ScPreview::UpdateDrawView() // nTab must be right +{ + ScDocument& rDoc = pDocShell->GetDocument(); + ScDrawLayer* pModel = rDoc.GetDrawLayer(); // is not 0 + + if ( pModel ) + { + SdrPage* pPage = pModel->GetPage(nTab); + if ( pDrawView && ( !pDrawView->GetSdrPageView() || pDrawView->GetSdrPageView()->GetPage() != pPage ) ) + { + // convert the displayed Page of drawView (see below) does not work?!? + pDrawView.reset(); + } + + if ( !pDrawView ) // New Drawing? + { + pDrawView.reset( new FmFormView( *pModel, GetOutDev()) ); + + // The DrawView takes over the Design-Mode from the Model + // (Settings "In opening Draftmode"), therefore to restore here + pDrawView->SetDesignMode(); + pDrawView->SetPrintPreview(); + pDrawView->ShowSdrPage(pPage); + } + } + else if ( pDrawView ) + { + pDrawView.reset(); // for this Chart is not needed + } +} + +void ScPreview::TestLastPage() +{ + if (nPageNo < nTotalPages) + return; + + if (nTotalPages) + { + nPageNo = nTotalPages - 1; + nTab = static_cast(nPages.size()) -1; + while (nTab > 0 && !nPages[nTab]) // not the last empty Table + --nTab; + OSL_ENSURE(0 < static_cast(nPages.size()),"are all tables empty?"); + nTabPage = nPages[nTab] - 1; + nTabStart = 0; + for (sal_uInt16 i=0; iGetDocument(); + nDisplayStart = lcl_GetDisplayStart( nTab, &rDoc, nPages ); + } + else // empty Document + { + nTab = 0; + nPageNo = nTabPage = nTabStart = nDisplayStart = 0; + aState.nPrintTab = 0; + aState.nStartCol = aState.nEndCol = 0; + aState.nStartRow = aState.nEndRow = 0; + aState.nZoom = 0; + aState.nPagesX = aState.nPagesY = 0; + aState.nTabPages = aState.nTotalPages = + aState.nPageStart = aState.nDocPages = 0; + } +} + +void ScPreview::CalcPages() +{ + weld::WaitObject aWait(GetFrameWeld()); + + ScDocument& rDoc = pDocShell->GetDocument(); + nTabCount = rDoc.GetTableCount(); + + if (maSelectedTabs.empty()) + { + SCTAB nCurrentTab = ScDocShell::GetCurTab(); + maSelectedTabs.insert(nCurrentTab); + } + + SCTAB nStart = nTabsTested; + if (!bValid) + { + nStart = 0; + nTotalPages = 0; + nTabsTested = 0; + } + + // update all pending row heights with a single progress bar, + // instead of a separate progress for each sheet from ScPrintFunc + pDocShell->UpdatePendingRowHeights( nTabCount-1, true ); + + // PrintOptions is passed to PrintFunc for SkipEmpty flag, + // but always all sheets are used (there is no selected sheet) + ScPrintOptions aOptions = SC_MOD()->GetPrintOptions(); + + while (nStart > static_cast(nPages.size())) + nPages.push_back(0); + while (nStart > static_cast(nFirstAttr.size())) + nFirstAttr.push_back(1); + + for (SCTAB i=nStart; i(nPages.size())) + nPages.push_back(0); + if ( i == static_cast(nFirstAttr.size())) + nFirstAttr.push_back(1); + if (!aOptions.GetAllSheets() && maSelectedTabs.count(i) == 0) + { + nPages[i] = 0; + nFirstAttr[i] = 1; + continue; + } + + tools::Long nAttrPage = i > 0 ? nFirstAttr[i-1] : 1; + + tools::Long nThisStart = nTotalPages; + ScPrintFunc aPrintFunc( GetOutDev(), pDocShell, i, nAttrPage, 0, nullptr, &aOptions ); + tools::Long nThisTab = aPrintFunc.GetTotalPages(); + if (!aPrintFunc.HasPrintRange()) + mbHasEmptyRangeTable = true; + + nPages[i] = nThisTab; + nTotalPages += nThisTab; + nFirstAttr[i] = aPrintFunc.GetFirstPageNo(); // to keep or from template + + if (nPageNo>=nThisStart && nPageNo nTabsTested) + nTabsTested = nTabCount; + + TestLastPage(); + + aState.nDocPages = nTotalPages; + + bValid = true; + bStateValid = true; + DoInvalidate(); +} + +void ScPreview::RecalcPages() // only nPageNo is changed +{ + if (!bValid) + return; // then CalcPages is called + + SCTAB nOldTab = nTab; + + bool bDone = false; + while (nPageNo >= nTotalPages && nTabsTested < nTabCount) + { + CalcPages(); + bDone = true; + } + + if (!bDone) + { + tools::Long nPartPages = 0; + for (SCTAB i=0; i(nPages.size()); i++) + { + tools::Long nThisStart = nPartPages; + nPartPages += nPages[i]; + + if (nPageNo>=nThisStart && nPageNoGetDocument(); + nDisplayStart = lcl_GetDisplayStart( nTab, &rDoc, nPages ); + } + + TestLastPage(); // to test, if after last page + + if ( nTab != nOldTab ) + bStateValid = false; + + DoInvalidate(); +} + +void ScPreview::DoPrint( ScPreviewLocationData* pFillLocation ) +{ + if (!bValid) + { + CalcPages(); + RecalcPages(); + UpdateDrawView(); // Spreadsheet eventually changes + } + + Fraction aPreviewZoom( nZoom, 100 ); + Fraction aHorPrevZoom( static_cast( 100 * nZoom / pDocShell->GetOutputFactor() ), 10000 ); + MapMode aMMMode( MapUnit::Map100thMM, Point(), aHorPrevZoom, aPreviewZoom ); + + bool bDoPrint = ( pFillLocation == nullptr ); + bool bValidPage = ( nPageNo < nTotalPages ); + + ScModule* pScMod = SC_MOD(); + const svtools::ColorConfig& rColorCfg = pScMod->GetColorConfig(); + Color aBackColor( rColorCfg.GetColorValue(svtools::APPBACKGROUND).nColor ); + + if ( bDoPrint && ( aOffset.X() < 0 || aOffset.Y() < 0 ) && bValidPage ) + { + SetMapMode( aMMMode ); + GetOutDev()->SetLineColor(); + GetOutDev()->SetFillColor(aBackColor); + + Size aWinSize = GetOutDev()->GetOutputSize(); + if ( aOffset.X() < 0 ) + GetOutDev()->DrawRect(tools::Rectangle( 0, 0, -aOffset.X(), aWinSize.Height() )); + if ( aOffset.Y() < 0 ) + GetOutDev()->DrawRect(tools::Rectangle( 0, 0, aWinSize.Width(), -aOffset.Y() )); + } + + tools::Long nLeftMargin = 0; + tools::Long nRightMargin = 0; + tools::Long nTopMargin = 0; + tools::Long nBottomMargin = 0; + bool bHeaderOn = false; + bool bFooterOn = false; + + ScDocument& rDoc = pDocShell->GetDocument(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + + Size aLocalPageSize; + if ( bValidPage ) + { + ScPrintOptions aOptions = pScMod->GetPrintOptions(); + + std::unique_ptr> pPrintFunc; + if (bStateValid) + pPrintFunc.reset(new ScPrintFunc(GetOutDev(), pDocShell, aState, &aOptions)); + else + pPrintFunc.reset(new ScPrintFunc(GetOutDev(), pDocShell, nTab, nFirstAttr[nTab], nTotalPages, nullptr, &aOptions)); + + pPrintFunc->SetOffset(aOffset); + pPrintFunc->SetManualZoom(nZoom); + pPrintFunc->SetDateTime(aDateTime); + pPrintFunc->SetClearFlag(true); + pPrintFunc->SetUseStyleColor( officecfg::Office::Common::Accessibility::IsForPagePreviews::get() ); + + pPrintFunc->SetDrawView( pDrawView.get() ); + + // MultiSelection for the one Page must produce something inconvenient + Range aPageRange( nPageNo+1, nPageNo+1 ); + MultiSelection aPage( aPageRange ); + aPage.SetTotalRange( Range(0,RANGE_MAX) ); + aPage.Select( aPageRange ); + + tools::Long nPrinted = pPrintFunc->DoPrint( aPage, nTabStart, nDisplayStart, bDoPrint, pFillLocation ); + OSL_ENSURE(nPrinted<=1, "What is happening?"); + + SetMapMode(aMMMode); + + //init nLeftMargin ... in the ScPrintFunc::InitParam!!! + nLeftMargin = pPrintFunc->GetLeftMargin(); + nRightMargin = pPrintFunc->GetRightMargin(); + nTopMargin = pPrintFunc->GetTopMargin(); + nBottomMargin = pPrintFunc->GetBottomMargin(); + nHeaderHeight = pPrintFunc->GetHeader().nHeight; + nFooterHeight = pPrintFunc->GetFooter().nHeight; + bHeaderOn = pPrintFunc->GetHeader().bEnable; + bFooterOn = pPrintFunc->GetFooter().bEnable; + mnScale = pPrintFunc->GetZoom(); + + if ( bDoPrint && bPageMargin && pLocationData ) // don't make use of pLocationData while filling it + { + tools::Rectangle aPixRect; + tools::Rectangle aRectCellPosition; + tools::Rectangle aRectPosition; + pLocationData->GetMainCellRange( aPageArea, aPixRect ); + mvRight.resize(aPageArea.aEnd.Col()+1); + if( !bLayoutRTL ) + { + pLocationData->GetCellPosition( aPageArea.aStart, aRectPosition ); + nLeftPosition = aRectPosition.Left(); + for( SCCOL i = aPageArea.aStart.Col(); i <= aPageArea.aEnd.Col(); i++ ) + { + pLocationData->GetCellPosition( ScAddress( i,aPageArea.aStart.Row(),aPageArea.aStart.Tab()),aRectCellPosition ); + mvRight[i] = aRectCellPosition.Right(); + } + } + else + { + pLocationData->GetCellPosition( aPageArea.aEnd, aRectPosition ); + nLeftPosition = aRectPosition.Right()+1; + + pLocationData->GetCellPosition( aPageArea.aStart,aRectCellPosition ); + mvRight[ aPageArea.aEnd.Col() ] = aRectCellPosition.Left(); + for( SCCOL i = aPageArea.aEnd.Col(); i > aPageArea.aStart.Col(); i-- ) + { + pLocationData->GetCellPosition( ScAddress( i,aPageArea.aEnd.Row(),aPageArea.aEnd.Tab()),aRectCellPosition ); + mvRight[ i-1 ] = mvRight[ i ] + aRectCellPosition.Right() - aRectCellPosition.Left() + 1; + } + } + } + + if (nPrinted) // if not, draw everything grey + { + aLocalPageSize = pPrintFunc->GetPageSize(); + aLocalPageSize.setWidth( + o3tl::convert(aLocalPageSize.Width(), o3tl::Length::twip, o3tl::Length::mm100)); + aLocalPageSize.setHeight( + o3tl::convert(aLocalPageSize.Height(), o3tl::Length::twip, o3tl::Length::mm100)); + + nLeftMargin = o3tl::convert(nLeftMargin, o3tl::Length::twip, o3tl::Length::mm100); + nRightMargin = o3tl::convert(nRightMargin, o3tl::Length::twip, o3tl::Length::mm100); + nTopMargin = o3tl::convert(nTopMargin, o3tl::Length::twip, o3tl::Length::mm100); + nBottomMargin = o3tl::convert(nBottomMargin, o3tl::Length::twip, o3tl::Length::mm100); + constexpr auto md = o3tl::getConversionMulDiv(o3tl::Length::twip, o3tl::Length::mm10); + const auto m = md.first * mnScale, d = md.second * 100; + nHeaderHeight = o3tl::convert(nHeaderHeight, m, d) + nTopMargin; + nFooterHeight = o3tl::convert(nFooterHeight, m, d) + nBottomMargin; + } + + if (!bStateValid) + { + pPrintFunc->GetPrintState( aState ); + aState.nDocPages = nTotalPages; + bStateValid = true; + } + } + + if ( !bDoPrint ) + return; + + tools::Long nPageEndX = aLocalPageSize.Width() - aOffset.X(); + tools::Long nPageEndY = aLocalPageSize.Height() - aOffset.Y(); + if ( !bValidPage ) + nPageEndX = nPageEndY = 0; + + Size aWinSize = GetOutDev()->GetOutputSize(); + Point aWinEnd( aWinSize.Width(), aWinSize.Height() ); + bool bRight = nPageEndX <= aWinEnd.X(); + bool bBottom = nPageEndY <= aWinEnd.Y(); + + if (!nTotalPages) + { + // There is no data to print. Print a friendly warning message and + // bail out. + + SetMapMode(aMMMode); + + // Draw background first. + GetOutDev()->SetLineColor(); + GetOutDev()->SetFillColor(aBackColor); + GetOutDev()->DrawRect(tools::Rectangle(0, 0, aWinEnd.X(), aWinEnd.Y())); + + const ScPatternAttr& rDefPattern = + rDoc.GetPool()->GetDefaultItem(ATTR_PATTERN); + + std::unique_ptr pEditEng( + new ScEditEngineDefaulter(EditEngine::CreatePool().get(), true)); + + pEditEng->SetRefMapMode(aMMMode); + auto pEditDefaults = std::make_unique( pEditEng->GetEmptyItemSet() ); + rDefPattern.FillEditItemSet(pEditDefaults.get()); + pEditDefaults->Put(SvxColorItem(COL_LIGHTGRAY, EE_CHAR_COLOR)); + pEditEng->SetDefaults(std::move(pEditDefaults)); + + OUString aEmptyMsg; + if (mbHasEmptyRangeTable) + aEmptyMsg = ScResId(STR_PRINT_PREVIEW_EMPTY_RANGE); + else + aEmptyMsg = ScResId(STR_PRINT_PREVIEW_NODATA); + + tools::Long nHeight = 3000; + pEditEng->SetDefaultItem(SvxFontHeightItem(nHeight, 100, EE_CHAR_FONTHEIGHT)); + pEditEng->SetDefaultItem(SvxFontHeightItem(nHeight, 100, EE_CHAR_FONTHEIGHT_CJK)); + pEditEng->SetDefaultItem(SvxFontHeightItem(nHeight, 100, EE_CHAR_FONTHEIGHT_CTL)); + + pEditEng->SetTextCurrentDefaults(aEmptyMsg); + + Point aCenter( + (aWinEnd.X() - pEditEng->CalcTextWidth())/2, + (aWinEnd.Y() - pEditEng->GetTextHeight())/2); + + pEditEng->Draw(*GetOutDev(), aCenter); + + return; + } + + if( bPageMargin && bValidPage ) + { + SetMapMode(aMMMode); + GetOutDev()->SetLineColor( COL_BLACK ); + DrawInvert( static_cast( nTopMargin - aOffset.Y() ), PointerStyle::VSizeBar ); + DrawInvert( static_cast(nPageEndY - nBottomMargin ), PointerStyle::VSizeBar ); + DrawInvert( static_cast( nLeftMargin - aOffset.X() ), PointerStyle::HSizeBar ); + DrawInvert( static_cast( nPageEndX - nRightMargin ) , PointerStyle::HSizeBar ); + if( bHeaderOn ) + { + DrawInvert( nHeaderHeight - aOffset.Y(), PointerStyle::VSizeBar ); + } + if( bFooterOn ) + { + DrawInvert( nPageEndY - nFooterHeight, PointerStyle::VSizeBar ); + } + + SetMapMode( MapMode( MapUnit::MapPixel ) ); + for( int i= aPageArea.aStart.Col(); i<= aPageArea.aEnd.Col(); i++ ) + { + Point aColumnTop = LogicToPixel( Point( 0, -aOffset.Y() ) ,aMMMode ); + GetOutDev()->SetLineColor( COL_BLACK ); + GetOutDev()->SetFillColor( COL_BLACK ); + GetOutDev()->DrawRect( tools::Rectangle( Point( mvRight[i] - 2, aColumnTop.Y() ),Point( mvRight[i] + 2 , 4 + aColumnTop.Y()) )); + GetOutDev()->DrawLine( Point( mvRight[i], aColumnTop.Y() ), Point( mvRight[i], 10 + aColumnTop.Y()) ); + } + SetMapMode( aMMMode ); + } + + if (bRight || bBottom) + { + SetMapMode(aMMMode); + GetOutDev()->SetLineColor(); + GetOutDev()->SetFillColor(aBackColor); + if (bRight) + GetOutDev()->DrawRect(tools::Rectangle(nPageEndX,0, aWinEnd.X(),aWinEnd.Y())); + if (bBottom) + { + if (bRight) + GetOutDev()->DrawRect(tools::Rectangle(0,nPageEndY, nPageEndX,aWinEnd.Y())); // Corner not duplicated + else + GetOutDev()->DrawRect(tools::Rectangle(0,nPageEndY, aWinEnd.X(),aWinEnd.Y())); + } + } + + if ( !bValidPage ) + return; + + Color aBorderColor( SC_MOD()->GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor ); + + // draw border + + if ( aOffset.X() <= 0 || aOffset.Y() <= 0 || bRight || bBottom ) + { + GetOutDev()->SetLineColor( aBorderColor ); + GetOutDev()->SetFillColor(); + + tools::Rectangle aPixel( LogicToPixel( tools::Rectangle( -aOffset.X(), -aOffset.Y(), nPageEndX, nPageEndY ) ) ); + aPixel.AdjustRight( -1 ); + aPixel.AdjustBottom( -1 ); + GetOutDev()->DrawRect( PixelToLogic( aPixel ) ); + } + + // draw shadow + + GetOutDev()->SetLineColor(); + GetOutDev()->SetFillColor( aBorderColor ); + + tools::Rectangle aPixel; + + aPixel = LogicToPixel( tools::Rectangle( nPageEndX, -aOffset.Y(), nPageEndX, nPageEndY ) ); + aPixel.AdjustTop(SC_PREVIEW_SHADOWSIZE ); + aPixel.AdjustRight(SC_PREVIEW_SHADOWSIZE - 1 ); + aPixel.AdjustBottom(SC_PREVIEW_SHADOWSIZE - 1 ); + GetOutDev()->DrawRect( PixelToLogic( aPixel ) ); + + aPixel = LogicToPixel( tools::Rectangle( -aOffset.X(), nPageEndY, nPageEndX, nPageEndY ) ); + aPixel.AdjustLeft(SC_PREVIEW_SHADOWSIZE ); + aPixel.AdjustRight(SC_PREVIEW_SHADOWSIZE - 1 ); + aPixel.AdjustBottom(SC_PREVIEW_SHADOWSIZE - 1 ); + GetOutDev()->DrawRect( PixelToLogic( aPixel ) ); +} + +void ScPreview::Paint( vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& /* rRect */ ) +{ + bool bWasInPaint = bInPaint; // nested calls shouldn't be necessary, but allow for now + bInPaint = true; + + if (bPageMargin) + GetLocationData(); // fill location data for column positions + DoPrint( nullptr ); + pViewShell->UpdateScrollBars(); + + bInPaint = bWasInPaint; +} + +void ScPreview::Command( const CommandEvent& rCEvt ) +{ + CommandEventId nCmd = rCEvt.GetCommand(); + if ( nCmd == CommandEventId::Wheel || nCmd == CommandEventId::StartAutoScroll || nCmd == CommandEventId::AutoScroll ) + { + bool bDone = pViewShell->ScrollCommand( rCEvt ); + if (!bDone) + Window::Command(rCEvt); + } + else if ( nCmd == CommandEventId::ContextMenu ) + SfxDispatcher::ExecutePopup(); + else + Window::Command( rCEvt ); +} + +void ScPreview::KeyInput( const KeyEvent& rKEvt ) +{ + // The + and - keys can't be configured as accelerator entries, so they must be handled directly + // (in ScPreview, not ScPreviewShell -> only if the preview window has the focus) + + const vcl::KeyCode& rKeyCode = rKEvt.GetKeyCode(); + sal_uInt16 nKey = rKeyCode.GetCode(); + bool bHandled = false; + if(!rKeyCode.GetModifier()) + { + sal_uInt16 nSlot = 0; + switch(nKey) + { + case KEY_ADD: nSlot = SID_ZOOM_IN; break; + case KEY_ESCAPE: nSlot = ScViewUtil::IsFullScreen( *pViewShell ) ? SID_CANCEL : SID_PREVIEW_CLOSE; break; + case KEY_SUBTRACT: nSlot = SID_ZOOM_OUT; break; + } + if(nSlot) + { + bHandled = true; + pViewShell->GetViewFrame().GetDispatcher()->Execute( nSlot, SfxCallMode::ASYNCHRON ); + } + } + + if ( !bHandled && !pViewShell->KeyInput(rKEvt) ) + Window::KeyInput(rKEvt); +} + +const ScPreviewLocationData& ScPreview::GetLocationData() +{ + if ( !pLocationData ) + { + pLocationData.reset( new ScPreviewLocationData( &pDocShell->GetDocument(), GetOutDev() ) ); + bLocationValid = false; + } + if ( !bLocationValid ) + { + pLocationData->Clear(); + DoPrint( pLocationData.get() ); + bLocationValid = true; + } + return *pLocationData; +} + +void ScPreview::DataChanged(bool bNewTime) +{ + if (bNewTime) + aDateTime = DateTime( DateTime::SYSTEM ); + + bValid = false; + InvalidateLocationData( SfxHintId::ScDataChanged ); + Invalidate(); +} + +OUString ScPreview::GetPosString() +{ + if (!bValid) + { + CalcPages(); + UpdateDrawView(); // The table eventually changes + } + + OUString aString = ScResId( STR_PAGE ) + + " " + OUString::number(nPageNo+1); + + if (nTabsTested >= nTabCount) + aString += " / " + OUString::number(nTotalPages); + + return aString; +} + +void ScPreview::SetZoom(sal_uInt16 nNewZoom) +{ + if (nNewZoom < 20) + nNewZoom = 20; + if (nNewZoom > 400) + nNewZoom = 400; + if (nNewZoom == nZoom) + return; + + nZoom = nNewZoom; + + // apply new MapMode and call UpdateScrollBars to update aOffset + + Fraction aPreviewZoom( nZoom, 100 ); + Fraction aHorPrevZoom( static_cast( 100 * nZoom / pDocShell->GetOutputFactor() ), 10000 ); + MapMode aMMMode( MapUnit::Map100thMM, Point(), aHorPrevZoom, aPreviewZoom ); + SetMapMode( aMMMode ); + + bInSetZoom = true; // don't scroll during SetYOffset in UpdateScrollBars + pViewShell->UpdateNeededScrollBars(true); + bInSetZoom = false; + + bStateValid = false; + InvalidateLocationData( SfxHintId::ScAccVisAreaChanged ); + DoInvalidate(); + Invalidate(); +} + +void ScPreview::SetPageNo( tools::Long nPage ) +{ + nPageNo = nPage; + RecalcPages(); + UpdateDrawView(); // The table eventually changes + InvalidateLocationData( SfxHintId::ScDataChanged ); + Invalidate(); +} + +tools::Long ScPreview::GetFirstPage(SCTAB nTabP) +{ + SCTAB nDocTabCount = pDocShell->GetDocument().GetTableCount(); + if (nTabP >= nDocTabCount) + nTabP = nDocTabCount-1; + + tools::Long nPage = 0; + if (nTabP>0) + { + CalcPages(); + if (nTabP >= static_cast(nPages.size()) ) + OSL_FAIL("nPages out of bounds, FIX IT"); + UpdateDrawView(); // The table eventually changes + + for (SCTAB i=0; i 0 ) + --nPage; + } + + return nPage; +} + +static Size lcl_GetDocPageSize( const ScDocument* pDoc, SCTAB nTab ) +{ + OUString aName = pDoc->GetPageStyle( nTab ); + ScStyleSheetPool* pStylePool = pDoc->GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet = pStylePool->Find( aName, SfxStyleFamily::Page ); + if ( pStyleSheet ) + { + SfxItemSet& rStyleSet = pStyleSheet->GetItemSet(); + return rStyleSet.Get(ATTR_PAGE_SIZE).GetSize(); + } + else + { + OSL_FAIL( "PageStyle not found" ); + return Size(); + } +} + +sal_uInt16 ScPreview::GetOptimalZoom(bool bWidthOnly) +{ + double nWinScaleX = ScGlobal::nScreenPPTX / pDocShell->GetOutputFactor(); + double nWinScaleY = ScGlobal::nScreenPPTY; + Size aWinSize = GetOutputSizePixel(); + + // desired margin is 0.25cm in default MapMode (like Writer), + // but some additional margin is introduced by integer scale values + // -> add only 0.10cm, so there is some margin in all cases. + Size aMarginSize( LogicToPixel(Size(100, 100), MapMode(MapUnit::Map100thMM)) ); + aWinSize.AdjustWidth( -(2 * aMarginSize.Width()) ); + aWinSize.AdjustHeight( -(2 * aMarginSize.Height()) ); + + Size aLocalPageSize = lcl_GetDocPageSize( &pDocShell->GetDocument(), nTab ); + if ( aLocalPageSize.Width() && aLocalPageSize.Height() ) + { + tools::Long nZoomX = static_cast( aWinSize.Width() * 100 / ( aLocalPageSize.Width() * nWinScaleX )); + tools::Long nZoomY = static_cast( aWinSize.Height() * 100 / ( aLocalPageSize.Height() * nWinScaleY )); + + tools::Long nOptimal = nZoomX; + if (!bWidthOnly && nZoomY400) + nOptimal = 400; + + return static_cast(nOptimal); + } + else + return nZoom; +} + +void ScPreview::SetXOffset( tools::Long nX ) +{ + if ( aOffset.X() == nX ) + return; + + if (bValid) + { + tools::Long nDif = LogicToPixel(aOffset).X() - LogicToPixel(Point(nX,0)).X(); + aOffset.setX( nX ); + if (nDif && !bInSetZoom) + { + MapMode aOldMode = GetMapMode(); + SetMapMode(MapMode(MapUnit::MapPixel)); + Scroll( nDif, 0 ); + SetMapMode(aOldMode); + } + } + else + { + aOffset.setX( nX ); + if (!bInSetZoom) + Invalidate(); + } + InvalidateLocationData( SfxHintId::ScAccVisAreaChanged ); + Invalidate(); +} + +void ScPreview::SetYOffset( tools::Long nY ) +{ + if ( aOffset.Y() == nY ) + return; + + if (bValid) + { + tools::Long nDif = LogicToPixel(aOffset).Y() - LogicToPixel(Point(0,nY)).Y(); + aOffset.setY( nY ); + if (nDif && !bInSetZoom) + { + MapMode aOldMode = GetMapMode(); + SetMapMode(MapMode(MapUnit::MapPixel)); + Scroll( 0, nDif ); + SetMapMode(aOldMode); + } + } + else + { + aOffset.setY( nY ); + if (!bInSetZoom) + Invalidate(); + } + InvalidateLocationData( SfxHintId::ScAccVisAreaChanged ); + Invalidate(); +} + +void ScPreview::DoInvalidate() +{ + // If the whole GetState of the shell is called + // The Invalidate must come behind asynchronously + + if (bInGetState) + Application::PostUserEvent( LINK( this, ScPreview, InvalidateHdl ), nullptr, true ); + else + StaticInvalidate(); // Immediately +} + +void ScPreview::StaticInvalidate() +{ + // static method, because it's called asynchronously + // -> must use current viewframe + + SfxViewFrame* pViewFrm = SfxViewFrame::Current(); + if (!pViewFrm) + return; + + SfxBindings& rBindings = pViewFrm->GetBindings(); + rBindings.Invalidate(SID_STATUS_DOCPOS); + rBindings.Invalidate(SID_ROWCOL_SELCOUNT); + rBindings.Invalidate(SID_STATUS_PAGESTYLE); + rBindings.Invalidate(SID_PREVIEW_PREVIOUS); + rBindings.Invalidate(SID_PREVIEW_NEXT); + rBindings.Invalidate(SID_PREVIEW_FIRST); + rBindings.Invalidate(SID_PREVIEW_LAST); + rBindings.Invalidate(SID_ATTR_ZOOM); + rBindings.Invalidate(SID_ZOOM_IN); + rBindings.Invalidate(SID_ZOOM_OUT); + rBindings.Invalidate(SID_PREVIEW_SCALINGFACTOR); + rBindings.Invalidate(SID_ATTR_ZOOMSLIDER); +} + +IMPL_STATIC_LINK_NOARG( ScPreview, InvalidateHdl, void*, void ) +{ + StaticInvalidate(); +} + +void ScPreview::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged(rDCEvt); + + if ( !((rDCEvt.GetType() == DataChangedEventType::PRINTER) || + (rDCEvt.GetType() == DataChangedEventType::DISPLAY) || + (rDCEvt.GetType() == DataChangedEventType::FONTS) || + (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) || + ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))) ) + return; + + if ( rDCEvt.GetType() == DataChangedEventType::FONTS ) + pDocShell->UpdateFontList(); + + // #i114518# Paint of form controls may modify the window's settings. + // Ignore the event if it is called from within Paint. + if ( !bInPaint ) + { + if ( rDCEvt.GetType() == DataChangedEventType::SETTINGS && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + { + // scroll bar size may have changed + pViewShell->InvalidateBorder(); // calls OuterResizePixel + } + Invalidate(); + InvalidateLocationData( SfxHintId::ScDataChanged ); + } +} + +void ScPreview::MouseButtonDown( const MouseEvent& rMEvt ) +{ + Fraction aPreviewZoom( nZoom, 100 ); + Fraction aHorPrevZoom( static_cast( 100 * nZoom / pDocShell->GetOutputFactor() ), 10000 ); + MapMode aMMMode( MapUnit::Map100thMM, Point(), aHorPrevZoom, aPreviewZoom ); + + aButtonDownChangePoint = PixelToLogic( rMEvt.GetPosPixel(),aMMMode ); + aButtonDownPt = PixelToLogic( rMEvt.GetPosPixel(),aMMMode ); + + CaptureMouse(); + + if( rMEvt.IsLeft() && GetPointer() == PointerStyle::HSizeBar ) + { + SetMapMode( aMMMode ); + if( bLeftRulerChange ) + { + DrawInvert( aButtonDownChangePoint.X(), PointerStyle::HSizeBar ); + bLeftRulerMove = true; + bRightRulerMove = false; + } + else if( bRightRulerChange ) + { + DrawInvert( aButtonDownChangePoint.X(), PointerStyle::HSizeBar ); + bLeftRulerMove = false; + bRightRulerMove = true; + } + } + + if( rMEvt.IsLeft() && GetPointer() == PointerStyle::VSizeBar ) + { + SetMapMode( aMMMode ); + if( bTopRulerChange ) + { + DrawInvert( aButtonDownChangePoint.Y(), PointerStyle::VSizeBar ); + bTopRulerMove = true; + bBottomRulerMove = false; + } + else if( bBottomRulerChange ) + { + DrawInvert( aButtonDownChangePoint.Y(), PointerStyle::VSizeBar ); + bTopRulerMove = false; + bBottomRulerMove = true; + } + else if( bHeaderRulerChange ) + { + DrawInvert( aButtonDownChangePoint.Y(), PointerStyle::VSizeBar ); + bHeaderRulerMove = true; + bFooterRulerMove = false; + } + else if( bFooterRulerChange ) + { + DrawInvert( aButtonDownChangePoint.Y(), PointerStyle::VSizeBar ); + bHeaderRulerMove = false; + bFooterRulerMove = true; + } + } + + if( !(rMEvt.IsLeft() && GetPointer() == PointerStyle::HSplit) ) + return; + + Point aNowPt = rMEvt.GetPosPixel(); + SCCOL i = 0; + for( i = aPageArea.aStart.Col(); i<= aPageArea.aEnd.Col(); i++ ) + { + if( aNowPt.X() < mvRight[i] + 2 && aNowPt.X() > mvRight[i] - 2 ) + { + nColNumberButtonDown = i; + break; + } + } + if( i == aPageArea.aEnd.Col()+1 ) + return; + + SetMapMode( aMMMode ); + if( nColNumberButtonDown == aPageArea.aStart.Col() ) + DrawInvert( PixelToLogic( Point( nLeftPosition, 0 ),aMMMode ).X() ,PointerStyle::HSplit ); + else + DrawInvert( PixelToLogic( Point( mvRight[ nColNumberButtonDown-1 ], 0 ),aMMMode ).X() ,PointerStyle::HSplit ); + + DrawInvert( aButtonDownChangePoint.X(), PointerStyle::HSplit ); + bColRulerMove = true; +} + +void ScPreview::MouseButtonUp( const MouseEvent& rMEvt ) +{ + Fraction aPreviewZoom( nZoom, 100 ); + Fraction aHorPrevZoom( static_cast( 100 * nZoom / pDocShell->GetOutputFactor() ), 10000 ); + MapMode aMMMode( MapUnit::Map100thMM, Point(), aHorPrevZoom, aPreviewZoom ); + + aButtonUpPt = PixelToLogic( rMEvt.GetPosPixel(),aMMMode ); + + tools::Long nWidth = lcl_GetDocPageSize(&pDocShell->GetDocument(), nTab).Width(); + tools::Long nHeight = lcl_GetDocPageSize(&pDocShell->GetDocument(), nTab).Height(); + + if( rMEvt.IsLeft() && GetPointer() == PointerStyle::HSizeBar ) + { + SetPointer( PointerStyle::Arrow ); + + ScDocument& rDoc = pDocShell->GetDocument(); + OUString aOldName = rDoc.GetPageStyle( nTab ); + bool bUndo = rDoc.IsUndoEnabled(); + ScStyleSheetPool* pStylePool = rDoc.GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet = pStylePool->Find( aOldName, SfxStyleFamily::Page ); + + if ( pStyleSheet ) + { + bool bMoveRulerAction= true; + ScStyleSaveData aOldData; + if( bUndo ) + aOldData.InitFromStyle( pStyleSheet ); + + SfxItemSet& rStyleSet = pStyleSheet->GetItemSet(); + + SvxLRSpaceItem aLRItem = rStyleSet.Get( ATTR_LRSPACE ); + + if(( bLeftRulerChange || bRightRulerChange ) && ( aButtonUpPt.X() <= ( 0 - aOffset.X() ) || aButtonUpPt.X() > o3tl::convert(nWidth, o3tl::Length::twip, o3tl::Length::mm100) - aOffset.X() ) ) + { + bMoveRulerAction = false; + Invalidate(tools::Rectangle(0, 0, 10000, 10000)); + } + else if( bLeftRulerChange && ( o3tl::convert(aButtonUpPt.X(), o3tl::Length::mm100, o3tl::Length::twip) > nWidth - aLRItem.GetRight() - o3tl::convert(aOffset.X(), o3tl::Length::mm100, o3tl::Length::twip) ) ) + { + bMoveRulerAction = false; + Invalidate(tools::Rectangle(0, 0, 10000, 10000)); + } + else if( bRightRulerChange && ( o3tl::convert(aButtonUpPt.X(), o3tl::Length::mm100, o3tl::Length::twip) < aLRItem.GetLeft() - o3tl::convert(aOffset.X(), o3tl::Length::mm100, o3tl::Length::twip) ) ) + { + bMoveRulerAction = false; + Invalidate(tools::Rectangle(0, 0, 10000, 10000)); + } + else if( aButtonDownPt.X() == aButtonUpPt.X() ) + { + bMoveRulerAction = false; + DrawInvert( aButtonUpPt.X(), PointerStyle::HSizeBar ); + } + if( bMoveRulerAction ) + { + ScDocShellModificator aModificator( *pDocShell ); + if( bLeftRulerChange && bLeftRulerMove ) + { + aLRItem.SetLeft(o3tl::convert( aButtonUpPt.X(), o3tl::Length::mm100, o3tl::Length::twip) + o3tl::convert(aOffset.X(), o3tl::Length::mm100, o3tl::Length::twip)); + rStyleSet.Put( aLRItem ); + pDocShell->SetModified(); + } + else if( bRightRulerChange && bRightRulerMove ) + { + aLRItem.SetRight(nWidth - o3tl::convert(aButtonUpPt.X(), o3tl::Length::mm100, o3tl::Length::twip) - o3tl::convert(aOffset.X(), o3tl::Length::mm100, o3tl::Length::twip)); + rStyleSet.Put( aLRItem ); + pDocShell->SetModified(); + } + + ScStyleSaveData aNewData; + aNewData.InitFromStyle( pStyleSheet ); + if( bUndo ) + { + pDocShell->GetUndoManager()->AddUndoAction( + std::make_unique( pDocShell, SfxStyleFamily::Page, + aOldData, aNewData ) ); + } + + if ( ValidTab( nTab ) ) + { + ScPrintFunc aPrintFunc( GetOutDev(), pDocShell, nTab ); + aPrintFunc.UpdatePages(); + } + + tools::Rectangle aRect(0,0,10000,10000); + Invalidate(aRect); + aModificator.SetDocumentModified(); + bLeftRulerChange = false; + bRightRulerChange = false; + } + } + bLeftRulerMove = false; + bRightRulerMove = false; + } + + if( rMEvt.IsLeft() && GetPointer() == PointerStyle::VSizeBar ) + { + SetPointer( PointerStyle::Arrow ); + + bool bMoveRulerAction = true; + if( ( bTopRulerChange || bBottomRulerChange || bHeaderRulerChange || bFooterRulerChange ) && ( aButtonUpPt.Y() <= ( 0 - aOffset.Y() ) || aButtonUpPt.Y() > o3tl::convert(nHeight, o3tl::Length::twip, o3tl::Length::mm100) -aOffset.Y() ) ) + { + bMoveRulerAction = false; + Invalidate(tools::Rectangle(0, 0, 10000, 10000)); + } + else if( aButtonDownPt.Y() == aButtonUpPt.Y() ) + { + bMoveRulerAction = false; + DrawInvert( aButtonUpPt.Y(), PointerStyle::VSizeBar ); + } + if( bMoveRulerAction ) + { + ScDocument& rDoc = pDocShell->GetDocument(); + bool bUndo = rDoc.IsUndoEnabled(); + ScStyleSheetPool* pStylePool = rDoc.GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet = pStylePool->Find( rDoc.GetPageStyle( nTab ), SfxStyleFamily::Page ); + OSL_ENSURE( pStyleSheet, "PageStyle not found" ); + if ( pStyleSheet ) + { + ScDocShellModificator aModificator( *pDocShell ); + ScStyleSaveData aOldData; + if( bUndo ) + aOldData.InitFromStyle( pStyleSheet ); + + SfxItemSet& rStyleSet = pStyleSheet->GetItemSet(); + + SvxULSpaceItem aULItem = rStyleSet.Get( ATTR_ULSPACE ); + + if( bTopRulerMove && bTopRulerChange ) + { + aULItem.SetUpperValue(o3tl::convert(aButtonUpPt.Y(), o3tl::Length::mm100, o3tl::Length::twip) + o3tl::convert(aOffset.Y(), o3tl::Length::mm100, o3tl::Length::twip)); + rStyleSet.Put( aULItem ); + pDocShell->SetModified(); + } + else if( bBottomRulerMove && bBottomRulerChange ) + { + aULItem.SetLowerValue(nHeight - o3tl::convert(aButtonUpPt.Y(), o3tl::Length::mm100, o3tl::Length::twip) - o3tl::convert(aOffset.Y(), o3tl::Length::mm100, o3tl::Length::twip)); + rStyleSet.Put( aULItem ); + pDocShell->SetModified(); + } + else if( bHeaderRulerMove && bHeaderRulerChange ) + { + if ( const SvxSetItem* pSetItem = rStyleSet.GetItemIfSet( ATTR_PAGE_HEADERSET, false ) ) + { + const SfxItemSet& rHeaderSet = pSetItem->GetItemSet(); + Size aHeaderSize = rHeaderSet.Get(ATTR_PAGE_SIZE).GetSize(); + aHeaderSize.setHeight(o3tl::convert( aButtonUpPt.Y(), o3tl::Length::mm100, o3tl::Length::twip) + o3tl::convert(aOffset.Y(), o3tl::Length::mm100, o3tl::Length::twip) - aULItem.GetUpper()); + aHeaderSize.setHeight( aHeaderSize.Height() * 100 / mnScale ); + SvxSetItem aNewHeader( rStyleSet.Get(ATTR_PAGE_HEADERSET) ); + aNewHeader.GetItemSet().Put( SvxSizeItem( ATTR_PAGE_SIZE, aHeaderSize ) ); + rStyleSet.Put( aNewHeader ); + pDocShell->SetModified(); + } + } + else if( bFooterRulerMove && bFooterRulerChange ) + { + if( const SvxSetItem* pSetItem = rStyleSet.GetItemIfSet( ATTR_PAGE_FOOTERSET, false ) ) + { + const SfxItemSet& rFooterSet = pSetItem->GetItemSet(); + Size aFooterSize = rFooterSet.Get(ATTR_PAGE_SIZE).GetSize(); + aFooterSize.setHeight(nHeight - o3tl::convert(aButtonUpPt.Y(), o3tl::Length::mm100, o3tl::Length::twip) - o3tl::convert(aOffset.Y(), o3tl::Length::mm100, o3tl::Length::twip) - aULItem.GetLower()); + aFooterSize.setHeight( aFooterSize.Height() * 100 / mnScale ); + SvxSetItem aNewFooter( rStyleSet.Get(ATTR_PAGE_FOOTERSET) ); + aNewFooter.GetItemSet().Put( SvxSizeItem( ATTR_PAGE_SIZE, aFooterSize ) ); + rStyleSet.Put( aNewFooter ); + pDocShell->SetModified(); + } + } + + ScStyleSaveData aNewData; + aNewData.InitFromStyle( pStyleSheet ); + if( bUndo ) + { + pDocShell->GetUndoManager()->AddUndoAction( + std::make_unique( pDocShell, SfxStyleFamily::Page, + aOldData, aNewData ) ); + } + + if ( ValidTab( nTab ) ) + { + ScPrintFunc aPrintFunc( GetOutDev(), pDocShell, nTab ); + aPrintFunc.UpdatePages(); + } + + tools::Rectangle aRect(0, 0, 10000, 10000); + Invalidate(aRect); + aModificator.SetDocumentModified(); + bTopRulerChange = false; + bBottomRulerChange = false; + bHeaderRulerChange = false; + bFooterRulerChange = false; + } + } + bTopRulerMove = false; + bBottomRulerMove = false; + bHeaderRulerMove = false; + bFooterRulerMove = false; + } + if( rMEvt.IsLeft() && GetPointer() == PointerStyle::HSplit ) + { + SetPointer(PointerStyle::Arrow); + ScDocument& rDoc = pDocShell->GetDocument(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + bool bMoveRulerAction = true; + if( aButtonDownPt.X() == aButtonUpPt.X() ) + { + bMoveRulerAction = false; + if( nColNumberButtonDown == aPageArea.aStart.Col() ) + DrawInvert( PixelToLogic( Point( nLeftPosition, 0 ),aMMMode ).X() ,PointerStyle::HSplit ); + else + DrawInvert( PixelToLogic( Point( mvRight[ nColNumberButtonDown-1 ], 0 ),aMMMode ).X() ,PointerStyle::HSplit ); + DrawInvert( aButtonUpPt.X(), PointerStyle::HSplit ); + } + if( bMoveRulerAction ) + { + tools::Long nNewColWidth = 0; + std::vector aCols(1, sc::ColRowSpan(nColNumberButtonDown,nColNumberButtonDown)); + + constexpr auto md = o3tl::getConversionMulDiv(o3tl::Length::mm100, o3tl::Length::twip); + const auto m = md.first * 100, d = md.second * mnScale; + if( !bLayoutRTL ) + { + nNewColWidth = o3tl::convert(PixelToLogic( Point( rMEvt.GetPosPixel().X() - mvRight[ nColNumberButtonDown ], 0), aMMMode ).X(), m, d); + nNewColWidth += pDocShell->GetDocument().GetColWidth( nColNumberButtonDown, nTab ); + } + else + { + + nNewColWidth = o3tl::convert(PixelToLogic( Point( mvRight[ nColNumberButtonDown ] - rMEvt.GetPosPixel().X(), 0), aMMMode ).X(), m, d); + nNewColWidth += pDocShell->GetDocument().GetColWidth( nColNumberButtonDown, nTab ); + } + + if( nNewColWidth >= 0 ) + { + pDocShell->GetDocFunc().SetWidthOrHeight( + true, aCols, nTab, SC_SIZE_DIRECT, static_cast(nNewColWidth), true, true); + pDocShell->SetModified(); + } + if ( ValidTab( nTab ) ) + { + ScPrintFunc aPrintFunc( GetOutDev(), pDocShell, nTab ); + aPrintFunc.UpdatePages(); + } + tools::Rectangle aRect(0, 0, 10000, 10000); + Invalidate(aRect); + } + bColRulerMove = false; + } + ReleaseMouse(); +} + +void ScPreview::MouseMove( const MouseEvent& rMEvt ) +{ + Fraction aPreviewZoom( nZoom, 100 ); + Fraction aHorPrevZoom( static_cast( 100 * nZoom / pDocShell->GetOutputFactor() ), 10000 ); + MapMode aMMMode( MapUnit::Map100thMM, Point(), aHorPrevZoom, aPreviewZoom ); + Point aMouseMovePoint = PixelToLogic( rMEvt.GetPosPixel(), aMMMode ); + + tools::Long nLeftMargin = 0; + tools::Long nRightMargin = 0; + tools::Long nTopMargin = 0; + tools::Long nBottomMargin = 0; + + tools::Long nWidth = lcl_GetDocPageSize(&pDocShell->GetDocument(), nTab).Width(); + tools::Long nHeight = lcl_GetDocPageSize(&pDocShell->GetDocument(), nTab).Height(); + + if ( nPageNo < nTotalPages ) + { + ScPrintOptions aOptions = SC_MOD()->GetPrintOptions(); + + std::unique_ptr> pPrintFunc; + if (bStateValid) + pPrintFunc.reset(new ScPrintFunc( GetOutDev(), pDocShell, aState, &aOptions )); + else + pPrintFunc.reset(new ScPrintFunc( GetOutDev(), pDocShell, nTab, nFirstAttr[nTab], nTotalPages, nullptr, &aOptions )); + + nLeftMargin = o3tl::convert(pPrintFunc->GetLeftMargin(), o3tl::Length::twip, o3tl::Length::mm100) - aOffset.X(); + nRightMargin = o3tl::convert(pPrintFunc->GetRightMargin(), o3tl::Length::twip, o3tl::Length::mm100); + nRightMargin = o3tl::convert(nWidth, o3tl::Length::twip, o3tl::Length::mm100) - nRightMargin - aOffset.X(); + nTopMargin = o3tl::convert(pPrintFunc->GetTopMargin(), o3tl::Length::twip, o3tl::Length::mm100) - aOffset.Y(); + nBottomMargin = o3tl::convert(pPrintFunc->GetBottomMargin(), o3tl::Length::twip, o3tl::Length::mm100); + nBottomMargin = o3tl::convert(nHeight, o3tl::Length::twip, o3tl::Length::mm100) - nBottomMargin - aOffset.Y(); + if( mnScale > 0 ) + { + constexpr auto md = o3tl::getConversionMulDiv(o3tl::Length::twip, o3tl::Length::mm100); + const auto m = md.first * mnScale, d = md.second * 100; + nHeaderHeight = nTopMargin + o3tl::convert(pPrintFunc->GetHeader().nHeight, m, d); + nFooterHeight = nBottomMargin - o3tl::convert(pPrintFunc->GetFooter().nHeight, m, d); + } + else + { + nHeaderHeight = nTopMargin + o3tl::convert(pPrintFunc->GetHeader().nHeight, o3tl::Length::twip, o3tl::Length::mm100); + nFooterHeight = nBottomMargin - o3tl::convert(pPrintFunc->GetFooter().nHeight, o3tl::Length::twip, o3tl::Length::mm100); + } + } + + Point aPixPt( rMEvt.GetPosPixel() ); + Point aLeftTop = LogicToPixel( Point( nLeftMargin, -aOffset.Y() ) , aMMMode ); + Point aLeftBottom = LogicToPixel( Point( nLeftMargin, o3tl::convert(nHeight, o3tl::Length::twip, o3tl::Length::mm100) - aOffset.Y()), aMMMode ); + Point aRightTop = LogicToPixel( Point( nRightMargin, -aOffset.Y() ), aMMMode ); + Point aTopLeft = LogicToPixel( Point( -aOffset.X(), nTopMargin ), aMMMode ); + Point aTopRight = LogicToPixel( Point( o3tl::convert(nWidth, o3tl::Length::twip, o3tl::Length::mm100) - aOffset.X(), nTopMargin ), aMMMode ); + Point aBottomLeft = LogicToPixel( Point( -aOffset.X(), nBottomMargin ), aMMMode ); + Point aHeaderLeft = LogicToPixel( Point( -aOffset.X(), nHeaderHeight ), aMMMode ); + Point aFooderLeft = LogicToPixel( Point( -aOffset.X(), nFooterHeight ), aMMMode ); + + bool bOnColRulerChange = false; + + for( SCCOL i=aPageArea.aStart.Col(); i<= aPageArea.aEnd.Col(); i++ ) + { + Point aColumnTop = LogicToPixel( Point( 0, -aOffset.Y() ) ,aMMMode ); + Point aColumnBottom = LogicToPixel( Point( 0, o3tl::convert(nHeight, o3tl::Length::twip, o3tl::Length::mm100) - aOffset.Y()), aMMMode ); + tools::Long nRight = i < static_cast(mvRight.size()) ? mvRight[i] : 0; + if( aPixPt.X() < ( nRight + 2 ) && ( aPixPt.X() > ( nRight - 2 ) ) && ( aPixPt.X() < aRightTop.X() ) && ( aPixPt.X() > aLeftTop.X() ) + && ( aPixPt.Y() > aColumnTop.Y() ) && ( aPixPt.Y() < aColumnBottom.Y() ) && !bLeftRulerMove && !bRightRulerMove + && !bTopRulerMove && !bBottomRulerMove && !bHeaderRulerMove && !bFooterRulerMove ) + { + bOnColRulerChange = true; + if( !rMEvt.GetButtons() && GetPointer() == PointerStyle::HSplit ) + nColNumberButtonDown = i; + break; + } + } + + if( aPixPt.X() < ( aLeftTop.X() + 2 ) && aPixPt.X() > ( aLeftTop.X() - 2 ) && !bRightRulerMove ) + { + bLeftRulerChange = true; + bRightRulerChange = false; + } + else if( aPixPt.X() < ( aRightTop.X() + 2 ) && aPixPt.X() > ( aRightTop.X() - 2 ) && !bLeftRulerMove ) + { + bLeftRulerChange = false; + bRightRulerChange = true; + } + else if( aPixPt.Y() < ( aTopLeft.Y() + 2 ) && aPixPt.Y() > ( aTopLeft.Y() - 2 ) && !bBottomRulerMove && !bHeaderRulerMove && !bFooterRulerMove ) + { + bTopRulerChange = true; + bBottomRulerChange = false; + bHeaderRulerChange = false; + bFooterRulerChange = false; + } + else if( aPixPt.Y() < ( aBottomLeft.Y() + 2 ) && aPixPt.Y() > ( aBottomLeft.Y() - 2 ) && !bTopRulerMove && !bHeaderRulerMove && !bFooterRulerMove ) + { + bTopRulerChange = false; + bBottomRulerChange = true; + bHeaderRulerChange = false; + bFooterRulerChange = false; + } + else if( aPixPt.Y() < ( aHeaderLeft.Y() + 2 ) && aPixPt.Y() > ( aHeaderLeft.Y() - 2 ) && !bTopRulerMove && !bBottomRulerMove && !bFooterRulerMove ) + { + bTopRulerChange = false; + bBottomRulerChange = false; + bHeaderRulerChange = true; + bFooterRulerChange = false; + } + else if( aPixPt.Y() < ( aFooderLeft.Y() + 2 ) && aPixPt.Y() > ( aFooderLeft.Y() - 2 ) && !bTopRulerMove && !bBottomRulerMove && !bHeaderRulerMove ) + { + bTopRulerChange = false; + bBottomRulerChange = false; + bHeaderRulerChange = false; + bFooterRulerChange = true; + } + + if( !bPageMargin ) + return; + + if(( (aPixPt.X() < ( aLeftTop.X() + 2 ) && aPixPt.X() > ( aLeftTop.X() - 2 )) || bLeftRulerMove || + ( aPixPt.X() < ( aRightTop.X() + 2 ) && aPixPt.X() > ( aRightTop.X() - 2 ) ) || bRightRulerMove || bOnColRulerChange || bColRulerMove ) + && aPixPt.Y() > aLeftTop.Y() && aPixPt.Y() < aLeftBottom.Y() ) + { + if( bOnColRulerChange || bColRulerMove ) + { + SetPointer( PointerStyle::HSplit ); + if( bColRulerMove ) + { + if( aMouseMovePoint.X() > -aOffset.X() && aMouseMovePoint.X() < o3tl::convert(nWidth, o3tl::Length::twip, o3tl::Length::mm100) - aOffset.X() ) + DragMove( aMouseMovePoint.X(), PointerStyle::HSplit ); + } + } + else + { + if( bLeftRulerChange && !bTopRulerMove && !bBottomRulerMove && !bHeaderRulerMove && !bFooterRulerMove ) + { + SetPointer( PointerStyle::HSizeBar ); + if( bLeftRulerMove ) + { + if( aMouseMovePoint.X() > -aOffset.X() && aMouseMovePoint.X() < o3tl::convert(nWidth, o3tl::Length::twip, o3tl::Length::mm100) - aOffset.X() ) + DragMove( aMouseMovePoint.X(), PointerStyle::HSizeBar ); + } + } + else if( bRightRulerChange && !bTopRulerMove && !bBottomRulerMove && !bHeaderRulerMove && !bFooterRulerMove ) + { + SetPointer( PointerStyle::HSizeBar ); + if( bRightRulerMove ) + { + if( aMouseMovePoint.X() > -aOffset.X() && aMouseMovePoint.X() < o3tl::convert(nWidth, o3tl::Length::twip, o3tl::Length::mm100) - aOffset.X() ) + DragMove( aMouseMovePoint.X(), PointerStyle::HSizeBar ); + } + } + } + } + else + { + if( ( ( aPixPt.Y() < ( aTopLeft.Y() + 2 ) && aPixPt.Y() > ( aTopLeft.Y() - 2 ) ) || bTopRulerMove || + ( aPixPt.Y() < ( aBottomLeft.Y() + 2 ) && aPixPt.Y() > ( aBottomLeft.Y() - 2 ) ) || bBottomRulerMove || + ( aPixPt.Y() < ( aHeaderLeft.Y() + 2 ) && aPixPt.Y() > ( aHeaderLeft.Y() - 2 ) ) || bHeaderRulerMove || + ( aPixPt.Y() < ( aFooderLeft.Y() + 2 ) && aPixPt.Y() > ( aFooderLeft.Y() - 2 ) ) || bFooterRulerMove ) + && aPixPt.X() > aTopLeft.X() && aPixPt.X() < aTopRight.X() ) + { + if( bTopRulerChange ) + { + SetPointer( PointerStyle::VSizeBar ); + if( bTopRulerMove ) + { + if( aMouseMovePoint.Y() > -aOffset.Y() && aMouseMovePoint.Y() < o3tl::convert(nHeight, o3tl::Length::twip, o3tl::Length::mm100) - aOffset.Y() ) + DragMove( aMouseMovePoint.Y(), PointerStyle::VSizeBar ); + } + } + else if( bBottomRulerChange ) + { + SetPointer( PointerStyle::VSizeBar ); + if( bBottomRulerMove ) + { + if( aMouseMovePoint.Y() > -aOffset.Y() && aMouseMovePoint.Y() < o3tl::convert(nHeight, o3tl::Length::twip, o3tl::Length::mm100) - aOffset.Y() ) + DragMove( aMouseMovePoint.Y(), PointerStyle::VSizeBar ); + } + } + else if( bHeaderRulerChange ) + { + SetPointer( PointerStyle::VSizeBar ); + if( bHeaderRulerMove ) + { + if( aMouseMovePoint.Y() > -aOffset.Y() && aMouseMovePoint.Y() < o3tl::convert(nHeight, o3tl::Length::twip, o3tl::Length::mm100) - aOffset.Y() ) + DragMove( aMouseMovePoint.Y(), PointerStyle::VSizeBar ); + } + } + else if( bFooterRulerChange ) + { + SetPointer( PointerStyle::VSizeBar ); + if( bFooterRulerMove ) + { + if( aMouseMovePoint.Y() > -aOffset.Y() && aMouseMovePoint.Y() < o3tl::convert(nHeight, o3tl::Length::twip, o3tl::Length::mm100) - aOffset.Y() ) + DragMove( aMouseMovePoint.Y(), PointerStyle::VSizeBar ); + } + } + } + else + SetPointer( PointerStyle::Arrow ); + } +} + +void ScPreview::InvalidateLocationData(SfxHintId nId) +{ + bLocationValid = false; + if (pViewShell->HasAccessibilityObjects()) + pViewShell->BroadcastAccessibility( SfxHint( nId ) ); +} + +void ScPreview::GetFocus() +{ + Window::GetFocus(); + if (pViewShell && pViewShell->HasAccessibilityObjects()) + pViewShell->BroadcastAccessibility( ScAccWinFocusGotHint() ); +} + +void ScPreview::LoseFocus() +{ + if (pViewShell && pViewShell->HasAccessibilityObjects()) + pViewShell->BroadcastAccessibility( ScAccWinFocusLostHint() ); + Window::LoseFocus(); +} + +css::uno::Reference ScPreview::CreateAccessible() +{ + css::uno::Reference xAcc= GetAccessible(false); + if (xAcc.is()) + { + return xAcc; + } + + rtl::Reference pAccessible = + new ScAccessibleDocumentPagePreview( GetAccessibleParentWindow()->GetAccessible(), pViewShell ); + + xAcc = pAccessible; + SetAccessible(xAcc); + pAccessible->Init(); + return xAcc; +} + +void ScPreview::DragMove( tools::Long nDragMovePos, PointerStyle nFlags ) +{ + Fraction aPreviewZoom( nZoom, 100 ); + Fraction aHorPrevZoom( static_cast( 100 * nZoom / pDocShell->GetOutputFactor() ), 10000 ); + MapMode aMMMode( MapUnit::Map100thMM, Point(), aHorPrevZoom, aPreviewZoom ); + SetMapMode( aMMMode ); + tools::Long nPos = nDragMovePos; + if( nFlags == PointerStyle::HSizeBar || nFlags == PointerStyle::HSplit ) + { + if( nDragMovePos != aButtonDownChangePoint.X() ) + { + DrawInvert( aButtonDownChangePoint.X(), nFlags ); + aButtonDownChangePoint.setX( nPos ); + DrawInvert( aButtonDownChangePoint.X(), nFlags ); + } + } + else if( nFlags == PointerStyle::VSizeBar ) + { + if( nDragMovePos != aButtonDownChangePoint.Y() ) + { + DrawInvert( aButtonDownChangePoint.Y(), nFlags ); + aButtonDownChangePoint.setY( nPos ); + DrawInvert( aButtonDownChangePoint.Y(), nFlags ); + } + } +} + +void ScPreview::DrawInvert( tools::Long nDragPos, PointerStyle nFlags ) +{ + tools::Long nHeight = lcl_GetDocPageSize( &pDocShell->GetDocument(), nTab ).Height(); + tools::Long nWidth = lcl_GetDocPageSize( &pDocShell->GetDocument(), nTab ).Width(); + if( nFlags == PointerStyle::HSizeBar || nFlags == PointerStyle::HSplit ) + { + tools::Rectangle aRect( nDragPos, -aOffset.Y(), nDragPos + 1, o3tl::convert(nHeight, o3tl::Length::twip, o3tl::Length::mm100) - aOffset.Y()); + GetOutDev()->Invert( aRect, InvertFlags::N50 ); + } + else if( nFlags == PointerStyle::VSizeBar ) + { + tools::Rectangle aRect( -aOffset.X(), nDragPos, o3tl::convert(nWidth, o3tl::Length::twip, o3tl::Length::mm100) - aOffset.X(), nDragPos + 1 ); + GetOutDev()->Invert( aRect, InvertFlags::N50 ); + } +} + +void ScPreview::SetSelectedTabs(const ScMarkData& rMark) +{ + maSelectedTabs = rMark.GetSelectedTabs(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/prevloc.cxx b/sc/source/ui/view/prevloc.cxx new file mode 100644 index 0000000000..1e2375ab5c --- /dev/null +++ b/sc/source/ui/view/prevloc.cxx @@ -0,0 +1,718 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include + +namespace { + +enum ScPreviewLocationType : sal_uInt8 +{ + SC_PLOC_CELLRANGE, + SC_PLOC_COLHEADER, + SC_PLOC_ROWHEADER, + SC_PLOC_LEFTHEADER, + SC_PLOC_RIGHTHEADER, + SC_PLOC_LEFTFOOTER, + SC_PLOC_RIGHTFOOTER, + SC_PLOC_NOTEMARK, + SC_PLOC_NOTETEXT +}; + +} + +struct ScPreviewLocationEntry +{ + tools::Rectangle aPixelRect; + ScRange aCellRange; + ScPreviewLocationType eType; + bool bRepeatCol; + bool bRepeatRow; + + ScPreviewLocationEntry( ScPreviewLocationType eNewType, const tools::Rectangle& rPixel, const ScRange& rRange, + bool bRepCol, bool bRepRow ) : + aPixelRect( rPixel ), + aCellRange( rRange ), + eType( eNewType ), + bRepeatCol( bRepCol ), + bRepeatRow( bRepRow ) + { + } +}; + +ScPreviewTableInfo::ScPreviewTableInfo() : + nTab(0), + nCols(0), + nRows(0) +{ +} + +ScPreviewTableInfo::~ScPreviewTableInfo() +{ +} + +void ScPreviewTableInfo::SetTab( SCTAB nNewTab ) +{ + nTab = nNewTab; +} + +void ScPreviewTableInfo::SetColInfo( SCCOL nCount, ScPreviewColRowInfo* pNewInfo ) +{ + pColInfo.reset(pNewInfo); + nCols = nCount; +} + +void ScPreviewTableInfo::SetRowInfo( SCROW nCount, ScPreviewColRowInfo* pNewInfo ) +{ + pRowInfo.reset(pNewInfo); + nRows = nCount; +} + +void ScPreviewTableInfo::LimitToArea( const tools::Rectangle& rPixelArea ) +{ + if ( pColInfo ) + { + // cells completely left of the visible area + SCCOL nStart = 0; + while ( nStart < nCols && pColInfo[nStart].nPixelEnd < rPixelArea.Left() ) + ++nStart; + + // cells completely right of the visible area + SCCOL nEnd = nCols; + while ( nEnd > 0 && pColInfo[nEnd-1].nPixelStart > rPixelArea.Right() ) + --nEnd; + + if ( nStart > 0 || nEnd < nCols ) + { + if ( nEnd > nStart ) + { + SCCOL nNewCount = nEnd - nStart; + ScPreviewColRowInfo* pNewInfo = new ScPreviewColRowInfo[nNewCount]; + for (SCCOL i=0; i 0 && pRowInfo[nEnd-1].nPixelStart > rPixelArea.Bottom() ) + --nEnd; + + if ( nStart <= 0 && nEnd >= nRows ) + return; + + if ( nEnd > nStart ) + { + SCROW nNewCount = nEnd - nStart; + ScPreviewColRowInfo* pNewInfo = new ScPreviewColRowInfo[nNewCount]; + for (SCROW i=0; iLogicToPixel( rRect ) ); + m_Entries.push_front( std::make_unique(SC_PLOC_CELLRANGE, aPixelRect, rRange, bRepCol, bRepRow) ); + + OSL_ENSURE( nDrawRanges < SC_PREVIEW_MAXRANGES, "too many ranges" ); + + if ( nDrawRanges >= SC_PREVIEW_MAXRANGES ) + return; + + aDrawRectangle[nDrawRanges] = aPixelRect; + aDrawMapMode[nDrawRanges] = rDrawMap; + + if (bRepCol) + { + if (bRepRow) + aDrawRangeId[nDrawRanges] = SC_PREVIEW_RANGE_EDGE; + else + aDrawRangeId[nDrawRanges] = SC_PREVIEW_RANGE_REPCOL; + } + else + { + if (bRepRow) + aDrawRangeId[nDrawRanges] = SC_PREVIEW_RANGE_REPROW; + else + aDrawRangeId[nDrawRanges] = SC_PREVIEW_RANGE_TAB; + } + + ++nDrawRanges; +} + +void ScPreviewLocationData::AddColHeaders( const tools::Rectangle& rRect, SCCOL nStartCol, SCCOL nEndCol, bool bRepCol ) +{ + SCTAB nTab = 0; //! ? + ScRange aRange( nStartCol, 0, nTab, nEndCol, 0, nTab ); + tools::Rectangle aPixelRect( pWindow->LogicToPixel( rRect ) ); + + m_Entries.push_front( std::make_unique(SC_PLOC_COLHEADER, aPixelRect, aRange, bRepCol, false) ); +} + +void ScPreviewLocationData::AddRowHeaders( const tools::Rectangle& rRect, SCROW nStartRow, SCROW nEndRow, bool bRepRow ) +{ + SCTAB nTab = 0; //! ? + ScRange aRange( 0, nStartRow, nTab, 0, nEndRow, nTab ); + tools::Rectangle aPixelRect( pWindow->LogicToPixel( rRect ) ); + + m_Entries.push_front( std::make_unique(SC_PLOC_ROWHEADER, aPixelRect, aRange, false, bRepRow) ); +} + +void ScPreviewLocationData::AddHeaderFooter( const tools::Rectangle& rRect, bool bHeader, bool bLeft ) +{ + ScRange aRange; //! ? + tools::Rectangle aPixelRect( pWindow->LogicToPixel( rRect ) ); + + ScPreviewLocationType eType = bHeader ? + ( bLeft ? SC_PLOC_LEFTHEADER : SC_PLOC_RIGHTHEADER ) : + ( bLeft ? SC_PLOC_LEFTFOOTER : SC_PLOC_RIGHTFOOTER ); + + m_Entries.push_front( std::make_unique(eType, aPixelRect, aRange, false, false) ); +} + +void ScPreviewLocationData::AddNoteMark( const tools::Rectangle& rRect, const ScAddress& rPos ) +{ + ScRange aRange( rPos ); + tools::Rectangle aPixelRect( pWindow->LogicToPixel( rRect ) ); + + m_Entries.push_front( std::make_unique(SC_PLOC_NOTEMARK, aPixelRect, aRange, false, false) ); +} + +void ScPreviewLocationData::AddNoteText( const tools::Rectangle& rRect, const ScAddress& rPos ) +{ + ScRange aRange( rPos ); + tools::Rectangle aPixelRect( pWindow->LogicToPixel( rRect ) ); + + m_Entries.push_front( std::make_unique(SC_PLOC_NOTETEXT, aPixelRect, aRange, false, false) ); +} + +void ScPreviewLocationData::GetDrawRange( sal_uInt16 nPos, tools::Rectangle& rPixelRect, MapMode& rMapMode, sal_uInt8& rRangeId ) const +{ + OSL_ENSURE( nPos < nDrawRanges, "wrong position" ); + if ( nPos < nDrawRanges ) + { + rPixelRect = aDrawRectangle[nPos]; + rMapMode = aDrawMapMode[nPos]; + rRangeId = aDrawRangeId[nPos]; + } +} + +static ScPreviewLocationEntry* lcl_GetEntryByAddress( + ScPreviewLocationData::Entries_t const& rEntries, + const ScAddress& rPos, ScPreviewLocationType const eType) +{ + for (auto const& it : rEntries) + { + if ( it->eType == eType && it->aCellRange.Contains( rPos ) ) + return it.get(); + } + + return nullptr; +} + +tools::Rectangle ScPreviewLocationData::GetOffsetPixel( const ScAddress& rCellPos, const ScRange& rRange ) const +{ + SCTAB nTab = rRange.aStart.Tab(); + + tools::Long nPosX = 0; + SCCOL nEndCol = rCellPos.Col(); + for (SCCOL nCol = rRange.aStart.Col(); nCol < nEndCol; nCol++) + { + sal_uInt16 nDocW = pDoc->GetColWidth( nCol, nTab ); + if (nDocW) + nPosX += o3tl::convert(nDocW, o3tl::Length::twip, o3tl::Length::mm100); + } + const tools::Long nSizeX + = o3tl::convert(pDoc->GetColWidth(nEndCol, nTab), o3tl::Length::twip, o3tl::Length::mm100); + + SCROW nEndRow = rCellPos.Row(); + tools::Long nPosY = o3tl::convert(pDoc->GetRowHeight(rRange.aStart.Row(), nEndRow, nTab), + o3tl::Length::twip, o3tl::Length::mm100); + tools::Long nSizeY + = o3tl::convert(pDoc->GetRowHeight(nEndRow, nTab), o3tl::Length::twip, o3tl::Length::mm100); + + Size aOffsetLogic( nPosX, nPosY ); + Size aSizeLogic( nSizeX, nSizeY ); + Size aOffsetPixel = pWindow->LogicToPixel( aOffsetLogic, aCellMapMode ); + Size aSizePixel = pWindow->LogicToPixel( aSizeLogic, aCellMapMode ); + + return tools::Rectangle( Point( aOffsetPixel.Width(), aOffsetPixel.Height() ), aSizePixel ); +} + +void ScPreviewLocationData::GetCellPosition( const ScAddress& rCellPos, tools::Rectangle& rCellRect ) const +{ + ScPreviewLocationEntry* pEntry = lcl_GetEntryByAddress( m_Entries, rCellPos, SC_PLOC_CELLRANGE ); + if ( pEntry ) + { + tools::Rectangle aOffsetRect = GetOffsetPixel( rCellPos, pEntry->aCellRange ); + rCellRect = tools::Rectangle( aOffsetRect.Left() + pEntry->aPixelRect.Left(), + aOffsetRect.Top() + pEntry->aPixelRect.Top(), + aOffsetRect.Right() + pEntry->aPixelRect.Left(), + aOffsetRect.Bottom() + pEntry->aPixelRect.Top() ); + } +} + +bool ScPreviewLocationData::HasCellsInRange( const tools::Rectangle& rVisiblePixel ) const +{ + for (auto const& it : m_Entries) + { + if ( it->eType == SC_PLOC_CELLRANGE || it->eType == SC_PLOC_COLHEADER || it->eType == SC_PLOC_ROWHEADER ) + if ( it->aPixelRect.Overlaps( rVisiblePixel ) ) + return true; + } + + return false; +} + +bool ScPreviewLocationData::GetHeaderPosition( tools::Rectangle& rRect ) const +{ + for (auto const& it : m_Entries) + { + if ( it->eType == SC_PLOC_LEFTHEADER || it->eType == SC_PLOC_RIGHTHEADER ) + { + rRect = it->aPixelRect; + return true; + } + } + + return false; +} + +bool ScPreviewLocationData::GetFooterPosition( tools::Rectangle& rRect ) const +{ + for (auto const& it : m_Entries) + { + if ( it->eType == SC_PLOC_LEFTFOOTER || it->eType == SC_PLOC_RIGHTFOOTER ) + { + rRect = it->aPixelRect; + return true; + } + } + + return false; +} + +bool ScPreviewLocationData::IsHeaderLeft() const +{ + for (auto const& it : m_Entries) + { + if ( it->eType == SC_PLOC_LEFTHEADER ) + return true; + + if ( it->eType == SC_PLOC_RIGHTHEADER ) + return false; + } + + return false; +} + +bool ScPreviewLocationData::IsFooterLeft() const +{ + for (auto const& it : m_Entries) + { + if ( it->eType == SC_PLOC_LEFTFOOTER ) + return true; + + if ( it->eType == SC_PLOC_RIGHTFOOTER ) + return false; + } + + return false; +} + +tools::Long ScPreviewLocationData::GetNoteCountInRange( const tools::Rectangle& rVisiblePixel, bool bNoteMarks ) const +{ + ScPreviewLocationType eType = bNoteMarks ? SC_PLOC_NOTEMARK : SC_PLOC_NOTETEXT; + + sal_uLong nRet = 0; + for (auto const& it : m_Entries) + { + if ( it->eType == eType && it->aPixelRect.Overlaps( rVisiblePixel ) ) + ++nRet; + } + + return nRet; +} + +bool ScPreviewLocationData::GetNoteInRange( const tools::Rectangle& rVisiblePixel, tools::Long nIndex, bool bNoteMarks, + ScAddress& rCellPos, tools::Rectangle& rNoteRect ) const +{ + ScPreviewLocationType eType = bNoteMarks ? SC_PLOC_NOTEMARK : SC_PLOC_NOTETEXT; + + sal_uLong nPos = 0; + for (auto const& it : m_Entries) + { + if ( it->eType == eType && it->aPixelRect.Overlaps( rVisiblePixel ) ) + { + if ( nPos == sal::static_int_cast(nIndex) ) + { + rCellPos = it->aCellRange.aStart; + rNoteRect = it->aPixelRect; + return true; + } + ++nPos; + } + } + + return false; +} + +tools::Rectangle ScPreviewLocationData::GetNoteInRangeOutputRect(const tools::Rectangle& rVisiblePixel, bool bNoteMarks, const ScAddress& aCellPos) const +{ + ScPreviewLocationType eType = bNoteMarks ? SC_PLOC_NOTEMARK : SC_PLOC_NOTETEXT; + + for (auto const& it : m_Entries) + { + if ( it->eType == eType && it->aPixelRect.Overlaps( rVisiblePixel ) ) + { + if ( aCellPos == it->aCellRange.aStart ) + return it->aPixelRect; + } + } + + return tools::Rectangle(); +} + +void ScPreviewLocationData::GetTableInfo( const tools::Rectangle& rVisiblePixel, ScPreviewTableInfo& rInfo ) const +{ + // from left to right: + bool bHasHeaderCol = false; + bool bHasRepCols = false; + bool bHasMainCols = false; + SCCOL nRepeatColStart = 0; + SCCOL nRepeatColEnd = 0; + SCCOL nMainColStart = 0; + SCCOL nMainColEnd = 0; + + // from top to bottom: + bool bHasHeaderRow = false; + bool bHasRepRows = false; + bool bHasMainRows = false; + SCROW nRepeatRowStart = 0; + SCROW nRepeatRowEnd = 0; + SCROW nMainRowStart = 0; + SCROW nMainRowEnd = 0; + + tools::Rectangle aHeaderRect, aRepeatRect, aMainRect; + SCTAB nTab = 0; + + for (auto const& it : m_Entries) + { + if ( it->eType == SC_PLOC_CELLRANGE ) + { + if ( it->bRepeatCol ) + { + bHasRepCols = true; + nRepeatColStart = it->aCellRange.aStart.Col(); + nRepeatColEnd = it->aCellRange.aEnd.Col(); + aRepeatRect.SetLeft( it->aPixelRect.Left() ); + aRepeatRect.SetRight( it->aPixelRect.Right() ); + } + else + { + bHasMainCols = true; + nMainColStart = it->aCellRange.aStart.Col(); + nMainColEnd = it->aCellRange.aEnd.Col(); + aMainRect.SetLeft( it->aPixelRect.Left() ); + aMainRect.SetRight( it->aPixelRect.Right() ); + } + if ( it->bRepeatRow ) + { + bHasRepRows = true; + nRepeatRowStart = it->aCellRange.aStart.Row(); + nRepeatRowEnd = it->aCellRange.aEnd.Row(); + aRepeatRect.SetTop( it->aPixelRect.Top() ); + aRepeatRect.SetBottom( it->aPixelRect.Bottom() ); + } + else + { + bHasMainRows = true; + nMainRowStart = it->aCellRange.aStart.Row(); + nMainRowEnd = it->aCellRange.aEnd.Row(); + aMainRect.SetTop( it->aPixelRect.Top() ); + aMainRect.SetBottom( it->aPixelRect.Bottom() ); + } + nTab = it->aCellRange.aStart.Tab(); //! store separately? + } + else if ( it->eType == SC_PLOC_ROWHEADER ) + { + // row headers result in an additional column + bHasHeaderCol = true; + aHeaderRect.SetLeft( it->aPixelRect.Left() ); + aHeaderRect.SetRight( it->aPixelRect.Right() ); + } + else if ( it->eType == SC_PLOC_COLHEADER ) + { + // column headers result in an additional row + bHasHeaderRow = true; + aHeaderRect.SetTop( it->aPixelRect.Top() ); + aHeaderRect.SetBottom( it->aPixelRect.Bottom() ); + } + } + + // get column info + + SCCOL nColCount = 0; + SCCOL nCol; + if ( bHasHeaderCol ) + ++nColCount; + if ( bHasRepCols ) + for ( nCol=nRepeatColStart; nCol<=nRepeatColEnd; nCol++ ) + if (!pDoc->ColHidden(nCol, nTab)) + ++nColCount; + if ( bHasMainCols ) + for ( nCol=nMainColStart; nCol<=nMainColEnd; nCol++ ) + if (!pDoc->ColHidden(nCol, nTab)) + ++nColCount; + + if ( nColCount > 0 ) + { + ScPreviewColRowInfo* pColInfo = new ScPreviewColRowInfo[ nColCount ]; + SCCOL nColPos = 0; + + if ( bHasHeaderCol ) + { + pColInfo[nColPos].Set( true, 0, aHeaderRect.Left(), aHeaderRect.Right() ); + ++nColPos; + } + if ( bHasRepCols ) + { + tools::Long nPosX = 0; + for ( nCol=nRepeatColStart; nCol<=nRepeatColEnd; nCol++ ) + if (!pDoc->ColHidden(nCol, nTab)) + { + sal_uInt16 nDocW = pDoc->GetColWidth( nCol, nTab ); + tools::Long nNextX + = nPosX + o3tl::convert(nDocW, o3tl::Length::twip, o3tl::Length::mm100); + + tools::Long nPixelStart = pWindow->LogicToPixel( Size( nPosX, 0 ), aCellMapMode ).Width(); + tools::Long nPixelEnd = pWindow->LogicToPixel( Size( nNextX, 0 ), aCellMapMode ).Width() - 1; + pColInfo[nColPos].Set( false, nCol, + aRepeatRect.Left() + nPixelStart, + aRepeatRect.Left() + nPixelEnd ); + + nPosX = nNextX; + ++nColPos; + } + } + if ( bHasMainCols ) + { + tools::Long nPosX = 0; + for ( nCol=nMainColStart; nCol<=nMainColEnd; nCol++ ) + if (!pDoc->ColHidden(nCol, nTab)) + { + sal_uInt16 nDocW = pDoc->GetColWidth( nCol, nTab ); + tools::Long nNextX + = nPosX + o3tl::convert(nDocW, o3tl::Length::twip, o3tl::Length::mm100); + + tools::Long nPixelStart = pWindow->LogicToPixel( Size( nPosX, 0 ), aCellMapMode ).Width(); + tools::Long nPixelEnd = pWindow->LogicToPixel( Size( nNextX, 0 ), aCellMapMode ).Width() - 1; + pColInfo[nColPos].Set( false, nCol, + aMainRect.Left() + nPixelStart, + aMainRect.Left() + nPixelEnd ); + + nPosX = nNextX; + ++nColPos; + } + } + rInfo.SetColInfo( nColCount, pColInfo ); + } + else + rInfo.SetColInfo( 0, nullptr ); + + // get row info + + SCROW nRowCount = 0; + if ( bHasHeaderRow ) + ++nRowCount; + if ( bHasRepRows ) + nRowCount += pDoc->CountVisibleRows(nRepeatRowStart, nRepeatRowEnd, nTab); + if ( bHasMainRows ) + nRowCount += pDoc->CountVisibleRows(nMainRowStart, nMainRowEnd, nTab); + + if ( nRowCount > 0 ) + { + ScPreviewColRowInfo* pRowInfo = new ScPreviewColRowInfo[ nRowCount ]; + SCROW nRowPos = 0; + + if ( bHasHeaderRow ) + { + pRowInfo[nRowPos].Set( true, 0, aHeaderRect.Top(), aHeaderRect.Bottom() ); + ++nRowPos; + } + if ( bHasRepRows ) + { + tools::Long nPosY = 0; + for (SCROW nRow = nRepeatRowStart; nRow <= nRepeatRowEnd; ++nRow) + { + if (pDoc->RowHidden(nRow, nTab)) + continue; + + sal_uInt16 nDocH = pDoc->GetOriginalHeight( nRow, nTab ); + tools::Long nNextY + = nPosY + o3tl::convert(nDocH, o3tl::Length::twip, o3tl::Length::mm100); + + tools::Long nPixelStart = pWindow->LogicToPixel( Size( 0, nPosY ), aCellMapMode ).Height(); + tools::Long nPixelEnd = pWindow->LogicToPixel( Size( 0, nNextY ), aCellMapMode ).Height() - 1; + pRowInfo[nRowPos].Set( false, nRow, + aRepeatRect.Top() + nPixelStart, + aRepeatRect.Top() + nPixelEnd ); + + nPosY = nNextY; + ++nRowPos; + } + } + if ( bHasMainRows ) + { + tools::Long nPosY = 0; + for (SCROW nRow = nMainRowStart; nRow <= nMainRowEnd; ++nRow) + { + if (pDoc->RowHidden(nRow, nTab)) + continue; + + sal_uInt16 nDocH = pDoc->GetOriginalHeight( nRow, nTab ); + tools::Long nNextY + = nPosY + o3tl::convert(nDocH, o3tl::Length::twip, o3tl::Length::mm100); + + tools::Long nPixelStart = pWindow->LogicToPixel( Size( 0, nPosY ), aCellMapMode ).Height(); + tools::Long nPixelEnd = pWindow->LogicToPixel( Size( 0, nNextY ), aCellMapMode ).Height() - 1; + pRowInfo[nRowPos].Set( false, nRow, + aMainRect.Top() + nPixelStart, + aMainRect.Top() + nPixelEnd ); + + nPosY = nNextY; + ++nRowPos; + } + } + rInfo.SetRowInfo( nRowCount, pRowInfo ); + } + else + rInfo.SetRowInfo( 0, nullptr ); + + // limit to visible area + + rInfo.SetTab( nTab ); + rInfo.LimitToArea( rVisiblePixel ); +} + +tools::Rectangle ScPreviewLocationData::GetHeaderCellOutputRect(const tools::Rectangle& rVisRect, const ScAddress& rCellPos, bool bColHeader) const +{ + // first a stupid implementation + // NN says here should be done more + tools::Rectangle aClipRect; + ScPreviewTableInfo aTableInfo; + GetTableInfo( rVisRect, aTableInfo ); + + if ( (rCellPos.Col() >= 0) && + (rCellPos.Row() >= 0) && (rCellPos.Col() < aTableInfo.GetCols()) && + (rCellPos.Row() < aTableInfo.GetRows()) ) + { + SCCOL nCol(0); + SCROW nRow(0); + if (bColHeader) + nCol = rCellPos.Col(); + else + nRow = rCellPos.Row(); + const ScPreviewColRowInfo& rColInfo = aTableInfo.GetColInfo()[nCol]; + const ScPreviewColRowInfo& rRowInfo = aTableInfo.GetRowInfo()[nRow]; + + if ( rColInfo.bIsHeader || rRowInfo.bIsHeader ) + aClipRect = tools::Rectangle( rColInfo.nPixelStart, rRowInfo.nPixelStart, rColInfo.nPixelEnd, rRowInfo.nPixelEnd ); + } + return aClipRect; +} + +tools::Rectangle ScPreviewLocationData::GetCellOutputRect(const ScAddress& rCellPos) const +{ + // first a stupid implementation + // NN says here should be done more + tools::Rectangle aRect; + GetCellPosition(rCellPos, aRect); + return aRect; +} + +// GetMainCellRange is used for links in PDF export + +bool ScPreviewLocationData::GetMainCellRange( ScRange& rRange, tools::Rectangle& rPixRect ) const +{ + for (auto const& it : m_Entries) + { + if ( it->eType == SC_PLOC_CELLRANGE && !it->bRepeatCol && !it->bRepeatRow ) + { + rRange = it->aCellRange; + rPixRect = it->aPixelRect; + return true; + } + } + + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/prevwsh.cxx b/sc/source/ui/view/prevwsh.cxx new file mode 100644 index 0000000000..c526331d58 --- /dev/null +++ b/sc/source/ui/view/prevwsh.cxx @@ -0,0 +1,1181 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +// for mouse wheel +#define MINZOOM_SLIDER 10 +#define MAXZOOM_SLIDER 400 + +#define SC_USERDATA_SEP ';' + +using namespace com::sun::star; + +#define ShellClass_ScPreviewShell +#include + +#include + + +SFX_IMPL_INTERFACE(ScPreviewShell, SfxViewShell) + +void ScPreviewShell::InitInterface_Impl() +{ + GetStaticInterface()->RegisterObjectBar(SFX_OBJECTBAR_OBJECT, + SfxVisibilityFlags::Standard|SfxVisibilityFlags::Server|SfxVisibilityFlags::ReadonlyDoc, + ToolbarId::Objectbar_Preview); + + GetStaticInterface()->RegisterPopupMenu("preview"); +} + +SFX_IMPL_NAMED_VIEWFACTORY( ScPreviewShell, "PrintPreview" ) +{ + SFX_VIEW_REGISTRATION(ScDocShell); +} + +void ScPreviewShell::Construct( vcl::Window* pParent ) +{ + // Find the top-most window, and set the close window handler to intercept + // the window close event. + vcl::Window* pWin = pParent; + while (!pWin->IsSystemWindow()) + { + if (pWin->GetParent()) + pWin = pWin->GetParent(); + else + break; + } + + mpFrameWindow = dynamic_cast(pWin); + if (mpFrameWindow) + mpFrameWindow->SetCloseHdl(LINK(this, ScPreviewShell, CloseHdl)); + + eZoom = SvxZoomType::WHOLEPAGE; + + pHorScroll = VclPtr::Create(pParent, true); + pVerScroll = VclPtr::Create(pParent, false); + + // RTL: no mirroring for horizontal scrollbars + pHorScroll->EnableRTL( false ); + + pHorScroll->SetScrollHdl(LINK(this, ScPreviewShell, HorzScrollHandler)); + pVerScroll->SetScrollHdl(LINK(this, ScPreviewShell, VertScrollHandler)); + + pPreview = VclPtr::Create( pParent, pDocShell, this ); + + SetPool( &SC_MOD()->GetPool() ); + SetWindow( pPreview ); + StartListening(*pDocShell, DuplicateHandling::Prevent); + StartListening(*SfxGetpApp(), DuplicateHandling::Prevent); // #i62045# #i62046# application is needed for Calc's own hints + SfxBroadcaster* pDrawBC = pDocShell->GetDocument().GetDrawBroadcaster(); + if (pDrawBC) + StartListening(*pDrawBC); + + pHorScroll->Show( false ); + pVerScroll->Show( false ); + SetName("Preview"); +} + +ScPreviewShell::ScPreviewShell(SfxViewFrame& rViewFrame, + SfxViewShell* pOldSh) : + SfxViewShell(rViewFrame, SfxViewShellFlags::HAS_PRINTOPTIONS), + pDocShell( static_cast(rViewFrame.GetObjectShell()) ), + mpFrameWindow(nullptr), + nSourceDesignMode( TRISTATE_INDET ), + nMaxVertPos(0), + nPrevHThumbPos(0), + nPrevVThumbPos(0) +{ + Construct(&rViewFrame.GetWindow()); + SfxShell::SetContextName(vcl::EnumContext::GetContextName(vcl::EnumContext::Context::Printpreview)); + + if ( auto pTabViewShell = dynamic_cast( pOldSh) ) + { + // store view settings, show table from TabView + //! store live ScViewData instead, and update on ScTablesHint? + //! or completely forget aSourceData on ScTablesHint? + + const ScViewData& rData = pTabViewShell->GetViewData(); + pPreview->SetSelectedTabs(rData.GetMarkData()); + InitStartTable( rData.GetTabNo() ); + + // also have to store the TabView's DesignMode state + // (only if draw view exists) + SdrView* pDrawView = pTabViewShell->GetScDrawView(); + if ( pDrawView ) + nSourceDesignMode + = pDrawView->IsDesignMode() ? TRISTATE_TRUE : TRISTATE_FALSE; + } + + new ScPreviewObj(this); +} + +ScPreviewShell::~ScPreviewShell() +{ + if (mpFrameWindow) + mpFrameWindow->SetCloseHdl(Link()); // Remove close handler. + + // #108333#; notify Accessibility that Shell is dying and before destroy all + BroadcastAccessibility( SfxHint( SfxHintId::Dying ) ); + pAccessibilityBroadcaster.reset(); + + SfxBroadcaster* pDrawBC = pDocShell->GetDocument().GetDrawBroadcaster(); + if (pDrawBC) + EndListening(*pDrawBC); + EndListening(*SfxGetpApp()); + EndListening(*pDocShell); + + SetWindow(nullptr); + pPreview.disposeAndClear(); + pHorScroll.disposeAndClear(); + pVerScroll.disposeAndClear(); + + // normal mode of operation is switching back to default view in the same frame, + // so there's no need to activate any other window here anymore +} + +void ScPreviewShell::InitStartTable(SCTAB nTab) +{ + pPreview->SetPageNo( pPreview->GetFirstPage(nTab) ); +} + +void ScPreviewShell::AdjustPosSizePixel( const Point &rPos, const Size &rSize ) +{ + Size aOutSize( rSize ); + pPreview->SetPosSizePixel( rPos, aOutSize ); + + if ( SvxZoomType::WHOLEPAGE == eZoom ) + pPreview->SetZoom( pPreview->GetOptimalZoom(false) ); + else if ( SvxZoomType::PAGEWIDTH == eZoom ) + pPreview->SetZoom( pPreview->GetOptimalZoom(true) ); + + UpdateNeededScrollBars(false); +} + +void ScPreviewShell::InnerResizePixel( const Point &rOfs, const Size &rSize, bool ) +{ + AdjustPosSizePixel( rOfs,rSize ); +} + +void ScPreviewShell::OuterResizePixel( const Point &rOfs, const Size &rSize ) +{ + AdjustPosSizePixel( rOfs,rSize ); +} + +bool ScPreviewShell::GetPageSize( Size& aPageSize ) +{ + ScDocument& rDoc = pDocShell->GetDocument(); + SCTAB nTab = pPreview->GetTab(); + + ScStyleSheetPool* pStylePool = rDoc.GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet = pStylePool->Find( rDoc.GetPageStyle( nTab ), + SfxStyleFamily::Page ); + OSL_ENSURE(pStyleSheet,"No style sheet"); + if (!pStyleSheet) return false; + const SfxItemSet* pParamSet = &pStyleSheet->GetItemSet(); + + aPageSize = pParamSet->Get(ATTR_PAGE_SIZE).GetSize(); + aPageSize.setWidth(o3tl::convert(aPageSize.Width(), o3tl::Length::twip, o3tl::Length::mm100)); + aPageSize.setHeight(o3tl::convert(aPageSize.Height(), o3tl::Length::twip, o3tl::Length::mm100)); + return true; +} + +void ScPreviewShell::UpdateNeededScrollBars( bool bFromZoom ) +{ + Size aPageSize; + OutputDevice* pDevice = Application::GetDefaultDevice(); + + tools::Long nBarW = GetViewFrame().GetWindow().GetSettings().GetStyleSettings().GetScrollBarSize(); + tools::Long nBarH = nBarW; + + tools::Long aHeightOffSet = pDevice ? pDevice->PixelToLogic( Size( nBarW, nBarH ), pPreview->GetMapMode() ).Height() : 0; + tools::Long aWidthOffSet = aHeightOffSet; + + if (!GetPageSize( aPageSize )) + return; + + // for centering, page size without the shadow is used + bool bVert = pVerScroll->IsVisible(); + bool bHori = pHorScroll->IsVisible(); + Size aWindowSize = pPreview->GetOutDev()->GetOutputSize(); + Point aPos = pPreview->GetPosPixel(); + Size aWindowPixelSize = pPreview->GetOutputSizePixel(); + + // if we are called from Zoom then we need to compensate for whatever + // scrollbars were displayed before the zoom was called + if ( bFromZoom ) + { + if ( bVert ) + { + aWindowPixelSize.AdjustWidth(nBarH ); + aWindowSize.AdjustWidth(aHeightOffSet ); + } + if ( bHori ) + { + aWindowPixelSize.AdjustHeight(nBarW ); + aWindowSize.AdjustHeight(aWidthOffSet ); + } + } + + // recalculate any needed scrollbars + tools::Long nMaxWidthPos = aPageSize.Width() - aWindowSize.Width(); + bHori = nMaxWidthPos >= 0; + tools::Long nMaxHeightPos = aPageSize.Height() - aWindowSize.Height(); + bVert = nMaxHeightPos >= 0; + + // see if having a scroll bar requires the other + if ( bVert != bHori && ( bVert || bHori ) ) + { + if ( bVert && ( (nMaxWidthPos + aWidthOffSet ) > 0 ) ) + bHori = true; + else if ( (nMaxHeightPos + aHeightOffSet ) > 0 ) + bVert = true; + } + pHorScroll->Show( bHori ); + pVerScroll->Show( bVert ); + + // make room for needed scrollbars ( and reduce the size + // of the preview appropriately ) + if ( bHori ) + aWindowPixelSize.AdjustHeight( -nBarW ); + if ( bVert ) + aWindowPixelSize.AdjustWidth( -nBarH ); + + pPreview->SetSizePixel( aWindowPixelSize ); + pHorScroll->SetPosSizePixel( Point( aPos.X(), aPos.Y() + aWindowPixelSize.Height() ), + Size( aWindowPixelSize.Width(), nBarH ) ); + pVerScroll->SetPosSizePixel( Point( aPos.X() + aWindowPixelSize.Width(), aPos.Y() ), + Size( nBarW, aWindowPixelSize.Height() ) ); + UpdateScrollBars(); +} + +void ScPreviewShell::UpdateScrollBars() +{ + Size aPageSize; + if ( !GetPageSize( aPageSize ) ) + return; + + // for centering, page size without the shadow is used + + Size aWindowSize = pPreview->GetOutDev()->GetOutputSize(); + + Point aOfs = pPreview->GetOffset(); + + if( pHorScroll ) + { + pHorScroll->SetRange( Range( 0, aPageSize.Width() ) ); + pHorScroll->SetLineSize( aWindowSize.Width() / 16 ); + pHorScroll->SetPageSize( aWindowSize.Width() ); + pHorScroll->SetVisibleSize( aWindowSize.Width() ); + tools::Long nMaxPos = aPageSize.Width() - aWindowSize.Width(); + if ( nMaxPos<0 ) + { + // page smaller than window -> center (but put scrollbar to 0) + aOfs.setX( 0 ); + pPreview->SetXOffset( nMaxPos / 2 ); + } + else if (aOfs.X() < 0) + { + // page larger than window -> never use negative offset + aOfs.setX( 0 ); + pPreview->SetXOffset( 0 ); + } + else if (aOfs.X() > nMaxPos) + { + // limit offset to align with right edge of window + aOfs.setX( nMaxPos ); + pPreview->SetXOffset(nMaxPos); + } + pHorScroll->SetThumbPos( aOfs.X() ); + nPrevHThumbPos = pHorScroll->GetThumbPos(); + } + + if( !pVerScroll ) + return; + + tools::Long nPageNo = pPreview->GetPageNo(); + tools::Long nTotalPages = pPreview->GetTotalPages(); + + nMaxVertPos = aPageSize.Height() - aWindowSize.Height(); + pVerScroll->SetLineSize( aWindowSize.Height() / 16 ); + pVerScroll->SetPageSize( aWindowSize.Height() ); + pVerScroll->SetVisibleSize( aWindowSize.Height() ); + if ( nMaxVertPos < 0 ) + { + // page smaller than window -> center (but put scrollbar to 0) + aOfs.setY( 0 ); + pPreview->SetYOffset( nMaxVertPos / 2 ); + pVerScroll->SetThumbPos( nPageNo * aWindowSize.Height() ); + pVerScroll->SetRange( Range( 0, aWindowSize.Height() * nTotalPages )); + } + else if (aOfs.Y() < 0) + { + // page larger than window -> never use negative offset + pVerScroll->SetRange( Range( 0, aPageSize.Height() ) ); + aOfs.setY( 0 ); + pPreview->SetYOffset( 0 ); + pVerScroll->SetThumbPos( aOfs.Y() ); + } + else if (aOfs.Y() > nMaxVertPos ) + { + // limit offset to align with window bottom + pVerScroll->SetRange( Range( 0, aPageSize.Height() ) ); + aOfs.setY( nMaxVertPos ); + pPreview->SetYOffset( nMaxVertPos ); + pVerScroll->SetThumbPos( aOfs.Y() ); + } + nPrevVThumbPos = pVerScroll->GetThumbPos(); +} + +IMPL_LINK_NOARG(ScPreviewShell, HorzScrollHandler, weld::Scrollbar&, void) +{ + ScrollHandler(pHorScroll); +} + +IMPL_LINK_NOARG(ScPreviewShell, VertScrollHandler, weld::Scrollbar&, void) +{ + ScrollHandler(pVerScroll); +} + +void ScPreviewShell::ScrollHandler(ScrollAdaptor* pScroll) +{ + tools::Long nPos = pScroll->GetThumbPos(); + tools::Long nMaxRange = pScroll->GetRangeMax(); + tools::Long nTotalPages = pPreview->GetTotalPages(); + tools::Long nPageNo = 0; + tools::Long nPerPageLength = 0; + bool bIsDivide = true; + + if( nTotalPages ) + nPerPageLength = nMaxRange / nTotalPages; + + if( nPerPageLength ) + { + nPageNo = nPos / nPerPageLength; + if( nPos % nPerPageLength ) + { + bIsDivide = false; + nPageNo ++; + } + } + + bool bHoriz = ( pScroll == pHorScroll ); + + tools::Long nDelta = bHoriz ? (pHorScroll->GetThumbPos() - nPrevHThumbPos) + : (pVerScroll->GetThumbPos() - nPrevVThumbPos); + + if( bHoriz ) + pPreview->SetXOffset( nPos ); + else + { + if( nMaxVertPos > 0 ) + pPreview->SetYOffset( nPos ); + else + { + Point aMousePos = pScroll->OutputToNormalizedScreenPixel( pScroll->GetPointerPosPixel() ); + Point aPos = pScroll->GetParent()->OutputToNormalizedScreenPixel( pScroll->GetPosPixel() ); + OUString aHelpStr; + tools::Rectangle aRect; + QuickHelpFlags nAlign; + + if( nDelta < 0 ) + { + if ( nTotalPages && nPageNo > 0 && !bIsDivide ) + pPreview->SetPageNo( nPageNo-1 ); + if( bIsDivide ) + pPreview->SetPageNo( nPageNo ); + + aHelpStr = ScResId( STR_PAGE ) + + " " + OUString::number( nPageNo ) + + " / " + OUString::number( nTotalPages ); + } + else if( nDelta > 0 ) + { + bool bAllTested = pPreview->AllTested(); + if ( nTotalPages && ( nPageNo < nTotalPages || !bAllTested ) ) + pPreview->SetPageNo( nPageNo ); + + aHelpStr = ScResId( STR_PAGE ) + + " " + OUString::number( nPageNo+1 ) + + " / " + OUString::number( nTotalPages ); + } + + aRect.SetLeft( aPos.X() - 8 ); + aRect.SetTop( aMousePos.Y() ); + aRect.SetRight( aRect.Left() ); + aRect.SetBottom( aRect.Top() ); + nAlign = QuickHelpFlags::Bottom|QuickHelpFlags::Center; + Help::ShowQuickHelp( pScroll->GetParent(), aRect, aHelpStr, nAlign ); + } + } +} + +IMPL_LINK_NOARG(ScPreviewShell, CloseHdl, SystemWindow&, void) +{ + ExitPreview(); +} + +bool ScPreviewShell::ScrollCommand( const CommandEvent& rCEvt ) +{ + bool bDone = false; + const CommandWheelData* pData = rCEvt.GetWheelData(); + if ( pData && pData->GetMode() == CommandWheelMode::ZOOM ) + { + sal_uInt16 nOld = pPreview->GetZoom(); + sal_uInt16 nNew; + if ( pData->GetDelta() < 0 ) + nNew = std::max( MINZOOM, basegfx::zoomtools::zoomOut( nOld )); + else + nNew = std::min( MAXZOOM, basegfx::zoomtools::zoomIn( nOld )); + + if ( nNew != nOld ) + { + eZoom = SvxZoomType::PERCENT; + pPreview->SetZoom( nNew ); + } + + bDone = true; + } + else + { + bDone = pPreview->HandleScrollCommand( rCEvt, pHorScroll, pVerScroll ); + } + + return bDone; +} + +SfxPrinter* ScPreviewShell::GetPrinter( bool bCreate ) +{ + return pDocShell->GetPrinter(bCreate); +} + +sal_uInt16 ScPreviewShell::SetPrinter( SfxPrinter *pNewPrinter, SfxPrinterChangeFlags nDiffFlags ) +{ + return pDocShell->SetPrinter( pNewPrinter, nDiffFlags ); +} + +bool ScPreviewShell::HasPrintOptionsPage() const +{ + return true; +} + +std::unique_ptr ScPreviewShell::CreatePrintOptionsPage(weld::Container* pPage, weld::DialogController* pController, const SfxItemSet &rOptions) +{ + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + ::CreateTabPage ScTpPrintOptionsCreate = pFact->GetTabPageCreatorFunc(RID_SC_TP_PRINT); + if ( ScTpPrintOptionsCreate ) + return ScTpPrintOptionsCreate(pPage, pController, &rOptions); + return nullptr; +} + +void ScPreviewShell::Activate(bool bMDI) +{ + SfxViewShell::Activate(bMDI); + + //! Basic etc. -> outsource to its own file (see tabvwsh4) + + if (bMDI) + { + // InputHdl is now mostly Null, no more assertion! + ScInputHandler* pInputHdl = SC_MOD()->GetInputHdl(); + if ( pInputHdl ) + pInputHdl->NotifyChange( nullptr ); + } + + SfxShell::Activate(bMDI); +} + +void ScPreviewShell::Execute( SfxRequest& rReq ) +{ + sal_uInt16 nSlot = rReq.GetSlot(); + const SfxItemSet* pReqArgs = rReq.GetArgs(); + + switch ( nSlot ) + { + case SID_FORMATPAGE: + case SID_STATUS_PAGESTYLE: + case SID_HFEDIT: + pDocShell->ExecutePageStyle( *this, rReq, pPreview->GetTab() ); + break; + case SID_REPAINT: + pPreview->Invalidate(); + rReq.Done(); + break; + case SID_PREV_TABLE: // Accelerator + case SID_PREVIEW_PREVIOUS: + { + tools::Long nPage = pPreview->GetPageNo(); + tools::Long nTotal = pPreview->GetTotalPages(); + if (nTotal && nPage > 0) + pPreview->SetPageNo( nPage-1 ); + } + break; + case SID_NEXT_TABLE: // Accelerator + case SID_PREVIEW_NEXT: + { + bool bAllTested = pPreview->AllTested(); + tools::Long nPage = pPreview->GetPageNo(); + tools::Long nTotal = pPreview->GetTotalPages(); + if (nTotal && (nPage+1 < nTotal || !bAllTested)) + pPreview->SetPageNo( nPage+1 ); + } + break; + case SID_CURSORTOPOFFILE: // Accelerator + case SID_PREVIEW_FIRST: + { + tools::Long nPage = pPreview->GetPageNo(); + tools::Long nTotal = pPreview->GetTotalPages(); + if (nTotal && nPage != 0) + pPreview->SetPageNo( 0 ); + } + break; + case SID_CURSORENDOFFILE: // Accelerator + case SID_PREVIEW_LAST: + { + if (!pPreview->AllTested()) + pPreview->CalcAll(); + + tools::Long nPage = pPreview->GetPageNo(); + tools::Long nTotal = pPreview->GetTotalPages(); + if (nTotal && nPage+1 != nTotal) + pPreview->SetPageNo( nTotal-1 ); + } + break; + case SID_ATTR_ZOOM: + case FID_SCALE: + { + sal_uInt16 nZoom = 100; + bool bCancel = false; + + eZoom = SvxZoomType::PERCENT; + + if ( pReqArgs ) + { + + const SvxZoomItem& rZoomItem = pReqArgs->Get(SID_ATTR_ZOOM); + + eZoom = rZoomItem.GetType(); + nZoom = rZoomItem.GetValue(); + } + else + { + SfxItemSetFixed aSet( GetPool() ); + SvxZoomItem aZoomItem( SvxZoomType::PERCENT, pPreview->GetZoom(), SID_ATTR_ZOOM ); + + aSet.Put( aZoomItem ); + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + ScopedVclPtr pDlg(pFact->CreateSvxZoomDialog(nullptr, aSet)); + pDlg->SetLimits( 20, 400 ); + pDlg->HideButton( ZoomButtonId::OPTIMAL ); + bCancel = ( RET_CANCEL == pDlg->Execute() ); + + if ( !bCancel ) + { + const SvxZoomItem& rZoomItem = pDlg->GetOutputItemSet()-> + Get( SID_ATTR_ZOOM ); + + eZoom = rZoomItem.GetType(); + nZoom = rZoomItem.GetValue(); + } + } + + if ( !bCancel ) + { + switch ( eZoom ) + { + case SvxZoomType::OPTIMAL: + case SvxZoomType::WHOLEPAGE: + nZoom = pPreview->GetOptimalZoom(false); + break; + case SvxZoomType::PAGEWIDTH: + nZoom = pPreview->GetOptimalZoom(true); + break; + default: + { + // added to avoid warnings + } + } + + pPreview->SetZoom( nZoom ); + rReq.Done(); + } + } + break; + case SID_ZOOM_IN: + { + sal_uInt16 nNew = pPreview->GetZoom() + 20 ; + nNew -= nNew % 20; + pPreview->SetZoom( nNew ); + eZoom = SvxZoomType::PERCENT; + rReq.Done(); + } + break; + case SID_ZOOM_OUT: + { + sal_uInt16 nNew = pPreview->GetZoom() - 1; + nNew -= nNew % 20; + pPreview->SetZoom( nNew ); + eZoom = SvxZoomType::PERCENT; + rReq.Done(); + } + break; + case SID_PREVIEW_MARGIN: + { + bool bMargin = pPreview->GetPageMargins(); + pPreview->SetPageMargins( !bMargin ); + pPreview->Invalidate(); + rReq.Done(); + } + break; + case SID_ATTR_ZOOMSLIDER: + { + const SvxZoomSliderItem* pItem; + eZoom = SvxZoomType::PERCENT; + if( pReqArgs && (pItem = pReqArgs->GetItemIfSet( SID_ATTR_ZOOMSLIDER )) ) + { + const sal_uInt16 nCurrentZoom = pItem->GetValue(); + if( nCurrentZoom ) + { + pPreview->SetZoom( nCurrentZoom ); + rReq.Done(); + } + } + } + break; + case SID_PREVIEW_SCALINGFACTOR: + { + const SvxZoomSliderItem* pItem; + SCTAB nTab = pPreview->GetTab(); + OUString aOldName = pDocShell->GetDocument().GetPageStyle( pPreview->GetTab() ); + ScStyleSheetPool* pStylePool = pDocShell->GetDocument().GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet = pStylePool->Find( aOldName, SfxStyleFamily::Page ); + OSL_ENSURE( pStyleSheet, "PageStyle not found! :-/" ); + + if ( pReqArgs && pStyleSheet && (pItem = pReqArgs->GetItemIfSet( SID_PREVIEW_SCALINGFACTOR )) ) + { + const sal_uInt16 nCurrentZoom = pItem->GetValue(); + SfxItemSet& rSet = pStyleSheet->GetItemSet(); + rSet.Put( SfxUInt16Item( ATTR_PAGE_SCALE, nCurrentZoom ) ); + ScPrintFunc aPrintFunc( pDocShell, pDocShell->GetPrinter(), nTab ); + aPrintFunc.UpdatePages(); + rReq.Done(); + } + GetViewFrame().GetBindings().Invalidate( nSlot ); + } + break; + case SID_PRINTPREVIEW: + case SID_PREVIEW_CLOSE: + // print preview is now always in the same frame as the tab view + // -> always switch this frame back to normal view + // (ScTabViewShell ctor reads stored view data) + + ExitPreview(); + break; + case SID_CURSORPAGEUP: + case SID_CURSORPAGEDOWN: + case SID_CURSORHOME: + case SID_CURSOREND: + case SID_CURSORUP: + case SID_CURSORDOWN: + case SID_CURSORLEFT: + case SID_CURSORRIGHT: + DoScroll( nSlot ); + break; + case SID_CANCEL: + if( ScViewUtil::IsFullScreen( *this ) ) + ScViewUtil::SetFullScreen( *this, false ); + break; + + default: + break; + } +} + +void ScPreviewShell::GetState( SfxItemSet& rSet ) +{ + pPreview->SetInGetState(true); + + SCTAB nTab = pPreview->GetTab(); + tools::Long nPage = pPreview->GetPageNo(); + tools::Long nTotal = pPreview->GetTotalPages(); + sal_uInt16 nZoom = pPreview->GetZoom(); + bool bAllTested = pPreview->AllTested(); + + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + while ( nWhich ) + { + switch (nWhich) + { + case SID_STATUS_PAGESTYLE: + case SID_HFEDIT: + pDocShell->GetStatePageStyle( rSet, nTab ); + break; + case SID_UNDO: + case SID_REDO: + case SID_REPEAT: + case SID_SAVEDOC: + case SID_SAVEASDOC: + case SID_MAIL_SENDDOC: + case SID_VIEW_DATA_SOURCE_BROWSER: + case SID_QUITAPP: + rSet.DisableItem(nWhich); + break; + case SID_PREVIEW_PREVIOUS: + case SID_PREVIEW_FIRST: + if (!nTotal || nPage==0) + rSet.DisableItem(nWhich); + break; + case SID_PREVIEW_NEXT: + case SID_PREVIEW_LAST: + if (bAllTested) + if (!nTotal || nPage==nTotal-1) + rSet.DisableItem(nWhich); + break; + case SID_ZOOM_IN: + if (nZoom >= 400) + rSet.DisableItem(nWhich); + break; + case SID_ZOOM_OUT: + if (nZoom <= 20) + rSet.DisableItem(nWhich); + break; + case SID_ATTR_ZOOM: + { + SvxZoomItem aZoom( eZoom, nZoom, TypedWhichId(nWhich) ); + aZoom.SetValueSet( SvxZoomEnableFlags::ALL & ~SvxZoomEnableFlags::OPTIMAL ); + rSet.Put( aZoom ); + } + break; + case SID_ATTR_ZOOMSLIDER: + { + SvxZoomSliderItem aZoomSliderItem( nZoom, MINZOOM, MAXZOOM, SID_ATTR_ZOOMSLIDER ); + aZoomSliderItem.AddSnappingPoint( 100 ); + rSet.Put( aZoomSliderItem ); + } + break; + case SID_PREVIEW_SCALINGFACTOR: + { + if( pDocShell->IsReadOnly() ) + rSet.DisableItem( nWhich ); + else + { + OUString aOldName = pDocShell->GetDocument().GetPageStyle( pPreview->GetTab() ); + ScStyleSheetPool* pStylePool = pDocShell->GetDocument().GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet = pStylePool->Find( aOldName, SfxStyleFamily::Page ); + OSL_ENSURE( pStyleSheet, "PageStyle not found! :-/" ); + + if ( pStyleSheet ) + { + SfxItemSet& rStyleSet = pStyleSheet->GetItemSet(); + sal_uInt16 nCurrentZoom = rStyleSet.Get(ATTR_PAGE_SCALE).GetValue(); + if( nCurrentZoom ) + { + SvxZoomSliderItem aZoomSliderItem( nCurrentZoom, MINZOOM_SLIDER, MAXZOOM_SLIDER, SID_PREVIEW_SCALINGFACTOR ); + aZoomSliderItem.AddSnappingPoint( 100 ); + rSet.Put( aZoomSliderItem ); + } + else + rSet.DisableItem( nWhich ); + } + } + } + break; + case SID_STATUS_DOCPOS: + rSet.Put( SfxStringItem( nWhich, pPreview->GetPosString() ) ); + break; + case SID_PRINTPREVIEW: + rSet.Put( SfxBoolItem( nWhich, true ) ); + break; + case SID_FORMATPAGE: + case SID_PREVIEW_MARGIN: + if( pDocShell->IsReadOnly() ) + rSet.DisableItem( nWhich ); + break; + } + + nWhich = aIter.NextWhich(); + } + + pPreview->SetInGetState(false); +} + +void ScPreviewShell::FillFieldData( ScHeaderFieldData& rData ) +{ + ScDocument& rDoc = pDocShell->GetDocument(); + SCTAB nTab = pPreview->GetTab(); + OUString aTmp; + rDoc.GetName(nTab, aTmp); + rData.aTabName = aTmp; + + if( pDocShell->getDocProperties()->getTitle().getLength() != 0 ) + rData.aTitle = pDocShell->getDocProperties()->getTitle(); + else + rData.aTitle = pDocShell->GetTitle(); + + const INetURLObject& rURLObj = pDocShell->GetMedium()->GetURLObject(); + rData.aLongDocName = rURLObj.GetMainURL( INetURLObject::DecodeMechanism::Unambiguous ); + if ( !rData.aLongDocName.isEmpty() ) + rData.aShortDocName = rURLObj.GetLastName(INetURLObject::DecodeMechanism::Unambiguous); + else + rData.aShortDocName = rData.aLongDocName = rData.aTitle; + rData.nPageNo = pPreview->GetPageNo() + 1; + + bool bAllTested = pPreview->AllTested(); + if (bAllTested) + rData.nTotalPages = pPreview->GetTotalPages(); + else + rData.nTotalPages = 99; + + // the dialog knows eNumType +} + +void ScPreviewShell::WriteUserData(OUString& rData, bool /* bBrowse */) +{ + // nZoom + // nPageNo + + rData = OUString::number(pPreview->GetZoom()) + + OUStringChar(SC_USERDATA_SEP) + + OUString::number(pPreview->GetPageNo()); +} + +void ScPreviewShell::ReadUserData(const OUString& rData, bool /* bBrowse */) +{ + if (!rData.isEmpty()) + { + sal_Int32 nIndex = 0; + pPreview->SetZoom(static_cast(o3tl::toInt32(o3tl::getToken(rData, 0, SC_USERDATA_SEP, nIndex)))); + pPreview->SetPageNo(o3tl::toInt32(o3tl::getToken(rData, 0, SC_USERDATA_SEP, nIndex))); + eZoom = SvxZoomType::PERCENT; + } +} + +void ScPreviewShell::WriteUserDataSequence(uno::Sequence < beans::PropertyValue >& rSeq) +{ + // tdf#130559: don't export preview view data if active + if (comphelper::IsContextFlagActive("NoPreviewData")) + return; + + rSeq.realloc(3); + beans::PropertyValue* pSeq = rSeq.getArray(); + sal_uInt16 nViewID(GetViewFrame().GetCurViewId()); + pSeq[0].Name = SC_VIEWID; + pSeq[0].Value <<= SC_VIEW + OUString::number(nViewID); + pSeq[1].Name = SC_ZOOMVALUE; + pSeq[1].Value <<= sal_Int32 (pPreview->GetZoom()); + pSeq[2].Name = "PageNumber"; + pSeq[2].Value <<= pPreview->GetPageNo(); + + // Common SdrModel processing + if (ScDrawLayer* pDrawLayer = GetDocument().GetDrawLayer()) + pDrawLayer->WriteUserDataSequence(rSeq); +} + +void ScPreviewShell::ReadUserDataSequence(const uno::Sequence < beans::PropertyValue >& rSeq) +{ + for (const auto& propval : rSeq) + { + if (propval.Name == SC_ZOOMVALUE) + { + sal_Int32 nTemp = 0; + if (propval.Value >>= nTemp) + pPreview->SetZoom(sal_uInt16(nTemp)); + } + else if (propval.Name == "PageNumber") + { + sal_Int32 nTemp = 0; + if (propval.Value >>= nTemp) + pPreview->SetPageNo(nTemp); + } + // Fallback to common SdrModel processing + else + pDocShell->MakeDrawLayer()->ReadUserDataSequenceValue(&propval); + } +} + +void ScPreviewShell::DoScroll( sal_uInt16 nMode ) +{ + Point aCurPos, aPrevPos; + + tools::Long nHRange = pHorScroll->GetRange().Max(); + tools::Long nHLine = pHorScroll->GetLineSize(); + tools::Long nHPage = pHorScroll->GetPageSize(); + tools::Long nVRange = pVerScroll->GetRange().Max(); + tools::Long nVLine = pVerScroll->GetLineSize(); + tools::Long nVPage = pVerScroll->GetPageSize(); + + aCurPos.setX( pHorScroll->GetThumbPos() ); + aCurPos.setY( pVerScroll->GetThumbPos() ); + aPrevPos = aCurPos; + + tools::Long nThumbPos = pVerScroll->GetThumbPos(); + tools::Long nRangeMax = pVerScroll->GetRangeMax(); + + switch( nMode ) + { + case SID_CURSORUP: + if( nMaxVertPos<0 ) + { + tools::Long nPage = pPreview->GetPageNo(); + + if( nPage>0 ) + { + SfxViewFrame& rSfxViewFrame = GetViewFrame(); + SfxRequest aSfxRequest(rSfxViewFrame, SID_PREVIEW_PREVIOUS); + Execute( aSfxRequest ); + } + } + else + aCurPos.AdjustY( -nVLine ); + break; + case SID_CURSORDOWN: + if( nMaxVertPos<0 ) + { + tools::Long nPage = pPreview->GetPageNo(); + tools::Long nTotal = pPreview->GetTotalPages(); + + // before testing for last page, make sure all page counts are calculated + if ( nPage+1 == nTotal && !pPreview->AllTested() ) + { + pPreview->CalcAll(); + nTotal = pPreview->GetTotalPages(); + } + + if( nPageGetPageNo(); + + if( nPage>0 ) + { + SfxViewFrame& rSfxViewFrame = GetViewFrame(); + SfxRequest aSfxRequest(rSfxViewFrame, SID_PREVIEW_PREVIOUS); + Execute( aSfxRequest ); + aCurPos.setY( nVRange ); + } + } + else + aCurPos.AdjustY( -nVPage ); + break; + case SID_CURSORPAGEDOWN: + if( (std::abs(nVPage+nThumbPos-nRangeMax)<10) || nMaxVertPos<0 ) + { + tools::Long nPage = pPreview->GetPageNo(); + tools::Long nTotal = pPreview->GetTotalPages(); + + // before testing for last page, make sure all page counts are calculated + if ( nPage+1 == nTotal && !pPreview->AllTested() ) + { + pPreview->CalcAll(); + nTotal = pPreview->GetTotalPages(); + } + if( nPageGetPageNo(); + tools::Long nTotal = pPreview->GetTotalPages(); + if( nTotal && nPage != 0 ) + { + SfxViewFrame& rSfxViewFrame = GetViewFrame(); + SfxRequest aSfxRequest(rSfxViewFrame, SID_PREVIEW_FIRST); + Execute( aSfxRequest ); + } + } + else + { + aCurPos.setY( 0 ); + aCurPos.setX( 0 ); + } + break; + case SID_CURSOREND: + if( nMaxVertPos<0 ) + { + if( !pPreview->AllTested() ) + pPreview->CalcAll(); + tools::Long nPage = pPreview->GetPageNo(); + tools::Long nTotal = pPreview->GetTotalPages(); + if( nTotal && nPage+1 != nTotal ) + { + SfxViewFrame& rSfxViewFrame = GetViewFrame(); + SfxRequest aSfxRequest(rSfxViewFrame, SID_PREVIEW_LAST); + Execute( aSfxRequest ); + } + } + else + { + aCurPos.setY( nVRange ); + aCurPos.setX( nHRange ); + } + break; + } + + // nHRange-nHPage might be negative, that's why we check for < 0 afterwards + + if( aCurPos.Y() > (nVRange-nVPage) ) + aCurPos.setY( nVRange-nVPage ); + if( aCurPos.Y() < 0 ) + aCurPos.setY( 0 ); + if( aCurPos.X() > (nHRange-nHPage) ) + aCurPos.setX( nHRange-nHPage ); + if( aCurPos.X() < 0 ) + aCurPos.setX( 0 ); + + if( nMaxVertPos>=0 ) + { + if( aCurPos.Y() != aPrevPos.Y() ) + { + pVerScroll->SetThumbPos( aCurPos.Y() ); + nPrevVThumbPos = pVerScroll->GetThumbPos(); + pPreview->SetYOffset( aCurPos.Y() ); + } + } + + if( aCurPos.X() != aPrevPos.X() ) + { + pHorScroll->SetThumbPos( aCurPos.X() ); + nPrevHThumbPos = pHorScroll->GetThumbPos(); + pPreview->SetXOffset( aCurPos.X() ); + } + +} + +void ScPreviewShell::ExitPreview() +{ + GetViewFrame().GetDispatcher()->Execute(SID_VIEWSHELL0, SfxCallMode::ASYNCHRON); +} + +void ScPreviewShell::AddAccessibilityObject( SfxListener& rObject ) +{ + if (!pAccessibilityBroadcaster) + pAccessibilityBroadcaster.reset( new SfxBroadcaster ); + + rObject.StartListening( *pAccessibilityBroadcaster ); +} + +void ScPreviewShell::RemoveAccessibilityObject( SfxListener& rObject ) +{ + if (pAccessibilityBroadcaster) + rObject.EndListening( *pAccessibilityBroadcaster ); + else + { + OSL_FAIL("no accessibility broadcaster?"); + } +} + +void ScPreviewShell::BroadcastAccessibility( const SfxHint &rHint ) +{ + if (pAccessibilityBroadcaster) + pAccessibilityBroadcaster->Broadcast( rHint ); +} + +bool ScPreviewShell::HasAccessibilityObjects() const +{ + return pAccessibilityBroadcaster && pAccessibilityBroadcaster->HasListeners(); +} + +const ScPreviewLocationData& ScPreviewShell::GetLocationData() +{ + return pPreview->GetLocationData(); +} + +ScDocument& ScPreviewShell::GetDocument() +{ + return pDocShell->GetDocument(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/prevwsh2.cxx b/sc/source/ui/view/prevwsh2.cxx new file mode 100644 index 0000000000..bb15ac24ad --- /dev/null +++ b/sc/source/ui/view/prevwsh2.cxx @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include +#include +#include +#include + +void ScPreviewShell::Notify( SfxBroadcaster&, const SfxHint& rHint ) +{ + bool bDataChanged = false; + + if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint) + { + const SdrHint* pSdrHint = static_cast(&rHint); + // SdrHints are no longer used for invalidating, thus react on objectchange instead + if(SdrHintKind::ObjectChange == pSdrHint->GetKind()) + bDataChanged = true; + } + else if (const ScPaintHint* pPaintHint = dynamic_cast(&rHint)) + { + PaintPartFlags nParts = pPaintHint->GetParts(); + if (nParts & ( PaintPartFlags::Grid | PaintPartFlags::Left | PaintPartFlags::Top | PaintPartFlags::Size )) + bDataChanged = true; + } + else + { + switch ( rHint.GetId() ) + { + case SfxHintId::ScDataChanged: + case SfxHintId::ScPrintOptions: + bDataChanged = true; + break; + case SfxHintId::ScDrawLayerNew: + { + SfxBroadcaster* pDrawBC = pDocShell->GetDocument().GetDrawBroadcaster(); + if (pDrawBC) + StartListening(*pDrawBC); + } + break; + default: break; + } + } + + if (bDataChanged) + pPreview->DataChanged(true); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/printfun.cxx b/sc/source/ui/view/printfun.cxx new file mode 100644 index 0000000000..84e21da022 --- /dev/null +++ b/sc/source/ui/view/printfun.cxx @@ -0,0 +1,3204 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define ZOOM_MIN 10 + +namespace{ + +bool lcl_GetBool(const SfxItemSet* pSet, sal_uInt16 nWhich) +{ + return static_cast(pSet->Get(nWhich)).GetValue(); +} + +sal_uInt16 lcl_GetUShort(const SfxItemSet* pSet, sal_uInt16 nWhich) +{ + return static_cast(pSet->Get(nWhich)).GetValue(); +} + +bool lcl_GetShow(const SfxItemSet* pSet, sal_uInt16 nWhich) +{ + return ScVObjMode::VOBJ_MODE_SHOW == static_cast(pSet->Get(nWhich)).GetValue(); +} + + +} // namespace + +ScPageRowEntry::ScPageRowEntry(const ScPageRowEntry& r) +{ + nStartRow = r.nStartRow; + nEndRow = r.nEndRow; + nPagesX = r.nPagesX; + aHidden = r.aHidden; + aHidden.resize(nPagesX, false); +} + +ScPageRowEntry& ScPageRowEntry::operator=(const ScPageRowEntry& r) +{ + nStartRow = r.nStartRow; + nEndRow = r.nEndRow; + nPagesX = r.nPagesX; + aHidden = r.aHidden; + aHidden.resize(nPagesX, false); + return *this; +} + +void ScPageRowEntry::SetPagesX(size_t nNew) +{ + nPagesX = nNew; + aHidden.resize(nPagesX, false); +} + +void ScPageRowEntry::SetHidden(size_t nX) +{ + if ( nX < nPagesX ) + { + if ( nX+1 == nPagesX ) // last page? + --nPagesX; + else + { + aHidden.resize(nPagesX, false); + aHidden[nX] = true; + } + } +} + +bool ScPageRowEntry::IsHidden(size_t nX) const +{ + return nX >= nPagesX || aHidden[nX]; //! inline? +} + +size_t ScPageRowEntry::CountVisible() const +{ + if (!aHidden.empty()) + { + size_t nVis = 0; + for (size_t i=0; iGetScaledWidth() ) : 0; +} + +void ScPrintFunc::Construct( const ScPrintOptions* pOptions ) +{ + pDocShell->UpdatePendingRowHeights( nPrintTab ); + + SfxPrinter* pDocPrinter = rDoc.GetPrinter(); // use the printer, even for preview + if (pDocPrinter) + aOldPrinterMode = pDocPrinter->GetMapMode(); + + // unified MapMode for all calls (e.g. Repaint!!!) + // else, EditEngine outputs different text heights + pDev->SetMapMode(MapMode(MapUnit::MapPixel)); + + pBorderItem = nullptr; + pBackgroundItem = nullptr; + pShadowItem = nullptr; + + pEditEngine = nullptr; + pEditDefaults = nullptr; + + ScStyleSheetPool* pStylePool = rDoc.GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet = pStylePool->Find( + rDoc.GetPageStyle( nPrintTab ), + SfxStyleFamily::Page ); + if (pStyleSheet) + pParamSet = &pStyleSheet->GetItemSet(); + else + { + OSL_FAIL("Template not found" ); + pParamSet = nullptr; + } + + if (!bFromPrintState) + nZoom = 100; + nManualZoom = 100; + bClearWin = false; + bUseStyleColor = false; + bIsRender = false; + + InitParam(pOptions); + + pPageData = nullptr; // is only needed for initialisation +} + +ScPrintFunc::ScPrintFunc( ScDocShell* pShell, SfxPrinter* pNewPrinter, SCTAB nTab, + tools::Long nPage, tools::Long nDocP, const ScRange* pArea, + const ScPrintOptions* pOptions, + ScPageBreakData* pData ) + : pDocShell ( pShell ), + rDoc(pDocShell->GetDocument()), + pPrinter ( pNewPrinter ), + pDrawView ( nullptr ), + nPrintTab ( nTab ), + nPageStart ( nPage ), + nDocPages ( nDocP ), + pUserArea ( pArea ), + bFromPrintState ( false ), + bSourceRangeValid ( false ), + bPrintCurrentTable ( false ), + bMultiArea ( false ), + mbHasPrintRange(true), + nTabPages ( 0 ), + nTotalPages ( 0 ), + bPrintAreaValid ( false ), + pPageData ( pData ) +{ + pDev = pPrinter.get(); + aSrcOffset = pPrinter->PixelToLogic(pPrinter->GetPageOffsetPixel(), MapMode(MapUnit::Map100thMM)); + m_aRanges.m_xPageEndX = std::make_shared>(); + m_aRanges.m_xPageEndY = std::make_shared>(); + m_aRanges.m_xPageRows = std::make_shared>(); + Construct( pOptions ); +} + +ScPrintFunc::ScPrintFunc(ScDocShell* pShell, SfxPrinter* pNewPrinter, + const ScPrintState& rState, const ScPrintOptions* pOptions) + : pDocShell ( pShell ), + rDoc(pDocShell->GetDocument()), + pPrinter ( pNewPrinter ), + pDrawView ( nullptr ), + pUserArea ( nullptr ), + bSourceRangeValid ( false ), + bPrintCurrentTable ( false ), + bMultiArea ( false ), + mbHasPrintRange(true), + pPageData ( nullptr ) +{ + pDev = pPrinter.get(); + + nPrintTab = rState.nPrintTab; + nStartCol = rState.nStartCol; + nStartRow = rState.nStartRow; + nEndCol = rState.nEndCol; + nEndRow = rState.nEndRow; + bPrintAreaValid = rState.bPrintAreaValid; + nZoom = rState.nZoom; + m_aRanges.m_nPagesX = rState.nPagesX; + m_aRanges.m_nPagesY = rState.nPagesY; + nTabPages = rState.nTabPages; + nTotalPages = rState.nTotalPages; + nPageStart = rState.nPageStart; + nDocPages = rState.nDocPages; + bFromPrintState = true; + + if (rState.bSavedStateRanges) + { + m_aRanges.m_nTotalY = rState.nTotalY; + m_aRanges.m_xPageEndX = rState.xPageEndX; + m_aRanges.m_xPageEndY = rState.xPageEndY; + m_aRanges.m_xPageRows = rState.xPageRows; + m_aRanges.m_aInput = rState.aPrintPageRangesInput; + } + else + { + m_aRanges.m_xPageEndX = std::make_shared>(); + m_aRanges.m_xPageEndY = std::make_shared>(); + m_aRanges.m_xPageRows = std::make_shared>(); + } + + aSrcOffset = pPrinter->PixelToLogic(pPrinter->GetPageOffsetPixel(), MapMode(MapUnit::Map100thMM)); + Construct( pOptions ); +} + +ScPrintFunc::ScPrintFunc( OutputDevice* pOutDev, ScDocShell* pShell, SCTAB nTab, + tools::Long nPage, tools::Long nDocP, const ScRange* pArea, + const ScPrintOptions* pOptions ) + : pDocShell ( pShell ), + rDoc(pDocShell->GetDocument()), + pPrinter ( nullptr ), + pDrawView ( nullptr ), + nPrintTab ( nTab ), + nPageStart ( nPage ), + nDocPages ( nDocP ), + pUserArea ( pArea ), + bFromPrintState ( false ), + bSourceRangeValid ( false ), + bPrintCurrentTable ( false ), + bMultiArea ( false ), + mbHasPrintRange(true), + nTabPages ( 0 ), + nTotalPages ( 0 ), + bPrintAreaValid ( false ), + pPageData ( nullptr ) +{ + pDev = pOutDev; + m_aRanges.m_xPageEndX = std::make_shared>(); + m_aRanges.m_xPageEndY = std::make_shared>(); + m_aRanges.m_xPageRows = std::make_shared>(); + Construct( pOptions ); +} + +ScPrintFunc::ScPrintFunc( OutputDevice* pOutDev, ScDocShell* pShell, + const ScPrintState& rState, const ScPrintOptions* pOptions ) + : pDocShell ( pShell ), + rDoc(pDocShell->GetDocument()), + pPrinter ( nullptr ), + pDrawView ( nullptr ), + pUserArea ( nullptr ), + bSourceRangeValid ( false ), + bPrintCurrentTable ( false ), + bMultiArea ( false ), + mbHasPrintRange(true), + pPageData ( nullptr ) +{ + pDev = pOutDev; + + nPrintTab = rState.nPrintTab; + nStartCol = rState.nStartCol; + nStartRow = rState.nStartRow; + nEndCol = rState.nEndCol; + nEndRow = rState.nEndRow; + bPrintAreaValid = rState.bPrintAreaValid; + nZoom = rState.nZoom; + m_aRanges.m_nPagesX = rState.nPagesX; + m_aRanges.m_nPagesY = rState.nPagesY; + nTabPages = rState.nTabPages; + nTotalPages = rState.nTotalPages; + nPageStart = rState.nPageStart; + nDocPages = rState.nDocPages; + bFromPrintState = true; + + if (rState.bSavedStateRanges) + { + m_aRanges.m_nTotalY = rState.nTotalY; + m_aRanges.m_xPageEndX = rState.xPageEndX; + m_aRanges.m_xPageEndY = rState.xPageEndY; + m_aRanges.m_xPageRows = rState.xPageRows; + m_aRanges.m_aInput = rState.aPrintPageRangesInput; + } + else + { + m_aRanges.m_xPageEndX = std::make_shared>(); + m_aRanges.m_xPageEndY = std::make_shared>(); + m_aRanges.m_xPageRows = std::make_shared>(); + } + + Construct( pOptions ); +} + +void ScPrintFunc::GetPrintState(ScPrintState& rState, bool bSavePageRanges) +{ + rState.nPrintTab = nPrintTab; + rState.nStartCol = nStartCol; + rState.nStartRow = nStartRow; + rState.nEndCol = nEndCol; + rState.nEndRow = nEndRow; + rState.bPrintAreaValid = bPrintAreaValid; + rState.nZoom = nZoom; + rState.nPagesX = m_aRanges.m_nPagesX; + rState.nPagesY = m_aRanges.m_nPagesY; + rState.nTabPages = nTabPages; + rState.nTotalPages = nTotalPages; + rState.nPageStart = nPageStart; + rState.nDocPages = nDocPages; + if (bSavePageRanges) + { + rState.bSavedStateRanges = true; + rState.nTotalY = m_aRanges.m_nTotalY; + rState.xPageEndX = m_aRanges.m_xPageEndX; + rState.xPageEndY = m_aRanges.m_xPageEndY; + rState.xPageRows = m_aRanges.m_xPageRows; + rState.aPrintPageRangesInput = m_aRanges.m_aInput; + } +} + +bool ScPrintFunc::GetLastSourceRange( ScRange& rRange ) const +{ + rRange = aLastSourceRange; + return bSourceRangeValid; +} + +void ScPrintFunc::FillPageData() +{ + if (!pPageData) + return; + + sal_uInt16 nCount = sal::static_int_cast( pPageData->GetCount() ); + ScPrintRangeData& rData = pPageData->GetData(nCount); // count up + + assert( bPrintAreaValid ); + rData.SetPrintRange( ScRange( nStartCol, nStartRow, nPrintTab, + nEndCol, nEndRow, nPrintTab ) ); + // #i123672# + if(m_aRanges.m_xPageEndX->empty()) + { + OSL_ENSURE(false, "vector access error for maPageEndX (!)"); + } + else + { + rData.SetPagesX( m_aRanges.m_nPagesX, m_aRanges.m_xPageEndX->data()); + } + + // #i123672# + if(m_aRanges.m_xPageEndY->empty()) + { + OSL_ENSURE(false, "vector access error for maPageEndY (!)"); + } + else + { + rData.SetPagesY( m_aRanges.m_nTotalY, m_aRanges.m_xPageEndY->data()); + } + + // Settings + rData.SetTopDown( aTableParam.bTopDown ); + rData.SetAutomatic( !aAreaParam.bPrintArea ); +} + +ScPrintFunc::~ScPrintFunc() +{ + pEditDefaults.reset(); + pEditEngine.reset(); + + // Printer settings are now restored from outside + + // For DrawingLayer/Charts, the MapMode of the printer (RefDevice) must always be correct + SfxPrinter* pDocPrinter = rDoc.GetPrinter(); // use Preview also for the printer + if (pDocPrinter) + pDocPrinter->SetMapMode(aOldPrinterMode); +} + +void ScPrintFunc::SetDrawView( FmFormView* pNew ) +{ + pDrawView = pNew; +} + +static void lcl_HidePrint( const ScTableInfo& rTabInfo, SCCOL nX1, SCCOL nX2 ) +{ + for (SCSIZE nArrY=1; nArrY+1cellInfo(nX); + ScBasicCellInfo& rBasicCellInfo = pThisRowInfo->basicCellInfo(nX); + if (!rBasicCellInfo.bEmptyCellText) + if (rCellInfo.pPatternAttr-> + GetItem(ATTR_PROTECTION, rCellInfo.pConditionSet).GetHidePrint()) + { + rCellInfo.maCell.clear(); + rBasicCellInfo.bEmptyCellText = true; + } + } + } +} + +// output to Device (static) +// +// us used for: +// - Clipboard/Bitmap +// - Ole-Object (DocShell::Draw) +// - Preview of templates + +void ScPrintFunc::DrawToDev(ScDocument& rDoc, OutputDevice* pDev, double /* nPrintFactor */, + const tools::Rectangle& rBound, ScViewData* pViewData, bool bMetaFile) +{ + if (rDoc.GetMaxTableNumber() < 0) + return; + + //! evaluate nPrintFactor !!! + + SCTAB nTab = 0; + if (pViewData) + nTab = pViewData->GetTabNo(); + + bool bDoGrid, bNullVal, bFormula; + ScStyleSheetPool* pStylePool = rDoc.GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet = pStylePool->Find( rDoc.GetPageStyle( nTab ), SfxStyleFamily::Page ); + if (pStyleSheet) + { + SfxItemSet& rSet = pStyleSheet->GetItemSet(); + bDoGrid = rSet.Get(ATTR_PAGE_GRID).GetValue(); + bNullVal = rSet.Get(ATTR_PAGE_NULLVALS).GetValue(); + bFormula = rSet.Get(ATTR_PAGE_FORMULAS).GetValue(); + } + else + { + const ScViewOptions& rOpt = rDoc.GetViewOptions(); + bDoGrid = rOpt.GetOption(VOPT_GRID); + bNullVal = rOpt.GetOption(VOPT_NULLVALS); + bFormula = rOpt.GetOption(VOPT_FORMULAS); + } + + MapMode aMode = pDev->GetMapMode(); + + tools::Rectangle aRect = rBound; + + if (aRect.Right() < aRect.Left() || aRect.Bottom() < aRect.Top()) + aRect = tools::Rectangle( Point(), pDev->GetOutputSize() ); + + SCCOL nX1 = 0; + SCROW nY1 = 0; + SCCOL nX2 = OLE_STD_CELLS_X - 1; + SCROW nY2 = OLE_STD_CELLS_Y - 1; + if (bMetaFile) + { + ScRange aRange = rDoc.GetRange( nTab, rBound ); + nX1 = aRange.aStart.Col(); + nY1 = aRange.aStart.Row(); + nX2 = aRange.aEnd.Col(); + nY2 = aRange.aEnd.Row(); + } + else if (pViewData) + { + ScSplitPos eWhich = pViewData->GetActivePart(); + ScHSplitPos eHWhich = WhichH(eWhich); + ScVSplitPos eVWhich = WhichV(eWhich); + nX1 = pViewData->GetPosX(eHWhich); + nY1 = pViewData->GetPosY(eVWhich); + nX2 = nX1 + pViewData->VisibleCellsX(eHWhich); + if (nX2>nX1) --nX2; + nY2 = nY1 + pViewData->VisibleCellsY(eVWhich); + if (nY2>nY1) --nY2; + } + + if (nX1 > rDoc.MaxCol()) nX1 = rDoc.MaxCol(); + if (nX2 > rDoc.MaxCol()) nX2 = rDoc.MaxCol(); + if (nY1 > rDoc.MaxRow()) nY1 = rDoc.MaxRow(); + if (nY2 > rDoc.MaxRow()) nY2 = rDoc.MaxRow(); + + tools::Long nDevSizeX = aRect.Right()-aRect.Left()+1; + tools::Long nDevSizeY = aRect.Bottom()-aRect.Top()+1; + + tools::Long nTwipsSizeX = 0; + for (SCCOL i=nX1; i<=nX2; i++) + nTwipsSizeX += rDoc.GetColWidth( i, nTab ); + tools::Long nTwipsSizeY = rDoc.GetRowHeight( nY1, nY2, nTab ); + + // if no lines, still space for the outline frame (20 Twips = 1pt) + // (HasLines initializes aLines to 0,0,0,0) + nTwipsSizeX += 20; + nTwipsSizeY += 20; + + double nScaleX = static_cast(nDevSizeX) / nTwipsSizeX; + double nScaleY = static_cast(nDevSizeY) / nTwipsSizeY; + + //! hand over Flag at FillInfo !!!!! + ScRange aERange; + bool bEmbed = rDoc.IsEmbedded(); + if (bEmbed) + { + rDoc.GetEmbedded(aERange); + rDoc.ResetEmbedded(); + } + + // Assemble data + + ScTableInfo aTabInfo; + rDoc.FillInfo( aTabInfo, nX1, nY1, nX2, nY2, nTab, + nScaleX, nScaleY, false, bFormula ); + lcl_HidePrint( aTabInfo, nX1, nX2 ); + + if (bEmbed) + rDoc.SetEmbedded(aERange); + + tools::Long nScrX = aRect.Left(); + tools::Long nScrY = aRect.Top(); + + // If no lines, still leave space for grid lines + // (would be elseways cut away) + nScrX += 1; + nScrY += 1; + + ScOutputData aOutputData( pDev, OUTTYPE_PRINTER, aTabInfo, &rDoc, nTab, + nScrX, nScrY, nX1, nY1, nX2, nY2, nScaleX, nScaleY ); + aOutputData.SetMetaFileMode(bMetaFile); + aOutputData.SetShowNullValues(bNullVal); + aOutputData.SetShowFormulas(bFormula); + + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + std::unique_ptr pDrawView; + + if( pModel ) + { + pDrawView.reset( + new FmFormView( + *pModel, + pDev)); + pDrawView->ShowSdrPage(pDrawView->GetModel().GetPage(nTab)); + pDrawView->SetPrintPreview(); + aOutputData.SetDrawView( pDrawView.get() ); + } + + //! SetUseStyleColor ?? + + if ( bMetaFile && pDev->IsVirtual() ) + aOutputData.SetSnapPixel(); + + Point aLogStart = pDev->PixelToLogic(Point(nScrX, nScrY), MapMode(MapUnit::Map100thMM)); + tools::Long nLogStX = aLogStart.X(); + tools::Long nLogStY = aLogStart.Y(); + + //! nZoom for GetFont in OutputData ??? + + if (!bMetaFile && pViewData) + pDev->SetMapMode(pViewData->GetLogicMode(pViewData->GetActivePart())); + + // #i72502# + const Point aMMOffset(aOutputData.PrePrintDrawingLayer(nLogStX, nLogStY)); + aOutputData.PrintDrawingLayer(SC_LAYER_BACK, aMMOffset); + + if (!bMetaFile && pViewData) + pDev->SetMapMode(aMode); + + aOutputData.DrawBackground(*pDev); + + aOutputData.DrawShadow(); + aOutputData.DrawFrame(*pDev); + aOutputData.DrawSparklines(*pDev); + aOutputData.DrawStrings(); + + if (!bMetaFile && pViewData) + pDev->SetMapMode(pViewData->GetLogicMode(pViewData->GetActivePart())); + + aOutputData.DrawEdit(!bMetaFile); + + if (bDoGrid) + { + if (!bMetaFile && pViewData) + pDev->SetMapMode(aMode); + + aOutputData.DrawGrid(*pDev, true, false); // no page breaks + + pDev->SetLineColor( COL_BLACK ); + + Size aOne = pDev->PixelToLogic( Size(1,1) ); + if (bMetaFile) + aOne = Size(1,1); // compatible with DrawGrid + tools::Long nRight = nScrX + aOutputData.GetScrW() - aOne.Width(); + tools::Long nBottom = nScrY + aOutputData.GetScrH() - aOne.Height(); + + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + + // extra line at the left edge for left-to-right, right for right-to-left + if ( bLayoutRTL ) + pDev->DrawLine( Point(nRight,nScrY), Point(nRight,nBottom) ); + else + pDev->DrawLine( Point(nScrX,nScrY), Point(nScrX,nBottom) ); + // extra line at the top in both cases + pDev->DrawLine( Point(nScrX,nScrY), Point(nRight,nScrY) ); + } + + // #i72502# + aOutputData.PrintDrawingLayer(SC_LAYER_FRONT, aMMOffset); + aOutputData.PrintDrawingLayer(SC_LAYER_INTERN, aMMOffset); + aOutputData.PostPrintDrawingLayer(aMMOffset); // #i74768# +} + +// Printing + +static void lcl_FillHFParam( ScPrintHFParam& rParam, const SfxItemSet* pHFSet ) +{ + // nDistance must be initialized differently before + + if ( pHFSet == nullptr ) + { + rParam.bEnable = false; + rParam.pBorder = nullptr; + rParam.pBack = nullptr; + rParam.pShadow = nullptr; + } + else + { + rParam.bEnable = pHFSet->Get(ATTR_PAGE_ON).GetValue(); + rParam.bDynamic = pHFSet->Get(ATTR_PAGE_DYNAMIC).GetValue(); + rParam.bShared = pHFSet->Get(ATTR_PAGE_SHARED).GetValue(); + rParam.bSharedFirst = pHFSet->Get(ATTR_PAGE_SHARED_FIRST).GetValue(); + rParam.nHeight = pHFSet->Get(ATTR_PAGE_SIZE).GetSize().Height(); + const SvxLRSpaceItem* pHFLR = &pHFSet->Get(ATTR_LRSPACE); + tools::Long nTmp; + nTmp = pHFLR->GetLeft(); + rParam.nLeft = nTmp < 0 ? 0 : sal_uInt16(nTmp); + nTmp = pHFLR->GetRight(); + rParam.nRight = nTmp < 0 ? 0 : sal_uInt16(nTmp); + rParam.pBorder = &pHFSet->Get(ATTR_BORDER); + rParam.pBack = &pHFSet->Get(ATTR_BACKGROUND); + rParam.pShadow = &pHFSet->Get(ATTR_SHADOW); + +// now back in the dialog: +// rParam.nHeight += rParam.nDistance; // not in the dialog any more ??? + + rParam.nHeight += lcl_LineTotal( rParam.pBorder->GetTop() ) + + lcl_LineTotal( rParam.pBorder->GetBottom() ); + + rParam.nManHeight = rParam.nHeight; + } + + if (!rParam.bEnable) + rParam.nHeight = 0; +} + +// bNew = TRUE: search for used part of the document +// bNew = FALSE: only limit whole lines/columns + +bool ScPrintFunc::AdjustPrintArea( bool bNew ) +{ + SCCOL nOldEndCol = nEndCol; // only important for !bNew + SCROW nOldEndRow = nEndRow; + bool bChangeCol = true; // at bNew both are being adjusted + bool bChangeRow = true; + + bool bNotes = aTableParam.bNotes; + if ( bNew ) + { + nStartCol = 0; + nStartRow = 0; + if (!rDoc.GetPrintArea( nPrintTab, nEndCol, nEndRow, bNotes )) + return false; // nothing + bPrintAreaValid = true; + } + else + { + bool bFound = true; + bChangeCol = ( nStartCol == 0 && nEndCol == rDoc.MaxCol() ); + bChangeRow = ( nStartRow == 0 && nEndRow == rDoc.MaxRow() ); + bool bForcedChangeRow = false; + + // #i53558# Crop entire column of old row limit to real print area with + // some fuzzyness. + if (!bChangeRow && nStartRow == 0) + { + SCROW nPAEndRow; + bFound = rDoc.GetPrintAreaVer( nPrintTab, nStartCol, nEndCol, nPAEndRow, bNotes ); + // Say we don't want to print more than ~1000 empty rows, which are + // about 14 pages intentionally left blank... + const SCROW nFuzzy = 23*42; + if (nPAEndRow + nFuzzy < nEndRow) + { + bForcedChangeRow = true; + nEndRow = nPAEndRow; + } + else + bFound = true; // user seems to _want_ to print some empty rows + } + // TODO: in case we extend the number of columns we may have to do the + // same for horizontal cropping. + + if ( bChangeCol && bChangeRow ) + bFound = rDoc.GetPrintArea( nPrintTab, nEndCol, nEndRow, bNotes ); + else if ( bChangeCol ) + bFound = rDoc.GetPrintAreaHor( nPrintTab, nStartRow, nEndRow, nEndCol ); + else if ( bChangeRow ) + bFound = rDoc.GetPrintAreaVer( nPrintTab, nStartCol, nEndCol, nEndRow, bNotes ); + + if (!bFound) + return false; // empty + + bPrintAreaValid = true; + if (bForcedChangeRow) + bChangeRow = true; + } + + assert( bPrintAreaValid ); + rDoc.ExtendMerge( nStartCol,nStartRow, nEndCol,nEndRow, nPrintTab ); // no Refresh, incl. Attrs + + if ( bChangeCol ) + { + OutputDevice* pRefDev = rDoc.GetPrinter(); // use the printer also for Preview + pRefDev->SetMapMode(MapMode(MapUnit::MapPixel)); // important for GetNeededSize + + rDoc.ExtendPrintArea( pRefDev, + nPrintTab, nStartCol, nStartRow, nEndCol, nEndRow ); + // changing nEndCol + } + + if ( nEndCol < rDoc.MaxCol() && rDoc.HasAttrib( + nEndCol,nStartRow,nPrintTab, nEndCol,nEndRow,nPrintTab, HasAttrFlags::ShadowRight ) ) + ++nEndCol; + if ( nEndRow < rDoc.MaxRow() && rDoc.HasAttrib( + nStartCol,nEndRow,nPrintTab, nEndCol,nEndRow,nPrintTab, HasAttrFlags::ShadowDown ) ) + ++nEndRow; + + if (!bChangeCol) nEndCol = nOldEndCol; + if (!bChangeRow) nEndRow = nOldEndRow; + + return true; +} + +tools::Long ScPrintFunc::TextHeight( const EditTextObject* pObject ) +{ + if (!pObject) + return 0; + + pEditEngine->SetTextNewDefaults( *pObject, *pEditDefaults, false ); + + return static_cast(pEditEngine->GetTextHeight()); +} + +// nZoom must be set !!! +// and the respective Twip-MapMode configured + +void ScPrintFunc::UpdateHFHeight( ScPrintHFParam& rParam ) +{ + OSL_ENSURE( aPageSize.Width(), "UpdateHFHeight without aPageSize"); + + if (!(rParam.bEnable && rParam.bDynamic)) + return; + + // calculate nHeight from content + + MakeEditEngine(); + tools::Long nPaperWidth = ( aPageSize.Width() - nLeftMargin - nRightMargin - + rParam.nLeft - rParam.nRight ) * 100 / nZoom; + if (rParam.pBorder) + nPaperWidth -= ( rParam.pBorder->GetDistance(SvxBoxItemLine::LEFT) + + rParam.pBorder->GetDistance(SvxBoxItemLine::RIGHT) + + lcl_LineTotal(rParam.pBorder->GetLeft()) + + lcl_LineTotal(rParam.pBorder->GetRight()) ) * 100 / nZoom; + + if (rParam.pShadow && rParam.pShadow->GetLocation() != SvxShadowLocation::NONE) + nPaperWidth -= ( rParam.pShadow->CalcShadowSpace(SvxShadowItemSide::LEFT) + + rParam.pShadow->CalcShadowSpace(SvxShadowItemSide::RIGHT) ) * 100 / nZoom; + + pEditEngine->SetPaperSize( Size( nPaperWidth, 10000 ) ); + + tools::Long nMaxHeight = 0; + if ( rParam.pLeft ) + { + nMaxHeight = std::max( nMaxHeight, TextHeight( rParam.pLeft->GetLeftArea() ) ); + nMaxHeight = std::max( nMaxHeight, TextHeight( rParam.pLeft->GetCenterArea() ) ); + nMaxHeight = std::max( nMaxHeight, TextHeight( rParam.pLeft->GetRightArea() ) ); + } + if ( rParam.pRight ) + { + nMaxHeight = std::max( nMaxHeight, TextHeight( rParam.pRight->GetLeftArea() ) ); + nMaxHeight = std::max( nMaxHeight, TextHeight( rParam.pRight->GetCenterArea() ) ); + nMaxHeight = std::max( nMaxHeight, TextHeight( rParam.pRight->GetRightArea() ) ); + } + if ( rParam.pFirst ) + { + nMaxHeight = std::max( nMaxHeight, TextHeight( rParam.pFirst->GetLeftArea() ) ); + nMaxHeight = std::max( nMaxHeight, TextHeight( rParam.pFirst->GetCenterArea() ) ); + nMaxHeight = std::max( nMaxHeight, TextHeight( rParam.pFirst->GetRightArea() ) ); + } + + rParam.nHeight = nMaxHeight + rParam.nDistance; + if (rParam.pBorder) + rParam.nHeight += rParam.pBorder->GetDistance(SvxBoxItemLine::TOP) + + rParam.pBorder->GetDistance(SvxBoxItemLine::BOTTOM) + + lcl_LineTotal( rParam.pBorder->GetTop() ) + + lcl_LineTotal( rParam.pBorder->GetBottom() ); + if (rParam.pShadow && rParam.pShadow->GetLocation() != SvxShadowLocation::NONE) + rParam.nHeight += rParam.pShadow->CalcShadowSpace(SvxShadowItemSide::TOP) + + rParam.pShadow->CalcShadowSpace(SvxShadowItemSide::BOTTOM); + + if (rParam.nHeight < rParam.nManHeight) + rParam.nHeight = rParam.nManHeight; // configured minimum +} + +void ScPrintFunc::InitParam( const ScPrintOptions* pOptions ) +{ + if (!pParamSet) + return; + + // TabPage "Page" + const SvxLRSpaceItem* pLRItem = &pParamSet->Get( ATTR_LRSPACE ); + tools::Long nTmp; + nTmp = pLRItem->GetLeft(); + nLeftMargin = nTmp < 0 ? 0 : sal_uInt16(nTmp); + nTmp = pLRItem->GetRight(); + nRightMargin = nTmp < 0 ? 0 : sal_uInt16(nTmp); + const SvxULSpaceItem* pULItem = &pParamSet->Get( ATTR_ULSPACE ); + nTopMargin = pULItem->GetUpper(); + nBottomMargin = pULItem->GetLower(); + + const SvxPageItem* pPageItem = &pParamSet->Get( ATTR_PAGE ); + nPageUsage = pPageItem->GetPageUsage(); + bLandscape = pPageItem->IsLandscape(); + aFieldData.eNumType = pPageItem->GetNumType(); + + bCenterHor = pParamSet->Get(ATTR_PAGE_HORCENTER).GetValue(); + bCenterVer = pParamSet->Get(ATTR_PAGE_VERCENTER).GetValue(); + + aPageSize = pParamSet->Get(ATTR_PAGE_SIZE).GetSize(); + if ( !aPageSize.Width() || !aPageSize.Height() ) + { + OSL_FAIL("PageSize Null ?!?!?"); + aPageSize = SvxPaperInfo::GetPaperSize( PAPER_A4 ); + } + + pBorderItem = &pParamSet->Get(ATTR_BORDER); + pBackgroundItem = &pParamSet->Get(ATTR_BACKGROUND); + pShadowItem = &pParamSet->Get(ATTR_SHADOW); + + // TabPage "Headline" + + aHdr.pLeft = &pParamSet->Get(ATTR_PAGE_HEADERLEFT); // Content + aHdr.pRight = &pParamSet->Get(ATTR_PAGE_HEADERRIGHT); + aHdr.pFirst = &pParamSet->Get(ATTR_PAGE_HEADERFIRST); + + const SfxItemSet* pHeaderSet = nullptr; + if ( const SvxSetItem* pHeaderSetItem = pParamSet->GetItemIfSet( ATTR_PAGE_HEADERSET, false ) ) + { + pHeaderSet = &pHeaderSetItem->GetItemSet(); + // Headline has space below + aHdr.nDistance = pHeaderSet->Get(ATTR_ULSPACE).GetLower(); + } + lcl_FillHFParam( aHdr, pHeaderSet ); + + // TabPage "Footline" + + aFtr.pLeft = &pParamSet->Get(ATTR_PAGE_FOOTERLEFT); // Content + aFtr.pRight = &pParamSet->Get(ATTR_PAGE_FOOTERRIGHT); + aFtr.pFirst = &pParamSet->Get(ATTR_PAGE_FOOTERFIRST); + + const SfxItemSet* pFooterSet = nullptr; + if ( const SvxSetItem* pFooterSetItem = pParamSet->GetItemIfSet( ATTR_PAGE_FOOTERSET, false ) ) + { + pFooterSet = &pFooterSetItem->GetItemSet(); + // Footline has space above + aFtr.nDistance = pFooterSet->Get(ATTR_ULSPACE).GetUpper(); + } + lcl_FillHFParam( aFtr, pFooterSet ); + + // Compile Table-/Area-Params from single Items + + // TabPage "Table" + + const SfxUInt16Item* pScaleItem = nullptr; + const ScPageScaleToItem* pScaleToItem = nullptr; + const SfxUInt16Item* pScaleToPagesItem = nullptr; + SfxItemState eState; + + eState = pParamSet->GetItemState( ATTR_PAGE_SCALE, false, + reinterpret_cast(&pScaleItem) ); + if ( SfxItemState::DEFAULT == eState ) + pScaleItem = &pParamSet->GetPool()->GetDefaultItem( ATTR_PAGE_SCALE ); + + eState = pParamSet->GetItemState( ATTR_PAGE_SCALETO, false, + reinterpret_cast(&pScaleToItem) ); + if ( SfxItemState::DEFAULT == eState ) + pScaleToItem = &pParamSet->GetPool()->GetDefaultItem( ATTR_PAGE_SCALETO ); + + eState = pParamSet->GetItemState( ATTR_PAGE_SCALETOPAGES, false, + reinterpret_cast(&pScaleToPagesItem) ); + if ( SfxItemState::DEFAULT == eState ) + pScaleToPagesItem = &pParamSet->GetPool()->GetDefaultItem( ATTR_PAGE_SCALETOPAGES ); + + OSL_ENSURE( pScaleItem && pScaleToItem && pScaleToPagesItem, "Missing ScaleItem! :-/" ); + + aTableParam.bCellContent = true; + aTableParam.bNotes = lcl_GetBool(pParamSet,ATTR_PAGE_NOTES); + aTableParam.bGrid = lcl_GetBool(pParamSet,ATTR_PAGE_GRID); + aTableParam.bHeaders = lcl_GetBool(pParamSet,ATTR_PAGE_HEADERS); + aTableParam.bFormulas = lcl_GetBool(pParamSet,ATTR_PAGE_FORMULAS); + aTableParam.bNullVals = lcl_GetBool(pParamSet,ATTR_PAGE_NULLVALS); + aTableParam.bCharts = lcl_GetShow(pParamSet,ATTR_PAGE_CHARTS); + aTableParam.bObjects = lcl_GetShow(pParamSet,ATTR_PAGE_OBJECTS); + aTableParam.bDrawings = lcl_GetShow(pParamSet,ATTR_PAGE_DRAWINGS); + aTableParam.bTopDown = lcl_GetBool(pParamSet,ATTR_PAGE_TOPDOWN); + aTableParam.bLeftRight = !aTableParam.bLeftRight; + aTableParam.nFirstPageNo = lcl_GetUShort(pParamSet,ATTR_PAGE_FIRSTPAGENO); + if (!aTableParam.nFirstPageNo) + aTableParam.nFirstPageNo = static_cast(nPageStart); // from previous table + + if ( pScaleItem && pScaleToItem && pScaleToPagesItem ) + { + sal_uInt16 nScaleAll = pScaleItem->GetValue(); + sal_uInt16 nScaleToPages = pScaleToPagesItem->GetValue(); + + aTableParam.bScaleNone = (nScaleAll == 100); + aTableParam.bScaleAll = (nScaleAll > 0 ); + aTableParam.bScaleTo = pScaleToItem->IsValid(); + aTableParam.bScalePageNum = (nScaleToPages > 0 ); + aTableParam.nScaleAll = nScaleAll; + aTableParam.nScaleWidth = pScaleToItem->GetWidth(); + aTableParam.nScaleHeight = pScaleToItem->GetHeight(); + aTableParam.nScalePageNum = nScaleToPages; + } + else + { + aTableParam.bScaleNone = true; + aTableParam.bScaleAll = false; + aTableParam.bScaleTo = false; + aTableParam.bScalePageNum = false; + aTableParam.nScaleAll = 0; + aTableParam.nScaleWidth = 0; + aTableParam.nScaleHeight = 0; + aTableParam.nScalePageNum = 0; + } + + // skip empty pages only if options with that flag are passed + aTableParam.bSkipEmpty = pOptions && pOptions->GetSkipEmpty(); + if ( pPageData ) + aTableParam.bSkipEmpty = false; + // If pPageData is set, only the breaks are interesting for the + // pagebreak preview, empty pages are not addressed separately. + + aTableParam.bForceBreaks = pOptions && pOptions->GetForceBreaks(); + + // TabPage "Parts": + + //! walk through all PrintAreas of the table !!! + const ScRange* pPrintArea = rDoc.GetPrintRange( nPrintTab, 0 ); + std::optional oRepeatCol = rDoc.GetRepeatColRange( nPrintTab ); + std::optional oRepeatRow = rDoc.GetRepeatRowRange( nPrintTab ); + + // ignoring ATTR_PAGE_PRINTTABLES + + bool bHasPrintRange = rDoc.HasPrintRange(); + sal_uInt16 nPrintRangeCount = rDoc.GetPrintRangeCount(nPrintTab); + bool bPrintEntireSheet = rDoc.IsPrintEntireSheet(nPrintTab); + + if (!bPrintEntireSheet && !nPrintRangeCount) + mbHasPrintRange = false; + + if ( pUserArea ) // UserArea (selection) has priority + { + bPrintCurrentTable = + aAreaParam.bPrintArea = true; // Selection + aAreaParam.aPrintArea = *pUserArea; + + // The table-query is already in DocShell::Print, here always + aAreaParam.aPrintArea.aStart.SetTab(nPrintTab); + aAreaParam.aPrintArea.aEnd.SetTab(nPrintTab); + } + else if (bHasPrintRange) + { + if ( pPrintArea ) // at least one set? + { + bPrintCurrentTable = + aAreaParam.bPrintArea = true; + aAreaParam.aPrintArea = *pPrintArea; + + bMultiArea = nPrintRangeCount > 1; + } + else + { + // do not print hidden sheets with "Print entire sheet" flag + bPrintCurrentTable = rDoc.IsPrintEntireSheet( nPrintTab ) && rDoc.IsVisible( nPrintTab ); + aAreaParam.bPrintArea = !bPrintCurrentTable; // otherwise the table is always counted + } + } + else + { + // don't print hidden tables if there's no print range defined there + if ( rDoc.IsVisible( nPrintTab ) ) + { + aAreaParam.bPrintArea = false; + bPrintCurrentTable = true; + } + else + { + aAreaParam.bPrintArea = true; // otherwise the table is always counted + bPrintCurrentTable = false; + } + } + + if ( oRepeatCol ) + { + aAreaParam.bRepeatCol = true; + nRepeatStartCol = oRepeatCol->aStart.Col(); + nRepeatEndCol = oRepeatCol->aEnd .Col(); + } + else + { + aAreaParam.bRepeatCol = false; + nRepeatStartCol = nRepeatEndCol = SCCOL_REPEAT_NONE; + } + + if ( oRepeatRow ) + { + aAreaParam.bRepeatRow = true; + nRepeatStartRow = oRepeatRow->aStart.Row(); + nRepeatEndRow = oRepeatRow->aEnd .Row(); + } + else + { + aAreaParam.bRepeatRow = false; + nRepeatStartRow = nRepeatEndRow = SCROW_REPEAT_NONE; + } + + // Split pages + + if (!bPrintAreaValid) + { + nTabPages = CountPages(); // also calculates zoom + nTotalPages = nTabPages; + nTotalPages += CountNotePages(); + } + else + { + CalcPages(); // search breaks only + CountNotePages(); // Count notes, even if number of pages is already known + } + + if (nDocPages) + aFieldData.nTotalPages = nDocPages; + else + aFieldData.nTotalPages = nTotalPages; + + SetDateTime( DateTime( DateTime::SYSTEM ) ); + + if( pDocShell->getDocProperties()->getTitle().getLength() != 0 ) + aFieldData.aTitle = pDocShell->getDocProperties()->getTitle(); + else + aFieldData.aTitle = pDocShell->GetTitle(); + + const INetURLObject& rURLObj = pDocShell->GetMedium()->GetURLObject(); + aFieldData.aLongDocName = rURLObj.GetMainURL( INetURLObject::DecodeMechanism::Unambiguous ); + if ( !aFieldData.aLongDocName.isEmpty() ) + aFieldData.aShortDocName = rURLObj.GetLastName(INetURLObject::DecodeMechanism::Unambiguous); + else + aFieldData.aShortDocName = aFieldData.aLongDocName = aFieldData.aTitle; + + // Printer settings (Orientation, Paper) at DoPrint +} + +Size ScPrintFunc::GetDataSize() const +{ + Size aSize = aPageSize; + aSize.AdjustWidth( -(nLeftMargin + nRightMargin) ); + aSize.AdjustHeight( -(nTopMargin + nBottomMargin) ); + aSize.AdjustHeight( -(aHdr.nHeight + aFtr.nHeight) ); + return aSize; +} + +void ScPrintFunc::GetScaleData( Size& rPhysSize, tools::Long& rDocHdr, tools::Long& rDocFtr ) +{ + rPhysSize = aPageSize; + rPhysSize.AdjustWidth( -(nLeftMargin + nRightMargin) ); + rPhysSize.AdjustHeight( -(nTopMargin + nBottomMargin) ); + + rDocHdr = aHdr.nHeight; + rDocFtr = aFtr.nHeight; +} + +void ScPrintFunc::SetDateTime( const DateTime& rDateTime ) +{ + aFieldData.aDateTime = rDateTime; +} + +static void lcl_DrawGraphic( const Graphic &rGraphic, vcl::RenderContext& rOutDev, + const tools::Rectangle &rGrf, const tools::Rectangle &rOut ) +{ + const bool bNotInside = !rOut.Contains( rGrf ); + if ( bNotInside ) + { + rOutDev.Push(); + rOutDev.IntersectClipRegion( rOut ); + } + + rGraphic.Draw(rOutDev, rGrf.TopLeft(), rGrf.GetSize()); + + if ( bNotInside ) + rOutDev.Pop(); +} + +static void lcl_DrawGraphic( const SvxBrushItem &rBrush, vcl::RenderContext& rOutDev, const OutputDevice* pRefDev, + const tools::Rectangle &rOrg, const tools::Rectangle &rOut, + OUString const & referer ) +{ + Size aGrfSize(0,0); + const Graphic *pGraphic = rBrush.GetGraphic(referer); + SvxGraphicPosition ePos; + if ( pGraphic && pGraphic->IsSupportedGraphic() ) + { + const MapMode aMapMM( MapUnit::Map100thMM ); + if ( pGraphic->GetPrefMapMode().GetMapUnit() == MapUnit::MapPixel ) + aGrfSize = pRefDev->PixelToLogic( pGraphic->GetPrefSize(), aMapMM ); + else + aGrfSize = OutputDevice::LogicToLogic( pGraphic->GetPrefSize(), + pGraphic->GetPrefMapMode(), aMapMM ); + ePos = rBrush.GetGraphicPos(); + } + else + ePos = GPOS_NONE; + + Point aPos; + Size aDrawSize = aGrfSize; + + bool bDraw = true; + switch ( ePos ) + { + case GPOS_LT: aPos = rOrg.TopLeft(); + break; + case GPOS_MT: aPos.setY( rOrg.Top() ); + aPos.setX( rOrg.Left() + rOrg.GetSize().Width()/2 - aGrfSize.Width()/2 ); + break; + case GPOS_RT: aPos.setY( rOrg.Top() ); + aPos.setX( rOrg.Right() - aGrfSize.Width() ); + break; + + case GPOS_LM: aPos.setY( rOrg.Top() + rOrg.GetSize().Height()/2 - aGrfSize.Height()/2 ); + aPos.setX( rOrg.Left() ); + break; + case GPOS_MM: aPos.setY( rOrg.Top() + rOrg.GetSize().Height()/2 - aGrfSize.Height()/2 ); + aPos.setX( rOrg.Left() + rOrg.GetSize().Width()/2 - aGrfSize.Width()/2 ); + break; + case GPOS_RM: aPos.setY( rOrg.Top() + rOrg.GetSize().Height()/2 - aGrfSize.Height()/2 ); + aPos.setX( rOrg.Right() - aGrfSize.Width() ); + break; + + case GPOS_LB: aPos.setY( rOrg.Bottom() - aGrfSize.Height() ); + aPos.setX( rOrg.Left() ); + break; + case GPOS_MB: aPos.setY( rOrg.Bottom() - aGrfSize.Height() ); + aPos.setX( rOrg.Left() + rOrg.GetSize().Width()/2 - aGrfSize.Width()/2 ); + break; + case GPOS_RB: aPos.setY( rOrg.Bottom() - aGrfSize.Height() ); + aPos.setX( rOrg.Right() - aGrfSize.Width() ); + break; + + case GPOS_AREA: + aPos = rOrg.TopLeft(); + aDrawSize = rOrg.GetSize(); + break; + case GPOS_TILED: + { + // use GraphicObject::DrawTiled instead of an own loop + // (pixel rounding is handled correctly, and a very small bitmap + // is duplicated into a bigger one for better performance) + + GraphicObject aObject( *pGraphic ); + + if( rOutDev.GetOutDevType() == OUTDEV_PDF && + (aObject.GetType() == GraphicType::Bitmap || aObject.GetType() == GraphicType::Default) ) + { + // For PDF export, every draw + // operation for bitmaps takes a noticeable + // amount of place (~50 characters). Thus, + // optimize between tile bitmap size and + // number of drawing operations here. + // + // A_out + // n_chars = k1 * ---------- + k2 * A_bitmap + // A_bitmap + // + // minimum n_chars is obtained for (derive for + // A_bitmap, set to 0, take positive + // solution): + // k1 + // A_bitmap = Sqrt( ---- A_out ) + // k2 + // + // where k1 is the number of chars per draw + // operation, and k2 is the number of chars + // per bitmap pixel. This is approximately 50 + // and 7 for current PDF writer, respectively. + + const double k1( 50 ); + const double k2( 7 ); + const Size aSize( rOrg.GetSize() ); + const double Abitmap( k1/k2 * aSize.Width()*aSize.Height() ); + + aObject.DrawTiled( rOutDev, rOrg, aGrfSize, Size(0,0), + ::std::max( 128, static_cast( sqrt(sqrt( Abitmap)) + .5 ) ) ); + } + else + { + aObject.DrawTiled( rOutDev, rOrg, aGrfSize, Size(0,0) ); + } + + bDraw = false; + } + break; + + case GPOS_NONE: + bDraw = false; + break; + + default: OSL_ENSURE( false, "new Graphic position?" ); + } + tools::Rectangle aGrf( aPos,aDrawSize ); + if ( bDraw && aGrf.Overlaps( rOut ) ) + { + lcl_DrawGraphic( *pGraphic, rOutDev, aGrf, rOut ); + } +} + +// The frame is drawn inwards + +void ScPrintFunc::DrawBorder( tools::Long nScrX, tools::Long nScrY, tools::Long nScrW, tools::Long nScrH, + const SvxBoxItem* pBorderData, const SvxBrushItem* pBackground, + const SvxShadowItem* pShadow ) +{ + //! direct output from SvxBoxItem !!! + + if (pBorderData) + if ( !pBorderData->GetTop() && !pBorderData->GetBottom() && !pBorderData->GetLeft() && + !pBorderData->GetRight() ) + pBorderData = nullptr; + + if (!pBorderData && !pBackground && !pShadow) + return; // nothing to do + + tools::Long nLeft = 0; + tools::Long nRight = 0; + tools::Long nTop = 0; + tools::Long nBottom = 0; + + // aFrameRect - outside around frame, without shadow + if ( pShadow && pShadow->GetLocation() != SvxShadowLocation::NONE ) + { + nLeft += static_cast( pShadow->CalcShadowSpace(SvxShadowItemSide::LEFT) * nScaleX ); + nRight += static_cast( pShadow->CalcShadowSpace(SvxShadowItemSide::RIGHT) * nScaleX ); + nTop += static_cast( pShadow->CalcShadowSpace(SvxShadowItemSide::TOP) * nScaleY ); + nBottom += static_cast( pShadow->CalcShadowSpace(SvxShadowItemSide::BOTTOM) * nScaleY ); + } + tools::Rectangle aFrameRect( Point(nScrX+nLeft, nScrY+nTop), + Size(nScrW-nLeft-nRight, nScrH-nTop-nBottom) ); + + // center of frame, to paint lines through OutputData + if (pBorderData) + { + nLeft += static_cast( lcl_LineTotal(pBorderData->GetLeft()) * nScaleX / 2 ); + nRight += static_cast( lcl_LineTotal(pBorderData->GetRight()) * nScaleX / 2 ); + nTop += static_cast( lcl_LineTotal(pBorderData->GetTop()) * nScaleY / 2 ); + nBottom += static_cast( lcl_LineTotal(pBorderData->GetBottom()) * nScaleY / 2 ); + } + tools::Long nEffHeight = nScrH - nTop - nBottom; + tools::Long nEffWidth = nScrW - nLeft - nRight; + if (nEffHeight<=0 || nEffWidth<=0) + return; // empty + + if ( pBackground ) + { + if (pBackground->GetGraphicPos() != GPOS_NONE) + { + OutputDevice* pRefDev; + if ( bIsRender ) + pRefDev = pDev; // don't use printer for PDF + else + pRefDev = rDoc.GetPrinter(); // use printer also for preview + OUString referer; + if (pDocShell->HasName()) { + referer = pDocShell->GetMedium()->GetName(); + } + lcl_DrawGraphic(*pBackground, *pDev, pRefDev, aFrameRect, aFrameRect, referer); + } + else + { + pDev->SetFillColor(pBackground->GetColor()); + pDev->SetLineColor(); + pDev->DrawRect(aFrameRect); + } + } + + if ( pShadow && pShadow->GetLocation() != SvxShadowLocation::NONE ) + { + pDev->SetFillColor(pShadow->GetColor()); + pDev->SetLineColor(); + tools::Long nShadowX = static_cast( pShadow->GetWidth() * nScaleX ); + tools::Long nShadowY = static_cast( pShadow->GetWidth() * nScaleY ); + switch (pShadow->GetLocation()) + { + case SvxShadowLocation::TopLeft: + pDev->DrawRect( tools::Rectangle( + aFrameRect.Left()-nShadowX, aFrameRect.Top()-nShadowY, + aFrameRect.Right()-nShadowX, aFrameRect.Top() ) ); + pDev->DrawRect( tools::Rectangle( + aFrameRect.Left()-nShadowX, aFrameRect.Top()-nShadowY, + aFrameRect.Left(), aFrameRect.Bottom()-nShadowY ) ); + break; + case SvxShadowLocation::TopRight: + pDev->DrawRect( tools::Rectangle( + aFrameRect.Left()+nShadowX, aFrameRect.Top()-nShadowY, + aFrameRect.Right()+nShadowX, aFrameRect.Top() ) ); + pDev->DrawRect( tools::Rectangle( + aFrameRect.Right(), aFrameRect.Top()-nShadowY, + aFrameRect.Right()+nShadowX, aFrameRect.Bottom()-nShadowY ) ); + break; + case SvxShadowLocation::BottomLeft: + pDev->DrawRect( tools::Rectangle( + aFrameRect.Left()-nShadowX, aFrameRect.Bottom(), + aFrameRect.Right()-nShadowX, aFrameRect.Bottom()+nShadowY ) ); + pDev->DrawRect( tools::Rectangle( + aFrameRect.Left()-nShadowX, aFrameRect.Top()+nShadowY, + aFrameRect.Left(), aFrameRect.Bottom()+nShadowY ) ); + break; + case SvxShadowLocation::BottomRight: + pDev->DrawRect( tools::Rectangle( + aFrameRect.Left()+nShadowX, aFrameRect.Bottom(), + aFrameRect.Right()+nShadowX, aFrameRect.Bottom()+nShadowY ) ); + pDev->DrawRect( tools::Rectangle( + aFrameRect.Right(), aFrameRect.Top()+nShadowY, + aFrameRect.Right()+nShadowX, aFrameRect.Bottom()+nShadowY ) ); + break; + default: + { + // added to avoid warnings + } + } + } + + if (!pBorderData) + return; + + ScDocumentUniquePtr pBorderDoc(new ScDocument( SCDOCMODE_UNDO )); + pBorderDoc->InitUndo( rDoc, 0,0, true,true ); + pBorderDoc->ApplyAttr( 0,0,0, *pBorderData ); + + ScTableInfo aTabInfo; + pBorderDoc->FillInfo( aTabInfo, 0,0, 0,0, 0, + nScaleX, nScaleY, false, false ); + OSL_ENSURE(aTabInfo.mnArrCount,"nArrCount == 0"); + + aTabInfo.mpRowInfo[1].nHeight = static_cast(nEffHeight); + aTabInfo.mpRowInfo[0].basicCellInfo(0).nWidth = + aTabInfo.mpRowInfo[1].basicCellInfo(0).nWidth = static_cast(nEffWidth); + + ScOutputData aOutputData( pDev, OUTTYPE_PRINTER, aTabInfo, pBorderDoc.get(), 0, + nScrX+nLeft, nScrY+nTop, 0,0, 0,0, nScaleX, nScaleY ); + aOutputData.SetUseStyleColor( bUseStyleColor ); + + aOutputData.DrawFrame(*pDev); +} + +void ScPrintFunc::PrintColHdr( SCCOL nX1, SCCOL nX2, tools::Long nScrX, tools::Long nScrY ) +{ + bool bLayoutRTL = rDoc.IsLayoutRTL( nPrintTab ); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + Size aOnePixel = pDev->PixelToLogic(Size(1,1)); + tools::Long nOneX = aOnePixel.Width(); + tools::Long nOneY = aOnePixel.Height(); + SCCOL nCol; + + tools::Long nHeight = static_cast(PRINT_HEADER_HEIGHT * nScaleY); + tools::Long nEndY = nScrY + nHeight - nOneY; + + tools::Long nPosX = nScrX; + if ( bLayoutRTL ) + { + for (nCol=nX1; nCol<=nX2; nCol++) + nPosX += static_cast( rDoc.GetColWidth( nCol, nPrintTab ) * nScaleX ); + } + else + nPosX -= nOneX; + tools::Long nPosY = nScrY - nOneY; + OUString aText; + + for (nCol=nX1; nCol<=nX2; nCol++) + { + sal_uInt16 nDocW = rDoc.GetColWidth( nCol, nPrintTab ); + if (nDocW) + { + tools::Long nWidth = static_cast(nDocW * nScaleX); + tools::Long nEndX = nPosX + nWidth * nLayoutSign; + + pDev->DrawRect( tools::Rectangle( nPosX,nPosY,nEndX,nEndY ) ); + + aText = ::ScColToAlpha( nCol); + tools::Long nTextWidth = pDev->GetTextWidth(aText); + tools::Long nTextHeight = pDev->GetTextHeight(); + tools::Long nAddX = ( nWidth - nTextWidth ) / 2; + tools::Long nAddY = ( nHeight - nTextHeight ) / 2; + tools::Long nTextPosX = nPosX+nAddX; + if ( bLayoutRTL ) + nTextPosX -= nWidth; + pDev->DrawText( Point( nTextPosX,nPosY+nAddY ), aText ); + + nPosX = nEndX; + } + } +} + +void ScPrintFunc::PrintRowHdr( SCROW nY1, SCROW nY2, tools::Long nScrX, tools::Long nScrY ) +{ + Size aOnePixel = pDev->PixelToLogic(Size(1,1)); + tools::Long nOneX = aOnePixel.Width(); + tools::Long nOneY = aOnePixel.Height(); + + bool bLayoutRTL = rDoc.IsLayoutRTL( nPrintTab ); + + tools::Long nWidth = static_cast(PRINT_HEADER_WIDTH * nScaleX); + tools::Long nEndX = nScrX + nWidth; + tools::Long nPosX = nScrX; + if ( !bLayoutRTL ) + { + nEndX -= nOneX; + nPosX -= nOneX; + } + tools::Long nPosY = nScrY - nOneY; + OUString aText; + + for (SCROW nRow=nY1; nRow<=nY2; nRow++) + { + sal_uInt16 nDocH = rDoc.GetRowHeight( nRow, nPrintTab ); + if (nDocH) + { + tools::Long nHeight = static_cast(nDocH * nScaleY); + tools::Long nEndY = nPosY + nHeight; + + pDev->DrawRect( tools::Rectangle( nPosX,nPosY,nEndX,nEndY ) ); + + aText = OUString::number( nRow+1 ); + tools::Long nTextWidth = pDev->GetTextWidth(aText); + tools::Long nTextHeight = pDev->GetTextHeight(); + tools::Long nAddX = ( nWidth - nTextWidth ) / 2; + tools::Long nAddY = ( nHeight - nTextHeight ) / 2; + pDev->DrawText( Point( nPosX+nAddX,nPosY+nAddY ), aText ); + + nPosY = nEndY; + } + } +} + +void ScPrintFunc::LocateColHdr( SCCOL nX1, SCCOL nX2, tools::Long nScrX, tools::Long nScrY, + bool bRepCol, ScPreviewLocationData& rLocationData ) +{ + Size aOnePixel = pDev->PixelToLogic(Size(1,1)); + tools::Long nOneX = aOnePixel.Width(); + tools::Long nOneY = aOnePixel.Height(); + + tools::Long nHeight = static_cast(PRINT_HEADER_HEIGHT * nScaleY); + tools::Long nEndY = nScrY + nHeight - nOneY; + + tools::Long nPosX = nScrX - nOneX; + for (SCCOL nCol=nX1; nCol<=nX2; nCol++) + { + sal_uInt16 nDocW = rDoc.GetColWidth( nCol, nPrintTab ); + if (nDocW) + nPosX += static_cast(nDocW * nScaleX); + } + tools::Rectangle aCellRect( nScrX, nScrY, nPosX, nEndY ); + rLocationData.AddColHeaders( aCellRect, nX1, nX2, bRepCol ); +} + +void ScPrintFunc::LocateRowHdr( SCROW nY1, SCROW nY2, tools::Long nScrX, tools::Long nScrY, + bool bRepRow, ScPreviewLocationData& rLocationData ) +{ + Size aOnePixel = pDev->PixelToLogic(Size(1,1)); + tools::Long nOneX = aOnePixel.Width(); + tools::Long nOneY = aOnePixel.Height(); + + bool bLayoutRTL = rDoc.IsLayoutRTL( nPrintTab ); + + tools::Long nWidth = static_cast(PRINT_HEADER_WIDTH * nScaleX); + tools::Long nEndX = nScrX + nWidth; + if ( !bLayoutRTL ) + nEndX -= nOneX; + + tools::Long nPosY = nScrY - nOneY; + nPosY += rDoc.GetScaledRowHeight( nY1, nY2, nPrintTab, nScaleY); + tools::Rectangle aCellRect( nScrX, nScrY, nEndX, nPosY ); + rLocationData.AddRowHeaders( aCellRect, nY1, nY2, bRepRow ); +} + +void ScPrintFunc::LocateArea( SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2, + tools::Long nScrX, tools::Long nScrY, bool bRepCol, bool bRepRow, + ScPreviewLocationData& rLocationData ) +{ + // get MapMode for drawing objects (same MapMode as in ScOutputData::PrintDrawingLayer) + + Point aLogPos = OutputDevice::LogicToLogic(Point(nScrX,nScrY), aOffsetMode, aLogicMode); + tools::Long nLogStX = aLogPos.X(); + tools::Long nLogStY = aLogPos.Y(); + + SCCOL nCol; + Point aTwipOffset; + for (nCol=0; nColPixelToLogic(Size(1,1)); + tools::Long nOneX = aOnePixel.Width(); + tools::Long nOneY = aOnePixel.Height(); + + tools::Long nPosX = nScrX - nOneX; + for (nCol=nX1; nCol<=nX2; nCol++) + { + sal_uInt16 nDocW = rDoc.GetColWidth( nCol, nPrintTab ); + if (nDocW) + nPosX += static_cast(nDocW * nScaleX); + } + + tools::Long nPosY = nScrY - nOneY; + nPosY += rDoc.GetScaledRowHeight( nY1, nY2, nPrintTab, nScaleY); + tools::Rectangle aCellRect( nScrX, nScrY, nPosX, nPosY ); + rLocationData.AddCellRange( aCellRect, ScRange( nX1,nY1,nPrintTab, nX2,nY2,nPrintTab ), + bRepCol, bRepRow, aDrawMapMode ); +} + +void ScPrintFunc::PrintArea( SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2, + tools::Long nScrX, tools::Long nScrY, + bool bShLeft, bool bShTop, bool bShRight, bool bShBottom ) +{ + // #i47547# nothing to do if the end of the print area is before the end of + // the repeat columns/rows (don't use negative size for ScOutputData) + if ( nX2 < nX1 || nY2 < nY1 ) + return; + + //! hand over Flag at FillInfo !!!!! + ScRange aERange; + bool bEmbed = rDoc.IsEmbedded(); + if (bEmbed) + { + rDoc.GetEmbedded(aERange); + rDoc.ResetEmbedded(); + } + + Point aPos = OutputDevice::LogicToLogic(Point(nScrX,nScrY), aOffsetMode, aLogicMode); + tools::Long nLogStX = aPos.X(); + tools::Long nLogStY = aPos.Y(); + + // Assemble data + + ScTableInfo aTabInfo; + rDoc.FillInfo( aTabInfo, nX1, nY1, nX2, nY2, nPrintTab, + nScaleX, nScaleY, true, aTableParam.bFormulas ); + lcl_HidePrint( aTabInfo, nX1, nX2 ); + + if (bEmbed) + rDoc.SetEmbedded(aERange); + + ScOutputData aOutputData( pDev, OUTTYPE_PRINTER, aTabInfo, &rDoc, nPrintTab, + nScrX, nScrY, nX1, nY1, nX2, nY2, nScaleX, nScaleY ); + + aOutputData.SetDrawView( pDrawView ); + + // test if all paint parts are hidden, then a paint is not necessary at all + const Point aMMOffset(aOutputData.PrePrintDrawingLayer(nLogStX, nLogStY)); + const bool bHideAllDrawingLayer( pDrawView && pDrawView->getHideOle() && pDrawView->getHideChart() + && pDrawView->getHideDraw() && pDrawView->getHideFormControl() ); + + if(!bHideAllDrawingLayer) + { + pDev->SetMapMode(aLogicMode); + // don's set Clipping here (Mapmode is being moved) + + // #i72502# + aOutputData.PrintDrawingLayer(SC_LAYER_BACK, aMMOffset); + } + + pDev->SetMapMode(aOffsetMode); + + aOutputData.SetShowFormulas( aTableParam.bFormulas ); + aOutputData.SetShowNullValues( aTableParam.bNullVals ); + aOutputData.SetUseStyleColor( bUseStyleColor ); + + Color aGridColor( COL_BLACK ); + if ( bUseStyleColor ) + aGridColor = SC_MOD()->GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor; + aOutputData.SetGridColor( aGridColor ); + + if ( !pPrinter ) + { + OutputDevice* pRefDev = rDoc.GetPrinter(); // use the printer also for Preview + Fraction aPrintFrac( nZoom, 100 ); // without nManualZoom + // MapMode, as it would arrive at the printer: + pRefDev->SetMapMode( MapMode( MapUnit::Map100thMM, Point(), aPrintFrac, aPrintFrac ) ); + + // when rendering (PDF), don't use printer as ref device, but printer's MapMode + // has to be set anyway, as charts still use it (#106409#) + if ( !bIsRender ) + aOutputData.SetRefDevice( pRefDev ); + } + + if( aTableParam.bCellContent ) + aOutputData.DrawBackground(*pDev); + + pDev->SetClipRegion(vcl::Region(tools::Rectangle( + aPos, Size(aOutputData.GetScrW(), aOutputData.GetScrH())))); + pDev->SetClipRegion(); + + if( aTableParam.bCellContent ) + { + aOutputData.DrawExtraShadow( bShLeft, bShTop, bShRight, bShBottom ); + aOutputData.DrawFrame(*pDev); + aOutputData.DrawSparklines(*pDev); + aOutputData.DrawStrings(); + aOutputData.DrawEdit(false); + } + + if (aTableParam.bGrid) + aOutputData.DrawGrid(*pDev, true, false); // no page breaks + + aOutputData.AddPDFNotes(); // has no effect if not rendering PDF with notes enabled + + // test if all paint parts are hidden, then a paint is not necessary at all + if(!bHideAllDrawingLayer) + { + // #i72502# + aOutputData.PrintDrawingLayer(SC_LAYER_FRONT, aMMOffset); + } + + // #i72502# + aOutputData.PrintDrawingLayer(SC_LAYER_INTERN, aMMOffset); + aOutputData.PostPrintDrawingLayer(aMMOffset); // #i74768# +} + +bool ScPrintFunc::IsMirror( tools::Long nPageNo ) // Mirror margins? +{ + return nPageUsage == SvxPageUsage::Mirror && (nPageNo & 1); +} + +bool ScPrintFunc::IsLeft( tools::Long nPageNo ) // left foot notes? +{ + bool bLeft; + if (nPageUsage == SvxPageUsage::Left) + bLeft = true; + else if (nPageUsage == SvxPageUsage::Right) + bLeft = false; + else + bLeft = (nPageNo & 1) != 0; + return bLeft; +} + +void ScPrintFunc::MakeTableString() +{ + OUString aTmp; + rDoc.GetName(nPrintTab, aTmp); + aFieldData.aTabName = aTmp; +} + +void ScPrintFunc::MakeEditEngine() +{ + if (!pEditEngine) + { + // can't use document's edit engine pool here, + // because pool must have twips as default metric + pEditEngine.reset( new ScHeaderEditEngine( EditEngine::CreatePool().get() ) ); + + pEditEngine->EnableUndo(false); + //fdo#45869 we want text to be positioned as it would be for the + //high dpi printed output, not as would be ideal for the 96dpi preview + //window itself + pEditEngine->SetRefDevice(pPrinter ? pPrinter : rDoc.GetRefDevice()); + pEditEngine->SetWordDelimiters( + ScEditUtil::ModifyDelimiters( pEditEngine->GetWordDelimiters() ) ); + pEditEngine->SetControlWord( pEditEngine->GetControlWord() & ~EEControlBits::RTFSTYLESHEETS ); + rDoc.ApplyAsianEditSettings( *pEditEngine ); + pEditEngine->EnableAutoColor( bUseStyleColor ); + + // Default-Set for alignment + pEditDefaults.reset( new SfxItemSet( pEditEngine->GetEmptyItemSet() ) ); + + const ScPatternAttr& rPattern = rDoc.GetPool()->GetDefaultItem(ATTR_PATTERN); + rPattern.FillEditItemSet( pEditDefaults.get() ); + // FillEditItemSet adjusts font height to 1/100th mm, + // but for header/footer twips is needed, as in the PatternAttr: + pEditDefaults->Put( rPattern.GetItem(ATTR_FONT_HEIGHT).CloneSetWhich(EE_CHAR_FONTHEIGHT) ); + pEditDefaults->Put( rPattern.GetItem(ATTR_CJK_FONT_HEIGHT).CloneSetWhich(EE_CHAR_FONTHEIGHT_CJK) ); + pEditDefaults->Put( rPattern.GetItem(ATTR_CTL_FONT_HEIGHT).CloneSetWhich(EE_CHAR_FONTHEIGHT_CTL) ); + // don't use font color, because background color is not used + //! there's no way to set the background for note pages + pEditDefaults->ClearItem( EE_CHAR_COLOR ); + if (ScGlobal::IsSystemRTL()) + pEditDefaults->Put( SvxFrameDirectionItem( SvxFrameDirection::Horizontal_RL_TB, EE_PARA_WRITINGDIR ) ); + } + + pEditEngine->SetData( aFieldData ); // Set page count etc. +} + +// nStartY = logic +void ScPrintFunc::PrintHF( tools::Long nPageNo, bool bHeader, tools::Long nStartY, + bool bDoPrint, ScPreviewLocationData* pLocationData ) +{ + const ScPrintHFParam& rParam = bHeader ? aHdr : aFtr; + + pDev->SetMapMode( aTwipMode ); // Head-/Footlines in Twips + + bool bFirst = 0 == nPageNo && !rParam.bSharedFirst; + bool bLeft = IsLeft(nPageNo) && !rParam.bShared; + const ScPageHFItem* pHFItem = bFirst ? rParam.pFirst : (bLeft ? rParam.pLeft : rParam.pRight); + + tools::Long nLineStartX = aPageRect.Left() + rParam.nLeft; + tools::Long nLineEndX = aPageRect.Right() - rParam.nRight; + tools::Long nLineWidth = nLineEndX - nLineStartX + 1; + + // Edit-Engine + + Point aStart( nLineStartX, nStartY ); + Size aPaperSize( nLineWidth, rParam.nHeight-rParam.nDistance ); + if ( rParam.pBorder ) + { + tools::Long nLeft = lcl_LineTotal( rParam.pBorder->GetLeft() ) + rParam.pBorder->GetDistance(SvxBoxItemLine::LEFT); + tools::Long nTop = lcl_LineTotal( rParam.pBorder->GetTop() ) + rParam.pBorder->GetDistance(SvxBoxItemLine::TOP); + aStart.AdjustX(nLeft ); + aStart.AdjustY(nTop ); + aPaperSize.AdjustWidth( -(nLeft + lcl_LineTotal( rParam.pBorder->GetRight() ) + rParam.pBorder->GetDistance(SvxBoxItemLine::RIGHT)) ); + aPaperSize.AdjustHeight( -(nTop + lcl_LineTotal( rParam.pBorder->GetBottom() ) + rParam.pBorder->GetDistance(SvxBoxItemLine::BOTTOM)) ); + } + + if ( rParam.pShadow && rParam.pShadow->GetLocation() != SvxShadowLocation::NONE ) + { + tools::Long nLeft = rParam.pShadow->CalcShadowSpace(SvxShadowItemSide::LEFT); + tools::Long nTop = rParam.pShadow->CalcShadowSpace(SvxShadowItemSide::TOP); + aStart.AdjustX(nLeft ); + aStart.AdjustY(nTop ); + aPaperSize.AdjustWidth( -(nLeft + rParam.pShadow->CalcShadowSpace(SvxShadowItemSide::RIGHT)) ); + aPaperSize.AdjustHeight( -(nTop + rParam.pShadow->CalcShadowSpace(SvxShadowItemSide::BOTTOM)) ); + } + + aFieldData.nPageNo = nPageNo+aTableParam.nFirstPageNo; + MakeEditEngine(); + + pEditEngine->SetPaperSize(aPaperSize); + + // Frame / Background + + Point aBorderStart( nLineStartX, nStartY ); + Size aBorderSize( nLineWidth, rParam.nHeight-rParam.nDistance ); + if ( rParam.bDynamic ) + { + // adjust here again, for even/odd head-/footlines + // and probably other breaks by variable (page number etc.) + + tools::Long nMaxHeight = 0; + nMaxHeight = std::max( nMaxHeight, TextHeight( pHFItem->GetLeftArea() ) ); + nMaxHeight = std::max( nMaxHeight, TextHeight( pHFItem->GetCenterArea() ) ); + nMaxHeight = std::max( nMaxHeight, TextHeight( pHFItem->GetRightArea() ) ); + if (rParam.pBorder) + nMaxHeight += lcl_LineTotal( rParam.pBorder->GetTop() ) + + lcl_LineTotal( rParam.pBorder->GetBottom() ) + + rParam.pBorder->GetDistance(SvxBoxItemLine::TOP) + + rParam.pBorder->GetDistance(SvxBoxItemLine::BOTTOM); + if (rParam.pShadow && rParam.pShadow->GetLocation() != SvxShadowLocation::NONE) + nMaxHeight += rParam.pShadow->CalcShadowSpace(SvxShadowItemSide::TOP) + + rParam.pShadow->CalcShadowSpace(SvxShadowItemSide::BOTTOM); + + if (nMaxHeight < rParam.nManHeight-rParam.nDistance) + nMaxHeight = rParam.nManHeight-rParam.nDistance; // configured Minimum + + aBorderSize.setHeight( nMaxHeight ); + } + + if ( bDoPrint ) + { + double nOldScaleX = nScaleX; + double nOldScaleY = nScaleY; + nScaleX = nScaleY = 1.0; // output directly in Twips + DrawBorder( aBorderStart.X(), aBorderStart.Y(), aBorderSize.Width(), aBorderSize.Height(), + rParam.pBorder, rParam.pBack, rParam.pShadow ); + nScaleX = nOldScaleX; + nScaleY = nOldScaleY; + + // Clipping for Text + + pDev->SetClipRegion(vcl::Region(tools::Rectangle(aStart, aPaperSize))); + + // left + + const EditTextObject* pObject = pHFItem->GetLeftArea(); + if (pObject) + { + pEditDefaults->Put( SvxAdjustItem( SvxAdjust::Left, EE_PARA_JUST ) ); + pEditEngine->SetTextNewDefaults( *pObject, *pEditDefaults, false ); + Point aDraw = aStart; + tools::Long nDif = aPaperSize.Height() - static_cast(pEditEngine->GetTextHeight()); + if (nDif > 0) + aDraw.AdjustY(nDif / 2 ); + pEditEngine->Draw(*pDev, aDraw); + } + + // center + + pObject = pHFItem->GetCenterArea(); + if (pObject) + { + pEditDefaults->Put( SvxAdjustItem( SvxAdjust::Center, EE_PARA_JUST ) ); + pEditEngine->SetTextNewDefaults( *pObject, *pEditDefaults, false ); + Point aDraw = aStart; + tools::Long nDif = aPaperSize.Height() - static_cast(pEditEngine->GetTextHeight()); + if (nDif > 0) + aDraw.AdjustY(nDif / 2 ); + pEditEngine->Draw(*pDev, aDraw); + } + + // right + + pObject = pHFItem->GetRightArea(); + if (pObject) + { + pEditDefaults->Put( SvxAdjustItem( SvxAdjust::Right, EE_PARA_JUST ) ); + pEditEngine->SetTextNewDefaults( *pObject, *pEditDefaults, false ); + Point aDraw = aStart; + tools::Long nDif = aPaperSize.Height() - static_cast(pEditEngine->GetTextHeight()); + if (nDif > 0) + aDraw.AdjustY(nDif / 2 ); + pEditEngine->Draw(*pDev, aDraw); + } + + pDev->SetClipRegion(); + } + + if ( pLocationData ) + { + tools::Rectangle aHeaderRect( aBorderStart, aBorderSize ); + pLocationData->AddHeaderFooter( aHeaderRect, bHeader, bLeft ); + } +} + +tools::Long ScPrintFunc::DoNotes( tools::Long nNoteStart, bool bDoPrint, ScPreviewLocationData* pLocationData ) +{ + if (bDoPrint) + pDev->SetMapMode(aTwipMode); + + MakeEditEngine(); + pEditDefaults->Put( SvxAdjustItem( SvxAdjust::Left, EE_PARA_JUST ) ); + pEditEngine->SetDefaults( *pEditDefaults ); + + vcl::Font aMarkFont; + ScAutoFontColorMode eColorMode = bUseStyleColor ? ScAutoFontColorMode::Display : ScAutoFontColorMode::Print; + rDoc.GetPool()->GetDefaultItem(ATTR_PATTERN).fillFont(aMarkFont, eColorMode); + pDev->SetFont(aMarkFont); + tools::Long nMarkLen = pDev->GetTextWidth("GW99999:"); + // without Space-Char, because it rarely arrives there + + Size aDataSize = aPageRect.GetSize(); + if ( nMarkLen > aDataSize.Width() / 2 ) // everything much too small? + nMarkLen = aDataSize.Width() / 2; // split the page appropriately + aDataSize.AdjustWidth( -nMarkLen ); + + pEditEngine->SetPaperSize( aDataSize ); + tools::Long nPosX = aPageRect.Left() + nMarkLen; + tools::Long nPosY = aPageRect.Top(); + + tools::Long nCount = 0; + tools::Long nSize = aNotePosList.size(); + bool bOk; + do + { + bOk = false; + if ( nNoteStart + nCount < nSize) + { + ScAddress &rPos = aNotePosList[ nNoteStart + nCount ]; + + if( const ScPostIt* pNote = rDoc.GetNote( rPos ) ) + { + if(const EditTextObject *pEditText = pNote->GetEditTextObject()) + pEditEngine->SetTextCurrentDefaults(*pEditText); + tools::Long nTextHeight = pEditEngine->GetTextHeight(); + if ( nPosY + nTextHeight < aPageRect.Bottom() ) + { + if (bDoPrint) + { + pEditEngine->Draw(*pDev, Point(nPosX, nPosY)); + + OUString aMarkStr(rPos.Format(ScRefFlags::VALID, &rDoc, rDoc.GetAddressConvention()) + ":"); + + // cell position also via EditEngine, for correct positioning + pEditEngine->SetTextCurrentDefaults(aMarkStr); + pEditEngine->Draw(*pDev, Point(aPageRect.Left(), nPosY)); + } + + if ( pLocationData ) + { + tools::Rectangle aTextRect( Point( nPosX, nPosY ), Size( aDataSize.Width(), nTextHeight ) ); + pLocationData->AddNoteText( aTextRect, rPos ); + tools::Rectangle aMarkRect( Point( aPageRect.Left(), nPosY ), Size( nMarkLen, nTextHeight ) ); + pLocationData->AddNoteMark( aMarkRect, rPos ); + } + + nPosY += nTextHeight; + nPosY += 200; // Distance + ++nCount; + bOk = true; + } + } + } + } + while (bOk); + + return nCount; +} + +tools::Long ScPrintFunc::PrintNotes( tools::Long nPageNo, tools::Long nNoteStart, bool bDoPrint, ScPreviewLocationData* pLocationData ) +{ + if ( nNoteStart >= static_cast(aNotePosList.size()) || !aTableParam.bNotes ) + return 0; + + if ( bDoPrint && bClearWin ) + { + //! aggregate PrintPage !!! + + Color aBackgroundColor( COL_WHITE ); + if ( bUseStyleColor ) + aBackgroundColor = SC_MOD()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor; + + pDev->SetMapMode(aOffsetMode); + pDev->SetLineColor(); + pDev->SetFillColor(aBackgroundColor); + pDev->DrawRect(tools::Rectangle(Point(), + Size(static_cast(aPageSize.Width() * nScaleX * 100 / nZoom), + static_cast(aPageSize.Height() * nScaleY * 100 / nZoom)))); + } + + // adjust aPageRect for left/right page + + tools::Rectangle aTempRect( Point(), aPageSize ); + if (IsMirror(nPageNo)) + { + aPageRect.SetLeft( ( aTempRect.Left() + nRightMargin ) * 100 / nZoom ); + aPageRect.SetRight( ( aTempRect.Right() - nLeftMargin ) * 100 / nZoom ); + } + else + { + aPageRect.SetLeft( ( aTempRect.Left() + nLeftMargin ) * 100 / nZoom ); + aPageRect.SetRight( ( aTempRect.Right() - nRightMargin ) * 100 / nZoom ); + } + + if ( pPrinter && bDoPrint ) + { + OSL_FAIL( "StartPage does not exist anymore" ); + } + + if ( bDoPrint || pLocationData ) + { + // Head and foot lines + + if (aHdr.bEnable) + { + tools::Long nHeaderY = aPageRect.Top()-aHdr.nHeight; + PrintHF( nPageNo, true, nHeaderY, bDoPrint, pLocationData ); + } + if (aFtr.bEnable) + { + tools::Long nFooterY = aPageRect.Bottom()+aFtr.nDistance; + PrintHF( nPageNo, false, nFooterY, bDoPrint, pLocationData ); + } + } + + tools::Long nCount = DoNotes( nNoteStart, bDoPrint, pLocationData ); + + if ( pPrinter && bDoPrint ) + { + OSL_FAIL( "EndPage does not exist anymore" ); + } + + return nCount; +} + +void ScPrintFunc::PrintPage( tools::Long nPageNo, SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2, + bool bDoPrint, ScPreviewLocationData* pLocationData ) +{ + bool bLayoutRTL = rDoc.IsLayoutRTL( nPrintTab ); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + // nPageNo is the page number within all sheets of one "start page" setting + + if ( bClearWin && bDoPrint ) + { + // must exactly fit to painting the frame in preview.cxx !!! + + Color aBackgroundColor( COL_WHITE ); + if ( bUseStyleColor ) + aBackgroundColor = SC_MOD()->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor; + + pDev->SetMapMode(aOffsetMode); + pDev->SetLineColor(); + pDev->SetFillColor(aBackgroundColor); + pDev->DrawRect(tools::Rectangle(Point(), + Size(static_cast(aPageSize.Width() * nScaleX * 100 / nZoom), + static_cast(aPageSize.Height() * nScaleY * 100 / nZoom)))); + } + + // adjust aPageRect for left/right page + + tools::Rectangle aTempRect( Point(), aPageSize ); + if (IsMirror(nPageNo)) + { + aPageRect.SetLeft( ( aTempRect.Left() + nRightMargin ) * 100 / nZoom ); + aPageRect.SetRight( ( aTempRect.Right() - nLeftMargin ) * 100 / nZoom ); + } + else + { + aPageRect.SetLeft( ( aTempRect.Left() + nLeftMargin ) * 100 / nZoom ); + aPageRect.SetRight( ( aTempRect.Right() - nRightMargin ) * 100 / nZoom ); + } + + if ( aAreaParam.bRepeatCol ) + if ( nX1 > nRepeatStartCol && nX1 <= nRepeatEndCol ) + nX1 = nRepeatEndCol + 1; + bool bDoRepCol = (aAreaParam.bRepeatCol && nX1 > nRepeatEndCol); + if ( aAreaParam.bRepeatRow ) + if ( nY1 > nRepeatStartRow && nY1 <= nRepeatEndRow ) + nY1 = nRepeatEndRow + 1; + bool bDoRepRow = (aAreaParam.bRepeatRow && nY1 > nRepeatEndRow); + + // use new object hide flags in SdrPaintView + if(pDrawView) + { + pDrawView->setHideOle(!aTableParam.bObjects); + pDrawView->setHideChart(!aTableParam.bCharts); + pDrawView->setHideDraw(!aTableParam.bDrawings); + pDrawView->setHideFormControl(!aTableParam.bDrawings); + } + + if ( pPrinter && bDoPrint ) + { + OSL_FAIL( "StartPage does not exist anymore" ); + } + + // head and foot lines (without centering) + + if (aHdr.bEnable) + { + tools::Long nHeaderY = aPageRect.Top()-aHdr.nHeight; + PrintHF( nPageNo, true, nHeaderY, bDoPrint, pLocationData ); + } + if (aFtr.bEnable) + { + tools::Long nFooterY = aPageRect.Bottom()+aFtr.nDistance; + PrintHF( nPageNo, false, nFooterY, bDoPrint, pLocationData ); + } + + // Position ( margins / centering ) + + tools::Long nLeftSpace = aPageRect.Left(); // Document-Twips + tools::Long nTopSpace = aPageRect.Top(); + if ( bCenterHor || bLayoutRTL ) + { + tools::Long nDataWidth = 0; + SCCOL i; + for (i=nX1; i<=nX2; i++) + nDataWidth += rDoc.GetColWidth( i,nPrintTab ); + if (bDoRepCol) + for (i=nRepeatStartCol; i<=nRepeatEndCol; i++) + nDataWidth += rDoc.GetColWidth( i,nPrintTab ); + if (aTableParam.bHeaders) + nDataWidth += tools::Long(PRINT_HEADER_WIDTH); + if (pBorderItem) + nDataWidth += pBorderItem->GetDistance(SvxBoxItemLine::LEFT) + + pBorderItem->GetDistance(SvxBoxItemLine::RIGHT); //! Line width? + if (pShadowItem && pShadowItem->GetLocation() != SvxShadowLocation::NONE) + nDataWidth += pShadowItem->CalcShadowSpace(SvxShadowItemSide::LEFT) + + pShadowItem->CalcShadowSpace(SvxShadowItemSide::RIGHT); + if ( bCenterHor ) + { + nLeftSpace += ( aPageRect.GetWidth() - nDataWidth ) / 2; // LTR or RTL + if (pBorderItem) + nLeftSpace -= lcl_LineTotal(pBorderItem->GetLeft()); + } + else if ( bLayoutRTL ) + nLeftSpace += aPageRect.GetWidth() - nDataWidth; // align to the right edge of the page + } + if ( bCenterVer ) + { + tools::Long nDataHeight = rDoc.GetRowHeight( nY1, nY2, nPrintTab); + if (bDoRepRow) + nDataHeight += rDoc.GetRowHeight( nRepeatStartRow, + nRepeatEndRow, nPrintTab); + if (aTableParam.bHeaders) + nDataHeight += tools::Long(PRINT_HEADER_HEIGHT); + if (pBorderItem) + nDataHeight += pBorderItem->GetDistance(SvxBoxItemLine::TOP) + + pBorderItem->GetDistance(SvxBoxItemLine::BOTTOM); //! Line width? + if (pShadowItem && pShadowItem->GetLocation() != SvxShadowLocation::NONE) + nDataHeight += pShadowItem->CalcShadowSpace(SvxShadowItemSide::TOP) + + pShadowItem->CalcShadowSpace(SvxShadowItemSide::BOTTOM); + nTopSpace += ( aPageRect.GetHeight() - nDataHeight ) / 2; + if (pBorderItem) + nTopSpace -= lcl_LineTotal(pBorderItem->GetTop()); + } + + // calculate sizes of the elements for partitioning + // (header, repeat, data) + + tools::Long nHeaderWidth = 0; + tools::Long nHeaderHeight = 0; + tools::Long nRepeatWidth = 0; + tools::Long nRepeatHeight = 0; + tools::Long nContentWidth = 0; // scaled - not the same as nDataWidth above + tools::Long nContentHeight = 0; + if (aTableParam.bHeaders) + { + nHeaderWidth = static_cast(PRINT_HEADER_WIDTH * nScaleX); + nHeaderHeight = static_cast(PRINT_HEADER_HEIGHT * nScaleY); + } + if (bDoRepCol) + for (SCCOL i=nRepeatStartCol; i<=nRepeatEndCol; i++) + nRepeatWidth += static_cast(rDoc.GetColWidth(i,nPrintTab) * nScaleX); + if (bDoRepRow) + nRepeatHeight += rDoc.GetScaledRowHeight( nRepeatStartRow, + nRepeatEndRow, nPrintTab, nScaleY); + for (SCCOL i=nX1; i<=nX2; i++) + nContentWidth += static_cast(rDoc.GetColWidth(i,nPrintTab) * nScaleX); + nContentHeight += rDoc.GetScaledRowHeight( nY1, nY2, nPrintTab, + nScaleY); + + // partition the page + + tools::Long nStartX = static_cast( nLeftSpace * nScaleX ); + tools::Long nStartY = static_cast( nTopSpace * nScaleY ); + tools::Long nInnerStartX = nStartX; + tools::Long nInnerStartY = nStartY; + if (pBorderItem) + { + nInnerStartX += static_cast( ( lcl_LineTotal(pBorderItem->GetLeft()) + + pBorderItem->GetDistance(SvxBoxItemLine::LEFT) ) * nScaleX ); + nInnerStartY += static_cast( ( lcl_LineTotal(pBorderItem->GetTop()) + + pBorderItem->GetDistance(SvxBoxItemLine::TOP) ) * nScaleY ); + } + if (pShadowItem && pShadowItem->GetLocation() != SvxShadowLocation::NONE) + { + nInnerStartX += static_cast( pShadowItem->CalcShadowSpace(SvxShadowItemSide::LEFT) * nScaleX ); + nInnerStartY += static_cast( pShadowItem->CalcShadowSpace(SvxShadowItemSide::TOP) * nScaleY ); + } + + if ( bLayoutRTL ) + { + // arrange elements starting from the right edge + nInnerStartX += nHeaderWidth + nRepeatWidth + nContentWidth; + + // make rounding easier so the elements are really next to each other in preview + Size aOffsetOnePixel = pDev->PixelToLogic( Size(1,1), aOffsetMode ); + tools::Long nOffsetOneX = aOffsetOnePixel.Width(); + nInnerStartX += nOffsetOneX / 2; + } + + tools::Long nFrameStartX = nInnerStartX; + tools::Long nFrameStartY = nInnerStartY; + + tools::Long nRepStartX = nInnerStartX + nHeaderWidth * nLayoutSign; // widths/heights are 0 if not used + tools::Long nRepStartY = nInnerStartY + nHeaderHeight; + tools::Long nDataX = nRepStartX + nRepeatWidth * nLayoutSign; + tools::Long nDataY = nRepStartY + nRepeatHeight; + tools::Long nEndX = nDataX + nContentWidth * nLayoutSign; + tools::Long nEndY = nDataY + nContentHeight; + tools::Long nFrameEndX = nEndX; + tools::Long nFrameEndY = nEndY; + + if ( bLayoutRTL ) + { + // each element's start position is its left edge + //! subtract one pixel less? + nInnerStartX -= nHeaderWidth; // used for header + nRepStartX -= nRepeatWidth; + nDataX -= nContentWidth; + + // continue right of the main elements again + nEndX += nHeaderWidth + nRepeatWidth + nContentWidth; + } + + // Page frame / background + + //! adjust nEndX/Y + + tools::Long nBorderEndX = nEndX; + tools::Long nBorderEndY = nEndY; + if (pBorderItem) + { + nBorderEndX += static_cast( ( lcl_LineTotal(pBorderItem->GetRight()) + + pBorderItem->GetDistance(SvxBoxItemLine::RIGHT) ) * nScaleX ); + nBorderEndY += static_cast( ( lcl_LineTotal(pBorderItem->GetBottom()) + + pBorderItem->GetDistance(SvxBoxItemLine::BOTTOM) ) * nScaleY ); + } + if (pShadowItem && pShadowItem->GetLocation() != SvxShadowLocation::NONE) + { + nBorderEndX += static_cast( pShadowItem->CalcShadowSpace(SvxShadowItemSide::RIGHT) * nScaleX ); + nBorderEndY += static_cast( pShadowItem->CalcShadowSpace(SvxShadowItemSide::BOTTOM) * nScaleY ); + } + + if ( bDoPrint ) + { + pDev->SetMapMode( aOffsetMode ); + DrawBorder( nStartX, nStartY, nBorderEndX-nStartX, nBorderEndY-nStartY, + pBorderItem, pBackgroundItem, pShadowItem ); + + pDev->SetMapMode( aTwipMode ); + } + + pDev->SetMapMode( aOffsetMode ); + + // Output repeating rows/columns + + if (bDoRepCol && bDoRepRow) + { + if ( bDoPrint ) + PrintArea( nRepeatStartCol,nRepeatStartRow, nRepeatEndCol,nRepeatEndRow, + nRepStartX,nRepStartY, true, true, false, false ); + if ( pLocationData ) + LocateArea( nRepeatStartCol,nRepeatStartRow, nRepeatEndCol,nRepeatEndRow, + nRepStartX,nRepStartY, true, true, *pLocationData ); + } + if (bDoRepCol) + { + if ( bDoPrint ) + PrintArea( nRepeatStartCol,nY1, nRepeatEndCol,nY2, nRepStartX,nDataY, + true, !bDoRepRow, false, true ); + if ( pLocationData ) + LocateArea( nRepeatStartCol,nY1, nRepeatEndCol,nY2, nRepStartX,nDataY, true, false, *pLocationData ); + } + if (bDoRepRow) + { + if ( bDoPrint ) + PrintArea( nX1,nRepeatStartRow, nX2,nRepeatEndRow, nDataX,nRepStartY, + !bDoRepCol, true, true, false ); + if ( pLocationData ) + LocateArea( nX1,nRepeatStartRow, nX2,nRepeatEndRow, nDataX,nRepStartY, false, true, *pLocationData ); + } + + // output data + + if ( bDoPrint ) + PrintArea( nX1,nY1, nX2,nY2, nDataX,nDataY, !bDoRepCol,!bDoRepRow, true, true ); + if ( pLocationData ) + LocateArea( nX1,nY1, nX2,nY2, nDataX,nDataY, false,false, *pLocationData ); + + // output column/row headers + // after data (through probably shadow) + + Color aGridColor( COL_BLACK ); + if ( bUseStyleColor ) + aGridColor = SC_MOD()->GetColorConfig().GetColorValue(svtools::FONTCOLOR).nColor; + + if (aTableParam.bHeaders) + { + if ( bDoPrint ) + { + pDev->SetLineColor( aGridColor ); + pDev->SetFillColor(); + pDev->SetMapMode(aOffsetMode); + } + + ScPatternAttr aPattern( rDoc.GetPool() ); + vcl::Font aFont; + ScAutoFontColorMode eColorMode = bUseStyleColor ? ScAutoFontColorMode::Display : ScAutoFontColorMode::Print; + aPattern.fillFont(aFont, eColorMode, pDev); + pDev->SetFont(aFont); + + if (bDoRepCol) + { + if ( bDoPrint ) + PrintColHdr( nRepeatStartCol,nRepeatEndCol, nRepStartX,nInnerStartY ); + if ( pLocationData ) + LocateColHdr( nRepeatStartCol,nRepeatEndCol, nRepStartX,nInnerStartY, true, *pLocationData ); + } + if ( bDoPrint ) + PrintColHdr( nX1,nX2, nDataX,nInnerStartY ); + if ( pLocationData ) + LocateColHdr( nX1,nX2, nDataX,nInnerStartY, false, *pLocationData ); + if (bDoRepRow) + { + if ( bDoPrint ) + PrintRowHdr( nRepeatStartRow,nRepeatEndRow, nInnerStartX,nRepStartY ); + if ( pLocationData ) + LocateRowHdr( nRepeatStartRow,nRepeatEndRow, nInnerStartX,nRepStartY, true, *pLocationData ); + } + if ( bDoPrint ) + PrintRowHdr( nY1,nY2, nInnerStartX,nDataY ); + if ( pLocationData ) + LocateRowHdr( nY1,nY2, nInnerStartX,nDataY, false, *pLocationData ); + } + + // simple frame + + if ( bDoPrint && ( aTableParam.bGrid || aTableParam.bHeaders ) ) + { + Size aOnePixel = pDev->PixelToLogic(Size(1,1)); + tools::Long nOneX = aOnePixel.Width(); + tools::Long nOneY = aOnePixel.Height(); + + tools::Long nLeftX = nFrameStartX; + tools::Long nTopY = nFrameStartY - nOneY; + tools::Long nRightX = nFrameEndX; + tools::Long nBottomY = nFrameEndY - nOneY; + if ( !bLayoutRTL ) + { + nLeftX -= nOneX; + nRightX -= nOneX; + } + pDev->SetMapMode(aOffsetMode); + pDev->SetLineColor( aGridColor ); + pDev->SetFillColor(); + pDev->DrawRect( tools::Rectangle( nLeftX, nTopY, nRightX, nBottomY ) ); + // nEndX/Y without frame-adaptation + } + + if ( pPrinter && bDoPrint ) + { + OSL_FAIL( "EndPage does not exist anymore" ); + } + + aLastSourceRange = ScRange( nX1, nY1, nPrintTab, nX2, nY2, nPrintTab ); + bSourceRangeValid = true; +} + +void ScPrintFunc::SetOffset( const Point& rOfs ) +{ + aSrcOffset = rOfs; +} + +void ScPrintFunc::SetManualZoom( sal_uInt16 nNewZoom ) +{ + nManualZoom = nNewZoom; +} + +void ScPrintFunc::SetClearFlag( bool bFlag ) +{ + bClearWin = bFlag; +} + +void ScPrintFunc::SetUseStyleColor( bool bFlag ) +{ + bUseStyleColor = bFlag; + if (pEditEngine) + pEditEngine->EnableAutoColor( bUseStyleColor ); +} + +void ScPrintFunc::SetRenderFlag( bool bFlag ) +{ + bIsRender = bFlag; // set when using XRenderable (PDF) +} + +void ScPrintFunc::SetExclusivelyDrawOleAndDrawObjects() +{ + aTableParam.bCellContent = false; + aTableParam.bNotes = false; + aTableParam.bGrid = false; + aTableParam.bHeaders = false; + aTableParam.bFormulas = false; + aTableParam.bNullVals = false; +} + +// UpdatePages is only called from outside to set the breaks correctly for viewing +// - always without UserArea + +bool ScPrintFunc::UpdatePages() +{ + if (!pParamSet) + return false; + + // Zoom + + nZoom = 100; + if (aTableParam.bScalePageNum || aTableParam.bScaleTo) + nZoom = ZOOM_MIN; // correct for breaks + else if (aTableParam.bScaleAll) + { + nZoom = aTableParam.nScaleAll; + if ( nZoom <= ZOOM_MIN ) + nZoom = ZOOM_MIN; + } + + OUString aName = rDoc.GetPageStyle( nPrintTab ); + SCTAB nTabCount = rDoc.GetTableCount(); + for (SCTAB nTab=0; nTabPostPaint(0,0,nTab,rDoc.MaxCol(),rDoc.MaxRow(),nTab, PaintPartFlags::Grid); + } + + return true; +} + +tools::Long ScPrintFunc::CountPages() // sets also nPagesX, nPagesY +{ + bool bAreaOk = false; + + if (rDoc.HasTable( nPrintTab )) + { + if (aAreaParam.bPrintArea) // Specify print area? + { + if ( bPrintCurrentTable ) + { + ScRange& rRange = aAreaParam.aPrintArea; + + // Here, no comparison of the tables any more. Area is always valid for this table + // If comparison should be done here, the table of print ranges must be adjusted + // when inserting tables etc.! + + nStartCol = rRange.aStart.Col(); + nStartRow = rRange.aStart.Row(); + nEndCol = rRange.aEnd .Col(); + nEndRow = rRange.aEnd .Row(); + bAreaOk = AdjustPrintArea(false); // limit + } + else + bAreaOk = false; + } + else // search from document + bAreaOk = AdjustPrintArea(true); + } + + if (bAreaOk) + { + tools::Long nPages = 0; + size_t nY; + if (bMultiArea) + { + sal_uInt16 nRCount = rDoc.GetPrintRangeCount( nPrintTab ); + for (sal_uInt16 i=0; i(m_aRanges.m_nPagesX) * m_aRanges.m_nPagesY; + if ( pPageData ) + FillPageData(); + } + } + else + { + CalcZoom(RANGENO_NORANGE); // calculate Zoom + if ( aTableParam.bSkipEmpty ) + for (nY=0; nY(m_aRanges.m_nPagesX) * m_aRanges.m_nPagesY; + if ( pPageData ) + FillPageData(); + } + return nPages; + } + else + { + m_aRanges.m_nPagesX = m_aRanges.m_nPagesY = m_aRanges.m_nTotalY = 0; + return 0; + } +} + +tools::Long ScPrintFunc::CountNotePages() +{ + if ( !aTableParam.bNotes || !bPrintCurrentTable ) + return 0; + + bool bError = false; + if (!aAreaParam.bPrintArea) + bError = !AdjustPrintArea(true); // completely search in Doc + + sal_uInt16 nRepeats = 1; // how often go through it ? + if (bMultiArea) + nRepeats = rDoc.GetPrintRangeCount(nPrintTab); + if (bError) + nRepeats = 0; + + for (sal_uInt16 nStep=0; nStepaStart.Col(); + nStartRow = pThisRange->aStart.Row(); + nEndCol = pThisRange->aEnd .Col(); + nEndRow = pThisRange->aEnd .Row(); + bDoThis = AdjustPrintArea(false); + } + } + + if (bDoThis) + { + assert( bPrintAreaValid ); + for ( SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol ) + { + if (rDoc.HasColNotes(nCol, nPrintTab)) + { + for ( SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow ) + { + if ( rDoc.HasNote(nCol, nRow, nPrintTab) ) + aNotePosList.emplace_back( nCol, nRow, nPrintTab ); + } + } + } + } + } + + tools::Long nPages = 0; + tools::Long nNoteNr = 0; + tools::Long nNoteAdd; + do + { + nNoteAdd = PrintNotes( nPages, nNoteNr, false, nullptr ); + if (nNoteAdd) + { + nNoteNr += nNoteAdd; + ++nPages; + } + } + while (nNoteAdd); + + return nPages; +} + +void ScPrintFunc::InitModes() // set MapModes from nZoom etc. +{ + aOffset = Point( aSrcOffset.X()*100/nZoom, aSrcOffset.Y()*100/nZoom ); + + tools::Long nEffZoom = nZoom * static_cast(nManualZoom); + constexpr auto HMM_PER_TWIPS = o3tl::convert(1.0, o3tl::Length::twip, o3tl::Length::mm100); + nScaleX = nScaleY = HMM_PER_TWIPS; // output in 1/100 mm + + Fraction aZoomFract( nEffZoom,10000 ); + Fraction aHorFract = aZoomFract; + + if ( !pPrinter && !bIsRender ) // adjust scale for preview + { + double nFact = pDocShell->GetOutputFactor(); + aHorFract = Fraction( static_cast( nEffZoom / nFact ), 10000 ); + } + + aLogicMode = MapMode( MapUnit::Map100thMM, Point(), aHorFract, aZoomFract ); + + Point aLogicOfs( -aOffset.X(), -aOffset.Y() ); + aOffsetMode = MapMode( MapUnit::Map100thMM, aLogicOfs, aHorFract, aZoomFract ); + + Point aTwipsOfs( static_cast( -aOffset.X() / nScaleX + 0.5 ), static_cast( -aOffset.Y() / nScaleY + 0.5 ) ); + aTwipMode = MapMode( MapUnit::MapTwip, aTwipsOfs, aHorFract, aZoomFract ); +} + +void ScPrintFunc::ApplyPrintSettings() +{ + if ( !pPrinter ) + return; + + // Configure Printer to Printing + + Size aEnumSize = aPageSize; + + pPrinter->SetOrientation( bLandscape ? Orientation::Landscape : Orientation::Portrait ); + if ( bLandscape ) + { + // landscape is always interpreted as a rotation by 90 degrees ! + // this leads to non WYSIWIG but at least it prints! + // #i21775# + tools::Long nTemp = aEnumSize.Width(); + aEnumSize.setWidth( aEnumSize.Height() ); + aEnumSize.setHeight( nTemp ); + } + Paper ePaper = SvxPaperInfo::GetSvxPaper( aEnumSize, MapUnit::MapTwip ); + sal_uInt16 nPaperBin = pParamSet->Get(ATTR_PAGE_PAPERBIN).GetValue(); + + pPrinter->SetPaper( ePaper ); + if ( PAPER_USER == ePaper ) + { + MapMode aPrinterMode = pPrinter->GetMapMode(); + MapMode aLocalMode( MapUnit::MapTwip ); + pPrinter->SetMapMode( aLocalMode ); + pPrinter->SetPaperSizeUser( aEnumSize ); + pPrinter->SetMapMode( aPrinterMode ); + } + + pPrinter->SetPaperBin( nPaperBin ); +} + +// rPageRanges = range for all tables +// nStartPage = rPageRanges starts at nStartPage +// nDisplayStart = continuous number for displaying the page number + +tools::Long ScPrintFunc::DoPrint( const MultiSelection& rPageRanges, + tools::Long nStartPage, tools::Long nDisplayStart, bool bDoPrint, + ScPreviewLocationData* pLocationData ) +{ + OSL_ENSURE(pDev,"Device == NULL"); + if (!pParamSet) + return 0; + + if ( pPrinter && bDoPrint ) + ApplyPrintSettings(); + + InitModes(); + if ( pLocationData ) + { + pLocationData->SetCellMapMode( aOffsetMode ); + pLocationData->SetPrintTab( nPrintTab ); + } + + MakeTableString(); + + tools::Long nPageNo = 0; + tools::Long nPrinted = 0; + tools::Long nEndPage = rPageRanges.GetTotalRange().Max(); + + sal_uInt16 nRepeats = 1; + if (bMultiArea) + nRepeats = rDoc.GetPrintRangeCount(nPrintTab); + for (sal_uInt16 nStep=0; nStepsize(), "vector access error for aPageEndX (!)"); + nX2 = (*m_aRanges.m_xPageEndX)[nCountX]; + for (nCountY=0; nCountYsize(), "vector access error for aPageEndX"); + nX2 = (*m_aRanges.m_xPageEndX)[nCountX]; + if ( !aTableParam.bSkipEmpty || !rPageRow.IsHidden(nCountX) ) + { + if ( rPageRanges.IsSelected( nPageNo+nStartPage+1 ) ) + { + PrintPage( nPageNo+nDisplayStart, nX1, nY1, nX2, nY2, + bDoPrint, pLocationData ); + ++nPrinted; + } + ++nPageNo; + } + nX1 = nX2 + 1; + } + } + } + } + + aFieldData.aTabName = ScResId( STR_NOTES ); + + tools::Long nNoteNr = 0; + tools::Long nNoteAdd; + do + { + if ( nPageNo+nStartPage <= nEndPage ) + { + bool bPageSelected = rPageRanges.IsSelected( nPageNo+nStartPage+1 ); + nNoteAdd = PrintNotes( nPageNo+nStartPage, nNoteNr, bDoPrint && bPageSelected, + ( bPageSelected ? pLocationData : nullptr ) ); + if ( nNoteAdd ) + { + nNoteNr += nNoteAdd; + if (bPageSelected) + { + ++nPrinted; + bSourceRangeValid = false; // last page was no cell range + } + ++nPageNo; + } + } + else + nNoteAdd = 0; + } + while (nNoteAdd); + + if ( bMultiArea ) + ResetBreaks(nPrintTab); //breaks correct for displaying + + return nPrinted; +} + +void ScPrintFunc::CalcZoom( sal_uInt16 nRangeNo ) // calculate zoom +{ + sal_uInt16 nRCount = rDoc.GetPrintRangeCount( nPrintTab ); + const ScRange* pThisRange = nullptr; + if (nRangeNo != RANGENO_NORANGE && nRangeNo < nRCount) + pThisRange = rDoc.GetPrintRange( nPrintTab, nRangeNo ); + if ( pThisRange ) + { + nStartCol = pThisRange->aStart.Col(); + nStartRow = pThisRange->aStart.Row(); + nEndCol = pThisRange->aEnd .Col(); + nEndRow = pThisRange->aEnd .Row(); + } + + if (!AdjustPrintArea(false)) // empty + { + nZoom = 100; + m_aRanges.m_nPagesX = m_aRanges.m_nPagesY = m_aRanges.m_nTotalY = 0; + return; + } + + rDoc.SetRepeatArea( nPrintTab, nRepeatStartCol,nRepeatEndCol, nRepeatStartRow,nRepeatEndRow ); + + if (aTableParam.bScalePageNum) + { + nZoom = 100; + sal_uInt16 nPagesToFit = aTableParam.nScalePageNum; + + // If manual breaks are forced, calculate minimum # pages required + if (aTableParam.bForceBreaks) + { + sal_uInt16 nMinPages = 0; + std::set aRowBreaks; + std::set aColBreaks; + rDoc.GetAllRowBreaks(aRowBreaks, nPrintTab, false, true); + rDoc.GetAllColBreaks(aColBreaks, nPrintTab, false, true); + nMinPages = (aRowBreaks.size() + 1) * (aColBreaks.size() + 1); + + // #i54993# use min forced by breaks if it's > # pages in + // scale parameter to avoid bottoming out at <= ZOOM_MIN + nPagesToFit = std::max(nMinPages, nPagesToFit); + } + + sal_uInt16 nLastFitZoom = 0, nLastNonFitZoom = 0; + while (true) + { + if (nZoom <= ZOOM_MIN) + break; + + CalcPages(); + bool bFitsPage = (m_aRanges.m_nPagesX * m_aRanges.m_nPagesY <= nPagesToFit); + + if (bFitsPage) + { + if (nZoom == 100) + // If it fits at 100%, it's good enough for me. + break; + + nLastFitZoom = nZoom; + nZoom = (nLastNonFitZoom + nZoom) / 2; + + if (nLastFitZoom == nZoom) + // It converged. Use this zoom level. + break; + } + else + { + if (nZoom - nLastFitZoom <= 1) + { + nZoom = nLastFitZoom; + CalcPages(); + break; + } + + nLastNonFitZoom = nZoom; + nZoom = (nLastFitZoom + nZoom) / 2; + } + } + } + else if (aTableParam.bScaleTo) + { + nZoom = 100; + sal_uInt16 nW = aTableParam.nScaleWidth; + sal_uInt16 nH = aTableParam.nScaleHeight; + + // If manual breaks are forced, calculate minimum # pages required + if (aTableParam.bForceBreaks) + { + sal_uInt16 nMinPagesW = 0, nMinPagesH = 0; + std::set aRowBreaks; + std::set aColBreaks; + rDoc.GetAllRowBreaks(aRowBreaks, nPrintTab, false, true); + rDoc.GetAllColBreaks(aColBreaks, nPrintTab, false, true); + nMinPagesW = aColBreaks.size() + 1; + nMinPagesH = aRowBreaks.size() + 1; + + // #i54993# use min forced by breaks if it's > # pages in + // scale parameters to avoid bottoming out at <= ZOOM_MIN + nW = std::max(nMinPagesW, nW); + nH = std::max(nMinPagesH, nH); + } + + sal_uInt16 nLastFitZoom = 0, nLastNonFitZoom = 0; + while (true) + { + if (nZoom <= ZOOM_MIN) + break; + + CalcPages(); + bool bFitsPage = ((!nW || (m_aRanges.m_nPagesX <= nW)) && (!nH || (m_aRanges.m_nPagesY <= nH))); + + if (bFitsPage) + { + if (nZoom == 100) + // If it fits at 100%, it's good enough for me. + break; + + nLastFitZoom = nZoom; + nZoom = (nLastNonFitZoom + nZoom) / 2; + + if (nLastFitZoom == nZoom) + // It converged. Use this zoom level. + break; + } + else + { + if (nZoom - nLastFitZoom <= 1) + { + nZoom = nLastFitZoom; + CalcPages(); + break; + } + + nLastNonFitZoom = nZoom; + nZoom = (nLastFitZoom + nZoom) / 2; + } + } + // tdf#103516 remove the almost blank page(s) for better + // interoperability by using slightly smaller zoom + if (nW > 0 && nH == 0 && m_aRanges.m_nPagesY > 1) + { + sal_uInt32 nLastPagesY = m_aRanges.m_nPagesY; + nLastFitZoom = nZoom; + nZoom *= 0.98; + if (nZoom < nLastFitZoom) + { + CalcPages(); + // same page count with smaller zoom: use the original zoom + if (m_aRanges.m_nPagesY == nLastPagesY) + { + nZoom = nLastFitZoom; + CalcPages(); + } + } + } + } + else if (aTableParam.bScaleAll) + { + nZoom = aTableParam.nScaleAll; + if ( nZoom <= ZOOM_MIN ) + nZoom = ZOOM_MIN; + CalcPages(); + } + else + { + OSL_ENSURE( aTableParam.bScaleNone, "no scale flag is set" ); + nZoom = 100; + CalcPages(); + } +} + +Size ScPrintFunc::GetDocPageSize() +{ + // Adjust height of head/foot line + + InitModes(); // initialize aTwipMode from nZoom + pDev->SetMapMode( aTwipMode ); // head/foot line in Twips + UpdateHFHeight( aHdr ); + UpdateHFHeight( aFtr ); + + // Page size in Document-Twips + // Calculating Left / Right also in PrintPage + + aPageRect = tools::Rectangle( Point(), aPageSize ); + aPageRect.SetLeft( ( aPageRect.Left() + nLeftMargin ) * 100 / nZoom ); + aPageRect.SetRight( ( aPageRect.Right() - nRightMargin ) * 100 / nZoom ); + aPageRect.SetTop( ( aPageRect.Top() + nTopMargin ) * 100 / nZoom + aHdr.nHeight ); + aPageRect.SetBottom( ( aPageRect.Bottom() - nBottomMargin ) * 100 / nZoom - aFtr.nHeight ); + + Size aDocPageSize = aPageRect.GetSize(); + if (aTableParam.bHeaders) + { + aDocPageSize.AdjustWidth( -(tools::Long(PRINT_HEADER_WIDTH)) ); + aDocPageSize.AdjustHeight( -(tools::Long(PRINT_HEADER_HEIGHT)) ); + } + if (pBorderItem) + { + aDocPageSize.AdjustWidth( -(lcl_LineTotal(pBorderItem->GetLeft()) + + lcl_LineTotal(pBorderItem->GetRight()) + + pBorderItem->GetDistance(SvxBoxItemLine::LEFT) + + pBorderItem->GetDistance(SvxBoxItemLine::RIGHT)) ); + aDocPageSize.AdjustHeight( -(lcl_LineTotal(pBorderItem->GetTop()) + + lcl_LineTotal(pBorderItem->GetBottom()) + + pBorderItem->GetDistance(SvxBoxItemLine::TOP) + + pBorderItem->GetDistance(SvxBoxItemLine::BOTTOM)) ); + } + if (pShadowItem && pShadowItem->GetLocation() != SvxShadowLocation::NONE) + { + aDocPageSize.AdjustWidth( -(pShadowItem->CalcShadowSpace(SvxShadowItemSide::LEFT) + + pShadowItem->CalcShadowSpace(SvxShadowItemSide::RIGHT)) ); + aDocPageSize.AdjustHeight( -(pShadowItem->CalcShadowSpace(SvxShadowItemSide::TOP) + + pShadowItem->CalcShadowSpace(SvxShadowItemSide::BOTTOM)) ); + } + return aDocPageSize; +} + +void ScPrintFunc::ResetBreaks( SCTAB nTab ) // Set Breaks correctly for view +{ + rDoc.SetPageSize( nTab, GetDocPageSize() ); + rDoc.UpdatePageBreaks( nTab ); +} + +static void lcl_SetHidden( const ScDocument& rDoc, SCTAB nPrintTab, ScPageRowEntry& rPageRowEntry, + SCCOL nStartCol, const std::vector< SCCOL >& rPageEndX ) +{ + size_t nPagesX = rPageRowEntry.GetPagesX(); + SCROW nStartRow = rPageRowEntry.GetStartRow(); + SCROW nEndRow = rPageRowEntry.GetEndRow(); + + bool bLeftIsEmpty = false; + ScRange aTempRange; + tools::Rectangle aTempRect = rDoc.GetMMRect( 0,0, 0,0, 0 ); + + for (size_t i=0; iclear(); + + // #i123672# use dynamic mem to react on size changes + if (m_xPageEndX->size() < static_cast(rDoc.MaxCol()) + 1) + { + m_xPageEndX->resize(rDoc.MaxCol()+1, SCCOL()); + } + + if (m_aInput.m_bPrintArea) + { + ScRange aRange(m_aInput.getStartColumn(), m_aInput.getStartRow(), m_aInput.getPrintTab(), m_aInput.getEndColumn(), m_aInput.getEndRow(), m_aInput.getPrintTab()); + rDoc.UpdatePageBreaks(m_aInput.getPrintTab(), &aRange); + } + else + { + rDoc.UpdatePageBreaks(m_aInput.getPrintTab()); // else, end is marked + } + + const size_t nRealCnt = m_aInput.getEndRow() - m_aInput.getStartRow() + 1; + + // #i123672# use dynamic mem to react on size changes + if (m_xPageEndY->size() < nRealCnt+1) + { + m_xPageEndY->resize(nRealCnt + 1, SCROW()); + } + + // Page alignment/splitting after breaks in Col/RowFlags + // Of several breaks in a hidden area, only one counts. + + m_nPagesX = 0; + m_nPagesY = 0; + m_nTotalY = 0; + + bool bVisCol = false; + for (SCCOL i = m_aInput.getStartColumn(); i <= m_aInput.getEndColumn(); i++) + { + bool bHidden = rDoc.ColHidden(i, m_aInput.getPrintTab()); + bool bPageBreak(rDoc.HasColBreak(i, m_aInput.getPrintTab()) & ScBreakType::Page); + if (i > m_aInput.getStartColumn() && bVisCol && bPageBreak) + { + OSL_ENSURE(m_nPagesX < m_xPageEndX->size(), "vector access error for aPageEndX"); + (*m_xPageEndX)[m_nPagesX] = i-1; + ++m_nPagesX; + bVisCol = false; + } + if (!bHidden) + bVisCol = true; + } + if (bVisCol) // also at the end, no empty pages + { + OSL_ENSURE(m_nPagesX < m_xPageEndX->size(), "vector access error for aPageEndX"); + (*m_xPageEndX)[m_nPagesX] = m_aInput.getEndColumn(); + ++m_nPagesX; + } + + bool bVisRow = false; + SCROW nPageStartRow = m_aInput.getStartRow(); + SCROW nLastVisibleRow = -1; + + std::unique_ptr pRowBreakIter(rDoc.GetRowBreakIterator(m_aInput.getPrintTab())); + SCROW nNextPageBreak = pRowBreakIter->first(); + while (nNextPageBreak != ScRowBreakIterator::NOT_FOUND && nNextPageBreak < m_aInput.getStartRow()) + // Skip until the page break position is at the start row or greater. + nNextPageBreak = pRowBreakIter->next(); + + for (SCROW nRow = m_aInput.getStartRow(); nRow <= m_aInput.getEndRow(); ++nRow) + { + bool bPageBreak = (nNextPageBreak == nRow); + if (bPageBreak) + nNextPageBreak = pRowBreakIter->next(); + + if (nRow > m_aInput.getStartRow() && bVisRow && bPageBreak) + { + OSL_ENSURE(m_nTotalY < m_xPageEndY->size(), "vector access error for rPageEndY"); + (*m_xPageEndY)[m_nTotalY] = nRow - 1; + ++m_nTotalY; + + if (!m_aInput.m_bSkipEmpty || !rDoc.IsPrintEmpty(m_aInput.getStartColumn(), nPageStartRow, m_aInput.getEndColumn(), nRow-1, m_aInput.getPrintTab())) + { + auto& rPageRow = (*m_xPageRows)[m_nPagesY]; + rPageRow.SetStartRow(nPageStartRow); + rPageRow.SetEndRow(nRow - 1); + rPageRow.SetPagesX(m_nPagesX); + if (m_aInput.m_bSkipEmpty) + lcl_SetHidden(rDoc, m_aInput.getPrintTab(), rPageRow, m_aInput.getStartColumn(), *m_xPageEndX); + ++m_nPagesY; + } + + nPageStartRow = nRow; + bVisRow = false; + } + + if (nRow <= nLastVisibleRow) + { + // This row is still visible. Don't bother calling RowHidden() to + // find out, for speed optimization. + bVisRow = true; + continue; + } + + SCROW nLastRow = -1; + if (!rDoc.RowHidden(nRow, m_aInput.getPrintTab(), nullptr, &nLastRow)) + { + bVisRow = true; + nLastVisibleRow = nLastRow; + } + else + { + // Skip all hidden rows until next pagebreak. + nRow = ((nNextPageBreak == ScRowBreakIterator::NOT_FOUND) ? nLastRow : + std::min(nLastRow, nNextPageBreak - 1)); + } + } + + if (!bVisRow) + return; + + OSL_ENSURE(m_nTotalY < m_xPageEndY->size(), "vector access error for maPageEndY"); + (*m_xPageEndY)[m_nTotalY] = m_aInput.getEndRow(); + ++m_nTotalY; + + if (!m_aInput.m_bSkipEmpty || !rDoc.IsPrintEmpty(m_aInput.getStartColumn(), nPageStartRow, m_aInput.getEndColumn(), m_aInput.getEndRow(), m_aInput.getPrintTab())) + { + auto& rPageRow = (*m_xPageRows)[m_nPagesY]; + rPageRow.SetStartRow(nPageStartRow); + rPageRow.SetEndRow(m_aInput.getEndRow()); + rPageRow.SetPagesX(m_nPagesX); + if (m_aInput.m_bSkipEmpty) + lcl_SetHidden(rDoc, m_aInput.getPrintTab(), rPageRow, m_aInput.getStartColumn(), *m_xPageEndX); + ++m_nPagesY; + } +} + +} // end namespace sc +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/reffact.cxx b/sc/source/ui/view/reffact.cxx new file mode 100644 index 0000000000..3834e3b8c3 --- /dev/null +++ b/sc/source/ui/view/reffact.cxx @@ -0,0 +1,299 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScNameDlgWrapper, FID_DEFINE_NAME) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScNameDefDlgWrapper, FID_ADD_NAME) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScSolverDlgWrapper, SID_OPENDLG_SOLVE) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScOptSolverDlgWrapper, SID_OPENDLG_OPTSOLVER) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScXMLSourceDlgWrapper, SID_MANAGE_XML_SOURCE) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScPivotLayoutWrapper, SID_OPENDLG_PIVOTTABLE) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScTabOpDlgWrapper, SID_OPENDLG_TABOP) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScFilterDlgWrapper, SID_FILTER) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScSpecialFilterDlgWrapper, SID_SPECIAL_FILTER) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScDbNameDlgWrapper, SID_DEFINE_DBNAME) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScConsolidateDlgWrapper, SID_OPENDLG_CONSOLIDATE) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScPrintAreasDlgWrapper, SID_OPENDLG_EDIT_PRINTAREA) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScColRowNameRangesDlgWrapper, SID_DEFINE_COLROWNAMERANGES) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScFormulaDlgWrapper, SID_OPENDLG_FUNCTION) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScAcceptChgDlgWrapper, FID_CHG_ACCEPT) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScHighlightChgDlgWrapper, FID_CHG_SHOW) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScSimpleRefDlgWrapper, WID_SIMPLE_REF) +SFX_IMPL_MODELESSDIALOGCONTOLLER_WITHID(ScCondFormatDlgWrapper, WID_CONDFRMT_REF) + +SFX_IMPL_CHILDWINDOW_WITHID(ScValidityRefChildWin, SID_VALIDITY_REFERENCE) + +SfxChildWinInfo ScValidityRefChildWin::GetInfo() const +{ + SfxChildWinInfo aInfo = SfxChildWindow::GetInfo(); + return aInfo; +} + +namespace +{ + ScTabViewShell* lcl_GetTabViewShell( const SfxBindings* pBindings ); +} + +#define IMPL_CONTROLLER_CHILD_CTOR(Class,sid) \ + Class::Class( vcl::Window* pParentP, \ + sal_uInt16 nId, \ + SfxBindings* p, \ + const SfxChildWinInfo* pInfo ) \ + : SfxChildWindow(pParentP, nId) \ + { \ + /************************************************************************************/\ + /* When a new document is creating, the SfxViewFrame may be ready, */\ + /* But the ScTabViewShell may have not been activated yet. In this */\ + /* situation, SfxViewShell::Current() does not get the correct shell, */\ + /* and we should lcl_GetTabViewShell( p ) instead of SfxViewShell::Current() */\ + /************************************************************************************/\ + ScTabViewShell* pViewShell = lcl_GetTabViewShell( p ); \ + if (!pViewShell) \ + pViewShell = dynamic_cast( SfxViewShell::Current() ); \ + OSL_ENSURE( pViewShell, "missing view shell :-(" ); \ + SetController( pViewShell ? \ + pViewShell->CreateRefDialogController( p, this, pInfo, pParentP->GetFrameWeld(), sid ) : nullptr ); \ + if (pViewShell && !GetController()) \ + pViewShell->GetViewFrame().SetChildWindow( nId, false ); \ + } + + +IMPL_CONTROLLER_CHILD_CTOR( ScNameDlgWrapper, FID_DEFINE_NAME ) + +IMPL_CONTROLLER_CHILD_CTOR( ScNameDefDlgWrapper, FID_ADD_NAME ) + +IMPL_CONTROLLER_CHILD_CTOR( ScSolverDlgWrapper, SID_OPENDLG_SOLVE ) + +IMPL_CONTROLLER_CHILD_CTOR( ScOptSolverDlgWrapper, SID_OPENDLG_OPTSOLVER ) + +IMPL_CONTROLLER_CHILD_CTOR( ScXMLSourceDlgWrapper, SID_MANAGE_XML_SOURCE) + +IMPL_CONTROLLER_CHILD_CTOR( ScPivotLayoutWrapper, SID_OPENDLG_PIVOTTABLE ) + +IMPL_CONTROLLER_CHILD_CTOR( ScTabOpDlgWrapper, SID_OPENDLG_TABOP ) + +IMPL_CONTROLLER_CHILD_CTOR( ScFilterDlgWrapper, SID_FILTER ) + +IMPL_CONTROLLER_CHILD_CTOR( ScSpecialFilterDlgWrapper, SID_SPECIAL_FILTER ) + +IMPL_CONTROLLER_CHILD_CTOR( ScDbNameDlgWrapper, SID_DEFINE_DBNAME ) + +IMPL_CONTROLLER_CHILD_CTOR( ScColRowNameRangesDlgWrapper, SID_DEFINE_COLROWNAMERANGES ) + +IMPL_CONTROLLER_CHILD_CTOR( ScConsolidateDlgWrapper, SID_OPENDLG_CONSOLIDATE ) + +IMPL_CONTROLLER_CHILD_CTOR( ScPrintAreasDlgWrapper, SID_OPENDLG_EDIT_PRINTAREA ) + +IMPL_CONTROLLER_CHILD_CTOR( ScFormulaDlgWrapper, SID_OPENDLG_FUNCTION ) + + +// ScSimpleRefDlgWrapper + +static bool bScSimpleRefFlag; +static tools::Long nScSimpleRefHeight; +static tools::Long nScSimpleRefWidth; +static tools::Long nScSimpleRefX; +static tools::Long nScSimpleRefY; +static bool bAutoReOpen = true; + +ScSimpleRefDlgWrapper::ScSimpleRefDlgWrapper( vcl::Window* pParentP, + sal_uInt16 nId, + SfxBindings* p, + SfxChildWinInfo* pInfo ) + : SfxChildWindow(pParentP, nId) +{ + + ScTabViewShell* pViewShell = nullptr; + SfxDispatcher* pDisp = p->GetDispatcher(); + if ( pDisp ) + { + SfxViewFrame* pViewFrm = pDisp->GetFrame(); + if ( pViewFrm ) + pViewShell = dynamic_cast( pViewFrm->GetViewShell() ); + } + + OSL_ENSURE( pViewShell, "missing view shell :-(" ); + + if(pInfo!=nullptr && bScSimpleRefFlag) + { + pInfo->aPos.setX(nScSimpleRefX ); + pInfo->aPos.setY(nScSimpleRefY ); + pInfo->aSize.setHeight(nScSimpleRefHeight ); + pInfo->aSize.setWidth(nScSimpleRefWidth ); + } + SetController(nullptr); + + if (bAutoReOpen && pViewShell) + SetController(pViewShell->CreateRefDialogController(p, this, pInfo, pParentP->GetFrameWeld(), WID_SIMPLE_REF)); + + if (!GetController()) + { + SC_MOD()->SetRefDialog( nId, false ); + } +} + +void ScSimpleRefDlgWrapper::SetAutoReOpen(bool bFlag) +{ + bAutoReOpen=bFlag; +} + +void ScSimpleRefDlgWrapper::SetRefString(const OUString& rStr) +{ + auto xDlgController = GetController(); + if (xDlgController) + { + static_cast(xDlgController.get())->SetRefString(rStr); + } +} + +void ScSimpleRefDlgWrapper::SetCloseHdl( const Link& rLink ) +{ + auto xDlgController = GetController(); + if (xDlgController) + { + static_cast(xDlgController.get())->SetCloseHdl(rLink); + } +} + +void ScSimpleRefDlgWrapper::SetUnoLinks( const Link& rDone, + const Link& rAbort, const Link& rChange ) +{ + auto xDlgController = GetController(); + if (xDlgController) + { + static_cast(xDlgController.get())->SetUnoLinks( rDone, rAbort, rChange ); + } +} + +void ScSimpleRefDlgWrapper::SetFlags( bool bCloseOnButtonUp, bool bSingleCell, bool bMultiSelection ) +{ + auto xDlgController = GetController(); + if (xDlgController) + { + static_cast(xDlgController.get())->SetFlags( bCloseOnButtonUp, bSingleCell, bMultiSelection ); + } +} + +void ScSimpleRefDlgWrapper::StartRefInput() +{ + auto xDlgController = GetController(); + if (xDlgController) + { + static_cast(xDlgController.get())->StartRefInput(); + } +} + +// ScAcceptChgDlgWrapper //FIXME: should be moved into ViewShell + +ScAcceptChgDlgWrapper::ScAcceptChgDlgWrapper(vcl::Window* pParentP, + sal_uInt16 nId, + SfxBindings* pBindings, + SfxChildWinInfo* pInfo ) : + SfxChildWindow( pParentP, nId ) +{ + ScTabViewShell* pViewShell = + dynamic_cast( SfxViewShell::Current() ); + OSL_ENSURE( pViewShell, "missing view shell :-(" ); + if (pViewShell) + { + auto xDlg = std::make_shared(pBindings, this, pParentP->GetFrameWeld(), &pViewShell->GetViewData()); + SetController(xDlg); + xDlg->Initialize( pInfo ); + } + else + SetController( nullptr ); + if (pViewShell && !GetController()) + pViewShell->GetViewFrame().SetChildWindow( nId, false ); +} + +void ScAcceptChgDlgWrapper::ReInitDlg() +{ + ScTabViewShell* pViewShell = + dynamic_cast( SfxViewShell::Current() ); + OSL_ENSURE( pViewShell, "missing view shell :-(" ); + + if (GetController() && pViewShell) + { + static_cast(GetController().get())->ReInit(&pViewShell->GetViewData()); + } +} + +// ScHighlightChgDlgWrapper + +IMPL_CONTROLLER_CHILD_CTOR(ScHighlightChgDlgWrapper, FID_CHG_SHOW) + +namespace +{ + ScTabViewShell * lcl_GetTabViewShell( const SfxBindings *pBindings ) + { + if( pBindings ) + if( SfxDispatcher* pDisp = pBindings ->GetDispatcher() ) + if( SfxViewFrame *pFrm = pDisp->GetFrame() ) + if( SfxViewShell* pViewSh = pFrm->GetViewShell() ) + return dynamic_cast( pViewSh ); + + return nullptr; + } +} + +ScValidityRefChildWin::ScValidityRefChildWin(vcl::Window* pParentP, + sal_uInt16 nId, + const SfxBindings* p, + SAL_UNUSED_PARAMETER SfxChildWinInfo* /*pInfo*/ ) + : SfxChildWindow(pParentP, nId) + , m_bVisibleLock(false) + , m_bFreeWindowLock(false) +{ + SetWantsFocus( false ); + std::shared_ptr xDlg(ScValidationDlg::Find1AliveObject(pParentP->GetFrameWeld())); + SetController(xDlg); + ScTabViewShell* pViewShell; + if (xDlg) + pViewShell = static_cast(xDlg.get())->GetTabViewShell(); + else + pViewShell = lcl_GetTabViewShell( p ); + if (!pViewShell) + pViewShell = dynamic_cast( SfxViewShell::Current() ); + OSL_ENSURE( pViewShell, "missing view shell :-(" ); + if (pViewShell && !xDlg) + pViewShell->GetViewFrame().SetChildWindow( nId, false ); +} + +ScValidityRefChildWin::~ScValidityRefChildWin() +{ + if (m_bFreeWindowLock) + SetController(nullptr); +} + +IMPL_CONTROLLER_CHILD_CTOR( ScCondFormatDlgWrapper, WID_CONDFRMT_REF ) + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/scextopt.cxx b/sc/source/ui/view/scextopt.cxx new file mode 100644 index 0000000000..206b7cdc8d --- /dev/null +++ b/sc/source/ui/view/scextopt.cxx @@ -0,0 +1,218 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include + +#include + +#include +#include +#include + +ScExtDocSettings::ScExtDocSettings() : + mfTabBarWidth( -1.0 ), + mnLinkCnt( 0 ), + mnDisplTab( -1 ) +{ +} + +ScExtTabSettings::ScExtTabSettings() : + maUsedArea( ScAddress::INITIALIZE_INVALID ), + maCursor( ScAddress::INITIALIZE_INVALID ), + maFirstVis( ScAddress::INITIALIZE_INVALID ), + maSecondVis( ScAddress::INITIALIZE_INVALID ), + maFreezePos( 0, 0, 0 ), + maSplitPos( 0, 0 ), + meActivePane( SCEXT_PANE_TOPLEFT ), + maGridColor( COL_AUTO ), + mnNormalZoom( 0 ), + mnPageZoom( 0 ), + mbSelected( false ), + mbFrozenPanes( false ), + mbPageMode( false ), + mbShowGrid( true ) +{ +} + +namespace { + +/** A container for ScExtTabSettings objects. + @descr Internally, a std::map with shared pointers to ScExtTabSettings is + used. The copy constructor and assignment operator make deep copies of the + objects. */ +class ScExtTabSettingsCont +{ +public: + explicit ScExtTabSettingsCont(); + ScExtTabSettingsCont( const ScExtTabSettingsCont& rSrc ); + ScExtTabSettingsCont& operator=( const ScExtTabSettingsCont& rSrc ); + + const ScExtTabSettings* GetTabSettings( SCTAB nTab ) const; + ScExtTabSettings& GetOrCreateTabSettings( SCTAB nTab ); + + SCTAB GetLastTab() const; + +private: + typedef std::shared_ptr< ScExtTabSettings > ScExtTabSettingsRef; + typedef ::std::map< SCTAB, ScExtTabSettingsRef > ScExtTabSettingsMap; + + /** Makes a deep copy of all objects in the passed map. */ + void CopyFromMap( const ScExtTabSettingsMap& rMap ); + + ScExtTabSettingsMap maMap; +}; + +} + +ScExtTabSettingsCont::ScExtTabSettingsCont() +{ +} + +ScExtTabSettingsCont::ScExtTabSettingsCont( const ScExtTabSettingsCont& rSrc ) +{ + CopyFromMap( rSrc.maMap ); +} + +ScExtTabSettingsCont& ScExtTabSettingsCont::operator=( const ScExtTabSettingsCont& rSrc ) +{ + CopyFromMap( rSrc.maMap ); + return *this; +} + +const ScExtTabSettings* ScExtTabSettingsCont::GetTabSettings( SCTAB nTab ) const +{ + ScExtTabSettingsMap::const_iterator aIt = maMap.find( nTab ); + return (aIt == maMap.end()) ? nullptr : aIt->second.get(); +} + +ScExtTabSettings& ScExtTabSettingsCont::GetOrCreateTabSettings( SCTAB nTab ) +{ + ScExtTabSettingsRef& rxTabSett = maMap[ nTab ]; + if( !rxTabSett ) + rxTabSett = std::make_shared(); + return *rxTabSett; +} + +SCTAB ScExtTabSettingsCont::GetLastTab() const +{ + return maMap.empty() ? -1 : maMap.rbegin()->first; +} + +void ScExtTabSettingsCont::CopyFromMap( const ScExtTabSettingsMap& rMap ) +{ + maMap.clear(); + for( const auto& [rTab, rxSettings] : rMap ) + maMap[ rTab ] = std::make_shared( *rxSettings ); +} + +/** Implementation struct for ScExtDocOptions containing all members. */ +struct ScExtDocOptionsImpl +{ + ScExtDocSettings maDocSett; /// Global document settings. + ScExtTabSettingsCont maTabSett; /// Settings for all sheets. + std::vector< OUString > maCodeNames; /// Codenames for all sheets (VBA module names). + bool mbChanged; /// Use only if something has been changed. + + explicit ScExtDocOptionsImpl(); +}; + +ScExtDocOptionsImpl::ScExtDocOptionsImpl() : + mbChanged( false ) +{ +} + +ScExtDocOptions::ScExtDocOptions() : + mxImpl( new ScExtDocOptionsImpl ) +{ +} + +ScExtDocOptions::ScExtDocOptions( const ScExtDocOptions& rSrc ) : + mxImpl( new ScExtDocOptionsImpl( *rSrc.mxImpl ) ) +{ +} + +ScExtDocOptions::~ScExtDocOptions() +{ +} + +ScExtDocOptions& ScExtDocOptions::operator=( const ScExtDocOptions& rSrc ) +{ + *mxImpl = *rSrc.mxImpl; + return *this; +} + +bool ScExtDocOptions::IsChanged() const +{ + return mxImpl->mbChanged; +} + +void ScExtDocOptions::SetChanged( bool bChanged ) +{ + mxImpl->mbChanged = bChanged; +} + +const ScExtDocSettings& ScExtDocOptions::GetDocSettings() const +{ + return mxImpl->maDocSett; +} + +ScExtDocSettings& ScExtDocOptions::GetDocSettings() +{ + return mxImpl->maDocSett; +} + +const ScExtTabSettings* ScExtDocOptions::GetTabSettings( SCTAB nTab ) const +{ + return mxImpl->maTabSett.GetTabSettings( nTab ); +} + +SCTAB ScExtDocOptions::GetLastTab() const +{ + return mxImpl->maTabSett.GetLastTab(); +} + +ScExtTabSettings& ScExtDocOptions::GetOrCreateTabSettings( SCTAB nTab ) +{ + return mxImpl->maTabSett.GetOrCreateTabSettings( nTab ); +} + +SCTAB ScExtDocOptions::GetCodeNameCount() const +{ + return static_cast< SCTAB >( mxImpl->maCodeNames.size() ); +} + +OUString ScExtDocOptions::GetCodeName( SCTAB nTab ) const +{ + OSL_ENSURE( (0 <= nTab) && (nTab < GetCodeNameCount()), "ScExtDocOptions::GetCodeName - invalid sheet index" ); + return ((0 <= nTab) && (nTab < GetCodeNameCount())) ? mxImpl->maCodeNames[ static_cast< size_t >( nTab ) ] : OUString(); +} + +void ScExtDocOptions::SetCodeName( SCTAB nTab, const OUString& rCodeName ) +{ + OSL_ENSURE( nTab >= 0, "ScExtDocOptions::SetCodeName - invalid sheet index" ); + if( nTab >= 0 ) + { + size_t nIndex = static_cast< size_t >( nTab ); + if( nIndex >= mxImpl->maCodeNames.size() ) + mxImpl->maCodeNames.resize( nIndex + 1 ); + mxImpl->maCodeNames[ nIndex ] = rCodeName; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/select.cxx b/sc/source/ui/view/select.cxx new file mode 100644 index 0000000000..d972c9b4eb --- /dev/null +++ b/sc/source/ui/view/select.cxx @@ -0,0 +1,986 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(_WIN32) +#define SC_SELENG_REFMODE_UPDATE_INTERVAL_MIN 65 +#endif + +using namespace com::sun::star; + +static Point aSwitchPos; //! Member +static bool bDidSwitch = false; + +// View (Gridwin / keyboard) +ScViewFunctionSet::ScViewFunctionSet( ScViewData* pNewViewData ) : + m_pViewData( pNewViewData ), + m_pEngine( nullptr ), + m_bAnchor( false ), + m_bStarted( false ) +{ + OSL_ENSURE(m_pViewData, "ViewData==0 at FunctionSet"); +} + +ScSplitPos ScViewFunctionSet::GetWhich() const +{ + if (m_pEngine) + return m_pEngine->GetWhich(); + else + return m_pViewData->GetActivePart(); +} + +sal_uInt64 ScViewFunctionSet::CalcUpdateInterval( const Size& rWinSize, const Point& rEffPos, + bool bLeftScroll, bool bTopScroll, bool bRightScroll, bool bBottomScroll ) +{ + sal_uInt64 nUpdateInterval = SELENG_AUTOREPEAT_INTERVAL_MAX; + vcl::Window* pWin = m_pEngine->GetWindow(); + AbsoluteScreenPixelRectangle aScrRect = pWin->GetDesktopRectPixel(); + AbsoluteScreenPixelPoint aRootPos = pWin->OutputToAbsoluteScreenPixel(Point(0,0)); + if (bRightScroll) + { + double nWinRight = rWinSize.getWidth() + aRootPos.getX(); + double nMarginRight = aScrRect.GetWidth() - nWinRight; + double nHOffset = rEffPos.X() - rWinSize.Width(); + double nHAccelRate = nHOffset / nMarginRight; + + if (nHAccelRate > 1.0) + nHAccelRate = 1.0; + + nUpdateInterval = SELENG_AUTOREPEAT_INTERVAL_MAX*(1.0 - nHAccelRate); + } + + if (bLeftScroll) + { + double nMarginLeft = aRootPos.getX(); + double nHOffset = -rEffPos.X(); + double nHAccelRate = nHOffset / nMarginLeft; + + if (nHAccelRate > 1.0) + nHAccelRate = 1.0; + + sal_uLong nTmp = static_cast(SELENG_AUTOREPEAT_INTERVAL_MAX*(1.0 - nHAccelRate)); + if (nUpdateInterval > nTmp) + nUpdateInterval = nTmp; + } + + if (bBottomScroll) + { + double nWinBottom = rWinSize.getHeight() + aRootPos.getY(); + double nMarginBottom = aScrRect.GetHeight() - nWinBottom; + double nVOffset = rEffPos.Y() - rWinSize.Height(); + double nVAccelRate = nVOffset / nMarginBottom; + + if (nVAccelRate > 1.0) + nVAccelRate = 1.0; + + sal_uInt64 nTmp = SELENG_AUTOREPEAT_INTERVAL_MAX*(1.0 - nVAccelRate); + if (nUpdateInterval > nTmp) + nUpdateInterval = nTmp; + } + + if (bTopScroll) + { + double nMarginTop = aRootPos.getY(); + double nVOffset = -rEffPos.Y(); + double nVAccelRate = nVOffset / nMarginTop; + + if (nVAccelRate > 1.0) + nVAccelRate = 1.0; + + sal_uInt64 nTmp = SELENG_AUTOREPEAT_INTERVAL_MAX*(1.0 - nVAccelRate); + if (nUpdateInterval > nTmp) + nUpdateInterval = nTmp; + } + +#ifdef _WIN32 + ScTabViewShell* pViewShell = m_pViewData->GetViewShell(); + bool bRefMode = pViewShell && pViewShell->IsRefInputMode(); + if (bRefMode && nUpdateInterval < SC_SELENG_REFMODE_UPDATE_INTERVAL_MIN) + // Lower the update interval during ref mode, because re-draw can be + // expensive on Windows. Making this interval too small would queue up + // the scroll/paint requests which would cause semi-infinite + // scrolls even after the mouse cursor is released. We don't have + // this problem on Linux. + nUpdateInterval = SC_SELENG_REFMODE_UPDATE_INTERVAL_MIN; +#endif + return nUpdateInterval; +} + +void ScViewFunctionSet::SetSelectionEngine( ScViewSelectionEngine* pSelEngine ) +{ + m_pEngine = pSelEngine; +} + +// Drag & Drop +void ScViewFunctionSet::BeginDrag() +{ + SCTAB nTab = m_pViewData->GetTabNo(); + + SCCOL nPosX; + SCROW nPosY; + if (m_pEngine) + { + Point aMPos = m_pEngine->GetMousePosPixel(); + m_pViewData->GetPosFromPixel( aMPos.X(), aMPos.Y(), GetWhich(), nPosX, nPosY ); + } + else + { + nPosX = m_pViewData->GetCurX(); + nPosY = m_pViewData->GetCurY(); + } + + ScModule* pScMod = SC_MOD(); + bool bRefMode = pScMod->IsFormulaMode(); + if (bRefMode) + return; + + m_pViewData->GetView()->FakeButtonUp( GetWhich() ); // ButtonUp is swallowed + + ScMarkData& rMark = m_pViewData->GetMarkData(); + rMark.MarkToSimple(); + if ( !rMark.IsMarked() || rMark.IsMultiMarked() ) + return; + + ScDocumentUniquePtr pClipDoc(new ScDocument( SCDOCMODE_CLIP )); + // bApi = TRUE -> no error messages + bool bCopied = m_pViewData->GetView()->CopyToClip( pClipDoc.get(), false, true ); + if ( !bCopied ) + return; + + sal_Int8 nDragActions = m_pViewData->GetView()->SelectionEditable() ? + ( DND_ACTION_COPYMOVE | DND_ACTION_LINK ) : + ( DND_ACTION_COPY | DND_ACTION_LINK ); + + ScDocShell* pDocSh = m_pViewData->GetDocShell(); + TransferableObjectDescriptor aObjDesc; + pDocSh->FillTransferableObjectDescriptor( aObjDesc ); + aObjDesc.maDisplayName = pDocSh->GetMedium()->GetURLObject().GetURLNoPass(); + // maSize is set in ScTransferObj ctor + + rtl::Reference pTransferObj = new ScTransferObj( std::move(pClipDoc), std::move(aObjDesc) ); + + // set position of dragged cell within range + ScRange aMarkRange = pTransferObj->GetRange(); + SCCOL nStartX = aMarkRange.aStart.Col(); + SCROW nStartY = aMarkRange.aStart.Row(); + SCCOL nHandleX = (nPosX >= nStartX) ? nPosX - nStartX : 0; + SCROW nHandleY = (nPosY >= nStartY) ? nPosY - nStartY : 0; + pTransferObj->SetDragHandlePos( nHandleX, nHandleY ); + pTransferObj->SetSourceCursorPos( m_pViewData->GetCurX(), m_pViewData->GetCurY() ); + pTransferObj->SetVisibleTab( nTab ); + + pTransferObj->SetDragSource( pDocSh, rMark ); + + vcl::Window* pWindow = m_pViewData->GetActiveWin(); + if ( pWindow->IsTracking() ) + pWindow->EndTracking( TrackingEventFlags::Cancel ); // abort selecting + + if (comphelper::LibreOfficeKit::isActive()) + pWindow->LocalStartDrag(); + + SC_MOD()->SetDragObject( pTransferObj.get(), nullptr ); // for internal D&D + pTransferObj->StartDrag( pWindow, nDragActions ); + + return; // dragging started + +} + +// Selection +void ScViewFunctionSet::CreateAnchor() +{ + if (m_bAnchor) return; + + bool bRefMode = SC_MOD()->IsFormulaMode(); + if (bRefMode) + SetAnchor( m_pViewData->GetRefStartX(), m_pViewData->GetRefStartY() ); + else + SetAnchor( m_pViewData->GetCurX(), m_pViewData->GetCurY() ); +} + +void ScViewFunctionSet::SetAnchor( SCCOL nPosX, SCROW nPosY ) +{ + bool bRefMode = SC_MOD()->IsFormulaMode(); + ScTabView* pView = m_pViewData->GetView(); + SCTAB nTab = m_pViewData->GetTabNo(); + + if (bRefMode) + { + pView->DoneRefMode(); + m_aAnchorPos.Set( nPosX, nPosY, nTab ); + pView->InitRefMode( m_aAnchorPos.Col(), m_aAnchorPos.Row(), m_aAnchorPos.Tab(), + SC_REFTYPE_REF ); + m_bStarted = true; + } + else if (m_pViewData->IsAnyFillMode()) + { + m_aAnchorPos.Set( nPosX, nPosY, nTab ); + m_bStarted = true; + } + else + { + // don't go there and back again + if ( m_bStarted && pView->IsMarking( nPosX, nPosY, nTab ) ) + { + // don't do anything + } + else + { + pView->DoneBlockMode( true ); + m_aAnchorPos.Set( nPosX, nPosY, nTab ); + ScMarkData& rMark = m_pViewData->GetMarkData(); + if ( rMark.IsMarked() || rMark.IsMultiMarked() ) + { + pView->InitBlockMode( m_aAnchorPos.Col(), m_aAnchorPos.Row(), + m_aAnchorPos.Tab(), true ); + m_bStarted = true; + } + else + m_bStarted = false; + } + } + m_bAnchor = true; +} + +void ScViewFunctionSet::DestroyAnchor() +{ + if (m_pViewData->IsAnyFillMode()) + return; + + bool bRefMode = SC_MOD()->IsFormulaMode(); + if (bRefMode) + m_pViewData->GetView()->DoneRefMode( true ); + else + m_pViewData->GetView()->DoneBlockMode( true ); + + m_bAnchor = false; +} + +void ScViewFunctionSet::SetAnchorFlag( bool bSet ) +{ + m_bAnchor = bSet; +} + +void ScViewFunctionSet::SetCursorAtPoint( const Point& rPointPixel, bool /* bDontSelectAtCursor */ ) +{ + if ( bDidSwitch ) + { + if ( rPointPixel == aSwitchPos ) + return; // don't scroll in wrong window + else + bDidSwitch = false; + } + aSwitchPos = rPointPixel; // only important, if bDidSwitch + + // treat position 0 as -1, so scrolling is always possible + // (with full screen and hidden headers, the top left border may be at 0) + // (moved from ScViewData::GetPosFromPixel) + + Point aEffPos = rPointPixel; + if ( aEffPos.X() == 0 ) + aEffPos.setX( -1 ); + if ( aEffPos.Y() == 0 ) + aEffPos.setY( -1 ); + + // Scrolling + Size aWinSize = m_pEngine->GetWindow()->GetOutputSizePixel(); + bool bLeftScroll = ( aEffPos.X() < 0 ); + bool bTopScroll = ( aEffPos.Y() < 0 ); + + SCCOL nPosX; + SCROW nPosY; + m_pViewData->GetPosFromPixel( aEffPos.X(), aEffPos.Y(), GetWhich(), + nPosX, nPosY, true, true ); // with Repair + + tools::Rectangle aEditArea = m_pViewData->GetEditArea(GetWhich(), nPosX, nPosY, + m_pEngine->GetWindow(), + nullptr, false); + + bool bFillingSelection = m_pViewData->IsFillMode() || m_pViewData->GetFillMode() == ScFillMode::MATRIX; + bool bBottomScroll; + bool bRightScroll; + // for Autofill don't yet assume we want to auto-scroll to the cell under the mouse + // because the autofill handle extends into a cells neighbours so initial click is usually + // above a neighbour cell + if (bFillingSelection) + { + bBottomScroll = aEffPos.Y() >= aWinSize.Height(); + bRightScroll = aEffPos.X() >= aWinSize.Width(); + } + else + { + //in the normal case make the full selected cell visible + bBottomScroll = aEditArea.Bottom() >= aWinSize.Height(); + bRightScroll = aEditArea.Right() >= aWinSize.Width(); + } + + bool bScroll = bRightScroll || bBottomScroll || bLeftScroll || bTopScroll; + + // for Autofill switch in the center of cell thereby don't prevent scrolling to bottom/right + if (bFillingSelection) + { + bool bLeft, bTop; + m_pViewData->GetMouseQuadrant( aEffPos, GetWhich(), nPosX, nPosY, bLeft, bTop ); + ScDocument& rDoc = m_pViewData->GetDocument(); + SCTAB nTab = m_pViewData->GetTabNo(); + if ( bLeft && !bRightScroll ) + do --nPosX; while ( nPosX>=0 && rDoc.ColHidden( nPosX, nTab ) ); + if ( bTop && !bBottomScroll ) + { + if (--nPosY >= 0) + { + nPosY = rDoc.LastVisibleRow(0, nPosY, nTab); + if (!rDoc.ValidRow(nPosY)) + nPosY = -1; + } + } + // negative value is allowed + } + + // moved out of fix limit? + ScSplitPos eWhich = GetWhich(); + if ( eWhich == m_pViewData->GetActivePart() ) + { + if ( m_pViewData->GetHSplitMode() == SC_SPLIT_FIX ) + if ( aEffPos.X() >= aWinSize.Width() ) + { + if ( eWhich == SC_SPLIT_TOPLEFT ) + { + m_pViewData->GetView()->ActivatePart( SC_SPLIT_TOPRIGHT ); + bScroll = false; + bDidSwitch = true; + } + else if ( eWhich == SC_SPLIT_BOTTOMLEFT ) + { + m_pViewData->GetView()->ActivatePart( SC_SPLIT_BOTTOMRIGHT ); + bScroll = false; + bDidSwitch = true; + } + } + + if ( m_pViewData->GetVSplitMode() == SC_SPLIT_FIX ) + if ( aEffPos.Y() >= aWinSize.Height() ) + { + if ( eWhich == SC_SPLIT_TOPLEFT ) + { + m_pViewData->GetView()->ActivatePart( SC_SPLIT_BOTTOMLEFT ); + bScroll = false; + bDidSwitch = true; + } + else if ( eWhich == SC_SPLIT_TOPRIGHT ) + { + m_pViewData->GetView()->ActivatePart( SC_SPLIT_BOTTOMRIGHT ); + bScroll = false; + bDidSwitch = true; + } + } + } + + if (bScroll) + { + // Adjust update interval based on how far the mouse pointer is from the edge. + sal_uInt64 nUpdateInterval = CalcUpdateInterval( + aWinSize, aEffPos, bLeftScroll, bTopScroll, bRightScroll, bBottomScroll); + m_pEngine->SetUpdateInterval(nUpdateInterval); + } + else + { + // Don't forget to reset the interval when not scrolling! + m_pEngine->SetUpdateInterval(SELENG_AUTOREPEAT_INTERVAL); + } + + m_pViewData->ResetOldCursor(); + SetCursorAtCell( nPosX, nPosY, bScroll ); +} + +bool ScViewFunctionSet::CheckRefBounds(SCCOL nPosX, SCROW nPosY) +{ + SCCOL startX = m_pViewData->GetRefStartX(); + SCROW startY = m_pViewData->GetRefStartY(); + + SCCOL endX = m_pViewData->GetRefEndX(); + SCROW endY = m_pViewData->GetRefEndY(); + + return nPosX >= startX && nPosX <= endX && nPosY >= startY && nPosY <= endY; +} + +bool ScViewFunctionSet::SetCursorAtCell( SCCOL nPosX, SCROW nPosY, bool bScroll ) +{ + ScTabView* pView = m_pViewData->GetView(); + SCTAB nTab = m_pViewData->GetTabNo(); + ScDocument& rDoc = m_pViewData->GetDocument(); + + if ( rDoc.IsTabProtected(nTab) ) + { + if (nPosX < 0 || nPosY < 0) + return false; + + const ScTableProtection* pProtect = rDoc.GetTabProtection(nTab); + if (!pProtect) + return false; + + bool bSkipProtected = !pProtect->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS); + bool bSkipUnprotected = !pProtect->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS); + + if ( bSkipProtected && bSkipUnprotected ) + return false; + + bool bCellProtected = rDoc.HasAttrib(nPosX, nPosY, nTab, nPosX, nPosY, nTab, HasAttrFlags::Protected); + if ( (bCellProtected && bSkipProtected) || (!bCellProtected && bSkipUnprotected) ) + // Don't select this cell! + return false; + } + + ScModule* pScMod = SC_MOD(); + ScTabViewShell* pViewShell = m_pViewData->GetViewShell(); + bool bRefMode = pViewShell && pViewShell->IsRefInputMode(); + + bool bHide = !bRefMode && !m_pViewData->IsAnyFillMode() && + ( nPosX != m_pViewData->GetCurX() || nPosY != m_pViewData->GetCurY() ); + + if (bHide) + pView->HideAllCursors(); + + if (bScroll) + { + if (bRefMode) + { + ScSplitPos eWhich = GetWhich(); + pView->AlignToCursor( nPosX, nPosY, SC_FOLLOW_LINE, &eWhich ); + } + else + pView->AlignToCursor( nPosX, nPosY, SC_FOLLOW_LINE ); + } + + if (bRefMode) + { + // if no input is possible from this doc, don't move the reference cursor around + if ( !pScMod->IsModalMode(m_pViewData->GetSfxDocShell()) && (!CheckRefBounds(nPosX, nPosY) || SfxLokHelper::getDeviceFormFactor() != LOKDeviceFormFactor::MOBILE)) + { + if (!m_bAnchor) + { + pView->DoneRefMode( true ); + pView->InitRefMode( nPosX, nPosY, m_pViewData->GetTabNo(), SC_REFTYPE_REF ); + } + + if(SfxLokHelper::getDeviceFormFactor() != LOKDeviceFormFactor::MOBILE) + pView->UpdateRef( nPosX, nPosY, m_pViewData->GetTabNo() ); + + pView->SelectionChanged(); + } + } + else if (m_pViewData->IsFillMode() || + (m_pViewData->GetFillMode() == ScFillMode::MATRIX && (nScFillModeMouseModifier & KEY_MOD1) )) + { + // If a matrix got touched, switch back to Autofill is possible with Ctrl + + SCCOL nStartX, nEndX; + SCROW nStartY, nEndY; // Block + SCTAB nDummy; + m_pViewData->GetSimpleArea( nStartX, nStartY, nDummy, nEndX, nEndY, nDummy ); + + if (m_pViewData->GetRefType() != SC_REFTYPE_FILL) + { + pView->InitRefMode( nStartX, nStartY, nTab, SC_REFTYPE_FILL ); + CreateAnchor(); + } + + ScRange aDelRange; + bool bOldDelMark = m_pViewData->GetDelMark( aDelRange ); + + if ( nPosX+1 >= nStartX && nPosX <= nEndX && + nPosY+1 >= nStartY && nPosY <= nEndY && + ( nPosX != nEndX || nPosY != nEndY ) ) // minimize? + { + // direction (left or top) + + tools::Long nSizeX = 0; + for (SCCOL i=nPosX+1; i<=nEndX; i++) + nSizeX += rDoc.GetColWidth( i, nTab ); + tools::Long nSizeY = rDoc.GetRowHeight( nPosY+1, nEndY, nTab ); + + SCCOL nDelStartX = nStartX; + SCROW nDelStartY = nStartY; + if ( nSizeX > nSizeY ) + nDelStartX = nPosX + 1; + else + nDelStartY = nPosY + 1; + // there is no need to check for zero, because nPosX/Y is also negative + + if ( nDelStartX < nStartX ) + nDelStartX = nStartX; + if ( nDelStartY < nStartY ) + nDelStartY = nStartY; + + // set range + + m_pViewData->SetDelMark( ScRange( nDelStartX,nDelStartY,nTab, + nEndX,nEndY,nTab ) ); + m_pViewData->GetView()->UpdateShrinkOverlay(); + + m_pViewData->GetView()-> + PaintArea( nStartX,nDelStartY, nEndX,nEndY, ScUpdateMode::Marks ); + + nPosX = nEndX; // keep red border around range + nPosY = nEndY; + + // reference the right way up, if it's upside down below + if ( nStartX != m_pViewData->GetRefStartX() || nStartY != m_pViewData->GetRefStartY() ) + { + m_pViewData->GetView()->DoneRefMode(); + m_pViewData->GetView()->InitRefMode( nStartX, nStartY, nTab, SC_REFTYPE_FILL ); + } + } + else + { + if ( bOldDelMark ) + { + m_pViewData->ResetDelMark(); + m_pViewData->GetView()->UpdateShrinkOverlay(); + } + + bool bNegX = ( nPosX < nStartX ); + bool bNegY = ( nPosY < nStartY ); + + tools::Long nSizeX = 0; + if ( bNegX ) + { + // in SetCursorAtPoint hidden columns are skipped. + // They must be skipped here too, or the result will always be the first hidden column. + do ++nPosX; while ( nPosX