diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-15 05:54:39 +0000 |
commit | 267c6f2ac71f92999e969232431ba04678e7437e (patch) | |
tree | 358c9467650e1d0a1d7227a21dac2e3d08b622b2 /sc/source/ui/view | |
parent | Initial commit. (diff) | |
download | libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.tar.xz libreoffice-267c6f2ac71f92999e969232431ba04678e7437e.zip |
Adding upstream version 4:24.2.0.upstream/4%24.2.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'sc/source/ui/view')
85 files changed, 86849 insertions, 0 deletions
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 <scitems.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/request.hxx> +#include <svl/whiter.hxx> +#include <vcl/EnumContext.hxx> + +#include <sc.hrc> +#include <SparklineShell.hxx> +#include <tabvwsh.hxx> +#include <docsh.hxx> +#include <document.hxx> + +#define ShellClass_SparklineShell +#include <scslots.hxx> + +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 <sfx2/bindings.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/request.hxx> +#include <vcl/EnumContext.hxx> + +#include <auditsh.hxx> +#include <tabvwsh.hxx> +#include <sc.hrc> +#include <document.hxx> + +#define ShellClass_ScAuditingShell +#include <scslots.hxx> + + +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<SCCOL>(pXItem->GetValue()); + SCROW nRow = static_cast<SCROW>(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 <cellmergeoption.hxx> +#include <address.hxx> + +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 <scitems.hxx> + +#include <svl/slstitm.hxx> +#include <svl/stritem.hxx> +#include <svl/whiter.hxx> +#include <svtools/cliplistener.hxx> +#include <svtools/insdlg.hxx> +#include <sot/formats.hxx> +#include <svx/hlnkitem.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/childwin.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/request.hxx> +#include <sfx2/viewfrm.hxx> +#include <vcl/EnumContext.hxx> +#include <vcl/svapp.hxx> +#include <svx/clipfmtitem.hxx> +#include <svx/statusitem.hxx> + +#include <cellsh.hxx> +#include <sc.hrc> +#include <docsh.hxx> +#include <attrib.hxx> +#include <tabvwsh.hxx> +#include <formulacell.hxx> +#include <scmod.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <transobj.hxx> +#include <drwtrans.hxx> +#include <scabstdlg.hxx> +#include <postit.hxx> +#include <cliputil.hxx> +#include <clipparam.hxx> +#include <markdata.hxx> +#include <gridwin.hxx> +#include <Sparkline.hxx> +#include <SparklineGroup.hxx> + +#define ShellClass_ScCellShell +#define ShellClass_CellMovement +#include <scslots.hxx> + + +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<vcl::Window>& 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<OUString> 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<sc::NoteEntry> 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<SfxDialogController> 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<const ScCondFormatItem*>(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 <config_features.h> + +#include <com/sun/star/i18n/TextConversionOption.hpp> +#include <com/sun/star/sheet/DataPilotFieldFilter.hpp> + +#include <scitems.hxx> +#include <sfx2/viewfrm.hxx> + +#include <basic/sberrors.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/propertysequence.hxx> +#include <svl/stritem.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <svl/zformat.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/request.hxx> +#include <vcl/commandinfoprovider.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <svx/svxdlg.hxx> +#include <sot/formats.hxx> +#include <svx/postattr.hxx> +#include <editeng/fontitem.hxx> +#include <svx/clipfmtitem.hxx> +#include <svx/hlnkitem.hxx> +#include <basic/sbxcore.hxx> +#include <editeng/editview.hxx> +#include <svtools/cliplistener.hxx> + +#include <cellsh.hxx> +#include <ftools.hxx> +#include <sc.hrc> +#include <document.hxx> +#include <patattr.hxx> +#include <scmod.hxx> +#include <tabvwsh.hxx> +#include <uiitems.hxx> +#include <reffact.hxx> +#include <inputhdl.hxx> +#include <transobj.hxx> +#include <drwtrans.hxx> +#include <docfunc.hxx> +#include <editable.hxx> +#include <dpobject.hxx> +#include <dpsave.hxx> +#include <spellparam.hxx> +#include <postit.hxx> +#include <dpsdbtab.hxx> +#include <dpshttab.hxx> +#include <dbdata.hxx> +#include <docsh.hxx> +#include <cliputil.hxx> +#include <markdata.hxx> +#include <colorscale.hxx> +#include <condformatdlg.hxx> +#include <attrib.hxx> +#include <condformatdlgitem.hxx> +#include <impex.hxx> + +#include <globstr.hrc> +#include <scresid.hxx> +#include <scui_def.hxx> +#include <scabstdlg.hxx> +#include <tokenstringcontext.hxx> +#include <cellvalue.hxx> +#include <tokenarray.hxx> +#include <formulacell.hxx> +#include <gridwin.hxx> +#include <searchresults.hxx> +#include <Sparkline.hxx> + +#include <com/sun/star/ui/dialogs/XExecutableDialog.hpp> +#include <com/sun/star/lang/XInitialization.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/uno/XComponentContext.hpp> +#include <cppuhelper/bootstrap.hxx> +#include <o3tl/string_view.hxx> + +#include <memory> + +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<sc::NoteEntry> 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<const SfxStringItem*>(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<AbstractScInsertCellDlg> 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<const SfxStringItem*>(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<AbstractScDeleteCellDlg> 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<const SfxStringItem*>(pItem)->GetValue(); + + nFlags |= FlagsFromString(aFlags, InsertDeleteFlags::ALL); + } + else + { + ScEditableTester aTester( pTabViewShell ); + if (aTester.IsEditable()) + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr<AbstractScDeleteContentsDlg> 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<const SfxStringItem*>(pItem)->GetValue(); + + nFlags |= FlagsFromString(aFlags); + } + else + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr<AbstractScInsertContentsDlg> 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<const SfxStringItem*>(pItem)->GetValue(); + if( pReqArgs->HasItem( FN_PARAM_1, &pItem ) ) + aFillCmd = static_cast<const SfxStringItem*>(pItem)->GetValue(); + if( pReqArgs->HasItem( FN_PARAM_2, &pItem ) ) + aFillDateCmd = static_cast<const SfxStringItem*>(pItem)->GetValue(); + if( pReqArgs->HasItem( FN_PARAM_3, &pItem ) ) + aFillStep = static_cast<const SfxStringItem*>(pItem)->GetValue(); + if( pReqArgs->HasItem( FN_PARAM_4, &pItem ) ) + aFillStart = static_cast<const SfxStringItem*>(pItem)->GetValue(); + if( pReqArgs->HasItem( FN_PARAM_5, &pItem ) ) + aFillMax = static_cast<const SfxStringItem*>(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<AbstractScFillSeriesDlg> 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<const SfxBoolItem*>(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<sc::SparklineGroup> 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<sheet::DataPilotFieldFilter> aFilters; + css::sheet::DataPilotFieldOrientation nOrientation; + if ( pTabViewShell->HasSelectionForDrillDown( nOrientation ) ) + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractScDPShowDetailDlg> 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<AbstractScDPDateGroupDlg> 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<AbstractScDPNumGroupDlg> 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<const SfxStringItem*>(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<AbstractScGroupDlg> 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<const SfxStringItem*>(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<AbstractScGroupDlg> 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<const SfxUInt32Item*>(pItem) ) + nFormat = static_cast<SotClipboardFormatId>(pIntItem->GetValue()); + + if ( nFormat != SotClipboardFormatId::NONE ) + { + css::uno::Reference<css::datatransfer::XTransferable2> 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<const SfxStringItem*>(pItem)->GetValue(); + + nFlags |= FlagsFromString(aFlags); + + const SfxUInt16Item* pFuncItem = rReq.GetArg<SfxUInt16Item>(FN_PARAM_1); + const SfxBoolItem* pSkipItem = rReq.GetArg<SfxBoolItem>(FN_PARAM_2); + const SfxBoolItem* pTransposeItem = rReq.GetArg<SfxBoolItem>(FN_PARAM_3); + const SfxBoolItem* pLinkItem = rReq.GetArg<SfxBoolItem>(FN_PARAM_4); + const SfxInt16Item* pMoveItem = rReq.GetArg<SfxInt16Item>(FN_PARAM_5); + if ( pFuncItem ) + nFunction = static_cast<ScPasteFunc>(pFuncItem->GetValue()); + if ( pSkipItem ) + bSkipEmpty = pSkipItem->GetValue(); + if ( pTransposeItem ) + bTranspose = pTransposeItem->GetValue(); + if ( pLinkItem ) + bAsLink = pLinkItem->GetValue(); + if ( pMoveItem ) + eMoveMode = static_cast<InsCellCmd>(pMoveItem->GetValue()); + } + else + { + ScEditableTester aTester( pTabViewShell ); + if (aTester.IsEditable()) + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr<AbstractScInsertContentsDlg> 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<sal_uInt16>(nFunction) ) ); + rReq.AppendItem( SfxInt16Item( FN_PARAM_5, static_cast<sal_Int16>(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<datatransfer::XTransferable>& 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<ScImportStringStream>(sStrBuffer); + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + VclPtr<AbstractScImportAsciiDlg> 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<ScImportExport>(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<css::datatransfer::XTransferable2> 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<const SfxUInt32Item*>( pItem) != nullptr ) + { + SotClipboardFormatId nFormat = static_cast<SotClipboardFormatId>(static_cast<const SfxUInt32Item*>(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<SfxAbstractPasteDialog> pDlg(pFact->CreatePasteDialog(pTabViewShell->GetFrameWeld())); + for (sal_uInt16 i=0; i<nFormatCount; i++) + { + SotClipboardFormatId nFormatId = aFormats.GetClipbrdFormatId( i ); + OUString aName = aFormats.GetClipbrdFormatName( i ); + // special case for paste dialog: '*' is replaced by object type + if ( nFormatId == SotClipboardFormatId::EMBED_SOURCE ) + aName = "*"; + pDlg->Insert( 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<sal_uInt32>(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<uno::Any> 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<AbstractScNameCreateDlg> 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<ScConsolidateParam>(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<const SfxBoolItem*>(&pReqArgs->Get( SID_DLG_RETOK )); + + if ( pOkItem->GetValue() ) // OK + { + OUString aFormula; + const SfxStringItem* pSItem = &pReqArgs->Get( SCITEM_STRING ); + const SfxBoolItem* pMatrixItem = static_cast<const SfxBoolItem*>(&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<const SfxStringItem*>(pItem)->GetValue(); + + if( pReqArgs->HasItem( FN_PARAM_1, &pItem ) ) + aSymbol = static_cast<const SfxStringItem*>(pItem)->GetValue(); + + if( pReqArgs->HasItem( FN_PARAM_2, &pItem ) ) + aAttrib = static_cast<const SfxStringItem*>(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<const ScCondFormatDlgItem*>(*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<SfxInt16Item>(FN_PARAM_1); + if (pParam) + { + auto pFormat = std::make_unique<ScConditionalFormat>(0, &rDoc); + pFormat->SetRange(aRangeList); + + if (nSlot == SID_OPENDLG_ICONSET) + { + ScIconSetType eIconSetType = limit_cast<ScIconSetType>(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<double, Color, ScColorScaleEntryType> ScaleEntry; + static std::vector<std::vector<ScaleEntry>> 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<Color, bool> DatabarEntry; + static std::vector<DatabarEntry> 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<weld::MessageDialog> 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<const SfxBoolItem*>(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<const ScTabOpItem&>( + 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<AbstractScNamePasteDlg> 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<OUString> 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<const SfxBoolItem*>(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<sc::NoteEntry> 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<sc::NoteEntry> 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<sc::NoteEntry> 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<const SfxStringItem*>( 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<SfxAbstractDialog> 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<const SvxHyperlinkItem*>(pItem); + const OUString& rName = pHyper->GetName(); + const OUString& rURL = pHyper->GetURL(); + const OUString& rTarget = pHyper->GetTargetFrame(); + sal_uInt16 nType = static_cast<sal_uInt16>(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<const ScCondFormatDlgItem*>(*itemsRange.begin()); + pList = const_cast<ScCondFormatDlgItem*>(pDlgItem)->GetConditionalFormatList(); + } + + if (!pList) + pList = rDoc.GetCondFormList( aPos.Tab() ); + + VclPtr<AbstractScCondFormatManagerDlg> 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<ScConditionalFormatList> 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<ScConditionalFormatList>(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<ScConditionalFormatList>(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<SfxStringItem>(SID_FILE_NAME); + const SfxStringItem* pSource = rReq.GetArg<SfxStringItem>(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<SfxStringItem>(SID_FILTER_NAME); + if ( pFilter ) + aFilter = pFilter->GetValue(); + const SfxStringItem* pOptions = rReq.GetArg<SfxStringItem>(SID_FILE_FILTEROPTIONS); + if ( pOptions ) + aOptions = pOptions->GetValue(); + const SfxUInt32Item* pRefresh = rReq.GetArg<SfxUInt32Item>(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<const SfxStringItem&>( 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<SfxInt32Item>(FN_PARAM_1); + SCCOL colStart = param1 ? param1->GetValue() : 0; + + const SfxInt32Item* param2 = rReq.GetArg<SfxInt32Item>(FN_PARAM_2); + SCROW rowStart = param2 ? param2->GetValue() : 0; + + const SfxInt32Item* param3 = rReq.GetArg<SfxInt32Item>(FN_PARAM_3); + SCCOL colEnd = param3 ? param3->GetValue() : 0; + + const SfxInt32Item* param4 = rReq.GetArg<SfxInt32Item>(FN_PARAM_4); + SCROW rowEnd = param4 ? param4->GetValue() : 0; + + const SfxInt32Item* param5 = rReq.GetArg<SfxInt32Item>(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<ScDPObject>& 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<ScDPObject>& 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<ScDPObject>& pNewDPObject) +{ + if (pSrcErrorId) + { + // Error occurred during data creation. Launch an error and bail out. + std::unique_ptr<weld::MessageDialog> 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<ScDPObject> 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<AbstractScDataPilotSourceTypeDlg> 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<OUString> aSources = ScDPObject::GetRegisteredSources(); + VclPtr<AbstractScDataPilotServiceDlg> 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<ScDPObject> 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<AbstractScDataPilotDatabaseDlg> 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<ScDPObject> 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<ScDPObject> 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<weld::MessageDialog> 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<ScDPObject> pNewDPObject; + SetupRangeForPivotTableDialog(aRange, aDestPos, pDoc, pSrcErrorId, pNewDPObject); + ErrorOrRunPivotLayoutDialog(pSrcErrorId, aDestPos, pScMod, pTabViewShell, pNewDPObject); + }); + + pTypeDlg->disposeOnce(); + return; + } + + std::unique_ptr<ScDPObject> 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<SfxAbstractTabDialog> pDlg; + ScSubTotalParam aSubTotalParam; + SfxItemSetFixed<SCITEM_SUBTDATA, SCITEM_SUBTDATA> 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 <config_features.h> + +#include <basic/sberrors.hxx> +#include <scitems.hxx> +#include <comphelper/lok.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/request.hxx> +#include <basic/sbxcore.hxx> +#include <svl/whiter.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <svl/stritem.hxx> +#include <svl/visitem.hxx> +#include <unotools/moduleoptions.hxx> + +#include <com/sun/star/frame/FrameSearchFlag.hpp> +#include <com/sun/star/sheet/TableValidationVisibility.hpp> + +#include <cellsh.hxx> +#include <dbdata.hxx> +#include <queryparam.hxx> +#include <tabvwsh.hxx> +#include <sc.hrc> +#include <globstr.hrc> +#include <scresid.hxx> +#include <global.hxx> +#include <scmod.hxx> +#include <docsh.hxx> +#include <document.hxx> +#include <uiitems.hxx> +#include <dbdocfun.hxx> +#include <reffact.hxx> +#include <utility> +#include <validat.hxx> +#include <validate.hxx> +#include <datamapper.hxx> + +#include <scui_def.hxx> +#include <scabstdlg.hxx> +#include <impex.hxx> +#include <asciiopt.hxx> +#include <datastream.hxx> +#include <datastreamdlg.hxx> +#include <dataproviderdlg.hxx> +#include <queryentry.hxx> +#include <markdata.hxx> +#include <documentlinkmgr.hxx> +#include <officecfg/Office/Common.hxx> + +#include <o3tl/make_shared.hxx> +#include <memory> + +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<SCROW>( 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<AbstractScSortWarningDlg> 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<SfxDialogController> m_xDlg; + public: + ScValidationRegisteredDlg(weld::Window* pParent, std::shared_ptr<SfxDialogController> xDlg) + : m_xDlg(std::move(xDlg)) + { + SC_MOD()->RegisterRefController(static_cast<sal_uInt16>(ScValidationDlg::SLOTID), m_xDlg, pParent); + } + ~ScValidationRegisteredDlg() + { + m_xDlg->Close(); + SC_MOD()->UnregisterRefController(static_cast<sal_uInt16>(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<frame::XFrame> xFrame = rViewFrame.GetFrame().GetFrameInterface(); + uno::Reference<frame::XFrame> 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<AbstractScDataFormDlg> 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; i<aSortParam.GetSortKeyCount(); i++ ) + aSortParam.maKeyState[i].bDoSort = false; + + pTabViewShell->UISort( 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<const SfxInt32Item*>(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<const SfxBoolItem*>(pItem)->GetValue(); + SCCOLROW nField1 = 0; + if ( pArgs->GetItemState( FN_PARAM_3, true, &pItem ) == SfxItemState::SET ) + nField1 = static_cast<const SfxInt32Item*>(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<const SfxBoolItem*>(pItem)->GetValue(); + SCCOLROW nField2 = 0; + if ( pArgs->GetItemState( FN_PARAM_5, true, &pItem ) == SfxItemState::SET ) + nField2 = static_cast<const SfxInt32Item*>(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<const SfxBoolItem*>(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<SCITEM_SORTDATA, SCITEM_SORTDATA> 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<ScAsyncTabController> 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<SfxInt32Item>(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<SfxInt32Item>(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<SfxInt32Item>(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; i<nEC; i++) + aParam.GetEntry(i).bDoQuery = false; + aParam.bDuplicate = true; + pTabViewShell->Query( 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<OUString> aList; + const ScDBCollection::NamedDBs& rDBs = pDBCol->getNamedDBs(); + for (const auto& rxDB : rDBs) + aList.push_back(rxDB->GetName()); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr<AbstractScSelEntryDlg> 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<ScDocument>(); + 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<sal_uInt16>(eMode) ) ); + aArgSet.Put( SfxUInt16Item( FID_VALID_CONDMODE, sal::static_int_cast<sal_uInt16>(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<sal_uInt16>(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<ScValidationDlg>(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<ScValidationMode>(pItem->GetValue()); + if ( const SfxUInt16Item* pItem = pOutSet->GetItemIfSet( FID_VALID_CONDMODE ) ) + eOper = static_cast<ScConditionMode>(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<ScValidErrorStyle>(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<AbstractScImportAsciiDlg> 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 <scitems.hxx> +#include <editeng/editview.hxx> +#include <editeng/editeng.hxx> +#include <formula/formulahelper.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/request.hxx> +#include <svl/stritem.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <scmod.hxx> +#include <appoptio.hxx> +#include <tabvwsh.hxx> +#include <document.hxx> +#include <sc.hrc> +#include <reffact.hxx> +#include <uiitems.hxx> +#include <autoform.hxx> +#include <cellsh.hxx> +#include <inputhdl.hxx> +#include <editable.hxx> +#include <funcdesc.hxx> +#include <markdata.hxx> +#include <scabstdlg.hxx> +#include <condformateasydlg.hxx> +#include <columnspanset.hxx> +#include <comphelper/lok.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <inputwin.hxx> + +#include <memory> + +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<OUString> 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<sal_Int64>(nCurIndex)) + + ", " + "\"category\": " + + OString::number(static_cast<sal_Int64>(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<const SfxUInt16Item&>(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<const SfxBoolItem*>(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<const SfxStringItem&>(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<const ScInputStatusItem*>(&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<const SfxInt16Item*>(pFormat)->GetValue(); + sal_uInt16 nId = sc::ConditionalFormatEasyDialogWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrame = pTabViewShell->GetViewFrame(); + SfxChildWindow* pWindow = rViewFrame.GetChildWindow( nId ); + GetViewData().GetDocument().SetEasyConditionalFormatDialogData(std::make_unique<ScConditionMode>(static_cast<ScConditionMode>(nFormat))); + + pScMod->SetRefDialog( nId, pWindow == nullptr ); + } + } + } + break; + + case FID_CELL_FORMAT: + { + if ( pReqArgs != nullptr ) + { + + // set cell attribute without dialog: + + SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END> aEmptySet( *pReqArgs->GetPool() ); + + SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END> 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<weld::MessageDialog> 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<weld::MessageDialog> 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<AbstractScNewScenarioDlg> 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<sc::ColRowSpan> aRanges; + SCCOLROW nRow = static_cast<const SfxInt32Item*>(pRow)->GetValue() - 1; + nHeight = pHeight->GetValue(); + ScMarkData& rMark = GetViewData().GetMarkData(); + + if ( rMark.IsRowMarked( static_cast<SCROW>(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<AbstractScMetricInputDlg> 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<sal_uInt16>(nVal) ); + + // #101390#; the value of the macro should be in HMM so use TwipsToEvenHMM to convert + pRequest.AppendItem( SfxUInt16Item( FID_ROW_HEIGHT, static_cast<sal_uInt16>(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<AbstractScMetricInputDlg> 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<sal_uInt16>(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<sal_uInt16>(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<sc::ColRowSpan> aRanges; + SCCOLROW nColumn = static_cast<const SfxUInt16Item*>(pColumn)->GetValue() - 1; + nWidth = pWidth->GetValue(); + ScMarkData& rMark = GetViewData().GetMarkData(); + + if ( rMark.IsColumnMarked( static_cast<SCCOL>(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<AbstractScMetricInputDlg> 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<sal_uInt16>(nVal) ); + + // #101390#; the value of the macro should be in HMM so use TwipsToEvenHMM to convert + pRequest.AppendItem( SfxUInt16Item( FID_COL_WIDTH, static_cast<sal_uInt16>(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<AbstractScMetricInputDlg> 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<sal_uInt16>(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<sal_uInt16>(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<SfxPoolItem> 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<const SfxBoolItem*>( pItem) && "wrong item"); + bMoveContents = static_cast<const SfxBoolItem*>(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<ScAutoFormatData> pNewEntry(pTabViewShell->CreateAutoFormatData()); + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr<AbstractScAutoFormatDlg> 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<weld::MessageDialog> 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 <sfx2/request.hxx> +#include <osl/diagnose.h> + +#include <cellsh.hxx> +#include <tabvwsh.hxx> +#include <global.hxx> +#include <scmod.hxx> +#include <inputhdl.hxx> +#include <inputwin.hxx> +#include <document.hxx> +#include <officecfg/Office/Calc.hxx> +#include <sc.hrc> + +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<SCCOLROW>(static_cast<const SfxInt16Item*>(pItem)->GetValue()); + if (pReqArgs->HasItem(FN_PARAM_2, &pItem)) + bSel = static_cast<const SfxBoolItem*>(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<SCCOL>(-nRepeat * nRTLSign), SC_SPLIT_LEFT); + else + pTabViewShell->MoveCursorRel( static_cast<SCCOL>(-nRepeat * nRTLSign), 0, SC_FOLLOW_LINE, bSel, bKeep ); + break; + + case SID_CURSORBLKLEFT: + pTabViewShell->MoveCursorArea( static_cast<SCCOL>(-nRepeat * nRTLSign), 0, SC_FOLLOW_JUMP, bSel, bKeep, !rReq.IsAPI() ); + break; + + case SID_CURSORRIGHT: + if (bScrollLock) + pTabViewShell->ScrollX( static_cast<SCCOL>(nRepeat * nRTLSign), SC_SPLIT_LEFT); + else + pTabViewShell->MoveCursorRel( static_cast<SCCOL>(nRepeat * nRTLSign), 0, SC_FOLLOW_LINE, bSel, bKeep ); + break; + + case SID_CURSORBLKRIGHT: + pTabViewShell->MoveCursorArea( static_cast<SCCOL>(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<SCCOL>(nRepeat), 0, nPageX, nPageY); + pTabViewShell->ScrollX( nPageX, SC_SPLIT_LEFT); + } + else + pTabViewShell->MoveCursorPage( static_cast<SCCOL>(nRepeat), 0, SC_FOLLOW_FIX, bSel, bKeep ); + break; + + case SID_CURSORPAGELEFT_: //XXX !!! + if (bScrollLock) + { + SCCOL nPageX; + SCROW nPageY; + pTabViewShell->GetPageMoveEndPosition( static_cast<SCCOL>(nRepeat), 0, nPageX, nPageY); + pTabViewShell->ScrollX( -nPageX, SC_SPLIT_LEFT); + } + else + pTabViewShell->MoveCursorPage( static_cast<SCCOL>(-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<sal_Int16>(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<SCCOLROW>(static_cast<const SfxInt16Item*>(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<sal_Int16>(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<SCCOL>(static_cast<const SfxInt32Item*>(pColItem)->GetValue()); + sal_Int16 nModifier = static_cast<const SfxInt16Item*>(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<SCROW>(static_cast<const SfxInt32Item*>(pRowItem)->GetValue()); + sal_Int16 nModifier = static_cast<const SfxInt16Item*>(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<const SfxBoolItem*>(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 <cliputil.hxx> +#include <attrib.hxx> +#include <viewdata.hxx> +#include <tabvwsh.hxx> +#include <transobj.hxx> +#include <document.hxx> +#include <dpobject.hxx> +#include <globstr.hrc> +#include <clipparam.hxx> +#include <clipoptions.hxx> +#include <rangelst.hxx> +#include <viewutil.hxx> +#include <markdata.hxx> +#include <gridwin.hxx> +#include <scitems.hxx> + +#include <sfx2/classificationhelper.hxx> +#include <comphelper/lok.hxx> + +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 <sal/config.h> + +#include <string_view> + +#include <unotools/localedatawrapper.hxx> +#include <vcl/fieldvalues.hxx> + +#include <colrowba.hxx> +#include <document.hxx> +#include <scmod.hxx> +#include <tabvwsh.hxx> +#include <appoptio.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <markdata.hxx> +#include <tabview.hxx> +#include <columnspanset.hxx> + +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<SCCOL>(nEntryNo), nTab)) + return 0; + else + return static_cast<sal_uInt16>(ScViewData::ToPixel( rDoc.GetColWidth( static_cast<SCCOL>(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<SCCOL>(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<sal_uInt16>( nNewSize / rViewData.GetPPTX() ); + + const ScMarkData& rMark = rViewData.GetMarkData(); + + std::vector<sc::ColRowSpan> aRanges; + if ( rMark.IsColumnMarked( static_cast<SCCOL>(nPos) ) ) + { + ScDocument& rDoc = rViewData.GetDocument(); + SCCOL nStart = 0; + while (nStart<=rDoc.MaxCol()) + { + while (nStart<rDoc.MaxCol() && !rMark.IsColumnMarked(nStart)) + ++nStart; + if (rMark.IsColumnMarked(nStart)) + { + SCCOL nEnd = nStart; + while (nEnd<rDoc.MaxCol() && rMark.IsColumnMarked(nEnd)) + ++nEnd; + if (!rMark.IsColumnMarked(nEnd)) + --nEnd; + aRanges.emplace_back(nStart,nEnd); + nStart = nEnd+1; + } + else + nStart = rDoc.MaxCol()+1; + } + } + else + { + aRanges.emplace_back(nPos,nPos); + } + + rViewData.GetView()->SetWidthOrHeight(true, aRanges, eMode, nSizeTwips); +} + +void ScColBar::HideEntries( SCCOLROW nStart, SCCOLROW nEnd ) +{ + std::vector<sc::ColRowSpan> 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<tools::Long>( 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<sal_uInt16>(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<sal_uInt16>( nNewSize / rViewData.GetPPTY() ); + + const ScMarkData& rMark = rViewData.GetMarkData(); + + std::vector<sc::ColRowSpan> aRanges; + if ( rMark.IsRowMarked( nPos ) ) + { + ScDocument& rDoc = rViewData.GetDocument(); + SCROW nStart = 0; + while (nStart<=rDoc.MaxRow()) + { + while (nStart<rDoc.MaxRow() && !rMark.IsRowMarked(nStart)) + ++nStart; + if (rMark.IsRowMarked(nStart)) + { + SCROW nEnd = nStart; + while (nEnd<rDoc.MaxRow() && rMark.IsRowMarked(nEnd)) + ++nEnd; + if (!rMark.IsRowMarked(nEnd)) + --nEnd; + aRanges.emplace_back(nStart,nEnd); + nStart = nEnd+1; + } + else + nStart = rDoc.MaxRow()+1; + } + } + else + { + aRanges.emplace_back(nPos,nPos); + } + + rViewData.GetView()->SetWidthOrHeight(false, aRanges, eMode, nSizeTwips); +} + +void ScRowBar::HideEntries( SCCOLROW nStart, SCCOLROW nEnd ) +{ + std::vector<sc::ColRowSpan> 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<tools::Long>( 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 <scitems.hxx> +#include <sfx2/bindings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <unotools/charclass.hxx> +#include <osl/diagnose.h> + +#include <dbfunc.hxx> +#include <docsh.hxx> +#include <attrib.hxx> +#include <sc.hrc> +#include <undodat.hxx> +#include <dbdata.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <global.hxx> +#include <dbdocfun.hxx> +#include <editable.hxx> +#include <queryentry.hxx> +#include <markdata.hxx> +#include <tabvwsh.hxx> +#include <sortparam.hxx> + +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<ScUndoAutoFilter>( pDocSh, aRange, pDBData->GetName(), false ) ); + + pDBData->SetAutoFilter(false); + + // remove filter (incl. Paint / Undo) + + SCSIZE nEC = aParam.GetEntryCount(); + for (SCSIZE i=0; i<nEC; i++) + aParam.GetEntry(i).bDoQuery = false; + aParam.bDuplicate = true; + Query( aParam, nullptr, true ); + + pDocSh->GetUndoManager()->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<weld::MessageDialog> 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<weld::MessageDialog> 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<ScUndoAutoFilter>(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<ScUndoAutoFilter>( 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 <dbfunc.hxx> +#include <document.hxx> +#include <globstr.hrc> + +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 <dbfunc.hxx> +#include <scitems.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <sfx2/app.hxx> +#include <unotools/collatorwrapper.hxx> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNameAccess.hpp> +#include <com/sun/star/sheet/DataPilotFieldFilter.hpp> +#include <com/sun/star/sheet/DataPilotFieldGroupBy.hpp> +#include <com/sun/star/sheet/DataPilotFieldOrientation.hpp> +#include <com/sun/star/sheet/DataPilotFieldSortMode.hpp> +#include <com/sun/star/sheet/DataPilotTableHeaderData.hpp> +#include <com/sun/star/sheet/MemberResultFlags.hpp> +#include <com/sun/star/sheet/XDimensionsSupplier.hpp> +#include <com/sun/star/sheet/XDrillDownDataSupplier.hpp> + +#include <global.hxx> +#include <scresid.hxx> +#include <globstr.hrc> +#include <undotab.hxx> +#include <undodat.hxx> +#include <dbdata.hxx> +#include <rangenam.hxx> +#include <docsh.hxx> +#include <olinetab.hxx> +#include <olinefun.hxx> +#include <dpobject.hxx> +#include <dpsave.hxx> +#include <dpdimsave.hxx> +#include <dbdocfun.hxx> +#include <dpoutput.hxx> +#include <editable.hxx> +#include <docpool.hxx> +#include <patattr.hxx> +#include <unonames.hxx> +#include <userlist.hxx> +#include <queryentry.hxx> +#include <markdata.hxx> +#include <tabvwsh.hxx> +#include <generalfunction.hxx> +#include <sortparam.hxx> + +#include <comphelper/lok.hxx> +#include <osl/diagnose.h> + +#include <memory> +#include <string_view> +#include <unordered_set> +#include <unordered_map> +#include <vector> +#include <algorithm> + +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<SCCOL>(nEnd) && nEndCol>=static_cast<SCCOL>(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<sal_uInt16>(nLevel) ); + else + { + if ( !bHidden ) + ShowOutline( bColumns, sal::static_int_cast<sal_uInt16>(nLevel), sal::static_int_cast<sal_uInt16>(nEntry) ); + else + HideOutline( bColumns, sal::static_int_cast<sal_uInt16>(nLevel), sal::static_int_cast<sal_uInt16>(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<SCCOL>(nEnd) && nEndCol>=static_cast<SCCOL>(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<weld::MessageDialog> 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<ScOutlineTable> pUndoTab; + std::unique_ptr<ScRangeName> pUndoRange; + std::unique_ptr<ScDBCollection> 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<SCCOL>(nOutStartCol), 0, nTab, static_cast<SCCOL>(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<ScUndoSubTotals>( 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<ScUndoInsertTab>( 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<ScDPDimensionSaveData*>( 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<ScDPDimensionSaveData*>( 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<OUString> 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<ScDPSaveGroupDimension> 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<tools::Long>(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<OUString> & 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<ScDPObject*> 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<ScDPSaveMember> 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<OUString> aMembers; + std::unordered_set<OUString> 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<OUString, sal_uInt16> UserSortMap; + UserSortMap aSubStrs; + sal_uInt16 nSubCount = 0; + ScUserList* pUserList = ScGlobal::GetUserList(); + if (!pUserList) + return; + + { + size_t n = pUserList->size(); + if (!n || *pUserListId >= static_cast<sal_uInt16>(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<OUString> 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<ScDPSaveMember>(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<ScDPObject> 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<SCSIZE>( rSource.aEnd.Col() - rSource.aStart.Col() + 1 ), + static_cast<SCSIZE>( 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<OUString> 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<ScDPObject> 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<ScDPObject> 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<sheet::DataPilotFieldFilter>& rFilters ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + if (rDoc.GetDocumentShell()->IsReadOnly()) + { + ErrorMessage(STR_READONLYERR); + return; + } + + Reference<sheet::XDimensionsSupplier> xDimSupplier = rDPObj.GetSource(); + Reference<container::XNameAccess> xDims = xDimSupplier->getDimensions(); + Reference<sheet::XDrillDownDataSupplier> xDDSupplier(xDimSupplier, UNO_QUERY); + if (!xDDSupplier.is()) + return; + + Sequence< Sequence<Any> > 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<XPropertySet> 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<sal_uInt32>(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<ScOutlineTable> pUndoTab; + std::unique_ptr<ScRangeName> pUndoRange; + std::unique_ptr<ScDBCollection> 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<SCCOL>(nOutStartCol), 0, nTab, static_cast<SCCOL>(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<ScUndoRepeatDB>( 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<ScTabViewShell*>(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 <svx/svditer.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svdpage.hxx> +#include <osl/diagnose.h> + +#include <dbfunc.hxx> +#include <drwlayer.hxx> +#include <document.hxx> + +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<SdrOle2Obj*>(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 <sal/config.h> + +#include <o3tl/unit_conversion.hxx> +#include <vcl/outdev.hxx> + +#include <drawutil.hxx> +#include <document.hxx> +#include <viewdata.hxx> + +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<nEndCol; i++) + { + sal_uInt16 nWidth = rDoc.GetColWidth(i,nTab); + nTwipsX += static_cast<tools::Long>(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<tools::Long>(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<double>(aPixelLog.X()) * + static_cast<double>(rZoomX.GetNumerator()) / + o3tl::convert<double>(nTwipsX, o3tl::Length::twip, o3tl::Length::mm100) / + static_cast<double>(rZoomX.GetDenominator()) ); + else + rScaleX = Fraction( 1, 1 ); + + if ( aPixelLog.Y() && nTwipsY ) + rScaleY = Fraction( static_cast<double>(aPixelLog.Y()) * + static_cast<double>(rZoomY.GetNumerator()) / + o3tl::convert<double>(nTwipsY, o3tl::Length::twip, o3tl::Length::mm100) / + static_cast<double>(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 <sal/config.h> + +#include <cstdlib> + +#include <svx/svdograf.hxx> +#include <svx/svdoole2.hxx> +#include <svx/ImageMapInfo.hxx> +#include <sfx2/viewfrm.hxx> +#include <comphelper/lok.hxx> +#include <svtools/optionsdrawinglayer.hxx> + +#include <strings.hrc> +#include <scresid.hxx> +#include <drawview.hxx> +#include <drwlayer.hxx> +#include "imapwrap.hxx" +#include <viewdata.hxx> +#include <document.hxx> +#include <userdat.hxx> +#include <tabvwsh.hxx> +#include <docsh.hxx> + +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; i<nCount; ++i ) + { + SdrObject* pObj = pMark->GetMark(i)->GetMarkedSdrObj(); + AddUndo (std::make_unique<ScUndoAnchorData>( 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; i<nCount; ++i ) + { + SdrObject* pObj = pMark->GetMark(i)->GetMarkedSdrObj(); + AddUndo (std::make_unique<ScUndoAnchorData>( 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; i<nCount; ++i ) + { + const SdrObject* pObj = pMark->GetMark(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<SdrObject*>(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<const SdrHint*>( &rHint ); + adjustAnchoredPosition(*pSdrHint, rDoc, nTab); + FmFormView::Notify( rBC,rHint ); + } + else if (auto pDeletedHint = dynamic_cast<const ScTabDeletedHint*>(&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<const ScTabSizeChangedHint*>(&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<const SdrGrafObj*>( pObj) != nullptr || dynamic_cast<const SdrOle2Obj*>( 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<SdrGrafObj*>( pObj) ) + aGraphic = pGrafObj->GetGraphic(); + else + { + const Graphic* pGraphic = static_cast<const SdrOle2Obj*>(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 <svx/svditer.hxx> +#include <svx/svdograf.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svdundo.hxx> +#include <sfx2/docfile.hxx> +#include <tools/urlobj.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <drawview.hxx> +#include <global.hxx> +#include <drwlayer.hxx> +#include <viewdata.hxx> +#include <document.hxx> +#include <docsh.hxx> +#include <drwtrans.hxx> +#include <transobj.hxx> +#include <drawutil.hxx> +#include <scmod.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <gridwin.hxx> +#include <userdat.hxx> + +#include <com/sun/star/embed/NoVisualAreaSizeException.hpp> +#include <com/sun/star/embed/Aspects.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <com/sun/star/chart2/XChartTypeContainer.hpp> +#include <com/sun/star/chart2/XCoordinateSystemContainer.hpp> +#include <com/sun/star/chart2/XDataSeriesContainer.hpp> +#include <com/sun/star/chart2/XChartDocument.hpp> +#include <comphelper/diagnose_ex.hxx> + +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<SdrModel> 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<ScDrawTransferObj> 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<OUString>& rRangeRep) +{ + const uno::Sequence<uno::Reference<chart2::data::XLabeledDataSequence> > xSeqs = xDataSource->getDataSequences(); + for (const uno::Reference<chart2::data::XLabeledDataSequence>& xLS : xSeqs) + { + uno::Reference<chart2::data::XDataSequence> 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<OUString>& rRangeRep) +{ + uno::Reference <chart2::XDiagram > 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<chart2::XDataSeries>& 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<OUString>& rRangeRep) +{ + if (!rObj.IsChart()) + // not a chart object. + return; + + const uno::Reference<embed::XEmbeddedObject>& xObj = rObj.GetObjRef(); + if (!xObj.is()) + return; + + uno::Reference<chart2::XChartDocument> xChartDoc(xObj->getComponent(), uno::UNO_QUERY); + if (!xChartDoc.is()) + return; + + if(xChartDoc->hasInternalDataProvider()) + return; + + getRangeFromErrorBar(xChartDoc, rRangeRep); + + uno::Reference<chart2::data::XDataSource> 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<ScRange>* pRanges = nullptr, const ScDocument* pDoc = nullptr ) +{ + bool bCalcSourceRanges = pRanges && pDoc; + std::vector<OUString> aRangeReps; + rAnyOle = rOneOle = false; + const size_t nCount = rMarkList.GetMarkCount(); + for (size_t i=0; i<nCount; ++i) + { + SdrMark* pMark = rMarkList.GetMark(i); + if ( !pMark ) + continue; + + SdrObject* pObj = pMark->GetMarkedSdrObj(); + if ( !pObj ) + continue; + + SdrObjKind nSdrObjKind = pObj->GetObjIdentifier(); + if (nSdrObjKind == SdrObjKind::OLE2) + { + rAnyOle = true; + rOneOle = (nCount == 1); + if ( bCalcSourceRanges ) + getRangeFromOle2Object( static_cast<const SdrOle2Obj&>( *pObj ), aRangeReps ); + else + break; + } + else if ( dynamic_cast<const SdrObjGroup*>( 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<const SdrOle2Obj&>( *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<SCTAB>& mrTabs; +public: + explicit InsertTabIndex(std::vector<SCTAB>& 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<ScRange>& rRanges) +{ + // Get a list of referenced table indices. + std::vector<SCTAB> 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<SCTAB>::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<ScRange> 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<SdrModel> 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<ScDrawTransferObj> 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<datatransfer::XTransferable> ScDrawView::CopyToTransferable() +{ + bool bAnyOle, bOneOle; + const SdrMarkList& rMarkList = GetMarkedObjectList(); + CheckOle( rMarkList, bAnyOle, bOneOle ); + + // update ScGlobal::xDrawClipDocShellRef + ScDrawLayer::SetGlobalDrawPersist( ScTransferObj::SetDrawClipDoc( bAnyOle ) ); + std::unique_ptr<SdrModel> 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<ScDrawTransferObj> 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<SdrUndoGroup> pUndoGroup(new SdrUndoGroup(GetModel())); + + const SdrMarkList& rMarkList = GetMarkedObjectList(); + tools::Long nDone = 0; + const size_t nCount = rMarkList.GetMarkCount(); + for (size_t i=0; i<nCount; ++i) + { + SdrObject* pObj = rMarkList.GetMark(i)->GetMarkedSdrObj(); + 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<SdrOle2Obj*>(pObj)->GetObjRef(); + if ( xObj.is() ) // NULL for an invalid object that couldn't be loaded + { + sal_Int64 nAspect = static_cast<SdrOle2Obj*>(pObj)->GetAspect(); + + if ( nAspect == embed::Aspects::MSOLE_ICON ) + { + MapMode aMapMode( MapUnit::Map100thMM ); + aOriginalSize = static_cast<SdrOle2Obj*>(pObj)->GetOrigObjSize( &aMapMode ); + bDo = true; + } + else + { + MapUnit aUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( xObj->getMapUnit( static_cast<SdrOle2Obj*>(pObj)->GetAspect() ) ); + try + { + awt::Size aSz = xObj->getVisualAreaSize( static_cast<SdrOle2Obj*>(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<const SdrGrafObj*>(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<SdrUndoGeoObj>( *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<SdrUndoGroup> 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<double>(aCellRect.GetWidth()) / static_cast<double>(nWidth); + tools::Long nHeight = aGraphicRect.GetHeight(); + assert(nHeight && "div-by-zero"); + double fScaleY = static_cast<double>(aCellRect.GetHeight()) / static_cast<double>(nHeight); + double fScaleMin = std::min(fScaleX, fScaleY); + + aCellRect.setWidth(static_cast<double>(aGraphicRect.GetWidth()) * fScaleMin); + aCellRect.setHeight(static_cast<double>(aGraphicRect.GetHeight()) * fScaleMin); + } + + pUndoGroup->AddAction( std::make_unique<SdrUndoGeoObj>( *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 <com/sun/star/embed/XEmbeddedObject.hpp> + +#include <svx/svditer.hxx> +#include <svx/svdograf.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svdouno.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdundo.hxx> +#include <svx/svdocapt.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/viewfrm.hxx> +#include <svx/sdrundomanager.hxx> +#include <svx/xfillit0.hxx> +#include <svx/xbtmpit.hxx> +#include <comphelper/lok.hxx> +#include <sfx2/lokhelper.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <svx/sdr/contact/objectcontactofpageview.hxx> +#include <svx/sdr/contact/viewobjectcontact.hxx> +#include <svx/sdr/contact/viewcontact.hxx> +#include <svx/sdrpagewindow.hxx> +#include <tools/UnitConversion.hxx> +#include <osl/diagnose.h> + +#include <drawview.hxx> +#include <global.hxx> +#include <viewdata.hxx> +#include <document.hxx> +#include <drawutil.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <tabvwsh.hxx> +#include <client.hxx> +#include <scmod.hxx> +#include <drwlayer.hxx> +#include <docsh.hxx> +#include <viewuno.hxx> +#include <userdat.hxx> +#include <postit.hxx> +#include <undocell.hxx> +#include <gridwin.hxx> + +#include <sc.hrc> + +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; nPos<nCount; ++nPos ) + { + SdrObject* pObj = rMrkList.GetMark(nPos)->GetMarkedSdrObj(); + 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; i<nCount; ++i) + { + SdrObject* pObj = rMark.GetMark(i)->GetMarkedSdrObj(); + if ( dynamic_cast<const SdrUnoObj*>( pObj) == nullptr && (pObj->GetLayer() != SC_LAYER_INTERN) ) + { + AddUndo( std::make_unique<SdrUndoObjectLayerChange>( *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<const SdrUnoObj*>( 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<sal_uInt16>(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<SdrObject>& 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<ScClient*>( 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<SdrOle2Obj*>(pObj); + if (!ScDocument::IsChart(pObj) ) + pViewSh->SetOleObjectShell(true); + else + pViewSh->SetChartShell(true); + bSubShellSet = true; + } + else if (pObj->GetObjIdentifier() == SdrObjKind::Graphic) + { + pGrafObj = static_cast<SdrGrafObj*>(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; i<nMarkCount; ++i) + { + SdrObject* pObj = rMarkList.GetMark(i)->GetMarkedSdrObj(); + if ( auto pObjGroup = dynamic_cast<const SdrObjGroup*>( 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<const SdrUnoObj*>( pSubObj) == nullptr) + bOnlyControls = false; + if (pSubObj->GetObjIdentifier() != SdrObjKind::Graphic) + bOnlyGraf = false; + + if ( !bOnlyControls && !bOnlyGraf ) break; + } + } + else + { + if (dynamic_cast<const SdrUnoObj*>( 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<frame::XController> xController = rFrame.GetController(); + if (xController.is()) + { + ScTabViewObj* pImp = dynamic_cast<ScTabViewObj*>( 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<ScTabViewObj*>( 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<ScTabViewObj*>( 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; i<nTabCount; i++) + { + SdrPage* pPage = rDrawLayer.GetPage(i); + DBG_ASSERT(pPage,"Page ?"); + if (pPage) + { + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( ScDrawLayer::GetVisibleName( pObject ) == rName ) + { + return pObject; + } + pObject = aIter.Next(); + } + } + } + } + return nullptr; +} + +//realize multi-selection of objects + +void ScDrawView::SelectCurrentViewObject( std::u16string_view rName ) +{ + sal_uInt16 nObjectTab = 0; + SdrObject* pFound = nullptr; + ScDocShell* pShell = rDoc.GetDocumentShell(); + if (pShell) + { + SdrModel& rDrawLayer = GetModel(); + sal_uInt16 nTabCount = rDoc.GetTableCount(); + for (sal_uInt16 i=0; i<nTabCount && !pFound; i++) + { + SdrPage* pPage = rDrawLayer.GetPage(i); + DBG_ASSERT(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 ) + return; + + ScTabView* pView = pViewData->GetView(); + 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<nTabCount && !pFound; i++) + { + SdrPage* pPage = rDrawLayer.GetPage(static_cast<sal_uInt16>(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<ScPostIt> 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<ScUndoReplaceNote>( *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<const SdrObjGroup*>( pObj) ) + { + SdrObjList *pLst = pObjGroup->GetSubList(); + for (const rtl::Reference<SdrObject>& 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<SdrUndoManager> ScDrawView::createLocalTextUndoManager() +{ + std::unique_ptr<SdrUndoManager> 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<SdrGrafObj> 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<SdrUndoAttrObj>(rHitObject)); + + SfxItemSetFixed<XATTR_FILLSTYLE, XATTR_FILLBITMAP> 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 <comphelper/string.hxx> +#include <comphelper/lok.hxx> +#include <scitems.hxx> +#include <editeng/eeitem.hxx> +#include <i18nutil/unicode.hxx> +#include <i18nutil/transliteration.hxx> + +#include <svx/clipfmtitem.hxx> +#include <svx/svxdlg.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/outliner.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/editeng.hxx> +#include <editeng/editview.hxx> +#include <editeng/escapementitem.hxx> +#include <editeng/flditem.hxx> +#include <editeng/flstitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/urlfieldhelper.hxx> +#include <editeng/editund2.hxx> +#include <svx/hlnkitem.hxx> +#include <vcl/EnumContext.hxx> +#include <editeng/postitem.hxx> +#include <editeng/scripttypeitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/wghtitem.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/msg.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/request.hxx> +#include <sfx2/viewfrm.hxx> +#include <svtools/cliplistener.hxx> +#include <svl/whiter.hxx> +#include <sot/formats.hxx> +#include <vcl/transfer.hxx> +#include <vcl/unohelp2.hxx> +#include <svl/stritem.hxx> +#include <editeng/colritem.hxx> + +#include <editsh.hxx> +#include <global.hxx> +#include <appoptio.hxx> +#include <scmod.hxx> +#include <sc.hrc> +#include <inputhdl.hxx> +#include <viewutil.hxx> +#include <viewdata.hxx> +#include <document.hxx> +#include <reffind.hxx> +#include <tabvwsh.hxx> +#include <editutil.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <gridwin.hxx> + +#define ShellClass_ScEditShell +#include <scslots.hxx> + +#include <scui_def.hxx> +#include <scabstdlg.hxx> +#include <memory> + +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<ScEditEngineDefaulter*>(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<const SfxUInt32Item*>( pItem)) + nFormat = static_cast<SotClipboardFormatId>(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<SfxAbstractPasteDialog> 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<const SvxFontItem&>( + attribs.Get(nFontWhich)); + + OUString aString; + std::shared_ptr<SvxFontItem> aNewItem(std::make_shared<SvxFontItem>(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<const SfxStringItem*>(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<SvxFontItem>( + 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<AbstractScNamePasteDlg> 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<OUString> 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<SfxAbstractTabDialog> 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<const SvxHyperlinkItem*>(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<const SvxFieldData> aSvxFieldDataPtr(GetURLField()); + const SvxURLField* pURLField(static_cast<const SvxURLField*>(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<sal_uInt16>(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<const SvxURLField*>(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<const SvxURLField*>(pField)) + { + uno::Reference<datatransfer::clipboard::XClipboard> 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<const SvxFieldData> aSvxFieldDataPtr(GetURLField()); + const SvxURLField* pURLField(static_cast<const SvxURLField*>(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<sal_Int32>(255))); + aHLinkItem.SetName(comphelper::string::stripEnd(sReturn, ' ')); + } + } + else + { + if (!pURLField) + { + aSvxFieldDataPtr = GetFirstURLFieldFromCell(); + pURLField = static_cast<const SvxURLField*>(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<const SvxFieldData> ScEditShell::GetURLField() +{ + ScInputHandler* pHdl = GetMyInputHdl(); + EditView* pActiveView = pHdl ? pHdl->GetActiveView() : pEditView; + if (!pActiveView) + return std::unique_ptr<const SvxFieldData>(); + + const SvxFieldData* pField = pActiveView->GetFieldUnderMouseOrInSelectionOrAtCursor(); + if (auto pURLField = dynamic_cast<const SvxURLField*>(pField)) + return pURLField->Clone(); + + return std::unique_ptr<const SvxFieldData>(); +} + +std::unique_ptr<const SvxFieldData> 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<sal_Int32> 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<const SvxURLField*>(pField)) + { + return pUrlField->Clone(); + } + } + } + aSel.nStartPos = aSel.nEndPos; + } + } + + return std::unique_ptr<const SvxFieldData>(); +} + +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<const SvxWeightItem*>(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<const SvxPostureItem*>(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<SvxEscapement>(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<SvxEscapement>(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<const SvxFontListItem*> + (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<SvxEscapement>(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<const SfxUInt16Item*>(pItem)->GetValue(); + + for (sal_uInt16 i=0; i<nCount; i++) + { + if ( bIsUndo ) + { + pTableView->Undo(); + 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 <com/sun/star/style/XStyleFamiliesSupplier.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/container/XNameAccess.hpp> + +#include <scitems.hxx> +#include <editeng/borderline.hxx> + +#include <sfx2/viewfrm.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/request.hxx> +#include <sfx2/sfxdlg.hxx> +#include <svl/whiter.hxx> + +#include <svl/stritem.hxx> +#include <svl/numformat.hxx> +#include <svl/zformat.hxx> +#include <svl/languageoptions.hxx> +#include <svl/cjkoptions.hxx> +#include <svl/ctloptions.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/langitem.hxx> +#include <svx/numinf.hxx> +#include <editeng/svxenum.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/lineitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/brushitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/scripttypeitem.hxx> +#include <editeng/shaditem.hxx> +#include <editeng/justifyitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> +#include <comphelper/lok.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> + +#include <formatsh.hxx> +#include <sc.hrc> +#include <globstr.hrc> +#include <scresid.hxx> +#include <docsh.hxx> +#include <patattr.hxx> +#include <scmod.hxx> +#include <stlpool.hxx> +#include <stlsheet.hxx> +#include <printfun.hxx> +#include <docpool.hxx> +#include <tabvwsh.hxx> +#include <attrib.hxx> + +#define ShellClass_ScFormatShell +#define ShellClass_TableFont +#define ShellClass_FormatForSelection +#include <scslots.hxx> + +#include <editeng/fontitem.hxx> +#include <sfx2/classificationhelper.hxx> + +#include <memory> + +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<SfxStyleFamily>(pFamItem->GetValue()); + const SfxPoolItem* pNameItem; + OUString aStyleName; + if (pArgs && SfxItemState::SET == pArgs->GetItemState( nSlotId, true, &pNameItem )) + aStyleName = static_cast<const SfxStringItem*>(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<ScStyleSheet*>( 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<const SfxStringItem*>(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<const SfxUInt32Item*>(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<const SfxStringItem*>(pItem)->GetValue(); + sal_uInt16 aLen = aCode.getLength(); + std::unique_ptr<OUString[]> 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<bool>(sFormat[0].toInt32()); + const bool bNegRed = static_cast<bool>(sFormat[1].toInt32()); + const sal_uInt16 nPrecision = static_cast<sal_uInt16>(sFormat[2].toInt32()); + const sal_uInt16 nLeadZeroes = static_cast<sal_uInt16>(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<ATTR_PATTERN_START, ATTR_PATTERN_END> 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<const SfxUInt16Item *>(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<const SvxHorJustifyItem*>(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<const SvxVerJustifyItem*>(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<SfxAllItemSet> 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<const SvxWeightItem*>(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<const SvxPostureItem*>(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<const SvxUnderlineItem*>( &rUnderline) != nullptr ) + { + pTabViewShell->ApplyAttr( rUnderline ); + pNewSet->Put( rUnderline,rUnderline.Which() ); + } + else if ( auto pTextLineItem = dynamic_cast<const SvxTextLineItem*>( &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<const SvxFontHeightItem*>( 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<const SvxFontItem&>(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<ATTR_PATTERN_START, ATTR_PATTERN_END> aOldSet( *rDoc.GetPool() ); + SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END> 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<const SvxBrushItem&>( + 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<SvxBoxItem> aBoxItem(std::make_shared<SvxBoxItem>(ATTR_BORDER)); + std::shared_ptr<SvxBoxInfoItem> aInfoItem(std::make_shared<SvxBoxInfoItem>(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<const SfxPoolItem**>(&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<const SfxPoolItem**>(&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<SvxBoxItem> aBoxItem(std::make_shared<SvxBoxItem>(ATTR_BORDER)); + std::shared_ptr<SvxBoxInfoItem> aInfoItem(std::make_shared<SvxBoxInfoItem>(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<sal_Int32>(bThousand)); + const OUString sNegRed = OUString::number(static_cast<sal_Int32>(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 <sal/config.h> + +#include <vcl/lineinfo.hxx> +#include <vcl/outdev.hxx> + +#include <gridmerg.hxx> + +#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 <scitems.hxx> + +#include <cstdlib> +#include <memory> +#include <editeng/adjustitem.hxx> +#include <osl/diagnose.h> +#include <sal/log.hxx> +#include <sot/storage.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/editobj.hxx> +#include <editeng/editstat.hxx> +#include <editeng/editview.hxx> +#include <editeng/flditem.hxx> +#include <editeng/justifyitem.hxx> +#include <editeng/outliner.hxx> +#include <editeng/misspellrange.hxx> +#include <o3tl/unit_conversion.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/ipclient.hxx> +#include <svl/stritem.hxx> +#include <svl/sharedstringpool.hxx> +#include <vcl/canvastools.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/cursor.hxx> +#include <vcl/dialoghelper.hxx> +#include <vcl/inputctx.hxx> +#include <vcl/settings.hxx> +#include <vcl/virdev.hxx> +#include <vcl/weldutils.hxx> +#include <sot/formats.hxx> +#include <comphelper/classids.hxx> +#include <comphelper/scopeguard.hxx> + +#include <svx/svdview.hxx> +#include <svx/svdocapt.hxx> +#include <svx/svdpagv.hxx> +#include <svtools/optionsdrawinglayer.hxx> + +#include <com/sun/star/sheet/DataPilotFieldFilter.hpp> +#include <com/sun/star/sheet/DataPilotFieldOrientation.hpp> +#include <com/sun/star/sheet/DataPilotTableHeaderData.hpp> +#include <com/sun/star/sheet/MemberResultFlags.hpp> +#include <com/sun/star/sheet/TableValidationVisibility.hpp> +#include <com/sun/star/awt/KeyModifier.hpp> +#include <com/sun/star/awt/MouseButton.hpp> +#include <com/sun/star/awt/XVclWindowPeer.hpp> +#include <com/sun/star/script/vba/VBAEventId.hpp> +#include <com/sun/star/script/vba/XVBAEventProcessor.hpp> +#include <com/sun/star/text/textfield/Type.hpp> + +#include <gridwin.hxx> +#include <tabvwsh.hxx> +#include <docsh.hxx> +#include <viewdata.hxx> +#include <tabview.hxx> +#include <select.hxx> +#include <scmod.hxx> +#include <document.hxx> +#include <attrib.hxx> +#include <dbdata.hxx> +#include <stlpool.hxx> +#include <printfun.hxx> +#include <cbutton.hxx> +#include <sc.hrc> +#include <helpids.h> +#include <globstr.hrc> +#include <strings.hrc> +#include <editutil.hxx> +#include <scresid.hxx> +#include <inputhdl.hxx> +#include <uiitems.hxx> +#include <formulacell.hxx> +#include <patattr.hxx> +#include <notemark.hxx> +#include <rfindlst.hxx> +#include <output.hxx> +#include <docfunc.hxx> +#include <dbdocfun.hxx> +#include <dpobject.hxx> +#include <transobj.hxx> +#include <drwtrans.hxx> +#include <seltrans.hxx> +#include <sizedev.hxx> +#include <AccessibilityHints.hxx> +#include <dpsave.hxx> +#include <viewuno.hxx> +#include <compiler.hxx> +#include <editable.hxx> +#include <fillinfo.hxx> +#include <filterentries.hxx> +#include <drwlayer.hxx> +#include <validat.hxx> +#include <tabprotection.hxx> +#include <postit.hxx> +#include <dpcontrol.hxx> +#include <checklistmenu.hxx> +#include <clipparam.hxx> +#include <overlayobject.hxx> +#include <cellsuno.hxx> +#include <drawview.hxx> +#include <dragdata.hxx> +#include <cliputil.hxx> +#include <queryentry.hxx> +#include <markdata.hxx> +#include <externalrefmgr.hxx> +#include <spellcheckcontext.hxx> +#include <uiobject.hxx> +#include <undoblk.hxx> +#include <datamapper.hxx> +#include <inputopt.hxx> +#include <queryparam.hxx> +#include <SparklineList.hxx> + +#include <officecfg/Office/Common.hxx> + +#include <svx/PaletteManager.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <vcl/svapp.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/uitest/eventdescription.hxx> +#include <svx/sdr/overlay/overlayselection.hxx> +#include <comphelper/lok.hxx> +#include <sfx2/lokhelper.hxx> + +#include <LibreOfficeKit/LibreOfficeKitEnums.h> + +#include <vector> +#include <boost/property_tree/json_parser.hpp> + +#include <FilterListBox.hxx> + +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<ScGridWindow> 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<ScGridWindow> 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<const AutoFilterData*>(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<const AutoFilterData*>(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<const AutoFilterData*>(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<OUString> 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<Color> 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<VirtualDevice> 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<OUString>& mrSetString; + std::unordered_set<double>& mrSetValue; +public: + explicit AddSelectedItemString(std::unordered_set<OUString>& rString, std::unordered_set<double>& 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<int>(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<int>(1024, nMaxTextWidth + 70); + nWindowWidth = mpAutoFilterPopup->IncreaseWindowWidthToFitText(nWindowWidth); + nMaxTextWidth = std::max<int>(nMaxTextWidth, nWindowWidth - 70); + + mpAutoFilterPopup->setOKAction(new AutoFilterAction(this, AutoFilterMode::Normal)); + mpAutoFilterPopup->setPopupEndAction( + new AutoFilterPopupEndAction(this, ScAddress(nCol, nRow, nTab))); + std::unique_ptr<AutoFilterData> 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<ScQueryEntry*> aEntries = aParam.FindAllEntriesByField(nCol); + std::unordered_set<OUString> aSelectedString; + std::unordered_set<double> 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<const AutoFilterData*>(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<decltype(nSizeX)>(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<ScFilterListBox>(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<nTabCount && rDoc.IsScenario(i); i++) + { + if (rDoc.HasScenarioRange( i, rScenRange )) + if (rDoc.GetName( i, aTabName )) + { + rFilterBox.append_text(aTabName); + if (rDoc.IsActiveScenario(i)) + aCurrent = aTabName; + } + } + rFilterBox.thaw(); + + ShowFilterMenu(pParent, aCellRect, bLayoutRTL); + + rFilterBox.grab_focus(); + + sal_Int32 nPos = -1; + if (!aCurrent.isEmpty()) + { + nPos = rFilterBox.find_text(aCurrent); + } + if (nPos == -1 && rFilterBox.n_children() > 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<ScFilterListBox>(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<ScTypedStrData> 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<ScTypedStrData> 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<sal_Int32>(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<sal_Int32>(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<sal_Int32>(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<ScUndoSelectionStyle*>(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<SfxStyleSheet*>( + 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<sheet::DataPilotFieldFilter> 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<css::frame::XController> xController = rViewFrame.GetFrame().GetController(); + if (xController.is()) + { + ScTabViewObj* pImp = dynamic_cast<ScTabViewObj*>( 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<SpellCallbackInfo&,void> 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<datatransfer::clipboard::XClipboard> xSystemClipboard = + GetClipboard(); + if (xSystemClipboard.is()) + { + xSystemClipboard->setContents( + uno::Reference<datatransfer::XTransferable>(), + uno::Reference<datatransfer::clipboard::XClipboardOwner>()); + } + + // 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; i<nTabCount && pDoc->IsScenario(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<datatransfer::XTransferable>& 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<SotTempStream> 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<datatransfer::XTransferable>& 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<SCTAB> nTabs; + + ScMarkData aMark = pTransObj->GetSourceMarkData(); + SCTAB nTabCount = pSourceDoc->GetTableCount(); + + for(SCTAB i=0; i<nTabCount; i++) + { + if(aMark.GetTableSelect(i)) + { + nTabs.push_back(i); + for(SCTAB j=i+1;j<nTabCount;j++) + { + if((!pSourceDoc->IsVisible(j))&&(pSourceDoc->IsScenario(j))) + { + nTabs.push_back( j ); + i=j; + } + else break; + } + } + } + + pView->ImportTables( pSrcShell,static_cast<SCTAB>(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<ScTransferObj> 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<ScDrawTransferObj> 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<datatransfer::XTransferable>& 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 = ( nEndCol<mrViewData.GetPosX(eHWhich) || nEndRow<mrViewData.GetPosY(eVWhich) ); + if ( SC_MOD()->IsFormulaMode() ) + 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<unsigned int>(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<sal_uInt16>(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<SCCOL>(nStartX)); + aNew.aStart.SetRow(static_cast<SCROW>(nStartY)); + aNew.aEnd.SetCol(static_cast<SCCOL>(nEndX)); + aNew.aEnd.SetRow(static_cast<SCROW>(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<ScFieldEditEngine> createEditEngine( ScDocShell* pDocSh, const ScPatternAttr& rPat ) +{ + ScDocument& rDoc = pDocSh->GetDocument(); + + auto pEngine = std::make_shared<ScFieldEditEngine>(&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<const SvxURLField*>(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<ScFieldEditEngine> 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<EditTextObject> 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<nTabCount && rDoc.IsScenario(nTab+1) && !rDoc.IsScenario(nTab) ) + { + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + + Size aButSize = mrViewData.GetScenButSize(); + tools::Long nBWidth = aButSize.Width(); + if (!nBWidth) + return false; // No Button drawn yet -> there is none + tools::Long nBHeight = aButSize.Height(); + tools::Long nHSpace = static_cast<tools::Long>( SC_SCENARIO_HSPACE * mrViewData.GetPPTX() ); + + //! cache the Ranges in Table!!!! + + ScMarkData aMarks(rDoc.GetSheetLimits()); + for (SCTAB i=nTab+1; i<nTabCount && rDoc.IsScenario(i); i++) + rDoc.MarkScenario( i, nTab, aMarks, false, ScScenarioFlags::ShowFrame ); + ScRangeList aRanges; + aMarks.FillRangeListWithMarks( &aRanges, false ); + + size_t nRangeCount = aRanges.size(); + for (size_t j=0; j< nRangeCount; ++j) + { + ScRange aRange = aRanges[j]; + // Always extend scenario frame to merged cells where no new non-covered cells + // are framed + rDoc.ExtendTotalMerge( aRange ); + + bool bTextBelow = ( aRange.aStart.Row() == 0 ); + + Point aButtonPos; + if ( bTextBelow ) + { + aButtonPos = mrViewData.GetScrPos( aRange.aEnd.Col()+1, aRange.aEnd.Row()+1, + eWhich, true ); + } + else + { + aButtonPos = mrViewData.GetScrPos( aRange.aEnd.Col()+1, aRange.aStart.Row(), + eWhich, true ); + aButtonPos.AdjustY( -nBHeight ); + } + if ( bLayoutRTL ) + aButtonPos.AdjustX( -(nHSpace - 1) ); + else + aButtonPos.AdjustX( -(nBWidth - nHSpace) ); // same for top or bottom + + tools::Rectangle aButRect( aButtonPos, Size(nBWidth,nBHeight) ); + if ( aButRect.Contains( rPosPixel ) ) + { + rScenRange = aRange; + return true; + } + } + } + + return false; +} + +void ScGridWindow::DrawLayerCreated() +{ + SetMapMode( GetDrawMapMode() ); + + // initially create overlay objects + ImpCreateOverlayObjects(); +} + +void ScGridWindow::SetAutoSpellContext( const std::shared_ptr<sc::SpellCheckContext> &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<editeng::MisspellRanges>* pRanges ) +{ + if (!mpSpellCheckCxt) + return; + + mpSpellCheckCxt->setMisspellRanges(nPosX, nPosY, pRanges); +} + +const std::vector<editeng::MisspellRanges>* 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<tools::Rectangle> 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<const ScTabViewShell *>(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<const ScTabViewShell *>(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<sdr::overlay::OverlayManager> 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<ScOverlayDashedBorder> pDashedBorder(new ScOverlayDashedBorder(aRange, aHighlight)); + xOverlayManager->add(*pDashedBorder); + mpOOSelectionBorder->append(std::move(pDashedBorder)); + } + + if ( aOldMode != aDrawMode ) + SetMapMode( aOldMode ); +} + +static std::vector<tools::Rectangle> convertPixelToLogical( + const ScViewData& rViewData, + const std::vector<tools::Rectangle>& rRectangles, + tools::Rectangle &rBoundingBox) +{ + std::vector<tools::Rectangle> 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<tools::Rectangle> &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<tools::Rectangle>& rRectangles, std::vector<tools::Rectangle>* 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<tools::Rectangle> 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<tools::Rectangle>& 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<const ScTabViewShell *>(it); + if (!pOther) + return; + + const ScGridWindow *pGrid = pOther->GetViewData().GetActiveWin(); + assert(pGrid); + + // Fetch pixels & convert for each view separately. + tools::Rectangle aDummyBBox; + std::vector<tools::Rectangle> 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<const ScTabViewShell *>(it); + if (!pOther) + return; + + // Fetch pixels & convert for each view separately. + tools::Rectangle aBoundingBox; + std::vector<tools::Rectangle> 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<tools::Rectangle> 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<sdr::overlay::OverlayManager> 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<sdr::overlay::OverlayObject> 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<tools::Rectangle>& rLogicRects) +{ + std::vector<tools::Rectangle> 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<tools::Rectangle> 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<sdr::overlay::OverlayManager> 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<sdr::overlay::OverlayObject> 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<ReferenceMark> aReferenceMarks; + ScInputHandler::SendReferenceMarks(pViewShell, aReferenceMarks); + } + } + + if ( aOldMode != aDrawMode ) + SetMapMode( aOldMode ); +} + +void ScGridWindow::UpdateHighlightOverlay() +{ + mpOOHighlight.reset(); // DeleteHighlightOverlay + std::vector<tools::Rectangle> 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<sdr::overlay::OverlayManager> 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<sdr::overlay::OverlayObject> 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<sdr::overlay::OverlayManager> 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<sdr::overlay::OverlayObject> 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<tools::Rectangle> 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<SCCOL>(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<sdr::overlay::OverlayManager> 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<sdr::overlay::OverlayObject> 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<tools::Rectangle> 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<sdr::overlay::OverlayManager> 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<sdr::overlay::OverlayObject> 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<sdr::overlay::OverlayManager> 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<sdr::overlay::OverlayObject> 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<sdr::overlay::OverlayManager> 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<basegfx::B2DRange> 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<sdr::overlay::OverlayObject> 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<sdr::overlay::OverlayManager> ScGridWindow::getOverlayManager() const +{ + SdrPageView* pPV = mrViewData.GetView()->GetScDrawView()->GetSdrPageView(); + + if(pPV) + { + SdrPageWindow* pPageWin = pPV->FindPageWindow( *GetOutDev() ); + + if ( pPageWin ) + { + return pPageWin->GetOverlayManager(); + } + } + + return rtl::Reference<sdr::overlay::OverlayManager>(); +} + +void ScGridWindow::flushOverlayManager() +{ + // #i70788# get the OverlayManager safely + rtl::Reference<sdr::overlay::OverlayManager> 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 <scitems.hxx> +#include <vcl/settings.hxx> +#include <comphelper/lok.hxx> + +#include <attrib.hxx> +#include <gridwin.hxx> +#include <tabvwsh.hxx> +#include <docsh.hxx> +#include <viewdata.hxx> +#include <pivot.hxx> +#include <uiitems.hxx> +#include <scresid.hxx> +#include <globstr.hrc> +#include <strings.hrc> +#include <pagedata.hxx> +#include <dpobject.hxx> +#include <dpsave.hxx> +#include <dpshttab.hxx> +#include <dbdocfun.hxx> +#include <checklistmenu.hxx> +#include <dpcontrol.hxx> +#include <userlist.hxx> +#include <scabstdlg.hxx> + +#include <com/sun/star/sheet/DataPilotFieldOrientation.hpp> +#include <com/sun/star/sheet/DataPilotTableHeaderData.hpp> + +#include <unordered_map> +#include <memory> +#include <vector> + +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<SCITEM_QUERYDATA, SCITEM_QUERYDATA> aArgSet( mrViewData.GetViewShell()->GetPool() ); + aArgSet.Put( ScQueryItem( SCITEM_QUERYDATA, &mrViewData, &aQueryParam ) ); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr<AbstractScPivotFilterDlg> 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<tools::Long> maFieldIndices; + std::vector<OUString> maFieldNames; +}; + +class DPFieldPopupOKAction : public ScCheckListMenuControl::Action +{ +public: + explicit DPFieldPopupOKAction(ScGridWindow* p) : + mpGridWindow(p) {} + + virtual bool execute() override + { + mpGridWindow->UpdateDPFromFieldPopupMenu(); + return true; + } +private: + VclPtr<ScGridWindow> mpGridWindow; +}; + +class DPFieldChangedAction : public ScCheckListMenuControl::Action +{ +public: + explicit DPFieldChangedAction(ScGridWindow* p) : + mpGridWindow(p) {} + + virtual bool execute() override + { + mpGridWindow->UpdateDPPopupMenuForFieldChange(); + return true; + } +private: + VclPtr<ScGridWindow> 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<DPMultiFieldPopupData> 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<ScCheckListMenuControl::ExtendedData> pDPData, + bool bDimOrientNotPage, ScDPObject* pDPObj, + bool bMultiField) +{ + if (!mpDPFieldPopup || !pDPObj) + return; + + const ScDPLabelData& rLabelData = static_cast<DPFieldPopupData*>(pDPData.get())->maLabels; + const tools::Long nDimIndex = static_cast<DPFieldPopupData*>(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<OUString> 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<DPFieldPopupData> 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<DPMultiFieldPopupData*>(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<OUString, OUString> MemNameMapType; + + if (!mpDPFieldPopup) + return; + + DPFieldPopupData* pDPData = static_cast<DPFieldPopupData*>(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<OUString, bool> 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 <typename T> +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<SCCOL>(rPosRect.Left()) : SCCOL_MAX; + SCROW nStartY = ( rPosRect.Top() >= 0 ) ? static_cast<SCROW>(rPosRect.Top()) : SCROW_MAX; + SCCOL nEndX = ( rPosRect.Right() >= 0 ) ? static_cast<SCCOL>(rPosRect.Right()) : SCCOL_MAX; + SCROW nEndY = ( rPosRect.Bottom() >= 0 ) ? static_cast<SCROW>(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<sal_uInt16>( pPageData->GetCount() ); + for (sal_uInt16 nPos=0; nPos<nCount && !nFound; nPos++) + { + ScPrintRangeData& rData = pPageData->GetData(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<nRowCount; nRowPos++) + if ( pRowEnd[nRowPos]+1 == nHitY ) + { + nFound = SC_PD_BREAK_V; + aSource = aRange; + nBreak = nHitY; + if ( nRowPos ) + nPrev = pRowEnd[nRowPos-1]+1; + else + nPrev = aRange.aStart.Row(); + } + } + if ( bHori && bInsideV && !nFound ) + { + size_t nColCount = rData.GetPagesX(); + const SCCOL* pColEnd = rData.GetPageEndX(); + for (size_t nColPos=0; nColPos+1<nColCount; nColPos++) + if ( pColEnd[nColPos]+1 == nHitX ) + { + nFound = SC_PD_BREAK_H; + aSource = aRange; + nBreak = nHitX; + if ( nColPos ) + nPrev = pColEnd[nColPos-1]+1; + else + nPrev = aRange.aStart.Col(); + } + } + } + } + } + + if (pSource) + *pSource = aSource; // print break + if (pBreak) + *pBreak = nBreak; // X/Y position of the moved page break + if (pPrev) + *pPrev = nPrev; // X/Y beginning of the page, which is above the break + return nFound; +} + +void ScGridWindow::PagebreakMove( const MouseEvent& rMEvt, bool bUp ) +{ + //! Combine scrolling and switching with RFMouseMove ! + //! (Inverting before scrolling is different) + + // 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 ( 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<SCCOLROW>(nPosX) : static_cast<SCCOLROW>(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<SCCOL>(nPagebreakBreak), nTab) & ScBreakType::Manual) + { + ScAddress aOldAddr( static_cast<SCCOL>(nPagebreakBreak), nPosY, nTab ); + pViewFunc->DeletePageBreak( true, true, &aOldAddr, false ); + } + if ( !bHide && !bToEnd ) // not at the end + { + ScAddress aNewAddr( static_cast<SCCOL>(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<SCCOL>(nPagebreakPrev), nTab) & ScBreakType::Manual); + if ( static_cast<SCCOL>(nPagebreakPrev) > aPagebreakSource.aStart.Col() && !bManualBreak ) + { + ScAddress aPrev( static_cast<SCCOL>(nPagebreakPrev), nPosY, nTab ); + pViewFunc->InsertPageBreak( true, true, &aPrev, false ); + } + + if (!pDocSh->AdjustPrintZoom( ScRange( + static_cast<SCCOL>(nPagebreakPrev),0,nTab, static_cast<SCCOL>(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; nPos<nOldCount; nPos++) + { + const ScRange* pOld = rDoc.GetPrintRange( nTab, nPos ); + if ( pOld ) + { + OUString aTemp; + if ( *pOld != aPagebreakSource ) + aTemp = pOld->Format(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 <svx/svdpagv.hxx> +#include <svx/svxids.hrc> +#include <editeng/sizeitem.hxx> +#include <sfx2/bindings.hxx> +#include <svl/ptitem.hxx> +#include <osl/diagnose.h> + +#include <tabvwsh.hxx> +#include <gridwin.hxx> +#include <dbfunc.hxx> +#include <viewdata.hxx> +#include <output.hxx> +#include <drawview.hxx> +#include <fupoor.hxx> + +#include <drawutil.hxx> +#include <document.hxx> +#include <comphelper/lok.hxx> + +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<SdrHdl>(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<SID_ATTR_POSITION, SID_ATTR_SIZE> 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 <memory> +#include <scitems.hxx> +#include <editeng/eeitem.hxx> + +#include <svtools/colorcfg.hxx> +#include <editeng/colritem.hxx> +#include <editeng/editview.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/brushitem.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/printer.hxx> +#include <vcl/cursor.hxx> +#include <vcl/settings.hxx> +#include <o3tl/unit_conversion.hxx> +#include <osl/diagnose.h> + +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/lok.hxx> +#include <comphelper/scopeguard.hxx> +#include <sfx2/lokhelper.hxx> +#include <sfx2/lokcomponenthelpers.hxx> + +#include <svx/svdview.hxx> +#include <svx/svdpagv.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/sdr/contact/objectcontactofpageview.hxx> +#include <svx/sdr/contact/viewobjectcontact.hxx> +#include <svx/sdr/contact/viewcontact.hxx> +#include <tabvwsh.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/sysdata.hxx> + +#include <gridwin.hxx> +#include <viewdata.hxx> +#include <output.hxx> +#include <document.hxx> +#include <attrib.hxx> +#include <patattr.hxx> +#include <dbdata.hxx> +#include <docoptio.hxx> +#include <notemark.hxx> +#include <dbfunc.hxx> +#include <scmod.hxx> +#include <inputhdl.hxx> +#include <rfindlst.hxx> +#include <hiranges.hxx> +#include <pagedata.hxx> +#include <docpool.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <docsh.hxx> +#include <cbutton.hxx> +#include <invmerge.hxx> +#include <editutil.hxx> +#include <inputopt.hxx> +#include <fillinfo.hxx> +#include <dpcontrol.hxx> +#include <queryparam.hxx> +#include <queryentry.hxx> +#include <markdata.hxx> +#include <sc.hrc> +#include <vcl/virdev.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <drwlayer.hxx> + +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<tools::Long>( SC_SCENARIO_HSPACE * nPPTX ); + tools::Long nVer = static_cast<tools::Long>( 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; i<nTabCount && rDoc.IsScenario(i); i++) + rDoc.MarkScenario( i, nTab, aMarks, false, ScScenarioFlags::ShowFrame ); + ScRangeListRef xRanges = new ScRangeList; + aMarks.FillRangeListWithMarks( xRanges.get(), false ); + + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + for (size_t j = 0, n = xRanges->size(); 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<nTabCount && rDoc.IsScenario(nAct); nAct++) + if ( rDoc.IsActiveScenario(nAct) && rDoc.HasScenarioRange(nAct,aRange) ) + { + OUString aDummyComment; + ScScenarioFlags nDummyFlags; + rDoc.GetName( nAct, aCurrent ); + rDoc.GetScenarioData( nAct, aDummyComment, aColor, nDummyFlags ); + } + + if (aCurrent.isEmpty()) + aCurrent = ScResId( STR_EMPTYDATA ); + + //! Own text "(None)" instead of "(Empty)" ??? + + lcl_DrawOneFrame( pDev, tools::Rectangle( aStartPos, aEndPos ), + aCurrent, aColor, bTextBelow, + rViewData.GetPPTX(), rViewData.GetPPTY(), rViewData.GetZoomY(), + rDoc, rViewData, bLayoutRTL ); + } + } +} + +static void lcl_DrawHighlight( ScOutputData& rOutputData, const ScViewData& rViewData, + const std::vector<ScHighlightEntry>& 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<ScTabViewShell*>(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<VirtualDevice>::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<ScLokRTLContext> 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<ScHighlightEntry> &rHigh = mrViewData.GetView()->GetHighlightRanges(); + bool bHasScenario = ( nTab+1<nTabCount && rDoc.IsScenario(nTab+1) && !rDoc.IsScenario(nTab) ); + bool bHasChange = ( rDoc.GetChangeTrack() != nullptr ); + + if ( bHasChange || bHasScenario || !rHigh.empty() ) + { + //! Merge SetChangedClip() with DrawMarks() ?? (different MapMode!) + + if ( bHasChange ) + aOutputData.DrawChangeTrack(); + + if ( bHasScenario ) + lcl_DrawScenarioFrames( pContentDev, mrViewData, eWhich, nX1,nY1,nX2,nY2 ); + + lcl_DrawHighlight( aOutputData, mrViewData, rHigh ); + } + + // Drawing foreground + + pContentDev->SetMapMode(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<sal_uInt16>(pRangeFinder->Count()); + for (sal_uInt16 i=0; i<nCount; i++) + { + ScRangeFindData& rData = pRangeFinder->GetObject(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<ScTabViewShell*>(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<ScTabViewShell*>(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<double>(aDrawMode.GetScaleX()), + (aOrigin.getY() * twipFactor) / static_cast<double>(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<double>(aDrawMode.GetScaleX()), + (aOrigin.getY() * twipFactor) / static_cast<double>(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<SCCOL> aColBreaks; + std::set<SCROW> 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<typename IndexType> + 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<IndexType, SCCOL>::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<double>(nTilePosX) * nOutputWidth / nTileWidth; + const double fTilePosYPixel = static_cast<double>(nTilePosY) * nOutputHeight / nTileHeight; + const double fTileBottomPixel = static_cast<double>(nTilePosY + nTileHeight) * nOutputHeight / nTileHeight; + const double fTileRightPixel = static_cast<double>(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<SCROW>(mrViewData, + fTilePosYPixel, fTileBottomPixel, + nTopLeftTileRowOffset, nTopLeftTileRowOrigin, + nTopLeftTileRow, nBottomRightTileRow); + + lcl_getBoundingRowColumnforTile<SCCOL>(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<sal_Int32>(nTopLeftTileCol, 0); + nTopLeftTileRow = std::max<sal_Int32>(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; i<nX2; i++) { + if (rDoc.ColHidden(i,nTab) && (i<rDoc.MaxCol() ? !rDoc.ColHidden(i+1,nTab) : true)) { + Point aStart = mrViewData.GetScrPos(i, nY1, eWhich, true ); + Point aEnd = mrViewData.GetScrPos(i, nY2, eWhich, true ); + rRenderContext.DrawLine(aStart,aEnd,aLineInfo); + } + } + for (int i=nY1; i<nY2; i++) { + if (rDoc.RowHidden(i,nTab) && (i<rDoc.MaxRow() ? !rDoc.RowHidden(i+1,nTab) : true)) { + Point aStart = mrViewData.GetScrPos(nX1, i, eWhich, true ); + Point aEnd = mrViewData.GetScrPos(nX2, i, eWhich, true ); + rRenderContext.DrawLine(aStart,aEnd,aLineInfo); + } + } + } //visible +} + +void ScGridWindow::DrawPagePreview( SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2, vcl::RenderContext& rRenderContext) +{ + ScPageBreakData* pPageData = mrViewData.GetView()->GetPageBreakData(); + 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<ScEditEngineDefaulter> 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<SfxItemSet>( 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<sal_uInt16>( pPageData->GetCount() ); + for (sal_uInt16 nPos=0; nPos<nCount; nPos++) + { + ScPrintRangeData& rData = pPageData->GetData(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<nColBreaks; nColPos++) + { + SCCOL nBreak = pColEnd[nColPos]+1; + if ( nBreak >= 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<nRowBreaks; nRowPos++) + { + SCROW nBreak = pRowEnd[nRowPos]+1; + if ( nBreak >= 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<nRowBreaks; nRowPos++) + { + SCROW nPrEndY = pRowEnd[nRowPos]; + if ( nPrEndY >= nY1 && nPrStartY <= nY2 ) + { + SCCOL nPrStartX = aRange.aStart.Col(); + for (nColPos=0; nColPos<nColBreaks; nColPos++) + { + SCCOL nPrEndX = pColEnd[nColPos]; + if ( nPrEndX >= 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<tools::Long>(nColPos)*nRowBreaks+nRowPos; + else + nPageNo += static_cast<tools::Long>(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<ScQueryParam> 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+1<nArrCount; nArrY++) + { + if ( pRowInfo[nArrY].bAutoFilter && pRowInfo[nArrY].bChanged ) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + + nRow = pThisRowInfo->nRowNo; + + 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 <editeng/flditem.hxx> + +#include <svx/fmpage.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdpagv.hxx> +#include <svx/ImageMapInfo.hxx> +#include <vcl/imapobj.hxx> +#include <vcl/help.hxx> +#include <tools/urlobj.hxx> +#include <sfx2/sfxhelp.hxx> + +#include <AccessibleDocument.hxx> +#include <com/sun/star/accessibility/XAccessible.hpp> + +#include <gridwin.hxx> +#include <viewdata.hxx> +#include <drawview.hxx> +#include <drwlayer.hxx> +#include <document.hxx> +#include <notemark.hxx> +#include <chgtrack.hxx> +#include <chgviset.hxx> +#include <dbfunc.hxx> +#include <postit.hxx> +#include <global.hxx> + +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<const ScChangeActionMove*>(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<ScAccessibleDocument> 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 <iostream> + +#include <gridwin.hxx> +#include <svx/svdpage.hxx> +#include <libxml/xmlwriter.h> +#include <viewdata.hxx> +#include <document.hxx> +#include <patattr.hxx> +#include <userdat.hxx> +#include <dpobject.hxx> + +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 <sfx2/dispatch.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/help.hxx> +#include <vcl/settings.hxx> +#include <svtools/colorcfg.hxx> +#include <osl/diagnose.h> + +#include <tabvwsh.hxx> +#include <hdrcont.hxx> +#include <dbdata.hxx> +#include <scmod.hxx> +#include <inputopt.hxx> +#include <gridmerg.hxx> +#include <document.hxx> +#include <markdata.hxx> +#include <tabview.hxx> +#include <viewdata.hxx> +#include <columnspanset.hxx> + +#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<nEntryNo && nScrPos<nMax; i++) + { + sal_uInt16 nAdd = GetEntrySize(i); + if (nAdd) + nScrPos += nAdd; + else + { + SCCOLROW nHidden = GetHiddenCount(i); + if (nHidden > 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<sal_uInt16>(GetSizePixel().Width()); + else + nBarSize = static_cast<sal_uInt16>(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<nSize; i++) + { + sal_uInt16 nSizePix = GetEntrySize( i ); + if (nSizePix) + { + nLineEnd += nSizePix * nLayoutSign; + + if ( bMarkRange && 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<sc::ColRowSpan> 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<SCCOLROW>(aRange.aStart.Row()); + SCCOLROW nEndRow = static_cast<SCCOLROW>(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<SCCOLROW>(aRange.aStart.Row()); + SCCOLROW nEndRow = static_cast<SCCOLROW>(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<ScTabViewShell*>(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<SCROW>(nPos); + bCellsProtected = rDoc.HasAttrib(0, nRPos, nTab, rDoc.MaxCol(), nRPos, nTab, HasAttrFlags::Protected); + } + else + { + // column header + SCCOL nCPos = static_cast<SCCOL>(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<sal_uInt16>(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<SCROW>(nPos), nTab, + rDoc.MaxCol(), sal::static_int_cast<SCROW>(nPos), nTab ); + else + aNewRange = ScRange( sal::static_int_cast<SCCOL>(nPos), 0, nTab, + sal::static_int_cast<SCCOL>(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 <overlayobject.hxx> + +#include <drawinglayer/attribute/fontattribute.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/primitive2d/PolygonHairlinePrimitive2D.hxx> +#include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> +#include <drawinglayer/primitive2d/textlayoutdevice.hxx> +#include <drawinglayer/primitive2d/textprimitive2d.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/matrix/b2dhommatrixtools.hxx> +#include <tools/lineend.hxx> +#include <utility> +#include <vcl/outdev.hxx> +#include <vcl/settings.hxx> +#include <vcl/metric.hxx> +#include <sal/log.hxx> + +#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<drawinglayer::primitive2d::TextSimplePortionPrimitive2D> pTitle = + new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + aTextMatrix, m_aTitle, 0, m_aTitle.getLength(), + std::vector<double>(), {}, 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<drawinglayer::primitive2d::TextSimplePortionPrimitive2D> pMessage = + new drawinglayer::primitive2d::TextSimplePortionPrimitive2D( + aTextMatrix, aLine, 0, aLine.getLength(), + std::vector<double>(), {}, 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 <svx/imapdlg.hxx> + +#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 <sal/types.h> +#include <sfx2/frame.hxx> + +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 <osl/diagnose.h> + +#include <invmerge.hxx> + +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 <svx/svdoutl.hxx> +#include <svx/svdmodel.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdocapt.hxx> +#include <svl/itempool.hxx> +#include <utility> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <vcl/window.hxx> + +#include <notemark.hxx> +#include <document.hxx> +#include <postit.hxx> + +#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<SdrPage> 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 <sal/config.h> + +#include <vcl/image.hxx> +#include <vcl/taskpanelist.hxx> +#include <vcl/settings.hxx> +#include <vcl/syswin.hxx> +#include <osl/diagnose.h> + +#include <olinewin.hxx> +#include <olinetab.hxx> +#include <document.hxx> +#include <dbfunc.hxx> +#include <bitmaps.hlst> + +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<sal_uInt16>(nLevel), sal::static_int_cast<sal_uInt16>(nEntry) ) : nullptr; +} + +bool ScOutlineWindow::IsHidden( SCCOLROW nColRowIndex ) const +{ + return mbHoriz ? + GetDoc().ColHidden(static_cast<SCCOL>(nColRowIndex), GetTab()) : + GetDoc().RowHidden(static_cast<SCROW>(nColRowIndex), GetTab()); +} + +bool ScOutlineWindow::IsFiltered( SCCOLROW nColRowIndex ) const +{ + // columns cannot be filtered + return !mbHoriz && GetDoc().RowFiltered( static_cast<SCROW>(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<SCCOL>(nColRowIndex), 0, meWhich, true ).X() : + mrViewData.GetScrPos( 0, static_cast<SCROW>(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<sal_uInt16>(nLevel) ); + while ( nEntry ) + { + --nEntry; + + const ScOutlineEntry* pEntry = pArray->GetEntry( sal::static_int_cast<sal_uInt16>(nLevel), + sal::static_int_cast<sal_uInt16>(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<sal_uInt16>(nLevel) ); + else + { + const ScOutlineEntry* pEntry = GetOutlineEntry( nLevel, nEntry ); + if ( pEntry ) + { + if ( pEntry->IsHidden() ) + rFunc.ShowOutline( mbHoriz, sal::static_int_cast<sal_uInt16>(nLevel), sal::static_int_cast<sal_uInt16>(nEntry) ); + else + rFunc.HideOutline( mbHoriz, sal::static_int_cast<sal_uInt16>(nLevel), sal::static_int_cast<sal_uInt16>(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<sal_uInt16>(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<sal_uInt16>(nLevel), + sal::static_int_cast<sal_uInt16>(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<sal_uInt16>(nLevel), + sal::static_int_cast<sal_uInt16>(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<sal_uInt16>(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 <scitems.hxx> +#include <editeng/brushitem.hxx> +#include <svtools/colorcfg.hxx> +#include <svx/rotmodit.hxx> +#include <editeng/shaditem.hxx> +#include <editeng/svxfont.hxx> +#include <tools/poly.hxx> +#include <vcl/svapp.hxx> +#include <vcl/pdfextoutdevdata.hxx> +#include <svtools/accessibilityoptions.hxx> +#include <svx/framelinkarray.hxx> +#include <drawinglayer/geometry/viewinformation2d.hxx> +#include <drawinglayer/processor2d/baseprocessor2d.hxx> +#include <drawinglayer/processor2d/processor2dtools.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/gradient.hxx> +#include <vcl/settings.hxx> +#include <svx/unoapi.hxx> +#include <sal/log.hxx> +#include <comphelper/lok.hxx> +#include <o3tl/unit_conversion.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <basegfx/matrix/b2dhommatrix.hxx> +#include <basegfx/range/b2drectangle.hxx> + +#include <output.hxx> +#include <document.hxx> +#include <drwlayer.hxx> +#include <formulacell.hxx> +#include <attrib.hxx> +#include <patattr.hxx> +#include <progress.hxx> +#include <pagedata.hxx> +#include <chgtrack.hxx> +#include <chgviset.hxx> +#include <viewutil.hxx> +#include <gridmerg.hxx> +#include <fillinfo.hxx> +#include <scmod.hxx> +#include <appoptio.hxx> +#include <postit.hxx> +#include <validat.hxx> +#include <detfunc.hxx> +#include <editutil.hxx> + +#include <SparklineRenderer.hxx> +#include <colorscale.hxx> + +#include <math.h> +#include <memory> + +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<OUString>::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+1<nArrCount; nArrY++) + nScrH += pRowInfo[nArrY].nHeight; + + bTabProtected = mpDoc->IsTabProtected( 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<int>(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 ( nX<mpDoc->MaxCol() && !bSingle ) + { + bSingle = ( nWidthXplus1 == 0 ); + for (nArrY=1; nArrY+1<nArrCount && !bSingle; nArrY++) + { + if (pRowInfo[nArrY].cellInfo(nX+1).bHOverlapped) + bSingle = true; + if (pRowInfo[nArrY].cellInfo(nX).bHideGrid) + bSingle = true; + } + } + + if (bDraw) + { + if ( nX<mpDoc->MaxCol() && bSingle ) + { + SCCOL nVisX = nX + 1; + while ( nVisX < mpDoc->MaxCol() && !mpDoc->GetColWidth(nVisX,nTab) ) + ++nVisX; + + nPosY = nScrY; + for (nArrY=1; nArrY+1<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + const tools::Long nNextY = nPosY + pThisRowInfo->nHeight; + + 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+1<nArrCount; nArrY++) + { + SCSIZE nArrYplus1 = nArrY+1; + nY = pRowInfo[nArrY].nRowNo; + SCROW nYplus1 = nY+1; + nPosY += pRowInfo[nArrY].nHeight; + + if (pRowInfo[nArrY].bChanged) + { + if ( bPage ) + { + for (SCROW i = nYplus1; i <= mpDoc->MaxRow(); ++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<int>(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 && nY<mpDoc->MaxRow() ) + { + 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<sal_uInt16>(pPageData->GetCount()); + for (sal_uInt16 nPos=0; nPos<nRangeCount; nPos++) + { + ScRange aRange = pPageData->GetData( 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+1<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + if ( pThisRowInfo->bChanged && 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<nArrCount; nRotY++) + if (pRowInfo[nRotY].nRotMaxCol != SC_ROTMAX_NONE && pRowInfo[nRotY].nRotMaxCol > nRotMax) + nRotMax = pRowInfo[nRotY].nRotMaxCol; + + for (SCSIZE nArrY=1; nArrY<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + if ( pThisRowInfo->nRotMaxCol != SC_ROTMAX_NONE && + ( pThisRowInfo->bChanged || pRowInfo[nArrY-1].bChanged || + ( nArrY+1<nArrCount && pRowInfo[nArrY+1].bChanged ) ) ) + { + SCROW nY = pThisRowInfo->nRowNo; + + 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<Color> const & pCol1 = rFirst.cellInfo(nX).mxColorScale; + std::optional<Color> 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<tools::Long>(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<tools::Long>(nLength * pOldDataBarInfo->mnLength/100.0) ); + } + else if(pOldDataBarInfo->mnLength > 0) + { + aPaintRect.SetLeft( nPosZero ); + tools::Long nLength = aPaintRect.Right() - nPosZero; + aPaintRect.SetRight( nPosZero + static_cast<tools::Long>(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<double>(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<Color> const & pColor, const SvxBrushItem* pBackground, std::optional<Color>& 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<SvxBrushItem>( aProtectedColor, ATTR_BACKGROUND ); + + // iterate through the rows to show + for (SCSIZE nArrY=1; nArrY+1<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + tools::Long nRowHeight = pThisRowInfo->nHeight; + + if ( pThisRowInfo->bChanged ) + { + if ( ( ( pThisRowInfo->bEmptyBack ) || mbSyntaxMode ) && !bDoAll ) + { + // nothing + } + else + { + // scan for rows with the same background: + SCSIZE nSkip = 0; + while ( nArrY+nSkip+2<nArrCount && + lcl_EqualBack( *pThisRowInfo, pRowInfo[nArrY+nSkip+1], + nX1, nX2, bShowProt, bPagebreakMode ) ) + { + ++nSkip; + nRowHeight += pRowInfo[nArrY+nSkip].nHeight; // after incrementing + } + + tools::Long nPosX = nScrX; + + if ( bLayoutRTL ) + nPosX += nMirrorW - nOneX; + + aRect = tools::Rectangle(nPosX, nPosY - nOneY, nPosX, nPosY - nOneY + nRowHeight); + if (bWorksInPixels) + aRect = rRenderContext.PixelToLogic(aRect); // internal data in pixels, but we'll be drawing in logic units + + const SvxBrushItem* pOldBackground = nullptr; + const SvxBrushItem* pBackground = nullptr; + std::optional<Color> 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<SCCOL>(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<Color> 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<Color>(), 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; nArrY<nArrCount; nArrY++) + { + bool bCornerY = ( nArrY == 0 ) || ( nArrY+1 == nArrCount ); + bool bSkipY = ( nArrY==0 && !bTop ) || ( nArrY+1 == nArrCount && !bBottom ); + + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + tools::Long nRowHeight = pThisRowInfo->nHeight; + + 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 (nWx<nX2 && !pRowInfo[0].basicCellInfo(nWx).nWidth) + ++nWx; + nMaxWidth = pRowInfo[0].basicCellInfo(nWx).nWidth; + } + + // rectangle is in logical orientation + tools::Rectangle aRect( nPosX, nPosY, + nPosX + ( nThisWidth - 1 ) * nLayoutSign, + nPosY + pRowInfo[nArrY].nHeight - 1 ); + + tools::Long nSize = pAttr->GetWidth(); + tools::Long nSizeX = static_cast<tools::Long>(nSize*mnPPTX); + if (nSizeX >= nMaxWidth) nSizeX = nMaxWidth-1; + tools::Long nSizeY = static_cast<tools::Long>(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+1<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + tools::Long nRowHeight = pThisRowInfo->nHeight; + + if ( pThisRowInfo->bChanged ) + { + // scan for more rows which must be painted: + SCSIZE nSkip = 0; + while ( nArrY+nSkip+2<nArrCount && pRowInfo[nArrY+nSkip+1].bChanged ) + { + ++nSkip; + nRowHeight += pRowInfo[nArrY+nSkip].nHeight; // after incrementing + } + + aRect = tools::Rectangle( Point( nScrX, nPosY ), + Size( nScrW+1-nOneX, nRowHeight+1-nOneY) ); + mpDev->DrawRect( 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<drawinglayer::processor2d::BaseProcessor2D> 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<nArrCount; nRotY++) + if (pRowInfo[nRotY].nRotMaxCol != SC_ROTMAX_NONE && pRowInfo[nRotY].nRotMaxCol > 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<drawinglayer::processor2d::BaseProcessor2D> pProcessor(CreateProcessor2D( )); + tools::Long nPosY = nScrY; + for (SCSIZE nArrY=1; nArrY<nArrCount; nArrY++) + { + // Rotated is also drawn one line above/below Changed if parts extend into the cell + + RowInfo& rPrevRowInfo = pRowInfo[nArrY-1]; + RowInfo& rThisRowInfo = pRowInfo[nArrY]; + RowInfo& rNextRowInfo = pRowInfo[nArrY+1]; + + tools::Long nRowHeight = rThisRowInfo.nHeight; + if ( rThisRowInfo.nRotMaxCol != SC_ROTMAX_NONE && + ( rThisRowInfo.bChanged || rPrevRowInfo.bChanged || + ( nArrY+1<nArrCount && rNextRowInfo.bChanged ) ) ) + { + SCROW nY = rThisRowInfo.nRowNo; + tools::Long nPosX = 0; + SCCOL nX; + for (nX=0; nX<=nRotMax; nX++) + { + if (nX==nX1) nPosX = nInitPosX; // calculated individually for preceding positions + + ScCellInfo* pInfo = &rThisRowInfo.cellInfo(nX); + tools::Long nColWidth = pRowInfo[0].basicCellInfo(nX).nWidth; + if ( pInfo->nRotateDir > 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<tools::Long>(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<tools::Long>(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<Color> 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<drawinglayer::processor2d::BaseProcessor2D> 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+1<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + + if(pThisRowInfo->bChanged) + { + 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+1<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + + if ( pThisRowInfo->bChanged ) + { + 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; nArrY<nArrCount; nArrY++) + pRowInfo[nArrY].bChanged = false; + + SCCOL nCol1 = mpDoc->MaxCol(), nCol2 = 0; + SCROW nRow1 = mpDoc->MaxRow(), nRow2 = 0; + bool bAnyDirty = false; + bool bAnyChanged = false; + + for (nArrY=0; nArrY<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + for (nX=nX1; nX<=nX2; nX++) + { + const ScRefCellValue& rCell = pThisRowInfo->cellInfo(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; nArrY<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + for (nX=nX1; nX<=nX2; nX++) + { + const ScRefCellValue& rCell = pThisRowInfo->cellInfo(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 ( nOverY<nArrCount && + pRowInfo[nOverY].cellInfo(nX).bVOverlapped ) + { + pRowInfo[nOverY].bChanged = true; + ++nOverY; + } + } + } + } + + if (bAnyDirty) + ScProgress::DeleteInterpretProgress(); + } + + mpDoc->EnableIdle(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; nArrY<nArrCount; nArrY++) // loop to end for bNoEndY check + { + SCROW nY = pRowInfo[nArrY].nRowNo; + + if ( nY==nRefStartY || (nY>nRefStartY && 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; nArrY<nArrCount; nArrY++) // loop to end for bNoEndY check + { + SCROW nY = pRowInfo[nArrY].nRowNo; + + if ( nY==nRefStartY || (nY>nRefStartY && 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; nArrY<nArrCount; nArrY++) // loop to end for bNoEndY check + { + SCROW nY = pRowInfo[nArrY].nRowNo; + + if ( nY==nRefStartY || (nY>nRefStartY && 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<sal_uInt16>(eActionType) ); + + } + } + if ( eActionType == SC_CAT_MOVE && + static_cast<const ScChangeActionMove*>(pAction)-> + GetFromRange().aStart.Tab() == nTab ) + { + ScRange aRange = static_cast<const ScChangeActionMove*>(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<sal_uInt16>(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+1<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + if ( pThisRowInfo->bChanged ) + { + 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<sc::Sparkline> 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+1<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + if ( pThisRowInfo->bChanged ) + { + 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+1<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + if ( pThisRowInfo->bChanged ) + { + 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+1<nArrCount; nNextY++) + { + bool bVOver; + if (pRowInfo[nNextY + 1].nRowNo == (pRowInfo[nNextY].nRowNo + 1)) { + bVOver = pRowInfo[nNextY].cellInfo(nX).bVOverlapped; + } else { + bVOver = mpDoc->GetAttr(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+1<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + if ( pThisRowInfo->bChanged ) + { + 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<tools::Long>( SC_CLIPMARK_SIZE * mnPPTX ); + tools::Long nNoteHeight = static_cast<tools::Long>( 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+1<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + if ( pThisRowInfo->bChanged ) + { + 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<tools::Long>( mpDoc->GetColWidth(nOverX,nTab) * mnPPTX ); + } + + while ( nOverY > 0 && ( mpDoc->GetAttr( + nOverX, nOverY, nTab, ATTR_MERGE_FLAG )->GetValue() & ScMF::Ver ) ) + { + --nOverY; + nStartPosY -= nLayoutSign * static_cast<tools::Long>( mpDoc->GetRowHeight(nOverY,nTab) * mnPPTY ); + } + + tools::Long nOutWidth = static_cast<tools::Long>( mpDoc->GetColWidth(nOverX,nTab) * mnPPTX ); + tools::Long nOutHeight = static_cast<tools::Long>( mpDoc->GetRowHeight(nOverY,nTab) * mnPPTY ); + + const ScMergeAttr* pMerge = mpDoc->GetAttr( nOverX, nOverY, nTab, ATTR_MERGE ); + SCCOL nCountX = pMerge->GetColMerge(); + for (SCCOL i=1; i<nCountX; i++) + nOutWidth += mpDoc->GetColWidth(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; i<nCountX; i++) + nOutWidth += mpDoc->GetColWidth(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<tools::Long>( 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 <scitems.hxx> +#include <editeng/eeitem.hxx> + +#include <editeng/adjustitem.hxx> +#include <svx/algitem.hxx> +#include <editeng/brushitem.hxx> +#include <svtools/colorcfg.hxx> +#include <editeng/colritem.hxx> +#include <editeng/charreliefitem.hxx> +#include <editeng/crossedoutitem.hxx> +#include <editeng/contouritem.hxx> +#include <editeng/editobj.hxx> +#include <editeng/editstat.hxx> +#include <editeng/emphasismarkitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/forbiddenruleitem.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/justifyitem.hxx> +#include <svx/rotmodit.hxx> +#include <editeng/udlnitem.hxx> +#include <editeng/unolingu.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/postitem.hxx> +#include <editeng/shdditem.hxx> +#include <editeng/wghtitem.hxx> +#include <editeng/wrlmitem.hxx> +#include <formula/errorcodes.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <svl/zformat.hxx> +#include <vcl/kernarray.hxx> +#include <vcl/svapp.hxx> +#include <vcl/metric.hxx> +#include <vcl/outdev.hxx> +#include <vcl/pdfextoutdevdata.hxx> +#include <vcl/settings.hxx> +#include <vcl/glyphitem.hxx> +#include <vcl/vcllayout.hxx> +#include <vcl/glyphitemcache.hxx> +#include <sal/log.hxx> +#include <unotools/charclass.hxx> +#include <osl/diagnose.h> +#include <tools/stream.hxx> + +#include <output.hxx> +#include <document.hxx> +#include <formulacell.hxx> +#include <attrib.hxx> +#include <patattr.hxx> +#include <cellform.hxx> +#include <editutil.hxx> +#include <progress.hxx> +#include <scmod.hxx> +#include <fillinfo.hxx> +#include <stlsheet.hxx> +#include <spellcheckcontext.hxx> +#include <scopetools.hxx> + +#include <com/sun/star/i18n/DirectionProperty.hpp> +#include <comphelper/scopeguard.hxx> +#include <comphelper/string.hxx> + +#include <memory> +#include <vector> +#include <o3tl/lru_map.hxx> +#include <o3tl/hash_combine.hxx> + +#include <math.h> + +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<typename ItemType, typename EnumType> +EnumType lcl_GetValue(const ScPatternAttr& rPattern, sal_uInt16 nWhich, const SfxItemSet* pCondSet) +{ + const ItemType& rItem = static_cast<const ItemType&>(rPattern.GetItem(nWhich, pCondSet)); + return static_cast<EnumType>(rItem.GetValue()); +} + +bool lcl_GetBoolValue(const ScPatternAttr& rPattern, sal_uInt16 nWhich, const SfxItemSet* pCondSet) +{ + return lcl_GetValue<SfxBoolItem, bool>(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<SvxJustifyMethodItem, SvxCellJustifyMethod>(*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<sal_uInt16>(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<sal_uInt16>(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<tools::Long>(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<tools::Long>(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<double>(aOld.GetScaleY()) / static_cast<double>(aOld.GetScaleX()) * static_cast<double>(aZoomY) / static_cast<double>(aZoomX); + } + else + { + // when formatting for printer, device map mode has already been taken care of + return static_cast<double>(aZoomY) / static_cast<double>(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<tools::Long>( 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<tools::Long>( 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<tools::Long>( 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<nMergeCols; i++ ) + { + tools::Long nColWidth = ( nCellX+i <= nX2 ) ? + pRowInfo[0].basicCellInfo(nCellX+i).nWidth : + static_cast<tools::Long>( mpDoc->GetColWidth( sal::static_int_cast<SCCOL>(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<SvxCellHorJustify>(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<tools::Long>( 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<tools::Long>( 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<tools::Long>( 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<tools::Long>( 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<double>(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<SvxFrameDirectionItem, SvxFrameDirection>(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<std::unique_ptr<ScPatternAttr> > aAltPatterns; + + KernArray aDX; + tools::Long nPosY = nScrY; + for (SCSIZE nArrY=1; nArrY+1<nArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + SCROW nY = pThisRowInfo->nRowNo; + 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<ScPatternAttr>(*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<ScPatternAttr>(*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<tools::Long>(aVars.GetLeftTotal() * mnPPTX) + + static_cast<tools::Long>(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<sal_uInt16>(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<tools::Long>(aVars.GetMargin()->GetTopMargin()*mnPPTY) + + static_cast<tools::Long>(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<tools::Long>( aVars.GetLeftTotal() * mnPPTX ) + + static_cast<tools::Long>( 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<tools::Long>( aVars.GetLeftTotal() * mnPPTX ); + break; + case SvxCellHorJustify::Right: + nJustPosX += nAvailWidth - aVars.GetTextSize().Width() - + static_cast<tools::Long>( aVars.GetRightTotal() * mnPPTX ); + bRightAdjusted = true; + break; + case SvxCellHorJustify::Center: + nJustPosX += ( nAvailWidth - aVars.GetTextSize().Width() + + static_cast<tools::Long>( aVars.GetLeftTotal() * mnPPTX ) - + static_cast<tools::Long>( 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<tools::Long>( aVars.GetMargin()->GetTopMargin() * mnPPTY ); + nJustPosY += nTop; + nTestClipHeight += nTop; + } + break; + case SvxCellVerJustify::Bottom: + { + tools::Long nBot = static_cast<tools::Long>( aVars.GetMargin()->GetBottomMargin() * mnPPTY ); + nJustPosY += nOutHeight - aVars.GetTextSize().Height() - nBot; + nTestClipHeight += nBot; + } + break; + case SvxCellVerJustify::Center: + { + tools::Long nTop = static_cast<tools::Long>( aVars.GetMargin()->GetTopMargin() * mnPPTY ); + tools::Long nBot = static_cast<tools::Long>( 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<sal_Int32>(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<ScFieldEditEngine>(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<nParCount; nPar++) + { + std::vector<sal_Int32> 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<tools::Long>(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<tools::Long>( nRealWidth * nAbsCos + nRealHeight * nAbsSin ); + else + return static_cast<tools::Long>( nRealHeight * nAbsCos + nRealWidth * nAbsSin ); + } + else if ( bWidth ) + return static_cast<tools::Long>(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<SvxHorJustifyItem, SvxCellHorJustify>(*pPattern, ATTR_HOR_JUSTIFY, pCondSet) ), + meHorJustContext( meHorJustAttr ), + meHorJustResult( meHorJustAttr ), + meVerJust( lcl_GetValue<SvxVerJustifyItem, SvxCellVerJustify>(*pPattern, ATTR_VER_JUSTIFY, pCondSet) ), + meHorJustMethod( lcl_GetValue<SvxJustifyMethodItem, SvxCellJustifyMethod>(*pPattern, ATTR_HOR_JUSTIFY_METHOD, pCondSet) ), + meVerJustMethod( lcl_GetValue<SvxJustifyMethodItem, SvxCellJustifyMethod>(*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<SfxItemSet>( 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<css::linguistic2::XHyphenator> 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<ScIndentItem, sal_uInt16>(*mpPattern, ATTR_INDENT, mpCondSet); + + rLeftM = static_cast<tools::Long>(((rMargin.GetLeftMargin() + nIndent) * nPPTX)); + rTopM = static_cast<tools::Long>((rMargin.GetTopMargin() * nPPTY)); + rRightM = static_cast<tools::Long>((rMargin.GetRightMargin() * nPPTX)); + rBottomM = static_cast<tools::Long>((rMargin.GetBottomMargin() * nPPTY)); + if(meHorJustAttr == SvxCellHorJustify::Right) + { + rLeftM = static_cast<tools::Long>((rMargin.GetLeftMargin() * nPPTX)); + rRightM = static_cast<tools::Long>(((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<tools::Long>(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<EditTextObject*>(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<vcl::PDFExtOutDevData* >( pDev->GetExtOutDevData() ); + bool bHasURL = pPDFData && isHyperlinkCell(); + if (!bHasURL) + return; + + tools::Long nURLWidth = static_cast<tools::Long>(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<OutputDevice>& 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<OutputDevice> 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<tools::Long>( 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<ScRotateValueItem, Degree100>(*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<sal_uInt16>(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<sal_uInt16>(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<tools::Long>( 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<tools::Long>(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<sal_uInt16>(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<sal_uInt16>(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<sal_uInt16>(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<sal_uInt16>(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<sal_uInt16>(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<sal_uInt16>(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<tools::Long>( 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<ScRotateValueItem, Degree100>(*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<sal_uInt16>(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<sal_uInt16>(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<tools::Long>( 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+1<nArrCount; nArrY++) // 0 of the rest of the merged + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + + if (nArrY==1) nRowPosY = nScrY; // positions before are calculated individually + + if ( pThisRowInfo->bChanged || 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<nArrCount; nRotY++) + if (pRowInfo[nRotY].nRotMaxCol != SC_ROTMAX_NONE && pRowInfo[nRotY].nRotMaxCol > 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<nArrCount; nArrY++) // 0 for the rest of the merged + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + tools::Long nCellHeight = static_cast<tools::Long>(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<tools::Long>(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<nX1) + { + if ((bBreak || eOrient!=SvxCellOrientation::Standard) && !bMerged) + bHidden = true; + else + { + nStartX = nInitPosX; + SCCOL nCol = nX1; + while (nCol > nX) + { + --nCol; + nStartX -= nLayoutSign * static_cast<tools::Long>(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; i<nCountX; i++) + nOutWidth += mpDoc->GetColWidth(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<SfxItemSet>( 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<css::linguistic2::XHyphenator> 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<tools::Long>( (pMargin->GetLeftMargin() + nIndent) * mnPPTX ); + tools::Long nTopM = static_cast<tools::Long>( pMargin->GetTopMargin() * mnPPTY ); + tools::Long nRightM = static_cast<tools::Long>( pMargin->GetRightMargin() * mnPPTX ); + tools::Long nBottomM = static_cast<tools::Long>( 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<tools::Long>(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<tools::Long>(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<tools::Long>(nEnginePixel * nAbsCos) + 2; + tools::Long nNewWidth = static_cast<tools::Long>(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<tools::Long>(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<tools::Long>(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<tools::Long>( nRealWidth * nAbsCos + + nRealHeight * nAbsSin ); + else + nEngineWidth = static_cast<tools::Long>( nRealHeight / nAbsSin ); + //! limit !!! + + nEngineHeight = static_cast<tools::Long>( 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<tools::Long>( 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<sal_uInt16>(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<tools::Long>(mxOutputEditEngine->CalcTextWidth()); + nRealHeight = mxOutputEditEngine->GetTextHeight(); + + if ( eRotMode != SVX_ROTATE_MODE_STANDARD ) + nEngineWidth = static_cast<tools::Long>( nRealHeight / fabs( nSin ) ); + } + + tools::Long nClipStartX = nStartX; + if (nX<nX1) + { + //! clipping is not needed when on the left side of the window + + if (nStartX<nScrX) + { + tools::Long nDif = nScrX - nStartX; + nClipStartX = nScrX; + aClipSize.AdjustWidth( -nDif ); + } + } + + tools::Long nClipStartY = nStartY; + if (nArrY==0 && nClipStartY < nRowPosY ) + { + tools::Long nDif = nRowPosY - nClipStartY; + nClipStartY = nRowPosY; + aClipSize.AdjustHeight( -nDif ); + } + + if ( nAttrRotate /* && eRotMode != SVX_ROTATE_MODE_STANDARD */ ) + { + // only clip rotated output text at the page border + nClipStartX = nScrX; + aClipSize.setWidth( nScrW ); + } + + if (bPixelToLogic) + aAreaParam.maClipRect = mpRefDevice->PixelToLogic( 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<double>(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 (nAvailWidth<nComp) nAvailWidth=nComp; + } + + // horizontal orientation + + if (eOrient==SvxCellOrientation::Standard && !nAttrRotate) + { + if (eHorJust==SvxCellHorJustify::Right || + eHorJust==SvxCellHorJustify::Center) + { + mxOutputEditEngine->SetUpdateLayout( 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<Degree10>(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<tools::Long>(nAddX) ); + aLogicStart.AdjustY(static_cast<tools::Long>(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 <sal/config.h> + +#include <o3tl/unit_conversion.hxx> +#include <svx/svdoutl.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdview.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <osl/diagnose.h> + +#include <output.hxx> +#include <drwlayer.hxx> +#include <document.hxx> +#include <tabvwsh.hxx> + +#include <svx/fmview.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <svx/sdrpagewindow.hxx> + +// #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; nCol<nX1; nCol++) + aOffset.AdjustX( -(mpDoc->GetColWidth( 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<SdrLayerID>(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<SdrLayerID>(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<SdrLayerID>(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 <overlayobject.hxx> +#include <basegfx/range/b2drange.hxx> +#include <basegfx/polygon/b2dpolygon.hxx> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <drawinglayer/primitive2d/PolyPolygonMarkerPrimitive2D.hxx> +#include <officecfg/Office/Common.hxx> +#include <vcl/settings.hxx> + +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 <tools/multisel.hxx> +#include <osl/diagnose.h> + +#include <pfuncache.hxx> +#include <printfun.hxx> +#include <docsh.hxx> +#include <markdata.hxx> +#include <prevloc.hxx> +#include <utility> + +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<nTabCount; nTab++ ) + { + tools::Long nAttrPage = 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; nPage<nPages[nTab]; nPage++ ) + { + Range aPageRange( nRenderer+1, nRenderer+1 ); + MultiSelection aPage( aPageRange ); + aPage.SetTotalRange( Range(0,RANGE_MAX) ); + aPage.Select( aPageRange ); + + ScPreviewLocationData aLocData( &rDoc, pDev ); + aFunc.DoPrint( aPage, nTabStart, nDisplayStart, false, &aLocData ); + + ScRange aCellRange; + tools::Rectangle aPixRect; + if ( aLocData.GetMainCellRange( aCellRange, aPixRect ) ) + aLocations.emplace_back( nRenderer, aCellRange, aPixRect ); + + ++nRenderer; + } + + nTabStart += nPages[nTab]; + } + + bLocInitialized = true; +} + +bool ScPrintFuncCache::FindLocation( const ScAddress& rCell, ScPrintPageLocation& rLocation ) const +{ + auto aIter = std::find_if(aLocations.begin(), aLocations.end(), + [&rCell](const ScPrintPageLocation& rLoc) { return rLoc.aCellRange.Contains(rCell); }); + if (aIter != aLocations.end()) + { + rLocation = *aIter; + return true; + } + return false; // not found +} + +bool ScPrintFuncCache::IsSameSelection( const ScPrintSelectionStatus& rStatus ) const +{ + return aSelection == rStatus; +} + +SCTAB ScPrintFuncCache::GetTabForPage( tools::Long nPage ) const +{ + ScDocument& rDoc = pDocSh->GetDocument(); + 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<SCTAB>(nPages.size())); + for ( SCTAB i=0; i<maxIndex; i++ ) + nRet += nPages[i]; + return nRet; +} + +tools::Long ScPrintFuncCache::GetDisplayStart( SCTAB nTab ) const +{ + //! merge with lcl_GetDisplayStart in preview? + + tools::Long nDisplayStart = 0; + ScDocument& rDoc = pDocSh->GetDocument(); + for (SCTAB i=0; i<nTab; i++) + { + if ( rDoc.NeedPageResetAfterTab(i) ) + nDisplayStart = 0; + else + { + if ( i < static_cast<SCTAB>(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 <sfx2/objface.hxx> +#include <sfx2/objsh.hxx> + +#include <pgbrksh.hxx> +#include <tabvwsh.hxx> +#include <document.hxx> + +#define ShellClass_ScPageBreakShell +#include <scslots.hxx> + +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 <scitems.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/request.hxx> +#include <svl/whiter.hxx> +#include <vcl/EnumContext.hxx> + +#include <sc.hrc> +#include <pivotsh.hxx> +#include <tabvwsh.hxx> +#include <docsh.hxx> +#include <document.hxx> +#include <dpobject.hxx> +#include <dpshttab.hxx> +#include <dbdocfun.hxx> +#include <uiitems.hxx> +#include <scabstdlg.hxx> + +#define ShellClass_ScPivotShell +#include <scslots.hxx> + + +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<SCITEM_QUERYDATA, SCITEM_QUERYDATA> aArgSet( pViewShell->GetPool() ); + aArgSet.Put( ScQueryItem( SCITEM_QUERYDATA, &rViewData, &aQueryParam ) ); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr<AbstractScPivotFilterDlg> 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 <scitems.hxx> +#include <editeng/eeitem.hxx> + +#include <officecfg/Office/Common.hxx> +#include <svtools/colorcfg.hxx> +#include <svx/fmview.hxx> +#include <editeng/sizeitem.hxx> +#include <svx/svdpagv.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/dispatch.hxx> +#include <svl/itemset.hxx> +#include <tools/multisel.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/settings.hxx> +#include <o3tl/deleter.hxx> +#include <o3tl/unit_conversion.hxx> + +#include <preview.hxx> +#include <prevwsh.hxx> +#include <prevloc.hxx> +#include <docsh.hxx> +#include <docfunc.hxx> +#include <printfun.hxx> +#include <printopt.hxx> +#include <stlpool.hxx> +#include <undostyl.hxx> +#include <drwlayer.hxx> +#include <scmod.hxx> +#include <markdata.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <sc.hrc> +#include <helpids.h> +#include <AccessibleDocumentPagePreview.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/ulspitem.hxx> +#include <editeng/colritem.hxx> +#include <editeng/fhgtitem.hxx> +#include <com/sun/star/accessibility/XAccessible.hpp> +#include <AccessibilityHints.hxx> +#include <vcl/svapp.hxx> +#include <viewutil.hxx> +#include <docpool.hxx> +#include <patattr.hxx> +#include <columnspanset.hxx> + +#include <memory> + +#define SC_PREVIEW_SHADOWSIZE 2 + +static tools::Long lcl_GetDisplayStart( SCTAB nTab, const ScDocument* pDoc, std::vector<tools::Long>& nPages ) +{ + tools::Long nDisplayStart = 0; + for (SCTAB i=0; i<nTab; i++) + { + if ( pDoc->NeedPageResetAfterTab(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<SCTAB>(nPages.size()) -1; + while (nTab > 0 && !nPages[nTab]) // not the last empty Table + --nTab; + OSL_ENSURE(0 < static_cast<SCTAB>(nPages.size()),"are all tables empty?"); + nTabPage = nPages[nTab] - 1; + nTabStart = 0; + for (sal_uInt16 i=0; i<nTab; i++) + nTabStart += nPages[i]; + + ScDocument& rDoc = pDocShell->GetDocument(); + 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<SCTAB>(nPages.size())) + nPages.push_back(0); + while (nStart > static_cast<SCTAB>(nFirstAttr.size())) + nFirstAttr.push_back(1); + + for (SCTAB i=nStart; i<nTabCount; i++) + { + if ( i == static_cast<SCTAB>(nPages.size())) + nPages.push_back(0); + if ( i == static_cast<SCTAB>(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<nTotalPages) + { + nTab = i; + nTabPage = nPageNo - nThisStart; + nTabStart = nThisStart; + + aPrintFunc.GetPrintState( aState ); + } + } + + nDisplayStart = lcl_GetDisplayStart( nTab, &rDoc, nPages ); + + if (nTabCount > 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<nTabsTested && nTab < static_cast<SCTAB>(nPages.size()); i++) + { + tools::Long nThisStart = nPartPages; + nPartPages += nPages[i]; + + if (nPageNo>=nThisStart && nPageNo<nPartPages) + { + nTab = i; + nTabPage = nPageNo - nThisStart; + nTabStart = nThisStart; + } + } + + ScDocument& rDoc = pDocShell->GetDocument(); + 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<tools::Long>( 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<ScPrintFunc, o3tl::default_delete<ScPrintFunc>> 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<ScEditEngineDefaulter> pEditEng( + new ScEditEngineDefaulter(EditEngine::CreatePool().get(), true)); + + pEditEng->SetRefMapMode(aMMMode); + auto pEditDefaults = std::make_unique<SfxItemSet>( 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<tools::Long>( nTopMargin - aOffset.Y() ), PointerStyle::VSizeBar ); + DrawInvert( static_cast<tools::Long>(nPageEndY - nBottomMargin ), PointerStyle::VSizeBar ); + DrawInvert( static_cast<tools::Long>( nLeftMargin - aOffset.X() ), PointerStyle::HSizeBar ); + DrawInvert( static_cast<tools::Long>( 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<tools::Long>( 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<SCTAB>(nPages.size()) ) + OSL_FAIL("nPages out of bounds, FIX IT"); + UpdateDrawView(); // The table eventually changes + + for (SCTAB i=0; i<nTabP; i++) + nPage += nPages[i]; + + // An empty Table on the previous Page + + if ( nPages[nTabP]==0 && nPage > 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<tools::Long>( aWinSize.Width() * 100 / ( aLocalPageSize.Width() * nWinScaleX )); + tools::Long nZoomY = static_cast<tools::Long>( aWinSize.Height() * 100 / ( aLocalPageSize.Height() * nWinScaleY )); + + tools::Long nOptimal = nZoomX; + if (!bWidthOnly && nZoomY<nOptimal) + nOptimal = nZoomY; + + if (nOptimal<20) + nOptimal = 20; + if (nOptimal>400) + nOptimal = 400; + + return static_cast<sal_uInt16>(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<tools::Long>( 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<tools::Long>( 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<ScUndoModifyStyle>( 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<ScUndoModifyStyle>( 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<sc::ColRowSpan> 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<sal_uInt16>(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<tools::Long>( 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<ScPrintFunc, o3tl::default_delete<ScPrintFunc>> 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<SCCOL>(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<css::accessibility::XAccessible> ScPreview::CreateAccessible() +{ + css::uno::Reference<css::accessibility::XAccessible> xAcc= GetAccessible(false); + if (xAcc.is()) + { + return xAcc; + } + + rtl::Reference<ScAccessibleDocumentPagePreview> 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<tools::Long>( 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 <prevloc.hxx> +#include <document.hxx> + +#include <o3tl/unit_conversion.hxx> +#include <osl/diagnose.h> +#include <vcl/outdev.hxx> + +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<nNewCount; i++) + pNewInfo[i] = pColInfo[nStart + i]; + SetColInfo( nNewCount, pNewInfo ); + } + else + SetColInfo( 0, nullptr ); // all invisible + } + } + + if ( !pRowInfo ) + return; + + // cells completely above the visible area + SCROW nStart = 0; + while ( nStart < nRows && pRowInfo[nStart].nPixelEnd < rPixelArea.Top() ) + ++nStart; + + // cells completely below the visible area + SCROW nEnd = nRows; + while ( nEnd > 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; i<nNewCount; i++) + pNewInfo[i] = pRowInfo[nStart + i]; + SetRowInfo( nNewCount, pNewInfo ); + } + else + SetRowInfo( 0, nullptr ); // all invisible +} + +ScPreviewLocationData::ScPreviewLocationData( ScDocument* pDocument, OutputDevice* pWin ) : + pWindow( pWin ), + pDoc( pDocument ), + nDrawRanges( 0 ), + nPrintTab( 0 ) +{ +} + +ScPreviewLocationData::~ScPreviewLocationData() +{ + Clear(); +} + +void ScPreviewLocationData::SetCellMapMode( const MapMode& rMapMode ) +{ + aCellMapMode = rMapMode; +} + +void ScPreviewLocationData::SetPrintTab( SCTAB nNew ) +{ + nPrintTab = nNew; +} + +void ScPreviewLocationData::Clear() +{ + m_Entries.clear(); + + nDrawRanges = 0; +} + +void ScPreviewLocationData::AddCellRange( const tools::Rectangle& rRect, const ScRange& rRange, bool bRepCol, bool bRepRow, + const MapMode& rDrawMap ) +{ + tools::Rectangle aPixelRect( pWindow->LogicToPixel( rRect ) ); + m_Entries.push_front( std::make_unique<ScPreviewLocationEntry>(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<ScPreviewLocationEntry>(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<ScPreviewLocationEntry>(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<ScPreviewLocationEntry>(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<ScPreviewLocationEntry>(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<ScPreviewLocationEntry>(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<sal_uLong>(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 <sal/config.h> + +#include <scitems.hxx> + +#include <comphelper/SetFlagContextHelper.hxx> +#include <sfx2/app.hxx> +#include <editeng/sizeitem.hxx> +#include <svx/zoomslideritem.hxx> +#include <svx/svdview.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/request.hxx> +#include <svl/stritem.hxx> +#include <svl/whiter.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/help.hxx> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <tools/urlobj.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/printer.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/viewfac.hxx> +#include <o3tl/unit_conversion.hxx> +#include <o3tl/string_view.hxx> + +#include <drwlayer.hxx> +#include <prevwsh.hxx> +#include <preview.hxx> +#include <printfun.hxx> +#include <scmod.hxx> +#include <inputhdl.hxx> +#include <docsh.hxx> +#include <tabvwsh.hxx> +#include <stlpool.hxx> +#include <editutil.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <sc.hrc> +#include <ViewSettingsSequenceDefines.hxx> +#include <viewuno.hxx> + +#include <svx/svxdlg.hxx> +#include <svx/dialogs.hrc> + +#include <basegfx/utils/zoomtools.hxx> +#include <svx/zoom_def.hxx> +#include <com/sun/star/document/XDocumentProperties.hpp> + +#include <scabstdlg.hxx> +#include <vcl/EnumContext.hxx> +#include <vcl/notebookbar/notebookbar.hxx> + +// for mouse wheel +#define MINZOOM_SLIDER 10 +#define MAXZOOM_SLIDER 400 + +#define SC_USERDATA_SEP ';' + +using namespace com::sun::star; + +#define ShellClass_ScPreviewShell +#include <scslots.hxx> + +#include <memory> + + +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<SystemWindow*>(pWin); + if (mpFrameWindow) + mpFrameWindow->SetCloseHdl(LINK(this, ScPreviewShell, CloseHdl)); + + eZoom = SvxZoomType::WHOLEPAGE; + + pHorScroll = VclPtr<ScrollAdaptor>::Create(pParent, true); + pVerScroll = VclPtr<ScrollAdaptor>::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<ScPreview>::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<ScDocShell*>(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<ScTabViewShell*>( 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<SystemWindow&,void>()); // 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<SfxTabPage> 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<SID_ATTR_ZOOM, SID_ATTR_ZOOM> aSet( GetPool() ); + SvxZoomItem aZoomItem( SvxZoomType::PERCENT, pPreview->GetZoom(), SID_ATTR_ZOOM ); + + aSet.Put( aZoomItem ); + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractSvxZoomDialog> 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<SvxZoomItem>(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<sal_uInt16>(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( nPage<nTotal-1 ) + { + SfxViewFrame& rSfxViewFrame = GetViewFrame(); + SfxRequest aSfxRequest(rSfxViewFrame, SID_PREVIEW_NEXT); + Execute( aSfxRequest ); + } + } + else + aCurPos.AdjustY(nVLine ); + break; + case SID_CURSORLEFT: + aCurPos.AdjustX( -nHLine ); + break; + case SID_CURSORRIGHT: + aCurPos.AdjustX(nHLine ); + break; + case SID_CURSORPAGEUP: + if( nThumbPos==0 || nMaxVertPos<0 ) + { + tools::Long nPage = pPreview->GetPageNo(); + + 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( nPage<nTotal-1 ) + { + SfxViewFrame& rSfxViewFrame = GetViewFrame(); + SfxRequest aSfxRequest(rSfxViewFrame, SID_PREVIEW_NEXT); + Execute( aSfxRequest ); + aCurPos.setY( 0 ); + } + } + else + aCurPos.AdjustY(nVPage ); + break; + case SID_CURSORHOME: + if( nMaxVertPos<0 ) + { + tools::Long nPage = pPreview->GetPageNo(); + 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 <svx/svdmodel.hxx> +#include <svl/hint.hxx> + +#include <prevwsh.hxx> +#include <docsh.hxx> +#include <preview.hxx> +#include <hints.hxx> + +void ScPreviewShell::Notify( SfxBroadcaster&, const SfxHint& rHint ) +{ + bool bDataChanged = false; + + if (rHint.GetId() == SfxHintId::ThisIsAnSdrHint) + { + const SdrHint* pSdrHint = static_cast<const SdrHint*>(&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<const ScPaintHint*>(&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 <scitems.hxx> +#include <editeng/eeitem.hxx> + +#include <printfun.hxx> + +#include <editeng/adjustitem.hxx> +#include <editeng/borderline.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/brushitem.hxx> +#include <svtools/colorcfg.hxx> +#include <editeng/editstat.hxx> +#include <svx/fmview.hxx> +#include <editeng/frmdiritem.hxx> +#include <editeng/lrspitem.hxx> +#include <editeng/paperinf.hxx> +#include <editeng/pbinitem.hxx> +#include <editeng/shaditem.hxx> +#include <editeng/sizeitem.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/ulspitem.hxx> +#include <sfx2/printer.hxx> +#include <tools/multisel.hxx> +#include <sfx2/docfile.hxx> +#include <tools/urlobj.hxx> +#include <osl/diagnose.h> + +#include <editutil.hxx> +#include <docsh.hxx> +#include <output.hxx> +#include <viewdata.hxx> +#include <viewopti.hxx> +#include <stlpool.hxx> +#include <pagepar.hxx> +#include <attrib.hxx> +#include <patattr.hxx> +#include <docpool.hxx> +#include <dociter.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <pagedata.hxx> +#include <printopt.hxx> +#include <prevloc.hxx> +#include <scmod.hxx> +#include <drwlayer.hxx> +#include <fillinfo.hxx> +#include <postit.hxx> + +#include <memory> +#include <com/sun/star/document/XDocumentProperties.hpp> + +#define ZOOM_MIN 10 + +namespace{ + +bool lcl_GetBool(const SfxItemSet* pSet, sal_uInt16 nWhich) +{ + return static_cast<const SfxBoolItem&>(pSet->Get(nWhich)).GetValue(); +} + +sal_uInt16 lcl_GetUShort(const SfxItemSet* pSet, sal_uInt16 nWhich) +{ + return static_cast<const SfxUInt16Item&>(pSet->Get(nWhich)).GetValue(); +} + +bool lcl_GetShow(const SfxItemSet* pSet, sal_uInt16 nWhich) +{ + return ScVObjMode::VOBJ_MODE_SHOW == static_cast<const ScViewObjectModeItem&>(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; i<nPagesX; i++) + if (!aHidden[i]) + ++nVis; + return nVis; + } + else + return nPagesX; +} + +static tools::Long lcl_LineTotal(const ::editeng::SvxBorderLine* pLine) +{ + return pLine ? ( pLine->GetScaledWidth() ) : 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<std::vector<SCCOL>>(); + m_aRanges.m_xPageEndY = std::make_shared<std::vector<SCROW>>(); + m_aRanges.m_xPageRows = std::make_shared<std::map<size_t, ScPageRowEntry>>(); + 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<std::vector<SCCOL>>(); + m_aRanges.m_xPageEndY = std::make_shared<std::vector<SCROW>>(); + m_aRanges.m_xPageRows = std::make_shared<std::map<size_t, ScPageRowEntry>>(); + } + + 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<std::vector<SCCOL>>(); + m_aRanges.m_xPageEndY = std::make_shared<std::vector<SCROW>>(); + m_aRanges.m_xPageRows = std::make_shared<std::map<size_t, ScPageRowEntry>>(); + 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<std::vector<SCCOL>>(); + m_aRanges.m_xPageEndY = std::make_shared<std::vector<SCROW>>(); + m_aRanges.m_xPageRows = std::make_shared<std::map<size_t, ScPageRowEntry>>(); + } + + 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<sal_uInt16>( 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+1<rTabInfo.mnArrCount; nArrY++) + { + RowInfo* pThisRowInfo = &rTabInfo.mpRowInfo[nArrY]; + for (SCCOL nX=nX1; nX<=nX2; nX++) + { + ScCellInfo& rCellInfo = pThisRowInfo->cellInfo(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<double>(nDevSizeX) / nTwipsSizeX; + double nScaleY = static_cast<double>(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<FmFormView> 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<tools::Long>(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<const SfxPoolItem**>(&pScaleItem) ); + if ( SfxItemState::DEFAULT == eState ) + pScaleItem = &pParamSet->GetPool()->GetDefaultItem( ATTR_PAGE_SCALE ); + + eState = pParamSet->GetItemState( ATTR_PAGE_SCALETO, false, + reinterpret_cast<const SfxPoolItem**>(&pScaleToItem) ); + if ( SfxItemState::DEFAULT == eState ) + pScaleToItem = &pParamSet->GetPool()->GetDefaultItem( ATTR_PAGE_SCALETO ); + + eState = pParamSet->GetItemState( ATTR_PAGE_SCALETOPAGES, false, + reinterpret_cast<const SfxPoolItem**>(&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<sal_uInt16>(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<ScRange> oRepeatCol = rDoc.GetRepeatColRange( nPrintTab ); + std::optional<ScRange> 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<int>( 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<tools::Long>( pShadow->CalcShadowSpace(SvxShadowItemSide::LEFT) * nScaleX ); + nRight += static_cast<tools::Long>( pShadow->CalcShadowSpace(SvxShadowItemSide::RIGHT) * nScaleX ); + nTop += static_cast<tools::Long>( pShadow->CalcShadowSpace(SvxShadowItemSide::TOP) * nScaleY ); + nBottom += static_cast<tools::Long>( 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<tools::Long>( lcl_LineTotal(pBorderData->GetLeft()) * nScaleX / 2 ); + nRight += static_cast<tools::Long>( lcl_LineTotal(pBorderData->GetRight()) * nScaleX / 2 ); + nTop += static_cast<tools::Long>( lcl_LineTotal(pBorderData->GetTop()) * nScaleY / 2 ); + nBottom += static_cast<tools::Long>( 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<tools::Long>( pShadow->GetWidth() * nScaleX ); + tools::Long nShadowY = static_cast<tools::Long>( 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<sal_uInt16>(nEffHeight); + aTabInfo.mpRowInfo[0].basicCellInfo(0).nWidth = + aTabInfo.mpRowInfo[1].basicCellInfo(0).nWidth = static_cast<sal_uInt16>(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<tools::Long>(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<tools::Long>( 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<tools::Long>(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<tools::Long>(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<tools::Long>(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<tools::Long>(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<tools::Long>(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<tools::Long>(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; nCol<nX1; nCol++) + aTwipOffset.AdjustX( -(rDoc.GetColWidth( nCol, nPrintTab )) ); + aTwipOffset.AdjustY( -sal_Int32(rDoc.GetRowHeight( 0, nY1-1, nPrintTab )) ); + + Point aMMOffset(o3tl::convert(aTwipOffset, o3tl::Length::twip, o3tl::Length::mm100)); + aMMOffset += Point( nLogStX, nLogStY ); + MapMode aDrawMapMode( MapUnit::Map100thMM, aMMOffset, aLogicMode.GetScaleX(), aLogicMode.GetScaleY() ); + + // get pixel rectangle + + Size aOnePixel = pDev->PixelToLogic(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<tools::Long>(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<tools::Long>(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<tools::Long>(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<tools::Long>(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<tools::Long>(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<tools::Long>(aPageSize.Width() * nScaleX * 100 / nZoom), + static_cast<tools::Long>(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<tools::Long>(aPageSize.Width() * nScaleX * 100 / nZoom), + static_cast<tools::Long>(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<tools::Long>(PRINT_HEADER_WIDTH * nScaleX); + nHeaderHeight = static_cast<tools::Long>(PRINT_HEADER_HEIGHT * nScaleY); + } + if (bDoRepCol) + for (SCCOL i=nRepeatStartCol; i<=nRepeatEndCol; i++) + nRepeatWidth += static_cast<tools::Long>(rDoc.GetColWidth(i,nPrintTab) * nScaleX); + if (bDoRepRow) + nRepeatHeight += rDoc.GetScaledRowHeight( nRepeatStartRow, + nRepeatEndRow, nPrintTab, nScaleY); + for (SCCOL i=nX1; i<=nX2; i++) + nContentWidth += static_cast<tools::Long>(rDoc.GetColWidth(i,nPrintTab) * nScaleX); + nContentHeight += rDoc.GetScaledRowHeight( nY1, nY2, nPrintTab, + nScaleY); + + // partition the page + + tools::Long nStartX = static_cast<tools::Long>( nLeftSpace * nScaleX ); + tools::Long nStartY = static_cast<tools::Long>( nTopSpace * nScaleY ); + tools::Long nInnerStartX = nStartX; + tools::Long nInnerStartY = nStartY; + if (pBorderItem) + { + nInnerStartX += static_cast<tools::Long>( ( lcl_LineTotal(pBorderItem->GetLeft()) + + pBorderItem->GetDistance(SvxBoxItemLine::LEFT) ) * nScaleX ); + nInnerStartY += static_cast<tools::Long>( ( lcl_LineTotal(pBorderItem->GetTop()) + + pBorderItem->GetDistance(SvxBoxItemLine::TOP) ) * nScaleY ); + } + if (pShadowItem && pShadowItem->GetLocation() != SvxShadowLocation::NONE) + { + nInnerStartX += static_cast<tools::Long>( pShadowItem->CalcShadowSpace(SvxShadowItemSide::LEFT) * nScaleX ); + nInnerStartY += static_cast<tools::Long>( 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<tools::Long>( ( lcl_LineTotal(pBorderItem->GetRight()) + + pBorderItem->GetDistance(SvxBoxItemLine::RIGHT) ) * nScaleX ); + nBorderEndY += static_cast<tools::Long>( ( lcl_LineTotal(pBorderItem->GetBottom()) + + pBorderItem->GetDistance(SvxBoxItemLine::BOTTOM) ) * nScaleY ); + } + if (pShadowItem && pShadowItem->GetLocation() != SvxShadowLocation::NONE) + { + nBorderEndX += static_cast<tools::Long>( pShadowItem->CalcShadowSpace(SvxShadowItemSide::RIGHT) * nScaleX ); + nBorderEndY += static_cast<tools::Long>( 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; nTab<nTabCount; nTab++) + if ( nTab==nPrintTab || rDoc.GetPageStyle(nTab)==aName ) + { + // Repeating rows/columns + rDoc.SetRepeatArea( nTab, nRepeatStartCol,nRepeatEndCol, nRepeatStartRow,nRepeatEndRow ); + + // set breaks + ResetBreaks(nTab); + pDocShell->PostPaint(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<nRCount; i++) + { + CalcZoom(i); + if ( aTableParam.bSkipEmpty ) + for (nY=0; nY< m_aRanges.m_nPagesY; nY++) + nPages += (*m_aRanges.m_xPageRows)[nY].CountVisible(); + else + nPages += static_cast<tools::Long>(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_nPagesY; nY++) + nPages += (*m_aRanges.m_xPageRows)[nY].CountVisible(); + else + nPages += static_cast<tools::Long>(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; nStep<nRepeats; nStep++) + { + bool bDoThis = true; + if (bMultiArea) // go through all Areas + { + const ScRange* pThisRange = rDoc.GetPrintRange( nPrintTab, nStep ); + if ( pThisRange ) + { + nStartCol = pThisRange->aStart.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<tools::Long>(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<tools::Long>( 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<tools::Long>( -aOffset.X() / nScaleX + 0.5 ), static_cast<tools::Long>( -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; nStep<nRepeats; nStep++) + { + if (bMultiArea) // replace area + { + CalcZoom(nStep); // also sets nStartCol etc. new + InitModes(); + } + + SCCOL nX1; + SCROW nY1; + SCCOL nX2; + SCROW nY2; + size_t nCountX; + size_t nCountY; + + if (aTableParam.bTopDown) // top-bottom + { + nX1 = nStartCol; + for (nCountX=0; nCountX<m_aRanges.m_nPagesX; nCountX++) + { + OSL_ENSURE(nCountX < m_aRanges.m_xPageEndX->size(), "vector access error for aPageEndX (!)"); + nX2 = (*m_aRanges.m_xPageEndX)[nCountX]; + for (nCountY=0; nCountY<m_aRanges.m_nPagesY; nCountY++) + { + auto& rPageRow = (*m_aRanges.m_xPageRows)[nCountY]; + nY1 = rPageRow.GetStartRow(); + nY2 = rPageRow.GetEndRow(); + 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; + } + } + else // left to right + { + for (nCountY=0; nCountY<m_aRanges.m_nPagesY; nCountY++) + { + auto& rPageRow = (*m_aRanges.m_xPageRows)[nCountY]; + nY1 = rPageRow.GetStartRow(); + nY2 = rPageRow.GetEndRow(); + nX1 = nStartCol; + for (nCountX=0; nCountX<m_aRanges.m_nPagesX; nCountX++) + { + OSL_ENSURE(nCountX < m_aRanges.m_xPageEndX->size(), "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<SCROW> aRowBreaks; + std::set<SCCOL> 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<SCROW> aRowBreaks; + std::set<SCCOL> 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; i<nPagesX; i++) + { + OSL_ENSURE(i < rPageEndX.size(), "vector access error for aPageEndX"); + SCCOL nEndCol = rPageEndX[i]; + if ( rDoc.IsPrintEmpty( nStartCol, nStartRow, nEndCol, nEndRow, nPrintTab, + bLeftIsEmpty, &aTempRange, &aTempRect ) ) + { + rPageRowEntry.SetHidden(i); + bLeftIsEmpty = true; + } + else + bLeftIsEmpty = false; + + nStartCol = nEndCol+1; + } +} + +void ScPrintFunc::CalcPages() // calculates aPageRect and pages from nZoom +{ + assert( bPrintAreaValid ); + + sc::PrintPageRangesInput aInput(aTableParam.bSkipEmpty, aAreaParam.bPrintArea, + ScRange(nStartCol, nStartRow, nPrintTab, nEndCol, nEndRow, nPrintTab), + GetDocPageSize()); + m_aRanges.calculate(rDoc, aInput); +} + +namespace sc +{ + +PrintPageRanges::PrintPageRanges() + : m_nPagesX(0) + , m_nPagesY(0) + , m_nTotalY(0) +{} + +void PrintPageRanges::calculate(ScDocument& rDoc, PrintPageRangesInput const& rInput) +{ + // Already calculated? + if (m_aInput == rInput) + return; + + m_aInput = rInput; + + rDoc.SetPageSize(m_aInput.getPrintTab(), m_aInput.m_aDocSize); + + // Clear the map to prevent any outdated values to "survive" when + // we have to recalculate the new values anyway + m_xPageRows->clear(); + + // #i123672# use dynamic mem to react on size changes + if (m_xPageEndX->size() < static_cast<size_t>(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<ScRowBreakIterator> 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 <sfx2/basedlgs.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/viewfrm.hxx> + +#include <reffact.hxx> +#include <tabvwsh.hxx> +#include <sc.hrc> +#include <acredlin.hxx> +#include <simpref.hxx> +#include <scmod.hxx> +#include <scres.hrc> +#include <validate.hxx> + +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<ScTabViewShell*>( 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<ScTabViewShell*>( 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<ScSimpleRefDlg*>(xDlgController.get())->SetRefString(rStr); + } +} + +void ScSimpleRefDlgWrapper::SetCloseHdl( const Link<const OUString*,void>& rLink ) +{ + auto xDlgController = GetController(); + if (xDlgController) + { + static_cast<ScSimpleRefDlg*>(xDlgController.get())->SetCloseHdl(rLink); + } +} + +void ScSimpleRefDlgWrapper::SetUnoLinks( const Link<const OUString&,void>& rDone, + const Link<const OUString&,void>& rAbort, const Link<const OUString&,void>& rChange ) +{ + auto xDlgController = GetController(); + if (xDlgController) + { + static_cast<ScSimpleRefDlg*>(xDlgController.get())->SetUnoLinks( rDone, rAbort, rChange ); + } +} + +void ScSimpleRefDlgWrapper::SetFlags( bool bCloseOnButtonUp, bool bSingleCell, bool bMultiSelection ) +{ + auto xDlgController = GetController(); + if (xDlgController) + { + static_cast<ScSimpleRefDlg*>(xDlgController.get())->SetFlags( bCloseOnButtonUp, bSingleCell, bMultiSelection ); + } +} + +void ScSimpleRefDlgWrapper::StartRefInput() +{ + auto xDlgController = GetController(); + if (xDlgController) + { + static_cast<ScSimpleRefDlg*>(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<ScTabViewShell*>( SfxViewShell::Current() ); + OSL_ENSURE( pViewShell, "missing view shell :-(" ); + if (pViewShell) + { + auto xDlg = std::make_shared<ScAcceptChgDlg>(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<ScTabViewShell*>( SfxViewShell::Current() ); + OSL_ENSURE( pViewShell, "missing view shell :-(" ); + + if (GetController() && pViewShell) + { + static_cast<ScAcceptChgDlg*>(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<ScTabViewShell*>( 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<SfxDialogController> xDlg(ScValidationDlg::Find1AliveObject(pParentP->GetFrameWeld())); + SetController(xDlg); + ScTabViewShell* pViewShell; + if (xDlg) + pViewShell = static_cast<ScValidationDlg*>(xDlg.get())->GetTabViewShell(); + else + pViewShell = lcl_GetTabViewShell( p ); + if (!pViewShell) + pViewShell = dynamic_cast<ScTabViewShell*>( 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 <scextopt.hxx> + +#include <osl/diagnose.h> + +#include <map> +#include <memory> +#include <vector> + +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<ScExtTabSettings>(); + 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<ScExtTabSettings>( *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 <tools/urlobj.hxx> +#include <sfx2/docfile.hxx> +#include <osl/diagnose.h> + +#include <select.hxx> +#include <tabvwsh.hxx> +#include <scmod.hxx> +#include <document.hxx> +#include <transobj.hxx> +#include <docsh.hxx> +#include <tabprotection.hxx> +#include <markdata.hxx> +#include <gridwin.hxx> +#include <sfx2/lokhelper.hxx> +#include <comphelper/lok.hxx> + +#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<sal_uLong>(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<ScTransferObj> 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<nStartX && rDoc.ColHidden(nPosX, nTab) ); + for (SCCOL i=nPosX; i<nStartX; i++) + nSizeX += rDoc.GetColWidth( i, nTab ); + } + else + for (SCCOL i=nEndX+1; i<=nPosX; i++) + nSizeX += rDoc.GetColWidth( i, nTab ); + + tools::Long nSizeY = 0; + if ( bNegY ) + { + // in SetCursorAtPoint hidden rows are skipped. + // They must be skipped here too, or the result will always be the first hidden row. + if (++nPosY < nStartY) + { + nPosY = rDoc.FirstVisibleRow(nPosY, nStartY-1, nTab); + if (!rDoc.ValidRow(nPosY)) + nPosY = nStartY; + } + nSizeY += rDoc.GetRowHeight( nPosY, nStartY-1, nTab ); + } + else + nSizeY += rDoc.GetRowHeight( nEndY+1, nPosY, nTab ); + + if ( nSizeX > nSizeY ) // Fill only ever in one direction + { + nPosY = nEndY; + bNegY = false; + } + else + { + nPosX = nEndX; + bNegX = false; + } + + SCCOL nRefStX = bNegX ? nEndX : nStartX; + SCROW nRefStY = bNegY ? nEndY : nStartY; + if ( nRefStX != m_pViewData->GetRefStartX() || nRefStY != m_pViewData->GetRefStartY() ) + { + m_pViewData->GetView()->DoneRefMode(); + m_pViewData->GetView()->InitRefMode( nRefStX, nRefStY, nTab, SC_REFTYPE_FILL ); + } + } + + pView->UpdateRef( nPosX, nPosY, nTab ); + } + else if (m_pViewData->IsAnyFillMode()) + { + ScFillMode nMode = m_pViewData->GetFillMode(); + if ( nMode == ScFillMode::EMBED_LT || nMode == ScFillMode::EMBED_RB ) + { + OSL_ENSURE( rDoc.IsEmbedded(), "!rDoc.IsEmbedded()" ); + ScRange aRange; + rDoc.GetEmbedded( aRange); + ScRefType eRefMode = (nMode == ScFillMode::EMBED_LT) ? SC_REFTYPE_EMBED_LT : SC_REFTYPE_EMBED_RB; + if (m_pViewData->GetRefType() != eRefMode) + { + if ( nMode == ScFillMode::EMBED_LT ) + pView->InitRefMode( aRange.aEnd.Col(), aRange.aEnd.Row(), nTab, eRefMode ); + else + pView->InitRefMode( aRange.aStart.Col(), aRange.aStart.Row(), nTab, eRefMode ); + CreateAnchor(); + } + + pView->UpdateRef( nPosX, nPosY, nTab ); + } + else if ( nMode == ScFillMode::MATRIX ) + { + 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(); + } + + if ( nPosX < nStartX ) nPosX = nStartX; + if ( nPosY < nStartY ) nPosY = nStartY; + + pView->UpdateRef( nPosX, nPosY, nTab ); + } + // else new modes + } + else // regular selection + { + bool bHideCur = m_bAnchor && ( nPosX != m_pViewData->GetCurX() || + nPosY != m_pViewData->GetCurY() ); + if (bHideCur) + pView->HideAllCursors(); // otherwise twice: Block and SetCursor + + if (m_bAnchor) + { + if (!m_bStarted) + { + bool bMove = ( nPosX != m_aAnchorPos.Col() || + nPosY != m_aAnchorPos.Row() ); + if ( bMove || ( m_pEngine && m_pEngine->GetMouseEvent().IsShift() ) ) + { + pView->InitBlockMode( m_aAnchorPos.Col(), m_aAnchorPos.Row(), + m_aAnchorPos.Tab(), true ); + m_bStarted = true; + } + } + if (m_bStarted) + // If the selection is already started, don't set the cursor. + pView->MarkCursor( nPosX, nPosY, nTab, false, false, true ); + else + pView->SetCursor( nPosX, nPosY ); + } + else + { + ScMarkData& rMark = m_pViewData->GetMarkData(); + if (rMark.IsMarked() || rMark.IsMultiMarked()) + { + pView->DoneBlockMode(true); + pView->InitBlockMode( nPosX, nPosY, nTab, true ); + pView->MarkCursor( nPosX, nPosY, nTab ); + + m_aAnchorPos.Set( nPosX, nPosY, nTab ); + m_bStarted = true; + } + // #i3875# *Hack* When a new cell is Ctrl-clicked with no pre-selected cells, + // it highlights that new cell as well as the old cell where the cursor is + // positioned prior to the click. A selection mode via Shift-F8 should also + // follow the same behavior. + else if ( m_pViewData->IsSelCtrlMouseClick() ) + { + SCCOL nOldX = m_pViewData->GetCurX(); + SCROW nOldY = m_pViewData->GetCurY(); + + pView->InitBlockMode( nOldX, nOldY, nTab, true ); + pView->MarkCursor( nOldX, nOldY, nTab ); + + if ( nOldX != nPosX || nOldY != nPosY ) + { + pView->DoneBlockMode( true ); + pView->InitBlockMode( nPosX, nPosY, nTab, true ); + pView->MarkCursor( nPosX, nPosY, nTab ); + m_aAnchorPos.Set( nPosX, nPosY, nTab ); + } + + m_bStarted = true; + } + pView->SetCursor( nPosX, nPosY ); + } + + m_pViewData->SetRefStart( nPosX, nPosY, nTab ); + if (bHideCur) + pView->ShowAllCursors(); + } + + if (bHide) + pView->ShowAllCursors(); + + return true; +} + +bool ScViewFunctionSet::IsSelectionAtPoint( const Point& rPointPixel ) +{ + bool bRefMode = SC_MOD()->IsFormulaMode(); + if (bRefMode) + return false; + + if (m_pViewData->IsAnyFillMode()) + return false; + + ScMarkData& rMark = m_pViewData->GetMarkData(); + if (m_bAnchor || !rMark.IsMultiMarked()) + { + SCCOL nPosX; + SCROW nPosY; + m_pViewData->GetPosFromPixel( rPointPixel.X(), rPointPixel.Y(), GetWhich(), nPosX, nPosY ); + return m_pViewData->GetMarkData().IsCellMarked( nPosX, nPosY ); + } + + return false; +} + +void ScViewFunctionSet::DeselectAtPoint( const Point& /* rPointPixel */ ) +{ + // doesn't exist +} + +void ScViewFunctionSet::DeselectAll() +{ + if (m_pViewData->IsAnyFillMode()) + return; + + bool bRefMode = SC_MOD()->IsFormulaMode(); + if (bRefMode) + { + m_pViewData->GetView()->DoneRefMode(); + } + else + { + m_pViewData->GetView()->DoneBlockMode(); + m_pViewData->GetViewShell()->UpdateInputHandler(); + } + + m_bAnchor = false; +} + +ScViewSelectionEngine::ScViewSelectionEngine( vcl::Window* pWindow, ScTabView* pView, + ScSplitPos eSplitPos ) : + SelectionEngine( pWindow, &pView->GetFunctionSet() ), + eWhich( eSplitPos ) +{ + SetSelectionMode( SelectionMode::Multiple ); + EnableDrag( true ); +} + +// column and row headers +ScHeaderFunctionSet::ScHeaderFunctionSet( ScViewData* pNewViewData ) : + pViewData( pNewViewData ), + bColumn( false ), + eWhich( SC_SPLIT_TOPLEFT ), + bAnchor( false ), + nCursorPos( 0 ) +{ + OSL_ENSURE(pViewData, "ViewData==0 at FunctionSet"); +} + +void ScHeaderFunctionSet::SetColumn( bool bSet ) +{ + bColumn = bSet; +} + +void ScHeaderFunctionSet::SetWhich( ScSplitPos eNew ) +{ + eWhich = eNew; +} + +void ScHeaderFunctionSet::BeginDrag() +{ + // doesn't exist +} + +void ScHeaderFunctionSet::CreateAnchor() +{ + if (bAnchor) + return; + + ScTabView* pView = pViewData->GetView(); + pView->DoneBlockMode( true ); + if (bColumn) + { + pView->InitBlockMode( static_cast<SCCOL>(nCursorPos), 0, pViewData->GetTabNo(), true, true ); + pView->MarkCursor( static_cast<SCCOL>(nCursorPos), pViewData->MaxRow(), pViewData->GetTabNo() ); + } + else + { + pView->InitBlockMode( 0, nCursorPos, pViewData->GetTabNo(), true, false, true ); + pView->MarkCursor( pViewData->MaxCol(), nCursorPos, pViewData->GetTabNo() ); + } + bAnchor = true; +} + +void ScHeaderFunctionSet::DestroyAnchor() +{ + pViewData->GetView()->DoneBlockMode( true ); + bAnchor = false; +} + +void ScHeaderFunctionSet::SetCursorAtPoint( const Point& rPointPixel, bool /* bDontSelectAtCursor */ ) +{ + if ( bDidSwitch ) + { + // next valid position has to be originated from another window + if ( rPointPixel == aSwitchPos ) + return; // don't scroll in the wrong window + else + bDidSwitch = false; + } + + // Scrolling + Size aWinSize = pViewData->GetActiveWin()->GetOutputSizePixel(); + bool bScroll; + if (bColumn) + bScroll = ( rPointPixel.X() < 0 || rPointPixel.X() >= aWinSize.Width() ); + else + bScroll = ( rPointPixel.Y() < 0 || rPointPixel.Y() >= aWinSize.Height() ); + + // moved out of fix limit? + bool bSwitched = false; + if ( bColumn ) + { + if ( pViewData->GetHSplitMode() == SC_SPLIT_FIX ) + { + if ( rPointPixel.X() > aWinSize.Width() ) + { + if ( eWhich == SC_SPLIT_TOPLEFT ) + { + pViewData->GetView()->ActivatePart( SC_SPLIT_TOPRIGHT ); + bSwitched = true; + } + else if ( eWhich == SC_SPLIT_BOTTOMLEFT ) + { + pViewData->GetView()->ActivatePart( SC_SPLIT_BOTTOMRIGHT ); + bSwitched = true; + } + } + } + } + else // column headers + { + if ( pViewData->GetVSplitMode() == SC_SPLIT_FIX ) + { + if ( rPointPixel.Y() > aWinSize.Height() ) + { + if ( eWhich == SC_SPLIT_TOPLEFT ) + { + pViewData->GetView()->ActivatePart( SC_SPLIT_BOTTOMLEFT ); + bSwitched = true; + } + else if ( eWhich == SC_SPLIT_TOPRIGHT ) + { + pViewData->GetView()->ActivatePart( SC_SPLIT_BOTTOMRIGHT ); + bSwitched = true; + } + } + } + } + if (bSwitched) + { + aSwitchPos = rPointPixel; + bDidSwitch = true; + return; // do not crunch with wrong positions + } + + SCCOL nPosX; + SCROW nPosY; + pViewData->GetPosFromPixel( rPointPixel.X(), rPointPixel.Y(), pViewData->GetActivePart(), + nPosX, nPosY, false ); + if (bColumn) + { + nCursorPos = static_cast<SCCOLROW>(nPosX); + nPosY = pViewData->GetPosY(WhichV(pViewData->GetActivePart())); + } + else + { + nCursorPos = static_cast<SCCOLROW>(nPosY); + nPosX = pViewData->GetPosX(WhichH(pViewData->GetActivePart())); + } + + ScTabView* pView = pViewData->GetView(); + bool bHide = pViewData->GetCurX() != nPosX || + pViewData->GetCurY() != nPosY; + if (bHide) + pView->HideAllCursors(); + + if (bScroll) + pView->AlignToCursor( nPosX, nPosY, SC_FOLLOW_LINE ); + pView->SetCursor( nPosX, nPosY ); + + if ( !bAnchor || !pView->IsBlockMode() ) + { + pView->DoneBlockMode( true ); + pViewData->GetMarkData().MarkToMulti(); //! who changes this? + pView->InitBlockMode( nPosX, nPosY, pViewData->GetTabNo(), true, bColumn, !bColumn ); + + bAnchor = true; + } + + pView->MarkCursor( nPosX, nPosY, pViewData->GetTabNo(), bColumn, !bColumn ); + + // SelectionChanged inside of HideCursor because of UpdateAutoFillMark + pView->SelectionChanged(); + + if (bHide) + pView->ShowAllCursors(); +} + +bool ScHeaderFunctionSet::IsSelectionAtPoint( const Point& rPointPixel ) +{ + SCCOL nPosX; + SCROW nPosY; + pViewData->GetPosFromPixel( rPointPixel.X(), rPointPixel.Y(), pViewData->GetActivePart(), + nPosX, nPosY, false ); + + ScMarkData& rMark = pViewData->GetMarkData(); + if (bColumn) + return rMark.IsColumnMarked( nPosX ); + else + return rMark.IsRowMarked( nPosY ); +} + +void ScHeaderFunctionSet::DeselectAtPoint( const Point& /* rPointPixel */ ) +{ +} + +void ScHeaderFunctionSet::DeselectAll() +{ + pViewData->GetView()->DoneBlockMode(); + bAnchor = false; +} + +ScHeaderSelectionEngine::ScHeaderSelectionEngine( vcl::Window* pWindow, ScHeaderFunctionSet* pFuncSet ) : + SelectionEngine( pWindow, pFuncSet ) +{ + SetSelectionMode( SelectionMode::Multiple ); + EnableDrag( false ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/selectionstate.cxx b/sc/source/ui/view/selectionstate.cxx new file mode 100644 index 0000000000..91c6a278cb --- /dev/null +++ b/sc/source/ui/view/selectionstate.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/. + * + * 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 <selectionstate.hxx> + +#include <editeng/editview.hxx> +#include <viewdata.hxx> +#include <markdata.hxx> + +ScSelectionState::ScSelectionState( ScViewData& rViewData ) : + meType( SC_SELECTTYPE_NONE ) +{ + maCursor.SetTab( rViewData.GetTabNo() ); + ScSplitPos eWhich = rViewData.GetActivePart(); + + if( rViewData.HasEditView( eWhich ) ) + { + meType = SC_SELECTTYPE_EDITCELL; + maCursor.SetCol( rViewData.GetEditViewCol() ); + maCursor.SetRow( rViewData.GetEditViewRow() ); + maEditSel = rViewData.GetEditView( eWhich )->GetSelection(); + } + else + { + maCursor.SetCol( rViewData.GetCurX() ); + maCursor.SetRow( rViewData.GetCurY() ); + + ScMarkData& rMarkData = rViewData.GetMarkData(); + rMarkData.MarkToMulti(); + if( rMarkData.IsMultiMarked() ) + { + meType = SC_SELECTTYPE_SHEET; + } + // else type is SC_SELECTTYPE_NONE - already initialized + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/spellcheckcontext.cxx b/sc/source/ui/view/spellcheckcontext.cxx new file mode 100644 index 0000000000..b18483aa88 --- /dev/null +++ b/sc/source/ui/view/spellcheckcontext.cxx @@ -0,0 +1,387 @@ +/* -*- 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 <spellcheckcontext.hxx> +#include <svl/sharedstring.hxx> +#include <editeng/eeitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/unolingu.hxx> + +#include <scitems.hxx> +#include <document.hxx> +#include <cellvalue.hxx> +#include <editutil.hxx> +#include <dpobject.hxx> + +#include <com/sun/star/linguistic2/XSpellChecker1.hpp> + +#include <o3tl/hash_combine.hxx> + +#include <unordered_map> + +using namespace css; + +using sc::SpellCheckContext; + +class SpellCheckContext::SpellCheckCache +{ + struct CellPos + { + struct Hash + { + size_t operator() (const CellPos& rPos) const + { + std::size_t seed = 0; + o3tl::hash_combine(seed, rPos.mnCol); + o3tl::hash_combine(seed, rPos.mnRow); + return seed; + } + }; + + SCCOL mnCol; + SCROW mnRow; + + CellPos(SCCOL nCol, SCROW nRow) : mnCol(nCol), mnRow(nRow) {} + + bool operator== (const CellPos& r) const + { + return mnCol == r.mnCol && mnRow == r.mnRow; + } + + }; + + typedef std::vector<editeng::MisspellRanges> MisspellType; + typedef std::unordered_map<CellPos, std::unique_ptr<MisspellType>, CellPos::Hash> CellMapType; + typedef std::unordered_map<const rtl_uString*, std::unique_ptr<MisspellType>> SharedStringMapType; + typedef std::unordered_map<CellPos, LanguageType, CellPos::Hash> CellLangMapType; + + SharedStringMapType maStringMisspells; + CellMapType maEditTextMisspells; + CellLangMapType maCellLanguages; + LanguageType meDefCellLanguage; + +public: + + SpellCheckCache(LanguageType eDefaultCellLanguage) : meDefCellLanguage(eDefaultCellLanguage) + { + } + + bool query(SCCOL nCol, SCROW nRow, const ScRefCellValue& rCell, MisspellType*& rpRanges) const + { + CellType eType = rCell.getType(); + if (eType == CELLTYPE_STRING) + { + SharedStringMapType::const_iterator it = maStringMisspells.find(rCell.getSharedString()->getData()); + if (it == maStringMisspells.end()) + return false; // Not available + + rpRanges = it->second.get(); + return true; + } + + if (eType == CELLTYPE_EDIT) + { + CellMapType::const_iterator it = maEditTextMisspells.find(CellPos(nCol, nRow)); + if (it == maEditTextMisspells.end()) + return false; // Not available + + rpRanges = it->second.get(); + return true; + } + + rpRanges = nullptr; + return true; + } + + void set(SCCOL nCol, SCROW nRow, const ScRefCellValue& rCell, std::unique_ptr<MisspellType> pRanges) + { + CellType eType = rCell.getType(); + if (eType == CELLTYPE_STRING) + maStringMisspells.insert_or_assign(rCell.getSharedString()->getData(), std::move(pRanges)); + else if (eType == CELLTYPE_EDIT) + maEditTextMisspells.insert_or_assign(CellPos(nCol, nRow), std::move(pRanges)); + } + + LanguageType getLanguage(SCCOL nCol, SCROW nRow) const + { + CellLangMapType::const_iterator it = maCellLanguages.find(CellPos(nCol, nRow)); + if (it == maCellLanguages.end()) + return meDefCellLanguage; + + return it->second; + } + + void setLanguage(LanguageType eCellLang, SCCOL nCol, SCROW nRow) + { + if (eCellLang == meDefCellLanguage) + maCellLanguages.erase(CellPos(nCol, nRow)); + else + maCellLanguages.insert_or_assign(CellPos(nCol, nRow), eCellLang); + } + + void clear(LanguageType eDefaultCellLanguage) + { + maStringMisspells.clear(); + maEditTextMisspells.clear(); + maCellLanguages.clear(); + meDefCellLanguage = eDefaultCellLanguage; + } + + void clearEditTextMap() + { + maEditTextMisspells.clear(); + } +}; + +struct SpellCheckContext::SpellCheckStatus +{ + bool mbModified; + + SpellCheckStatus() : mbModified(false) {}; + + DECL_LINK( EventHdl, EditStatus&, void ); +}; + +IMPL_LINK(SpellCheckContext::SpellCheckStatus, EventHdl, EditStatus&, rStatus, void) +{ + EditStatusFlags nStatus = rStatus.GetStatusWord(); + if (nStatus & EditStatusFlags::WRONGWORDCHANGED) + mbModified = true; +} + +struct SpellCheckContext::SpellCheckResult +{ + SCCOL mnCol; + SCROW mnRow; + const std::vector<editeng::MisspellRanges>* pRanges; + + SpellCheckResult() : mnCol(-1), mnRow(-1), pRanges(nullptr) {} + + void set(SCCOL nCol, SCROW nRow, const std::vector<editeng::MisspellRanges>* pMisspells) + { + mnCol = nCol; + mnRow = nRow; + pRanges = pMisspells; + } + + const std::vector<editeng::MisspellRanges>* query(SCCOL nCol, SCROW nRow) const + { + assert(mnCol == nCol); + assert(mnRow == nRow); + (void)nCol; + (void)nRow; + return pRanges; + } + + void clear() + { + mnCol = -1; + mnRow = -1; + pRanges = nullptr; + } +}; + +SpellCheckContext::SpellCheckContext(ScDocument* pDocument, SCTAB nTab) : + pDoc(pDocument), + mnTab(nTab), + meLanguage(ScGlobal::GetEditDefaultLanguage()) +{ + // defer init of engine and cache till the first query/set +} + +SpellCheckContext::~SpellCheckContext() +{ +} + +void SpellCheckContext::dispose() +{ + mpEngine.reset(); + mpCache.reset(); + pDoc = nullptr; +} + +void SpellCheckContext::setTabNo(SCTAB nTab) +{ + if (mnTab == nTab) + return; + mnTab = nTab; + reset(); +} + +bool SpellCheckContext::isMisspelled(SCCOL nCol, SCROW nRow) const +{ + const_cast<SpellCheckContext*>(this)->ensureResults(nCol, nRow); + return mpResult->query(nCol, nRow); +} + +const std::vector<editeng::MisspellRanges>* SpellCheckContext::getMisspellRanges( + SCCOL nCol, SCROW nRow ) const +{ + const_cast<SpellCheckContext*>(this)->ensureResults(nCol, nRow); + return mpResult->query(nCol, nRow); +} + +void SpellCheckContext::setMisspellRanges( + SCCOL nCol, SCROW nRow, const std::vector<editeng::MisspellRanges>* pRanges ) +{ + if (!mpEngine || !mpCache) + reset(); + + ScRefCellValue aCell(*pDoc, ScAddress(nCol, nRow, mnTab)); + CellType eType = aCell.getType(); + + if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT) + return; + + typedef std::vector<editeng::MisspellRanges> MisspellType; + std::unique_ptr<MisspellType> pMisspells(pRanges ? new MisspellType(*pRanges) : nullptr); + mpCache->set(nCol, nRow, aCell, std::move(pMisspells)); +} + +void SpellCheckContext::reset() +{ + meLanguage = ScGlobal::GetEditDefaultLanguage(); + resetCache(); + mpEngine.reset(); + mpStatus.reset(); +} + +void SpellCheckContext::resetForContentChange() +{ + resetCache(true /* bContentChangeOnly */); +} + +void SpellCheckContext::ensureResults(SCCOL nCol, SCROW nRow) +{ + if (!mpEngine || !mpCache || + ScGlobal::GetEditDefaultLanguage() != meLanguage) + { + reset(); + setup(); + } + + // perhaps compute the pivot rangelist once in some pivot-table change handler ? + if (pDoc->HasPivotTable()) + { + if (ScDPCollection* pDPs = pDoc->GetDPCollection()) + { + ScRangeList aPivotRanges = pDPs->GetAllTableRanges(mnTab); + if (aPivotRanges.Contains(ScAddress(nCol, nRow, mnTab))) // Don't spell check within pivot tables + { + mpResult->set(nCol, nRow, nullptr); + return; + } + } + } + + ScRefCellValue aCell(*pDoc, ScAddress(nCol, nRow, mnTab)); + CellType eType = aCell.getType(); + + if (eType != CELLTYPE_STRING && eType != CELLTYPE_EDIT) + { + // No spell-check required. + mpResult->set(nCol, nRow, nullptr); + return; + } + + + // Cell content is either shared-string or EditTextObject + + // For spell-checking, we currently only use the primary + // language; not CJK nor CTL. + const ScPatternAttr* pPattern = pDoc->GetPattern(nCol, nRow, mnTab); + LanguageType eCellLang = pPattern->GetItem(ATTR_FONT_LANGUAGE).GetValue(); + + if (eCellLang == LANGUAGE_SYSTEM) + eCellLang = meLanguage; // never use SYSTEM for spelling + + if (eCellLang == LANGUAGE_NONE) + { + mpResult->set(nCol, nRow, nullptr); // No need to spell check this cell. + return; + } + + typedef std::vector<editeng::MisspellRanges> MisspellType; + + LanguageType eCachedCellLang = mpCache->getLanguage(nCol, nRow); + + if (eCellLang != eCachedCellLang) + mpCache->setLanguage(eCellLang, nCol, nRow); + + else + { + MisspellType* pRanges = nullptr; + bool bFound = mpCache->query(nCol, nRow, aCell, pRanges); + if (bFound) + { + // Cache hit. + mpResult->set(nCol, nRow, pRanges); + return; + } + } + + // Cache miss, the cell needs spell-check.. + if (eType == CELLTYPE_STRING) + mpEngine->SetText(aCell.getSharedString()->getString()); + else + mpEngine->SetText(*aCell.getEditText()); + + // it has to happen after we set text + mpEngine->SetDefaultItem(SvxLanguageItem(eCellLang, EE_CHAR_LANGUAGE)); + + mpStatus->mbModified = false; + mpEngine->CompleteOnlineSpelling(); + std::unique_ptr<MisspellType> pRanges; + if (mpStatus->mbModified) + { + pRanges.reset(new MisspellType); + mpEngine->GetAllMisspellRanges(*pRanges); + + if (pRanges->empty()) + pRanges.reset(nullptr); + } + // else : No change in status for EditStatusFlags::WRONGWORDCHANGED => no spell errors (which is the default status). + + mpResult->set(nCol, nRow, pRanges.get()); + mpCache->set(nCol, nRow, aCell, std::move(pRanges)); +} + +void SpellCheckContext::resetCache(bool bContentChangeOnly) +{ + if (!mpResult) + mpResult.reset(new SpellCheckResult()); + else + mpResult->clear(); + + if (!mpCache) + mpCache.reset(new SpellCheckCache(meLanguage)); + else if (bContentChangeOnly) + mpCache->clearEditTextMap(); + else + mpCache->clear(meLanguage); +} + +void SpellCheckContext::setup() +{ + mpEngine.reset(new ScTabEditEngine(pDoc)); + mpStatus.reset(new SpellCheckStatus()); + + mpEngine->SetControlWord( + mpEngine->GetControlWord() | (EEControlBits::ONLINESPELLING | EEControlBits::ALLOWBIGOBJS)); + mpEngine->SetStatusEventHdl(LINK(mpStatus.get(), SpellCheckStatus, EventHdl)); + // Delimiters here like in inputhdl.cxx !!! + mpEngine->SetWordDelimiters( + ScEditUtil::ModifyDelimiters(mpEngine->GetWordDelimiters())); + + uno::Reference<linguistic2::XSpellChecker1> xXSpellChecker1(LinguMgr::GetSpellChecker()); + mpEngine->SetSpeller(xXSpellChecker1); + mpEngine->SetDefaultLanguage(meLanguage); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/spelldialog.cxx b/sc/source/ui/view/spelldialog.cxx new file mode 100644 index 0000000000..da1e90698b --- /dev/null +++ b/sc/source/ui/view/spelldialog.cxx @@ -0,0 +1,281 @@ +/* -*- 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 <spelldialog.hxx> + +#include <sfx2/bindings.hxx> +#include <svx/svxids.hrc> +#include <editeng/editstat.hxx> +#include <editeng/editview.hxx> +#include <editeng/unolingu.hxx> +#include <selectionstate.hxx> +#include <osl/diagnose.h> + +#include <spelleng.hxx> +#include <tabvwsh.hxx> +#include <docsh.hxx> +#include <scmod.hxx> +#include <editable.hxx> +#include <undoblk.hxx> +#include <gridwin.hxx> +#include <refupdatecontext.hxx> +#include <vcl/svapp.hxx> + +SFX_IMPL_CHILDWINDOW_WITHID( ScSpellDialogChildWindow, SID_SPELL_DIALOG ) + +ScSpellDialogChildWindow::ScSpellDialogChildWindow( vcl::Window* pParentP, sal_uInt16 nId, + SfxBindings* pBindings, SAL_UNUSED_PARAMETER SfxChildWinInfo* /*pInfo*/ ) : + svx::SpellDialogChildWindow( pParentP, nId, pBindings ), + mpViewShell( nullptr ), + mpViewData( nullptr ), + mpDocShell( nullptr ), + mpDoc( nullptr ), + mbNeedNextObj( false ), + mbOldIdleEnabled(true) +{ + Init(); +} + +ScSpellDialogChildWindow::~ScSpellDialogChildWindow() +{ + Reset(); +} + +SfxChildWinInfo ScSpellDialogChildWindow::GetInfo() const +{ + return svx::SpellDialogChildWindow::GetInfo(); +} + +void ScSpellDialogChildWindow::InvalidateSpellDialog() +{ + svx::SpellDialogChildWindow::InvalidateSpellDialog(); +} + +// protected ------------------------------------------------------------------ + +svx::SpellPortions ScSpellDialogChildWindow::GetNextWrongSentence( bool /*bRecheck*/ ) +{ + svx::SpellPortions aPortions; + if( mxEngine && mpViewData ) + { + if( EditView* pEditView = mpViewData->GetSpellingView() ) + { + // edit engine handles cell iteration internally + do + { + if( mbNeedNextObj ) + mxEngine->SpellNextDocument(); + mbNeedNextObj = !mxEngine->IsFinished() && !mxEngine->SpellSentence( *pEditView, aPortions ); + } + while( mbNeedNextObj ); + } + } + return aPortions; +} + +void ScSpellDialogChildWindow::ApplyChangedSentence( const svx::SpellPortions& rChanged, bool bRecheck ) +{ + if( mxEngine && mpViewData ) + if( EditView* pEditView = mpViewData->GetSpellingView() ) + { + mxEngine->ApplyChangedSentence( *pEditView, rChanged, bRecheck ); + + // Reset the spell checking results to clear the markers. + mpViewData->GetActiveWin()->ResetAutoSpell(); + } +} + +void ScSpellDialogChildWindow::GetFocus() +{ + SolarMutexGuard aGuard; + + if( IsSelectionChanged() ) + { + Reset(); + InvalidateSpellDialog(); + Init(); + } +} + +void ScSpellDialogChildWindow::LoseFocus() +{ +} + +// private -------------------------------------------------------------------- + +void ScSpellDialogChildWindow::Reset() +{ + if( mpViewShell && (mpViewShell == dynamic_cast<ScTabViewShell*>( SfxViewShell::Current() )) ) + { + if( mxEngine && mxEngine->IsAnyModified() ) + { + const ScAddress& rCursor = mxOldSel->GetCellCursor(); + SCTAB nTab = rCursor.Tab(); + SCCOL nOldCol = rCursor.Col(); + SCROW nOldRow = rCursor.Row(); + SCCOL nNewCol = mpViewData->GetCurX(); + SCROW nNewRow = mpViewData->GetCurY(); + mpDocShell->GetUndoManager()->AddUndoAction( std::make_unique<ScUndoConversion>( + mpDocShell, mpViewData->GetMarkData(), + nOldCol, nOldRow, nTab, std::move(mxUndoDoc), + nNewCol, nNewRow, nTab, std::move(mxRedoDoc), + ScConversionParam( SC_CONVERSION_SPELLCHECK ) ) ); + + sc::SetFormulaDirtyContext aCxt; + mpDoc->SetAllFormulasDirty(aCxt); + + mpDocShell->SetDocumentModified(); + } + + mpViewData->SetSpellingView( nullptr ); + mpViewShell->KillEditView( true ); + mpDocShell->PostPaintGridAll(); + mpViewShell->UpdateInputHandler(); + mpDoc->EnableIdle(mbOldIdleEnabled); + } + mxEngine.reset(); + mxUndoDoc.reset(); + mxRedoDoc.reset(); + mxOldSel.reset(); + mxOldRangeList.clear(); + mpViewShell = nullptr; + mpViewData = nullptr; + mpDocShell = nullptr; + mpDoc = nullptr; + mbNeedNextObj = false; + mbOldIdleEnabled = true; +} + +void ScSpellDialogChildWindow::Init() +{ + if( mpViewShell ) + return; + if( (mpViewShell = dynamic_cast<ScTabViewShell*>( SfxViewShell::Current() )) == nullptr ) + return; + + mpViewData = &mpViewShell->GetViewData(); + + // exit edit mode - TODO support spelling in edit mode + if( mpViewData->HasEditView( mpViewData->GetActivePart() ) ) + SC_MOD()->InputEnterHandler(); + + mxOldSel.reset( new ScSelectionState( *mpViewData ) ); + + mpDocShell = mpViewData->GetDocShell(); + mpDoc = &mpDocShell->GetDocument(); + + const ScAddress& rCursor = mxOldSel->GetCellCursor(); + SCCOL nCol = rCursor.Col(); + SCROW nRow = rCursor.Row(); + SCTAB nTab = rCursor.Tab(); + + ScMarkData& rMarkData = mpViewData->GetMarkData(); + + mxOldRangeList = new ScRangeList; + rMarkData.FillRangeListWithMarks(mxOldRangeList.get(), true); + + rMarkData.MarkToMulti(); + + switch( mxOldSel->GetSelectionType() ) + { + case SC_SELECTTYPE_NONE: + case SC_SELECTTYPE_SHEET: + { + // test if there is something editable + ScEditableTester aTester( *mpDoc, rMarkData ); + if( !aTester.IsEditable() ) + { + // #i85751# Don't show an ErrorMessage here, because the vcl + // parent of the InfoBox is not fully initialized yet. + // This leads to problems in the modality behaviour of the + // ScSpellDialogChildWindow. + + //mpViewShell->ErrorMessage( aTester.GetMessageId() ); + return; + } + } + break; + + // edit mode exited, see TODO above +// case SC_SELECTTYPE_EDITCELL: +// break; + + default: + OSL_FAIL( "ScSpellDialogChildWindow::Init - unknown selection type" ); + } + + mbOldIdleEnabled = mpDoc->IsIdleEnabled(); + mpDoc->EnableIdle(false); // stop online spelling + + // *** create Undo/Redo documents *** ------------------------------------- + + mxUndoDoc.reset( new ScDocument( SCDOCMODE_UNDO ) ); + mxUndoDoc->InitUndo( *mpDoc, nTab, nTab ); + mxRedoDoc.reset( new ScDocument( SCDOCMODE_UNDO ) ); + mxRedoDoc->InitUndo( *mpDoc, nTab, nTab ); + + if ( rMarkData.GetSelectCount() > 1 ) + { + for (const auto& rTab : rMarkData) + { + if( rTab != nTab ) + { + mxUndoDoc->AddUndoTab( rTab, rTab ); + mxRedoDoc->AddUndoTab( rTab, rTab ); + } + } + } + + // *** create and init the edit engine *** -------------------------------- + + mxEngine.reset( new ScSpellingEngine( + mpDoc->GetEnginePool(), *mpViewData, mxUndoDoc.get(), mxRedoDoc.get(), LinguMgr::GetSpellChecker() ) ); + mxEngine->SetRefDevice( mpViewData->GetActiveWin()->GetOutDev() ); + + mpViewShell->MakeEditView( mxEngine.get(), nCol, nRow ); + EditView* pEditView = mpViewData->GetEditView( mpViewData->GetActivePart() ); + mpViewData->SetSpellingView( pEditView ); + tools::Rectangle aRect( Point( 0, 0 ), Point( 0, 0 ) ); + pEditView->SetOutputArea( aRect ); + mxEngine->SetControlWord( EEControlBits::USECHARATTRIBS ); + mxEngine->EnableUndo( false ); + mxEngine->SetPaperSize( aRect.GetSize() ); + mxEngine->SetTextCurrentDefaults( OUString() ); + mxEngine->ClearModifyFlag(); + + mbNeedNextObj = true; +} + +bool ScSpellDialogChildWindow::IsSelectionChanged() +{ + if (!mxOldRangeList || !mpViewShell + || (mpViewShell != dynamic_cast<ScTabViewShell*>(SfxViewShell::Current()))) + return true; + + if( EditView* pEditView = mpViewData->GetSpellingView() ) + if( pEditView->GetEditEngine() != mxEngine.get() ) + return true; + + ScRangeList aCurrentRangeList; + mpViewData->GetMarkData().FillRangeListWithMarks(&aCurrentRangeList, true); + + return (*mxOldRangeList != aCurrentRangeList); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/spelleng.cxx b/sc/source/ui/view/spelleng.cxx new file mode 100644 index 0000000000..ae50d82930 --- /dev/null +++ b/sc/source/ui/view/spelleng.cxx @@ -0,0 +1,448 @@ +/* -*- 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 <spelleng.hxx> +#include <com/sun/star/i18n/TextConversionOption.hpp> + +#include <scitems.hxx> + +#include <editeng/langitem.hxx> +#include <editeng/editobj.hxx> +#include <editeng/editview.hxx> +#include <editeng/eeitem.hxx> +#include <sfx2/viewfrm.hxx> +#include <utility> +#include <vcl/settings.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <osl/diagnose.h> + +#include <spelldialog.hxx> +#include <tabvwsh.hxx> +#include <docsh.hxx> +#include <cellvalue.hxx> +#include <cellform.hxx> +#include <patattr.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <markdata.hxx> +#include <docpool.hxx> + +#include <memory> + +using namespace ::com::sun::star; + +ScConversionEngineBase::ScConversionEngineBase( + SfxItemPool* pEnginePoolP, ScViewData& rViewData, + ScDocument* pUndoDoc, ScDocument* pRedoDoc ) : + ScEditEngineDefaulter( pEnginePoolP ), + mrViewData( rViewData ), + mrDocShell( *rViewData.GetDocShell() ), + mrDoc( rViewData.GetDocShell()->GetDocument() ), + maSelState( rViewData ), + mpUndoDoc( pUndoDoc ), + mpRedoDoc( pRedoDoc ), + meCurrLang( LANGUAGE_ENGLISH_US ), + mbIsAnyModified( false ), + mbInitialState( true ), + mbWrappedInTable( false ), + mbFinished( false ) +{ + maSelState.GetCellCursor().GetVars( mnStartCol, mnStartRow, mnStartTab ); + // start with cell A1 in cell/range/multi-selection, will seek to first selected + if( maSelState.GetSelectionType() == SC_SELECTTYPE_SHEET ) + { + mnStartCol = 0; + mnStartRow = 0; + } + mnCurrCol = mnStartCol; + mnCurrRow = mnStartRow; +} + +ScConversionEngineBase::~ScConversionEngineBase() +{ +} + +bool ScConversionEngineBase::FindNextConversionCell() +{ + ScMarkData& rMark = mrViewData.GetMarkData(); + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + const ScPatternAttr* pPattern = nullptr; + const ScPatternAttr* pLastPattern = nullptr; + + SfxItemSet aEditDefaults(GetEmptyItemSet()); + + if( IsModified() ) + { + mbIsAnyModified = true; + + OUString aNewStr = GetText(); + + // Check if the user has changed the language. If the new language is + // applied to the entire string length, we will set the language as cell + // attribute. Otherwise we will commit this as an edit-engine string. + editeng::LanguageSpan aLang = GetLanguage(0, 0); + + bool bSimpleString = GetParagraphCount() == 1 && + aLang.nLang != LANGUAGE_DONTKNOW && + aLang.nStart == 0 && + aLang.nEnd == aNewStr.getLength(); + + bool bMultiTab = (rMark.GetSelectCount() > 1); + + OUString aVisibleStr; + if( bMultiTab ) + aVisibleStr = mrDoc.GetString(mnCurrCol, mnCurrRow, mnStartTab); + + for( SCTAB nTab = 0, nTabCount = mrDoc.GetTableCount(); nTab < nTabCount; ++nTab ) + { + // always change the cell on the visible tab, + // on the other selected tabs only if they contain the same text + + if ((nTab == mnStartTab) || + (bMultiTab && rMark.GetTableSelect(nTab) && mrDoc.GetString(mnCurrCol, mnCurrRow, nTab) == aVisibleStr)) + { + ScAddress aPos( mnCurrCol, mnCurrRow, nTab ); + CellType eCellType = mrDoc.GetCellType( aPos ); + bool bEmptyCell = eCellType == CELLTYPE_NONE; + + if (mpUndoDoc && !bEmptyCell) + mrDoc.CopyCellToDocument(aPos, aPos, *mpUndoDoc); + + if (!bSimpleString || eCellType == CELLTYPE_EDIT) + { + std::unique_ptr<EditTextObject> pEditObj(CreateTextObject()); + mrDoc.SetEditText(aPos, *pEditObj, GetEditTextObjectPool()); + } + else + { + // Set the new string and update the language with the cell. + mrDoc.SetString(aPos, aNewStr); + + const ScPatternAttr* pAttr = mrDoc.GetPattern(aPos); + std::unique_ptr<ScPatternAttr> pNewAttr; + + if (pAttr) + pNewAttr = std::make_unique<ScPatternAttr>(*pAttr); + else + pNewAttr = std::make_unique<ScPatternAttr>(mrDoc.GetPool()); + + pNewAttr->GetItemSet().Put(SvxLanguageItem(aLang.nLang, EE_CHAR_LANGUAGE), ATTR_FONT_LANGUAGE); + mrDoc.SetPattern(aPos, std::move(pNewAttr)); + } + + if (mpRedoDoc && !bEmptyCell) + mrDoc.CopyCellToDocument(aPos, aPos, *mpRedoDoc); + + mrDocShell.PostPaintCell(aPos); + } + } + } + + SCCOL nNewCol = mnCurrCol; + SCROW nNewRow = mnCurrRow; + + if( mbInitialState ) + { + /* On very first call, decrement row to let GetNextSpellingCell() find + the first cell of current range. */ + mbInitialState = false; + --nNewRow; + } + + bool bSheetSel = maSelState.GetSelectionType() == SC_SELECTTYPE_SHEET; + bool bLoop = true; + bool bFound = false; + while( bLoop && !bFound ) + { + bLoop = mrDoc.GetNextSpellingCell( nNewCol, nNewRow, mnStartTab, bSheetSel, rMark ); + if( bLoop ) + { + FillFromCell( mnCurrCol, mnCurrRow, mnStartTab ); + + if( mbWrappedInTable && ((nNewCol > mnStartCol) || ((nNewCol == mnStartCol) && (nNewRow >= mnStartRow))) ) + { + ShowFinishDialog(); + bLoop = false; + mbFinished = true; + } + else if( nNewCol >= mrDoc.GetAllocatedColumnsCount(mnStartTab) ) + { + // no more cells in the sheet - try to restart at top of sheet + + if( bSheetSel || ((mnStartCol == 0) && (mnStartRow == 0)) ) + { + // conversion started at cell A1 or in selection, do not query to restart at top + ShowFinishDialog(); + bLoop = false; + mbFinished = true; + } + else if( ShowTableWrapDialog() ) + { + // conversion started anywhere but in cell A1, user wants to restart + nNewRow = mrDoc.MaxRow() + 2; + mbWrappedInTable = true; + } + else + { + bLoop = false; + mbFinished = true; + } + } + else + { + // GetPattern may implicitly allocates the column if not exists, + pPattern = mrDoc.GetPattern( nNewCol, nNewRow, mnStartTab ); + if( pPattern && !SfxPoolItem::areSame(pPattern, pLastPattern) ) + { + pPattern->FillEditItemSet( &aEditDefaults ); + SetDefaults( aEditDefaults ); + pLastPattern = pPattern; + } + + // language changed? + const SfxPoolItem* pItem = mrDoc.GetAttr( nNewCol, nNewRow, mnStartTab, ATTR_FONT_LANGUAGE ); + if( const SvxLanguageItem* pLangItem = dynamic_cast<const SvxLanguageItem*>( pItem ) ) + { + LanguageType eLang = pLangItem->GetValue(); + if( eLang == LANGUAGE_SYSTEM ) + eLang = Application::GetSettings().GetLanguageTag().getLanguageType(); // never use SYSTEM for spelling + if( eLang != meCurrLang ) + { + meCurrLang = eLang; + SetDefaultLanguage( eLang ); + } + } + + FillFromCell( nNewCol, nNewRow, mnStartTab ); + + bFound = bLoop && NeedsConversion(); + } + } + } + + if( bFound ) + { + pViewShell->AlignToCursor( nNewCol, nNewRow, SC_FOLLOW_JUMP ); + pViewShell->SetCursor( nNewCol, nNewRow, true ); + mrViewData.GetView()->MakeEditView( this, nNewCol, nNewRow ); + EditView* pEditView = mrViewData.GetSpellingView(); + // maSelState.GetEditSelection() returns (0,0) if not in edit mode -> ok + pEditView->SetSelection( maSelState.GetEditSelection() ); + + ClearModifyFlag(); + mnCurrCol = nNewCol; + mnCurrRow = nNewRow; + } + + return bFound; +} + +void ScConversionEngineBase::RestoreCursorPos() +{ + const ScAddress& rPos = maSelState.GetCellCursor(); + mrViewData.GetViewShell()->SetCursor( rPos.Col(), rPos.Row() ); +} + +bool ScConversionEngineBase::ShowTableWrapDialog() +{ + // default: no dialog, always restart at top + return true; +} + +void ScConversionEngineBase::ShowFinishDialog() +{ + // default: no dialog +} + +// private -------------------------------------------------------------------- + +void ScConversionEngineBase::FillFromCell( SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + ScAddress aPos(nCol, nRow, nTab); + + ScRefCellValue aCell(mrDoc, aPos); + switch (aCell.getType()) + { + case CELLTYPE_STRING: + { + SvNumberFormatter* pFormatter = mrDoc.GetFormatTable(); + sal_uInt32 nNumFmt = mrDoc.GetNumberFormat(aPos); + const Color* pColor; + OUString aText = ScCellFormat::GetString(aCell, nNumFmt, &pColor, *pFormatter, mrDoc); + + SetTextCurrentDefaults(aText); + } + break; + case CELLTYPE_EDIT: + { + const EditTextObject* pNewEditObj = aCell.getEditText(); + SetTextCurrentDefaults(*pNewEditObj); + } + break; + default: + SetTextCurrentDefaults(OUString()); + } +} + +ScSpellingEngine::ScSpellingEngine( + SfxItemPool* pEnginePoolP, ScViewData& rViewData, + ScDocument* pUndoDoc, ScDocument* pRedoDoc, + css::uno::Reference< css::linguistic2::XSpellChecker1 > const & xSpeller ) : + ScConversionEngineBase( pEnginePoolP, rViewData, pUndoDoc, pRedoDoc ) +{ + SetSpeller( xSpeller ); +} + +void ScSpellingEngine::ConvertAll(weld::Widget* pDialogParent, EditView& rEditView) +{ + EESpellState eState = EESpellState::Ok; + if( FindNextConversionCell() ) + eState = rEditView.StartSpeller(pDialogParent, true); + + OSL_ENSURE( eState != EESpellState::NoSpeller, "ScSpellingEngine::Convert - no spell checker" ); +} + +bool ScSpellingEngine::SpellNextDocument() +{ + return FindNextConversionCell(); +} + +bool ScSpellingEngine::NeedsConversion() +{ + return HasSpellErrors() != EESpellState::Ok; +} + +bool ScSpellingEngine::ShowTableWrapDialog() +{ + weld::Widget* pParent = GetDialogParent(); + weld::WaitObject aWaitOff(pParent); + + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(pParent, + VclMessageType::Question, VclButtonsType::YesNo, + ScResId(STR_SPELLING_BEGIN_TAB))); // "delete data?" + xBox->set_title(ScResId(STR_MSSG_DOSUBTOTALS_0)); + xBox->set_default_response(RET_YES); + return xBox->run() == RET_YES; +} + +void ScSpellingEngine::ShowFinishDialog() +{ + weld::Widget* pParent = GetDialogParent(); + weld::WaitObject aWaitOff(pParent); + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(pParent, + VclMessageType::Info, VclButtonsType::Ok, + ScResId(STR_SPELLING_STOP_OK))); + xInfoBox->run(); +} + +weld::Widget* ScSpellingEngine::GetDialogParent() +{ + sal_uInt16 nWinId = ScSpellDialogChildWindow::GetChildWindowId(); + SfxViewFrame& rViewFrm = mrViewData.GetViewShell()->GetViewFrame(); + if( rViewFrm.HasChildWindow( nWinId ) ) + { + if( SfxChildWindow* pChild = rViewFrm.GetChildWindow( nWinId ) ) + { + auto xController = pChild->GetController(); + if (xController) + { + if (weld::Window *pRet = xController->getDialog()) + { + if (pRet->get_visible()) + return pRet; + } + } + } + } + + // fall back to standard dialog parent + return ScDocShell::GetActiveDialogParent(); +} + +ScConversionParam::ScConversionParam( ScConversionType eConvType ) : + meConvType( eConvType ), + meSourceLang( LANGUAGE_NONE ), + meTargetLang( LANGUAGE_NONE ), + mnOptions( 0 ), + mbUseTargetFont( false ), + mbIsInteractive( false ) +{ +} + +ScConversionParam::ScConversionParam( ScConversionType eConvType, + LanguageType eLang, sal_Int32 nOptions, bool bIsInteractive ) : + meConvType( eConvType ), + meSourceLang( eLang ), + meTargetLang( eLang ), + mnOptions( nOptions ), + mbUseTargetFont( false ), + mbIsInteractive( bIsInteractive ) +{ + if (LANGUAGE_KOREAN == eLang) + mnOptions = i18n::TextConversionOption::CHARACTER_BY_CHARACTER; +} + +ScConversionParam::ScConversionParam( ScConversionType eConvType, + LanguageType eSourceLang, LanguageType eTargetLang, vcl::Font aTargetFont, + sal_Int32 nOptions, bool bIsInteractive ) : + meConvType( eConvType ), + meSourceLang( eSourceLang ), + meTargetLang( eTargetLang ), + maTargetFont(std::move( aTargetFont )), + mnOptions( nOptions ), + mbUseTargetFont( true ), + mbIsInteractive( bIsInteractive ) +{ + if (LANGUAGE_KOREAN == meSourceLang && LANGUAGE_KOREAN == meTargetLang) + mnOptions = i18n::TextConversionOption::CHARACTER_BY_CHARACTER; +} + +ScTextConversionEngine::ScTextConversionEngine( + SfxItemPool* pEnginePoolP, ScViewData& rViewData, + ScConversionParam aConvParam, + ScDocument* pUndoDoc, ScDocument* pRedoDoc ) : + ScConversionEngineBase( pEnginePoolP, rViewData, pUndoDoc, pRedoDoc ), + maConvParam(std::move( aConvParam )) +{ +} + +void ScTextConversionEngine::ConvertAll(weld::Widget* pDialogParent, EditView& rEditView) +{ + if( FindNextConversionCell() ) + { + rEditView.StartTextConversion(pDialogParent, + maConvParam.GetSourceLang(), maConvParam.GetTargetLang(), maConvParam.GetTargetFont(), + maConvParam.GetOptions(), maConvParam.IsInteractive(), true ); + // #i34769# restore initial cursor position + RestoreCursorPos(); + } +} + +bool ScTextConversionEngine::ConvertNextDocument() +{ + return FindNextConversionCell(); +} + +bool ScTextConversionEngine::NeedsConversion() +{ + return HasConvertibleTextPortion( maConvParam.GetSourceLang() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabcont.cxx b/sc/source/ui/view/tabcont.cxx new file mode 100644 index 0000000000..12fc1a7a6c --- /dev/null +++ b/sc/source/ui/view/tabcont.cxx @@ -0,0 +1,666 @@ +/* -*- 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 <osl/diagnose.h> +#include <sfx2/viewfrm.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/docfile.hxx> +#include <tools/urlobj.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weldutils.hxx> +#include <tabcont.hxx> +#include <tabvwsh.hxx> +#include <docsh.hxx> +#include <scmod.hxx> +#include <sc.hrc> +#include <globstr.hrc> +#include <transobj.hxx> +#include <clipparam.hxx> +#include <dragdata.hxx> +#include <markdata.hxx> +#include <gridwin.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/lok.hxx> + +ScTabControl::ScTabControl( vcl::Window* pParent, ScViewData* pData ) + : TabBar(pParent, WB_3DLOOK | WB_MINSCROLL | WB_SCROLL | WB_RANGESELECT | WB_MULTISELECT | WB_DRAG, true) + , DropTargetHelper(this) + , DragSourceHelper(this) + , pViewData(pData) + , nMouseClickPageId(TabBar::PAGE_NOT_FOUND) + , nSelPageIdByMouse(TabBar::PAGE_NOT_FOUND) + , bErrorShown(false) +{ + ScDocument& rDoc = pViewData->GetDocument(); + + OUString aString; + Color aTabBgColor; + SCTAB nCount = rDoc.GetTableCount(); + for (SCTAB i=0; i<nCount; i++) + { + if (rDoc.IsVisible(i)) + { + if (rDoc.GetName(i,aString)) + { + if ( rDoc.IsScenario(i) ) + InsertPage( static_cast<sal_uInt16>(i)+1, aString, TabBarPageBits::Blue); + else + InsertPage( static_cast<sal_uInt16>(i)+1, aString ); + + if ( rDoc.IsTabProtected(i) ) + SetProtectionSymbol(static_cast<sal_uInt16>(i)+1, true); + + if ( !rDoc.IsDefaultTabBgColor(i) ) + { + aTabBgColor = rDoc.GetTabBgColor(i); + SetTabBgColor( static_cast<sal_uInt16>(i)+1, aTabBgColor ); + } + } + } + } + + SetCurPageId( static_cast<sal_uInt16>(pViewData->GetTabNo()) + 1 ); + + SetSizePixel( Size(SC_TABBAR_DEFWIDTH, 0) ); + + SetSplitHdl( LINK( pViewData->GetView(), ScTabView, TabBarResize ) ); + + EnableEditMode(); + UpdateInputContext(); + + SetScrollAlwaysEnabled(false); + + SetScrollAreaContextHdl( LINK( this, ScTabControl, ShowPageList ) ); +} + +IMPL_LINK(ScTabControl, ShowPageList, const CommandEvent &, rEvent, void) +{ + tools::Rectangle aRect(rEvent.GetMousePosPixel(), Size(1, 1)); + weld::Window* pPopupParent = weld::GetPopupParent(*this, aRect); + std::unique_ptr<weld::Builder> xBuilder(Application::CreateBuilder(pPopupParent, "modules/scalc/ui/pagelistmenu.ui")); + std::unique_ptr<weld::Menu> xPopup(xBuilder->weld_menu("menu")); + + sal_uInt16 nCurPageId = GetCurPageId(); + + ScDocument& rDoc = pViewData->GetDocument(); + SCTAB nCount = rDoc.GetTableCount(); + for (SCTAB i=0; i<nCount; ++i) + { + if (!rDoc.IsVisible(i)) + continue; + OUString aString; + if (!rDoc.GetName(i, aString)) + continue; + sal_uInt16 nId = static_cast<sal_uInt16>(i)+1; + OUString sId = OUString::number(nId); + xPopup->append_radio(sId, aString); + if (nId == nCurPageId) + xPopup->set_active(sId, true); + } + + OUString sIdent(xPopup->popup_at_rect(pPopupParent, aRect)); + if (!sIdent.isEmpty()) + SwitchToPageId(sIdent.toUInt32()); +} + +ScTabControl::~ScTabControl() +{ + disposeOnce(); +} + +void ScTabControl::dispose() +{ + DragSourceHelper::dispose(); + DropTargetHelper::dispose(); + TabBar::dispose(); +} + +sal_uInt16 ScTabControl::GetMaxId() const +{ + sal_uInt16 nVisCnt = GetPageCount(); + if (nVisCnt) + return GetPageId(nVisCnt-1); + + return 0; +} + +SCTAB ScTabControl::GetPrivatDropPos(const Point& rPos ) +{ + sal_uInt16 nPos = ShowDropPos(rPos); + + SCTAB nRealPos = static_cast<SCTAB>(nPos); + + if(nPos !=0 ) + { + ScDocument& rDoc = pViewData->GetDocument(); + + SCTAB nCount = rDoc.GetTableCount(); + + sal_uInt16 nViewPos=0; + nRealPos = nCount; + for (SCTAB i=0; i<nCount; i++) + { + if (rDoc.IsVisible(i)) + { + nViewPos++; + if(nViewPos==nPos) + { + SCTAB j; + for (j=i+1; j<nCount; j++) + { + if (rDoc.IsVisible(j)) + { + break; + } + } + nRealPos =j; + break; + } + } + } + } + return nRealPos ; +} + +void ScTabControl::MouseButtonDown( const MouseEvent& rMEvt ) +{ + ScModule* pScMod = SC_MOD(); + if ( !pScMod->IsModalMode() && !pScMod->IsFormulaMode() && !IsInEditMode() ) + { + // activate View + pViewData->GetViewShell()->SetActive(); // Appear and SetViewFrame + pViewData->GetView()->ActiveGrabFocus(); + } + + if (rMEvt.IsLeft() && rMEvt.GetModifier() == 0) + nMouseClickPageId = GetPageId(rMEvt.GetPosPixel()); + + TabBar::MouseButtonDown( rMEvt ); +} + +void ScTabControl::MouseButtonUp( const MouseEvent& rMEvt ) +{ + Point aPos = PixelToLogic( rMEvt.GetPosPixel() ); + + // mouse button down and up on same page? + if( nMouseClickPageId != GetPageId(aPos)) + nMouseClickPageId = TabBar::PAGE_NOT_FOUND; + + if ( rMEvt.GetClicks() == 2 && rMEvt.IsLeft() && nMouseClickPageId != 0 && nMouseClickPageId != TabBar::PAGE_NOT_FOUND ) + { + SfxDispatcher* pDispatcher = pViewData->GetViewShell()->GetViewFrame().GetDispatcher(); + pDispatcher->Execute( FID_TAB_MENU_RENAME, SfxCallMode::SYNCHRON | SfxCallMode::RECORD ); + return; + } + + if( nMouseClickPageId == 0 ) + { + // Click in the area next to the existing tabs: + SfxDispatcher* pDispatcher = pViewData->GetViewShell()->GetViewFrame().GetDispatcher(); + pDispatcher->Execute( FID_TAB_DESELECTALL, SfxCallMode::SYNCHRON | SfxCallMode::RECORD ); + // forget page ID, to be really sure that the dialog is not called twice + nMouseClickPageId = TabBar::PAGE_NOT_FOUND; + } + + TabBar::MouseButtonUp( rMEvt ); +} + +void ScTabControl::AddTabClick() +{ + TabBar::AddTabClick(); + + // Insert a new sheet at the right end, with default name. + ScDocument& rDoc = pViewData->GetDocument(); + ScModule* pScMod = SC_MOD(); + if (!rDoc.IsDocEditable() || pScMod->IsTableLocked()) + return; + + // auto-accept any in-process input - which would otherwise end up on the new sheet + if (!pScMod->IsFormulaMode()) + pScMod->InputEnterHandler(); + + OUString aName; + rDoc.CreateValidTabName(aName); + SCTAB nTabCount = rDoc.GetTableCount(); + pViewData->GetViewShell()->InsertTable(aName, nTabCount); +} + +void ScTabControl::Select() +{ + /* Remember last clicked page ID. */ + nSelPageIdByMouse = nMouseClickPageId; + /* Reset nMouseClickPageId, so that next Select() call may invalidate + nSelPageIdByMouse (i.e. if called from keyboard). */ + nMouseClickPageId = TabBar::PAGE_NOT_FOUND; + + ScModule* pScMod = SC_MOD(); + ScDocument& rDoc = pViewData->GetDocument(); + ScMarkData& rMark = pViewData->GetMarkData(); + SCTAB nCount = rDoc.GetTableCount(); + SCTAB i; + + if ( pScMod->IsTableLocked() ) // may not be switched now ? + { + // restore the old state of TabControls + + for (i=0; i<nCount; i++) + SelectPage( static_cast<sal_uInt16>(i)+1, rMark.GetTableSelect(i) ); + SetCurPageId( static_cast<sal_uInt16>(pViewData->GetTabNo()) + 1 ); + + return; + } + + sal_uInt16 nCurId = GetCurPageId(); + if (!nCurId) return; // for Excel import it can happen that everything is hidden + sal_uInt16 nPage = nCurId - 1; + + // OLE-inplace deactivate + if ( nPage != static_cast<sal_uInt16>(pViewData->GetTabNo()) ) + pViewData->GetView()->DrawMarkListHasChanged(); + + // InputEnterHandler onlw when not reference input + + bool bRefMode = pScMod->IsFormulaMode(); + if (!bRefMode) + pScMod->InputEnterHandler(); + + for (i=0; i<nCount; i++) + rMark.SelectTable( i, IsPageSelected(static_cast<sal_uInt16>(i)+1) ); + + SfxDispatcher& rDisp = pViewData->GetDispatcher(); + if (rDisp.IsLocked()) + pViewData->GetView()->SetTabNo( static_cast<SCTAB>(nPage) ); + else + { + // sheet for basic is 1-based + SfxUInt16Item aItem( SID_CURRENTTAB, nPage + 1 ); + rDisp.ExecuteList(SID_CURRENTTAB, + SfxCallMode::SLOT | SfxCallMode::RECORD, { &aItem }); + } + + SfxBindings& rBind = pViewData->GetBindings(); + rBind.Invalidate( FID_FILL_TAB ); + rBind.Invalidate( FID_TAB_DESELECTALL ); + + rBind.Invalidate( FID_INS_TABLE ); + rBind.Invalidate( FID_TAB_APPEND ); + rBind.Invalidate( FID_TAB_MOVE ); + rBind.Invalidate( FID_TAB_DUPLICATE ); + rBind.Invalidate( FID_TAB_RENAME ); + rBind.Invalidate( FID_DELETE_TABLE ); + rBind.Invalidate( FID_TABLE_SHOW ); + rBind.Invalidate( FID_TABLE_HIDE ); + rBind.Invalidate( FID_TAB_SET_TAB_BG_COLOR ); + + // Recalculate status bar functions. + rBind.Invalidate( SID_TABLE_CELL ); + + // SetReference onlw when the consolidate dialog is open + // (for references over multiple sheets) + // for others this is only needed fidgeting + + if ( bRefMode && pViewData->GetRefType() == SC_REFTYPE_REF ) + if ( pViewData->GetViewShell()->GetViewFrame().HasChildWindow(SID_OPENDLG_CONSOLIDATE) ) + { + ScRange aRange( + pViewData->GetRefStartX(), pViewData->GetRefStartY(), pViewData->GetRefStartZ(), + pViewData->GetRefEndX(), pViewData->GetRefEndY(), pViewData->GetRefEndZ() ); + pScMod->SetReference( aRange, rDoc, &rMark ); + pScMod->EndReference(); // due to Auto-Hide + } +} + +void ScTabControl::UpdateInputContext() +{ + ScDocument& rDoc = pViewData->GetDocument(); + WinBits nStyle = GetStyle(); + if (rDoc.GetDocumentShell()->IsReadOnly()) + // no insert sheet tab for readonly doc. + SetStyle(nStyle & ~WB_INSERTTAB); + else + SetStyle(nStyle | WB_INSERTTAB); +} + +void ScTabControl::UpdateStatus() +{ + ScDocument& rDoc = pViewData->GetDocument(); + ScMarkData& rMark = pViewData->GetMarkData(); + bool bActive = pViewData->IsActive(); + + SCTAB nCount = rDoc.GetTableCount(); + SCTAB i; + OUString aString; + SCTAB nMaxCnt = std::max( nCount, static_cast<SCTAB>(GetMaxId()) ); + Color aTabBgColor; + + bool bModified = false; // sheet name + for (i=0; i<nMaxCnt && !bModified; i++) + { + if (rDoc.IsVisible(i)) + { + rDoc.GetName(i,aString); + aTabBgColor = rDoc.GetTabBgColor(i); + } + else + { + aString.clear(); + } + + if ( aString != GetPageText(static_cast<sal_uInt16>(i)+1) || (GetTabBgColor(static_cast<sal_uInt16>(i)+1) != aTabBgColor) ) + bModified = true; + } + + if (bModified) + { + Clear(); + for (i=0; i<nCount; i++) + { + if (rDoc.IsVisible(i)) + { + if (rDoc.GetName(i,aString)) + { + if ( rDoc.IsScenario(i) ) + InsertPage(static_cast<sal_uInt16>(i)+1, aString, TabBarPageBits::Blue); + else + InsertPage( static_cast<sal_uInt16>(i)+1, aString ); + + if ( rDoc.IsTabProtected(i) ) + SetProtectionSymbol(static_cast<sal_uInt16>(i)+1, true); + + if ( !rDoc.IsDefaultTabBgColor(i) ) + { + aTabBgColor = rDoc.GetTabBgColor(i); + SetTabBgColor(static_cast<sal_uInt16>(i)+1, aTabBgColor ); + } + } + } + } + } + SetCurPageId( static_cast<sal_uInt16>(pViewData->GetTabNo()) + 1 ); + + if (bActive) + { + bModified = false; // selection + for (i=0; i<nMaxCnt && !bModified; i++) + if ( rMark.GetTableSelect(i) != IsPageSelected(static_cast<sal_uInt16>(i)+1) ) + bModified = true; + + if ( bModified ) + for (i=0; i<nCount; i++) + SelectPage( static_cast<sal_uInt16>(i)+1, rMark.GetTableSelect(i) ); + } +} + +void ScTabControl::SetSheetLayoutRTL( bool bSheetRTL ) +{ + SetEffectiveRTL( bSheetRTL ); + nSelPageIdByMouse = TabBar::PAGE_NOT_FOUND; +} + +void ScTabControl::SwitchToPageId(sal_uInt16 nId) +{ + if (!nId) + return; + + bool bAlreadySelected = IsPageSelected( nId ); + //make the clicked page the current one + SetCurPageId( nId ); + //change the selection when the current one is not already + //selected or part of a multi selection + if(bAlreadySelected) + return; + + sal_uInt16 nCount = GetMaxId(); + + for (sal_uInt16 i=1; i<=nCount; i++) + SelectPage( i, i==nId ); + Select(); + + if (comphelper::LibreOfficeKit::isActive()) + { + // notify LibreOfficeKit about changed page + OString aPayload = OString::number(nId - 1); + pViewData->GetViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_SET_PART, aPayload); + } +} + +void ScTabControl::Command( const CommandEvent& rCEvt ) +{ + ScModule* pScMod = SC_MOD(); + ScTabViewShell* pViewSh = pViewData->GetViewShell(); + bool bDisable = pScMod->IsFormulaMode() || pScMod->IsModalMode(); + + // first activate ViewFrame (Bug 19493): + pViewSh->SetActive(); + + if (rCEvt.GetCommand() != CommandEventId::ContextMenu || bDisable) + return; + + // #i18735# select the page that is under the mouse cursor + // if multiple tables are selected and the one under the cursor + // is not part of them then unselect them + sal_uInt16 nId = GetPageId( rCEvt.GetMousePosPixel() ); + SwitchToPageId(nId); + + // #i52073# OLE inplace editing has to be stopped before showing the sheet tab context menu + pViewSh->DeactivateOle(); + + // Popup-Menu: + // get Dispatcher from ViewData (ViewFrame) instead of Shell (Frame), so it can't be null + pViewData->GetDispatcher().ExecutePopup( "sheettab" ); +} + +void ScTabControl::StartDrag( sal_Int8 /* nAction */, const Point& rPosPixel ) +{ + ScModule* pScMod = SC_MOD(); + bool bDisable = pScMod->IsFormulaMode() || pScMod->IsModalMode(); + + if (!bDisable) + { + vcl::Region aRegion( tools::Rectangle(0,0,0,0) ); + CommandEvent aCEvt( rPosPixel, CommandEventId::StartDrag, true ); // needed for StartDrag + if (TabBar::StartDrag( aCEvt, aRegion )) + DoDrag(); + } +} + +void ScTabControl::DoDrag() +{ + ScDocShell* pDocSh = pViewData->GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + + SCTAB nTab = pViewData->GetTabNo(); + ScRange aTabRange( 0, 0, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab ); + ScMarkData aTabMark = pViewData->GetMarkData(); + aTabMark.ResetMark(); // doesn't change marked table information + aTabMark.SetMarkArea( aTabRange ); + + ScDocumentUniquePtr pClipDoc(new ScDocument( SCDOCMODE_CLIP )); + ScClipParam aClipParam(aTabRange, false); + rDoc.CopyToClip(aClipParam, pClipDoc.get(), &aTabMark, false, false); + + TransferableObjectDescriptor aObjDesc; + pDocSh->FillTransferableObjectDescriptor( aObjDesc ); + aObjDesc.maDisplayName = pDocSh->GetMedium()->GetURLObject().GetURLNoPass(); + // maSize is set in ScTransferObj ctor + + rtl::Reference<ScTransferObj> pTransferObj = new ScTransferObj( std::move(pClipDoc), std::move(aObjDesc) ); + + pTransferObj->SetDragSourceFlags(ScDragSrc::Table); + + pTransferObj->SetDragSource( pDocSh, aTabMark ); + + pTransferObj->SetSourceCursorPos( pViewData->GetCurX(), pViewData->GetCurY() ); + + vcl::Window* pWindow = pViewData->GetActiveWin(); + SC_MOD()->SetDragObject( pTransferObj.get(), nullptr ); // for internal D&D + pTransferObj->StartDrag( pWindow, DND_ACTION_COPYMOVE | DND_ACTION_LINK ); +} + +static sal_uInt16 lcl_DocShellNr( const ScDocument& rDoc ) +{ + sal_uInt16 nShellCnt = 0; + SfxObjectShell* pShell = SfxObjectShell::GetFirst(); + while ( pShell ) + { + if ( auto pDocShell = dynamic_cast<const ScDocShell *>(pShell) ) + { + if ( &pDocShell->GetDocument() == &rDoc ) + return nShellCnt; + + ++nShellCnt; + } + pShell = SfxObjectShell::GetNext( *pShell ); + } + + OSL_FAIL("Document not found"); + return 0; +} + +sal_Int8 ScTabControl::ExecuteDrop( const ExecuteDropEvent& rEvt ) +{ + EndSwitchPage(); + + ScDocument& rDoc = pViewData->GetDocument(); + const ScDragData& rData = SC_MOD()->GetDragData(); + if ( rData.pCellTransfer && (rData.pCellTransfer->GetDragSourceFlags() & ScDragSrc::Table) && + rData.pCellTransfer->GetSourceDocument() == &rDoc ) + { + // moving of tables within the document + SCTAB nPos = GetPrivatDropPos( rEvt.maPosPixel ); + HideDropPos(); + + if ( nPos == rData.pCellTransfer->GetVisibleTab() && rEvt.mnAction == DND_ACTION_MOVE ) + { + // #i83005# do nothing - don't move to the same position + // (too easily triggered unintentionally, and might take a long time in large documents) + } + else + { + if ( !rDoc.GetChangeTrack() && rDoc.IsDocEditable() ) + { + //! use table selection from the tab control where dragging was started? + pViewData->GetView()->MoveTable( lcl_DocShellNr(rDoc), nPos, rEvt.mnAction != DND_ACTION_MOVE ); + + rData.pCellTransfer->SetDragWasInternal(); // don't delete + return DND_ACTION_COPY; + } + } + } + + return DND_ACTION_NONE; +} + +sal_Int8 ScTabControl::AcceptDrop( const AcceptDropEvent& rEvt ) +{ + if ( rEvt.mbLeaving ) + { + EndSwitchPage(); + HideDropPos(); + return rEvt.mnAction; + } + + const ScDocument& rDoc = pViewData->GetDocument(); + const ScDragData& rData = SC_MOD()->GetDragData(); + if ( rData.pCellTransfer && (rData.pCellTransfer->GetDragSourceFlags() & ScDragSrc::Table) && + rData.pCellTransfer->GetSourceDocument() == &rDoc ) + { + // moving of tables within the document + if ( !rDoc.GetChangeTrack() && rDoc.IsDocEditable() ) + { + ShowDropPos( rEvt.maPosPixel ); + return rEvt.mnAction; + } + } + else // switch sheets for all formats + { + SwitchPage( rEvt.maPosPixel ); // switch sheet after timeout + return 0; // nothing can be dropped here + } + + return 0; +} + +bool ScTabControl::StartRenaming() +{ + return pViewData->GetDocument().IsDocEditable(); +} + +TabBarAllowRenamingReturnCode ScTabControl::AllowRenaming() +{ + ScTabViewShell* pViewSh = pViewData->GetViewShell(); + OSL_ENSURE( pViewSh, "pViewData->GetViewShell()" ); + + TabBarAllowRenamingReturnCode nRet = TABBAR_RENAMING_CANCEL; + sal_uInt16 nId = GetEditPageId(); + if ( nId ) + { + SCTAB nTab = nId - 1; + OUString aNewName = GetEditText(); + bool bDone = pViewSh->RenameTable( aNewName, nTab ); + if ( bDone ) + nRet = TABBAR_RENAMING_YES; + else if ( bErrorShown ) + { + // if the error message from this TabControl is currently visible, + // don't end edit mode now, to avoid problems when returning to + // the other call (showing the error) - this should not happen + OSL_FAIL("ScTabControl::AllowRenaming: nested calls"); + nRet = TABBAR_RENAMING_NO; + } + else if (pViewData->GetDocShell()->IsInModalMode()) + { + // don't show error message above any modal dialog + // instead cancel renaming without error message + // e.g. start with default Sheet1, add another sheet + // alt+left click on Sheet2 tab, edit to say Sheet1 + // ctrl+S to trigger modal file save dialog + nRet = TABBAR_RENAMING_CANCEL; + } + else + { + bErrorShown = true; + pViewSh->ErrorMessage( STR_INVALIDTABNAME ); + bErrorShown = false; + nRet = TABBAR_RENAMING_NO; + } + } + return nRet; +} + +void ScTabControl::EndRenaming() +{ + if ( HasFocus() ) + pViewData->GetView()->ActiveGrabFocus(); +} + +void ScTabControl::Mirror() +{ + TabBar::Mirror(); + if( nSelPageIdByMouse != TabBar::PAGE_NOT_FOUND ) + { + tools::Rectangle aRect( GetPageRect( GetCurPageId() ) ); + if( !aRect.IsEmpty() ) + SetPointerPosPixel( aRect.Center() ); + nSelPageIdByMouse = TabBar::PAGE_NOT_FOUND; // only once after a Select() + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabsplit.cxx b/sc/source/ui/view/tabsplit.cxx new file mode 100644 index 0000000000..fb8435b271 --- /dev/null +++ b/sc/source/ui/view/tabsplit.cxx @@ -0,0 +1,127 @@ +/* -*- 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 <tabsplit.hxx> +#include <viewdata.hxx> + +#include <vcl/settings.hxx> + +ScTabSplitter::ScTabSplitter( vcl::Window* pParent, WinBits nWinStyle, const ScViewData* pData ) : + Splitter(pParent, nWinStyle), + pViewData(pData) +{ + SetFixed(false); + EnableRTL(false); +} + +ScTabSplitter::~ScTabSplitter() +{ +} + +void ScTabSplitter::MouseButtonDown( const MouseEvent& rMEvt ) +{ + if (bFixed) + Window::MouseButtonDown( rMEvt ); + else + Splitter::MouseButtonDown( rMEvt ); +} + +void ScTabSplitter::SetFixed(bool bSet) +{ + bFixed = bSet; + if (bSet) + SetPointer(PointerStyle::Arrow); + else if (IsHorizontal()) + SetPointer(PointerStyle::HSplit); + else + SetPointer(PointerStyle::VSplit); +} + +void ScTabSplitter::Paint( vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect ) +{ + rRenderContext.Push(vcl::PushFlags::FILLCOLOR | vcl::PushFlags::LINECOLOR); + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + + if (IsHorizontal()) + { + switch (pViewData->GetHSplitMode()) + { + case SC_SPLIT_NONE: + { + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + rRenderContext.SetFillColor(rStyleSettings.GetShadowColor()); + rRenderContext.DrawRect(tools::Rectangle(rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom())); + + // Draw handle + rRenderContext.SetLineColor(COL_BLACK); + rRenderContext.SetFillColor(COL_BLACK); + const tools::Long xc = rRect.Right() + rRect.Left(); + const tools::Long h4 = rRect.GetHeight() / 4; + // First xc fraction is truncated, second one is rounded. This will draw a centered line + // in handlers with odd width and a centered rectangle in those with even width. + rRenderContext.DrawRect(tools::Rectangle(Point(xc / 2, rRect.Top() + h4), + Point((xc + 1) / 2, rRect.Bottom() - h4))); + break; + } + case SC_SPLIT_NORMAL: + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + rRenderContext.SetFillColor(rStyleSettings.GetShadowColor()); + rRenderContext.DrawRect(tools::Rectangle(rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom())); + break; + case SC_SPLIT_FIX: + // Nothing to draw + break; + } + } + else + { + switch (pViewData->GetVSplitMode()) + { + case SC_SPLIT_NONE: + { + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + rRenderContext.SetFillColor(rStyleSettings.GetShadowColor()); + rRenderContext.DrawRect(tools::Rectangle(rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom())); + + // Draw handle + rRenderContext.SetLineColor(COL_BLACK); + rRenderContext.SetFillColor(COL_BLACK); + const tools::Long yc = rRect.Top() + rRect.Bottom(); + const tools::Long w4 = rRect.GetWidth() / 4; + // First yc fraction is truncated, second one is rounded. This will draw a centered line + // in handlers with odd height and a centered rectangle in those with even height. + GetOutDev()->DrawRect(tools::Rectangle(Point(rRect.Left() + w4, yc / 2), + Point(rRect.Right() - w4, (yc + 1) / 2))); + break; + } + case SC_SPLIT_NORMAL: + rRenderContext.SetLineColor(rStyleSettings.GetShadowColor()); + rRenderContext.SetFillColor(rStyleSettings.GetShadowColor()); + rRenderContext.DrawRect(tools::Rectangle(rRect.Left(), rRect.Top(), rRect.Right(), rRect.Bottom())); + break; + case SC_SPLIT_FIX: + // Nothing to draw + break; + } + } + + rRenderContext.Pop(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabview.cxx b/sc/source/ui/view/tabview.cxx new file mode 100644 index 0000000000..9eff50195e --- /dev/null +++ b/sc/source/ui/view/tabview.cxx @@ -0,0 +1,3162 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <scitems.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/bindings.hxx> +#include <vcl/commandevent.hxx> +#include <vcl/help.hxx> +#include <vcl/settings.hxx> +#include <sal/log.hxx> +#include <tools/svborder.hxx> +#include <tools/json_writer.hxx> +#include <o3tl/unit_conversion.hxx> + +#include <pagedata.hxx> +#include <tabview.hxx> +#include <tabvwsh.hxx> +#include <document.hxx> +#include <gridwin.hxx> +#include <olinewin.hxx> +#include <olinetab.hxx> +#include <tabsplit.hxx> +#include <colrowba.hxx> +#include <tabcont.hxx> +#include <scmod.hxx> +#include <sc.hrc> +#include <globstr.hrc> +#include <scresid.hxx> +#include <drawview.hxx> +#include <docsh.hxx> +#include <viewuno.hxx> +#include <appoptio.hxx> +#include <attrib.hxx> +#include <spellcheckcontext.hxx> +#include <comphelper/lok.hxx> +#include <sfx2/lokhelper.hxx> +#include <osl/diagnose.h> + +#include <boost/property_tree/ptree.hpp> +#include <boost/property_tree/json_parser.hpp> + +#include <com/sun/star/sheet/DataPilotFieldOrientation.hpp> + +#include <algorithm> + +#include <basegfx/utils/zoomtools.hxx> + +#define SPLIT_MARGIN 30 +#define SPLIT_HANDLE_SIZE 5 +constexpr sal_Int32 TAB_HEIGHT_MARGIN = 10; + +#define SC_ICONSIZE 36 + +#define SC_SCROLLBAR_MIN 30 +#define SC_TABBAR_MIN 6 + +using namespace ::com::sun::star; + +// Corner-Button + +ScCornerButton::ScCornerButton( vcl::Window* pParent, ScViewData* pData ) : + Window( pParent, WinBits( 0 ) ), + pViewData( pData ) +{ + EnableRTL( false ); +} + +ScCornerButton::~ScCornerButton() +{ +} + +void ScCornerButton::Paint(vcl::RenderContext& rRenderContext, const tools::Rectangle& rRect) +{ + const StyleSettings& rStyleSettings = rRenderContext.GetSettings().GetStyleSettings(); + SetBackground(rStyleSettings.GetFaceColor()); + + Size aSize(GetOutputSizePixel()); + tools::Long nPosX = aSize.Width() - 1; + tools::Long nPosY = aSize.Height() - 1; + + Window::Paint(rRenderContext, rRect); + + bool bLayoutRTL = pViewData->GetDocument().IsLayoutRTL( pViewData->GetTabNo() ); + tools::Long nDarkX = bLayoutRTL ? 0 : nPosX; + + // both buttons have the same look now - only dark right/bottom lines + rRenderContext.SetLineColor(rStyleSettings.GetDarkShadowColor()); + rRenderContext.DrawLine(Point(0, nPosY), Point(nPosX, nPosY)); + rRenderContext.DrawLine(Point(nDarkX, 0), Point(nDarkX, nPosY)); +} + +void ScCornerButton::StateChanged( StateChangedType nType ) +{ + Window::StateChanged( nType ); + + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + SetBackground( rStyleSettings.GetFaceColor() ); + Invalidate(); +} + +void ScCornerButton::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged( rDCEvt ); + + const StyleSettings& rStyleSettings = GetSettings().GetStyleSettings(); + SetBackground( rStyleSettings.GetFaceColor() ); + Invalidate(); +} + +void ScCornerButton::Resize() +{ + Invalidate(); +} + +void ScCornerButton::MouseButtonDown( const MouseEvent& rMEvt ) +{ + ScModule* pScMod = SC_MOD(); + bool bDisable = pScMod->IsFormulaMode() || pScMod->IsModalMode(); + if (!bDisable) + { + ScTabViewShell* pViewSh = pViewData->GetViewShell(); + pViewSh->SetActive(); // Appear and SetViewFrame + pViewSh->ActiveGrabFocus(); + + bool bControl = rMEvt.IsMod1(); + pViewSh->SelectAll( bControl ); + } +} +namespace +{ + +bool lcl_HasColOutline( const ScViewData& rViewData ) +{ + const ScOutlineTable* pTable = rViewData.GetDocument().GetOutlineTable(rViewData.GetTabNo()); + if (pTable) + { + const ScOutlineArray& rArray = pTable->GetColArray(); + if ( rArray.GetDepth() > 0 ) + return true; + } + return false; +} + +bool lcl_HasRowOutline( const ScViewData& rViewData ) +{ + const ScOutlineTable* pTable = rViewData.GetDocument().GetOutlineTable(rViewData.GetTabNo()); + if (pTable) + { + const ScOutlineArray& rArray = pTable->GetRowArray(); + if ( rArray.GetDepth() > 0 ) + return true; + } + return false; +} + +} // anonymous namespace + +ScTabView::ScTabView( vcl::Window* pParent, ScDocShell& rDocSh, ScTabViewShell* pViewShell ) : + pFrameWin( pParent ), + aViewData( rDocSh, pViewShell ), + aFunctionSet( &aViewData ), + aHdrFunc( &aViewData ), + aVScrollTop( VclPtr<ScrollAdaptor>::Create( pFrameWin, false ) ), + aVScrollBottom( VclPtr<ScrollAdaptor>::Create( pFrameWin, false ) ), + aHScrollLeft( VclPtr<ScrollAdaptor>::Create( pFrameWin, true ) ), + aHScrollRight( VclPtr<ScrollAdaptor>::Create( pFrameWin, true ) ), + aCornerButton( VclPtr<ScCornerButton>::Create( pFrameWin, &aViewData ) ), + aTopButton( VclPtr<ScCornerButton>::Create( pFrameWin, &aViewData ) ), + aScrollTimer("ScTabView aScrollTimer"), + pTimerWindow( nullptr ), + aExtraEditViewManager( pViewShell, pGridWin ), + nTipVisible( nullptr ), + nTipAlign( QuickHelpFlags::NONE ), + nPrevDragPos( 0 ), + meBlockMode(None), + meHighlightBlockMode(None), + nBlockStartX( 0 ), + nBlockStartXOrig( 0 ), + nBlockEndX( 0 ), + nBlockStartY( 0 ), + nBlockStartYOrig( 0 ), + nBlockEndY( 0 ), + nBlockStartZ( 0 ), + nBlockEndZ( 0 ), + nOldCurX( 0 ), + nOldCurY( 0 ), + mfPendingTabBarWidth( -1.0 ), + mnLOKStartHeaderRow( -2 ), + mnLOKEndHeaderRow( -1 ), + mnLOKStartHeaderCol( -2 ), + mnLOKEndHeaderCol( -1 ), + bMinimized( false ), + bInUpdateHeader( false ), + bInActivatePart( false ), + bInZoomUpdate( false ), + bMoveIsShift( false ), + bDrawSelMode( false ), + bLockPaintBrush( false ), + bDragging( false ), + bBlockNeg( false ), + bBlockCols( false ), + bBlockRows( false ), + mbInlineWithScrollbar( false ) +{ + Init(); +} + +void ScTabView::InitScrollBar(ScrollAdaptor& rScrollBar, tools::Long nMaxVal, const Link<weld::Scrollbar&, void>& rLink) +{ + rScrollBar.SetRange( Range( 0, nMaxVal ) ); + rScrollBar.SetLineSize( 1 ); + rScrollBar.SetPageSize( 1 ); // is queried separately + rScrollBar.SetVisibleSize( 10 ); // is reset by Resize + + rScrollBar.SetScrollHdl(rLink); + rScrollBar.SetMouseReleaseHdl(LINK(this, ScTabView, EndScrollHdl)); + + rScrollBar.EnableRTL( aViewData.GetDocument().IsLayoutRTL( aViewData.GetTabNo() ) ); + + // Related: tdf#155266 Eliminate delayed scrollbar redrawing when swiping + // By default, the layout idle timer in the InterimWindowItem class + // is set to TaskPriority::RESIZE. That is too high of a priority as + // it appears that other timers are drawing after the scrollbar has been + // redrawn. + // As a result, when swiping, the content moves fluidly but the scrollbar + // thumb does not move until after swiping stops or pauses. Then, after a + // short lag, the scrollbar thumb finally "jumps" to the expected + // position. + // So, to fix this scrollbar "stickiness" when swiping, setting the + // priority to TaskPriority::POST_PAINT causes the scrollbar to be + // redrawn after any competing timers. + rScrollBar.SetPriority(TaskPriority::POST_PAINT); +} + +// Scroll-Timer +void ScTabView::SetTimer( ScGridWindow* pWin, const MouseEvent& rMEvt ) +{ + pTimerWindow = pWin; + aTimerMEvt = rMEvt; + aScrollTimer.Start(); +} + +void ScTabView::ResetTimer() +{ + aScrollTimer.Stop(); + pTimerWindow = nullptr; +} + +IMPL_LINK_NOARG(ScTabView, TimerHdl, Timer *, void) +{ + if (pTimerWindow) + pTimerWindow->MouseMove( aTimerMEvt ); +} + +// --- Resize --------------------------------------------------------------------- + +static void lcl_SetPosSize( vcl::Window& rWindow, const Point& rPos, const Size& rSize, + tools::Long nTotalWidth, bool bLayoutRTL ) +{ + Point aNewPos = rPos; + if ( bLayoutRTL ) + { + aNewPos.setX( nTotalWidth - rPos.X() - rSize.Width() ); + if ( aNewPos == rWindow.GetPosPixel() && rSize.Width() != rWindow.GetSizePixel().Width() ) + { + // Document windows are manually painted right-to-left, so they need to + // be repainted if the size changes. + rWindow.Invalidate(); + } + } + rWindow.SetPosSizePixel( aNewPos, rSize ); +} + +void ScTabView::DoResize( const Point& rOffset, const Size& rSize, bool bInner ) +{ + HideListBox(); + + bool bHasHint = HasHintWindow(); + if (bHasHint) + RemoveHintWindow(); + + bool bLayoutRTL = aViewData.GetDocument().IsLayoutRTL( aViewData.GetTabNo() ); + tools::Long nTotalWidth = rSize.Width(); + if ( bLayoutRTL ) + nTotalWidth += 2*rOffset.X(); + + bool bVScroll = aViewData.IsVScrollMode(); + bool bHScroll = aViewData.IsHScrollMode(); + bool bTabControl = aViewData.IsTabMode(); + bool bHeaders = aViewData.IsHeaderMode(); + bool bOutlMode = aViewData.IsOutlineMode(); + bool bHOutline = bOutlMode && lcl_HasColOutline(aViewData); + bool bVOutline = bOutlMode && lcl_HasRowOutline(aViewData); + + if ( aViewData.GetDocShell()->IsPreview() ) + bHScroll = bVScroll = bTabControl = bHeaders = bHOutline = bVOutline = false; + + tools::Long nBarX = 0; + tools::Long nBarY = 0; + tools::Long nOutlineX = 0; + tools::Long nOutlineY = 0; + tools::Long nOutPosX; + tools::Long nOutPosY; + + tools::Long nPosX = rOffset.X(); + tools::Long nPosY = rOffset.Y(); + tools::Long nSizeX = rSize.Width(); + tools::Long nSizeY = rSize.Height(); + + bMinimized = ( nSizeX<=SC_ICONSIZE || nSizeY<=SC_ICONSIZE ); + if ( bMinimized ) + return; + + float fScaleFactor = pFrameWin->GetDPIScaleFactor(); + + tools::Long nSplitSizeX = SPLIT_HANDLE_SIZE * fScaleFactor; + if ( aViewData.GetHSplitMode() == SC_SPLIT_FIX ) + nSplitSizeX = 1; + tools::Long nSplitSizeY = SPLIT_HANDLE_SIZE * fScaleFactor; + if ( aViewData.GetVSplitMode() == SC_SPLIT_FIX ) + nSplitSizeY = 1; + + aBorderPos = rOffset; + aFrameSize = rSize; + + const StyleSettings& rStyleSettings = pFrameWin->GetSettings().GetStyleSettings(); + + + Size aFontSize = rStyleSettings.GetTabFont().GetFontSize(); + MapMode aPtMapMode(MapUnit::MapPoint); + aFontSize = pFrameWin->LogicToPixel(aFontSize, aPtMapMode); + sal_Int32 nTabHeight = aFontSize.Height() + TAB_HEIGHT_MARGIN; + + if ( aViewData.GetHSplitMode() != SC_SPLIT_NONE ) + { + if ( aViewData.GetHSplitPos() > nSizeX - SPLIT_MARGIN ) + { + aViewData.SetHSplitMode( SC_SPLIT_NONE ); + if ( WhichH( aViewData.GetActivePart() ) == SC_SPLIT_RIGHT ) + ActivatePart( SC_SPLIT_BOTTOMLEFT ); + InvalidateSplit(); + } + } + if ( aViewData.GetVSplitMode() != SC_SPLIT_NONE ) + { + if ( aViewData.GetVSplitPos() > nSizeY - SPLIT_MARGIN ) + { + aViewData.SetVSplitMode( SC_SPLIT_NONE ); + if ( WhichV( aViewData.GetActivePart() ) == SC_SPLIT_TOP ) + ActivatePart( SC_SPLIT_BOTTOMLEFT ); + InvalidateSplit(); + } + } + + UpdateShow(); + + if (bHScroll || bVScroll) // Scrollbars horizontal or vertical + { + tools::Long nScrollBarSize = rStyleSettings.GetScrollBarSize(); + if (bVScroll) + { + nBarX = nScrollBarSize; + nSizeX -= nBarX; + } + if (bHScroll) + { + nBarY = nTabHeight; + + if (!mbInlineWithScrollbar) + nBarY += nScrollBarSize; + + nSizeY -= nBarY; + } + + if (bHScroll) // Scrollbars horizontal + { + tools::Long nSizeLt = 0; // left scroll bar + tools::Long nSizeRt = 0; // right scroll bar + tools::Long nSizeSp = 0; // splitter + + switch (aViewData.GetHSplitMode()) + { + case SC_SPLIT_NONE: + nSizeSp = nSplitSizeX; + nSizeLt = nSizeX - nSizeSp; // Convert the corner + break; + case SC_SPLIT_NORMAL: + nSizeSp = nSplitSizeX; + nSizeLt = aViewData.GetHSplitPos(); + break; + case SC_SPLIT_FIX: + nSizeSp = 0; + nSizeLt = 0; + break; + } + nSizeRt = nSizeX - nSizeLt - nSizeSp; + + tools::Long nTabSize = 0; + + if (bTabControl) + { + // pending relative tab bar width from extended document options + if( mfPendingTabBarWidth >= 0.0 ) + { + SetRelTabBarWidth( mfPendingTabBarWidth ); + mfPendingTabBarWidth = -1.0; + } + + if (mbInlineWithScrollbar) + { + nTabSize = pTabControl->GetSizePixel().Width(); + + if ( aViewData.GetHSplitMode() != SC_SPLIT_FIX ) // left Scrollbar + { + if (nTabSize > nSizeLt-SC_SCROLLBAR_MIN) + nTabSize = nSizeLt-SC_SCROLLBAR_MIN; + if (nTabSize < SC_TABBAR_MIN) + nTabSize = SC_TABBAR_MIN; + nSizeLt -= nTabSize; + } + else // right Scrollbar + { + if (nTabSize > nSizeRt-SC_SCROLLBAR_MIN) + nTabSize = nSizeRt-SC_SCROLLBAR_MIN; + if (nTabSize < SC_TABBAR_MIN) + nTabSize = SC_TABBAR_MIN; + nSizeRt -= nTabSize; + } + } + } + + if (mbInlineWithScrollbar) + { + Point aTabPoint(nPosX, nPosY + nSizeY); + Size aTabSize(nTabSize, nBarY); + lcl_SetPosSize(*pTabControl, aTabPoint, aTabSize, nTotalWidth, bLayoutRTL); + pTabControl->SetSheetLayoutRTL(bLayoutRTL); + + Point aHScrollLeftPoint(nPosX + nTabSize, nPosY + nSizeY); + Size aHScrollLeftSize(nSizeLt, nBarY); + lcl_SetPosSize(*aHScrollLeft, aHScrollLeftPoint, aHScrollLeftSize, nTotalWidth, bLayoutRTL); + + Point aHSplitterPoint(nPosX + nTabSize + nSizeLt, nPosY + nSizeY); + Size aHSplitterSize(nSizeSp, nBarY); + lcl_SetPosSize(*pHSplitter, aHSplitterPoint, aHSplitterSize, nTotalWidth, bLayoutRTL); + + Point aHScrollRightPoint(nPosX + nTabSize + nSizeLt + nSizeSp, nPosY + nSizeY); + Size aHScrollRightSize(nSizeRt, nBarY); + lcl_SetPosSize(*aHScrollRight, aHScrollRightPoint, aHScrollRightSize, nTotalWidth, bLayoutRTL); + } + else + { + Point aTabPoint(nPosX, nPosY + nSizeY + nScrollBarSize); + Size aTabSize(nSizeX, nTabHeight); + lcl_SetPosSize(*pTabControl, aTabPoint, aTabSize, nTotalWidth, bLayoutRTL); + pTabControl->SetSheetLayoutRTL(bLayoutRTL); + + Point aHScrollLeftPoint(nPosX, nPosY + nSizeY); + Size aHScrollLeftSize(nSizeLt, nScrollBarSize); + lcl_SetPosSize(*aHScrollLeft, aHScrollLeftPoint, aHScrollLeftSize, nTotalWidth, bLayoutRTL); + + Point aHSplitterPoint(nPosX + nSizeLt, nPosY + nSizeY); + Size aHSplitterSize(nSizeSp, nScrollBarSize); + lcl_SetPosSize(*pHSplitter, aHSplitterPoint, aHSplitterSize, nTotalWidth, bLayoutRTL); + + Point aHScrollRightPoint(nPosX + nSizeLt + nSizeSp, nPosY + nSizeY); + Size aHScrollRightSize(nSizeRt, nScrollBarSize); + lcl_SetPosSize(*aHScrollRight, aHScrollRightPoint, aHScrollRightSize, nTotalWidth, bLayoutRTL); + } + // SetDragRectPixel is done below + } + + if (bVScroll) + { + tools::Long nSizeUp = 0; // upper scroll bar + tools::Long nSizeSp = 0; // splitter + tools::Long nSizeDn; // lower scroll bar + + switch (aViewData.GetVSplitMode()) + { + case SC_SPLIT_NONE: + nSizeUp = 0; + nSizeSp = nSplitSizeY; + break; + case SC_SPLIT_NORMAL: + nSizeUp = aViewData.GetVSplitPos(); + nSizeSp = nSplitSizeY; + break; + case SC_SPLIT_FIX: + nSizeUp = 0; + nSizeSp = 0; + break; + } + nSizeDn = nSizeY - nSizeUp - nSizeSp; + + lcl_SetPosSize( *aVScrollTop, Point(nPosX + nSizeX, nPosY), + Size(nBarX, nSizeUp), nTotalWidth, bLayoutRTL ); + lcl_SetPosSize( *pVSplitter, Point( nPosX + nSizeX, nPosY+nSizeUp ), + Size( nBarX, nSizeSp ), nTotalWidth, bLayoutRTL ); + lcl_SetPosSize( *aVScrollBottom, Point(nPosX + nSizeX, + nPosY + nSizeUp + nSizeSp), + Size(nBarX, nSizeDn), nTotalWidth, bLayoutRTL ); + + // SetDragRectPixel is done below + } + } + + // SetDragRectPixel also without Scrollbars etc., when already split + if ( bHScroll || aViewData.GetHSplitMode() != SC_SPLIT_NONE ) + pHSplitter->SetDragRectPixel( + tools::Rectangle( nPosX, nPosY, nPosX+nSizeX, nPosY+nSizeY ), pFrameWin ); + if ( bVScroll || aViewData.GetVSplitMode() != SC_SPLIT_NONE ) + pVSplitter->SetDragRectPixel( + tools::Rectangle( nPosX, nPosY, nPosX+nSizeX, nPosY+nSizeY ), pFrameWin ); + + if (bTabControl && ! bHScroll ) + { + nBarY = aHScrollLeft->GetSizePixel().Height(); + + tools::Long nSize1 = nSizeX; + + tools::Long nTabSize = nSize1; + if (nTabSize < 0) nTabSize = 0; + + lcl_SetPosSize( *pTabControl, Point(nPosX, nPosY+nSizeY-nBarY), + Size(nTabSize, nBarY), nTotalWidth, bLayoutRTL ); + nSizeY -= nBarY; + + if( bVScroll ) + { + Size aVScrSize = aVScrollBottom->GetSizePixel(); + aVScrSize.AdjustHeight( -nBarY ); + aVScrollBottom->SetSizePixel( aVScrSize ); + } + } + + nOutPosX = nPosX; + nOutPosY = nPosY; + + // Outline-Controls + if (bVOutline && pRowOutline[SC_SPLIT_BOTTOM]) + { + nOutlineX = pRowOutline[SC_SPLIT_BOTTOM]->GetDepthSize(); + nSizeX -= nOutlineX; + nPosX += nOutlineX; + } + if (bHOutline && pColOutline[SC_SPLIT_LEFT]) + { + nOutlineY = pColOutline[SC_SPLIT_LEFT]->GetDepthSize(); + nSizeY -= nOutlineY; + nPosY += nOutlineY; + } + + if (bHeaders) // column/row header + { + nBarX = pRowBar[SC_SPLIT_BOTTOM]->GetSizePixel().Width(); + nBarY = pColBar[SC_SPLIT_LEFT]->GetSizePixel().Height(); + nSizeX -= nBarX; + nSizeY -= nBarY; + nPosX += nBarX; + nPosY += nBarY; + } + else + nBarX = nBarY = 0; + + // evaluate splitter + + tools::Long nLeftSize = nSizeX; + tools::Long nRightSize = 0; + tools::Long nTopSize = 0; + tools::Long nBottomSize = nSizeY; + tools::Long nSplitPosX = nPosX; + tools::Long nSplitPosY = nPosY; + + if ( aViewData.GetHSplitMode() != SC_SPLIT_NONE ) + { + tools::Long nSplitHeight = rSize.Height(); + if ( aViewData.GetHSplitMode() == SC_SPLIT_FIX ) + { + // Do not allow freeze splitter to overlap scroll bar/tab bar + if ( bHScroll ) + nSplitHeight -= aHScrollLeft->GetSizePixel().Height(); + else if ( bTabControl && pTabControl ) + nSplitHeight -= pTabControl->GetSizePixel().Height(); + } + nSplitPosX = aViewData.GetHSplitPos(); + lcl_SetPosSize( *pHSplitter, + Point(nSplitPosX, nOutPosY), + Size(nSplitSizeX, nSplitHeight - nTabHeight), nTotalWidth, bLayoutRTL); + nLeftSize = nSplitPosX - nPosX; + nSplitPosX += nSplitSizeX; + nRightSize = nSizeX - nLeftSize - nSplitSizeX; + } + if ( aViewData.GetVSplitMode() != SC_SPLIT_NONE ) + { + tools::Long nSplitWidth = rSize.Width(); + if ( aViewData.GetVSplitMode() == SC_SPLIT_FIX && bVScroll ) + nSplitWidth -= aVScrollBottom->GetSizePixel().Width(); + nSplitPosY = aViewData.GetVSplitPos(); + lcl_SetPosSize( *pVSplitter, + Point( nOutPosX, nSplitPosY ), Size( nSplitWidth, nSplitSizeY ), nTotalWidth, bLayoutRTL ); + nTopSize = nSplitPosY - nPosY; + nSplitPosY += nSplitSizeY; + nBottomSize = nSizeY - nTopSize - nSplitSizeY; + } + + // ShowHide for pColOutline / pRowOutline happens in UpdateShow + + if (bHOutline) // Outline-Controls + { + if (pColOutline[SC_SPLIT_LEFT]) + { + pColOutline[SC_SPLIT_LEFT]->SetHeaderSize( nBarX ); + lcl_SetPosSize( *pColOutline[SC_SPLIT_LEFT], + Point(nPosX-nBarX,nOutPosY), Size(nLeftSize+nBarX,nOutlineY), nTotalWidth, bLayoutRTL ); + } + if (pColOutline[SC_SPLIT_RIGHT]) + { + pColOutline[SC_SPLIT_RIGHT]->SetHeaderSize( 0 ); // always call to update RTL flag + lcl_SetPosSize( *pColOutline[SC_SPLIT_RIGHT], + Point(nSplitPosX,nOutPosY), Size(nRightSize,nOutlineY), nTotalWidth, bLayoutRTL ); + } + } + if (bVOutline) + { + if (nTopSize) + { + if (pRowOutline[SC_SPLIT_TOP] && pRowOutline[SC_SPLIT_BOTTOM]) + { + pRowOutline[SC_SPLIT_TOP]->SetHeaderSize( nBarY ); + lcl_SetPosSize( *pRowOutline[SC_SPLIT_TOP], + Point(nOutPosX,nPosY-nBarY), Size(nOutlineX,nTopSize+nBarY), nTotalWidth, bLayoutRTL ); + pRowOutline[SC_SPLIT_BOTTOM]->SetHeaderSize( 0 ); + lcl_SetPosSize( *pRowOutline[SC_SPLIT_BOTTOM], + Point(nOutPosX,nSplitPosY), Size(nOutlineX,nBottomSize), nTotalWidth, bLayoutRTL ); + } + } + else if (pRowOutline[SC_SPLIT_BOTTOM]) + { + pRowOutline[SC_SPLIT_BOTTOM]->SetHeaderSize( nBarY ); + lcl_SetPosSize( *pRowOutline[SC_SPLIT_BOTTOM], + Point(nOutPosX,nSplitPosY-nBarY), Size(nOutlineX,nBottomSize+nBarY), nTotalWidth, bLayoutRTL ); + } + } + if (bHOutline && bVOutline) + { + lcl_SetPosSize( *aTopButton, Point(nOutPosX,nOutPosY), Size(nOutlineX,nOutlineY), nTotalWidth, bLayoutRTL ); + aTopButton->Show(); + } + else + aTopButton->Hide(); + + if (bHeaders) // column/row header + { + lcl_SetPosSize( *pColBar[SC_SPLIT_LEFT], + Point(nPosX,nPosY-nBarY), Size(nLeftSize,nBarY), nTotalWidth, bLayoutRTL ); + if (pColBar[SC_SPLIT_RIGHT]) + lcl_SetPosSize( *pColBar[SC_SPLIT_RIGHT], + Point(nSplitPosX,nPosY-nBarY), Size(nRightSize,nBarY), nTotalWidth, bLayoutRTL ); + + if (pRowBar[SC_SPLIT_TOP]) + lcl_SetPosSize( *pRowBar[SC_SPLIT_TOP], + Point(nPosX-nBarX,nPosY), Size(nBarX,nTopSize), nTotalWidth, bLayoutRTL ); + lcl_SetPosSize( *pRowBar[SC_SPLIT_BOTTOM], + Point(nPosX-nBarX,nSplitPosY), Size(nBarX,nBottomSize), nTotalWidth, bLayoutRTL ); + + lcl_SetPosSize( *aCornerButton, Point(nPosX-nBarX,nPosY-nBarY), Size(nBarX,nBarY), nTotalWidth, bLayoutRTL ); + aCornerButton->Show(); + pColBar[SC_SPLIT_LEFT]->Show(); + pRowBar[SC_SPLIT_BOTTOM]->Show(); + } + else + { + aCornerButton->Hide(); + pColBar[SC_SPLIT_LEFT]->Hide(); // always here + pRowBar[SC_SPLIT_BOTTOM]->Hide(); + } + + // Grid-Windows + + if (bInner) + { + tools::Long nInnerPosX = bLayoutRTL ? ( nTotalWidth - nPosX - nLeftSize ) : nPosX; + pGridWin[SC_SPLIT_BOTTOMLEFT]->SetPosPixel( Point(nInnerPosX,nSplitPosY) ); + } + else + { + lcl_SetPosSize( *pGridWin[SC_SPLIT_BOTTOMLEFT], + Point(nPosX,nSplitPosY), Size(nLeftSize,nBottomSize), nTotalWidth, bLayoutRTL ); + if ( aViewData.GetHSplitMode() != SC_SPLIT_NONE ) + lcl_SetPosSize( *pGridWin[SC_SPLIT_BOTTOMRIGHT], + Point(nSplitPosX,nSplitPosY), Size(nRightSize,nBottomSize), nTotalWidth, bLayoutRTL ); + if ( aViewData.GetVSplitMode() != SC_SPLIT_NONE ) + lcl_SetPosSize( *pGridWin[SC_SPLIT_TOPLEFT], + Point(nPosX,nPosY), Size(nLeftSize,nTopSize), nTotalWidth, bLayoutRTL ); + if ( aViewData.GetHSplitMode() != SC_SPLIT_NONE && aViewData.GetVSplitMode() != SC_SPLIT_NONE ) + lcl_SetPosSize( *pGridWin[SC_SPLIT_TOPRIGHT], + Point(nSplitPosX,nPosY), Size(nRightSize,nTopSize), nTotalWidth, bLayoutRTL ); + } + + // update scroll bars + + if (!bInUpdateHeader) + { + UpdateScrollBars(); // don't reset scroll bars when scrolling + UpdateHeaderWidth(); + + InterpretVisible(); // have everything calculated before painting + } + + if (bHasHint) + TestHintWindow(); // reposition + + UpdateVarZoom(); // update variable zoom types (after resizing GridWindows) + + if (aViewData.GetViewShell()->HasAccessibilityObjects()) + aViewData.GetViewShell()->BroadcastAccessibility(SfxHint(SfxHintId::ScAccWindowResized)); +} + +void ScTabView::UpdateVarZoom() +{ + // update variable zoom types + + SvxZoomType eZoomType = GetZoomType(); + if (eZoomType == SvxZoomType::PERCENT || bInZoomUpdate) + return; + + bInZoomUpdate = true; + const Fraction& rOldX = GetViewData().GetZoomX(); + const Fraction& rOldY = GetViewData().GetZoomY(); + tools::Long nOldPercent = tools::Long(rOldY * 100); + sal_uInt16 nNewZoom = CalcZoom( eZoomType, static_cast<sal_uInt16>(nOldPercent) ); + Fraction aNew( nNewZoom, 100 ); + + if ( aNew != rOldX || aNew != rOldY ) + { + SetZoom( aNew, aNew, false ); // always separately per sheet + PaintGrid(); + PaintTop(); + PaintLeft(); + aViewData.GetViewShell()->GetViewFrame().GetBindings().Invalidate( SID_ATTR_ZOOM ); + aViewData.GetViewShell()->GetViewFrame().GetBindings().Invalidate( SID_ATTR_ZOOMSLIDER ); + aViewData.GetBindings().Invalidate(SID_ZOOM_IN); + aViewData.GetBindings().Invalidate(SID_ZOOM_OUT); + } + bInZoomUpdate = false; +} + +void ScTabView::UpdateFixPos() +{ + bool bResize = false; + if ( aViewData.GetHSplitMode() == SC_SPLIT_FIX ) + if (aViewData.UpdateFixX()) + bResize = true; + if ( aViewData.GetVSplitMode() == SC_SPLIT_FIX ) + if (aViewData.UpdateFixY()) + bResize = true; + if (bResize) + RepeatResize(false); +} + +void ScTabView::RepeatResize( bool bUpdateFix ) +{ + if ( bUpdateFix ) + { + ScSplitMode eHSplit = aViewData.GetHSplitMode(); + ScSplitMode eVSplit = aViewData.GetVSplitMode(); + + // #i46796# UpdateFixX / UpdateFixY uses GetGridOffset, which requires the + // outline windows to be available. So UpdateShow has to be called before + // (also called from DoResize). + if ( eHSplit == SC_SPLIT_FIX || eVSplit == SC_SPLIT_FIX ) + UpdateShow(); + + if ( eHSplit == SC_SPLIT_FIX ) + aViewData.UpdateFixX(); + if ( eVSplit == SC_SPLIT_FIX ) + aViewData.UpdateFixY(); + } + + DoResize( aBorderPos, aFrameSize ); + + //! border must be reset ??? +} + +void ScTabView::GetBorderSize( SvBorder& rBorder, const Size& /* rSize */ ) +{ + bool bScrollBars = aViewData.IsVScrollMode(); + bool bHeaders = aViewData.IsHeaderMode(); + bool bOutlMode = aViewData.IsOutlineMode(); + bool bHOutline = bOutlMode && lcl_HasColOutline(aViewData); + bool bVOutline = bOutlMode && lcl_HasRowOutline(aViewData); + bool bLayoutRTL = aViewData.GetDocument().IsLayoutRTL( aViewData.GetTabNo() ); + + rBorder = SvBorder(); + + if (bScrollBars) // Scrollbars horizontal or vertical + { + rBorder.Right() += aVScrollBottom->GetSizePixel().Width(); + rBorder.Bottom() += aHScrollLeft->GetSizePixel().Height(); + } + + // Outline-Controls + if (bVOutline && pRowOutline[SC_SPLIT_BOTTOM]) + rBorder.Left() += pRowOutline[SC_SPLIT_BOTTOM]->GetDepthSize(); + if (bHOutline && pColOutline[SC_SPLIT_LEFT]) + rBorder.Top() += pColOutline[SC_SPLIT_LEFT]->GetDepthSize(); + + if (bHeaders) // column/row headers + { + rBorder.Left() += pRowBar[SC_SPLIT_BOTTOM]->GetSizePixel().Width(); + rBorder.Top() += pColBar[SC_SPLIT_LEFT]->GetSizePixel().Height(); + } + + if ( bLayoutRTL ) + ::std::swap( rBorder.Left(), rBorder.Right() ); +} + +IMPL_LINK_NOARG(ScTabView, TabBarResize, TabBar*, void) +{ + if (!aViewData.IsHScrollMode()) + return; + + tools::Long nSize = pTabControl->GetSplitSize(); + + if (aViewData.GetHSplitMode() != SC_SPLIT_FIX) + { + tools::Long nMax = pHSplitter->GetPosPixel().X(); + if( pTabControl->IsEffectiveRTL() ) + nMax = pFrameWin->GetSizePixel().Width() - nMax; + --nMax; + if (nSize>nMax) nSize = nMax; + } + + if ( nSize != pTabControl->GetSizePixel().Width() ) + { + pTabControl->SetSizePixel( Size( nSize, + pTabControl->GetSizePixel().Height() ) ); + RepeatResize(); + } +} + +void ScTabView::SetTabBarWidth( tools::Long nNewWidth ) +{ + Size aSize = pTabControl->GetSizePixel(); + + if ( aSize.Width() != nNewWidth ) + { + aSize.setWidth( nNewWidth ); + pTabControl->SetSizePixel( aSize ); + } +} + +void ScTabView::SetRelTabBarWidth( double fRelTabBarWidth ) +{ + if( (0.0 <= fRelTabBarWidth) && (fRelTabBarWidth <= 1.0) ) + if( tools::Long nFrameWidth = pFrameWin->GetSizePixel().Width() ) + SetTabBarWidth( static_cast< tools::Long >( fRelTabBarWidth * nFrameWidth + 0.5 ) ); +} + +void ScTabView::SetPendingRelTabBarWidth( double fRelTabBarWidth ) +{ + mfPendingTabBarWidth = fRelTabBarWidth; + SetRelTabBarWidth( fRelTabBarWidth ); +} + +tools::Long ScTabView::GetTabBarWidth() const +{ + return pTabControl->GetSizePixel().Width(); +} + +double ScTabView::GetRelTabBarWidth() +{ + return 0.5; +} + +ScGridWindow* ScTabView::GetActiveWin() +{ + ScSplitPos ePos = aViewData.GetActivePart(); + OSL_ENSURE(pGridWin[ePos],"no active window"); + return pGridWin[ePos]; +} + +void ScTabView::SetActivePointer( PointerStyle nPointer ) +{ + for (VclPtr<ScGridWindow> & pWin : pGridWin) + if (pWin) + pWin->SetPointer( nPointer ); +} + +void ScTabView::ActiveGrabFocus() +{ + ScSplitPos ePos = aViewData.GetActivePart(); + if (pGridWin[ePos]) + pGridWin[ePos]->GrabFocus(); +} + +ScSplitPos ScTabView::FindWindow( const vcl::Window* pWindow ) const +{ + ScSplitPos eVal = SC_SPLIT_BOTTOMLEFT; // Default + for (sal_uInt16 i=0; i<4; i++) + if ( pGridWin[i] == pWindow ) + eVal = static_cast<ScSplitPos>(i); + + return eVal; +} + +Point ScTabView::GetGridOffset() const +{ + Point aPos; + + // size as in DoResize + + bool bHeaders = aViewData.IsHeaderMode(); + bool bOutlMode = aViewData.IsOutlineMode(); + bool bHOutline = bOutlMode && lcl_HasColOutline(aViewData); + bool bVOutline = bOutlMode && lcl_HasRowOutline(aViewData); + + // Outline-Controls + if (bVOutline && pRowOutline[SC_SPLIT_BOTTOM]) + aPos.AdjustX(pRowOutline[SC_SPLIT_BOTTOM]->GetDepthSize() ); + if (bHOutline && pColOutline[SC_SPLIT_LEFT]) + aPos.AdjustY(pColOutline[SC_SPLIT_LEFT]->GetDepthSize() ); + + if (bHeaders) // column/row headers + { + if (pRowBar[SC_SPLIT_BOTTOM]) + aPos.AdjustX(pRowBar[SC_SPLIT_BOTTOM]->GetSizePixel().Width() ); + if (pColBar[SC_SPLIT_LEFT]) + aPos.AdjustY(pColBar[SC_SPLIT_LEFT]->GetSizePixel().Height() ); + } + + return aPos; +} + +// --- Scroll-Bars -------------------------------------------------------- + +void ScTabView::SetZoomPercentFromCommand(sal_uInt16 nZoomPercent) +{ + // scroll wheel doesn't set the AppOptions default + + bool bSyncZoom = SC_MOD()->GetAppOptions().GetSynchronizeZoom(); + SetZoomType(SvxZoomType::PERCENT, bSyncZoom); + Fraction aFract(nZoomPercent, 100); + SetZoom(aFract, aFract, bSyncZoom); + PaintGrid(); + PaintTop(); + PaintLeft(); + aViewData.GetBindings().Invalidate( SID_ATTR_ZOOM); + aViewData.GetBindings().Invalidate( SID_ATTR_ZOOMSLIDER); + aViewData.GetBindings().Invalidate( SID_ZOOM_IN); + aViewData.GetBindings().Invalidate( SID_ZOOM_OUT); +} + +bool ScTabView::ScrollCommand( const CommandEvent& rCEvt, ScSplitPos ePos ) +{ + HideNoteMarker(); + + bool bDone = false; + const CommandWheelData* pData = rCEvt.GetWheelData(); + if (pData && pData->GetMode() == CommandWheelMode::ZOOM) + { + if ( !aViewData.GetViewShell()->GetViewFrame().GetFrame().IsInPlace() ) + { + // for ole inplace editing, the scale is defined by the visarea and client size + // and can't be changed directly + + const Fraction& rOldY = aViewData.GetZoomY(); + sal_uInt16 nOld = static_cast<tools::Long>( rOldY * 100 ); + 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 ) + { + SetZoomPercentFromCommand(nNew); + } + + bDone = true; + } + } + else + { + ScHSplitPos eHPos = WhichH(ePos); + ScVSplitPos eVPos = WhichV(ePos); + ScrollAdaptor* pHScroll = ( eHPos == SC_SPLIT_LEFT ) ? aHScrollLeft.get() : aHScrollRight.get(); + ScrollAdaptor* pVScroll = ( eVPos == SC_SPLIT_TOP ) ? aVScrollTop.get() : aVScrollBottom.get(); + if ( pGridWin[ePos] ) + bDone = pGridWin[ePos]->HandleScrollCommand( rCEvt, pHScroll, pVScroll ); + } + return bDone; +} + +bool ScTabView::GestureZoomCommand(const CommandEvent& rCEvt) +{ + HideNoteMarker(); + + const CommandGestureZoomData* pData = rCEvt.GetGestureZoomData(); + if (!pData) + return false; + + if (aViewData.GetViewShell()->GetViewFrame().GetFrame().IsInPlace()) + return false; + + if (pData->meEventType == GestureEventZoomType::Begin) + { + mfLastZoomScale = pData->mfScaleDelta; + return true; + } + + if (pData->meEventType == GestureEventZoomType::Update) + { + double deltaBetweenEvents = (pData->mfScaleDelta - mfLastZoomScale) / mfLastZoomScale; + mfLastZoomScale = pData->mfScaleDelta; + + // Accumulate fractional zoom to avoid small zoom changes from being ignored + mfAccumulatedZoom += deltaBetweenEvents; + int nZoomChangePercent = mfAccumulatedZoom * 100; + mfAccumulatedZoom -= nZoomChangePercent / 100.0; + + const Fraction& rOldY = aViewData.GetZoomY(); + sal_uInt16 nOld = static_cast<tools::Long>(rOldY * 100); + sal_uInt16 nNew = nOld + nZoomChangePercent; + nNew = std::clamp<sal_uInt16>(nNew, MINZOOM, MAXZOOM); + + if (nNew != nOld) + { + SetZoomPercentFromCommand(nNew); + } + + return true; + } + return true; +} + +IMPL_LINK_NOARG(ScTabView, HScrollLeftHdl, weld::Scrollbar&, void) +{ + ScrollHdl(aHScrollLeft.get()); +} + +IMPL_LINK_NOARG(ScTabView, HScrollRightHdl, weld::Scrollbar&, void) +{ + ScrollHdl(aHScrollRight.get()); +} + +IMPL_LINK_NOARG(ScTabView, VScrollTopHdl, weld::Scrollbar&, void) +{ + ScrollHdl(aVScrollTop.get()); +} + +IMPL_LINK_NOARG(ScTabView, VScrollBottomHdl, weld::Scrollbar&, void) +{ + ScrollHdl(aVScrollBottom.get()); +} + +IMPL_LINK_NOARG(ScTabView, EndScrollHdl, const MouseEvent&, bool) +{ + if (bDragging) + { + UpdateScrollBars(); + bDragging = false; + } + return false; +} + +void ScTabView::ScrollHdl(ScrollAdaptor* pScroll) +{ + bool bHoriz = ( pScroll == aHScrollLeft.get() || pScroll == aHScrollRight.get() ); + tools::Long nViewPos; + if ( bHoriz ) + nViewPos = aViewData.GetPosX( (pScroll == aHScrollLeft.get()) ? + SC_SPLIT_LEFT : SC_SPLIT_RIGHT ); + else + nViewPos = aViewData.GetPosY( (pScroll == aVScrollTop.get()) ? + SC_SPLIT_TOP : SC_SPLIT_BOTTOM ); + + bool bLayoutRTL = aViewData.GetDocument().IsLayoutRTL( aViewData.GetTabNo() ); + + ScrollType eType = pScroll->GetScrollType(); + if ( eType == ScrollType::Drag ) + { + if (!bDragging) + { + bDragging = true; + nPrevDragPos = nViewPos; + } + + // show scroll position + // (only QuickHelp, there is no entry for it in the status bar) + + if (Help::IsQuickHelpEnabled()) + { + Size aSize = pScroll->GetSizePixel(); + + /* Convert scrollbar mouse position to screen position. If RTL + mode of scrollbar differs from RTL mode of its parent, then the + direct call to Window::OutputToNormalizedScreenPixel() will + give unusable results, because calculation of screen position + is based on parent orientation and expects equal orientation of + the child position. Need to mirror mouse position before. */ + Point aMousePos = pScroll->GetPointerPosPixel(); + if( pScroll->IsRTLEnabled() != pScroll->GetParent()->IsRTLEnabled() ) + aMousePos.setX( aSize.Width() - aMousePos.X() - 1 ); + aMousePos = pScroll->OutputToNormalizedScreenPixel( aMousePos ); + + // convert top-left position of scrollbar to screen position + Point aPos = pScroll->OutputToNormalizedScreenPixel( Point() ); + + // get scrollbar scroll position for help text (row number/column name) + tools::Long nScrollMin = 0; // simulate RangeMin + if ( aViewData.GetHSplitMode()==SC_SPLIT_FIX && pScroll == aHScrollRight.get() ) + nScrollMin = aViewData.GetFixPosX(); + if ( aViewData.GetVSplitMode()==SC_SPLIT_FIX && pScroll == aVScrollBottom.get() ) + nScrollMin = aViewData.GetFixPosY(); + tools::Long nScrollPos = GetScrollBarPos( *pScroll ) + nScrollMin; + + OUString aHelpStr; + tools::Rectangle aRect; + QuickHelpFlags nAlign; + if (bHoriz) + { + aHelpStr = ScResId(STR_COLUMN) + + " " + ScColToAlpha(static_cast<SCCOL>(nScrollPos)); + + aRect.SetLeft( aMousePos.X() ); + aRect.SetTop( aPos.Y() - 4 ); + nAlign = QuickHelpFlags::Bottom|QuickHelpFlags::Center; + } + else + { + aHelpStr = ScResId(STR_ROW) + + " " + OUString::number(nScrollPos + 1); + + // show quicktext always inside sheet area + aRect.SetLeft( bLayoutRTL ? (aPos.X() + aSize.Width() + 8) : (aPos.X() - 8) ); + aRect.SetTop( aMousePos.Y() ); + nAlign = (bLayoutRTL ? QuickHelpFlags::Left : QuickHelpFlags::Right) | QuickHelpFlags::VCenter; + } + aRect.SetRight( aRect.Left() ); + aRect.SetBottom( aRect.Top() ); + + Help::ShowQuickHelp(pScroll->GetParent(), aRect, aHelpStr, nAlign); + } + } + else + bDragging = false; + + tools::Long nDelta(0); + switch ( eType ) + { + case ScrollType::LineUp: + nDelta = -1; + break; + case ScrollType::LineDown: + nDelta = 1; + break; + case ScrollType::PageUp: + if ( pScroll == aHScrollLeft.get() ) nDelta = -static_cast<tools::Long>(aViewData.PrevCellsX( SC_SPLIT_LEFT )); + if ( pScroll == aHScrollRight.get() ) nDelta = -static_cast<tools::Long>(aViewData.PrevCellsX( SC_SPLIT_RIGHT )); + if ( pScroll == aVScrollTop.get() ) nDelta = -static_cast<tools::Long>(aViewData.PrevCellsY( SC_SPLIT_TOP )); + if ( pScroll == aVScrollBottom.get() ) nDelta = -static_cast<tools::Long>(aViewData.PrevCellsY( SC_SPLIT_BOTTOM )); + if (nDelta==0) nDelta=-1; + break; + case ScrollType::PageDown: + if ( pScroll == aHScrollLeft.get() ) nDelta = aViewData.VisibleCellsX( SC_SPLIT_LEFT ); + if ( pScroll == aHScrollRight.get() ) nDelta = aViewData.VisibleCellsX( SC_SPLIT_RIGHT ); + if ( pScroll == aVScrollTop.get() ) nDelta = aViewData.VisibleCellsY( SC_SPLIT_TOP ); + if ( pScroll == aVScrollBottom.get() ) nDelta = aViewData.VisibleCellsY( SC_SPLIT_BOTTOM ); + if (nDelta==0) nDelta=1; + break; + default: + { + // only scroll in the correct direction, do not jitter around hidden ranges + tools::Long nScrollMin = 0; // simulate RangeMin + if ( aViewData.GetHSplitMode()==SC_SPLIT_FIX && pScroll == aHScrollRight.get() ) + nScrollMin = aViewData.GetFixPosX(); + if ( aViewData.GetVSplitMode()==SC_SPLIT_FIX && pScroll == aVScrollBottom.get() ) + nScrollMin = aViewData.GetFixPosY(); + + tools::Long nScrollPos = GetScrollBarPos( *pScroll ) + nScrollMin; + nDelta = nScrollPos - nViewPos; + + // tdf#152406 Disable anti-jitter code for scroll wheel events + // After moving thousands of columns to the right via + // horizontal scroll wheel or trackpad swipe events, most + // vertical scroll wheel or trackpad swipe events will trigger + // the anti-jitter code because nScrollPos and nPrevDragPos + // will be equal and nDelta will be overridden and set to zero. + // So, only use the anti-jitter code for mouse drag events. + if ( eType == ScrollType::Drag ) + { + if ( nScrollPos > nPrevDragPos ) + { + if (nDelta<0) nDelta=0; + } + else if ( nScrollPos < nPrevDragPos ) + { + if (nDelta>0) nDelta=0; + } + else + nDelta = 0; + } + else if ( bHoriz ) + { + // tdf#135478 Reduce sensitivity of horizontal scrollwheel + // Problem: at least on macOS, swipe events are very + // precise. So, when swiping at a slight angle off of + // vertical, swipe events will include a small amount + // of horizontal movement. Since horizontal swipe units + // are measured in cell widths, these small amounts of + // horizontal movement results in shifting many columns + // to the right or left while swiping almost vertically. + // So my hacky fix is to reduce the amount of horizontal + // swipe events to roughly match the "visual distance" + // of vertical swipe events. + // The reduction factor is arbitrary but is set to + // roughly the ratio of default cell width divided by + // default cell height. This hacky fix isn't a perfect + // fix, but hopefully it reduces the amount of + // unexpected horizontal shifting while swiping + // vertically to a tolerable amount for most users. + // Note: the potential downside of doing this is that + // some users might find horizontal swiping to be + // slower than they are used to. If that becomes an + // issue for enough users, the reduction factor may + // need to be lowered to find a good balance point. + static const sal_uInt16 nHScrollReductionFactor = 8; + if ( pScroll == aHScrollLeft.get() ) + { + mnPendingaHScrollLeftDelta += nDelta; + nDelta = 0; + if ( abs(mnPendingaHScrollLeftDelta) > nHScrollReductionFactor ) + { + nDelta = mnPendingaHScrollLeftDelta / nHScrollReductionFactor; + mnPendingaHScrollLeftDelta = mnPendingaHScrollLeftDelta % nHScrollReductionFactor; + } + } + else if ( pScroll == aHScrollRight.get() ) + { + mnPendingaHScrollRightDelta += nDelta; + nDelta = 0; + if ( abs(mnPendingaHScrollRightDelta) > nHScrollReductionFactor ) + { + nDelta = mnPendingaHScrollRightDelta / nHScrollReductionFactor; + mnPendingaHScrollRightDelta = mnPendingaHScrollRightDelta % nHScrollReductionFactor; + } + } + } + + nPrevDragPos = nScrollPos; + } + break; + } + + if (nDelta) + { + bool bUpdate = ( eType != ScrollType::Drag ); // don't alter the ranges while dragging + if ( bHoriz ) + ScrollX( nDelta, (pScroll == aHScrollLeft.get()) ? SC_SPLIT_LEFT : SC_SPLIT_RIGHT, bUpdate ); + else + ScrollY( nDelta, (pScroll == aVScrollTop.get()) ? SC_SPLIT_TOP : SC_SPLIT_BOTTOM, bUpdate ); + } +} + +void ScTabView::ScrollX( tools::Long nDeltaX, ScHSplitPos eWhich, bool bUpdBars ) +{ + ScDocument& rDoc = aViewData.GetDocument(); + SCCOL nOldX = aViewData.GetPosX(eWhich); + SCCOL nNewX = nOldX + static_cast<SCCOL>(nDeltaX); + if ( nNewX < 0 ) + { + nDeltaX -= nNewX; + nNewX = 0; + } + if ( nNewX > rDoc.MaxCol() ) + { + nDeltaX -= nNewX - rDoc.MaxCol(); + nNewX = rDoc.MaxCol(); + } + + SCCOL nDir = ( nDeltaX > 0 ) ? 1 : -1; + SCTAB nTab = aViewData.GetTabNo(); + while ( rDoc.ColHidden(nNewX, nTab) && + nNewX+nDir >= 0 && nNewX+nDir <= rDoc.MaxCol() ) + nNewX = sal::static_int_cast<SCCOL>( nNewX + nDir ); + + // freeze + + if (aViewData.GetHSplitMode() == SC_SPLIT_FIX) + { + if (eWhich == SC_SPLIT_LEFT) + nNewX = nOldX; // always keep the left part + else + { + SCCOL nFixX = aViewData.GetFixPosX(); + if (nNewX < nFixX) + nNewX = nFixX; + } + } + if (nNewX == nOldX) + return; + + HideAllCursors(); + + if ( nNewX >= 0 && nNewX <= rDoc.MaxCol() && nDeltaX ) + { + SCCOL nTrackX = std::max( nOldX, nNewX ); + + // with VCL Update() affects all windows at the moment, that is why + // calling Update after scrolling of the GridWindow would possibly + // already have painted the column/row bar with updated position. - + // Therefore call Update once before on column/row bar + if (pColBar[eWhich]) + pColBar[eWhich]->PaintImmediately(); + + tools::Long nOldPos = aViewData.GetScrPos( nTrackX, 0, eWhich ).X(); + aViewData.SetPosX( eWhich, nNewX ); + tools::Long nDiff = aViewData.GetScrPos( nTrackX, 0, eWhich ).X() - nOldPos; + + if ( eWhich==SC_SPLIT_LEFT ) + { + pGridWin[SC_SPLIT_BOTTOMLEFT]->ScrollPixel( nDiff, 0 ); + if ( aViewData.GetVSplitMode() != SC_SPLIT_NONE ) + pGridWin[SC_SPLIT_TOPLEFT]->ScrollPixel( nDiff, 0 ); + } + else + { + pGridWin[SC_SPLIT_BOTTOMRIGHT]->ScrollPixel( nDiff, 0 ); + if ( aViewData.GetVSplitMode() != SC_SPLIT_NONE ) + pGridWin[SC_SPLIT_TOPRIGHT]->ScrollPixel( nDiff, 0 ); + } + if (pColBar[eWhich]) { pColBar[eWhich]->Scroll( nDiff,0 ); pColBar[eWhich]->PaintImmediately(); } + if (pColOutline[eWhich]) pColOutline[eWhich]->ScrollPixel( nDiff ); + if (bUpdBars) + UpdateScrollBars(); + } + + if (nDeltaX==1 || nDeltaX==-1) + pGridWin[aViewData.GetActivePart()]->PaintImmediately(); + + ShowAllCursors(); + + SetNewVisArea(); // MapMode must already be set + + TestHintWindow(); +} + +void ScTabView::ScrollY( tools::Long nDeltaY, ScVSplitPos eWhich, bool bUpdBars ) +{ + ScDocument& rDoc = aViewData.GetDocument(); + SCROW nOldY = aViewData.GetPosY(eWhich); + SCROW nNewY = nOldY + static_cast<SCROW>(nDeltaY); + if ( nNewY < 0 ) + { + nDeltaY -= nNewY; + nNewY = 0; + } + if ( nNewY > rDoc.MaxRow() ) + { + nDeltaY -= nNewY - rDoc.MaxRow(); + nNewY = rDoc.MaxRow(); + } + + SCROW nDir = ( nDeltaY > 0 ) ? 1 : -1; + SCTAB nTab = aViewData.GetTabNo(); + while ( rDoc.RowHidden(nNewY, nTab) && + nNewY+nDir >= 0 && nNewY+nDir <= rDoc.MaxRow() ) + nNewY += nDir; + + // freeze + + if (aViewData.GetVSplitMode() == SC_SPLIT_FIX) + { + if (eWhich == SC_SPLIT_TOP) + nNewY = nOldY; // always keep the upper part + else + { + SCROW nFixY = aViewData.GetFixPosY(); + if (nNewY < nFixY) + nNewY = nFixY; + } + } + if (nNewY == nOldY) + return; + + HideAllCursors(); + + if ( nNewY >= 0 && nNewY <= rDoc.MaxRow() && nDeltaY ) + { + SCROW nTrackY = std::max( nOldY, nNewY ); + + // adjust row headers before the actual scrolling, so it does not get painted twice + // PosY may then also not be set yet, pass on new value + SCROW nUNew = nNewY; + UpdateHeaderWidth( &eWhich, &nUNew ); // adjust row headers + + if (pRowBar[eWhich]) + pRowBar[eWhich]->PaintImmediately(); + + tools::Long nOldPos = aViewData.GetScrPos( 0, nTrackY, eWhich ).Y(); + aViewData.SetPosY( eWhich, nNewY ); + tools::Long nDiff = aViewData.GetScrPos( 0, nTrackY, eWhich ).Y() - nOldPos; + + if ( eWhich==SC_SPLIT_TOP ) + { + pGridWin[SC_SPLIT_TOPLEFT]->ScrollPixel( 0, nDiff ); + if ( aViewData.GetHSplitMode() != SC_SPLIT_NONE ) + pGridWin[SC_SPLIT_TOPRIGHT]->ScrollPixel( 0, nDiff ); + } + else + { + pGridWin[SC_SPLIT_BOTTOMLEFT]->ScrollPixel( 0, nDiff ); + if ( aViewData.GetHSplitMode() != SC_SPLIT_NONE ) + pGridWin[SC_SPLIT_BOTTOMRIGHT]->ScrollPixel( 0, nDiff ); + } + if (pRowBar[eWhich]) { pRowBar[eWhich]->Scroll( 0,nDiff ); pRowBar[eWhich]->PaintImmediately(); } + if (pRowOutline[eWhich]) pRowOutline[eWhich]->ScrollPixel( nDiff ); + if (bUpdBars) + UpdateScrollBars(); + } + + if (nDeltaY==1 || nDeltaY==-1) + pGridWin[aViewData.GetActivePart()]->PaintImmediately(); + + ShowAllCursors(); + + SetNewVisArea(); // MapMode must already be set + + TestHintWindow(); +} + +void ScTabView::ScrollLines( tools::Long nDeltaX, tools::Long nDeltaY ) +{ + ScSplitPos eWhich = aViewData.GetActivePart(); + if (nDeltaX) + ScrollX(nDeltaX,WhichH(eWhich)); + if (nDeltaY) + ScrollY(nDeltaY,WhichV(eWhich)); +} + +namespace +{ + +SCROW lcl_LastVisible( const ScViewData& rViewData ) +{ + // If many rows are hidden at end of the document, + // then there should not be a switch to wide row headers because of this + ScDocument& rDoc = rViewData.GetDocument(); + SCTAB nTab = rViewData.GetTabNo(); + + SCROW nVis = rDoc.MaxRow(); + SCROW startRow; + while ( nVis > 0 && rDoc.GetRowHeight( nVis, nTab, &startRow, nullptr ) == 0 ) + nVis = std::max<SCROW>( startRow - 1, 0 ); + return nVis; +} + +} // anonymous namespace + +void ScTabView::UpdateHeaderWidth( const ScVSplitPos* pWhich, const SCROW* pPosY ) +{ + if (!pRowBar[SC_SPLIT_BOTTOM]) + return; + + ScDocument& rDoc = aViewData.GetDocument(); + SCROW nEndPos = rDoc.MaxRow(); + if ( !aViewData.GetViewShell()->GetViewFrame().GetFrame().IsInPlace() ) + { + // for OLE Inplace always MAXROW + + if ( pWhich && *pWhich == SC_SPLIT_BOTTOM && pPosY ) + nEndPos = *pPosY; + else + nEndPos = aViewData.GetPosY( SC_SPLIT_BOTTOM ); + nEndPos += aViewData.CellsAtY( nEndPos, 1, SC_SPLIT_BOTTOM ); // VisibleCellsY + if (nEndPos > rDoc.MaxRow()) + nEndPos = lcl_LastVisible( aViewData ); + + if ( aViewData.GetVSplitMode() != SC_SPLIT_NONE ) + { + SCROW nTopEnd; + if ( pWhich && *pWhich == SC_SPLIT_TOP && pPosY ) + nTopEnd = *pPosY; + else + nTopEnd = aViewData.GetPosY( SC_SPLIT_TOP ); + nTopEnd += aViewData.CellsAtY( nTopEnd, 1, SC_SPLIT_TOP );// VisibleCellsY + if (nTopEnd > rDoc.MaxRow()) + nTopEnd = lcl_LastVisible( aViewData ); + + if ( nTopEnd > nEndPos ) + nEndPos = nTopEnd; + } + } + + tools::Long nSmall = pRowBar[SC_SPLIT_BOTTOM]->GetSmallWidth(); + tools::Long nBig = pRowBar[SC_SPLIT_BOTTOM]->GetBigWidth(); + tools::Long nDiff = nBig - nSmall; + + if (nEndPos>10000) + nEndPos = 10000; + else if (nEndPos<1) // avoid extra step at 0 (when only one row is visible) + nEndPos = 1; + tools::Long nWidth = nBig - ( 10000 - nEndPos ) * nDiff / 10000; + + if (nWidth == pRowBar[SC_SPLIT_BOTTOM]->GetWidth() || bInUpdateHeader) + return; + + bInUpdateHeader = true; + + pRowBar[SC_SPLIT_BOTTOM]->SetWidth( nWidth ); + if (pRowBar[SC_SPLIT_TOP]) + pRowBar[SC_SPLIT_TOP]->SetWidth( nWidth ); + + RepeatResize(); + + // on VCL there are endless updates (each Update is valid for all windows) + //aCornerButton->Update(); // otherwise this never gets an Update + + bInUpdateHeader = false; +} + +static void ShowHide( vcl::Window* pWin, bool bShow ) +{ + OSL_ENSURE(pWin || !bShow, "window is not present"); + if (pWin) + pWin->Show(bShow); +} + +void ScTabView::UpdateShow() +{ + bool bHScrollMode = aViewData.IsHScrollMode(); + bool bVScrollMode = aViewData.IsVScrollMode(); + bool bTabMode = aViewData.IsTabMode(); + bool bOutlMode = aViewData.IsOutlineMode(); + bool bHOutline = bOutlMode && lcl_HasColOutline(aViewData); + bool bVOutline = bOutlMode && lcl_HasRowOutline(aViewData); + bool bHeader = aViewData.IsHeaderMode(); + + bool bShowH = ( aViewData.GetHSplitMode() != SC_SPLIT_NONE ); + bool bShowV = ( aViewData.GetVSplitMode() != SC_SPLIT_NONE ); + + if ( aViewData.GetDocShell()->IsPreview() ) + bHScrollMode = bVScrollMode = bTabMode = bHeader = bHOutline = bVOutline = false; + + // create Windows + + if (bShowH && !pGridWin[SC_SPLIT_BOTTOMRIGHT]) + { + pGridWin[SC_SPLIT_BOTTOMRIGHT] = VclPtr<ScGridWindow>::Create( pFrameWin, aViewData, SC_SPLIT_BOTTOMRIGHT ); + DoAddWin( pGridWin[SC_SPLIT_BOTTOMRIGHT] ); + } + if (bShowV && !pGridWin[SC_SPLIT_TOPLEFT]) + { + pGridWin[SC_SPLIT_TOPLEFT] = VclPtr<ScGridWindow>::Create( pFrameWin, aViewData, SC_SPLIT_TOPLEFT ); + DoAddWin( pGridWin[SC_SPLIT_TOPLEFT] ); + } + if (bShowH && bShowV && !pGridWin[SC_SPLIT_TOPRIGHT]) + { + pGridWin[SC_SPLIT_TOPRIGHT] = VclPtr<ScGridWindow>::Create( pFrameWin, aViewData, SC_SPLIT_TOPRIGHT ); + DoAddWin( pGridWin[SC_SPLIT_TOPRIGHT] ); + } + + if (bHOutline && !pColOutline[SC_SPLIT_LEFT]) + pColOutline[SC_SPLIT_LEFT] = VclPtr<ScOutlineWindow>::Create( pFrameWin, SC_OUTLINE_HOR, &aViewData, SC_SPLIT_BOTTOMLEFT ); + if (bShowH && bHOutline && !pColOutline[SC_SPLIT_RIGHT]) + pColOutline[SC_SPLIT_RIGHT] = VclPtr<ScOutlineWindow>::Create( pFrameWin, SC_OUTLINE_HOR, &aViewData, SC_SPLIT_BOTTOMRIGHT ); + + if (bVOutline && !pRowOutline[SC_SPLIT_BOTTOM]) + pRowOutline[SC_SPLIT_BOTTOM] = VclPtr<ScOutlineWindow>::Create( pFrameWin, SC_OUTLINE_VER, &aViewData, SC_SPLIT_BOTTOMLEFT ); + if (bShowV && bVOutline && !pRowOutline[SC_SPLIT_TOP]) + pRowOutline[SC_SPLIT_TOP] = VclPtr<ScOutlineWindow>::Create( pFrameWin, SC_OUTLINE_VER, &aViewData, SC_SPLIT_TOPLEFT ); + + if (bShowH && bHeader && !pColBar[SC_SPLIT_RIGHT]) + pColBar[SC_SPLIT_RIGHT] = VclPtr<ScColBar>::Create( pFrameWin, SC_SPLIT_RIGHT, + &aHdrFunc, pHdrSelEng.get(), this ); + if (bShowV && bHeader && !pRowBar[SC_SPLIT_TOP]) + pRowBar[SC_SPLIT_TOP] = VclPtr<ScRowBar>::Create( pFrameWin, SC_SPLIT_TOP, + &aHdrFunc, pHdrSelEng.get(), this ); + + // show Windows + + ShowHide( aHScrollLeft.get(), bHScrollMode ); + ShowHide( aHScrollRight.get(), bShowH && bHScrollMode ); + ShowHide( aVScrollBottom.get(), bVScrollMode ); + ShowHide( aVScrollTop.get(), bShowV && bVScrollMode ); + + ShowHide( pHSplitter, bHScrollMode || bShowH ); // always generated + ShowHide( pVSplitter, bVScrollMode || bShowV ); + ShowHide( pTabControl, bTabMode ); + + // from here dynamically generated + + ShowHide( pGridWin[SC_SPLIT_BOTTOMRIGHT], bShowH ); + ShowHide( pGridWin[SC_SPLIT_TOPLEFT], bShowV ); + ShowHide( pGridWin[SC_SPLIT_TOPRIGHT], bShowH && bShowV ); + + ShowHide( pColOutline[SC_SPLIT_LEFT], bHOutline ); + ShowHide( pColOutline[SC_SPLIT_RIGHT], bShowH && bHOutline ); + + ShowHide( pRowOutline[SC_SPLIT_BOTTOM], bVOutline ); + ShowHide( pRowOutline[SC_SPLIT_TOP], bShowV && bVOutline ); + + ShowHide( pColBar[SC_SPLIT_RIGHT], bShowH && bHeader ); + ShowHide( pRowBar[SC_SPLIT_TOP], bShowV && bHeader ); + + //! register new Gridwindows +} + +bool ScTabView::UpdateVisibleRange() +{ + bool bChanged = false; + for (VclPtr<ScGridWindow> & pWin : pGridWin) + { + if (!pWin || !pWin->IsVisible()) + continue; + + if (pWin->UpdateVisibleRange()) + bChanged = true; + } + + return bChanged; +} + +// --- Splitter -------------------------------------------------------- + +IMPL_LINK( ScTabView, SplitHdl, Splitter*, pSplitter, void ) +{ + if ( pSplitter == pHSplitter ) + DoHSplit( pHSplitter->GetSplitPosPixel() ); + else + DoVSplit( pVSplitter->GetSplitPosPixel() ); + + if ( aViewData.GetHSplitMode() == SC_SPLIT_FIX || aViewData.GetVSplitMode() == SC_SPLIT_FIX ) + FreezeSplitters( true ); + + DoResize( aBorderPos, aFrameSize ); +} + +void ScTabView::DoHSplit(tools::Long nSplitPos) +{ + // nSplitPos is the real pixel position on the frame window, + // mirroring for RTL has to be done here. + + bool bLayoutRTL = aViewData.GetDocument().IsLayoutRTL( aViewData.GetTabNo() ); + if ( bLayoutRTL ) + nSplitPos = pFrameWin->GetOutputSizePixel().Width() - nSplitPos - 1; + + tools::Long nMinPos; + tools::Long nMaxPos; + + nMinPos = SPLIT_MARGIN; + if ( pRowBar[SC_SPLIT_BOTTOM] && pRowBar[SC_SPLIT_BOTTOM]->GetSizePixel().Width() >= nMinPos ) + nMinPos = pRowBar[SC_SPLIT_BOTTOM]->GetSizePixel().Width() + 1; + nMaxPos = aFrameSize.Width() - SPLIT_MARGIN; + + ScSplitMode aOldMode = aViewData.GetHSplitMode(); + ScSplitMode aNewMode = SC_SPLIT_NORMAL; + + aViewData.SetHSplitPos( nSplitPos ); + if ( nSplitPos < nMinPos || nSplitPos > nMaxPos ) + aNewMode = SC_SPLIT_NONE; + + aViewData.SetHSplitMode( aNewMode ); + + if ( aNewMode == aOldMode ) + return; + + UpdateShow(); // before ActivatePart !! + + if ( aNewMode == SC_SPLIT_NONE ) + { + if (aViewData.GetActivePart() == SC_SPLIT_TOPRIGHT) + ActivatePart( SC_SPLIT_TOPLEFT ); + if (aViewData.GetActivePart() == SC_SPLIT_BOTTOMRIGHT) + ActivatePart( SC_SPLIT_BOTTOMLEFT ); + } + else + { + SCCOL nOldDelta = aViewData.GetPosX( SC_SPLIT_LEFT ); + tools::Long nLeftWidth = nSplitPos - pRowBar[SC_SPLIT_BOTTOM]->GetSizePixel().Width(); + if ( nLeftWidth < 0 ) nLeftWidth = 0; + SCCOL nNewDelta = nOldDelta + aViewData.CellsAtX( nOldDelta, 1, SC_SPLIT_LEFT, + static_cast<sal_uInt16>(nLeftWidth) ); + ScDocument& rDoc = aViewData.GetDocument(); + if ( nNewDelta > rDoc.MaxCol() ) + nNewDelta = rDoc.MaxCol(); + aViewData.SetPosX( SC_SPLIT_RIGHT, nNewDelta ); + if ( nNewDelta > aViewData.GetCurX() ) + ActivatePart( (WhichV(aViewData.GetActivePart()) == SC_SPLIT_BOTTOM) ? + SC_SPLIT_BOTTOMLEFT : SC_SPLIT_TOPLEFT ); + else + ActivatePart( (WhichV(aViewData.GetActivePart()) == SC_SPLIT_BOTTOM) ? + SC_SPLIT_BOTTOMRIGHT : SC_SPLIT_TOPRIGHT ); + } + + // Form Layer needs to know the visible part of all windows + // that is why MapMode must already be correct here + for (VclPtr<ScGridWindow> & pWin : pGridWin) + if (pWin) + pWin->SetMapMode( pWin->GetDrawMapMode() ); + SetNewVisArea(); + + PaintGrid(); + PaintTop(); + + InvalidateSplit(); +} + +void ScTabView::DoVSplit(tools::Long nSplitPos) +{ + tools::Long nMinPos; + tools::Long nMaxPos; + SCROW nOldDelta; + + nMinPos = SPLIT_MARGIN; + if ( pColBar[SC_SPLIT_LEFT] && pColBar[SC_SPLIT_LEFT]->GetSizePixel().Height() >= nMinPos ) + nMinPos = pColBar[SC_SPLIT_LEFT]->GetSizePixel().Height() + 1; + nMaxPos = aFrameSize.Height() - SPLIT_MARGIN; + + ScSplitMode aOldMode = aViewData.GetVSplitMode(); + ScSplitMode aNewMode = SC_SPLIT_NORMAL; + + aViewData.SetVSplitPos( nSplitPos ); + if ( nSplitPos < nMinPos || nSplitPos > nMaxPos ) + aNewMode = SC_SPLIT_NONE; + + aViewData.SetVSplitMode( aNewMode ); + + if ( aNewMode == aOldMode ) + return; + + UpdateShow(); // before ActivatePart !! + + if ( aNewMode == SC_SPLIT_NONE ) + { + nOldDelta = aViewData.GetPosY( SC_SPLIT_TOP ); + aViewData.SetPosY( SC_SPLIT_BOTTOM, nOldDelta ); + + if (aViewData.GetActivePart() == SC_SPLIT_TOPLEFT) + ActivatePart( SC_SPLIT_BOTTOMLEFT ); + if (aViewData.GetActivePart() == SC_SPLIT_TOPRIGHT) + ActivatePart( SC_SPLIT_BOTTOMRIGHT ); + } + else + { + if ( aOldMode == SC_SPLIT_NONE ) + nOldDelta = aViewData.GetPosY( SC_SPLIT_BOTTOM ); + else + nOldDelta = aViewData.GetPosY( SC_SPLIT_TOP ); + + aViewData.SetPosY( SC_SPLIT_TOP, nOldDelta ); + tools::Long nTopHeight = nSplitPos - pColBar[SC_SPLIT_LEFT]->GetSizePixel().Height(); + if ( nTopHeight < 0 ) nTopHeight = 0; + SCROW nNewDelta = nOldDelta + aViewData.CellsAtY( nOldDelta, 1, SC_SPLIT_TOP, + static_cast<sal_uInt16>(nTopHeight) ); + ScDocument& rDoc = aViewData.GetDocument(); + if ( nNewDelta > rDoc.MaxRow() ) + nNewDelta = rDoc.MaxRow(); + aViewData.SetPosY( SC_SPLIT_BOTTOM, nNewDelta ); + if ( nNewDelta > aViewData.GetCurY() ) + ActivatePart( (WhichH(aViewData.GetActivePart()) == SC_SPLIT_LEFT) ? + SC_SPLIT_TOPLEFT : SC_SPLIT_TOPRIGHT ); + else + ActivatePart( (WhichH(aViewData.GetActivePart()) == SC_SPLIT_LEFT) ? + SC_SPLIT_BOTTOMLEFT : SC_SPLIT_BOTTOMRIGHT ); + } + + // Form Layer needs to know the visible part of all windows + // that is why MapMode must already be correct here + for (VclPtr<ScGridWindow> & pWin : pGridWin) + if (pWin) + pWin->SetMapMode( pWin->GetDrawMapMode() ); + SetNewVisArea(); + + PaintGrid(); + PaintLeft(); + + InvalidateSplit(); +} + +Point ScTabView::GetInsertPos() const +{ + ScDocument& rDoc = aViewData.GetDocument(); + SCCOL nCol = aViewData.GetCurX(); + SCROW nRow = aViewData.GetCurY(); + SCTAB nTab = aViewData.GetTabNo(); + tools::Long nPosX = 0; + for (SCCOL i=0; i<nCol; i++) + nPosX += rDoc.GetColWidth(i,nTab); + nPosX = o3tl::convert(nPosX, o3tl::Length::twip, o3tl::Length::mm100); + if ( rDoc.IsNegativePage( nTab ) ) + nPosX = -nPosX; + tools::Long nPosY = rDoc.GetRowHeight( 0, nRow-1, nTab); + nPosY = o3tl::convert(nPosY, o3tl::Length::twip, o3tl::Length::mm100); + return Point(nPosX,nPosY); +} + +Point ScTabView::GetChartInsertPos( const Size& rSize, const ScRange& rCellRange ) +{ + Point aInsertPos; + const tools::Long nBorder = 100; // leave 1mm for border + tools::Long nNeededWidth = rSize.Width() + 2 * nBorder; + tools::Long nNeededHeight = rSize.Height() + 2 * nBorder; + + // use the active window, or lower/right if frozen (as in CalcZoom) + ScSplitPos eUsedPart = aViewData.GetActivePart(); + if ( aViewData.GetHSplitMode() == SC_SPLIT_FIX ) + eUsedPart = (WhichV(eUsedPart)==SC_SPLIT_TOP) ? SC_SPLIT_TOPRIGHT : SC_SPLIT_BOTTOMRIGHT; + if ( aViewData.GetVSplitMode() == SC_SPLIT_FIX ) + eUsedPart = (WhichH(eUsedPart)==SC_SPLIT_LEFT) ? SC_SPLIT_BOTTOMLEFT : SC_SPLIT_BOTTOMRIGHT; + + ScGridWindow* pWin = pGridWin[eUsedPart].get(); + OSL_ENSURE( pWin, "Window not found" ); + if (pWin) + { + ActivatePart( eUsedPart ); + + // get the visible rectangle in logic units + bool bLOKActive = comphelper::LibreOfficeKit::isActive(); + MapMode aDrawMode = pWin->GetDrawMapMode(); + tools::Rectangle aVisible( + bLOKActive ? + OutputDevice::LogicToLogic( aViewData.getLOKVisibleArea(), MapMode(MapUnit::MapTwip), MapMode(MapUnit::Map100thMM) ) + : pWin->PixelToLogic( tools::Rectangle( Point(0,0), pWin->GetOutputSizePixel() ), aDrawMode ) ); + + ScDocument& rDoc = aViewData.GetDocument(); + SCTAB nTab = aViewData.GetTabNo(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + tools::Long nDocX = o3tl::convert(rDoc.GetColOffset(rDoc.MaxCol() + 1, nTab), o3tl::Length::twip, o3tl::Length::mm100) * nLayoutSign; + tools::Long nDocY = o3tl::convert(rDoc.GetRowOffset( rDoc.MaxRow() + 1, nTab ), o3tl::Length::twip, o3tl::Length::mm100); + + if ( aVisible.Left() * nLayoutSign > nDocX * nLayoutSign ) + aVisible.SetLeft( nDocX ); + if ( aVisible.Right() * nLayoutSign > nDocX * nLayoutSign ) + aVisible.SetRight( nDocX ); + if ( aVisible.Top() > nDocY ) + aVisible.SetTop( nDocY ); + if ( aVisible.Bottom() > nDocY ) + aVisible.SetBottom( nDocY ); + + // get the logic position of the selection + + tools::Rectangle aSelection = rDoc.GetMMRect( rCellRange.aStart.Col(), rCellRange.aStart.Row(), + rCellRange.aEnd.Col(), rCellRange.aEnd.Row(), nTab ); + + if (bLOKActive && bLayoutRTL) + { + // In this case we operate in negative X coordinates. The rectangle aSelection already + // has negative X coordinates. So the x coordinates in the rectangle aVisible(from getLOKVisibleArea) + // need be negated to match. + aVisible = tools::Rectangle(-aVisible.Right(), aVisible.Top(), -aVisible.Left(), aVisible.Bottom()); + } + + tools::Long nLeftSpace = aSelection.Left() - aVisible.Left(); + tools::Long nRightSpace = aVisible.Right() - aSelection.Right(); + tools::Long nTopSpace = aSelection.Top() - aVisible.Top(); + tools::Long nBottomSpace = aVisible.Bottom() - aSelection.Bottom(); + + bool bFitLeft = ( nLeftSpace >= nNeededWidth ); + bool bFitRight = ( nRightSpace >= nNeededWidth ); + + if ( bFitLeft || bFitRight ) + { + // first preference: completely left or right of the selection + + // if both fit, prefer left in RTL mode, right otherwise + bool bPutLeft = bFitLeft && ( bLayoutRTL || !bFitRight ); + + if ( bPutLeft ) + aInsertPos.setX( aSelection.Left() - nNeededWidth ); + else + aInsertPos.setX( aSelection.Right() + 1 ); + + // align with top of selection (is moved again if it doesn't fit) + aInsertPos.setY( std::max( aSelection.Top(), aVisible.Top() ) ); + } + else if ( nTopSpace >= nNeededHeight || nBottomSpace >= nNeededHeight ) + { + // second preference: completely above or below the selection + if ( nBottomSpace > nNeededHeight ) // bottom is preferred + aInsertPos.setY( aSelection.Bottom() + 1 ); + else + aInsertPos.setY( aSelection.Top() - nNeededHeight ); + + // align with (logic) left edge of selection (moved again if it doesn't fit) + if ( bLayoutRTL ) + aInsertPos.setX( std::min( aSelection.Right(), aVisible.Right() ) - nNeededWidth + 1 ); + else + aInsertPos.setX( std::max( aSelection.Left(), aVisible.Left() ) ); + } + else + { + // place to the (logic) right of the selection and move so it fits + + if ( bLayoutRTL ) + aInsertPos.setX( aSelection.Left() - nNeededWidth ); + else + aInsertPos.setX( aSelection.Right() + 1 ); + aInsertPos.setY( std::max( aSelection.Top(), aVisible.Top() ) ); + } + + // move the position if the object doesn't fit in the screen + + tools::Rectangle aCompareRect( aInsertPos, Size( nNeededWidth, nNeededHeight ) ); + if ( aCompareRect.Right() > aVisible.Right() ) + aInsertPos.AdjustX( -(aCompareRect.Right() - aVisible.Right()) ); + if ( aCompareRect.Bottom() > aVisible.Bottom() ) + aInsertPos.AdjustY( -(aCompareRect.Bottom() - aVisible.Bottom()) ); + + if ( aInsertPos.X() < aVisible.Left() ) + aInsertPos.setX( aVisible.Left() ); + if ( aInsertPos.Y() < aVisible.Top() ) + aInsertPos.setY( aVisible.Top() ); + + // nNeededWidth / nNeededHeight includes all borders - move aInsertPos to the + // object position, inside the border + + aInsertPos.AdjustX(nBorder ); + aInsertPos.AdjustY(nBorder ); + } + return aInsertPos; +} + +Point ScTabView::GetChartDialogPos( const Size& rDialogSize, const tools::Rectangle& rLogicChart ) +{ + // rDialogSize must be in pixels, rLogicChart in 1/100 mm. Return value is in pixels. + + Point aRet; + + // use the active window, or lower/right if frozen (as in CalcZoom) + ScSplitPos eUsedPart = aViewData.GetActivePart(); + if ( aViewData.GetHSplitMode() == SC_SPLIT_FIX ) + eUsedPart = (WhichV(eUsedPart)==SC_SPLIT_TOP) ? SC_SPLIT_TOPRIGHT : SC_SPLIT_BOTTOMRIGHT; + if ( aViewData.GetVSplitMode() == SC_SPLIT_FIX ) + eUsedPart = (WhichH(eUsedPart)==SC_SPLIT_LEFT) ? SC_SPLIT_BOTTOMLEFT : SC_SPLIT_BOTTOMRIGHT; + + ScGridWindow* pWin = pGridWin[eUsedPart].get(); + OSL_ENSURE( pWin, "Window not found" ); + if (pWin) + { + MapMode aDrawMode = pWin->GetDrawMapMode(); + tools::Rectangle aObjPixel = pWin->LogicToPixel( rLogicChart, aDrawMode ); + AbsoluteScreenPixelRectangle aObjAbs( pWin->OutputToAbsoluteScreenPixel( aObjPixel.TopLeft() ), + pWin->OutputToAbsoluteScreenPixel( aObjPixel.BottomRight() ) ); + + AbsoluteScreenPixelRectangle aDesktop = pWin->GetDesktopRectPixel(); + Size aSpace = pWin->LogicToPixel( Size(8, 12), MapMode(MapUnit::MapAppFont)); + + ScDocument& rDoc = aViewData.GetDocument(); + SCTAB nTab = aViewData.GetTabNo(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + + bool bCenterHor = false; + + if ( aDesktop.Bottom() - aObjAbs.Bottom() >= rDialogSize.Height() + aSpace.Height() ) + { + // first preference: below the chart + + aRet.setY( aObjAbs.Bottom() + aSpace.Height() ); + bCenterHor = true; + } + else if ( aObjAbs.Top() - aDesktop.Top() >= rDialogSize.Height() + aSpace.Height() ) + { + // second preference: above the chart + + aRet.setY( aObjAbs.Top() - rDialogSize.Height() - aSpace.Height() ); + bCenterHor = true; + } + else + { + bool bFitLeft = ( aObjAbs.Left() - aDesktop.Left() >= rDialogSize.Width() + aSpace.Width() ); + bool bFitRight = ( aDesktop.Right() - aObjAbs.Right() >= rDialogSize.Width() + aSpace.Width() ); + + if ( bFitLeft || bFitRight ) + { + // if both fit, prefer right in RTL mode, left otherwise + bool bPutRight = bFitRight && ( bLayoutRTL || !bFitLeft ); + if ( bPutRight ) + aRet.setX( aObjAbs.Right() + aSpace.Width() ); + else + aRet.setX( aObjAbs.Left() - rDialogSize.Width() - aSpace.Width() ); + + // center vertically + aRet.setY( aObjAbs.Top() + ( aObjAbs.GetHeight() - rDialogSize.Height() ) / 2 ); + } + else + { + // doesn't fit on any edge - put at the bottom of the screen + aRet.setY( aDesktop.Bottom() - rDialogSize.Height() ); + bCenterHor = true; + } + } + if ( bCenterHor ) + aRet.setX( aObjAbs.Left() + ( aObjAbs.GetWidth() - rDialogSize.Width() ) / 2 ); + + // limit to screen (centering might lead to invalid positions) + if ( aRet.X() + rDialogSize.Width() - 1 > aDesktop.Right() ) + aRet.setX( aDesktop.Right() - rDialogSize.Width() + 1 ); + if ( aRet.X() < aDesktop.Left() ) + aRet.setX( aDesktop.Left() ); + if ( aRet.Y() + rDialogSize.Height() - 1 > aDesktop.Bottom() ) + aRet.setY( aDesktop.Bottom() - rDialogSize.Height() + 1 ); + if ( aRet.Y() < aDesktop.Top() ) + aRet.setY( aDesktop.Top() ); + } + + return aRet; +} + +void ScTabView::LockModifiers( sal_uInt16 nModifiers ) +{ + pSelEngine->LockModifiers( nModifiers ); + pHdrSelEng->LockModifiers( nModifiers ); +} + +sal_uInt16 ScTabView::GetLockedModifiers() const +{ + return pSelEngine->GetLockedModifiers(); +} + +Point ScTabView::GetMousePosPixel() +{ + Point aPos; + ScGridWindow* pWin = GetActiveWin(); + + if ( pWin ) + aPos = pWin->GetMousePosPixel(); + + return aPos; +} + +void ScTabView::FreezeSplitters( bool bFreeze, SplitMethod eSplitMethod, SCCOLROW nFreezeIndex) +{ + if ((eSplitMethod == SC_SPLIT_METHOD_COL || eSplitMethod == SC_SPLIT_METHOD_ROW) && nFreezeIndex < 0) + nFreezeIndex = 0; + + ScSplitMode eOldH = aViewData.GetHSplitMode(); + ScSplitMode eOldV = aViewData.GetVSplitMode(); + + ScSplitPos ePos = SC_SPLIT_BOTTOMLEFT; + if ( eOldV != SC_SPLIT_NONE ) + ePos = SC_SPLIT_TOPLEFT; + vcl::Window* pWin = pGridWin[ePos]; + + bool bLayoutRTL = aViewData.GetDocument().IsLayoutRTL( aViewData.GetTabNo() ); + bool bUpdateFix = false; + + if ( bFreeze ) + { + Point aWinStart = pWin->GetPosPixel(); + aViewData.GetDocShell()->SetDocumentModified(); + + Point aSplit; + SCCOL nPosX = 1; + SCROW nPosY = 1; + if (eOldV != SC_SPLIT_NONE || eOldH != SC_SPLIT_NONE) + { + if ( eOldV != SC_SPLIT_NONE && (eSplitMethod == SC_SPLIT_METHOD_ROW || eSplitMethod == SC_SPLIT_METHOD_CURSOR)) + aSplit.setY( aViewData.GetVSplitPos() - aWinStart.Y() ); + + if ( eOldH != SC_SPLIT_NONE && (eSplitMethod == SC_SPLIT_METHOD_COL || eSplitMethod == SC_SPLIT_METHOD_CURSOR)) + { + tools::Long nSplitPos = aViewData.GetHSplitPos(); + if ( bLayoutRTL ) + nSplitPos = pFrameWin->GetOutputSizePixel().Width() - nSplitPos - 1; + aSplit.setX( nSplitPos - aWinStart.X() ); + } + + aViewData.GetPosFromPixel( aSplit.X(), aSplit.Y(), ePos, nPosX, nPosY ); + bool bLeft; + bool bTop; + aViewData.GetMouseQuadrant( aSplit, ePos, nPosX, nPosY, bLeft, bTop ); + if (eSplitMethod == SC_SPLIT_METHOD_COL) + nPosX = static_cast<SCCOL>(nFreezeIndex); + else if (!bLeft) + ++nPosX; + if (eSplitMethod == SC_SPLIT_METHOD_ROW) + nPosY = static_cast<SCROW>(nFreezeIndex); + else if (!bTop) + ++nPosY; + } + else + { + switch(eSplitMethod) + { + case SC_SPLIT_METHOD_ROW: + { + nPosX = 0; + nPosY = static_cast<SCROW>(nFreezeIndex); + } + break; + case SC_SPLIT_METHOD_COL: + { + nPosX = static_cast<SCCOL>(nFreezeIndex); + nPosY = 0; + } + break; + case SC_SPLIT_METHOD_CURSOR: + { + nPosX = aViewData.GetCurX(); + nPosY = aViewData.GetCurY(); + } + break; + } + } + + SCROW nTopPos = aViewData.GetPosY(SC_SPLIT_BOTTOM); + SCROW nBottomPos = nPosY; + SCCOL nLeftPos = aViewData.GetPosX(SC_SPLIT_LEFT); + SCCOL nRightPos = nPosX; + + if (eSplitMethod == SC_SPLIT_METHOD_ROW || eSplitMethod == SC_SPLIT_METHOD_CURSOR) + { + if (eOldV != SC_SPLIT_NONE) + { + nTopPos = aViewData.GetPosY(SC_SPLIT_TOP); + if (aViewData.GetPosY(SC_SPLIT_BOTTOM) > nBottomPos) + nBottomPos = aViewData.GetPosY(SC_SPLIT_BOTTOM); + } + aSplit = aViewData.GetScrPos(nPosX, nPosY, ePos, true); + if (aSplit.Y() > 0) + { + aViewData.SetVSplitMode(SC_SPLIT_FIX); + aViewData.SetVSplitPos(aSplit.Y() + aWinStart.Y()); + aViewData.SetFixPosY(nPosY); + + aViewData.SetPosY(SC_SPLIT_TOP, nTopPos); + aViewData.SetPosY(SC_SPLIT_BOTTOM, nBottomPos); + } + else if (nPosY == 1 && eSplitMethod == SC_SPLIT_METHOD_ROW) + { + // Freeze first row, but row 1 is not visible on screen now == special handling + aViewData.SetVSplitMode(SC_SPLIT_FIX); + aViewData.SetFixPosY(nPosY); + + aViewData.SetPosY(SC_SPLIT_TOP, 0); + bUpdateFix = true; + } + else + aViewData.SetVSplitMode(SC_SPLIT_NONE); + } + + if (eSplitMethod == SC_SPLIT_METHOD_COL || eSplitMethod == SC_SPLIT_METHOD_CURSOR) + { + if (eOldH != SC_SPLIT_NONE) + { + if (aViewData.GetPosX(SC_SPLIT_RIGHT) > nRightPos) + nRightPos = aViewData.GetPosX(SC_SPLIT_RIGHT); + } + aSplit = aViewData.GetScrPos( nPosX, nPosY, ePos, true ); + if (nPosX > aViewData.GetPosX(SC_SPLIT_LEFT)) // (aSplit.X() > 0) doesn't work for RTL + { + tools::Long nSplitPos = aSplit.X() + aWinStart.X(); + if ( bLayoutRTL ) + nSplitPos = pFrameWin->GetOutputSizePixel().Width() - nSplitPos - 1; + + aViewData.SetHSplitMode( SC_SPLIT_FIX ); + aViewData.SetHSplitPos( nSplitPos ); + aViewData.SetFixPosX( nPosX ); + + aViewData.SetPosX(SC_SPLIT_LEFT, nLeftPos); + aViewData.SetPosX(SC_SPLIT_RIGHT, nRightPos); + } + else if (nPosX == 1 && eSplitMethod == SC_SPLIT_METHOD_COL) + { + // Freeze first column, but col A is not visible on screen now == special handling + aViewData.SetHSplitMode(SC_SPLIT_FIX); + aViewData.SetFixPosX(nPosX); + + aViewData.SetPosX(SC_SPLIT_RIGHT, aViewData.GetPosX(SC_SPLIT_LEFT)); + aViewData.SetPosX(SC_SPLIT_LEFT, 0); + bUpdateFix = true; + } + else + aViewData.SetHSplitMode( SC_SPLIT_NONE ); + } + } + else // unfreeze + { + if ( eOldH == SC_SPLIT_FIX ) + aViewData.SetHSplitMode( SC_SPLIT_NORMAL ); + if ( eOldV == SC_SPLIT_FIX ) + aViewData.SetVSplitMode( SC_SPLIT_NORMAL ); + } + + // Form Layer needs to know the visible part of all windows + // that is why MapMode must already be correct here + for (VclPtr<ScGridWindow> & p : pGridWin) + if (p) + p->SetMapMode( p->GetDrawMapMode() ); + SetNewVisArea(); + + RepeatResize(bUpdateFix); + + UpdateShow(); + PaintLeft(); + PaintTop(); + PaintGrid(); + + // SC_FOLLOW_NONE: only update active part + AlignToCursor( aViewData.GetCurX(), aViewData.GetCurY(), SC_FOLLOW_NONE ); + UpdateAutoFillMark(); + + InvalidateSplit(); +} + +void ScTabView::RemoveSplit() +{ + if (aViewData.GetHSplitMode() == SC_SPLIT_FIX || aViewData.GetVSplitMode() == SC_SPLIT_FIX) + aViewData.GetDocShell()->SetDocumentModified(); + DoHSplit( 0 ); + DoVSplit( 0 ); + RepeatResize(); +} + +void ScTabView::SplitAtCursor() +{ + ScSplitPos ePos = SC_SPLIT_BOTTOMLEFT; + if ( aViewData.GetVSplitMode() != SC_SPLIT_NONE ) + ePos = SC_SPLIT_TOPLEFT; + vcl::Window* pWin = pGridWin[ePos]; + Point aWinStart = pWin->GetPosPixel(); + + SCCOL nPosX = aViewData.GetCurX(); + SCROW nPosY = aViewData.GetCurY(); + Point aSplit = aViewData.GetScrPos( nPosX, nPosY, ePos, true ); + if ( nPosX > 0 ) + DoHSplit( aSplit.X() + aWinStart.X() ); + else + DoHSplit( 0 ); + if ( nPosY > 0 ) + DoVSplit( aSplit.Y() + aWinStart.Y() ); + else + DoVSplit( 0 ); + RepeatResize(); +} + +void ScTabView::SplitAtPixel( const Point& rPixel ) +{ + // pixel is relative to the entire View, not to the first GridWin + + if ( rPixel.X() > 0 ) + DoHSplit( rPixel.X() ); + else + DoHSplit( 0 ); + if ( rPixel.Y() > 0 ) + DoVSplit( rPixel.Y() ); + else + DoVSplit( 0 ); + RepeatResize(); +} + +void ScTabView::InvalidateSplit() +{ + SfxBindings& rBindings = aViewData.GetBindings(); + rBindings.Invalidate( SID_WINDOW_SPLIT ); + rBindings.Invalidate( SID_WINDOW_FIX ); + rBindings.Invalidate( SID_WINDOW_FIX_COL ); + rBindings.Invalidate( SID_WINDOW_FIX_ROW ); + + pHSplitter->SetFixed( aViewData.GetHSplitMode() == SC_SPLIT_FIX ); + pVSplitter->SetFixed( aViewData.GetVSplitMode() == SC_SPLIT_FIX ); +} + +void ScTabView::SetNewVisArea() +{ + // Draw-MapMode must be set for Controls when VisAreaChanged + // (also when Edit-MapMode is set instead) + MapMode aOldMode[4]; + MapMode aDrawMode[4]; + sal_uInt16 i; + for (i=0; i<4; i++) + if (pGridWin[i]) + { + aOldMode[i] = pGridWin[i]->GetMapMode(); + aDrawMode[i] = pGridWin[i]->GetDrawMapMode(); + if (aDrawMode[i] != aOldMode[i]) + pGridWin[i]->SetMapMode(aDrawMode[i]); + } + + vcl::Window* pActive = pGridWin[aViewData.GetActivePart()]; + if (pActive) + aViewData.GetViewShell()->VisAreaChanged(); + if (pDrawView) + pDrawView->VisAreaChanged(nullptr); // no window passed on -> for all windows + + UpdateAllOverlays(); // #i79909# with drawing MapMode set + + for (i=0; i<4; i++) + if (pGridWin[i] && aDrawMode[i] != aOldMode[i]) + { + pGridWin[i]->flushOverlayManager(); // #i79909# flush overlays before switching to edit MapMode + pGridWin[i]->SetMapMode(aOldMode[i]); + } + + SfxViewFrame& rViewFrame = aViewData.GetViewShell()->GetViewFrame(); + SfxFrame& rFrame = rViewFrame.GetFrame(); + css::uno::Reference<css::frame::XController> xController = rFrame.GetController(); + if (xController.is()) + { + ScTabViewObj* pImp = dynamic_cast<ScTabViewObj*>( xController.get() ); + if (pImp) + pImp->VisAreaChanged(); + } + if (aViewData.GetViewShell()->HasAccessibilityObjects()) + aViewData.GetViewShell()->BroadcastAccessibility(SfxHint(SfxHintId::ScAccVisAreaChanged)); +} + +bool ScTabView::HasPageFieldDataAtCursor() const +{ + ScGridWindow* pWin = pGridWin[aViewData.GetActivePart()].get(); + SCCOL nCol = aViewData.GetCurX(); + SCROW nRow = aViewData.GetCurY(); + if (pWin) + return pWin->GetDPFieldOrientation( nCol, nRow ) == sheet::DataPilotFieldOrientation_PAGE; + + return false; +} + +void ScTabView::StartDataSelect() +{ + ScGridWindow* pWin = pGridWin[aViewData.GetActivePart()].get(); + SCCOL nCol = aViewData.GetCurX(); + SCROW nRow = aViewData.GetCurY(); + + if (!pWin) + return; + + switch (pWin->GetDPFieldOrientation(nCol, nRow)) + { + case sheet::DataPilotFieldOrientation_PAGE: + // #i36598# If the cursor is on a page field's data cell, + // no meaningful input is possible anyway, so this function + // can be used to select a page field entry. + pWin->LaunchPageFieldMenu( nCol, nRow ); + return; + case sheet::DataPilotFieldOrientation_COLUMN: + case sheet::DataPilotFieldOrientation_ROW: + pWin->LaunchDPFieldMenu( nCol, nRow ); + return; + default: + ; + } + + // Do autofilter if the current cell has autofilter button. Otherwise do + // a normal data select popup. + const ScMergeFlagAttr* pAttr = + aViewData.GetDocument().GetAttr( + nCol, nRow, aViewData.GetTabNo(), ATTR_MERGE_FLAG); + + if (pAttr->HasAutoFilter()) + pWin->LaunchAutoFilterMenu(nCol, nRow); + else + pWin->LaunchDataSelectMenu(nCol, nRow); +} + +void ScTabView::EnableRefInput(bool bFlag) +{ + aHScrollLeft->EnableInput(bFlag); + aHScrollRight->EnableInput(bFlag); + aVScrollBottom->EnableInput(bFlag); + aVScrollTop->EnableInput(bFlag); + + // from here on dynamically created ones + + if(pTabControl!=nullptr) pTabControl->EnableInput(bFlag); + + for (auto& p : pGridWin) + if (p) + p->EnableInput(bFlag, false); + for (auto& p : pColBar) + if (p) + p->EnableInput(bFlag, false); + for (auto& p : pRowBar) + if (p) + p->EnableInput(bFlag, false); +} + +void ScTabView::EnableAutoSpell( bool bEnable ) +{ + if (bEnable) + mpSpellCheckCxt = + std::make_shared<sc::SpellCheckContext>(&aViewData.GetDocument(), + aViewData.GetTabNo()); + else + mpSpellCheckCxt.reset(); + + for (VclPtr<ScGridWindow> & pWin : pGridWin) + { + if (!pWin) + continue; + + pWin->SetAutoSpellContext(mpSpellCheckCxt); + } +} + +void ScTabView::ResetAutoSpell() +{ + for (VclPtr<ScGridWindow> & pWin : pGridWin) + { + if (!pWin) + continue; + + pWin->ResetAutoSpell(); + } +} + +void ScTabView::ResetAutoSpellForContentChange() +{ + for (VclPtr<ScGridWindow> & pWin : pGridWin) + { + if (!pWin) + continue; + + pWin->ResetAutoSpellForContentChange(); + } +} + +void ScTabView::SetAutoSpellData( SCCOL nPosX, SCROW nPosY, const std::vector<editeng::MisspellRanges>* pRanges ) +{ + for (VclPtr<ScGridWindow> & pWin: pGridWin) + { + if (!pWin) + continue; + + pWin->SetAutoSpellData(nPosX, nPosY, pRanges); + } +} + +namespace +{ + +tools::Long lcl_GetRowHeightPx(const ScViewData &rViewData, SCROW nRow, SCTAB nTab) +{ + const sal_uInt16 nSize = rViewData.GetDocument().GetRowHeight(nRow, nTab); + return ScViewData::ToPixel(nSize, rViewData.GetPPTY()); +} + +tools::Long lcl_GetColWidthPx(const ScViewData &rViewData, SCCOL nCol, SCTAB nTab) +{ + const sal_uInt16 nSize = rViewData.GetDocument().GetColWidth(nCol, nTab); + return ScViewData::ToPixel(nSize, rViewData.GetPPTX()); +} + +void lcl_getGroupIndexes(const ScOutlineArray& rArray, SCCOLROW nStart, SCCOLROW nEnd, std::vector<size_t>& rGroupIndexes) +{ + rGroupIndexes.clear(); + const size_t nGroupDepth = rArray.GetDepth(); + rGroupIndexes.resize(nGroupDepth); + + // Get first group per each level + for (size_t nLevel = 0; nLevel < nGroupDepth; ++nLevel) + { + if (rArray.GetCount(nLevel)) + { + // look for a group inside the [nStartRow+1, nEndRow] range + size_t nIndex; + bool bFound = rArray.GetEntryIndexInRange(nLevel, nStart + 1, nEnd, nIndex); + if (bFound) + { + if (nIndex > 0) + { + // is there a previous group not inside the range + // but anyway intersecting it ? + const ScOutlineEntry* pPrevEntry = rArray.GetEntry(nLevel, nIndex - 1); + if (pPrevEntry && nStart < pPrevEntry->GetEnd()) + { + --nIndex; + } + } + } + else + { + // look for a group which contains nStartRow+1 + bFound = rArray.GetEntryIndex(nLevel, nStart + 1, nIndex); + if (!bFound) + { + // look for a group which contains nEndRow + bFound = rArray.GetEntryIndex(nLevel, nEnd, nIndex); + } + } + + if (bFound) + { + // skip groups with no visible control + bFound = false; + while (nIndex < rArray.GetCount(nLevel)) + { + const ScOutlineEntry* pEntry = rArray.GetEntry(nLevel, nIndex); + if (pEntry && pEntry->IsVisible()) + { + bFound = true; + break; + } + if (pEntry && pEntry->GetStart() > nEnd) + { + break; + } + ++nIndex; + } + } + + rGroupIndexes[nLevel] = bFound ? nIndex : -1; + } + } +} + +void lcl_createGroupsData( + SCCOLROW nHeaderIndex, SCCOLROW nEnd, tools::Long nSizePx, tools::Long nTotalPx, + const ScOutlineArray& rArray, std::vector<size_t>& rGroupIndexes, + std::vector<tools::Long>& rGroupStartPositions, OStringBuffer& rGroupsBuffer) +{ + const size_t nGroupDepth = rArray.GetDepth(); + // create string data for group controls + for (size_t nLevel = nGroupDepth - 1; nLevel != size_t(-1); --nLevel) + { + size_t nIndex = rGroupIndexes[nLevel]; + if (nIndex == size_t(-1)) + continue; + const ScOutlineEntry* pEntry = rArray.GetEntry(nLevel, nIndex); + if (pEntry) + { + if (nHeaderIndex < pEntry->GetStart()) + { + continue; + } + else if (nHeaderIndex == pEntry->GetStart()) + { + rGroupStartPositions[nLevel] = nTotalPx - nSizePx; + } + else if (nHeaderIndex > pEntry->GetStart() && (nHeaderIndex < nEnd && nHeaderIndex < pEntry->GetEnd())) + { + // for handling group started before the current view range + if (rGroupStartPositions[nLevel] < 0) + rGroupStartPositions[nLevel] *= -1; + break; + } + if (nHeaderIndex == pEntry->GetEnd() || (nHeaderIndex == nEnd && rGroupStartPositions[nLevel] != -1)) + { + // nHeaderIndex is the end col/row of a group or is the last col/row and a group started and not yet ended + + // append a new group control data + auto len = rGroupsBuffer.getLength(); + if (len && rGroupsBuffer[len-1] == '}') + { + rGroupsBuffer.append(", "); + } + + bool bGroupHidden = pEntry->IsHidden(); + + rGroupsBuffer.append( + "{ \"level\": " + OString::number(nLevel + 1) + ", " + "\"index\": " + OString::number(nIndex) + ", " + "\"startPos\": " + OString::number(rGroupStartPositions[nLevel]) + ", " + "\"endPos\": " + OString::number(nTotalPx) + ", " + "\"hidden\": " + OString::number(bGroupHidden ? 1 : 0) + " }"); + + // look for the next visible group control at level nLevel + bool bFound = false; + ++nIndex; + while (nIndex < rArray.GetCount(nLevel)) + { + pEntry = rArray.GetEntry(nLevel, nIndex); + if (pEntry && pEntry->IsVisible()) + { + bFound = true; + break; + } + if (pEntry && pEntry->GetStart() > nEnd) + { + break; + } + ++nIndex; + } + rGroupIndexes[nLevel] = bFound ? nIndex : -1; + rGroupStartPositions[nLevel] = -1; + } + } + } +} + +class ScRangeProvider +{ +public: + ScRangeProvider(const tools::Rectangle& rArea, bool bInPixels, + ScViewData& rViewData): + mrViewData(rViewData) + { + tools::Rectangle aAreaPx = bInPixels ? rArea : + tools::Rectangle(rArea.Left() * mrViewData.GetPPTX(), + rArea.Top() * mrViewData.GetPPTY(), + rArea.Right() * mrViewData.GetPPTX(), + rArea.Bottom() * mrViewData.GetPPTY()); + calculateBounds(aAreaPx); + } + + const ScRange& getCellRange() const + { + return maRange; + } + + void getColPositions(tools::Long& rStartColPos, tools::Long& rEndColPos) const + { + rStartColPos = maBoundPositions.Left(); + rEndColPos = maBoundPositions.Right(); + } + + void getRowPositions(tools::Long& rStartRowPos, tools::Long& rEndRowPos) const + { + rStartRowPos = maBoundPositions.Top(); + rEndRowPos = maBoundPositions.Bottom(); + } + +private: + void calculateBounds(const tools::Rectangle& rAreaPx) + { + tools::Long nLeftPx = 0, nRightPx = 0; + SCCOLROW nStartCol = -1, nEndCol = -1; + calculateDimensionBounds(rAreaPx.Left(), rAreaPx.Right(), true, + nStartCol, nEndCol, nLeftPx, nRightPx, + mnEnlargeX, mrViewData); + tools::Long nTopPx = 0, nBottomPx = 0; + SCCOLROW nStartRow = -1, nEndRow = -1; + calculateDimensionBounds(rAreaPx.Top(), rAreaPx.Bottom(), false, + nStartRow, nEndRow, nTopPx, nBottomPx, + mnEnlargeY, mrViewData); + + maRange.aStart.Set(nStartCol, nStartRow, mrViewData.GetTabNo()); + maRange.aEnd.Set(nEndCol, nEndRow, mrViewData.GetTabNo()); + + maBoundPositions.SetLeft(nLeftPx); + maBoundPositions.SetRight(nRightPx); + maBoundPositions.SetTop(nTopPx); + maBoundPositions.SetBottom(nBottomPx); + } + + // All positions are in pixels. + static void calculateDimensionBounds(const tools::Long nStartPos, const tools::Long nEndPos, + bool bColumns, SCCOLROW& rStartIndex, + SCCOLROW& rEndIndex, tools::Long& rBoundStart, + tools::Long& rBoundEnd, SCCOLROW nEnlarge, + ScViewData& rViewData) + { + ScPositionHelper& rPosHelper = bColumns ? rViewData.GetLOKWidthHelper() : + rViewData.GetLOKHeightHelper(); + const auto& rStartNearest = rPosHelper.getNearestByPosition(nStartPos); + const auto& rEndNearest = rPosHelper.getNearestByPosition(nEndPos); + + ScBoundsProvider aBoundsProvider(rViewData, rViewData.GetTabNo(), bColumns); + aBoundsProvider.Compute(rStartNearest, rEndNearest, nStartPos, nEndPos); + aBoundsProvider.EnlargeBy(nEnlarge); + if (bColumns) + { + SCCOL nStartCol = -1, nEndCol = -1; + aBoundsProvider.GetStartIndexAndPosition(nStartCol, rBoundStart); + aBoundsProvider.GetEndIndexAndPosition(nEndCol, rBoundEnd); + rStartIndex = nStartCol; + rEndIndex = nEndCol; + } + else + { + SCROW nStartRow = -1, nEndRow = -1; + aBoundsProvider.GetStartIndexAndPosition(nStartRow, rBoundStart); + aBoundsProvider.GetEndIndexAndPosition(nEndRow, rBoundEnd); + rStartIndex = nStartRow; + rEndIndex = nEndRow; + } + } + +private: + + ScRange maRange; + tools::Rectangle maBoundPositions; + ScViewData& mrViewData; + static const SCCOLROW mnEnlargeX = 2; + static const SCCOLROW mnEnlargeY = 2; +}; + +void lcl_ExtendTiledDimension(bool bColumn, const SCCOLROW nEnd, const SCCOLROW nExtra, + ScTabView& rTabView, ScViewData& rViewData) +{ + ScDocument& rDoc = rViewData.GetDocument(); + // If we are approaching current max tiled row/col, signal a size changed event + // and invalidate the involved area + SCCOLROW nMaxTiledIndex = bColumn ? rViewData.GetMaxTiledCol() : rViewData.GetMaxTiledRow(); + SCCOLROW nHardLimit = !bColumn ? MAXTILEDROW : rDoc.MaxCol(); + + if (nMaxTiledIndex >= nHardLimit) + return; + + if (nEnd <= nMaxTiledIndex - nExtra) // No need to extend. + return; + + ScDocShell* pDocSh = rViewData.GetDocShell(); + ScModelObj* pModelObj = pDocSh ? pDocSh->GetModel() : nullptr; + Size aOldSize(0, 0); + if (pModelObj) + aOldSize = pModelObj->getDocumentSize(); + + SCCOLROW nNewMaxTiledIndex = std::min(std::max(nEnd, nMaxTiledIndex) + nExtra, nHardLimit); + + if (bColumn) + rViewData.SetMaxTiledCol(nNewMaxTiledIndex); + else + rViewData.SetMaxTiledRow(nNewMaxTiledIndex); + + Size aNewSize(0, 0); + if (pModelObj) + aNewSize = pModelObj->getDocumentSize(); + + if (aOldSize == aNewSize) + return; + + if (!pDocSh) + return; + + // New area extended to the right/bottom of the sheet after last col/row + // excluding overlapping area with aNewArea + tools::Rectangle aNewArea = bColumn ? + tools::Rectangle(aOldSize.getWidth(), 0, aNewSize.getWidth(), aNewSize.getHeight()): + tools::Rectangle(0, aOldSize.getHeight(), aNewSize.getWidth(), aNewSize.getHeight()); + + // Only invalidate if spreadsheet has extended to the right or bottom + if ((bColumn && aNewArea.getOpenWidth()) || (!bColumn && aNewArea.getOpenHeight())) + { + rTabView.UpdateSelectionOverlay(); + SfxLokHelper::notifyInvalidation(rViewData.GetViewShell(), &aNewArea); + } + + // Provide size in the payload, so clients don't have to query for that. + std::stringstream ss; + ss << aNewSize.Width() << ", " << aNewSize.Height(); + OString sSize( ss.str() ); + ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>( + rViewData.GetViewShell()->GetCurrentDocument()); + SfxLokHelper::notifyDocumentSizeChanged(rViewData.GetViewShell(), sSize, pModel, false); +} + +} // anonymous namespace + +void ScTabView::getRowColumnHeaders(const tools::Rectangle& rRectangle, tools::JsonWriter& rJsonWriter) +{ + ScDocument& rDoc = aViewData.GetDocument(); + + if (rRectangle.IsEmpty()) + return; + + bool bRangeHeaderSupport = comphelper::LibreOfficeKit::isRangeHeaders(); + + rJsonWriter.put("commandName", ".uno:ViewRowColumnHeaders"); + + SCTAB nTab = aViewData.GetTabNo(); + SCROW nStartRow = -1; + SCROW nEndRow = -1; + tools::Long nStartHeightPx = 0; + SCCOL nStartCol = -1; + SCCOL nEndCol = -1; + tools::Long nStartWidthPx = 0; + + tools::Rectangle aOldVisArea( + mnLOKStartHeaderCol + 1, mnLOKStartHeaderRow + 1, + mnLOKEndHeaderCol, mnLOKEndHeaderRow); + + ScRangeProvider aRangeProvider(rRectangle, /* bInPixels */ false, aViewData); + const ScRange& rCellRange = aRangeProvider.getCellRange(); + + /// *** start collecting ROWS *** + + /// 1) compute start and end rows + + if (rRectangle.Top() < rRectangle.Bottom()) + { + SAL_INFO("sc.lok.header", "Row Header: compute start/end rows."); + tools::Long nEndHeightPx = 0; + nStartRow = rCellRange.aStart.Row(); + nEndRow = rCellRange.aEnd.Row(); + aRangeProvider.getRowPositions(nStartHeightPx, nEndHeightPx); + + aViewData.GetLOKHeightHelper().removeByIndex(mnLOKStartHeaderRow); + aViewData.GetLOKHeightHelper().removeByIndex(mnLOKEndHeaderRow); + aViewData.GetLOKHeightHelper().insert(nStartRow, nStartHeightPx); + aViewData.GetLOKHeightHelper().insert(nEndRow, nEndHeightPx); + + mnLOKStartHeaderRow = nStartRow; + mnLOKEndHeaderRow = nEndRow; + } + + sal_Int32 nVisibleRows = nEndRow - nStartRow; + if (nVisibleRows < 25) + nVisibleRows = 25; + + SAL_INFO("sc.lok.header", "Row Header: visible rows: " << nVisibleRows); + + + // Get row groups + // per each level store the index of the first group intersecting + // [nStartRow, nEndRow] range + + const ScOutlineTable* pTable = rDoc.GetOutlineTable(nTab); + const ScOutlineArray* pRowArray = pTable ? &(pTable->GetRowArray()) : nullptr; + size_t nRowGroupDepth = 0; + std::vector<size_t> aRowGroupIndexes; + if (bRangeHeaderSupport && pTable) + { + nRowGroupDepth = pRowArray->GetDepth(); + lcl_getGroupIndexes(*pRowArray, nStartRow, nEndRow, aRowGroupIndexes); + } + + /// 2) if we are approaching current max tiled row, signal a size changed event + /// and invalidate the involved area + lcl_ExtendTiledDimension(/* bColumn */ false, nEndRow, nVisibleRows, *this, aViewData); + + /// 3) create string data for rows + + tools::Long nTotalPixels = nStartHeightPx; + tools::Long nPrevSizePx = -1; + OStringBuffer aRowGroupsBuffer = "\"rowGroups\": [\n"; + { + auto rowsNode = rJsonWriter.startArray("rows"); + + SAL_INFO("sc.lok.header", "Row Header: [create string data for rows]: start row: " + << nStartRow << " start height: " << nTotalPixels); + + if (nStartRow != nEndRow) + { + auto node = rJsonWriter.startStruct(); + rJsonWriter.put("text", nStartRow + 1); + rJsonWriter.put("size", nTotalPixels); + rJsonWriter.put("groupLevels", static_cast<sal_Int64>(nRowGroupDepth)); + } + + std::vector<tools::Long> aRowGroupStartPositions(nRowGroupDepth, -nTotalPixels); + for (SCROW nRow = nStartRow + 1; nRow <= nEndRow; ++nRow) + { + // nSize will be 0 for hidden rows. + const tools::Long nSizePx = lcl_GetRowHeightPx(aViewData, nRow, nTab); + nTotalPixels += nSizePx; + + if (bRangeHeaderSupport && nRowGroupDepth > 0) + { + lcl_createGroupsData(nRow, nEndRow, nSizePx, nTotalPixels, + *pRowArray, aRowGroupIndexes, aRowGroupStartPositions, + aRowGroupsBuffer); + } + + if (bRangeHeaderSupport && nRow < nEndRow && nSizePx == nPrevSizePx) + continue; + nPrevSizePx = nSizePx; + + auto node = rJsonWriter.startStruct(); + rJsonWriter.put("text", pRowBar[SC_SPLIT_BOTTOM]->GetEntryText(nRow)); + rJsonWriter.put("size", nTotalPixels); + } + + aRowGroupsBuffer.append("]"); + } + if (nRowGroupDepth > 0) + { + aRowGroupsBuffer.append(",\n"); + rJsonWriter.putRaw(aRowGroupsBuffer); + } + /// end collecting ROWS + + + /// *** start collecting COLS *** + + /// 1) compute start and end columns + + if (rRectangle.Left() < rRectangle.Right()) + { + SAL_INFO("sc.lok.header", "Column Header: compute start/end columns."); + tools::Long nEndWidthPx = 0; + nStartCol = rCellRange.aStart.Col(); + nEndCol = rCellRange.aEnd.Col(); + aRangeProvider.getColPositions(nStartWidthPx, nEndWidthPx); + + aViewData.GetLOKWidthHelper().removeByIndex(mnLOKStartHeaderCol); + aViewData.GetLOKWidthHelper().removeByIndex(mnLOKEndHeaderCol); + aViewData.GetLOKWidthHelper().insert(nStartCol, nStartWidthPx); + aViewData.GetLOKWidthHelper().insert(nEndCol, nEndWidthPx); + + mnLOKStartHeaderCol = nStartCol; + mnLOKEndHeaderCol = nEndCol; + } + + sal_Int32 nVisibleCols = nEndCol - nStartCol; + if (nVisibleCols < 10) + nVisibleCols = 10; + + + // Get column groups + // per each level store the index of the first group intersecting + // [nStartCol, nEndCol] range + + const ScOutlineArray* pColArray = pTable ? &(pTable->GetColArray()) : nullptr; + size_t nColGroupDepth = 0; + std::vector<size_t> aColGroupIndexes; + if (bRangeHeaderSupport && pTable) + { + nColGroupDepth = pColArray->GetDepth(); + lcl_getGroupIndexes(*pColArray, nStartCol, nEndCol, aColGroupIndexes); + } + + /// 2) if we are approaching current max tiled column, signal a size changed event + /// and invalidate the involved area + lcl_ExtendTiledDimension(/* bColumn */ true, nEndCol, nVisibleCols, *this, aViewData); + + /// 3) create string data for columns + OStringBuffer aColGroupsBuffer = "\"columnGroups\": [\n"; + { + auto columnsNode = rJsonWriter.startArray("columns"); + + nTotalPixels = nStartWidthPx; + SAL_INFO("sc.lok.header", "Col Header: [create string data for cols]: start col: " + << nStartRow << " start width: " << nTotalPixels); + + if (nStartCol != nEndCol) + { + auto node = rJsonWriter.startStruct(); + rJsonWriter.put("text", static_cast<sal_Int64>(nStartCol + 1)); + rJsonWriter.put("size", nTotalPixels); + rJsonWriter.put("groupLevels", static_cast<sal_Int64>(nColGroupDepth)); + } + + std::vector<tools::Long> aColGroupStartPositions(nColGroupDepth, -nTotalPixels); + nPrevSizePx = -1; + for (SCCOL nCol = nStartCol + 1; nCol <= nEndCol; ++nCol) + { + // nSize will be 0 for hidden columns. + const tools::Long nSizePx = lcl_GetColWidthPx(aViewData, nCol, nTab); + nTotalPixels += nSizePx; + + if (bRangeHeaderSupport && nColGroupDepth > 0) + lcl_createGroupsData(nCol, nEndCol, nSizePx, nTotalPixels, + *pColArray, aColGroupIndexes, + aColGroupStartPositions, aColGroupsBuffer); + + if (bRangeHeaderSupport && nCol < nEndCol && nSizePx == nPrevSizePx) + continue; + nPrevSizePx = nSizePx; + + OUString aText = bRangeHeaderSupport ? + OUString::number(nCol + 1) : pColBar[SC_SPLIT_LEFT]->GetEntryText(nCol); + + auto node = rJsonWriter.startStruct(); + rJsonWriter.put("text", aText); + rJsonWriter.put("size", nTotalPixels); + } + + aColGroupsBuffer.append("]"); + } + if (nColGroupDepth > 0) + { + aColGroupsBuffer.append(",\n"); + rJsonWriter.putRaw(aColGroupsBuffer); + } + /// end collecting COLs + + vcl::Region aNewVisArea( + tools::Rectangle(mnLOKStartHeaderCol + 1, mnLOKStartHeaderRow + 1, + mnLOKEndHeaderCol, mnLOKEndHeaderRow)); + aNewVisArea.Exclude(aOldVisArea); + tools::Rectangle aChangedArea = aNewVisArea.GetBoundRect(); + if (!aChangedArea.IsEmpty()) + { + UpdateVisibleRange(); + UpdateFormulas(aChangedArea.Left(), aChangedArea.Top(), aChangedArea.Right(), aChangedArea.Bottom()); + } +} + +OString ScTabView::getSheetGeometryData(bool bColumns, bool bRows, bool bSizes, bool bHidden, + bool bFiltered, bool bGroups) +{ + ScDocument& rDoc = aViewData.GetDocument(); + + boost::property_tree::ptree aTree; + aTree.put("commandName", ".uno:SheetGeometryData"); + aTree.put("maxtiledcolumn", rDoc.MaxCol()); + aTree.put("maxtiledrow", MAXTILEDROW); + + auto getJSONString = [](const boost::property_tree::ptree& rTree) { + std::stringstream aStream; + boost::property_tree::write_json(aStream, rTree); + return aStream.str(); + }; + + if ((!bSizes && !bHidden && !bFiltered && !bGroups) || + (!bColumns && !bRows)) + { + return OString(getJSONString(aTree)); + } + + struct GeomEntry + { + SheetGeomType eType; + const char* pKey; + bool bEnabled; + }; + + const GeomEntry aGeomEntries[] = { + { SheetGeomType::SIZES, "sizes", bSizes }, + { SheetGeomType::HIDDEN, "hidden", bHidden }, + { SheetGeomType::FILTERED, "filtered", bFiltered }, + { SheetGeomType::GROUPS, "groups", bGroups } + }; + + struct DimensionEntry + { + const char* pKey; + bool bDimIsCol; + bool bEnabled; + }; + + const DimensionEntry aDimEntries[] = { + { "columns", true, bColumns }, + { "rows", false, bRows } + }; + + SCTAB nTab = aViewData.GetTabNo(); + + for (const auto& rDimEntry : aDimEntries) + { + if (!rDimEntry.bEnabled) + continue; + + bool bDimIsCol = rDimEntry.bDimIsCol; + + boost::property_tree::ptree aDimTree; + for (const auto& rGeomEntry : aGeomEntries) + { + if (!rGeomEntry.bEnabled) + continue; + + OString aGeomDataEncoding = rDoc.dumpSheetGeomData(nTab, bDimIsCol, rGeomEntry.eType); + // TODO: Investigate if we can avoid the copy of the 'value' string in put(). + aDimTree.put(rGeomEntry.pKey, aGeomDataEncoding.getStr()); + } + + aTree.add_child(rDimEntry.pKey, aDimTree); + } + + return OString(getJSONString(aTree)); +} + +void ScTabView::extendTiledAreaIfNeeded() +{ + SAL_INFO("sc.lok.header", + "extendTiledAreaIfNeeded: START: ClientView: ColRange[" + << mnLOKStartHeaderCol << "," << mnLOKEndHeaderCol + << "] RowRange[" << mnLOKStartHeaderRow << "," << mnLOKEndHeaderRow + << "] MaxTiledCol = " << aViewData.GetMaxTiledCol() + << " MaxTiledRow = " << aViewData.GetMaxTiledRow()); + + const tools::Rectangle rVisArea = aViewData.getLOKVisibleArea(); + if (rVisArea.Top() >= rVisArea.Bottom() || + rVisArea.Left() >= rVisArea.Right()) + return; + + // Needed for conditional updating of visible-range/formula. + tools::Rectangle aOldVisCellRange(mnLOKStartHeaderCol + 1, mnLOKStartHeaderRow + 1, + mnLOKEndHeaderCol, mnLOKEndHeaderRow); + + ScRangeProvider aRangeProvider(rVisArea, /* bInPixels */ false, aViewData); + // Index bounds. + const ScRange& rCellRange = aRangeProvider.getCellRange(); + const SCCOL nStartCol = rCellRange.aStart.Col(); + const SCCOL nEndCol = rCellRange.aEnd.Col(); + const SCROW nStartRow = rCellRange.aStart.Row(); + const SCROW nEndRow = rCellRange.aEnd.Row(); + + // Column/Row positions. + tools::Long nStartColPos, nEndColPos, nStartRowPos, nEndRowPos; + aRangeProvider.getColPositions(nStartColPos, nEndColPos); + aRangeProvider.getRowPositions(nStartRowPos, nEndRowPos); + + ScPositionHelper& rWidthHelper = aViewData.GetLOKWidthHelper(); + ScPositionHelper& rHeightHelper = aViewData.GetLOKHeightHelper(); + + // Update mnLOKStartHeaderCol and mnLOKEndHeaderCol members. + // These are consulted in some ScGridWindow methods. + if (mnLOKStartHeaderCol != nStartCol) + { + rWidthHelper.removeByIndex(mnLOKStartHeaderCol); + rWidthHelper.insert(nStartCol, nStartColPos); + mnLOKStartHeaderCol = nStartCol; + } + + if (mnLOKEndHeaderCol != nEndCol) + { + rWidthHelper.removeByIndex(mnLOKEndHeaderCol); + rWidthHelper.insert(nEndCol, nEndColPos); + mnLOKEndHeaderCol = nEndCol; + } + + // Update mnLOKStartHeaderRow and mnLOKEndHeaderRow members. + // These are consulted in some ScGridWindow methods. + if (mnLOKStartHeaderRow != nStartRow) + { + rHeightHelper.removeByIndex(mnLOKStartHeaderRow); + rHeightHelper.insert(nStartRow, nStartRowPos); + mnLOKStartHeaderRow = nStartRow; + } + + if (mnLOKEndHeaderRow != nEndRow) + { + rHeightHelper.removeByIndex(mnLOKEndHeaderRow); + rHeightHelper.insert(nEndRow, nEndRowPos); + mnLOKEndHeaderRow = nEndRow; + } + + constexpr SCCOL nMinExtraCols = 10; + SCCOL nExtraCols = std::max<SCCOL>(nMinExtraCols, nEndCol - nStartCol); + // If we are approaching current max tiled column, signal a size changed event + // and invalidate the involved area. + lcl_ExtendTiledDimension(/* bColumn */ true, nEndCol, nExtraCols, *this, aViewData); + + constexpr SCROW nMinExtraRows = 25; + SCROW nExtraRows = std::max(nMinExtraRows, nEndRow - nStartRow); + // If we are approaching current max tiled row, signal a size changed event + // and invalidate the involved area. + lcl_ExtendTiledDimension(/* bColumn */ false, nEndRow, nExtraRows, *this, aViewData); + + vcl::Region aNewVisCellRange( + tools::Rectangle(mnLOKStartHeaderCol + 1, mnLOKStartHeaderRow + 1, + mnLOKEndHeaderCol, mnLOKEndHeaderRow)); + aNewVisCellRange.Exclude(aOldVisCellRange); + tools::Rectangle aChangedCellRange = aNewVisCellRange.GetBoundRect(); + if (!aChangedCellRange.IsEmpty()) + { + UpdateVisibleRange(); + UpdateFormulas(aChangedCellRange.Left(), aChangedCellRange.Top(), + aChangedCellRange.Right(), aChangedCellRange.Bottom()); + } + + SAL_INFO("sc.lok.header", + "extendTiledAreaIfNeeded: END: ClientView: ColRange[" + << mnLOKStartHeaderCol << "," << mnLOKEndHeaderCol + << "] RowRange[" << mnLOKStartHeaderRow << "," << mnLOKEndHeaderRow + << "] MaxTiledCol = " << aViewData.GetMaxTiledCol() + << " MaxTiledRow = " << aViewData.GetMaxTiledRow()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabview2.cxx b/sc/source/ui/view/tabview2.cxx new file mode 100644 index 0000000000..d5be3d5b59 --- /dev/null +++ b/sc/source/ui/view/tabview2.cxx @@ -0,0 +1,1709 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <scitems.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <sfx2/bindings.hxx> +#include <osl/diagnose.h> + +#include <attrib.hxx> +#include <pagedata.hxx> +#include <tabview.hxx> +#include <tabvwsh.hxx> +#include <printfun.hxx> +#include <stlpool.hxx> +#include <docsh.hxx> +#include <gridwin.hxx> +#include <sc.hrc> +#include <viewutil.hxx> +#include <colrowba.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <scmod.hxx> +#include <table.hxx> +#include <tabprotection.hxx> +#include <markdata.hxx> +#include <inputopt.hxx> +#include <comphelper/lok.hxx> + +namespace { + +bool isCellQualified(const ScDocument* pDoc, SCCOL nCol, SCROW nRow, SCTAB nTab, bool bSelectLocked, bool bSelectUnlocked) +{ + bool bCellProtected = pDoc->HasAttrib( + nCol, nRow, nTab, nCol, nRow, nTab, HasAttrFlags::Protected); + + if (bCellProtected && !bSelectLocked) + return false; + + if (!bCellProtected && !bSelectUnlocked) + return false; + + return true; +} + +bool areCellsQualified(const ScDocument* pDoc, SCCOL nColStart, SCROW nRowStart, SCCOL nColEnd, + SCROW nRowEnd, SCTAB nTab, bool bSelectLocked, bool bSelectUnlocked) +{ + PutInOrder(nColStart, nColEnd); + PutInOrder(nRowStart, nRowEnd); + for (SCCOL col = nColStart; col <= nColEnd; ++col) + for (SCROW row = nRowStart; row <= nRowEnd; ++row) + if (!isCellQualified(pDoc, col, row, nTab, bSelectLocked, bSelectUnlocked)) + return false; + + return true; +} + +void moveCursorByProtRule( + SCCOL& rCol, SCROW& rRow, SCCOL nMovX, SCROW nMovY, SCTAB nTab, const ScDocument* pDoc) +{ + bool bSelectLocked = true; + bool bSelectUnlocked = true; + const ScTableProtection* pTabProtection = pDoc->GetTabProtection(nTab); + if (pTabProtection && pTabProtection->isProtected()) + { + bSelectLocked = pTabProtection->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS); + bSelectUnlocked = pTabProtection->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS); + } + + if (nMovX > 0) + { + for (SCCOL i = 0; i < nMovX && rCol < pDoc->MaxCol(); ++i) + { + SCCOL nNewUnhiddenCol = rCol + 1; + SCCOL nEndCol = 0; + while(pDoc->ColHidden(nNewUnhiddenCol, nTab, nullptr, &nEndCol)) + { + if(nNewUnhiddenCol >= pDoc->MaxCol()) + return; + + i += nEndCol - nNewUnhiddenCol + 1; + nNewUnhiddenCol = nEndCol +1; + } + + if (!isCellQualified(pDoc, nNewUnhiddenCol, rRow, nTab, bSelectLocked, bSelectUnlocked)) + break; + rCol = nNewUnhiddenCol; + } + } + else if (nMovX < 0) + { + for (SCCOL i = 0; i > nMovX && rCol > 0; --i) + { + SCCOL nNewUnhiddenCol = rCol - 1; + SCCOL nStartCol = 0; + while(pDoc->ColHidden(nNewUnhiddenCol, nTab, &nStartCol)) + { + if(nNewUnhiddenCol <= 0) + return; + + i -= nNewUnhiddenCol - nStartCol + 1; + nNewUnhiddenCol = nStartCol - 1; + } + + if (!isCellQualified(pDoc, nNewUnhiddenCol, rRow, nTab, bSelectLocked, bSelectUnlocked)) + break; + rCol = nNewUnhiddenCol; + } + } + + if (nMovY > 0) + { + for (SCROW i = 0; i < nMovY && rRow < pDoc->MaxRow(); ++i) + { + SCROW nNewUnhiddenRow = rRow + 1; + SCROW nEndRow = 0; + while(pDoc->RowHidden(nNewUnhiddenRow, nTab, nullptr, &nEndRow)) + { + if(nNewUnhiddenRow >= pDoc->MaxRow()) + return; + + i += nEndRow - nNewUnhiddenRow + 1; + nNewUnhiddenRow = nEndRow + 1; + } + + if (!isCellQualified(pDoc, rCol, nNewUnhiddenRow, nTab, bSelectLocked, bSelectUnlocked)) + break; + rRow = nNewUnhiddenRow; + } + } + else if (nMovY < 0) + { + for (SCROW i = 0; i > nMovY && rRow > 0; --i) + { + SCROW nNewUnhiddenRow = rRow - 1; + SCROW nStartRow = 0; + while(pDoc->RowHidden(nNewUnhiddenRow, nTab, &nStartRow)) + { + if(nNewUnhiddenRow <= 0) + return; + + i -= nNewUnhiddenRow - nStartRow + 1; + nNewUnhiddenRow = nStartRow - 1; + } + + if (!isCellQualified(pDoc, rCol, nNewUnhiddenRow, nTab, bSelectLocked, bSelectUnlocked)) + break; + rRow = nNewUnhiddenRow; + } + } +} + +bool checkBoundary(const ScDocument* pDoc, SCCOL& rCol, SCROW& rRow) +{ + bool bGood = true; + if (rCol < 0) + { + rCol = 0; + bGood = false; + } + else if (rCol > pDoc->MaxCol()) + { + rCol = pDoc->MaxCol(); + bGood = false; + } + + if (rRow < 0) + { + rRow = 0; + bGood = false; + } + else if (rRow > pDoc->MaxRow()) + { + rRow = pDoc->MaxRow(); + bGood = false; + } + return bGood; +} + +void moveRefByCell(SCCOL& rNewX, SCROW& rNewY, + SCCOL nMovX, SCROW nMovY, SCTAB nRefTab, + const ScDocument& rDoc) +{ + SCCOL nOldX = rNewX; + SCROW nOldY = rNewY; + bool bSelectLocked = true; + bool bSelectUnlocked = true; + const ScTableProtection* pTabProtection = rDoc.GetTabProtection(nRefTab); + if (pTabProtection && pTabProtection->isProtected()) + { + bSelectLocked = pTabProtection->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS); + bSelectUnlocked = pTabProtection->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS); + } + + moveCursorByProtRule(rNewX, rNewY, nMovX, nMovY, nRefTab, &rDoc); + checkBoundary(&rDoc, rNewX, rNewY); + + if (nMovX) + { + SCCOL nTempX = rNewX; + while (rDoc.IsHorOverlapped(nTempX, rNewY, nRefTab)) + { + nTempX = (nMovX > 0) ? nTempX + 1 : nTempX - 1; + if (!checkBoundary(&rDoc, nTempX, rNewY)) + break; + } + if (isCellQualified(&rDoc, nTempX, rNewY, nRefTab, bSelectLocked, bSelectUnlocked)) + rNewX = nTempX; + + if (nMovX < 0 && rNewX > 0) + { + const ScMergeAttr* pMergeAttr = rDoc.GetAttr(rNewX, rNewY, nRefTab, ATTR_MERGE); + if (pMergeAttr && pMergeAttr->IsMerged() && + nOldX >= rNewX && + nOldX <= rNewX + pMergeAttr->GetRowMerge() - 1) + rNewX = rNewX - 1; + } + } + + if (nMovY) + { + SCROW nTempY = rNewY; + while (rDoc.IsVerOverlapped(rNewX, nTempY, nRefTab)) + { + nTempY = (nMovY > 0) ? nTempY + 1 : nTempY - 1; + if (!checkBoundary(&rDoc, rNewX, nTempY)) + break; + } + if (isCellQualified(&rDoc, rNewX, nTempY, nRefTab, bSelectLocked, bSelectUnlocked)) + rNewY = nTempY; + + if (nMovY < 0 && rNewY > 0) + { + const ScMergeAttr* pMergeAttr = rDoc.GetAttr(rNewX, rNewY, nRefTab, ATTR_MERGE); + if (pMergeAttr && pMergeAttr->IsMerged() && + nOldY >= rNewY && + nOldY <= rNewY + pMergeAttr->GetRowMerge() - 1) + rNewY = rNewY - 1; + } + } + + rDoc.SkipOverlapped(rNewX, rNewY, nRefTab); +} + +void moveCursorByMergedCell(SCCOL& rCol, SCROW& rRow, SCCOL nMovX, SCROW nMovY, SCCOL nStartX, + SCROW nStartY, SCTAB nTab, const ScDocument* pDoc) +{ + const ScTableProtection* pTabProtection = pDoc->GetTabProtection(nTab); + bool bSelectLocked = true; + bool bSelectUnlocked = true; + if (pTabProtection && pTabProtection->isProtected()) + { + bSelectLocked = pTabProtection->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS); + bSelectUnlocked = pTabProtection->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS); + } + + if (nMovX > 0) + { + SCROW rowStart = std::min(rRow, nStartY); + SCROW rowEnd = std::max(rRow, nStartY); + + for (SCROW i = rowStart; i <= rowEnd && rCol < nStartX;) + { + SCCOL tmpCol = rCol; + while (tmpCol < pDoc->MaxCol() && pDoc->IsHorOverlapped(tmpCol, i, nTab)) + ++tmpCol; + if (tmpCol != rCol) + { + i = rowStart; + if (tmpCol > nStartX) + --tmpCol; + if (!areCellsQualified(pDoc, rCol + 1, rowStart, tmpCol, rowEnd, nTab, + bSelectLocked, bSelectUnlocked)) + break; + rCol = tmpCol; + } + else + ++i; + } + } + else if (nMovX < 0) + { + SCROW rowStart = std::min(rRow, nStartY); + SCROW rowEnd = std::max(rRow, nStartY); + + for (SCROW i = rowStart; i <= rowEnd && rCol > nStartX;) + { + SCCOL tmpCol = rCol; + while (tmpCol >= 0 && pDoc->IsHorOverlapped(tmpCol + 1, i, nTab)) + --tmpCol; + if (tmpCol != rCol) + { + i = rowStart; + if (tmpCol < nStartX) + ++tmpCol; + if (!areCellsQualified(pDoc, rCol - 1, rowStart, tmpCol, rowEnd, nTab, + bSelectLocked, bSelectUnlocked)) + break; + rCol = tmpCol; + } + else + ++i; + } + } + + if (nMovY > 0) + { + SCCOL colStart = std::min(rCol, nStartX); + SCCOL colEnd = std::max(rCol, nStartX); + + for (SCCOL i = colStart; i <= colEnd && rRow < nStartY;) + { + SCROW tmpRow = rRow; + while (tmpRow < pDoc->MaxRow() && pDoc->IsVerOverlapped(i, tmpRow, nTab)) + ++tmpRow; + if (tmpRow != rRow) + { + i = colStart; + if (tmpRow > nStartY) + --tmpRow; + if (!areCellsQualified(pDoc, colStart, rRow + 1, colEnd, tmpRow, nTab, + bSelectLocked, bSelectUnlocked)) + break; + rRow = tmpRow; + } + else + ++i; + } + } + else if (nMovY < 0) + { + SCCOL colStart = std::min(rCol, nStartX); + SCCOL colEnd = std::max(rCol, nStartX); + + for (SCCOL i = colStart; i <= colEnd && rRow > nStartY;) + { + SCROW tmpRow = rRow; + while (tmpRow >= 0 && pDoc->IsVerOverlapped(i, tmpRow + 1, nTab)) + --tmpRow; + if (tmpRow != rRow) + { + i = colStart; + if (tmpRow < nStartY) + ++tmpRow; + if (!areCellsQualified(pDoc, colStart, rRow - 1, colEnd, tmpRow, nTab, + bSelectLocked, bSelectUnlocked)) + break; + rRow = tmpRow; + } + else + ++i; + } + } +} + +void moveCursorToProperSide(SCCOL& rCol, SCROW& rRow, SCCOL nMovX, SCROW nMovY, SCCOL nStartX, + SCROW nStartY, SCTAB nTab, const ScDocument* pDoc) +{ + SCCOL tmpCol = rCol; + SCROW tmpRow = rRow; + + if (nMovX > 0 && nStartX < pDoc->MaxCol() && rCol < nStartX) + { + SCROW rowStart = std::min(rRow, nStartY); + SCROW rowEnd = std::max(rRow, nStartY); + for (SCROW i = rowStart; i <= rowEnd && tmpCol < nStartX;) + { + if (pDoc->IsHorOverlapped(tmpCol + 1, i, nTab)) + { + do + { + ++tmpCol; + } while (pDoc->IsHorOverlapped(tmpCol + 1, i, nTab)); + i = rowStart; + } + else + ++i; + } + if (tmpCol < nStartX) + tmpCol = rCol; + } + else if (nMovX < 0 && nStartX > 0 && rCol > nStartX) + { + SCROW rowStart = std::min(rRow, nStartY); + SCROW rowEnd = std::max(rRow, nStartY); + for (SCROW i = rowStart; i <= rowEnd && tmpCol > nStartX;) + { + if (pDoc->IsHorOverlapped(tmpCol, i, nTab)) + { + do + { + --tmpCol; + } while (pDoc->IsHorOverlapped(tmpCol, i, nTab)); + i = rowStart; + } + else + ++i; + } + if (tmpCol > nStartX) + tmpCol = rCol; + } + + if (nMovY > 0 && nStartY < pDoc->MaxRow() && rRow < nStartY) + { + SCCOL colStart = std::min(rCol, nStartX); + SCCOL colEnd = std::max(rCol, nStartX); + for (SCCOL i = colStart; i <= colEnd && tmpRow < nStartY;) + { + if (pDoc->IsVerOverlapped(i, tmpRow + 1, nTab)) + { + do + { + ++tmpRow; + } while (pDoc->IsVerOverlapped(i, tmpRow + 1, nTab)); + i = colStart; + } + else + ++i; + } + if (tmpRow < nStartY) + tmpRow = rRow; + } + else if (nMovY < 0 && nStartY > 0 && rRow > nStartY) + { + SCCOL colStart = std::min(rCol, nStartX); + SCCOL colEnd = std::max(rCol, nStartX); + for (SCCOL i = colStart; i <= colEnd && tmpRow > nStartY;) + { + if (pDoc->IsVerOverlapped(i, tmpRow, nTab)) + { + do + { + --tmpRow; + } while (pDoc->IsVerOverlapped(i, tmpRow, nTab)); + i = colStart; + } + else + ++i; + } + if (tmpRow > nStartY) + tmpRow = rRow; + } + + if (tmpCol != rCol) + rCol = tmpCol; + if (tmpRow != rRow) + rRow = tmpRow; +} +} + +void ScTabView::PaintMarks(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow ) +{ + auto& rDoc = aViewData.GetDocument(); + if (!rDoc.ValidCol(nStartCol)) nStartCol = rDoc.MaxCol(); + if (!rDoc.ValidRow(nStartRow)) nStartRow = rDoc.MaxRow(); + if (!rDoc.ValidCol(nEndCol)) nEndCol = rDoc.MaxCol(); + if (!rDoc.ValidRow(nEndRow)) nEndRow = rDoc.MaxRow(); + + bool bLeft = (nStartCol==0 && nEndCol==rDoc.MaxCol()); + bool bTop = (nStartRow==0 && nEndRow==rDoc.MaxRow()); + + if (bLeft) + PaintLeftArea( nStartRow, nEndRow ); + if (bTop) + PaintTopArea( nStartCol, nEndCol ); + + aViewData.GetDocument().ExtendMerge( nStartCol, nStartRow, nEndCol, nEndRow, + aViewData.GetTabNo() ); + PaintArea( nStartCol, nStartRow, nEndCol, nEndRow, ScUpdateMode::Marks ); +} + +bool ScTabView::IsMarking( SCCOL nCol, SCROW nRow, SCTAB nTab ) const +{ + return IsBlockMode() + && nBlockStartX == nCol + && nBlockStartY == nRow + && nBlockStartZ == nTab; +} + +void ScTabView::InitOwnBlockMode( const ScRange& rMarkRange ) +{ + if (IsBlockMode()) + return; + + // when there is no (old) selection anymore, delete anchor in SelectionEngine: + ScMarkData& rMark = aViewData.GetMarkData(); + if (!rMark.IsMarked() && !rMark.IsMultiMarked()) + GetSelEngine()->CursorPosChanging( false, false ); + + meBlockMode = Own; + nBlockStartX = rMarkRange.aStart.Col(); + nBlockStartY = rMarkRange.aStart.Row(); + nBlockStartZ = rMarkRange.aStart.Tab(); + nBlockEndX = rMarkRange.aEnd.Col(); + nBlockEndY = rMarkRange.aEnd.Row(); + nBlockEndZ = rMarkRange.aEnd.Tab(); + + SelectionChanged(); // status is checked with mark set +} + +void ScTabView::InitBlockModeHighlight( SCCOL nCurX, SCROW nCurY, SCTAB nCurZ, + bool bCols, bool bRows ) +{ + if (meHighlightBlockMode != None) + return; + + auto& rDoc = aViewData.GetDocument(); + if (!rDoc.ValidCol(nCurX)) nCurX = rDoc.MaxCol(); + if (!rDoc.ValidRow(nCurY)) nCurY = rDoc.MaxRow(); + + ScMarkData& rMark = aViewData.GetHighlightData(); + meHighlightBlockMode = Normal; + + SCROW nStartY = nCurY; + SCCOL nStartX = nCurX; + SCROW nEndY = nCurY; + SCCOL nEndX = nCurX; + + if (bCols) + { + nStartY = 0; + nEndY = rDoc.MaxRow(); + } + + if (bRows) + { + nStartX = 0; + nEndX = rDoc.MaxCol(); + } + + rMark.SetMarkArea( ScRange( nStartX, nStartY, nCurZ, nEndX, nEndY, nCurZ ) ); + UpdateHighlightOverlay(); +} + +void ScTabView::InitBlockMode( SCCOL nCurX, SCROW nCurY, SCTAB nCurZ, + bool bTestNeg, bool bCols, bool bRows, bool bForceNeg ) +{ + if (IsBlockMode()) + return; + + auto& rDoc = aViewData.GetDocument(); + if (!rDoc.ValidCol(nCurX)) nCurX = rDoc.MaxCol(); + if (!rDoc.ValidRow(nCurY)) nCurY = rDoc.MaxRow(); + + ScMarkData& rMark = aViewData.GetMarkData(); + SCTAB nTab = aViewData.GetTabNo(); + + // unmark part? + if (bForceNeg) + bBlockNeg = true; + else if (bTestNeg) + { + if ( bCols ) + bBlockNeg = rMark.IsColumnMarked( nCurX ); + else if ( bRows ) + bBlockNeg = rMark.IsRowMarked( nCurY ); + else + bBlockNeg = rMark.IsCellMarked( nCurX, nCurY ); + } + else + bBlockNeg = false; + rMark.SetMarkNegative(bBlockNeg); + + meBlockMode = Normal; + bBlockCols = bCols; + bBlockRows = bRows; + nBlockStartX = nBlockStartXOrig = nCurX; + nBlockStartY = nBlockStartYOrig = nCurY; + nBlockStartZ = nCurZ; + nBlockEndX = nOldCurX = nBlockStartX; + nBlockEndY = nOldCurY = nBlockStartY; + nBlockEndZ = nBlockStartZ; + + if (bBlockCols) + { + nBlockStartY = nBlockStartYOrig = 0; + nBlockEndY = rDoc.MaxRow(); + } + + if (bBlockRows) + { + nBlockStartX = nBlockStartXOrig = 0; + nBlockEndX = rDoc.MaxCol(); + } + + rMark.SetMarkArea( ScRange( nBlockStartX,nBlockStartY, nTab, nBlockEndX,nBlockEndY, nTab ) ); + + UpdateSelectionOverlay(); +} + +void ScTabView::DoneBlockModeHighlight( bool bContinue ) +{ + if (meHighlightBlockMode == None) + return; + + ScMarkData& rMark = aViewData.GetHighlightData(); + bool bFlag = rMark.GetMarkingFlag(); + rMark.SetMarking(false); + + if (bContinue) + rMark.MarkToMulti(); + else + { + SCTAB nTab = aViewData.GetTabNo(); + ScDocument& rDoc = aViewData.GetDocument(); + if ( rDoc.HasTable(nTab) ) + rMark.ResetMark(); + } + meHighlightBlockMode = None; + + rMark.SetMarking(bFlag); + if (bContinue) + rMark.SetMarking(false); +} + +void ScTabView::DoneBlockMode( bool bContinue ) +{ + // When switching between sheet and header SelectionEngine DeselectAll may be called, + // because the other engine does not have any anchor. + // bMoveIsShift prevents the selection to be canceled. + + if (!IsBlockMode() || bMoveIsShift) + return; + + ScMarkData& rMark = aViewData.GetMarkData(); + bool bFlag = rMark.GetMarkingFlag(); + rMark.SetMarking(false); + + if (bBlockNeg && !bContinue) + rMark.MarkToMulti(); + + if (bContinue) + rMark.MarkToMulti(); + else + { + // the sheet may be invalid at this point because DoneBlockMode from SetTabNo is + // called (for example, when the current sheet is closed from another View) + SCTAB nTab = aViewData.GetTabNo(); + ScDocument& rDoc = aViewData.GetDocument(); + if ( rDoc.HasTable(nTab) ) + PaintBlock( true ); // true -> delete block + else + rMark.ResetMark(); + } + meBlockMode = None; + + rMark.SetMarking(bFlag); + rMark.SetMarkNegative(false); +} + +bool ScTabView::IsBlockMode() const +{ + return meBlockMode != None; +} + +void ScTabView::MarkCursor( SCCOL nCurX, SCROW nCurY, SCTAB nCurZ, + bool bCols, bool bRows, bool bCellSelection ) +{ + ScDocument& rDocument = aViewData.GetDocument(); + if (!rDocument.ValidCol(nCurX)) nCurX = rDocument.MaxCol(); + if (!rDocument.ValidRow(nCurY)) nCurY = rDocument.MaxRow(); + + if (!IsBlockMode()) + { + OSL_FAIL( "MarkCursor not in BlockMode" ); + InitBlockMode( nCurX, nCurY, nCurZ, false, bCols, bRows ); + } + + if (bCols) + nCurY = rDocument.MaxRow(); + if (bRows) + nCurX = rDocument.MaxCol(); + + ScMarkData& rMark = aViewData.GetMarkData(); + OSL_ENSURE(rMark.IsMarked() || rMark.IsMultiMarked(), "MarkCursor, !IsMarked()"); + const ScRange& aMarkRange = rMark.GetMarkArea(); + if (( aMarkRange.aStart.Col() != nBlockStartX && aMarkRange.aEnd.Col() != nBlockStartX ) || + ( aMarkRange.aStart.Row() != nBlockStartY && aMarkRange.aEnd.Row() != nBlockStartY ) || + ( meBlockMode == Own )) + { + // Mark has been changed + // (Eg MarkToSimple if by negative everything was erased, except for a rectangle) + // or after InitOwnBlockMode is further marked with shift- + bool bOldShift = bMoveIsShift; + bMoveIsShift = false; // really move + DoneBlockMode(); //! Set variables directly? (-> no flicker) + bMoveIsShift = bOldShift; + + InitBlockMode( aMarkRange.aStart.Col(), aMarkRange.aStart.Row(), + nBlockStartZ, rMark.IsMarkNegative(), bCols, bRows ); + } + + if ( nCurX != nOldCurX || nCurY != nOldCurY ) + { + // Current cursor has moved + + SCTAB nTab = nCurZ; + + if ( bCellSelection ) + { + // Expand selection area accordingly when the current selection cuts + // through a merged cell. + ScRange cellSel(nBlockStartXOrig, nBlockStartYOrig, nTab, nCurX, nCurY, nTab); + cellSel.PutInOrder(); + ScRange oldSel; + do + { + oldSel = cellSel; + rDocument.ExtendOverlapped(cellSel); + rDocument.ExtendMerge(cellSel); + } while (oldSel != cellSel); + + // Preserve the directionality of the selection + if (nCurX >= nBlockStartXOrig) + { + nBlockStartX = cellSel.aStart.Col(); + nBlockEndX = cellSel.aEnd.Col(); + } + else + { + nBlockStartX = cellSel.aEnd.Col(); + nBlockEndX = cellSel.aStart.Col(); + } + if (nCurY >= nBlockStartYOrig) + { + nBlockStartY = cellSel.aStart.Row(); + nBlockEndY = cellSel.aEnd.Row(); + } + else + { + nBlockStartY = cellSel.aEnd.Row(); + nBlockEndY = cellSel.aStart.Row(); + } + } + else + { + nBlockEndX = nCurX; + nBlockEndY = nCurY; + } + + // Set new selection area + rMark.SetMarkArea( ScRange( nBlockStartX, nBlockStartY, nTab, nBlockEndX, nBlockEndY, nTab ) ); + + UpdateSelectionOverlay(); + SelectionChanged(); + + nOldCurX = nBlockEndX; + nOldCurY = nBlockEndY; + + aViewData.GetViewShell()->UpdateInputHandler(); + } + + if ( !bCols && !bRows ) + aHdrFunc.SetAnchorFlag( false ); +} + +void ScTabView::GetPageMoveEndPosition(SCCOL nMovX, SCROW nMovY, SCCOL& rPageX, SCROW& rPageY) +{ + SCCOL nCurX; + SCROW nCurY; + if (aViewData.IsRefMode()) + { + nCurX = aViewData.GetRefEndX(); + nCurY = aViewData.GetRefEndY(); + } + else if (IsBlockMode()) + { + // block end position. + nCurX = nBlockEndX; + nCurY = nBlockEndY; + } + else + { + // cursor position + nCurX = aViewData.GetCurX(); + nCurY = aViewData.GetCurY(); + } + + ScSplitPos eWhich = aViewData.GetActivePart(); + ScHSplitPos eWhichX = WhichH( eWhich ); + ScVSplitPos eWhichY = WhichV( eWhich ); + + sal_uInt16 nScrSizeY = SC_SIZE_NONE; + if (comphelper::LibreOfficeKit::isActive() && aViewData.GetPageUpDownOffset() > 0) { + nScrSizeY = ScViewData::ToPixel( aViewData.GetPageUpDownOffset(), aViewData.GetPPTX() ); + } + + SCCOL nPageX; + SCROW nPageY; + if (nMovX >= 0) + nPageX = aViewData.CellsAtX( nCurX, 1, eWhichX ) * nMovX; + else + nPageX = aViewData.CellsAtX( nCurX, -1, eWhichX ) * nMovX; + + if (nMovY >= 0) + nPageY = aViewData.CellsAtY( nCurY, 1, eWhichY, nScrSizeY ) * nMovY; + else + nPageY = aViewData.CellsAtY( nCurY, -1, eWhichY, nScrSizeY ) * nMovY; + + if (nMovX != 0 && nPageX == 0) nPageX = (nMovX>0) ? 1 : -1; + if (nMovY != 0 && nPageY == 0) nPageY = (nMovY>0) ? 1 : -1; + + rPageX = nPageX; + rPageY = nPageY; +} + +void ScTabView::GetAreaMoveEndPosition(SCCOL nMovX, SCROW nMovY, ScFollowMode eMode, + SCCOL& rAreaX, SCROW& rAreaY, ScFollowMode& rMode, + bool bInteractiveByUser) +{ + SCCOL nNewX = -1; + SCROW nNewY = -1; + + // current cursor position. + SCCOL nCurX = aViewData.GetCurX(); + SCROW nCurY = aViewData.GetCurY(); + + ScModule* pScModule = SC_MOD(); + bool bLegacyCellSelection = pScModule->GetInputOptions().GetLegacyCellSelection(); + bool bIncrementallyExpandToDocLimits(false); + + if (aViewData.IsRefMode()) + { + nNewX = aViewData.GetRefEndX(); + nNewY = aViewData.GetRefEndY(); + nCurX = aViewData.GetRefStartX(); + nCurY = aViewData.GetRefStartY(); + } + else if (IsBlockMode()) + { + // block end position. + nNewX = nBlockEndX; + nNewY = nBlockEndY; + } + else + { + nNewX = nCurX; + nNewY = nCurY; + // cool#6931 on ctrl+[right/down] don't immediately leap to the far limits of the document when no more data, + // instead jump a generous block of emptiness. Limit to direct interaction by user and the simple + // case. + bIncrementallyExpandToDocLimits = bInteractiveByUser && (nMovX == 1 || nMovY == 1) && + !bLegacyCellSelection && comphelper::LibreOfficeKit::isActive(); + } + + ScDocument& rDoc = aViewData.GetDocument(); + SCTAB nTab = aViewData.GetTabNo(); + + // FindAreaPos knows only -1 or 1 as direction + SCCOL nVirtualX = bLegacyCellSelection ? nNewX : nCurX; + SCROW nVirtualY = bLegacyCellSelection ? nNewY : nCurY; + + SCCOLROW i; + if ( nMovX > 0 ) + for ( i=0; i<nMovX; i++ ) + rDoc.FindAreaPos( nNewX, nVirtualY, nTab, SC_MOVE_RIGHT ); + if ( nMovX < 0 ) + for ( i=0; i<-nMovX; i++ ) + rDoc.FindAreaPos( nNewX, nVirtualY, nTab, SC_MOVE_LEFT ); + if ( nMovY > 0 ) + for ( i=0; i<nMovY; i++ ) + rDoc.FindAreaPos( nVirtualX, nNewY, nTab, SC_MOVE_DOWN ); + if ( nMovY < 0 ) + for ( i=0; i<-nMovY; i++ ) + rDoc.FindAreaPos( nVirtualX, nNewY, nTab, SC_MOVE_UP ); + + if (eMode==SC_FOLLOW_JUMP) // bottom right do not show too much grey + { + if (nMovX != 0 && nNewX == rDoc.MaxCol()) + { + eMode = SC_FOLLOW_LINE; + if (bIncrementallyExpandToDocLimits) + { + if (const ScTable* pTab = rDoc.FetchTable(nTab)) + { + if (!pTab->HasData(nNewX, nCurY)) + { + SCCOL nLastUsedCol(0); + SCROW nLastUsedRow(0); + rDoc.GetPrintArea(nTab, nLastUsedCol, nLastUsedRow); + SCCOL nJumpFrom = std::max(nCurX, nLastUsedCol); + nNewX = ((nJumpFrom / 13) + 2) * 13 - 1; + } + } + } + } + if (nMovY != 0 && nNewY == rDoc.MaxRow()) + { + eMode = SC_FOLLOW_LINE; + if (bIncrementallyExpandToDocLimits) + { + if (const ScTable* pTab = rDoc.FetchTable(nTab)) + { + if (!pTab->HasData(nCurX, nNewY)) + { + SCCOL nLastUsedCol(0); + SCROW nLastUsedRow(0); + rDoc.GetPrintArea(nTab, nLastUsedCol, nLastUsedRow); + SCROW nJumpFrom = std::max(nCurY, nLastUsedRow); + nNewY = ((nJumpFrom / 500) + 2) * 500 - 1; + } + } + } + } + } + + if (aViewData.IsRefMode()) + { + rAreaX = nNewX - aViewData.GetRefEndX(); + rAreaY = nNewY - aViewData.GetRefEndY(); + } + else if (IsBlockMode()) + { + rAreaX = nNewX - nBlockEndX; + rAreaY = nNewY - nBlockEndY; + } + else + { + rAreaX = nNewX - nCurX; + rAreaY = nNewY - nCurY; + } + rMode = eMode; +} + +void ScTabView::SkipCursorHorizontal(SCCOL& rCurX, SCROW& rCurY, SCCOL nOldX, SCCOL nMovX) +{ + ScDocument& rDoc = aViewData.GetDocument(); + SCTAB nTab = aViewData.GetTabNo(); + + bool bSkipProtected = false, bSkipUnprotected = false; + const ScTableProtection* pProtect = rDoc.GetTabProtection(nTab); + if (pProtect && pProtect->isProtected()) + { + bSkipProtected = !pProtect->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS); + bSkipUnprotected = !pProtect->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS); + } + + bool bSkipCell = false; + bool bHFlip = false; + // If a number of last columns are hidden, search up to and including the first of them, + // because after it nothing changes. + SCCOL nMaxCol; + if(rDoc.ColHidden(rDoc.MaxCol(), nTab, &nMaxCol)) + ++nMaxCol; + else + nMaxCol = rDoc.MaxCol(); + // Search also at least up to and including the first unallocated column (all unallocated columns + // share a set of attrs). + nMaxCol = std::max( nMaxCol, std::min<SCCOL>( rDoc.GetAllocatedColumnsCount(nTab) + 1, rDoc.MaxCol())); + do + { + bSkipCell = rDoc.ColHidden(rCurX, nTab) || rDoc.IsHorOverlapped(rCurX, rCurY, nTab); + if (bSkipProtected && !bSkipCell) + bSkipCell = rDoc.HasAttrib(rCurX, rCurY, nTab, rCurX, rCurY, nTab, HasAttrFlags::Protected); + if (bSkipUnprotected && !bSkipCell) + bSkipCell = !rDoc.HasAttrib(rCurX, rCurY, nTab, rCurX, rCurY, nTab, HasAttrFlags::Protected); + + if (bSkipCell) + { + if (rCurX <= 0 || rCurX >= nMaxCol) + { + if (bHFlip) + { + rCurX = nOldX; + bSkipCell = false; + } + else + { + nMovX = -nMovX; + if (nMovX > 0) + ++rCurX; + else + --rCurX; + bHFlip = true; + } + } + else + if (nMovX > 0) + ++rCurX; + else + --rCurX; + } + } + while (bSkipCell); + + if (rDoc.IsVerOverlapped(rCurX, rCurY, nTab)) + { + aViewData.SetOldCursor(rCurX, rCurY); + while (rDoc.IsVerOverlapped(rCurX, rCurY, nTab)) + --rCurY; + } +} + +void ScTabView::SkipCursorVertical(SCCOL& rCurX, SCROW& rCurY, SCROW nOldY, SCROW nMovY) +{ + ScDocument& rDoc = aViewData.GetDocument(); + SCTAB nTab = aViewData.GetTabNo(); + + bool bSkipProtected = false, bSkipUnprotected = false; + const ScTableProtection* pProtect = rDoc.GetTabProtection(nTab); + if (pProtect && pProtect->isProtected()) + { + bSkipProtected = !pProtect->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS); + bSkipUnprotected = !pProtect->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS); + } + + bool bSkipCell = false; + bool bVFlip = false; + // Avoid repeated calls to RowHidden(), IsVerOverlapped() and HasAttrib(). + SCROW nFirstSameHiddenRow = -1; + SCROW nLastSameHiddenRow = -1; + bool bRowHidden = false; + SCROW nFirstSameIsVerOverlapped = -1; + SCROW nLastSameIsVerOverlapped = -1; + bool bIsVerOverlapped = false; + SCROW nFirstSameHasAttribRow = -1; + SCROW nLastSameHasAttribRow = -1; + bool bHasAttribProtected = false; + do + { + if( rCurY < nFirstSameHiddenRow || rCurY > nLastSameHiddenRow ) + bRowHidden = rDoc.RowHidden(rCurY, nTab, &nFirstSameHiddenRow, &nLastSameHiddenRow); + bSkipCell = bRowHidden; + if( !bSkipCell ) + { + if( rCurY < nFirstSameIsVerOverlapped || rCurY > nLastSameIsVerOverlapped ) + bIsVerOverlapped = rDoc.IsVerOverlapped(rCurX, rCurY, nTab, &nFirstSameIsVerOverlapped, &nLastSameIsVerOverlapped); + bSkipCell = bIsVerOverlapped; + } + if (bSkipProtected && !bSkipCell) + { + if( rCurY < nFirstSameHasAttribRow || rCurY > nLastSameHasAttribRow ) + bHasAttribProtected = rDoc.HasAttrib(rCurX, rCurY, nTab, HasAttrFlags::Protected, + &nFirstSameHasAttribRow, &nLastSameHasAttribRow); + bSkipCell = bHasAttribProtected; + } + if (bSkipUnprotected && !bSkipCell) + { + if( rCurY < nFirstSameHasAttribRow || rCurY > nLastSameHasAttribRow ) + bHasAttribProtected = rDoc.HasAttrib(rCurX, rCurY, nTab, HasAttrFlags::Protected, + &nFirstSameHasAttribRow, &nLastSameHasAttribRow); + bSkipCell = !bHasAttribProtected; + } + + if (bSkipCell) + { + if (rCurY <= 0 || rCurY >= rDoc.MaxRow()) + { + if (bVFlip) + { + rCurY = nOldY; + bSkipCell = false; + } + else + { + nMovY = -nMovY; + if (nMovY > 0) + ++rCurY; + else + --rCurY; + bVFlip = true; + } + } + else + if (nMovY > 0) + ++rCurY; + else + --rCurY; + } + } + while (bSkipCell); + + if (rDoc.IsHorOverlapped(rCurX, rCurY, nTab)) + { + aViewData.SetOldCursor(rCurX, rCurY); + while (rDoc.IsHorOverlapped(rCurX, rCurY, nTab)) + --rCurX; + } +} + +void ScTabView::ExpandBlock(SCCOL nMovX, SCROW nMovY, ScFollowMode eMode) +{ + if (!nMovX && !nMovY) + // Nothing to do. Bail out. + return; + + ScTabViewShell* pViewShell = aViewData.GetViewShell(); + bool bRefInputMode = pViewShell && pViewShell->IsRefInputMode(); + if (bRefInputMode && !aViewData.IsRefMode()) + // initialize formula reference mode if it hasn't already. + InitRefMode(aViewData.GetCurX(), aViewData.GetCurY(), aViewData.GetTabNo(), SC_REFTYPE_REF); + + ScDocument& rDoc = aViewData.GetDocument(); + + if (aViewData.IsRefMode()) + { + // formula reference mode + + SCCOL nNewX = aViewData.GetRefEndX(); + SCROW nNewY = aViewData.GetRefEndY(); + SCTAB nRefTab = aViewData.GetRefEndZ(); + + moveRefByCell(nNewX, nNewY, nMovX, nMovY, nRefTab, rDoc); + + UpdateRef(nNewX, nNewY, nRefTab); + SCCOL nTargetCol = nNewX; + SCROW nTargetRow = nNewY; + if (((aViewData.GetRefStartX() == 0) || (aViewData.GetRefStartY() == 0)) && + ((nNewX != rDoc.MaxCol()) || (nNewY != rDoc.MaxRow()))) + { + // Row selection + if ((aViewData.GetRefStartX() == 0) && (nNewX == rDoc.MaxCol())) + nTargetCol = aViewData.GetCurX(); + // Column selection + if ((aViewData.GetRefStartY() == 0) && (nNewY == rDoc.MaxRow())) + nTargetRow = aViewData.GetCurY(); + } + AlignToCursor(nTargetCol, nTargetRow, eMode); + } + else + { + // normal selection mode + + SCTAB nTab = aViewData.GetTabNo(); + SCCOL nOrigX = aViewData.GetCurX(); + SCROW nOrigY = aViewData.GetCurY(); + + // Note that the origin position *never* moves during selection. + + if (!IsBlockMode()) + { + InitBlockMode(nOrigX, nOrigY, nTab, true); + const ScMergeAttr* pMergeAttr = rDoc.GetAttr(nOrigX, nOrigY, nTab, ATTR_MERGE); + if (pMergeAttr && pMergeAttr->IsMerged()) + { + nBlockEndX = nOrigX + pMergeAttr->GetColMerge() - 1; + nBlockEndY = nOrigY + pMergeAttr->GetRowMerge() - 1; + } + } + + moveCursorToProperSide(nBlockEndX, nBlockEndY, nMovX, nMovY, nBlockStartX, nBlockStartY, + nTab, &rDoc); + moveCursorByProtRule(nBlockEndX, nBlockEndY, nMovX, nMovY, nTab, &rDoc); + checkBoundary(&rDoc, nBlockEndX, nBlockEndY); + moveCursorByMergedCell(nBlockEndX, nBlockEndY, nMovX, nMovY, nBlockStartX, nBlockStartY, + nTab, &rDoc); + checkBoundary(&rDoc, nBlockEndX, nBlockEndY); + + MarkCursor(nBlockEndX, nBlockEndY, nTab, false, false, true); + + // Check if the entire row(s) or column(s) are selected. + ScSplitPos eActive = aViewData.GetActivePart(); + bool bRowSelected = (nBlockStartX == 0 && nBlockEndX == rDoc.MaxCol()); + bool bColSelected = (nBlockStartY == 0 && nBlockEndY == rDoc.MaxRow()); + SCCOL nAlignX = bRowSelected ? aViewData.GetPosX(WhichH(eActive)) : nBlockEndX; + SCROW nAlignY = bColSelected ? aViewData.GetPosY(WhichV(eActive)) : nBlockEndY; + AlignToCursor(nAlignX, nAlignY, eMode); + + SelectionChanged(); + } +} + +void ScTabView::ExpandBlockPage(SCCOL nMovX, SCROW nMovY) +{ + SCCOL nPageX; + SCROW nPageY; + GetPageMoveEndPosition(nMovX, nMovY, nPageX, nPageY); + ExpandBlock(nPageX, nPageY, SC_FOLLOW_FIX); +} + +void ScTabView::ExpandBlockArea(SCCOL nMovX, SCROW nMovY) +{ + SCCOL nAreaX; + SCROW nAreaY; + ScFollowMode eMode; + GetAreaMoveEndPosition(nMovX, nMovY, SC_FOLLOW_JUMP, nAreaX, nAreaY, eMode); + ExpandBlock(nAreaX, nAreaY, eMode); +} + +void ScTabView::UpdateCopySourceOverlay() +{ + for (VclPtr<ScGridWindow> & pWin : pGridWin) + if (pWin && pWin->IsVisible()) + pWin->UpdateCopySourceOverlay(); +} + +void ScTabView::UpdateSelectionOverlay() +{ + for (VclPtr<ScGridWindow> & pWin : pGridWin) + if ( pWin && pWin->IsVisible() ) + pWin->UpdateSelectionOverlay(); +} + +void ScTabView::UpdateHighlightOverlay() +{ + for (VclPtr<ScGridWindow> & pWin : pGridWin) + if ( pWin && pWin->IsVisible() ) + pWin->UpdateHighlightOverlay(); +} + +void ScTabView::UpdateShrinkOverlay() +{ + for (VclPtr<ScGridWindow> & pWin : pGridWin) + if ( pWin && pWin->IsVisible() ) + pWin->UpdateShrinkOverlay(); +} + +void ScTabView::UpdateAllOverlays() +{ + for (VclPtr<ScGridWindow> & pWin : pGridWin) + if ( pWin && pWin->IsVisible() ) + pWin->UpdateAllOverlays(); +} + +//! +//! divide PaintBlock into two methods: RepaintBlock and RemoveBlock or similar +//! + +void ScTabView::PaintBlock( bool bReset ) +{ + ScMarkData& rMark = aViewData.GetMarkData(); + SCTAB nTab = aViewData.GetTabNo(); + bool bMulti = rMark.IsMultiMarked(); + if (!(rMark.IsMarked() || bMulti)) + return; + + ScRange aMarkRange; + HideAllCursors(); + if (bMulti) + { + bool bFlag = rMark.GetMarkingFlag(); + rMark.SetMarking(false); + rMark.MarkToMulti(); + aMarkRange = rMark.GetMultiMarkArea(); + rMark.MarkToSimple(); + rMark.SetMarking(bFlag); + } + else + aMarkRange = rMark.GetMarkArea(); + + nBlockStartX = aMarkRange.aStart.Col(); + nBlockStartY = aMarkRange.aStart.Row(); + nBlockStartZ = aMarkRange.aStart.Tab(); + nBlockEndX = aMarkRange.aEnd.Col(); + nBlockEndY = aMarkRange.aEnd.Row(); + nBlockEndZ = aMarkRange.aEnd.Tab(); + + bool bDidReset = false; + + if ( nTab>=nBlockStartZ && nTab<=nBlockEndZ ) + { + if ( bReset ) + { + // Inverting when deleting only on active View + if ( aViewData.IsActive() ) + { + rMark.ResetMark(); + UpdateSelectionOverlay(); + bDidReset = true; + } + } + else + PaintMarks( nBlockStartX, nBlockStartY, nBlockEndX, nBlockEndY ); + } + + if ( bReset && !bDidReset ) + rMark.ResetMark(); + + ShowAllCursors(); +} + +void ScTabView::SelectAll( bool bContinue ) +{ + ScDocument& rDoc = aViewData.GetDocument(); + ScMarkData& rMark = aViewData.GetMarkData(); + SCTAB nTab = aViewData.GetTabNo(); + + if (rMark.IsMarked()) + { + if ( rMark.GetMarkArea() == ScRange( 0,0,nTab, rDoc.MaxCol(),rDoc.MaxRow(),nTab ) ) + return; + } + + DoneBlockMode( bContinue ); + InitBlockMode( 0,0,nTab ); + MarkCursor( rDoc.MaxCol(),rDoc.MaxRow(),nTab ); + + SelectionChanged(); +} + +void ScTabView::SelectAllTables() +{ + ScDocument& rDoc = aViewData.GetDocument(); + ScMarkData& rMark = aViewData.GetMarkData(); + SCTAB nCount = rDoc.GetTableCount(); + + if (nCount>1) + { + for (SCTAB i=0; i<nCount; i++) + rMark.SelectTable( i, true ); + + aViewData.GetDocShell()->PostPaintExtras(); + SfxBindings& rBind = aViewData.GetBindings(); + rBind.Invalidate( FID_FILL_TAB ); + rBind.Invalidate( FID_TAB_DESELECTALL ); + } +} + +void ScTabView::DeselectAllTables() +{ + ScDocument& rDoc = aViewData.GetDocument(); + ScMarkData& rMark = aViewData.GetMarkData(); + SCTAB nTab = aViewData.GetTabNo(); + SCTAB nCount = rDoc.GetTableCount(); + + for (SCTAB i=0; i<nCount; i++) + rMark.SelectTable( i, ( i == nTab ) ); + + aViewData.GetDocShell()->PostPaintExtras(); + SfxBindings& rBind = aViewData.GetBindings(); + rBind.Invalidate( FID_FILL_TAB ); + rBind.Invalidate( FID_TAB_DESELECTALL ); +} + +static bool lcl_FitsInWindow( double fScaleX, double fScaleY, sal_uInt16 nZoom, + tools::Long nWindowX, tools::Long nWindowY, const ScDocument* pDoc, SCTAB nTab, + SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, + SCCOL nFixPosX, SCROW nFixPosY ) +{ + double fZoomFactor = static_cast<double>(Fraction(nZoom,100)); + fScaleX *= fZoomFactor; + fScaleY *= fZoomFactor; + + tools::Long nBlockX = 0; + SCCOL nCol; + for (nCol=0; nCol<nFixPosX; nCol++) + { + // for frozen panes, add both parts + sal_uInt16 nColTwips = pDoc->GetColWidth( nCol, nTab ); + if (nColTwips) + { + nBlockX += static_cast<tools::Long>(nColTwips * fScaleX); + if (nBlockX > nWindowX) + return false; + } + } + for (nCol=nStartCol; nCol<=nEndCol; nCol++) + { + sal_uInt16 nColTwips = pDoc->GetColWidth( nCol, nTab ); + if (nColTwips) + { + nBlockX += static_cast<tools::Long>(nColTwips * fScaleX); + if (nBlockX > nWindowX) + return false; + } + } + + tools::Long nBlockY = 0; + for (SCROW nRow = 0; nRow <= nFixPosY-1; ++nRow) + { + if (pDoc->RowHidden(nRow, nTab)) + continue; + + // for frozen panes, add both parts + sal_uInt16 nRowTwips = pDoc->GetRowHeight(nRow, nTab); + if (nRowTwips) + { + nBlockY += static_cast<tools::Long>(nRowTwips * fScaleY); + if (nBlockY > nWindowY) + return false; + } + } + for (SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow) + { + sal_uInt16 nRowTwips = pDoc->GetRowHeight(nRow, nTab); + if (nRowTwips) + { + nBlockY += static_cast<tools::Long>(nRowTwips * fScaleY); + if (nBlockY > nWindowY) + return false; + } + } + + return true; +} + +sal_uInt16 ScTabView::CalcZoom( SvxZoomType eType, sal_uInt16 nOldZoom ) +{ + sal_uInt16 nZoom = 100; + + switch ( eType ) + { + case SvxZoomType::PERCENT: // rZoom is no particular percent value + nZoom = nOldZoom; + break; + + case SvxZoomType::OPTIMAL: // nZoom corresponds to the optimal size + { + ScMarkData& rMark = aViewData.GetMarkData(); + ScDocument& rDoc = aViewData.GetDocument(); + + if (!rMark.IsMarked() && !rMark.IsMultiMarked()) + nZoom = 100; // nothing selected + else + { + SCTAB nTab = aViewData.GetTabNo(); + ScRange aMarkRange; + if ( aViewData.GetSimpleArea( aMarkRange ) != SC_MARK_SIMPLE ) + aMarkRange = rMark.GetMultiMarkArea(); + + SCCOL nStartCol = aMarkRange.aStart.Col(); + SCROW nStartRow = aMarkRange.aStart.Row(); + SCTAB nStartTab = aMarkRange.aStart.Tab(); + SCCOL nEndCol = aMarkRange.aEnd.Col(); + SCROW nEndRow = aMarkRange.aEnd.Row(); + SCTAB nEndTab = aMarkRange.aEnd.Tab(); + + if ( nTab < nStartTab && nTab > nEndTab ) + nTab = nStartTab; + + ScSplitPos eUsedPart = aViewData.GetActivePart(); + + SCCOL nFixPosX = 0; + SCROW nFixPosY = 0; + if ( aViewData.GetHSplitMode() == SC_SPLIT_FIX ) + { + // use right part + eUsedPart = (WhichV(eUsedPart)==SC_SPLIT_TOP) ? SC_SPLIT_TOPRIGHT : SC_SPLIT_BOTTOMRIGHT; + nFixPosX = aViewData.GetFixPosX(); + if ( nStartCol < nFixPosX ) + nStartCol = nFixPosX; + } + if ( aViewData.GetVSplitMode() == SC_SPLIT_FIX ) + { + // use bottom part + eUsedPart = (WhichH(eUsedPart)==SC_SPLIT_LEFT) ? SC_SPLIT_BOTTOMLEFT : SC_SPLIT_BOTTOMRIGHT; + nFixPosY = aViewData.GetFixPosY(); + if ( nStartRow < nFixPosY ) + nStartRow = nFixPosY; + } + + if (pGridWin[eUsedPart]) + { + // Because scale is rounded to pixels, the only reliable way to find + // the right scale is to check if a zoom fits + + Size aWinSize = pGridWin[eUsedPart]->GetOutputSizePixel(); + + // for frozen panes, use sum of both parts for calculation + + if ( nFixPosX != 0 ) + aWinSize.AdjustWidth(GetGridWidth( SC_SPLIT_LEFT ) ); + if ( nFixPosY != 0 ) + aWinSize.AdjustHeight(GetGridHeight( SC_SPLIT_TOP ) ); + + ScDocShell* pDocSh = aViewData.GetDocShell(); + double nPPTX = ScGlobal::nScreenPPTX / pDocSh->GetOutputFactor(); + double nPPTY = ScGlobal::nScreenPPTY; + + sal_uInt16 nMin = MINZOOM; + sal_uInt16 nMax = MAXZOOM; + while ( nMax > nMin ) + { + sal_uInt16 nTest = (nMin+nMax+1)/2; + if ( lcl_FitsInWindow( + nPPTX, nPPTY, nTest, aWinSize.Width(), aWinSize.Height(), + &rDoc, nTab, nStartCol, nStartRow, nEndCol, nEndRow, + nFixPosX, nFixPosY ) ) + nMin = nTest; + else + nMax = nTest-1; + } + OSL_ENSURE( nMin == nMax, "Nesting is wrong" ); + nZoom = nMin; + + if ( nZoom != nOldZoom ) + { + // scroll to block only in active split part + // (the part for which the size was calculated) + + if ( nStartCol <= nEndCol ) + aViewData.SetPosX( WhichH(eUsedPart), nStartCol ); + if ( nStartRow <= nEndRow ) + aViewData.SetPosY( WhichV(eUsedPart), nStartRow ); + } + } + } + } + break; + + case SvxZoomType::WHOLEPAGE: // nZoom corresponds to the whole page or + case SvxZoomType::PAGEWIDTH: // nZoom corresponds to the page width + { + SCTAB nCurTab = aViewData.GetTabNo(); + ScDocument& rDoc = aViewData.GetDocument(); + ScStyleSheetPool* pStylePool = rDoc.GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet = + pStylePool->Find( rDoc.GetPageStyle( nCurTab ), + SfxStyleFamily::Page ); + + OSL_ENSURE( pStyleSheet, "PageStyle not found :-/" ); + + if ( pStyleSheet ) + { + ScPrintFunc aPrintFunc( aViewData.GetDocShell(), + aViewData.GetViewShell()->GetPrinter(true), + nCurTab ); + + Size aPageSize = aPrintFunc.GetDataSize(); + + // use the size of the largest GridWin for normal split, + // or both combined for frozen panes, with the (document) size + // of the frozen part added to the page size + // (with frozen panes, the size of the individual parts + // depends on the scale that is to be calculated) + + if (!pGridWin[SC_SPLIT_BOTTOMLEFT]) + return nZoom; + + Size aWinSize = pGridWin[SC_SPLIT_BOTTOMLEFT]->GetOutputSizePixel(); + ScSplitMode eHMode = aViewData.GetHSplitMode(); + if ( eHMode != SC_SPLIT_NONE && pGridWin[SC_SPLIT_BOTTOMRIGHT] ) + { + tools::Long nOtherWidth = pGridWin[SC_SPLIT_BOTTOMRIGHT]-> + GetOutputSizePixel().Width(); + if ( eHMode == SC_SPLIT_FIX ) + { + aWinSize.AdjustWidth(nOtherWidth ); + for ( SCCOL nCol = aViewData.GetPosX(SC_SPLIT_LEFT); + nCol < aViewData.GetFixPosX(); nCol++ ) + aPageSize.AdjustWidth(rDoc.GetColWidth( nCol, nCurTab ) ); + } + else if ( nOtherWidth > aWinSize.Width() ) + aWinSize.setWidth( nOtherWidth ); + } + ScSplitMode eVMode = aViewData.GetVSplitMode(); + if ( eVMode != SC_SPLIT_NONE && pGridWin[SC_SPLIT_TOPLEFT] ) + { + tools::Long nOtherHeight = pGridWin[SC_SPLIT_TOPLEFT]-> + GetOutputSizePixel().Height(); + if ( eVMode == SC_SPLIT_FIX ) + { + aWinSize.AdjustHeight(nOtherHeight ); + aPageSize.AdjustHeight(rDoc.GetRowHeight( + aViewData.GetPosY(SC_SPLIT_TOP), + aViewData.GetFixPosY()-1, nCurTab) ); + } + else if ( nOtherHeight > aWinSize.Height() ) + aWinSize.setHeight( nOtherHeight ); + } + + double nPPTX = ScGlobal::nScreenPPTX / aViewData.GetDocShell()->GetOutputFactor(); + double nPPTY = ScGlobal::nScreenPPTY; + + tools::Long nZoomX = static_cast<tools::Long>( aWinSize.Width() * 100 / + ( aPageSize.Width() * nPPTX ) ); + tools::Long nZoomY = static_cast<tools::Long>( aWinSize.Height() * 100 / + ( aPageSize.Height() * nPPTY ) ); + + if (nZoomX > 0) + nZoom = static_cast<sal_uInt16>(nZoomX); + + if (eType == SvxZoomType::WHOLEPAGE && nZoomY > 0 && nZoomY < nZoom) + nZoom = static_cast<sal_uInt16>(nZoomY); + } + } + break; + + default: + OSL_FAIL("Unknown Zoom-Revision"); + } + + return nZoom; +} + +// is called for instance when the view window is shifted: + +void ScTabView::StopMarking() +{ + ScSplitPos eActive = aViewData.GetActivePart(); + if (pGridWin[eActive]) + pGridWin[eActive]->StopMarking(); + + ScHSplitPos eH = WhichH(eActive); + if (pColBar[eH]) + pColBar[eH]->StopMarking(); + + ScVSplitPos eV = WhichV(eActive); + if (pRowBar[eV]) + pRowBar[eV]->StopMarking(); +} + +void ScTabView::HideNoteMarker() +{ + for (VclPtr<ScGridWindow> & pWin : pGridWin) + if (pWin && pWin->IsVisible()) + pWin->HideNoteMarker(); +} + +void ScTabView::MakeDrawLayer() +{ + if (pDrawView) + return; + + aViewData.GetDocShell()->MakeDrawLayer(); + + // pDrawView is set per Notify + OSL_ENSURE(pDrawView,"ScTabView::MakeDrawLayer does not work"); + + for(VclPtr<ScGridWindow> & pWin : pGridWin) + { + if(pWin) + { + pWin->DrawLayerCreated(); + } + } +} + +IMPL_STATIC_LINK_NOARG(ScTabView, InstallLOKNotifierHdl, void*, vcl::ILibreOfficeKitNotifier*) +{ + return GetpApp(); +} + +void ScTabView::ErrorMessage(TranslateId pGlobStrId) +{ + if ( SC_MOD()->IsInExecuteDrop() ) + { + // #i28468# don't show error message when called from Drag&Drop, silently abort instead + return; + } + + StopMarking(); // if called by Focus from MouseButtonDown + + weld::Window* pParent = aViewData.GetDialogParent(); + weld::WaitObject aWaitOff( pParent ); + bool bFocus = pParent && pParent->has_focus(); + + if (pGlobStrId && pGlobStrId == STR_PROTECTIONERR) + { + if (aViewData.GetDocShell()->IsReadOnly()) + { + pGlobStrId = STR_READONLYERR; + } + } + + m_xMessageBox.reset(Application::CreateMessageDialog(pParent, + VclMessageType::Info, VclButtonsType::Ok, + ScResId(pGlobStrId))); + + if (comphelper::LibreOfficeKit::isActive()) + m_xMessageBox->SetInstallLOKNotifierHdl(LINK(this, ScTabView, InstallLOKNotifierHdl)); + + weld::Window* pGrabOnClose = bFocus ? pParent : nullptr; + m_xMessageBox->runAsync(m_xMessageBox, [this, pGrabOnClose](sal_Int32 /*nResult*/) { + m_xMessageBox.reset(); + if (pGrabOnClose) + pGrabOnClose->grab_focus(); + }); +} + +void ScTabView::UpdatePageBreakData( bool bForcePaint ) +{ + std::unique_ptr<ScPageBreakData> pNewData; + + if (aViewData.IsPagebreakMode()) + { + ScDocShell* pDocSh = aViewData.GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + SCTAB nTab = aViewData.GetTabNo(); + + sal_uInt16 nCount = rDoc.GetPrintRangeCount(nTab); + if (!nCount) + nCount = 1; + pNewData.reset( new ScPageBreakData(nCount) ); + + ScPrintFunc aPrintFunc( pDocSh, pDocSh->GetPrinter(), nTab, 0,0,nullptr, nullptr, pNewData.get() ); + // ScPrintFunc fills the PageBreakData in ctor + if ( nCount > 1 ) + { + aPrintFunc.ResetBreaks(nTab); + pNewData->AddPages(); + } + + // print area changed? + if ( bForcePaint || ( pPageBreakData && !( *pPageBreakData == *pNewData ) ) ) + PaintGrid(); + } + + pPageBreakData = std::move(pNewData); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabview3.cxx b/sc/source/ui/view/tabview3.cxx new file mode 100644 index 0000000000..4a78aa38e7 --- /dev/null +++ b/sc/source/ui/view/tabview3.cxx @@ -0,0 +1,3171 @@ +/* -*- 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 <officecfg/Office/Calc.hxx> +#include <rangelst.hxx> +#include <scitems.hxx> + +#include <editeng/editview.hxx> +#include <svx/fmshell.hxx> +#include <svx/sdr/overlay/overlaymanager.hxx> +#include <svx/svdoole2.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/lokhelper.hxx> +#include <sfx2/viewfrm.hxx> +#include <vcl/cursor.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/uitest/eventdescription.hxx> +#include <sal/log.hxx> +#include <osl/diagnose.h> + +#include <IAnyRefDialog.hxx> +#include <tabview.hxx> +#include <tabvwsh.hxx> +#include <docsh.hxx> +#include <gridwin.hxx> +#include <olinewin.hxx> +#include <overlayobject.hxx> +#include <colrowba.hxx> +#include <tabcont.hxx> +#include <scmod.hxx> +#include <sc.hrc> +#include <viewutil.hxx> +#include <editutil.hxx> +#include <inputhdl.hxx> +#include <inputwin.hxx> +#include <validat.hxx> +#include <inputopt.hxx> +#include <rfindlst.hxx> +#include <hiranges.hxx> +#include <viewuno.hxx> +#include <dpobject.hxx> +#include <seltrans.hxx> +#include <fillinfo.hxx> +#include <rangeutl.hxx> +#include <client.hxx> +#include <tabprotection.hxx> +#include <spellcheckcontext.hxx> +#include <markdata.hxx> +#include <formula/FormulaCompiler.hxx> +#include <comphelper/lok.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <output.hxx> + +#include <utility> + +#include <com/sun/star/chart2/data/HighlightedRange.hpp> + +namespace +{ + +ScRange lcl_getSubRangeByIndex( const ScRange& rRange, sal_Int32 nIndex ) +{ + ScAddress aResult( rRange.aStart ); + + SCCOL nWidth = rRange.aEnd.Col() - rRange.aStart.Col() + 1; + SCROW nHeight = rRange.aEnd.Row() - rRange.aStart.Row() + 1; + SCTAB nDepth = rRange.aEnd.Tab() - rRange.aStart.Tab() + 1; + if( (nWidth > 0) && (nHeight > 0) && (nDepth > 0) ) + { + // row by row from first to last sheet + sal_Int32 nArea = nWidth * nHeight; + aResult.IncCol( static_cast< SCCOL >( nIndex % nWidth ) ); + aResult.IncRow( static_cast< SCROW >( (nIndex % nArea) / nWidth ) ); + aResult.IncTab( static_cast< SCTAB >( nIndex / nArea ) ); + if( !rRange.Contains( aResult ) ) + aResult = rRange.aStart; + } + + return ScRange( aResult ); +} + +} // anonymous namespace + +using namespace com::sun::star; + +ScExtraEditViewManager::~ScExtraEditViewManager() +{ + DBG_ASSERT(nTotalWindows == 0, "ScExtraEditViewManager dtor: some out window has not yet been removed!"); +} + +inline void ScExtraEditViewManager::Add(SfxViewShell* pViewShell, ScSplitPos eWhich) +{ + Apply<Adder>(pViewShell, eWhich); +} + +inline void ScExtraEditViewManager::Remove(SfxViewShell* pViewShell, ScSplitPos eWhich) +{ + Apply<Remover>(pViewShell, eWhich); +} + + +template<ScExtraEditViewManager::ModifierTagType ModifierTag> +void ScExtraEditViewManager::Apply(SfxViewShell* pViewShell, ScSplitPos eWhich) +{ + ScTabViewShell* pOtherViewShell = dynamic_cast<ScTabViewShell*>(pViewShell); + if (pOtherViewShell == nullptr || pOtherViewShell == mpThisViewShell) + return; + + mpOtherEditView = pOtherViewShell->GetViewData().GetEditView(eWhich); + if (mpOtherEditView != nullptr) + { + DBG_ASSERT(mpOtherEditView->GetEditEngine(), "Edit view has no valid engine."); + for (int i = 0; i < 4; ++i) + { + ScGridWindow* pWin = mpGridWin[i].get(); + if (pWin != nullptr) + { + Modifier<ModifierTag>(pWin); + } + } + } +} + +template<ScExtraEditViewManager::ModifierTagType ModifierTag> +void ScExtraEditViewManager::Modifier(ScGridWindow* /*pWin*/) +{ + (void)this; + SAL_WARN("sc", "ScExtraEditViewManager::Modifier<ModifierTag>: non-specialized version should not be invoked."); +} + +template<> +void ScExtraEditViewManager::Modifier<ScExtraEditViewManager::Adder>(ScGridWindow* pWin) +{ + if (mpOtherEditView->AddOtherViewWindow(pWin)) + ++nTotalWindows; +} + +template<> +void ScExtraEditViewManager::Modifier<ScExtraEditViewManager::Remover>(ScGridWindow* pWin) +{ + if (mpOtherEditView->RemoveOtherViewWindow(pWin)) + --nTotalWindows; +} + +// --- public functions + +void ScTabView::ClickCursor( SCCOL nPosX, SCROW nPosY, bool bControl ) +{ + ScDocument& rDoc = aViewData.GetDocument(); + SCTAB nTab = aViewData.GetTabNo(); + rDoc.SkipOverlapped(nPosX, nPosY, nTab); + + bool bRefMode = SC_MOD()->IsFormulaMode(); + + if ( bRefMode ) + { + DoneRefMode(); + + if (bControl) + SC_MOD()->AddRefEntry(); + + InitRefMode( nPosX, nPosY, nTab, SC_REFTYPE_REF ); + } + else + { + DoneBlockMode( bControl ); + aViewData.ResetOldCursor(); + SetCursor( nPosX, nPosY ); + } +} + +void ScTabView::UpdateAutoFillMark(bool bFromPaste) +{ + // single selection or cursor + ScRange aMarkRange; + ScMarkType eMarkType = aViewData.GetSimpleArea(aMarkRange); + bool bMarked = eMarkType == SC_MARK_SIMPLE || eMarkType == SC_MARK_SIMPLE_FILTERED; + + for (sal_uInt16 i = 0; i < 4; i++) + { + if (pGridWin[i] && pGridWin[i]->IsVisible()) + pGridWin[i]->UpdateAutoFillMark( bMarked, aMarkRange ); + } + + for (sal_uInt16 i = 0; i < 2; i++) + { + if (pColBar[i] && pColBar[i]->IsVisible()) + pColBar[i]->SetMark( bMarked, aMarkRange.aStart.Col(), aMarkRange.aEnd.Col() ); + if (pRowBar[i] && pRowBar[i]->IsVisible()) + pRowBar[i]->SetMark( bMarked, aMarkRange.aStart.Row(), aMarkRange.aEnd.Row() ); + } + + // selection transfer object is checked together with AutoFill marks, + // because it has the same requirement of a single continuous block. + if (!bFromPaste) + CheckSelectionTransfer(); // update selection transfer object +} + +void ScTabView::FakeButtonUp( ScSplitPos eWhich ) +{ + if (pGridWin[eWhich]) + pGridWin[eWhich]->FakeButtonUp(); +} + +void ScTabView::HideAllCursors() +{ + for (VclPtr<ScGridWindow> & pWin : pGridWin) + { + if (pWin && pWin->IsVisible()) + { + vcl::Cursor* pCur = pWin->GetCursor(); + if (pCur && pCur->IsVisible()) + pCur->Hide(); + pWin->HideCursor(); + } + } +} + +void ScTabView::ShowAllCursors() +{ + for (VclPtr<ScGridWindow> & pWin : pGridWin) + { + if (pWin && pWin->IsVisible()) + { + pWin->ShowCursor(); + pWin->CursorChanged(); + } + } +} + +void ScTabView::ShowCursor() +{ + pGridWin[aViewData.GetActivePart()]->ShowCursor(); + pGridWin[aViewData.GetActivePart()]->CursorChanged(); +} + +void ScTabView::InvalidateAttribs() +{ + SfxBindings& rBindings = aViewData.GetBindings(); + + rBindings.Invalidate( SID_STYLE_APPLY ); + rBindings.Invalidate( SID_STYLE_FAMILY2 ); + rBindings.Invalidate( SID_STYLE_FAMILY3 ); + + rBindings.Invalidate( SID_ATTR_CHAR_FONT ); + rBindings.Invalidate( SID_ATTR_CHAR_FONTHEIGHT ); + rBindings.Invalidate( SID_ATTR_CHAR_COLOR ); + + 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_KERNING ); + rBindings.Invalidate( SID_SET_SUPER_SCRIPT ); + rBindings.Invalidate( SID_SET_SUB_SCRIPT ); + rBindings.Invalidate( SID_ATTR_CHAR_STRIKEOUT ); + rBindings.Invalidate( SID_ATTR_CHAR_SHADOWED ); + + 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_NUMBER_TYPE_FORMAT); + + rBindings.Invalidate( SID_ALIGNLEFT ); + rBindings.Invalidate( SID_ALIGNRIGHT ); + rBindings.Invalidate( SID_ALIGNBLOCK ); + rBindings.Invalidate( SID_ALIGNCENTERHOR ); + + rBindings.Invalidate( SID_ALIGNTOP ); + rBindings.Invalidate( SID_ALIGNBOTTOM ); + rBindings.Invalidate( SID_ALIGNCENTERVER ); + + rBindings.Invalidate( SID_SCATTR_CELLPROTECTION ); + + // stuff for sidebar panels + { + rBindings.Invalidate( SID_H_ALIGNCELL ); + rBindings.Invalidate( SID_V_ALIGNCELL ); + rBindings.Invalidate( SID_ATTR_ALIGN_INDENT ); + rBindings.Invalidate( SID_FRAME_LINECOLOR ); + rBindings.Invalidate( SID_FRAME_LINESTYLE ); + rBindings.Invalidate( SID_ATTR_BORDER_OUTER ); + rBindings.Invalidate( SID_ATTR_BORDER_INNER ); + rBindings.Invalidate( SID_ATTR_BORDER_DIAG_TLBR ); + rBindings.Invalidate( SID_ATTR_BORDER_DIAG_BLTR ); + rBindings.Invalidate( SID_NUMBER_TYPE_FORMAT ); + } + + rBindings.Invalidate( SID_BACKGROUND_COLOR ); + + rBindings.Invalidate( SID_ATTR_ALIGN_LINEBREAK ); + rBindings.Invalidate( SID_NUMBER_FORMAT ); + + 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 ); + + // 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.Invalidate( SID_NUMBER_CURRENCY ); + rBindings.Invalidate( SID_NUMBER_SCIENTIFIC ); + rBindings.Invalidate( SID_NUMBER_DATE ); + rBindings.Invalidate( SID_NUMBER_CURRENCY ); + rBindings.Invalidate( SID_NUMBER_PERCENT ); + rBindings.Invalidate( SID_NUMBER_TWODEC ); + rBindings.Invalidate( SID_NUMBER_TIME ); + rBindings.Invalidate( SID_NUMBER_STANDARD ); + rBindings.Invalidate( SID_NUMBER_THOUSANDS ); +} + +namespace { + +void collectUIInformation(std::map<OUString, OUString>&& aParameters) +{ + EventDescription aDescription; + aDescription.aID = "grid_window"; + aDescription.aAction = "SELECT"; + aDescription.aParameters = std::move(aParameters); + aDescription.aParent = "MainWindow"; + aDescription.aKeyWord = "ScGridWinUIObject"; + + UITestLogger::getInstance().logEvent(aDescription); +} + +} + +// SetCursor - Cursor, set, draw, update InputWin +// or send reference +// Optimising breaks the functionality + +void ScTabView::SetCursor( SCCOL nPosX, SCROW nPosY, bool bNew ) +{ + SCCOL nOldX = aViewData.GetCurX(); + SCROW nOldY = aViewData.GetCurY(); + + // DeactivateIP only for MarkListHasChanged + + // 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 = std::min(nPosY, MAXTILEDROW); + + if ( !(nPosX != nOldX || nPosY != nOldY || bNew) ) + { + HighlightOverlay(); + return; + } + + ScTabViewShell* pViewShell = aViewData.GetViewShell(); + bool bRefMode = pViewShell && pViewShell->IsRefInputMode(); + if ( aViewData.HasEditView( aViewData.GetActivePart() ) && !bRefMode ) // 23259 or so + { + UpdateInputLine(); + } + + HideAllCursors(); + + aViewData.SetCurX( nPosX ); + aViewData.SetCurY( nPosY ); + + ShowAllCursors(); + + HighlightOverlay(); + + CursorPosChanged(); + + OUString aCurrAddress = ScAddress(nPosX,nPosY,0).GetColRowString(); + collectUIInformation({{"CELL", aCurrAddress}}); + + if (!comphelper::LibreOfficeKit::isActive()) + return; + + if (nPosX <= aViewData.GetMaxTiledCol() - 10 && nPosY <= aViewData.GetMaxTiledRow() - 25) + return; + + ScDocument& rDoc = aViewData.GetDocument(); + ScDocShell* pDocSh = aViewData.GetDocShell(); + ScModelObj* pModelObj = pDocSh ? pDocSh->GetModel() : nullptr; + Size aOldSize(0, 0); + if (pModelObj) + aOldSize = pModelObj->getDocumentSize(); + + if (nPosX > aViewData.GetMaxTiledCol() - 10) + aViewData.SetMaxTiledCol(std::min<SCCOL>(std::max(nPosX, aViewData.GetMaxTiledCol()) + 10, rDoc.MaxCol())); + + if (nPosY > aViewData.GetMaxTiledRow() - 25) + aViewData.SetMaxTiledRow(std::min<SCROW>(std::max(nPosY, aViewData.GetMaxTiledRow()) + 25, MAXTILEDROW)); + + Size aNewSize(0, 0); + if (pModelObj) + aNewSize = pModelObj->getDocumentSize(); + + if (!pDocSh) + return; + + // New area extended to the right of the sheet after last column + // including overlapping area with aNewRowArea + tools::Rectangle aNewColArea(aOldSize.getWidth(), 0, aNewSize.getWidth(), aNewSize.getHeight()); + // New area extended to the bottom of the sheet after last row + // excluding overlapping area with aNewColArea + tools::Rectangle aNewRowArea(0, aOldSize.getHeight(), aOldSize.getWidth(), aNewSize.getHeight()); + + // Only invalidate if spreadsheet extended to the right + if (aNewColArea.getOpenWidth()) + { + SfxLokHelper::notifyInvalidation(aViewData.GetViewShell(), &aNewColArea); + } + + // Only invalidate if spreadsheet extended to the bottom + if (aNewRowArea.getOpenHeight()) + { + SfxLokHelper::notifyInvalidation(aViewData.GetViewShell(), &aNewRowArea); + } + + // Provide size in the payload, so clients don't have to + // call lok::Document::getDocumentSize(). + std::stringstream ss; + ss << aNewSize.Width() << ", " << aNewSize.Height(); + OString sSize( ss.str() ); + ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>(aViewData.GetViewShell()->GetCurrentDocument()); + SfxLokHelper::notifyDocumentSizeChanged(aViewData.GetViewShell(), sSize, pModel, false); +} + +static bool lcl_IsRefDlgActive(SfxViewFrame& rViewFrm) +{ + ScModule* pScMod = SC_MOD(); + if (!pScMod->IsRefDialogOpen()) + return false; + + auto nDlgId = pScMod->GetCurRefDlgId(); + if (!rViewFrm.HasChildWindow(nDlgId)) + return false; + + SfxChildWindow* pChild = rViewFrm.GetChildWindow(nDlgId); + if (!pChild) + return false; + + auto xDlgController = pChild->GetController(); + if (!xDlgController || !xDlgController->getDialog()->get_visible()) + return false; + + IAnyRefDialog* pRefDlg = dynamic_cast<IAnyRefDialog*>(xDlgController.get()); + return pRefDlg && pRefDlg->IsRefInputMode(); +} + +void ScTabView::CheckSelectionTransfer() +{ + if ( !aViewData.IsActive() ) // only for active view + return; + + ScModule* pScMod = SC_MOD(); + ScSelectionTransferObj* pOld = pScMod->GetSelectionTransfer(); + rtl::Reference<ScSelectionTransferObj> pNew = ScSelectionTransferObj::CreateFromView( this ); + if ( !pNew ) + return; + + // create new selection + + if (pOld) + pOld->ForgetView(); + + pScMod->SetSelectionTransfer( pNew.get() ); + + // tdf#124975/tdf#136242 changing the calc selection can trigger removal of the + // selection of an open RefDlg dialog, so don't inform the + // desktop clipboard of the changed selection if that dialog is open + if (!lcl_IsRefDlgActive(aViewData.GetViewShell()->GetViewFrame())) + pNew->CopyToPrimarySelection(); // may delete pOld + + // Log the selection change + ScMarkData& rMark = aViewData.GetMarkData(); + if (rMark.IsMarked()) + { + const ScRange& aMarkRange = rMark.GetMarkArea(); + OUString aStartAddress = aMarkRange.aStart.GetColRowString(); + OUString aEndAddress = aMarkRange.aEnd.GetColRowString(); + collectUIInformation({{"RANGE", aStartAddress + ":" + aEndAddress}}); + } +} + +// update input row / menus +// CursorPosChanged calls SelectionChanged +// SelectionChanged calls CellContentChanged + +void ScTabView::CellContentChanged() +{ + SfxBindings& rBindings = aViewData.GetBindings(); + + rBindings.Invalidate( SID_ATTR_SIZE ); // -> show error message + rBindings.Invalidate( SID_THESAURUS ); + rBindings.Invalidate( SID_HYPERLINK_GETLINK ); + rBindings.Invalidate( SID_ROWCOL_SELCOUNT ); + + InvalidateAttribs(); // attributes updates + + aViewData.GetViewShell()->UpdateInputHandler(); +} + +void ScTabView::SetTabProtectionSymbol( SCTAB nTab, const bool bProtect ) +{ + pTabControl->SetProtectionSymbol( static_cast<sal_uInt16>(nTab)+1, bProtect); +} + +void ScTabView::SelectionChanged(bool bFromPaste) +{ + SfxViewFrame& rViewFrame = aViewData.GetViewShell()->GetViewFrame(); + uno::Reference<frame::XController> xController = rViewFrame.GetFrame().GetController(); + if (xController.is()) + { + ScTabViewObj* pImp = dynamic_cast<ScTabViewObj*>( xController.get() ); + if (pImp) + pImp->SelectionChanged(); + } + + UpdateAutoFillMark(bFromPaste); // also calls CheckSelectionTransfer + + SfxBindings& rBindings = aViewData.GetBindings(); + + rBindings.Invalidate( SID_CURRENTCELL ); // -> Navigator + rBindings.Invalidate( SID_AUTO_FILTER ); // -> Menu + rBindings.Invalidate( FID_NOTE_VISIBLE ); + rBindings.Invalidate( FID_SHOW_NOTE ); + rBindings.Invalidate( FID_HIDE_NOTE ); + rBindings.Invalidate( FID_SHOW_ALL_NOTES ); + rBindings.Invalidate( FID_HIDE_ALL_NOTES ); + rBindings.Invalidate( SID_TOGGLE_NOTES ); + rBindings.Invalidate( SID_DELETE_NOTE ); + rBindings.Invalidate( SID_ROWCOL_SELCOUNT ); + + // functions than may need to be disabled + + rBindings.Invalidate( FID_INS_ROWBRK ); + rBindings.Invalidate( FID_INS_COLBRK ); + rBindings.Invalidate( FID_DEL_ROWBRK ); + rBindings.Invalidate( FID_DEL_COLBRK ); + rBindings.Invalidate( FID_MERGE_ON ); + rBindings.Invalidate( FID_MERGE_OFF ); + rBindings.Invalidate( FID_MERGE_TOGGLE ); + rBindings.Invalidate( SID_AUTOFILTER_HIDE ); + rBindings.Invalidate( SID_UNFILTER ); + rBindings.Invalidate( SID_REIMPORT_DATA ); + rBindings.Invalidate( SID_REFRESH_DBAREA ); + rBindings.Invalidate( SID_OUTLINE_SHOW ); + rBindings.Invalidate( SID_OUTLINE_HIDE ); + rBindings.Invalidate( SID_OUTLINE_REMOVE ); + rBindings.Invalidate( FID_FILL_TO_BOTTOM ); + rBindings.Invalidate( FID_FILL_TO_RIGHT ); + rBindings.Invalidate( FID_FILL_TO_TOP ); + rBindings.Invalidate( FID_FILL_TO_LEFT ); + rBindings.Invalidate( FID_FILL_SERIES ); + rBindings.Invalidate( SID_SCENARIOS ); + rBindings.Invalidate( SID_AUTOFORMAT ); + rBindings.Invalidate( SID_OPENDLG_TABOP ); + rBindings.Invalidate( SID_DATA_SELECT ); + + rBindings.Invalidate( SID_CUT ); + rBindings.Invalidate( SID_COPY ); + rBindings.Invalidate( SID_PASTE ); + rBindings.Invalidate( SID_PASTE_SPECIAL ); + rBindings.Invalidate( SID_PASTE_UNFORMATTED ); + + rBindings.Invalidate( FID_INS_ROW ); + rBindings.Invalidate( FID_INS_COLUMN ); + rBindings.Invalidate( FID_INS_ROWS_BEFORE ); + rBindings.Invalidate( FID_INS_COLUMNS_BEFORE ); + rBindings.Invalidate( FID_INS_ROWS_AFTER ); + rBindings.Invalidate( FID_INS_COLUMNS_AFTER ); + rBindings.Invalidate( FID_INS_CELL ); + rBindings.Invalidate( FID_INS_CELLSDOWN ); + rBindings.Invalidate( FID_INS_CELLSRIGHT ); + + rBindings.Invalidate( FID_CHG_COMMENT ); + + // only due to protect cell: + + rBindings.Invalidate( SID_CELL_FORMAT_RESET ); + rBindings.Invalidate( SID_DELETE ); + rBindings.Invalidate( SID_DELETE_CONTENTS ); + rBindings.Invalidate( FID_DELETE_CELL ); + rBindings.Invalidate( FID_CELL_FORMAT ); + rBindings.Invalidate( SID_ENABLE_HYPHENATION ); + rBindings.Invalidate( SID_INSERT_POSTIT ); + rBindings.Invalidate( SID_CHARMAP ); + rBindings.Invalidate( SID_OPENDLG_FUNCTION ); + rBindings.Invalidate( FID_VALIDATION ); + rBindings.Invalidate( SID_EXTERNAL_SOURCE ); + rBindings.Invalidate( SID_TEXT_TO_COLUMNS ); + rBindings.Invalidate( SID_SORT_ASCENDING ); + rBindings.Invalidate( SID_SORT_DESCENDING ); + rBindings.Invalidate( SID_SELECT_UNPROTECTED_CELLS ); + + if (aViewData.GetViewShell()->HasAccessibilityObjects()) + aViewData.GetViewShell()->BroadcastAccessibility(SfxHint(SfxHintId::ScAccCursorChanged)); + + CellContentChanged(); +} + +void ScTabView::CursorPosChanged() +{ + bool bRefMode = SC_MOD()->IsFormulaMode(); + if ( !bRefMode ) // check that RefMode works when switching sheets + aViewData.GetDocShell()->Broadcast( SfxHint( SfxHintId::ScKillEditView ) ); + + // Broadcast, so that other Views of the document also switch + + ScDocument& rDocument = aViewData.GetDocument(); + bool bDataPilot = rDocument.HasDataPilotAtPosition(aViewData.GetCurPos()); + aViewData.GetViewShell()->SetPivotShell(bDataPilot); + + if (!bDataPilot) + { + bool bSparkline = rDocument.HasSparkline(aViewData.GetCurPos()); + aViewData.GetViewShell()->SetSparklineShell(bSparkline); + } + + // UpdateInputHandler now in CellContentChanged + + SelectionChanged(); + + aViewData.SetTabStartCol( SC_TABSTART_NONE ); +} + +namespace { + +Point calcHintWindowPosition( + const Point& rCellPos, const Size& rCellSize, const Size& rFrameWndSize, const Size& rHintWndSize) +{ + const tools::Long nMargin = 20; + + tools::Long nMLeft = rCellPos.X(); + tools::Long nMRight = rFrameWndSize.Width() - rCellPos.X() - rCellSize.Width(); + tools::Long nMTop = rCellPos.Y(); + tools::Long nMBottom = rFrameWndSize.Height() - rCellPos.Y() - rCellSize.Height(); + + // First, see if we can fit the entire hint window in the visible region. + + if (nMRight - nMargin >= rHintWndSize.Width()) + { + // Right margin is wide enough. + if (rFrameWndSize.Height() >= rHintWndSize.Height()) + { + // The frame has enough height. Take it. + Point aPos = rCellPos; + aPos.AdjustX(rCellSize.Width() + nMargin ); + if (aPos.Y() + rHintWndSize.Height() > rFrameWndSize.Height()) + { + // Push the hint window up a bit to make it fit. + aPos.setY( rFrameWndSize.Height() - rHintWndSize.Height() ); + } + return aPos; + } + } + + if (nMBottom - nMargin >= rHintWndSize.Height()) + { + // Bottom margin is high enough. + if (rFrameWndSize.Width() >= rHintWndSize.Width()) + { + // The frame has enough width. Take it. + Point aPos = rCellPos; + aPos.AdjustY(rCellSize.Height() + nMargin ); + if (aPos.X() + rHintWndSize.Width() > rFrameWndSize.Width()) + { + // Move the hint window to the left to make it fit. + aPos.setX( rFrameWndSize.Width() - rHintWndSize.Width() ); + } + return aPos; + } + } + + if (nMLeft - nMargin >= rHintWndSize.Width()) + { + // Left margin is wide enough. + if (rFrameWndSize.Height() >= rHintWndSize.Height()) + { + // The frame is high enough. Take it. + Point aPos = rCellPos; + aPos.AdjustX( -(rHintWndSize.Width() + nMargin) ); + if (aPos.Y() + rHintWndSize.Height() > rFrameWndSize.Height()) + { + // Push the hint window up a bit to make it fit. + aPos.setY( rFrameWndSize.Height() - rHintWndSize.Height() ); + } + return aPos; + } + } + + if (nMTop - nMargin >= rHintWndSize.Height()) + { + // Top margin is high enough. + if (rFrameWndSize.Width() >= rHintWndSize.Width()) + { + // The frame is wide enough. Take it. + Point aPos = rCellPos; + aPos.AdjustY( -(rHintWndSize.Height() + nMargin) ); + if (aPos.X() + rHintWndSize.Width() > rFrameWndSize.Width()) + { + // Move the hint window to the left to make it fit. + aPos.setX( rFrameWndSize.Width() - rHintWndSize.Width() ); + } + return aPos; + } + } + + // The popup doesn't fit in any direction in its entirety. Do our best. + + if (nMRight - nMargin >= rHintWndSize.Width()) + { + // Right margin is good enough. + Point aPos = rCellPos; + aPos.AdjustX(nMargin + rCellSize.Width() ); + aPos.setY( 0 ); + return aPos; + } + + if (nMBottom - nMargin >= rHintWndSize.Height()) + { + // Bottom margin is good enough. + Point aPos = rCellPos; + aPos.AdjustY(nMargin + rCellSize.Height() ); + aPos.setX( 0 ); + return aPos; + } + + if (nMLeft - nMargin >= rHintWndSize.Width()) + { + // Left margin is good enough. + Point aPos = rCellPos; + aPos.AdjustX( -(rHintWndSize.Width() + nMargin) ); + aPos.setY( 0 ); + return aPos; + } + + if (nMTop - nMargin >= rHintWndSize.Height()) + { + // Top margin is good enough. + Point aPos = rCellPos; + aPos.AdjustY( -(rHintWndSize.Height() + nMargin) ); + aPos.setX( 0 ); + return aPos; + } + + // None of the above. Hopeless. At least try not to cover the current + // cell. + Point aPos = rCellPos; + aPos.AdjustX(rCellSize.Width() ); + return aPos; +} + +} + +void ScTabView::TestHintWindow() +{ + // show input help window and list drop-down button for validity + + mxInputHintOO.reset(); + + bool bListValButton = false; + ScAddress aListValPos; + + ScDocument& rDoc = aViewData.GetDocument(); + const SfxUInt32Item* pItem = rDoc.GetAttr( aViewData.GetCurX(), + aViewData.GetCurY(), + aViewData.GetTabNo(), + ATTR_VALIDDATA ); + if ( pItem->GetValue() ) + { + const ScValidationData* pData = rDoc.GetValidationEntry( pItem->GetValue() ); + OSL_ENSURE(pData,"ValidationData not found"); + OUString aTitle, aMessage; + + if ( pData && pData->GetInput( aTitle, aMessage ) && !aMessage.isEmpty() ) + { + ScSplitPos eWhich = aViewData.GetActivePart(); + ScGridWindow* pWin = pGridWin[eWhich].get(); + SCCOL nCol = aViewData.GetCurX(); + SCROW nRow = aViewData.GetCurY(); + Point aPos = aViewData.GetScrPos( nCol, nRow, eWhich ); + Size aWinSize = pWin->GetOutputSizePixel(); + // cursor visible? + if ( nCol >= aViewData.GetPosX(WhichH(eWhich)) && + nRow >= aViewData.GetPosY(WhichV(eWhich)) && + aPos.X() < aWinSize.Width() && aPos.Y() < aWinSize.Height() ) + { + const svtools::ColorConfig& rColorCfg = SC_MOD()->GetColorConfig(); + Color aCommentColor = rColorCfg.GetColorValue(svtools::CALCNOTESBACKGROUND).nColor; + // create HintWindow, determines its size by itself + ScOverlayHint* pOverlay = new ScOverlayHint(aTitle, aMessage, aCommentColor, pFrameWin->GetFont()); + + mxInputHintOO.reset(new sdr::overlay::OverlayObjectList); + mxInputHintOO->append(std::unique_ptr<sdr::overlay::OverlayObject>(pOverlay)); + + Size aHintWndSize = pOverlay->GetSizePixel(); + tools::Long nCellSizeX = 0; + tools::Long nCellSizeY = 0; + aViewData.GetMergeSizePixel(nCol, nRow, nCellSizeX, nCellSizeY); + + Point aHintPos = calcHintWindowPosition( + aPos, Size(nCellSizeX,nCellSizeY), aWinSize, aHintWndSize); + + pOverlay->SetPos(pWin->PixelToLogic(aHintPos, pWin->GetDrawMapMode()), pWin->GetDrawMapMode()); + for (VclPtr<ScGridWindow> & pWindow : pGridWin) + { + if (!pWindow) + continue; + if (!pWindow->IsVisible()) + continue; + rtl::Reference<sdr::overlay::OverlayManager> xOverlayManager = pWindow->getOverlayManager(); + if (!xOverlayManager.is()) + continue; + if (pWindow == pWin) + { + xOverlayManager->add(*pOverlay); + pWindow->updateLOKInputHelp(aTitle, aMessage); + } + else + { + //tdf#92530 if the help tip doesn't fit into its allocated area in a split window + //scenario, then because here we place it into the other split windows as well the + //missing portions will be displayed in the other split windows to form an apparent + //single tip, albeit "under" the split lines + Point aOtherPos(pWindow->ScreenToOutputPixel(pWin->OutputToScreenPixel(aHintPos))); + std::unique_ptr<ScOverlayHint> pOtherOverlay(new ScOverlayHint(aTitle, aMessage, aCommentColor, pFrameWin->GetFont())); + Point aFooPos(pWindow->PixelToLogic(aOtherPos, pWindow->GetDrawMapMode())); + pOtherOverlay->SetPos(aFooPos, pWindow->GetDrawMapMode()); + xOverlayManager->add(*pOtherOverlay); + mxInputHintOO->append(std::move(pOtherOverlay)); + } + } + } + } + + // list drop-down button + if ( pData && pData->HasSelectionList() ) + { + aListValPos.Set( aViewData.GetCurX(), aViewData.GetCurY(), aViewData.GetTabNo() ); + bListValButton = true; + } + } + + for (VclPtr<ScGridWindow> const & pWin : pGridWin) + { + if (pWin && pWin->IsVisible()) + pWin->UpdateListValPos(bListValButton, aListValPos); + } +} + +bool ScTabView::HasHintWindow() const { return mxInputHintOO != nullptr; } + +void ScTabView::RemoveHintWindow() +{ + mxInputHintOO.reset(); +} + +// find window that should not be over the cursor +static weld::Window* lcl_GetCareWin(SfxViewFrame& rViewFrm) +{ + //! also spelling ??? (then set the member variables when calling) + + // search & replace + if (rViewFrm.HasChildWindow(SID_SEARCH_DLG)) + { + SfxChildWindow* pChild = rViewFrm.GetChildWindow(SID_SEARCH_DLG); + if (pChild) + { + auto xDlgController = pChild->GetController(); + if (xDlgController && xDlgController->getDialog()->get_visible()) + return xDlgController->getDialog(); + } + } + + // apply changes + if ( rViewFrm.HasChildWindow(FID_CHG_ACCEPT) ) + { + SfxChildWindow* pChild = rViewFrm.GetChildWindow(FID_CHG_ACCEPT); + if (pChild) + { + auto xDlgController = pChild->GetController(); + if (xDlgController && xDlgController->getDialog()->get_visible()) + return xDlgController->getDialog(); + } + } + + return nullptr; +} + + // adjust screen with respect to cursor position + +void ScTabView::AlignToCursor( SCCOL nCurX, SCROW nCurY, ScFollowMode eMode, + const ScSplitPos* pWhich ) +{ + // now switch active part here + + ScSplitPos eActive = aViewData.GetActivePart(); + ScHSplitPos eActiveX = WhichH(eActive); + ScVSplitPos eActiveY = WhichV(eActive); + bool bHFix = (aViewData.GetHSplitMode() == SC_SPLIT_FIX); + bool bVFix = (aViewData.GetVSplitMode() == SC_SPLIT_FIX); + if (bHFix && eActiveX == SC_SPLIT_LEFT && nCurX >= aViewData.GetFixPosX()) + { + ActivatePart( (eActiveY==SC_SPLIT_TOP) ? SC_SPLIT_TOPRIGHT : SC_SPLIT_BOTTOMRIGHT ); + eActiveX = SC_SPLIT_RIGHT; + } + if (bVFix && eActiveY == SC_SPLIT_TOP && nCurY >= aViewData.GetFixPosY()) + { + ActivatePart( (eActiveX==SC_SPLIT_LEFT) ? SC_SPLIT_BOTTOMLEFT : SC_SPLIT_BOTTOMRIGHT ); + eActiveY = SC_SPLIT_BOTTOM; + } + + // actual align + + if ( eMode != SC_FOLLOW_NONE ) + { + ScSplitPos eAlign; + if (pWhich) + eAlign = *pWhich; + else + eAlign = aViewData.GetActivePart(); + ScHSplitPos eAlignX = WhichH(eAlign); + ScVSplitPos eAlignY = WhichV(eAlign); + + SCCOL nDeltaX = aViewData.GetPosX(eAlignX); + SCROW nDeltaY = aViewData.GetPosY(eAlignY); + SCCOL nSizeX = aViewData.VisibleCellsX(eAlignX); + SCROW nSizeY = aViewData.VisibleCellsY(eAlignY); + + tools::Long nCellSizeX; + tools::Long nCellSizeY; + if ( nCurX >= 0 && nCurY >= 0 ) + aViewData.GetMergeSizePixel( nCurX, nCurY, nCellSizeX, nCellSizeY ); + else + nCellSizeX = nCellSizeY = 0; + Size aScrSize = aViewData.GetScrSize(); + + tools::Long nDenom; + if ( eMode == SC_FOLLOW_JUMP_END && nCurX > aViewData.GetRefStartX() + && nCurY > aViewData.GetRefStartY() ) + nDenom = 1; // tdf#154271 Selected cell will be at the bottom corner + // to maximize the visible/usable area + else + nDenom = 2; // Selected cell will be at the center of the screen, so that + // it will be visible. This is useful for search results, etc. + tools::Long nSpaceX = ( aScrSize.Width() - nCellSizeX ) / nDenom; + tools::Long nSpaceY = ( aScrSize.Height() - nCellSizeY ) / nDenom; + // nSpaceY: desired start position of cell for FOLLOW_JUMP, modified if dialog interferes + + bool bForceNew = false; // force new calculation of JUMP position (vertical only) + + // VisibleCellsY == CellsAtY( GetPosY( eWhichY ), 1, eWhichY ) + + // when for instance a search dialog is open, don't put the cursor behind the dialog + // if possible, put the row with the cursor above or below the dialog + //! not if already completely visible + + if ( eMode == SC_FOLLOW_JUMP || eMode == SC_FOLLOW_JUMP_END ) + { + weld::Window* pCare = lcl_GetCareWin( aViewData.GetViewShell()->GetViewFrame() ); + if (pCare) + { + bool bLimit = false; + tools::Rectangle aDlgPixel; + Size aWinSize; + vcl::Window* pWin = GetActiveWin(); + weld::Window* pFrame = pWin ? pWin->GetFrameWeld() : nullptr; + int x, y, width, height; + if (pFrame && pCare->get_extents_relative_to(*pFrame, x, y, width, height)) + { + aDlgPixel = tools::Rectangle(Point(x, y), Size(width, height)); + aWinSize = pWin->GetOutputSizePixel(); + // dos the dialog cover the GridWin? + if ( aDlgPixel.Right() >= 0 && aDlgPixel.Left() < aWinSize.Width() ) + { + if ( nCurX < nDeltaX || nCurX >= nDeltaX+nSizeX || + nCurY < nDeltaY || nCurY >= nDeltaY+nSizeY ) + bLimit = true; // scroll anyway + else + { + // cursor is on the screen + Point aStart = aViewData.GetScrPos( nCurX, nCurY, eAlign ); + tools::Long nCSX, nCSY; + aViewData.GetMergeSizePixel( nCurX, nCurY, nCSX, nCSY ); + tools::Rectangle aCursor( aStart, Size( nCSX, nCSY ) ); + if ( aCursor.Overlaps( aDlgPixel ) ) + bLimit = true; // cell is covered by the dialog + } + } + } + + if (bLimit) + { + bool bBottom = false; + tools::Long nTopSpace = aDlgPixel.Top(); + tools::Long nBotSpace = aWinSize.Height() - aDlgPixel.Bottom(); + if ( nBotSpace > 0 && nBotSpace > nTopSpace ) + { + tools::Long nDlgBot = aDlgPixel.Bottom(); + SCCOL nWPosX; + SCROW nWPosY; + aViewData.GetPosFromPixel( 0,nDlgBot, eAlign, nWPosX, nWPosY ); + ++nWPosY; // below the last affected cell + + SCROW nDiff = nWPosY - nDeltaY; + if ( nCurY >= nDiff ) // position can not be negative + { + nSpaceY = nDlgBot + ( nBotSpace - nCellSizeY ) / 2; + bBottom = true; + bForceNew = true; + } + } + if ( !bBottom && nTopSpace > 0 ) + { + nSpaceY = ( nTopSpace - nCellSizeY ) / 2; + bForceNew = true; + } + } + } + } + + SCCOL nNewDeltaX = nDeltaX; + SCROW nNewDeltaY = nDeltaY; + bool bDoLine = false; + + switch (eMode) + { + case SC_FOLLOW_JUMP: + case SC_FOLLOW_JUMP_END: + if ( nCurX < nDeltaX || nCurX >= nDeltaX+nSizeX ) + { + nNewDeltaX = nCurX - aViewData.CellsAtX( nCurX, -1, eAlignX, static_cast<sal_uInt16>(nSpaceX) ); + if (nNewDeltaX < 0) + nNewDeltaX = 0; + nSizeX = aViewData.CellsAtX( nNewDeltaX, 1, eAlignX ); + } + if ( nCurY < nDeltaY || nCurY >= nDeltaY+nSizeY || bForceNew ) + { + nNewDeltaY = nCurY - aViewData.CellsAtY( nCurY, -1, eAlignY, static_cast<sal_uInt16>(nSpaceY) ); + if (nNewDeltaY < 0) + nNewDeltaY = 0; + nSizeY = aViewData.CellsAtY( nNewDeltaY, 1, eAlignY ); + } + bDoLine = true; + break; + + case SC_FOLLOW_LINE: + bDoLine = true; + break; + + case SC_FOLLOW_FIX: + if ( nCurX < nDeltaX || nCurX >= nDeltaX+nSizeX ) + { + nNewDeltaX = nDeltaX + nCurX - aViewData.GetCurX(); + if (nNewDeltaX < 0) + nNewDeltaX = 0; + nSizeX = aViewData.CellsAtX( nNewDeltaX, 1, eAlignX ); + } + if ( nCurY < nDeltaY || nCurY >= nDeltaY+nSizeY ) + { + nNewDeltaY = nDeltaY + nCurY - aViewData.GetCurY(); + if (nNewDeltaY < 0) + nNewDeltaY = 0; + nSizeY = aViewData.CellsAtY( nNewDeltaY, 1, eAlignY ); + } + + // like old version of SC_FOLLOW_JUMP: + + if ( nCurX < nNewDeltaX || nCurX >= nNewDeltaX+nSizeX ) + { + nNewDeltaX = nCurX - (nSizeX / 2); + if (nNewDeltaX < 0) + nNewDeltaX = 0; + nSizeX = aViewData.CellsAtX( nNewDeltaX, 1, eAlignX ); + } + if ( nCurY < nNewDeltaY || nCurY >= nNewDeltaY+nSizeY ) + { + nNewDeltaY = nCurY - (nSizeY / 2); + if (nNewDeltaY < 0) + nNewDeltaY = 0; + nSizeY = aViewData.CellsAtY( nNewDeltaY, 1, eAlignY ); + } + + bDoLine = true; + break; + + case SC_FOLLOW_NONE: + break; + default: + OSL_FAIL("Wrong cursor mode"); + break; + } + + ScDocument& rDoc = aViewData.GetDocument(); + if (bDoLine) + { + while ( nCurX >= nNewDeltaX+nSizeX ) + { + nNewDeltaX = nCurX-nSizeX+1; + SCTAB nTab = aViewData.GetTabNo(); + while ( nNewDeltaX < rDoc.MaxCol() && !rDoc.GetColWidth( nNewDeltaX, nTab ) ) + ++nNewDeltaX; + nSizeX = aViewData.CellsAtX( nNewDeltaX, 1, eAlignX ); + } + while ( nCurY >= nNewDeltaY+nSizeY ) + { + nNewDeltaY = nCurY-nSizeY+1; + SCTAB nTab = aViewData.GetTabNo(); + while ( nNewDeltaY < rDoc.MaxRow() && !rDoc.GetRowHeight( nNewDeltaY, nTab ) ) + ++nNewDeltaY; + nSizeY = aViewData.CellsAtY( nNewDeltaY, 1, eAlignY ); + } + if ( nCurX < nNewDeltaX ) + nNewDeltaX = nCurX; + if ( nCurY < nNewDeltaY ) + nNewDeltaY = nCurY; + } + + if ( nNewDeltaX != nDeltaX ) + nSizeX = aViewData.CellsAtX( nNewDeltaX, 1, eAlignX ); + if (nNewDeltaX+nSizeX-1 > rDoc.MaxCol()) + nNewDeltaX = rDoc.MaxCol()-nSizeX+1; + if (nNewDeltaX < 0) + nNewDeltaX = 0; + + if ( nNewDeltaY != nDeltaY ) + nSizeY = aViewData.CellsAtY( nNewDeltaY, 1, eAlignY ); + if (nNewDeltaY+nSizeY-1 > rDoc.MaxRow()) + nNewDeltaY = rDoc.MaxRow()-nSizeY+1; + if (nNewDeltaY < 0) + nNewDeltaY = 0; + + if ( nNewDeltaX != nDeltaX ) + ScrollX( nNewDeltaX - nDeltaX, eAlignX ); + if ( nNewDeltaY != nDeltaY ) + ScrollY( nNewDeltaY - nDeltaY, eAlignY ); + } + + // switch active part again + + if (bHFix) + if (eActiveX == SC_SPLIT_RIGHT && nCurX < aViewData.GetFixPosX()) + { + ActivatePart( (eActiveY==SC_SPLIT_TOP) ? SC_SPLIT_TOPLEFT : SC_SPLIT_BOTTOMLEFT ); + eActiveX = SC_SPLIT_LEFT; + } + if (bVFix) + if (eActiveY == SC_SPLIT_BOTTOM && nCurY < aViewData.GetFixPosY()) + { + ActivatePart( (eActiveX==SC_SPLIT_LEFT) ? SC_SPLIT_TOPLEFT : SC_SPLIT_TOPRIGHT ); + } +} + +bool ScTabView::SelMouseButtonDown( const MouseEvent& rMEvt ) +{ + bool bRet = false; + + // #i3875# *Hack* + bool bMod1Locked = (aViewData.GetViewShell()->GetLockedModifiers() & KEY_MOD1) != 0; + aViewData.SetSelCtrlMouseClick( rMEvt.IsMod1() || bMod1Locked ); + + if ( pSelEngine ) + { + bMoveIsShift = rMEvt.IsShift(); + bRet = pSelEngine->SelMouseButtonDown( rMEvt ); + bMoveIsShift = false; + } + + aViewData.SetSelCtrlMouseClick( false ); // #i3875# *Hack* + + return bRet; +} + + // MoveCursor - with adjustment of the view section + +void ScTabView::MoveCursorAbs( SCCOL nCurX, SCROW nCurY, ScFollowMode eMode, + bool bShift, bool bControl, bool bKeepOld, bool bKeepSel ) +{ + if (!bKeepOld) + aViewData.ResetOldCursor(); + + ScDocument& rDoc = aViewData.GetDocument(); + // #i123629# + if( aViewData.GetViewShell()->GetForceFocusOnCurCell() ) + aViewData.GetViewShell()->SetForceFocusOnCurCell( !rDoc.ValidColRow(nCurX, nCurY) ); + + if (nCurX < 0) nCurX = 0; + if (nCurY < 0) nCurY = 0; + if (nCurX > rDoc.MaxCol()) nCurX = rDoc.MaxCol(); + if (nCurY > rDoc.MaxRow()) nCurY = rDoc.MaxRow(); + + // 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()) + nCurY = std::min(nCurY, MAXTILEDROW); + + HideAllCursors(); + + // switch of active now in AlignToCursor + + AlignToCursor( nCurX, nCurY, eMode ); + + if (bKeepSel) + { + SetCursor( nCurX, nCurY ); // keep selection + + // If the cursor is in existing selection, it's a cursor movement by + // ENTER or TAB. If not, then it's a new selection during ADD + // selection mode. + + const ScMarkData& rMark = aViewData.GetMarkData(); + ScRangeList aSelList; + rMark.FillRangeListWithMarks(&aSelList, false); + if (!aSelList.Contains(ScRange(nCurX, nCurY, aViewData.GetTabNo()))) + // Cursor not in existing selection. Start a new selection. + DoneBlockMode(true); + } + else + { + if (!bShift) + { + // Remove all marked data on cursor movement unless the Shift is + // locked or while editing a formula. It is cheaper to check for + // marks first and then formula mode. + ScMarkData& rMark = aViewData.GetMarkData(); + bool bMarked = rMark.IsMarked() || rMark.IsMultiMarked(); + if (bMarked && !SC_MOD()->IsFormulaMode()) + { + rMark.ResetMark(); + DoneBlockMode(); + InitOwnBlockMode( ScRange( nCurX, nCurY, aViewData.GetTabNo())); + MarkDataChanged(); + } + } + + bool bSame = ( nCurX == aViewData.GetCurX() && nCurY == aViewData.GetCurY() ); + bMoveIsShift = bShift; + pSelEngine->CursorPosChanging( bShift, bControl ); + bMoveIsShift = false; + aFunctionSet.SetCursorAtCell( nCurX, nCurY, false ); + + // If the cursor has not been moved, the SelectionChanged for canceling the + // selection has to happen here individually: + if (bSame) + SelectionChanged(); + } + + ShowAllCursors(); + TestHintWindow(); +} + +void ScTabView::MoveCursorRel( SCCOL nMovX, SCROW nMovY, ScFollowMode eMode, + bool bShift, bool bKeepSel ) +{ + ScDocument& rDoc = aViewData.GetDocument(); + SCTAB nTab = aViewData.GetTabNo(); + + bool bSkipProtected = false, bSkipUnprotected = false; + const ScTableProtection* pProtect = rDoc.GetTabProtection(nTab); + if ( pProtect && pProtect->isProtected() ) + { + bSkipProtected = !pProtect->isOptionEnabled(ScTableProtection::SELECT_LOCKED_CELLS); + bSkipUnprotected = !pProtect->isOptionEnabled(ScTableProtection::SELECT_UNLOCKED_CELLS); + } + + if ( bSkipProtected && bSkipUnprotected ) + return; + + SCCOL nOldX; + SCROW nOldY; + SCCOL nCurX; + SCROW nCurY; + if ( aViewData.IsRefMode() ) + { + nOldX = aViewData.GetRefEndX(); + nOldY = aViewData.GetRefEndY(); + nCurX = nOldX + nMovX; + nCurY = nOldY + nMovY; + } + else + { + nOldX = aViewData.GetCurX(); + nOldY = aViewData.GetCurY(); + nCurX = (nMovX != 0) ? nOldX+nMovX : aViewData.GetOldCurX(); + nCurY = (nMovY != 0) ? nOldY+nMovY : aViewData.GetOldCurY(); + } + + if (nMovX < 0 && nOldX == 0) + { // trying to go left from 1st column + if (nMovY == 0) // done, because no vertical move is requested + return; + } + if (nMovY < 0 && nOldY == 0) + { // trying to go up from 1st row + if (nMovX == 0) // done, because no horizontal move is requested + return; + } + + aViewData.ResetOldCursor(); + + if (nMovX != 0 && rDoc.ValidColRow(nCurX,nCurY)) + SkipCursorHorizontal(nCurX, nCurY, nOldX, nMovX); + + if (nMovY != 0 && rDoc.ValidColRow(nCurX,nCurY)) + SkipCursorVertical(nCurX, nCurY, nOldY, nMovY); + + MoveCursorAbs( nCurX, nCurY, eMode, bShift, false, true, bKeepSel ); +} + +void ScTabView::MoveCursorPage( SCCOL nMovX, SCROW nMovY, ScFollowMode eMode, bool bShift, bool bKeepSel ) +{ + SCCOL nPageX; + SCROW nPageY; + GetPageMoveEndPosition(nMovX, nMovY, nPageX, nPageY); + MoveCursorRel( nPageX, nPageY, eMode, bShift, bKeepSel ); +} + +void ScTabView::MoveCursorArea( SCCOL nMovX, SCROW nMovY, ScFollowMode eMode, bool bShift, bool bKeepSel, bool bInteractiveByUser ) +{ + SCCOL nNewX; + SCROW nNewY; + GetAreaMoveEndPosition(nMovX, nMovY, eMode, nNewX, nNewY, eMode, bInteractiveByUser); + MoveCursorRel(nNewX, nNewY, eMode, bShift, bKeepSel); +} + +void ScTabView::MoveCursorEnd( SCCOL nMovX, SCROW nMovY, ScFollowMode eMode, bool bShift, bool bKeepSel ) +{ + ScDocument& rDoc = aViewData.GetDocument(); + SCTAB nTab = aViewData.GetTabNo(); + + SCCOL nCurX; + SCROW nCurY; + aViewData.GetMoveCursor( nCurX,nCurY ); + SCCOL nNewX = nCurX; + SCROW nNewY = nCurY; + + SCCOL nUsedX = 0; + SCROW nUsedY = 0; + if ( nMovX > 0 || nMovY > 0 ) + rDoc.GetPrintArea( nTab, nUsedX, nUsedY ); // get end + + if (nMovX<0) + nNewX=0; + else if (nMovX>0) + nNewX=nUsedX; // last used range + + if (nMovY<0) + nNewY=0; + else if (nMovY>0) + nNewY=nUsedY; + + aViewData.ResetOldCursor(); + MoveCursorRel( nNewX-nCurX, nNewY-nCurY, eMode, bShift, bKeepSel ); +} + +void ScTabView::MoveCursorScreen( SCCOL nMovX, SCROW nMovY, ScFollowMode eMode, bool bShift ) +{ + ScDocument& rDoc = aViewData.GetDocument(); + SCTAB nTab = aViewData.GetTabNo(); + + SCCOL nCurX; + SCROW nCurY; + aViewData.GetMoveCursor( nCurX,nCurY ); + SCCOL nNewX = nCurX; + SCROW nNewY = nCurY; + + ScSplitPos eWhich = aViewData.GetActivePart(); + SCCOL nPosX = aViewData.GetPosX( WhichH(eWhich) ); + SCROW nPosY = aViewData.GetPosY( WhichV(eWhich) ); + + SCCOL nAddX = aViewData.VisibleCellsX( WhichH(eWhich) ); + if (nAddX != 0) + --nAddX; + SCROW nAddY = aViewData.VisibleCellsY( WhichV(eWhich) ); + if (nAddY != 0) + --nAddY; + + if (nMovX<0) + nNewX=nPosX; + else if (nMovX>0) + nNewX=nPosX+nAddX; + + if (nMovY<0) + nNewY=nPosY; + else if (nMovY>0) + nNewY=nPosY+nAddY; + + aViewData.SetOldCursor( nNewX,nNewY ); + rDoc.SkipOverlapped(nNewX, nNewY, nTab); + MoveCursorAbs( nNewX, nNewY, eMode, bShift, false, true ); +} + +void ScTabView::MoveCursorEnter( bool bShift ) // bShift -> up/down +{ + const ScInputOptions& rOpt = SC_MOD()->GetInputOptions(); + if (!rOpt.GetMoveSelection()) + { + aViewData.UpdateInputHandler(true); + return; + } + + SCCOL nMoveX = 0; + SCROW nMoveY = 0; + switch (static_cast<ScDirection>(rOpt.GetMoveDir())) + { + case DIR_BOTTOM: + nMoveY = bShift ? -1 : 1; + break; + case DIR_RIGHT: + nMoveX = bShift ? -1 : 1; + break; + case DIR_TOP: + nMoveY = bShift ? 1 : -1; + break; + case DIR_LEFT: + nMoveX = bShift ? 1 : -1; + break; + } + + SCCOL nCurX; + SCROW nCurY; + aViewData.GetMoveCursor( nCurX,nCurY ); + SCCOL nNewX = nCurX; + SCROW nNewY = nCurY; + SCTAB nTab = aViewData.GetTabNo(); + + ScMarkData& rMark = aViewData.GetMarkData(); + ScDocument& rDoc = aViewData.GetDocument(); + + if (rMark.IsMarked() || rMark.IsMultiMarked()) + { + rDoc.GetNextPos( nNewX, nNewY, nTab, nMoveX, nMoveY, true, false, rMark ); + + MoveCursorRel( nNewX - nCurX, nNewY - nCurY, SC_FOLLOW_LINE, false, true ); + + // update input line even if cursor was not moved + if ( nNewX == nCurX && nNewY == nCurY ) + aViewData.UpdateInputHandler(true); + } + else + { + // After Tab and Enter back to the starting column again. + const SCCOL nTabStartCol = ((nMoveY != 0 && !nMoveX) ? aViewData.GetTabStartCol() : SC_TABSTART_NONE); + rDoc.GetNextPos( nNewX, nNewY, nTab, nMoveX, nMoveY, false, true, rMark, nTabStartCol ); + + MoveCursorRel( nNewX - nCurX, nNewY - nCurY, SC_FOLLOW_LINE, false); + } +} + +bool ScTabView::MoveCursorKeyInput( const KeyEvent& rKeyEvent ) +{ + const vcl::KeyCode& rKCode = rKeyEvent.GetKeyCode(); + + enum { MOD_NONE, MOD_CTRL, MOD_ALT, MOD_BOTH } eModifier = + rKCode.IsMod1() ? + (rKCode.IsMod2() ? MOD_BOTH : MOD_CTRL) : + (rKCode.IsMod2() ? MOD_ALT : MOD_NONE); + + bool bSel = rKCode.IsShift(); + sal_uInt16 nCode = rKCode.GetCode(); + + // CURSOR keys + SCCOL nDX = 0; + SCROW nDY = 0; + switch( nCode ) + { + case KEY_LEFT: nDX = -1; break; + case KEY_RIGHT: nDX = 1; break; + case KEY_UP: nDY = -1; break; + case KEY_DOWN: nDY = 1; break; + } + if( nDX != 0 || nDY != 0 ) + { + switch( eModifier ) + { + case MOD_NONE: MoveCursorRel( nDX, nDY, SC_FOLLOW_LINE, bSel ); break; + case MOD_CTRL: MoveCursorArea( nDX, nDY, SC_FOLLOW_JUMP, bSel ); break; + default: + { + // added to avoid warnings + } + } + // always true to suppress changes of col/row size (ALT+CURSOR) + return true; + } + + // PAGEUP/PAGEDOWN + if( (nCode == KEY_PAGEUP) || (nCode == KEY_PAGEDOWN) ) + { + nDX = (nCode == KEY_PAGEUP) ? -1 : 1; + switch( eModifier ) + { + case MOD_NONE: MoveCursorPage( 0, static_cast<SCCOLROW>(nDX), SC_FOLLOW_FIX, bSel ); break; + case MOD_ALT: MoveCursorPage( nDX, 0, SC_FOLLOW_FIX, bSel ); break; + case MOD_CTRL: SelectNextTab( nDX, false ); break; + default: + { + // added to avoid warnings + } + } + return true; + } + + // HOME/END + if( (nCode == KEY_HOME) || (nCode == KEY_END) ) + { + nDX = (nCode == KEY_HOME) ? -1 : 1; + ScFollowMode eMode = (nCode == KEY_HOME) ? SC_FOLLOW_LINE : SC_FOLLOW_JUMP_END; + switch( eModifier ) + { + case MOD_NONE: MoveCursorEnd( nDX, 0, eMode, bSel ); break; + case MOD_CTRL: MoveCursorEnd( nDX, static_cast<SCCOLROW>(nDX), eMode, bSel ); break; + default: + { + // added to avoid warnings + } + } + return true; + } + + return false; +} + + // next/previous unprotected cell +void ScTabView::FindNextUnprot( bool bShift, bool bInSelection ) +{ + short nMove = bShift ? -1 : 1; + + ScMarkData& rMark = aViewData.GetMarkData(); + bool bMarked = bInSelection && (rMark.IsMarked() || rMark.IsMultiMarked()); + + SCCOL nCurX; + SCROW nCurY; + aViewData.GetMoveCursor( nCurX,nCurY ); + SCCOL nNewX = nCurX; + SCROW nNewY = nCurY; + SCTAB nTab = aViewData.GetTabNo(); + + ScDocument& rDoc = aViewData.GetDocument(); + rDoc.GetNextPos( nNewX,nNewY, nTab, nMove,0, bMarked, true, rMark ); + + SCCOL nTabCol = aViewData.GetTabStartCol(); + if ( nTabCol == SC_TABSTART_NONE ) + nTabCol = nCurX; // back to this column after Enter + + MoveCursorRel( nNewX-nCurX, nNewY-nCurY, SC_FOLLOW_LINE, false, true ); + + // TabCol is reset in MoveCursorRel... + aViewData.SetTabStartCol( nTabCol ); +} + +void ScTabView::MarkColumns() +{ + SCCOL nStartCol; + SCCOL nEndCol; + + ScMarkData& rMark = aViewData.GetMarkData(); + if (rMark.IsMarked()) + { + const ScRange& aMarkRange = rMark.GetMarkArea(); + nStartCol = aMarkRange.aStart.Col(); + nEndCol = aMarkRange.aEnd.Col(); + } + else + { + SCROW nDummy; + aViewData.GetMoveCursor( nStartCol, nDummy ); + nEndCol=nStartCol; + } + + SCTAB nTab = aViewData.GetTabNo(); + ScDocument& rDoc = aViewData.GetDocument(); + DoneBlockMode(); + InitBlockMode( nStartCol,0, nTab ); + MarkCursor( nEndCol, rDoc.MaxRow(), nTab ); + SelectionChanged(); +} + +void ScTabView::MarkRows() +{ + SCROW nStartRow; + SCROW nEndRow; + + ScMarkData& rMark = aViewData.GetMarkData(); + if (rMark.IsMarked()) + { + const ScRange& aMarkRange = rMark.GetMarkArea(); + nStartRow = aMarkRange.aStart.Row(); + nEndRow = aMarkRange.aEnd.Row(); + } + else + { + SCCOL nDummy; + aViewData.GetMoveCursor( nDummy, nStartRow ); + nEndRow=nStartRow; + } + + SCTAB nTab = aViewData.GetTabNo(); + ScDocument& rDoc = aViewData.GetDocument(); + DoneBlockMode(); + InitBlockMode( 0,nStartRow, nTab ); + MarkCursor( rDoc.MaxCol(), nEndRow, nTab ); + SelectionChanged(); +} + + +void ScTabView::MarkColumns(SCCOL nCol, sal_Int16 nModifier) +{ + ScDocument& rDoc = aViewData.GetDocument(); + SCCOL nStartCol = nCol; + SCTAB nTab = aViewData.GetTabNo(); + + if ((nModifier & KEY_SHIFT) == KEY_SHIFT) + bMoveIsShift = true; + + if ( SC_MOD()->IsFormulaMode() ) + { + DoneRefMode( nModifier != 0 ); + InitRefMode( nCol, 0, nTab, SC_REFTYPE_REF ); + UpdateRef( nCol, rDoc.MaxRow(), nTab ); + bMoveIsShift = false; + } + else + { + DoneBlockMode( nModifier != 0 ); + InitBlockMode( nStartCol, 0, nTab, true, true); + MarkCursor( nCol, rDoc.MaxRow(), nTab ); + bMoveIsShift = false; + SetCursor( nCol, 0 ); + SelectionChanged(); + } +} + +void ScTabView::MarkRows(SCROW nRow, sal_Int16 nModifier) +{ + ScDocument& rDoc = aViewData.GetDocument(); + SCROW nStartRow = nRow; + SCTAB nTab = aViewData.GetTabNo(); + + if ((nModifier & KEY_SHIFT) == KEY_SHIFT) + bMoveIsShift = true; + + if ( SC_MOD()->IsFormulaMode() ) + { + DoneRefMode( nModifier != 0 ); + InitRefMode( 0, nRow, nTab, SC_REFTYPE_REF ); + UpdateRef( rDoc.MaxCol(), nRow, nTab ); + bMoveIsShift = false; + } + else + { + DoneBlockMode( nModifier != 0 ); + InitBlockMode( 0, nStartRow, nTab, true, false, true ); + MarkCursor( rDoc.MaxCol(), nRow, nTab ); + bMoveIsShift = false; + SetCursor( 0, nRow ); + SelectionChanged(); + } +} + +void ScTabView::HighlightOverlay() +{ + if (!officecfg::Office::Calc::Content::Display::ColumnRowHighlighting::get()) + { + aViewData.GetHighlightData().ResetMark(); + UpdateHighlightOverlay(); + return; + } + + ScAddress aCell = GetViewData().GetCurPos(); + SCROW nRow = aCell.Row(); + SCCOL nCol = aCell.Col(); + + bool nModifier = false; // modifier key pressed? + DoneBlockModeHighlight( nModifier ); + InitBlockModeHighlight( nCol, 0, aCell.Tab(), true, false); + nModifier = true; + DoneBlockModeHighlight( nModifier ); + InitBlockModeHighlight( 0, nRow, aCell.Tab(), false, true ); +} + +void ScTabView::MarkDataArea( bool bIncludeCursor ) +{ + ScDocument& rDoc = aViewData.GetDocument(); + SCTAB nTab = aViewData.GetTabNo(); + SCCOL nStartCol = aViewData.GetCurX(); + SCROW nStartRow = aViewData.GetCurY(); + SCCOL nEndCol = nStartCol; + SCROW nEndRow = nStartRow; + + rDoc.GetDataArea( nTab, nStartCol, nStartRow, nEndCol, nEndRow, bIncludeCursor, false ); + + HideAllCursors(); + DoneBlockMode(); + InitBlockMode( nStartCol, nStartRow, nTab ); + MarkCursor( nEndCol, nEndRow, nTab ); + ShowAllCursors(); + + SelectionChanged(); +} + +void ScTabView::MarkMatrixFormula() +{ + ScDocument& rDoc = aViewData.GetDocument(); + ScAddress aCursor( aViewData.GetCurX(), aViewData.GetCurY(), aViewData.GetTabNo() ); + ScRange aMatrix; + if ( rDoc.GetMatrixFormulaRange( aCursor, aMatrix ) ) + { + MarkRange( aMatrix, false ); // cursor is already within the range + } +} + +void ScTabView::MarkRange( const ScRange& rRange, bool bSetCursor, bool bContinue ) +{ + ScDocument& rDoc = aViewData.GetDocument(); + SCTAB nTab = rRange.aStart.Tab(); + SetTabNo( nTab ); + + HideAllCursors(); + DoneBlockMode( bContinue ); // bContinue==true -> clear old mark + if (bSetCursor) // if Cursor is set, also always align + { + SCCOL nAlignX = rRange.aStart.Col(); + SCROW nAlignY = rRange.aStart.Row(); + bool bCol = ( rRange.aStart.Col() == 0 && rRange.aEnd.Col() == rDoc.MaxCol() ) && !aViewData.GetDocument().IsInVBAMode(); + bool bRow = ( rRange.aStart.Row() == 0 && rRange.aEnd.Row() == rDoc.MaxRow() ); + if ( bCol ) + nAlignX = aViewData.GetPosX(WhichH(aViewData.GetActivePart())); + if ( bRow ) + nAlignY = aViewData.GetPosY(WhichV(aViewData.GetActivePart())); + AlignToCursor( nAlignX, nAlignY, SC_FOLLOW_JUMP ); + } + InitBlockMode( rRange.aStart.Col(), rRange.aStart.Row(), nTab ); + MarkCursor( rRange.aEnd.Col(), rRange.aEnd.Row(), nTab ); + if (bSetCursor) + { + SCCOL nPosX = rRange.aStart.Col(); + SCROW nPosY = rRange.aStart.Row(); + rDoc.SkipOverlapped(nPosX, nPosY, nTab); + + aViewData.ResetOldCursor(); + SetCursor( nPosX, nPosY ); + } + ShowAllCursors(); + + SelectionChanged(); +} + +void ScTabView::Unmark() +{ + ScMarkData& rMark = aViewData.GetMarkData(); + if ( rMark.IsMarked() || rMark.IsMultiMarked() ) + { + SCCOL nCurX; + SCROW nCurY; + aViewData.GetMoveCursor( nCurX,nCurY ); + MoveCursorAbs( nCurX, nCurY, SC_FOLLOW_NONE, false, false ); + + SelectionChanged(); + } +} + +void ScTabView::SetMarkData( const ScMarkData& rNew ) +{ + DoneBlockMode(); + InitOwnBlockMode( rNew.GetMarkArea()); + aViewData.GetMarkData() = rNew; + + MarkDataChanged(); +} + +void ScTabView::MarkDataChanged() +{ + // has to be called after making direct changes to mark data (not via MarkCursor etc) + + UpdateSelectionOverlay(); +} + +void ScTabView::SelectNextTab( short nDir, bool bExtendSelection ) +{ + if (!nDir) + return; + OSL_ENSURE( nDir==-1 || nDir==1, "SelectNextTab: invalid value"); + + ScDocument& rDoc = aViewData.GetDocument(); + SCTAB nTab = aViewData.GetTabNo(); + SCTAB nNextTab = nTab; + if (nDir < 0) + { + do + { + --nNextTab; + if (nNextTab < 0) + nNextTab = rDoc.GetTableCount(); + if (rDoc.IsVisible(nNextTab)) + break; + } while (nNextTab != nTab); + } + if (nDir > 0) + { + SCTAB nCount = rDoc.GetTableCount(); + do + { + ++nNextTab; + if (nNextTab >= nCount) + nNextTab = 0; + if (rDoc.IsVisible(nNextTab)) + break; + } while (nNextTab != nTab); + } + if (nNextTab == nTab) + return; + + SetTabNo(nNextTab, false, bExtendSelection); + PaintExtras(); +} + +void ScTabView::SelectTabPage( const sal_uInt16 nTab ) +{ + pTabControl->SwitchToPageId( nTab ); +} + +// SetTabNo - set the displayed sheet + +void ScTabView::SetTabNo( SCTAB nTab, bool bNew, bool bExtendSelection, bool bSameTabButMoved ) +{ + if ( !ValidTab(nTab) ) + { + OSL_FAIL("SetTabNo: invalid sheet"); + return; + } + + if (!bNew && nTab == aViewData.GetTabNo()) + return; + + // FormShell would like to be informed before the switch + FmFormShell* pFormSh = aViewData.GetViewShell()->GetFormShell(); + if (pFormSh) + { + bool bAllowed = pFormSh->PrepareClose(); + if (!bAllowed) + { + //! error message? or does FormShell do it? + //! return error flag and cancel actions + + return; // FormShell says that it can not be switched + } + } + + // not InputEnterHandler due to reference input + + ScDocument& rDoc = aViewData.GetDocument(); + + rDoc.MakeTable( nTab ); + + // Update pending row heights before switching the sheet, so Reschedule from the progress bar + // doesn't paint the new sheet with old heights + aViewData.GetDocShell()->UpdatePendingRowHeights( nTab ); + + SCTAB nTabCount = rDoc.GetTableCount(); + SCTAB nOldPos = nTab; + while (!rDoc.IsVisible(nTab)) // search for next visible + { + bool bUp = (nTab>=nOldPos); + if (bUp) + { + ++nTab; + if (nTab>=nTabCount) + { + nTab = nOldPos; + bUp = false; + } + } + + if (!bUp) + { + if (nTab != 0) + --nTab; + else + { + OSL_FAIL("no visible sheets"); + rDoc.SetVisible( 0, true ); + } + } + } + + // #i71490# Deselect drawing objects before changing the sheet number in view data, + // so the handling of notes still has the sheet selected on which the notes are. + DrawDeselectAll(); + + ScModule* pScMod = SC_MOD(); + bool bRefMode = pScMod->IsFormulaMode(); + if ( !bRefMode ) // query, so that RefMode works when switching sheet + { + DoneBlockMode(); + pSelEngine->Reset(); // reset all flags, including locked modifiers + aViewData.SetRefTabNo( nTab ); + } + + ScSplitPos eOldActive = aViewData.GetActivePart(); // before switching + bool bFocus = pGridWin[eOldActive] && pGridWin[eOldActive]->HasFocus(); + + aViewData.SetTabNo( nTab ); + if (mpSpellCheckCxt) + mpSpellCheckCxt->setTabNo( nTab ); + // UpdateShow before SetCursor, so that UpdateAutoFillMark finds the correct + // window (is called from SetCursor) + UpdateShow(); + aViewData.GetView()->TestHintWindow(); + + SfxBindings& rBindings = aViewData.GetBindings(); + ScMarkData& rMark = aViewData.GetMarkData(); + + bool bAllSelected = true; + for (SCTAB nSelTab = 0; nSelTab < nTabCount; ++nSelTab) + { + if (!rDoc.IsVisible(nSelTab) || rMark.GetTableSelect(nSelTab)) + { + if (nTab == nSelTab) + // This tab is already in selection. Keep the current + // selection. + bExtendSelection = true; + } + else + { + bAllSelected = false; + if (bExtendSelection) + // We got what we need. No need to stay in the loop. + break; + } + } + if (bAllSelected && !bNew) + // #i6327# if all tables are selected, a selection event (#i6330#) will deselect all + // (not if called with bNew to update settings) + bExtendSelection = false; + + if (bExtendSelection) + rMark.SelectTable( nTab, true ); + else + { + rMark.SelectOneTable( nTab ); + rBindings.Invalidate( FID_FILL_TAB ); + rBindings.Invalidate( FID_TAB_DESELECTALL ); + } + + bool bUnoRefDialog = pScMod->IsRefDialogOpen() && pScMod->GetCurRefDlgId() == WID_SIMPLE_REF; + + // recalc zoom-dependent values (before TabChanged, before UpdateEditViewPos) + RefreshZoom(); + UpdateVarZoom(); + + if ( bRefMode ) // hide EditView if necessary (after aViewData.SetTabNo !) + { + for (VclPtr<ScGridWindow> & pWin : pGridWin) + { + if (pWin && pWin->IsVisible()) + pWin->UpdateEditViewPos(); + } + } + + TabChanged(bSameTabButMoved); // DrawView + collectUIInformation({{"TABLE", OUString::number(nTab)}}); + UpdateVisibleRange(); + + aViewData.GetViewShell()->WindowChanged(); // if the active window has changed + aViewData.ResetOldCursor(); + SetCursor( aViewData.GetCurX(), aViewData.GetCurY(), true ); + + if ( !bUnoRefDialog ) + aViewData.GetViewShell()->DisconnectAllClients(); // important for floating frames + else + { + // hide / show inplace client + ScClient* pClient = static_cast<ScClient*>(aViewData.GetViewShell()->GetIPClient()); + if ( pClient && pClient->IsObjectInPlaceActive() ) + { + tools::Rectangle aObjArea = pClient->GetObjArea(); + if ( nTab == aViewData.GetRefTabNo() ) + { + // move to its original position + + SdrOle2Obj* pDrawObj = pClient->GetDrawObj(); + if ( pDrawObj ) + { + tools::Rectangle aRect = pDrawObj->GetLogicRect(); + MapMode aMapMode( MapUnit::Map100thMM ); + Size aOleSize = pDrawObj->GetOrigObjSize( &aMapMode ); + aRect.SetSize( aOleSize ); + aObjArea = aRect; + } + } + else + { + // move to an invisible position + + aObjArea.SetPos( Point( 0, -2*aObjArea.GetHeight() ) ); + } + pClient->SetObjArea( aObjArea ); + } + } + + if ( bFocus && aViewData.GetActivePart() != eOldActive && !bRefMode ) + ActiveGrabFocus(); // grab focus to the pane that's active now + + // freeze + + bool bResize = false; + if ( aViewData.GetHSplitMode() == SC_SPLIT_FIX ) + if (aViewData.UpdateFixX()) + bResize = true; + if ( aViewData.GetVSplitMode() == SC_SPLIT_FIX ) + if (aViewData.UpdateFixY()) + bResize = true; + if (bResize) + RepeatResize(); + InvalidateSplit(); + + if ( aViewData.IsPagebreakMode() ) + UpdatePageBreakData(); //! asynchronously ?? + + // Form Layer must know the visible area of the new sheet + // that is why MapMode must already be correct here + for (VclPtr<ScGridWindow> & pWin : pGridWin) + { + if (pWin) + pWin->SetMapMode(pWin->GetDrawMapMode()); + } + SetNewVisArea(); + + PaintGrid(); + PaintTop(); + PaintLeft(); + PaintExtras(); + + DoResize( aBorderPos, aFrameSize ); + rBindings.Invalidate( SID_DELETE_PRINTAREA ); // Menu + rBindings.Invalidate( FID_DEL_MANUALBREAKS ); + rBindings.Invalidate( FID_RESET_PRINTZOOM ); + rBindings.Invalidate( SID_STATUS_DOCPOS ); // Status bar + rBindings.Invalidate( SID_ROWCOL_SELCOUNT ); // Status bar + rBindings.Invalidate( SID_STATUS_PAGESTYLE ); // Status bar + rBindings.Invalidate( SID_CURRENTTAB ); // Navigator + rBindings.Invalidate( SID_STYLE_FAMILY2 ); // Designer + rBindings.Invalidate( SID_STYLE_FAMILY4 ); // Designer + rBindings.Invalidate( SID_TABLES_COUNT ); + + if (pScMod->IsRefDialogOpen()) + { + sal_uInt16 nCurRefDlgId=pScMod->GetCurRefDlgId(); + SfxViewFrame& rViewFrm = aViewData.GetViewShell()->GetViewFrame(); + SfxChildWindow* pChildWnd = rViewFrm.GetChildWindow( nCurRefDlgId ); + if (pChildWnd) + { + if (pChildWnd->GetController()) + { + IAnyRefDialog* pRefDlg = dynamic_cast<IAnyRefDialog*>(pChildWnd->GetController().get()); + if (pRefDlg) + pRefDlg->ViewShellChanged(); + } + } + } + + OnLibreOfficeKitTabChanged(); +} + +void ScTabView::AddWindowToForeignEditView(SfxViewShell* pViewShell, ScSplitPos eWhich) +{ + aExtraEditViewManager.Add(pViewShell, eWhich); +} + +void ScTabView::RemoveWindowFromForeignEditView(SfxViewShell* pViewShell, ScSplitPos eWhich) +{ + aExtraEditViewManager.Remove(pViewShell, eWhich); +} + +void ScTabView::OnLibreOfficeKitTabChanged() +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + ScTabViewShell* pThisViewShell = aViewData.GetViewShell(); + SCTAB nThisTabNo = pThisViewShell->GetViewData().GetTabNo(); + auto lTabSwitch = [pThisViewShell, nThisTabNo] (ScTabViewShell* pOtherViewShell) + { + ScViewData& rOtherViewData = pOtherViewShell->GetViewData(); + SCTAB nOtherTabNo = rOtherViewData.GetTabNo(); + if (nThisTabNo == nOtherTabNo) + { + for (int i = 0; i < 4; ++i) + { + if (rOtherViewData.HasEditView(ScSplitPos(i))) + { + pThisViewShell->AddWindowToForeignEditView(pOtherViewShell, ScSplitPos(i)); + } + } + } + else + { + for (int i = 0; i < 4; ++i) + { + if (rOtherViewData.HasEditView(ScSplitPos(i))) + { + pThisViewShell->RemoveWindowFromForeignEditView(pOtherViewShell, ScSplitPos(i)); + } + } + } + }; + + SfxLokHelper::forEachOtherView(pThisViewShell, lTabSwitch); + + pThisViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_INVALIDATE_HEADER, "all"_ostr); + + if (pThisViewShell->GetInputHandler()) + pThisViewShell->GetInputHandler()->UpdateLokReferenceMarks(); +} + +// paint functions - only for this View + +void ScTabView::MakeEditView( ScEditEngineDefaulter* pEngine, SCCOL nCol, SCROW nRow ) +{ + DrawDeselectAll(); + + if (pDrawView) + DrawEnableAnim( false ); + + EditView* pSpellingView = aViewData.GetSpellingView(); + + for (sal_uInt16 i = 0; i < 4; i++) + { + if (pGridWin[i] && pGridWin[i]->IsVisible() && !aViewData.HasEditView(ScSplitPos(i))) + { + ScHSplitPos eHWhich = WhichH( static_cast<ScSplitPos>(i) ); + ScVSplitPos eVWhich = WhichV( static_cast<ScSplitPos>(i) ); + SCCOL nScrX = aViewData.GetPosX( eHWhich ); + SCROW nScrY = aViewData.GetPosY( eVWhich ); + + bool bPosVisible = + ( nCol >= nScrX && nCol <= nScrX + aViewData.VisibleCellsX(eHWhich) - 1 && + nRow >= nScrY && nRow <= nScrY + aViewData.VisibleCellsY(eVWhich) - 1 ); + + // for the active part, create edit view even if outside the visible area, + // so input isn't lost (and the edit view may be scrolled into the visible area) + + // #i26433# during spelling, the spelling view must be active + if ( bPosVisible || aViewData.GetActivePart() == static_cast<ScSplitPos>(i) || + ( pSpellingView && aViewData.GetEditView(static_cast<ScSplitPos>(i)) == pSpellingView ) ) + { + pGridWin[i]->HideCursor(); + + pGridWin[i]->DeleteCursorOverlay(); + pGridWin[i]->DeleteAutoFillOverlay(); + pGridWin[i]->DeleteCopySourceOverlay(); + + // flush OverlayManager before changing MapMode to text edit + pGridWin[i]->flushOverlayManager(); + + // MapMode must be set after HideCursor + pGridWin[i]->SetMapMode(aViewData.GetLogicMode()); + + aViewData.SetEditEngine( static_cast<ScSplitPos>(i), pEngine, pGridWin[i], nCol, nRow ); + + if ( !bPosVisible ) + { + // move the edit view area to the real (possibly negative) position, + // or hide if completely above or left of the window + pGridWin[i]->UpdateEditViewPos(); + } + } + } + } + + if (aViewData.GetViewShell()->HasAccessibilityObjects()) + aViewData.GetViewShell()->BroadcastAccessibility(SfxHint(SfxHintId::ScAccEnterEditMode)); +} + +void ScTabView::UpdateEditView() +{ + if (aViewData.GetTabNo() != aViewData.GetRefTabNo() && SC_MOD()->IsFormulaMode()) + return; + + ScSplitPos eActive = aViewData.GetActivePart(); + for (sal_uInt16 i = 0; i < 4; i++) + { + ScSplitPos eCurrent = ScSplitPos(i); + if (aViewData.HasEditView(eCurrent)) + { + EditView* pEditView = aViewData.GetEditView(eCurrent); + + tools::Long nRefTabNo = GetViewData().GetRefTabNo(); + tools::Long nX = GetViewData().GetCurXForTab(nRefTabNo); + tools::Long nY = GetViewData().GetCurYForTab(nRefTabNo); + + aViewData.SetEditEngine(eCurrent, + static_cast<ScEditEngineDefaulter*>(pEditView->GetEditEngine()), + pGridWin[i], nX, nY ); + if (eCurrent == eActive) + pEditView->ShowCursor( false ); + } + } +} + +void ScTabView::KillEditView( bool bNoPaint ) +{ + SCCOL nCol1 = aViewData.GetEditStartCol(); + SCROW nRow1 = aViewData.GetEditStartRow(); + SCCOL nCol2 = aViewData.GetEditEndCol(); + SCROW nRow2 = aViewData.GetEditEndRow(); + SCTAB nTab = aViewData.GetTabNo(); + bool bPaint[4]; + bool bNotifyAcc = false; + tools::Rectangle aRectangle[4]; + + bool bExtended = nRow1 != nRow2; // column is painted to the end anyway + + bool bAtCursor = nCol1 <= aViewData.GetCurX() && + nCol2 >= aViewData.GetCurX() && + nRow1 == aViewData.GetCurY(); + for (sal_uInt16 i = 0; i < 4; i++) + { + bPaint[i] = aViewData.HasEditView( static_cast<ScSplitPos>(i) ); + if (bPaint[i]) + { + bNotifyAcc = true; + + EditView* pView = aViewData.GetEditView( static_cast<ScSplitPos>(i) ); + aRectangle[i] = pView->GetInvalidateRect(); + } + } + + // notify accessibility before all things happen + if (bNotifyAcc && aViewData.GetViewShell()->HasAccessibilityObjects()) + aViewData.GetViewShell()->BroadcastAccessibility(SfxHint(SfxHintId::ScAccLeaveEditMode)); + + aViewData.ResetEditView(); + for (sal_uInt16 i = 0; i < 4; i++) + { + if (pGridWin[i] && bPaint[i] && pGridWin[i]->IsVisible()) + { + pGridWin[i]->ShowCursor(); + + pGridWin[i]->SetMapMode(pGridWin[i]->GetDrawMapMode()); + + if (comphelper::LibreOfficeKit::isActive()) + { + const tools::Rectangle& rInvRect = aRectangle[i]; + pGridWin[i]->LogicInvalidatePart(&rInvRect, nTab); + + // invalidate other views + auto lInvalidateWindows = + [nTab, &rInvRect] (ScTabView* pTabView) + { + for (VclPtr<ScGridWindow> const & pWin: pTabView->pGridWin) + { + if (pWin) + pWin->LogicInvalidatePart(&rInvRect, nTab); + } + }; + + SfxLokHelper::forEachOtherView(GetViewData().GetViewShell(), lInvalidateWindows); + } + // #i73567# the cell still has to be repainted + else if (bExtended || ( bAtCursor && !bNoPaint )) + { + pGridWin[i]->Draw( nCol1, nRow1, nCol2, nRow2, ScUpdateMode::All ); + pGridWin[i]->UpdateSelectionOverlay(); + } + } + } + + if (pDrawView) + DrawEnableAnim( true ); + + // GrabFocus always when this View is active and + // when the input row has the focus + + bool bGrabFocus = false; + if (aViewData.IsActive()) + { + ScInputHandler* pInputHdl = SC_MOD()->GetInputHdl(); + if ( pInputHdl ) + { + ScInputWindow* pInputWin = pInputHdl->GetInputWindow(); + if (pInputWin && pInputWin->IsInputActive()) + bGrabFocus = true; + } + } + + if (bGrabFocus) + { +// should be done like this, so that Sfx notice it, but it does not work: +//! aViewData.GetViewShell()->GetViewFrame().GetWindow().GrabFocus(); +// therefore first like this: + GetActiveWin()->GrabFocus(); + } + + // cursor query only after GrabFocus + + for (sal_uInt16 i = 0; i < 4; i++) + { + if (pGridWin[i] && pGridWin[i]->IsVisible()) + { + vcl::Cursor* pCur = pGridWin[i]->GetCursor(); + if (pCur && pCur->IsVisible()) + pCur->Hide(); + + if (bPaint[i]) + { + pGridWin[i]->UpdateCursorOverlay(); + pGridWin[i]->UpdateAutoFillOverlay(); + } + } + } +} + +void ScTabView::UpdateFormulas(SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow) +{ + if ( aViewData.GetDocument().IsAutoCalcShellDisabled() ) + return; + + for (sal_uInt16 i = 0; i < 4; i++) + { + if (pGridWin[i] && pGridWin[i]->IsVisible()) + pGridWin[i]->UpdateFormulas(nStartCol, nStartRow, nEndCol, nEndRow); + } + + if ( aViewData.IsPagebreakMode() ) + UpdatePageBreakData(); //! asynchronous + + UpdateHeaderWidth(); + + // if in edit mode, adjust edit view area because widths/heights may have changed + if ( aViewData.HasEditView( aViewData.GetActivePart() ) ) + UpdateEditView(); +} + +// PaintArea - repaint block + +void ScTabView::PaintArea( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, + ScUpdateMode eMode ) +{ + SCCOL nCol1; + SCROW nRow1; + SCCOL nCol2; + SCROW nRow2; + bool bIsTiledRendering = comphelper::LibreOfficeKit::isActive(); + ScDocument& rDoc = aViewData.GetDocument(); + + PutInOrder( nStartCol, nEndCol ); + PutInOrder( nStartRow, nEndRow ); + + for (size_t i = 0; i < 4; ++i) + { + if (!pGridWin[i] || !pGridWin[i]->IsVisible()) + continue; + + ScHSplitPos eHWhich = WhichH( static_cast<ScSplitPos>(i) ); + ScVSplitPos eVWhich = WhichV( static_cast<ScSplitPos>(i) ); + bool bOut = false; + + nCol1 = nStartCol; + nRow1 = nStartRow; + nCol2 = nEndCol; + nRow2 = nEndRow; + + SCCOL nLastX = 0; + SCROW nLastY = 0; + + if (bIsTiledRendering) + { + nLastX = aViewData.GetMaxTiledCol(); + nLastY = aViewData.GetMaxTiledRow(); + } + else + { + + SCCOL nScrX = aViewData.GetPosX( eHWhich ); + SCROW nScrY = aViewData.GetPosY( eVWhich ); + + if (nCol1 < nScrX) + nCol1 = nScrX; + if (nCol2 < nScrX) + { + if ( eMode == ScUpdateMode::All ) // for UPDATE_ALL, paint anyway + nCol2 = nScrX; // (because of extending strings to the right) + else + bOut = true; // completely outside the window + } + if (nRow1 < nScrY) + nRow1 = nScrY; + if (nRow2 < nScrY) + bOut = true; + + nLastX = nScrX + aViewData.VisibleCellsX( eHWhich ) + 1; + nLastY = nScrY + aViewData.VisibleCellsY( eVWhich ) + 1; + } + + if (nCol1 > nLastX) + bOut = true; + if (nCol2 > nLastX) + nCol2 = nLastX; + if (nRow1 > nLastY) + bOut = true; + if (nRow2 > nLastY) + nRow2 = nLastY; + + if (bOut) + continue; + + bool bLayoutRTL = aViewData.GetDocument().IsLayoutRTL( aViewData.GetTabNo() ); + tools::Long nLayoutSign = (!bIsTiledRendering && bLayoutRTL) ? -1 : 1; + + Point aStart = aViewData.GetScrPos( nCol1, nRow1, static_cast<ScSplitPos>(i) ); + Point aEnd = aViewData.GetScrPos( nCol2+1, nRow2+1, static_cast<ScSplitPos>(i) ); + + if ( eMode == ScUpdateMode::All ) + { + if (bIsTiledRendering) + { + // When a cell content is deleted we have no clue about + // the width of the embedded text. + // Anyway, clients will ask only for tiles that overlaps + // the visible area. + // Remember that wsd expects int and that aEnd.X() is + // in pixels and will be converted in twips, before performing + // the lok callback, so we need to avoid that an overflow occurs. + aEnd.setX( std::numeric_limits<int>::max() / 1000 ); + } + else + { + aEnd.setX( bLayoutRTL ? 0 : pGridWin[i]->GetOutputSizePixel().Width() ); + } + } + aEnd.AdjustX( -nLayoutSign ); + aEnd.AdjustY( -1 ); + + // #i85232# include area below cells (could be done in GetScrPos?) + if ( eMode == ScUpdateMode::All && nRow2 >= rDoc.MaxRow() && !bIsTiledRendering ) + aEnd.setY( pGridWin[i]->GetOutputSizePixel().Height() ); + + aStart.AdjustX( -nLayoutSign ); // include change marks + aStart.AdjustY( -1 ); + + bool bMarkClipped = SC_MOD()->GetColorConfig().GetColorValue(svtools::CALCTEXTOVERFLOW).bIsVisible; + if (bMarkClipped) + { + // ScColumn::IsEmptyData has to be optimized for this + // (switch to Search() ) + //!if ( nCol1 > 0 && !aViewData.GetDocument()->IsBlockEmpty( + //! 0, nRow1, nCol1-1, nRow2. + //! aViewData.GetTabNo() ) ) + tools::Long nMarkPixel = static_cast<tools::Long>( SC_CLIPMARK_SIZE * aViewData.GetPPTX() ); + aStart.AdjustX( -(nMarkPixel * nLayoutSign) ); + } + + pGridWin[i]->Invalidate( pGridWin[i]->PixelToLogic( tools::Rectangle( aStart,aEnd ) ) ); + } + + // #i79909# Calling UpdateAllOverlays here isn't necessary and would lead to overlay calls from a timer, + // with a wrong MapMode if editing in a cell (reference input). + // #i80499# Overlays need updates in a lot of cases, e.g. changing row/column size, + // or showing/hiding outlines. TODO: selections in inactive windows are vanishing. + // #i84689# With relative conditional formats, PaintArea may be called often (for each changed cell), + // so UpdateAllOverlays was moved to ScTabViewShell::Notify and is called only if PaintPartFlags::Left/PaintPartFlags::Top + // is set (width or height changed). +} + +void ScTabView::PaintRangeFinderEntry (const ScRangeFindData* pData, const SCTAB nTab) +{ + ScRange aRef = pData->aRef; + aRef.PutInOrder(); // PutInOrder for the queries below + + if ( aRef.aStart == aRef.aEnd ) //! ignore sheet? + aViewData.GetDocument().ExtendMerge(aRef); + + if (aRef.aStart.Tab() < nTab || aRef.aEnd.Tab() > nTab) + return; + + SCCOL nCol1 = aRef.aStart.Col(); + SCROW nRow1 = aRef.aStart.Row(); + SCCOL nCol2 = aRef.aEnd.Col(); + SCROW nRow2 = aRef.aEnd.Row(); + + // remove -> repaint + // ScUpdateMode::Marks: Invalidate, nothing until end of row + + bool bHiddenEdge = false; + SCROW nTmp; + ScDocument& rDoc = aViewData.GetDocument(); + while ( nCol1 > 0 && rDoc.ColHidden(nCol1, nTab) ) + { + --nCol1; + bHiddenEdge = true; + } + while ( nCol2 < rDoc.MaxCol() && rDoc.ColHidden(nCol2, nTab) ) + { + ++nCol2; + bHiddenEdge = true; + } + nTmp = rDoc.LastVisibleRow(0, nRow1, nTab); + if (!rDoc.ValidRow(nTmp)) + nTmp = 0; + if (nTmp < nRow1) + { + nRow1 = nTmp; + bHiddenEdge = true; + } + nTmp = rDoc.FirstVisibleRow(nRow2, rDoc.MaxRow(), nTab); + 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 + PaintArea( nCol1, nRow1, nCol2, nRow1, ScUpdateMode::Marks ); + PaintArea( nCol1, nRow1+1, nCol1, nRow2-1, ScUpdateMode::Marks ); + PaintArea( nCol2, nRow1+1, nCol2, nRow2-1, ScUpdateMode::Marks ); + PaintArea( nCol1, nRow2, nCol2, nRow2, ScUpdateMode::Marks ); + } + else // all in one + PaintArea( nCol1, nRow1, nCol2, nRow2, ScUpdateMode::Marks ); +} + +void ScTabView::PaintRangeFinder( tools::Long nNumber ) +{ + ScInputHandler* pHdl = SC_MOD()->GetInputHdl( aViewData.GetViewShell() ); + if (!pHdl) + return; + + ScRangeFindList* pRangeFinder = pHdl->GetRangeFindList(); + if ( !(pRangeFinder && pRangeFinder->GetDocName() == aViewData.GetDocShell()->GetTitle()) ) + return; + + SCTAB nTab = aViewData.GetTabNo(); + sal_uInt16 nCount = static_cast<sal_uInt16>(pRangeFinder->Count()); + + if (nNumber < 0) + { + for (sal_uInt16 i=0; i<nCount; i++) + PaintRangeFinderEntry(&pRangeFinder->GetObject(i),nTab); + } + else + { + sal_uInt16 idx = nNumber; + if (idx < nCount) + PaintRangeFinderEntry(&pRangeFinder->GetObject(idx),nTab); + } +} + +// for chart data selection + +void ScTabView::AddHighlightRange( const ScRange& rRange, const Color& rColor ) +{ + maHighlightRanges.emplace_back( rRange, rColor ); + + SCTAB nTab = aViewData.GetTabNo(); + if ( nTab >= rRange.aStart.Tab() && nTab <= rRange.aEnd.Tab() ) + PaintArea( rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row(), ScUpdateMode::Marks ); +} + +void ScTabView::ClearHighlightRanges() +{ + SCTAB nTab = aViewData.GetTabNo(); + for (ScHighlightEntry const & rEntry : maHighlightRanges) + { + ScRange aRange = rEntry.aRef; + if ( nTab >= aRange.aStart.Tab() && nTab <= aRange.aEnd.Tab() ) + PaintArea( aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), ScUpdateMode::Marks ); + } + + maHighlightRanges.clear(); +} + +void ScTabView::DoChartSelection( + const uno::Sequence< chart2::data::HighlightedRange > & rHilightRanges ) +{ + ClearHighlightRanges(); + const sal_Unicode sep = ::formula::FormulaCompiler::GetNativeSymbolChar(ocSep); + size_t nSize = 0; + size_t nIndex = 0; + std::vector<ReferenceMark> aReferenceMarks( nSize ); + + for (chart2::data::HighlightedRange const & rHighlightedRange : rHilightRanges) + { + Color aSelColor(ColorTransparency, rHighlightedRange.PreferredColor); + ScRangeList aRangeList; + ScDocument& rDoc = aViewData.GetDocShell()->GetDocument(); + if( ScRangeStringConverter::GetRangeListFromString( + aRangeList, rHighlightedRange.RangeRepresentation, rDoc, rDoc.GetAddressConvention(), sep )) + { + size_t nListSize = aRangeList.size(); + nSize += nListSize; + aReferenceMarks.resize(nSize); + + for ( size_t j = 0; j < nListSize; ++j ) + { + ScRange& p = aRangeList[j]; + ScRange aTargetRange; + if( rHighlightedRange.Index == - 1 ) + { + aTargetRange = p; + AddHighlightRange( aTargetRange, aSelColor ); + } + else + { + aTargetRange = lcl_getSubRangeByIndex( p, rHighlightedRange.Index ); + AddHighlightRange( aTargetRange, aSelColor ); + } + + if ( comphelper::LibreOfficeKit::isActive() && aViewData.GetViewShell() ) + { + aTargetRange.PutInOrder(); + + tools::Long nX1 = aTargetRange.aStart.Col(); + tools::Long nX2 = aTargetRange.aEnd.Col(); + tools::Long nY1 = aTargetRange.aStart.Row(); + tools::Long nY2 = aTargetRange.aEnd.Row(); + tools::Long nTab = aTargetRange.aStart.Tab(); + + aReferenceMarks[nIndex++] = ScInputHandler::GetReferenceMark( aViewData, aViewData.GetDocShell(), + nX1, nX2, nY1, nY2, + nTab, aSelColor ); + } + } + } + } + + if ( comphelper::LibreOfficeKit::isActive() && aViewData.GetViewShell() ) + ScInputHandler::SendReferenceMarks( aViewData.GetViewShell(), aReferenceMarks ); +} + +void ScTabView::DoDPFieldPopup(std::u16string_view rPivotTableName, sal_Int32 nDimensionIndex, Point aPoint, Size aSize) +{ + ScDocument& rDocument = aViewData.GetDocShell()->GetDocument(); + ScGridWindow* pWin = pGridWin[aViewData.GetActivePart()].get(); + + if (!pWin) + return; + + ScDPCollection* pDPCollection = rDocument.GetDPCollection(); + ScDPObject* pDPObject = pDPCollection->GetByName(rPivotTableName); + if (!pDPObject) + return; + + pDPObject->BuildAllDimensionMembers(); + + Point aPos = pWin->LogicToPixel(aPoint); + bool bLOK = comphelper::LibreOfficeKit::isActive(); + Point aScreenPoint = bLOK ? aPos : pWin->OutputToScreenPixel(aPos); + Size aScreenSize = pWin->LogicToPixel(aSize); + + pWin->DPLaunchFieldPopupMenu(aScreenPoint, aScreenSize, nDimensionIndex, pDPObject); +} + +// PaintGrid - repaint data range + +void ScTabView::PaintGrid() +{ + for (sal_uInt16 i = 0; i < 4; i++) + { + if (pGridWin[i] && pGridWin[i]->IsVisible()) + pGridWin[i]->Invalidate(); + } +} + +// PaintTop - repaint top control elements + +void ScTabView::PaintTop() +{ + for (sal_uInt16 i = 0; i < 2; i++) + { + if (pColBar[i]) + pColBar[i]->Invalidate(); + if (pColOutline[i]) + pColOutline[i]->Invalidate(); + } +} + +void ScTabView::CreateAnchorHandles(SdrHdlList& rHdl, const ScAddress& rAddress) +{ + for (sal_uInt16 i = 0; i < 4; i++) + { + if(pGridWin[i] && pGridWin[i]->IsVisible()) + pGridWin[i]->CreateAnchorHandle(rHdl, rAddress); + } +} + +void ScTabView::PaintTopArea( SCCOL nStartCol, SCCOL nEndCol ) +{ + // pixel position of the left edge + + if ( nStartCol < aViewData.GetPosX(SC_SPLIT_LEFT) || + nStartCol < aViewData.GetPosX(SC_SPLIT_RIGHT) ) + aViewData.RecalcPixPos(); + + // adjust freeze (UpdateFixX resets HSplitPos) + + if ( aViewData.GetHSplitMode() == SC_SPLIT_FIX && nStartCol < aViewData.GetFixPosX() ) + if (aViewData.UpdateFixX()) + RepeatResize(); + + // paint + + if (nStartCol>0) + --nStartCol; //! general ? + + ScDocument& rDoc = aViewData.GetDocument(); + bool bLayoutRTL = rDoc.IsLayoutRTL( aViewData.GetTabNo() ); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + for (sal_uInt16 i = 0; i < 2; i++) + { + ScHSplitPos eWhich = ScHSplitPos(i); + if (pColBar[eWhich]) + { + Size aWinSize = pColBar[eWhich]->GetSizePixel(); + tools::Long nStartX = aViewData.GetScrPos( nStartCol, 0, eWhich ).X(); + tools::Long nEndX; + if (nEndCol >= rDoc.MaxCol()) + nEndX = bLayoutRTL ? 0 : ( aWinSize.Width()-1 ); + else + nEndX = aViewData.GetScrPos( nEndCol+1, 0, eWhich ).X() - nLayoutSign; + if (nStartX > nEndX) + std::swap(nStartX, nEndX); + pColBar[eWhich]->Invalidate( + tools::Rectangle( nStartX, 0, nEndX, aWinSize.Height()-1 ) ); + } + if (pColOutline[eWhich]) + pColOutline[eWhich]->Invalidate(); + } +} + +// PaintLeft - repaint left control elements + +void ScTabView::PaintLeft() +{ + for (sal_uInt16 i = 0; i < 2; i++) + { + if (pRowBar[i]) + pRowBar[i]->Invalidate(); + if (pRowOutline[i]) + pRowOutline[i]->Invalidate(); + } +} + +void ScTabView::PaintLeftArea( SCROW nStartRow, SCROW nEndRow ) +{ + // pixel position of the upper edge + + if ( nStartRow < aViewData.GetPosY(SC_SPLIT_TOP) || + nStartRow < aViewData.GetPosY(SC_SPLIT_BOTTOM) ) + aViewData.RecalcPixPos(); + + // adjust freeze (UpdateFixY reset VSplitPos) + + if ( aViewData.GetVSplitMode() == SC_SPLIT_FIX && nStartRow < aViewData.GetFixPosY() ) + if (aViewData.UpdateFixY()) + RepeatResize(); + + // paint + + if (nStartRow>0) + --nStartRow; + + ScDocument& rDoc = aViewData.GetDocument(); + for (sal_uInt16 i = 0; i < 2; i++) + { + ScVSplitPos eWhich = ScVSplitPos(i); + if (pRowBar[eWhich]) + { + Size aWinSize = pRowBar[eWhich]->GetSizePixel(); + tools::Long nStartY = aViewData.GetScrPos( 0, nStartRow, eWhich ).Y(); + tools::Long nEndY; + if (nEndRow >= rDoc.MaxRow()) + nEndY = aWinSize.Height() - 1; + else + nEndY = aViewData.GetScrPos( 0, nEndRow+1, eWhich ).Y() - 1; + if (nStartY > nEndY) + std::swap(nStartY, nEndY); + pRowBar[eWhich]->Invalidate( + tools::Rectangle( 0, nStartY, aWinSize.Width()-1, nEndY ) ); + } + if (pRowOutline[eWhich]) + pRowOutline[eWhich]->Invalidate(); + } +} + +bool ScTabView::PaintExtras() +{ + bool bRet = false; + ScDocument& rDoc = aViewData.GetDocument(); + SCTAB nTab = aViewData.GetTabNo(); + if (!rDoc.HasTable(nTab)) // sheet is deleted? + { + SCTAB nCount = rDoc.GetTableCount(); + aViewData.SetTabNo(nCount-1); + bRet = true; + } + pTabControl->UpdateStatus(); // true = active + return bRet; +} + +void ScTabView::RecalcPPT() +{ + // called after changes that require the PPT values to be recalculated + // (currently from detective operations) + + double nOldX = aViewData.GetPPTX(); + double nOldY = aViewData.GetPPTY(); + + aViewData.RefreshZoom(); // pre-calculate new PPT values + + bool bChangedX = ( aViewData.GetPPTX() != nOldX ); + bool bChangedY = ( aViewData.GetPPTY() != nOldY ); + if ( !(bChangedX || bChangedY) ) + return; + + // call view SetZoom (including draw scale, split update etc) + // and paint only if values changed + + Fraction aZoomX = aViewData.GetZoomX(); + Fraction aZoomY = aViewData.GetZoomY(); + SetZoom( aZoomX, aZoomY, false ); + + PaintGrid(); + if (bChangedX) + PaintTop(); + if (bChangedY) + PaintLeft(); +} + +void ScTabView::ActivateView( bool bActivate, bool bFirst ) +{ + if ( bActivate == aViewData.IsActive() && !bFirst ) + { + // no assertion anymore - occurs when previously in Drag&Drop switching over + // to another document + return; + } + + // is only called for MDI-(De)Activate + // aViewData.Activate behind due to cursor show for KillEditView + // don't delete selection - if Activate(false) is set in ViewData, + // then the selection is not displayed + + if (!bActivate) + { + ScModule* pScMod = SC_MOD(); + bool bRefMode = pScMod->IsFormulaMode(); + + // don't cancel reference input, to allow reference + // to other document + + if (!bRefMode) + { + // pass view to GetInputHdl, this view may not be current anymore + ScInputHandler* pHdl = SC_MOD()->GetInputHdl(aViewData.GetViewShell()); + if (pHdl) + pHdl->EnterHandler(); + } + } + + PaintExtras(); + + aViewData.Activate(bActivate); + + PaintBlock(false); // repaint, selection after active status + + if (!bActivate) + HideAllCursors(); // Cursor + else if (!bFirst) + ShowAllCursors(); + + if (bActivate) + { + if ( bFirst ) + { + ScSplitPos eWin = aViewData.GetActivePart(); + OSL_ENSURE( pGridWin[eWin], "Corrupted document, not all SplitPos in GridWin" ); + if ( !pGridWin[eWin] ) + { + eWin = SC_SPLIT_BOTTOMLEFT; + if ( !pGridWin[eWin] ) + { + short i; + for ( i=0; i<4; i++ ) + { + if ( pGridWin[i] ) + { + eWin = static_cast<ScSplitPos>(i); + break; // for + } + } + OSL_ENSURE( i<4, "and BOOM" ); + } + aViewData.SetActivePart( eWin ); + } + } + // do not call GrabFocus from here! + // if the document is processed, then Sfx calls GrabFocus in the window of the shell. + // if it is a mail body for instance, then it can't get the focus + UpdateInputContext(); + } + else + pGridWin[aViewData.GetActivePart()]->ClickExtern(); +} + +void ScTabView::ActivatePart( ScSplitPos eWhich ) +{ + ScSplitPos eOld = aViewData.GetActivePart(); + if ( eOld == eWhich ) + return; + + bInActivatePart = true; + + bool bRefMode = SC_MOD()->IsFormulaMode(); + + // the HasEditView call during SetCursor would fail otherwise + if ( aViewData.HasEditView(eOld) && !bRefMode ) + UpdateInputLine(); + + ScHSplitPos eOldH = WhichH(eOld); + ScVSplitPos eOldV = WhichV(eOld); + ScHSplitPos eNewH = WhichH(eWhich); + ScVSplitPos eNewV = WhichV(eWhich); + bool bTopCap = pColBar[eOldH] && pColBar[eOldH]->IsMouseCaptured(); + bool bLeftCap = pRowBar[eOldV] && pRowBar[eOldV]->IsMouseCaptured(); + + bool bFocus = pGridWin[eOld]->HasFocus(); + bool bCapture = pGridWin[eOld]->IsMouseCaptured(); + if (bCapture) + pGridWin[eOld]->ReleaseMouse(); + pGridWin[eOld]->ClickExtern(); + pGridWin[eOld]->HideCursor(); + pGridWin[eWhich]->HideCursor(); + aViewData.SetActivePart( eWhich ); + + ScTabViewShell* pShell = aViewData.GetViewShell(); + pShell->WindowChanged(); + + pSelEngine->SetWindow(pGridWin[eWhich]); + pSelEngine->SetWhich(eWhich); + pSelEngine->SetVisibleArea( tools::Rectangle(Point(), pGridWin[eWhich]->GetOutputSizePixel()) ); + + pGridWin[eOld]->MoveMouseStatus(*pGridWin[eWhich]); + + if ( bCapture || pGridWin[eWhich]->IsMouseCaptured() ) + { + // tracking instead of CaptureMouse, so it can be cancelled cleanly + // (SelectionEngine calls CaptureMouse for SetWindow) + //! someday SelectionEngine itself should call StartTracking!?! + pGridWin[eWhich]->ReleaseMouse(); + pGridWin[eWhich]->StartTracking(); + } + + if ( bTopCap && pColBar[eNewH] ) + { + pColBar[eOldH]->SetIgnoreMove(true); + pColBar[eNewH]->SetIgnoreMove(false); + pHdrSelEng->SetWindow( pColBar[eNewH] ); + tools::Long nWidth = pColBar[eNewH]->GetOutputSizePixel().Width(); + pHdrSelEng->SetVisibleArea( tools::Rectangle( 0, LONG_MIN, nWidth-1, LONG_MAX ) ); + pColBar[eNewH]->CaptureMouse(); + } + if ( bLeftCap && pRowBar[eNewV] ) + { + pRowBar[eOldV]->SetIgnoreMove(true); + pRowBar[eNewV]->SetIgnoreMove(false); + pHdrSelEng->SetWindow( pRowBar[eNewV] ); + tools::Long nHeight = pRowBar[eNewV]->GetOutputSizePixel().Height(); + pHdrSelEng->SetVisibleArea( tools::Rectangle( LONG_MIN, 0, LONG_MAX, nHeight-1 ) ); + pRowBar[eNewV]->CaptureMouse(); + } + aHdrFunc.SetWhich(eWhich); + + pGridWin[eOld]->ShowCursor(); + pGridWin[eWhich]->ShowCursor(); + + SfxInPlaceClient* pClient = aViewData.GetViewShell()->GetIPClient(); + bool bOleActive = ( pClient && pClient->IsObjectInPlaceActive() ); + + // don't switch ViewShell's active window during RefInput, because the focus + // might change, and subsequent SetReference calls wouldn't find the right EditView + if ( !bRefMode && !bOleActive ) + aViewData.GetViewShell()->SetWindow( pGridWin[eWhich] ); + + if ( bFocus && !aViewData.IsAnyFillMode() && !bRefMode ) + { + // GrabFocus only if previously the other GridWindow had the focus + // (for instance due to search and replace) + pGridWin[eWhich]->GrabFocus(); + } + + bInActivatePart = false; +} + +void ScTabView::HideListBox() +{ + for (VclPtr<ScGridWindow> & pWin : pGridWin) + { + if (pWin) + pWin->ClickExtern(); + } +} + +void ScTabView::UpdateInputContext() +{ + ScGridWindow* pWin = pGridWin[aViewData.GetActivePart()].get(); + if (pWin) + pWin->UpdateInputContext(); + + if (pTabControl) + pTabControl->UpdateInputContext(); +} + +// GetGridWidth - width of an output range (for ViewData) + +tools::Long ScTabView::GetGridWidth( ScHSplitPos eWhich ) +{ + // at present only the size of the current pane is synchronized with + // the size of the visible area in Online; + // as a workaround we use the same width for all panes independently + // from the eWhich value + if (comphelper::LibreOfficeKit::isActive()) + { + ScGridWindow* pGridWindow = aViewData.GetActiveWin(); + if (pGridWindow) + return pGridWindow->GetSizePixel().Width(); + } + + ScSplitPos eGridWhich = ( eWhich == SC_SPLIT_LEFT ) ? SC_SPLIT_BOTTOMLEFT : SC_SPLIT_BOTTOMRIGHT; + if (pGridWin[eGridWhich]) + return pGridWin[eGridWhich]->GetSizePixel().Width(); + else + return 0; +} + +// GetGridHeight - height of an output range (for ViewData) + +tools::Long ScTabView::GetGridHeight( ScVSplitPos eWhich ) +{ + // at present only the size of the current pane is synchronized with + // the size of the visible area in Online; + // as a workaround we use the same height for all panes independently + // from the eWhich value + if (comphelper::LibreOfficeKit::isActive()) + { + ScGridWindow* pGridWindow = aViewData.GetActiveWin(); + if (pGridWindow) + return pGridWindow->GetSizePixel().Height(); + } + + ScSplitPos eGridWhich = ( eWhich == SC_SPLIT_TOP ) ? SC_SPLIT_TOPLEFT : SC_SPLIT_BOTTOMLEFT; + if (pGridWin[eGridWhich]) + return pGridWin[eGridWhich]->GetSizePixel().Height(); + else + return 0; +} + +void ScTabView::UpdateInputLine() +{ + SC_MOD()->InputEnterHandler(); +} + +void ScTabView::ZoomChanged() +{ + ScInputHandler* pHdl = SC_MOD()->GetInputHdl(aViewData.GetViewShell()); + if (pHdl) + pHdl->SetRefScale( aViewData.GetZoomX(), aViewData.GetZoomY() ); + + UpdateFixPos(); + + UpdateScrollBars(); + + // VisArea... + // AW: Discussed with NN if there is a reason that new map mode was only set for one window, + // but is not. Setting only on one window causes the first repaint to have the old mapMode + // in three of four views, so the overlay will save the wrong content e.g. when zooming out. + // Changing to setting map mode at all windows. + + for (sal_uInt32 i = 0; i < 4; i++) + { + if (pGridWin[i]) + pGridWin[i]->SetMapMode(pGridWin[i]->GetDrawMapMode()); + } + + SetNewVisArea(); + + InterpretVisible(); // have everything calculated before painting + + SfxBindings& rBindings = aViewData.GetBindings(); + rBindings.Invalidate( SID_ATTR_ZOOM ); + rBindings.Invalidate( SID_ATTR_ZOOMSLIDER ); + rBindings.Invalidate(SID_ZOOM_IN); + rBindings.Invalidate(SID_ZOOM_OUT); + + HideNoteMarker(); + + // To not change too much, use pWin here + ScGridWindow* pWin = pGridWin[aViewData.GetActivePart()].get(); + + if ( pWin && aViewData.HasEditView( aViewData.GetActivePart() ) ) + { + // flush OverlayManager before changing the MapMode + pWin->flushOverlayManager(); + + // make sure the EditView's position and size are updated + // with the right (logic, not drawing) MapMode + pWin->SetMapMode( aViewData.GetLogicMode() ); + UpdateEditView(); + } +} + +void ScTabView::CheckNeedsRepaint() +{ + for (sal_uInt16 i = 0; i < 4; i++) + { + if (pGridWin[i] && pGridWin[i]->IsVisible()) + pGridWin[i]->CheckNeedsRepaint(); + } +} + +bool ScTabView::NeedsRepaint() +{ + for (VclPtr<ScGridWindow> & pWin : pGridWin) + { + if (pWin && pWin->IsVisible() && pWin->NeedsRepaint()) + return true; + } + return false; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabview4.cxx b/sc/source/ui/view/tabview4.cxx new file mode 100644 index 0000000000..a7de6bdf67 --- /dev/null +++ b/sc/source/ui/view/tabview4.cxx @@ -0,0 +1,538 @@ +/* -*- 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 <vcl/help.hxx> + +#include <tabview.hxx> +#include <tabvwsh.hxx> +#include <document.hxx> +#include <docsh.hxx> +#include <scmod.hxx> +#include <gridwin.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <inputhdl.hxx> + +// --- Referenz-Eingabe / Fill-Cursor + +void ScTabView::HideTip() +{ + if ( nTipVisible ) + { + ScSplitPos eWhich = aViewData.GetActivePart(); + vcl::Window* pWin = pGridWin[eWhich]; + Help::HidePopover(pWin, nTipVisible); + nTipVisible = nullptr; + aTipRectangle = tools::Rectangle(); + nTipAlign = QuickHelpFlags::NONE; + sTipString.clear(); + sTopParent.clear(); + } +} + +void ScTabView::ShowRefTip() +{ + bool bDone = false; + if ( aViewData.GetRefType() == SC_REFTYPE_REF && Help::IsQuickHelpEnabled() ) + { + SCCOL nStartX = aViewData.GetRefStartX(); + SCROW nStartY = aViewData.GetRefStartY(); + SCCOL nEndX = aViewData.GetRefEndX(); + SCROW nEndY = aViewData.GetRefEndY(); + if ( nEndX != nStartX || nEndY != nStartY ) // not for a single cell + { + bool bLeft = ( nEndX < nStartX ); + bool bTop = ( nEndY < nStartY ); + PutInOrder( nStartX, nEndX ); + PutInOrder( nStartY, nEndY ); + SCCOL nCols = nEndX+1-nStartX; + SCROW nRows = nEndY+1-nStartY; + + OUString aHelp = ScResId( STR_QUICKHELP_REF ); + aHelp = aHelp.replaceFirst("%1", OUString::number(nRows) ); + aHelp = aHelp.replaceFirst("%2", OUString::number(nCols) ); + + ScSplitPos eWhich = aViewData.GetActivePart(); + vcl::Window* pWin = pGridWin[eWhich]; + if ( pWin ) + { + Point aStart = aViewData.GetScrPos( nStartX, nStartY, eWhich ); + Point aEnd = aViewData.GetScrPos( nEndX+1, nEndY+1, eWhich ); + + Point aPos( bLeft ? aStart.X() : ( aEnd.X() + 3 ), + bTop ? aStart.Y() : ( aEnd.Y() + 3 ) ); + QuickHelpFlags nFlags = ( bLeft ? QuickHelpFlags::Right : QuickHelpFlags::Left ) | + ( bTop ? QuickHelpFlags::Bottom : QuickHelpFlags::Top ); + + // not over the edited formula + if ( !bTop && aViewData.HasEditView( eWhich ) && + nEndY+1 == aViewData.GetEditViewRow() ) + { + // then align at the upper border of edited cell + aPos.AdjustY( -2 ); // the three from above + nFlags = ( nFlags & ~QuickHelpFlags::Top ) | QuickHelpFlags::Bottom; + } + + tools::Rectangle aRect( pWin->OutputToScreenPixel( aPos ), Size(1,1) ); + + // Test if changed. + if (!nTipVisible || nFlags != nTipAlign || aRect != aTipRectangle || sTipString != aHelp || sTopParent != pWin) + { + HideTip(); + nTipVisible = Help::ShowPopover(pWin, aRect, aHelp, nFlags); + aTipRectangle = aRect; + nTipAlign = nFlags; + sTipString = aHelp; + sTopParent = pWin; + } + bDone = true; + } + } + } + + if (!bDone) + HideTip(); +} + +void ScTabView::StopRefMode() +{ + if (aViewData.IsRefMode()) + { + aViewData.SetRefMode( false, SC_REFTYPE_NONE ); + + HideTip(); + UpdateShrinkOverlay(); + + if ( aViewData.GetTabNo() >= aViewData.GetRefStartZ() && + aViewData.GetTabNo() <= aViewData.GetRefEndZ() ) + { + ScDocument& rDoc = aViewData.GetDocument(); + SCCOL nStartX = aViewData.GetRefStartX(); + SCROW nStartY = aViewData.GetRefStartY(); + SCCOL nEndX = aViewData.GetRefEndX(); + SCROW nEndY = aViewData.GetRefEndY(); + if ( nStartX == nEndX && nStartY == nEndY ) + rDoc.ExtendMerge( nStartX, nStartY, nEndX, nEndY, aViewData.GetTabNo() ); + + PaintArea( nStartX,nStartY,nEndX,nEndY, ScUpdateMode::Marks ); + } + + pSelEngine->Reset(); + pSelEngine->SetAddMode( false ); //! shouldn't that happen during reset? + + ScSplitPos eOld = pSelEngine->GetWhich(); + ScSplitPos eNew = aViewData.GetActivePart(); + if ( eNew != eOld ) + { + pSelEngine->SetWindow( pGridWin[ eNew ] ); + pSelEngine->SetWhich( eNew ); + pSelEngine->SetVisibleArea( tools::Rectangle(Point(), + pGridWin[eNew]->GetOutputSizePixel()) ); + pGridWin[eOld]->MoveMouseStatus(*pGridWin[eNew]); + } + } + + // AlignToCursor(SC_FOLLOW_NONE): Only switch active part. + // This must also be done if no RefMode was active (for RangeFinder dragging), + // but if RefMode was set, AlignToCursor must be after SelectionEngine reset, + // so the SelectionEngine SetWindow call from AlignToCursor doesn't capture + // the mouse again when called from Tracking/MouseButtonUp (#94562#). + AlignToCursor( aViewData.GetCurX(), aViewData.GetCurY(), SC_FOLLOW_NONE ); +} + +void ScTabView::DoneRefMode( bool bContinue ) +{ + ScDocument& rDoc = aViewData.GetDocument(); + if ( aViewData.GetRefType() == SC_REFTYPE_REF && bContinue ) + SC_MOD()->AddRefEntry(); + + bool bWasRef = aViewData.IsRefMode(); + aViewData.SetRefMode( false, SC_REFTYPE_NONE ); + + HideTip(); + UpdateShrinkOverlay(); + + // Paint: + if ( bWasRef && aViewData.GetTabNo() >= aViewData.GetRefStartZ() && + aViewData.GetTabNo() <= aViewData.GetRefEndZ() ) + { + SCCOL nStartX = aViewData.GetRefStartX(); + SCROW nStartY = aViewData.GetRefStartY(); + SCCOL nEndX = aViewData.GetRefEndX(); + SCROW nEndY = aViewData.GetRefEndY(); + if ( nStartX == nEndX && nStartY == nEndY ) + rDoc.ExtendMerge( nStartX, nStartY, nEndX, nEndY, aViewData.GetTabNo() ); + + PaintArea( nStartX,nStartY,nEndX,nEndY, ScUpdateMode::Marks ); + } +} + +void ScTabView::UpdateRef( SCCOL nCurX, SCROW nCurY, SCTAB nCurZ ) +{ + ScDocument& rDoc = aViewData.GetDocument(); + + if (!aViewData.IsRefMode()) + { + // This will happen, when first at a reference dialog with Control in + // the table is clicked. Then append the new reference to the old content: + + ScModule* pScMod = SC_MOD(); + if (pScMod->IsFormulaMode()) + pScMod->AddRefEntry(); + + InitRefMode( nCurX, nCurY, nCurZ, SC_REFTYPE_REF ); + } + + if ( nCurX != aViewData.GetRefEndX() || nCurY != aViewData.GetRefEndY() || + nCurZ != aViewData.GetRefEndZ() ) + { + ScMarkData& rMark = aViewData.GetMarkData(); + SCTAB nTab = aViewData.GetTabNo(); + + SCCOL nStartX = aViewData.GetRefStartX(); + SCROW nStartY = aViewData.GetRefStartY(); + SCCOL nEndX = aViewData.GetRefEndX(); + SCROW nEndY = aViewData.GetRefEndY(); + if ( nStartX == nEndX && nStartY == nEndY ) + rDoc.ExtendMerge( nStartX, nStartY, nEndX, nEndY, nTab ); + ScUpdateRect aRect( nStartX, nStartY, nEndX, nEndY ); + + if (rDoc.HasAttrib(nCurX, nCurY, nCurZ, HasAttrFlags::Merged)) + rDoc.ExtendMerge(nStartX, nStartY, nCurX, nCurY, nCurZ); + + aViewData.SetRefEnd( nCurX, nCurY, nCurZ ); + + nStartX = aViewData.GetRefStartX(); + nStartY = aViewData.GetRefStartY(); + nEndX = aViewData.GetRefEndX(); + nEndY = aViewData.GetRefEndY(); + if ( nStartX == nEndX && nStartY == nEndY ) + rDoc.ExtendMerge( nStartX, nStartY, nEndX, nEndY, nTab ); + aRect.SetNew( nStartX, nStartY, nEndX, nEndY ); + + ScRefType eType = aViewData.GetRefType(); + if ( eType == SC_REFTYPE_REF ) + { + if ((nStartX > nEndX || nStartY > nEndY) && + rDoc.HasAttrib(nStartX, nStartY, nTab, HasAttrFlags::Merged)) + rDoc.ExtendMerge( nStartX, nStartY, nStartX, nStartY, nTab ); + + ScRange aRef( + nStartX, nStartY, aViewData.GetRefStartZ(), + nEndX, nEndY, aViewData.GetRefEndZ() ); + SC_MOD()->SetReference( aRef, rDoc, &rMark ); + ShowRefTip(); + } + else if ( eType == SC_REFTYPE_EMBED_LT || eType == SC_REFTYPE_EMBED_RB ) + { + PutInOrder(nStartX,nEndX); + PutInOrder(nStartY,nEndY); + rDoc.SetEmbedded( ScRange(nStartX,nStartY,nTab, nEndX,nEndY,nTab) ); + ScDocShell* pDocSh = aViewData.GetDocShell(); + pDocSh->UpdateOle( aViewData, true ); + pDocSh->SetDocumentModified(); + } + + SCCOL nPaintStartX; + SCROW nPaintStartY; + SCCOL nPaintEndX; + SCROW nPaintEndY; + if (aRect.GetDiff( nPaintStartX, nPaintStartY, nPaintEndX, nPaintEndY )) + PaintArea( nPaintStartX, nPaintStartY, nPaintEndX, nPaintEndY, ScUpdateMode::Marks ); + + ScInputHandler* pInputHandler = SC_MOD()->GetInputHdl(); + if (pInputHandler) + { + pInputHandler->UpdateLokReferenceMarks(); + } + } + + // autocomplete for Auto-Fill + if ( !(aViewData.GetRefType() == SC_REFTYPE_FILL && Help::IsQuickHelpEnabled()) ) + return; + + vcl::Window* pWin = GetActiveWin(); + if ( !pWin ) + return; + + OUString aHelpStr; + ScRange aMarkRange; + aViewData.GetSimpleArea( aMarkRange ); + SCCOL nEndX = aViewData.GetRefEndX(); + SCROW nEndY = aViewData.GetRefEndY(); + ScRange aDelRange; + if ( aViewData.GetFillMode() == ScFillMode::MATRIX && !(nScFillModeMouseModifier & KEY_MOD1) ) + { + aHelpStr = ScResId( STR_TIP_RESIZEMATRIX ); + SCCOL nCols = nEndX + 1 - aViewData.GetRefStartX(); // order is right + SCROW nRows = nEndY + 1 - aViewData.GetRefStartY(); + aHelpStr = aHelpStr.replaceFirst("%1", OUString::number(nRows) ); + aHelpStr = aHelpStr.replaceFirst("%2", OUString::number(nCols) ); + } + else if ( aViewData.GetDelMark( aDelRange ) ) + aHelpStr = ScResId( STR_QUICKHELP_DELETE ); + else if ( nEndX != aMarkRange.aEnd.Col() || nEndY != aMarkRange.aEnd.Row() ) + aHelpStr = rDoc.GetAutoFillPreview( aMarkRange, nEndX, nEndY ); + + if (!aHelpStr.getLength()) + return; + + // depending on direction the upper or lower corner + SCCOL nAddX = ( nEndX >= aMarkRange.aEnd.Col() ) ? 1 : 0; + SCROW nAddY = ( nEndY >= aMarkRange.aEnd.Row() ) ? 1 : 0; + Point aPos = aViewData.GetScrPos( nEndX+nAddX, nEndY+nAddY, aViewData.GetActivePart() ); + aPos.AdjustX(8 ); + aPos.AdjustY(4 ); + aPos = pWin->OutputToScreenPixel( aPos ); + tools::Rectangle aRect( aPos, aPos ); + QuickHelpFlags nAlign = QuickHelpFlags::Left|QuickHelpFlags::Top; + if (!nTipVisible || nAlign != nTipAlign || aRect != aTipRectangle || sTipString != aHelpStr || sTopParent != pWin) + { + HideTip(); + nTipVisible = Help::ShowPopover(pWin, aRect, aHelpStr, nAlign); + aTipRectangle = aRect; + nTipAlign = nAlign; + sTipString = aHelpStr; + sTopParent = pWin; + } +} + +void ScTabView::InitRefMode( SCCOL nCurX, SCROW nCurY, SCTAB nCurZ, ScRefType eType ) +{ + ScDocument& rDoc = aViewData.GetDocument(); + ScMarkData& rMark = aViewData.GetMarkData(); + if (aViewData.IsRefMode()) + return; + + aViewData.SetRefMode( true, eType ); + aViewData.SetRefStart( nCurX, nCurY, nCurZ ); + aViewData.SetRefEnd( nCurX, nCurY, nCurZ ); + + if (nCurZ == aViewData.GetTabNo()) + { + SCCOL nStartX = nCurX; + SCROW nStartY = nCurY; + SCCOL nEndX = nCurX; + SCROW nEndY = nCurY; + rDoc.ExtendMerge( nStartX, nStartY, nEndX, nEndY, aViewData.GetTabNo() ); + + //! draw only markings over content! + PaintArea( nStartX,nStartY,nEndX,nEndY, ScUpdateMode::Marks ); + + // SetReference without Merge-Adjustment + ScRange aRef( nCurX,nCurY,nCurZ, nCurX,nCurY,nCurZ ); + SC_MOD()->SetReference( aRef, rDoc, &rMark ); + } + + ScInputHandler* pInputHandler = SC_MOD()->GetInputHdl(); + if (pInputHandler) + { + pInputHandler->UpdateLokReferenceMarks(); + } +} + +void ScTabView::SetScrollBar( ScrollAdaptor& rScroll, tools::Long nRangeMax, tools::Long nVisible, tools::Long nPos, bool bLayoutRTL ) +{ + if ( nVisible == 0 ) + nVisible = 1; // #i59893# don't use visible size 0 + + rScroll.SetRange( Range( 0, nRangeMax ) ); + rScroll.SetVisibleSize( nVisible ); + rScroll.SetThumbPos( nPos ); + + rScroll.EnableRTL( bLayoutRTL ); +} + +tools::Long ScTabView::GetScrollBarPos( const ScrollAdaptor& rScroll ) +{ + return rScroll.GetThumbPos(); +} + +// UpdateScrollBars - set visible area and scroll width of scroll bars +static tools::Long lcl_UpdateBar( ScrollAdaptor& rScroll, SCCOLROW nSize ) // Size = (complete) cells +{ + tools::Long nOldPos; + tools::Long nNewPos; + + nOldPos = rScroll.GetThumbPos(); + rScroll.SetPageSize( static_cast<tools::Long>(nSize) ); + nNewPos = rScroll.GetThumbPos(); +#ifndef UNX + rScroll.SetPageSize( 1 ); // always possible ! +#endif + + return nNewPos - nOldPos; +} + +static tools::Long lcl_GetScrollRange( SCCOLROW nDocEnd, SCCOLROW nPos, SCCOLROW nVis, SCCOLROW nMax, SCCOLROW nStart ) +{ + // get the end (positive) of a scroll bar range that always starts at 0 + + ++nVis; + ++nMax; // for partially visible cells + SCCOLROW nEnd = std::max(nDocEnd, static_cast<SCCOLROW>(nPos+nVis)) + nVis; + if (nEnd > nMax) + nEnd = nMax; + + return ( nEnd - nStart ); // for range starting at 0 +} + +void ScTabView::UpdateScrollBars( HeaderType eHeaderType ) +{ + ScTabViewShell::notifyAllViewsHeaderInvalidation(GetViewData().GetViewShell(), eHeaderType, GetViewData().GetTabNo()); + + tools::Long nDiff; + bool bTop = ( aViewData.GetVSplitMode() != SC_SPLIT_NONE ); + bool bRight = ( aViewData.GetHSplitMode() != SC_SPLIT_NONE ); + ScDocument& rDoc = aViewData.GetDocument(); + SCTAB nTab = aViewData.GetTabNo(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + SCCOL nUsedX; + SCROW nUsedY; + rDoc.GetTableArea( nTab, nUsedX, nUsedY ); //! cached !!!!!!!!!!!!!!! + + SCCOL nVisXL = 0; + SCCOL nVisXR = 0; + SCROW nVisYB = 0; + SCROW nVisYT = 0; + + SCCOL nStartX = 0; + SCROW nStartY = 0; + if (aViewData.GetHSplitMode()==SC_SPLIT_FIX) + nStartX = aViewData.GetFixPosX(); + if (aViewData.GetVSplitMode()==SC_SPLIT_FIX) + nStartY = aViewData.GetFixPosY(); + + nVisXL = aViewData.VisibleCellsX( SC_SPLIT_LEFT ); + tools::Long nMaxXL = lcl_GetScrollRange( nUsedX, aViewData.GetPosX(SC_SPLIT_LEFT), nVisXL, rDoc.MaxCol(), 0 ); + SetScrollBar( *aHScrollLeft, nMaxXL, nVisXL, aViewData.GetPosX( SC_SPLIT_LEFT ), bLayoutRTL ); + + nVisYB = aViewData.VisibleCellsY( SC_SPLIT_BOTTOM ); + tools::Long nMaxYB = lcl_GetScrollRange( nUsedY, aViewData.GetPosY(SC_SPLIT_BOTTOM), nVisYB, rDoc.MaxRow(), nStartY ); + SetScrollBar( *aVScrollBottom, nMaxYB, nVisYB, aViewData.GetPosY( SC_SPLIT_BOTTOM ) - nStartY, bLayoutRTL ); + + if (bRight) + { + nVisXR = aViewData.VisibleCellsX( SC_SPLIT_RIGHT ); + tools::Long nMaxXR = lcl_GetScrollRange( nUsedX, aViewData.GetPosX(SC_SPLIT_RIGHT), nVisXR, rDoc.MaxCol(), nStartX ); + SetScrollBar( *aHScrollRight, nMaxXR, nVisXR, aViewData.GetPosX( SC_SPLIT_RIGHT ) - nStartX, bLayoutRTL ); + } + + if (bTop) + { + nVisYT = aViewData.VisibleCellsY( SC_SPLIT_TOP ); + tools::Long nMaxYT = lcl_GetScrollRange( nUsedY, aViewData.GetPosY(SC_SPLIT_TOP), nVisYT, rDoc.MaxRow(), 0 ); + SetScrollBar( *aVScrollTop, nMaxYT, nVisYT, aViewData.GetPosY( SC_SPLIT_TOP ), bLayoutRTL ); + } + + // test the range + + nDiff = lcl_UpdateBar( *aHScrollLeft, nVisXL ); + if (nDiff) ScrollX( nDiff, SC_SPLIT_LEFT ); + if (bRight) + { + nDiff = lcl_UpdateBar( *aHScrollRight, nVisXR ); + if (nDiff) ScrollX( nDiff, SC_SPLIT_RIGHT ); + } + + nDiff = lcl_UpdateBar( *aVScrollBottom, nVisYB ); + if (nDiff) ScrollY( nDiff, SC_SPLIT_BOTTOM ); + if (bTop) + { + nDiff = lcl_UpdateBar( *aVScrollTop, nVisYT ); + if (nDiff) ScrollY( nDiff, SC_SPLIT_TOP ); + } + + // set visible area for online spelling + + if ( aViewData.IsActive() ) + { + if (UpdateVisibleRange()) + SC_MOD()->AnythingChanged(); // if visible area has changed + } +} + +#ifndef HDR_SLIDERSIZE +#define HDR_SLIDERSIZE 2 +#endif + +void ScTabView::InvertHorizontal( ScVSplitPos eWhich, tools::Long nDragPos ) +{ + for (sal_uInt16 i=0; i<4; i++) + if (WhichV(static_cast<ScSplitPos>(i))==eWhich) + { + ScGridWindow* pWin = pGridWin[i].get(); + if (pWin) + { + tools::Rectangle aRect( 0,nDragPos, pWin->GetOutputSizePixel().Width()-1,nDragPos+HDR_SLIDERSIZE-1 ); + pWin->PaintImmediately(); + pWin->DoInvertRect( aRect ); // Pixel + } + } +} + +void ScTabView::InvertVertical( ScHSplitPos eWhich, tools::Long nDragPos ) +{ + for (sal_uInt16 i=0; i<4; i++) + if (WhichH(static_cast<ScSplitPos>(i))==eWhich) + { + ScGridWindow* pWin = pGridWin[i].get(); + if (pWin) + { + tools::Rectangle aRect( nDragPos,0, nDragPos+HDR_SLIDERSIZE-1,pWin->GetOutputSizePixel().Height()-1 ); + pWin->PaintImmediately(); + pWin->DoInvertRect( aRect ); // Pixel + } + } +} + +void ScTabView::InterpretVisible() +{ + // make sure all visible cells are interpreted, + // so the next paint will not execute a macro function + + ScDocument& rDoc = aViewData.GetDocument(); + if ( !rDoc.GetAutoCalc() ) + return; + + SCTAB nTab = aViewData.GetTabNo(); + for (sal_uInt16 i=0; i<4; i++) + { + // rely on gridwin pointers to find used panes + // no IsVisible test in case the whole view is not yet shown + + if (pGridWin[i]) + { + ScHSplitPos eHWhich = WhichH( ScSplitPos(i) ); + ScVSplitPos eVWhich = WhichV( ScSplitPos(i) ); + + SCCOL nX1 = rDoc.SanitizeCol( aViewData.GetPosX( eHWhich )); + SCROW nY1 = rDoc.SanitizeRow( aViewData.GetPosY( eVWhich )); + SCCOL nX2 = rDoc.SanitizeCol( nX1 + aViewData.VisibleCellsX( eHWhich )); + SCROW nY2 = rDoc.SanitizeRow( nY1 + aViewData.VisibleCellsY( eVWhich )); + + rDoc.InterpretDirtyCells(ScRange(nX1, nY1, nTab, nX2, nY2, nTab)); + } + } + + // #i65047# repaint during the above loop may have set the bNeedsRepaint flag + CheckNeedsRepaint(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabview5.cxx b/sc/source/ui/view/tabview5.cxx new file mode 100644 index 0000000000..c9b65bb58c --- /dev/null +++ b/sc/source/ui/view/tabview5.cxx @@ -0,0 +1,704 @@ +/* -*- 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 <svx/fmshell.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdocapt.hxx> +#include <svx/svdoutl.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/lokhelper.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/viewfrm.hxx> +#include <osl/diagnose.h> + +#include <tabview.hxx> +#include <tabvwsh.hxx> +#include <document.hxx> +#include <gridwin.hxx> +#include <olinewin.hxx> +#include <tabsplit.hxx> +#include <colrowba.hxx> +#include <tabcont.hxx> +#include <sc.hrc> +#include <pagedata.hxx> +#include <hiranges.hxx> +#include <drawview.hxx> +#include <drwlayer.hxx> +#include <fusel.hxx> +#include <seltrans.hxx> +#include <scmod.hxx> +#include <docsh.hxx> +#include <viewuno.hxx> +#include <postit.hxx> +#include <spellcheckcontext.hxx> + +#include <vcl/settings.hxx> + +#include <comphelper/lok.hxx> +#include <officecfg/Office/Calc.hxx> + +using namespace com::sun::star; + +void ScTabView::Init() +{ + /* RTL layout of the view windows is done manually, because it depends on + the sheet orientation, not the UI setting. Note: controls that are + already constructed (e.g. scroll bars) have the RTL setting of the GUI. + Eventually this has to be disabled manually (see below). */ + pFrameWin->EnableRTL( false ); + + sal_uInt16 i; + + mbInlineWithScrollbar = officecfg::Office::Calc::Layout::Other::TabbarInlineWithScrollbar::get(); + + aScrollTimer.SetTimeout(10); + aScrollTimer.SetInvokeHandler( LINK( this, ScTabView, TimerHdl ) ); + + for (i=0; i<4; i++) + pGridWin[i] = nullptr; + pGridWin[SC_SPLIT_BOTTOMLEFT] = VclPtr<ScGridWindow>::Create( pFrameWin, aViewData, SC_SPLIT_BOTTOMLEFT ); + + pSelEngine.reset( new ScViewSelectionEngine( pGridWin[SC_SPLIT_BOTTOMLEFT], this, + SC_SPLIT_BOTTOMLEFT ) ); + aFunctionSet.SetSelectionEngine( pSelEngine.get() ); + + pHdrSelEng.reset( new ScHeaderSelectionEngine( pFrameWin, &aHdrFunc ) ); + + pColBar[SC_SPLIT_LEFT] = VclPtr<ScColBar>::Create( pFrameWin, SC_SPLIT_LEFT, + &aHdrFunc, pHdrSelEng.get(), this ); + pColBar[SC_SPLIT_RIGHT] = nullptr; + pRowBar[SC_SPLIT_BOTTOM] = VclPtr<ScRowBar>::Create( pFrameWin, SC_SPLIT_BOTTOM, + &aHdrFunc, pHdrSelEng.get(), this ); + pRowBar[SC_SPLIT_TOP] = nullptr; + for (i=0; i<2; i++) + pColOutline[i] = pRowOutline[i] = nullptr; + + pHSplitter = VclPtr<ScTabSplitter>::Create( pFrameWin, WinBits( WB_HSCROLL ), &aViewData ); + pVSplitter = VclPtr<ScTabSplitter>::Create( pFrameWin, WinBits( WB_VSCROLL ), &aViewData ); + + // SSA: override default keyboard step size to allow snap to row/column + pHSplitter->SetKeyboardStepSize( 1 ); + pVSplitter->SetKeyboardStepSize( 1 ); + + pTabControl = VclPtr<ScTabControl>::Create(pFrameWin, &aViewData); + if (mbInlineWithScrollbar) + pTabControl->SetStyle(pTabControl->GetStyle() | WB_SIZEABLE); + + /* #i97900# The tab control has to remain in RTL mode if GUI is RTL, this + is needed to draw the 3D effect correctly. The base TabBar implements + mirroring independent from the GUI direction. Have to set RTL mode + explicitly because the parent frame window is already RTL disabled. */ + pTabControl->EnableRTL( AllSettings::GetLayoutRTL() ); + + InitScrollBar( *aHScrollLeft, aViewData.GetDocument().MaxCol()+1, LINK(this, ScTabView, HScrollLeftHdl) ); + InitScrollBar( *aHScrollRight, aViewData.GetDocument().MaxCol()+1, LINK(this, ScTabView, HScrollRightHdl) ); + InitScrollBar( *aVScrollTop, aViewData.GetDocument().MaxRow()+1, LINK(this, ScTabView, VScrollTopHdl) ); + InitScrollBar( *aVScrollBottom, aViewData.GetDocument().MaxRow()+1, LINK(this, ScTabView, VScrollBottomHdl) ); + /* #i97900# scrollbars remain in correct RTL mode, needed mirroring etc. + is now handled correctly at the respective places. */ + + // Don't show anything here, because still in wrong order + // Show is received from UpdateShow during first resize + // pTabControl, pGridWin, aHScrollLeft, aVScrollBottom, + // aCornerButton, pHSplitter, pVSplitter + + // fragment + + pHSplitter->SetSplitHdl( LINK( this, ScTabView, SplitHdl ) ); + pVSplitter->SetSplitHdl( LINK( this, ScTabView, SplitHdl ) ); + + // UpdateShow is done during resize or a copy of an existing view from ctor + + pDrawActual = nullptr; + pDrawOld = nullptr; + + // DrawView cannot be create in the TabView - ctor + // when the ViewShell isn't constructed yet... + // The also applies to ViewOptionsHasChanged() + + TestHintWindow(); +} + +ScTabView::~ScTabView() +{ + sal_uInt16 i; + + // remove selection object + ScModule* pScMod = SC_MOD(); + ScSelectionTransferObj* pOld = pScMod->GetSelectionTransfer(); + if ( pOld && pOld->GetView() == this ) + { + pOld->ForgetView(); + pScMod->SetSelectionTransfer( nullptr ); + TransferableHelper::ClearPrimarySelection(); // may delete pOld + } + + pBrushDocument.reset(); + pDrawBrushSet.reset(); + + pPageBreakData.reset(); + + delete pDrawActual; + pDrawActual = nullptr; + delete pDrawOld; + pDrawOld = nullptr; + + if (comphelper::LibreOfficeKit::isActive()) + { + ScTabViewShell* pThisViewShell = GetViewData().GetViewShell(); + + auto lRemoveWindows = + [pThisViewShell] (ScTabViewShell* pOtherViewShell) + { + ScViewData& rOtherViewData = pOtherViewShell->GetViewData(); + for (int k = 0; k < 4; ++k) + { + if (rOtherViewData.HasEditView(static_cast<ScSplitPos>(k))) + pThisViewShell->RemoveWindowFromForeignEditView(pOtherViewShell, static_cast<ScSplitPos>(k)); + } + }; + + SfxLokHelper::forEachOtherView(pThisViewShell, lRemoveWindows); + } + + aViewData.KillEditView(); // as long as GridWins still exist + + if (pDrawView) + { + for (i=0; i<4; i++) + if (pGridWin[i]) + { + pDrawView->DeleteDeviceFromPaintView(*pGridWin[i]->GetOutDev()); + } + + pDrawView->HideSdrPage(); + pDrawView.reset(); + } + + pSelEngine.reset(); + + if (mpSpellCheckCxt) + mpSpellCheckCxt->dispose(); + mpSpellCheckCxt.reset(); + + mxInputHintOO.reset(); + for (i=0; i<4; i++) + pGridWin[i].disposeAndClear(); + + pHdrSelEng.reset(); + + for (i=0; i<2; i++) + { + pColBar[i].disposeAndClear(); + pRowBar[i].disposeAndClear(); + pColOutline[i].disposeAndClear(); + pRowOutline[i].disposeAndClear(); + } + + aCornerButton.disposeAndClear(); + aTopButton.disposeAndClear(); + aHScrollLeft.disposeAndClear(); + aHScrollRight.disposeAndClear(); + aVScrollTop.disposeAndClear(); + aVScrollBottom.disposeAndClear(); + + pHSplitter.disposeAndClear(); + pVSplitter.disposeAndClear(); + pTabControl.disposeAndClear(); +} + +void ScTabView::MakeDrawView( TriState nForceDesignMode ) +{ + if (pDrawView) + return; + + ScDrawLayer* pLayer = aViewData.GetDocument().GetDrawLayer(); + OSL_ENSURE(pLayer, "Where is the Draw Layer ??"); + + sal_uInt16 i; + pDrawView.reset( new ScDrawView( pGridWin[SC_SPLIT_BOTTOMLEFT]->GetOutDev(), &aViewData ) ); + for (i=0; i<4; i++) + if (pGridWin[i]) + { + if ( SC_SPLIT_BOTTOMLEFT != static_cast<ScSplitPos>(i) ) + pDrawView->AddDeviceToPaintView(*pGridWin[i]->GetOutDev(), nullptr); + } + pDrawView->RecalcScale(); + for (i=0; i<4; i++) + if (pGridWin[i]) + { + pGridWin[i]->SetMapMode(pGridWin[i]->GetDrawMapMode()); + + pGridWin[i]->PaintImmediately(); // because of Invalidate in DrawView ctor (ShowPage), + // so that immediately can be drawn + } + SfxRequest aSfxRequest(SID_OBJECT_SELECT, SfxCallMode::SLOT, aViewData.GetViewShell()->GetPool()); + SetDrawFuncPtr(new FuSelection(*aViewData.GetViewShell(), GetActiveWin(), pDrawView.get(), + pLayer,aSfxRequest)); + + // used when switching back from page preview: restore saved design mode state + // (otherwise, keep the default from the draw view ctor) + if ( nForceDesignMode != TRISTATE_INDET ) + pDrawView->SetDesignMode( nForceDesignMode != TRISTATE_FALSE ); + + // register at FormShell + FmFormShell* pFormSh = aViewData.GetViewShell()->GetFormShell(); + if (pFormSh) + pFormSh->SetView(pDrawView.get()); + + if (aViewData.GetViewShell()->HasAccessibilityObjects()) + aViewData.GetViewShell()->BroadcastAccessibility(SfxHint(SfxHintId::ScAccMakeDrawLayer)); +} + +void ScTabView::DoAddWin( ScGridWindow* pWin ) +{ + if (pDrawView) + { + pDrawView->AddDeviceToPaintView(*pWin->GetOutDev(), nullptr); + pWin->DrawLayerCreated(); + } + pWin->SetAutoSpellContext(mpSpellCheckCxt); +} + +void ScTabView::TabChanged( bool bSameTabButMoved ) +{ + if (pDrawView) + { + DrawDeselectAll(); // end also text edit mode + + SCTAB nTab = aViewData.GetTabNo(); + pDrawView->HideSdrPage(); + pDrawView->ShowSdrPage(pDrawView->GetModel().GetPage(nTab)); + + UpdateLayerLocks(); + + pDrawView->RecalcScale(); + pDrawView->UpdateWorkArea(); // PageSize is different per page + } + + SfxBindings& rBindings = aViewData.GetBindings(); + + // There is no easy way to invalidate all slots of the FormShell + // (for disabled slots on protected tables), therefore simply everything... + rBindings.InvalidateAll(false); + + if (aViewData.GetViewShell()->HasAccessibilityObjects()) + { + SfxHint aAccHint(SfxHintId::ScAccTableChanged); + aViewData.GetViewShell()->BroadcastAccessibility(aAccHint); + } + + // notification for XActivationBroadcaster + SfxViewFrame& rViewFrame = aViewData.GetViewShell()->GetViewFrame(); + uno::Reference<frame::XController> xController = rViewFrame.GetFrame().GetController(); + if (xController.is()) + { + ScTabViewObj* pImp = dynamic_cast<ScTabViewObj*>( xController.get() ); + if (pImp) + pImp->SheetChanged( bSameTabButMoved ); + } + + for (int i = 0; i < 4; i++) + { + if (pGridWin[i]) + { + pGridWin[i]->initiatePageBreaks(); + // Trigger calculating page breaks only once. + break; + } + } + + if (!comphelper::LibreOfficeKit::isActive()) + return; + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScModelObj* pModelObj = pDocSh ? pDocSh->GetModel() : nullptr; + + if (!pModelObj) + return; + + Size aDocSize = pModelObj->getDocumentSize(); + std::stringstream ss; + ss << aDocSize.Width() << ", " << aDocSize.Height(); + OString sRect(ss.str()); + ScTabViewShell* pViewShell = aViewData.GetViewShell(); + + // Invalidate first + tools::Rectangle aRectangle(0, 0, 1000000000, 1000000000); + pViewShell->libreOfficeKitViewInvalidateTilesCallback(&aRectangle, aViewData.GetTabNo(), 0); + + ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>(pViewShell->GetCurrentDocument()); + SfxLokHelper::notifyDocumentSizeChanged(pViewShell, sRect, pModel, false); +} + +void ScTabView::UpdateLayerLocks() +{ + if (!pDrawView) + return; + + SCTAB nTab = aViewData.GetTabNo(); + bool bEx = aViewData.GetViewShell()->IsDrawSelMode(); + bool bProt = aViewData.GetDocument().IsTabProtected( nTab ) || + aViewData.GetSfxDocShell()->IsReadOnly(); + bool bShared = aViewData.GetDocShell()->IsDocShared(); + + SdrLayer* pLayer; + SdrLayerAdmin& rAdmin = pDrawView->GetModel().GetLayerAdmin(); + pLayer = rAdmin.GetLayerPerID(SC_LAYER_BACK); + if (pLayer) + pDrawView->SetLayerLocked( pLayer->GetName(), bProt || !bEx || bShared ); + pLayer = rAdmin.GetLayerPerID(SC_LAYER_INTERN); + if (pLayer) + pDrawView->SetLayerLocked( pLayer->GetName() ); + pLayer = rAdmin.GetLayerPerID(SC_LAYER_FRONT); + if (pLayer) + pDrawView->SetLayerLocked( pLayer->GetName(), bProt || bShared ); + pLayer = rAdmin.GetLayerPerID(SC_LAYER_CONTROLS); + if (pLayer) + pDrawView->SetLayerLocked( pLayer->GetName(), bProt || bShared ); + pLayer = rAdmin.GetLayerPerID(SC_LAYER_HIDDEN); + if (pLayer) + { + pDrawView->SetLayerLocked( pLayer->GetName(), bProt || bShared ); + pDrawView->SetLayerVisible( pLayer->GetName(), false); + } + pTabControl->SetAddButtonEnabled(aViewData.GetDocument().IsDocEditable()); +} + +void ScTabView::DrawDeselectAll() +{ + if (!pDrawView) + return; + + ScTabViewShell* pViewSh = aViewData.GetViewShell(); + if ( pDrawActual && + ( pViewSh->IsDrawTextShell() || pDrawActual->GetSlotID() == SID_DRAW_NOTEEDIT ) ) + { + // end text edit (as if escape pressed, in FuDraw) + aViewData.GetDispatcher().Execute( pDrawActual->GetSlotID(), + SfxCallMode::SLOT | SfxCallMode::RECORD ); + } + + pDrawView->ScEndTextEdit(); + pDrawView->UnmarkAll(); + + if (!pViewSh->IsDrawSelMode()) + pViewSh->SetDrawShell( false ); +} + +bool ScTabView::IsDrawTextEdit() const +{ + if (pDrawView) + return pDrawView->IsTextEdit(); + else + return false; +} + +SvxZoomType ScTabView::GetZoomType() const +{ + return aViewData.GetZoomType(); +} + +void ScTabView::SetZoomType( SvxZoomType eNew, bool bAll ) +{ + aViewData.SetZoomType( eNew, bAll ); +} + +void ScTabView::SetZoom( const Fraction& rNewX, const Fraction& rNewY, bool bAll ) +{ + aViewData.SetZoom( rNewX, rNewY, bAll ); + if (pDrawView) + pDrawView->RecalcScale(); + ZoomChanged(); +} + +void ScTabView::RefreshZoom() +{ + aViewData.RefreshZoom(); + if (pDrawView) + pDrawView->RecalcScale(); + ZoomChanged(); +} + +void ScTabView::SetPagebreakMode( bool bSet ) +{ + aViewData.SetPagebreakMode(bSet); + if (pDrawView) + pDrawView->RecalcScale(); + ZoomChanged(); +} + +void ScTabView::ResetDrawDragMode() +{ + if (pDrawView) + pDrawView->SetDragMode( SdrDragMode::Move ); +} + +void ScTabView::ViewOptionsHasChanged( bool bHScrollChanged, bool bGraphicsChanged ) +{ + // create DrawView when grid should be displayed + if ( !pDrawView && aViewData.GetOptions().GetGridOptions().GetGridVisible() ) + MakeDrawLayer(); + + if (pDrawView) + pDrawView->UpdateUserViewOptions(); + + if (bGraphicsChanged) + DrawEnableAnim(true); // DrawEnableAnim checks the options state + + // if TabBar is set to visible, make sure its size is not 0 + bool bGrow = ( aViewData.IsTabMode() && pTabControl->GetSizePixel().Width() <= 0 ); + + // if ScrollBar is set to visible, TabBar must make room + bool bShrink = ( bHScrollChanged && aViewData.IsTabMode() && aViewData.IsHScrollMode() && + pTabControl->GetSizePixel().Width() > SC_TABBAR_DEFWIDTH ); + + if ( bGrow || bShrink ) + { + Size aSize = pTabControl->GetSizePixel(); + aSize.setWidth( SC_TABBAR_DEFWIDTH ); // initial size + pTabControl->SetSizePixel(aSize); // DoResize is called later... + } +} + +// helper function against including the drawing layer + +void ScTabView::DrawMarkListHasChanged() +{ + if ( pDrawView ) + pDrawView->MarkListHasChanged(); +} + +void ScTabView::UpdateAnchorHandles() +{ + if ( pDrawView ) + pDrawView->AdjustMarkHdl(); +} + +void ScTabView::UpdateIMap( SdrObject* pObj ) +{ + if ( pDrawView ) + pDrawView->UpdateIMap( pObj ); +} + +void ScTabView::DrawEnableAnim(bool bSet) +{ + sal_uInt16 i; + if ( !pDrawView ) + return; + + // don't start animations if display of graphics is disabled + // graphics are controlled by VOBJ_TYPE_OLE + if ( bSet && aViewData.GetOptions().GetObjMode(VOBJ_TYPE_OLE) == VOBJ_MODE_SHOW ) + { + if ( !pDrawView->IsAnimationEnabled() ) + { + pDrawView->SetAnimationEnabled(); + + // animated GIFs must be restarted: + ScDocument& rDoc = aViewData.GetDocument(); + for (i=0; i<4; i++) + if ( pGridWin[i] && pGridWin[i]->IsVisible() ) + rDoc.StartAnimations( aViewData.GetTabNo() ); + } + } + else + { + pDrawView->SetAnimationEnabled(false); + } +} + +void ScTabView::UpdateDrawTextOutliner() +{ + if ( pDrawView ) + { + Outliner* pOL = pDrawView->GetTextEditOutliner(); + if (pOL) + aViewData.UpdateOutlinerFlags( *pOL ); + } +} + +void ScTabView::DigitLanguageChanged() +{ + LanguageType eNewLang = ScModule::GetOptDigitLanguage(); + for (VclPtr<ScGridWindow> & pWin : pGridWin) + if ( pWin ) + pWin->GetOutDev()->SetDigitLanguage( eNewLang ); +} + +void ScTabView::ScrollToObject( const SdrObject* pDrawObj ) +{ + if ( pDrawObj ) + { + // #i118524# use the BoundRect, this defines the visible area + MakeVisible(pDrawObj->GetCurrentBoundRect()); + } +} + +void ScTabView::MakeVisible( const tools::Rectangle& rHMMRect ) +{ + vcl::Window* pWin = GetActiveWin(); + Size aWinSize = pWin->GetOutputSizePixel(); + SCTAB nTab = aViewData.GetTabNo(); + + tools::Rectangle aRect = pWin->LogicToPixel( rHMMRect ); + + tools::Long nScrollX=0, nScrollY=0; // pixel + + if ( aRect.Right() >= aWinSize.Width() ) // right out + { + nScrollX = aRect.Right() - aWinSize.Width() + 1; // right border visible + if ( aRect.Left() < nScrollX ) + nScrollX = aRect.Left(); // left visible (if too big) + } + if ( aRect.Bottom() >= aWinSize.Height() ) // bottom out + { + nScrollY = aRect.Bottom() - aWinSize.Height() + 1; // bottom border visible + if ( aRect.Top() < nScrollY ) + nScrollY = aRect.Top(); // top visible (if too big) + } + + if ( aRect.Left() < 0 ) // left out + nScrollX = aRect.Left(); // left border visible + if ( aRect.Top() < 0 ) // top out + nScrollY = aRect.Top(); // top border visible + + if (!(nScrollX || nScrollY)) + return; + + ScDocument& rDoc = aViewData.GetDocument(); + if ( rDoc.IsNegativePage( nTab ) ) + nScrollX = -nScrollX; + + double nPPTX = aViewData.GetPPTX(); + double nPPTY = aViewData.GetPPTY(); + ScSplitPos eWhich = aViewData.GetActivePart(); + SCCOL nPosX = aViewData.GetPosX(WhichH(eWhich)); + SCROW nPosY = aViewData.GetPosY(WhichV(eWhich)); + + tools::Long nLinesX=0, nLinesY=0; // columns/rows - scroll at least nScrollX/Y + + if (nScrollX > 0) + while (nScrollX > 0 && nPosX < rDoc.MaxCol()) + { + nScrollX -= static_cast<tools::Long>( rDoc.GetColWidth(nPosX, nTab) * nPPTX ); + ++nPosX; + ++nLinesX; + } + else if (nScrollX < 0) + while (nScrollX < 0 && nPosX > 0) + { + --nPosX; + nScrollX += static_cast<tools::Long>( rDoc.GetColWidth(nPosX, nTab) * nPPTX ); + --nLinesX; + } + + if (nScrollY > 0) + while (nScrollY > 0 && nPosY < rDoc.MaxRow()) + { + nScrollY -= static_cast<tools::Long>( rDoc.GetRowHeight(nPosY, nTab) * nPPTY ); + ++nPosY; + ++nLinesY; + } + else if (nScrollY < 0) + while (nScrollY < 0 && nPosY > 0) + { + --nPosY; + nScrollY += static_cast<tools::Long>( rDoc.GetRowHeight(nPosY, nTab) * nPPTY ); + --nLinesY; + } + + ScrollLines( nLinesX, nLinesY ); // execute +} + +void ScTabView::SetBrushDocument( ScDocumentUniquePtr pNew, bool bLock ) +{ + pDrawBrushSet.reset(); + pBrushDocument = std::move(pNew); + + bLockPaintBrush = bLock; + + aViewData.GetBindings().Invalidate(SID_FORMATPAINTBRUSH); +} + +void ScTabView::SetDrawBrushSet( std::unique_ptr<SfxItemSet> pNew, bool bLock ) +{ + pBrushDocument.reset(); + pDrawBrushSet = std::move(pNew); + + bLockPaintBrush = bLock; + + aViewData.GetBindings().Invalidate(SID_FORMATPAINTBRUSH); +} + +void ScTabView::ResetBrushDocument() +{ + if ( HasPaintBrush() ) + { + SetBrushDocument( nullptr, false ); + SetActivePointer( aViewData.IsThemedCursor() ? PointerStyle::FatCross : PointerStyle::Arrow ); // switch pointers also when ended with escape key + } +} + +void ScTabView::OnLOKNoteStateChanged(const ScPostIt* pNote) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + const SdrCaptionObj* pCaption = pNote->GetCaption(); + if (!pCaption) return; + + tools::Rectangle aRect = pCaption->GetLogicRect(); + basegfx::B2DRange aTailRange = pCaption->getTailPolygon().getB2DRange(); + tools::Rectangle aTailRect(aTailRange.getMinX(), aTailRange.getMinY(), + aTailRange.getMaxX(), aTailRange.getMaxY()); + aRect.Union( aTailRect ); + + // This is a temporary workaround: sometime in tiled rendering mode + // the tip of the note arrow is misplaced by a fixed offset. + // The value used below is enough to get the tile, where the arrow tip is + // placed, invalidated. + const int nBorderSize = 200; + tools::Rectangle aInvalidRect = aRect; + aInvalidRect.AdjustLeft( -nBorderSize ); + aInvalidRect.AdjustRight( nBorderSize ); + aInvalidRect.AdjustTop( -nBorderSize ); + aInvalidRect.AdjustBottom( nBorderSize ); + + SfxViewShell* pCurrentViewShell = SfxViewShell::Current(); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(pViewShell); + if (pTabViewShell && pViewShell->GetDocId() == pCurrentViewShell->GetDocId()) + { + for (auto& pWin: pTabViewShell->pGridWin) + { + if (pWin && pWin->IsVisible()) + { + pWin->Invalidate(aInvalidRect); + } + } + } + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabvwsh.cxx b/sc/source/ui/view/tabvwsh.cxx new file mode 100644 index 0000000000..f64b960485 --- /dev/null +++ b/sc/source/ui/view/tabvwsh.cxx @@ -0,0 +1,121 @@ +/* -*- 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 <svx/imapdlg.hxx> +#include <svx/srchdlg.hxx> +#include <sfx2/objface.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/infobar.hxx> +#include <sfx2/sidebar/SidebarChildWindow.hxx> +#include <sfx2/devtools/DevelopmentToolChildWindow.hxx> +#include <sfx2/viewfac.hxx> + +#include <cellvalue.hxx> + +#include <tabvwsh.hxx> +#include <docsh.hxx> +#include <reffact.hxx> +#include <sc.hrc> +#include <spelldialog.hxx> +#include <formulacell.hxx> +#include <searchresults.hxx> + + // needed for -fsanitize=function visibility of typeinfo for functions of + // type void(SfxShell*,SfxRequest&) defined in scslots.hxx +#define ShellClass_ScTabViewShell +#include <scslots.hxx> + + +SFX_IMPL_INTERFACE(ScTabViewShell, SfxViewShell) + +void ScTabViewShell::InitInterface_Impl() +{ + GetStaticInterface()->RegisterObjectBar(SFX_OBJECTBAR_TOOLS, + SfxVisibilityFlags::Standard | SfxVisibilityFlags::FullScreen | SfxVisibilityFlags::Server, + ToolbarId::Objectbar_Tools); + + GetStaticInterface()->RegisterChildWindow(FID_INPUTLINE_STATUS); + GetStaticInterface()->RegisterChildWindow(SfxInfoBarContainerChild::GetChildWindowId()); + + GetStaticInterface()->RegisterChildWindow(SID_NAVIGATOR, true); + + GetStaticInterface()->RegisterChildWindow(::sfx2::sidebar::SidebarChildWindow::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(DevelopmentToolChildWindow::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScNameDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScNameDefDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScSolverDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScOptSolverDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScXMLSourceDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScPivotLayoutWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScTabOpDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScFilterDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScSpecialFilterDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScDbNameDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScConsolidateDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScPrintAreasDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScColRowNameRangesDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScFormulaDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(SvxIMapDlgChildWindow::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScFormulaDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScAcceptChgDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScHighlightChgDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScSimpleRefDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(SvxSearchDialogWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(SID_HYPERLINK_DIALOG); + GetStaticInterface()->RegisterChildWindow(ScSpellDialogChildWindow::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScValidityRefChildWin::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(sc::SearchResultsDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(sc::ConditionalFormatEasyDialogWrapper::GetChildWindowId()); + + GetStaticInterface()->RegisterChildWindow(ScRandomNumberGeneratorDialogWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScSamplingDialogWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScDescriptiveStatisticsDialogWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScAnalysisOfVarianceDialogWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScCorrelationDialogWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScCovarianceDialogWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScExponentialSmoothingDialogWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScMovingAverageDialogWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScRegressionDialogWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScTTestDialogWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScFTestDialogWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScZTestDialogWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScChiSquareTestDialogWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScFourierAnalysisDialogWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(ScCondFormatDlgWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(sc::SparklineDialogWrapper::GetChildWindowId()); + GetStaticInterface()->RegisterChildWindow(sc::SparklineDataRangeDialogWrapper::GetChildWindowId()); +} + +SFX_IMPL_NAMED_VIEWFACTORY( ScTabViewShell, "Default" ) +{ + SFX_VIEW_REGISTRATION(ScDocShell); +} + +OUString ScTabViewShell::GetFormula(const ScAddress& rAddress) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + ScRefCellValue aCell(rDoc, rAddress); + if (!aCell.isEmpty() && aCell.getType() == CELLTYPE_FORMULA) + { + return aCell.getFormula()->GetFormula(); + } + return OUString(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabvwsh2.cxx b/sc/source/ui/view/tabvwsh2.cxx new file mode 100644 index 0000000000..b2fef44d96 --- /dev/null +++ b/sc/source/ui/view/tabvwsh2.cxx @@ -0,0 +1,472 @@ +/* -*- 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 <comphelper/lok.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/viewfrm.hxx> +#include <svl/whiter.hxx> +#include <unotools/moduleoptions.hxx> +#include <svl/cjkoptions.hxx> +#include <sfx2/dispatch.hxx> +#include <tools/UnitConversion.hxx> + +#include <tabvwsh.hxx> +#include <drawview.hxx> +#include <fupoor.hxx> +#include <fuconrec.hxx> +#include <fuconpol.hxx> +#include <fuconarc.hxx> +#include <fuconuno.hxx> +#include <fusel.hxx> +#include <futext.hxx> +#include <fuinsert.hxx> +#include <sc.hrc> +#include <scmod.hxx> +#include <appoptio.hxx> +#include <gridwin.hxx> + +// Create default drawing objects via keyboard +#include <svx/svdpagv.hxx> +#include <svl/stritem.hxx> +#include <fuconcustomshape.hxx> + +SdrView* ScTabViewShell::GetDrawView() const +{ + return const_cast<ScTabViewShell*>(this)->GetScDrawView(); // GetScDrawView is non-const +} + +void ScTabViewShell::WindowChanged() +{ + vcl::Window* pWin = GetActiveWin(); + + ScDrawView* pDrView = GetScDrawView(); + if (pDrView) + pDrView->SetActualWin(pWin->GetOutDev()); + + FuPoor* pFunc = GetDrawFuncPtr(); + if (pFunc) + pFunc->SetWindow(pWin); + + // when font from InputContext is used, + // this must be moved to change of cursor position: + UpdateInputContext(); +} + +void ScTabViewShell::ExecDraw(SfxRequest& rReq) +{ + SC_MOD()->InputEnterHandler(); + UpdateInputHandler(); + + MakeDrawLayer(); + + ScTabView* pTabView = GetViewData().GetView(); + SfxBindings& rBindings = GetViewFrame().GetBindings(); + + vcl::Window* pWin = pTabView->GetActiveWin(); + ScDrawView* pView = pTabView->GetScDrawView(); + SdrModel& rModel = pView->GetModel(); + + const SfxItemSet *pArgs = rReq.GetArgs(); + sal_uInt16 nNewId = rReq.GetSlot(); + + if ( nNewId == SID_DRAW_CHART ) + { + // #i71254# directly insert a chart instead of drawing its output rectangle + FuInsertChart(*this, pWin, pView, &rModel, rReq, LINK( this, ScTabViewShell, DialogClosedHdl )); + return; + } + + if ( nNewId == SID_DRAW_SELECT ) + nNewId = SID_OBJECT_SELECT; + + SdrObjKind eNewFormObjKind = SdrObjKind::NONE; + if (nNewId == SID_FM_CREATE_CONTROL) + { + const SfxUInt16Item* pIdentifierItem = rReq.GetArg<SfxUInt16Item>(SID_FM_CONTROL_IDENTIFIER); + if (pIdentifierItem) + eNewFormObjKind = static_cast<SdrObjKind>(pIdentifierItem->GetValue()); + } + + OUString sStringItemValue; + if ( pArgs ) + { + const SfxPoolItem* pItem; + if ( pArgs->GetItemState( nNewId, true, &pItem ) == SfxItemState::SET ) + if (auto pStringItem = dynamic_cast<const SfxStringItem*>(pItem) ) + sStringItemValue = pStringItem->GetValue(); + } + bool bSwitchCustom = ( !sStringItemValue.isEmpty() && !sDrawCustom.isEmpty() && sStringItemValue != sDrawCustom ); + + if (nNewId == SID_INSERT_FRAME) // from Tbx button + nNewId = SID_DRAW_TEXT; + + // CTRL-SID_OBJECT_SELECT is used to select the first object, + // but not if SID_OBJECT_SELECT is the result of clicking a create function again, + // so this must be tested before changing nNewId below. + bool bSelectFirst = ( nNewId == SID_OBJECT_SELECT && (rReq.GetModifier() & KEY_MOD1) ); + + bool bEx = IsDrawSelMode(); + if ( rReq.GetModifier() & KEY_MOD1 ) + { + // always allow keyboard selection also on background layer + // also allow creation of default objects if the same object type + // was already active + bEx = true; + } + else if ( nNewId == nDrawSfxId && ( nNewId != SID_FM_CREATE_CONTROL || + eNewFormObjKind == eFormObjKind || eNewFormObjKind == SdrObjKind::NONE ) && !bSwitchCustom ) + { + // #i52871# if a different custom shape is selected, the slot id can be the same, + // so the custom shape type string has to be compared, too. + + // SID_FM_CREATE_CONTROL with eNewFormObjKind==OBJ_NONE (without parameter) comes + // from FuConstruct::SimpleMouseButtonUp when deactivating + // Execute for the form shell, to deselect the controller + if ( nNewId == SID_FM_CREATE_CONTROL ) + { + GetViewData().GetDispatcher().Execute(SID_FM_LEAVE_CREATE); + GetViewFrame().GetBindings().InvalidateAll(false); + //! what kind of slot does the weird controller really need to display this???? + } + + bEx = !bEx; + nNewId = SID_OBJECT_SELECT; + } + else + bEx = true; + + if ( nDrawSfxId == SID_FM_CREATE_CONTROL && nNewId != nDrawSfxId ) + { + // switching from control- to paint function -> deselect in control-controller + GetViewData().GetDispatcher().Execute(SID_FM_LEAVE_CREATE); + GetViewFrame().GetBindings().InvalidateAll(false); + //! what kind of slot does the weird controller really need to display this???? + } + + SetDrawSelMode(bEx); + + pView->LockBackgroundLayer( !bEx ); + + if ( bSelectFirst ) + { + // select first draw object if none is selected yet + if(!pView->AreObjectsMarked()) + { + // select first object + pView->UnmarkAllObj(); + pView->MarkNextObj(true); + + // ...and make it visible + if(pView->AreObjectsMarked()) + pView->MakeVisible(pView->GetAllMarkedRect(), *pWin); + } + } + + nDrawSfxId = nNewId; + sDrawCustom.clear(); // value is set below for custom shapes + + if (nNewId == SID_DRAW_TEXT || nNewId == SID_DRAW_TEXT_VERTICAL + || nNewId == SID_DRAW_TEXT_MARQUEE || nNewId == SID_DRAW_NOTEEDIT) + SetDrawTextShell(true); + else + { + if (bEx || pView->GetMarkedObjectList().GetMarkCount() != 0) + SetDrawShellOrSub(); + else + SetDrawShell(false); + } + + if (pTabView->GetDrawFuncPtr()) + { + if (pTabView->GetDrawFuncOldPtr() != pTabView->GetDrawFuncPtr()) + delete pTabView->GetDrawFuncOldPtr(); + + pTabView->GetDrawFuncPtr()->Deactivate(); + pTabView->SetDrawFuncOldPtr(pTabView->GetDrawFuncPtr()); + pTabView->SetDrawFuncPtr(nullptr); + } + + SfxRequest aNewReq(rReq); + aNewReq.SetSlot(nDrawSfxId); + + assert(nNewId != SID_DRAW_CHART); //#i71254# handled already above + + // for LibreOfficeKit - choosing a shape should construct it directly + bool bCreateDirectly = false; + + switch (nNewId) + { + case SID_OBJECT_SELECT: + // not always switch back + if(pView->GetMarkedObjectList().GetMarkCount() == 0) SetDrawShell(bEx); + pTabView->SetDrawFuncPtr(new FuSelection(*this, pWin, pView, &rModel, aNewReq)); + break; + + case SID_DRAW_LINE: + case SID_DRAW_XLINE: + case SID_LINE_ARROW_END: + case SID_LINE_ARROW_CIRCLE: + case SID_LINE_ARROW_SQUARE: + case SID_LINE_ARROW_START: + case SID_LINE_CIRCLE_ARROW: + case SID_LINE_SQUARE_ARROW: + case SID_LINE_ARROWS: + case SID_DRAW_RECT: + case SID_DRAW_ELLIPSE: + case SID_DRAW_MEASURELINE: + pTabView->SetDrawFuncPtr(new FuConstRectangle(*this, pWin, pView, &rModel, aNewReq)); + bCreateDirectly = comphelper::LibreOfficeKit::isActive(); + break; + + case SID_DRAW_CAPTION: + case SID_DRAW_CAPTION_VERTICAL: + pTabView->SetDrawFuncPtr(new FuConstRectangle(*this, pWin, pView, &rModel, aNewReq)); + pView->SetFrameDragSingles( false ); + rBindings.Invalidate( SID_BEZIER_EDIT ); + break; + + case SID_DRAW_XPOLYGON: + case SID_DRAW_XPOLYGON_NOFILL: + case SID_DRAW_POLYGON: + case SID_DRAW_POLYGON_NOFILL: + case SID_DRAW_BEZIER_NOFILL: + case SID_DRAW_BEZIER_FILL: + case SID_DRAW_FREELINE: + case SID_DRAW_FREELINE_NOFILL: + pTabView->SetDrawFuncPtr(new FuConstPolygon(*this, pWin, pView, &rModel, aNewReq)); + break; + + case SID_DRAW_ARC: + case SID_DRAW_PIE: + case SID_DRAW_CIRCLECUT: + pTabView->SetDrawFuncPtr(new FuConstArc(*this, pWin, pView, &rModel, aNewReq)); + break; + + case SID_DRAW_TEXT: + case SID_DRAW_TEXT_VERTICAL: + case SID_DRAW_TEXT_MARQUEE: + case SID_DRAW_NOTEEDIT: + pTabView->SetDrawFuncPtr(new FuText(*this, pWin, pView, &rModel, aNewReq)); + bCreateDirectly = comphelper::LibreOfficeKit::isActive(); + break; + + case SID_FM_CREATE_CONTROL: + SetDrawFormShell(true); + pTabView->SetDrawFuncPtr(new FuConstUnoControl(*this, pWin, pView, &rModel, aNewReq)); + eFormObjKind = eNewFormObjKind; + break; + + case SID_DRAWTBX_CS_BASIC : + case SID_DRAWTBX_CS_SYMBOL : + case SID_DRAWTBX_CS_ARROW : + case SID_DRAWTBX_CS_FLOWCHART : + case SID_DRAWTBX_CS_CALLOUT : + case SID_DRAWTBX_CS_STAR : + case SID_DRAW_CS_ID : + { + pTabView->SetDrawFuncPtr(new FuConstCustomShape(*this, pWin, pView, &rModel, aNewReq)); + + bCreateDirectly = comphelper::LibreOfficeKit::isActive(); + + if ( nNewId != SID_DRAW_CS_ID ) + { + const SfxStringItem* pEnumCommand = rReq.GetArg<SfxStringItem>(nNewId); + if ( pEnumCommand ) + { + SfxBindings& rBind = GetViewFrame().GetBindings(); + rBind.Invalidate( nNewId ); + rBind.Update( nNewId ); + + sDrawCustom = pEnumCommand->GetValue(); // to detect when a different shape type is selected + } + } + } + break; + + default: + break; + } + + if (pTabView->GetDrawFuncPtr()) + pTabView->GetDrawFuncPtr()->Activate(); + + rReq.Done(); + + Invalidate(); + + // Create default drawing objects via keyboard + // with qualifier construct directly + FuPoor* pFuActual = GetDrawFuncPtr(); + + if(!(pFuActual && ((rReq.GetModifier() & KEY_MOD1) || bCreateDirectly))) + return; + + // Create default drawing objects via keyboard + const ScAppOptions& rAppOpt = SC_MOD()->GetAppOptions(); + sal_uInt32 nDefaultObjectSizeWidth = rAppOpt.GetDefaultObjectSizeWidth(); + sal_uInt32 nDefaultObjectSizeHeight = rAppOpt.GetDefaultObjectSizeHeight(); + + // calc position and size + bool bLOKIsActive = comphelper::LibreOfficeKit::isActive(); + Point aInsertPos; + if(!bLOKIsActive) + { + tools::Rectangle aVisArea = pWin->PixelToLogic(tools::Rectangle(Point(0,0), pWin->GetOutputSizePixel())); + aInsertPos = aVisArea.Center(); + aInsertPos.AdjustX( -sal_Int32(nDefaultObjectSizeWidth / 2) ); + aInsertPos.AdjustY( -sal_Int32(nDefaultObjectSizeHeight / 2) ); + } + else + { + ScViewData& rViewData = GetViewData(); + tools::Long nLayoutSign = rViewData.GetDocument().IsLayoutRTL(rViewData.GetTabNo()) ? -1 : 1; + aInsertPos = rViewData.getLOKVisibleArea().Center(); + if (comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs)) + aInsertPos = rViewData.GetPrintTwipsPosFromTileTwips(aInsertPos); + + aInsertPos.setX(nLayoutSign * convertTwipToMm100(aInsertPos.X())); + aInsertPos.setY(convertTwipToMm100(aInsertPos.Y())); + + aInsertPos.AdjustX( -sal_Int32(nDefaultObjectSizeWidth / 2) ); + aInsertPos.AdjustY( -sal_Int32(nDefaultObjectSizeHeight / 2) ); + } + + tools::Rectangle aNewObjectRectangle(aInsertPos, Size(nDefaultObjectSizeWidth, nDefaultObjectSizeHeight)); + + ScDrawView* pDrView = GetScDrawView(); + + if(!pDrView) + return; + + SdrPageView* pPageView = pDrView->GetSdrPageView(); + + if(!pPageView) + return; + + // create the default object + rtl::Reference<SdrObject> pObj = pFuActual->CreateDefaultObject(nNewId, aNewObjectRectangle); + + if(!pObj) + return; + + // insert into page + pView->InsertObjectAtView(pObj.get(), *pPageView); + + switch ( nNewId ) + { + case SID_DRAW_CAPTION: + case SID_DRAW_CAPTION_VERTICAL: + case SID_DRAW_TEXT: + case SID_DRAW_TEXT_VERTICAL: + // use KeyInput to start edit mode (FuText is created). + // For FuText objects, edit mode is handled within CreateDefaultObject. + // KEY_F2 is handled in FuDraw::KeyInput. + + pFuActual->KeyInput( KeyEvent( 0, vcl::KeyCode( KEY_F2 ) ) ); + break; + default: + break; + } +} + +void ScTabViewShell::GetDrawState(SfxItemSet &rSet) +{ + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + + while ( nWhich ) + { + switch ( nWhich ) + { + case SID_DRAW_CHART: + { + bool bOle = GetViewFrame().GetFrame().IsInPlace(); + if ( bOle || !SvtModuleOptions().IsChart() ) + rSet.DisableItem( nWhich ); + } + break; + + case SID_DRAW_LINE: + case SID_DRAW_XLINE: + case SID_LINE_ARROW_END: + case SID_LINE_ARROW_CIRCLE: + case SID_LINE_ARROW_SQUARE: + case SID_LINE_ARROW_START: + case SID_LINE_CIRCLE_ARROW: + case SID_LINE_SQUARE_ARROW: + case SID_LINE_ARROWS: + case SID_DRAW_MEASURELINE: + case SID_DRAW_RECT: + case SID_DRAW_ELLIPSE: + case SID_DRAW_POLYGON: + case SID_DRAW_POLYGON_NOFILL: + case SID_DRAW_XPOLYGON: + case SID_DRAW_XPOLYGON_NOFILL: + case SID_DRAW_BEZIER_FILL: + case SID_DRAW_BEZIER_NOFILL: + case SID_DRAW_FREELINE: + case SID_DRAW_FREELINE_NOFILL: + case SID_DRAW_ARC: + case SID_DRAW_PIE: + case SID_DRAW_CIRCLECUT: + case SID_DRAW_TEXT: + case SID_DRAW_TEXT_MARQUEE: + case SID_DRAW_CAPTION: + rSet.Put( SfxBoolItem( nWhich, nDrawSfxId == nWhich ) ); + break; + + case SID_DRAW_TEXT_VERTICAL: + case SID_DRAW_CAPTION_VERTICAL: + if ( !SvtCJKOptions::IsVerticalTextEnabled() ) + rSet.DisableItem( nWhich ); + else + rSet.Put( SfxBoolItem( nWhich, nDrawSfxId == nWhich ) ); + break; + + case SID_OBJECT_SELECT: // important for the old control-controller + rSet.Put( SfxBoolItem( nWhich, nDrawSfxId == SID_OBJECT_SELECT && IsDrawSelMode() ) ); + break; + + case SID_DRAWTBX_CS_BASIC: + case SID_DRAWTBX_CS_SYMBOL: + case SID_DRAWTBX_CS_ARROW: + case SID_DRAWTBX_CS_FLOWCHART: + case SID_DRAWTBX_CS_CALLOUT: + case SID_DRAWTBX_CS_STAR: + rSet.Put( SfxStringItem( nWhich, nDrawSfxId == nWhich ? sDrawCustom : OUString() ) ); + break; + } + nWhich = aIter.NextWhich(); + } +} + +bool ScTabViewShell::SelectObject( std::u16string_view rName ) +{ + ScDrawView* pView = GetViewData().GetScDrawView(); + if (!pView) + return false; + + bool bFound = pView->SelectObject( rName ); + // DrawShell etc. is handled in MarkListHasChanged + + return bFound; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabvwsh3.cxx b/sc/source/ui/view/tabvwsh3.cxx new file mode 100644 index 0000000000..208748b711 --- /dev/null +++ b/sc/source/ui/view/tabvwsh3.cxx @@ -0,0 +1,1398 @@ +/* -*- 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 <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/passwd.hxx> +#include <sfx2/request.hxx> +#include <sfx2/sidebar/Sidebar.hxx> +#include <svl/ptitem.hxx> +#include <svl/stritem.hxx> +#include <tools/urlobj.hxx> +#include <sfx2/objface.hxx> +#include <vcl/vclenum.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/uitest/eventdescription.hxx> + +#include <globstr.hrc> +#include <strings.hrc> +#include <scmod.hxx> +#include <appoptio.hxx> +#include <tabvwsh.hxx> +#include <document.hxx> +#include <sc.hrc> +#include <helpids.h> +#include <inputwin.hxx> +#include <scresid.hxx> +#include <docsh.hxx> +#include <rangeutl.hxx> +#include <reffact.hxx> +#include <tabprotection.hxx> +#include <protectiondlg.hxx> +#include <markdata.hxx> + +#include <svl/ilstitem.hxx> +#include <vector> + +#include <svx/zoomslideritem.hxx> +#include <svx/svxdlg.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/string.hxx> +#include <sfx2/lokhelper.hxx> +#include <scabstdlg.hxx> +#include <officecfg/Office/Calc.hxx> + +#include <basegfx/utils/zoomtools.hxx> + +#include <svx/svdpagv.hxx> +#include <svx/svdpage.hxx> +#include <svx/dialog/ThemeDialog.hxx> +#include <ThemeColorChanger.hxx> + +namespace +{ + void collectUIInformation(const OUString& aZoom) + { + EventDescription aDescription; + aDescription.aID = "grid_window"; + aDescription.aParameters = {{"ZOOM", aZoom}}; + aDescription.aAction = "SET"; + aDescription.aKeyWord = "ScGridWinUIObject"; + aDescription.aParent = "MainWindow"; + UITestLogger::getInstance().logEvent(aDescription); + } + + enum class DetectFlags + { + NONE, + RANGE, + ADDRESS + }; + + struct ScRefFlagsAndType + { + ScRefFlags nResult; + DetectFlags eDetected; + }; + + ScRefFlagsAndType lcl_ParseRangeOrAddress(ScRange& rScRange, ScAddress& rScAddress, + const OUString& aAddress, const ScDocument& rDoc, + SCCOL nCurCol, SCROW nCurRow) + { + ScRefFlagsAndType aRet; + + // Relative address parsing needs current position. + // row,col parameters, not col,row! + ScAddress::Details aDetails( rDoc.GetAddressConvention(), nCurRow, nCurCol); + + // start with the address convention set in the document + aRet.nResult = rScRange.Parse(aAddress, rDoc, aDetails); + if (aRet.nResult & ScRefFlags::VALID) + { + aRet.eDetected = DetectFlags::RANGE; + return aRet; + } + + aRet.nResult = rScAddress.Parse(aAddress, rDoc, aDetails); + if (aRet.nResult & ScRefFlags::VALID) + { + aRet.eDetected = DetectFlags::ADDRESS; + return aRet; + } + + // try the default Calc (A1) address convention + aRet.nResult = rScRange.Parse(aAddress, rDoc); + if (aRet.nResult & ScRefFlags::VALID) + { + aRet.eDetected = DetectFlags::RANGE; + return aRet; + } + + aRet.nResult = rScAddress.Parse(aAddress, rDoc); + if (aRet.nResult & ScRefFlags::VALID) + { + aRet.eDetected = DetectFlags::ADDRESS; + return aRet; + } + + // try the Excel A1 address convention + aRet.nResult = rScRange.Parse(aAddress, rDoc, formula::FormulaGrammar::CONV_XL_A1); + if (aRet.nResult & ScRefFlags::VALID) + { + aRet.eDetected = DetectFlags::RANGE; + return aRet; + } + + // try the Excel A1 address convention + aRet.nResult = rScAddress.Parse(aAddress, rDoc, formula::FormulaGrammar::CONV_XL_A1); + if (aRet.nResult & ScRefFlags::VALID) + { + aRet.eDetected = DetectFlags::ADDRESS; + return aRet; + } + + // try Excel R1C1 address convention + aDetails.eConv = formula::FormulaGrammar::CONV_XL_R1C1; + aRet.nResult = rScRange.Parse(aAddress, rDoc, aDetails); + if (aRet.nResult & ScRefFlags::VALID) + { + aRet.eDetected = DetectFlags::RANGE; + return aRet; + } + + aRet.nResult = rScAddress.Parse(aAddress, rDoc, aDetails); + if (aRet.nResult & ScRefFlags::VALID) + { + aRet.eDetected = DetectFlags::ADDRESS; + return aRet; + } + + aRet.nResult = ScRefFlags::ZERO; + aRet.eDetected = DetectFlags::NONE; + + return aRet; + } +} + +void ScTabViewShell::Execute( SfxRequest& rReq ) +{ + SfxViewFrame& rThisFrame = GetViewFrame(); + SfxBindings& rBindings = rThisFrame.GetBindings(); + ScModule* pScMod = SC_MOD(); + const SfxItemSet* pReqArgs = rReq.GetArgs(); + sal_uInt16 nSlot = rReq.GetSlot(); + + if (nSlot != SID_CURRENTCELL) // comes with MouseButtonUp + HideListBox(); // Autofilter-DropDown-Listbox + + switch ( nSlot ) + { + case FID_INSERT_FILE: + { + const SfxPoolItem* pItem; + if ( pReqArgs && + pReqArgs->GetItemState(FID_INSERT_FILE,true,&pItem) == SfxItemState::SET ) + { + OUString aFileName = static_cast<const SfxStringItem*>(pItem)->GetValue(); + + // insert position + + Point aInsertPos; + if ( pReqArgs->GetItemState(FN_PARAM_1,true,&pItem) == SfxItemState::SET ) + aInsertPos = static_cast<const SfxPointItem*>(pItem)->GetValue(); + else + aInsertPos = GetInsertPos(); + + // as Link? + + bool bAsLink = false; + if ( pReqArgs->GetItemState(FN_PARAM_2,true,&pItem) == SfxItemState::SET ) + bAsLink = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + + // execute + + PasteFile( aInsertPos, aFileName, bAsLink ); + } + } + break; + + case SID_OPENDLG_EDIT_PRINTAREA: + { + sal_uInt16 nId = ScPrintAreasDlgWrapper::GetChildWindowId(); + SfxChildWindow* pWnd = rThisFrame.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + break; + + case SID_CHANGE_PRINTAREA: + { + if ( pReqArgs ) // OK from dialog + { + OUString aPrintStr; + OUString aRowStr; + OUString aColStr; + bool bEntire = false; + const SfxPoolItem* pItem; + if ( pReqArgs->GetItemState( SID_CHANGE_PRINTAREA, true, &pItem ) == SfxItemState::SET ) + aPrintStr = static_cast<const SfxStringItem*>(pItem)->GetValue(); + if ( pReqArgs->GetItemState( FN_PARAM_2, true, &pItem ) == SfxItemState::SET ) + aRowStr = static_cast<const SfxStringItem*>(pItem)->GetValue(); + if ( pReqArgs->GetItemState( FN_PARAM_3, true, &pItem ) == SfxItemState::SET ) + aColStr = static_cast<const SfxStringItem*>(pItem)->GetValue(); + if ( pReqArgs->GetItemState( FN_PARAM_4, true, &pItem ) == SfxItemState::SET ) + bEntire = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + + SetPrintRanges( bEntire, &aPrintStr, &aColStr, &aRowStr, false ); + + rReq.Done(); + } + } + break; + + case SID_ADD_PRINTAREA: + case SID_DEFINE_PRINTAREA: // menu or basic + { + bool bAdd = ( nSlot == SID_ADD_PRINTAREA ); + if ( pReqArgs ) + { + OUString aPrintStr; + const SfxPoolItem* pItem; + if ( pReqArgs->GetItemState( SID_DEFINE_PRINTAREA, true, &pItem ) == SfxItemState::SET ) + aPrintStr = static_cast<const SfxStringItem*>(pItem)->GetValue(); + SetPrintRanges( false, &aPrintStr, nullptr, nullptr, bAdd ); + } + else + { + SetPrintRanges( false, nullptr, nullptr, nullptr, bAdd ); // from selection + rReq.Done(); + } + } + break; + + case SID_DELETE_PRINTAREA: + { + // Clear currently defined print range if any, and reset it to + // print entire sheet which is the default. + OUString aEmpty; + SetPrintRanges(true, &aEmpty, nullptr, nullptr, false); + rReq.Done(); + } + break; + + case FID_DEL_MANUALBREAKS: + RemoveManualBreaks(); + rReq.Done(); + break; + + case FID_ADJUST_PRINTZOOM: + AdjustPrintZoom(); + rReq.Done(); + break; + + case FID_RESET_PRINTZOOM: + SetPrintZoom( 100 ); // 100%, not on pages + rReq.Done(); + break; + + case SID_FORMATPAGE: + case SID_STATUS_PAGESTYLE: + case SID_HFEDIT: + GetViewData().GetDocShell()-> + ExecutePageStyle( *this, rReq, GetViewData().GetTabNo() ); + break; + + case SID_JUMPTOMARK: + case SID_CURRENTCELL: + if ( pReqArgs ) + { + OUString aAddress; + const SfxPoolItem* pItem; + if ( pReqArgs->GetItemState( nSlot, true, &pItem ) == SfxItemState::SET ) + aAddress = static_cast<const SfxStringItem*>(pItem)->GetValue(); + else if ( nSlot == SID_JUMPTOMARK && pReqArgs->GetItemState( + SID_JUMPTOMARK, true, &pItem ) == SfxItemState::SET ) + aAddress = static_cast<const SfxStringItem*>(pItem)->GetValue(); + + // #i14927# SID_CURRENTCELL with a single cell must unmark if FN_PARAM_1 + // isn't set (for recorded macros, because IsAPI is no longer available). + // ScGridWindow::MouseButtonUp no longer executes the slot for a single + // cell if there is a multi selection. + bool bUnmark = ( nSlot == SID_CURRENTCELL ); + if ( pReqArgs->GetItemState( FN_PARAM_1, true, &pItem ) == SfxItemState::SET ) + bUnmark = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + + bool bAlignToCursor = true; + if (pReqArgs->GetItemState(FN_PARAM_2, true, &pItem) == SfxItemState::SET) + bAlignToCursor = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + + bool bForceGlobalName = false; + if (pReqArgs->GetItemState(FN_PARAM_3, true, &pItem) == SfxItemState::SET) + bForceGlobalName = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + + if ( nSlot == SID_JUMPTOMARK ) + { + // URL has to be decoded for escaped characters (%20) + aAddress = INetURLObject::decode( aAddress, + INetURLObject::DecodeMechanism::WithCharset ); + } + + bool bFound = false; + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + ScMarkData& rMark = rViewData.GetMarkData(); + ScRange aScRange; + ScAddress aScAddress; + ScRefFlagsAndType aResult = lcl_ParseRangeOrAddress(aScRange, aScAddress, aAddress, rDoc, + rViewData.GetCurX(), rViewData.GetCurY()); + ScRefFlags nResult = aResult.nResult; + SCTAB nTab = rViewData.GetTabNo(); + bool bMark = true; + + // Is this a range ? + if (aResult.eDetected == DetectFlags::RANGE) + { + if ( nResult & ScRefFlags::TAB_3D ) + { + if( aScRange.aStart.Tab() != nTab ) + { + nTab = aScRange.aStart.Tab(); + SetTabNo( nTab ); + } + } + else + { + aScRange.aStart.SetTab( nTab ); + aScRange.aEnd.SetTab( nTab ); + } + } + // Is this a cell ? + else if (aResult.eDetected == DetectFlags::ADDRESS) + { + if ( nResult & ScRefFlags::TAB_3D ) + { + if( aScAddress.Tab() != nTab ) + { + nTab = aScAddress.Tab(); + SetTabNo( nTab ); + } + } + else + aScAddress.SetTab( nTab ); + + aScRange = ScRange( aScAddress, aScAddress ); + // cells should not be marked + bMark = false; + } + // Is it a named area (first named ranges then database ranges)? + else + { + const RutlNameScope eScope = (bForceGlobalName ? RUTL_NAMES_GLOBAL : RUTL_NAMES); + ScAddress::Details aDetails( rDoc.GetAddressConvention(), rViewData.GetCurY(), rViewData.GetCurX()); + if (ScRangeUtil::MakeRangeFromName( aAddress, rDoc, nTab, aScRange, eScope, aDetails, true) || + ScRangeUtil::MakeRangeFromName( aAddress, rDoc, nTab, aScRange, RUTL_DBASE, aDetails, true)) + { + nResult |= ScRefFlags::VALID; + if( aScRange.aStart.Tab() != nTab ) + { + nTab = aScRange.aStart.Tab(); + SetTabNo( nTab ); + } + } + } + + if ( !(nResult & ScRefFlags::VALID) && comphelper::string::isdigitAsciiString(aAddress) ) + { + sal_Int32 nNumeric = aAddress.toInt32(); + if ( nNumeric > 0 && nNumeric <= rDoc.MaxRow()+1 ) + { + // one-based row numbers + + aScAddress.SetRow( static_cast<SCROW>(nNumeric - 1) ); + aScAddress.SetCol( rViewData.GetCurX() ); + aScAddress.SetTab( nTab ); + aScRange = ScRange( aScAddress, aScAddress ); + bMark = false; + nResult = ScRefFlags::VALID; + } + } + + if ( !rDoc.ValidRow(aScRange.aStart.Row()) || !rDoc.ValidRow(aScRange.aEnd.Row()) ) + nResult = ScRefFlags::ZERO; + + // we have found something + if( nResult & ScRefFlags::VALID ) + { + bFound = true; + SCCOL nCol = aScRange.aStart.Col(); + SCROW nRow = aScRange.aStart.Row(); + bool bNothing = ( rViewData.GetCurX()==nCol && rViewData.GetCurY()==nRow ); + + // mark + if( bMark ) + { + if (rMark.IsMarked()) // is the same range already marked? + { + ScRange aOldMark = rMark.GetMarkArea(); + aOldMark.PutInOrder(); + ScRange aCurrent = aScRange; + aCurrent.PutInOrder(); + bNothing = ( aCurrent == aOldMark ); + } + else + bNothing = false; + + if (!bNothing) + MarkRange( aScRange, false ); // cursor comes after... + } + else + { + // remove old selection, unless bUnmark argument is sal_False (from navigator) + if( bUnmark ) + { + MoveCursorAbs( nCol, nRow, + SC_FOLLOW_NONE, false, false ); + } + } + + // and set cursor + + // consider merged cells: + rDoc.SkipOverlapped(nCol, nRow, nTab); + + // navigator calls are not part of the API!!! + + if( bNothing ) + { + if (rReq.IsAPI()) + rReq.Ignore(); // if macro, then nothing + else + rReq.Done(); // then at least paint it + } + else + { + rViewData.ResetOldCursor(); + SetCursor( nCol, nRow ); + rBindings.Invalidate( SID_CURRENTCELL ); + rBindings.Update( nSlot ); + + if (!rReq.IsAPI()) + rReq.Done(); + } + + if (bAlignToCursor) + { + // align to cursor even if the cursor position hasn't changed, + // because the cursor may be set outside the visible area. + AlignToCursor( nCol, nRow, SC_FOLLOW_JUMP ); + if ( nSlot == SID_JUMPTOMARK && comphelper::LibreOfficeKit::isActive() ) + rViewData.GetActiveWin()->notifyKitCellFollowJump(); + } + + rReq.SetReturnValue( SfxStringItem( SID_CURRENTCELL, aAddress ) ); + } + + if (!bFound) // no valid range + { + // if it is a sheet name, then switch (for Navigator/URL) + + SCTAB nNameTab; + if ( rDoc.GetTable( aAddress, nNameTab ) ) + { + bFound = true; + if ( nNameTab != nTab ) + SetTabNo( nNameTab ); + } + } + + if ( !bFound && nSlot == SID_JUMPTOMARK ) + { + // test graphics objects (only for URL) + + bFound = SelectObject( aAddress ); + } + + if (!bFound && !rReq.IsAPI()) + ErrorMessage( STR_ERR_INVALID_AREA ); + } + break; + + case SID_CURRENTOBJECT: + if ( pReqArgs ) + { + OUString aName = static_cast<const SfxStringItem&>(pReqArgs->Get(nSlot)).GetValue(); + SelectObject( aName ); + } + break; + + case SID_CURRENTTAB: + { + SCTAB nTab; + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + SCTAB nTabCount = rDoc.GetTableCount(); + if ( pReqArgs ) // command from Navigator with nTab + { + // sheet for basic is one-based + nTab = static_cast<const SfxUInt16Item&>(pReqArgs->Get(nSlot)).GetValue() - 1; + } + else // command from Menu: ask for nTab + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr<AbstractScGoToTabDlg> pDlg(pFact->CreateScGoToTabDlg(GetFrameWeld())); + pDlg->SetDescription( + ScResId( STR_DLG_SELECTTABLE_TITLE ), + ScResId( STR_DLG_SELECTTABLE_MASK ), + ScResId( STR_DLG_SELECTTABLE_LBNAME ), + GetStaticInterface()->GetSlot(SID_CURRENTTAB)->GetCommand(), HID_GOTOTABLEMASK, HID_GOTOTABLE ); + + // fill all table names and select current tab + OUString aTabName; + for( nTab = 0; nTab < nTabCount; ++nTab ) + { + if( rDoc.IsVisible( nTab ) ) + { + rDoc.GetName( nTab, aTabName ); + pDlg->Insert( aTabName, rViewData.GetTabNo() == nTab ); + } + } + + if( pDlg->Execute() == RET_OK ) + { + if( !rDoc.GetTable( pDlg->GetSelectedEntry(), nTab ) ) + nTab = nTabCount; + pDlg.disposeAndClear(); + } + else + { + rReq.Ignore(); + } + } + if ( nTab < nTabCount ) + { + SetTabNo( nTab ); + rBindings.Update( nSlot ); + + if( ! rReq.IsAPI() ) + rReq.Done(); + } + //! otherwise an error ? + } + break; + + case SID_CURRENTDOC: + if ( pReqArgs ) + { + OUString aStrDocName( static_cast<const SfxStringItem&>(pReqArgs-> + Get(nSlot)).GetValue() ); + + SfxViewFrame* pViewFrame = nullptr; + ScDocShell* pDocSh = static_cast<ScDocShell*>(SfxObjectShell::GetFirst()); + bool bFound = false; + + // search for ViewFrame to be activated + + while ( pDocSh && !bFound ) + { + if ( pDocSh->GetTitle() == aStrDocName ) + { + pViewFrame = SfxViewFrame::GetFirst( pDocSh ); + bFound = ( nullptr != pViewFrame ); + } + + pDocSh = static_cast<ScDocShell*>(SfxObjectShell::GetNext( *pDocSh )); + } + + if ( bFound ) + pViewFrame->GetFrame().Appear(); + + rReq.Ignore();//XXX is handled by SFX + } + break; + + case SID_PRINTPREVIEW: + { + if ( !rThisFrame.GetFrame().IsInPlace() ) // not for OLE + { + // print preview is now always in the same frame as the tab view + // -> always switch this frame back to normal view + // (ScPreviewShell ctor reads view data) + + // #102785#; finish input + pScMod->InputEnterHandler(); + + rThisFrame.GetDispatcher()->Execute( SID_VIEWSHELL1, SfxCallMode::ASYNCHRON ); + } + // else error (e.g. Ole) + } + break; + + case SID_DETECTIVE_DEL_ALL: + DetectiveDelAll(); + rReq.Done(); + break; + + // SID_TABLE_ACTIVATE and SID_MARKAREA are called by basic for the + // hidden View, to mark/switch on the visible View: + + case SID_TABLE_ACTIVATE: + OSL_FAIL("old slot SID_TABLE_ACTIVATE"); + break; + + case SID_REPAINT: + PaintGrid(); + PaintTop(); + PaintLeft(); + PaintExtras(); + rReq.Done(); + break; + + case FID_NORMALVIEWMODE: + case FID_PAGEBREAKMODE: + { + bool bWantPageBreak = nSlot == FID_PAGEBREAKMODE; + + // check whether there is an explicit argument, use it + const SfxPoolItem* pItem; + if ( pReqArgs && pReqArgs->GetItemState(nSlot, true, &pItem) == SfxItemState::SET ) + { + bool bItemValue = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + bWantPageBreak = (nSlot == FID_PAGEBREAKMODE) == bItemValue; + } + + if( GetViewData().IsPagebreakMode() != bWantPageBreak ) + { + SetPagebreakMode( bWantPageBreak ); + UpdatePageBreakData(); + SetCurSubShell( GetCurObjectSelectionType(), true ); + PaintGrid(); + PaintTop(); + PaintLeft(); + rBindings.Invalidate( nSlot ); + rReq.AppendItem( SfxBoolItem( nSlot, true ) ); + rReq.Done(); + } + } + break; + + case FID_FUNCTION_BOX: + { + // First make sure that the sidebar is visible + rThisFrame.ShowChildWindow(SID_SIDEBAR); + + ::sfx2::sidebar::Sidebar::ShowPanel(u"ScFunctionsPanel", + rThisFrame.GetFrame().GetFrameInterface(), + true); + rReq.Done (); + } + break; + + case FID_TOGGLESYNTAX: + { + bool bSet = !GetViewData().IsSyntaxMode(); + const SfxPoolItem* pItem; + if ( pReqArgs && pReqArgs->GetItemState(nSlot, true, &pItem) == SfxItemState::SET ) + bSet = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + GetViewData().SetSyntaxMode( bSet ); + PaintGrid(); + rBindings.Invalidate( FID_TOGGLESYNTAX ); + rReq.AppendItem( SfxBoolItem( nSlot, bSet ) ); + rReq.Done(); + } + break; + case FID_TOGGLECOLROWHIGHLIGHTING: + { + bool bNewVal = !officecfg::Office::Calc::Content::Display::ColumnRowHighlighting::get(); + + auto pChange(comphelper::ConfigurationChanges::create()); + officecfg::Office::Calc::Content::Display::ColumnRowHighlighting::set(bNewVal, pChange); + pChange->commit(); + + rReq.AppendItem(SfxBoolItem(nSlot, bNewVal)); + rReq.Done(); + } + break; + case FID_TOGGLEHEADERS: + { + bool bSet = !GetViewData().IsHeaderMode(); + const SfxPoolItem* pItem; + if ( pReqArgs && pReqArgs->GetItemState(nSlot, true, &pItem) == SfxItemState::SET ) + bSet = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + GetViewData().SetHeaderMode( bSet ); + RepeatResize(); + rBindings.Invalidate( FID_TOGGLEHEADERS ); + rReq.AppendItem( SfxBoolItem( nSlot, bSet ) ); + rReq.Done(); + } + break; + + case FID_TOGGLEFORMULA: + { + ScViewData& rViewData = GetViewData(); + const ScViewOptions& rOpts = rViewData.GetOptions(); + bool bFormulaMode = !rOpts.GetOption( VOPT_FORMULAS ); + const SfxPoolItem *pItem; + if( pReqArgs && pReqArgs->GetItemState(nSlot, true, &pItem) == SfxItemState::SET ) + bFormulaMode = static_cast<const SfxBoolItem *>(pItem)->GetValue(); + + ScViewOptions aSetOpts = rOpts; + aSetOpts.SetOption( VOPT_FORMULAS, bFormulaMode ); + rViewData.SetOptions( aSetOpts ); + ScDocument& rDoc = rViewData.GetDocument(); + rDoc.SetViewOptions(aSetOpts); + + rViewData.GetDocShell()->PostPaintGridAll(); + + rBindings.Invalidate( FID_TOGGLEFORMULA ); + rReq.AppendItem( SfxBoolItem( nSlot, bFormulaMode ) ); + rReq.Done(); + } + break; + + case FID_TOGGLEINPUTLINE: + { + sal_uInt16 nId = ScInputWindowWrapper::GetChildWindowId(); + SfxChildWindow* pWnd = rThisFrame.GetChildWindow( nId ); + bool bSet = ( pWnd == nullptr ); + const SfxPoolItem* pItem; + if ( pReqArgs && pReqArgs->GetItemState(nSlot, true, &pItem) == SfxItemState::SET ) + bSet = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + + rThisFrame.SetChildWindow( nId, bSet ); + rBindings.Invalidate( FID_TOGGLEINPUTLINE ); + rReq.AppendItem( SfxBoolItem( nSlot, bSet ) ); + rReq.Done(); + } + break; + + // handling for SID_ZOOM_IN and SID_ZOOM_OUT is ScTabView::ScrollCommand + // CommandWheelMode::ZOOM inspired + case SID_ZOOM_IN: + case SID_ZOOM_OUT: + { + HideNoteMarker(); + + if (!GetViewData().GetViewShell()->GetViewFrame().GetFrame().IsInPlace()) + { + // for ole inplace editing, the scale is defined by the visarea and client size + // and can't be changed directly + + const Fraction& rOldY = GetViewData().GetZoomY(); + sal_uInt16 nOld = tools::Long(rOldY * 100); + sal_uInt16 nNew; + if (SID_ZOOM_OUT == nSlot) + nNew = std::max(MINZOOM, basegfx::zoomtools::zoomOut(nOld)); + else + nNew = std::min(MAXZOOM, basegfx::zoomtools::zoomIn(nOld)); + if ( nNew != nOld) + { + bool bSyncZoom = SC_MOD()->GetAppOptions().GetSynchronizeZoom(); + SetZoomType(SvxZoomType::PERCENT, bSyncZoom); + Fraction aFract(nNew, 100); + SetZoom(aFract, aFract, bSyncZoom); + PaintGrid(); + PaintTop(); + PaintLeft(); + rBindings.Invalidate(SID_ATTR_ZOOM); + rBindings.Invalidate(SID_ATTR_ZOOMSLIDER); + rBindings.Invalidate(SID_ZOOM_IN); + rBindings.Invalidate(SID_ZOOM_OUT); + rReq.Done(); + } + } + } + break; + + case SID_ATTR_ZOOM: // status row + case FID_SCALE: + { + bool bSyncZoom = SC_MOD()->GetAppOptions().GetSynchronizeZoom(); + SvxZoomType eOldZoomType = GetZoomType(); + SvxZoomType eNewZoomType = eOldZoomType; + const Fraction& rOldY = GetViewData().GetZoomY(); // Y is shown + sal_uInt16 nOldZoom = static_cast<sal_uInt16>(tools::Long( rOldY * 100 )); + sal_uInt16 nZoom = nOldZoom; + bool bCancel = false; + + if ( pReqArgs ) + { + const SvxZoomItem& rZoomItem = pReqArgs->Get(SID_ATTR_ZOOM); + + eNewZoomType = rZoomItem.GetType(); + nZoom = rZoomItem.GetValue(); + } + else + { + SfxItemSetFixed<SID_ATTR_ZOOM, SID_ATTR_ZOOM> aSet( GetPool() ); + SvxZoomItem aZoomItem( eOldZoomType, nOldZoom, SID_ATTR_ZOOM ); + ScopedVclPtr<AbstractSvxZoomDialog> pDlg; + ScMarkData& rMark = GetViewData().GetMarkData(); + SvxZoomEnableFlags nBtnFlags = SvxZoomEnableFlags::N50 + | SvxZoomEnableFlags::N75 + | SvxZoomEnableFlags::N100 + | SvxZoomEnableFlags::N150 + | SvxZoomEnableFlags::N200 + | SvxZoomEnableFlags::WHOLEPAGE + | SvxZoomEnableFlags::PAGEWIDTH; + + if ( rMark.IsMarked() || rMark.IsMultiMarked() ) + nBtnFlags = nBtnFlags | SvxZoomEnableFlags::OPTIMAL; + + aZoomItem.SetValueSet( nBtnFlags ); + aSet.Put( aZoomItem ); + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + pDlg.disposeAndReset(pFact->CreateSvxZoomDialog(GetFrameWeld(), aSet)); + pDlg->SetLimits( MINZOOM, MAXZOOM ); + + bCancel = ( RET_CANCEL == pDlg->Execute() ); + + // bCancel is True only if we were in the previous if block, + // so no need to check again pDlg + if ( !bCancel ) + { + const SvxZoomItem& rZoomItem = pDlg->GetOutputItemSet()-> + Get( SID_ATTR_ZOOM ); + + eNewZoomType = rZoomItem.GetType(); + nZoom = rZoomItem.GetValue(); + } + } + + if ( !bCancel ) + { + if ( eNewZoomType == SvxZoomType::PERCENT ) + { + if ( nZoom < MINZOOM ) nZoom = MINZOOM; + if ( nZoom > MAXZOOM ) nZoom = MAXZOOM; + } + else + { + nZoom = CalcZoom( eNewZoomType, nOldZoom ); + bCancel = nZoom == 0; + } + + switch ( eNewZoomType ) + { + case SvxZoomType::WHOLEPAGE: + case SvxZoomType::PAGEWIDTH: + SetZoomType( eNewZoomType, bSyncZoom ); + break; + + default: + SetZoomType( SvxZoomType::PERCENT, bSyncZoom ); + } + } + + if ( nZoom != nOldZoom && !bCancel ) + { + if (!GetViewData().IsPagebreakMode()) + { + ScAppOptions aNewOpt = pScMod->GetAppOptions(); + aNewOpt.SetZoom( nZoom ); + aNewOpt.SetZoomType( GetZoomType() ); + pScMod->SetAppOptions( aNewOpt ); + } + Fraction aFract( nZoom, 100 ); + SetZoom( aFract, aFract, bSyncZoom ); + PaintGrid(); + PaintTop(); + PaintLeft(); + rBindings.Invalidate( SID_ATTR_ZOOM ); + rReq.AppendItem( SvxZoomItem( GetZoomType(), nZoom, TypedWhichId<SvxZoomItem>(nSlot) ) ); + rReq.Done(); + } + } + break; + + case SID_ATTR_ZOOMSLIDER: + { + const SfxPoolItem* pItem = nullptr; + bool bSyncZoom = SC_MOD()->GetAppOptions().GetSynchronizeZoom(); + if ( pReqArgs && pReqArgs->GetItemState(SID_ATTR_ZOOMSLIDER, true, &pItem) == SfxItemState::SET ) + { + const sal_uInt16 nCurrentZoom = static_cast<const SvxZoomSliderItem *>(pItem)->GetValue(); + if( nCurrentZoom ) + { + SetZoomType( SvxZoomType::PERCENT, bSyncZoom ); + if (!GetViewData().IsPagebreakMode()) + { + ScAppOptions aNewOpt = pScMod->GetAppOptions(); + aNewOpt.SetZoom( nCurrentZoom ); + collectUIInformation(OUString::number(nCurrentZoom)); + aNewOpt.SetZoomType( GetZoomType() ); + pScMod->SetAppOptions( aNewOpt ); + } + Fraction aFract( nCurrentZoom,100 ); + SetZoom( aFract, aFract, bSyncZoom ); + PaintGrid(); + PaintTop(); + PaintLeft(); + rBindings.Invalidate( SID_ATTR_ZOOMSLIDER ); + rBindings.Invalidate( SID_ZOOM_IN ); + rBindings.Invalidate( SID_ZOOM_OUT ); + rReq.Done(); + } + } + } + break; + + case FID_TAB_SELECTALL: + SelectAllTables(); + rReq.Done(); + break; + + case FID_TAB_DESELECTALL: + DeselectAllTables(); + rReq.Done(); + break; + + case SID_SELECT_TABLES: + { + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + ScMarkData& rMark = rViewData.GetMarkData(); + SCTAB nTabCount = rDoc.GetTableCount(); + SCTAB nTab; + + ::std::vector < sal_Int32 > aIndexList; + const SfxIntegerListItem* pItem = rReq.GetArg<SfxIntegerListItem>(SID_SELECT_TABLES); + if ( pItem ) + aIndexList = pItem->GetList(); + else + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr<AbstractScShowTabDlg> pDlg(pFact->CreateScShowTabDlg(GetFrameWeld())); + pDlg->SetDescription( + ScResId( STR_DLG_SELECTTABLES_TITLE ), + ScResId( STR_DLG_SELECTTABLES_LBNAME ), + GetStaticInterface()->GetSlot(SID_SELECT_TABLES)->GetCommand(), HID_SELECTTABLES ); + + // fill all table names with selection state + OUString aTabName; + for( nTab = 0; nTab < nTabCount; ++nTab ) + { + rDoc.GetName( nTab, aTabName ); + pDlg->Insert( aTabName, rMark.GetTableSelect( nTab ) ); + } + + if( pDlg->Execute() == RET_OK ) + { + aIndexList = pDlg->GetSelectedRows(); + pDlg.disposeAndClear(); + rReq.AppendItem( SfxIntegerListItem( SID_SELECT_TABLES, std::vector(aIndexList) ) ); + } + else + rReq.Ignore(); + } + + if ( !aIndexList.empty() ) + { + sal_uInt16 nSelCount = aIndexList.size(); + sal_uInt16 nSelIx; + SCTAB nFirstVisTab = 0; + + // special case: only hidden tables selected -> do nothing + bool bVisSelected = false; + for( nSelIx = 0; !bVisSelected && (nSelIx < nSelCount); ++nSelIx ) + { + nFirstVisTab = static_cast<SCTAB>(aIndexList[nSelIx]); + bVisSelected = rDoc.IsVisible( nFirstVisTab ); + } + if( !bVisSelected ) + nSelCount = 0; + + // select the tables + if( nSelCount ) + { + for( nTab = 0; nTab < nTabCount; ++nTab ) + rMark.SelectTable( nTab, false ); + + for( nSelIx = 0; nSelIx < nSelCount; ++nSelIx ) + rMark.SelectTable( static_cast<SCTAB>(aIndexList[nSelIx]), true ); + + // activate another table, if current is deselected + if( !rMark.GetTableSelect( rViewData.GetTabNo() ) ) + { + rMark.SelectTable( nFirstVisTab, true ); + SetTabNo( nFirstVisTab ); + } + + rViewData.GetDocShell()->PostPaintExtras(); + SfxBindings& rBind = rViewData.GetBindings(); + rBind.Invalidate( FID_FILL_TAB ); + rBind.Invalidate( FID_TAB_DESELECTALL ); + } + + rReq.Done(); + } + } + break; + + case SID_OUTLINE_DELETEALL: + RemoveAllOutlines(); + rReq.Done(); + break; + + case SID_AUTO_OUTLINE: + AutoOutline(); + rReq.Done(); + break; + + case SID_WINDOW_SPLIT: + { + ScSplitMode eHSplit = GetViewData().GetHSplitMode(); + ScSplitMode eVSplit = GetViewData().GetVSplitMode(); + if ( eHSplit == SC_SPLIT_NORMAL || eVSplit == SC_SPLIT_NORMAL ) // remove + RemoveSplit(); + else if ( eHSplit == SC_SPLIT_FIX || eVSplit == SC_SPLIT_FIX ) // normal + FreezeSplitters( false ); + else // create + SplitAtCursor(); + rReq.Done(); + + InvalidateSplit(); + } + break; + + case SID_WINDOW_FIX: + { + if (!comphelper::LibreOfficeKit::isActive()) + { + ScSplitMode eHSplit = GetViewData().GetHSplitMode(); + ScSplitMode eVSplit = GetViewData().GetVSplitMode(); + if ( eHSplit == SC_SPLIT_FIX || eVSplit == SC_SPLIT_FIX ) // remove + RemoveSplit(); + else + FreezeSplitters( true, SC_SPLIT_METHOD_CURSOR); // create or fixate + rReq.Done(); + InvalidateSplit(); + } + else + { + ScViewData& rViewData = GetViewData(); + SCTAB nThisTab = rViewData.GetTabNo(); + bool bChangedX = false, bChangedY = false; + if (rViewData.GetLOKSheetFreezeIndex(true) > 0 || + rViewData.GetLOKSheetFreezeIndex(false) > 0 ) // remove freeze + { + bChangedX = rViewData.RemoveLOKFreeze(); + } // create or fixate + else + { + bChangedX = rViewData.SetLOKSheetFreezeIndex(rViewData.GetCurX(), true); // Freeze column + bChangedY = rViewData.SetLOKSheetFreezeIndex(rViewData.GetCurY(), false); // Freeze row + } + + rReq.Done(); + if (bChangedX || bChangedY) + { + rBindings.Invalidate( SID_WINDOW_FIX ); + rBindings.Invalidate( SID_WINDOW_FIX_COL ); + rBindings.Invalidate( SID_WINDOW_FIX_ROW ); + // Invalidate the slot for all views on the same tab of the document. + SfxLokHelper::forEachOtherView(this, [nThisTab](ScTabViewShell* pOther) { + ScViewData& rOtherViewData = pOther->GetViewData(); + if (rOtherViewData.GetTabNo() != nThisTab) + return; + + SfxBindings& rOtherBind = rOtherViewData.GetBindings(); + rOtherBind.Invalidate( SID_WINDOW_FIX ); + rOtherBind.Invalidate( SID_WINDOW_FIX_COL ); + rOtherBind.Invalidate( SID_WINDOW_FIX_ROW ); + }); + if (!GetViewData().GetDocShell()->IsReadOnly()) + GetViewData().GetDocShell()->SetDocumentModified(); + } + } + } + break; + + case SID_WINDOW_FIX_COL: + case SID_WINDOW_FIX_ROW: + { + bool bIsCol = (nSlot == SID_WINDOW_FIX_COL); + sal_Int32 nFreezeIndex = 1; + if (const SfxInt32Item* pItem = rReq.GetArg<SfxInt32Item>(nSlot)) + { + nFreezeIndex = pItem->GetValue(); + if (nFreezeIndex < 0) + nFreezeIndex = 0; + } + + if (comphelper::LibreOfficeKit::isActive()) + { + ScViewData& rViewData = GetViewData(); + SCTAB nThisTab = rViewData.GetTabNo(); + bool bChanged = rViewData.SetLOKSheetFreezeIndex(nFreezeIndex, bIsCol); + rReq.Done(); + if (bChanged) + { + rBindings.Invalidate( SID_WINDOW_FIX ); + rBindings.Invalidate(nSlot); + // Invalidate the slot for all views on the same tab of the document. + SfxLokHelper::forEachOtherView(this, [nSlot, nThisTab](ScTabViewShell* pOther) { + ScViewData& rOtherViewData = pOther->GetViewData(); + if (rOtherViewData.GetTabNo() != nThisTab) + return; + + SfxBindings& rOtherBind = rOtherViewData.GetBindings(); + rOtherBind.Invalidate( SID_WINDOW_FIX ); + rOtherBind.Invalidate(nSlot); + }); + if (!GetViewData().GetDocShell()->IsReadOnly()) + GetViewData().GetDocShell()->SetDocumentModified(); + } + } + else + { + FreezeSplitters( true, bIsCol ? SC_SPLIT_METHOD_COL : SC_SPLIT_METHOD_ROW, nFreezeIndex); + rReq.Done(); + InvalidateSplit(); + } + } + break; + + case FID_CHG_SHOW: + { + sal_uInt16 nId = ScHighlightChgDlgWrapper::GetChildWindowId(); + SfxChildWindow* pWnd = rThisFrame.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd == nullptr ); + } + break; + + case FID_CHG_ACCEPT: + { + rThisFrame.ToggleChildWindow(ScAcceptChgDlgWrapper::GetChildWindowId()); + GetViewFrame().GetBindings().Invalidate(FID_CHG_ACCEPT); + rReq.Done (); + + /* + sal_uInt16 nId = ScAcceptChgDlgWrapper::GetChildWindowId(); + SfxChildWindow* pWnd = rThisFrame.GetChildWindow( nId ); + + pScMod->SetRefDialog( nId, pWnd ? sal_False : sal_True ); + */ + } + break; + + case FID_CHG_COMMENT: + { + ScViewData& rData = GetViewData(); + ScAddress aCursorPos( rData.GetCurX(), rData.GetCurY(), rData.GetTabNo() ); + ScDocShell* pDocSh = rData.GetDocShell(); + + ScChangeAction* pAction = pDocSh->GetChangeAction( aCursorPos ); + if ( pAction ) + { + const SfxPoolItem* pItem; + if ( pReqArgs && + pReqArgs->GetItemState( nSlot, true, &pItem ) == SfxItemState::SET && + dynamic_cast<const SfxStringItem*>( pItem) != nullptr ) + { + OUString aComment = static_cast<const SfxStringItem*>(pItem)->GetValue(); + pDocSh->SetChangeComment( pAction, aComment ); + rReq.Done(); + } + else + { + pDocSh->ExecuteChangeCommentDialog(pAction, GetFrameWeld()); + rReq.Done(); + } + } + } + break; + + case SID_CREATE_SW_DRAWVIEW: + // is called by Forms, when the DrawView has to be created with all + // the extras + if (!GetScDrawView()) + { + GetViewData().GetDocShell()->MakeDrawLayer(); + rBindings.InvalidateAll(false); + } + break; + + case FID_PROTECT_DOC: + { + ScDocument& rDoc = GetViewData().GetDocument(); + + if( pReqArgs ) + { + const SfxPoolItem* pItem; + if( pReqArgs->HasItem( FID_PROTECT_DOC, &pItem ) && + static_cast<const SfxBoolItem*>(pItem)->GetValue() == rDoc.IsDocProtected() ) + { + rReq.Ignore(); + break; + } + } + + ScDocProtection* pProtect = rDoc.GetDocProtection(); + if (pProtect && pProtect->isProtected()) + { + bool bCancel = false; + OUString aPassword; + + if (pProtect->isProtectedWithPass()) + { + OUString aText(ScResId(SCSTR_PASSWORD)); + + SfxPasswordDialog aDlg(GetFrameWeld(), &aText); + aDlg.set_title(ScResId(SCSTR_UNPROTECTDOC)); + aDlg.SetMinLen(0); + aDlg.set_help_id(GetStaticInterface()->GetSlot(FID_PROTECT_DOC)->GetCommand()); + aDlg.SetEditHelpId(HID_PASSWD_DOC); + + if (aDlg.run() == RET_OK) + aPassword = aDlg.GetPassword(); + else + bCancel = true; + } + if (!bCancel) + { + Unprotect( TABLEID_DOC, aPassword ); + rReq.AppendItem( SfxBoolItem( FID_PROTECT_DOC, false ) ); + rReq.Done(); + } + } + else + { + OUString aText(ScResId(SCSTR_PASSWORDOPT)); + + SfxPasswordDialog aDlg(GetFrameWeld(), &aText); + aDlg.set_title(ScResId(SCSTR_PROTECTDOC)); + aDlg.SetMinLen( 0 ); + aDlg.set_help_id(GetStaticInterface()->GetSlot(FID_PROTECT_DOC)->GetCommand()); + aDlg.SetEditHelpId(HID_PASSWD_DOC); + aDlg.ShowExtras(SfxShowExtras::CONFIRM); + aDlg.SetConfirmHelpId(HID_PASSWD_DOC_CONFIRM); + + if (aDlg.run() == RET_OK) + { + OUString aPassword = aDlg.GetPassword(); + ProtectDoc( aPassword ); + rReq.AppendItem( SfxBoolItem( FID_PROTECT_DOC, true ) ); + rReq.Done(); + } + } + rBindings.Invalidate( FID_PROTECT_DOC ); + } + break; + + case FID_PROTECT_TABLE: + { + ScDocument& rDoc = GetViewData().GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + bool bOldProtection = rDoc.IsTabProtected(nTab); + + if( pReqArgs ) + { + const SfxPoolItem* pItem; + bool bNewProtection = !bOldProtection; + if( pReqArgs->HasItem( FID_PROTECT_TABLE, &pItem ) ) + bNewProtection = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + if( bNewProtection == bOldProtection ) + { + rReq.Ignore(); + break; + } + } + + if (bOldProtection) + { + // Unprotect a protected sheet. + + const ScTableProtection* pProtect = rDoc.GetTabProtection(nTab); + if (pProtect && pProtect->isProtectedWithPass()) + { + OUString aText( ScResId(SCSTR_PASSWORDOPT) ); + SfxPasswordDialog aDlg(GetFrameWeld(), &aText); + aDlg.set_title(ScResId(SCSTR_UNPROTECTTAB)); + aDlg.SetMinLen(0); + aDlg.set_help_id(GetStaticInterface()->GetSlot(FID_PROTECT_TABLE)->GetCommand()); + aDlg.SetEditHelpId(HID_PASSWD_TABLE); + + if (aDlg.run() == RET_OK) + { + OUString aPassword = aDlg.GetPassword(); + Unprotect(nTab, aPassword); + } + } + else + // this sheet is not password-protected. + Unprotect(nTab, OUString()); + + if (!pReqArgs) + { + rReq.AppendItem( SfxBoolItem(FID_PROTECT_TABLE, false) ); + rReq.Done(); + } + } + else + { + // Protect a current sheet. + + ScTableProtectionDlg aDlg(GetFrameWeld()); + + const ScTableProtection* pProtect = rDoc.GetTabProtection(nTab); + if (pProtect) + aDlg.SetDialogData(*pProtect); + + if (aDlg.run() == RET_OK) + { + pScMod->InputEnterHandler(); + + ScTableProtection aNewProtect; + aDlg.WriteData(aNewProtect); + ProtectSheet(nTab, aNewProtect); + if (!pReqArgs) + { + rReq.AppendItem( SfxBoolItem(FID_PROTECT_TABLE, true) ); + rReq.Done(); + } + } + } + TabChanged(); + UpdateInputHandler(true); // to immediately enable input again + SelectionChanged(); + } + break; + case SID_THEME_DIALOG: + { + MakeDrawLayer(); + ScViewData& rViewData = GetViewData(); + ScDocument& rDocument = rViewData.GetDocument(); + ScDrawLayer* pModel = rDocument.GetDrawLayer(); + auto const& pTheme = pModel->getTheme(); + if (pTheme) + { + vcl::Window* pWin = rViewData.GetActiveWin(); + auto pDialog = std::make_shared<svx::ThemeDialog>(pWin ? pWin->GetFrameWeld() : nullptr, pTheme.get()); + weld::DialogController::runAsync(pDialog, [this, pDialog](sal_uInt32 nResult) { + if (RET_OK != nResult) + return; + + auto pColorSet = pDialog->getCurrentColorSet(); + if (pColorSet) + { + sc::ThemeColorChanger aChanger(*GetViewData().GetDocShell()); + aChanger.apply(pColorSet); + } + }); + } + rReq.Done(); + } + break; + case SID_OPT_LOCALE_CHANGED : + { // locale changed, SYSTEM number formats changed => repaint cell contents + PaintGrid(); + rReq.Done(); + } + break; + + default: + OSL_FAIL("Unknown Slot at ScTabViewShell::Execute"); + break; + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabvwsh4.cxx b/sc/source/ui/view/tabvwsh4.cxx new file mode 100644 index 0000000000..345a33534d --- /dev/null +++ b/sc/source/ui/view/tabvwsh4.cxx @@ -0,0 +1,1946 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <sal/config.h> + +#include <formdata.hxx> + +#include <sfx2/app.hxx> +#include <svx/dialogs.hrc> +#include <svx/extrusionbar.hxx> +#include <svx/fontworkbar.hxx> +#include <editeng/borderline.hxx> +#include <svx/fmshell.hxx> +#include <svx/sidebar/ContextChangeEventMultiplexer.hxx> +#include <sfx2/printer.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/ipclient.hxx> +#include <tools/urlobj.hxx> +#include <sfx2/docfile.hxx> +#include <tools/svborder.hxx> + +#include <IAnyRefDialog.hxx> +#include <tabvwsh.hxx> +#include <sc.hrc> +#include <globstr.hrc> +#include <docsh.hxx> +#include <scmod.hxx> +#include <appoptio.hxx> +#include <drawsh.hxx> +#include <drformsh.hxx> +#include <editsh.hxx> +#include <pivotsh.hxx> +#include <SparklineShell.hxx> +#include <auditsh.hxx> +#include <drtxtob.hxx> +#include <inputhdl.hxx> +#include <editutil.hxx> +#include <inputopt.hxx> +#include <inputwin.hxx> +#include <dbdata.hxx> +#include <reffact.hxx> +#include <viewuno.hxx> +#include <dispuno.hxx> +#include <chgtrack.hxx> +#include <cellsh.hxx> +#include <oleobjsh.hxx> +#include <chartsh.hxx> +#include <graphsh.hxx> +#include <mediash.hxx> +#include <pgbrksh.hxx> +#include <dpobject.hxx> +#include <prevwsh.hxx> +#include <scextopt.hxx> +#include <drawview.hxx> +#include <fupoor.hxx> +#include <navsett.hxx> +#include <scabstdlg.hxx> +#include <externalrefmgr.hxx> +#include <defaultsoptions.hxx> +#include <markdata.hxx> +#include <preview.hxx> +#include <docoptio.hxx> +#include <documentlinkmgr.hxx> +#include <gridwin.hxx> + +#include <com/sun/star/document/XDocumentProperties.hpp> +#include <sfx2/lokhelper.hxx> +#include <comphelper/flagguard.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/lok.hxx> +#include <sfx2/sidebar/SidebarController.hxx> + +using namespace com::sun::star; +using namespace sfx2::sidebar; + +namespace { + +bool inChartOrMathContext(const ScTabViewShell* pViewShell) +{ + SidebarController* pSidebar = SidebarController::GetSidebarControllerForView(pViewShell); + if (pSidebar) + return pSidebar->hasChartOrMathContextCurrently(); + + return false; +} + +} // anonymous namespace + +void ScTabViewShell::Activate(bool bMDI) +{ + SfxViewShell::Activate(bMDI); + bIsActive = true; + // here no GrabFocus, otherwise there will be problems when something is edited inplace! + + if ( bMDI ) + { + // for input row (ClearCache) + ScModule* pScMod = SC_MOD(); + pScMod->ViewShellChanged(/*bStopEditing=*/ !comphelper::LibreOfficeKit::isActive()); + + ActivateView( true, bFirstActivate ); + + // update AutoCorrect, if Writer has newly created this + UpdateDrawTextOutliner(); + + // RegisterNewTargetNames does not exist anymore + + SfxViewFrame& rThisFrame = GetViewFrame(); + if ( mpInputHandler && rThisFrame.HasChildWindow(FID_INPUTLINE_STATUS) ) + { + // actually only required for Reload (last version): + // The InputWindow remains, but the View along with the InputHandler is newly created, + // that is why the InputHandler must be set at the InputWindow. + SfxChildWindow* pChild = rThisFrame.GetChildWindow(FID_INPUTLINE_STATUS); + if (pChild) + { + ScInputWindow* pWin = static_cast<ScInputWindow*>(pChild->GetWindow()); + if (pWin && pWin->IsVisible()) + { + pWin->NumLinesChanged(); // tdf#150664 + ScInputHandler* pOldHdl=pWin->GetInputHandler(); + + SfxViewShell* pSh = SfxViewShell::GetFirst( true, checkSfxViewShell<ScTabViewShell> ); + while ( pSh!=nullptr && pOldHdl!=nullptr) + { + // Hmm, what if pSh is a shell for a different document? But as this code + // does not seem to be LibreOfficeKit-specific, probably that doesn't + // happen, because having multiple documents open simultaneously has of + // course not been a problem at all in traditional desktop LibreOffice. + // (Unlike in a LibreOfficeKit-based process where it has been a problem.) + if (static_cast<ScTabViewShell*>(pSh)->GetInputHandler() == pOldHdl) + { + pOldHdl->ResetDelayTimer(); + break; + } + pSh = SfxViewShell::GetNext( *pSh, true, checkSfxViewShell<ScTabViewShell> ); + } + + pWin->SetInputHandler( mpInputHandler.get() ); + } + } + } + + bool isLOK = comphelper::LibreOfficeKit::isActive(); + UpdateInputHandler( /*bForce=*/ !isLOK, /*bStopEditing=*/ !isLOK ); + + if ( bFirstActivate ) + { + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScNavigatorUpdateAll ) ); + bFirstActivate = false; + + // ReadExtOptions (view settings from Excel import) must also be done + // after the ctor, because of the potential calls to Window::Show. + // Even after a bugfix (Window::Show no longer notifies the access + // bridge, it's done in ImplSetReallyVisible), there are problems if Window::Show + // is called during the ViewShell ctor and reschedules asynchronous calls + // (for example from the FmFormShell ctor). + ScExtDocOptions* pExtOpt = GetViewData().GetDocument().GetExtDocOptions(); + if ( pExtOpt && pExtOpt->IsChanged() ) + { + GetViewData().ReadExtOptions(*pExtOpt); // Excel view settings + SetTabNo( GetViewData().GetTabNo(), true ); + pExtOpt->SetChanged( false ); + } + } + + pScActiveViewShell = this; + + ScInputHandler* pHdl = pScMod->GetInputHdl(this); + if (pHdl) + { + pHdl->SetRefScale( GetViewData().GetZoomX(), GetViewData().GetZoomY() ); + } + + // update change dialog + + if ( rThisFrame.HasChildWindow(FID_CHG_ACCEPT) ) + { + SfxChildWindow* pChild = rThisFrame.GetChildWindow(FID_CHG_ACCEPT); + if (pChild) + { + static_cast<ScAcceptChgDlgWrapper*>(pChild)->ReInitDlg(); + } + } + + if(pScMod->IsRefDialogOpen()) + { + sal_uInt16 nModRefDlgId=pScMod->GetCurRefDlgId(); + SfxChildWindow* pChildWnd = rThisFrame.GetChildWindow( nModRefDlgId ); + if ( pChildWnd ) + { + if (auto pController = pChildWnd->GetController()) + { + IAnyRefDialog* pRefDlg = dynamic_cast<IAnyRefDialog*>(pController.get()); + if (pRefDlg) + pRefDlg->ViewShellChanged(); + } + } + } + } + + // don't call CheckSelectionTransfer here - activating a view should not change the + // primary selection (may be happening just because the mouse was moved over the window) + + if (!inChartOrMathContext(this)) + { + ContextChangeEventMultiplexer::NotifyContextChange( + GetController(), + vcl::EnumContext::Context::Default); + } +} + +void ScTabViewShell::Deactivate(bool bMDI) +{ + HideTip(); + + ScDocument& rDoc = GetViewData().GetDocument(); + + ScChangeTrack* pChanges = rDoc.GetChangeTrack(); + + if(pChanges!=nullptr) + { + Link<ScChangeTrack&,void> aLink; + pChanges->SetModifiedLink(aLink); + } + + SfxViewShell::Deactivate(bMDI); + bIsActive = false; + ScInputHandler* pHdl = SC_MOD()->GetInputHdl(this); + + if( bMDI && !comphelper::LibreOfficeKit::isActive()) + { + // during shell deactivation, shells must not be switched, or the loop + // through the shell stack (in SfxDispatcher::DoDeactivate_Impl) will not work + bool bOldDontSwitch = bDontSwitch; + bDontSwitch = true; + + ActivateView( false, false ); + + if ( GetViewFrame().GetFrame().IsInPlace() ) // inplace + GetViewData().GetDocShell()->UpdateOle(GetViewData(), true); + + if ( pHdl ) + pHdl->NotifyChange( nullptr, true ); // timer-delayed due to document switching + + if (pScActiveViewShell == this) + pScActiveViewShell = nullptr; + + bDontSwitch = bOldDontSwitch; + } + else + { + HideNoteMarker(); // note marker + + if ( pHdl ) + pHdl->HideTip(); // Hide formula auto input tip + } +} + +void ScTabViewShell::SetActive() +{ + // SFX-View would like to activate itself, since then magical things would happen + // (eg else the designer may crash) + ActiveGrabFocus(); +} + +bool ScTabViewShell::PrepareClose(bool bUI) +{ + comphelper::FlagRestorationGuard aFlagGuard(bInPrepareClose, true); + + // Call EnterHandler even in formula mode here, + // so a formula change in an embedded object isn't lost + // (ScDocShell::PrepareClose isn't called then). + ScInputHandler* pHdl = SC_MOD()->GetInputHdl( this ); + if ( pHdl && pHdl->IsInputMode() ) + { + pHdl->EnterHandler(); + } + + // draw text edit mode must be closed + FuPoor* pPoor = GetDrawFuncPtr(); + if (pPoor && IsDrawTextShell()) + { + // "clean" end of text edit, including note handling, subshells and draw func switching, + // as in FuDraw and ScTabView::DrawDeselectAll + GetViewData().GetDispatcher().Execute( pPoor->GetSlotID(), SfxCallMode::SLOT | SfxCallMode::RECORD ); + } + ScDrawView* pDrView = GetScDrawView(); + if ( pDrView ) + { + // force end of text edit, to be safe + // ScEndTextEdit must always be used, to ensure correct UndoManager + pDrView->ScEndTextEdit(); + } + + if ( pFormShell ) + { + bool bRet = pFormShell->PrepareClose(bUI); + if (!bRet) + return bRet; + } + return SfxViewShell::PrepareClose(bUI); +} + +// calculate zoom for in-place +// from the ratio of VisArea and window size of GridWin + +void ScTabViewShell::UpdateOleZoom() +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + if ( pDocSh->GetCreateMode() == SfxObjectCreateMode::EMBEDDED ) + { + //TODO/LATER: is there a difference between the two GetVisArea methods? + Size aObjSize = static_cast<const SfxObjectShell*>(pDocSh)->GetVisArea().GetSize(); + if ( !aObjSize.IsEmpty() ) + { + vcl::Window* pWin = GetActiveWin(); + Size aWinHMM = pWin->PixelToLogic(pWin->GetOutputSizePixel(), MapMode(MapUnit::Map100thMM)); + SetZoomFactor( Fraction( aWinHMM.Width(),aObjSize.Width() ), + Fraction( aWinHMM.Height(),aObjSize.Height() ) ); + } + } +} + +void ScTabViewShell::InnerResizePixel( const Point &rOfs, const Size &rSize, bool inplaceEditModeChange ) +{ + Size aNewSize( rSize ); + if ( GetViewFrame().GetFrame().IsInPlace() ) + { + SvBorder aBorder; + GetBorderSize( aBorder, rSize ); + SetBorderPixel( aBorder ); + + Size aObjSize = GetObjectShell()->GetVisArea().GetSize(); + + Size aSize( rSize ); + aSize.AdjustWidth( -(aBorder.Left() + aBorder.Right()) ); + aSize.AdjustHeight( -(aBorder.Top() + aBorder.Bottom()) ); + + if ( !aObjSize.IsEmpty() ) + { + Size aLogicSize = GetWindow()->PixelToLogic(aSize, MapMode(MapUnit::Map100thMM)); + SfxViewShell::SetZoomFactor( Fraction( aLogicSize.Width(),aObjSize.Width() ), + Fraction( aLogicSize.Height(),aObjSize.Height() ) ); + } + + Point aPos( rOfs ); + aPos.AdjustX(aBorder.Left() ); + aPos.AdjustY(aBorder.Top() ); + GetWindow()->SetPosSizePixel( aPos, aSize ); + } + else + { + SvBorder aBorder; + GetBorderSize( aBorder, rSize ); + SetBorderPixel( aBorder ); + aNewSize.AdjustWidth(aBorder.Left() + aBorder.Right() ); + aNewSize.AdjustHeight(aBorder.Top() + aBorder.Bottom() ); + } + + DoResize( rOfs, aNewSize, true ); // rSize = size of gridwin + + UpdateOleZoom(); // calculate zoom for in-place + + if (!inplaceEditModeChange) + { + GetViewData().GetDocShell()->SetDocumentModified(); + } +} + +void ScTabViewShell::OuterResizePixel( const Point &rOfs, const Size &rSize ) +{ + SvBorder aBorder; + GetBorderSize( aBorder, rSize ); + SetBorderPixel( aBorder ); + + DoResize( rOfs, rSize ); // position and size of tabview as passed + + // ForceMove as replacement for Sfx-Move mechanism + // (aWinPos must be kept current, so that ForceMove works for Ole deactivation) + + ForceMove(); +} + +void ScTabViewShell::SetZoomFactor( const Fraction &rZoomX, const Fraction &rZoomY ) +{ + // for OLE... + + Fraction aFrac20( 1,5 ); + Fraction aFrac400( 4,1 ); + + Fraction aNewX( rZoomX ); + if ( aNewX < aFrac20 ) + aNewX = aFrac20; + if ( aNewX > aFrac400 ) + aNewX = aFrac400; + Fraction aNewY( rZoomY ); + if ( aNewY < aFrac20 ) + aNewY = aFrac20; + if ( aNewY > aFrac400 ) + aNewY = aFrac400; + + GetViewData().UpdateScreenZoom( aNewX, aNewY ); + SetZoom( aNewX, aNewY, true ); + + PaintGrid(); + PaintTop(); + PaintLeft(); + + SfxViewShell::SetZoomFactor( rZoomX, rZoomY ); +} + +void ScTabViewShell::QueryObjAreaPixel( tools::Rectangle& rRect ) const +{ + // adjust to entire cells (in 1/100 mm) + + Size aPixelSize = rRect.GetSize(); + vcl::Window* pWin = const_cast<ScTabViewShell*>(this)->GetActiveWin(); + Size aLogicSize = pWin->PixelToLogic( aPixelSize ); + + const ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + ScSplitPos ePos = rViewData.GetActivePart(); + SCCOL nCol = rViewData.GetPosX(WhichH(ePos)); + SCROW nRow = rViewData.GetPosY(WhichV(ePos)); + SCTAB nTab = rViewData.GetTabNo(); + bool bNegativePage = rDoc.IsNegativePage( nTab ); + + tools::Rectangle aLogicRect = rDoc.GetMMRect( nCol, nRow, nCol, nRow, nTab ); + if ( bNegativePage ) + { + // use right edge of aLogicRect, and aLogicSize + aLogicRect.SetLeft( aLogicRect.Right() - aLogicSize.Width() + 1 ); // Right() is set below + } + aLogicRect.SetSize( aLogicSize ); + + rViewData.GetDocShell()->SnapVisArea( aLogicRect ); + + rRect.SetSize( pWin->LogicToPixel( aLogicRect.GetSize() ) ); +} + +void ScTabViewShell::Move() +{ + Point aNewPos = GetViewFrame().GetWindow().OutputToScreenPixel(Point()); + + if (aNewPos != aWinPos) + { + StopMarking(); + aWinPos = aNewPos; + } +} + +void ScTabViewShell::ShowCursor(bool /* bOn */) +{ +/*!!! ShowCursor is not called as a pair as in gridwin. + here the CursorLockCount for Gridwin must be set directly to 0 + + if (bOn) + ShowAllCursors(); + else + HideAllCursors(); +*/ +} + +void ScTabViewShell::WriteUserData(OUString& rData, bool /* bBrowse */) +{ + GetViewData().WriteUserData(rData); +} + +void ScTabViewShell::WriteUserDataSequence (uno::Sequence < beans::PropertyValue >& rSettings ) +{ + GetViewData().WriteUserDataSequence(rSettings); +} + +void ScTabViewShell::ReadUserData(const OUString& rData, bool /* bBrowse */) +{ + if ( !GetViewData().GetDocShell()->IsPreview() ) + DoReadUserData( rData ); +} + +void ScTabViewShell::ReadUserDataSequence (const uno::Sequence < beans::PropertyValue >& rSettings ) +{ + if ( !GetViewData().GetDocShell()->IsPreview() ) + DoReadUserDataSequence( rSettings ); +} + +void ScTabViewShell::DoReadUserDataSequence( const uno::Sequence < beans::PropertyValue >& rSettings ) +{ + vcl::Window* pOldWin = GetActiveWin(); + bool bFocus = pOldWin && pOldWin->HasFocus(); + + GetViewData().ReadUserDataSequence(rSettings); + SetTabNo( GetViewData().GetTabNo(), true ); + + if ( GetViewData().IsPagebreakMode() ) + SetCurSubShell( GetCurObjectSelectionType(), true ); + + vcl::Window* pNewWin = GetActiveWin(); + if (pNewWin && pNewWin != pOldWin) + { + SetWindow( pNewWin ); //! is this ViewShell always active??? + if (bFocus) + pNewWin->GrabFocus(); + WindowChanged(); // drawing layer (for instance #56771#) + } + + if (GetViewData().GetHSplitMode() == SC_SPLIT_FIX || + GetViewData().GetVSplitMode() == SC_SPLIT_FIX) + { + InvalidateSplit(); + } + + ZoomChanged(); + + TestHintWindow(); + + //! if ViewData has more tables than document, remove tables in ViewData +} + +// DoReadUserData is also called from ctor when switching from print preview + +void ScTabViewShell::DoReadUserData( std::u16string_view rData ) +{ + vcl::Window* pOldWin = GetActiveWin(); + bool bFocus = pOldWin && pOldWin->HasFocus(); + + GetViewData().ReadUserData(rData); + SetTabNo( GetViewData().GetTabNo(), true ); + + if ( GetViewData().IsPagebreakMode() ) + SetCurSubShell( GetCurObjectSelectionType(), true ); + + vcl::Window* pNewWin = GetActiveWin(); + if (pNewWin && pNewWin != pOldWin) + { + SetWindow( pNewWin ); //! is this ViewShell always active??? + if (bFocus) + pNewWin->GrabFocus(); + WindowChanged(); // drawing layer (for instance #56771#) + } + + if (GetViewData().GetHSplitMode() == SC_SPLIT_FIX || + GetViewData().GetVSplitMode() == SC_SPLIT_FIX) + { + InvalidateSplit(); + } + + ZoomChanged(); + + TestHintWindow(); + + //! if ViewData has more tables than document, remove tables in ViewData +} + +void ScTabViewShell::UpdateDrawShell() +{ + // Called after user interaction that may delete the selected drawing object. + // Remove DrawShell if nothing is selected. + + SdrView* pDrView = GetScDrawView(); + if ( pDrView && !pDrView->AreObjectsMarked() && !IsDrawSelMode() ) + SetDrawShell( false ); +} + +void ScTabViewShell::SetDrawShellOrSub() +{ + bActiveDrawSh = true; + + if(bActiveDrawFormSh) + { + SetCurSubShell(OST_DrawForm); + } + else if(bActiveGraphicSh) + { + SetCurSubShell(OST_Graphic); + } + else if(bActiveMediaSh) + { + SetCurSubShell(OST_Media); + } + else if(bActiveChartSh) + { + SetCurSubShell(OST_Chart); + } + else if(bActiveOleObjectSh) + { + SetCurSubShell(OST_OleObject); + } + else + { + SetCurSubShell(OST_Drawing, true /* force: different toolbars are + visible concerning shape type + and shape state */); + } +} + +void ScTabViewShell::SetDrawShell( bool bActive ) +{ + if(bActive) + { + SetCurSubShell(OST_Drawing, true /* force: different toolbars are + visible concerning shape type + and shape state */); + } + else + { + if(bActiveDrawFormSh || bActiveDrawSh || + bActiveGraphicSh || bActiveMediaSh || bActiveOleObjectSh|| + bActiveChartSh || bActiveDrawTextSh) + { + SetCurSubShell(OST_Cell); + } + bActiveDrawFormSh=false; + bActiveGraphicSh=false; + bActiveMediaSh=false; + bActiveOleObjectSh=false; + bActiveChartSh=false; + } + + bool bWasDraw = bActiveDrawSh || bActiveDrawTextSh; + + bActiveDrawSh = bActive; + bActiveDrawTextSh = false; + + if ( !bActive ) + { + ResetDrawDragMode(); // switch off Mirror / Rotate + + if (bWasDraw && (GetViewData().GetHSplitMode() == SC_SPLIT_FIX || + GetViewData().GetVSplitMode() == SC_SPLIT_FIX)) + { + // adjust active part to cursor, etc. + MoveCursorAbs( GetViewData().GetCurX(), GetViewData().GetCurY(), + SC_FOLLOW_NONE, false, false, true ); + } + } +} + +void ScTabViewShell::SetDrawTextShell( bool bActive ) +{ + bActiveDrawTextSh = bActive; + if ( bActive ) + { + bActiveDrawFormSh=false; + bActiveGraphicSh=false; + bActiveMediaSh=false; + bActiveOleObjectSh=false; + bActiveChartSh=false; + bActiveDrawSh = false; + SetCurSubShell(OST_DrawText); + } + else + SetCurSubShell(OST_Cell); + +} + +void ScTabViewShell::SetPivotShell( bool bActive ) +{ + // SetPivotShell is called from CursorPosChanged every time + // -> don't change anything except switching between cell and pivot shell + + if (eCurOST != OST_Pivot && eCurOST != OST_Cell) + return; + + if ( bActive ) + { + bActiveDrawTextSh = bActiveDrawSh = false; + bActiveDrawFormSh=false; + bActiveGraphicSh=false; + bActiveMediaSh=false; + bActiveOleObjectSh=false; + bActiveChartSh=false; + SetCurSubShell(OST_Pivot); + } + else + SetCurSubShell(OST_Cell); +} + +void ScTabViewShell::SetSparklineShell(bool bActive) +{ + if (eCurOST != OST_Sparkline && eCurOST != OST_Cell) + return; + + if (bActive) + { + bActiveDrawTextSh = bActiveDrawSh = false; + bActiveDrawFormSh=false; + bActiveGraphicSh=false; + bActiveMediaSh=false; + bActiveOleObjectSh=false; + bActiveChartSh=false; + SetCurSubShell(OST_Sparkline); + } + else + SetCurSubShell(OST_Cell); +} + +void ScTabViewShell::SetAuditShell( bool bActive ) +{ + if ( bActive ) + { + bActiveDrawTextSh = bActiveDrawSh = false; + bActiveDrawFormSh=false; + bActiveGraphicSh=false; + bActiveMediaSh=false; + bActiveOleObjectSh=false; + bActiveChartSh=false; + SetCurSubShell(OST_Auditing); + } + else + SetCurSubShell(OST_Cell); +} + +void ScTabViewShell::SetDrawFormShell( bool bActive ) +{ + bActiveDrawFormSh = bActive; + + if(bActiveDrawFormSh) + SetCurSubShell(OST_DrawForm); +} +void ScTabViewShell::SetChartShell( bool bActive ) +{ + bActiveChartSh = bActive; + + if(bActiveChartSh) + SetCurSubShell(OST_Chart); +} + +void ScTabViewShell::SetGraphicShell( bool bActive ) +{ + bActiveGraphicSh = bActive; + + if(bActiveGraphicSh) + SetCurSubShell(OST_Graphic); +} + +void ScTabViewShell::SetMediaShell( bool bActive ) +{ + bActiveMediaSh = bActive; + + if(bActiveMediaSh) + SetCurSubShell(OST_Media); +} + +void ScTabViewShell::SetOleObjectShell( bool bActive ) +{ + bActiveOleObjectSh = bActive; + + if(bActiveOleObjectSh) + SetCurSubShell(OST_OleObject); + else + SetCurSubShell(OST_Cell); +} + +void ScTabViewShell::SetEditShell(EditView* pView, bool bActive ) +{ + if(bActive) + { + if (pEditShell) + pEditShell->SetEditView( pView ); + else + pEditShell.reset( new ScEditShell(pView, GetViewData()) ); + + SetCurSubShell(OST_Editing); + } + else if(bActiveEditSh) + { + SetCurSubShell(OST_Cell); + } + bActiveEditSh = bActive; +} + +void ScTabViewShell::SetCurSubShell(ObjectSelectionType eOST, bool bForce) +{ + ScViewData& rViewData = GetViewData(); + ScDocShell* pDocSh = rViewData.GetDocShell(); + + if(bDontSwitch) return; + + if(!pCellShell) // is anyway always used + { + pCellShell.reset(new ScCellShell(GetViewData(), GetFrameWin())); + pCellShell->SetRepeatTarget( &aTarget ); + } + + bool bPgBrk = rViewData.IsPagebreakMode(); + + if(bPgBrk && !pPageBreakShell) + { + pPageBreakShell.reset( new ScPageBreakShell( this ) ); + pPageBreakShell->SetRepeatTarget( &aTarget ); + } + + if ( !(eOST!=eCurOST || bForce) ) + return; + + bool bCellBrush = false; // "format paint brush" allowed for cells + bool bDrawBrush = false; // "format paint brush" allowed for drawing objects + + if(eCurOST!=OST_NONE) RemoveSubShell(); + + if (pFormShell && !bFormShellAtTop) + AddSubShell(*pFormShell); // add below own subshells + + switch(eOST) + { + case OST_Cell: + { + AddSubShell(*pCellShell); + if(bPgBrk) AddSubShell(*pPageBreakShell); + bCellBrush = true; + } + break; + case OST_Editing: + { + AddSubShell(*pCellShell); + if(bPgBrk) AddSubShell(*pPageBreakShell); + + if(pEditShell) + { + AddSubShell(*pEditShell); + } + } + break; + case OST_DrawText: + { + if ( !pDrawTextShell ) + { + pDocSh->MakeDrawLayer(); + pDrawTextShell.reset( new ScDrawTextObjectBar(GetViewData()) ); + } + AddSubShell(*pDrawTextShell); + } + break; + case OST_Drawing: + { + if (svx::checkForSelectedCustomShapes( + GetScDrawView(), true /* bOnlyExtruded */ )) { + if (pExtrusionBarShell == nullptr) + pExtrusionBarShell.reset( new svx::ExtrusionBar(this) ); + AddSubShell( *pExtrusionBarShell ); + } + + if (svx::checkForSelectedFontWork( + GetScDrawView() )) { + if (pFontworkBarShell == nullptr) + pFontworkBarShell.reset( new svx::FontworkBar(this) ); + AddSubShell( *pFontworkBarShell ); + } + + if ( !pDrawShell ) + { + pDocSh->MakeDrawLayer(); + pDrawShell.reset(new ScDrawShell(GetViewData())); + pDrawShell->SetRepeatTarget( &aTarget ); + } + AddSubShell(*pDrawShell); + bDrawBrush = true; + } + break; + + case OST_DrawForm: + { + if ( !pDrawFormShell ) + { + pDocSh->MakeDrawLayer(); + pDrawFormShell.reset( new ScDrawFormShell(GetViewData()) ); + pDrawFormShell->SetRepeatTarget( &aTarget ); + } + AddSubShell(*pDrawFormShell); + bDrawBrush = true; + } + break; + + case OST_Chart: + { + if ( !pChartShell ) + { + pDocSh->MakeDrawLayer(); + pChartShell.reset( new ScChartShell(GetViewData()) ); + pChartShell->SetRepeatTarget( &aTarget ); + } + AddSubShell(*pChartShell); + bDrawBrush = true; + } + break; + + case OST_OleObject: + { + if ( !pOleObjectShell ) + { + pDocSh->MakeDrawLayer(); + pOleObjectShell.reset( new ScOleObjectShell(GetViewData()) ); + pOleObjectShell->SetRepeatTarget( &aTarget ); + } + AddSubShell(*pOleObjectShell); + bDrawBrush = true; + } + break; + + case OST_Graphic: + { + if ( !pGraphicShell) + { + pDocSh->MakeDrawLayer(); + pGraphicShell.reset( new ScGraphicShell(GetViewData()) ); + pGraphicShell->SetRepeatTarget( &aTarget ); + } + AddSubShell(*pGraphicShell); + bDrawBrush = true; + } + break; + + case OST_Media: + { + if ( !pMediaShell) + { + pDocSh->MakeDrawLayer(); + pMediaShell.reset( new ScMediaShell(GetViewData()) ); + pMediaShell->SetRepeatTarget( &aTarget ); + } + AddSubShell(*pMediaShell); + } + break; + + case OST_Pivot: + { + AddSubShell(*pCellShell); + if(bPgBrk) AddSubShell(*pPageBreakShell); + + if ( !pPivotShell ) + { + pPivotShell.reset( new ScPivotShell( this ) ); + pPivotShell->SetRepeatTarget( &aTarget ); + } + AddSubShell(*pPivotShell); + bCellBrush = true; + } + break; + case OST_Auditing: + { + AddSubShell(*pCellShell); + if(bPgBrk) AddSubShell(*pPageBreakShell); + + if ( !pAuditingShell ) + { + pDocSh->MakeDrawLayer(); // the waiting time rather now as on the click + + pAuditingShell.reset( new ScAuditingShell(GetViewData()) ); + pAuditingShell->SetRepeatTarget( &aTarget ); + } + AddSubShell(*pAuditingShell); + bCellBrush = true; + } + break; + case OST_Sparkline: + { + AddSubShell(*pCellShell); + if(bPgBrk) AddSubShell(*pPageBreakShell); + + if (!m_pSparklineShell) + { + m_pSparklineShell.reset(new sc::SparklineShell(this)); + m_pSparklineShell->SetRepeatTarget(&aTarget); + } + AddSubShell(*m_pSparklineShell); + bCellBrush = true; + } + break; + default: + OSL_FAIL("wrong shell requested"); + break; + } + + if (pFormShell && bFormShellAtTop) + AddSubShell(*pFormShell); // add on top of own subshells + + eCurOST=eOST; + + // abort "format paint brush" when switching to an incompatible shell + if ( ( GetBrushDocument() && !bCellBrush ) || ( GetDrawBrushSet() && !bDrawBrush ) ) + ResetBrushDocument(); +} + +void ScTabViewShell::SetFormShellAtTop( bool bSet ) +{ + if ( pFormShell && !bSet ) + pFormShell->ForgetActiveControl(); // let the FormShell know it no longer has the focus + + if ( bFormShellAtTop != bSet ) + { + bFormShellAtTop = bSet; + SetCurSubShell( GetCurObjectSelectionType(), true ); + } +} + +IMPL_LINK_NOARG(ScTabViewShell, FormControlActivated, LinkParamNone*, void) +{ + // a form control got the focus, so the form shell has to be on top + SetFormShellAtTop( true ); +} + +// GetMySubShell / SetMySubShell: simulate old behavior, +// so that there is only one SubShell (only within the 5 own SubShells) + +SfxShell* ScTabViewShell::GetMySubShell() const +{ + // GetSubShell() was const before, and GetSubShell(sal_uInt16) should also be const... + + sal_uInt16 nPos = 0; + SfxShell* pSub = const_cast<ScTabViewShell*>(this)->GetSubShell(nPos); + while (pSub) + { + if (pSub == pDrawShell.get() || pSub == pDrawTextShell.get() || pSub == pEditShell.get() || + pSub == pPivotShell.get() || pSub == pAuditingShell.get() || pSub == pDrawFormShell.get() || + pSub == pCellShell.get() || pSub == pOleObjectShell.get() || pSub == pChartShell.get() || + pSub == pGraphicShell.get() || pSub == pMediaShell.get() || pSub == pPageBreakShell.get() || + pSub == m_pSparklineShell.get()) + { + return pSub; // found + } + + pSub = const_cast<ScTabViewShell*>(this)->GetSubShell(++nPos); + } + return nullptr; // none from mine present +} + +bool ScTabViewShell::IsDrawTextShell() const +{ + return ( pDrawTextShell && ( GetMySubShell() == pDrawTextShell.get() ) ); +} + +bool ScTabViewShell::IsAuditShell() const +{ + return ( pAuditingShell && ( GetMySubShell() == pAuditingShell.get() ) ); +} + +void ScTabViewShell::SetDrawTextUndo( SfxUndoManager* pNewUndoMgr ) +{ + // Default: undo manager for DocShell + if (!pNewUndoMgr) + pNewUndoMgr = GetViewData().GetDocShell()->GetUndoManager(); + + if (pDrawTextShell) + { + pDrawTextShell->SetUndoManager(pNewUndoMgr); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + if ( pNewUndoMgr == pDocSh->GetUndoManager() && + !pDocSh->GetDocument().IsUndoEnabled() ) + { + pNewUndoMgr->SetMaxUndoActionCount( 0 ); + } + } + else + { + OSL_FAIL("SetDrawTextUndo without DrawTextShell"); + } +} + +ScTabViewShell* ScTabViewShell::GetActiveViewShell() +{ + return dynamic_cast< ScTabViewShell *>( Current() ); +} + +SfxPrinter* ScTabViewShell::GetPrinter( bool bCreate ) +{ + // printer is always present (is created for the FontList already on start-up) + return GetViewData().GetDocShell()->GetPrinter(bCreate); +} + +sal_uInt16 ScTabViewShell::SetPrinter( SfxPrinter *pNewPrinter, SfxPrinterChangeFlags nDiffFlags ) +{ + return GetViewData().GetDocShell()->SetPrinter( pNewPrinter, nDiffFlags ); +} + +bool ScTabViewShell::HasPrintOptionsPage() const +{ + return true; +} + +std::unique_ptr<SfxTabPage> ScTabViewShell::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 ScTabViewShell::StopEditShell() +{ + if ( pEditShell != nullptr && !bDontSwitch ) + SetEditShell(nullptr, false ); +} + +// close handler to ensure function of dialog: + +IMPL_LINK_NOARG(ScTabViewShell, SimpleRefClose, const OUString*, void) +{ + SfxInPlaceClient* pClient = GetIPClient(); + if ( pClient && pClient->IsObjectInPlaceActive() ) + { + // If range selection was started with an active embedded object, + // switch back to original sheet (while the dialog is still open). + + SetTabNo( GetViewData().GetRefTabNo() ); + } + + ScSimpleRefDlgWrapper::SetAutoReOpen( true ); +} + +// handlers to call UNO listeners: + +static ScTabViewObj* lcl_GetViewObj( const ScTabViewShell& rShell ) +{ + ScTabViewObj* pRet = nullptr; + SfxViewFrame& rViewFrame = rShell.GetViewFrame(); + SfxFrame& rFrame = rViewFrame.GetFrame(); + uno::Reference<frame::XController> xController = rFrame.GetController(); + if (xController.is()) + pRet = dynamic_cast<ScTabViewObj*>( xController.get() ); + return pRet; +} + +IMPL_LINK( ScTabViewShell, SimpleRefDone, const OUString&, aResult, void ) +{ + ScTabViewObj* pImpObj = lcl_GetViewObj( *this ); + if ( pImpObj ) + pImpObj->RangeSelDone( aResult ); +} + +IMPL_LINK( ScTabViewShell, SimpleRefAborted, const OUString&, rResult, void ) +{ + ScTabViewObj* pImpObj = lcl_GetViewObj( *this ); + if ( pImpObj ) + pImpObj->RangeSelAborted( rResult ); +} + +IMPL_LINK( ScTabViewShell, SimpleRefChange, const OUString&, rResult, void ) +{ + ScTabViewObj* pImpObj = lcl_GetViewObj( *this ); + if ( pImpObj ) + pImpObj->RangeSelChanged( rResult ); +} + +void ScTabViewShell::StartSimpleRefDialog( + const OUString& rTitle, const OUString& rInitVal, + bool bCloseOnButtonUp, bool bSingleCell, bool bMultiSelection ) +{ + SfxViewFrame& rViewFrm = GetViewFrame(); + + if ( GetActiveViewShell() != this ) + { + // #i18833# / #i34499# The API method can be called for a view that's not active. + // Then the view has to be activated first, the same way as in Execute for SID_CURRENTDOC. + // Can't use GrabFocus here, because it needs to take effect immediately. + + rViewFrm.GetFrame().Appear(); + } + + sal_uInt16 nId = ScSimpleRefDlgWrapper::GetChildWindowId(); + + SC_MOD()->SetRefDialog( nId, true, &rViewFrm ); + + ScSimpleRefDlgWrapper* pWnd = static_cast<ScSimpleRefDlgWrapper*>(rViewFrm.GetChildWindow( nId )); + if (!pWnd) + return; + + pWnd->SetCloseHdl( LINK( this, ScTabViewShell, SimpleRefClose ) ); + pWnd->SetUnoLinks( LINK( this, ScTabViewShell, SimpleRefDone ), + LINK( this, ScTabViewShell, SimpleRefAborted ), + LINK( this, ScTabViewShell, SimpleRefChange ) ); + pWnd->SetRefString( rInitVal ); + pWnd->SetFlags( bCloseOnButtonUp, bSingleCell, bMultiSelection ); + ScSimpleRefDlgWrapper::SetAutoReOpen( false ); + if (auto xWin = pWnd->GetController()) + xWin->set_title(rTitle); + pWnd->StartRefInput(); +} + +void ScTabViewShell::StopSimpleRefDialog() +{ + SfxViewFrame& rViewFrm = GetViewFrame(); + sal_uInt16 nId = ScSimpleRefDlgWrapper::GetChildWindowId(); + + ScSimpleRefDlgWrapper* pWnd = static_cast<ScSimpleRefDlgWrapper*>(rViewFrm.GetChildWindow( nId )); + if (pWnd) + { + if (auto pWin = pWnd->GetController()) + pWin->response(RET_CLOSE); + } +} + +bool ScTabViewShell::TabKeyInput(const KeyEvent& rKEvt) +{ + ScModule* pScMod = SC_MOD(); + + SfxViewFrame& rThisFrame = GetViewFrame(); + if ( rThisFrame.GetChildWindow( SID_OPENDLG_FUNCTION ) ) + return false; + + vcl::KeyCode aCode = rKEvt.GetKeyCode(); + bool bShift = aCode.IsShift(); + bool bControl = aCode.IsMod1(); + bool bAlt = aCode.IsMod2(); + sal_uInt16 nCode = aCode.GetCode(); + bool bUsed = false; + bool bInPlace = pScMod->IsEditMode(); // Editengine gets all + bool bAnyEdit = pScMod->IsInputMode(); // only characters & backspace + bool bDraw = IsDrawTextEdit(); + + HideNoteMarker(); // note marker + + // don't do extra HideCursor/ShowCursor calls if EnterHandler will switch to a different sheet + bool bOnRefSheet = ( GetViewData().GetRefTabNo() == GetViewData().GetTabNo() ); + bool bHideCursor = ( ( nCode == KEY_RETURN && bInPlace ) || nCode == KEY_TAB ) && bOnRefSheet; + + if (bHideCursor) + HideAllCursors(); + + ScDocument& rDoc = GetViewData().GetDocument(); + rDoc.KeyInput(); // TimerDelays etc. + + if( bInPlace ) + { + bUsed = pScMod->InputKeyEvent( rKEvt ); // input + if( !bUsed ) + bUsed = SfxViewShell::KeyInput( rKEvt ); // accelerators + } + else if( bAnyEdit ) + { + bool bIsType = false; + sal_uInt16 nModi = aCode.GetModifier(); + sal_uInt16 nGroup = aCode.GetGroup(); + + if ( nGroup == KEYGROUP_NUM || nGroup == KEYGROUP_ALPHA || nGroup == 0 ) + if ( !bControl && !bAlt ) + bIsType = true; + + if ( nGroup == KEYGROUP_MISC ) + switch ( nCode ) + { + case KEY_RETURN: + bIsType = bControl && !bAlt; // Control, Shift-Control-Return + if ( !bIsType && nModi == 0 ) + { + // Does the Input Handler also want a simple Return? + + ScInputHandler* pHdl = pScMod->GetInputHdl(this); + bIsType = pHdl && pHdl->TakesReturn(); + } + break; + case KEY_SPACE: + bIsType = !bControl && !bAlt; // without modifier or Shift-Space + break; + case KEY_ESCAPE: + bIsType = (nModi == 0); // only without modifier + break; + default: + bIsType = true; + } + else if (nCode == KEY_RIGHT && !bControl && !bShift && !bAlt) + { + ScInputHandler* pHdl = pScMod->GetInputHdl(this); + bIsType = pHdl && pHdl->HasPartialComplete(); + } + + if( bIsType ) + bUsed = pScMod->InputKeyEvent( rKEvt ); // input + + if( !bUsed ) + bUsed = SfxViewShell::KeyInput( rKEvt ); // accelerators + + if ( !bUsed && !bIsType && nCode != KEY_RETURN ) // input once again afterwards + bUsed = pScMod->InputKeyEvent( rKEvt ); + } + else + { + // special case: copy/cut for multiselect -> error message + // (Slot is disabled, so SfxViewShell::KeyInput would be swallowed without a comment) + KeyFuncType eFunc = aCode.GetFunction(); + if ( eFunc == KeyFuncType::CUT ) + { + ScRange aDummy; + ScMarkType eMarkType = GetViewData().GetSimpleArea( aDummy ); + if (eMarkType != SC_MARK_SIMPLE) + { + ErrorMessage(STR_NOMULTISELECT); + bUsed = true; + } + } + if (!bUsed) + bUsed = SfxViewShell::KeyInput( rKEvt ); // accelerators + + // during inplace editing, some slots are handled by the + // container app and are executed during Window::KeyInput. + // -> don't pass keys to input handler that would be used there + // but should call slots instead. + bool bParent = ( GetViewFrame().GetFrame().IsInPlace() && eFunc != KeyFuncType::DONTKNOW ); + + if( !bUsed && !bDraw && nCode != KEY_RETURN && !bParent ) + bUsed = pScMod->InputKeyEvent( rKEvt, true ); // input + } + + if (!bInPlace && !bUsed && !bDraw) + { + switch (nCode) + { + case KEY_RETURN: + { + bool bNormal = !bControl && !bAlt; + if ( !bAnyEdit && bNormal ) + { + // Depending on options, Enter switches to edit mode. + const ScInputOptions& rOpt = pScMod->GetInputOptions(); + if ( rOpt.GetEnterEdit() ) + { + pScMod->SetInputMode( SC_INPUT_TABLE ); + bUsed = true; + } + } + + bool bEditReturn = bControl && !bShift; // pass on to edit engine + if ( !bUsed && !bEditReturn ) + { + if ( bOnRefSheet ) + HideAllCursors(); + + ScEnterMode nMode = ScEnterMode::NORMAL; + if ( bShift && bControl ) + nMode = ScEnterMode::MATRIX; + else if ( bAlt ) + nMode = ScEnterMode::BLOCK; + pScMod->InputEnterHandler(nMode); + + if (nMode == ScEnterMode::NORMAL) + { + if( bShift ) + GetViewData().GetDispatcher().Execute( SID_CURSORENTERUP, + SfxCallMode::SLOT | SfxCallMode::RECORD ); + else + GetViewData().GetDispatcher().Execute( SID_CURSORENTERDOWN, + SfxCallMode::SLOT | SfxCallMode::RECORD ); + } + else + UpdateInputHandler(true); + + if ( bOnRefSheet ) + ShowAllCursors(); + + // here no UpdateInputHandler, since during reference input on another + // document this ViewShell is not the one that is used for input. + + bUsed = true; + } + } + break; + } + } + + // hard-code Alt-Cursor key, since Alt is not configurable + + if ( !bUsed && bAlt && !bControl ) + { + sal_uInt16 nSlotId = 0; + switch (nCode) + { + case KEY_UP: + ModifyCellSize( DIR_TOP, bShift ); + bUsed = true; + break; + case KEY_DOWN: + ModifyCellSize( DIR_BOTTOM, bShift ); + bUsed = true; + break; + case KEY_LEFT: + ModifyCellSize( DIR_LEFT, bShift ); + bUsed = true; + break; + case KEY_RIGHT: + ModifyCellSize( DIR_RIGHT, bShift ); + bUsed = true; + break; + case KEY_PAGEUP: + nSlotId = bShift ? SID_CURSORPAGELEFT_SEL : SID_CURSORPAGELEFT_; + break; + case KEY_PAGEDOWN: + nSlotId = bShift ? SID_CURSORPAGERIGHT_SEL : SID_CURSORPAGERIGHT_; + break; + case KEY_EQUAL: + { + // #tdf39302: Use "Alt + =" for autosum + if ( !bAnyEdit ) // Ignore shortcut if currently editing a cell + { + ScInputHandler* pHdl = pScMod->GetInputHdl(this); + if ( pHdl ) + { + ScInputWindow* pWin = pHdl->GetInputWindow(); + if ( pWin ) + { + bool bRangeFinder = false; + bool bSubTotal = false; + pWin->AutoSum( bRangeFinder, bSubTotal, ocSum ); + } + } + + bUsed = true; + break; + } + } + } + if ( nSlotId ) + { + GetViewData().GetDispatcher().Execute( nSlotId, SfxCallMode::SLOT | SfxCallMode::RECORD ); + bUsed = true; + } + } + + // use Ctrl+Alt+Shift+arrow keys to move the cursor in cells + // while keeping the last selection + if ( !bUsed && bAlt && bControl && bShift) + { + sal_uInt16 nSlotId = 0; + switch (nCode) + { + case KEY_UP: + nSlotId = SID_CURSORUP; + break; + case KEY_DOWN: + nSlotId = SID_CURSORDOWN; + break; + case KEY_LEFT: + nSlotId = SID_CURSORLEFT; + break; + case KEY_RIGHT: + nSlotId = SID_CURSORRIGHT; + break; + case KEY_PAGEUP: + nSlotId = SID_CURSORPAGEUP; + break; + case KEY_PAGEDOWN: + nSlotId = SID_CURSORPAGEDOWN; + break; + case KEY_HOME: + nSlotId = SID_CURSORHOME; + break; + case KEY_END: + nSlotId = SID_CURSOREND; + break; + default: + nSlotId = 0; + break; + } + if ( nSlotId ) + { + sal_uInt16 nMode = GetLockedModifiers(); + LockModifiers(KEY_MOD1); + GetViewData().GetDispatcher().Execute( nSlotId, SfxCallMode::SLOT | SfxCallMode::RECORD ); + LockModifiers(nMode); + bUsed = true; + } + } + if (bHideCursor) + ShowAllCursors(); + + return bUsed; +} + +bool ScTabViewShell::SfxKeyInput(const KeyEvent& rKeyEvent) +{ + return SfxViewShell::KeyInput( rKeyEvent ); +} + +bool ScTabViewShell::KeyInput( const KeyEvent &rKeyEvent ) +{ + return TabKeyInput( rKeyEvent ); +} + +void ScTabViewShell::Construct( TriState nForceDesignMode ) +{ + SfxApplication* pSfxApp = SfxGetpApp(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + bReadOnly = pDocSh->IsReadOnly(); + bIsActive = false; + + EnableAutoSpell(rDoc.GetDocOptions().IsAutoSpell()); + + SetName("View"); // for SBX + Color aColBlack( COL_BLACK ); + SetPool( &SC_MOD()->GetPool() ); + SetWindow( GetActiveWin() ); + + pCurFrameLine.reset( new ::editeng::SvxBorderLine(&aColBlack, 20, SvxBorderLineStyle::SOLID) ); + StartListening(*GetViewData().GetDocShell(), DuplicateHandling::Prevent); + StartListening(GetViewFrame(), DuplicateHandling::Prevent); + StartListening(*pSfxApp, DuplicateHandling::Prevent); // #i62045# #i62046# application is needed for Calc's own hints + + SfxViewFrame* pFirst = SfxViewFrame::GetFirst(pDocSh); + bool bFirstView = !pFirst + || (pFirst == &GetViewFrame() && !SfxViewFrame::GetNext(*pFirst,pDocSh)); + + if ( pDocSh->GetCreateMode() == SfxObjectCreateMode::EMBEDDED ) + { + //TODO/LATER: is there a difference between the two GetVisArea methods? + tools::Rectangle aVisArea = static_cast<const SfxObjectShell*>(pDocSh)->GetVisArea(); + + SCTAB nVisTab = rDoc.GetVisibleTab(); + if (!rDoc.HasTable(nVisTab)) + { + nVisTab = 0; + rDoc.SetVisibleTab(nVisTab); + } + SetTabNo( nVisTab ); + bool bNegativePage = rDoc.IsNegativePage( nVisTab ); + // show the right cells + GetViewData().SetScreenPos( bNegativePage ? aVisArea.TopRight() : aVisArea.TopLeft() ); + + if ( GetViewFrame().GetFrame().IsInPlace() ) // inplace + { + pDocSh->SetInplace( true ); // already initiated like this + if (rDoc.IsEmbedded()) + rDoc.ResetEmbedded(); // no blue mark + } + else if ( bFirstView ) + { + pDocSh->SetInplace( false ); + GetViewData().RefreshZoom(); // recalculate PPT + if (!rDoc.IsEmbedded()) + rDoc.SetEmbedded( rDoc.GetVisibleTab(), aVisArea ); // mark VisArea + } + } + + // ViewInputHandler + // Each task now has its own InputWindow, + // therefore either should each task get its own InputHandler, + // or the InputWindow should create its own InputHandler + // (then always search via InputWindow and only if not found + // use the InputHandler of the App). + // As an intermediate solution each View gets its own InputHandler, + // which only yields problems if two Views are in one task window. + mpInputHandler.reset(new ScInputHandler); + + // old version: + // if ( !GetViewFrame().ISA(SfxTopViewFrame) ) // OLE or Plug-In + // pInputHandler = new ScInputHandler; + + // create FormShell before MakeDrawView, so that DrawView can be registered at the + // FormShell in every case + // the FormShell is pushed in the first activate + pFormShell.reset( new FmFormShell(this) ); + pFormShell->SetControlActivationHandler( LINK( this, ScTabViewShell, FormControlActivated ) ); + + // DrawView must not be created in TabView - ctor, + // if the ViewShell is not yet constructed... + if (rDoc.GetDrawLayer()) + MakeDrawView( nForceDesignMode ); + ViewOptionsHasChanged(false, false); // possibly also creates DrawView + + SfxUndoManager* pMgr = pDocSh->GetUndoManager(); + SetUndoManager( pMgr ); + pFormShell->SetUndoManager( pMgr ); + if ( !rDoc.IsUndoEnabled() ) + { + pMgr->SetMaxUndoActionCount( 0 ); + } + SetRepeatTarget( &aTarget ); + pFormShell->SetRepeatTarget( &aTarget ); + + if ( bFirstView ) // first view? + { + rDoc.SetDocVisible( true ); // used when creating new sheets + if ( pDocSh->IsEmpty() ) + { + // set first sheet's RTL flag (following will already be initialized because of SetDocVisible) + rDoc.SetLayoutRTL( 0, ScGlobal::IsSystemRTL() ); + + // append additional sheets (not for OLE object) + if ( pDocSh->GetCreateMode() != SfxObjectCreateMode::EMBEDDED ) + { + // Get the customized initial tab count + const ScDefaultsOptions& rOpt = SC_MOD()->GetDefaultsOptions(); + SCTAB nInitTabCount = rOpt.GetInitTabCount(); + + for (SCTAB i=1; i<nInitTabCount; i++) + rDoc.MakeTable(i,false); + } + + pDocSh->SetEmpty( false ); // #i6232# make sure this is done only once + } + + // ReadExtOptions is now in Activate + + // link update no nesting + if ( pDocSh->GetCreateMode() != SfxObjectCreateMode::INTERNAL && + pDocSh->IsUpdateEnabled() ) // #105575#; update only in the first creation of the ViewShell + { + // Check if there are any external data. + bool bLink = rDoc.GetExternalRefManager()->hasExternalData(); + if (!bLink) + { + // #i100042# sheet links can still exist independently from external formula references + SCTAB nTabCount = rDoc.GetTableCount(); + for (SCTAB i=0; i<nTabCount && !bLink; i++) + if (rDoc.IsLinked(i)) + bLink = true; + } + if (!bLink) + { + const sc::DocumentLinkManager& rMgr = rDoc.GetDocLinkManager(); + if (rDoc.HasLinkFormulaNeedingCheck() || rDoc.HasAreaLinks() || rMgr.hasDdeOrOleOrWebServiceLinks()) + bLink = true; + } + if (bLink) + { + if ( !pFirst ) + pFirst = &GetViewFrame(); + + if(SC_MOD()->GetCurRefDlgId()==0) + { + pFirst->GetDispatcher()->Execute( SID_UPDATETABLINKS, + SfxCallMode::ASYNCHRON | SfxCallMode::RECORD ); + } + } + else + { + // No links yet, but loading an existing document may have + // disabled link update but there's no "Allow updating" infobar + // that could enable it again. So in order to enable the user + // to add formulas with external references allow link updates + // again. + pDocSh->AllowLinkUpdate(); + } + + bool bReImport = false; // update imported data + ScDBCollection* pDBColl = rDoc.GetDBCollection(); + if ( pDBColl ) + { + const ScDBCollection::NamedDBs& rDBs = pDBColl->getNamedDBs(); + bReImport = std::any_of(rDBs.begin(), rDBs.end(), + [](const std::unique_ptr<ScDBData>& rxDB) { return rxDB->IsStripData() && rxDB->HasImportParam() && !rxDB->HasImportSelection(); }); + } + if (bReImport) + { + if ( !pFirst ) + pFirst = &GetViewFrame(); + if(SC_MOD()->GetCurRefDlgId()==0) + { + pFirst->GetDispatcher()->Execute( SID_REIMPORT_AFTER_LOAD, + SfxCallMode::ASYNCHRON | SfxCallMode::RECORD ); + } + } + } + } + + UpdateAutoFillMark(); + + // ScDispatchProviderInterceptor registers itself in ctor + xDisProvInterceptor = new ScDispatchProviderInterceptor( this ); + + bFirstActivate = true; // delay NavigatorUpdate until Activate() + + // #105575#; update only in the first creation of the ViewShell + pDocSh->SetUpdateEnabled(false); + + if ( GetViewFrame().GetFrame().IsInPlace() ) + UpdateHeaderWidth(); // The inplace activation requires headers to be calculated + + SvBorder aBorder; + GetBorderSize( aBorder, Size() ); + SetBorderPixel( aBorder ); +} + +ScTabViewShell::ScTabViewShell( SfxViewFrame& rViewFrame, + SfxViewShell* pOldSh ) : + SfxViewShell(rViewFrame, SfxViewShellFlags::HAS_PRINTOPTIONS), + ScDBFunc( &rViewFrame.GetWindow(), static_cast<ScDocShell&>(*rViewFrame.GetObjectShell()), this ), + eCurOST(OST_NONE), + nDrawSfxId(0), + aTarget(this), + bActiveDrawSh(false), + bActiveDrawTextSh(false), + bActiveDrawFormSh(false), + bActiveOleObjectSh(false), + bActiveChartSh(false), + bActiveGraphicSh(false), + bActiveMediaSh(false), + bFormShellAtTop(false), + bDontSwitch(false), + bInFormatDialog(false), + bReadOnly(false), + bForceFocusOnCurCell(false), + bInPrepareClose(false), + bInDispose(false), + nCurRefDlgId(0), + mbInSwitch(false), + m_pDragData(new ScDragData) +{ + const ScAppOptions& rAppOpt = SC_MOD()->GetAppOptions(); + + // if switching back from print preview, + // restore the view settings that were active when creating the preview + // ReadUserData must not happen from ctor, because the view's edit window + // has to be shown by the sfx. ReadUserData is deferred until the first Activate call. + // old DesignMode state from form layer must be restored, too + + TriState nForceDesignMode = TRISTATE_INDET; + if ( auto pPreviewShell = dynamic_cast<ScPreviewShell*>( pOldSh) ) + { + nForceDesignMode = pPreviewShell->GetSourceDesignMode(); + ScPreview* p = pPreviewShell->GetPreview(); + if (p) + GetViewData().GetMarkData().SetSelectedTabs(p->GetSelectedTabs()); + } + + Construct( nForceDesignMode ); + + // make Controller known to SFX + new ScTabViewObj( this ); + + // Resolves: tdf#53899 if there is no controller, register the above + // ScTabViewObj as the current controller for the duration of the first + // round of calculations triggered here by SetZoom. That way any StarBasic + // macros triggered while the document is loading have a CurrentController + // available to them. + bool bInstalledScTabViewObjAsTempController = false; + uno::Reference<frame::XController> xCurrentController(GetViewData().GetDocShell()->GetModel()->getCurrentController()); + if (!xCurrentController) + { + //GetController here returns the ScTabViewObj above + GetViewData().GetDocShell()->GetModel()->setCurrentController(GetController()); + bInstalledScTabViewObjAsTempController = true; + } + xCurrentController.clear(); + + if ( GetViewData().GetDocShell()->IsPreview() ) + { + // preview for template dialog: always show whole page + SetZoomType( SvxZoomType::WHOLEPAGE, true ); // zoom value is recalculated at next Resize + } + else + { + Fraction aFract( rAppOpt.GetZoom(), 100 ); + SetZoom( aFract, aFract, true ); + SetZoomType( rAppOpt.GetZoomType(), true ); + } + + SetCurSubShell(OST_Cell); + SvBorder aBorder; + GetBorderSize( aBorder, Size() ); + SetBorderPixel( aBorder ); + + MakeDrawLayer(); + + //put things back as we found them + if (bInstalledScTabViewObjAsTempController) + GetViewData().GetDocShell()->GetModel()->setCurrentController(nullptr); + + // formula mode in online is not usable in collaborative mode, + // this is a workaround for disabling formula mode in online + // when there is more than a single view + if (!comphelper::LibreOfficeKit::isActive()) + return; + + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + // have we already one view ? + if (!pViewShell) + return; + + // this view is not yet visible at this stage, so we look for not visible views, too, for this same document + SfxViewShell* pViewShell2 = pViewShell; + do + { + pViewShell2 = SfxViewShell::GetNext(*pViewShell2, /*only visible shells*/ false); + } while (pViewShell2 && pViewShell2->GetDocId() != pViewShell->GetDocId()); + // if the second view is not this one, it means that there is + // already more than one active view and so the formula mode + // has already been disabled + if (pViewShell2 && pViewShell2 == this) + { + ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(pViewShell); + assert(pTabViewShell); + ScInputHandler* pInputHdl = pTabViewShell->GetInputHandler(); + if (pInputHdl && pInputHdl->IsFormulaMode()) + { + pInputHdl->SetMode(SC_INPUT_NONE); + } + } + + if (comphelper::LibreOfficeKit::isActive()) + { + ScModelObj* pModel = comphelper::getFromUnoTunnel<ScModelObj>(GetCurrentDocument()); + SfxLokHelper::notifyViewRenderState(this, pModel); + } +} + +ScTabViewShell::~ScTabViewShell() +{ + bInDispose = true; + + // Notify other LOK views that we are going away. + SfxLokHelper::notifyOtherViews(this, LOK_CALLBACK_VIEW_CURSOR_VISIBLE, "visible", "false"_ostr); + SfxLokHelper::notifyOtherViews(this, LOK_CALLBACK_TEXT_VIEW_SELECTION, "selection", ""_ostr); + SfxLokHelper::notifyOtherViews(this, LOK_CALLBACK_GRAPHIC_VIEW_SELECTION, "selection", "EMPTY"_ostr); + SfxLokHelper::notifyOtherViews(this, LOK_CALLBACK_CELL_VIEW_CURSOR, "rectangle", "EMPTY"_ostr); + + // all to NULL, in case the TabView-dtor tries to access them + //! (should not really! ??!?!) + if (mpInputHandler) + { + mpInputHandler->SetDocumentDisposing(true); + } + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + EndListening(*pDocSh); + EndListening(GetViewFrame()); + EndListening(*SfxGetpApp()); // #i62045# #i62046# needed now - SfxViewShell no longer does it + + SC_MOD()->ViewShellGone(this); + + RemoveSubShell(); // all + SetWindow(nullptr); + + // need kill editview or we will touch the editengine after it has been freed by the ScInputHandler + KillEditView(true); + + pFontworkBarShell.reset(); + pExtrusionBarShell.reset(); + pCellShell.reset(); + pPageBreakShell.reset(); + pDrawShell.reset(); + pDrawFormShell.reset(); + pOleObjectShell.reset(); + pChartShell.reset(); + pGraphicShell.reset(); + pMediaShell.reset(); + pDrawTextShell.reset(); + pEditShell.reset(); + pPivotShell.reset(); + m_pSparklineShell.reset(); + pAuditingShell.reset(); + pCurFrameLine.reset(); + mpFormEditData.reset(); + mpInputHandler.reset(); + pDialogDPObject.reset(); + pNavSettings.reset(); + + pFormShell.reset(); + pAccessibilityBroadcaster.reset(); +} + +void ScTabViewShell::SetDialogDPObject( std::unique_ptr<ScDPObject> pObj ) +{ + pDialogDPObject = std::move(pObj); +} + +void ScTabViewShell::FillFieldData( ScHeaderFieldData& rData ) +{ + ScDocShell* pDocShell = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocShell->GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + 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 = 1; + rData.nTotalPages = 99; + + // eNumType is known by the dialog +} + +ScNavigatorSettings* ScTabViewShell::GetNavigatorSettings() +{ + if( !pNavSettings ) + pNavSettings.reset(new ScNavigatorSettings); + return pNavSettings.get(); +} + +tools::Rectangle ScTabViewShell::getLOKVisibleArea() const +{ + return GetViewData().getLOKVisibleArea(); +} + +void ScTabViewShell::SetDragObject(ScTransferObj* pCellObj, ScDrawTransferObj* pDrawObj) +{ + ResetDragObject(); + m_pDragData->pCellTransfer = pCellObj; + m_pDragData->pDrawTransfer = pDrawObj; +} + +void ScTabViewShell::ResetDragObject() +{ + m_pDragData->pCellTransfer = nullptr; + m_pDragData->pDrawTransfer = nullptr; + m_pDragData->pJumpLocalDoc = nullptr; + m_pDragData->aLinkDoc.clear(); + m_pDragData->aLinkTable.clear(); + m_pDragData->aLinkArea.clear(); + m_pDragData->aJumpTarget.clear(); + m_pDragData->aJumpText.clear(); +} + +void ScTabViewShell::SetDragLink(const OUString& rDoc, const OUString& rTab, const OUString& rArea) +{ + ResetDragObject(); + m_pDragData->aLinkDoc = rDoc; + m_pDragData->aLinkTable = rTab; + m_pDragData->aLinkArea = rArea; +} + +void ScTabViewShell::SetDragJump(ScDocument* pLocalDoc, const OUString& rTarget, const OUString& rText) +{ + ResetDragObject(); + m_pDragData->pJumpLocalDoc = pLocalDoc; + m_pDragData->aJumpTarget = rTarget; + m_pDragData->aJumpText = rText; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabvwsh5.cxx b/sc/source/ui/view/tabvwsh5.cxx new file mode 100644 index 0000000000..fab96304f3 --- /dev/null +++ b/sc/source/ui/view/tabvwsh5.cxx @@ -0,0 +1,390 @@ +/* -*- 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 <svl/hint.hxx> +#include <comphelper/lok.hxx> +#include <svx/numfmtsh.hxx> +#include <svx/numinf.hxx> +#include <svx/svxids.hrc> +#include <sfx2/dispatch.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/viewfrm.hxx> +#include <osl/diagnose.h> + +#include <tabvwsh.hxx> +#include <global.hxx> +#include <docsh.hxx> +#include <document.hxx> +#include <formulacell.hxx> +#include <scmod.hxx> +#include <uiitems.hxx> +#include <hints.hxx> +#include <cellvalue.hxx> +#include <svl/numformat.hxx> +#include <svl/sharedstring.hxx> + +void ScTabViewShell::Notify( SfxBroadcaster& rBC, const SfxHint& rHint ) +{ + if (const ScPaintHint* pPaintHint = dynamic_cast<const ScPaintHint*>(&rHint)) // draw new + { + PaintPartFlags nParts = pPaintHint->GetParts(); + SCTAB nTab = GetViewData().GetTabNo(); + if (pPaintHint->GetStartTab() <= nTab && pPaintHint->GetEndTab() >= nTab) + { + if (nParts & PaintPartFlags::Extras) // first if table vanished !!! + if (PaintExtras()) + nParts = PaintPartFlags::All; + + // if the current sheet has pending row height updates (sheet links refreshed), + // execute them before invalidating the window + GetViewData().GetDocShell()->UpdatePendingRowHeights( GetViewData().GetTabNo() ); + + if (nParts & PaintPartFlags::Size) + RepeatResize(); //! InvalidateBorder ??? + if (nParts & PaintPartFlags::Grid) + PaintArea( pPaintHint->GetStartCol(), pPaintHint->GetStartRow(), + pPaintHint->GetEndCol(), pPaintHint->GetEndRow() ); + if (nParts & PaintPartFlags::Marks) + PaintArea( pPaintHint->GetStartCol(), pPaintHint->GetStartRow(), + pPaintHint->GetEndCol(), pPaintHint->GetEndRow(), ScUpdateMode::Marks ); + if (nParts & PaintPartFlags::Left) + PaintLeftArea( pPaintHint->GetStartRow(), pPaintHint->GetEndRow() ); + if (nParts & PaintPartFlags::Top) + PaintTopArea( pPaintHint->GetStartCol(), pPaintHint->GetEndCol() ); + + // #i84689# call UpdateAllOverlays here instead of in ScTabView::PaintArea + if (nParts & ( PaintPartFlags::Left | PaintPartFlags::Top )) // only if widths or heights changed + UpdateAllOverlays(); + + HideNoteMarker(); + } + } + else if (auto pEditViewHint = dynamic_cast<const ScEditViewHint*>(&rHint)) // create Edit-View + { + // ScEditViewHint is only received at active view + + SCTAB nTab = GetViewData().GetTabNo(); + if ( pEditViewHint->GetTab() == nTab ) + { + SCCOL nCol = pEditViewHint->GetCol(); + SCROW nRow = pEditViewHint->GetRow(); + { + HideNoteMarker(); + + MakeEditView( pEditViewHint->GetEngine(), nCol, nRow ); + + StopEditShell(); // shouldn't be set + + ScSplitPos eActive = GetViewData().GetActivePart(); + if ( GetViewData().HasEditView(eActive) ) + { + // MakeEditView will fail, if the cursor is outside the screen. + // Then GetEditView will return a none-active view, therefore + // calling HasEditView. + + EditView* pView = GetViewData().GetEditView(eActive); // isn't zero + + SetEditShell(pView, true); + } + } + } + } + else if (auto pTablesHint = dynamic_cast<const ScTablesHint*>(&rHint)) // table insert / deleted + { + // first fetch current table (can be changed during DeleteTab on ViewData) + SCTAB nActiveTab = GetViewData().GetTabNo(); + + SCTAB nTab1 = pTablesHint->GetTab1(); + SCTAB nTab2 = pTablesHint->GetTab2(); + sal_uInt16 nId = pTablesHint->GetTablesHintId(); + switch (nId) + { + case SC_TAB_INSERTED: + GetViewData().InsertTab( nTab1 ); + break; + case SC_TAB_DELETED: + GetViewData().DeleteTab( nTab1 ); + break; + case SC_TAB_MOVED: + GetViewData().MoveTab( nTab1, nTab2 ); + break; + case SC_TAB_COPIED: + GetViewData().CopyTab( nTab1, nTab2 ); + break; + case SC_TAB_HIDDEN: + break; + case SC_TABS_INSERTED: + GetViewData().InsertTabs( nTab1, nTab2 ); + break; + case SC_TABS_DELETED: + GetViewData().DeleteTabs( nTab1, nTab2 ); + break; + default: + OSL_FAIL("unknown ScTablesHint"); + } + + // No calling of IsActive() here, because the actions can be coming from Basic + // and then also the active view has to be switched. + + SCTAB nNewTab = nActiveTab; + bool bStayOnActiveTab = true; + switch (nId) + { + case SC_TAB_INSERTED: + if ( nTab1 <= nNewTab ) // insert before + ++nNewTab; + break; + case SC_TAB_DELETED: + if ( nTab1 < nNewTab ) // deleted before + --nNewTab; + else if ( nTab1 == nNewTab ) // deleted current + bStayOnActiveTab = false; + break; + case SC_TAB_MOVED: + if ( nNewTab == nTab1 ) // moved table + nNewTab = nTab2; + else if ( nTab1 < nTab2 ) // moved back + { + if ( nNewTab > nTab1 && nNewTab <= nTab2 ) // succeeding area + --nNewTab; + } + else // move in front + { + if ( nNewTab >= nTab2 && nNewTab < nTab1 ) // succeeding area + ++nNewTab; + } + break; + case SC_TAB_COPIED: + if ( nNewTab >= nTab2 ) // insert before + ++nNewTab; + break; + case SC_TAB_HIDDEN: + if ( nTab1 == nNewTab ) // current is hidden + bStayOnActiveTab = false; + break; + case SC_TABS_INSERTED: + if ( nTab1 <= nNewTab ) + nNewTab += nTab2; + break; + case SC_TABS_DELETED: + if ( nTab1 < nNewTab ) + nNewTab -= nTab2; + break; + } + + ScDocument& rDoc = GetViewData().GetDocument(); + if ( nNewTab >= rDoc.GetTableCount() ) + nNewTab = rDoc.GetTableCount() - 1; + + bool bForce = !bStayOnActiveTab; + SetTabNo( nNewTab, bForce, false, bStayOnActiveTab ); + } + else if (const ScIndexHint* pIndexHint = dynamic_cast<const ScIndexHint*>(&rHint)) + { + SfxHintId nId = pIndexHint->GetId(); + sal_uInt16 nIndex = pIndexHint->GetIndex(); + switch (nId) + { + case SfxHintId::ScShowRangeFinder: + PaintRangeFinder( nIndex ); + break; + default: break; + } + } + else // without parameter + { + const SfxHintId nSlot = rHint.GetId(); + switch ( nSlot ) + { + case SfxHintId::ScDataChanged: + UpdateFormulas(); + break; + + case SfxHintId::ScRefModeChanged: + { + bool bRefMode = SC_MOD()->IsFormulaMode(); + if (!bRefMode) + StopRefMode(); + else + GetSelEngine()->Reset(); + } + break; + + case SfxHintId::ScKillEditView: + case SfxHintId::ScKillEditViewNoPaint: + if (!comphelper::LibreOfficeKit::isActive() + || this == SfxViewShell::Current() + || bInPrepareClose + || bInDispose) + { + StopEditShell(); + KillEditView( nSlot == SfxHintId::ScKillEditViewNoPaint ); + } + break; + + case SfxHintId::DocChanged: + { + ScDocument& rDoc = GetViewData().GetDocument(); + if (!rDoc.HasTable( GetViewData().GetTabNo() )) + { + SetTabNo(0); + } + } + break; + + case SfxHintId::ScDrawLayerNew: + MakeDrawView(TRISTATE_INDET); + break; + + case SfxHintId::ScDocSaved: + { + // "Save as" can make a write-protected document writable, + // therefore the Layer-Locks anew (#39884#) + // (Invalidate etc. is happening already from Sfx) + // by SID_EDITDOC no SfxHintId::TitleChanged will occur, that + // is why the own hint from DoSaveCompleted + //! what is with SfxHintId::SAVECOMPLETED ? + + UpdateLayerLocks(); + + // Would be too much to change Design-Mode with every save + // (when saving under the name, it should remain unchanged) + // Therefore only by SfxHintId::ModeChanged (from ViewFrame) + } + break; + + case SfxHintId::ModeChanged: + // Since you can no longer rely on it where this hint was coming + // from, always switch the design mode when the ReadOnly state + // really was changed: + + if ( GetViewData().GetSfxDocShell()->IsReadOnly() != bReadOnly ) + { + bReadOnly = GetViewData().GetSfxDocShell()->IsReadOnly(); + + SfxBoolItem aItem( SID_FM_DESIGN_MODE, !bReadOnly); + GetViewData().GetDispatcher().ExecuteList(SID_FM_DESIGN_MODE, + SfxCallMode::ASYNCHRON, { &aItem }); + + UpdateInputContext(); + } + break; + + case SfxHintId::ScShowRangeFinder: + PaintRangeFinder(-1); + break; + + case SfxHintId::ScForceSetTab: + SetTabNo( GetViewData().GetTabNo(), true ); + break; + + case SfxHintId::LanguageChanged: + { + GetViewFrame().GetBindings().Invalidate(SID_LANGUAGE_STATUS); + if ( ScGridWindow* pWin = GetViewData().GetActiveWin() ) + pWin->ResetAutoSpell(); + } + break; + + default: + break; + } + } + + SfxViewShell::Notify( rBC, rHint ); +} + +std::unique_ptr<SvxNumberInfoItem> ScTabViewShell::MakeNumberInfoItem( ScDocument& rDoc, const ScViewData& rViewData ) +{ + + // construct NumberInfo item + + SvxNumberValueType eValType = SvxNumberValueType::Undefined; + double nCellValue = 0; + OUString aCellString; + + ScRefCellValue aCell(rDoc, rViewData.GetCurPos()); + + switch (aCell.getType()) + { + case CELLTYPE_VALUE: + { + nCellValue = aCell.getDouble(); + eValType = SvxNumberValueType::Number; + } + break; + + case CELLTYPE_STRING: + { + aCellString = aCell.getSharedString()->getString(); + eValType = SvxNumberValueType::String; + } + break; + + case CELLTYPE_FORMULA: + { + if (aCell.getFormula()->IsValue()) + { + nCellValue = aCell.getFormula()->GetValue(); + eValType = SvxNumberValueType::Number; + } + else + { + nCellValue = 0; + eValType = SvxNumberValueType::Undefined; + } + } + break; + + default: + nCellValue = 0; + eValType = SvxNumberValueType::Undefined; + } + + switch ( eValType ) + { + case SvxNumberValueType::String: + return std::make_unique<SvxNumberInfoItem>( + rDoc.GetFormatTable(), + aCellString, + SID_ATTR_NUMBERFORMAT_INFO ); + + case SvxNumberValueType::Number: + return std::make_unique<SvxNumberInfoItem>( + rDoc.GetFormatTable(), + nCellValue, + SID_ATTR_NUMBERFORMAT_INFO ); + + case SvxNumberValueType::Undefined: + default: + ; + } + + return std::make_unique<SvxNumberInfoItem>( + rDoc.GetFormatTable(), SID_ATTR_NUMBERFORMAT_INFO); +} + +void ScTabViewShell::UpdateNumberFormatter( + const SvxNumberInfoItem& rInfoItem ) +{ + for ( sal_uInt32 key : rInfoItem.GetDelFormats() ) + rInfoItem.GetNumberFormatter()->DeleteEntry( key ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabvwsh8.cxx b/sc/source/ui/view/tabvwsh8.cxx new file mode 100644 index 0000000000..9a743c295b --- /dev/null +++ b/sc/source/ui/view/tabvwsh8.cxx @@ -0,0 +1,76 @@ +/* -*- 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 <editeng/borderline.hxx> + +#include <tabvwsh.hxx> +#include <document.hxx> + +void ScTabViewShell::SetDefaultFrameLine( const ::editeng::SvxBorderLine* pLine ) +{ + if ( pLine ) + { + pCurFrameLine.reset( new ::editeng::SvxBorderLine( &pLine->GetColor(), + pLine->GetWidth(), + pLine->GetBorderLineStyle() ) ); + } + else + pCurFrameLine.reset(); +} + +bool ScTabViewShell::HasSelection( bool bText ) const +{ + bool bHas = false; + ScViewData& rData = const_cast<ScViewData&>(GetViewData()); + if ( bText ) + { + // Content contained: Count2 >= 1 + ScDocument& rDoc = rData.GetDocument(); + ScMarkData& rMark = rData.GetMarkData(); + ScAddress aCursor( rData.GetCurX(), rData.GetCurY(), rData.GetTabNo() ); + double fVal = 0.0; + if ( rDoc.GetSelectionFunction( SUBTOTAL_FUNC_CNT2, aCursor, rMark, fVal ) ) + bHas = ( fVal > 0.5 ); + } + else + { + ScRange aRange; + ScMarkType eMarkType = rData.GetSimpleArea( aRange ); + if ( eMarkType == SC_MARK_SIMPLE ) + bHas = ( aRange.aStart != aRange.aEnd ); // more than 1 cell + else + bHas = true; // multiple selection or filtered + } + return bHas; +} + +void ScTabViewShell::UIDeactivated( SfxInPlaceClient* pClient ) +{ + ClearHighlightRanges(); + + // Move in the ViewShell should really be called from Sfx, when the + // frame window is moved due to different toolboxes or other things + // (to not move painted objects by mistake, #56515#). + // this mechanism does however not work at the moment, that is why this + // call is here (in Move it is checked if the position has really changed). + ForceMove(); + SfxViewShell::UIDeactivated( pClient ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabvwsh9.cxx b/sc/source/ui/view/tabvwsh9.cxx new file mode 100644 index 0000000000..9127bb598a --- /dev/null +++ b/sc/source/ui/view/tabvwsh9.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 <svx/imapdlg.hxx> +#include <svx/svdmark.hxx> +#include <svx/svdview.hxx> +#include <svx/ImageMapInfo.hxx> +#include <svx/svxids.hrc> +#include <sfx2/bindings.hxx> +#include <sfx2/request.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/sidebar/Sidebar.hxx> +#include <svl/whiter.hxx> +#include <svl/stritem.hxx> + +#include "imapwrap.hxx" +#include <tabvwsh.hxx> +#include <viewdata.hxx> +#include <docsh.hxx> + +#include <svx/galleryitem.hxx> +#include <com/sun/star/gallery/GalleryItemType.hpp> + +class SvxIMapDlg; + +void ScTabViewShell::ExecChildWin(const SfxRequest& rReq) +{ + sal_uInt16 nSlot = rReq.GetSlot(); + switch(nSlot) + { + case SID_GALLERY: + { + // First make sure that the sidebar is visible + GetViewFrame().ShowChildWindow(SID_SIDEBAR); + + ::sfx2::sidebar::Sidebar::ShowPanel( + u"GalleryPanel", + GetViewFrame().GetFrame().GetFrameInterface()); + } + break; + } +} + +void ScTabViewShell::ExecGallery( const SfxRequest& rReq ) +{ + const SfxItemSet* pArgs = rReq.GetArgs(); + + const SvxGalleryItem* pGalleryItem = SfxItemSet::GetItem<SvxGalleryItem>(pArgs, SID_GALLERY_FORMATS, false); + if ( !pGalleryItem ) + return; + + sal_Int8 nType( pGalleryItem->GetType() ); + if ( nType == css::gallery::GalleryItemType::GRAPHIC ) + { + MakeDrawLayer(); + + Graphic aGraphic( pGalleryItem->GetGraphic() ); + Point aPos = GetInsertPos(); + + PasteGraphic( aPos, aGraphic, OUString() ); + } + else if ( nType == css::gallery::GalleryItemType::MEDIA ) + { + // for sounds (linked or not), insert a hyperlink button, + // like in Impress and Writer + const SfxStringItem aMediaURLItem( SID_INSERT_AVMEDIA, pGalleryItem->GetURL() ); + GetViewFrame().GetDispatcher()->ExecuteList(SID_INSERT_AVMEDIA, + SfxCallMode::SYNCHRON, { &aMediaURLItem }); + } +} + +void ScTabViewShell::ExecImageMap( SfxRequest& rReq ) +{ + sal_uInt16 nSlot = rReq.GetSlot(); + switch(nSlot) + { + case SID_IMAP: + { + SfxViewFrame& rThisFrame = GetViewFrame(); + sal_uInt16 nId = ScIMapChildWindowId(); + rThisFrame.ToggleChildWindow( nId ); + GetViewFrame().GetBindings().Invalidate( SID_IMAP ); + + if ( rThisFrame.HasChildWindow( nId ) ) + { + SvxIMapDlg* pDlg = GetIMapDlg(); + if ( pDlg ) + { + SdrView* pDrView = GetScDrawView(); + if ( pDrView ) + { + const SdrMarkList& rMarkList = pDrView->GetMarkedObjectList(); + if ( rMarkList.GetMarkCount() == 1 ) + UpdateIMap( rMarkList.GetMark( 0 )->GetMarkedSdrObj() ); + } + } + } + + rReq.Ignore(); + } + break; + + case SID_IMAP_EXEC: + { + SdrView* pDrView = GetScDrawView(); + SdrMark* pMark = pDrView ? pDrView->GetMarkedObjectList().GetMark(0) : nullptr; + + if ( pMark ) + { + SdrObject* pSdrObj = pMark->GetMarkedSdrObj(); + SvxIMapDlg* pDlg = GetIMapDlg(); + + if ( ScIMapDlgGetObj(pDlg) == static_cast<void*>(pSdrObj) ) + { + const ImageMap& rImageMap = ScIMapDlgGetMap(pDlg); + SvxIMapInfo* pIMapInfo = SvxIMapInfo::GetIMapInfo( pSdrObj ); + + if ( !pIMapInfo ) + pSdrObj->AppendUserData( std::unique_ptr<SdrObjUserData>(new SvxIMapInfo( rImageMap )) ); + else + pIMapInfo->SetImageMap( rImageMap ); + + GetViewData().GetDocShell()->SetDrawModified(); + } + } + } + break; + } +} + +void ScTabViewShell::GetImageMapState( SfxItemSet& rSet ) +{ + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + while ( nWhich ) + { + switch ( nWhich ) + { + case SID_IMAP: + { + // We don't disable this anymore + + bool bThere = false; + SfxViewFrame& rThisFrame = GetViewFrame(); + sal_uInt16 nId = ScIMapChildWindowId(); + if ( rThisFrame.KnowsChildWindow(nId) ) + if ( rThisFrame.HasChildWindow(nId) ) + bThere = true; + + ObjectSelectionType eType=GetCurObjectSelectionType(); + bool bEnable=(eType==OST_OleObject) ||(eType==OST_Graphic); + if(!bThere && !bEnable) + { + rSet.DisableItem( nWhich ); + } + else + { + rSet.Put( SfxBoolItem( nWhich, bThere ) ); + } + } + break; + + case SID_IMAP_EXEC: + { + bool bDisable = true; + + SdrView* pDrView = GetScDrawView(); + if ( pDrView ) + { + const SdrMarkList& rMarkList = pDrView->GetMarkedObjectList(); + if ( rMarkList.GetMarkCount() == 1 ) + if ( ScIMapDlgGetObj(GetIMapDlg()) == + static_cast<void*>(rMarkList.GetMark(0)->GetMarkedSdrObj()) ) + bDisable = false; + } + + rSet.Put( SfxBoolItem( SID_IMAP_EXEC, bDisable ) ); + } + break; + } + + nWhich = aIter.NextWhich(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabvwsha.cxx b/sc/source/ui/view/tabvwsha.cxx new file mode 100644 index 0000000000..c332c9542a --- /dev/null +++ b/sc/source/ui/view/tabvwsha.cxx @@ -0,0 +1,1817 @@ +/* -*- 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 <com/sun/star/table/BorderLineStyle.hpp> +#include <officecfg/Office/Calc.hxx> + +#include <comphelper/lok.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/langitem.hxx> +#include <o3tl/temporary.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/request.hxx> +#include <sfx2/sfxdlg.hxx> +#include <sfx2/sidebar/Sidebar.hxx> +#include <sfx2/viewfrm.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/newstyle.hxx> +#include <sfx2/tplpitem.hxx> +#include <svl/ilstitem.hxx> +#include <svl/numformat.hxx> +#include <svl/zformat.hxx> +#include <svl/int64item.hxx> +#include <svl/ptitem.hxx> +#include <svl/srchitem.hxx> +#include <svl/srchdefs.hxx> +#include <svl/stritem.hxx> +#include <svl/whiter.hxx> +#include <svx/numinf.hxx> +#include <svx/xbtmpit.hxx> +#include <svx/xlndsit.hxx> +#include <svx/xlnstit.hxx> +#include <svx/xlnedit.hxx> +#include <svx/xflgrit.hxx> +#include <svx/xflftrit.hxx> +#include <svx/xflhtit.hxx> +#include <svx/zoomslideritem.hxx> + +#include <global.hxx> +#include <appoptio.hxx> +#include <attrib.hxx> +#include <cellform.hxx> +#include <cellvalue.hxx> +#include <compiler.hxx> +#include <docsh.hxx> +#include <document.hxx> +#include <formulacell.hxx> +#include <globstr.hrc> +#include <inputhdl.hxx> +#include <inputwin.hxx> +#include <markdata.hxx> +#include <patattr.hxx> +#include <sc.hrc> +#include <scabstdlg.hxx> +#include <scitems.hxx> +#include <scmod.hxx> +#include <scresid.hxx> +#include <stlpool.hxx> +#include <tabvwsh.hxx> +#include <tokenarray.hxx> +#include <viewdata.hxx> +#include <docpool.hxx> +#include <printfun.hxx> +#include <undostyl.hxx> +#include <futext.hxx> + +#include <memory> + +using namespace com::sun::star; + +bool ScTabViewShell::GetFunction( OUString& rFuncStr, FormulaError nErrCode ) +{ + sal_uInt32 nFuncs = SC_MOD()->GetAppOptions().GetStatusFunc(); + ScViewData& rViewData = GetViewData(); + ScMarkData& rMark = rViewData.GetMarkData(); + bool bIgnoreError = (rMark.IsMarked() || rMark.IsMultiMarked()); + bool bFirst = true; + for ( sal_uInt16 nFunc = 0; nFunc < 32; nFunc++ ) + { + if ( !(nFuncs & (1U << nFunc)) ) + continue; + ScSubTotalFunc eFunc = static_cast<ScSubTotalFunc>(nFunc); + + if (bIgnoreError && (eFunc == SUBTOTAL_FUNC_CNT || eFunc == SUBTOTAL_FUNC_CNT2)) + nErrCode = FormulaError::NONE; + + if (nErrCode != FormulaError::NONE) + { + rFuncStr = ScGlobal::GetLongErrorString(nErrCode); + return true; + } + + TranslateId pGlobStrId; + switch (eFunc) + { + case SUBTOTAL_FUNC_AVE: pGlobStrId = STR_FUN_TEXT_AVG; break; + case SUBTOTAL_FUNC_CNT: pGlobStrId = STR_FUN_TEXT_COUNT; break; + case SUBTOTAL_FUNC_CNT2: pGlobStrId = STR_FUN_TEXT_COUNT2; break; + case SUBTOTAL_FUNC_MAX: pGlobStrId = STR_FUN_TEXT_MAX; break; + case SUBTOTAL_FUNC_MIN: pGlobStrId = STR_FUN_TEXT_MIN; break; + case SUBTOTAL_FUNC_SUM: pGlobStrId = STR_FUN_TEXT_SUM; break; + case SUBTOTAL_FUNC_SELECTION_COUNT: pGlobStrId = STR_FUN_TEXT_SELECTION_COUNT; break; + + default: + { + // added to avoid warnings + } + } + if (pGlobStrId) + { + ScDocument& rDoc = rViewData.GetDocument(); + SCCOL nPosX = rViewData.GetCurX(); + SCROW nPosY = rViewData.GetCurY(); + SCTAB nTab = rViewData.GetTabNo(); + + OUString aStr = ScResId(pGlobStrId) + ": "; + + ScAddress aCursor( nPosX, nPosY, nTab ); + double nVal; + if ( rDoc.GetSelectionFunction( eFunc, aCursor, rMark, nVal ) ) + { + if ( nVal == 0.0 ) + aStr += "0"; + else + { + // Number in the standard format, the other on the cursor position + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + sal_uInt32 nNumFmt = 0; + if ( eFunc != SUBTOTAL_FUNC_CNT && eFunc != SUBTOTAL_FUNC_CNT2 && eFunc != SUBTOTAL_FUNC_SELECTION_COUNT) + { + // number format from attributes or formula + nNumFmt = rDoc.GetNumberFormat( nPosX, nPosY, nTab ); + // If the number format is time (without date) and the + // result is not within 24 hours, use a duration + // format. Summing date+time doesn't make much sense + // otherwise but we also don't want to display duration + // for a single date+time value. + if (nVal < 0.0 || nVal >= 1.0) + { + const SvNumberformat* pFormat = pFormatter->GetEntry(nNumFmt); + if (pFormat && (pFormat->GetType() == SvNumFormatType::TIME)) + nNumFmt = pFormatter->GetTimeFormat( nVal, pFormat->GetLanguage(), true); + } + } + + OUString aValStr; + const Color* pDummy; + pFormatter->GetOutputString( nVal, nNumFmt, aValStr, &pDummy ); + aStr += aValStr; + } + } + if ( bFirst ) + { + rFuncStr += aStr; + bFirst = false; + } + else + rFuncStr += "; " + aStr; + } + } + + return !rFuncStr.isEmpty(); +} + +// Functions that are disabled, depending on the selection +// Default: +// SID_DELETE, +// SID_DELETE_CONTENTS, +// FID_DELETE_CELL +// FID_VALIDATION + +void ScTabViewShell::GetState( SfxItemSet& rSet ) +{ + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + ScDocShell* pDocShell = rViewData.GetDocShell(); + ScMarkData& rMark = rViewData.GetMarkData(); + SCCOL nPosX = rViewData.GetCurX(); + SCROW nPosY = rViewData.GetCurY(); + SCTAB nTab = rViewData.GetTabNo(); + + SfxViewFrame& rThisFrame = GetViewFrame(); + bool bOle = GetViewFrame().GetFrame().IsInPlace(); + + SCTAB nTabSelCount = rMark.GetSelectCount(); + + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + + while ( nWhich ) + { + switch ( nWhich ) + { + case FID_CHG_COMMENT: + { + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScAddress aPos( nPosX, nPosY, nTab ); + if ( pDocSh->IsReadOnly() || !pDocSh->GetChangeAction(aPos) || pDocSh->IsDocShared() ) + rSet.DisableItem( nWhich ); + } + break; + + case SID_OPENDLG_EDIT_PRINTAREA: + case SID_ADD_PRINTAREA: + case SID_DEFINE_PRINTAREA: + { + if ( pDocShell && pDocShell->IsDocShared() ) + { + rSet.DisableItem( nWhich ); + } + } + break; + + case SID_DELETE_PRINTAREA: + if ( pDocShell && pDocShell->IsDocShared() ) + { + rSet.DisableItem( nWhich ); + } + else if (rDoc.IsPrintEntireSheet(nTab)) + rSet.DisableItem(nWhich); + break; + + case SID_STATUS_PAGESTYLE: + case SID_HFEDIT: + GetViewData().GetDocShell()->GetStatePageStyle( rSet, nTab ); + break; + + case SID_SEARCH_ITEM: + { + SvxSearchItem aItem(ScGlobal::GetSearchItem()); // make a copy. + // Search on current selection if a range is marked. + aItem.SetSelection(rMark.IsMarked()); + rSet.Put(aItem); + break; + } + + case SID_SEARCH_OPTIONS: + { + // Anything goes + SearchOptionFlags nOptions = SearchOptionFlags::ALL; + + // No replacement if ReadOnly + if (GetViewData().GetDocShell()->IsReadOnly()) + nOptions &= ~SearchOptionFlags( SearchOptionFlags::REPLACE | SearchOptionFlags::REPLACE_ALL ); + rSet.Put( SfxUInt16Item( nWhich, static_cast<sal_uInt16>(nOptions) ) ); + } + break; + + case SID_CURRENTCELL: + { + ScAddress aScAddress( GetViewData().GetCurX(), GetViewData().GetCurY(), 0 ); + OUString aAddr(aScAddress.Format(ScRefFlags::ADDR_ABS, nullptr, rDoc.GetAddressConvention())); + SfxStringItem aPosItem( SID_CURRENTCELL, aAddr ); + + rSet.Put( aPosItem ); + } + break; + + case SID_CURRENTTAB: + // Table for Basic is 1-based + rSet.Put( SfxUInt16Item( nWhich, static_cast<sal_uInt16>(GetViewData().GetTabNo()) + 1 ) ); + break; + + case SID_CURRENTDOC: + rSet.Put( SfxStringItem( nWhich, GetViewData().GetDocShell()->GetTitle() ) ); + break; + + case FID_TOGGLEINPUTLINE: + { + sal_uInt16 nId = ScInputWindowWrapper::GetChildWindowId(); + + if ( rThisFrame.KnowsChildWindow( nId ) ) + { + SfxChildWindow* pWnd = rThisFrame.GetChildWindow( nId ); + rSet.Put( SfxBoolItem( nWhich, pWnd != nullptr ) ); + } + else + rSet.DisableItem( nWhich ); + } + break; + + case FID_DEL_MANUALBREAKS: + if (!rDoc.HasManualBreaks(nTab)) + rSet.DisableItem( nWhich ); + break; + + case FID_RESET_PRINTZOOM: + { + // disable if already set to default + + OUString aStyleName = rDoc.GetPageStyle( nTab ); + ScStyleSheetPool* pStylePool = rDoc.GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet = pStylePool->Find( aStyleName, + SfxStyleFamily::Page ); + OSL_ENSURE( pStyleSheet, "PageStyle not found" ); + if ( pStyleSheet ) + { + SfxItemSet& rStyleSet = pStyleSheet->GetItemSet(); + sal_uInt16 nScale = rStyleSet.Get(ATTR_PAGE_SCALE).GetValue(); + sal_uInt16 nPages = rStyleSet.Get(ATTR_PAGE_SCALETOPAGES).GetValue(); + if ( nScale == 100 && nPages == 0 ) + rSet.DisableItem( nWhich ); + } + } + break; + + case SID_ZOOM_IN: + { + const Fraction& rZoomY = GetViewData().GetZoomY(); + tools::Long nZoom = tools::Long(rZoomY * 100); + if (nZoom >= MAXZOOM) + rSet.DisableItem(nWhich); + } + break; + case SID_ZOOM_OUT: + { + const Fraction& rZoomY = GetViewData().GetZoomY(); + tools::Long nZoom = tools::Long(rZoomY * 100); + if (nZoom <= MINZOOM) + rSet.DisableItem(nWhich); + } + break; + + case FID_SCALE: + case SID_ATTR_ZOOM: + if ( bOle ) + rSet.DisableItem( nWhich ); + else + { + const Fraction& rOldY = GetViewData().GetZoomY(); + sal_uInt16 nZoom = static_cast<sal_uInt16>(tools::Long( rOldY * 100 )); + rSet.Put( SvxZoomItem( SvxZoomType::PERCENT, nZoom, TypedWhichId<SvxZoomItem>(nWhich) ) ); + } + break; + + case SID_ATTR_ZOOMSLIDER: + { + if ( bOle ) + rSet.DisableItem( nWhich ); + else + { + const Fraction& rOldY = GetViewData().GetZoomY(); + sal_uInt16 nCurrentZoom = static_cast<sal_uInt16>(tools::Long( rOldY * 100 )); + + if( nCurrentZoom ) + { + SvxZoomSliderItem aZoomSliderItem( nCurrentZoom, MINZOOM, MAXZOOM, SID_ATTR_ZOOMSLIDER ); + aZoomSliderItem.AddSnappingPoint( 100 ); + rSet.Put( aZoomSliderItem ); + } + } + } + break; + + case FID_FUNCTION_BOX: + { + const bool bBoxOpen = ::sfx2::sidebar::Sidebar::IsPanelVisible(u"ScFunctionsPanel", + rThisFrame.GetFrame().GetFrameInterface()); + rSet.Put(SfxBoolItem(nWhich, bBoxOpen)); + break; + } + + case FID_TOGGLESYNTAX: + rSet.Put(SfxBoolItem(nWhich, GetViewData().IsSyntaxMode())); + break; + + case FID_TOGGLECOLROWHIGHLIGHTING: + rSet.Put(SfxBoolItem( + nWhich, + officecfg::Office::Calc::Content::Display::ColumnRowHighlighting::get())); + break; + + case FID_TOGGLEHEADERS: + rSet.Put(SfxBoolItem(nWhich, GetViewData().IsHeaderMode())); + break; + + case FID_TOGGLEFORMULA: + { + const ScViewOptions& rOpts = rViewData.GetOptions(); + bool bFormulaMode = rOpts.GetOption( VOPT_FORMULAS ); + rSet.Put(SfxBoolItem(nWhich, bFormulaMode )); + } + break; + + case FID_NORMALVIEWMODE: + case FID_PAGEBREAKMODE: + // always handle both slots - they exclude each other + if ( bOle ) + { + rSet.DisableItem( FID_NORMALVIEWMODE ); + rSet.DisableItem( FID_PAGEBREAKMODE ); + } + else + { + rSet.Put(SfxBoolItem(FID_NORMALVIEWMODE, !GetViewData().IsPagebreakMode())); + rSet.Put(SfxBoolItem(FID_PAGEBREAKMODE, GetViewData().IsPagebreakMode())); + } + break; + + case FID_PROTECT_DOC: + { + if ( pDocShell && pDocShell->IsDocShared() ) + { + rSet.DisableItem( nWhich ); + } + else + { + rSet.Put( SfxBoolItem( nWhich, rDoc.IsDocProtected() ) ); + } + } + break; + + case FID_PROTECT_TABLE: + { + if ( pDocShell && pDocShell->IsDocShared() ) + { + rSet.DisableItem( nWhich ); + } + else + { + rSet.Put( SfxBoolItem( nWhich, rDoc.IsTabProtected( nTab ) ) ); + } + } + break; + + case SID_AUTO_OUTLINE: + { + if (rDoc.GetChangeTrack()!=nullptr || GetViewData().IsMultiMarked()) + { + rSet.DisableItem( nWhich ); + } + } + break; + + case SID_OUTLINE_DELETEALL: + { + SCTAB nOlTab = GetViewData().GetTabNo(); + ScOutlineTable* pOlTable = rDoc.GetOutlineTable( nOlTab ); + if (pOlTable == nullptr) + rSet.DisableItem( nWhich ); + } + break; + + case SID_WINDOW_SPLIT: + rSet.Put(SfxBoolItem(nWhich, + rViewData.GetHSplitMode() == SC_SPLIT_NORMAL || + rViewData.GetVSplitMode() == SC_SPLIT_NORMAL )); + break; + + case SID_WINDOW_FIX: + if(!comphelper::LibreOfficeKit::isActive()) + { + rSet.Put(SfxBoolItem(nWhich, + rViewData.GetHSplitMode() == SC_SPLIT_FIX || + rViewData.GetVSplitMode() == SC_SPLIT_FIX )); + } + else + { + rSet.Put(SfxBoolItem(nWhich, + rViewData.GetLOKSheetFreezeIndex(true) > 0 || + rViewData.GetLOKSheetFreezeIndex(false) > 0 )); + } + break; + + case SID_WINDOW_FIX_COL: + case SID_WINDOW_FIX_ROW: + { + Point aPos; + bool bIsCol = (nWhich == SID_WINDOW_FIX_COL); + aPos.setX(rViewData.GetLOKSheetFreezeIndex(bIsCol)); + aPos.setY(rViewData.GetTabNo()); + rSet.Put(SfxPointItem(nWhich, aPos)); + } + break; + + case FID_CHG_SHOW: + { + if ( rDoc.GetChangeTrack() == nullptr || ( pDocShell && pDocShell->IsDocShared() ) ) + rSet.DisableItem( nWhich ); + } + break; + case FID_CHG_ACCEPT: + { + if( + ( !rDoc.GetChangeTrack() && !rThisFrame.HasChildWindow(FID_CHG_ACCEPT) ) + || + ( pDocShell && pDocShell->IsDocShared() ) + ) + { + rSet.DisableItem( nWhich); + } + else + { + rSet.Put(SfxBoolItem(FID_CHG_ACCEPT, + rThisFrame.HasChildWindow(FID_CHG_ACCEPT))); + } + } + break; + + case SID_FORMATPAGE: + // in protected tables + if ( pDocShell && ( pDocShell->IsReadOnly() || pDocShell->IsDocShared() ) ) + rSet.DisableItem( nWhich ); + break; + + case SID_PRINTPREVIEW: + // Toggle slot needs a State + rSet.Put( SfxBoolItem( nWhich, false ) ); + break; + + case SID_READONLY_MODE: + rSet.Put( SfxBoolItem( nWhich, GetViewData().GetDocShell()->IsReadOnly() ) ); + break; + + case FID_TAB_DESELECTALL: + if ( nTabSelCount == 1 ) + rSet.DisableItem( nWhich ); // enabled only if several sheets are selected + break; + + case FID_TOGGLEHIDDENCOLROW: + const svtools::ColorConfig& rColorCfg = SC_MOD()->GetColorConfig(); + rSet.Put( SfxBoolItem( nWhich, rColorCfg.GetColorValue(svtools::CALCHIDDENROWCOL).bIsVisible) ); + break; + + } // switch ( nWitch ) + nWhich = aIter.NextWhich(); + } // while ( nWitch ) +} + +void ScTabViewShell::ExecuteCellFormatDlg(SfxRequest& rReq, const OUString &rName) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + + std::shared_ptr<SvxBoxItem> aLineOuter(std::make_shared<SvxBoxItem>(ATTR_BORDER)); + std::shared_ptr<SvxBoxInfoItem> aLineInner(std::make_shared<SvxBoxInfoItem>(ATTR_BORDER_INNER)); + + const ScPatternAttr* pOldAttrs = GetSelectionPattern(); + + auto pOldSet = std::make_shared<SfxItemSet>(pOldAttrs->GetItemSet()); + + pOldSet->MergeRange(XATTR_FILLSTYLE, XATTR_FILLCOLOR); + + pOldSet->MergeRange(SID_ATTR_BORDER_STYLES, SID_ATTR_BORDER_DEFAULT_WIDTH); + + // We only allow these border line types. + std::vector<sal_Int32> aBorderStyles{ + table::BorderLineStyle::SOLID, + table::BorderLineStyle::DOTTED, + table::BorderLineStyle::DASHED, + table::BorderLineStyle::FINE_DASHED, + table::BorderLineStyle::DASH_DOT, + table::BorderLineStyle::DASH_DOT_DOT, + table::BorderLineStyle::DOUBLE_THIN }; + + pOldSet->Put(SfxIntegerListItem(SID_ATTR_BORDER_STYLES, std::move(aBorderStyles))); + + // Set the default border width to 0.75 points. + SfxInt64Item aBorderWidthItem(SID_ATTR_BORDER_DEFAULT_WIDTH, 75); + pOldSet->Put(aBorderWidthItem); + + // Get border items and put them in the set: + GetSelectionFrame( aLineOuter, aLineInner ); + + //Fix border incorrect for RTL fdo#62399 + if( rDoc.IsLayoutRTL( GetViewData().GetTabNo() ) ) + { + std::unique_ptr<SvxBoxItem> aNewFrame(aLineOuter->Clone()); + std::unique_ptr<SvxBoxInfoItem> aTempInfo(aLineInner->Clone()); + + if ( aLineInner->IsValid(SvxBoxInfoItemValidFlags::LEFT) ) + aNewFrame->SetLine( aLineOuter->GetLeft(), SvxBoxItemLine::RIGHT ); + if ( aLineInner->IsValid(SvxBoxInfoItemValidFlags::RIGHT) ) + aNewFrame->SetLine( aLineOuter->GetRight(), SvxBoxItemLine::LEFT ); + + aLineInner->SetValid( SvxBoxInfoItemValidFlags::LEFT, aTempInfo->IsValid(SvxBoxInfoItemValidFlags::RIGHT)); + aLineInner->SetValid( SvxBoxInfoItemValidFlags::RIGHT, aTempInfo->IsValid(SvxBoxInfoItemValidFlags::LEFT)); + + pOldSet->Put( std::move(aNewFrame) ); + } + else + { + pOldSet->Put( *aLineOuter ); + } + + pOldSet->Put( *aLineInner ); + + // Generate NumberFormat Value from Value and Language and box it. + pOldSet->Put( SfxUInt32Item( ATTR_VALUE_FORMAT, + pOldAttrs->GetNumberFormat( rDoc.GetFormatTable() ) ) ); + + std::unique_ptr<SvxNumberInfoItem> pNumberInfoItem = MakeNumberInfoItem(rDoc, GetViewData()); + pOldSet->MergeRange( SID_ATTR_NUMBERFORMAT_INFO, SID_ATTR_NUMBERFORMAT_INFO ); + pOldSet->Put( std::move(pNumberInfoItem) ); + + bInFormatDialog = true; + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + VclPtr<SfxAbstractTabDialog> pDlg(pFact->CreateScAttrDlg(GetFrameWeld(), pOldSet.get())); + + if (!rName.isEmpty()) + pDlg->SetCurPageId(rName); + + auto pRequest = std::make_shared<SfxRequest>(rReq); + rReq.Ignore(); // the 'old' request is not relevant any more + + pDlg->StartExecuteAsync([pDlg, pOldSet, pRequest, this](sal_Int32 nResult){ + bInFormatDialog = false; + + if ( nResult == RET_OK ) + { + const SfxItemSet* pOutSet = pDlg->GetOutputItemSet(); + if(const SvxNumberInfoItem* pItem = pOutSet->GetItemIfSet(SID_ATTR_NUMBERFORMAT_INFO)) + { + UpdateNumberFormatter(*pItem); + } + + ApplyAttributes(*pOutSet, *pOldSet); + + pRequest->Done(*pOutSet); + } + + pDlg->disposeOnce(); + }); +} + +const OUString* ScTabViewShell::GetEditString() const +{ + if (mpInputHandler) + return &mpInputHandler->GetEditString(); + + return nullptr; +} + +bool ScTabViewShell::IsRefInputMode() const +{ + ScModule* pScMod = SC_MOD(); + if ( pScMod ) + { + if( pScMod->IsRefDialogOpen() ) + return pScMod->IsFormulaMode(); + if( pScMod->IsFormulaMode() ) + { + ScInputHandler* pHdl = pScMod->GetInputHdl(); + if ( pHdl ) + { + const ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + const ScAddress aPos( rViewData.GetCurPos() ); + const sal_uInt32 nIndex = rDoc.GetAttr(aPos, ATTR_VALUE_FORMAT )->GetValue(); + const SvNumFormatType nType = rDoc.GetFormatTable()->GetType(nIndex); + if (nType == SvNumFormatType::TEXT) + { + return false; + } + OUString aString = pHdl->GetEditString(); + if ( !pHdl->GetSelIsRef() && aString.getLength() > 1 && + ( aString[0] == '+' || aString[0] == '-' ) ) + { + ScCompiler aComp( rDoc, aPos, rDoc.GetGrammar() ); + aComp.SetCloseBrackets( false ); + std::unique_ptr<ScTokenArray> pArr(aComp.CompileString(aString)); + if ( pArr && pArr->MayReferenceFollow() ) + { + return true; + } + } + else + { + return true; + } + } + } + } + + return false; +} + +void ScTabViewShell::ExecuteInputDirect() +{ + if ( !IsRefInputMode() ) + { + ScModule* pScMod = SC_MOD(); + if ( pScMod ) + { + pScMod->InputEnterHandler(); + } + } +} + +void ScTabViewShell::UpdateInputHandler( bool bForce /* = sal_False */, bool bStopEditing /* = sal_True */ ) +{ + ScInputHandler* pHdl = mpInputHandler ? mpInputHandler.get() : SC_MOD()->GetInputHdl(); + + if ( pHdl ) + { + OUString aString; + const EditTextObject* pObject = nullptr; + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + SCCOL nPosX = rViewData.GetCurX(); + SCROW nPosY = rViewData.GetCurY(); + SCTAB nTab = rViewData.GetTabNo(); + SCTAB nStartTab = 0; + SCTAB nEndTab = 0; + SCCOL nStartCol = 0; + SCROW nStartRow = 0; + SCCOL nEndCol = 0; + SCROW nEndRow = 0; + ScAddress aPos = rViewData.GetCurPos(); + + rViewData.GetSimpleArea( nStartCol, nStartRow, nStartTab, + nEndCol, nEndRow, nEndTab ); + + PutInOrder( nStartCol, nEndCol ); + PutInOrder( nStartRow, nEndRow ); + PutInOrder( nStartTab, nEndTab ); + + bool bHideFormula = false; + bool bHideAll = false; + + if (rDoc.IsTabProtected(nTab)) + { + const ScProtectionAttr* pProt = rDoc.GetAttr( nPosX,nPosY,nTab, + ATTR_PROTECTION); + bHideFormula = pProt->GetHideFormula(); + bHideAll = pProt->GetHideCell(); + } + + if (!bHideAll) + { + ScRefCellValue rCell(rDoc, aPos); + if (rCell.getType() == CELLTYPE_FORMULA) + { + if (!bHideFormula) + aString = rCell.getFormula()->GetFormula(); + } + else if (rCell.getType() == CELLTYPE_EDIT) + { + pObject = rCell.getEditText(); + } + else + { + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + sal_uInt32 nNumFmt = rDoc.GetNumberFormat( aPos ); + + aString = ScCellFormat::GetInputString( rCell, nNumFmt, *pFormatter, rDoc ); + if (rCell.getType() == CELLTYPE_STRING) + { + // Put a ' in front if necessary, so that the string is not + // unintentionally interpreted as a number, and to show the + // user that it is a string (#35060#). + // If cell is not formatted as Text, a leading apostrophe + // needs another prepended, also '=' or '+' or '-' + // otherwise starting a formula. + // NOTE: this corresponds with + // sc/source/core/data/column3.cxx ScColumn::ParseString() + // removing one apostrophe. + // For number format Text IsNumberFormat() would never + // result in numeric anyway. + if (!pFormatter->IsTextFormat(nNumFmt) && (aString.startsWith("'") + || aString.startsWith("=") || aString.startsWith("+") || aString.startsWith("-") + || pFormatter->IsNumberFormat(aString, nNumFmt, o3tl::temporary(double())))) + aString = "'" + aString; + } + } + } + + ScInputHdlState aState( ScAddress( nPosX, nPosY, nTab ), + ScAddress( nStartCol, nStartRow, nTab ), + ScAddress( nEndCol, nEndRow, nTab ), + aString, + pObject ); + + // if using the view's local input handler, this view can always be set + // as current view inside NotifyChange. + ScTabViewShell* pSourceSh = mpInputHandler ? this : nullptr; + + pHdl->NotifyChange( &aState, bForce, pSourceSh, bStopEditing ); + } + + SfxBindings& rBindings = GetViewFrame().GetBindings(); + rBindings.Invalidate( SID_STATUS_SUM ); // always together with the input row + rBindings.Invalidate( SID_ATTR_SIZE ); + rBindings.Invalidate( SID_TABLE_CELL ); +} + +void ScTabViewShell::UpdateInputHandlerCellAdjust( SvxCellHorJustify eJust ) +{ + if( ScInputHandler* pHdl = mpInputHandler ? mpInputHandler.get() : SC_MOD()->GetInputHdl() ) + pHdl->UpdateCellAdjust( eJust ); +} + +void ScTabViewShell::ExecuteSave( SfxRequest& rReq ) +{ + // only SID_SAVEDOC / SID_SAVEASDOC + bool bCommitChanges = true; + const SfxItemSet* pReqArgs = rReq.GetArgs(); + const SfxPoolItem* pItem; + + if (pReqArgs && pReqArgs->HasItem(FN_PARAM_1, &pItem)) + bCommitChanges = !static_cast<const SfxBoolItem*>(pItem)->GetValue(); + + // Finish entering unless 'DontTerminateEdit' is specified, even if a formula is being processed + if (bCommitChanges) + { + bool bLOKActive = comphelper::LibreOfficeKit::isActive(); + + // Disable error dialog box when about to save in lok mode as + // this ultimately invokes SvpSalInstance::DoYield() when we want + // to save immediately without committing any erroneous input in possibly + // a cell with validation rules. After save is complete the user + // can continue editing. + SC_MOD()->InputEnterHandler(ScEnterMode::NORMAL, bLOKActive /* bBeforeSavingInLOK */); + + if (bLOKActive) + { + // Normally this isn't needed, but in Calc when editing a cell formula + // and manually saving (without changing cells or hitting enter), while + // InputEnterHandler will mark the doc as modified (when it is), because + // we will save the doc immediately afterwards, the modified state event + // is clobbered. To avoid that, we need to update SID_DOC_MODIFIED so that + // a possible state of "true" after "InputEnterHandler" will be sent + // as a notification. It is important that the notification goes through + // normal process (cache) rather than directly notifying the views. + // Otherwise, because there is a previous state of "false" in cache, the + // "false" state after saving will be ignored. + // This will work only if .uno:ModifiedStatus message will be removed from + // the mechanism that keeps in the message queue only last message of + // a particular status even if the values are different. + GetViewData().GetDocShell()->GetViewBindings()->Update(SID_DOC_MODIFIED); + } + } + + if ( GetViewData().GetDocShell()->IsDocShared() ) + { + GetViewData().GetDocShell()->SetDocumentModified(); + } + + // otherwise as normal + GetViewData().GetDocShell()->ExecuteSlot( rReq ); +} + +void ScTabViewShell::GetSaveState( SfxItemSet& rSet ) +{ + SfxShell* pDocSh = GetViewData().GetDocShell(); + + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + while( nWhich ) + { + if ( nWhich != SID_SAVEDOC || !GetViewData().GetDocShell()->IsDocShared() ) + { + // get state from DocShell + pDocSh->GetSlotState( nWhich, nullptr, &rSet ); + } + nWhich = aIter.NextWhich(); + } +} + +void ScTabViewShell::ExecDrawOpt( const SfxRequest& rReq ) +{ + ScViewOptions aViewOptions = GetViewData().GetOptions(); + ScGridOptions aGridOptions = aViewOptions.GetGridOptions(); + + SfxBindings& rBindings = GetViewFrame().GetBindings(); + const SfxItemSet* pArgs = rReq.GetArgs(); + const SfxPoolItem* pItem; + sal_uInt16 nSlotId = rReq.GetSlot(); + switch (nSlotId) + { + case SID_GRID_VISIBLE: + if ( pArgs && pArgs->GetItemState(nSlotId,true,&pItem) == SfxItemState::SET ) + { + aGridOptions.SetGridVisible( static_cast<const SfxBoolItem*>(pItem)->GetValue() ); + aViewOptions.SetGridOptions(aGridOptions); + rBindings.Invalidate(SID_GRID_VISIBLE); + } + break; + + case SID_GRID_USE: + if ( pArgs && pArgs->GetItemState(nSlotId,true,&pItem) == SfxItemState::SET ) + { + aGridOptions.SetUseGridSnap( static_cast<const SfxBoolItem*>(pItem)->GetValue() ); + aViewOptions.SetGridOptions(aGridOptions); + rBindings.Invalidate(SID_GRID_USE); + } + break; + + case SID_HELPLINES_MOVE: + if ( pArgs && pArgs->GetItemState(nSlotId,true,&pItem) == SfxItemState::SET ) + { + aViewOptions.SetOption( VOPT_HELPLINES, static_cast<const SfxBoolItem*>(pItem)->GetValue() ); + rBindings.Invalidate(SID_HELPLINES_MOVE); + } + break; + } + + GetViewData().SetOptions(aViewOptions); +} + +void ScTabViewShell::GetDrawOptState( SfxItemSet& rSet ) +{ + SfxBoolItem aBool; + + const ScViewOptions& rViewOptions = GetViewData().GetOptions(); + const ScGridOptions& rGridOptions = rViewOptions.GetGridOptions(); + + aBool.SetValue(rGridOptions.GetGridVisible()); + aBool.SetWhich( SID_GRID_VISIBLE ); + rSet.Put( aBool ); + + aBool.SetValue(rGridOptions.GetUseGridSnap()); + aBool.SetWhich( SID_GRID_USE ); + rSet.Put( aBool ); + + aBool.SetValue(rViewOptions.GetOption( VOPT_HELPLINES )); + aBool.SetWhich( SID_HELPLINES_MOVE ); + rSet.Put( aBool ); +} + +void ScTabViewShell::ExecStyle( SfxRequest& rReq ) +{ + const SfxItemSet* pArgs = rReq.GetArgs(); + const sal_uInt16 nSlotId = rReq.GetSlot(); + if ( !pArgs && nSlotId != SID_STYLE_NEW_BY_EXAMPLE && nSlotId != SID_STYLE_UPDATE_BY_EXAMPLE ) + { + // in case of vertical toolbar + GetDispatcher()->Execute( SID_STYLE_DESIGNER, SfxCallMode::ASYNCHRON | SfxCallMode::RECORD ); + return; + } + + SfxBindings& rBindings = GetViewData().GetBindings(); + const SCTAB nCurTab = GetViewData().GetTabNo(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScMarkData& rMark = GetViewData().GetMarkData(); + ScModule* pScMod = SC_MOD(); + SdrObject* pEditObject = GetDrawView()->GetTextEditObject(); + OutlinerView* pOLV = GetDrawView()->GetTextEditOutlinerView(); + ESelection aSelection = pOLV ? pOLV->GetSelection() : ESelection(); + OUString aRefName; + bool bUndo = rDoc.IsUndoEnabled(); + + SfxStyleSheetBasePool* pStylePool = rDoc.GetStyleSheetPool(); + SfxStyleSheetBase* pStyleSheet = nullptr; + + bool bStyleToMarked = false; + bool bListAction = false; + bool bAddUndo = false; // add ScUndoModifyStyle (style modified) + ScStyleSaveData aOldData; // for undo/redo + ScStyleSaveData aNewData; + + SfxStyleFamily eFamily = SfxStyleFamily::Para; + const SfxUInt16Item* pFamItem; + const SfxStringItem* pFamilyNameItem; + if ( pArgs && (pFamItem = pArgs->GetItemIfSet( SID_STYLE_FAMILY )) ) + eFamily = static_cast<SfxStyleFamily>(pFamItem->GetValue()); + else if ( pArgs && (pFamilyNameItem = pArgs->GetItemIfSet( SID_STYLE_FAMILYNAME )) ) + { + OUString sFamily = pFamilyNameItem->GetValue(); + if (sFamily == "CellStyles") + eFamily = SfxStyleFamily::Para; + else if (sFamily == "PageStyles") + eFamily = SfxStyleFamily::Page; + else if (sFamily == "GraphicStyles") + eFamily = SfxStyleFamily::Frame; + } + + OUString aStyleName; + sal_uInt16 nRetMask = 0xffff; + + switch ( nSlotId ) + { + case SID_STYLE_NEW: + { + const SfxPoolItem* pNameItem; + if (pArgs && SfxItemState::SET == pArgs->GetItemState( nSlotId, true, &pNameItem )) + aStyleName = static_cast<const SfxStringItem*>(pNameItem)->GetValue(); + + const SfxStringItem* pRefItem=nullptr; + if (pArgs && (pRefItem = pArgs->GetItemIfSet( SID_STYLE_REFERENCE ))) + { + aRefName = pRefItem->GetValue(); + } + + pStyleSheet = &(pStylePool->Make( aStyleName, eFamily, + SfxStyleSearchBits::UserDefined ) ); + + if (pStyleSheet->HasParentSupport()) + pStyleSheet->SetParent(aRefName); + } + break; + + case SID_STYLE_APPLY: + { + const SfxStringItem* pNameItem = rReq.GetArg<SfxStringItem>(SID_APPLY_STYLE); + const SfxStringItem* pFamilyItem = rReq.GetArg<SfxStringItem>(SID_STYLE_FAMILYNAME); + if ( pFamilyItem && pNameItem ) + { + try + { + css::uno::Reference< css::container::XNameAccess > xStyles; + css::uno::Reference< css::container::XNameAccess > xCont = pDocSh->GetModel()->getStyleFamilies(); + xCont->getByName(pFamilyItem->GetValue()) >>= xStyles; + css::uno::Reference< css::beans::XPropertySet > xInfo; + xStyles->getByName( pNameItem->GetValue() ) >>= xInfo; + OUString aUIName; + xInfo->getPropertyValue("DisplayName") >>= aUIName; + if ( !aUIName.isEmpty() ) + rReq.AppendItem( SfxStringItem( SID_STYLE_APPLY, aUIName ) ); + } + catch( css::uno::Exception& ) + { + } + } + [[fallthrough]]; + } + case SID_STYLE_EDIT: + case SID_STYLE_DELETE: + case SID_STYLE_HIDE: + case SID_STYLE_SHOW: + case SID_STYLE_NEW_BY_EXAMPLE: + { + const SfxPoolItem* pNameItem; + if (pArgs && SfxItemState::SET == pArgs->GetItemState( nSlotId, true, &pNameItem )) + aStyleName = static_cast<const SfxStringItem*>(pNameItem)->GetValue(); + else if ( nSlotId == SID_STYLE_NEW_BY_EXAMPLE ) + { + weld::Window* pDialogParent = rReq.GetFrameWeld(); + if (!pDialogParent) + pDialogParent = GetFrameWeld(); + SfxNewStyleDlg aDlg(pDialogParent, *pStylePool, eFamily); + if (aDlg.run() != RET_OK) + return; + aStyleName = aDlg.GetName(); + } + + pStyleSheet = pStylePool->Find( aStyleName, eFamily ); + + aOldData.InitFromStyle( pStyleSheet ); + } + break; + + case SID_STYLE_WATERCAN: + { + bool bWaterCan = pScMod->GetIsWaterCan(); + + if( !bWaterCan ) + { + const SfxPoolItem* pItem; + + if ( SfxItemState::SET == + pArgs->GetItemState( nSlotId, true, &pItem ) ) + { + const SfxStringItem* pStrItem = dynamic_cast< const SfxStringItem *>( pItem ); + if ( pStrItem ) + { + aStyleName = pStrItem->GetValue(); + pStyleSheet = pStylePool->Find( aStyleName, eFamily ); + + if ( pStyleSheet ) + { + static_cast<ScStyleSheetPool*>(pStylePool)-> + SetActualStyleSheet( pStyleSheet ); + rReq.Done(); + } + } + } + } + + if ( !bWaterCan && pStyleSheet ) + { + pScMod->SetWaterCan( true ); + SetActivePointer( PointerStyle::Fill ); + rReq.Done(); + } + else + { + pScMod->SetWaterCan( false ); + SetActivePointer( PointerStyle::Arrow ); + rReq.Done(); + } + } + break; + + default: + break; + } + + // set new style for paintbrush format mode + if ( nSlotId == SID_STYLE_APPLY && pScMod->GetIsWaterCan() && pStyleSheet ) + static_cast<ScStyleSheetPool*>(pStylePool)->SetActualStyleSheet( pStyleSheet ); + + switch ( eFamily ) + { + case SfxStyleFamily::Para: + { + switch ( nSlotId ) + { + case SID_STYLE_DELETE: + { + if ( pStyleSheet ) + { + RemoveStyleSheetInUse( pStyleSheet ); + pStylePool->Remove( pStyleSheet ); + InvalidateAttribs(); + nRetMask = sal_uInt16(true); + bAddUndo = true; + rReq.Done(); + } + else + nRetMask = sal_uInt16(false); + } + break; + + case SID_STYLE_HIDE: + case SID_STYLE_SHOW: + { + if ( pStyleSheet ) + { + pStyleSheet->SetHidden( nSlotId == SID_STYLE_HIDE ); + InvalidateAttribs(); + rReq.Done(); + } + else + nRetMask = sal_uInt16(false); + } + break; + + case SID_STYLE_APPLY: + { + if ( pStyleSheet && !pScMod->GetIsWaterCan() ) + { + // apply style sheet to document + SetStyleSheetToMarked( static_cast<SfxStyleSheet*>(pStyleSheet) ); + InvalidateAttribs(); + rReq.Done(); + } + } + break; + + case SID_STYLE_NEW_BY_EXAMPLE: + case SID_STYLE_UPDATE_BY_EXAMPLE: + { + // create/replace style sheet by attributes + // at cursor position: + + const ScPatternAttr* pAttrItem = nullptr; + + // The query if marked, was always wrong here, + // so now no more, and just from the cursor. + // If attributes are to be removed from the selection, still need to be + // cautious not to adopt items from templates + // (GetSelectionPattern also collects items from originals) (# 44748 #) + SCCOL nCol = GetViewData().GetCurX(); + SCROW nRow = GetViewData().GetCurY(); + pAttrItem = rDoc.GetPattern( nCol, nRow, nCurTab ); + + SfxItemSet aAttrSet = pAttrItem->GetItemSet(); + aAttrSet.ClearItem( ATTR_MERGE ); + aAttrSet.ClearItem( ATTR_MERGE_FLAG ); + + // Do not adopt conditional formatting and validity, + // because they can not be edited in the template + aAttrSet.ClearItem( ATTR_VALIDDATA ); + aAttrSet.ClearItem( ATTR_CONDITIONAL ); + + if ( SID_STYLE_NEW_BY_EXAMPLE == nSlotId ) + { + if ( bUndo ) + { + OUString aUndo = ScResId( STR_UNDO_EDITCELLSTYLE ); + pDocSh->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, GetViewShellId() ); + bListAction = true; + } + + bool bConvertBack = false; + SfxStyleSheet* pSheetInUse = const_cast<SfxStyleSheet*>(GetStyleSheetFromMarked()); + + // when a new style is present and is used in the selection, + // then the parent can not be adopted: + if ( pStyleSheet && pSheetInUse && pStyleSheet == pSheetInUse ) + pSheetInUse = nullptr; + + // if already present, first remove ... + if ( pStyleSheet ) + { + // style pointer to names before erase, + // otherwise cells will get invalid pointer + //!!! As it happens, a method that does it for a particular style + rDoc.StylesToNames(); + bConvertBack = true; + pStylePool->Remove(pStyleSheet); + } + + // ...and create new + pStyleSheet = &pStylePool->Make( aStyleName, eFamily, + SfxStyleSearchBits::UserDefined ); + + // when a style is present, then this will become + // the parent of the new style: + if ( pSheetInUse && pStyleSheet->HasParentSupport() ) + pStyleSheet->SetParent( pSheetInUse->GetName() ); + + if ( bConvertBack ) + // Name to style pointer + rDoc.UpdStlShtPtrsFrmNms(); + else + rDoc.GetPool()->CellStyleCreated( aStyleName, rDoc ); + + // Adopt attribute and use style + pStyleSheet->GetItemSet().Put( aAttrSet ); + UpdateStyleSheetInUse( pStyleSheet ); + + // call SetStyleSheetToMarked after adding the ScUndoModifyStyle + // (pStyleSheet pointer is used!) + bStyleToMarked = true; + } + else // ( nSlotId == SID_STYLE_UPDATE_BY_EXAMPLE ) + { + pStyleSheet = const_cast<SfxStyleSheet*>(GetStyleSheetFromMarked()); + + if ( pStyleSheet ) + { + aOldData.InitFromStyle( pStyleSheet ); + + if ( bUndo ) + { + OUString aUndo = ScResId( STR_UNDO_EDITCELLSTYLE ); + pDocSh->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, GetViewShellId() ); + bListAction = true; + } + + pStyleSheet->GetItemSet().Put( aAttrSet ); + UpdateStyleSheetInUse( pStyleSheet ); + + // call SetStyleSheetToMarked after adding the ScUndoModifyStyle + // (pStyleSheet pointer is used!) + bStyleToMarked = true; + } + } + + aNewData.InitFromStyle( pStyleSheet ); + bAddUndo = true; + rReq.Done(); + } + break; + + default: + break; + } + } // case SfxStyleFamily::Para: + break; + + case SfxStyleFamily::Page: + { + switch ( nSlotId ) + { + case SID_STYLE_DELETE: + { + nRetMask = sal_uInt16( nullptr != pStyleSheet ); + if ( pStyleSheet ) + { + if ( rDoc.RemovePageStyleInUse( pStyleSheet->GetName() ) ) + { + ScPrintFunc( pDocSh, GetPrinter(true), nCurTab ).UpdatePages(); + rBindings.Invalidate( SID_STATUS_PAGESTYLE ); + rBindings.Invalidate( FID_RESET_PRINTZOOM ); + } + pStylePool->Remove( pStyleSheet ); + rBindings.Invalidate( SID_STYLE_FAMILY4 ); + pDocSh->SetDocumentModified(); + bAddUndo = true; + rReq.Done(); + } + } + break; + + case SID_STYLE_HIDE: + case SID_STYLE_SHOW: + { + nRetMask = sal_uInt16( nullptr != pStyleSheet ); + if ( pStyleSheet ) + { + pStyleSheet->SetHidden( nSlotId == SID_STYLE_HIDE ); + rBindings.Invalidate( SID_STYLE_FAMILY4 ); + pDocSh->SetDocumentModified(); + rReq.Done(); + } + } + break; + + case SID_STYLE_APPLY: + { + nRetMask = sal_uInt16( nullptr != pStyleSheet ); + if ( pStyleSheet && !pScMod->GetIsWaterCan() ) + { + std::unique_ptr<ScUndoApplyPageStyle> pUndoAction; + SCTAB nTabCount = rDoc.GetTableCount(); + for (const auto& rTab : rMark) + { + if (rTab >= nTabCount) + break; + OUString aOldName = rDoc.GetPageStyle( rTab ); + if ( aOldName != aStyleName ) + { + rDoc.SetPageStyle( rTab, aStyleName ); + ScPrintFunc( pDocSh, GetPrinter(true), rTab ).UpdatePages(); + if( !pUndoAction ) + pUndoAction.reset(new ScUndoApplyPageStyle( pDocSh, aStyleName )); + pUndoAction->AddSheetAction( rTab, aOldName ); + } + } + if( pUndoAction ) + { + pDocSh->GetUndoManager()->AddUndoAction( std::move(pUndoAction) ); + pDocSh->SetDocumentModified(); + rBindings.Invalidate( SID_STYLE_FAMILY4 ); + rBindings.Invalidate( SID_STATUS_PAGESTYLE ); + rBindings.Invalidate( FID_RESET_PRINTZOOM ); + } + rReq.Done(); + } + } + break; + + case SID_STYLE_NEW_BY_EXAMPLE: + { + const OUString& rStrCurStyle = rDoc.GetPageStyle( nCurTab ); + + if ( rStrCurStyle != aStyleName ) + { + SfxStyleSheetBase* pCurStyle = pStylePool->Find( rStrCurStyle, eFamily ); + SfxItemSet aAttrSet = pCurStyle->GetItemSet(); + SCTAB nInTab; + bool bUsed = rDoc.IsPageStyleInUse( aStyleName, &nInTab ); + + // if already present, first remove... + if ( pStyleSheet ) + pStylePool->Remove( pStyleSheet ); + + // ...and create new + pStyleSheet = &pStylePool->Make( aStyleName, eFamily, + SfxStyleSearchBits::UserDefined ); + + // Adopt attribute + pStyleSheet->GetItemSet().Put( aAttrSet ); + pDocSh->SetDocumentModified(); + + // If being used -> Update + if ( bUsed ) + ScPrintFunc( pDocSh, GetPrinter(true), nInTab ).UpdatePages(); + + aNewData.InitFromStyle( pStyleSheet ); + bAddUndo = true; + rReq.Done(); + nRetMask = sal_uInt16(true); + } + } + break; + + default: + break; + } // switch ( nSlotId ) + } // case SfxStyleFamily::Page: + break; + + case SfxStyleFamily::Frame: + { + switch ( nSlotId ) + { + case SID_STYLE_DELETE: + { + if ( pStyleSheet ) + { + pStylePool->Remove( pStyleSheet ); + InvalidateAttribs(); + pDocSh->SetDocumentModified(); + nRetMask = sal_uInt16(true); + bAddUndo = true; + rReq.Done(); + } + else + nRetMask = sal_uInt16(false); + } + break; + + case SID_STYLE_HIDE: + case SID_STYLE_SHOW: + { + if ( pStyleSheet ) + { + pStyleSheet->SetHidden( nSlotId == SID_STYLE_HIDE ); + InvalidateAttribs(); + rReq.Done(); + } + else + nRetMask = sal_uInt16(false); + } + break; + + case SID_STYLE_APPLY: + { + if ( pStyleSheet && !pScMod->GetIsWaterCan() ) + { + GetScDrawView()->ScEndTextEdit(); + GetScDrawView()->SetStyleSheet(static_cast<SfxStyleSheet*>(pStyleSheet), false); + + GetScDrawView()->InvalidateAttribs(); + InvalidateAttribs(); + rReq.Done(); + } + } + break; + + case SID_STYLE_NEW_BY_EXAMPLE: + case SID_STYLE_UPDATE_BY_EXAMPLE: + { + if (nSlotId == SID_STYLE_NEW_BY_EXAMPLE) + { + pStyleSheet = &pStylePool->Make( aStyleName, eFamily, SfxStyleSearchBits::UserDefined ); + + // when a style is present, then this will become + // the parent of the new style: + if (SfxStyleSheet* pOldStyle = GetDrawView()->GetStyleSheet()) + pStyleSheet->SetParent(pOldStyle->GetName()); + } + else + { + pStyleSheet = GetDrawView()->GetStyleSheet(); + aOldData.InitFromStyle( pStyleSheet ); + } + + if ( bUndo ) + { + OUString aUndo = ScResId( STR_UNDO_EDITGRAPHICSTYLE ); + pDocSh->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, GetViewShellId() ); + bListAction = true; + } + + SfxItemSet aCoreSet(GetDrawView()->GetModel().GetItemPool()); + GetDrawView()->GetAttributes(aCoreSet, true); + + SfxItemSet* pStyleSet = &pStyleSheet->GetItemSet(); + pStyleSet->Put(aCoreSet); + static_cast<SfxStyleSheet*>(pStyleSheet)->Broadcast(SfxHint(SfxHintId::DataChanged)); + + aNewData.InitFromStyle( pStyleSheet ); + bAddUndo = true; + + // call SetStyleSheet after adding the ScUndoModifyStyle + // (pStyleSheet pointer is used!) + bStyleToMarked = true; + rReq.Done(); + } + break; + default: + break; + } + } + break; + default: + break; + } // switch ( eFamily ) + + // create new or process through Dialog: + if ( nSlotId == SID_STYLE_NEW || nSlotId == SID_STYLE_EDIT ) + { + if ( pStyleSheet ) + { + SfxStyleFamily eFam = pStyleSheet->GetFamily(); + ScopedVclPtr<SfxAbstractTabDialog> pDlg; + bool bPage = false; + + // Store old Items from the style + SfxItemSet aOldSet = pStyleSheet->GetItemSet(); + OUString aOldName = pStyleSheet->GetName(); + + switch ( eFam ) + { + case SfxStyleFamily::Page: + bPage = true; + break; + + case SfxStyleFamily::Para: + { + SfxItemSet& rSet = pStyleSheet->GetItemSet(); + + if ( const SfxUInt32Item* pItem = rSet.GetItemIfSet( ATTR_VALUE_FORMAT, + false ) ) + { + // Produce and format NumberFormat Value from Value and Language + sal_uLong nFormat = pItem->GetValue(); + LanguageType eLang = + rSet.Get(ATTR_LANGUAGE_FORMAT ).GetLanguage(); + sal_uLong nLangFormat = rDoc.GetFormatTable()-> + GetFormatForLanguageIfBuiltIn( nFormat, eLang ); + if ( nLangFormat != nFormat ) + { + SfxUInt32Item aNewItem( ATTR_VALUE_FORMAT, nLangFormat ); + rSet.Put( aNewItem ); + aOldSet.Put( aNewItem ); + // Also in aOldSet for comparison after the dialog, + // Otherwise might miss a language change + } + } + + std::unique_ptr<SvxNumberInfoItem> pNumberInfoItem( + ScTabViewShell::MakeNumberInfoItem(rDoc, GetViewData())); + + pDocSh->PutItem( *pNumberInfoItem ); + bPage = false; + + // Definitely a SvxBoxInfoItem with Table = sal_False in set: + // (If there is no item, the dialogue will also delete the + // BORDER_OUTER SvxBoxItem from the Template Set) + if ( rSet.GetItemState( ATTR_BORDER_INNER, false ) != SfxItemState::SET ) + { + SvxBoxInfoItem aBoxInfoItem( ATTR_BORDER_INNER ); + aBoxInfoItem.SetTable(false); // no inner lines + aBoxInfoItem.SetDist(true); + aBoxInfoItem.SetMinDist(false); + rSet.Put( aBoxInfoItem ); + } + } + break; + + case SfxStyleFamily::Frame: + default: + break; + } + + SetInFormatDialog(true); + + SfxItemSet& rStyleSet = pStyleSheet->GetItemSet(); + rStyleSet.MergeRange( XATTR_FILL_FIRST, XATTR_FILL_LAST ); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + weld::Window* pDialogParent = rReq.GetFrameWeld(); + if (!pDialogParent) + pDialogParent = GetFrameWeld(); + + if (eFam == SfxStyleFamily::Frame) + pDlg.disposeAndReset(pFact->CreateScDrawStyleDlg(pDialogParent, *pStyleSheet, GetDrawView())); + else + pDlg.disposeAndReset(pFact->CreateScStyleDlg(pDialogParent, *pStyleSheet, bPage)); + + short nResult = pDlg->Execute(); + SetInFormatDialog(false); + + if ( nResult == RET_OK ) + { + const SfxItemSet* pOutSet = pDlg->GetOutputItemSet(); + + if ( pOutSet ) + { + nRetMask = sal_uInt16(pStyleSheet->GetMask()); + + // Attribute comparisons (earlier in ModifyStyleSheet) now here + // with the old values (the style is already changed) + if ( SfxStyleFamily::Para == eFam ) + { + SfxItemSet& rNewSet = pStyleSheet->GetItemSet(); + bool bNumFormatChanged; + if ( ScGlobal::CheckWidthInvalidate( + bNumFormatChanged, rNewSet, aOldSet ) ) + rDoc.InvalidateTextWidth( nullptr, nullptr, bNumFormatChanged ); + + SCTAB nTabCount = rDoc.GetTableCount(); + for (SCTAB nTab=0; nTab<nTabCount; nTab++) + rDoc.SetStreamValid(nTab, false); + + sal_uLong nOldFormat = aOldSet.Get( ATTR_VALUE_FORMAT ).GetValue(); + sal_uLong nNewFormat = rNewSet.Get( ATTR_VALUE_FORMAT ).GetValue(); + if ( nNewFormat != nOldFormat ) + { + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + const SvNumberformat* pOld = pFormatter->GetEntry( nOldFormat ); + const SvNumberformat* pNew = pFormatter->GetEntry( nNewFormat ); + if ( pOld && pNew && pOld->GetLanguage() != pNew->GetLanguage() ) + rNewSet.Put( SvxLanguageItem( + pNew->GetLanguage(), ATTR_LANGUAGE_FORMAT ) ); + } + + rDoc.GetPool()->CellStyleCreated( pStyleSheet->GetName(), rDoc ); + } + else if ( SfxStyleFamily::Page == eFam ) + { + //! Here also queries for Page Styles + + OUString aNewName = pStyleSheet->GetName(); + if ( aNewName != aOldName && + rDoc.RenamePageStyleInUse( aOldName, aNewName ) ) + { + rBindings.Invalidate( SID_STATUS_PAGESTYLE ); + rBindings.Invalidate( FID_RESET_PRINTZOOM ); + } + + rDoc.ModifyStyleSheet( *pStyleSheet, *pOutSet ); + rBindings.Invalidate( FID_RESET_PRINTZOOM ); + } + else + { + SfxItemSet& rAttr = pStyleSheet->GetItemSet(); + sdr::properties::CleanupFillProperties(rAttr); + + // check for unique names of named items for xml + auto checkForUniqueItem = [&] (auto nWhichId) + { + if (auto pOldItem = rAttr.GetItemIfSet(nWhichId, false)) + { + if (auto pNewItem = pOldItem->checkForUniqueItem(&GetDrawView()->GetModel())) + rAttr.Put(std::move(pNewItem)); + } + }; + + checkForUniqueItem(XATTR_FILLBITMAP); + checkForUniqueItem(XATTR_LINEDASH); + checkForUniqueItem(XATTR_LINESTART); + checkForUniqueItem(XATTR_LINEEND); + checkForUniqueItem(XATTR_FILLGRADIENT); + checkForUniqueItem(XATTR_FILLFLOATTRANSPARENCE); + checkForUniqueItem(XATTR_FILLHATCH); + + static_cast<SfxStyleSheet*>(pStyleSheet)->Broadcast(SfxHint(SfxHintId::DataChanged)); + GetScDrawView()->InvalidateAttribs(); + } + + pDocSh->SetDocumentModified(); + + if ( SfxStyleFamily::Para == eFam ) + { + ScTabViewShell::UpdateNumberFormatter( + *( pDocSh->GetItem(SID_ATTR_NUMBERFORMAT_INFO) )); + + UpdateStyleSheetInUse( pStyleSheet ); + InvalidateAttribs(); + } + + aNewData.InitFromStyle( pStyleSheet ); + bAddUndo = true; + } + } + else + { + if ( nSlotId == SID_STYLE_NEW ) + pStylePool->Remove( pStyleSheet ); + else + { + // If in the meantime something was painted with the + // temporary changed item set + pDocSh->PostPaintGridAll(); + } + } + } + } + + rReq.SetReturnValue( SfxUInt16Item( nSlotId, nRetMask ) ); + + if ( bAddUndo && bUndo) + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoModifyStyle>( pDocSh, eFamily, aOldData, aNewData ) ); + + if ( bStyleToMarked ) + { + // call SetStyleSheetToMarked after adding the ScUndoModifyStyle, + // so redo will find the modified style + if (eFamily == SfxStyleFamily::Para) + { + SetStyleSheetToMarked( static_cast<SfxStyleSheet*>(pStyleSheet) ); + } + else if (eFamily == SfxStyleFamily::Frame) + { + GetScDrawView()->ScEndTextEdit(); + GetScDrawView()->SetStyleSheet( static_cast<SfxStyleSheet*>(pStyleSheet), false ); + } + InvalidateAttribs(); + } + + if ( bListAction ) + pDocSh->GetUndoManager()->LeaveListAction(); + + // The above call to ScEndTextEdit left us in an inconsistent state: + // Text editing isn't active, but the text edit shell still is. And we + // couldn't just deactivate it fully, because in case of editing a + // comment, that will make the comment disappear. So let's try to + // reactivate text editing instead: + auto pFuText = dynamic_cast<FuText*>(GetDrawFuncPtr()); + if (pFuText && pEditObject != GetDrawView()->GetTextEditObject()) + { + pFuText->SetInEditMode(pEditObject); + if (GetDrawView()->GetTextEditOutlinerView()) + GetDrawView()->GetTextEditOutlinerView()->SetSelection(aSelection); + } +} + +void ScTabViewShell::GetStyleState( SfxItemSet& rSet ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + SfxStyleSheetBasePool* pStylePool = rDoc.GetStyleSheetPool(); + + bool bProtected = false; + SCTAB nTabCount = rDoc.GetTableCount(); + for (SCTAB i=0; i<nTabCount && !bProtected; i++) + if (rDoc.IsTabProtected(i)) // look after protected table + bProtected = true; + + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + sal_uInt16 nSlotId = 0; + + while ( nWhich ) + { + nSlotId = SfxItemPool::IsWhich( nWhich ) + ? GetPool().GetSlotId( nWhich ) + : nWhich; + + switch ( nSlotId ) + { + case SID_STYLE_APPLY: + if ( !pStylePool ) + rSet.DisableItem( nSlotId ); + break; + + case SID_STYLE_FAMILY2: // cell style sheets + { + SfxStyleSheet* pStyleSheet = const_cast<SfxStyleSheet*>(GetStyleSheetFromMarked()); + + if ( pStyleSheet ) + rSet.Put( SfxTemplateItem( nSlotId, pStyleSheet->GetName() ) ); + else + rSet.Put( SfxTemplateItem( nSlotId, OUString() ) ); + } + break; + + case SID_STYLE_FAMILY3: // drawing style sheets + { + SfxStyleSheet* pStyleSheet = GetDrawView()->GetStyleSheet(); + + if ( pStyleSheet ) + rSet.Put( SfxTemplateItem( nSlotId, pStyleSheet->GetName() ) ); + else + rSet.Put( SfxTemplateItem( nSlotId, OUString() ) ); + } + break; + + case SID_STYLE_FAMILY4: // page style sheets + { + SCTAB nCurTab = GetViewData().GetTabNo(); + OUString aPageStyle = rDoc.GetPageStyle( nCurTab ); + SfxStyleSheet* pStyleSheet = pStylePool ? static_cast<SfxStyleSheet*>(pStylePool-> + Find( aPageStyle, SfxStyleFamily::Page )) : nullptr; + + if ( pStyleSheet ) + rSet.Put( SfxTemplateItem( nSlotId, aPageStyle ) ); + else + rSet.Put( SfxTemplateItem( nSlotId, OUString() ) ); + } + break; + + case SID_STYLE_WATERCAN: + { + rSet.Put( SfxBoolItem( nSlotId, SC_MOD()->GetIsWaterCan() ) ); + } + break; + + case SID_STYLE_UPDATE_BY_EXAMPLE: + { + std::unique_ptr<SfxUInt16Item> pFamilyItem; + GetViewFrame().GetBindings().QueryState(SID_STYLE_FAMILY, pFamilyItem); + + bool bPage = pFamilyItem && SfxStyleFamily::Page == static_cast<SfxStyleFamily>(pFamilyItem->GetValue()); + + if ( bProtected || bPage ) + rSet.DisableItem( nSlotId ); + } + break; + + case SID_STYLE_EDIT: + case SID_STYLE_DELETE: + case SID_STYLE_HIDE: + case SID_STYLE_SHOW: + { + std::unique_ptr<SfxUInt16Item> pFamilyItem; + GetViewFrame().GetBindings().QueryState(SID_STYLE_FAMILY, pFamilyItem); + bool bPage = pFamilyItem && SfxStyleFamily::Page == static_cast<SfxStyleFamily>(pFamilyItem->GetValue()); + + if ( bProtected && !bPage ) + rSet.DisableItem( nSlotId ); + } + break; + + default: + break; + } + + nWhich = aIter.NextWhich(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabvwshb.cxx b/sc/source/ui/view/tabvwshb.cxx new file mode 100644 index 0000000000..ad0e757ce0 --- /dev/null +++ b/sc/source/ui/view/tabvwshb.cxx @@ -0,0 +1,919 @@ +/* -*- 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 <com/sun/star/chart2/data/XDataReceiver.hpp> +#include <com/sun/star/awt/XRequestCallback.hpp> +#include <com/sun/star/awt/Rectangle.hpp> + +#include <com/sun/star/embed/EmbedMisc.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> +#include <vcl/errinf.hxx> +#include <sfx2/app.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <svx/svxdlg.hxx> +#include <svx/dataaccessdescriptor.hxx> +#include <svx/svditer.hxx> +#include <svx/svdmark.hxx> +#include <svx/svdograf.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svdouno.hxx> +#include <svx/svdview.hxx> +#include <sfx2/linkmgr.hxx> +#include <svx/fontworkbar.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/viewfrm.hxx> +#include <svtools/soerr.hxx> +#include <svl/rectitem.hxx> +#include <svl/stritem.hxx> +#include <svl/slstitm.hxx> +#include <svl/whiter.hxx> +#include <svtools/strings.hrc> +#include <unotools/moduleoptions.hxx> +#include <sot/exchange.hxx> +#include <comphelper/diagnose_ex.hxx> + +#include <tabvwsh.hxx> +#include <scmod.hxx> +#include <document.hxx> +#include <sc.hrc> +#include <client.hxx> +#include <fuinsert.hxx> +#include <docsh.hxx> +#include <drawview.hxx> +#include <ChartRangeSelectionListener.hxx> +#include <gridwin.hxx> +#include <undomanager.hxx> +#include <com/sun/star/ui/dialogs/ExecutableDialogResults.hpp> +#include <svx/svdpagv.hxx> +#include <o3tl/temporary.hxx> +#include <officecfg/Office/Common.hxx> + +#include <comphelper/lok.hxx> + +using namespace com::sun::star; + +void ScTabViewShell::ConnectObject( const SdrOle2Obj* pObj ) +{ + // is called from paint + + uno::Reference < embed::XEmbeddedObject > xObj = pObj->GetObjRef(); + vcl::Window* pWin = GetActiveWin(); + + // when already connected do not execute SetObjArea/SetSizeScale again + + SfxInPlaceClient* pClient = FindIPClient( xObj, pWin ); + if ( pClient ) + return; + + pClient = new ScClient( this, pWin, &GetScDrawView()->GetModel(), pObj ); + ScViewData& rViewData = GetViewData(); + ScDocShell* pDocSh = rViewData.GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + bool bNegativeX = comphelper::LibreOfficeKit::isActive() && rDoc.IsNegativePage(rViewData.GetTabNo()); + if (bNegativeX) + pClient->SetNegativeX(true); + + tools::Rectangle aRect = pObj->GetLogicRect(); + Size aDrawSize = aRect.GetSize(); + + Size aOleSize = pObj->GetOrigObjSize(); + + Fraction aScaleWidth (aDrawSize.Width(), aOleSize.Width() ); + Fraction aScaleHeight(aDrawSize.Height(), aOleSize.Height() ); + aScaleWidth.ReduceInaccurate(10); // compatible with SdrOle2Obj + aScaleHeight.ReduceInaccurate(10); + pClient->SetSizeScale(aScaleWidth,aScaleHeight); + + // visible section is only changed inplace! + // the object area must be set after the scaling since it triggers the resizing + aRect.SetSize( aOleSize ); + pClient->SetObjArea( aRect ); +} + +namespace { + +class PopupCallback : public cppu::WeakImplHelper<css::awt::XCallback> +{ + ScTabViewShell* m_pViewShell; + SdrOle2Obj* m_pObject; + +public: + explicit PopupCallback(ScTabViewShell* pViewShell, SdrOle2Obj* pObject) + : m_pViewShell(pViewShell) + , m_pObject(pObject) + {} + + // XCallback + virtual void SAL_CALL notify(const css::uno::Any& aData) override + { + uno::Sequence<beans::PropertyValue> aProperties; + if (!(aData >>= aProperties)) + return; + + awt::Rectangle xRectangle; + sal_Int32 dimensionIndex = 0; + OUString sPivotTableName("DataPilot1"); + + for (beans::PropertyValue const & rProperty : std::as_const(aProperties)) + { + if (rProperty.Name == "Rectangle") + rProperty.Value >>= xRectangle; + if (rProperty.Name == "DimensionIndex") + rProperty.Value >>= dimensionIndex; + if (rProperty.Name == "PivotTableName") + rProperty.Value >>= sPivotTableName; + } + + tools::Rectangle aChartRect = m_pObject->GetLogicRect(); + + Point aPoint(xRectangle.X + aChartRect.Left(), xRectangle.Y + aChartRect.Top()); + Size aSize(xRectangle.Width, xRectangle.Height); + + m_pViewShell->DoDPFieldPopup(sPivotTableName, dimensionIndex, aPoint, aSize); + } +}; + +} + +void ScTabViewShell::ActivateObject(SdrOle2Obj* pObj, sal_Int32 nVerb) +{ + // Do not leave the hint message box on top of the object + RemoveHintWindow(); + + uno::Reference < embed::XEmbeddedObject > xObj = pObj->GetObjRef(); + vcl::Window* pWin = GetActiveWin(); + ErrCodeMsg nErr = ERRCODE_NONE; + bool bErrorShown = false; + + { + ScViewData& rViewData = GetViewData(); + ScDocShell* pDocSh = rViewData.GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + bool bNegativeX = comphelper::LibreOfficeKit::isActive() && rDoc.IsNegativePage(rViewData.GetTabNo()); + SfxInPlaceClient* pClient = FindIPClient( xObj, pWin ); + if ( !pClient ) + pClient = new ScClient( this, pWin, &GetScDrawView()->GetModel(), pObj ); + + if (bNegativeX) + pClient->SetNegativeX(true); + + if ( (sal_uInt32(nErr.GetCode()) & ERRCODE_ERROR_MASK) == 0 && xObj.is() ) + { + tools::Rectangle aRect = pObj->GetLogicRect(); + + { + // #i118485# center on BoundRect for activation, + // OLE may be sheared/rotated now + const tools::Rectangle& rBoundRect = pObj->GetCurrentBoundRect(); + const Point aDelta(rBoundRect.Center() - aRect.Center()); + aRect.Move(aDelta.X(), aDelta.Y()); + } + + Size aDrawSize = aRect.GetSize(); + + MapMode aMapMode( MapUnit::Map100thMM ); + Size aOleSize = pObj->GetOrigObjSize( &aMapMode ); + + if ( pClient->GetAspect() != embed::Aspects::MSOLE_ICON + && ( xObj->getStatus( pClient->GetAspect() ) & embed::EmbedMisc::MS_EMBED_RECOMPOSEONRESIZE ) ) + { + // scale must always be 1 - change VisArea if different from client size + + if ( aDrawSize != aOleSize ) + { + MapUnit aUnit = VCLUnoHelper::UnoEmbed2VCLMapUnit( xObj->getMapUnit( pClient->GetAspect() ) ); + aOleSize = OutputDevice::LogicToLogic( aDrawSize, + MapMode(MapUnit::Map100thMM), MapMode(aUnit)); + awt::Size aSz( aOleSize.Width(), aOleSize.Height() ); + xObj->setVisualAreaSize( pClient->GetAspect(), aSz ); + } + Fraction aOne( 1, 1 ); + pClient->SetSizeScale( aOne, aOne ); + } + else + { + // calculate scale from client and VisArea size + + Fraction aScaleWidth (aDrawSize.Width(), aOleSize.Width() ); + Fraction aScaleHeight(aDrawSize.Height(), aOleSize.Height() ); + aScaleWidth.ReduceInaccurate(10); // compatible with SdrOle2Obj + aScaleHeight.ReduceInaccurate(10); + pClient->SetSizeScale(aScaleWidth,aScaleHeight); + } + + // visible section is only changed inplace! + // the object area must be set after the scaling since it triggers the resizing + aRect.SetSize( aOleSize ); + pClient->SetObjArea( aRect ); + + nErr = pClient->DoVerb( nVerb ); + bErrorShown = true; + // SfxViewShell::DoVerb shows its error messages + + // attach listener to selection changes in chart that affect cell + // ranges, so those can be highlighted + // note: do that after DoVerb, so that the chart controller exists + if ( SvtModuleOptions().IsChart() ) + { + SvGlobalName aObjClsId ( xObj->getClassID() ); + if (SotExchange::IsChart( aObjClsId )) + { + try + { + uno::Reference < embed::XComponentSupplier > xSup( xObj, uno::UNO_QUERY_THROW ); + uno::Reference< chart2::data::XDataReceiver > xDataReceiver( + xSup->getComponent(), uno::UNO_QUERY_THROW ); + uno::Reference< chart2::data::XRangeHighlighter > xRangeHighlighter( + xDataReceiver->getRangeHighlighter()); + if (xRangeHighlighter.is()) + { + uno::Reference< view::XSelectionChangeListener > xListener( + new ScChartRangeSelectionListener( this )); + xRangeHighlighter->addSelectionChangeListener( xListener ); + } + uno::Reference<awt::XRequestCallback> xPopupRequest(xDataReceiver->getPopupRequest()); + if (xPopupRequest.is()) + { + uno::Reference<awt::XCallback> xCallback(new PopupCallback(this, pObj)); + uno::Any aAny; + xPopupRequest->addCallback(xCallback, aAny); + } + } + catch( const uno::Exception & ) + { + TOOLS_WARN_EXCEPTION( "sc", "Exception caught while querying chart" ); + } + } + } + } + } + if (nErr != ERRCODE_NONE && !bErrorShown) + ErrorHandler::HandleError(nErr); + + // #i118524# refresh handles to suppress for activated OLE + if(GetScDrawView()) + { + GetScDrawView()->AdjustMarkHdl(); + } + //! SetDocumentName should already happen in Sfx ??? + //TODO/LATER: how "SetDocumentName"? + //xIPObj->SetDocumentName( GetViewData().GetDocShell()->GetTitle() ); +} + +ErrCode ScTabViewShell::DoVerb(sal_Int32 nVerb) +{ + SdrView* pView = GetScDrawView(); + if (!pView) + return ERRCODE_SO_NOTIMPL; // should not be + + SdrOle2Obj* pOle2Obj = nullptr; + + const SdrMarkList& rMarkList = pView->GetMarkedObjectList(); + if (rMarkList.GetMarkCount() == 1) + { + SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj(); + if (pObj->GetObjIdentifier() == SdrObjKind::OLE2) + pOle2Obj = static_cast<SdrOle2Obj*>(pObj); + } + + if (pOle2Obj) + { + ActivateObject( pOle2Obj, nVerb ); + } + else + { + OSL_FAIL("no object for Verb found"); + } + + return ERRCODE_NONE; +} + +void ScTabViewShell::DeactivateOle() +{ + // deactivate inplace editing if currently active + + ScModule* pScMod = SC_MOD(); + bool bUnoRefDialog = pScMod->IsRefDialogOpen() && pScMod->GetCurRefDlgId() == WID_SIMPLE_REF; + + ScClient* pClient = static_cast<ScClient*>(GetIPClient()); + if ( pClient && pClient->IsObjectInPlaceActive() && !bUnoRefDialog ) + pClient->DeactivateObject(); +} + +IMPL_LINK( ScTabViewShell, DialogClosedHdl, css::ui::dialogs::DialogClosedEvent*, pEvent, void ) +{ + if( pEvent->DialogResult == ui::dialogs::ExecutableDialogResults::CANCEL ) + { + ScTabView* pTabView = GetViewData().GetView(); + ScDrawView* pView = pTabView->GetScDrawView(); + ScViewData& rData = GetViewData(); + ScDocShell* pScDocSh = rData.GetDocShell(); + ScDocument& rScDoc = pScDocSh->GetDocument(); + // leave OLE inplace mode and unmark + OSL_ASSERT( pView ); + DeactivateOle(); + pView->UnMarkAll(); + + rScDoc.GetUndoManager()->Undo(); + rScDoc.GetUndoManager()->ClearRedo(); + + // leave the draw shell + SetDrawShell( false ); + + // reset marked cell area + ScMarkData aMark = GetViewData().GetMarkData(); + GetViewData().GetViewShell()->SetMarkData(aMark); + } + else + { + OSL_ASSERT( pEvent->DialogResult == ui::dialogs::ExecutableDialogResults::OK ); + //@todo maybe move chart to different table + } +} + +void ScTabViewShell::ExecDrawIns(SfxRequest& rReq) +{ + sal_uInt16 nSlot = rReq.GetSlot(); + if (nSlot != SID_OBJECTRESIZE ) + { + SC_MOD()->InputEnterHandler(); + UpdateInputHandler(); + } + + // insertion of border for Chart is cancelled: + FuPoor* pPoor = GetDrawFuncPtr(); + if ( pPoor && pPoor->GetSlotID() == SID_DRAW_CHART ) + GetViewData().GetDispatcher().Execute(SID_DRAW_CHART, SfxCallMode::SLOT | SfxCallMode::RECORD); + + MakeDrawLayer(); + + SfxBindings& rBindings = GetViewFrame().GetBindings(); + ScTabView* pTabView = GetViewData().GetView(); + vcl::Window* pWin = pTabView->GetActiveWin(); + ScDrawView* pView = pTabView->GetScDrawView(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + SdrModel& rModel = pView->GetModel(); + + switch ( nSlot ) + { + case SID_INSERT_GRAPHIC: + FuInsertGraphic(*this, pWin, pView, &rModel, rReq); + // shell is set in MarkListHasChanged + break; + + case SID_INSERT_AVMEDIA: + FuInsertMedia(*this, pWin, pView, &rModel, rReq); + // shell is set in MarkListHasChanged + break; + + case SID_INSERT_DIAGRAM: + FuInsertChart(*this, pWin, pView, &rModel, rReq, LINK( this, ScTabViewShell, DialogClosedHdl )); + if (comphelper::LibreOfficeKit::isActive()) + pDocSh->SetModified(); + break; + + case SID_INSERT_OBJECT: + case SID_INSERT_SMATH: + case SID_INSERT_FLOATINGFRAME: + FuInsertOLE(*this, pWin, pView, &rModel, rReq); + break; + + case SID_INSERT_SIGNATURELINE: + case SID_EDIT_SIGNATURELINE: + { + const uno::Reference<frame::XModel> xModel( GetViewData().GetDocShell()->GetBaseModel() ); + + VclAbstractDialogFactory* pFact = VclAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractSignatureLineDialog> pDialog(pFact->CreateSignatureLineDialog( + pWin->GetFrameWeld(), xModel, rReq.GetSlot() == SID_EDIT_SIGNATURELINE)); + pDialog->Execute(); + break; + } + + case SID_SIGN_SIGNATURELINE: + { + const uno::Reference<frame::XModel> xModel( + GetViewData().GetDocShell()->GetBaseModel()); + + VclAbstractDialogFactory* pFact = VclAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractSignSignatureLineDialog> pDialog( + pFact->CreateSignSignatureLineDialog(GetFrameWeld(), xModel)); + pDialog->Execute(); + break; + } + + case SID_INSERT_QRCODE: + case SID_EDIT_QRCODE: + { + const uno::Reference<frame::XModel> xModel( GetViewData().GetDocShell()->GetBaseModel() ); + + VclAbstractDialogFactory* pFact = VclAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractQrCodeGenDialog> pDialog(pFact->CreateQrCodeGenDialog( + pWin->GetFrameWeld(), xModel, rReq.GetSlot() == SID_EDIT_QRCODE)); + pDialog->Execute(); + break; + } + + case SID_ADDITIONS_DIALOG: + { + OUString sAdditionsTag = ""; + + const SfxStringItem* pStringArg = rReq.GetArg<SfxStringItem>(FN_PARAM_ADDITIONS_TAG); + if (pStringArg) + sAdditionsTag = pStringArg->GetValue(); + + VclAbstractDialogFactory* pFact = VclAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractAdditionsDialog> pDialog( + pFact->CreateAdditionsDialog(pWin->GetFrameWeld(), sAdditionsTag)); + pDialog->Execute(); + break; + } + + case SID_OBJECTRESIZE: + { + // the server would like to change the client size + + SfxInPlaceClient* pClient = GetIPClient(); + + if ( pClient && pClient->IsObjectInPlaceActive() ) + { + const SfxRectangleItem& rRect = rReq.GetArgs()->Get(SID_OBJECTRESIZE); + tools::Rectangle aRect( pWin->PixelToLogic( rRect.GetValue() ) ); + + if ( pView->AreObjectsMarked() ) + { + const SdrMarkList& rMarkList = pView->GetMarkedObjectList(); + + if (rMarkList.GetMarkCount() == 1) + { + SdrMark* pMark = rMarkList.GetMark(0); + SdrObject* pObj = pMark->GetMarkedSdrObj(); + + SdrObjKind nSdrObjKind = pObj->GetObjIdentifier(); + + if (nSdrObjKind == SdrObjKind::OLE2) + { + if ( static_cast<SdrOle2Obj*>(pObj)->GetObjRef().is() ) + { + pObj->SetLogicRect(aRect); + } + } + } + } + } + } + break; + + case SID_LINKS: + { + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + if (officecfg::Office::Common::Security::Scripting::DisableActiveContent::get()) + { + std::unique_ptr<weld::MessageDialog> xError(Application::CreateMessageDialog( + nullptr, VclMessageType::Warning, VclButtonsType::Ok, + SvtResId(STR_WARNING_EXTERNAL_LINK_EDIT_DISABLED))); + xError->run(); + break; + } + + ScopedVclPtr<SfxAbstractLinksDialog> pDlg(pFact->CreateLinksDialog(pWin->GetFrameWeld(), rDoc.GetLinkManager())); + pDlg->Execute(); + rBindings.Invalidate( nSlot ); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) ); // Navigator + rReq.Done(); + } + break; + + case SID_FM_CREATE_FIELDCONTROL: + { + const SfxUnoAnyItem* pDescriptorItem = rReq.GetArg<SfxUnoAnyItem>(SID_FM_DATACCESS_DESCRIPTOR); + OSL_ENSURE( pDescriptorItem, "SID_FM_CREATE_FIELDCONTROL: invalid request args!" ); + + if(pDescriptorItem) + { + //! merge with ScViewFunc::PasteDataFormat (SotClipboardFormatId::SBA_FIELDDATAEXCHANGE)? + + ScDrawView* pDrView = GetScDrawView(); + SdrPageView* pPageView = pDrView ? pDrView->GetSdrPageView() : nullptr; + if(pPageView) + { + svx::ODataAccessDescriptor aDescriptor(pDescriptorItem->GetValue()); + rtl::Reference<SdrObject> pNewDBField = pDrView->CreateFieldControl(aDescriptor); + + if(pNewDBField) + { + tools::Rectangle aVisArea = pWin->PixelToLogic(tools::Rectangle(Point(0,0), pWin->GetOutputSizePixel())); + Point aObjPos(aVisArea.Center()); + Size aObjSize(pNewDBField->GetLogicRect().GetSize()); + aObjPos.AdjustX( -(aObjSize.Width() / 2) ); + aObjPos.AdjustY( -(aObjSize.Height() / 2) ); + tools::Rectangle aNewObjectRectangle(aObjPos, aObjSize); + + pNewDBField->SetLogicRect(aNewObjectRectangle); + + // controls must be on control layer, groups on front layer + if ( dynamic_cast<const SdrUnoObj*>( pNewDBField.get() ) != nullptr ) + pNewDBField->NbcSetLayer(SC_LAYER_CONTROLS); + else + pNewDBField->NbcSetLayer(SC_LAYER_FRONT); + if (dynamic_cast<const SdrObjGroup*>( pNewDBField.get() ) != nullptr) + { + SdrObjListIter aIter( *pNewDBField, SdrIterMode::DeepWithGroups ); + SdrObject* pSubObj = aIter.Next(); + while (pSubObj) + { + if ( dynamic_cast<const SdrUnoObj*>( pSubObj) != nullptr ) + pSubObj->NbcSetLayer(SC_LAYER_CONTROLS); + else + pSubObj->NbcSetLayer(SC_LAYER_FRONT); + pSubObj = aIter.Next(); + } + } + + pView->InsertObjectAtView(pNewDBField.get(), *pPageView); + } + } + } + rReq.Done(); + } + break; + + case SID_FONTWORK_GALLERY_FLOATER: + svx::FontworkBar::execute(*pView, rReq, GetViewFrame().GetBindings()); + rReq.Ignore(); + break; + } +} + +void ScTabViewShell::GetDrawInsState(SfxItemSet &rSet) +{ + bool bOle = GetViewFrame().GetFrame().IsInPlace(); + bool bTabProt = GetViewData().GetDocument().IsTabProtected(GetViewData().GetTabNo()); + ScDocShell* pDocShell = GetViewData().GetDocShell(); + bool bShared = pDocShell && pDocShell->IsDocShared(); + SdrView* pSdrView = GetScDrawView(); + + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + while ( nWhich ) + { + switch ( nWhich ) + { + case SID_INSERT_DIAGRAM: + if ( bOle || bTabProt || !SvtModuleOptions().IsChart() || bShared ) + rSet.DisableItem( nWhich ); + break; + + case SID_INSERT_SMATH: + if ( bOle || bTabProt || !SvtModuleOptions().IsMath() || bShared ) + rSet.DisableItem( nWhich ); + break; + + case SID_INSERT_OBJECT: + case SID_INSERT_FLOATINGFRAME: + if ( bOle || bTabProt || bShared ) + rSet.DisableItem( nWhich ); + break; + + case SID_INSERT_AVMEDIA: + case SID_FONTWORK_GALLERY_FLOATER: + if ( bTabProt || bShared ) + rSet.DisableItem( nWhich ); + break; + + case SID_INSERT_SIGNATURELINE: + if ( bTabProt || bShared || (pSdrView && pSdrView->GetMarkedObjectCount() != 0)) + rSet.DisableItem( nWhich ); + break; + case SID_EDIT_SIGNATURELINE: + case SID_SIGN_SIGNATURELINE: + if (!IsSignatureLineSelected() || IsSignatureLineSigned()) + rSet.DisableItem(nWhich); + break; + + case SID_INSERT_QRCODE: + if ( bTabProt || bShared || (pSdrView && pSdrView->GetMarkedObjectCount() != 0)) + rSet.DisableItem( nWhich ); + break; + case SID_EDIT_QRCODE: + if (!IsQRCodeSelected()) + rSet.DisableItem(nWhich); + break; + + case SID_INSERT_GRAPHIC: + if (bTabProt || bShared) + { + // do not disable 'insert graphic' item if the currently marked area is editable (not protected) + // if there is no marked area, check the current cell + bool bDisableInsertImage = true; + ScMarkData& rMark = GetViewData().GetMarkData(); + if (!rMark.GetMarkedRanges().empty() && GetViewData().GetDocument().IsSelectionEditable(rMark)) + bDisableInsertImage = false; + else + { + if (GetViewData().GetDocument().IsBlockEditable + (GetViewData().GetTabNo(), GetViewData().GetCurX(), GetViewData().GetCurY(), GetViewData().GetCurX(), GetViewData().GetCurY())) + { + bDisableInsertImage = false; + } + } + + if (bDisableInsertImage) + rSet.DisableItem(nWhich); + } + break; + + case SID_LINKS: + { + if (GetViewData().GetDocument().GetLinkManager()->GetLinks().empty()) + rSet.DisableItem( SID_LINKS ); + } + break; + } + nWhich = aIter.NextWhich(); + } +} + +bool ScTabViewShell::IsSignatureLineSelected() +{ + SdrView* pSdrView = GetScDrawView(); + if (!pSdrView) + return false; + + if (pSdrView->GetMarkedObjectCount() != 1) + return false; + + SdrObject* pPickObj = pSdrView->GetMarkedObjectByIndex(0); + if (!pPickObj) + return false; + + SdrGrafObj* pGraphic = dynamic_cast<SdrGrafObj*>(pPickObj); + if (!pGraphic) + return false; + + return pGraphic->isSignatureLine(); +} + +bool ScTabViewShell::IsQRCodeSelected() +{ + SdrView* pSdrView = GetScDrawView(); + if (!pSdrView) + return false; + + if (pSdrView->GetMarkedObjectCount() != 1) + return false; + + SdrObject* pPickObj = pSdrView->GetMarkedObjectByIndex(0); + if (!pPickObj) + return false; + + SdrGrafObj* pGraphic = dynamic_cast<SdrGrafObj*>(pPickObj); + if (!pGraphic) + return false; + + if(pGraphic->getQrCode()) + { + return true; + } + else{ + return false; + } +} + +bool ScTabViewShell::IsSignatureLineSigned() +{ + SdrView* pSdrView = GetScDrawView(); + if (!pSdrView) + return false; + + if (pSdrView->GetMarkedObjectCount() != 1) + return false; + + SdrObject* pPickObj = pSdrView->GetMarkedObjectByIndex(0); + if (!pPickObj) + return false; + + SdrGrafObj* pGraphic = dynamic_cast<SdrGrafObj*>(pPickObj); + if (!pGraphic) + return false; + + return pGraphic->isSignatureLineSigned(); +} + +void ScTabViewShell::ExecuteUndo(SfxRequest& rReq) +{ + SfxShell* pSh = GetViewData().GetDispatcher().GetShell(0); + if (!pSh) + return; + + ScUndoManager* pUndoManager = static_cast<ScUndoManager*>(pSh->GetUndoManager()); + + const SfxItemSet* pReqArgs = rReq.GetArgs(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + + sal_uInt16 nSlot = rReq.GetSlot(); + switch ( nSlot ) + { + case SID_UNDO: + case SID_REDO: + if ( pUndoManager ) + { + bool bIsUndo = ( nSlot == SID_UNDO ); + + sal_uInt16 nCount = 1; + const SfxPoolItem* pItem; + if ( pReqArgs && pReqArgs->GetItemState( nSlot, true, &pItem ) == SfxItemState::SET ) + nCount = static_cast<const SfxUInt16Item*>(pItem)->GetValue(); + + // Repair mode: allow undo/redo of all undo actions, even if access would + // be limited based on the view shell ID. + bool bRepair = false; + if (pReqArgs && pReqArgs->GetItemState(SID_REPAIRPACKAGE, false, &pItem) == SfxItemState::SET) + bRepair = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + + sal_uInt16 nUndoOffset = 0; + if (comphelper::LibreOfficeKit::isActive() && !bRepair) + { + SfxUndoAction* pAction = nullptr; + if (bIsUndo) + { + if (pUndoManager->GetUndoActionCount() != 0) + pAction = pUndoManager->GetUndoAction(); + } + else + { + if (pUndoManager->GetRedoActionCount() != 0) + pAction = pUndoManager->GetRedoAction(); + } + if (pAction) + { + // If another view created the undo action, prevent undoing it from this view. + // Unless we know that the other view's undo action is independent from us. + ViewShellId nViewShellId = GetViewShellId(); + if (pAction->GetViewShellId() != nViewShellId) + { + sal_uInt16 nOffset = 0; + if (pUndoManager->IsViewUndoActionIndependent(this, nOffset)) + { + // Execute the undo with an offset: don't undo the top action, but an + // earlier one, since it's independent and that belongs to our view. + nUndoOffset += nOffset; + } + else + { + rReq.SetReturnValue(SfxUInt32Item(SID_UNDO, static_cast<sal_uInt32>(SID_REPAIRPACKAGE))); + return; + } + } + } + } + + // lock paint for more than one cell undo action (not for editing within a cell) + bool bLockPaint = ( nCount > 1 && pUndoManager == GetUndoManager() ); + if ( bLockPaint ) + pDocSh->LockPaint(); + + try + { + ScUndoRedoContext aUndoRedoContext; + aUndoRedoContext.SetUndoOffset(nUndoOffset); + + for (sal_uInt16 i=0; i<nCount; i++) + { + if ( bIsUndo ) + pUndoManager->UndoWithContext(aUndoRedoContext); + else + pUndoManager->RedoWithContext(aUndoRedoContext); + } + } + catch ( const uno::Exception& ) + { + // no need to handle. By definition, the UndoManager handled this by clearing the + // Undo/Redo stacks + } + + if ( bLockPaint ) + pDocSh->UnlockPaint(); + + GetViewFrame().GetBindings().InvalidateAll(false); + } + break; +// default: +// GetViewFrame().ExecuteSlot( rReq ); + } +} + +void ScTabViewShell::GetUndoState(SfxItemSet &rSet) +{ + SfxShell* pSh = GetViewData().GetDispatcher().GetShell(0); + if (!pSh) + return; + + SfxUndoManager* pUndoManager = pSh->GetUndoManager(); + ScUndoManager* pScUndoManager = dynamic_cast<ScUndoManager*>(pUndoManager); + + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + while ( nWhich ) + { + switch (nWhich) + { + case SID_GETUNDOSTRINGS: + case SID_GETREDOSTRINGS: + { + SfxStringListItem aStrLst( nWhich ); + if ( pUndoManager ) + { + std::vector<OUString> &aList = aStrLst.GetList(); + bool bIsUndo = ( nWhich == SID_GETUNDOSTRINGS ); + size_t nCount = bIsUndo ? pUndoManager->GetUndoActionCount() : pUndoManager->GetRedoActionCount(); + for (size_t i=0; i<nCount; ++i) + { + aList.push_back( bIsUndo ? pUndoManager->GetUndoActionComment(i) : + pUndoManager->GetRedoActionComment(i) ); + } + } + rSet.Put( aStrLst ); + } + break; + + case SID_UNDO: + { + if (pScUndoManager) + { + if (pScUndoManager->GetUndoActionCount()) + { + const SfxUndoAction* pAction = pScUndoManager->GetUndoAction(); + SfxViewShell *pViewSh = GetViewShell(); + if (pViewSh && pAction->GetViewShellId() != pViewSh->GetViewShellId() + && !pScUndoManager->IsViewUndoActionIndependent(this, o3tl::temporary(sal_uInt16()))) + { + rSet.Put(SfxUInt32Item(SID_UNDO, static_cast<sal_uInt32>(SID_REPAIRPACKAGE))); + } + else + { + rSet.Put( SfxStringItem( SID_UNDO, SvtResId(STR_UNDO)+pScUndoManager->GetUndoActionComment() ) ); + } + } + else + rSet.DisableItem( SID_UNDO ); + } + else + // get state from sfx view frame + GetViewFrame().GetSlotState( nWhich, nullptr, &rSet ); + break; + } + case SID_REDO: + { + if (pScUndoManager) + { + if (pScUndoManager->GetRedoActionCount()) + { + const SfxUndoAction* pAction = pScUndoManager->GetRedoAction(); + SfxViewShell *pViewSh = GetViewShell(); + if (pViewSh && pAction->GetViewShellId() != pViewSh->GetViewShellId()) + { + rSet.Put(SfxUInt32Item(SID_REDO, static_cast<sal_uInt32>(SID_REPAIRPACKAGE))); + } + else + { + rSet.Put(SfxStringItem(SID_REDO, SvtResId(STR_REDO) + pScUndoManager->GetRedoActionComment())); + } + } + else + rSet.DisableItem( SID_REDO ); + } + else + // get state from sfx view frame + GetViewFrame().GetSlotState( nWhich, nullptr, &rSet ); + break; + } + default: + // get state from sfx view frame + GetViewFrame().GetSlotState( nWhich, nullptr, &rSet ); + } + + nWhich = aIter.NextWhich(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabvwshc.cxx b/sc/source/ui/view/tabvwshc.cxx new file mode 100644 index 0000000000..8e2e74e8e4 --- /dev/null +++ b/sc/source/ui/view/tabvwshc.cxx @@ -0,0 +1,775 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <scitems.hxx> +#include <sfx2/childwin.hxx> +#include <sfx2/dispatch.hxx> +#include <editeng/editview.hxx> +#include <inputhdl.hxx> + +#include <tabvwsh.hxx> +#include <sc.hrc> +#include <scres.hrc> +#include <global.hxx> +#include <scmod.hxx> +#include <document.hxx> +#include <uiitems.hxx> +#include <namedlg.hxx> +#include <namedefdlg.hxx> +#include <solvrdlg.hxx> +#include <optsolver.hxx> +#include <tabopdlg.hxx> +#include <consdlg.hxx> +#include <filtdlg.hxx> +#include <dbnamdlg.hxx> +#include <areasdlg.hxx> +#include <crnrdlg.hxx> +#include <formula.hxx> +#include <highred.hxx> +#include <simpref.hxx> +#include <funcdesc.hxx> +#include <dpobject.hxx> +#include <markdata.hxx> +#include <reffact.hxx> +#include <condformatdlg.hxx> +#include <condformateasydlg.hxx> +#include <xmlsourcedlg.hxx> +#include <condformatdlgitem.hxx> +#include <formdata.hxx> +#include <inputwin.hxx> + +#include <RandomNumberGeneratorDialog.hxx> +#include <SamplingDialog.hxx> +#include <DescriptiveStatisticsDialog.hxx> +#include <AnalysisOfVarianceDialog.hxx> +#include <CorrelationDialog.hxx> +#include <CovarianceDialog.hxx> +#include <ExponentialSmoothingDialog.hxx> +#include <MovingAverageDialog.hxx> +#include <RegressionDialog.hxx> +#include <TTestDialog.hxx> +#include <FTestDialog.hxx> +#include <ZTestDialog.hxx> +#include <ChiSquareTestDialog.hxx> +#include <FourierAnalysisDialog.hxx> + +#include <PivotLayoutDialog.hxx> +#include <SparklineDialog.hxx> +#include <SparklineDataRangeDialog.hxx> + +#include <svtools/colorcfg.hxx> +#include <comphelper/lok.hxx> +#include <o3tl/unreachable.hxx> +#include <o3tl/make_shared.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> + +void ScTabViewShell::SetCurRefDlgId( sal_uInt16 nNew ) +{ + // CurRefDlgId is stored in ScModule to find if a ref dialog is open, + // and in the view to identify the view that has opened the dialog + nCurRefDlgId = nNew; +} + +//ugly hack to call Define Name from Manage Names +void ScTabViewShell::SwitchBetweenRefDialogs(SfxModelessDialogController* pDialog) +{ + sal_uInt16 nSlotId = SC_MOD()->GetCurRefDlgId(); + if( nSlotId == FID_ADD_NAME ) + { + static_cast<ScNameDefDlg*>(pDialog)->GetNewData(maName, maScope); + static_cast<ScNameDefDlg*>(pDialog)->Close(); + sal_uInt16 nId = ScNameDlgWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + SC_MOD()->SetRefDialog( nId, pWnd == nullptr ); + } + else if (nSlotId == FID_DEFINE_NAME) + { + mbInSwitch = true; + static_cast<ScNameDlg*>(pDialog)->GetRangeNames(m_RangeMap); + static_cast<ScNameDlg*>(pDialog)->Close(); + sal_uInt16 nId = ScNameDefDlgWrapper::GetChildWindowId(); + SfxViewFrame& rViewFrm = GetViewFrame(); + SfxChildWindow* pWnd = rViewFrm.GetChildWindow( nId ); + + SC_MOD()->SetRefDialog( nId, pWnd == nullptr ); + } +} + +std::shared_ptr<SfxModelessDialogController> ScTabViewShell::CreateRefDialogController( + SfxBindings* pB, SfxChildWindow* pCW, + const SfxChildWinInfo* pInfo, + weld::Window* pParent, sal_uInt16 nSlotId) +{ + // only open dialog when called through ScModule::SetRefDialog, + // so that it does not re appear for instance after a crash (#42341#). + + if ( SC_MOD()->GetCurRefDlgId() != nSlotId ) + return nullptr; + + if ( nCurRefDlgId != nSlotId ) + { + if (!(comphelper::LibreOfficeKit::isActive() && nSlotId == SID_OPENDLG_FUNCTION)) + { + // the dialog has been opened in a different view + // -> lock the dispatcher for this view (modal mode) + + GetViewData().GetDispatcher().Lock( true ); // lock is reset when closing dialog + } + return nullptr; + } + + std::shared_ptr<SfxModelessDialogController> xResult; + + if(pCW) + pCW->SetHideNotDelete(true); + + ScDocument& rDoc = GetViewData().GetDocument(); + + switch( nSlotId ) + { + case SID_CORRELATION_DIALOG: + xResult = std::make_shared<ScCorrelationDialog>(pB, pCW, pParent, GetViewData()); + break; + case SID_SAMPLING_DIALOG: + xResult = std::make_shared<ScSamplingDialog>(pB, pCW, pParent, GetViewData()); + break; + case SID_DESCRIPTIVE_STATISTICS_DIALOG: + xResult = std::make_shared<ScDescriptiveStatisticsDialog>(pB, pCW, pParent, GetViewData()); + break; + case SID_ANALYSIS_OF_VARIANCE_DIALOG: + xResult = std::make_shared<ScAnalysisOfVarianceDialog>(pB, pCW, pParent, GetViewData()); + break; + case SID_COVARIANCE_DIALOG: + xResult = std::make_shared<ScCovarianceDialog>(pB, pCW, pParent, GetViewData()); + break; + case SID_EXPONENTIAL_SMOOTHING_DIALOG: + xResult = std::make_shared<ScExponentialSmoothingDialog>(pB, pCW, pParent, GetViewData()); + break; + case SID_MOVING_AVERAGE_DIALOG: + xResult = std::make_shared<ScMovingAverageDialog>(pB, pCW, pParent, GetViewData()); + break; + case SID_REGRESSION_DIALOG: + xResult = std::make_shared<ScRegressionDialog>(pB, pCW, pParent, GetViewData()); + break; + case SID_FTEST_DIALOG: + xResult = std::make_shared<ScFTestDialog>(pB, pCW, pParent, GetViewData()); + break; + case SID_TTEST_DIALOG: + xResult = std::make_shared<ScTTestDialog>(pB, pCW, pParent, GetViewData()); + break; + case SID_ZTEST_DIALOG: + xResult = std::make_shared<ScZTestDialog>(pB, pCW, pParent, GetViewData()); + break; + case SID_CHI_SQUARE_TEST_DIALOG: + xResult = std::make_shared<ScChiSquareTestDialog>(pB, pCW, pParent, GetViewData()); + break; + case SID_FOURIER_ANALYSIS_DIALOG: + xResult = std::make_shared<ScFourierAnalysisDialog>(pB, pCW, pParent, GetViewData()); + break; + case WID_SIMPLE_REF: + { + // dialog checks, what is in the cell + + ScViewData& rViewData = GetViewData(); + rViewData.SetRefTabNo( rViewData.GetTabNo() ); + xResult = std::make_shared<ScSimpleRefDlg>(pB, pCW, pParent); + break; + } + case FID_DEFINE_NAME: + { + if (!mbInSwitch) + { + xResult = std::make_shared<ScNameDlg>(pB, pCW, pParent, GetViewData(), + ScAddress( GetViewData().GetCurX(), + GetViewData().GetCurY(), + GetViewData().GetTabNo() ) ); + } + else + { + xResult = std::make_shared<ScNameDlg>( pB, pCW, pParent, GetViewData(), + ScAddress( GetViewData().GetCurX(), + GetViewData().GetCurY(), + GetViewData().GetTabNo() ), &m_RangeMap); + static_cast<ScNameDlg*>(xResult.get())->SetEntry(maName, maScope); + mbInSwitch = false; + } + break; + } + case FID_ADD_NAME: + { + if (!mbInSwitch) + { + std::map<OUString, ScRangeName*> aRangeMap; + rDoc.GetRangeNameMap(aRangeMap); + xResult = std::make_shared<ScNameDefDlg>(pB, pCW, pParent, GetViewData(), std::move(aRangeMap), + ScAddress(GetViewData().GetCurX(), + GetViewData().GetCurY(), + GetViewData().GetTabNo()), true); + } + else + { + std::map<OUString, ScRangeName*> aRangeMap; + for (auto& itr : m_RangeMap) + { + aRangeMap.insert(std::pair<OUString, ScRangeName*>(itr.first, &itr.second)); + } + xResult = std::make_shared<ScNameDefDlg>(pB, pCW, pParent, GetViewData(), std::move(aRangeMap), + ScAddress(GetViewData().GetCurX(), + GetViewData().GetCurY(), + GetViewData().GetTabNo()), false); + } + break; + } + case SID_RANDOM_NUMBER_GENERATOR_DIALOG: + xResult = std::make_shared<ScRandomNumberGeneratorDialog>(pB, pCW, pParent, GetViewData()); + break; + case SID_SPARKLINE_DIALOG: + { + xResult = std::make_shared<sc::SparklineDialog>(pB, pCW, pParent, GetViewData()); + break; + } + case SID_SPARKLINE_DATA_RANGE_DIALOG: + { + xResult = std::make_shared<sc::SparklineDataRangeDialog>(pB, pCW, pParent, GetViewData()); + break; + } + case SID_DEFINE_DBNAME: + { + // when called for an existing range, then mark + GetDBData( true, SC_DB_OLD ); + const ScMarkData& rMark = GetViewData().GetMarkData(); + if ( !rMark.IsMarked() && !rMark.IsMultiMarked() ) + MarkDataArea( false ); + + xResult = std::make_shared<ScDbNameDlg>(pB, pCW, pParent, GetViewData()); + break; + } + case SID_OPENDLG_EDIT_PRINTAREA: + xResult = std::make_shared<ScPrintAreasDlg>(pB, pCW, pParent); + break; + case SID_DEFINE_COLROWNAMERANGES: + xResult = std::make_shared<ScColRowNameRangesDlg>(pB, pCW, pParent, GetViewData()); + break; + case SID_OPENDLG_SOLVE: + { + ScViewData& rViewData = GetViewData(); + ScAddress aCurPos( rViewData.GetCurX(), + rViewData.GetCurY(), + rViewData.GetTabNo()); + xResult = std::make_shared<ScSolverDlg>(pB, pCW, pParent, &rViewData.GetDocument(), aCurPos); + break; + } + case SID_OPENDLG_TABOP: + { + ScViewData& rViewData = GetViewData(); + ScRefAddress aCurPos ( rViewData.GetCurX(), + rViewData.GetCurY(), + rViewData.GetTabNo()); + + xResult = std::make_shared<ScTabOpDlg>(pB, pCW, pParent, &rViewData.GetDocument(), aCurPos); + break; + } + case SID_OPENDLG_CONSOLIDATE: + { + SfxItemSetFixed<SCITEM_CONSOLIDATEDATA, + SCITEM_CONSOLIDATEDATA> aArgSet( GetPool() ); + + const ScConsolidateParam* pDlgData = + rDoc.GetConsolidateDlgData(); + + if ( !pDlgData ) + { + ScConsolidateParam aConsParam; + SCCOL nStartCol, nEndCol; + SCROW nStartRow, nEndRow; + SCTAB nStartTab, nEndTab; + + GetViewData().GetSimpleArea( nStartCol, nStartRow, nStartTab, + nEndCol, nEndRow, nEndTab ); + + PutInOrder( nStartCol, nEndCol ); + PutInOrder( nStartRow, nEndRow ); + PutInOrder( nStartTab, nEndTab ); + + aConsParam.nCol = nStartCol; + aConsParam.nRow = nStartRow; + aConsParam.nTab = nStartTab; + + aArgSet.Put( ScConsolidateItem( SCITEM_CONSOLIDATEDATA, + &aConsParam ) ); + } + else + { + aArgSet.Put( ScConsolidateItem( SCITEM_CONSOLIDATEDATA, pDlgData ) ); + } + xResult = std::make_shared<ScConsolidateDlg>(pB, pCW, pParent, aArgSet); + break; + } + case SID_EASY_CONDITIONAL_FORMAT_DIALOG: + { + xResult = std::make_shared<sc::ConditionalFormatEasyDialog>(pB, pCW, pParent, &GetViewData()); + break; + } + case SID_FILTER: + { + + ScQueryParam aQueryParam; + SfxItemSetFixed<SCITEM_QUERYDATA, SCITEM_QUERYDATA> aArgSet( GetPool() ); + + ScDBData* pDBData = GetDBData(false, SC_DB_MAKE, ScGetDBSelection::RowDown); + pDBData->ExtendDataArea(rDoc); + pDBData->ExtendBackColorArea(rDoc); + pDBData->GetQueryParam( aQueryParam ); + + ScRange aArea; + pDBData->GetArea(aArea); + MarkRange(aArea, false); + + aArgSet.Put( ScQueryItem( SCITEM_QUERYDATA, + &GetViewData(), + &aQueryParam ) ); + + // mark current sheet (due to RefInput in dialog) + GetViewData().SetRefTabNo( GetViewData().GetTabNo() ); + + xResult = std::make_shared<ScFilterDlg>(pB, pCW, pParent, aArgSet); + break; + } + case SID_SPECIAL_FILTER: + { + ScQueryParam aQueryParam; + SfxItemSetFixed<SCITEM_QUERYDATA, + SCITEM_QUERYDATA> aArgSet( GetPool() ); + + ScDBData* pDBData = GetDBData(false, SC_DB_MAKE, ScGetDBSelection::RowDown); + pDBData->ExtendDataArea(rDoc); + pDBData->GetQueryParam( aQueryParam ); + + ScRange aArea; + pDBData->GetArea(aArea); + MarkRange(aArea, false); + + ScQueryItem aItem( SCITEM_QUERYDATA, &GetViewData(), &aQueryParam ); + ScRange aAdvSource; + if (pDBData->GetAdvancedQuerySource(aAdvSource)) + aItem.SetAdvancedQuerySource( &aAdvSource ); + + aArgSet.Put( aItem ); + + // mark current sheet (due to RefInput in dialog) + GetViewData().SetRefTabNo( GetViewData().GetTabNo() ); + + xResult = std::make_shared<ScSpecialFilterDlg>(pB, pCW, pParent, aArgSet); + break; + } + case SID_OPENDLG_OPTSOLVER: + { + ScViewData& rViewData = GetViewData(); + ScAddress aCurPos( rViewData.GetCurX(), rViewData.GetCurY(), rViewData.GetTabNo()); + xResult = std::make_shared<ScOptSolverDlg>(pB, pCW, pParent, rViewData.GetDocShell(), aCurPos); + break; + } + case FID_CHG_SHOW: + { + // dialog checks, what is in the cell + xResult = std::make_shared<ScHighlightChgDlg>(pB, pCW, pParent, GetViewData()); + break; + } + case SID_MANAGE_XML_SOURCE: + { + xResult = std::make_shared<ScXMLSourceDlg>(pB, pCW, pParent, &rDoc); + break; + } + case SID_OPENDLG_PIVOTTABLE: + { + // all settings must be in pDialogDPObject + + if( pDialogDPObject ) + { + // Check for an existing datapilot output. + ScViewData& rViewData = GetViewData(); + rViewData.SetRefTabNo( rViewData.GetTabNo() ); + ScDPObject* pObj = rDoc.GetDPAtCursor(rViewData.GetCurX(), rViewData.GetCurY(), rViewData.GetTabNo()); + xResult = std::make_shared<ScPivotLayoutDialog>(pB, pCW, pParent, &rViewData, pDialogDPObject.get(), pObj == nullptr); + } + + break; + } + case SID_OPENDLG_FUNCTION: + { + if (!isLOKMobilePhone()) + { + // dialog checks, what is in the cell + xResult = o3tl::make_shared<ScFormulaDlg>(pB, pCW, pParent, GetViewData(), ScGlobal::GetStarCalcFunctionMgr()); + } + break; + } + case WID_CONDFRMT_REF: + { + const ScCondFormatDlgItem* pDlgItem = nullptr; + // Get the pool item stored by Conditional Format Manager Dialog. + auto itemsRange = GetPool().GetItemSurrogates(SCITEM_CONDFORMATDLGDATA); + if (itemsRange.begin() != itemsRange.end()) + { + const SfxPoolItem* pItem = *itemsRange.begin(); + pDlgItem = static_cast<const ScCondFormatDlgItem*>(pItem); + } + + if (pDlgItem) + { + ScViewData& rViewData = GetViewData(); + rViewData.SetRefTabNo( rViewData.GetTabNo() ); + + xResult = std::make_shared<ScCondFormatDlg>(pB, pCW, pParent, &rViewData, pDlgItem); + + // Remove the pool item stored by Conditional Format Manager Dialog. + GetPool().DirectRemoveItemFromPool(*pDlgItem); + } + + break; + } + } + + if (xResult) + xResult->Initialize( pInfo ); + return xResult; +} + +int ScTabViewShell::getPart() const +{ + return GetViewData().GetTabNo(); +} + +void ScTabViewShell::afterCallbackRegistered() +{ + // common tasks + SfxViewShell::afterCallbackRegistered(); + + UpdateInputHandler(true, false); + + ScInputHandler* pHdl = mpInputHandler ? mpInputHandler.get() : SC_MOD()->GetInputHdl(); + if (pHdl) + { + ScInputWindow* pInputWindow = pHdl->GetInputWindow(); + if (pInputWindow) + { + pInputWindow->NotifyLOKClient(); + } + } +} + +void ScTabViewShell::NotifyCursor(SfxViewShell* pOtherShell) const +{ + ScDrawView* pDrView = const_cast<ScTabViewShell*>(this)->GetScDrawView(); + if (pDrView) + { + if (pDrView->GetTextEditObject()) + { + // Blinking cursor. + EditView& rEditView = pDrView->GetTextEditOutlinerView()->GetEditView(); + rEditView.RegisterOtherShell(pOtherShell); + rEditView.ShowCursor(); + rEditView.RegisterOtherShell(nullptr); + // Text selection, if any. + rEditView.DrawSelectionXOR(pOtherShell); + } + else + { + // Graphic selection. + pDrView->AdjustMarkHdl(pOtherShell); + } + } + + const ScGridWindow* pWin = GetViewData().GetActiveWin(); + if (pWin) + pWin->updateKitCellCursor(pOtherShell); +} + +::Color ScTabViewShell::GetColorConfigColor(svtools::ColorConfigEntry nColorType) const +{ + const ScViewOptions& rViewOptions = GetViewData().GetOptions(); + + switch (nColorType) + { + case svtools::ColorConfigEntry::DOCCOLOR: + { + return rViewOptions.GetDocColor(); + } + // Should never be called for an unimplemented color type + default: + { + O3TL_UNREACHABLE; + } + } +} + +css::uno::Reference<css::datatransfer::XTransferable2> ScTabViewShell::GetClipData(vcl::Window* pWin) +{ + SfxViewFrame* pViewFrame = nullptr; + css::uno::Reference<css::datatransfer::XTransferable2> xTransferable; + css::uno::Reference<css::datatransfer::clipboard::XClipboard> xClipboard; + + if (pWin) + xClipboard = pWin->GetClipboard(); + else if ((pViewFrame = SfxViewFrame::GetFirst(nullptr, false))) + xClipboard = pViewFrame->GetWindow().GetClipboard(); + + xTransferable.set(xClipboard.is() ? xClipboard->getContents() : nullptr, css::uno::UNO_QUERY); + + return xTransferable; +} + +void ScTabViewShell::notifyAllViewsHeaderInvalidation(const SfxViewShell* pForViewShell, HeaderType eHeaderType, SCTAB nCurrentTabIndex) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + OString aPayload; + switch (eHeaderType) + { + case COLUMN_HEADER: + aPayload = "column"_ostr; + break; + case ROW_HEADER: + aPayload = "row"_ostr; + break; + case BOTH_HEADERS: + default: + aPayload = "all"_ostr; + break; + } + + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(pViewShell); + if (pTabViewShell && pViewShell->GetDocId() == pForViewShell->GetDocId() + && (nCurrentTabIndex == -1 || pTabViewShell->getPart() == nCurrentTabIndex)) + { + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_INVALIDATE_HEADER, aPayload); + } + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +bool ScTabViewShell::isAnyEditViewInRange(const SfxViewShell* pForViewShell, bool bColumns, SCCOLROW nStart, SCCOLROW nEnd) +{ + if (comphelper::LibreOfficeKit::isActive()) + { + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(pViewShell); + if (pTabViewShell && pTabViewShell->GetDocId() == pForViewShell->GetDocId()) + { + ScInputHandler* pInputHandler = pTabViewShell->GetInputHandler(); + if (pInputHandler && pInputHandler->GetActiveView()) + { + const ScViewData& rViewData = pTabViewShell->GetViewData(); + SCCOLROW nPos = bColumns ? rViewData.GetCurX() : rViewData.GetCurY(); + if (nStart <= nPos && nPos <= nEnd) + return true; + } + } + pViewShell = SfxViewShell::GetNext(*pViewShell); + } + } + return false; +} + +void ScTabViewShell::notifyAllViewsSheetGeomInvalidation(const SfxViewShell* pForViewShell, bool bColumns, + bool bRows, bool bSizes, bool bHidden, bool bFiltered, + bool bGroups, SCTAB nCurrentTabIndex) +{ + if (!comphelper::LibreOfficeKit::isActive() || + !comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs)) + return; + + if (!bColumns && !bRows) + return; + + bool bAllTypes = bSizes && bHidden && bFiltered && bGroups; + bool bAllDims = bColumns && bRows; + OString aPayload = bAllDims ? "all" : bColumns ? "columns" : "rows"; + + if (!bAllTypes) + { + if (bSizes) + aPayload += " sizes"; + + if (bHidden) + aPayload += " hidden"; + + if (bFiltered) + aPayload += " filtered"; + + if (bGroups) + aPayload += " groups"; + } + + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(pViewShell); + if (pTabViewShell && pViewShell->GetDocId() == pForViewShell->GetDocId() && + (nCurrentTabIndex == -1 || pTabViewShell->getPart() == nCurrentTabIndex)) + { + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_INVALIDATE_SHEET_GEOMETRY, aPayload); + } + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +bool ScTabViewShell::UseSubTotal(ScRangeList* pRangeList) +{ + bool bSubTotal = false; + ScDocument& rDoc = GetViewData().GetDocument(); + size_t nRangeCount (pRangeList->size()); + size_t nRangeIndex (0); + while (!bSubTotal && nRangeIndex < nRangeCount) + { + const ScRange& rRange = (*pRangeList)[nRangeIndex]; + SCTAB nTabEnd(rRange.aEnd.Tab()); + SCTAB nTab(rRange.aStart.Tab()); + while (!bSubTotal && nTab <= nTabEnd) + { + SCROW nRowEnd(rRange.aEnd.Row()); + SCROW nRow(rRange.aStart.Row()); + while (!bSubTotal && nRow <= nRowEnd) + { + if (rDoc.RowFiltered(nRow, nTab)) + bSubTotal = true; + else + ++nRow; + } + ++nTab; + } + ++nRangeIndex; + } + + if (!bSubTotal) + { + const ScDBCollection::NamedDBs& rDBs = rDoc.GetDBCollection()->getNamedDBs(); + for (const auto& rxDB : rDBs) + { + const ScDBData& rDB = *rxDB; + if (!rDB.HasAutoFilter()) + continue; + + nRangeIndex = 0; + while (!bSubTotal && nRangeIndex < nRangeCount) + { + const ScRange & rRange = (*pRangeList)[nRangeIndex]; + ScRange aDBArea; + rDB.GetArea(aDBArea); + if (aDBArea.Intersects(rRange)) + bSubTotal = true; + ++nRangeIndex; + } + + if (bSubTotal) + break; + } + } + return bSubTotal; +} + +OUString ScTabViewShell::DoAutoSum(bool& rRangeFinder, bool& rSubTotal, const OpCode eCode) +{ + OUString aFormula; + const ScMarkData& rMark = GetViewData().GetMarkData(); + if ( rMark.IsMarked() || rMark.IsMultiMarked() ) + { + ScRangeList aMarkRangeList; + rRangeFinder = rSubTotal = false; + rMark.FillRangeListWithMarks( &aMarkRangeList, false ); + ScDocument& rDoc = GetViewData().GetDocument(); + + // check if one of the marked ranges is empty + bool bEmpty = false; + const size_t nCount = aMarkRangeList.size(); + for ( size_t i = 0; i < nCount; ++i ) + { + const ScRange & rRange( aMarkRangeList[i] ); + if ( rDoc.IsBlockEmpty( rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row(), rRange.aStart.Tab() ) ) + { + bEmpty = true; + break; + } + } + + if ( bEmpty ) + { + ScRangeList aRangeList; + const bool bDataFound = GetAutoSumArea( aRangeList ); + if ( bDataFound ) + { + ScAddress aAddr = aRangeList.back().aEnd; + aAddr.IncRow(); + const bool bSubTotal( UseSubTotal( &aRangeList ) ); + EnterAutoSum( aRangeList, bSubTotal, aAddr, eCode ); + } + } + else + { + const bool bSubTotal( UseSubTotal( &aMarkRangeList ) ); + for ( size_t i = 0; i < nCount; ++i ) + { + const ScRange & rRange = aMarkRangeList[i]; + const bool bSetCursor = ( i == nCount - 1 ); + const bool bContinue = ( i != 0 ); + if ( !AutoSum( rRange, bSubTotal, bSetCursor, bContinue, eCode ) ) + { + MarkRange( rRange, false ); + SetCursor( rRange.aEnd.Col(), rRange.aEnd.Row() ); + const ScRangeList aRangeList; + ScAddress aAddr = rRange.aEnd; + aAddr.IncRow(); + aFormula = GetAutoSumFormula( aRangeList, bSubTotal, aAddr , eCode); + break; + } + } + } + } + else // Only insert into input row + { + ScRangeList aRangeList; + rRangeFinder = GetAutoSumArea( aRangeList ); + rSubTotal = UseSubTotal( &aRangeList ); + ScAddress aAddr = GetViewData().GetCurPos(); + aFormula = GetAutoSumFormula( aRangeList, rSubTotal, aAddr , eCode); + } + return aFormula; +} + +void ScTabViewShell::InitFormEditData() +{ + mpFormEditData.reset(new ScFormEditData); +} + +void ScTabViewShell::ClearFormEditData() +{ + mpFormEditData.reset(); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabvwshd.cxx b/sc/source/ui/view/tabvwshd.cxx new file mode 100644 index 0000000000..52d97d1a25 --- /dev/null +++ b/sc/source/ui/view/tabvwshd.cxx @@ -0,0 +1,67 @@ +/* -*- 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 <sfx2/childwin.hxx> +#include <sfx2/viewfrm.hxx> + +#include <tabvwsh.hxx> +#include <scmod.hxx> +#include <docsh.hxx> +#include <gridwin.hxx> + +//! parent window for dialogs +//! Problem: OLE Server! + +weld::Window* ScTabViewShell::GetDialogParent() +{ + // if a ref-input dialog is open, use it as parent + // (necessary when a slot is executed from the dialog's OK handler) + if (nCurRefDlgId && nCurRefDlgId == SC_MOD()->GetCurRefDlgId()) + { + SfxViewFrame& rViewFrm = GetViewFrame(); + if (rViewFrm.HasChildWindow(nCurRefDlgId)) + { + SfxChildWindow* pChild = rViewFrm.GetChildWindow(nCurRefDlgId); + if (pChild) + { + auto xController = pChild->GetController(); + weld::Window* pRet = xController ? xController->getDialog() : nullptr; + if (pRet && pRet->get_visible()) + return pRet; + } + } + } + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + if (pDocSh->IsOle()) + { + // TODO/LATER: how to GetEditWindow in embedded document?! + // It should be OK to return the ViewShell Window! + vcl::Window* pWin = GetWindow(); + return pWin ? pWin->GetFrameWeld() : nullptr; + // SvInPlaceEnvironment* pEnv = pDocSh->GetIPEnv(); + // if (pEnv) + // return pEnv->GetEditWin(); + } + + vcl::Window* pWin = GetActiveWin(); // for normal views, too + return pWin ? pWin->GetFrameWeld() : nullptr; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabvwshe.cxx b/sc/source/ui/view/tabvwshe.cxx new file mode 100644 index 0000000000..0c809b31b3 --- /dev/null +++ b/sc/source/ui/view/tabvwshe.cxx @@ -0,0 +1,322 @@ +/* -*- 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 <comphelper/string.hxx> +#include <comphelper/lok.hxx> +#include <editeng/eeitem.hxx> +#include <osl/diagnose.h> + +#include <editeng/editview.hxx> +#include <editeng/flditem.hxx> +#include <svx/hlnkitem.hxx> +#include <svl/srchitem.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/request.hxx> +#include <svl/stritem.hxx> + +#include <tabvwsh.hxx> +#include <sc.hrc> +#include <scmod.hxx> +#include <impex.hxx> +#include <editsh.hxx> +#include <dociter.hxx> +#include <inputhdl.hxx> +#include <document.hxx> + +OUString ScTabViewShell::GetSelectionText( bool bWholeWord, bool bOnlyASample ) +{ + OUString aStrSelection; + + if ( pEditShell && pEditShell.get() == GetMySubShell() ) + { + aStrSelection = pEditShell->GetSelectionText( bWholeWord ); + } + else + { + ScRange aRange; + + if ( GetViewData().GetSimpleArea( aRange ) == SC_MARK_SIMPLE ) + { + ScDocument& rDoc = GetViewData().GetDocument(); + if ( (bOnlyASample || bInFormatDialog) && aRange.aStart.Row() != aRange.aEnd.Row() ) + { + // limit range to one data row + // (only when the call comes from a format dialog) + ScHorizontalCellIterator aIter( rDoc, aRange.aStart.Tab(), + aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row() ); + SCCOL nCol; + SCROW nRow; + if ( aIter.GetNext( nCol, nRow ) ) + { + aRange.aStart.SetCol( nCol ); + aRange.aStart.SetRow( nRow ); + aRange.aEnd.SetRow( nRow ); + } + else + aRange.aEnd = aRange.aStart; + } + else + { + // #i111531# with 1M rows it was necessary to limit the range + // to the actually used data area. + SCCOL nCol1, nCol2; + SCROW nRow1, nRow2; + SCTAB nTab1, nTab2; + aRange.GetVars( nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + bool bShrunk; + rDoc.ShrinkToUsedDataArea( bShrunk, nTab1, nCol1, nRow1, nCol2, nRow2, false); + if (bShrunk) + { + aRange.aStart.SetCol( nCol1 ); + aRange.aStart.SetRow( nRow1 ); + aRange.aEnd.SetCol( nCol2 ); + aRange.aEnd.SetRow( nRow2 ); + } + } + + ScImportExport aObj( rDoc, aRange ); + // tdf#148437 - if cell contains a formula, overwrite entire content of the cell + aObj.SetFormulas(true); + OUString aExportOUString; + /* TODO: STRING_TSVC under some circumstances? */ + aObj.ExportString( aExportOUString, SotClipboardFormatId::STRING ); + aStrSelection = convertLineEnd(aExportOUString, LINEEND_CR); + + // replace Tab/CR with space, if for dialog or through Basic/SelectionTextExt, + // or when it is a single row. + // Otherwise keep Tabs in multi-row (for instance mail or Basic/SelectionText). + // for mail the Tabs are then later changed into (multiple) spaces. + + if ( bInFormatDialog || bWholeWord || aRange.aEnd.Row() == aRange.aStart.Row() ) + { + aStrSelection = aStrSelection.replaceAll("\r", " "); + aStrSelection = aStrSelection.replaceAll("\t", " "); + aStrSelection = comphelper::string::stripEnd(aStrSelection, ' '); + } + } + } + + return aStrSelection; +} + +void ScTabViewShell::InsertURL( const OUString& rName, const OUString& rURL, const OUString& rTarget, + sal_uInt16 nMode ) +{ + SvxLinkInsertMode eMode = static_cast<SvxLinkInsertMode>(nMode); + bool bAsText = ( eMode != HLINK_BUTTON ); // default is now text + + if ( bAsText ) + { + if ( GetViewData().IsActive() ) + { + // if the view is active, always use InsertURLField, which starts EditMode + // and selects the URL, so it can be changed from the URL bar / dialog + + InsertURLField( rName, rURL, rTarget ); + } + else + { + // if the view is not active, InsertURLField doesn't work + // -> use InsertBookmark to directly manipulate cell content + // bTryReplace=sal_True -> if cell contains only one URL, replace it + + SCCOL nPosX = GetViewData().GetCurX(); + SCROW nPosY = GetViewData().GetCurY(); + InsertBookmark( rName, rURL, nPosX, nPosY, &rTarget, true ); + } + } + else + { + SC_MOD()->InputEnterHandler(); + InsertURLButton( rName, rURL, rTarget, nullptr ); + } +} + +static void lcl_SelectFieldAfterInsert( EditView& rView ) +{ + ESelection aSel = rView.GetSelection(); + if ( aSel.nStartPos == aSel.nEndPos && aSel.nStartPos > 0 ) + { + // Cursor is behind the inserted field -> extend selection to the left + + --aSel.nStartPos; + rView.SetSelection( aSel ); + } +} + +void ScTabViewShell::InsertURLField( const OUString& rName, const OUString& rURL, const OUString& rTarget ) +{ + SvxURLField aURLField( rURL, rName, SvxURLFormat::Repr ); + aURLField.SetTargetFrame( rTarget ); + SvxFieldItem aURLItem( aURLField, EE_FEATURE_FIELD ); + + ScViewData& rViewData = GetViewData(); + ScModule* pScMod = SC_MOD(); + ScInputHandler* pHdl = pScMod->GetInputHdl( rViewData.GetViewShell() ); + + bool bSelectFirst = false; + bool bIsEditMode = pScMod->IsEditMode(); + int nSelInd = 1; + OUString sSeltext(GetSelectionText()); + + if ( !bIsEditMode ) + { + if ( !SelectionEditable() ) + { + // no error message (may be called from drag&drop) + return; + } + + // single url in cell is shown in the dialog and replaced + bSelectFirst = HasBookmarkAtCursor( nullptr ); + pScMod->SetInputMode( SC_INPUT_TABLE ); + } + + EditView* pTopView = pHdl->GetTopView(); + EditView* pTableView = pHdl->GetTableView(); + OSL_ENSURE( pTopView || pTableView, "No EditView" ); + + // Check if user selected a whole cell by single click, and cell has content. + // tdf#80043 - if true, replace the entire content of the selected cell instead of + // inserting a duplicate, or appending the url. + if (!bIsEditMode && !bSelectFirst && pTableView && !sSeltext.isEmpty()) + { + nSelInd = sSeltext.getLength(); + bSelectFirst = true; + } + + if ( bSelectFirst ) + { + if ( pTopView ) + pTopView->SetSelection( ESelection(0,0,0,1) ); + if ( pTableView ) + pTableView->SetSelection( ESelection(0,0,0,nSelInd) ); + } + + pHdl->DataChanging(); + + if ( pTopView ) + { + pTopView->InsertField( aURLItem ); + lcl_SelectFieldAfterInsert( *pTopView ); + } + if ( pTableView ) + { + pTableView->InsertField( aURLItem ); + lcl_SelectFieldAfterInsert( *pTableView ); + } + + pHdl->DataChanged(); +} + +void ScTabViewShell::ExecSearch( SfxRequest& rReq ) +{ + const SfxItemSet* pReqArgs = rReq.GetArgs(); + sal_uInt16 nSlot = rReq.GetSlot(); + const SfxPoolItem* pItem; + + switch ( nSlot ) + { + case FID_SEARCH_NOW: + { + const SvxSearchItem* pSearchItem; + if ( pReqArgs && + (pSearchItem = pReqArgs->GetItemIfSet(SID_SEARCH_ITEM, false)) ) + { + ScGlobal::SetSearchItem( *pSearchItem ); + SearchAndReplace( pSearchItem, true, rReq.IsAPI() ); + rReq.Done(); + } + } + break; + + case SID_SEARCH_ITEM: + { + const SvxSearchItem* pSearchItem; + if (pReqArgs && (pSearchItem = + pReqArgs->GetItemIfSet(SID_SEARCH_ITEM, false))) + { + // remember search item + ScGlobal::SetSearchItem( *pSearchItem ); + } + else + { + OSL_FAIL("SID_SEARCH_ITEM without Parameter"); + } + break; + } + case FID_SEARCH: + case FID_REPLACE: + case FID_REPLACE_ALL: + case FID_SEARCH_ALL: + { + if (pReqArgs && SfxItemState::SET == pReqArgs->GetItemState(nSlot, false, &pItem)) + { + // get search item + + SvxSearchItem aSearchItem = ScGlobal::GetSearchItem(); + + // fill search item + + aSearchItem.SetSearchString(static_cast<const SfxStringItem*>(pItem)->GetValue()); + if(SfxItemState::SET == pReqArgs->GetItemState(FN_PARAM_1, false, &pItem)) + aSearchItem.SetReplaceString(static_cast<const SfxStringItem*>(pItem)->GetValue()); + + if (nSlot == FID_SEARCH) + aSearchItem.SetCommand(SvxSearchCmd::FIND); + else if(nSlot == FID_REPLACE) + aSearchItem.SetCommand(SvxSearchCmd::REPLACE); + else if(nSlot == FID_REPLACE_ALL) + aSearchItem.SetCommand(SvxSearchCmd::REPLACE_ALL); + else + aSearchItem.SetCommand(SvxSearchCmd::FIND_ALL); + + // execute request (which stores the SearchItem) + + aSearchItem.SetWhich(SID_SEARCH_ITEM); + GetViewData().GetDispatcher().ExecuteList(FID_SEARCH_NOW, + rReq.IsAPI() ? SfxCallMode::API|SfxCallMode::SYNCHRON : + SfxCallMode::RECORD, + { &aSearchItem }); + } + else + { + GetViewData().GetDispatcher().Execute( + SID_SEARCH_DLG, SfxCallMode::ASYNCHRON|SfxCallMode::RECORD ); + } + } + break; + case FID_REPEAT_SEARCH: + { + // once more with ScGlobal::GetSearchItem() + + SvxSearchItem aSearchItem = ScGlobal::GetSearchItem(); + aSearchItem.SetWhich(SID_SEARCH_ITEM); + GetViewData().GetDispatcher().ExecuteList( FID_SEARCH_NOW, + rReq.IsAPI() ? SfxCallMode::API|SfxCallMode::SYNCHRON : + SfxCallMode::RECORD, + { &aSearchItem }); + } + break; +// case FID_SEARCH_COUNT: + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabvwshf.cxx b/sc/source/ui/view/tabvwshf.cxx new file mode 100644 index 0000000000..2ac3b93760 --- /dev/null +++ b/sc/source/ui/view/tabvwshf.cxx @@ -0,0 +1,1094 @@ +/* -*- 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 <config_features.h> + +#include <memory> + +#include <sfx2/request.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/viewfrm.hxx> +#include <basic/sbstar.hxx> +#include <basic/sberrors.hxx> +#include <svl/ctloptions.hxx> +#include <svl/stritem.hxx> +#include <svl/whiter.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <sfx2/objface.hxx> +#include <svx/svxdlg.hxx> +#include <editeng/colritem.hxx> + +#include <tabvwsh.hxx> +#include <sc.hrc> +#include <helpids.h> +#include <docsh.hxx> +#include <document.hxx> +#include <scresid.hxx> +#include <globstr.hrc> +#include <strings.hrc> +#include <docfunc.hxx> +#include <eventuno.hxx> +#include <dpobject.hxx> +#include <dpshttab.hxx> + +#include <scabstdlg.hxx> + +#include <tabbgcolor.hxx> +#include <markdata.hxx> + +#include <vector> + +using std::unique_ptr; +using namespace com::sun::star; + +void ScTabViewShell::ExecuteTable( SfxRequest& rReq ) +{ + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + + SCTAB nCurrentTab = rViewData.GetTabNo(); + SCTAB nTabCount = rDoc.GetTableCount(); + sal_uInt16 nSlot = rReq.GetSlot(); + const SfxItemSet* pReqArgs = rReq.GetArgs(); + + HideListBox(); // Autofilter-DropDown-Listbox + + switch ( nSlot ) + { + case FID_TABLE_VISIBLE: + { + OUString aName; + rDoc.GetName( nCurrentTab, aName ); + + bool bVisible=true; + if( pReqArgs != nullptr ) + { + const SfxPoolItem* pItem; + if( pReqArgs->HasItem( FID_TABLE_VISIBLE, &pItem ) ) + bVisible = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + } + + if( ! bVisible ) // fade out + { + if ( rDoc.IsDocEditable() ) + { + ScMarkData& rMark = rViewData.GetMarkData(); + HideTable( rMark ); + } + } + else // fade in + { + std::vector<OUString> rNames { aName }; + ShowTable( rNames ); + } + } + break; + + case FID_TABLE_HIDE: + { + if ( rDoc.IsDocEditable() ) + { + ScMarkData& rMark = rViewData.GetMarkData(); + SCTAB nActiveTab = -1; + // For the cases when user right clicks on a non-active tab and hides it. This case is possible for Online. + if (pReqArgs) + { + const SfxPoolItem *pItem; + if( pReqArgs->HasItem( FID_TABLE_HIDE, &pItem ) ) + { + SCTAB nTabNumber = static_cast<const SfxInt16Item*>(pItem)->GetValue(); + // Does selected sheets (tabs) list include the sheet to be hidden? + std::set<SCTAB>::iterator it = rMark.GetSelectedTabs().find(nTabNumber); + if (it == rMark.GetSelectedTabs().end()) + { + // No it doesn't, so we won't shift the selected tab. Let's remember its position. + nActiveTab = GetViewData().GetTabNo(); + } + rMark.SelectOneTable(nTabNumber); + } + } + HideTable( rMark, nActiveTab ); + } + } + break; + + case FID_TABLE_SHOW: + { + std::vector<OUString> rNames; + if ( pReqArgs ) + { + const SfxPoolItem* pItem; + if( pReqArgs->HasItem( FID_TABLE_SHOW, &pItem ) ) + { + OUString aName = static_cast<const SfxStringItem*>(pItem)->GetValue(); + rNames.push_back(aName); + ShowTable( rNames ); + + if( ! rReq.IsAPI() ) + rReq.Done(); + } + } + else + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + VclPtr<AbstractScShowTabDlg> pDlg(pFact->CreateScShowTabDlg(GetFrameWeld())); + + OUString aTabName; + bool bFirst = true; + for ( SCTAB i=0; i != nTabCount; i++ ) + { + if (!rDoc.IsVisible(i)) + { + rDoc.GetName( i, aTabName ); + pDlg->Insert( aTabName, bFirst ); + bFirst = false; + } + } + + std::shared_ptr<SfxRequest> pReq = std::make_shared<SfxRequest>(rReq); + pDlg->StartExecuteAsync([this, pDlg, pReq](sal_Int32 nResult){ + std::vector<OUString> sTables; + if (RET_OK == nResult) + { + std::vector<sal_Int32> aSelectedRows = pDlg->GetSelectedRows(); + for (auto a : aSelectedRows) + { + OUString sTable = pDlg->GetEntry(a); + pReq->AppendItem( SfxStringItem( FID_TABLE_SHOW, sTable ) ); + sTables.push_back(sTable); + } + ShowTable( sTables ); + pReq->Done(); + } + pDlg->disposeOnce(); + }); + rReq.Ignore(); + } + } + break; + + case FID_INS_TABLE: + case FID_INS_TABLE_EXT: + { + ScMarkData& rMark = rViewData.GetMarkData(); + SCTAB nTabSelCount = rMark.GetSelectCount(); + SCTAB nTabNr = nCurrentTab; + + if ( !rDoc.IsDocEditable() ) + break; // locked + + if ( pReqArgs != nullptr ) // from basic + { + bool bOk = false; + const SfxPoolItem* pTabItem; + const SfxPoolItem* pNameItem; + + if ( pReqArgs->HasItem( FN_PARAM_1, &pTabItem ) && + pReqArgs->HasItem( nSlot, &pNameItem ) ) + { + OUString aName = static_cast<const SfxStringItem*>(pNameItem)->GetValue(); + rDoc.CreateValidTabName(aName); + + // sheet number from basic: 1-based + // 0 is special, means adding at the end + nTabNr = static_cast<const SfxUInt16Item*>(pTabItem)->GetValue(); + if (nTabNr == 0) + nTabNr = nTabCount; + else + --nTabNr; + + if (nTabNr > nTabCount) + nTabNr = nTabCount; + + bOk = InsertTable(aName, nTabNr); + } + + if (bOk) + rReq.Done( *pReqArgs ); + //! else set error + } + else // dialog + { + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr<AbstractScInsertTableDlg> pDlg(pFact->CreateScInsertTableDlg(GetFrameWeld(), rViewData, + nTabSelCount, nSlot == FID_INS_TABLE_EXT)); + if ( RET_OK == pDlg->Execute() ) + { + if (pDlg->GetTablesFromFile()) + { + std::vector<SCTAB> nTabs; + sal_uInt16 n = 0; + const OUString* pStr = pDlg->GetFirstTable( &n ); + while ( pStr ) + { + nTabs.push_back( static_cast<SCTAB>(n) ); + pStr = pDlg->GetNextTable( &n ); + } + bool bLink = pDlg->GetTablesAsLink(); + if (!nTabs.empty()) + { + if(pDlg->IsTableBefore()) + { + ImportTables( pDlg->GetDocShellTables(), nTabs.size(), nTabs.data(), + bLink,nTabNr ); + } + else + { + SCTAB nTabAfter = nTabNr+1; + + for(SCTAB j=nCurrentTab+1;j<nTabCount;j++) + { + if(!rDoc.IsScenario(j)) + { + nTabAfter=j; + break; + } + } + + ImportTables( pDlg->GetDocShellTables(), nTabs.size(), nTabs.data(), + bLink,nTabAfter ); + } + } + } + else + { + SCTAB nCount=pDlg->GetTableCount(); + if(pDlg->IsTableBefore()) + { + if(nCount==1 && !pDlg->GetFirstTable()->isEmpty()) + { + rReq.AppendItem( SfxStringItem( FID_INS_TABLE, *pDlg->GetFirstTable() ) ); + rReq.AppendItem( SfxUInt16Item( FN_PARAM_1, static_cast<sal_uInt16>(nTabNr) + 1 ) ); // 1-based + rReq.Done(); + + InsertTable( *pDlg->GetFirstTable(), nTabNr ); + } + else + { + std::vector<OUString> aNames(0); + InsertTables( aNames, nTabNr,nCount ); + } + } + else + { + SCTAB nTabAfter = nTabNr+1; + SCTAB nSelHigh = rMark.GetLastSelected(); + + for(SCTAB j=nSelHigh+1;j<nTabCount;j++) + { + if(!rDoc.IsScenario(j)) + { + nTabAfter=j; + break; + } + else // #101672#; increase nTabAfter, because it is possible that the scenario tables are the last + nTabAfter = j + 1; + } + + if(nCount==1 && !pDlg->GetFirstTable()->isEmpty()) + { + rReq.AppendItem( SfxStringItem( FID_INS_TABLE, *pDlg->GetFirstTable() ) ); + rReq.AppendItem( SfxUInt16Item( FN_PARAM_1, static_cast<sal_uInt16>(nTabAfter) + 1 ) ); // 1-based + rReq.Done(); + + InsertTable( *pDlg->GetFirstTable(), nTabAfter); + } + else + { + std::vector<OUString> aNames(0); + InsertTables( aNames, nTabAfter,nCount); + } + } + } + } + } + } + break; + + case FID_TAB_APPEND: + case FID_TAB_RENAME: + case FID_TAB_MENU_RENAME: + { + // FID_TAB_MENU_RENAME - "rename" in menu + // FID_TAB_RENAME - "name"-property for basic + // equal execute, but MENU_RENAME may be disabled inside GetState + + if ( nSlot == FID_TAB_MENU_RENAME ) + nSlot = FID_TAB_RENAME; // equal execute + + SCTAB nTabNr = rViewData.GetTabNo(); + ScMarkData& rMark = rViewData.GetMarkData(); + SCTAB nTabSelCount = rMark.GetSelectCount(); + + if ( !rDoc.IsDocEditable() ) + break; // everything locked + + if ( nSlot != FID_TAB_APPEND && + ( rDoc.IsTabProtected( nTabNr ) || nTabSelCount > 1 ) ) + break; // no rename + + if( pReqArgs != nullptr ) + { + bool bDone = false; + const SfxPoolItem* pItem; + OUString aName; + + if( pReqArgs->HasItem( FN_PARAM_1, &pItem ) ) + { + nTabNr = static_cast<const SfxUInt16Item*>(pItem)->GetValue(); + + // inserting is 1-based, let's be consistent + if (nTabNr > 0) + --nTabNr; + } + + if( pReqArgs->HasItem( nSlot, &pItem ) ) + aName = static_cast<const SfxStringItem*>(pItem)->GetValue(); + + switch ( nSlot ) + { + case FID_TAB_APPEND: + bDone = AppendTable( aName ); + break; + case FID_TAB_RENAME: + bDone = RenameTable( aName, nTabNr ); + break; + } + + if( bDone ) + { + rReq.Done( *pReqArgs ); + } + } + else + { + sal_uInt16 nRet = RET_OK; + bool bDone = false; + OUString aErrMsg ( ScResId( STR_INVALIDTABNAME ) ); + OUString aName; + OUString aDlgTitle; + OUString sHelpId; + + switch ( nSlot ) + { + case FID_TAB_APPEND: + aDlgTitle = ScResId(SCSTR_APDTABLE); + rDoc.CreateValidTabName( aName ); + sHelpId = HID_SC_APPEND_NAME; + break; + + case FID_TAB_RENAME: + aDlgTitle = ScResId(SCSTR_RENAMETAB); + rDoc.GetName( rViewData.GetTabNo(), aName ); + sHelpId = HID_SC_RENAME_NAME; + break; + } + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr<AbstractScStringInputDlg> pDlg(pFact->CreateScStringInputDlg( + GetFrameWeld(), aDlgTitle, ScResId(SCSTR_NAME), + aName, GetStaticInterface()->GetSlot(nSlot)->GetCommand(), + sHelpId)); + + + while ( !bDone && nRet == RET_OK ) + { + nRet = pDlg->Execute(); + + if ( nRet == RET_OK ) + { + aName = pDlg->GetInputString(); + + switch ( nSlot ) + { + case FID_TAB_APPEND: + bDone = AppendTable( aName ); + break; + case FID_TAB_RENAME: + bDone = RenameTable( aName, nTabNr ); + break; + } + + if ( bDone ) + { + rReq.AppendItem( SfxStringItem( nSlot, aName ) ); + rReq.Done(); + } + else + { + if( rReq.IsAPI() ) + { +#if HAVE_FEATURE_SCRIPTING + StarBASIC::Error( ERRCODE_BASIC_SETPROP_FAILED ); // XXX error handling??? +#endif + } + else + { + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(GetFrameWeld(), + VclMessageType::Warning, VclButtonsType::Ok, aErrMsg)); + nRet = xBox->run(); + } + } + } + } + } + } + break; + + case FID_TAB_MOVE: + { + if ( rDoc.GetChangeTrack() != nullptr ) + break; // if ChangeTracking is active, then no TabMove + + bool bDoIt = false; + sal_uInt16 nDoc = 0; + SCTAB nTab = rViewData.GetTabNo(); + bool bCpy = false, bUseCurrentDocument = false; + OUString aDocName; + OUString aTabName; + + if( pReqArgs != nullptr ) + { + SCTAB nTableCount = rDoc.GetTableCount(); + const SfxPoolItem* pItem; + + // if UseCurrentDocument(FN_PARAM_3) is true ignore the document name provided and use current document + if( pReqArgs->HasItem( FN_PARAM_3, &pItem ) ) + bUseCurrentDocument = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + + if (bUseCurrentDocument) + aDocName = GetViewData().GetDocShell()->GetTitle(); + else if(pReqArgs->HasItem( FID_TAB_MOVE, &pItem )) + aDocName = static_cast<const SfxStringItem*>(pItem)->GetValue(); + + if( pReqArgs->HasItem( FN_PARAM_1, &pItem ) ) + { + // table is 1-based + nTab = static_cast<const SfxUInt16Item*>(pItem)->GetValue() - 1; + if ( nTab >= nTableCount ) + nTab = SC_TAB_APPEND; + } + if( pReqArgs->HasItem( FN_PARAM_2, &pItem ) ) + bCpy = static_cast<const SfxBoolItem*>(pItem)->GetValue(); + + if (!aDocName.isEmpty()) + { + SfxObjectShell* pSh = SfxObjectShell::GetFirst(); + ScDocShell* pScSh = nullptr; + sal_uInt16 i=0; + + while ( pSh ) + { + pScSh = dynamic_cast<ScDocShell*>( pSh ); + + if( pScSh ) + { + pScSh->GetTitle(); + + if (aDocName == pScSh->GetTitle()) + { + nDoc = i; + ScDocument& rDestDoc = pScSh->GetDocument(); + nTableCount = rDestDoc.GetTableCount(); + bDoIt = rDestDoc.IsDocEditable(); + break; + } + + i++; // only count ScDocShell + } + pSh = SfxObjectShell::GetNext( *pSh ); + } + } + else // no doc-name -> new doc + { + nDoc = SC_DOC_NEW; + bDoIt = true; + } + + if ( bDoIt && nTab >= nTableCount ) // if necessary append + nTab = SC_TAB_APPEND; + } + else + { + OUString aDefaultName; + rDoc.GetName( rViewData.GetTabNo(), aDefaultName ); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + + ScopedVclPtr<AbstractScMoveTableDlg> pDlg(pFact->CreateScMoveTableDlg(GetFrameWeld(), + aDefaultName)); + + SCTAB nTableCount = rDoc.GetTableCount(); + ScMarkData& rMark = GetViewData().GetMarkData(); + SCTAB nTabSelCount = rMark.GetSelectCount(); + + if(nTableCount==nTabSelCount) + { + pDlg->SetForceCopyTable(); + } + + // We support direct renaming of sheet only when one sheet + // is selected. + pDlg->EnableRenameTable(nTabSelCount == 1); + + if ( pDlg->Execute() == RET_OK ) + { + nDoc = pDlg->GetSelectedDocument(); + nTab = pDlg->GetSelectedTable(); + bCpy = pDlg->GetCopyTable(); + bool bRna = pDlg->GetRenameTable(); + // Leave aTabName string empty, when Rename is FALSE. + if( bRna ) + { + pDlg->GetTabNameString( aTabName ); + } + bDoIt = true; + + OUString aFoundDocName; + if ( nDoc != SC_DOC_NEW ) + { + ScDocShell* pSh = ScDocShell::GetShellByNum( nDoc ); + if (pSh) + { + aFoundDocName = pSh->GetTitle(); + if ( !pSh->GetDocument().IsDocEditable() ) + { + ErrorMessage(STR_READONLYERR); + bDoIt = false; + } + } + } + rReq.AppendItem( SfxStringItem( FID_TAB_MOVE, aFoundDocName ) ); + // 1-based table, if not APPEND + SCTAB nBasicTab = ( nTab <= MAXTAB ) ? (nTab+1) : nTab; + rReq.AppendItem( SfxUInt16Item( FN_PARAM_1, static_cast<sal_uInt16>(nBasicTab) ) ); + rReq.AppendItem( SfxBoolItem( FN_PARAM_2, bCpy ) ); + } + } + + if( bDoIt ) + { + rReq.Done(); // record, while doc is active + + MoveTable( nDoc, nTab, bCpy, &aTabName ); + } + } + break; + + case FID_TAB_DUPLICATE: + { + // Get info about current document and selected tab + SCTAB nTab = rViewData.GetTabNo(); + OUString aDocName = GetViewData().GetDocShell()->GetTitle(); + sal_uInt16 nDoc = 0; + bool bCpy = true; + + SfxObjectShell* pSh = SfxObjectShell::GetFirst(); + ScDocShell* pScSh = nullptr; + sal_uInt16 i = 0; + + // Determine the index of the current document + while ( pSh ) + { + pScSh = dynamic_cast<ScDocShell*>( pSh ); + + if( pScSh ) + { + pScSh->GetTitle(); + + if (aDocName == pScSh->GetTitle()) + { + nDoc = i; + break; + } + // Only count ScDocShell + i++; + } + pSh = SfxObjectShell::GetNext( *pSh ); + } + + MoveTable( nDoc, nTab + 1, bCpy ); + } + break; + + case FID_DELETE_TABLE: + { + bool bHasIndex = (pReqArgs != nullptr); + + // allow removing via the Index/FID_DELETE_TABLE parameter + SCTAB nTabNr = nCurrentTab; + if (bHasIndex) + { + const SfxPoolItem* pItem; + if (pReqArgs->HasItem(FID_DELETE_TABLE, &pItem)) + { + nTabNr = static_cast<const SfxUInt16Item*>(pItem)->GetValue(); + + // inserting is 1-based, let's be consistent + if (nTabNr > 0) + --nTabNr; + } + } + + bool bDoIt = bHasIndex; + if (!bDoIt) + { + bool bTabWithPivotTable = false; + if (rDoc.HasPivotTable()) + { + const ScDPCollection* pDPs = rDoc.GetDPCollection(); + if (pDPs) + { + const ScMarkData::MarkedTabsType& rSelectedTabs = rViewData.GetMarkData().GetSelectedTabs(); + const size_t nCount = pDPs->GetCount(); + for (size_t i = 0; i < nCount; ++i) + { + const ScDPObject& rDPObj = (*pDPs)[i]; + const ScSheetSourceDesc* pSheetSourceDesc = rDPObj.GetSheetDesc(); + if (pSheetSourceDesc) + { + SCTAB nTabOut = rDPObj.GetOutRange().aStart.Tab(); + SCTAB nTabSource = pSheetSourceDesc->GetSourceRange().aStart.Tab(); + bool bTabOutSel = false; + for (const SCTAB nSelTab : rSelectedTabs) + { + if (nSelTab == nTabSource) + bTabWithPivotTable = true; + if (nSelTab == nTabOut) + bTabOutSel = true; + if (bTabWithPivotTable && bTabOutSel) + break; + } + // if both pivot table and data are selected + // no need to warn for source data losing + if (bTabWithPivotTable && bTabOutSel) + bTabWithPivotTable = false; + if (bTabWithPivotTable) + break; + } + } + } + } + + SCTAB nTabSelCnt = rViewData.GetMarkData().GetSelectCount(); + OUString aTabSelCnt = Application::GetSettings().GetUILocaleDataWrapper().getNum( nTabSelCnt, 0 ); + OUString aQueryDeleteTab = ScResId( STR_QUERY_DELTAB, nTabSelCnt ) + .replaceAll( "%d", aTabSelCnt ); + if (bTabWithPivotTable) + { + OUString aStr = ScResId( STR_QUERY_PIVOTTABLE_DELTAB, nTabSelCnt ) + .replaceAll( "%d", aTabSelCnt ) + + " " + aQueryDeleteTab; + + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(GetFrameWeld(), + VclMessageType::Question, VclButtonsType::YesNo, + aStr)); + xQueryBox->set_default_response(RET_NO); + + // Hard warning as there is potential of data loss on deletion + bDoIt = (RET_YES == xQueryBox->run()); + } + else + { + bool bHasData = false; + ScMarkData& rMark = rViewData.GetMarkData(); + for ( SCTAB i = 0; i < nTabCount && !bHasData; i++ ) + { + if ( rMark.GetTableSelect(i) && !rDoc.IsTabProtected(i) ) + { + SCCOL nStartCol; + SCROW nStartRow; + bHasData = rDoc.GetDataStart( i, nStartCol, nStartRow ); + } + } + // Do not ask for confirmation if all selected tabs are empty + if (bHasData) + { + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(GetFrameWeld(), + VclMessageType::Question, VclButtonsType::YesNo, + aQueryDeleteTab)); + xQueryBox->set_default_response(RET_YES); + + // no parameter given, ask for confirmation + bDoIt = (RET_YES == xQueryBox->run()); + } + else + bDoIt = true; + } + } + + if (bDoIt) + { + SCTAB nNewTab = nCurrentTab; + std::vector<SCTAB> TheTabs; + + if (bHasIndex) + { + // sheet no. provided by the parameter + TheTabs.push_back(nTabNr); + if (nNewTab > nTabNr && nNewTab > 0) + --nNewTab; + } + else + { + SCTAB nFirstTab = 0; + bool bTabFlag = false; + ScMarkData& rMark = rViewData.GetMarkData(); + for (SCTAB i = 0; i < nTabCount; i++) + { + if (rMark.GetTableSelect(i) && !rDoc.IsTabProtected(i)) + { + TheTabs.push_back(i); + bTabFlag = true; + if (nNewTab == i && i+1 < nTabCount) + nNewTab++; + } + if (!bTabFlag) + nFirstTab = i; + } + if (nNewTab >= nTabCount - static_cast<SCTAB>(TheTabs.size())) + nNewTab = nFirstTab; + } + + rViewData.SetTabNo(nNewTab); + DeleteTables(TheTabs); + TheTabs.clear(); + rReq.Done(); + } + } + break; + + case FID_TAB_RTL: + { + ScDocShell* pDocSh = rViewData.GetDocShell(); + ScDocFunc &rFunc = pDocSh->GetDocFunc(); + bool bSet = !rDoc.IsLayoutRTL( nCurrentTab ); + + const ScMarkData& rMark = rViewData.GetMarkData(); + if ( rMark.GetSelectCount() != 0 ) + { + // handle several sheets + + SfxUndoManager* pUndoManager = pDocSh->GetUndoManager(); + OUString aUndo = ScResId( STR_UNDO_TAB_RTL ); + pUndoManager->EnterListAction( aUndo, aUndo, 0, rViewData.GetViewShell()->GetViewShellId() ); + + for (const auto& rTab : rMark) + rFunc.SetLayoutRTL( rTab, bSet ); + + pUndoManager->LeaveListAction(); + } + else + rFunc.SetLayoutRTL( nCurrentTab, bSet ); + } + break; + + case FID_TAB_TOGGLE_GRID: + { + bool bShowGrid = rViewData.GetShowGrid(); + rViewData.SetShowGrid(!bShowGrid); + SfxBindings& rBindings = GetViewFrame().GetBindings(); + rBindings.Invalidate( FID_TAB_TOGGLE_GRID ); + ScDocShellModificator aModificator(*rViewData.GetDocShell()); + aModificator.SetDocumentModified(); + PaintGrid(); + rReq.Done(); + } + break; + + case FID_TAB_SET_TAB_BG_COLOR: + case FID_TAB_MENU_SET_TAB_BG_COLOR: + { + if ( nSlot == FID_TAB_MENU_SET_TAB_BG_COLOR ) + nSlot = FID_TAB_SET_TAB_BG_COLOR; + SCTAB nTabNr = rViewData.GetTabNo(); + ScMarkData& rMark = rViewData.GetMarkData(); + SCTAB nTabSelCount = rMark.GetSelectCount(); + if ( !rDoc.IsDocEditable() ) + break; + + if ( rDoc.IsTabProtected( nTabNr ) ) // ||nTabSelCount > 1 + break; + + if( pReqArgs != nullptr ) + { + bool bDone = false; + const SfxPoolItem* pItem; + Color aColor; + + if( pReqArgs->HasItem( nSlot, &pItem ) ) + aColor = static_cast<const SvxColorItem*>(pItem)->GetValue(); + + if ( nTabSelCount > 1 ) + { + std::unique_ptr<ScUndoTabColorInfo::List> + pTabColorList(new ScUndoTabColorInfo::List); + for (const auto& rTab : rMark) + { + if ( !rDoc.IsTabProtected(rTab) ) + { + ScUndoTabColorInfo aTabColorInfo(rTab); + aTabColorInfo.maNewTabBgColor = aColor; + pTabColorList->push_back(aTabColorInfo); + } + } + bDone = SetTabBgColor( *pTabColorList ); + } + else + { + bDone = SetTabBgColor( aColor, nCurrentTab ); //ScViewFunc.SetTabBgColor + } + if( bDone ) + { + rReq.Done( *pReqArgs ); + } + } + else + { + sal_uInt16 nRet = RET_OK; /// temp + bool bDone = false; /// temp + + Color aTabBgColor = rDoc.GetTabBgColor( nCurrentTab ); + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + ScopedVclPtr<AbstractScTabBgColorDlg> pDlg(pFact->CreateScTabBgColorDlg( + GetFrameWeld(), + ScResId(SCSTR_SET_TAB_BG_COLOR), + ScResId(SCSTR_NO_TAB_BG_COLOR), + aTabBgColor)); + while ( !bDone && nRet == RET_OK ) + { + nRet = pDlg->Execute(); + if( nRet == RET_OK ) + { + Color aSelectedColor; + pDlg->GetSelectedColor(aSelectedColor); + std::unique_ptr<ScUndoTabColorInfo::List> + pTabColorList(new ScUndoTabColorInfo::List); + if ( nTabSelCount > 1 ) + { + for (const auto& rTab : rMark) + { + if ( !rDoc.IsTabProtected(rTab) ) + { + ScUndoTabColorInfo aTabColorInfo(rTab); + aTabColorInfo.maNewTabBgColor = aSelectedColor; + pTabColorList->push_back(aTabColorInfo); + } + } + bDone = SetTabBgColor( *pTabColorList ); + } + else + { + bDone = SetTabBgColor( aSelectedColor, nCurrentTab ); //ScViewFunc.SetTabBgColor + } + + if ( bDone ) + { + rReq.AppendItem( SvxColorItem( aTabBgColor, nSlot ) ); + rReq.Done(); + } + else + { + if( rReq.IsAPI() ) + { +#if HAVE_FEATURE_SCRIPTING + StarBASIC::Error( ERRCODE_BASIC_SETPROP_FAILED ); +#endif + } + } + } + } + } + } + break; + + case FID_TAB_EVENTS: + { + ScDocShell* pDocSh = rViewData.GetDocShell(); + uno::Reference<container::XNameReplace> xEvents( new ScSheetEventsObj( pDocSh, nCurrentTab ) ); + uno::Reference<frame::XFrame> xFrame = GetViewFrame().GetFrame().GetFrameInterface(); + SvxAbstractDialogFactory* pDlgFactory = SvxAbstractDialogFactory::Create(); + ScopedVclPtr<VclAbstractDialog> pDialog( pDlgFactory->CreateSvxMacroAssignDlg( + GetFrameWeld(), xFrame, false, xEvents, 0 ) ); + if ( pDialog->Execute() == RET_OK ) + { + // the dialog modifies the settings directly + } + } + break; + case FID_TOGGLEHIDDENCOLROW: + { + svtools::EditableColorConfig aEditableConfig; + svtools::ColorConfigValue aValue = aEditableConfig.GetColorValue(svtools::CALCHIDDENROWCOL); + aValue.bIsVisible = !aValue.bIsVisible; + aEditableConfig.SetColorValue(svtools::CALCHIDDENROWCOL, aValue); + } + break; + default: + OSL_FAIL("unknown message for ViewShell"); + break; + } +} + +void ScTabViewShell::GetStateTable( SfxItemSet& rSet ) +{ + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + ScDocShell* pDocShell = rViewData.GetDocShell(); + ScMarkData& rMark = GetViewData().GetMarkData(); + SCTAB nTab = rViewData.GetTabNo(); + + SCTAB nTabCount = rDoc.GetTableCount(); + SCTAB nTabSelCount = rMark.GetSelectCount(); + + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + + while ( nWhich ) + { + switch ( nWhich ) + { + + case FID_TABLE_VISIBLE: + rSet.Put( SfxBoolItem( nWhich, rDoc.IsVisible(nTab) )); + break; + + case FID_TABLE_HIDE: + { + sal_uInt16 nVis = 0; + // enable menu : check to make sure we won't hide all sheets. we need at least one visible at all times. + for ( SCTAB i=0; i < nTabCount && nVis<nTabSelCount + 1; i++ ) + if (rDoc.IsVisible(i)) + ++nVis; + if ( nVis<=nTabSelCount || !rDoc.IsDocEditable() ) + rSet.DisableItem( nWhich ); + } + break; + + case FID_TABLE_SHOW: + { + bool bHasHidden = false; + for ( SCTAB i=0; i < nTabCount && !bHasHidden; i++ ) + if (!rDoc.IsVisible(i)) + bHasHidden = true; + if ( !bHasHidden || rDoc.IsDocProtected() || nTabSelCount > 1 ) + rSet.DisableItem( nWhich ); + } + break; + + case FID_DELETE_TABLE: + { + if ( rDoc.GetChangeTrack() ) + rSet.DisableItem( nWhich ); + else + { + sal_uInt16 nVis = 0; + for ( SCTAB i=0; i < nTabCount && nVis<2; i++ ) + if (rDoc.IsVisible(i)) + ++nVis; + if ( rDoc.IsTabProtected(nTab) + || !rDoc.IsDocEditable() + || nVis < 2 + || nTabSelCount == nTabCount) + rSet.DisableItem( nWhich ); + } + } + break; + + case FID_INS_TABLE: + case FID_INS_TABLE_EXT: + case FID_TAB_APPEND: + if ( !rDoc.IsDocEditable() || + nTabCount > MAXTAB || + ( nWhich == FID_INS_TABLE_EXT && pDocShell && pDocShell->IsDocShared() ) ) + rSet.DisableItem( nWhich ); + break; + + case FID_TAB_MOVE: + if ( !rDoc.IsDocEditable() + || rDoc.GetChangeTrack() != nullptr + || nTabCount > MAXTAB) + rSet.DisableItem( nWhich ); + break; + + case FID_TAB_DUPLICATE: + if ( !rDoc.IsDocEditable() + || rDoc.GetChangeTrack() != nullptr + || nTabCount > MAXTAB) + rSet.DisableItem( nWhich ); + break; + + // FID_TAB_MENU_RENAME - "rename" from Menu + // FID_TAB_RENAME - "name"-property for Basic + + case FID_TAB_MENU_RENAME: + if ( !rDoc.IsDocEditable() || + rDoc.IsTabProtected(nTab) ||nTabSelCount > 1 || + ( pDocShell && pDocShell->IsDocShared() ) ) + rSet.DisableItem( nWhich ); + break; + + case FID_TAB_RENAME: + { + OUString aTabName; + rDoc.GetName( nTab, aTabName ); + + rSet.Put( SfxStringItem( nWhich, aTabName )); + + } + break; + + case FID_TAB_RTL: + { + if ( !SvtCTLOptions::IsCTLFontEnabled() ) + rSet.DisableItem( nWhich ); + else + rSet.Put( SfxBoolItem( nWhich, rDoc.IsLayoutRTL( nTab ) ) ); + } + break; + + case FID_TAB_MENU_SET_TAB_BG_COLOR: + { + if ( !rDoc.IsDocEditable() + || ( pDocShell && pDocShell->IsDocShared() ) + || rDoc.IsTabProtected(nTab) ) + rSet.DisableItem( nWhich ); + } + break; + + case FID_TAB_SET_TAB_BG_COLOR: + { + Color aColor = rDoc.GetTabBgColor( nTab ); + rSet.Put( SvxColorItem( aColor, nWhich ) ); + } + break; + + case FID_TAB_TOGGLE_GRID: + rSet.Put( SfxBoolItem(nWhich, rViewData.GetShowGrid()) ); + break; + } + nWhich = aIter.NextWhich(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabvwshg.cxx b/sc/source/ui/view/tabvwshg.cxx new file mode 100644 index 0000000000..6b6820b9a3 --- /dev/null +++ b/sc/source/ui/view/tabvwshg.cxx @@ -0,0 +1,120 @@ +/* -*- 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 <config_features.h> + +#include <tools/urlobj.hxx> +#include <svx/svdobjkind.hxx> +#include <svx/svdouno.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/docfile.hxx> +#include <osl/diagnose.h> + +#include <com/sun/star/form/FormButtonType.hpp> +#include <com/sun/star/beans/XPropertySet.hpp> +#include <com/sun/star/awt/XControlModel.hpp> + +#include <tabvwsh.hxx> +#include <document.hxx> +#include <drawview.hxx> +#include <globstr.hrc> +#include <gridwin.hxx> +#include <avmedia/mediawindow.hxx> + +using namespace com::sun::star; + +void ScTabViewShell::InsertURLButton( const OUString& rName, const OUString& rURL, + const OUString& rTarget, + const Point* pInsPos ) +{ + // protected sheet ? + + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + SCTAB nTab = rViewData.GetTabNo(); + if ( rDoc.IsTabProtected(nTab) ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + MakeDrawLayer(); + + ScTabView* pView = rViewData.GetView(); + ScDrawView* pDrView = pView->GetScDrawView(); + SdrModel& rModel = pDrView->GetModel(); + + rtl::Reference<SdrObject> pObj = SdrObjFactory::MakeNewObject( + rModel, + SdrInventor::FmForm, + SdrObjKind::FormButton); + + SdrUnoObj* pUnoCtrl = dynamic_cast<SdrUnoObj*>( pObj.get() ); + OSL_ENSURE( pUnoCtrl, "no SdrUnoObj"); + if( !pUnoCtrl ) + return; + + uno::Reference<awt::XControlModel> xControlModel = pUnoCtrl->GetUnoControlModel(); + OSL_ENSURE( xControlModel.is(), "UNO control without model" ); + if( !xControlModel.is() ) + return; + + uno::Reference< beans::XPropertySet > xPropSet( xControlModel, uno::UNO_QUERY ); + + xPropSet->setPropertyValue("Label", uno::Any(rName) ); + + OUString aTmp = INetURLObject::GetAbsURL( rDoc.GetDocumentShell()->GetMedium()->GetBaseURL(), rURL ); + xPropSet->setPropertyValue("TargetURL", uno::Any(aTmp) ); + + if( !rTarget.isEmpty() ) + { + xPropSet->setPropertyValue("TargetFrame", uno::Any(rTarget) ); + } + + xPropSet->setPropertyValue("ButtonType", uno::Any(form::FormButtonType_URL) ); + +#if HAVE_FEATURE_AVMEDIA + if ( ::avmedia::MediaWindow::isMediaURL( rURL, ""/*TODO?*/ ) ) + { + xPropSet->setPropertyValue("DispatchURLInternal", uno::Any(true) ); + } +#endif + + Point aPos; + if (pInsPos) + aPos = *pInsPos; + else + aPos = GetInsertPos(); + + // Size as in 3.1: + Size aSize = GetActiveWin()->PixelToLogic(Size(140, 20)); + + if ( rDoc.IsNegativePage(nTab) ) + aPos.AdjustX( -(aSize.Width()) ); + + pObj->SetLogicRect(tools::Rectangle(aPos, aSize)); + + // for the old VC-Button the position/size had to be set explicitly once more + // that seems not to be needed with UnoControls + + // do not mark when Ole + pDrView->InsertObjectSafe( pObj.get(), *pDrView->GetSdrPageView() ); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/tabvwshh.cxx b/sc/source/ui/view/tabvwshh.cxx new file mode 100644 index 0000000000..d1263590a8 --- /dev/null +++ b/sc/source/ui/view/tabvwshh.cxx @@ -0,0 +1,261 @@ +/* -*- 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 <config_features.h> + +#include <basic/sberrors.hxx> +#include <svx/svdmark.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svdview.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/request.hxx> +#include <basic/sbxcore.hxx> +#include <svl/stritem.hxx> +#include <svl/whiter.hxx> +#include <vcl/svapp.hxx> +#include <osl/diagnose.h> + +#include <tabvwsh.hxx> +#include <document.hxx> +#include <sc.hrc> +#include <drwlayer.hxx> +#include <retypepassdlg.hxx> +#include <tabprotection.hxx> + +#include <com/sun/star/embed/EmbedVerbs.hpp> + +using namespace com::sun::star; + +void ScTabViewShell::ExecuteObject( const SfxRequest& rReq ) +{ + sal_uInt16 nSlotId = rReq.GetSlot(); + const SfxItemSet* pReqArgs = rReq.GetArgs(); + + // Always activate/deactivate object in the visible View + + ScTabViewShell* pVisibleSh = this; + if ( nSlotId == SID_OLE_SELECT || nSlotId == SID_OLE_ACTIVATE || nSlotId == SID_OLE_DEACTIVATE ) + { + OSL_FAIL("old slot SID_OLE..."); + } + + switch (nSlotId) + { + case SID_OLE_SELECT: + case SID_OLE_ACTIVATE: + { + // In both cases, first select in the visible View + + OUString aName; + SdrView* pDrView = GetScDrawView(); + if (pDrView) + { + const SdrMarkList& rMarkList = pDrView->GetMarkedObjectList(); + if (rMarkList.GetMarkCount() == 1) + aName = ScDrawLayer::GetVisibleName( rMarkList.GetMark(0)->GetMarkedSdrObj() ); + } + pVisibleSh->SelectObject( aName ); + + // activate + + if ( nSlotId == SID_OLE_ACTIVATE ) + pVisibleSh->DoVerb(css::embed::EmbedVerbs::MS_OLEVERB_PRIMARY); + } + break; + case SID_OLE_DEACTIVATE: + pVisibleSh->DeactivateOle(); + break; + + case SID_OBJECT_LEFT: + case SID_OBJECT_TOP: + case SID_OBJECT_WIDTH: + case SID_OBJECT_HEIGHT: + { + bool bDone = false; + const SfxPoolItem* pItem; + if ( pReqArgs && pReqArgs->GetItemState( nSlotId, true, &pItem ) == SfxItemState::SET ) + { + tools::Long nNewVal = static_cast<const SfxInt32Item*>(pItem)->GetValue(); + if ( nNewVal < 0 ) + nNewVal = 0; + + //! convert from something into 1/100mm ?????? + + SdrView* pDrView = GetScDrawView(); + if ( pDrView ) + { + const SdrMarkList& rMarkList = pDrView->GetMarkedObjectList(); + if (rMarkList.GetMarkCount() == 1) + { + SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj(); + tools::Rectangle aRect = pObj->GetLogicRect(); + + if ( nSlotId == SID_OBJECT_LEFT ) + pDrView->MoveMarkedObj( Size( nNewVal - aRect.Left(), 0 ) ); + else if ( nSlotId == SID_OBJECT_TOP ) + pDrView->MoveMarkedObj( Size( 0, nNewVal - aRect.Top() ) ); + else if ( nSlotId == SID_OBJECT_WIDTH ) + pDrView->ResizeMarkedObj( aRect.TopLeft(), + Fraction( nNewVal, aRect.GetWidth() ), + Fraction( 1, 1 ) ); + else // if ( nSlotId == SID_OBJECT_HEIGHT ) + pDrView->ResizeMarkedObj( aRect.TopLeft(), + Fraction( 1, 1 ), + Fraction( nNewVal, aRect.GetHeight() ) ); + bDone = true; + } + } + } +#if HAVE_FEATURE_SCRIPTING + if (!bDone) + SbxBase::SetError( ERRCODE_BASIC_BAD_PARAMETER ); // basic error +#endif + } + break; + + } +} + +static uno::Reference < embed::XEmbeddedObject > lcl_GetSelectedObj( const SdrView* pDrView ) //! member of ScDrawView? +{ + uno::Reference < embed::XEmbeddedObject > xRet; + if (pDrView) + { + const SdrMarkList& rMarkList = pDrView->GetMarkedObjectList(); + if (rMarkList.GetMarkCount() == 1) + { + SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj(); + if (pObj->GetObjIdentifier() == SdrObjKind::OLE2) + { + SdrOle2Obj* pOle2Obj = static_cast<SdrOle2Obj*>(pObj); + xRet = pOle2Obj->GetObjRef(); + } + } + } + + return xRet; +} + +void ScTabViewShell::GetObjectState( SfxItemSet& rSet ) +{ + SfxWhichIter aIter(rSet); + sal_uInt16 nWhich = aIter.FirstWhich(); + while ( nWhich ) + { + switch (nWhich) + { + case SID_ACTIVE_OBJ_NAME: + { + OUString aName; + uno::Reference < embed::XEmbeddedObject > xOLE = lcl_GetSelectedObj( GetScDrawView() ); + if (xOLE.is()) + { + aName = GetViewData().GetSfxDocShell()->GetEmbeddedObjectContainer().GetEmbeddedObjectName( xOLE ); + } + rSet.Put( SfxStringItem( nWhich, aName ) ); + } + break; + case SID_OBJECT_LEFT: + case SID_OBJECT_TOP: + case SID_OBJECT_WIDTH: + case SID_OBJECT_HEIGHT: + { + SdrView* pDrView = GetScDrawView(); + if ( pDrView ) + { + const SdrMarkList& rMarkList = pDrView->GetMarkedObjectList(); + if (rMarkList.GetMarkCount() == 1) + { + SdrObject* pObj = rMarkList.GetMark(0)->GetMarkedSdrObj(); + tools::Rectangle aRect = pObj->GetLogicRect(); + + tools::Long nVal; + if ( nWhich == SID_OBJECT_LEFT ) + nVal = aRect.Left(); + else if ( nWhich == SID_OBJECT_TOP ) + nVal = aRect.Top(); + else if ( nWhich == SID_OBJECT_WIDTH ) + nVal = aRect.GetWidth(); + else // if ( nWhich == SID_OBJECT_HEIGHT ) + nVal = aRect.GetHeight(); + + //! convert from 1/100mm to something else ?????? + + rSet.Put( SfxInt32Item( TypedWhichId<SfxInt32Item>(nWhich), nVal ) ); + } + } + } + break; + } + nWhich = aIter.NextWhich(); + } +} + +void ScTabViewShell::AddAccessibilityObject( SfxListener& rObject ) +{ + if (!pAccessibilityBroadcaster) + pAccessibilityBroadcaster.reset( new SfxBroadcaster ); + + rObject.StartListening( *pAccessibilityBroadcaster ); + ScDocument& rDoc = GetViewData().GetDocument(); + rDoc.AddUnoObject(rObject); +} + +void ScTabViewShell::RemoveAccessibilityObject( SfxListener& rObject ) +{ + SolarMutexGuard g; + + if (pAccessibilityBroadcaster) + { + rObject.EndListening( *pAccessibilityBroadcaster ); + ScDocument& rDoc = GetViewData().GetDocument(); + rDoc.RemoveUnoObject(rObject); + } + else + { + OSL_FAIL("no accessibility broadcaster?"); + } +} + +void ScTabViewShell::BroadcastAccessibility( const SfxHint &rHint ) +{ + if (pAccessibilityBroadcaster) + pAccessibilityBroadcaster->Broadcast( rHint ); +} + +bool ScTabViewShell::HasAccessibilityObjects() const +{ + return pAccessibilityBroadcaster && pAccessibilityBroadcaster->HasListeners(); +} + +bool ScTabViewShell::ExecuteRetypePassDlg(ScPasswordHash eDesiredHash) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + + ScRetypePassDlg aDlg(GetFrameWeld()); + aDlg.SetDataFromDocument(rDoc); + aDlg.SetDesiredHash(eDesiredHash); + if (aDlg.run() != RET_OK) + return false; + + aDlg.WriteNewDataToDocument(rDoc); + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/viewdata.cxx b/sc/source/ui/view/viewdata.cxx new file mode 100644 index 0000000000..25e602669e --- /dev/null +++ b/sc/source/ui/view/viewdata.cxx @@ -0,0 +1,4365 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <scitems.hxx> +#include <editeng/eeitem.hxx> +#include <o3tl/safeint.hxx> +#include <o3tl/unit_conversion.hxx> +#include <o3tl/string_view.hxx> +#include <sfx2/lokhelper.hxx> +#include <sfx2/viewfrm.hxx> +#include <editeng/adjustitem.hxx> +#include <editeng/brushitem.hxx> +#include <svtools/colorcfg.hxx> +#include <editeng/editview.hxx> +#include <editeng/editstat.hxx> +#include <editeng/outliner.hxx> +#include <editeng/unolingu.hxx> +#include <editeng/justifyitem.hxx> + +#include <vcl/svapp.hxx> +#include <rtl/math.hxx> +#include <sal/log.hxx> + +#include <viewdata.hxx> +#include <docoptio.hxx> +#include <scmod.hxx> +#include <global.hxx> +#include <document.hxx> +#include <drwlayer.hxx> +#include <attrib.hxx> +#include <tabview.hxx> +#include <tabvwsh.hxx> +#include <docsh.hxx> +#include <patattr.hxx> +#include <editutil.hxx> +#include <scextopt.hxx> +#include <miscuno.hxx> +#include <unonames.hxx> +#include <inputopt.hxx> +#include <inputhdl.hxx> +#include <inputwin.hxx> +#include <viewutil.hxx> +#include <markdata.hxx> +#include <ViewSettingsSequenceDefines.hxx> +#include <gridwin.hxx> +#include <transobj.hxx> +#include <clipparam.hxx> +#include <comphelper/flagguard.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/string.hxx> + +#include <vcl/uitest/logger.hxx> +#include <vcl/uitest/eventdescription.hxx> + +#include <com/sun/star/container/XNameContainer.hpp> +#include <com/sun/star/document/NamedPropertyValues.hpp> + +using namespace com::sun::star; + +#define SC_GROWY_SMALL_EXTRA 100 +#define SC_GROWY_BIG_EXTRA 200 + +constexpr OUString TAG_TABBARWIDTH = u"tw:"_ustr; + +namespace { + +void lcl_LOKRemoveWindow(ScTabViewShell* pTabViewShell, ScSplitPos eWhich) +{ + if (comphelper::LibreOfficeKit::isActive()) + { + auto lRemoveWindows = + [pTabViewShell, eWhich] (ScTabViewShell* pOtherViewShell) + { pOtherViewShell->RemoveWindowFromForeignEditView(pTabViewShell, eWhich); }; + + SfxLokHelper::forEachOtherView(pTabViewShell, lRemoveWindows); + } +} + +} // anonymous namespace + +namespace { + +void collectUIInformation(std::map<OUString, OUString>&& aParameters, const OUString& rAction) +{ + EventDescription aDescription; + aDescription.aID = "grid_window"; + aDescription.aAction = rAction; + aDescription.aParameters = std::move(aParameters); + aDescription.aParent = "MainWindow"; + aDescription.aKeyWord = "ScGridWinUIObject"; + + UITestLogger::getInstance().logEvent(aDescription); +} +} + +const ScPositionHelper::index_type ScPositionHelper::null; // definition + +bool ScPositionHelper::Comp::operator() (const value_type& rValue1, const value_type& rValue2) const +{ + if (rValue1.first == null || rValue2.first == null) + { + return rValue1.second < rValue2.second; + } + else + { + return rValue1.first < rValue2.first; + } +} + +ScPositionHelper::ScPositionHelper(const ScDocument *pDoc, bool bColumn) + : MAX_INDEX(bColumn ? (pDoc ? pDoc->MaxCol() : -1) : MAXTILEDROW) +{ + mData.insert(std::make_pair(-1, 0)); +} + +void ScPositionHelper::setDocument(const ScDocument& rDoc, bool bColumn) +{ + MAX_INDEX = bColumn ? rDoc.MaxCol() : MAXTILEDROW; +} + +void ScPositionHelper::insert(index_type nIndex, tools::Long nPos) +{ + if (nIndex < 0) return; + SAL_INFO("sc.lok.poshelper", "ScPositionHelper::insert: nIndex: " + << nIndex << ", nPos: " << nPos << ", size: " << mData.size()); + value_type aValue = std::make_pair(nIndex, nPos); + mData.erase(aValue); + mData.insert(aValue); + SAL_INFO("sc.lok.poshelper", + "ScPositionHelper::insert: after insert: size: " << mData.size()); +} + +void ScPositionHelper::removeByIndex(index_type nIndex) +{ + if (nIndex < 0) + return; + SAL_INFO("sc.lok.poshelper", "ScPositionHelper::remove: nIndex: " << nIndex + << ", size: " << mData.size()); + auto it = mData.find(std::make_pair(nIndex, 0)); + if (it == mData.end()) return; + mData.erase(it); + SAL_INFO("sc.lok.poshelper", + "ScPositionHelper::remove: after erase: size: " << mData.size()); +} + +void ScPositionHelper::invalidateByIndex(index_type nIndex) +{ + SAL_INFO("sc.lok.poshelper", "ScPositionHelper::invalidate: nIndex: " << nIndex); + if (nIndex < 0) + { + mData.clear(); + mData.insert(std::make_pair(-1, 0)); + } + else + { + auto it = mData.lower_bound(std::make_pair(nIndex, 0)); + mData.erase(it, mData.end()); + } +} + +void ScPositionHelper::invalidateByPosition(tools::Long nPos) +{ + SAL_INFO("sc.lok.poshelper", "ScPositionHelper::invalidate: nPos: " << nPos); + if (nPos <= 0) + { + mData.clear(); + mData.insert(std::make_pair(-1, 0)); + } + else + { + auto it = mData.lower_bound(std::make_pair(null, nPos)); + mData.erase(it, mData.end()); + } +} + +const ScPositionHelper::value_type& +ScPositionHelper::getNearestByIndex(index_type nIndex) const +{ + SAL_INFO("sc.lok.poshelper", + "ScPositionHelper::getNearest: nIndex: " << nIndex << ", size: " << mData.size()); + auto posUB = mData.upper_bound(std::make_pair(nIndex, 0)); + if (posUB == mData.begin()) + { + return *posUB; + } + + auto posLB = std::prev(posUB); + // coverity[copy_paste_error : FALSE] - posUB is correct + if (posUB == mData.end()) + { + return *posLB; + } + + tools::Long nDiffUB = posUB->first - nIndex; + tools::Long nDiffLB = posLB->first - nIndex; + if (nDiffUB < -nDiffLB) + { + return *posUB; + } + else + { + return *posLB; + } +} + +const ScPositionHelper::value_type& +ScPositionHelper::getNearestByPosition(tools::Long nPos) const +{ + SAL_INFO("sc.lok.poshelper", + "ScPositionHelper::getNearest: nPos: " << nPos << ", size: " << mData.size()); + auto posUB = mData.upper_bound(std::make_pair(null, nPos)); + + if (posUB == mData.begin()) + { + return *posUB; + } + + auto posLB = std::prev(posUB); + // coverity[copy_paste_error : FALSE] - posUB is correct + if (posUB == mData.end()) + { + return *posLB; + } + + tools::Long nDiffUB = posUB->second - nPos; + tools::Long nDiffLB = posLB->second - nPos; + + if (nDiffUB < -nDiffLB) + { + return *posUB; + } + else + { + return *posLB; + } +} + +tools::Long ScPositionHelper::getPosition(index_type nIndex) const +{ + auto it = mData.find(std::make_pair(nIndex, 0)); + if (it == mData.end()) return -1; + return it->second; +} + +tools::Long ScPositionHelper::computePosition(index_type nIndex, const std::function<long (index_type)>& getSizePx) +{ + assert(MAX_INDEX > 0); + if (nIndex < 0) nIndex = 0; + if (nIndex > MAX_INDEX) nIndex = MAX_INDEX; + + const auto& rNearest = getNearestByIndex(nIndex); + index_type nStartIndex = rNearest.first; + tools::Long nTotalPixels = rNearest.second; + + if (nStartIndex < nIndex) + { + for (index_type nIdx = nStartIndex + 1; nIdx <= nIndex; ++nIdx) + { + nTotalPixels += getSizePx(nIdx); + } + } + else + { + for (index_type nIdx = nStartIndex; nIdx > nIndex; --nIdx) + { + nTotalPixels -= getSizePx(nIdx); + } + } + return nTotalPixels; +} + +ScBoundsProvider::ScBoundsProvider(const ScViewData &rView, SCTAB nT, bool bColHeader) + : rDoc(rView.GetDocument()) + , nTab(nT) + , bColumnHeader(bColHeader) + , MAX_INDEX(bColHeader ? rDoc.MaxCol() : MAXTILEDROW) + , mfPPTX(rView.GetPPTX()) + , mfPPTY(rView.GetPPTY()) + , nFirstIndex(-1) + , nSecondIndex(-1) + , nFirstPositionPx(-1) + , nSecondPositionPx(-1) +{} + +void ScBoundsProvider::GetStartIndexAndPosition(SCCOL& nIndex, tools::Long& nPosition) const +{ + assert(bColumnHeader); + nIndex = nFirstIndex; + nPosition = nFirstPositionPx; +} + +void ScBoundsProvider::GetEndIndexAndPosition(SCCOL& nIndex, tools::Long& nPosition) const +{ + assert(bColumnHeader); + nIndex = nSecondIndex; + nPosition = nSecondPositionPx; +} + +void ScBoundsProvider::GetStartIndexAndPosition(SCROW& nIndex, tools::Long& nPosition) const +{ + assert(!bColumnHeader); + nIndex = nFirstIndex; + nPosition = nFirstPositionPx; +} + +void ScBoundsProvider::GetEndIndexAndPosition(SCROW& nIndex, tools::Long& nPosition) const +{ + assert(!bColumnHeader); + nIndex = nSecondIndex; + nPosition = nSecondPositionPx; +} + +tools::Long ScBoundsProvider::GetSize(index_type nIndex) const +{ + const sal_uInt16 nSize = bColumnHeader ? rDoc.GetColWidth(nIndex, nTab) : rDoc.GetRowHeight(nIndex, nTab); + return ScViewData::ToPixel(nSize, bColumnHeader ? mfPPTX : mfPPTY); +} + +void ScBoundsProvider::GetIndexAndPos(index_type nNearestIndex, tools::Long nNearestPosition, + tools::Long nBound, index_type& nFoundIndex, tools::Long& nPosition, + bool bTowards, tools::Long nDiff) +{ + if (nDiff > 0) // nBound < nNearestPosition + GeIndexBackwards(nNearestIndex, nNearestPosition, nBound, + nFoundIndex, nPosition, bTowards); + else + GetIndexTowards(nNearestIndex, nNearestPosition, nBound, + nFoundIndex, nPosition, bTowards); +} + +void ScBoundsProvider::Compute( + value_type aFirstNearest, value_type aSecondNearest, + tools::Long nFirstBound, tools::Long nSecondBound) +{ + SAL_INFO("sc.lok.header", "BoundsProvider: nFirstBound: " << nFirstBound + << ", nSecondBound: " << nSecondBound); + + tools::Long nFirstDiff = aFirstNearest.second - nFirstBound; + tools::Long nSecondDiff = aSecondNearest.second - nSecondBound; + SAL_INFO("sc.lok.header", "BoundsProvider: rTopNearest: index: " << aFirstNearest.first + << ", pos: " << aFirstNearest.second << ", diff: " << nFirstDiff); + SAL_INFO("sc.lok.header", "BoundsProvider: rBottomNearest: index: " << aSecondNearest.first + << ", pos: " << aSecondNearest.second << ", diff: " << nSecondDiff); + + bool bReverse = (std::abs(nFirstDiff) >= std::abs(nSecondDiff)); + + if(bReverse) + { + std::swap(aFirstNearest, aSecondNearest); + std::swap(nFirstBound, nSecondBound); + std::swap(nFirstDiff, nSecondDiff); + } + + index_type nNearestIndex = aFirstNearest.first; + tools::Long nNearestPosition = aFirstNearest.second; + SAL_INFO("sc.lok.header", "BoundsProvider: nearest to first bound: nNearestIndex: " + << nNearestIndex << ", nNearestPosition: " << nNearestPosition); + + GetIndexAndPos(nNearestIndex, nNearestPosition, nFirstBound, + nFirstIndex, nFirstPositionPx, !bReverse, nFirstDiff); + SAL_INFO("sc.lok.header", "BoundsProvider: nFirstIndex: " << nFirstIndex + << ", nFirstPositionPx: " << nFirstPositionPx); + + if (std::abs(nSecondDiff) < std::abs(nSecondBound - nFirstPositionPx)) + { + nNearestIndex = aSecondNearest.first; + nNearestPosition = aSecondNearest.second; + } + else + { + nNearestPosition = nFirstPositionPx; + nNearestIndex = nFirstIndex; + nSecondDiff = !bReverse ? -1 : 1; + } + SAL_INFO("sc.lok.header", "BoundsProvider: nearest to second bound: nNearestIndex: " + << nNearestIndex << ", nNearestPosition: " << nNearestPosition + << ", diff: " << nSecondDiff); + + GetIndexAndPos(nNearestIndex, nNearestPosition, nSecondBound, + nSecondIndex, nSecondPositionPx, bReverse, nSecondDiff); + SAL_INFO("sc.lok.header", "BoundsProvider: nSecondIndex: " << nSecondIndex + << ", nSecondPositionPx: " << nSecondPositionPx); + + if (bReverse) + { + std::swap(nFirstIndex, nSecondIndex); + std::swap(nFirstPositionPx, nSecondPositionPx); + } +} + +void ScBoundsProvider::EnlargeStartBy(tools::Long nOffset) +{ + const index_type nNewFirstIndex = + std::max(static_cast<index_type>(-1), + static_cast<index_type>(nFirstIndex - nOffset)); + for (index_type nIndex = nFirstIndex; nIndex > nNewFirstIndex; --nIndex) + { + const tools::Long nSizePx = GetSize(nIndex); + nFirstPositionPx -= nSizePx; + } + nFirstIndex = nNewFirstIndex; + SAL_INFO("sc.lok.header", "BoundsProvider: added offset: nFirstIndex: " << nFirstIndex + << ", nFirstPositionPx: " << nFirstPositionPx); +} + +void ScBoundsProvider::EnlargeEndBy(tools::Long nOffset) +{ + const index_type nNewSecondIndex = std::min(MAX_INDEX, static_cast<index_type>(nSecondIndex + nOffset)); + for (index_type nIndex = nSecondIndex + 1; nIndex <= nNewSecondIndex; ++nIndex) + { + const tools::Long nSizePx = GetSize(nIndex); + nSecondPositionPx += nSizePx; + } + nSecondIndex = nNewSecondIndex; + SAL_INFO("sc.lok.header", "BoundsProvider: added offset: nSecondIndex: " << nSecondIndex + << ", nSecondPositionPx: " << nSecondPositionPx); +} + +void ScBoundsProvider::GeIndexBackwards( + index_type nNearestIndex, tools::Long nNearestPosition, + tools::Long nBound, index_type& nFoundIndex, tools::Long& nPosition, bool bTowards) +{ + nFoundIndex = -1; + for (index_type nIndex = nNearestIndex; nIndex >= 0; --nIndex) + { + if (nBound >= nNearestPosition) + { + nFoundIndex = nIndex; // last index whose nPosition is less than nBound + nPosition = nNearestPosition; + break; + } + + const tools::Long nSizePx = GetSize(nIndex); + nNearestPosition -= nSizePx; + } + if (!bTowards && nFoundIndex != -1) + { + nFoundIndex += 1; + nPosition += GetSize(nFoundIndex); + } +} + +void ScBoundsProvider::GetIndexTowards( + index_type nNearestIndex, tools::Long nNearestPosition, + tools::Long nBound, index_type& nFoundIndex, tools::Long& nPosition, bool bTowards) +{ + nFoundIndex = -2; + for (index_type nIndex = nNearestIndex + 1; nIndex <= MAX_INDEX; ++nIndex) + { + const tools::Long nSizePx = GetSize(nIndex); + nNearestPosition += nSizePx; + + if (nNearestPosition > nBound) + { + nFoundIndex = nIndex; // first index whose nPosition is greater than nBound + nPosition = nNearestPosition; + break; + } + } + if (nFoundIndex == -2) + { + nFoundIndex = MAX_INDEX; + nPosition = nNearestPosition; + } + else if (bTowards) + { + nPosition -= GetSize(nFoundIndex); + nFoundIndex -= 1; + } +} + +ScViewDataTable::ScViewDataTable(const ScDocument *pDoc) : + eZoomType( SvxZoomType::PERCENT ), + aZoomX( 1,1 ), + aZoomY( 1,1 ), + aPageZoomX( 3,5 ), // Page-Default: 60% + aPageZoomY( 3,5 ), + nHSplitPos( 0 ), + nVSplitPos( 0 ), + eHSplitMode( SC_SPLIT_NONE ), + eVSplitMode( SC_SPLIT_NONE ), + eWhichActive( SC_SPLIT_BOTTOMLEFT ), + nFixPosX( 0 ), + nFixPosY( 0 ), + nCurX( 0 ), + nCurY( 0 ), + nOldCurX( 0 ), + nOldCurY( 0 ), + aWidthHelper(pDoc, true), + aHeightHelper(pDoc, false), + nMaxTiledCol( 20 ), + nMaxTiledRow( 50 ), + bShowGrid( true ), + mbOldCursorValid( false ) +{ + nPosX[0]=nPosX[1]=0; + nPosY[0]=nPosY[1]=0; + nTPosX[0]=nTPosX[1]=0; + nTPosY[0]=nTPosY[1]=0; + nMPosX[0]=nMPosX[1]=0; + nMPosY[0]=nMPosY[1]=0; + nPixPosX[0]=nPixPosX[1]=0; + nPixPosY[0]=nPixPosY[1]=0; +} + +void ScViewDataTable::InitData(const ScDocument& rDoc) +{ + aWidthHelper.setDocument(rDoc, true); + aHeightHelper.setDocument(rDoc, false); +} + +void ScViewDataTable::WriteUserDataSequence(uno::Sequence <beans::PropertyValue>& rSettings, const ScViewData& rViewData, SCTAB nTab) const +{ + rSettings.realloc(SC_TABLE_VIEWSETTINGS_COUNT); + beans::PropertyValue* pSettings = rSettings.getArray(); + + ScSplitMode eExHSplitMode = eHSplitMode; + ScSplitMode eExVSplitMode = eVSplitMode; + SCCOL nExFixPosX = nFixPosX; + SCROW nExFixPosY = nFixPosY; + tools::Long nExHSplitPos = nHSplitPos; + tools::Long nExVSplitPos = nVSplitPos; + + if (comphelper::LibreOfficeKit::isActive()) + { + rViewData.OverrideWithLOKFreeze(eExHSplitMode, eExVSplitMode, + nExFixPosX, nExFixPosY, + nExHSplitPos, nExVSplitPos, nTab); + } + + pSettings[SC_CURSOR_X].Name = SC_CURSORPOSITIONX; + pSettings[SC_CURSOR_X].Value <<= sal_Int32(nCurX); + pSettings[SC_CURSOR_Y].Name = SC_CURSORPOSITIONY; + pSettings[SC_CURSOR_Y].Value <<= sal_Int32(nCurY); + + // Write freezepan data only when freeze pans are set + if(nExFixPosX != 0 || nExFixPosY != 0 || nExHSplitPos != 0 || nExVSplitPos != 0) + { + pSettings[SC_HORIZONTAL_SPLIT_MODE].Name = SC_HORIZONTALSPLITMODE; + pSettings[SC_HORIZONTAL_SPLIT_MODE].Value <<= sal_Int16(eExHSplitMode); + pSettings[SC_VERTICAL_SPLIT_MODE].Name = SC_VERTICALSPLITMODE; + pSettings[SC_VERTICAL_SPLIT_MODE].Value <<= sal_Int16(eExVSplitMode); + pSettings[SC_HORIZONTAL_SPLIT_POSITION].Name = SC_HORIZONTALSPLITPOSITION; + if (eExHSplitMode == SC_SPLIT_FIX) + pSettings[SC_HORIZONTAL_SPLIT_POSITION].Value <<= sal_Int32(nExFixPosX); + else + pSettings[SC_HORIZONTAL_SPLIT_POSITION].Value <<= sal_Int32(nExHSplitPos); + pSettings[SC_VERTICAL_SPLIT_POSITION].Name = SC_VERTICALSPLITPOSITION; + if (eExVSplitMode == SC_SPLIT_FIX) + pSettings[SC_VERTICAL_SPLIT_POSITION].Value <<= sal_Int32(nExFixPosY); + else + pSettings[SC_VERTICAL_SPLIT_POSITION].Value <<= sal_Int32(nExVSplitPos); + } + + // Prevent writing odd settings that would make crash versions that + // don't apply SanitizeWhichActive() when reading the settings. + // See tdf#117093 + const ScSplitPos eActiveSplitRange = SanitizeWhichActive(); + // And point out to give us a chance to inspect weird things (if anyone + // remembers what s/he did). + assert(eWhichActive == eActiveSplitRange); + pSettings[SC_ACTIVE_SPLIT_RANGE].Name = SC_ACTIVESPLITRANGE; + pSettings[SC_ACTIVE_SPLIT_RANGE].Value <<= sal_Int16(eActiveSplitRange); + pSettings[SC_POSITION_LEFT].Name = SC_POSITIONLEFT; + pSettings[SC_POSITION_LEFT].Value <<= sal_Int32(nPosX[SC_SPLIT_LEFT]); + pSettings[SC_POSITION_RIGHT].Name = SC_POSITIONRIGHT; + pSettings[SC_POSITION_RIGHT].Value <<= sal_Int32(nPosX[SC_SPLIT_RIGHT]); + pSettings[SC_POSITION_TOP].Name = SC_POSITIONTOP; + pSettings[SC_POSITION_TOP].Value <<= sal_Int32(nPosY[SC_SPLIT_TOP]); + pSettings[SC_POSITION_BOTTOM].Name = SC_POSITIONBOTTOM; + pSettings[SC_POSITION_BOTTOM].Value <<= sal_Int32(nPosY[SC_SPLIT_BOTTOM]); + + sal_Int32 nZoomValue = tools::Long(aZoomY * 100); + sal_Int32 nPageZoomValue = tools::Long(aPageZoomY * 100); + pSettings[SC_TABLE_ZOOM_TYPE].Name = SC_ZOOMTYPE; + pSettings[SC_TABLE_ZOOM_TYPE].Value <<= sal_Int16(eZoomType); + pSettings[SC_TABLE_ZOOM_VALUE].Name = SC_ZOOMVALUE; + pSettings[SC_TABLE_ZOOM_VALUE].Value <<= nZoomValue; + pSettings[SC_TABLE_PAGE_VIEW_ZOOM_VALUE].Name = SC_PAGEVIEWZOOMVALUE; + pSettings[SC_TABLE_PAGE_VIEW_ZOOM_VALUE].Value <<= nPageZoomValue; + + pSettings[SC_TABLE_SHOWGRID].Name = SC_UNO_SHOWGRID; + pSettings[SC_TABLE_SHOWGRID].Value <<= bShowGrid; + + // Common SdrModel processing + rViewData.GetDocument().GetDrawLayer()->WriteUserDataSequence(rSettings); +} + +void ScViewDataTable::ReadUserDataSequence(const uno::Sequence <beans::PropertyValue>& aSettings, ScViewData& rViewData, SCTAB nTab, bool& rHasZoom ) +{ + rHasZoom = false; + + sal_Int32 nTemp32(0); + sal_Int16 nTemp16(0); + sal_Int32 nTempPosV(0); + sal_Int32 nTempPosH(0); + sal_Int32 nTempPosVTw(0); + sal_Int32 nTempPosHTw(0); + bool bHasVSplitInTwips = false; + bool bHasHSplitInTwips = false; + for (const auto& rSetting : aSettings) + { + OUString sName(rSetting.Name); + if (sName == SC_CURSORPOSITIONX) + { + rSetting.Value >>= nTemp32; + nCurX = rViewData.GetDocument().SanitizeCol( static_cast<SCCOL>(nTemp32)); + } + else if (sName == SC_CURSORPOSITIONY) + { + rSetting.Value >>= nTemp32; + nCurY = rViewData.GetDocument().SanitizeRow( static_cast<SCROW>(nTemp32)); + } + else if (sName == SC_HORIZONTALSPLITMODE) + { + if ((rSetting.Value >>= nTemp16) && nTemp16 <= ScSplitMode::SC_SPLIT_MODE_MAX_ENUM) + eHSplitMode = static_cast<ScSplitMode>(nTemp16); + } + else if (sName == SC_VERTICALSPLITMODE) + { + if ((rSetting.Value >>= nTemp16) && nTemp16 <= ScSplitMode::SC_SPLIT_MODE_MAX_ENUM) + eVSplitMode = static_cast<ScSplitMode>(nTemp16); + } + else if (sName == SC_HORIZONTALSPLITPOSITION) + { + rSetting.Value >>= nTempPosH; + bHasHSplitInTwips = false; + } + else if (sName == SC_VERTICALSPLITPOSITION) + { + rSetting.Value >>= nTempPosV; + bHasVSplitInTwips = false; + } + else if (sName == SC_HORIZONTALSPLITPOSITION_TWIPS) + { + rSetting.Value >>= nTempPosHTw; + bHasHSplitInTwips = true; + } + else if (sName == SC_VERTICALSPLITPOSITION_TWIPS) + { + rSetting.Value >>= nTempPosVTw; + bHasVSplitInTwips = true; + } + else if (sName == SC_ACTIVESPLITRANGE) + { + if ((rSetting.Value >>= nTemp16) && nTemp16 <= ScSplitPos::SC_SPLIT_POS_MAX_ENUM) + eWhichActive = static_cast<ScSplitPos>(nTemp16); + } + else if (sName == SC_POSITIONLEFT) + { + rSetting.Value >>= nTemp32; + nPosX[SC_SPLIT_LEFT] = rViewData.GetDocument().SanitizeCol( static_cast<SCCOL>(nTemp32)); + } + else if (sName == SC_POSITIONRIGHT) + { + rSetting.Value >>= nTemp32; + nPosX[SC_SPLIT_RIGHT] = rViewData.GetDocument().SanitizeCol( static_cast<SCCOL>(nTemp32)); + } + else if (sName == SC_POSITIONTOP) + { + rSetting.Value >>= nTemp32; + nPosY[SC_SPLIT_TOP] = rViewData.GetDocument().SanitizeRow( static_cast<SCROW>(nTemp32)); + } + else if (sName == SC_POSITIONBOTTOM) + { + rSetting.Value >>= nTemp32; + nPosY[SC_SPLIT_BOTTOM] = rViewData.GetDocument().SanitizeRow( static_cast<SCROW>(nTemp32)); + } + else if (sName == SC_ZOOMTYPE) + { + rSetting.Value >>= nTemp16; + eZoomType = SvxZoomType(nTemp16); + rHasZoom = true; // set if there is any zoom information + } + else if (sName == SC_ZOOMVALUE) + { + rSetting.Value >>= nTemp32; + Fraction aZoom(nTemp32, 100); + aZoomX = aZoomY = aZoom; + rHasZoom = true; + } + else if (sName == SC_PAGEVIEWZOOMVALUE) + { + rSetting.Value >>= nTemp32; + Fraction aZoom(nTemp32, 100); + aPageZoomX = aPageZoomY = aZoom; + rHasZoom = true; + } + else if (sName == SC_UNO_SHOWGRID) + { + rSetting.Value >>= bShowGrid; + } + else if (sName == SC_TABLESELECTED) + { + bool bSelected = false; + rSetting.Value >>= bSelected; + rViewData.GetMarkData().SelectTable( nTab, bSelected ); + } + else if (sName == SC_UNONAME_TABCOLOR) + { + // There are documents out there that have their tab color defined as a view setting. + Color aColor = COL_AUTO; + rSetting.Value >>= aColor; + if (aColor != COL_AUTO) + { + ScDocument& rDoc = rViewData.GetDocument(); + rDoc.SetTabBgColor(nTab, aColor); + } + } + // Fallback to common SdrModel processing + else rViewData.GetDocument().GetDrawLayer()->ReadUserDataSequenceValue(&rSetting); + } + + if (eHSplitMode == SC_SPLIT_FIX) + nFixPosX = rViewData.GetDocument().SanitizeCol( static_cast<SCCOL>( bHasHSplitInTwips ? nTempPosHTw : nTempPosH )); + else + nHSplitPos = bHasHSplitInTwips ? static_cast< tools::Long >( nTempPosHTw * rViewData.GetPPTX() ) : nTempPosH; + + if (eVSplitMode == SC_SPLIT_FIX) + nFixPosY = rViewData.GetDocument().SanitizeRow( static_cast<SCROW>( bHasVSplitInTwips ? nTempPosVTw : nTempPosV )); + else + nVSplitPos = bHasVSplitInTwips ? static_cast< tools::Long >( nTempPosVTw * rViewData.GetPPTY() ) : nTempPosV; + + eWhichActive = SanitizeWhichActive(); +} + +ScSplitPos ScViewDataTable::SanitizeWhichActive() const +{ + if ((WhichH(eWhichActive) == SC_SPLIT_RIGHT && eHSplitMode == SC_SPLIT_NONE) || + (WhichV(eWhichActive) == SC_SPLIT_TOP && eVSplitMode == SC_SPLIT_NONE)) + { + SAL_WARN("sc.ui","ScViewDataTable::SanitizeWhichActive - bad eWhichActive " << eWhichActive); + // The default always initialized grid window is SC_SPLIT_BOTTOMLEFT. + return SC_SPLIT_BOTTOMLEFT; + } + return eWhichActive; +} + +ScViewData::ScViewData(ScDocShell& rDocSh, ScTabViewShell* pViewSh) + : ScViewData(nullptr, &rDocSh, pViewSh) +{ +} + +ScViewData::ScViewData(ScDocument& rDoc) + : ScViewData(&rDoc, nullptr, nullptr) +{ +} + +static ScViewOptions DefaultOptions() +{ + ScViewOptions aOptions; + aOptions.SetOption(VOPT_GRID, true); + aOptions.SetOption(VOPT_SYNTAX, false); + aOptions.SetOption(VOPT_HEADER, true); + aOptions.SetOption(VOPT_TABCONTROLS, true); + aOptions.SetOption(VOPT_VSCROLL, true); + aOptions.SetOption(VOPT_HSCROLL, true); + aOptions.SetOption(VOPT_OUTLINER, true); + return aOptions; +} + +// Either pDoc or pDocSh must be valid +ScViewData::ScViewData(ScDocument* pDoc, ScDocShell* pDocSh, ScTabViewShell* pViewSh) : + nPPTX(0.0), + nPPTY(0.0), + maMarkData (pDocSh ? pDocSh->GetDocument().GetSheetLimits() : pDoc->GetSheetLimits()), + maHighlightData (pDocSh ? pDocSh->GetDocument().GetSheetLimits() : pDoc->GetSheetLimits()), + pDocShell ( pDocSh ), + mrDoc (pDocSh ? pDocSh->GetDocument() : *pDoc), + pView ( pViewSh ), + maOptions (pDocSh ? pDocSh->GetDocument().GetViewOptions() : DefaultOptions()), + pSpellingView ( nullptr ), + aLogicMode ( MapUnit::Map100thMM ), + eDefZoomType( SvxZoomType::PERCENT ), + aDefZoomX ( 1,1 ), + aDefZoomY ( 1,1 ), + aDefPageZoomX( 3,5 ), + aDefPageZoomY( 3,5 ), + eRefType ( SC_REFTYPE_NONE ), + nTabNo ( 0 ), + nRefTabNo ( 0 ), + nRefStartX(0), + nRefStartY(0), + nRefStartZ(0), + nRefEndX(0), + nRefEndY(0), + nRefEndZ(0), + nFillStartX(0), + nFillStartY(0), + nFillEndX(0), + nFillEndY(0), + nPasteFlags ( ScPasteFlags::NONE ), + eEditActivePart( SC_SPLIT_BOTTOMLEFT ), + nFillMode ( ScFillMode::NONE ), + eEditAdjust ( SvxAdjust::Left ), + bActive ( true ), // how to initialize? + bIsRefMode ( false ), + bDelMarkValid( false ), + bPagebreak ( false ), + bSelCtrlMouseClick( false ), + bMoveArea ( false ), + bGrowing (false), + nFormulaBarLines(1), + m_nLOKPageUpDownOffset( 0 ) +{ + assert(bool(pDoc) != bool(pDocSh)); // either one or the other, not both + maMarkData.SelectOneTable(0); // Sync with nTabNo + + aScrSize = Size( o3tl::convert(STD_COL_WIDTH * OLE_STD_CELLS_X, o3tl::Length::twip, o3tl::Length::px), + o3tl::convert(mrDoc.GetSheetOptimalMinRowHeight(nTabNo) * OLE_STD_CELLS_Y, + o3tl::Length::twip, o3tl::Length::px)); + maTabData.emplace_back( new ScViewDataTable(nullptr) ); + pThisTab = maTabData[nTabNo].get(); + + nEditEndCol = nEditStartCol = nEditCol = 0; + nEditEndRow = nEditRow = 0; + nTabStartCol = SC_TABSTART_NONE; + + // don't show hidden tables + if (!mrDoc.IsVisible(nTabNo)) + { + while (!mrDoc.IsVisible(nTabNo) && mrDoc.HasTable(nTabNo + 1)) + { + ++nTabNo; + maTabData.emplace_back(nullptr); + } + maTabData[nTabNo].reset( new ScViewDataTable(nullptr) ); + pThisTab = maTabData[nTabNo].get(); + } + + SCTAB nTableCount = mrDoc.GetTableCount(); + EnsureTabDataSize(nTableCount); + + for (auto& xTabData : maTabData) + { + if (xTabData) + xTabData->InitData(mrDoc); + } + + CalcPPT(); +} + +ScViewData::~ScViewData() COVERITY_NOEXCEPT_FALSE +{ + KillEditView(); +} + +ScDBFunc* ScViewData::GetView() const { return pView; } + +void ScViewData::UpdateCurrentTab() +{ + assert(0 <= nTabNo && o3tl::make_unsigned(nTabNo) < maTabData.size()); + pThisTab = maTabData[nTabNo].get(); + while (!pThisTab) + { + if (nTabNo > 0) + pThisTab = maTabData[--nTabNo].get(); + else + { + maTabData[0].reset(new ScViewDataTable(&mrDoc)); + pThisTab = maTabData[0].get(); + } + } +} + +void ScViewData::InsertTab( SCTAB nTab ) +{ + if( nTab >= static_cast<SCTAB>(maTabData.size())) + maTabData.resize(nTab+1); + else + maTabData.insert( maTabData.begin() + nTab, nullptr ); + CreateTabData( nTab ); + + UpdateCurrentTab(); + maMarkData.InsertTab(nTab); + + collectUIInformation({{}}, "InsertTab"); +} + +void ScViewData::InsertTabs( SCTAB nTab, SCTAB nNewSheets ) +{ + if (nTab >= static_cast<SCTAB>(maTabData.size())) + maTabData.resize(nTab+nNewSheets); + else + { + // insert nNewSheets new tables at position nTab + auto prevSize = maTabData.size(); + maTabData.resize(prevSize + nNewSheets); + std::move_backward(maTabData.begin() + nTab, maTabData.begin() + prevSize, maTabData.end()); + } + for (SCTAB i = nTab; i < nTab + nNewSheets; ++i) + { + CreateTabData( i ); + maMarkData.InsertTab(i); + } + UpdateCurrentTab(); +} + +void ScViewData::DeleteTab( SCTAB nTab ) +{ + assert(nTab < static_cast<SCTAB>(maTabData.size())); + maTabData.erase(maTabData.begin() + nTab); + + if (o3tl::make_unsigned(nTabNo) >= maTabData.size()) + { + EnsureTabDataSize(1); + nTabNo = maTabData.size() - 1; + } + UpdateCurrentTab(); + maMarkData.DeleteTab(nTab); +} + +void ScViewData::DeleteTabs( SCTAB nTab, SCTAB nSheets ) +{ + for (SCTAB i = 0; i < nSheets; ++i) + { + maMarkData.DeleteTab(nTab + i); + } + maTabData.erase(maTabData.begin() + nTab, maTabData.begin()+ nTab+nSheets); + if (o3tl::make_unsigned(nTabNo) >= maTabData.size()) + { + EnsureTabDataSize(1); + nTabNo = maTabData.size() - 1; + } + UpdateCurrentTab(); +} + +void ScViewData::CopyTab( SCTAB nSrcTab, SCTAB nDestTab ) +{ + if (nDestTab==SC_TAB_APPEND) + nDestTab = mrDoc.GetTableCount() - 1; // something had to have been copied + + if (nDestTab > MAXTAB) + { + OSL_FAIL("too many sheets"); + return; + } + + if (nSrcTab >= static_cast<SCTAB>(maTabData.size())) + OSL_FAIL("pTabData out of bounds, FIX IT"); + + EnsureTabDataSize(nDestTab + 1); + + if ( maTabData[nSrcTab] ) + maTabData.emplace(maTabData.begin() + nDestTab, new ScViewDataTable( *maTabData[nSrcTab] )); + else + maTabData.insert(maTabData.begin() + nDestTab, nullptr); + + UpdateCurrentTab(); + maMarkData.InsertTab(nDestTab); +} + +void ScViewData::MoveTab( SCTAB nSrcTab, SCTAB nDestTab ) +{ + if (nDestTab==SC_TAB_APPEND) + nDestTab = mrDoc.GetTableCount() - 1; + std::unique_ptr<ScViewDataTable> pTab; + if (nSrcTab < static_cast<SCTAB>(maTabData.size())) + { + pTab = std::move(maTabData[nSrcTab]); + maTabData.erase( maTabData.begin() + nSrcTab ); + } + + if (nDestTab < static_cast<SCTAB>(maTabData.size())) + maTabData.insert( maTabData.begin() + nDestTab, std::move(pTab) ); + else + { + EnsureTabDataSize(nDestTab + 1); + maTabData[nDestTab] = std::move(pTab); + } + + UpdateCurrentTab(); + maMarkData.DeleteTab(nSrcTab); + maMarkData.InsertTab(nDestTab); // adapted if needed +} + +void ScViewData::CreateTabData( std::vector< SCTAB >& rvTabs ) +{ + for ( const auto& rTab : rvTabs ) + CreateTabData(rTab); +} + +void ScViewData::SetZoomType( SvxZoomType eNew, std::vector< SCTAB >& tabs ) +{ + bool bAll = tabs.empty(); + + if ( !bAll ) // create associated table data + CreateTabData( tabs ); + + if ( bAll ) + { + for ( auto & i: maTabData ) + { + if ( i ) + i->eZoomType = eNew; + } + eDefZoomType = eNew; + } + else + { + for ( const SCTAB& i : tabs ) + { + if ( i < static_cast<SCTAB>(maTabData.size()) && maTabData[i] ) + maTabData[i]->eZoomType = eNew; + } + } +} + +void ScViewData::SetZoomType( SvxZoomType eNew, bool bAll ) +{ + std::vector< SCTAB > vTabs; // Empty for all tabs + if ( !bAll ) // get selected tabs + { + ScMarkData::const_iterator itr = maMarkData.begin(), itrEnd = maMarkData.end(); + vTabs.insert(vTabs.begin(), itr, itrEnd); + } + SetZoomType( eNew, vTabs ); +} + +void ScViewData::SetZoom( const Fraction& rNewX, const Fraction& rNewY, std::vector< SCTAB >& tabs ) +{ + bool bAll = tabs.empty(); + if ( !bAll ) // create associated table data + CreateTabData( tabs ); + + // sanity check - we shouldn't need something this low / big + SAL_WARN_IF(rNewX < Fraction(1, 100) || rNewX > Fraction(100, 1), "sc.viewdata", + "fraction rNewX not sensible: " << static_cast<double>(rNewX)); + SAL_WARN_IF(rNewY < Fraction(1, 100) || rNewY > Fraction(100, 1), "sc.viewdata", + "fraction rNewY not sensible: " << static_cast<double>(rNewY)); + + if ( bAll ) + { + for ( auto & i: maTabData ) + { + if ( i ) + { + if ( bPagebreak ) + { + i->aPageZoomX = rNewX; + i->aPageZoomY = rNewY; + } + else + { + i->aZoomX = rNewX; + i->aZoomY = rNewY; + } + } + } + if ( bPagebreak ) + { + aDefPageZoomX = rNewX; + aDefPageZoomY = rNewY; + } + else + { + aDefZoomX = rNewX; + aDefZoomY = rNewY; + } + } + else + { + for ( const SCTAB& i : tabs ) + { + if ( i < static_cast<SCTAB>(maTabData.size()) && maTabData[i] ) + { + if ( bPagebreak ) + { + maTabData[i]->aPageZoomX = rNewX; + maTabData[i]->aPageZoomY = rNewY; + } + else + { + maTabData[i]->aZoomX = rNewX; + maTabData[i]->aZoomY = rNewY; + } + } + } + } + RefreshZoom(); +} + +void ScViewData::SetZoom( const Fraction& rNewX, const Fraction& rNewY, bool bAll ) +{ + std::vector< SCTAB > vTabs; + if ( !bAll ) // get selected tabs + { + ScMarkData::const_iterator itr = maMarkData.begin(), itrEnd = maMarkData.end(); + vTabs.insert(vTabs.begin(), itr, itrEnd); + } + SetZoom( rNewX, rNewY, vTabs ); +} + +void ScViewData::SetShowGrid( bool bShow ) +{ + CreateSelectedTabData(); + maTabData[nTabNo]->bShowGrid = bShow; +} + +void ScViewData::RefreshZoom() +{ + // recalculate zoom-dependent values (only for current sheet) + + CalcPPT(); + RecalcPixPos(); + aScenButSize = Size(0,0); + aLogicMode.SetScaleX( GetZoomX() ); + aLogicMode.SetScaleY( GetZoomY() ); +} + +void ScViewData::SetPagebreakMode( bool bSet ) +{ + bPagebreak = bSet; + + RefreshZoom(); +} + +ScMarkType ScViewData::GetSimpleArea( ScRange & rRange, ScMarkData & rNewMark ) const +{ + ScMarkType eMarkType = SC_MARK_NONE; + + if ( rNewMark.IsMarked() || rNewMark.IsMultiMarked() ) + { + if ( rNewMark.IsMultiMarked() ) + rNewMark.MarkToSimple(); + + if ( rNewMark.IsMarked() && !rNewMark.IsMultiMarked() ) + { + rRange = rNewMark.GetMarkArea(); + if (ScViewUtil::HasFiltered(rRange, GetDocument())) + eMarkType = SC_MARK_SIMPLE_FILTERED; + else + eMarkType = SC_MARK_SIMPLE; + } + else + eMarkType = SC_MARK_MULTI; + } + if (eMarkType != SC_MARK_SIMPLE && eMarkType != SC_MARK_SIMPLE_FILTERED) + { + if (eMarkType == SC_MARK_NONE) + eMarkType = SC_MARK_SIMPLE; + const ScPatternAttr* pMarkPattern = mrDoc.GetPattern(GetCurX(), GetCurY(), GetTabNo()); + if (pMarkPattern && pMarkPattern->GetItemSet().GetItemState(ATTR_MERGE, false) == SfxItemState::SET) + { + SCROW nRow = pMarkPattern->GetItem(ATTR_MERGE).GetRowMerge(); + SCCOL nCol = pMarkPattern->GetItem(ATTR_MERGE).GetColMerge(); + if ( nRow < 1 || nCol < 1 ) + { + // This kind of cells do exist. Not sure if that is intended or a bug. + rRange = ScRange(GetCurX(), GetCurY(), GetTabNo()); + } + else + { + rRange = ScRange(GetCurX(), GetCurY(), GetTabNo(), + GetCurX() + nCol - 1, GetCurY() + nRow - 1, GetTabNo()); + if ( ScViewUtil::HasFiltered(rRange, GetDocument()) ) + eMarkType = SC_MARK_SIMPLE_FILTERED; + } + } + else + rRange = ScRange(GetCurX(), GetCurY(), GetTabNo()); + } + return eMarkType; +} + +ScMarkType ScViewData::GetSimpleArea( SCCOL& rStartCol, SCROW& rStartRow, SCTAB& rStartTab, + SCCOL& rEndCol, SCROW& rEndRow, SCTAB& rEndTab ) const +{ + // parameter bMergeMark is no longer needed: The view's selection is never modified + // (a local copy is used), and a multi selection that adds to a single range can always + // be treated like a single selection (GetSimpleArea isn't used in selection + // handling itself) + + ScRange aRange; + ScMarkData aNewMark(maMarkData); // use a local copy for MarkToSimple + ScMarkType eMarkType = GetSimpleArea( aRange, aNewMark); + aRange.GetVars( rStartCol, rStartRow, rStartTab, rEndCol, rEndRow, rEndTab); + return eMarkType; +} + +ScMarkType ScViewData::GetSimpleArea( ScRange& rRange ) const +{ + // parameter bMergeMark is no longer needed, see above + + ScMarkData aNewMark(maMarkData); // use a local copy for MarkToSimple + return GetSimpleArea( rRange, aNewMark); +} + +void ScViewData::GetMultiArea( ScRangeListRef& rRange ) const +{ + // parameter bMergeMark is no longer needed, see GetSimpleArea + + ScMarkData aNewMark(maMarkData); // use a local copy for MarkToSimple + + bool bMulti = aNewMark.IsMultiMarked(); + if (bMulti) + { + aNewMark.MarkToSimple(); + bMulti = aNewMark.IsMultiMarked(); + } + if (bMulti) + { + rRange = new ScRangeList; + aNewMark.FillRangeListWithMarks( rRange.get(), false ); + } + else + { + ScRange aSimple; + GetSimpleArea(aSimple); + rRange = new ScRangeList(aSimple); + } +} + +bool ScViewData::SimpleColMarked() +{ + SCCOL nStartCol; + SCROW nStartRow; + SCTAB nStartTab; + SCCOL nEndCol; + SCROW nEndRow; + SCTAB nEndTab; + if (GetSimpleArea(nStartCol,nStartRow,nStartTab,nEndCol,nEndRow,nEndTab) == SC_MARK_SIMPLE) + if (nStartRow == 0 && nEndRow == mrDoc.MaxRow()) + return true; + + return false; +} + +bool ScViewData::SimpleRowMarked() +{ + SCCOL nStartCol; + SCROW nStartRow; + SCTAB nStartTab; + SCCOL nEndCol; + SCROW nEndRow; + SCTAB nEndTab; + if (GetSimpleArea(nStartCol,nStartRow,nStartTab,nEndCol,nEndRow,nEndTab) == SC_MARK_SIMPLE) + if (nStartCol == 0 && nEndCol == mrDoc.MaxCol()) + return true; + + return false; +} + +bool ScViewData::IsMultiMarked() const +{ + // Test for "real" multi selection, calling MarkToSimple on a local copy, + // and taking filtered in simple area marks into account. + + ScRange aDummy; + ScMarkType eType = GetSimpleArea(aDummy); + return (eType & SC_MARK_SIMPLE) != SC_MARK_SIMPLE; +} + +bool ScViewData::SelectionForbidsPaste( ScDocument* pClipDoc ) +{ + if (!pClipDoc) + { + // Same as checkDestRanges() in sc/source/ui/view/cellsh.cxx but + // different return details. + + vcl::Window* pWin = GetActiveWin(); + if (!pWin) + // No window doesn't mean paste would be forbidden. + return false; + + const ScTransferObj* pOwnClip = ScTransferObj::GetOwnClipboard(ScTabViewShell::GetClipData(pWin)); + if (!pOwnClip) + // Foreign content does not get repeatedly replicated. + return false; + + pClipDoc = pOwnClip->GetDocument(); + if (!pClipDoc) + // No clipdoc doesn't mean paste would be forbidden. + return false; + } + + const ScRange aSrcRange = pClipDoc->GetClipParam().getWholeRange(); + const SCROW nRowSize = aSrcRange.aEnd.Row() - aSrcRange.aStart.Row() + 1; + const SCCOL nColSize = aSrcRange.aEnd.Col() - aSrcRange.aStart.Col() + 1; + + return SelectionForbidsPaste( nColSize, nRowSize); +} + +bool ScViewData::SelectionForbidsPaste( SCCOL nSrcCols, SCROW nSrcRows ) +{ + ScRange aSelRange( ScAddress::UNINITIALIZED ); + ScMarkType eMarkType = GetSimpleArea( aSelRange); + + if (eMarkType == SC_MARK_MULTI) + // Not because of DOOM. + return false; + + if (aSelRange.aEnd.Row() - aSelRange.aStart.Row() + 1 == nSrcRows) + // This also covers entire col(s) copied to be pasted on entire cols. + return false; + + if (aSelRange.aEnd.Col() - aSelRange.aStart.Col() + 1 == nSrcCols) + // This also covers entire row(s) copied to be pasted on entire rows. + return false; + + return SelectionFillDOOM( aSelRange); +} + +bool ScViewData::SelectionForbidsCellFill() +{ + ScRange aSelRange( ScAddress::UNINITIALIZED ); + ScMarkType eMarkType = GetSimpleArea( aSelRange); + return eMarkType != SC_MARK_MULTI && SelectionFillDOOM( aSelRange); +} + +// static +bool ScViewData::SelectionFillDOOM( const ScRange& rRange ) +{ + // Assume that more than 23 full columns (23M cells) will not be + // successful... Even with only 10 bytes per cell that would already be + // 230MB, formula cells would be 100 bytes and more per cell. + // rows * columns > 23m => rows > 23m / columns + // to not overflow in case number of available columns or rows would be + // arbitrarily increased. + // We could refine this and take some actual cell size into account, + // evaluate available memory and what not, but... + const sal_Int32 kMax = 23 * 1024 * 1024; // current MAXROWCOUNT1 is 1024*1024=1048576 + return (rRange.aEnd.Row() - rRange.aStart.Row() + 1) > (kMax / (rRange.aEnd.Col() - rRange.aStart.Col() + 1)); +} + +void ScViewData::SetFillMode( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow ) +{ + nFillMode = ScFillMode::FILL; + nFillStartX = nStartCol; + nFillStartY = nStartRow; + nFillEndX = nEndCol; + nFillEndY = nEndRow; +} + +void ScViewData::SetDragMode( SCCOL nStartCol, SCROW nStartRow, SCCOL nEndCol, SCROW nEndRow, + ScFillMode nMode ) +{ + nFillMode = nMode; + nFillStartX = nStartCol; + nFillStartY = nStartRow; + nFillEndX = nEndCol; + nFillEndY = nEndRow; +} + +void ScViewData::ResetFillMode() +{ + nFillMode = ScFillMode::NONE; +} + +void ScViewData::GetFillData( SCCOL& rStartCol, SCROW& rStartRow, + SCCOL& rEndCol, SCROW& rEndRow ) +{ + rStartCol = nFillStartX; + rStartRow = nFillStartY; + rEndCol = nFillEndX; + rEndRow = nFillEndY; +} + +SCCOL ScViewData::GetOldCurX() const +{ + if (pThisTab->mbOldCursorValid) + return pThisTab->nOldCurX; + else + return pThisTab->nCurX; +} + +SCROW ScViewData::GetOldCurY() const +{ + if (pThisTab->mbOldCursorValid) + return pThisTab->nOldCurY; + else + return pThisTab->nCurY; +} + +void ScViewData::SetOldCursor( SCCOL nNewX, SCROW nNewY ) +{ + pThisTab->nOldCurX = nNewX; + pThisTab->nOldCurY = nNewY; + pThisTab->mbOldCursorValid = true; +} + +void ScViewData::ResetOldCursor() +{ + pThisTab->mbOldCursorValid = false; +} + +SCCOL ScViewData::GetPosX( ScHSplitPos eWhich, SCTAB nForTab ) const +{ + if (comphelper::LibreOfficeKit::isActive()) + return 0; + + if (nForTab == -1) + return pThisTab->nPosX[eWhich]; + + if (!ValidTab(nForTab) || (nForTab >= static_cast<SCTAB>(maTabData.size()))) + return -1; + + return maTabData[nForTab]->nPosX[eWhich]; +} + +SCROW ScViewData::GetPosY( ScVSplitPos eWhich, SCTAB nForTab ) const +{ + if (comphelper::LibreOfficeKit::isActive()) + return 0; + + if (nForTab == -1) + return pThisTab->nPosY[eWhich]; + + if (!ValidTab(nForTab) || (nForTab >= static_cast<SCTAB>(maTabData.size()))) + return -1; + + return maTabData[nForTab]->nPosY[eWhich]; +} + +ScViewDataTable* ScViewData::FetchTableData(SCTAB nTabIndex) const +{ + if (!ValidTab(nTabIndex) || (nTabIndex >= static_cast<SCTAB>(maTabData.size()))) + return nullptr; + ScViewDataTable* pRet = maTabData[nTabIndex].get(); + SAL_WARN_IF(!pRet, "sc.viewdata", "ScViewData::FetchTableData: hidden sheet = " << nTabIndex); + return pRet; +} + +SCCOL ScViewData::GetCurXForTab( SCTAB nTabIndex ) const +{ + ScViewDataTable* pTabData = FetchTableData(nTabIndex); + return pTabData ? pTabData->nCurX : -1; +} + +SCROW ScViewData::GetCurYForTab( SCTAB nTabIndex ) const +{ + ScViewDataTable* pTabData = FetchTableData(nTabIndex); + return pTabData ? pTabData->nCurY : -1; +} + +void ScViewData::SetCurXForTab( SCCOL nNewCurX, SCTAB nTabIndex ) +{ + if (ScViewDataTable* pTabData = FetchTableData(nTabIndex)) + pTabData->nCurX = nNewCurX; +} + +void ScViewData::SetCurYForTab( SCCOL nNewCurY, SCTAB nTabIndex ) +{ + if (ScViewDataTable* pTabData = FetchTableData(nTabIndex)) + pTabData->nCurY = nNewCurY; +} + +void ScViewData::SetMaxTiledCol( SCCOL nNewMaxCol ) +{ + nNewMaxCol = std::clamp(nNewMaxCol, SCCOL(0), mrDoc.MaxCol()); + + const SCTAB nTab = GetTabNo(); + auto GetColWidthPx = [this, nTab](SCCOL nCol) { + const sal_uInt16 nSize = this->mrDoc.GetColWidth(nCol, nTab); + const tools::Long nSizePx = ScViewData::ToPixel(nSize, nPPTX); + return nSizePx; + }; + + tools::Long nTotalPixels = GetLOKWidthHelper().computePosition(nNewMaxCol, GetColWidthPx); + + SAL_INFO("sc.lok.docsize", "ScViewData::SetMaxTiledCol: nNewMaxCol: " + << nNewMaxCol << ", nTotalPixels: " << nTotalPixels); + + GetLOKWidthHelper().removeByIndex(pThisTab->nMaxTiledCol); + GetLOKWidthHelper().insert(nNewMaxCol, nTotalPixels); + + pThisTab->nMaxTiledCol = nNewMaxCol; +} + +void ScViewData::SetMaxTiledRow( SCROW nNewMaxRow ) +{ + if (nNewMaxRow < 0) + nNewMaxRow = 0; + if (nNewMaxRow > MAXTILEDROW) + nNewMaxRow = MAXTILEDROW; + + const SCTAB nTab = GetTabNo(); + auto GetRowHeightPx = [this, nTab](SCROW nRow) { + const sal_uInt16 nSize = this->mrDoc.GetRowHeight(nRow, nTab); + const tools::Long nSizePx = ScViewData::ToPixel(nSize, nPPTY); + return nSizePx; + }; + + tools::Long nTotalPixels = GetLOKHeightHelper().computePosition(nNewMaxRow, GetRowHeightPx); + + SAL_INFO("sc.lok.docsize", "ScViewData::SetMaxTiledRow: nNewMaxRow: " + << nNewMaxRow << ", nTotalPixels: " << nTotalPixels); + + GetLOKHeightHelper().removeByIndex(pThisTab->nMaxTiledRow); + GetLOKHeightHelper().insert(nNewMaxRow, nTotalPixels); + + pThisTab->nMaxTiledRow = nNewMaxRow; +} + +tools::Rectangle ScViewData::GetEditArea( ScSplitPos eWhich, SCCOL nPosX, SCROW nPosY, + vcl::Window* pWin, const ScPatternAttr* pPattern, + bool bForceToTop, bool bInPrintTwips ) +{ + Point aCellTopLeft = bInPrintTwips ? + GetPrintTwipsPos(nPosX, nPosY) : GetScrPos(nPosX, nPosY, eWhich, true); + return ScEditUtil(&mrDoc, nPosX, nPosY, nTabNo, aCellTopLeft, + pWin->GetOutDev(), nPPTX, nPPTY, GetZoomX(), GetZoomY(), bInPrintTwips ). + GetEditArea( pPattern, bForceToTop ); +} + +void ScViewData::SetEditEngine( ScSplitPos eWhich, + ScEditEngineDefaulter* pNewEngine, + vcl::Window* pWin, SCCOL nNewX, SCROW nNewY ) +{ + bool bLayoutRTL = mrDoc.IsLayoutRTL(nTabNo); + ScHSplitPos eHWhich = WhichH(eWhich); + ScVSplitPos eVWhich = WhichV(eWhich); + bool bLOKActive = comphelper::LibreOfficeKit::isActive(); + bool bLOKPrintTwips = bLOKActive && comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs); + bool bLOKLayoutRTL = bLOKActive && bLayoutRTL; + + bool bWasThere = false; + if (pEditView[eWhich]) + { + // if the view is already there don't call anything that changes the cursor position + if (bEditActive[eWhich]) + { + bWasThere = true; + } + else + { + lcl_LOKRemoveWindow(GetViewShell(), eWhich); + pEditView[eWhich]->SetEditEngine(pNewEngine); + } + + if (pEditView[eWhich]->GetWindow() != pWin) + { + lcl_LOKRemoveWindow(GetViewShell(), eWhich); + pEditView[eWhich]->SetWindow(pWin); + OSL_FAIL("EditView Window has changed"); + } + } + else + { + pEditView[eWhich].reset(new EditView( pNewEngine, pWin )); + + if (bLOKActive) + { + // We can broadcast the view-cursor message in print-twips for all views. + pEditView[eWhich]->SetBroadcastLOKViewCursor(bLOKPrintTwips); + pEditView[eWhich]->RegisterViewShell(pView); + } + } + + // add windows from other views + if (!bWasThere && bLOKActive) + { + ScTabViewShell* pThisViewShell = GetViewShell(); + SCTAB nThisTabNo = GetTabNo(); + auto lAddWindows = + [pThisViewShell, nThisTabNo, eWhich] (ScTabViewShell* pOtherViewShell) + { + ScViewData& rOtherViewData = pOtherViewShell->GetViewData(); + SCTAB nOtherTabNo = rOtherViewData.GetTabNo(); + if (nThisTabNo == nOtherTabNo) + pOtherViewShell->AddWindowToForeignEditView(pThisViewShell, eWhich); + }; + + SfxLokHelper::forEachOtherView(pThisViewShell, lAddWindows); + } + + // if view is gone then during IdleFormat sometimes a cursor is drawn + + EEControlBits nEC = pNewEngine->GetControlWord(); + pNewEngine->SetControlWord(nEC & ~EEControlBits::DOIDLEFORMAT); + + EVControlBits nVC = pEditView[eWhich]->GetControlWord(); + pEditView[eWhich]->SetControlWord(nVC & ~EVControlBits::AUTOSCROLL); + + bEditActive[eWhich] = true; + + const ScPatternAttr* pPattern = mrDoc.GetPattern(nNewX, nNewY, nTabNo); + SvxCellHorJustify eJust = pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue(); + + bool bBreak = ( eJust == SvxCellHorJustify::Block ) || + pPattern->GetItem(ATTR_LINEBREAK).GetValue(); + + bool bAsianVertical = pNewEngine->IsEffectivelyVertical(); // set by InputHandler + + tools::Rectangle aPixRect = ScEditUtil(&mrDoc, nNewX, nNewY, nTabNo, GetScrPos(nNewX, nNewY, eWhich), + pWin->GetOutDev(), nPPTX,nPPTY,GetZoomX(),GetZoomY() ). + GetEditArea( pPattern, true ); + + tools::Rectangle aPTwipsRect; + if (bLOKPrintTwips) + { + aPTwipsRect = ScEditUtil(&mrDoc, nNewX, nNewY, nTabNo, GetPrintTwipsPos(nNewX, nNewY), + pWin->GetOutDev(), nPPTX, nPPTY, GetZoomX(), GetZoomY(), true /* bInPrintTwips */). + GetEditArea(pPattern, true); + } + + // when right-aligned, leave space for the cursor + // in vertical mode, editing is always right-aligned + if ( GetEditAdjust() == SvxAdjust::Right || bAsianVertical ) + { + aPixRect.AdjustRight(1 ); + if (bLOKPrintTwips) + aPTwipsRect.AdjustRight(o3tl::convert(1, o3tl::Length::px, o3tl::Length::twip)); + } + + if (bLOKPrintTwips) + { + if (!pEditView[eWhich]->HasLOKSpecialPositioning()) + pEditView[eWhich]->InitLOKSpecialPositioning(MapUnit::MapTwip, aPTwipsRect, Point()); + else + pEditView[eWhich]->SetLOKSpecialOutputArea(aPTwipsRect); + } + + if (bLOKActive && pEditView[eWhich]->HasLOKSpecialPositioning()) + pEditView[eWhich]->SetLOKSpecialFlags(bLOKLayoutRTL ? LOKSpecialFlags::LayoutRTL : LOKSpecialFlags::NONE); + + tools::Rectangle aOutputArea = pWin->PixelToLogic( aPixRect, GetLogicMode() ); + pEditView[eWhich]->SetOutputArea( aOutputArea ); + + if ( bActive && eWhich == GetActivePart() ) + { + // keep the part that has the active edit view available after + // switching sheets or reference input on a different part + eEditActivePart = eWhich; + + // modify members nEditCol etc. only if also extending for needed area + nEditCol = nNewX; + nEditRow = nNewY; + const ScMergeAttr* pMergeAttr = &pPattern->GetItem(ATTR_MERGE); + nEditEndCol = nEditCol; + if (pMergeAttr->GetColMerge() > 1) + nEditEndCol += pMergeAttr->GetColMerge() - 1; + nEditEndRow = nEditRow; + if (pMergeAttr->GetRowMerge() > 1) + nEditEndRow += pMergeAttr->GetRowMerge() - 1; + nEditStartCol = nEditCol; + + // For growing use only the alignment value from the attribute, numbers + // (existing or started) with default alignment extend to the right. + bool bGrowCentered = ( eJust == SvxCellHorJustify::Center ); + bool bGrowToLeft = ( eJust == SvxCellHorJustify::Right ); // visual left + bool bLOKRTLInvert = (bLOKActive && bLayoutRTL); + if ( bAsianVertical ) + bGrowCentered = bGrowToLeft = false; // keep old behavior for asian mode + + tools::Long nSizeXPix, nSizeXPTwips = 0; + + const tools::Long nGridWidthPx = pView->GetGridWidth(eHWhich); + const tools::Long nGridHeightPx = pView->GetGridHeight(eVWhich); + tools::Long nGridWidthTwips = 0, nGridHeightTwips = 0; + if (bLOKPrintTwips) + { + Size aGridSize(nGridWidthPx, nGridHeightPx); + const MapMode& rWinMapMode = GetLogicMode(); + aGridSize = OutputDevice::LogicToLogic( + pWin->PixelToLogic(aGridSize, rWinMapMode), + rWinMapMode, MapMode(MapUnit::MapTwip)); + nGridWidthTwips = aGridSize.Width(); + nGridHeightTwips = aGridSize.Height(); + } + + if (bBreak && !bAsianVertical) + { + nSizeXPix = aPixRect.GetWidth(); // papersize -> no horizontal scrolling + if (bLOKPrintTwips) + nSizeXPTwips = aPTwipsRect.GetWidth(); + } + else + { + OSL_ENSURE(pView,"no View for EditView"); + + if ( bGrowCentered ) + { + // growing into both directions until one edge is reached + //! should be limited to whole cells in both directions + tools::Long nLeft = aPixRect.Left(); + tools::Long nRight = nGridWidthPx - aPixRect.Right(); + nSizeXPix = aPixRect.GetWidth() + 2 * std::min( nLeft, nRight ); + if (bLOKPrintTwips) + { + tools::Long nLeftPTwips = aPTwipsRect.Left(); + tools::Long nRightPTwips = nGridWidthTwips - aPTwipsRect.Right(); + nSizeXPTwips = aPTwipsRect.GetWidth() + 2 * std::min(nLeftPTwips, nRightPTwips); + } + } + else if ( (bGrowToLeft && !bLOKRTLInvert) || (!bGrowToLeft && bLOKRTLInvert) ) + { + nSizeXPix = aPixRect.Right(); // space that's available in the window when growing to the left + if (bLOKPrintTwips) + nSizeXPTwips = aPTwipsRect.Right(); + } + else + { + nSizeXPix = nGridWidthPx - aPixRect.Left(); + if (bLOKPrintTwips) + nSizeXPTwips = nGridWidthTwips - aPTwipsRect.Left(); + } + + if ( nSizeXPix <= 0 ) + { + nSizeXPix = aPixRect.GetWidth(); // editing outside to the right of the window -> keep cell width + if (bLOKPrintTwips) + nSizeXPTwips = aPTwipsRect.GetWidth(); + } + } + OSL_ENSURE(pView,"no View for EditView"); + tools::Long nSizeYPix = nGridHeightPx - aPixRect.Top(); + tools::Long nSizeYPTwips = bLOKPrintTwips ? (nGridHeightTwips - aPTwipsRect.Top()) : 0; + + if ( nSizeYPix <= 0 ) + { + nSizeYPix = aPixRect.GetHeight(); // editing outside below the window -> keep cell height + if (bLOKPrintTwips) + nSizeYPTwips = aPTwipsRect.GetHeight(); + } + + Size aPaperSize = pView->GetActiveWin()->PixelToLogic( Size( nSizeXPix, nSizeYPix ), GetLogicMode() ); + Size aPaperSizePTwips(nSizeXPTwips, nSizeYPTwips); + if ( bBreak && !bAsianVertical && SC_MOD()->GetInputOptions().GetTextWysiwyg() ) + { + // if text is formatted for printer, use the exact same paper width + // (and same line breaks) as for output. + + Fraction aFract(1,1); + constexpr auto HMM_PER_TWIPS = o3tl::convert(1.0, o3tl::Length::twip, o3tl::Length::mm100); + tools::Rectangle aUtilRect = ScEditUtil(&mrDoc, nNewX, nNewY, nTabNo, Point(0, 0), pWin->GetOutDev(), + HMM_PER_TWIPS, HMM_PER_TWIPS, aFract, aFract ).GetEditArea( pPattern, false ); + aPaperSize.setWidth( aUtilRect.GetWidth() ); + if (bLOKPrintTwips) + { + aPaperSizePTwips.setWidth(o3tl::convert(aUtilRect.GetWidth(), o3tl::Length::mm100, o3tl::Length::twip)); + } + } + + pNewEngine->SetPaperSize( aPaperSize ); + if (bLOKPrintTwips) + pNewEngine->SetLOKSpecialPaperSize(aPaperSizePTwips); + + // sichtbarer Ausschnitt + Size aPaper = pNewEngine->GetPaperSize(); + tools::Rectangle aVis = pEditView[eWhich]->GetVisArea(); + tools::Rectangle aVisPTwips; + if (bLOKPrintTwips) + aVisPTwips = pEditView[eWhich]->GetLOKSpecialVisArea(); + + tools::Long nDiff = aVis.Right() - aVis.Left(); + tools::Long nDiffPTwips = bLOKPrintTwips ? (aVisPTwips.Right() - aVisPTwips.Left()) : 0; + if ( GetEditAdjust() == SvxAdjust::Right ) + { + aVis.SetRight( aPaper.Width() - 1 ); + if (bLOKPrintTwips) + aVisPTwips.SetRight( aPaperSizePTwips.Width() - 1 ); + bMoveArea = !bLayoutRTL; + } + else if ( GetEditAdjust() == SvxAdjust::Center ) + { + aVis.SetRight( ( aPaper.Width() - 1 + nDiff ) / 2 ); + if (bLOKPrintTwips) + aVisPTwips.SetRight( ( aPaperSizePTwips.Width() - 1 + nDiffPTwips ) / 2 ); + bMoveArea = true; // always + } + else + { + aVis.SetRight( nDiff ); + if (bLOKPrintTwips) + aVisPTwips.SetRight(nDiffPTwips); + bMoveArea = bLayoutRTL; + } + aVis.SetLeft( aVis.Right() - nDiff ); + if (bLOKPrintTwips) + aVisPTwips.SetLeft(aVisPTwips.Right() - nDiffPTwips); + // #i49561# Important note: + // The set offset of the visible area of the EditView for centered and + // right alignment in horizontal layout is consider by instances of + // class <ScEditObjectViewForwarder> in its methods <LogicToPixel(..)> + // and <PixelToLogic(..)>. This is needed for the correct visibility + // of paragraphs in edit mode at the accessibility API. + pEditView[eWhich]->SetVisArea(aVis); + if (bLOKPrintTwips) + pEditView[eWhich]->SetLOKSpecialVisArea(aVisPTwips); + // UpdateMode has been disabled in ScInputHandler::StartTable + // must be enabled before EditGrowY (GetTextHeight) + pNewEngine->SetUpdateLayout( true ); + + pNewEngine->SetStatusEventHdl( LINK( this, ScViewData, EditEngineHdl ) ); + + EditGrowY( true ); // adjust to existing text content + EditGrowX(); + + Point aDocPos = pEditView[eWhich]->GetWindowPosTopLeft(0); + if (aDocPos.Y() < aOutputArea.Top()) + pEditView[eWhich]->Scroll( 0, aOutputArea.Top() - aDocPos.Y() ); + } + + // here bEditActive needs to be set already + // (due to Map-Mode during Paint) + if (!bWasThere) + pNewEngine->InsertView(pEditView[eWhich].get()); + + // background color of the cell + Color aBackCol = pPattern->GetItem(ATTR_BACKGROUND).GetColor(); + + ScModule* pScMod = SC_MOD(); + if ( aBackCol.IsTransparent() ) + { + aBackCol = pScMod->GetColorConfig().GetColorValue(svtools::DOCCOLOR).nColor; + } + pEditView[eWhich]->SetBackgroundColor( aBackCol ); + + pEditView[eWhich]->Invalidate(); // needed? + // needed, if position changed +} + +IMPL_LINK( ScViewData, EditEngineHdl, EditStatus&, rStatus, void ) +{ + EditStatusFlags nStatus = rStatus.GetStatusWord(); + if (nStatus & (EditStatusFlags::HSCROLL | EditStatusFlags::TextHeightChanged | EditStatusFlags::TEXTWIDTHCHANGED | EditStatusFlags::CURSOROUT)) + { + EditGrowY(); + EditGrowX(); + + if (nStatus & EditStatusFlags::CURSOROUT) + { + ScSplitPos eWhich = GetActivePart(); + if (pEditView[eWhich]) + pEditView[eWhich]->ShowCursor(false); + } + } +} + +void ScViewData::EditGrowX() +{ + // It is insane to call EditGrowX while the output area is already growing. + // That could occur because of the call to SetDefaultItem later. + // We end up with wrong start/end edit columns and the changes + // to the output area performed by the inner call to this method are + // useless since they are discarded by the outer call. + if (bGrowing) + return; + + comphelper::FlagRestorationGuard aFlagGuard(bGrowing, true); + + bool bLOKActive = comphelper::LibreOfficeKit::isActive(); + bool bLOKPrintTwips = bLOKActive && comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs); + + ScDocument& rLocalDoc = GetDocument(); + + ScSplitPos eWhich = GetActivePart(); + ScHSplitPos eHWhich = WhichH(eWhich); + EditView* pCurView = pEditView[eWhich].get(); + + if ( !pCurView || !bEditActive[eWhich]) + return; + + bool bLayoutRTL = rLocalDoc.IsLayoutRTL( nTabNo ); + + ScEditEngineDefaulter* pEngine = + static_cast<ScEditEngineDefaulter*>( pCurView->GetEditEngine() ); + vcl::Window* pWin = pCurView->GetWindow(); + + // Get the left- and right-most column positions. + SCCOL nLeft = GetPosX(eHWhich); + SCCOL nRight = nLeft + VisibleCellsX(eHWhich); + + Size aSize = pEngine->GetPaperSize(); + Size aSizePTwips; + if (bLOKPrintTwips) + aSizePTwips = pEngine->GetLOKSpecialPaperSize(); + + tools::Rectangle aArea = pCurView->GetOutputArea(); + tools::Rectangle aAreaPTwips; + if (bLOKPrintTwips) + aAreaPTwips = pCurView->GetLOKSpecialOutputArea(); + + tools::Long nOldRight = aArea.Right(); + + // Margin is already included in the original width. + tools::Long nTextWidth = pEngine->CalcTextWidth(); + + bool bChanged = false; + bool bAsianVertical = pEngine->IsEffectivelyVertical(); + + // get bGrow... variables the same way as in SetEditEngine + const ScPatternAttr* pPattern = rLocalDoc.GetPattern( nEditCol, nEditRow, nTabNo ); + SvxCellHorJustify eJust = pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue(); + bool bGrowCentered = ( eJust == SvxCellHorJustify::Center ); + bool bGrowToLeft = ( eJust == SvxCellHorJustify::Right ); // visual left + bool bGrowBackwards = bGrowToLeft; // logical left + if ( bLayoutRTL ) + bGrowBackwards = !bGrowBackwards; // invert on RTL sheet + if ( bAsianVertical ) + bGrowCentered = bGrowToLeft = bGrowBackwards = false; // keep old behavior for asian mode + + bool bUnevenGrow = false; + if ( bGrowCentered ) + { + while (aArea.GetWidth() + 0 < nTextWidth && ( nEditStartCol > nLeft || nEditEndCol < nRight ) ) + { + tools::Long nLogicLeft = 0; + tools::Long nLogicLeftPTwips = 0; + if ( nEditStartCol > nLeft ) + { + --nEditStartCol; + tools::Long nColWidth = rLocalDoc.GetColWidth( nEditStartCol, nTabNo ); + tools::Long nLeftPix = ToPixel( nColWidth, nPPTX ); + nLogicLeft = pWin->PixelToLogic(Size(nLeftPix,0)).Width(); + if (bLOKPrintTwips) + nLogicLeftPTwips = nColWidth; + } + tools::Long nLogicRight = 0; + tools::Long nLogicRightPTwips = 0; + if ( nEditEndCol < nRight ) + { + ++nEditEndCol; + tools::Long nColWidth = rLocalDoc.GetColWidth( nEditEndCol, nTabNo ); + tools::Long nRightPix = ToPixel( nColWidth, nPPTX ); + nLogicRight = pWin->PixelToLogic(Size(nRightPix,0)).Width(); + if (bLOKPrintTwips) + nLogicRightPTwips = nColWidth; + } + + aArea.AdjustLeft( -((bLayoutRTL && !bLOKActive) ? nLogicRight : nLogicLeft) ); + aArea.AdjustRight((bLayoutRTL && !bLOKActive) ? nLogicLeft : nLogicRight ); + if (bLOKPrintTwips) + { + aAreaPTwips.AdjustLeft(-nLogicLeftPTwips); + aAreaPTwips.AdjustRight(nLogicRightPTwips); + } + + if ( aArea.Right() > aArea.Left() + aSize.Width() - 1 ) + { + tools::Long nCenter = ( aArea.Left() + aArea.Right() ) / 2; + tools::Long nHalf = aSize.Width() / 2; + aArea.SetLeft( nCenter - nHalf + 1 ); + aArea.SetRight( nCenter + aSize.Width() - nHalf - 1 ); + + if (bLOKPrintTwips) + { + tools::Long nCenterPTwips = ( aAreaPTwips.Left() + aAreaPTwips.Right() ) / 2; + tools::Long nHalfPTwips = aSizePTwips.Width() / 2; + aAreaPTwips.SetLeft( nCenterPTwips - nHalfPTwips + 1 ); + aAreaPTwips.SetRight( nCenterPTwips + aSizePTwips.Width() - nHalfPTwips - 1 ); + } + } + + bChanged = true; + if ( nLogicLeft != nLogicRight ) + bUnevenGrow = true; + } + } + else if ( bGrowBackwards ) + { + while (aArea.GetWidth() + 0 < nTextWidth && nEditStartCol > nLeft) + { + --nEditStartCol; + tools::Long nColWidth = rLocalDoc.GetColWidth( nEditStartCol, nTabNo ); + tools::Long nPix = ToPixel( nColWidth, nPPTX ); + tools::Long nLogicWidth = pWin->PixelToLogic(Size(nPix,0)).Width(); + tools::Long& nLogicWidthPTwips = nColWidth; + + if ( !bLayoutRTL || bLOKActive ) + { + aArea.AdjustLeft( -nLogicWidth ); + if (bLOKPrintTwips) + aAreaPTwips.AdjustLeft( -nLogicWidthPTwips ); + } + else + { + aArea.AdjustRight(nLogicWidth ); + if (bLOKPrintTwips) + aAreaPTwips.AdjustRight(nLogicWidthPTwips); + } + + if ( aArea.Right() > aArea.Left() + aSize.Width() - 1 ) + { + if ( !bLayoutRTL || bLOKActive ) + { + aArea.SetLeft( aArea.Right() - aSize.Width() + 1 ); + if (bLOKPrintTwips) + aAreaPTwips.SetLeft( aAreaPTwips.Right() - aSizePTwips.Width() + 1 ); + } + else + { + aArea.SetRight( aArea.Left() + aSize.Width() - 1 ); + if (bLOKPrintTwips) + aAreaPTwips.SetRight( aAreaPTwips.Left() + aSizePTwips.Width() - 1 ); + } + } + + bChanged = true; + } + } + else + { + while (aArea.GetWidth() + 0 < nTextWidth && nEditEndCol < nRight) + { + ++nEditEndCol; + tools::Long nColWidth = rLocalDoc.GetColWidth( nEditEndCol, nTabNo ); + tools::Long nPix = ToPixel( nColWidth, nPPTX ); + tools::Long nLogicWidth = pWin->PixelToLogic(Size(nPix,0)).Width(); + tools::Long& nLogicWidthPTwips = nColWidth; + if ( bLayoutRTL && !bLOKActive ) + { + aArea.AdjustLeft( -nLogicWidth ); + } + else + { + aArea.AdjustRight(nLogicWidth ); + if (bLOKPrintTwips) + aAreaPTwips.AdjustRight(nLogicWidthPTwips); + } + + if ( aArea.Right() > aArea.Left() + aSize.Width() - 1 ) + { + if ( bLayoutRTL && !bLOKActive ) + { + aArea.SetLeft( aArea.Right() - aSize.Width() + 1 ); + } + else + { + aArea.SetRight( aArea.Left() + aSize.Width() - 1 ); + if (bLOKPrintTwips) + aAreaPTwips.SetRight( aAreaPTwips.Left() + aSizePTwips.Width() - 1 ); + } + } + + bChanged = true; + } + } + + if (!bChanged) + return; + + if ( bMoveArea || bGrowCentered || bGrowBackwards || bLayoutRTL ) + { + tools::Rectangle aVis = pCurView->GetVisArea(); + tools::Rectangle aVisPTwips; + if (bLOKPrintTwips) + aVisPTwips = pCurView->GetLOKSpecialVisArea(); + + if ( bGrowCentered ) + { + // switch to center-aligned (undo?) and reset VisArea to center + + pEngine->SetDefaultItem( SvxAdjustItem( SvxAdjust::Center, EE_PARA_JUST ) ); + + tools::Long nCenter = aSize.Width() / 2; + tools::Long nVisSize = aArea.GetWidth(); + aVis.SetLeft( nCenter - nVisSize / 2 ); + aVis.SetRight( aVis.Left() + nVisSize - 1 ); + + if (bLOKPrintTwips) + { + tools::Long nCenterPTwips = aSizePTwips.Width() / 2; + tools::Long nVisSizePTwips = aAreaPTwips.GetWidth(); + aVisPTwips.SetLeft( nCenterPTwips - nVisSizePTwips / 2 ); + aVisPTwips.SetRight( aVisPTwips.Left() + nVisSizePTwips - 1 ); + } + } + else if ( bGrowToLeft ) + { + // switch to right-aligned (undo?) and reset VisArea to the right + + pEngine->SetDefaultItem( SvxAdjustItem( SvxAdjust::Right, EE_PARA_JUST ) ); + + aVis.SetRight( aSize.Width() - 1 ); + aVis.SetLeft( aSize.Width() - aArea.GetWidth() ); // with the new, increased area + + if (bLOKPrintTwips) + { + aVisPTwips.SetRight( aSizePTwips.Width() - 1 ); + aVisPTwips.SetLeft( aSizePTwips.Width() - aAreaPTwips.GetWidth() ); // with the new, increased area + } + } + else + { + // switch to left-aligned (undo?) and reset VisArea to the left + + pEngine->SetDefaultItem( SvxAdjustItem( SvxAdjust::Left, EE_PARA_JUST ) ); + + tools::Long nMove = aVis.Left(); + aVis.SetLeft( 0 ); + aVis.AdjustRight( -nMove ); + + if (bLOKPrintTwips) + { + tools::Long nMovePTwips = aVisPTwips.Left(); + aVisPTwips.SetLeft( 0 ); + aVisPTwips.AdjustRight( -nMovePTwips ); + } + } + + pCurView->SetVisArea( aVis ); + if (bLOKPrintTwips) + pCurView->SetLOKSpecialVisArea( aVisPTwips ); + + bMoveArea = false; + } + + if (bLOKPrintTwips) + pCurView->SetLOKSpecialOutputArea(aAreaPTwips); + + pCurView->SetOutputArea(aArea); + + // In vertical mode, the whole text is moved to the next cell (right-aligned), + // so everything must be repainted. Otherwise, paint only the new area. + // If growing in centered alignment, if the cells left and right have different sizes, + // the whole text will move, and may not even obscure all of the original display. + if ( bUnevenGrow ) + { + aArea.SetLeft( pWin->PixelToLogic( Point(0,0) ).X() ); + aArea.SetRight( pWin->PixelToLogic( aScrSize ).Width() ); + } + else if ( !bAsianVertical && !bGrowToLeft && !bGrowCentered ) + aArea.SetLeft( nOldRight ); + pWin->Invalidate(aArea); + + // invalidate other views + pCurView->InvalidateOtherViewWindows(aArea); +} + +void ScViewData::EditGrowY( bool bInitial ) +{ + if (bGrowing) + return; + + comphelper::FlagRestorationGuard aFlagGuard(bGrowing, true); + + bool bLOKActive = comphelper::LibreOfficeKit::isActive(); + bool bLOKPrintTwips = bLOKActive && comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs); + + ScSplitPos eWhich = GetActivePart(); + ScVSplitPos eVWhich = WhichV(eWhich); + EditView* pCurView = pEditView[eWhich].get(); + + if ( !pCurView || !bEditActive[eWhich]) + return; + + EVControlBits nControl = pEditView[eWhich]->GetControlWord(); + if ( nControl & EVControlBits::AUTOSCROLL ) + { + // if end of screen had already been reached and scrolling enabled, + // don't further try to grow the edit area + + pCurView->SetOutputArea( pCurView->GetOutputArea() ); // re-align to pixels + return; + } + + EditEngine* pEngine = pCurView->GetEditEngine(); + vcl::Window* pWin = pCurView->GetWindow(); + + SCROW nBottom = GetPosY(eVWhich) + VisibleCellsY(eVWhich); + + Size aSize = pEngine->GetPaperSize(); + Size aSizePTwips; + tools::Rectangle aArea = pCurView->GetOutputArea(); + tools::Rectangle aAreaPTwips; + + if (bLOKPrintTwips) + { + aSizePTwips = pEngine->GetLOKSpecialPaperSize(); + aAreaPTwips = pCurView->GetLOKSpecialOutputArea(); + } + + tools::Long nOldBottom = aArea.Bottom(); + tools::Long nTextHeight = pEngine->GetTextHeight(); + + // When editing a formula in a cell with optimal height, allow a larger portion + // to be clipped before extending to following rows, to avoid obscuring cells for + // reference input (next row is likely to be useful in formulas). + tools::Long nAllowedExtra = SC_GROWY_SMALL_EXTRA; + if (nEditEndRow == nEditRow && !(mrDoc.GetRowFlags(nEditRow, nTabNo) & CRFlags::ManualSize) && + pEngine->GetParagraphCount() <= 1 ) + { + // If the (only) paragraph starts with a '=', it's a formula. + // If this is the initial call and the text is empty, allow the larger value, too, + // because this occurs in the normal progress of editing a formula. + // Subsequent calls with empty text might involve changed attributes (including + // font height), so they are treated like normal text. + OUString aText = pEngine->GetText( 0 ); + if ( ( aText.isEmpty() && bInitial ) || aText.startsWith("=") ) + nAllowedExtra = SC_GROWY_BIG_EXTRA; + } + + bool bChanged = false; + bool bMaxReached = false; + while (aArea.GetHeight() + nAllowedExtra < nTextHeight && nEditEndRow < nBottom && !bMaxReached) + { + ++nEditEndRow; + ScDocument& rLocalDoc = GetDocument(); + tools::Long nRowHeight = rLocalDoc.GetRowHeight( nEditEndRow, nTabNo ); + tools::Long nPix = ToPixel( nRowHeight, nPPTY ); + aArea.AdjustBottom(pWin->PixelToLogic(Size(0,nPix)).Height() ); + if (bLOKPrintTwips) + aAreaPTwips.AdjustBottom(nRowHeight); + + if ( aArea.Bottom() > aArea.Top() + aSize.Height() - 1 ) + { + aArea.SetBottom( aArea.Top() + aSize.Height() - 1 ); + if (bLOKPrintTwips) + aAreaPTwips.SetBottom( aAreaPTwips.Top() + aSizePTwips.Height() - 1 ); + bMaxReached = true; // don't occupy more cells beyond paper size + } + + bChanged = true; + nAllowedExtra = SC_GROWY_SMALL_EXTRA; // larger value is only for first row + } + + if (!bChanged) + return; + + if (bLOKPrintTwips) + pCurView->SetLOKSpecialOutputArea(aAreaPTwips); + + pCurView->SetOutputArea(aArea); + + if (nEditEndRow >= nBottom || bMaxReached) + { + if (!(nControl & EVControlBits::AUTOSCROLL)) + pCurView->SetControlWord( nControl | EVControlBits::AUTOSCROLL ); + } + + aArea.SetTop( nOldBottom ); + pWin->Invalidate(aArea); + + // invalidate other views + pCurView->InvalidateOtherViewWindows(aArea); +} + +void ScViewData::ResetEditView() +{ + EditEngine* pEngine = nullptr; + for (sal_uInt16 i=0; i<4; i++) + if (pEditView[i]) + { + if (bEditActive[i]) + { + lcl_LOKRemoveWindow(GetViewShell(), static_cast<ScSplitPos>(i)); + pEngine = pEditView[i]->GetEditEngine(); + pEngine->RemoveView(pEditView[i].get()); + pEditView[i]->SetOutputArea( tools::Rectangle() ); + } + bEditActive[i] = false; + } + + if (pEngine) + pEngine->SetStatusEventHdl( Link<EditStatus&,void>() ); +} + +void ScViewData::KillEditView() +{ + EditEngine* pEngine = nullptr; + for (sal_uInt16 i=0; i<4; i++) + if (pEditView[i]) + { + if (bEditActive[i]) + { + pEngine = pEditView[i]->GetEditEngine(); + if (pEngine) + pEngine->RemoveView(pEditView[i].get()); + } + pEditView[i].reset(); + } +} + +void ScViewData::GetEditView( ScSplitPos eWhich, EditView*& rViewPtr, SCCOL& rCol, SCROW& rRow ) +{ + rViewPtr = pEditView[eWhich].get(); + rCol = nEditCol; + rRow = nEditRow; +} + +void ScViewData::CreateTabData( SCTAB nNewTab ) +{ + EnsureTabDataSize(nNewTab + 1); + + if (!maTabData[nNewTab]) + { + maTabData[nNewTab].reset(new ScViewDataTable(&mrDoc)); + + maTabData[nNewTab]->eZoomType = eDefZoomType; + maTabData[nNewTab]->aZoomX = aDefZoomX; + maTabData[nNewTab]->aZoomY = aDefZoomY; + maTabData[nNewTab]->aPageZoomX = aDefPageZoomX; + maTabData[nNewTab]->aPageZoomY = aDefPageZoomY; + } +} + +void ScViewData::CreateSelectedTabData() +{ + for (const auto& rTab : maMarkData) + CreateTabData(rTab); +} + +void ScViewData::EnsureTabDataSize(size_t nSize) +{ + if (nSize > maTabData.size()) + maTabData.resize(nSize); +} + +void ScViewData::SetTabNo( SCTAB nNewTab ) +{ + if (!ValidTab(nNewTab)) + { + OSL_FAIL("wrong sheet number"); + return; + } + + nTabNo = nNewTab; + CreateTabData(nTabNo); + pThisTab = maTabData[nTabNo].get(); + + CalcPPT(); // for common column width correction + RecalcPixPos(); //! not always needed! +} + +ScPositionHelper* ScViewData::GetLOKWidthHelper(SCTAB nTabIndex) +{ + if (!ValidTab(nTabIndex) || (nTabIndex >= static_cast<SCTAB>(maTabData.size())) || + !maTabData[nTabIndex]) + { + return nullptr; + } + return &(maTabData[nTabIndex]->aWidthHelper); +} + +ScPositionHelper* ScViewData::GetLOKHeightHelper(SCTAB nTabIndex) +{ + if (!ValidTab(nTabIndex) || (nTabIndex >= static_cast<SCTAB>(maTabData.size())) || + !maTabData[nTabIndex]) + { + return nullptr; + } + return &(maTabData[nTabIndex]->aHeightHelper); +} + +void ScViewData::SetActivePart( ScSplitPos eNewActive ) +{ + pThisTab->eWhichActive = eNewActive; + + // Let's hope we find the culprit for tdf#117093 + // Don't sanitize the real value (yet?) because this function might be + // called before setting the then corresponding split modes. For which in + // fact then the order should be changed. + assert(eNewActive == pThisTab->SanitizeWhichActive()); +} + +Point ScViewData::GetScrPos( SCCOL nWhereX, SCROW nWhereY, ScHSplitPos eWhich ) const +{ + OSL_ENSURE( eWhich==SC_SPLIT_LEFT || eWhich==SC_SPLIT_RIGHT, "wrong position" ); + ScSplitPos ePos = ( eWhich == SC_SPLIT_LEFT ) ? SC_SPLIT_BOTTOMLEFT : SC_SPLIT_BOTTOMRIGHT; + return GetScrPos( nWhereX, nWhereY, ePos ); +} + +Point ScViewData::GetScrPos( SCCOL nWhereX, SCROW nWhereY, ScVSplitPos eWhich ) const +{ + OSL_ENSURE( eWhich==SC_SPLIT_TOP || eWhich==SC_SPLIT_BOTTOM, "wrong position" ); + ScSplitPos ePos = ( eWhich == SC_SPLIT_TOP ) ? SC_SPLIT_TOPLEFT : SC_SPLIT_BOTTOMLEFT; + return GetScrPos( nWhereX, nWhereY, ePos ); +} + +Point ScViewData::GetScrPos( SCCOL nWhereX, SCROW nWhereY, ScSplitPos eWhich, + bool bAllowNeg, SCTAB nForTab ) const +{ + ScHSplitPos eWhichX = SC_SPLIT_LEFT; + ScVSplitPos eWhichY = SC_SPLIT_BOTTOM; + switch( eWhich ) + { + case SC_SPLIT_TOPLEFT: + eWhichX = SC_SPLIT_LEFT; + eWhichY = SC_SPLIT_TOP; + break; + case SC_SPLIT_TOPRIGHT: + eWhichX = SC_SPLIT_RIGHT; + eWhichY = SC_SPLIT_TOP; + break; + case SC_SPLIT_BOTTOMLEFT: + eWhichX = SC_SPLIT_LEFT; + eWhichY = SC_SPLIT_BOTTOM; + break; + case SC_SPLIT_BOTTOMRIGHT: + eWhichX = SC_SPLIT_RIGHT; + eWhichY = SC_SPLIT_BOTTOM; + break; + } + + if (nForTab == -1) + nForTab = nTabNo; + bool bForCurTab = (nForTab == nTabNo); + if (!bForCurTab && (!ValidTab(nForTab) || (nForTab >= static_cast<SCTAB>(maTabData.size())))) + { + SAL_WARN("sc.viewdata", "ScViewData::GetScrPos : invalid nForTab = " << nForTab); + nForTab = nTabNo; + bForCurTab = true; + } + + ScViewDataTable* pViewTable = bForCurTab ? pThisTab : maTabData[nForTab].get(); + + if (pView) + { + const_cast<ScViewData*>(this)->aScrSize.setWidth( pView->GetGridWidth(eWhichX) ); + const_cast<ScViewData*>(this)->aScrSize.setHeight( pView->GetGridHeight(eWhichY) ); + } + + sal_uInt16 nTSize; + bool bIsTiledRendering = comphelper::LibreOfficeKit::isActive(); + + SCCOL nPosX = GetPosX(eWhichX, nForTab); + tools::Long nScrPosX = 0; + + if (bAllowNeg || nWhereX >= nPosX) + { + SCROW nStartPosX = nPosX; + if (bIsTiledRendering) + { + OSL_ENSURE(nPosX == 0, "Unsupported case."); + const auto& rNearest = pViewTable->aWidthHelper.getNearestByIndex(nWhereX - 1); + nStartPosX = rNearest.first + 1; + nScrPosX = rNearest.second; + } + + if (nWhereX >= nStartPosX) + { + for (SCCOL nX = nStartPosX; nX < nWhereX && (bAllowNeg || bIsTiledRendering || nScrPosX <= aScrSize.Width()); nX++) + { + if (nX > mrDoc.MaxCol()) + nScrPosX = 0x7FFFFFFF; + else + { + nTSize = mrDoc.GetColWidth(nX, nForTab); + if (nTSize) + { + tools::Long nSizeXPix = ToPixel( nTSize, nPPTX ); + nScrPosX += nSizeXPix; + } + else + { // If the width is zero, the column is possibly hidden, skips groups of such columns. + SCCOL lastHidden = -1; + if(mrDoc.ColHidden(nX, nForTab, nullptr, &lastHidden) && lastHidden > nX) + nX = lastHidden - 1; + } + } + } + } + else + { + for (SCCOL nX = nStartPosX; nX > nWhereX;) + { + --nX; + nTSize = mrDoc.GetColWidth(nX, nForTab); + if (nTSize) + { + tools::Long nSizeXPix = ToPixel( nTSize, nPPTX ); + nScrPosX -= nSizeXPix; + } + else + { // If the width is zero, the column is possibly hidden, skips groups of such columns. + SCCOL firstHidden = -1; + if(mrDoc.ColHidden(nX, nForTab, &firstHidden, nullptr) && firstHidden >= 0) + nX = firstHidden; + } + } + } + + } + + + SCROW nPosY = GetPosY(eWhichY, nForTab); + tools::Long nScrPosY = 0; + + if (bAllowNeg || nWhereY >= nPosY) + { + SCROW nStartPosY = nPosY; + if (bIsTiledRendering) + { + OSL_ENSURE(nPosY == 0, "Unsupported case."); + const auto& rNearest = pViewTable->aHeightHelper.getNearestByIndex(nWhereY - 1); + nStartPosY = rNearest.first + 1; + nScrPosY = rNearest.second; + } + + if (nWhereY >= nStartPosY) + { + for (SCROW nY = nStartPosY; nY < nWhereY && (bAllowNeg || bIsTiledRendering || nScrPosY <= aScrSize.Height()); nY++) + { + if ( nY > mrDoc.MaxRow() ) + nScrPosY = 0x7FFFFFFF; + else + { + nTSize = mrDoc.GetRowHeight( nY, nTabNo ); + if (nTSize) + { + tools::Long nSizeYPix = ToPixel( nTSize, nPPTY ); + nScrPosY += nSizeYPix; + } + else if ( nY < mrDoc.MaxRow() ) + { + // skip multiple hidden rows (forward only for now) + SCROW nNext = mrDoc.FirstVisibleRow(nY + 1, mrDoc.MaxRow(), nTabNo); + if ( nNext > mrDoc.MaxRow() ) + nY = mrDoc.MaxRow(); + else + nY = nNext - 1; // +=nDir advances to next visible row + } + } + } + } + else + { + for (SCROW nY = nStartPosY; nY > nWhereY;) + { + --nY; + nTSize = mrDoc.GetRowHeight(nY, nForTab); + if (nTSize) + { + tools::Long nSizeYPix = ToPixel( nTSize, nPPTY ); + nScrPosY -= nSizeYPix; + } + else + { // If the height is zero, the row is possibly hidden, skips groups of such rows. + SCROW firstHidden = -1; + if(mrDoc.RowHidden(nY, nForTab, &firstHidden, nullptr) && firstHidden >= 0) + nY = firstHidden; + } + } + } + } + + if (mrDoc.IsLayoutRTL(nForTab) && !bIsTiledRendering) + { + // mirror horizontal position + nScrPosX = aScrSize.Width() - 1 - nScrPosX; + } + + return Point( nScrPosX, nScrPosY ); +} + +Point ScViewData::GetPrintTwipsPos(SCCOL nCol, SCROW nRow) const +{ + // hidden ones are given 0 sizes by these by default. + // TODO: rewrite this to loop over spans (matters for jumbosheets). + tools::Long nPosX = nCol ? mrDoc.GetColWidth(0, nCol - 1, nTabNo) : 0; + // This is now fast as it loops over spans. + tools::Long nPosY = nRow ? mrDoc.GetRowHeight(0, nRow - 1, nTabNo) : 0; + // TODO: adjust for RTL layout case. + + return Point(nPosX, nPosY); +} + +Point ScViewData::GetPrintTwipsPosFromTileTwips(const Point& rTileTwipsPos) const +{ + const tools::Long nPixelX = static_cast<tools::Long>(rTileTwipsPos.X() * nPPTX); + const tools::Long nPixelY = static_cast<tools::Long>(rTileTwipsPos.Y() * nPPTY); + SCCOL nCol = 0; + SCROW nRow = 0; + + // The following call (with bTestMerge = false) will not modify any members. + const_cast<ScViewData*>(this)->GetPosFromPixel(nPixelX, nPixelY, SC_SPLIT_TOPLEFT, nCol, nRow, false /* bTestMerge */); + const Point aPixCellPos = GetScrPos(nCol, nRow, SC_SPLIT_TOPLEFT, true /* bAllowNeg */); + const Point aTileTwipsCellPos(aPixCellPos.X() / nPPTX, aPixCellPos.Y() / nPPTY); + const Point aPrintTwipsCellPos = GetPrintTwipsPos(nCol, nRow); + return aPrintTwipsCellPos + (rTileTwipsPos - aTileTwipsCellPos); +} + +OString ScViewData::describeCellCursorAt(SCCOL nX, SCROW nY, bool bPixelAligned) const +{ + const bool bPosSizeInPixels = bPixelAligned; + Point aCellPos = bPosSizeInPixels ? GetScrPos( nX, nY, SC_SPLIT_BOTTOMRIGHT, true ) : + GetPrintTwipsPos(nX, nY); + + tools::Long nSizeX; + tools::Long nSizeY; + if (bPosSizeInPixels) + GetMergeSizePixel( nX, nY, nSizeX, nSizeY ); + else + GetMergeSizePrintTwips(nX, nY, nSizeX, nSizeY); + + std::stringstream ss; + if (bPosSizeInPixels) + { + double fPPTX = GetPPTX(); + double fPPTY = GetPPTY(); + + // make it a slim cell cursor, but not empty + if (nSizeX == 0) + nSizeX = 1; + + if (nSizeY == 0) + nSizeY = 1; + + tools::Long nPosXTw = rtl::math::round(aCellPos.getX() / fPPTX); + tools::Long nPosYTw = rtl::math::round(aCellPos.getY() / fPPTY); + // look at Rectangle( const Point& rLT, const Size& rSize ) for the '- 1' + tools::Long nSizeXTw = rtl::math::round(nSizeX / fPPTX) - 1; + tools::Long nSizeYTw = rtl::math::round(nSizeY / fPPTY) - 1; + + ss << nPosXTw << ", " << nPosYTw << ", " << nSizeXTw << ", " << nSizeYTw << ", " + << nX << ", " << nY; + } + else + { + // look at Rectangle( const Point& rLT, const Size& rSize ) for the decrement. + if (nSizeX) + --nSizeX; + if (nSizeY) + --nSizeY; + ss << aCellPos.getX() << ", " << aCellPos.getY() + << ", " << nSizeX << ", " << nSizeY << ", " + << nX << ", " << nY; + } + + return OString(ss.str()); +} + +// Number of cells on a screen +SCCOL ScViewData::CellsAtX( SCCOL nPosX, SCCOL nDir, ScHSplitPos eWhichX, sal_uInt16 nScrSizeX ) const +{ + OSL_ENSURE( nDir==1 || nDir==-1, "wrong CellsAt call" ); + + if (pView) + const_cast<ScViewData*>(this)->aScrSize.setWidth( pView->GetGridWidth(eWhichX) ); + + SCCOL nX; + sal_uInt16 nScrPosX = 0; + if (nScrSizeX == SC_SIZE_NONE) nScrSizeX = static_cast<sal_uInt16>(aScrSize.Width()); + + if (nDir==1) + nX = nPosX; // forwards + else + nX = nPosX-1; // backwards + + bool bOut = false; + for ( ; nScrPosX<=nScrSizeX && !bOut; nX = sal::static_int_cast<SCCOL>(nX + nDir) ) + { + SCCOL nColNo = nX; + if (nColNo < 0 || nColNo > mrDoc.MaxCol()) + bOut = true; + else + { + sal_uInt16 nTSize = mrDoc.GetColWidth(nColNo, nTabNo); + if (nTSize) + { + tools::Long nSizeXPix = ToPixel( nTSize, nPPTX ); + nScrPosX = sal::static_int_cast<sal_uInt16>( nScrPosX + static_cast<sal_uInt16>(nSizeXPix) ); + } + } + } + + if (nDir==1) + nX = sal::static_int_cast<SCCOL>( nX - nPosX ); + else + nX = (nPosX-1)-nX; + + if (nX>0) --nX; + return nX; +} + +SCROW ScViewData::CellsAtY( SCROW nPosY, SCROW nDir, ScVSplitPos eWhichY, sal_uInt16 nScrSizeY ) const +{ + OSL_ENSURE( nDir==1 || nDir==-1, "wrong CellsAt call" ); + + if (pView) + const_cast<ScViewData*>(this)->aScrSize.setHeight( pView->GetGridHeight(eWhichY) ); + + if (nScrSizeY == SC_SIZE_NONE) nScrSizeY = static_cast<sal_uInt16>(aScrSize.Height()); + + SCROW nY; + + if (nDir==1) + { + // forward + nY = nPosY; + tools::Long nScrPosY = 0; + AddPixelsWhile(nScrPosY, nScrSizeY, nY, mrDoc.MaxRow(), nPPTY, &mrDoc, nTabNo); + // Original loop ended on last evaluated +1 or if that was MaxRow even on MaxRow+2. + nY += (nY == mrDoc.MaxRow() ? 2 : 1); + nY -= nPosY; + } + else + { + // backward + nY = nPosY-1; + tools::Long nScrPosY = 0; + AddPixelsWhileBackward(nScrPosY, nScrSizeY, nY, 0, nPPTY, &mrDoc, nTabNo); + // Original loop ended on last evaluated -1 or if that was 0 even on -2. + nY -= (nY == 0 ? 2 : 1); + nY = (nPosY-1)-nY; + } + + if (nY>0) --nY; + return nY; +} + +SCCOL ScViewData::VisibleCellsX( ScHSplitPos eWhichX ) const +{ + return CellsAtX( GetPosX( eWhichX ), 1, eWhichX ); +} + +SCROW ScViewData::VisibleCellsY( ScVSplitPos eWhichY ) const +{ + return CellsAtY( GetPosY( eWhichY ), 1, eWhichY ); +} + +SCCOL ScViewData::PrevCellsX( ScHSplitPos eWhichX ) const +{ + return CellsAtX( GetPosX( eWhichX ), -1, eWhichX ); +} + +SCROW ScViewData::PrevCellsY( ScVSplitPos eWhichY ) const +{ + return CellsAtY( GetPosY( eWhichY ), -1, eWhichY ); +} + +bool ScViewData::GetMergeSizePixel( SCCOL nX, SCROW nY, tools::Long& rSizeXPix, tools::Long& rSizeYPix ) const +{ + const ScMergeAttr* pMerge = mrDoc.GetAttr(nX, nY, nTabNo, ATTR_MERGE); + if ( pMerge->GetColMerge() > 1 || pMerge->GetRowMerge() > 1 ) + { + tools::Long nOutWidth = 0; + tools::Long nOutHeight = 0; + SCCOL nCountX = pMerge->GetColMerge(); + for (SCCOL i=0; i<nCountX; i++) + nOutWidth += ToPixel(mrDoc.GetColWidth(nX + i, nTabNo), nPPTX); + SCROW nCountY = pMerge->GetRowMerge(); + + for (SCROW nRow = nY; nRow <= nY+nCountY-1; ++nRow) + { + SCROW nLastRow = nRow; + if (mrDoc.RowHidden(nRow, nTabNo, nullptr, &nLastRow)) + { + nRow = nLastRow; + continue; + } + + sal_uInt16 nHeight = mrDoc.GetRowHeight(nRow, nTabNo); + nOutHeight += ToPixel(nHeight, nPPTY); + } + + rSizeXPix = nOutWidth; + rSizeYPix = nOutHeight; + return true; + } + else + { + rSizeXPix = ToPixel(mrDoc.GetColWidth(nX, nTabNo), nPPTX); + rSizeYPix = ToPixel(mrDoc.GetRowHeight(nY, nTabNo), nPPTY); + return false; + } +} + +bool ScViewData::GetMergeSizePrintTwips(SCCOL nX, SCROW nY, tools::Long& rSizeXTwips, tools::Long& rSizeYTwips) const +{ + const ScMergeAttr* pMerge = mrDoc.GetAttr(nX, nY, nTabNo, ATTR_MERGE); + SCCOL nCountX = pMerge->GetColMerge(); + if (!nCountX) + nCountX = 1; + rSizeXTwips = mrDoc.GetColWidth(nX, nX + nCountX - 1, nTabNo); + + SCROW nCountY = pMerge->GetRowMerge(); + if (!nCountY) + nCountY = 1; + rSizeYTwips = mrDoc.GetRowHeight(nY, nY + nCountY - 1, nTabNo); + + return (nCountX > 1 || nCountY > 1); +} + +void ScViewData::GetPosFromPixel( tools::Long nClickX, tools::Long nClickY, ScSplitPos eWhich, + SCCOL& rPosX, SCROW& rPosY, + bool bTestMerge, bool bRepair, SCTAB nForTab ) +{ + // special handling of 0 is now in ScViewFunctionSet::SetCursorAtPoint + + if (nForTab == -1) + nForTab = nTabNo; + bool bForCurTab = (nForTab == nTabNo); + if (!bForCurTab && (!ValidTab(nForTab) || (nForTab >= static_cast<SCTAB>(maTabData.size())))) + { + SAL_WARN("sc.viewdata", "ScViewData::GetPosFromPixel : invalid nForTab = " << nForTab); + nForTab = nTabNo; + bForCurTab = true; + } + + ScHSplitPos eHWhich = WhichH(eWhich); + ScVSplitPos eVWhich = WhichV(eWhich); + + if (mrDoc.IsLayoutRTL(nForTab)) + { + if (!comphelper::LibreOfficeKit::isActive()) + { + // mirror horizontal position + if (pView) + aScrSize.setWidth( pView->GetGridWidth(eHWhich) ); + nClickX = aScrSize.Width() - 1 - nClickX; + } + } + + SCCOL nStartPosX = GetPosX(eHWhich, nForTab); + SCROW nStartPosY = GetPosY(eVWhich, nForTab); + rPosX = nStartPosX; + rPosY = nStartPosY; + tools::Long nScrX = 0; + tools::Long nScrY = 0; + + if (nClickX > 0) + { + while (rPosX <= mrDoc.MaxCol() && nClickX >= nScrX) + { + nScrX += ToPixel(mrDoc.GetColWidth(rPosX, nForTab), nPPTX); + ++rPosX; + } + --rPosX; + } + else + { + while ( rPosX>0 && nClickX < nScrX ) + { + --rPosX; + nScrX -= ToPixel(mrDoc.GetColWidth(rPosX, nForTab), nPPTX); + } + } + + if (nClickY > 0) + AddPixelsWhile(nScrY, nClickY, rPosY, mrDoc.MaxRow(), nPPTY, &mrDoc, nForTab); + else + { + /* TODO: could need some "SubPixelsWhileBackward" method */ + while ( rPosY>0 && nClickY < nScrY ) + { + --rPosY; + nScrY -= ToPixel(mrDoc.GetRowHeight(rPosY, nForTab), nPPTY); + } + } + + // cells too big? + if ( rPosX == nStartPosX && nClickX > 0 ) + { + if (pView) + aScrSize.setWidth( pView->GetGridWidth(eHWhich) ); + if ( nClickX > aScrSize.Width() ) + ++rPosX; + } + if ( rPosY == nStartPosY && nClickY > 0 ) + { + if (pView) + aScrSize.setHeight( pView->GetGridHeight(eVWhich) ); + if ( nClickY > aScrSize.Height() ) + ++rPosY; + } + + rPosX = std::clamp(rPosX, SCCOL(0), mrDoc.MaxCol()); + rPosY = std::clamp(rPosY, SCROW(0), mrDoc.MaxRow()); + + if (!(bTestMerge && bForCurTab)) + return; + + // public method to adapt position + SCCOL nOrigX = rPosX; + SCROW nOrigY = rPosY; + mrDoc.SkipOverlapped(rPosX, rPosY, nTabNo); + bool bHOver = (nOrigX != rPosX); + bool bVOver = (nOrigY != rPosY); + + if ( !(bRepair && ( bHOver || bVOver )) ) + return; + + const ScMergeAttr* pMerge = mrDoc.GetAttr(rPosX, rPosY, nTabNo, ATTR_MERGE); + if ( ( bHOver && pMerge->GetColMerge() <= 1 ) || + ( bVOver && pMerge->GetRowMerge() <= 1 ) ) + { + OSL_FAIL("merge error found"); + + mrDoc.RemoveFlagsTab(0, 0, mrDoc.MaxCol(), mrDoc.MaxRow(), nTabNo, ScMF::Hor | ScMF::Ver); + SCCOL nEndCol = mrDoc.MaxCol(); + SCROW nEndRow = mrDoc.MaxRow(); + mrDoc.ExtendMerge(0, 0, nEndCol, nEndRow, nTabNo, true); + if (pDocShell) + pDocShell->PostPaint(ScRange(0, 0, nTabNo, mrDoc.MaxCol(), mrDoc.MaxRow(), nTabNo), + PaintPartFlags::Grid); + } +} + +void ScViewData::GetMouseQuadrant( const Point& rClickPos, ScSplitPos eWhich, + SCCOL nPosX, SCROW nPosY, bool& rLeft, bool& rTop ) +{ + bool bLayoutRTL = mrDoc.IsLayoutRTL(nTabNo); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + Point aCellStart = GetScrPos( nPosX, nPosY, eWhich, true ); + tools::Long nSizeX; + tools::Long nSizeY; + GetMergeSizePixel( nPosX, nPosY, nSizeX, nSizeY ); + rLeft = ( rClickPos.X() - aCellStart.X() ) * nLayoutSign <= nSizeX / 2; + rTop = rClickPos.Y() - aCellStart.Y() <= nSizeY / 2; +} + +void ScViewData::SetPosX( ScHSplitPos eWhich, SCCOL nNewPosX ) +{ + // in the tiled rendering case, nPosX [the leftmost visible column] must be 0 + bool bIsTiledRendering = comphelper::LibreOfficeKit::isActive(); + if (nNewPosX != 0 && !bIsTiledRendering) + { + SCCOL nOldPosX = pThisTab->nPosX[eWhich]; + tools::Long nTPosX = pThisTab->nTPosX[eWhich]; + tools::Long nPixPosX = pThisTab->nPixPosX[eWhich]; + SCCOL i; + if ( nNewPosX > nOldPosX ) + for ( i=nOldPosX; i<nNewPosX; i++ ) + { + tools::Long nThis = mrDoc.GetColWidth(i, nTabNo); + nTPosX -= nThis; + nPixPosX -= ToPixel(sal::static_int_cast<sal_uInt16>(nThis), nPPTX); + } + else + for ( i=nNewPosX; i<nOldPosX; i++ ) + { + tools::Long nThis = mrDoc.GetColWidth(i, nTabNo); + nTPosX += nThis; + nPixPosX += ToPixel(sal::static_int_cast<sal_uInt16>(nThis), nPPTX); + } + + pThisTab->nPosX[eWhich] = nNewPosX; + pThisTab->nTPosX[eWhich] = nTPosX; + pThisTab->nMPosX[eWhich] = o3tl::convert(nTPosX, o3tl::Length::twip, o3tl::Length::mm100); + pThisTab->nPixPosX[eWhich] = nPixPosX; + } + else + { + pThisTab->nPixPosX[eWhich] = + pThisTab->nTPosX[eWhich] = + pThisTab->nMPosX[eWhich] = + pThisTab->nPosX[eWhich] = 0; + } +} + +void ScViewData::SetPosY( ScVSplitPos eWhich, SCROW nNewPosY ) +{ + // in the tiled rendering case, nPosY [the topmost visible row] must be 0 + bool bIsTiledRendering = comphelper::LibreOfficeKit::isActive(); + if (nNewPosY != 0 && !bIsTiledRendering) + { + SCROW nOldPosY = pThisTab->nPosY[eWhich]; + tools::Long nTPosY = pThisTab->nTPosY[eWhich]; + tools::Long nPixPosY = pThisTab->nPixPosY[eWhich]; + SCROW i, nHeightEndRow; + if ( nNewPosY > nOldPosY ) + for ( i=nOldPosY; i<nNewPosY; i++ ) + { + tools::Long nThis = mrDoc.GetRowHeight(i, nTabNo, nullptr, &nHeightEndRow); + SCROW nRows = std::min( nNewPosY, nHeightEndRow + 1) - i; + i = nHeightEndRow; + nTPosY -= nThis * nRows; + nPixPosY -= ToPixel(sal::static_int_cast<sal_uInt16>(nThis), nPPTY) * nRows; + } + else + for ( i=nNewPosY; i<nOldPosY; i++ ) + { + tools::Long nThis = mrDoc.GetRowHeight(i, nTabNo, nullptr, &nHeightEndRow); + SCROW nRows = std::min( nOldPosY, nHeightEndRow + 1) - i; + i = nHeightEndRow; + nTPosY += nThis * nRows; + nPixPosY += ToPixel(sal::static_int_cast<sal_uInt16>(nThis), nPPTY) * nRows; + } + + pThisTab->nPosY[eWhich] = nNewPosY; + pThisTab->nTPosY[eWhich] = nTPosY; + pThisTab->nMPosY[eWhich] = o3tl::convert(nTPosY, o3tl::Length::twip, o3tl::Length::mm100); + pThisTab->nPixPosY[eWhich] = nPixPosY; + } + else + { + pThisTab->nPixPosY[eWhich] = + pThisTab->nTPosY[eWhich] = + pThisTab->nMPosY[eWhich] = + pThisTab->nPosY[eWhich] = 0; + } +} + +void ScViewData::RecalcPixPos() // after zoom changes +{ + for (sal_uInt16 eWhich=0; eWhich<2; eWhich++) + { + tools::Long nPixPosX = 0; + SCCOL nPosX = pThisTab->nPosX[eWhich]; + for (SCCOL i=0; i<nPosX; i++) + nPixPosX -= ToPixel(mrDoc.GetColWidth(i, nTabNo), nPPTX); + pThisTab->nPixPosX[eWhich] = nPixPosX; + + tools::Long nPixPosY = 0; + SCROW nPosY = pThisTab->nPosY[eWhich]; + tools::Long nRowHeight = -1; + SCROW nLastSameHeightRow = -1; + for (SCROW j=0; j<nPosY; j++) + { + if(nLastSameHeightRow < j) + nRowHeight = ToPixel(mrDoc.GetRowHeight(j, nTabNo, nullptr, &nLastSameHeightRow), nPPTY); + nPixPosY -= nRowHeight; + } + pThisTab->nPixPosY[eWhich] = nPixPosY; + } +} + +const MapMode& ScViewData::GetLogicMode( ScSplitPos eWhich ) +{ + aLogicMode.SetOrigin( Point( pThisTab->nMPosX[WhichH(eWhich)], + pThisTab->nMPosY[WhichV(eWhich)] ) ); + return aLogicMode; +} + +const MapMode& ScViewData::GetLogicMode() +{ + aLogicMode.SetOrigin( Point() ); + return aLogicMode; +} + +void ScViewData::SetScreen( SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2 ) +{ + SCCOL nCol; + SCROW nRow; + sal_uInt16 nTSize; + tools::Long nSizePix; + tools::Long nScrPosX = 0; + tools::Long nScrPosY = 0; + + SetActivePart( SC_SPLIT_BOTTOMLEFT ); + SetPosX( SC_SPLIT_LEFT, nCol1 ); + SetPosY( SC_SPLIT_BOTTOM, nRow1 ); + + for (nCol=nCol1; nCol<=nCol2; nCol++) + { + nTSize = mrDoc.GetColWidth(nCol, nTabNo); + if (nTSize) + { + nSizePix = ToPixel( nTSize, nPPTX ); + nScrPosX += static_cast<sal_uInt16>(nSizePix); + } + } + + for (nRow=nRow1; nRow<=nRow2; nRow++) + { + nTSize = mrDoc.GetRowHeight(nRow, nTabNo); + if (nTSize) + { + nSizePix = ToPixel( nTSize, nPPTY ); + nScrPosY += static_cast<sal_uInt16>(nSizePix); + } + } + + aScrSize = Size( nScrPosX, nScrPosY ); +} + +void ScViewData::SetScreenPos( const Point& rVisAreaStart ) +{ + tools::Long nSize; + tools::Long nTwips; + tools::Long nAdd; + bool bEnd; + + nSize = 0; + nTwips = o3tl::convert(rVisAreaStart.X(), o3tl::Length::mm100, o3tl::Length::twip); + if (mrDoc.IsLayoutRTL(nTabNo)) + nTwips = -nTwips; + SCCOL nX1 = 0; + bEnd = false; + while (!bEnd) + { + nAdd = static_cast<tools::Long>(mrDoc.GetColWidth(nX1, nTabNo)); + if (nSize + nAdd <= nTwips + 1 && nX1 < mrDoc.MaxCol()) + { + nSize += nAdd; + ++nX1; + } + else + bEnd = true; + } + + nSize = 0; + nTwips = o3tl::convert(rVisAreaStart.Y(), o3tl::Length::mm100, o3tl::Length::twip); + SCROW nY1 = 0; + bEnd = false; + while (!bEnd) + { + nAdd = static_cast<tools::Long>(mrDoc.GetRowHeight(nY1, nTabNo)); + if (nSize + nAdd <= nTwips + 1 && nY1 < mrDoc.MaxRow()) + { + nSize += nAdd; + ++nY1; + } + else + bEnd = true; + } + + SetActivePart( SC_SPLIT_BOTTOMLEFT ); + SetPosX( SC_SPLIT_LEFT, nX1 ); + SetPosY( SC_SPLIT_BOTTOM, nY1 ); + + SetCurX( nX1 ); + SetCurY( nY1 ); +} + +void ScViewData::SetScreen( const tools::Rectangle& rVisArea ) +{ + SetScreenPos( rVisArea.TopLeft() ); + + // here without GetOutputFactor(), since it's for the output into a Metafile + + aScrSize = rVisArea.GetSize(); + aScrSize.setWidth(std::round(o3tl::convert( aScrSize.Width(), o3tl::Length::mm100, o3tl::Length::twip) * ScGlobal::nScreenPPTX)); + aScrSize.setHeight(std::round(o3tl::convert( aScrSize.Height(), o3tl::Length::mm100, o3tl::Length::twip) * ScGlobal::nScreenPPTY)); +} + +ScDocFunc& ScViewData::GetDocFunc() const +{ + return pDocShell->GetDocFunc(); +} + +SfxBindings& ScViewData::GetBindings() +{ + assert(pView && "GetBindings() without ViewShell"); + return pView->GetViewFrame().GetBindings(); +} + +SfxDispatcher& ScViewData::GetDispatcher() +{ + assert(pView && "GetDispatcher() without ViewShell"); + return *pView->GetViewFrame().GetDispatcher(); +} + +ScMarkData& ScViewData::GetMarkData() +{ + return maMarkData; +} + +ScMarkData& ScViewData::GetHighlightData() +{ + return maHighlightData; +} + +const ScMarkData& ScViewData::GetMarkData() const +{ + return maMarkData; +} + +weld::Window* ScViewData::GetDialogParent() +{ + assert(pView && "GetDialogParent() without ViewShell"); + return pView->GetDialogParent(); +} + +ScGridWindow* ScViewData::GetActiveWin() +{ + assert(pView && "GetActiveWin() without View"); + return pView->GetActiveWin(); +} + +const ScGridWindow* ScViewData::GetActiveWin() const +{ + assert(pView && "GetActiveWin() without View"); + return pView->GetActiveWin(); +} + +ScDrawView* ScViewData::GetScDrawView() +{ + assert(pView && "GetScDrawView() without View"); + return pView->GetScDrawView(); +} + +bool ScViewData::IsMinimized() const +{ + assert(pView && "IsMinimized() without View"); + return pView->IsMinimized(); +} + +void ScViewData::UpdateScreenZoom( const Fraction& rNewX, const Fraction& rNewY ) +{ + Fraction aOldX = GetZoomX(); + Fraction aOldY = GetZoomY(); + + SetZoom( rNewX, rNewY, false ); + + Fraction aWidth = GetZoomX(); + aWidth *= Fraction( aScrSize.Width(),1 ); + aWidth /= aOldX; + + Fraction aHeight = GetZoomY(); + aHeight *= Fraction( aScrSize.Height(),1 ); + aHeight /= aOldY; + + aScrSize.setWidth( static_cast<tools::Long>(aWidth) ); + aScrSize.setHeight( static_cast<tools::Long>(aHeight) ); +} + +void ScViewData::CalcPPT() +{ + double nOldPPTX = nPPTX; + double nOldPPTY = nPPTY; + nPPTX = ScGlobal::nScreenPPTX * static_cast<double>(GetZoomX()); + if (pDocShell) + nPPTX = nPPTX / pDocShell->GetOutputFactor(); // Factor is printer to screen + nPPTY = ScGlobal::nScreenPPTY * static_cast<double>(GetZoomY()); + + // if detective objects are present, + // try to adjust horizontal scale so the most common column width has minimal rounding errors, + // to avoid differences between cell and drawing layer output + + if (mrDoc.HasDetectiveObjects(nTabNo)) + { + SCCOL nEndCol = 0; + SCROW nDummy = 0; + mrDoc.GetTableArea(nTabNo, nEndCol, nDummy); + if (nEndCol<20) + nEndCol = 20; // same end position as when determining draw scale + + sal_uInt16 nTwips = mrDoc.GetCommonWidth(nEndCol, nTabNo); + if ( nTwips ) + { + double fOriginal = nTwips * nPPTX; + if ( fOriginal < static_cast<double>(nEndCol) ) + { + // if one column is smaller than the column count, + // rounding errors are likely to add up to a whole column. + + double fRounded = ::rtl::math::approxFloor( fOriginal + 0.5 ); + if ( fRounded > 0.0 ) + { + double fScale = fRounded / fOriginal + 1E-6; + if ( fScale >= 0.9 && fScale <= 1.1 ) + nPPTX *= fScale; + } + } + } + } + + if (!comphelper::LibreOfficeKit::isActive()) + return; + + SCTAB nTabCount = maTabData.size(); + bool bResetWidths = (nPPTX != nOldPPTX); + bool bResetHeights = (nPPTY != nOldPPTY); + for (SCTAB nTabIdx = 0; nTabIdx < nTabCount; ++nTabIdx) + { + if (!maTabData[nTabIdx]) + continue; + + if (bResetWidths) + if (auto* pWHelper = GetLOKWidthHelper(nTabIdx)) + pWHelper->invalidateByPosition(0L); + + if (bResetHeights) + if (auto* pHHelper = GetLOKHeightHelper(nTabIdx)) + pHHelper->invalidateByPosition(0L); + } +} + +#define SC_OLD_TABSEP '/' +#define SC_NEW_TABSEP '+' + +void ScViewData::WriteUserData(OUString& rData) +{ + // nZoom (until 364v) or nZoom/nPageZoom/bPageMode (from 364w) + // nTab + // Tab control width + // per sheet: + // CursorX/CursorY/HSplitMode/VSplitMode/HSplitPos/VSplitPos/SplitActive/ + // PosX[left]/PosX[right]/PosY[top]/PosY[bottom] + // when rows bigger than 8192, "+" instead of "/" + + sal_uInt16 nZoom = static_cast<sal_uInt16>(tools::Long(pThisTab->aZoomY * 100)); + rData = OUString::number( nZoom ) + "/"; + nZoom = static_cast<sal_uInt16>(tools::Long(pThisTab->aPageZoomY * 100)); + rData += OUString::number( nZoom ) + "/"; + if (bPagebreak) + rData += "1"; + else + rData += "0"; + + rData += ";" + OUString::number( nTabNo ) + ";" + TAG_TABBARWIDTH + + OUString::number( pView->GetTabBarWidth() ); + + SCTAB nTabCount = mrDoc.GetTableCount(); + for (SCTAB i=0; i<nTabCount; i++) + { + rData += ";"; // Numbering must not get mixed up under any circumstances + if (i < static_cast<SCTAB>(maTabData.size()) && maTabData[i]) + { + OUString cTabSep(SC_OLD_TABSEP); // like 3.1 + if ( maTabData[i]->nCurY > MAXROW_30 || + maTabData[i]->nPosY[0] > MAXROW_30 || maTabData[i]->nPosY[1] > MAXROW_30 || + ( maTabData[i]->eVSplitMode == SC_SPLIT_FIX && + maTabData[i]->nFixPosY > MAXROW_30 ) ) + { + cTabSep = OUStringChar(SC_NEW_TABSEP); // in order to not kill a 3.1-version + } + + rData += OUString::number( maTabData[i]->nCurX ) + cTabSep + + OUString::number( maTabData[i]->nCurY ) + cTabSep + + OUString::number( maTabData[i]->eHSplitMode ) + cTabSep + + OUString::number( maTabData[i]->eVSplitMode ) + cTabSep; + if ( maTabData[i]->eHSplitMode == SC_SPLIT_FIX ) + rData += OUString::number( maTabData[i]->nFixPosX ); + else + rData += OUString::number( maTabData[i]->nHSplitPos ); + rData += cTabSep; + if ( maTabData[i]->eVSplitMode == SC_SPLIT_FIX ) + rData += OUString::number( maTabData[i]->nFixPosY ); + else + rData += OUString::number( maTabData[i]->nVSplitPos ); + rData += cTabSep + + OUString::number( maTabData[i]->eWhichActive ) + cTabSep + + OUString::number( maTabData[i]->nPosX[0] ) + cTabSep + + OUString::number( maTabData[i]->nPosX[1] ) + cTabSep + + OUString::number( maTabData[i]->nPosY[0] ) + cTabSep + + OUString::number( maTabData[i]->nPosY[1] ); + } + } +} + +void ScViewData::ReadUserData(std::u16string_view rData) +{ + if (rData.empty()) // empty string on "reload" + return; // then exit without assertion + + if ( comphelper::string::getTokenCount(rData, ';') <= 2 ) + { + // when reload, in page preview, the preview UserData may have been left intact. + // we don't want the zoom from the page preview here. + OSL_FAIL("ReadUserData: This is not my data"); + return; + } + + sal_Int32 nMainIdx {0}; + sal_Int32 nIdx {0}; + + std::u16string_view aZoomStr = o3tl::getToken(rData, 0, ';', nMainIdx); // Zoom/PageZoom/Mode + sal_Unicode cMode = o3tl::getToken(aZoomStr, 2, '/', nIdx)[0]; // 0 or "0"/"1" + SetPagebreakMode( cMode == '1' ); + // SetPagebreakMode must always be called due to CalcPPT / RecalcPixPos() + + // sheet may have become invalid (for instance last version): + SCTAB nNewTab = static_cast<SCTAB>(o3tl::toUInt32(o3tl::getToken(rData, 0, ';', nMainIdx))); + if (mrDoc.HasTable(nNewTab)) + SetTabNo(nNewTab); + + // if available, get tab bar width: + const sal_Int32 nMainIdxRef {nMainIdx}; + std::u16string_view aTabOpt = o3tl::getToken(rData, 0, ';', nMainIdx); + + std::u16string_view aRest; + if (o3tl::starts_with(aTabOpt, TAG_TABBARWIDTH, &aRest)) + { + pView->SetTabBarWidth(o3tl::toInt32(aRest)); + } + else + { + // Tab bar width not specified, token to be processed again + nMainIdx = nMainIdxRef; + } + + // per sheet + SCTAB nPos = 0; + while ( nMainIdx>0 ) + { + aTabOpt = o3tl::getToken(rData, 0, ';', nMainIdx); + EnsureTabDataSize(nPos + 1); + if (!maTabData[nPos]) + maTabData[nPos].reset(new ScViewDataTable(&mrDoc)); + + sal_Unicode cTabSep = 0; + if (comphelper::string::getTokenCount(aTabOpt, SC_OLD_TABSEP) >= 11) + cTabSep = SC_OLD_TABSEP; + else if (comphelper::string::getTokenCount(aTabOpt, SC_NEW_TABSEP) >= 11) + cTabSep = SC_NEW_TABSEP; + // '+' is only allowed, if we can deal with rows > 8192 + + if (cTabSep) + { + nIdx = 0; + maTabData[nPos]->nCurX = mrDoc.SanitizeCol(static_cast<SCCOL>(o3tl::toInt32(o3tl::getToken(aTabOpt, 0, cTabSep, nIdx)))); + maTabData[nPos]->nCurY = mrDoc.SanitizeRow(o3tl::toInt32(o3tl::getToken(aTabOpt, 0, cTabSep, nIdx))); + maTabData[nPos]->eHSplitMode = static_cast<ScSplitMode>(o3tl::toInt32(o3tl::getToken(aTabOpt, 0, cTabSep, nIdx))); + maTabData[nPos]->eVSplitMode = static_cast<ScSplitMode>(o3tl::toInt32(o3tl::getToken(aTabOpt, 0, cTabSep, nIdx))); + + sal_Int32 nTmp = o3tl::toInt32(o3tl::getToken(aTabOpt, 0, cTabSep, nIdx)); + if ( maTabData[nPos]->eHSplitMode == SC_SPLIT_FIX ) + { + maTabData[nPos]->nFixPosX = mrDoc.SanitizeCol(static_cast<SCCOL>(nTmp)); + UpdateFixX(nPos); + } + else + maTabData[nPos]->nHSplitPos = nTmp; + + nTmp = o3tl::toInt32(o3tl::getToken(aTabOpt, 0, cTabSep, nIdx)); + if ( maTabData[nPos]->eVSplitMode == SC_SPLIT_FIX ) + { + maTabData[nPos]->nFixPosY = mrDoc.SanitizeRow(nTmp); + UpdateFixY(nPos); + } + else + maTabData[nPos]->nVSplitPos = nTmp; + + maTabData[nPos]->eWhichActive = static_cast<ScSplitPos>(o3tl::toInt32(o3tl::getToken(aTabOpt, 0, cTabSep, nIdx))); + maTabData[nPos]->nPosX[0] = mrDoc.SanitizeCol(static_cast<SCCOL>(o3tl::toInt32(o3tl::getToken(aTabOpt, 0, cTabSep, nIdx)))); + maTabData[nPos]->nPosX[1] = mrDoc.SanitizeCol(static_cast<SCCOL>(o3tl::toInt32(o3tl::getToken(aTabOpt, 0, cTabSep, nIdx)))); + maTabData[nPos]->nPosY[0] = mrDoc.SanitizeRow(o3tl::toInt32(o3tl::getToken(aTabOpt, 0, cTabSep, nIdx))); + maTabData[nPos]->nPosY[1] = mrDoc.SanitizeRow(o3tl::toInt32(o3tl::getToken(aTabOpt, 0, cTabSep, nIdx))); + + maTabData[nPos]->eWhichActive = maTabData[nPos]->SanitizeWhichActive(); + } + ++nPos; + } + + RecalcPixPos(); +} + +void ScViewData::WriteExtOptions( ScExtDocOptions& rDocOpt ) const +{ + // *** Fill extended document data for export filters *** + + // document settings + ScExtDocSettings& rDocSett = rDocOpt.GetDocSettings(); + + // displayed sheet + rDocSett.mnDisplTab = GetTabNo(); + + // width of the tabbar, relative to frame window width + rDocSett.mfTabBarWidth = pView->GetPendingRelTabBarWidth(); + if( rDocSett.mfTabBarWidth < 0.0 ) + rDocSett.mfTabBarWidth = ScTabView::GetRelTabBarWidth(); + + bool bLOKActive = comphelper::LibreOfficeKit::isActive(); + + // sheet settings + for( SCTAB nTab = 0; nTab < static_cast<SCTAB>(maTabData.size()); ++nTab ) + { + if( const ScViewDataTable* pViewTab = maTabData[ nTab ].get() ) + { + ScExtTabSettings& rTabSett = rDocOpt.GetOrCreateTabSettings( nTab ); + + // split mode + ScSplitMode eExHSplit = pViewTab->eHSplitMode; + ScSplitMode eExVSplit = pViewTab->eVSplitMode; + SCCOL nExFixPosX = pViewTab->nFixPosX; + SCROW nExFixPosY = pViewTab->nFixPosY; + tools::Long nExHSplitPos = pViewTab->nHSplitPos; + tools::Long nExVSplitPos = pViewTab->nVSplitPos; + + if (bLOKActive) + { + OverrideWithLOKFreeze(eExHSplit, eExVSplit, + nExFixPosX, nExFixPosY, + nExHSplitPos, nExVSplitPos, nTab); + } + + bool bHSplit = eExHSplit != SC_SPLIT_NONE; + bool bVSplit = eExVSplit != SC_SPLIT_NONE; + bool bRealSplit = (eExHSplit == SC_SPLIT_NORMAL) || (eExVSplit == SC_SPLIT_NORMAL); + bool bFrozen = (eExHSplit == SC_SPLIT_FIX) || (eExVSplit == SC_SPLIT_FIX); + OSL_ENSURE( !bRealSplit || !bFrozen, "ScViewData::WriteExtOptions - split and freeze in same sheet" ); + rTabSett.mbFrozenPanes = !bRealSplit && bFrozen; + + // split and freeze position + rTabSett.maSplitPos = Point( 0, 0 ); + rTabSett.maFreezePos.Set( 0, 0, nTab ); + if( bRealSplit ) + { + Point& rSplitPos = rTabSett.maSplitPos; + rSplitPos = Point( bHSplit ? nExHSplitPos : 0, bVSplit ? nExVSplitPos : 0 ); + rSplitPos = Application::GetDefaultDevice()->PixelToLogic( rSplitPos, MapMode( MapUnit::MapTwip ) ); + if( pDocShell ) + rSplitPos.setX( static_cast<tools::Long>(static_cast<double>(rSplitPos.X()) / pDocShell->GetOutputFactor()) ); + } + else if( bFrozen ) + { + if( bHSplit ) rTabSett.maFreezePos.SetCol( nExFixPosX ); + if( bVSplit ) rTabSett.maFreezePos.SetRow( nExFixPosY ); + } + + // first visible cell in top-left and additional panes + rTabSett.maFirstVis.Set( pViewTab->nPosX[ SC_SPLIT_LEFT ], pViewTab->nPosY[ bVSplit ? SC_SPLIT_TOP : SC_SPLIT_BOTTOM ], nTab ); + rTabSett.maSecondVis.Set( pViewTab->nPosX[ SC_SPLIT_RIGHT ], pViewTab->nPosY[ SC_SPLIT_BOTTOM ], nTab ); + + // active pane + switch( pViewTab->eWhichActive ) + { + // no horizontal split -> always use left panes + // no vertical split -> always use top panes + case SC_SPLIT_TOPLEFT: + rTabSett.meActivePane = SCEXT_PANE_TOPLEFT; + break; + case SC_SPLIT_TOPRIGHT: + rTabSett.meActivePane = bHSplit ? SCEXT_PANE_TOPRIGHT : SCEXT_PANE_TOPLEFT; + break; + case SC_SPLIT_BOTTOMLEFT: + rTabSett.meActivePane = bVSplit ? SCEXT_PANE_BOTTOMLEFT : SCEXT_PANE_TOPLEFT; + break; + case SC_SPLIT_BOTTOMRIGHT: + rTabSett.meActivePane = bHSplit ? + (bVSplit ? SCEXT_PANE_BOTTOMRIGHT : SCEXT_PANE_TOPRIGHT) : + (bVSplit ? SCEXT_PANE_BOTTOMLEFT : SCEXT_PANE_TOPLEFT); + break; + } + + // cursor position + rTabSett.maCursor.Set( pViewTab->nCurX, pViewTab->nCurY, nTab ); + + // sheet selection and selected ranges + const ScMarkData& rMarkData = GetMarkData(); + rTabSett.mbSelected = rMarkData.GetTableSelect( nTab ); + rMarkData.FillRangeListWithMarks( &rTabSett.maSelection, true ); + rTabSett.mbShowGrid = pViewTab->bShowGrid; + + // view mode and zoom + rTabSett.mbPageMode = bPagebreak; + rTabSett.mnNormalZoom = static_cast< tools::Long >( pViewTab->aZoomY * Fraction( 100.0 ) ); + rTabSett.mnPageZoom = static_cast< tools::Long >( pViewTab->aPageZoomY * Fraction( 100.0 ) ); + } + } +} + +void ScViewData::ReadExtOptions( const ScExtDocOptions& rDocOpt ) +{ + // *** Get extended document data from import filters *** + + if( !rDocOpt.IsChanged() ) return; + + // document settings + const ScExtDocSettings& rDocSett = rDocOpt.GetDocSettings(); + + // displayed sheet + SetTabNo( rDocSett.mnDisplTab ); + + /* Width of the tabbar, relative to frame window width. We do not have the + correct width of the frame window here -> store in ScTabView, which sets + the size in the next resize. */ + pView->SetPendingRelTabBarWidth( rDocSett.mfTabBarWidth ); + + // sheet settings + SCTAB nLastTab = rDocOpt.GetLastTab(); + if (static_cast<SCTAB>(maTabData.size()) <= nLastTab) + maTabData.resize(nLastTab+1); + + for( SCTAB nTab = 0; nTab < static_cast<SCTAB>(maTabData.size()); ++nTab ) + { + if( const ScExtTabSettings* pTabSett = rDocOpt.GetTabSettings( nTab ) ) + { + if( !maTabData[ nTab ] ) + maTabData[nTab].reset(new ScViewDataTable(&mrDoc)); + + const ScExtTabSettings& rTabSett = *pTabSett; + ScViewDataTable& rViewTab = *maTabData[ nTab ]; + + // split mode initialization + bool bFrozen = rTabSett.mbFrozenPanes; + bool bHSplit = bFrozen ? (rTabSett.maFreezePos.Col() > 0) : (rTabSett.maSplitPos.X() > 0); + bool bVSplit = bFrozen ? (rTabSett.maFreezePos.Row() > 0) : (rTabSett.maSplitPos.Y() > 0); + + // first visible cell of top-left pane and additional panes + if (rTabSett.maFirstVis.IsValid()) + { + rViewTab.nPosX[ SC_SPLIT_LEFT ] = rTabSett.maFirstVis.Col(); + rViewTab.nPosY[ bVSplit ? SC_SPLIT_TOP : SC_SPLIT_BOTTOM ] = rTabSett.maFirstVis.Row(); + } + + if (rTabSett.maSecondVis.IsValid()) + { + if (bHSplit) + rViewTab.nPosX[ SC_SPLIT_RIGHT ] = rTabSett.maSecondVis.Col(); + if (bVSplit) + rViewTab.nPosY[ SC_SPLIT_BOTTOM ] = rTabSett.maSecondVis.Row(); + } + + // split mode, split and freeze position + rViewTab.eHSplitMode = rViewTab.eVSplitMode = SC_SPLIT_NONE; + rViewTab.nHSplitPos = rViewTab.nVSplitPos = 0; + rViewTab.nFixPosX = 0; + rViewTab.nFixPosY = 0; + if( bFrozen ) + { + if( bHSplit ) + { + rViewTab.eHSplitMode = SC_SPLIT_FIX; + rViewTab.nFixPosX = rTabSett.maFreezePos.Col(); + UpdateFixX( nTab ); + } + if( bVSplit ) + { + rViewTab.eVSplitMode = SC_SPLIT_FIX; + rViewTab.nFixPosY = rTabSett.maFreezePos.Row(); + UpdateFixY( nTab ); + } + } + else + { + Point aPixel = Application::GetDefaultDevice()->LogicToPixel( + rTabSett.maSplitPos, MapMode( MapUnit::MapTwip ) ); //! Zoom? + // the test for use of printer metrics for text formatting here + // effectively results in the nFactor = 1.0 regardless of the Option setting. + if( pDocShell && SC_MOD()->GetInputOptions().GetTextWysiwyg()) + { + double nFactor = pDocShell->GetOutputFactor(); + aPixel.setX( static_cast<tools::Long>( aPixel.X() * nFactor + 0.5 ) ); + } + + bHSplit = bHSplit && aPixel.X() > 0; + bVSplit = bVSplit && aPixel.Y() > 0; + if( bHSplit ) + { + rViewTab.eHSplitMode = SC_SPLIT_NORMAL; + rViewTab.nHSplitPos = aPixel.X(); + } + if( bVSplit ) + { + rViewTab.eVSplitMode = SC_SPLIT_NORMAL; + rViewTab.nVSplitPos = aPixel.Y(); + } + } + + // active pane + ScSplitPos ePos = SC_SPLIT_BOTTOMLEFT; + switch( rTabSett.meActivePane ) + { + // no horizontal split -> always use left panes + // no vertical split -> always use *bottom* panes + case SCEXT_PANE_TOPLEFT: + ePos = bVSplit ? SC_SPLIT_TOPLEFT : SC_SPLIT_BOTTOMLEFT; + break; + case SCEXT_PANE_TOPRIGHT: + ePos = bHSplit ? + (bVSplit ? SC_SPLIT_TOPRIGHT : SC_SPLIT_BOTTOMRIGHT) : + (bVSplit ? SC_SPLIT_TOPLEFT : SC_SPLIT_BOTTOMLEFT); + break; + case SCEXT_PANE_BOTTOMLEFT: + ePos = SC_SPLIT_BOTTOMLEFT; + break; + case SCEXT_PANE_BOTTOMRIGHT: + ePos = bHSplit ? SC_SPLIT_BOTTOMRIGHT : SC_SPLIT_BOTTOMLEFT; + break; + } + rViewTab.eWhichActive = ePos; + + // cursor position + const ScAddress& rCursor = rTabSett.maCursor; + if( rCursor.IsValid() ) + { + rViewTab.nCurX = rCursor.Col(); + rViewTab.nCurY = rCursor.Row(); + } + + // sheet selection and selected ranges + ScMarkData& rMarkData = GetMarkData(); + rMarkData.SelectTable( nTab, rTabSett.mbSelected ); + + // zoom for each sheet + if( rTabSett.mnNormalZoom ) + rViewTab.aZoomX = rViewTab.aZoomY = Fraction( rTabSett.mnNormalZoom, 100 ); + if( rTabSett.mnPageZoom ) + rViewTab.aPageZoomX = rViewTab.aPageZoomY = Fraction( rTabSett.mnPageZoom, 100 ); + + rViewTab.bShowGrid = rTabSett.mbShowGrid; + + // get some settings from displayed Excel sheet, set at Calc document + if( nTab == GetTabNo() ) + { + // view mode and default zoom (for new sheets) from current sheet + if( rTabSett.mnNormalZoom ) + aDefZoomX = aDefZoomY = Fraction( rTabSett.mnNormalZoom, 100L ); + if( rTabSett.mnPageZoom ) + aDefPageZoomX = aDefPageZoomY = Fraction( rTabSett.mnPageZoom, 100L ); + /* #i46820# set pagebreak mode via SetPagebreakMode(), this will + update map modes that are needed to draw text correctly. */ + SetPagebreakMode( rTabSett.mbPageMode ); + } + } + } + + if (comphelper::LibreOfficeKit::isActive()) + DeriveLOKFreezeAllSheets(); + + // RecalcPixPos or so - also nMPos - also for ReadUserData ??!?! +} + +void ScViewData::WriteUserDataSequence(uno::Sequence <beans::PropertyValue>& rSettings) const +{ + rSettings.realloc(SC_VIEWSETTINGS_COUNT); + // + 1, because we have to put the view id in the sequence + beans::PropertyValue* pSettings = rSettings.getArray(); + + sal_uInt16 nViewID(pView->GetViewFrame().GetCurViewId()); + pSettings[SC_VIEW_ID].Name = SC_VIEWID; + pSettings[SC_VIEW_ID].Value <<= SC_VIEW + OUString::number(nViewID); + + uno::Reference<container::XNameContainer> xNameContainer = + document::NamedPropertyValues::create( comphelper::getProcessComponentContext() ); + for (SCTAB nTab=0; nTab<static_cast<SCTAB>(maTabData.size()); nTab++) + { + if (maTabData[nTab]) + { + uno::Sequence <beans::PropertyValue> aTableViewSettings; + maTabData[nTab]->WriteUserDataSequence(aTableViewSettings, *this, nTab); + OUString sTabName; + GetDocument().GetName( nTab, sTabName ); + try + { + xNameContainer->insertByName(sTabName, uno::Any(aTableViewSettings)); + } + //#101739#; two tables with the same name are possible + catch ( container::ElementExistException& ) + { + OSL_FAIL("seems there are two tables with the same name"); + } + catch ( uno::RuntimeException& ) + { + OSL_FAIL("something went wrong"); + } + } + } + pSettings[SC_TABLE_VIEWSETTINGS].Name = SC_TABLES; + pSettings[SC_TABLE_VIEWSETTINGS].Value <<= xNameContainer; + + OUString sName; + GetDocument().GetName( nTabNo, sName ); + pSettings[SC_ACTIVE_TABLE].Name = SC_ACTIVETABLE; + pSettings[SC_ACTIVE_TABLE].Value <<= sName; + pSettings[SC_HORIZONTAL_SCROLL_BAR_WIDTH].Name = SC_HORIZONTALSCROLLBARWIDTH; + pSettings[SC_HORIZONTAL_SCROLL_BAR_WIDTH].Value <<= sal_Int32(pView->GetTabBarWidth()); + sal_Int32 nZoomValue = tools::Long(pThisTab->aZoomY * 100); + sal_Int32 nPageZoomValue = tools::Long(pThisTab->aPageZoomY * 100); + pSettings[SC_ZOOM_TYPE].Name = SC_ZOOMTYPE; + pSettings[SC_ZOOM_TYPE].Value <<= sal_Int16(pThisTab->eZoomType); + pSettings[SC_ZOOM_VALUE].Name = SC_ZOOMVALUE; + pSettings[SC_ZOOM_VALUE].Value <<= nZoomValue; + pSettings[SC_PAGE_VIEW_ZOOM_VALUE].Name = SC_PAGEVIEWZOOMVALUE; + pSettings[SC_PAGE_VIEW_ZOOM_VALUE].Value <<= nPageZoomValue; + pSettings[SC_PAGE_BREAK_PREVIEW].Name = SC_SHOWPAGEBREAKPREVIEW; + pSettings[SC_PAGE_BREAK_PREVIEW].Value <<= bPagebreak; + + pSettings[SC_SHOWZERO].Name = SC_UNO_SHOWZERO; + pSettings[SC_SHOWZERO].Value <<= maOptions.GetOption(VOPT_NULLVALS); + pSettings[SC_SHOWNOTES].Name = SC_UNO_SHOWNOTES; + pSettings[SC_SHOWNOTES].Value <<= maOptions.GetOption(VOPT_NOTES); + pSettings[SC_SHOWFORMULASMARKS].Name = SC_UNO_SHOWFORMULASMARKS; + pSettings[SC_SHOWFORMULASMARKS].Value <<= maOptions.GetOption(VOPT_FORMULAS_MARKS); + pSettings[SC_SHOWGRID].Name = SC_UNO_SHOWGRID; + pSettings[SC_SHOWGRID].Value <<= maOptions.GetOption(VOPT_GRID); + pSettings[SC_GRIDCOLOR].Name = SC_UNO_GRIDCOLOR; + OUString aColorName; + Color aColor = maOptions.GetGridColor(&aColorName); + pSettings[SC_GRIDCOLOR].Value <<= aColor; + pSettings[SC_SHOWPAGEBR].Name = SC_UNO_SHOWPAGEBR; + pSettings[SC_SHOWPAGEBR].Value <<= maOptions.GetOption(VOPT_PAGEBREAKS); + pSettings[SC_COLROWHDR].Name = SC_UNO_COLROWHDR; + pSettings[SC_COLROWHDR].Value <<= maOptions.GetOption(VOPT_HEADER); + pSettings[SC_SHEETTABS].Name = SC_UNO_SHEETTABS; + pSettings[SC_SHEETTABS].Value <<= maOptions.GetOption(VOPT_TABCONTROLS); + pSettings[SC_OUTLSYMB].Name = SC_UNO_OUTLSYMB; + pSettings[SC_OUTLSYMB].Value <<= maOptions.GetOption(VOPT_OUTLINER); + pSettings[SC_VALUE_HIGHLIGHTING].Name = SC_UNO_VALUEHIGH; + pSettings[SC_VALUE_HIGHLIGHTING].Value <<= maOptions.GetOption(VOPT_SYNTAX); + pSettings[SC_FORMULA_BAR_HEIGHT_VALUE].Name = SC_FORMULABARHEIGHT; + pSettings[SC_FORMULA_BAR_HEIGHT_VALUE].Value <<= GetFormulaBarLines();; + + const ScGridOptions& aGridOpt = maOptions.GetGridOptions(); + pSettings[SC_SNAPTORASTER].Name = SC_UNO_SNAPTORASTER; + pSettings[SC_SNAPTORASTER].Value <<= aGridOpt.GetUseGridSnap(); + pSettings[SC_RASTERVIS].Name = SC_UNO_RASTERVIS; + pSettings[SC_RASTERVIS].Value <<= aGridOpt.GetGridVisible(); + pSettings[SC_RASTERRESX].Name = SC_UNO_RASTERRESX; + pSettings[SC_RASTERRESX].Value <<= static_cast<sal_Int32>(aGridOpt.GetFieldDrawX()); + pSettings[SC_RASTERRESY].Name = SC_UNO_RASTERRESY; + pSettings[SC_RASTERRESY].Value <<= static_cast<sal_Int32>(aGridOpt.GetFieldDrawY()); + pSettings[SC_RASTERSUBX].Name = SC_UNO_RASTERSUBX; + pSettings[SC_RASTERSUBX].Value <<= static_cast<sal_Int32>(aGridOpt.GetFieldDivisionX()); + pSettings[SC_RASTERSUBY].Name = SC_UNO_RASTERSUBY; + pSettings[SC_RASTERSUBY].Value <<= static_cast<sal_Int32>(aGridOpt.GetFieldDivisionY()); + pSettings[SC_RASTERSYNC].Name = SC_UNO_RASTERSYNC; + pSettings[SC_RASTERSYNC].Value <<= aGridOpt.GetSynchronize(); + + // Common SdrModel processing + GetDocument().GetDrawLayer()->WriteUserDataSequence(rSettings); +} + +void ScViewData::ReadUserDataSequence(const uno::Sequence <beans::PropertyValue>& rSettings) +{ + std::vector<bool> aHasZoomVect( GetDocument().GetTableCount(), false ); + + sal_Int32 nTemp32(0); + sal_Int16 nTemp16(0); + sal_Int16 nFormulaBarLineCount(0); + bool bPageMode(false); + + EnsureTabDataSize(GetDocument().GetTableCount()); + + for (const auto& rSetting : rSettings) + { + // SC_VIEWID has to parse and use by mba + OUString sName(rSetting.Name); + if (sName == SC_TABLES) + { + uno::Reference<container::XNameContainer> xNameContainer; + if ((rSetting.Value >>= xNameContainer) && xNameContainer->hasElements()) + { + const uno::Sequence< OUString > aNames(xNameContainer->getElementNames()); + for (const OUString& sTabName : aNames) + { + SCTAB nTab(0); + if (GetDocument().GetTable(sTabName, nTab)) + { + uno::Any aAny = xNameContainer->getByName(sTabName); + uno::Sequence<beans::PropertyValue> aTabSettings; + if (aAny >>= aTabSettings) + { + EnsureTabDataSize(nTab + 1); + if (!maTabData[nTab]) + maTabData[nTab].reset(new ScViewDataTable(&mrDoc)); + + bool bHasZoom = false; + maTabData[nTab]->ReadUserDataSequence(aTabSettings, *this, nTab, bHasZoom); + aHasZoomVect[nTab] = bHasZoom; + } + } + } + } + } + else if (sName == SC_ACTIVETABLE) + { + OUString sTabName; + if(rSetting.Value >>= sTabName) + { + SCTAB nTab(0); + if (GetDocument().GetTable(sTabName, nTab)) + nTabNo = nTab; + } + } + else if (sName == SC_HORIZONTALSCROLLBARWIDTH) + { + if (rSetting.Value >>= nTemp32) + pView->SetTabBarWidth(nTemp32); + } + else if (sName == SC_RELHORIZONTALTABBARWIDTH) + { + double fWidth = 0.0; + if (rSetting.Value >>= fWidth) + pView->SetPendingRelTabBarWidth( fWidth ); + } + else if (sName == SC_ZOOMTYPE) + { + if (rSetting.Value >>= nTemp16) + eDefZoomType = SvxZoomType(nTemp16); + } + else if (sName == SC_ZOOMVALUE) + { + if (rSetting.Value >>= nTemp32) + { + Fraction aZoom(nTemp32, 100); + aDefZoomX = aDefZoomY = aZoom; + } + } + else if (sName == SC_PAGEVIEWZOOMVALUE) + { + if (rSetting.Value >>= nTemp32) + { + Fraction aZoom(nTemp32, 100); + aDefPageZoomX = aDefPageZoomY = aZoom; + } + } + else if (sName == SC_FORMULABARHEIGHT) + { + if (rSetting.Value >>= nFormulaBarLineCount) + { + SetFormulaBarLines(nFormulaBarLineCount); + // Notify formula bar about changed lines + ScInputHandler* pInputHdl = SC_MOD()->GetInputHdl(); + if (pInputHdl) + { + ScInputWindow* pInputWin = pInputHdl->GetInputWindow(); + if (pInputWin) + pInputWin->NumLinesChanged(); + } + } + } + else if (sName == SC_SHOWPAGEBREAKPREVIEW) + bPageMode = ScUnoHelpFunctions::GetBoolFromAny( rSetting.Value ); + else if ( sName == SC_UNO_SHOWZERO ) + maOptions.SetOption(VOPT_NULLVALS, ScUnoHelpFunctions::GetBoolFromAny(rSetting.Value)); + else if ( sName == SC_UNO_SHOWNOTES ) + maOptions.SetOption(VOPT_NOTES, ScUnoHelpFunctions::GetBoolFromAny(rSetting.Value)); + else if ( sName == SC_UNO_SHOWFORMULASMARKS ) + maOptions.SetOption(VOPT_FORMULAS_MARKS, ScUnoHelpFunctions::GetBoolFromAny(rSetting.Value)); + else if ( sName == SC_UNO_SHOWGRID ) + maOptions.SetOption(VOPT_GRID, ScUnoHelpFunctions::GetBoolFromAny(rSetting.Value)); + else if ( sName == SC_UNO_GRIDCOLOR ) + { + Color aColor; + if (rSetting.Value >>= aColor) + maOptions.SetGridColor(aColor, OUString()); + } + else if ( sName == SC_UNO_SHOWPAGEBR ) + maOptions.SetOption(VOPT_PAGEBREAKS, ScUnoHelpFunctions::GetBoolFromAny(rSetting.Value)); + else if ( sName == SC_UNO_COLROWHDR ) + maOptions.SetOption(VOPT_HEADER, ScUnoHelpFunctions::GetBoolFromAny(rSetting.Value)); + else if ( sName == SC_UNO_SHEETTABS ) + maOptions.SetOption(VOPT_TABCONTROLS, ScUnoHelpFunctions::GetBoolFromAny(rSetting.Value)); + else if ( sName == SC_UNO_OUTLSYMB ) + maOptions.SetOption(VOPT_OUTLINER, ScUnoHelpFunctions::GetBoolFromAny(rSetting.Value)); + else if ( sName == SC_UNO_SHOWOBJ ) + { + // #i80528# placeholders not supported anymore + if ( rSetting.Value >>= nTemp16 ) + maOptions.SetObjMode(VOBJ_TYPE_OLE, (nTemp16 == 1) ? VOBJ_MODE_HIDE : VOBJ_MODE_SHOW); + } + else if ( sName == SC_UNO_SHOWCHARTS ) + { + // #i80528# placeholders not supported anymore + if ( rSetting.Value >>= nTemp16 ) + maOptions.SetObjMode(VOBJ_TYPE_CHART, (nTemp16 == 1) ? VOBJ_MODE_HIDE : VOBJ_MODE_SHOW); + } + else if ( sName == SC_UNO_SHOWDRAW ) + { + // #i80528# placeholders not supported anymore + if ( rSetting.Value >>= nTemp16 ) + maOptions.SetObjMode(VOBJ_TYPE_DRAW, (nTemp16 == 1) ? VOBJ_MODE_HIDE : VOBJ_MODE_SHOW); + } + else if ( sName == SC_UNO_VALUEHIGH && !comphelper::LibreOfficeKit::isActive() ) + maOptions.SetOption(VOPT_SYNTAX, ScUnoHelpFunctions::GetBoolFromAny(rSetting.Value)); + else + { + ScGridOptions aGridOpt(maOptions.GetGridOptions()); + if ( sName == SC_UNO_SNAPTORASTER ) + aGridOpt.SetUseGridSnap( ScUnoHelpFunctions::GetBoolFromAny( rSetting.Value ) ); + else if ( sName == SC_UNO_RASTERVIS ) + aGridOpt.SetGridVisible( ScUnoHelpFunctions::GetBoolFromAny( rSetting.Value ) ); + else if ( sName == SC_UNO_RASTERRESX ) + aGridOpt.SetFieldDrawX( static_cast <sal_uInt32> ( ScUnoHelpFunctions::GetInt32FromAny( rSetting.Value ) ) ); + else if ( sName == SC_UNO_RASTERRESY ) + aGridOpt.SetFieldDrawY( static_cast <sal_uInt32> ( ScUnoHelpFunctions::GetInt32FromAny( rSetting.Value ) ) ); + else if ( sName == SC_UNO_RASTERSUBX ) + aGridOpt.SetFieldDivisionX( static_cast <sal_uInt32> ( ScUnoHelpFunctions::GetInt32FromAny( rSetting.Value ) ) ); + else if ( sName == SC_UNO_RASTERSUBY ) + aGridOpt.SetFieldDivisionY( static_cast <sal_uInt32> ( ScUnoHelpFunctions::GetInt32FromAny( rSetting.Value ) ) ); + else if ( sName == SC_UNO_RASTERSYNC ) + aGridOpt.SetSynchronize( ScUnoHelpFunctions::GetBoolFromAny( rSetting.Value ) ); + // Fallback to common SdrModel processing + else GetDocument().GetDrawLayer()->ReadUserDataSequenceValue(&rSetting); + + maOptions.SetGridOptions(aGridOpt); + } + } + + // copy default zoom to sheets where a different one wasn't specified + for (SCTAB nZoomTab=0; nZoomTab< static_cast<SCTAB>(maTabData.size()); ++nZoomTab) + if (maTabData[nZoomTab] && ( nZoomTab >= static_cast<SCTAB>(aHasZoomVect.size()) || !aHasZoomVect[nZoomTab] )) + { + maTabData[nZoomTab]->eZoomType = eDefZoomType; + maTabData[nZoomTab]->aZoomX = aDefZoomX; + maTabData[nZoomTab]->aZoomY = aDefZoomY; + maTabData[nZoomTab]->aPageZoomX = aDefPageZoomX; + maTabData[nZoomTab]->aPageZoomY = aDefPageZoomY; + } + + if (rSettings.hasElements()) + SetPagebreakMode( bPageMode ); + + // #i47426# write view options to document, needed e.g. for Excel export + mrDoc.SetViewOptions(maOptions); + + if (comphelper::LibreOfficeKit::isActive()) + DeriveLOKFreezeAllSheets(); +} + +void ScViewData::SetOptions( const ScViewOptions& rOpt ) +{ + // if visibility of horizontal ScrollBar is changed, TabBar may have to be resized... + bool bHScrollChanged = (rOpt.GetOption(VOPT_HSCROLL) != maOptions.GetOption(VOPT_HSCROLL)); + + // if graphics are turned on or off, animation has to be started or stopped + // graphics are controlled by VOBJ_TYPE_OLE + bool bGraphicsChanged = (maOptions.GetObjMode(VOBJ_TYPE_OLE) != + rOpt.GetObjMode(VOBJ_TYPE_OLE) ); + + maOptions = rOpt; + OSL_ENSURE( pView, "No View" ); + + if( pView ) + { + pView->ViewOptionsHasChanged( bHScrollChanged, bGraphicsChanged ); + } +} + +Point ScViewData::GetMousePosPixel() +{ + OSL_ENSURE( pView, "GetMousePosPixel() without View" ); + return pView->GetMousePosPixel(); +} + +void ScViewData::UpdateInputHandler( bool bForce ) +{ + if (pView) + pView->UpdateInputHandler(bForce); +} + +bool ScViewData::IsOle() const +{ + return pDocShell && pDocShell->IsOle(); +} + +bool ScViewData::UpdateFixX( SCTAB nTab ) // true = value changed +{ + if (!ValidTab(nTab)) // Default + nTab=nTabNo; // current table + + if (!pView || maTabData[nTab]->eHSplitMode != SC_SPLIT_FIX) + return false; + + ScDocument& rLocalDoc = GetDocument(); + if (!rLocalDoc.HasTable(nTab)) // if called from reload, the sheet may not exist + return false; + + SCCOL nFix = maTabData[nTab]->nFixPosX; + tools::Long nNewPos = 0; + for (SCCOL nX=maTabData[nTab]->nPosX[SC_SPLIT_LEFT]; nX<nFix; nX++) + { + sal_uInt16 nTSize = rLocalDoc.GetColWidth( nX, nTab ); + if (nTSize) + { + tools::Long nPix = ToPixel( nTSize, nPPTX ); + nNewPos += nPix; + } + } + nNewPos += pView->GetGridOffset().X(); + if (nNewPos != maTabData[nTab]->nHSplitPos) + { + maTabData[nTab]->nHSplitPos = nNewPos; + if (nTab == nTabNo) + RecalcPixPos(); // should not be needed + return true; + } + + return false; +} + +bool ScViewData::UpdateFixY( SCTAB nTab ) // true = value changed +{ + if (!ValidTab(nTab)) // Default + nTab=nTabNo; // current table + + if (!pView || maTabData[nTab]->eVSplitMode != SC_SPLIT_FIX) + return false; + + ScDocument& rLocalDoc = GetDocument(); + if (!rLocalDoc.HasTable(nTab)) // if called from reload, the sheet may not exist + return false; + + SCROW nFix = maTabData[nTab]->nFixPosY; + tools::Long nNewPos = 0; + for (SCROW nY=maTabData[nTab]->nPosY[SC_SPLIT_TOP]; nY<nFix; nY++) + { + sal_uInt16 nTSize = rLocalDoc.GetRowHeight( nY, nTab ); + if (nTSize) + { + tools::Long nPix = ToPixel( nTSize, nPPTY ); + nNewPos += nPix; + } + } + nNewPos += pView->GetGridOffset().Y(); + if (nNewPos != maTabData[nTab]->nVSplitPos) + { + maTabData[nTab]->nVSplitPos = nNewPos; + if (nTab == nTabNo) + RecalcPixPos(); // should not be needed + return true; + } + + return false; +} + +void ScViewData::UpdateOutlinerFlags( Outliner& rOutl ) const +{ + ScDocument& rLocalDoc = GetDocument(); + bool bOnlineSpell = rLocalDoc.GetDocOptions().IsAutoSpell(); + + EEControlBits nCntrl = rOutl.GetControlWord(); + nCntrl |= EEControlBits::MARKNONURLFIELDS; + nCntrl &= ~EEControlBits::MARKURLFIELDS; // URLs not shaded for output + nCntrl |= EEControlBits::AUTOCORRECT; + if( bOnlineSpell ) + nCntrl |= EEControlBits::ONLINESPELLING; + else + nCntrl &= ~EEControlBits::ONLINESPELLING; + rOutl.SetControlWord(nCntrl); + + rOutl.SetCalcFieldValueHdl( LINK( SC_MOD(), ScModule, CalcFieldValueHdl ) ); + + // don't call GetSpellChecker if online spelling isn't enabled. + // The language for AutoCorrect etc. is taken from the pool defaults + // (set in ScDocument::UpdateDrawLanguages) + + if ( bOnlineSpell ) + { + css::uno::Reference<css::linguistic2::XSpellChecker1> xXSpellChecker1( LinguMgr::GetSpellChecker() ); + rOutl.SetSpeller( xXSpellChecker1 ); + } + + rOutl.SetDefaultHorizontalTextDirection( + rLocalDoc.GetEditTextDirection( nTabNo ) ); +} + +ScAddress ScViewData::GetCurPos() const +{ + return ScAddress( GetCurX(), GetCurY(), GetTabNo() ); +} + +void ScViewData::SetRefStart( SCCOL nNewX, SCROW nNewY, SCTAB nNewZ ) +{ + nRefStartX = nNewX; nRefStartY = nNewY; nRefStartZ = nNewZ; +} + +void ScViewData::SetRefEnd( SCCOL nNewX, SCROW nNewY, SCTAB nNewZ ) +{ + nRefEndX = nNewX; nRefEndY = nNewY; nRefEndZ = nNewZ; +} + +void ScViewData::AddPixelsWhile( tools::Long & rScrY, tools::Long nEndPixels, SCROW & rPosY, + SCROW nEndRow, double nPPTY, const ScDocument * pDoc, SCTAB nTabNo ) +{ + SCROW nRow = rPosY; + while (rScrY <= nEndPixels && nRow <= nEndRow) + { + SCROW nHeightEndRow; + sal_uInt16 nHeight = pDoc->GetRowHeight( nRow, nTabNo, nullptr, &nHeightEndRow); + if (nHeightEndRow > nEndRow) + nHeightEndRow = nEndRow; + if (!nHeight) + { + if (ValidTab(nTabNo) && nTabNo <= pDoc->GetMaxTableNumber()) + nRow = nHeightEndRow + 1; + else + break; + } + else + { + SCROW nRows = nHeightEndRow - nRow + 1; + sal_Int64 nPixel = ToPixel( nHeight, nPPTY); + sal_Int64 nAdd = nPixel * nRows; + if (nAdd + rScrY > nEndPixels) + { + sal_Int64 nDiff = rScrY + nAdd - nEndPixels; + nRows -= static_cast<SCROW>(nDiff / nPixel); + nAdd = nPixel * nRows; + // We're looking for a value that satisfies loop condition. + if (nAdd + rScrY <= nEndPixels) + { + ++nRows; + nAdd += nPixel; + } + } + rScrY += static_cast<tools::Long>(nAdd); + nRow += nRows; + } + } + if (nRow > rPosY) + --nRow; + rPosY = nRow; +} + +void ScViewData::AddPixelsWhileBackward( tools::Long & rScrY, tools::Long nEndPixels, + SCROW & rPosY, SCROW nStartRow, double nPPTY, const ScDocument * pDoc, + SCTAB nTabNo ) +{ + SCROW nRow = rPosY; + while (rScrY <= nEndPixels && nRow >= nStartRow) + { + SCROW nHeightStartRow; + sal_uInt16 nHeight = pDoc->GetRowHeight( nRow, nTabNo, &nHeightStartRow, nullptr); + if (nHeightStartRow < nStartRow) + nHeightStartRow = nStartRow; + if (!nHeight) + nRow = nHeightStartRow - 1; + else + { + SCROW nRows = nRow - nHeightStartRow + 1; + sal_Int64 nPixel = ToPixel( nHeight, nPPTY); + sal_Int64 nAdd = nPixel * nRows; + if (nAdd + rScrY > nEndPixels) + { + sal_Int64 nDiff = nAdd + rScrY - nEndPixels; + nRows -= static_cast<SCROW>(nDiff / nPixel); + nAdd = nPixel * nRows; + // We're looking for a value that satisfies loop condition. + if (nAdd + rScrY <= nEndPixels) + { + ++nRows; + nAdd += nPixel; + } + } + rScrY += static_cast<tools::Long>(nAdd); + nRow -= nRows; + } + } + if (nRow < rPosY) + ++nRow; + rPosY = nRow; +} + +SCCOLROW ScViewData::GetLOKSheetFreezeIndex(bool bIsCol) const +{ + SCCOLROW nFreezeIndex = bIsCol ? mrDoc.GetLOKFreezeCol(nTabNo) : mrDoc.GetLOKFreezeRow(nTabNo); + return nFreezeIndex >= 0 ? nFreezeIndex : 0; +} + +bool ScViewData::SetLOKSheetFreezeIndex(const SCCOLROW nFreezeIndex, bool bIsCol, SCTAB nForTab) +{ + if (nForTab == -1) + { + nForTab = nTabNo; + } + else if (!ValidTab(nForTab) || (nForTab >= static_cast<SCTAB>(maTabData.size()))) + { + SAL_WARN("sc.viewdata", "ScViewData::SetLOKSheetFreezeIndex : invalid nForTab = " << nForTab); + return false; + } + + return bIsCol ? mrDoc.SetLOKFreezeCol(static_cast<SCCOL>(nFreezeIndex), nForTab) + : mrDoc.SetLOKFreezeRow(static_cast<SCROW>(nFreezeIndex), nForTab); +} + +bool ScViewData::RemoveLOKFreeze() +{ + bool colUnfreezed = SetLOKSheetFreezeIndex(0, true); + bool rowUnfreezed = SetLOKSheetFreezeIndex(0, false); + return colUnfreezed || rowUnfreezed; +} + +void ScViewData::DeriveLOKFreezeAllSheets() +{ + SCTAB nMaxTab = static_cast<SCTAB>(maTabData.size()) - 1; + for (SCTAB nTab = 0; nTab <= nMaxTab; ++nTab) + DeriveLOKFreezeIfNeeded(nTab); +} + +void ScViewData::DeriveLOKFreezeIfNeeded(SCTAB nForTab) +{ + if (!ValidTab(nForTab) || (nForTab >= static_cast<SCTAB>(maTabData.size()))) + { + SAL_WARN("sc.viewdata", "ScViewData::DeriveLOKFreezeIfNeeded : invalid nForTab = " << nForTab); + return; + } + + ScViewDataTable* pViewTable = maTabData[nForTab].get(); + if (!pViewTable) + return; + + bool bConvertToFreezeX = false; + bool bConvertToFreezeY = false; + SCCOL nFreezeCol = mrDoc.GetLOKFreezeCol(nForTab); + SCROW nFreezeRow = mrDoc.GetLOKFreezeRow(nForTab); + + if (nFreezeCol == -1) + { + ScSplitMode eSplitMode = pViewTable->eHSplitMode; + if (eSplitMode == SC_SPLIT_FIX) + nFreezeCol = pViewTable->nFixPosX; + else if (eSplitMode == SC_SPLIT_NORMAL) + bConvertToFreezeX = true; + else + nFreezeCol = 0; + } + + if (nFreezeRow == -1) + { + ScSplitMode eSplitMode = pViewTable->eVSplitMode; + if (eSplitMode == SC_SPLIT_FIX) + nFreezeRow = pViewTable->nFixPosY; + else if (eSplitMode == SC_SPLIT_NORMAL) + bConvertToFreezeY = true; + else + nFreezeRow = 0; + } + + if (bConvertToFreezeX || bConvertToFreezeY) + { + SCCOL nCol; + SCROW nRow; + GetPosFromPixel(bConvertToFreezeX ? pViewTable->nHSplitPos : 0, + bConvertToFreezeY ? pViewTable->nVSplitPos : 0, + SC_SPLIT_BOTTOMLEFT, nCol, nRow, + false /* bTestMerge */, false /* bRepair */, + nForTab); + if (bConvertToFreezeX) + nFreezeCol = nCol; + if (bConvertToFreezeY) + nFreezeRow = nRow; + } + + mrDoc.SetLOKFreezeCol(nFreezeCol, nForTab); + mrDoc.SetLOKFreezeRow(nFreezeRow, nForTab); +} + +void ScViewData::OverrideWithLOKFreeze(ScSplitMode& eExHSplitMode, ScSplitMode& eExVSplitMode, + SCCOL& nExFixPosX, SCROW& nExFixPosY, + tools::Long& nExHSplitPos, tools::Long& nExVSplitPos, SCTAB nForTab) const +{ + SCCOL nFreezeCol = mrDoc.GetLOKFreezeCol(nForTab); + SCROW nFreezeRow = mrDoc.GetLOKFreezeRow(nForTab); + + bool bConvertToScrPosX = false; + bool bConvertToScrPosY = false; + + if (nFreezeCol >= 0) + { + if (eExHSplitMode == SC_SPLIT_NONE) + eExHSplitMode = SC_SPLIT_FIX; + + if (eExHSplitMode == SC_SPLIT_FIX) + { + nExFixPosX = nFreezeCol; + pThisTab->nPosX[SC_SPLIT_RIGHT] = nFreezeCol; + } + else + bConvertToScrPosX = true; + } + + if (nFreezeRow >= 0) + { + if (eExVSplitMode == SC_SPLIT_NONE) + eExVSplitMode = SC_SPLIT_FIX; + + if (eExVSplitMode == SC_SPLIT_FIX) + { + nExFixPosY = nFreezeRow; + pThisTab->nPosY[SC_SPLIT_BOTTOM] = nFreezeRow; + } + else + bConvertToScrPosY = true; + } + + if (bConvertToScrPosX || bConvertToScrPosY) + { + Point aExSplitPos = GetScrPos(nFreezeCol, nFreezeRow, SC_SPLIT_BOTTOMLEFT, true, nForTab); + if (bConvertToScrPosX) + nExHSplitPos = aExSplitPos.X(); + if (bConvertToScrPosY) + nExVSplitPos = aExSplitPos.Y(); + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/viewfun2.cxx b/sc/source/ui/view/viewfun2.cxx new file mode 100644 index 0000000000..224bb722e0 --- /dev/null +++ b/sc/source/ui/view/viewfun2.cxx @@ -0,0 +1,3487 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source eCode Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <scitems.hxx> + +#include <sfx2/app.hxx> +#include <sfx2/request.hxx> +#include <editeng/borderline.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/lineitem.hxx> +#include <editeng/scripttypeitem.hxx> +#include <svl/srchitem.hxx> +#include <sfx2/linkmgr.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/objitem.hxx> +#include <sfx2/viewfrm.hxx> +#include <svl/numformat.hxx> +#include <svl/stritem.hxx> +#include <svl/zforlist.hxx> +#include <svx/srchdlg.hxx> +#include <svx/svdview.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <osl/diagnose.h> + +#include <viewfunc.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/uitest/eventdescription.hxx> + +#include <sc.hrc> +#include <globstr.hrc> +#include <scresid.hxx> + +#include <attrib.hxx> +#include <autoform.hxx> +#include <formulacell.hxx> +#include <cellmergeoption.hxx> +#include <compiler.hxx> +#include <docfunc.hxx> +#include <docpool.hxx> +#include <docsh.hxx> +#include <docoptio.hxx> +#include <global.hxx> +#include <patattr.hxx> +#include <printfun.hxx> +#include <refundo.hxx> +#include <table.hxx> +#include <tablink.hxx> +#include <tabvwsh.hxx> +#include <uiitems.hxx> +#include <undoblk.hxx> +#include <undotab.hxx> +#include <sizedev.hxx> +#include <editable.hxx> +#include <docuno.hxx> +#include <charthelper.hxx> +#include <tabbgcolor.hxx> +#include <clipparam.hxx> +#include <prnsave.hxx> +#include <searchresults.hxx> +#include <tokenarray.hxx> +#include <rowheightcontext.hxx> +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/lok.hxx> +#include <mergecellsdialog.hxx> +#include <sheetevents.hxx> +#include <columnspanset.hxx> + +#include <vector> +#include <memory> +#include <boost/property_tree/json_parser.hpp> +#include <tools/json_writer.hxx> + +#include <officecfg/Office/Calc.hxx> + +using namespace com::sun::star; +using ::editeng::SvxBorderLine; + +namespace { + +void collectUIInformation(std::map<OUString, OUString>&& aParameters, const OUString& rAction) +{ + EventDescription aDescription; + aDescription.aID = "grid_window"; + aDescription.aAction = rAction; + aDescription.aParameters = std::move(aParameters); + aDescription.aParent = "MainWindow"; + aDescription.aKeyWord = "ScGridWinUIObject"; + + UITestLogger::getInstance().logEvent(aDescription); +} +} + +using ::std::vector; +using ::std::unique_ptr; + +bool ScViewFunc::AdjustBlockHeight( bool bPaint, ScMarkData* pMarkData ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + if (!pMarkData) + pMarkData = &GetViewData().GetMarkData(); + + ScDocument& rDoc = pDocSh->GetDocument(); + std::vector<sc::ColRowSpan> aMarkedRows = pMarkData->GetMarkedRowSpans(); + + if (aMarkedRows.empty()) + { + SCROW nCurRow = GetViewData().GetCurY(); + aMarkedRows.emplace_back(nCurRow, nCurRow); + } + + if (comphelper::LibreOfficeKit::isActive()) + { + SCCOLROW nStart = aMarkedRows[0].mnStart; + OnLOKSetWidthOrHeight(nStart, /*width: */ false); + } + + double nPPTX = GetViewData().GetPPTX(); + double nPPTY = GetViewData().GetPPTY(); + Fraction aZoomX = GetViewData().GetZoomX(); + Fraction aZoomY = GetViewData().GetZoomY(); + + ScSizeDeviceProvider aProv(pDocSh); + if (aProv.IsPrinter()) + { + nPPTX = aProv.GetPPTX(); + nPPTY = aProv.GetPPTY(); + aZoomX = aZoomY = Fraction( 1, 1 ); + } + + sc::RowHeightContext aCxt(rDoc.MaxRow(), nPPTX, nPPTY, aZoomX, aZoomY, aProv.GetDevice()); + bool bAnyChanged = false; + for (const SCTAB& nTab : *pMarkData) + { + bool bChanged = false; + SCROW nPaintY = 0; + for (const auto& rRow : aMarkedRows) + { + SCROW nStartNo = rRow.mnStart; + SCROW nEndNo = rRow.mnEnd; + ScAddress aTopLeft(0, nStartNo, nTab); + rDoc.UpdateScriptTypes(aTopLeft, rDoc.GetSheetLimits().GetMaxColCount(), nEndNo-nStartNo+1); + if (rDoc.SetOptimalHeight(aCxt, nStartNo, nEndNo, nTab, true)) + { + if (!bChanged) + nPaintY = nStartNo; + bAnyChanged = bChanged = true; + } + } + // tdf#76183: recalculate objects' positions + if (bChanged) + rDoc.SetDrawPageSize(nTab); + if ( bPaint && bChanged ) + pDocSh->PostPaint( 0, nPaintY, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, + PaintPartFlags::Grid | PaintPartFlags::Left ); + } + + if ( bPaint && bAnyChanged ) + pDocSh->UpdateOle(GetViewData()); + + if (comphelper::LibreOfficeKit::isActive()) + { + SCTAB nTab = GetViewData().GetTabNo(); + ScTabViewShell::notifyAllViewsSheetGeomInvalidation( + GetViewData().GetViewShell(), + false /* bColumns */, true /* bRows */, + true /* bSizes*/, false /* bHidden */, false /* bFiltered */, + false /* bGroups */, nTab); + ScTabViewShell::notifyAllViewsHeaderInvalidation(GetViewData().GetViewShell(), ROW_HEADER, nTab); + } + + return bAnyChanged; +} + +bool ScViewFunc::AdjustRowHeight( SCROW nStartRow, SCROW nEndRow, bool bApi ) +{ + if (comphelper::LibreOfficeKit::isActive()) + { + OnLOKSetWidthOrHeight(nStartRow, /*width: */ false); + } + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + double nPPTX = GetViewData().GetPPTX(); + double nPPTY = GetViewData().GetPPTY(); + Fraction aZoomX = GetViewData().GetZoomX(); + Fraction aZoomY = GetViewData().GetZoomY(); + sal_uInt16 nOldPixel = 0; + if (nStartRow == nEndRow) + nOldPixel = static_cast<sal_uInt16>(rDoc.GetRowHeight(nStartRow,nTab) * nPPTY); + + ScSizeDeviceProvider aProv(pDocSh); + if (aProv.IsPrinter()) + { + nPPTX = aProv.GetPPTX(); + nPPTY = aProv.GetPPTY(); + aZoomX = aZoomY = Fraction( 1, 1 ); + } + sc::RowHeightContext aCxt(rDoc.MaxRow(), nPPTX, nPPTY, aZoomX, aZoomY, aProv.GetDevice()); + bool bChanged = rDoc.SetOptimalHeight(aCxt, nStartRow, nEndRow, nTab, bApi); + + // tdf#76183: recalculate objects' positions + if (bChanged) + rDoc.SetDrawPageSize(nTab); + + if (bChanged && ( nStartRow == nEndRow )) + { + sal_uInt16 nNewPixel = static_cast<sal_uInt16>(rDoc.GetRowHeight(nStartRow,nTab) * nPPTY); + if ( nNewPixel == nOldPixel ) + bChanged = false; + } + + if ( bChanged ) + pDocSh->PostPaint( 0, nStartRow, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, + PaintPartFlags::Grid | PaintPartFlags::Left ); + + if (comphelper::LibreOfficeKit::isActive()) + { + ScTabViewShell::notifyAllViewsSheetGeomInvalidation( + GetViewData().GetViewShell(), + false /* bColumns */, true /* bRows */, + true /* bSizes*/, false /* bHidden */, false /* bFiltered */, + false /* bGroups */, nTab); + ScTabViewShell::notifyAllViewsHeaderInvalidation(GetViewData().GetViewShell(), ROW_HEADER, GetViewData().GetTabNo()); + } + + return bChanged; +} + +namespace { + +enum ScAutoSum +{ + ScAutoSumNone = 0, + ScAutoSumData, + ScAutoSumSum, + ScAutoSumAverage, + ScAutoSumMax, + ScAutoSumMin, + ScAutoSumCount, + ScAutoSumCountA, + ScAutoSumProduct, + ScAutoSumStDev, + ScAutoSumStDevP, + ScAutoSumVar, + ScAutoSumVarP, + ScAutoSumEnd +}; + +} + +static ScAutoSum lcl_IsAutoSumData( ScDocument& rDoc, SCCOL nCol, SCROW nRow, + SCTAB nTab, ScDirection eDir, SCCOLROW& nExtend ) +{ + ScRefCellValue aCell(rDoc, ScAddress(nCol, nRow, nTab)); + if (aCell.hasNumeric()) + { + if (aCell.getType() == CELLTYPE_FORMULA) + { + ScAutoSum val = ScAutoSumNone; + ScTokenArray* pCode = aCell.getFormula()->GetCode(); + if ( pCode ) + { + switch( pCode->GetOuterFuncOpCode() ) + { + case ocSum : val = ScAutoSumSum; + break; + case ocAverage : val = ScAutoSumAverage; + break; + case ocMax : val = ScAutoSumMax; + break; + case ocMin : val = ScAutoSumMin; + break; + case ocCount : val = ScAutoSumCount; + break; + case ocCount2 : val = ScAutoSumCountA; + break; + case ocProduct : val = ScAutoSumProduct; + break; + case ocStDev : val = ScAutoSumStDev; + break; + case ocStDevP : val = ScAutoSumStDevP; + break; + case ocVar : val = ScAutoSumVar; + break; + case ocVarP : val = ScAutoSumVarP; + break; + default : + break; + } + if ( pCode->GetAdjacentExtendOfOuterFuncRefs( nExtend, + ScAddress( nCol, nRow, nTab ), eDir ) ) + return val; + } + } + return ScAutoSumData; + } + return ScAutoSumNone; +} + +#define SC_AUTOSUM_MAXCOUNT 20 + +static ScAutoSum lcl_SeekAutoSumData( ScDocument& rDoc, SCCOL& nCol, SCROW& nRow, + SCTAB nTab, ScDirection eDir, SCCOLROW& nExtend ) +{ + sal_uInt16 nCount = 0; + while (nCount < SC_AUTOSUM_MAXCOUNT) + { + if ( eDir == DIR_TOP ) + { + if (nRow > 0) + --nRow; + else + return ScAutoSumNone; + } + else + { + if (nCol > 0) + --nCol; + else + return ScAutoSumNone; + } + ScAutoSum eSum; + if ( (eSum = lcl_IsAutoSumData( + rDoc, nCol, nRow, nTab, eDir, nExtend )) != ScAutoSumNone ) + return eSum; + ++nCount; + } + return ScAutoSumNone; +} + +#undef SC_AUTOSUM_MAXCOUNT + +static bool lcl_FindNextSumEntryInColumn( ScDocument& rDoc, SCCOL nCol, SCROW& nRow, + SCTAB nTab, SCCOLROW& nExtend, SCROW nMinRow ) +{ + const SCROW nTmp = nRow; + ScAutoSum eSkip = ScAutoSumNone; + for (;;) + { + eSkip = lcl_IsAutoSumData( rDoc, nCol, nRow, nTab, DIR_TOP, nExtend ); + if (eSkip != ScAutoSumData || nRow <= nMinRow ) + break; + --nRow; + } + return eSkip >= ScAutoSumSum && nRow < nTmp; +} + +static bool lcl_FindNextSumEntryInRow( ScDocument& rDoc, SCCOL& nCol, SCROW nRow, + SCTAB nTab, SCCOLROW& nExtend, SCCOL nMinCol ) +{ + const SCCOL nTmp = nCol; + ScAutoSum eSkip = ScAutoSumNone; + for (;;) + { + eSkip = lcl_IsAutoSumData( rDoc, nCol, nRow, nTab, DIR_LEFT, nExtend ); + if (eSkip != ScAutoSumData || nCol <= nMinCol ) + break; + --nCol; + } + return eSkip >= ScAutoSumSum && nCol < nTmp; +} + +static ScAutoSum lcl_GetAutoSumForColumnRange( ScDocument& rDoc, ScRangeList& rRangeList, const ScRange& rRange ) +{ + const ScAddress aStart = rRange.aStart; + const ScAddress aEnd = rRange.aEnd; + if ( aStart.Col() != aEnd.Col() ) + { + return ScAutoSumNone; + } + + const SCTAB nTab = aEnd.Tab(); + const SCCOL nCol = aEnd.Col(); + SCROW nEndRow = aEnd.Row(); + SCROW nStartRow = nEndRow; + SCCOLROW nExtend = 0; + ScAutoSum eSum = lcl_IsAutoSumData( rDoc, nCol, nEndRow, nTab, DIR_TOP, nExtend /*out*/ ); + + if ( eSum >= ScAutoSumSum ) + { + bool bContinue = false; + do + { + rRangeList.push_back( ScRange( nCol, nStartRow, nTab, nCol, nEndRow, nTab ) ); + nEndRow = static_cast< SCROW >( nExtend ); + bContinue = lcl_FindNextSumEntryInColumn( rDoc, nCol, nEndRow /*inout*/, nTab, nExtend /*out*/, aStart.Row() ); + if ( bContinue ) + { + nStartRow = nEndRow; + } + } while ( bContinue ); + } + else + { + while ( nStartRow > aStart.Row() ) + { + eSum = lcl_IsAutoSumData( rDoc, nCol, nStartRow-1, nTab, DIR_TOP, nExtend /*out*/ ); + if (eSum >= ScAutoSumSum ) + break; + --nStartRow; + } + rRangeList.push_back( ScRange( nCol, nStartRow, nTab, nCol, nEndRow, nTab ) ); + if (eSum == ScAutoSumNone) + eSum = ScAutoSumData; + } + + return eSum; +} + +static ScAutoSum lcl_GetAutoSumForRowRange( ScDocument& rDoc, ScRangeList& rRangeList, const ScRange& rRange ) +{ + const ScAddress aStart = rRange.aStart; + const ScAddress aEnd = rRange.aEnd; + if ( aStart.Row() != aEnd.Row() ) + { + return ScAutoSumNone; + } + + const SCTAB nTab = aEnd.Tab(); + const SCROW nRow = aEnd.Row(); + SCCOL nEndCol = aEnd.Col(); + SCCOL nStartCol = nEndCol; + SCCOLROW nExtend = 0; + ScAutoSum eSum = lcl_IsAutoSumData( rDoc, nEndCol, nRow, nTab, DIR_LEFT, nExtend /*out*/ ); + + if ( eSum >= ScAutoSumSum ) + { + bool bContinue = false; + do + { + rRangeList.push_back( ScRange( nStartCol, nRow, nTab, nEndCol, nRow, nTab ) ); + nEndCol = static_cast< SCCOL >( nExtend ); + bContinue = lcl_FindNextSumEntryInRow( rDoc, nEndCol /*inout*/, nRow, nTab, nExtend /*out*/, aStart.Col() ); + if ( bContinue ) + { + nStartCol = nEndCol; + } + } while ( bContinue ); + } + else + { + while ( nStartCol > aStart.Col() ) + { + eSum = lcl_IsAutoSumData( rDoc, nStartCol-1, nRow, nTab, DIR_LEFT, nExtend /*out*/ ); + if (eSum >= ScAutoSumSum ) + break; + --nStartCol; + } + rRangeList.push_back( ScRange( nStartCol, nRow, nTab, nEndCol, nRow, nTab ) ); + if (eSum == ScAutoSumNone) + eSum = ScAutoSumData; + } + + return eSum; +} + +static sal_Int8 GetSubTotal( const OpCode eCode ) +{ + sal_Int8 val; + switch ( eCode ) + { + case ocSum : val = 9; + break; + case ocAverage : val = 1; + break; + case ocMax : val = 4; + break; + case ocMin : val = 5; + break; + case ocCount : val = 2; + break; + case ocCount2 : val = 3; + break; + case ocProduct : val = 6; + break; + case ocStDev : val = 7; + break; + case ocStDevP : val = 8; + break; + case ocVar : val = 10; + break; + case ocVarP : val = 11; + break; + default : val = 9; + } + + return val; +} + +bool ScViewFunc::GetAutoSumArea( ScRangeList& rRangeList ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + + SCCOL nCol = GetViewData().GetCurX(); + SCROW nRow = GetViewData().GetCurY(); + + SCCOL nStartCol = nCol; + SCROW nStartRow = nRow; + SCCOL nEndCol = nCol; + SCROW nEndRow = nRow; + SCCOL nSeekCol = nCol; + SCROW nSeekRow = nRow; + SCCOLROW nExtend; // will become valid via reference for ScAutoSumSum + + bool bCol = false; + bool bRow = false; + + ScAutoSum eSum; + if ( nRow != 0 + && ((eSum = lcl_IsAutoSumData( rDoc, nCol, nRow-1, nTab, + DIR_TOP, nExtend /*out*/ )) == ScAutoSumData ) + && ((eSum = lcl_IsAutoSumData( rDoc, nCol, nRow-1, nTab, + DIR_LEFT, nExtend /*out*/ )) == ScAutoSumData ) + ) + { + bRow = true; + nSeekRow = nRow - 1; + } + else if ( nCol != 0 && (eSum = lcl_IsAutoSumData( rDoc, nCol-1, nRow, nTab, + DIR_LEFT, nExtend /*out*/ )) == ScAutoSumData ) + { + bCol = true; + nSeekCol = nCol - 1; + } + else if ( (eSum = lcl_SeekAutoSumData( rDoc, nCol, nSeekRow, nTab, DIR_TOP, nExtend /*out*/ )) != ScAutoSumNone ) + bRow = true; + else if (( eSum = lcl_SeekAutoSumData( rDoc, nSeekCol, nRow, nTab, DIR_LEFT, nExtend /*out*/ )) != ScAutoSumNone ) + bCol = true; + + if ( bCol || bRow ) + { + if ( bRow ) + { + nStartRow = nSeekRow; // nSeekRow might be adjusted via reference + if ( eSum >= ScAutoSumSum && eSum < ScAutoSumEnd ) + nEndRow = nStartRow; // only sum sums + else + nEndRow = nRow - 1; // maybe extend data area at bottom + } + else + { + nStartCol = nSeekCol; // nSeekCol might be adjusted via reference + if ( eSum >= ScAutoSumSum ) + nEndCol = nStartCol; // only sum sums + else + nEndCol = nCol - 1; // maybe extend data area to the right + } + bool bContinue = false; + do + { + if ( eSum == ScAutoSumData ) + { + if ( bRow ) + { + while ( nStartRow != 0 && lcl_IsAutoSumData( rDoc, nCol, + nStartRow-1, nTab, DIR_TOP, nExtend /*out*/ ) == eSum ) + --nStartRow; + } + else + { + while ( nStartCol != 0 && lcl_IsAutoSumData( rDoc, nStartCol-1, + nRow, nTab, DIR_LEFT, nExtend /*out*/ ) == eSum ) + --nStartCol; + } + } + rRangeList.push_back( + ScRange( nStartCol, nStartRow, nTab, nEndCol, nEndRow, nTab ) ); + if ( eSum >= ScAutoSumSum ) + { + if ( bRow ) + { + nEndRow = static_cast< SCROW >( nExtend ); + bContinue = lcl_FindNextSumEntryInColumn( rDoc, nCol, nEndRow /*inout*/, nTab, nExtend /*out*/, 0 ); + if ( bContinue ) + { + nStartRow = nEndRow; + } + } + else + { + nEndCol = static_cast< SCCOL >( nExtend ); + bContinue = lcl_FindNextSumEntryInRow( rDoc, nEndCol /*inout*/, nRow, nTab, nExtend /*out*/, 0 ); + if ( bContinue ) + { + nStartCol = nEndCol; + } + } + } + } while ( bContinue ); + return true; + } + return false; +} + +void ScViewFunc::EnterAutoSum(const ScRangeList& rRangeList, bool bSubTotal, const ScAddress& rAddr, const OpCode eCode) +{ + OUString aFormula = GetAutoSumFormula( rRangeList, bSubTotal, rAddr , eCode); + EnterBlock( aFormula, nullptr ); +} + +bool ScViewFunc::AutoSum( const ScRange& rRange, bool bSubTotal, bool bSetCursor, bool bContinue , const OpCode eCode) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + const SCTAB nTab = rRange.aStart.Tab(); + SCCOL nStartCol = rRange.aStart.Col(); + SCROW nStartRow = rRange.aStart.Row(); + const SCCOL nEndCol = rRange.aEnd.Col(); + const SCROW nEndRow = rRange.aEnd.Row(); + SCCOLROW nExtend = 0; // out parameter for lcl_IsAutoSumData + + // ignore rows at the top of the given range which don't contain autosum data + bool bRowData = false; + for ( SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow ) + { + for ( SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol ) + { + if ( lcl_IsAutoSumData( rDoc, nCol, nRow, nTab, DIR_TOP, nExtend ) != ScAutoSumNone ) + { + bRowData = true; + break; + } + } + if ( bRowData ) + { + nStartRow = nRow; + break; + } + } + if ( !bRowData ) + { + return false; + } + + // ignore columns at the left of the given range which don't contain autosum data + bool bColData = false; + for ( SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol ) + { + for ( SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow ) + { + if ( lcl_IsAutoSumData( rDoc, nCol, nRow, nTab, DIR_LEFT, nExtend ) != ScAutoSumNone ) + { + bColData = true; + break; + } + } + if ( bColData ) + { + nStartCol = nCol; + break; + } + } + if ( !bColData ) + { + return false; + } + + const bool bEndRowEmpty = rDoc.IsBlockEmpty( nStartCol, nEndRow, nEndCol, nEndRow, nTab ); + const bool bEndColEmpty = rDoc.IsBlockEmpty( nEndCol, nStartRow, nEndCol, nEndRow, nTab ); + bool bRow = ( nStartRow != nEndRow ) && ( bEndRowEmpty || !bEndColEmpty ); + bool bCol = ( nStartCol != nEndCol ) && ( bEndColEmpty || nStartRow == nEndRow ); + + // find an empty row for entering the result + SCROW nInsRow = nEndRow; + if ( bRow && !bEndRowEmpty ) + { + if ( nInsRow < rDoc.MaxRow() ) + { + ++nInsRow; + while ( !rDoc.IsBlockEmpty( nStartCol, nInsRow, nEndCol, nInsRow, nTab ) ) + { + if ( nInsRow < rDoc.MaxRow() ) + { + ++nInsRow; + } + else + { + bRow = false; + break; + } + } + } + else + { + bRow = false; + } + } + + // find an empty column for entering the result + SCCOL nInsCol = nEndCol; + if ( bCol && !bEndColEmpty ) + { + if ( nInsCol < rDoc.MaxCol() ) + { + ++nInsCol; + while ( !rDoc.IsBlockEmpty( nInsCol, nStartRow, nInsCol, nEndRow, nTab ) ) + { + if ( nInsCol < rDoc.MaxCol() ) + { + ++nInsCol; + } + else + { + bCol = false; + break; + } + } + } + else + { + bCol = false; + } + } + + if ( !bRow && !bCol ) + { + return false; + } + + SCCOL nMarkEndCol = nEndCol; + SCROW nMarkEndRow = nEndRow; + ScAutoSum eSum = ScAutoSumNone; + SCROW nColSums = 0; + SCCOL nRowSums = 0; + SCROW nColSumsStartRow = 0; + SCCOL nRowSumsStartCol = 0; + + if ( bRow ) + { + // calculate the row sums for all columns of the given range + + SCROW nSumEndRow = nEndRow; + + if ( bEndRowEmpty ) + { + // the last row of the given range is empty; + // don't take into account for calculating the autosum + --nSumEndRow; + } + else + { + // increase mark range + ++nMarkEndRow; + } + + for ( SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol ) + { + if ( !rDoc.IsBlockEmpty( nCol, nStartRow, nCol, nSumEndRow, nTab ) ) + { + ScRangeList aRangeList; + // Include the originally selected start row. + const ScRange aRange( nCol, rRange.aStart.Row(), nTab, nCol, nSumEndRow, nTab ); + if ( (eSum = lcl_GetAutoSumForColumnRange( rDoc, aRangeList, aRange )) != ScAutoSumNone ) + { + if (++nRowSums == 1) + nRowSumsStartCol = aRangeList[0].aStart.Col(); + const OUString aFormula = GetAutoSumFormula( + aRangeList, bSubTotal, ScAddress(nCol, nInsRow, nTab), eCode); + EnterData( nCol, nInsRow, nTab, aFormula ); + } + } + } + } + + if ( bCol ) + { + // calculate the column sums for all rows of the given range + + SCCOL nSumEndCol = nEndCol; + + if ( bEndColEmpty ) + { + // the last column of the given range is empty; + // don't take into account for calculating the autosum + --nSumEndCol; + } + else + { + // increase mark range + ++nMarkEndCol; + } + + for ( SCROW nRow = nStartRow; nRow <= nEndRow; ++nRow ) + { + if ( !rDoc.IsBlockEmpty( nStartCol, nRow, nSumEndCol, nRow, nTab ) ) + { + ScRangeList aRangeList; + // Include the originally selected start column. + const ScRange aRange( rRange.aStart.Col(), nRow, nTab, nSumEndCol, nRow, nTab ); + if ( (eSum = lcl_GetAutoSumForRowRange( rDoc, aRangeList, aRange )) != ScAutoSumNone ) + { + if (++nColSums == 1) + nColSumsStartRow = aRangeList[0].aStart.Row(); + const OUString aFormula = GetAutoSumFormula( aRangeList, bSubTotal, ScAddress(nInsCol, nRow, nTab), eCode ); + EnterData( nInsCol, nRow, nTab, aFormula ); + } + } + } + } + + // Set new mark range and cursor position. + // For sum of sums (and data until sum) mark the actual resulting range if + // there is only one, or the data range if more than one. Otherwise use the + // original selection. All extended by end column/row where the sum is put. + const ScRange aMarkRange( + (eSum >= ScAutoSumSum ? + (nRowSums == 1 ? nRowSumsStartCol : nStartCol) : + rRange.aStart.Col()), + (eSum >= ScAutoSumSum ? + (nColSums == 1 ? nColSumsStartRow : nStartRow) : + rRange.aStart.Row()), + nTab, nMarkEndCol, nMarkEndRow, nTab ); + MarkRange( aMarkRange, false, bContinue ); + if ( bSetCursor ) + { + SetCursor( nMarkEndCol, nMarkEndRow ); + } + + return true; +} + +OUString ScViewFunc::GetAutoSumFormula( const ScRangeList& rRangeList, bool bSubTotal, const ScAddress& rAddr , const OpCode eCode) +{ + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + ScTokenArray aArray(rDoc); + + aArray.AddOpCode(bSubTotal ? ocSubTotal : eCode); + aArray.AddOpCode(ocOpen); + + if (bSubTotal) + { + aArray.AddDouble( GetSubTotal( eCode ) ); + aArray.AddOpCode(ocSep); + } + + if(!rRangeList.empty()) + { + ScRangeList aRangeList = rRangeList; + size_t ListSize = aRangeList.size(); + for ( size_t i = 0; i < ListSize; ++i ) + { + const ScRange & r = aRangeList[i]; + if (i != 0) + aArray.AddOpCode(ocSep); + ScComplexRefData aRef; + aRef.InitRangeRel(rDoc, r, rAddr); + aArray.AddDoubleReference(aRef); + } + } + + aArray.AddOpCode(ocClose); + + ScCompiler aComp(rDoc, rAddr, aArray, rDoc.GetGrammar()); + OUStringBuffer aBuf; + aComp.CreateStringFromTokenArray(aBuf); + aBuf.insert(0, "="); + return aBuf.makeStringAndClear(); +} + +void ScViewFunc::EnterBlock( const OUString& rString, const EditTextObject* pData ) +{ + // test for multi selection + + SCCOL nCol = GetViewData().GetCurX(); + SCROW nRow = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + ScMarkData& rMark = GetViewData().GetMarkData(); + if ( rMark.IsMultiMarked() ) + { + rMark.MarkToSimple(); + if ( rMark.IsMultiMarked() ) + { // "Insert into multi selection not possible" + ErrorMessage(STR_MSSG_PASTEFROMCLIP_0); + + // insert into single cell + if ( pData ) + EnterData(nCol, nRow, nTab, *pData); + else + EnterData( nCol, nRow, nTab, rString ); + return; + } + } + + if (GetViewData().SelectionForbidsCellFill()) + { + PaintArea(nCol, nRow, nCol, nRow); // possibly the edit-engine is still painted there + return; + } + + ScDocument& rDoc = GetViewData().GetDocument(); + OUString aNewStr = rString; + if ( pData ) + { + const ScPatternAttr* pOldPattern = rDoc.GetPattern( nCol, nRow, nTab ); + ScTabEditEngine aEngine( *pOldPattern, rDoc.GetEnginePool(), &rDoc ); + aEngine.SetTextCurrentDefaults(*pData); + + ScEditAttrTester aTester( &aEngine ); + if (!aTester.NeedsObject()) + { + aNewStr = aEngine.GetText(); + pData = nullptr; + } + } + + // Insert via PasteFromClip + weld::WaitObject aWait(GetViewData().GetDialogParent()); + + ScAddress aPos( nCol, nRow, nTab ); + + ScDocumentUniquePtr pInsDoc(new ScDocument( SCDOCMODE_CLIP )); + pInsDoc->ResetClip( &rDoc, nTab ); + + if (aNewStr[0] == '=') // Formula ? + { + // SetString not possible, because in Clipboard-Documents nothing will be compiled! + pInsDoc->SetFormulaCell(aPos, new ScFormulaCell(rDoc, aPos, aNewStr)); + } + else if ( pData ) + { + // A copy of pData will be stored. + pInsDoc->SetEditText(aPos, *pData, rDoc.GetEditPool()); + } + else + pInsDoc->SetString( nCol, nRow, nTab, aNewStr ); + + pInsDoc->SetClipArea( ScRange(aPos) ); + // insert Block, with Undo etc. + if ( !PasteFromClip( InsertDeleteFlags::CONTENTS, pInsDoc.get(), ScPasteFunc::NONE, false, false, + false, INS_NONE, InsertDeleteFlags::ATTRIB ) ) + return; + + const SfxUInt32Item* pItem = pInsDoc->GetAttr( + nCol, nRow, nTab, ATTR_VALUE_FORMAT ); + if ( pItem ) + { // set number format if incompatible + // MarkData was already MarkToSimple'ed in PasteFromClip + const ScRange& aRange = rMark.GetMarkArea(); + ScPatternAttr aPattern( rDoc.GetPool() ); + aPattern.GetItemSet().Put( *pItem ); + SvNumFormatType nNewType = rDoc.GetFormatTable()->GetType( pItem->GetValue() ); + rDoc.ApplyPatternIfNumberformatIncompatible( aRange, rMark, + aPattern, nNewType ); + } +} + +// manual page break + +void ScViewFunc::InsertPageBreak( bool bColumn, bool bRecord, const ScAddress* pPos, + bool bSetModified ) +{ + SCTAB nTab = GetViewData().GetTabNo(); + ScAddress aCursor; + if (pPos) + aCursor = *pPos; + else + aCursor = ScAddress( GetViewData().GetCurX(), GetViewData().GetCurY(), nTab ); + + bool bSuccess = GetViewData().GetDocShell()->GetDocFunc(). + InsertPageBreak( bColumn, aCursor, bRecord, bSetModified ); + + if ( bSuccess && bSetModified ) + UpdatePageBreakData( true ); // for PageBreak-Mode +} + +void ScViewFunc::DeletePageBreak( bool bColumn, bool bRecord, const ScAddress* pPos, + bool bSetModified ) +{ + SCTAB nTab = GetViewData().GetTabNo(); + ScAddress aCursor; + if (pPos) + aCursor = *pPos; + else + aCursor = ScAddress( GetViewData().GetCurX(), GetViewData().GetCurY(), nTab ); + + bool bSuccess = GetViewData().GetDocShell()->GetDocFunc(). + RemovePageBreak( bColumn, aCursor, bRecord, bSetModified ); + + if ( bSuccess && bSetModified ) + UpdatePageBreakData( true ); // for PageBreak-Mode +} + +void ScViewFunc::RemoveManualBreaks() +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + bool bUndo(rDoc.IsUndoEnabled()); + + if (bUndo) + { + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nTab, nTab, true, true ); + rDoc.CopyToDocument( 0,0,nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, false, *pUndoDoc ); + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoRemoveBreaks>( pDocSh, nTab, std::move(pUndoDoc) ) ); + } + + rDoc.RemoveManualBreaks(nTab); + rDoc.UpdatePageBreaks(nTab); + + UpdatePageBreakData( true ); + pDocSh->SetDocumentModified(); + pDocSh->PostPaint( 0,0,nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, PaintPartFlags::Grid ); +} + +void ScViewFunc::SetPrintZoom(sal_uInt16 nScale) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + SCTAB nTab = GetViewData().GetTabNo(); + pDocSh->SetPrintZoom( nTab, nScale, 0/*nPages*/ ); +} + +void ScViewFunc::AdjustPrintZoom() +{ + ScRange aRange; + if ( GetViewData().GetSimpleArea( aRange ) != SC_MARK_SIMPLE ) + aRange = GetViewData().GetMarkData().GetMultiMarkArea(); + GetViewData().GetDocShell()->AdjustPrintZoom( aRange ); +} + +void ScViewFunc::SetPrintRanges( bool bEntireSheet, const OUString* pPrint, + const OUString* pRepCol, const OUString* pRepRow, + bool bAddPrint ) +{ + // on all selected tables + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScMarkData& rMark = GetViewData().GetMarkData(); + bool bUndo (rDoc.IsUndoEnabled()); + + std::unique_ptr<ScPrintRangeSaver> pOldRanges = rDoc.CreatePrintRangeSaver(); + + ScAddress::Details aDetails(rDoc.GetAddressConvention(), 0, 0); + + for (const SCTAB& nTab : rMark) + { + ScRange aRange( 0,0,nTab ); + + // print ranges + + if( !bAddPrint ) + { + rDoc.ClearPrintRanges( nTab ); + rDoc.ClearPrintNamedRanges(nTab); + } + + if( bEntireSheet ) + { + rDoc.SetPrintEntireSheet( nTab ); + } + else if ( pPrint ) + { + if ( !pPrint->isEmpty() ) + { + const sal_Unicode sep = ScCompiler::GetNativeSymbolChar(ocSep); + sal_Int32 nPos = 0; + do + { + const OUString aToken = pPrint->getToken(0, sep, nPos); + if ( aRange.ParseAny( aToken, rDoc, aDetails ) & ScRefFlags::VALID ) + rDoc.AddPrintRange( nTab, aRange ); + } + while (nPos >= 0); + } + } + else // NULL = use selection (print range is always set), use empty string to delete all ranges + { + if ( GetViewData().GetSimpleArea( aRange ) == SC_MARK_SIMPLE ) + { + rDoc.AddPrintRange( nTab, aRange ); + } + else if ( rMark.IsMultiMarked() ) + { + rMark.MarkToMulti(); + ScRangeListRef pList( new ScRangeList ); + rMark.FillRangeListWithMarks( pList.get(), false ); + for (size_t i = 0, n = pList->size(); i < n; ++i) + { + const ScRange & rR = (*pList)[i]; + rDoc.AddPrintRange(nTab, rR); + } + } + } + + // repeat columns + + if ( pRepCol ) + { + if ( pRepCol->isEmpty() ) + rDoc.SetRepeatColRange( nTab, std::nullopt ); + else + if ( aRange.ParseAny( *pRepCol, rDoc, aDetails ) & ScRefFlags::VALID ) + rDoc.SetRepeatColRange( nTab, std::move(aRange) ); + } + + // repeat rows + + if ( pRepRow ) + { + if ( pRepRow->isEmpty() ) + rDoc.SetRepeatRowRange( nTab, std::nullopt ); + else + if ( aRange.ParseAny( *pRepRow, rDoc, aDetails ) & ScRefFlags::VALID ) + rDoc.SetRepeatRowRange( nTab, std::move(aRange) ); + } + } + + // undo (for all tables) + if (bUndo) + { + SCTAB nCurTab = GetViewData().GetTabNo(); + std::unique_ptr<ScPrintRangeSaver> pNewRanges = rDoc.CreatePrintRangeSaver(); + if (comphelper::LibreOfficeKit::isActive()) + { + tools::JsonWriter aJsonWriter; + pNewRanges->GetPrintRangesInfo(aJsonWriter); + + SfxViewShell* pViewShell = GetViewData().GetViewShell(); + const OString message = aJsonWriter.finishAndGetAsOString(); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_PRINT_RANGES, message); + } + + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoPrintRange>( pDocSh, nCurTab, std::move(pOldRanges), std::move(pNewRanges) ) ); + } + else + pOldRanges.reset(); + + // update page breaks + + for (const auto& rTab : rMark) + ScPrintFunc( pDocSh, pDocSh->GetPrinter(), rTab ).UpdatePages(); + + SfxBindings& rBindings = GetViewData().GetBindings(); + rBindings.Invalidate( SID_DELETE_PRINTAREA ); + + pDocSh->SetDocumentModified(); +} + +// Merge cells + +bool ScViewFunc::TestMergeCells() // pre-test (for menu) +{ + // simple test: true if there's a selection but no multi selection and not filtered + + const ScMarkData& rMark = GetViewData().GetMarkData(); + if ( rMark.IsMarked() || rMark.IsMultiMarked() ) + { + ScRange aRange; + bool bMergeable = ( GetViewData().GetSimpleArea( aRange ) == SC_MARK_SIMPLE ); + bMergeable = bMergeable && ( aRange.aStart.Col() != aRange.aEnd.Col() || + aRange.aStart.Row() != aRange.aEnd.Row() ); + return bMergeable; + } + else + return false; +} + +void ScViewFunc::MergeCells( bool bApi, bool bDoContents, bool bCenter, + const sal_uInt16 nSlot ) +{ + // Editable- and Being-Nested- test must be at the beginning (in DocFunc too), + // so that the Contents-QueryBox won't appear + ScEditableTester aTester( this ); + if (!aTester.IsEditable()) + { + ErrorMessage(aTester.GetMessageId()); + return; + } + + ScMarkData& rMark = GetViewData().GetMarkData(); + rMark.MarkToSimple(); + if (!rMark.IsMarked()) + { + ErrorMessage(STR_NOMULTISELECT); + return; + } + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + + const ScRange& aMarkRange = rMark.GetMarkArea(); + SCCOL nStartCol = aMarkRange.aStart.Col(); + SCROW nStartRow = aMarkRange.aStart.Row(); + SCTAB nStartTab = aMarkRange.aStart.Tab(); + SCCOL nEndCol = aMarkRange.aEnd.Col(); + SCROW nEndRow = aMarkRange.aEnd.Row(); + SCTAB nEndTab = aMarkRange.aEnd.Tab(); + if ( nStartCol == nEndCol && nStartRow == nEndRow ) + { + // nothing to do + return; + } + + if ( rDoc.HasAttrib( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab, + HasAttrFlags::Merged | HasAttrFlags::Overlapped ) ) + { // "Don't nest merging !" + ErrorMessage(STR_MSSG_MERGECELLS_0); + return; + } + + // Check for the contents of all selected tables. + bool bAskDialog = false; + ScCellMergeOption aMergeOption(nStartCol, nStartRow, nEndCol, nEndRow, bCenter); + for (const SCTAB& i : rMark) + { + aMergeOption.maTabs.insert(i); + + sc::MultiDataCellState aState = rDoc.HasMultipleDataCells(aMergeOption.getSingleRange(i)); + switch (aState.meState) + { + case sc::MultiDataCellState::HasMultipleCells: + { + // this range contains multiple data cells. + bAskDialog = true; + break; + } + case sc::MultiDataCellState::HasOneCell: + { + // this range contains only one data cell. + if (nStartCol != aState.mnCol1 || nStartRow != aState.mnRow1) + bDoContents = true; // move the value to the top-left. + break; + } + default: + ; + } + } + + bool bEmptyMergedCells = officecfg::Office::Calc::Compatibility::MergeCells::EmptyMergedCells::get(); + + auto doMerge = [this, pDocSh, aMergeOption, bApi, nStartCol, nStartRow, aMarkRange] + (bool bNowDoContents, bool bNowEmptyMergedCells) + { + if (pDocSh->GetDocFunc().MergeCells(aMergeOption, bNowDoContents, true/*bRecord*/, + bApi, bNowEmptyMergedCells)) + { + SetCursor( nStartCol, nStartRow ); + // DoneBlockMode( sal_False); + Unmark(); + + pDocSh->UpdateOle(GetViewData()); + UpdateInputLine(); + + OUString aStartAddress = aMarkRange.aStart.GetColRowString(); + OUString aEndAddress = aMarkRange.aEnd.GetColRowString(); + + collectUIInformation({{"RANGE", aStartAddress + ":" + aEndAddress}}, "MERGE_CELLS"); + } + }; + + if (bAskDialog) + { + bool bShowDialog = officecfg::Office::Calc::Compatibility::MergeCells::ShowDialog::get(); + if (!bApi && bShowDialog) + { + auto pBox = std::make_shared<ScMergeCellsDialog>(GetViewData().GetDialogParent()); + + SfxViewShell* pViewShell = GetViewData().GetViewShell(); + + weld::DialogController::runAsync(pBox, [=](sal_Int32 nRetVal) { + if (nRetVal == RET_OK) + { + bool bRealDoContents = bDoContents; + bool bRealEmptyMergedCells = bEmptyMergedCells; + switch (pBox->GetMergeCellsOption()) + { + case MoveContentHiddenCells: + bRealDoContents = true; + break; + case KeepContentHiddenCells: + bRealEmptyMergedCells = false; + break; + case EmptyContentHiddenCells: + bRealEmptyMergedCells = true; + break; + default: + assert(!"Unknown option for merge cells."); + break; + } + + doMerge(bRealDoContents, bRealEmptyMergedCells); + + if (nSlot != 0) + { + SfxRequest aReq(pViewShell->GetViewFrame(), nSlot); + if (!bApi && bRealDoContents) + aReq.AppendItem(SfxBoolItem(nSlot, bDoContents)); + SfxBindings& rBindings = pViewShell->GetViewFrame().GetBindings(); + rBindings.Invalidate(nSlot); + aReq.Done(); + } + } + // else cancelled + }); + } + } else + doMerge(bDoContents, bEmptyMergedCells); +} + +bool ScViewFunc::TestRemoveMerge() +{ + bool bMerged = false; + ScRange aRange; + if (GetViewData().GetSimpleArea( aRange ) == SC_MARK_SIMPLE) + { + ScDocument& rDoc = GetViewData().GetDocument(); + if ( rDoc.HasAttrib( aRange, HasAttrFlags::Merged ) ) + bMerged = true; + } + return bMerged; +} + +static bool lcl_extendMergeRange(ScCellMergeOption& rOption, const ScRange& rRange) +{ + bool bExtended = false; + if (rOption.mnStartCol > rRange.aStart.Col()) + { + rOption.mnStartCol = rRange.aStart.Col(); + bExtended = true; + } + if (rOption.mnStartRow > rRange.aStart.Row()) + { + rOption.mnStartRow = rRange.aStart.Row(); + bExtended = true; + } + if (rOption.mnEndCol < rRange.aEnd.Col()) + { + rOption.mnEndCol = rRange.aEnd.Col(); + bExtended = true; + } + if (rOption.mnEndRow < rRange.aEnd.Row()) + { + rOption.mnEndRow = rRange.aEnd.Row(); + bExtended = true; + } + return bExtended; +} + +bool ScViewFunc::RemoveMerge() +{ + ScRange aRange; + ScEditableTester aTester( this ); + if (!aTester.IsEditable()) + { + ErrorMessage(aTester.GetMessageId()); + return false; + } + else if (GetViewData().GetSimpleArea( aRange ) == SC_MARK_SIMPLE) + { + ScDocument& rDoc = GetViewData().GetDocument(); + ScRange aExtended( aRange ); + rDoc.ExtendMerge( aExtended ); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + const ScMarkData& rMark = GetViewData().GetMarkData(); + ScCellMergeOption aOption(aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row()); + bool bExtended = false; + do + { + bExtended = false; + for (const SCTAB& i : rMark) + { + aOption.maTabs.insert(i); + aExtended.aStart.SetTab(i); + aExtended.aEnd.SetTab(i); + rDoc.ExtendMerge(aExtended); + rDoc.ExtendOverlapped(aExtended); + + // Expand the current range to be inclusive of all merged + // areas on all sheets. + bExtended = lcl_extendMergeRange(aOption, aExtended); + } + } + while (bExtended); + + bool bOk = pDocSh->GetDocFunc().UnmergeCells(aOption, true/*bRecord*/, nullptr); + aExtended = aOption.getFirstSingleRange(); + MarkRange( aExtended ); + + if (bOk) + pDocSh->UpdateOle(GetViewData()); + } + + OUString aCellLocation = aRange.aStart.GetColRowString(); + collectUIInformation({{"CELL", aCellLocation}}, "UNMERGE_CELL"); + + return true; //! bOk ?? +} + +void ScViewFunc::FillSimple( FillDir eDir ) +{ + ScRange aRange; + if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE) + { + ScDocShell* pDocSh = GetViewData().GetDocShell(); + const ScMarkData& rMark = GetViewData().GetMarkData(); + bool bSuccess = pDocSh->GetDocFunc().FillSimple( aRange, &rMark, eDir, false ); + if (bSuccess) + { + pDocSh->UpdateOle(GetViewData()); + UpdateScrollBars(); + + auto& rDoc = pDocSh->GetDocument(); + bool bDoAutoSpell = rDoc.GetDocOptions().IsAutoSpell(); + if ( bDoAutoSpell ) + { + // Copy AutoSpellData from above(left/right/below) if no selection. + switch (eDir) + { + case FILL_TO_BOTTOM: + if (aRange.aStart.Row() > 0 && aRange.aStart.Row() == aRange.aEnd.Row()) + aRange.aStart.IncRow(-1); + break; + case FILL_TO_TOP: + if (aRange.aEnd.Row() < rDoc.MaxRow() && aRange.aStart.Row() == aRange.aEnd.Row()) + aRange.aEnd.IncRow(1); + break; + case FILL_TO_RIGHT: + if (aRange.aStart.Col() > 0 && aRange.aStart.Col() == aRange.aEnd.Col()) + aRange.aStart.IncCol(-1); + break; + case FILL_TO_LEFT: + if (aRange.aEnd.Col() < rDoc.MaxCol() && aRange.aStart.Col() == aRange.aEnd.Col()) + aRange.aEnd.IncCol(1); + break; + } + CopyAutoSpellData(eDir, aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row(), + ::std::numeric_limits<sal_uLong>::max()); + } + + // Invalidate cell slots and update input line with new content. + CellContentChanged(); + } + } + else + ErrorMessage(STR_NOMULTISELECT); +} + +void ScViewFunc::FillSeries( FillDir eDir, FillCmd eCmd, FillDateCmd eDateCmd, + double fStart, double fStep, double fMax ) +{ + ScRange aRange; + if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE) + { + ScDocShell* pDocSh = GetViewData().GetDocShell(); + const ScMarkData& rMark = GetViewData().GetMarkData(); + bool bSuccess = pDocSh->GetDocFunc(). + FillSeries( aRange, &rMark, eDir, eCmd, eDateCmd, + fStart, fStep, fMax, false ); + if (bSuccess) + { + pDocSh->UpdateOle(GetViewData()); + UpdateScrollBars(); + + HelperNotifyChanges::NotifyIfChangesListeners(*pDocSh, aRange); + } + } + else + ErrorMessage(STR_NOMULTISELECT); +} + +void ScViewFunc::FillAuto( FillDir eDir, SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, sal_uLong nCount ) +{ + SCTAB nTab = GetViewData().GetTabNo(); + ScRange aRange( nStartCol,nStartRow,nTab, nEndCol,nEndRow,nTab ); + ScRange aSourceRange( aRange ); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + const ScMarkData& rMark = GetViewData().GetMarkData(); + bool bSuccess = pDocSh->GetDocFunc(). + FillAuto( aRange, &rMark, eDir, nCount, false ); + if (!bSuccess) + return; + + MarkRange( aRange, false ); // aRange was modified in FillAuto + pDocSh->UpdateOle(GetViewData()); + UpdateScrollBars(); + + bool bDoAutoSpell = pDocSh->GetDocument().GetDocOptions().IsAutoSpell(); + if ( bDoAutoSpell ) + CopyAutoSpellData(eDir, nStartCol, nStartRow, nEndCol, nEndRow, nCount); + + ScModelObj* pModelObj = pDocSh->GetModel(); + + ScRangeList aChangeRanges; + ScRange aChangeRange( aRange ); + switch (eDir) + { + case FILL_TO_BOTTOM: + aChangeRange.aStart.SetRow( aSourceRange.aEnd.Row() + 1 ); + break; + case FILL_TO_TOP: + aChangeRange.aEnd.SetRow( aSourceRange.aStart.Row() - 1 ); + break; + case FILL_TO_RIGHT: + aChangeRange.aStart.SetCol( aSourceRange.aEnd.Col() + 1 ); + break; + case FILL_TO_LEFT: + aChangeRange.aEnd.SetCol( aSourceRange.aStart.Col() - 1 ); + break; + default: + break; + } + aChangeRanges.push_back( aChangeRange ); + + if (HelperNotifyChanges::getMustPropagateChangesModel(pModelObj)) + HelperNotifyChanges::Notify(*pModelObj, aChangeRanges); + else if (pModelObj) + HelperNotifyChanges::Notify(*pModelObj, aChangeRanges, "data-area-invalidate"); +} + +void ScViewFunc::CopyAutoSpellData( FillDir eDir, SCCOL nStartCol, SCROW nStartRow, + SCCOL nEndCol, SCROW nEndRow, sal_uLong nCount ) +{ + const ScDocument* pDoc = &GetViewData().GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + CellType eCellType; + + ScGridWindow* pWin = GetActiveWin(); + if ( pWin->InsideVisibleRange(nStartCol, nStartRow) && pWin->InsideVisibleRange(nEndCol, nEndRow) ) + { + if ( nCount == ::std::numeric_limits<sal_uLong>::max() ) + { + switch( eDir ) + { + case FILL_TO_BOTTOM: + for ( SCCOL nColItr = nStartCol; nColItr <= nEndCol; ++nColItr ) + { + eCellType = pDoc->GetCellType(nColItr, nStartRow, nTab); // We need this optimization only for EditTextObject source cells + if (eCellType != CELLTYPE_EDIT) + continue; + + const std::vector<editeng::MisspellRanges>* pRanges = pWin->GetAutoSpellData(nColItr, nStartRow); + if ( !pRanges ) + continue; + for ( SCROW nRowItr = nStartRow + 1; nRowItr <= nEndRow; ++nRowItr ) + pWin->SetAutoSpellData(nColItr, nRowItr, pRanges); + } + break; + case FILL_TO_TOP: + for ( SCCOL nColItr = nStartCol; nColItr <= nEndCol; ++nColItr ) + { + eCellType = pDoc->GetCellType(nColItr, nEndRow, nTab); // We need this optimization only for EditTextObject source cells + if (eCellType != CELLTYPE_EDIT) + continue; + + const std::vector<editeng::MisspellRanges>* pRanges = pWin->GetAutoSpellData(nColItr, nEndRow); + if ( !pRanges ) + continue; + for ( SCROW nRowItr = nEndRow - 1; nRowItr >= nStartRow; --nRowItr ) + pWin->SetAutoSpellData(nColItr, nRowItr, pRanges); + } + break; + case FILL_TO_RIGHT: + for ( SCROW nRowItr = nStartRow; nRowItr <= nEndRow; ++nRowItr ) + { + eCellType = pDoc->GetCellType(nStartCol, nRowItr, nTab); // We need this optimization only for EditTextObject source cells + if (eCellType != CELLTYPE_EDIT) + continue; + + const std::vector<editeng::MisspellRanges>* pRanges = pWin->GetAutoSpellData(nStartCol, nRowItr); + if ( !pRanges ) + continue; + for ( SCCOL nColItr = nStartCol + 1; nColItr <= nEndCol; ++nColItr ) + pWin->SetAutoSpellData(nColItr, nRowItr, pRanges); + } + break; + case FILL_TO_LEFT: + for ( SCROW nRowItr = nStartRow; nRowItr <= nEndRow; ++nRowItr ) + { + eCellType = pDoc->GetCellType(nEndCol, nRowItr, nTab); // We need this optimization only for EditTextObject source cells + if (eCellType != CELLTYPE_EDIT) + continue; + + const std::vector<editeng::MisspellRanges>* pRanges = pWin->GetAutoSpellData(nEndCol, nRowItr); + if ( !pRanges ) + continue; + for ( SCCOL nColItr = nEndCol - 1; nColItr >= nStartCol; --nColItr ) + pWin->SetAutoSpellData(nColItr, nRowItr, pRanges); + } + break; + } + return; + } + + typedef const std::vector<editeng::MisspellRanges>* MisspellRangesType; + SCROW nRowRepeatSize = nEndRow - nStartRow + 1; + SCCOL nColRepeatSize = nEndCol - nStartCol + 1; + SCROW nTillRow = 0; + SCCOL nTillCol = 0; + std::vector<std::vector<MisspellRangesType>> aSourceSpellRanges(nRowRepeatSize, std::vector<MisspellRangesType>(nColRepeatSize, nullptr)); + + for ( SCROW nRowIdx = 0; nRowIdx < nRowRepeatSize; ++nRowIdx ) + { + for ( SCCOL nColIdx = 0; nColIdx < nColRepeatSize; ++nColIdx ) + { + eCellType = pDoc->GetCellType(nStartCol + nColIdx, nStartRow + nRowIdx, nTab); // We need this optimization only for EditTextObject source cells + if (eCellType != CELLTYPE_EDIT) + continue; + + aSourceSpellRanges[nRowIdx][nColIdx] = pWin->GetAutoSpellData( nStartCol + nColIdx, nStartRow + nRowIdx ); + } + } + + switch( eDir ) + { + case FILL_TO_BOTTOM: + nTillRow = nEndRow + nCount; + for ( SCCOL nColItr = nStartCol; nColItr <= nEndCol; ++nColItr ) + { + for ( SCROW nRowItr = nEndRow + 1; nRowItr <= nTillRow; ++nRowItr ) + { + size_t nSourceRowIdx = ( nRowItr - nEndRow - 1 ) % nRowRepeatSize; + MisspellRangesType pRanges = aSourceSpellRanges[nSourceRowIdx][nColItr - nStartCol]; + if ( !pRanges ) + continue; + pWin->SetAutoSpellData(nColItr, nRowItr, pRanges); + } + } + break; + + case FILL_TO_TOP: + nTillRow = nStartRow - nCount; + for ( SCCOL nColItr = nStartCol; nColItr <= nEndCol; ++nColItr ) + { + for ( SCROW nRowItr = nStartRow - 1; nRowItr >= nTillRow; --nRowItr ) + { + size_t nSourceRowIdx = nRowRepeatSize - 1 - ( ( nStartRow - 1 - nRowItr ) % nRowRepeatSize ); + MisspellRangesType pRanges = aSourceSpellRanges[nSourceRowIdx][nColItr - nStartCol]; + if ( !pRanges ) + continue; + pWin->SetAutoSpellData(nColItr, nRowItr, pRanges); + } + } + break; + + case FILL_TO_RIGHT: + nTillCol = nEndCol + nCount; + for ( SCCOL nColItr = nEndCol + 1; nColItr <= nTillCol; ++nColItr ) + { + size_t nSourceColIdx = ( nColItr - nEndCol - 1 ) % nColRepeatSize; + for ( SCROW nRowItr = nStartRow; nRowItr <= nEndRow; ++nRowItr ) + { + MisspellRangesType pRanges = aSourceSpellRanges[nRowItr - nStartRow][nSourceColIdx]; + if ( !pRanges ) + continue; + pWin->SetAutoSpellData(nColItr, nRowItr, pRanges); + } + } + break; + + case FILL_TO_LEFT: + nTillCol = nStartCol - nCount; + for ( SCCOL nColItr = nStartCol - 1; nColItr >= nTillCol; --nColItr ) + { + size_t nSourceColIdx = nColRepeatSize - 1 - ( ( nStartCol - 1 - nColItr ) % nColRepeatSize ); + for ( SCROW nRowItr = nStartRow; nRowItr <= nEndRow; ++nRowItr ) + { + MisspellRangesType pRanges = aSourceSpellRanges[nRowItr - nStartRow][nSourceColIdx]; + if ( !pRanges ) + continue; + pWin->SetAutoSpellData(nColItr, nRowItr, pRanges); + } + } + break; + } + } + else + pWin->ResetAutoSpellForContentChange(); + +} + +void ScViewFunc::FillTab( InsertDeleteFlags nFlags, ScPasteFunc nFunction, bool bSkipEmpty, bool bAsLink ) +{ + //! allow source sheet to be protected + ScEditableTester aTester( this ); + if (!aTester.IsEditable()) + { + ErrorMessage(aTester.GetMessageId()); + return; + } + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScMarkData& rMark = GetViewData().GetMarkData(); + SCTAB nTab = GetViewData().GetTabNo(); + bool bUndo(rDoc.IsUndoEnabled()); + + ScRange aMarkRange; + rMark.MarkToSimple(); + bool bMulti = rMark.IsMultiMarked(); + if (bMulti) + aMarkRange = rMark.GetMultiMarkArea(); + else if (rMark.IsMarked()) + aMarkRange = rMark.GetMarkArea(); + else + aMarkRange = ScRange( GetViewData().GetCurX(), GetViewData().GetCurY(), nTab ); + + ScDocumentUniquePtr pUndoDoc; + + if (bUndo) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nTab, nTab ); + + for (const SCTAB& i : rMark) + if (i != nTab ) + { + pUndoDoc->AddUndoTab( i, i ); + aMarkRange.aStart.SetTab( i ); + aMarkRange.aEnd.SetTab( i ); + rDoc.CopyToDocument( aMarkRange, InsertDeleteFlags::ALL, bMulti, *pUndoDoc ); + } + } + + if (bMulti) + rDoc.FillTabMarked( nTab, rMark, nFlags, nFunction, bSkipEmpty, bAsLink ); + else + { + aMarkRange.aStart.SetTab( nTab ); + aMarkRange.aEnd.SetTab( nTab ); + rDoc.FillTab( aMarkRange, rMark, nFlags, nFunction, bSkipEmpty, bAsLink ); + } + + if (bUndo) + { //! for ChangeTrack not until the end + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoFillTable>( pDocSh, rMark, + aMarkRange.aStart.Col(), aMarkRange.aStart.Row(), nTab, + aMarkRange.aEnd.Col(), aMarkRange.aEnd.Row(), nTab, + std::move(pUndoDoc), bMulti, nTab, nFlags, nFunction, bSkipEmpty, bAsLink ) ); + } + + pDocSh->PostPaintGridAll(); + pDocSh->PostDataChanged(); +} + +/** Downward fill of selected cell(s) by double-clicking cross-hair cursor + + Either, extends a current selection if non-empty cells exist immediately + below the selection, overwriting cells below the selection up to the + minimum row of already filled cells. + + Or, extends a current selection down to the last non-empty cell of an + adjacent column when the lower-right corner of the selection is + double-clicked. It uses a left-adjoining non-empty column as a guide if + such is available, otherwise a right-adjoining non-empty column is used. + + @return No return value + + @see #i12313# +*/ +void ScViewFunc::FillCrossDblClick() +{ + ScRange aRange; + GetViewData().GetSimpleArea( aRange ); + aRange.PutInOrder(); + + SCTAB nTab = GetViewData().GetCurPos().Tab(); + SCCOL nStartX = aRange.aStart.Col(); + SCROW nStartY = aRange.aStart.Row(); + SCCOL nEndX = aRange.aEnd.Col(); + SCROW nEndY = aRange.aEnd.Row(); + + ScDocument& rDoc = GetViewData().GetDocument(); + + if (nEndY >= rDoc.MaxRow()) + // Nothing to fill. + return; + + // Make sure the selection is not empty + if ( rDoc.IsBlockEmpty( nStartX, nStartY, nEndX, nEndY, nTab ) ) + return; + + // If there is data in all columns immediately below the selection then + // switch to overwriting fill. + SCROW nOverWriteEndRow = rDoc.MaxRow(); + for (SCCOL nCol = nStartX; nCol <= nEndX; ++nCol) + { + if (rDoc.HasData( nCol, nEndY + 1, nTab)) + { + // Determine the shortest data column to end the fill. + SCROW nY = nEndY + 1; + // FindAreaPos() returns the start row of the next data block if + // the current row is the last row of a data block and an empty + // cell follows. Somewhat unexpected behaviour... + // So check beforehand if there is one non-empty cell following. + if (rDoc.HasData( nCol, nY + 1, nTab)) + { + rDoc.FindAreaPos( nCol, nY, nTab, SC_MOVE_DOWN); + if (nOverWriteEndRow > nY) + nOverWriteEndRow = nY; + } + else + { + nOverWriteEndRow = nY; + } + } + else + { + nOverWriteEndRow = 0; + break; // for + } + } + + if (nOverWriteEndRow > nEndY) + { + FillAuto( FILL_TO_BOTTOM, nStartX, nStartY, nEndX, nEndY, nOverWriteEndRow - nEndY); + return; + } + + // Non-overwriting fill follows. + + const bool bDataLeft = (nStartX > 0); + if (!bDataLeft && nEndX >= rDoc.MaxCol()) + // Absolutely no data left or right of selection. + return; + + // Check that there is + // 1) data immediately left (preferred) or right of start (row) of selection + // 2) data there below + // 3) no data immediately below selection + + SCCOL nMovX = (bDataLeft ? nStartX - 1 : nEndX + 1); + SCROW nMovY = nStartY; + bool bDataFound = (rDoc.HasData( nMovX, nStartY, nTab) && rDoc.HasData( nMovX, nStartY + 1, nTab)); + if (!bDataFound && bDataLeft && nEndX < rDoc.MaxCol()) + { + nMovX = nEndX + 1; // check right + bDataFound = (rDoc.HasData( nMovX, nStartY, nTab) && rDoc.HasData( nMovX, nStartY + 1, nTab)); + } + + if (!(bDataFound && rDoc.IsEmptyData( nStartX, nEndY + 1, nEndX, nEndY + 1, nTab ))) + return; + + // Get end of data left or right. + rDoc.FindAreaPos( nMovX, nMovY, nTab, SC_MOVE_DOWN); + // Find minimum end row of below empty area and data right. + for (SCCOL nX = nStartX; nX <= nEndX; ++nX) + { + SCROW nY = nEndY + 1; + // Get next row with data in this column. + rDoc.FindAreaPos( nX, nY, nTab, SC_MOVE_DOWN); + if (nMovY == rDoc.MaxRow() && nY == rDoc.MaxRow()) + { + // FindAreaPos() returns MAXROW also if there is no data at all + // from the start, so check if that contains data if the nearby + // (left or right) data ends there and increment if no data + // here, pretending the next data would be thereafter so nMovY + // will not be decremented. + if (!rDoc.HasData( nX, nY, nTab)) + ++nY; + } + if (nMovY > nY - 1) + nMovY = nY - 1; + } + + if (nMovY > nEndY) + { + FillAuto( FILL_TO_BOTTOM, nStartX, nStartY, nEndX, nEndY, nMovY - nEndY); + } +} + +void ScViewFunc::ConvertFormulaToValue() +{ + ScRange aRange; + GetViewData().GetSimpleArea(aRange); + aRange.PutInOrder(); + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + pDocSh->GetDocFunc().ConvertFormulaToValue(aRange, true); + // tdf#131326 - invalidate cell slots and update input line with new content + CellContentChanged(); + pDocSh->PostPaint(aRange, PaintPartFlags::Grid); +} + +void ScViewFunc::TransliterateText( TransliterationFlags nType ) +{ + ScMarkData aFuncMark = GetViewData().GetMarkData(); + if ( !aFuncMark.IsMarked() && !aFuncMark.IsMultiMarked() ) + { + // no selection -> use cursor position + + ScAddress aCursor( GetViewData().GetCurX(), GetViewData().GetCurY(), GetViewData().GetTabNo() ); + aFuncMark.SetMarkArea( ScRange( aCursor ) ); + } + + bool bSuccess = GetViewData().GetDocShell()->GetDocFunc(). + TransliterateText( aFuncMark, nType, false ); + if (bSuccess) + { + GetViewData().GetViewShell()->UpdateInputHandler(); + } +} + +// AutoFormat + +ScAutoFormatData* ScViewFunc::CreateAutoFormatData() +{ + ScAutoFormatData* pData = nullptr; + SCCOL nStartCol; + SCROW nStartRow; + SCTAB nStartTab; + SCCOL nEndCol; + SCROW nEndRow; + SCTAB nEndTab; + if (GetViewData().GetSimpleArea(nStartCol,nStartRow,nStartTab,nEndCol,nEndRow,nEndTab) == SC_MARK_SIMPLE) + { + if ( nEndCol-nStartCol >= 3 && nEndRow-nStartRow >= 3 ) + { + ScDocument& rDoc = GetViewData().GetDocument(); + pData = new ScAutoFormatData; + rDoc.GetAutoFormatData( nStartTab, nStartCol,nStartRow,nEndCol,nEndRow, *pData ); + } + } + return pData; +} + +void ScViewFunc::AutoFormat( sal_uInt16 nFormatNo ) +{ + ScRange aRange; + if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE) + { + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScMarkData& rMark = GetViewData().GetMarkData(); + + bool bSuccess = pDocSh->GetDocFunc().AutoFormat( aRange, &rMark, nFormatNo, false ); + if (bSuccess) + pDocSh->UpdateOle(GetViewData()); + } + else + ErrorMessage(STR_NOMULTISELECT); +} + +// Search & Replace + +bool ScViewFunc::SearchAndReplace( const SvxSearchItem* pSearchItem, + bool bAddUndo, bool bIsApi ) +{ + SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::Empty); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScMarkData& rMark = GetViewData().GetMarkData(); + if (bAddUndo && !rDoc.IsUndoEnabled()) + bAddUndo = false; + + if ( !rMark.IsMarked() && !rMark.IsMultiMarked() && (pSearchItem->HasStartPoint()) ) + { + // No selection -> but we have a start point (top left corner of the + // current view), start searching from there, not from the current + // cursor position. + SCCOL nPosX; + SCROW nPosY; + + int nPixelX = pSearchItem->GetStartPointX() * GetViewData().GetPPTX(); + int nPixelY = pSearchItem->GetStartPointY() * GetViewData().GetPPTY(); + + GetViewData().GetPosFromPixel(nPixelX, nPixelY, GetViewData().GetActivePart(), nPosX, nPosY); + + AlignToCursor( nPosX, nPosY, SC_FOLLOW_JUMP ); + SetCursor( nPosX, nPosY, true ); + } + + SCCOL nCol, nOldCol; + SCROW nRow, nOldRow; + SCTAB nTab, nOldTab; + nCol = nOldCol = GetViewData().GetCurX(); + nRow = nOldRow = GetViewData().GetCurY(); + nTab = nOldTab = GetViewData().GetTabNo(); + + SvxSearchCmd nCommand = pSearchItem->GetCommand(); + bool bAllTables = pSearchItem->IsAllTables(); + std::set<SCTAB> aOldSelectedTables; + SCTAB nLastTab = rDoc.GetTableCount() - 1; + SCTAB nStartTab, nEndTab; + if ( bAllTables ) + { + nStartTab = 0; + nEndTab = nLastTab; + std::set<SCTAB> aTmp(rMark.begin(), rMark.end()); + aOldSelectedTables.swap(aTmp); + } + else + { //! at least one is always selected + nStartTab = rMark.GetFirstSelected(); + nEndTab = rMark.GetLastSelected(); + } + + if ( nCommand == SvxSearchCmd::FIND + || nCommand == SvxSearchCmd::FIND_ALL) + bAddUndo = false; + + //! account for bAttrib during Undo !!! + + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr<ScMarkData> pUndoMark; + OUString aUndoStr; + if (bAddUndo) + { + pUndoMark.reset(new ScMarkData(rMark)); // Mark is being modified + if ( nCommand == SvxSearchCmd::REPLACE_ALL ) + { + pUndoDoc.reset(new ScDocument(SCDOCMODE_UNDO)); + pUndoDoc->InitUndo( rDoc, nStartTab, nEndTab ); + } + } + + if ( bAllTables ) + { //! select all, after pUndoMark has been created + for ( SCTAB j = nStartTab; j <= nEndTab; j++ ) + { + rMark.SelectTable( j, true ); + } + } + + DoneBlockMode(true); // don't delete mark + InitOwnBlockMode( ScRange( nCol, nRow, nStartTab, nCol, nRow, nEndTab)); + + // If search starts at the beginning don't ask again whether it shall start at the beginning + bool bFirst = true; + if ( nCol == 0 && nRow == 0 && nTab == nStartTab && !pSearchItem->GetBackward() ) + bFirst = false; + + bool bFound = false; + while (true) + { + GetFrameWin()->EnterWait(); + ScRangeList aMatchedRanges; + bool bMatchedRangesWereClamped = false; + if (rDoc.SearchAndReplace(*pSearchItem, nCol, nRow, nTab, rMark, aMatchedRanges, aUndoStr, pUndoDoc.get(), bMatchedRangesWereClamped)) + { + bFound = true; + if (bAddUndo) + { + GetViewData().GetDocShell()->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoReplace>( GetViewData().GetDocShell(), *pUndoMark, + nCol, nRow, nTab, + aUndoStr, std::move(pUndoDoc), pSearchItem ) ); + } + + if (nCommand == SvxSearchCmd::FIND_ALL || nCommand == SvxSearchCmd::REPLACE_ALL) + { + SfxViewFrame* pViewFrm = SfxViewFrame::Current(); + bool bShow = GetViewData().GetViewShell()->GetViewData().GetOptions().GetOption( VOPT_SUMMARY ); + + if (bShow && pViewFrm && !comphelper::LibreOfficeKit::isActive()) + { + pViewFrm->ShowChildWindow(sc::SearchResultsDlgWrapper::GetChildWindowId()); + SfxChildWindow* pWnd = pViewFrm->GetChildWindow(sc::SearchResultsDlgWrapper::GetChildWindowId()); + if (pWnd) + { + sc::SearchResultsDlg* pDlg = static_cast<sc::SearchResultsDlg*>(pWnd->GetController().get()); + if (pDlg) + { + const bool bCellNotes = (pSearchItem->GetCellType() == SvxSearchCellType::NOTE); + // ScCellIterator iterates over cells with content, + // for empty cells iterate over match positions. + const bool bEmptyCells = (!bCellNotes + && ((nCommand == SvxSearchCmd::FIND_ALL + && ScDocument::IsEmptyCellSearch(*pSearchItem)) + || (nCommand == SvxSearchCmd::REPLACE_ALL + && pSearchItem->GetReplaceString().isEmpty()))); + pDlg->FillResults(rDoc, aMatchedRanges, bCellNotes, bEmptyCells, bMatchedRangesWereClamped); + } + } + } + + rMark.ResetMark(); + for (size_t i = 0, n = aMatchedRanges.size(); i < n; ++i) + { + const ScRange& r = aMatchedRanges[i]; + if (r.aStart.Tab() == nTab) + rMark.SetMultiMarkArea(r); + } + } + + break; // break 'while (TRUE)' + } + else if ( bFirst && (nCommand == SvxSearchCmd::FIND || + nCommand == SvxSearchCmd::REPLACE) ) + { + bFirst = false; + GetFrameWin()->LeaveWait(); + if (!bIsApi) + { + if ( nStartTab == nEndTab ) + SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::EndSheet); + else + SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::End); + + rDoc.GetSearchAndReplaceStart( *pSearchItem, nCol, nRow ); + if (pSearchItem->GetBackward()) + nTab = nEndTab; + else + nTab = nStartTab; + } + else + { + break; // break 'while (TRUE)' + } + } + else // nothing found + { + if ( nCommand == SvxSearchCmd::FIND_ALL || nCommand == SvxSearchCmd::REPLACE_ALL ) + { + pDocSh->PostPaintGridAll(); // Mark + } + + GetFrameWin()->LeaveWait(); + if (!bIsApi) + { + GetViewData().GetViewShell()->libreOfficeKitViewCallback(LOK_CALLBACK_SEARCH_NOT_FOUND, pSearchItem->GetSearchString().toUtf8()); + SvxSearchDialogWrapper::SetSearchLabel(SearchLabel::NotFound); + } + + break; // break 'while (TRUE)' + } + } // of while true + + if (!aOldSelectedTables.empty()) + { + // restore originally selected table + for (SCTAB i = 0; i <= nEndTab; ++i) + rMark.SelectTable(i, false); + + for (const auto& rTab : aOldSelectedTables) + rMark.SelectTable(rTab, true); + + if ( bFound ) + { // if a table is selected as a "match" it remains selected. + rMark.SelectTable( nTab, true ); + // It's a swap if only one table was selected before + //! otherwise now one table more might be selected + if ( aOldSelectedTables.size() == 1 && nTab != nOldTab ) + rMark.SelectTable( nOldTab, false ); + } + } + + // Avoid LOK selection notifications before we have all the results. + GetViewData().GetViewShell()->setTiledSearching(true); + MarkDataChanged(); + GetViewData().GetViewShell()->setTiledSearching(false); + + if ( bFound ) + { + if ( nTab != GetViewData().GetTabNo() ) + SetTabNo( nTab ); + + // if nothing is marked, DoneBlockMode, then marking can start + // directly from this place via Shift-Cursor + if (!rMark.IsMarked() && !rMark.IsMultiMarked()) + DoneBlockMode(true); + + AlignToCursor( nCol, nRow, SC_FOLLOW_JUMP ); + SetCursor( nCol, nRow, true ); + + if (comphelper::LibreOfficeKit::isActive()) + { + Point aCurPos = GetViewData().GetScrPos(nCol, nRow, GetViewData().GetActivePart()); + + // just update the cell selection + ScGridWindow* pGridWindow = GetViewData().GetActiveWin(); + // Don't move cell selection handles for find-all: selection of all but the first result would be lost. + if (pGridWindow && nCommand == SvxSearchCmd::FIND) + { + // move the cell selection handles + pGridWindow->SetCellSelectionPixel(LOK_SETTEXTSELECTION_RESET, aCurPos.X(), aCurPos.Y()); + pGridWindow->SetCellSelectionPixel(LOK_SETTEXTSELECTION_START, aCurPos.X(), aCurPos.Y()); + pGridWindow->SetCellSelectionPixel(LOK_SETTEXTSELECTION_END, aCurPos.X(), aCurPos.Y()); + } + + if (pGridWindow) + { + std::vector<tools::Rectangle> aLogicRects; + pGridWindow->GetCellSelection(aLogicRects); + + boost::property_tree::ptree aTree; + aTree.put("searchString", pSearchItem->GetSearchString().toUtf8().getStr()); + aTree.put("highlightAll", nCommand == SvxSearchCmd::FIND_ALL); + + boost::property_tree::ptree aSelections; + for (const tools::Rectangle& rLogicRect : aLogicRects) + { + boost::property_tree::ptree aSelection; + aSelection.put("part", OString::number(nTab).getStr()); + aSelection.put("rectangles", rLogicRect.toString().getStr()); + aSelections.push_back(std::make_pair("", aSelection)); + } + aTree.add_child("searchResultSelection", aSelections); + + std::stringstream aStream; + boost::property_tree::write_json(aStream, aTree); + OString aPayload( aStream.str() ); + SfxViewShell* pViewShell = GetViewData().GetViewShell(); + pViewShell->libreOfficeKitViewCallback(LOK_CALLBACK_SEARCH_RESULT_SELECTION, aPayload); + + // Trigger LOK_CALLBACK_TEXT_SELECTION now. + MarkDataChanged(); + } + } + + if ( nCommand == SvxSearchCmd::REPLACE + || nCommand == SvxSearchCmd::REPLACE_ALL ) + { + if ( nCommand == SvxSearchCmd::REPLACE ) + { + pDocSh->PostPaint( nCol,nRow,nTab, nCol,nRow,nTab, PaintPartFlags::Grid ); + + // jump to next cell if we replaced everything in the cell + // where the cursor was positioned (but avoid switching tabs) + if ( nCol == nOldCol && nRow == nOldRow && nTab == nOldTab ) + { + SvxSearchItem aSearchItem = ScGlobal::GetSearchItem(); + aSearchItem.SetCommand(SvxSearchCmd::FIND); + aSearchItem.SetWhich(SID_SEARCH_ITEM); + + ScRangeList aMatchedRanges; + bool bMatchedRangesWereClamped; + ScTable::UpdateSearchItemAddressForReplace( aSearchItem, nCol, nRow ); + if ( rDoc.SearchAndReplace( aSearchItem, nCol, nRow, nTab, rMark, aMatchedRanges, aUndoStr, nullptr, bMatchedRangesWereClamped ) && + ( nTab == nOldTab ) && + ( nCol != nOldCol || nRow != nOldRow ) ) + { + AlignToCursor(nCol, nRow, SC_FOLLOW_JUMP); + SetCursor( nCol, nRow, true ); + } + } + } + else + pDocSh->PostPaintGridAll(); + pDocSh->SetDocumentModified(); + } + else if ( nCommand == SvxSearchCmd::FIND_ALL ) + pDocSh->PostPaintGridAll(); // mark + GetFrameWin()->LeaveWait(); + } + return bFound; +} + +// Goal Seek + +void ScViewFunc::Solve( const ScSolveParam& rParam ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + + SCCOL nDestCol = rParam.aRefVariableCell.Col(); + SCROW nDestRow = rParam.aRefVariableCell.Row(); + SCTAB nDestTab = rParam.aRefVariableCell.Tab(); + + ScEditableTester aTester( rDoc, nDestTab, nDestCol,nDestRow, nDestCol,nDestRow ); + if (!aTester.IsEditable()) + { + ErrorMessage(aTester.GetMessageId()); + return; + } + + OUString aTargetValStr; + if ( rParam.pStrTargetVal ) + aTargetValStr = *rParam.pStrTargetVal; + + OUString aMsgStr; + OUString aResStr; + double nSolveResult; + + GetFrameWin()->EnterWait(); + + bool bExact = + rDoc.Solver( + rParam.aRefFormulaCell.Col(), + rParam.aRefFormulaCell.Row(), + rParam.aRefFormulaCell.Tab(), + nDestCol, nDestRow, nDestTab, + aTargetValStr, + nSolveResult ); + + GetFrameWin()->LeaveWait(); + + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + sal_uLong nFormat = 0; + const ScPatternAttr* pPattern = rDoc.GetPattern( nDestCol, nDestRow, nDestTab ); + if ( pPattern ) + nFormat = pPattern->GetNumberFormat( pFormatter ); + const Color* p; + pFormatter->GetOutputString( nSolveResult, nFormat, aResStr, &p ); + + if ( bExact ) + { + aMsgStr += ScResId( STR_MSSG_SOLVE_0 ) + + aResStr + + ScResId( STR_MSSG_SOLVE_1 ); + } + else + { + aMsgStr = ScResId( STR_MSSG_SOLVE_2 ) + + ScResId( STR_MSSG_SOLVE_3 ) + + aResStr + + ScResId( STR_MSSG_SOLVE_4 ); + } + + std::unique_ptr<weld::MessageDialog> xBox(Application::CreateMessageDialog(GetViewData().GetDialogParent(), + VclMessageType::Question, VclButtonsType::YesNo, aMsgStr)); + xBox->set_title(ScResId(STR_MSSG_DOSUBTOTALS_0)); + xBox->set_default_response(RET_NO); + if (xBox->run() == RET_YES) + EnterValue( nDestCol, nDestRow, nDestTab, nSolveResult ); + + GetViewData().GetViewShell()->UpdateInputHandler( true ); +} + +// multi operation + +void ScViewFunc::TabOp( const ScTabOpParam& rParam, bool bRecord ) +{ + ScRange aRange; + if (GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE) + { + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScMarkData& rMark = GetViewData().GetMarkData(); + pDocSh->GetDocFunc().TabOp( aRange, &rMark, rParam, bRecord, false ); + } + else + ErrorMessage(STR_NOMULTISELECT); +} + +void ScViewFunc::MakeScenario( const OUString& rName, const OUString& rComment, + const Color& rColor, ScScenarioFlags nFlags ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScMarkData& rMark = GetViewData().GetMarkData(); + SCTAB nTab = GetViewData().GetTabNo(); + + SCTAB nNewTab = pDocSh->MakeScenario( nTab, rName, rComment, rColor, nFlags, rMark ); + if (nFlags & ScScenarioFlags::CopyAll) + SetTabNo( nNewTab, true ); // ScScenarioFlags::CopyAll -> visible + else + { + SfxBindings& rBindings = GetViewData().GetBindings(); + rBindings.Invalidate( SID_STATUS_DOCPOS ); // Statusbar + rBindings.Invalidate( SID_ROWCOL_SELCOUNT ); // Statusbar + rBindings.Invalidate( SID_TABLES_COUNT ); + rBindings.Invalidate( SID_SELECT_SCENARIO ); + rBindings.Invalidate( FID_TABLE_SHOW ); + } +} + +void ScViewFunc::ExtendScenario() +{ + ScEditableTester aTester( this ); + if (!aTester.IsEditable()) + { + ErrorMessage(aTester.GetMessageId()); + return; + } + + // Undo: apply attributes + + ScDocument& rDoc = GetViewData().GetDocument(); + ScPatternAttr aPattern( rDoc.GetPool() ); + aPattern.GetItemSet().Put( ScMergeFlagAttr( ScMF::Scenario ) ); + aPattern.GetItemSet().Put( ScProtectionAttr( true ) ); + ApplySelectionPattern(aPattern); +} + +void ScViewFunc::UseScenario( const OUString& rName ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + SCTAB nTab = GetViewData().GetTabNo(); + + DoneBlockMode(); + InitOwnBlockMode( ScRange( GetViewData().GetCurX(), GetViewData().GetCurY(), nTab)); + pDocSh->UseScenario( nTab, rName ); +} + +// Insert table + +bool ScViewFunc::InsertTable( const OUString& rName, SCTAB nTab, bool bRecord ) +{ + // Order Table/Name is inverted for DocFunc + bool bSuccess = GetViewData().GetDocShell()->GetDocFunc(). + InsertTable( nTab, rName, bRecord, false ); + if (bSuccess) + SetTabNo( nTab, true ); + + return bSuccess; +} + +// Insert tables + +void ScViewFunc::InsertTables(std::vector<OUString>& aNames, SCTAB nTab, + SCTAB nCount, bool bRecord ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + weld::WaitObject aWait(GetViewData().GetDialogParent()); + + if (bRecord) + { + rDoc.BeginDrawUndo(); // InsertTab creates a SdrUndoNewPage + } + + bool bFlag=false; + + if(aNames.empty()) + { + rDoc.CreateValidTabNames(aNames, nCount); + } + if (rDoc.InsertTabs(nTab, aNames)) + { + pDocSh->Broadcast( ScTablesHint( SC_TABS_INSERTED, nTab, nCount ) ); + bFlag = true; + } + + if (!bFlag) + return; + + if (bRecord) + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoInsertTables>( pDocSh, nTab, std::move(aNames))); + + // Update views + + SetTabNo( nTab, true ); + pDocSh->PostPaintExtras(); + pDocSh->SetDocumentModified(); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); +} + +bool ScViewFunc::AppendTable( const OUString& rName, bool bRecord ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + weld::WaitObject aWait(GetViewData().GetDialogParent()); + + if (bRecord) + rDoc.BeginDrawUndo(); // InsertTab creates a SdrUndoNewPage + + if (rDoc.InsertTab( SC_TAB_APPEND, rName )) + { + SCTAB nTab = rDoc.GetTableCount()-1; + if (bRecord) + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoInsertTab>( pDocSh, nTab, true, rName)); + GetViewData().InsertTab( nTab ); + SetTabNo( nTab, true ); + pDocSh->PostPaintExtras(); + pDocSh->SetDocumentModified(); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); + return true; + } + else + { + return false; + } +} + +void ScViewFunc::DeleteTable( SCTAB nTab, bool bRecord ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + + bool bSuccess = pDocSh->GetDocFunc().DeleteTable( nTab, bRecord ); + if (bSuccess) + { + SCTAB nNewTab = nTab; + if ( nNewTab >= rDoc.GetTableCount() ) + --nNewTab; + SetTabNo( nNewTab, true ); + } +} + +//only use this method for undo for now, all sheets must be connected +//this method doesn't support undo for now, merge it when it with the other method later +void ScViewFunc::DeleteTables( const SCTAB nTab, SCTAB nSheets ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + bool bVbaEnabled = rDoc.IsInVBAMode(); + SCTAB nNewTab = nTab; + weld::WaitObject aWait(GetViewData().GetDialogParent()); + + while ( nNewTab > 0 && !rDoc.IsVisible( nNewTab ) ) + --nNewTab; + + if (!rDoc.DeleteTabs(nTab, nSheets)) + return; + + if( bVbaEnabled ) + { + for (SCTAB aTab = 0; aTab < nSheets; ++aTab) + { + OUString sCodeName; + bool bHasCodeName = rDoc.GetCodeName( nTab + aTab, sCodeName ); + if ( bHasCodeName ) + VBA_DeleteModule( *pDocSh, sCodeName ); + } + } + + pDocSh->Broadcast( ScTablesHint( SC_TABS_DELETED, nTab, nSheets ) ); + if ( nNewTab >= rDoc.GetTableCount() ) + nNewTab = rDoc.GetTableCount() - 1; + SetTabNo( nNewTab, true ); + + pDocSh->PostPaintExtras(); + pDocSh->SetDocumentModified(); + + SfxApplication* pSfxApp = SfxGetpApp(); // Navigator + pSfxApp->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); + pSfxApp->Broadcast( SfxHint( SfxHintId::ScDbAreasChanged ) ); + pSfxApp->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) ); +} + +bool ScViewFunc::DeleteTables(const vector<SCTAB> &TheTabs, bool bRecord ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + bool bVbaEnabled = rDoc.IsInVBAMode(); + SCTAB nNewTab = TheTabs.front(); + weld::WaitObject aWait(GetViewData().GetDialogParent()); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + if ( bVbaEnabled ) + bRecord = false; + + while ( nNewTab > 0 && !rDoc.IsVisible( nNewTab ) ) + --nNewTab; + + bool bWasLinked = false; + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr<ScRefUndoData> pUndoData; + if (bRecord) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + SCTAB nCount = rDoc.GetTableCount(); + + OUString aOldName; + for(size_t i=0; i<TheTabs.size(); ++i) + { + SCTAB nTab = TheTabs[i]; + if (i==0) + pUndoDoc->InitUndo( rDoc, nTab,nTab, true,true ); // incl. column/fow flags + else + pUndoDoc->AddUndoTab( nTab,nTab, true,true ); // incl. column/fow flags + + rDoc.CopyToDocument(0,0,nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, InsertDeleteFlags::ALL,false, *pUndoDoc ); + rDoc.GetName( nTab, aOldName ); + pUndoDoc->RenameTab( nTab, aOldName ); + if (rDoc.IsLinked(nTab)) + { + bWasLinked = true; + pUndoDoc->SetLink( nTab, rDoc.GetLinkMode(nTab), rDoc.GetLinkDoc(nTab), + rDoc.GetLinkFlt(nTab), rDoc.GetLinkOpt(nTab), + rDoc.GetLinkTab(nTab), + rDoc.GetLinkRefreshDelay(nTab) ); + } + if ( rDoc.IsScenario(nTab) ) + { + pUndoDoc->SetScenario( nTab, true ); + OUString aComment; + Color aColor; + ScScenarioFlags nScenFlags; + rDoc.GetScenarioData( nTab, aComment, aColor, nScenFlags ); + pUndoDoc->SetScenarioData( nTab, aComment, aColor, nScenFlags ); + bool bActive = rDoc.IsActiveScenario( nTab ); + pUndoDoc->SetActiveScenario( nTab, bActive ); + } + pUndoDoc->SetVisible( nTab, rDoc.IsVisible( nTab ) ); + pUndoDoc->SetTabBgColor( nTab, rDoc.GetTabBgColor(nTab) ); + auto pSheetEvents = rDoc.GetSheetEvents( nTab ); + pUndoDoc->SetSheetEvents( nTab, std::unique_ptr<ScSheetEvents>(pSheetEvents ? new ScSheetEvents(*pSheetEvents) : nullptr) ); + pUndoDoc->SetLayoutRTL( nTab, rDoc.IsLayoutRTL( nTab ) ); + + if ( rDoc.IsTabProtected( nTab ) ) + pUndoDoc->SetTabProtection(nTab, rDoc.GetTabProtection(nTab)); + + // Drawing-Layer is responsible for its Undo !!! + // pUndoDoc->TransferDrawPage(rDoc, nTab,nTab); + } + + pUndoDoc->AddUndoTab( 0, nCount-1 ); // all Tabs for references + + rDoc.BeginDrawUndo(); // DeleteTab creates a SdrUndoDelPage + + pUndoData.reset(new ScRefUndoData( &rDoc )); + } + + bool bDelDone = false; + + for(int i=TheTabs.size()-1; i>=0; --i) + { + OUString sCodeName; + bool bHasCodeName = rDoc.GetCodeName( TheTabs[i], sCodeName ); + if (rDoc.DeleteTab(TheTabs[i])) + { + bDelDone = true; + if( bVbaEnabled && bHasCodeName ) + { + VBA_DeleteModule( *pDocSh, sCodeName ); + } + pDocSh->Broadcast( ScTablesHint( SC_TAB_DELETED, TheTabs[i] ) ); + } + } + if (bRecord) + { + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDeleteTab>( GetViewData().GetDocShell(), TheTabs, + std::move(pUndoDoc), std::move(pUndoData) )); + } + + if (bDelDone) + { + if ( nNewTab >= rDoc.GetTableCount() ) + nNewTab = rDoc.GetTableCount() - 1; + + SetTabNo( nNewTab, true ); + + if (bWasLinked) + { + pDocSh->UpdateLinks(); // update Link-Manager + GetViewData().GetBindings().Invalidate(SID_LINKS); + } + + pDocSh->PostPaintExtras(); + pDocSh->SetDocumentModified(); + + SfxApplication* pSfxApp = SfxGetpApp(); // Navigator + pSfxApp->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); + pSfxApp->Broadcast( SfxHint( SfxHintId::ScAreasChanged ) ); + pSfxApp->Broadcast( SfxHint( SfxHintId::ScDbAreasChanged ) ); + pSfxApp->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) ); + } + return bDelDone; +} + +bool ScViewFunc::RenameTable( const OUString& rName, SCTAB nTab ) +{ + // order Table/Name is inverted for DocFunc + bool bSuccess = GetViewData().GetDocShell()->GetDocFunc(). + RenameTable( nTab, rName, true, false ); + if (bSuccess) + { + // the table name might be part of a formula + GetViewData().GetViewShell()->UpdateInputHandler(); + } + return bSuccess; +} + +bool ScViewFunc::SetTabBgColor( const Color& rColor, SCTAB nTab ) +{ + bool bSuccess = GetViewData().GetDocShell()->GetDocFunc().SetTabBgColor( nTab, rColor, true, false ); + if (bSuccess) + { + GetViewData().GetViewShell()->UpdateInputHandler(); + } + return bSuccess; +} + +bool ScViewFunc::SetTabBgColor( ScUndoTabColorInfo::List& rUndoSetTabBgColorInfoList ) +{ + bool bSuccess = GetViewData().GetDocShell()->GetDocFunc().SetTabBgColor( rUndoSetTabBgColorInfoList, false ); + if (bSuccess) + { + GetViewData().GetViewShell()->UpdateInputHandler(); + } + return bSuccess; +} + +void ScViewFunc::InsertAreaLink( const OUString& rFile, + const OUString& rFilter, const OUString& rOptions, + const OUString& rSource ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + SCCOL nPosX = GetViewData().GetCurX(); + SCROW nPosY = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + ScAddress aPos( nPosX, nPosY, nTab ); + + pDocSh->GetDocFunc().InsertAreaLink( rFile, rFilter, rOptions, rSource, aPos, 0/*nRefresh*/, false, false ); +} + +void ScViewFunc::InsertTableLink( const OUString& rFile, + const OUString& rFilter, const OUString& rOptions, + std::u16string_view rTabName ) +{ + OUString aFilterName = rFilter; + OUString aOpt = rOptions; + ScDocumentLoader aLoader( rFile, aFilterName, aOpt ); + if (aLoader.IsError()) + return; + + ScDocShell* pSrcSh = aLoader.GetDocShell(); + ScDocument& rSrcDoc = pSrcSh->GetDocument(); + SCTAB nTab = MAXTAB+1; + if (rTabName.empty()) // no name given -> first table + nTab = 0; + else + { + OUString aTemp; + SCTAB nCount = rSrcDoc.GetTableCount(); + for (SCTAB i=0; i<nCount; i++) + { + rSrcDoc.GetName( i, aTemp ); + if ( aTemp == rTabName ) + nTab = i; + } + } + + if ( nTab <= MAXTAB ) + ImportTables( pSrcSh, 1, &nTab, true, + GetViewData().GetTabNo() ); +} + +// Copy/link tables from another document + +void ScViewFunc::ImportTables( ScDocShell* pSrcShell, + SCTAB nCount, const SCTAB* pSrcTabs, bool bLink,SCTAB nTab ) +{ + ScDocument& rSrcDoc = pSrcShell->GetDocument(); + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + bool bUndo(rDoc.IsUndoEnabled()); + + bool bError = false; + bool bRefs = false; + bool bName = false; + + if (rSrcDoc.GetDrawLayer()) + pDocSh->MakeDrawLayer(); + + if (bUndo) + rDoc.BeginDrawUndo(); // drawing layer must do its own undo actions + + SCTAB nInsCount = 0; + SCTAB i; + for( i=0; i<nCount; i++ ) + { // insert sheets first and update all references + OUString aName; + rSrcDoc.GetName( pSrcTabs[i], aName ); + rDoc.CreateValidTabName( aName ); + if ( !rDoc.InsertTab( nTab+i, aName ) ) + { + bError = true; // total error + break; // for + } + ++nInsCount; + } + for (i=0; i<nCount && !bError; i++) + { + SCTAB nSrcTab = pSrcTabs[i]; + SCTAB nDestTab1=nTab+i; + sal_uLong nErrVal = pDocSh->TransferTab( *pSrcShell, nSrcTab, nDestTab1, + false, false ); // no insert + + switch (nErrVal) + { + case 0: // internal error or full of errors + bError = true; + break; + case 2: + bRefs = true; + break; + case 3: + bName = true; + break; + case 4: + bRefs = bName = true; + break; + } + + } + + if (bLink) + { + sfx2::LinkManager* pLinkManager = rDoc.GetLinkManager(); + + SfxMedium* pMed = pSrcShell->GetMedium(); + OUString aFileName = pMed->GetName(); + OUString aFilterName; + if (pMed->GetFilter()) + aFilterName = pMed->GetFilter()->GetFilterName(); + OUString aOptions = ScDocumentLoader::GetOptions(*pMed); + + bool bWasThere = rDoc.HasLink( aFileName, aFilterName, aOptions ); + + sal_uLong nRefresh = 0; + OUString aTabStr; + for (i=0; i<nInsCount; i++) + { + rSrcDoc.GetName( pSrcTabs[i], aTabStr ); + rDoc.SetLink( nTab+i, ScLinkMode::NORMAL, + aFileName, aFilterName, aOptions, aTabStr, nRefresh ); + } + + if (!bWasThere) // Insert link only once per source document + { + ScTableLink* pLink = new ScTableLink( pDocSh, aFileName, aFilterName, aOptions, nRefresh ); + pLink->SetInCreate( true ); + pLinkManager->InsertFileLink( *pLink, sfx2::SvBaseLinkObjectType::ClientFile, aFileName, &aFilterName ); + pLink->Update(); + pLink->SetInCreate( false ); + + SfxBindings& rBindings = GetViewData().GetBindings(); + rBindings.Invalidate( SID_LINKS ); + } + } + + if (bUndo) + { + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoImportTab>( pDocSh, nTab, nCount ) ); + } + + for (i=0; i<nInsCount; i++) + GetViewData().InsertTab(nTab); + SetTabNo(nTab,true); + pDocSh->PostPaint( 0,0,0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB, + PaintPartFlags::Grid | PaintPartFlags::Top | PaintPartFlags::Left | PaintPartFlags::Extras ); + + SfxApplication* pSfxApp = SfxGetpApp(); + pSfxApp->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); + pSfxApp->Broadcast( SfxHint( SfxHintId::ScAreasChanged ) ); + + pDocSh->PostPaintExtras(); + pDocSh->PostPaintGridAll(); + pDocSh->SetDocumentModified(); + + if (bRefs) + ErrorMessage(STR_ABSREFLOST); + if (bName) + ErrorMessage(STR_NAMECONFLICT); +} + +// Move/Copy table to another document + +void ScViewFunc::MoveTable( + sal_uInt16 nDestDocNo, SCTAB nDestTab, bool bCopy, const OUString* pNewTabName ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + ScDocShell* pDocShell = GetViewData().GetDocShell(); + ScDocShell* pDestShell = nullptr; + ScTabViewShell* pDestViewSh = nullptr; + bool bUndo (rDoc.IsUndoEnabled()); + bool bRename = pNewTabName && !pNewTabName->isEmpty(); + + bool bNewDoc = (nDestDocNo == SC_DOC_NEW); + if ( bNewDoc ) + { + nDestTab = 0; // firstly insert + + // execute without SfxCallMode::RECORD, because already contained in move command + + SfxStringItem aItem( SID_FILE_NAME, "private:factory/" + STRING_SCAPP ); + SfxStringItem aTarget( SID_TARGETNAME, "_blank" ); + + const SfxPoolItemHolder aResult(GetViewData().GetDispatcher().ExecuteList( + SID_OPENDOC, SfxCallMode::API|SfxCallMode::SYNCHRON, + { &aItem, &aTarget })); + if (nullptr != aResult.getItem()) + { + if ( auto pObjectItem = dynamic_cast<const SfxObjectItem*>(aResult.getItem()) ) + pDestShell = dynamic_cast<ScDocShell*>( pObjectItem->GetShell() ); + else if ( auto pViewFrameItem = dynamic_cast<const SfxViewFrameItem*>(aResult.getItem())) + { + SfxViewFrame* pFrm = pViewFrameItem->GetFrame(); + if (pFrm) + pDestShell = dynamic_cast<ScDocShell*>( pFrm->GetObjectShell() ); + } + if (pDestShell) + pDestViewSh = pDestShell->GetBestViewShell(); + } + } + else + pDestShell = ScDocShell::GetShellByNum( nDestDocNo ); + + if (!pDestShell) + { + OSL_FAIL("Destination document not found !!!"); + return; + } + + ScMarkData& rMark = GetViewData().GetMarkData(); + if (bRename && rMark.GetSelectCount() != 1) + { + // Custom sheet name is provided, but more than one sheet is selected. + // We don't support this scenario at the moment. + return; + } + + ScDocument& rDestDoc = pDestShell->GetDocument(); + + if (&rDestDoc != &rDoc) + { + if (bNewDoc) + { + while (rDestDoc.GetTableCount() > 1) + rDestDoc.DeleteTab(0); + rDestDoc.RenameTab( 0, "______42_____" ); + } + + SCTAB nTabCount = rDoc.GetTableCount(); + SCTAB nTabSelCount = rMark.GetSelectCount(); + + vector<SCTAB> TheTabs; + + for(SCTAB i=0; i<nTabCount; ++i) + { + if(rMark.GetTableSelect(i)) + { + OUString aTabName; + rDoc.GetName( i, aTabName); + TheTabs.push_back(i); + for(SCTAB j=i+1;j<nTabCount;j++) + { + if((!rDoc.IsVisible(j)) && rDoc.IsScenario(j)) + { + rDoc.GetName( j, aTabName); + TheTabs.push_back(j); + i=j; + } + else break; + } + } + } + + GetFrameWin()->EnterWait(); + + if (rDoc.GetDrawLayer()) + pDestShell->MakeDrawLayer(); + + if (!bNewDoc && bUndo) + rDestDoc.BeginDrawUndo(); // drawing layer must do its own undo actions + + sal_uLong nErrVal =1; + if(nDestTab==SC_TAB_APPEND) + nDestTab=rDestDoc.GetTableCount(); + SCTAB nDestTab1=nDestTab; + ScClipParam aParam; + for( size_t j=0; j<TheTabs.size(); ++j, ++nDestTab1 ) + { // insert sheets first and update all references + OUString aName; + if (bRename) + aName = *pNewTabName; + else + rDoc.GetName( TheTabs[j], aName ); + + rDestDoc.CreateValidTabName( aName ); + if ( !rDestDoc.InsertTab( nDestTab1, aName ) ) + { + nErrVal = 0; // total error + break; // for + } + ScRange aRange( 0, 0, TheTabs[j], rDoc.MaxCol(), rDoc.MaxRow(), TheTabs[j] ); + aParam.maRanges.push_back(aRange); + } + rDoc.SetClipParam(aParam); + if ( nErrVal > 0 ) + { + nDestTab1 = nDestTab; + for(SCTAB nTab : TheTabs) + { + nErrVal = pDestShell->TransferTab( *pDocShell, nTab, nDestTab1, false, false ); + nDestTab1++; + } + } + if (!bNewDoc && bUndo) + { + OUString sName; + rDestDoc.GetName(nDestTab, sName); + pDestShell->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoImportTab>( pDestShell, nDestTab, + static_cast<SCTAB>(TheTabs.size()))); + + } + else + { + pDestShell->GetUndoManager()->Clear(); + } + + GetFrameWin()->LeaveWait(); + switch (nErrVal) + { + case 0: // internal error or full of errors + { + ErrorMessage(STR_TABINSERT_ERROR); + return; + } + case 2: + ErrorMessage(STR_ABSREFLOST); + break; + case 3: + ErrorMessage(STR_NAMECONFLICT); + break; + case 4: + { + ErrorMessage(STR_ABSREFLOST); + ErrorMessage(STR_NAMECONFLICT); + } + break; + default: + break; + } + + if (!bCopy) + { + if(nTabCount!=nTabSelCount) + DeleteTables(TheTabs); // incl. Paint & Undo + else + ErrorMessage(STR_TABREMOVE_ERROR); + } + + if (bNewDoc) + { + // ChartListenerCollection must be updated before DeleteTab + if ( rDestDoc.IsChartListenerCollectionNeedsUpdate() ) + rDestDoc.UpdateChartListenerCollection(); + + SCTAB nNumTabsInserted = static_cast<SCTAB>(TheTabs.size()); + pDestShell->Broadcast( ScTablesHint( SC_TABS_INSERTED, 0, nNumTabsInserted ) ); + + rDestDoc.DeleteTab( nNumTabsInserted ); // old first table + pDestShell->Broadcast( ScTablesHint( SC_TAB_DELETED, nNumTabsInserted ) ); + + if (pDestViewSh) + { + // Make sure to clear the cached page view after sheet + // deletion, which still points to the sdr page belonging to + // the deleted sheet. + SdrView* pSdrView = pDestViewSh->GetScDrawView(); + if (pSdrView) + pSdrView->ClearPageView(); + + pDestViewSh->TabChanged(); // pages on the drawing layer + } + pDestShell->PostPaint( 0,0,0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB, + PaintPartFlags::Grid | PaintPartFlags::Top | PaintPartFlags::Left | + PaintPartFlags::Extras | PaintPartFlags::Size ); + // PaintPartFlags::Size for outline + } + else + { + pDestShell->Broadcast( ScTablesHint( SC_TAB_INSERTED, nDestTab ) ); + pDestShell->PostPaintExtras(); + pDestShell->PostPaintGridAll(); + } + + TheTabs.clear(); + + pDestShell->SetDocumentModified(); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); + } + else + { + // Move or copy within the same document. + SCTAB nTabCount = rDoc.GetTableCount(); + + unique_ptr< vector<SCTAB> > pSrcTabs(new vector<SCTAB>); + unique_ptr< vector<SCTAB> > pDestTabs(new vector<SCTAB>); + unique_ptr< vector<OUString> > pTabNames(new vector<OUString>); + unique_ptr< vector<OUString> > pDestNames; + pSrcTabs->reserve(nTabCount); + pDestTabs->reserve(nTabCount); + pTabNames->reserve(nTabCount); + OUString aDestName; + + for(SCTAB i=0;i<nTabCount;i++) + { + if(rMark.GetTableSelect(i)) + { + OUString aTabName; + rDoc.GetName( i, aTabName); + pTabNames->push_back(aTabName); + + for(SCTAB j=i+1;j<nTabCount;j++) + { + if((!rDoc.IsVisible(j)) && rDoc.IsScenario(j)) + { + rDoc.GetName( j, aTabName); + pTabNames->push_back(aTabName); + i=j; + } + else break; + } + } + } + + if (bCopy && bUndo) + rDoc.BeginDrawUndo(); // drawing layer must do its own undo actions + + rDoc.GetName( nDestTab, aDestName); + SCTAB nDestTab1=nDestTab; + SCTAB nMovTab=0; + for (size_t j = 0, n = pTabNames->size(); j < n; ++j) + { + nTabCount = rDoc.GetTableCount(); + const OUString& rStr = (*pTabNames)[j]; + if(!rDoc.GetTable(rStr,nMovTab)) + { + nMovTab=nTabCount; + } + if(!rDoc.GetTable(aDestName,nDestTab1)) + { + nDestTab1=nTabCount; + } + pDocShell->MoveTable( nMovTab, nDestTab1, bCopy, false ); // Undo is here + + // tdf#43175 - Adjust chart references on every copied sheet + if (bCopy) + { + // New position of source table after moving + SCTAB nSrcTab = (nDestTab1 <= nMovTab) ? nMovTab + 1 : nMovTab; + //#i29848# adjust references to data on the copied sheet + ScChartHelper::AdjustRangesOfChartsOnDestinationPage(rDoc, rDestDoc, nSrcTab, + nDestTab1); + } + + if(bCopy && rDoc.IsScenario(nMovTab)) + { + OUString aComment; + Color aColor; + ScScenarioFlags nFlags; + + rDoc.GetScenarioData(nMovTab, aComment,aColor, nFlags); + rDoc.SetScenario(nDestTab1,true); + rDoc.SetScenarioData(nDestTab1,aComment,aColor,nFlags); + bool bActive = rDoc.IsActiveScenario(nMovTab ); + rDoc.SetActiveScenario( nDestTab1, bActive ); + bool bVisible=rDoc.IsVisible(nMovTab); + rDoc.SetVisible(nDestTab1,bVisible ); + } + + pSrcTabs->push_back(nMovTab); + + if(!bCopy) + { + if(!rDoc.GetTable(rStr,nDestTab1)) + { + nDestTab1=nTabCount; + } + } + + pDestTabs->push_back(nDestTab1); + } + + // Rename must be done after all sheets have been moved. + if (bRename) + { + pDestNames.reset(new vector<OUString>); + size_t n = pDestTabs->size(); + pDestNames->reserve(n); + for (size_t j = 0; j < n; ++j) + { + SCTAB nRenameTab = (*pDestTabs)[j]; + OUString aTabName = *pNewTabName; + rDoc.CreateValidTabName( aTabName ); + pDestNames->push_back(aTabName); + rDoc.RenameTab(nRenameTab, aTabName); + } + } + else + // No need to keep this around when we are not renaming. + pTabNames.reset(); + + SCTAB nTab = GetViewData().GetTabNo(); + + if (bUndo) + { + if (bCopy) + { + pDocShell->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoCopyTab>( + pDocShell, std::move(pSrcTabs), std::move(pDestTabs), std::move(pDestNames))); + } + else + { + pDocShell->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoMoveTab>( + pDocShell, std::move(pSrcTabs), std::move(pDestTabs), std::move(pTabNames), std::move(pDestNames))); + } + } + + SCTAB nNewTab = nDestTab; + if (nNewTab == SC_TAB_APPEND) + nNewTab = rDoc.GetTableCount()-1; + else if (!bCopy && nTab<nDestTab) + nNewTab--; + + SetTabNo( nNewTab, true ); + } +} + +void ScViewFunc::ShowTable( const std::vector<OUString>& rNames ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + bool bUndo(rDoc.IsUndoEnabled()); + + std::vector<SCTAB> undoTabs; + SCTAB nPos = 0; + + bool bFound(false); + + for (const OUString& aName : rNames) + { + if (rDoc.GetTable(aName, nPos)) + { + rDoc.SetVisible( nPos, true ); + SetTabNo( nPos, true ); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); + if (!bFound) + bFound = true; + if (bUndo) + undoTabs.push_back(nPos); + } + } + if (bFound) + { + if (bUndo) + { + pDocSh->GetUndoManager()->AddUndoAction( std::make_unique<ScUndoShowHideTab>( pDocSh, std::move(undoTabs), true ) ); + } + pDocSh->PostPaint(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB, PaintPartFlags::Extras); + pDocSh->SetDocumentModified(); + } +} + +void ScViewFunc::HideTable( const ScMarkData& rMark, SCTAB nTabToSelect ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + bool bUndo(rDoc.IsUndoEnabled()); + SCTAB nVisible = 0; + SCTAB nTabCount = rDoc.GetTableCount(); + + SCTAB nTabSelCount = rMark.GetSelectCount(); + + // check to make sure we won't hide all sheets. we need at least one visible at all times. + for ( SCTAB i=0; i < nTabCount && nVisible <= nTabSelCount ; i++ ) + if (rDoc.IsVisible(i)) + ++nVisible; + + if (nVisible <= nTabSelCount) + return; + + std::vector<SCTAB> undoTabs; + + // need to take a copy of selectedtabs since it is modified in the loop + const ScMarkData::MarkedTabsType selectedTabs = rMark.GetSelectedTabs(); + for (const SCTAB& nTab : selectedTabs) + { + if (rDoc.IsVisible( nTab )) + { + rDoc.SetVisible( nTab, false ); + // Update views + pDocSh->Broadcast( ScTablesHint( SC_TAB_HIDDEN, nTab ) ); + SetTabNo( nTab, true ); + // Store for undo + if (bUndo) + undoTabs.push_back(nTab); + } + } + + if (nTabToSelect != -1) + SetTabNo(nTabToSelect); + + if (bUndo) + { + pDocSh->GetUndoManager()->AddUndoAction( std::make_unique<ScUndoShowHideTab>( pDocSh, std::move(undoTabs), false ) ); + } + + // Update views + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScTablesChanged ) ); + pDocSh->PostPaint(0,0,0,rDoc.MaxCol(),rDoc.MaxRow(),MAXTAB, PaintPartFlags::Extras); + pDocSh->SetDocumentModified(); +} + +void ScViewFunc::InsertSpecialChar( const OUString& rStr, const vcl::Font& rFont ) +{ + ScEditableTester aTester( this ); + if (!aTester.IsEditable()) + { + ErrorMessage(aTester.GetMessageId()); + return; + } + + const sal_Unicode* pChar = rStr.getStr(); + ScTabViewShell* pViewShell = GetViewData().GetViewShell(); + SvxFontItem aFontItem( rFont.GetFamilyType(), + rFont.GetFamilyName(), + rFont.GetStyleName(), + rFont.GetPitch(), + rFont.GetCharSet(), + ATTR_FONT ); + + // if string contains WEAK characters, set all fonts + SvtScriptType nScript; + ScDocument& rDoc = GetViewData().GetDocument(); + if ( rDoc.HasStringWeakCharacters( rStr ) ) + nScript = SvtScriptType::LATIN | SvtScriptType::ASIAN | SvtScriptType::COMPLEX; + else + nScript = rDoc.GetStringScriptType( rStr ); + + SvxScriptSetItem aSetItem( SID_ATTR_CHAR_FONT, pViewShell->GetPool() ); + aSetItem.PutItemForScriptType( nScript, aFontItem ); + ApplyUserItemSet( aSetItem.GetItemSet() ); + + while ( *pChar ) + pViewShell->TabKeyInput( KeyEvent( *(pChar++), vcl::KeyCode() ) ); +} + +void ScViewFunc::UpdateLineAttrs( SvxBorderLine& rLine, + const SvxBorderLine* pDestLine, + const SvxBorderLine* pSrcLine, + bool bColor ) +{ + if ( !(pSrcLine && pDestLine) ) + return; + + if ( bColor ) + { + rLine.SetColor ( pSrcLine->GetColor() ); + rLine.SetBorderLineStyle(pDestLine->GetBorderLineStyle()); + rLine.SetWidth ( pDestLine->GetWidth() ); + } + else + { + rLine.SetColor ( pDestLine->GetColor() ); + rLine.SetBorderLineStyle(pSrcLine->GetBorderLineStyle()); + rLine.SetWidth ( pSrcLine->GetWidth() ); + } +} + +#define SET_LINE_ATTRIBUTES(LINE,BOXLINE) \ + pBoxLine = aBoxItem.Get##LINE(); \ + if ( pBoxLine ) \ + { \ + if ( pLine ) \ + { \ + UpdateLineAttrs( aLine, pBoxLine, pLine, bColorOnly ); \ + aBoxItem.SetLine( &aLine, BOXLINE ); \ + } \ + else \ + aBoxItem.SetLine( nullptr, BOXLINE ); \ + } + +void ScViewFunc::SetSelectionFrameLines( const SvxBorderLine* pLine, + bool bColorOnly ) +{ + // Not editable only due to a matrix? Attribute is ok anyhow. + bool bOnlyNotBecauseOfMatrix; + if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + ScDocument& rDoc = GetViewData().GetDocument(); + ScMarkData aFuncMark( GetViewData().GetMarkData() ); // local copy for UnmarkFiltered + ScViewUtil::UnmarkFiltered( aFuncMark, rDoc ); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + const ScPatternAttr* pSelAttrs = GetSelectionPattern(); + const SfxItemSet& rSelItemSet = pSelAttrs->GetItemSet(); + + const SfxPoolItem* pBorderAttr = nullptr; + SfxItemState eItemState = rSelItemSet.GetItemState( ATTR_BORDER, true, &pBorderAttr ); + + const SfxPoolItem* pTLBRItem = nullptr; + SfxItemState eTLBRState = rSelItemSet.GetItemState( ATTR_BORDER_TLBR, true, &pTLBRItem ); + + const SfxPoolItem* pBLTRItem = nullptr; + SfxItemState eBLTRState = rSelItemSet.GetItemState( ATTR_BORDER_BLTR, true, &pBLTRItem ); + + // any of the lines visible? + if( !((eItemState != SfxItemState::DEFAULT) || (eTLBRState != SfxItemState::DEFAULT) || (eBLTRState != SfxItemState::DEFAULT)) ) + return; + + // none of the lines don't care? + if( (eItemState != SfxItemState::DONTCARE) && (eTLBRState != SfxItemState::DONTCARE) && (eBLTRState != SfxItemState::DONTCARE) ) + { + SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END> aOldSet( *rDoc.GetPool() ); + SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END> aNewSet( *rDoc.GetPool() ); + + SvxBorderLine aLine; + + if( pBorderAttr ) + { + const SvxBorderLine* pBoxLine = nullptr; + SvxBoxItem aBoxItem( *static_cast<const SvxBoxItem*>(pBorderAttr) ); + SvxBoxInfoItem aBoxInfoItem( ATTR_BORDER_INNER ); + + // here pBoxLine is used + SET_LINE_ATTRIBUTES(Top,SvxBoxItemLine::TOP) + SET_LINE_ATTRIBUTES(Bottom,SvxBoxItemLine::BOTTOM) + SET_LINE_ATTRIBUTES(Left,SvxBoxItemLine::LEFT) + SET_LINE_ATTRIBUTES(Right,SvxBoxItemLine::RIGHT) + + aBoxInfoItem.SetLine( aBoxItem.GetTop(), SvxBoxInfoItemLine::HORI ); + aBoxInfoItem.SetLine( aBoxItem.GetLeft(), SvxBoxInfoItemLine::VERT ); + aBoxInfoItem.ResetFlags(); // set Lines to Valid + + aOldSet.Put( *pBorderAttr ); + aNewSet.Put( aBoxItem ); + aNewSet.Put( aBoxInfoItem ); + } + + if( pTLBRItem && static_cast<const SvxLineItem*>(pTLBRItem)->GetLine() ) + { + SvxLineItem aTLBRItem( *static_cast<const SvxLineItem*>(pTLBRItem) ); + UpdateLineAttrs( aLine, aTLBRItem.GetLine(), pLine, bColorOnly ); + aTLBRItem.SetLine( &aLine ); + aOldSet.Put( *pTLBRItem ); + aNewSet.Put( aTLBRItem ); + } + + if( pBLTRItem && static_cast<const SvxLineItem*>(pBLTRItem)->GetLine() ) + { + SvxLineItem aBLTRItem( *static_cast<const SvxLineItem*>(pBLTRItem) ); + UpdateLineAttrs( aLine, aBLTRItem.GetLine(), pLine, bColorOnly ); + aBLTRItem.SetLine( &aLine ); + aOldSet.Put( *pBLTRItem ); + aNewSet.Put( aBLTRItem ); + } + + ApplyAttributes( aNewSet, aOldSet ); + } + else // if ( eItemState == SfxItemState::DONTCARE ) + { + aFuncMark.MarkToMulti(); + rDoc.ApplySelectionLineStyle( aFuncMark, pLine, bColorOnly ); + } + + const ScRange& aMarkRange = aFuncMark.GetMultiMarkArea(); + SCCOL nStartCol = aMarkRange.aStart.Col(); + SCROW nStartRow = aMarkRange.aStart.Row(); + SCTAB nStartTab = aMarkRange.aStart.Tab(); + SCCOL nEndCol = aMarkRange.aEnd.Col(); + SCROW nEndRow = aMarkRange.aEnd.Row(); + SCTAB nEndTab = aMarkRange.aEnd.Tab(); + pDocSh->PostPaint( nStartCol, nStartRow, nStartTab, + nEndCol, nEndRow, nEndTab, + PaintPartFlags::Grid, SC_PF_LINES | SC_PF_TESTMERGE ); + + pDocSh->UpdateOle(GetViewData()); + pDocSh->SetDocumentModified(); +} + +#undef SET_LINE_ATTRIBUTES + +void ScViewFunc::SetValidation( const ScValidationData& rNew ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + sal_uInt32 nIndex = rDoc.AddValidationEntry(rNew); // for it there is no Undo + SfxUInt32Item aItem( ATTR_VALIDDATA, nIndex ); + + ApplyAttr( aItem ); // with Paint and Undo... +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/viewfun3.cxx b/sc/source/ui/view/viewfun3.cxx new file mode 100644 index 0000000000..7a6403237b --- /dev/null +++ b/sc/source/ui/view/viewfun3.cxx @@ -0,0 +1,2028 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <scitems.hxx> +#include <svx/svdpage.hxx> +#include <sfx2/docfile.hxx> +#include <comphelper/classids.hxx> +#include <comphelper/lok.hxx> +#include <sot/formats.hxx> +#include <sot/storage.hxx> +#include <vcl/graph.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <tools/urlobj.hxx> +#include <sot/exchange.hxx> +#include <memory> +#include <vcl/uitest/logger.hxx> +#include <vcl/uitest/eventdescription.hxx> +#include <vcl/TypeSerializer.hxx> +#include <osl/diagnose.h> + +#include <attrib.hxx> +#include <patattr.hxx> +#include <dociter.hxx> +#include <viewfunc.hxx> +#include <tabvwsh.hxx> +#include <docsh.hxx> +#include <docfunc.hxx> +#include <undoblk.hxx> +#include <refundo.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <global.hxx> +#include <transobj.hxx> +#include <drwtrans.hxx> +#include <chgtrack.hxx> +#include <waitoff.hxx> +#include <scmod.hxx> +#include <inputopt.hxx> +#include <warnbox.hxx> +#include <drwlayer.hxx> +#include <editable.hxx> +#include <docuno.hxx> +#include <clipparam.hxx> +#include <undodat.hxx> +#include <drawview.hxx> +#include <cliputil.hxx> +#include <clipoptions.hxx> +#include <gridwin.hxx> +#include <com/sun/star/util/XCloneable.hpp> + +using namespace com::sun::star; + +namespace { + +void collectUIInformation(std::map<OUString, OUString>&& aParameters, const OUString& action) +{ + EventDescription aDescription; + aDescription.aID = "grid_window"; + aDescription.aAction = action; + aDescription.aParameters = std::move(aParameters); + aDescription.aParent = "MainWindow"; + aDescription.aKeyWord = "ScGridWinUIObject"; + + UITestLogger::getInstance().logEvent(aDescription); +} + +} + +// GlobalName of writer-DocShell from comphelper/classids.hxx + +// C U T + +void ScViewFunc::CutToClip() +{ + UpdateInputLine(); + + ScEditableTester aTester( this ); + if (!aTester.IsEditable()) // selection editable? + { + ErrorMessage( aTester.GetMessageId() ); + return; + } + + ScRange aRange; // delete this range + if ( GetViewData().GetSimpleArea( aRange ) == SC_MARK_SIMPLE ) + { + ScDocument& rDoc = GetViewData().GetDocument(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScMarkData& rMark = GetViewData().GetMarkData(); + const bool bRecord(rDoc.IsUndoEnabled()); // Undo/Redo + + ScDocShellModificator aModificator( *pDocSh ); + + if ( !rMark.IsMarked() && !rMark.IsMultiMarked() ) // mark the range if not marked yet + { + DoneBlockMode(); + InitOwnBlockMode( aRange ); + rMark.SetMarkArea( aRange ); + MarkDataChanged(); + } + + CopyToClip( nullptr, true, false, true/*bIncludeObjects*/ ); // copy to clipboard + + ScAddress aOldEnd( aRange.aEnd ); // combined cells in this range? + rDoc.ExtendMerge( aRange, true ); + + ScDocumentUniquePtr pUndoDoc; + if ( bRecord ) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndoSelected( rDoc, rMark ); + // all sheets - CopyToDocument skips those that don't exist in pUndoDoc + ScRange aCopyRange = aRange; + aCopyRange.aStart.SetTab(0); + aCopyRange.aEnd.SetTab(rDoc.GetTableCount()-1); + rDoc.CopyToDocument( aCopyRange, (InsertDeleteFlags::ALL & ~InsertDeleteFlags::OBJECTS) | InsertDeleteFlags::NOCAPTIONS, false, *pUndoDoc ); + rDoc.BeginDrawUndo(); + } + + sal_uInt16 nExtFlags = 0; + pDocSh->UpdatePaintExt( nExtFlags, aRange ); + + rMark.MarkToMulti(); + rDoc.DeleteSelection( InsertDeleteFlags::ALL, rMark ); + rDoc.DeleteObjectsInSelection( rMark ); + rMark.MarkToSimple(); + + if ( !AdjustRowHeight( aRange.aStart.Row(), aRange.aEnd.Row(), true ) ) + pDocSh->PostPaint( aRange, PaintPartFlags::Grid, nExtFlags ); + + if ( bRecord ) // Draw-Undo now available + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoCut>( pDocSh, aRange, aOldEnd, rMark, std::move(pUndoDoc) ) ); + + aModificator.SetDocumentModified(); + pDocSh->UpdateOle(GetViewData()); + + CellContentChanged(); + + OUString aStartAddress = aRange.aStart.GetColRowString(); + OUString aEndAddress = aRange.aEnd.GetColRowString(); + + collectUIInformation({{"RANGE", aStartAddress + ":" + aEndAddress}}, "CUT"); + } + else + ErrorMessage( STR_NOMULTISELECT ); +} + +// C O P Y + +bool ScViewFunc::CopyToClip( ScDocument* pClipDoc, bool bCut, bool bApi, bool bIncludeObjects, bool bStopEdit ) +{ + ScRange aRange; + ScMarkType eMarkType = GetViewData().GetSimpleArea( aRange ); + ScMarkData& rMark = GetViewData().GetMarkData(); + bool bDone = false; + + if ( eMarkType == SC_MARK_SIMPLE || eMarkType == SC_MARK_SIMPLE_FILTERED ) + { + ScRangeList aRangeList( aRange ); + bDone = CopyToClip( pClipDoc, aRangeList, bCut, bApi, bIncludeObjects, bStopEdit ); + } + else if (eMarkType == SC_MARK_MULTI) + { + ScRangeList aRangeList; + rMark.MarkToSimple(); + rMark.FillRangeListWithMarks(&aRangeList, false); + bDone = CopyToClip( pClipDoc, aRangeList, bCut, bApi, bIncludeObjects, bStopEdit ); + } + else + { + if (!bApi) + ErrorMessage(STR_NOMULTISELECT); + } + if( !bCut ){ + OUString aStartAddress = aRange.aStart.GetColRowString(); + OUString aEndAddress = aRange.aEnd.GetColRowString(); + collectUIInformation({{"RANGE", aStartAddress + ":" + aEndAddress}}, "COPY"); + } + return bDone; +} + +// Copy the content of the Range into clipboard. +bool ScViewFunc::CopyToClip( ScDocument* pClipDoc, const ScRangeList& rRanges, bool bCut, bool bApi, bool bIncludeObjects, bool bStopEdit ) +{ + if ( rRanges.empty() ) + return false; + if ( bStopEdit ) + UpdateInputLine(); + + bool bDone; + if (rRanges.size() > 1) // isMultiRange + bDone = CopyToClipMultiRange(pClipDoc, rRanges, bCut, bApi, bIncludeObjects); + else + bDone = CopyToClipSingleRange(pClipDoc, rRanges, bCut, bIncludeObjects); + + return bDone; +} + +bool ScViewFunc::CopyToClipSingleRange( ScDocument* pClipDoc, const ScRangeList& rRanges, bool bCut, bool bIncludeObjects ) +{ + ScRange aRange = rRanges[0]; + ScClipParam aClipParam( aRange, bCut ); + aClipParam.maRanges = rRanges; + ScDocument& rDoc = GetViewData().GetDocument(); + ScMarkData& rMark = GetViewData().GetMarkData(); + + if (rDoc.HasSelectedBlockMatrixFragment( aRange.aStart.Col(), aRange.aStart.Row(), aRange.aEnd.Col(), aRange.aEnd.Row(), rMark ) ) + return false; + + std::shared_ptr<ScDocument> pSysClipDoc; + if ( !pClipDoc ) // no clip doc specified + { + // Create one (deleted by ScTransferObj), and copy into system. + pSysClipDoc = std::make_shared<ScDocument>( SCDOCMODE_CLIP ); + pClipDoc = pSysClipDoc.get(); + } + if ( !bCut ) + { + ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack(); + if ( pChangeTrack ) + pChangeTrack->ResetLastCut(); + } + + if ( pSysClipDoc && bIncludeObjects ) + { + bool bAnyOle = rDoc.HasOLEObjectsInArea( aRange ); + // Update ScGlobal::xDrawClipDocShellRef. + ScDrawLayer::SetGlobalDrawPersist( ScTransferObj::SetDrawClipDoc( bAnyOle, pSysClipDoc ) ); + } + + // is this necessary?, will setting the doc id upset the + // following paste operation with range? would be nicer to just set this always + // and lose the 'if' above + aClipParam.setSourceDocID( rDoc.GetDocumentID() ); + + if (ScDocShell* pObjectShell = rDoc.GetDocumentShell()) + { + // Copy document properties from pObjectShell to pClipDoc (to its clip options, as it has no object shell). + uno::Reference<util::XCloneable> xCloneable(pObjectShell->getDocProperties(), uno::UNO_QUERY_THROW); + std::unique_ptr<ScClipOptions> pOptions(new ScClipOptions); + pOptions->m_xDocumentProperties.set(xCloneable->createClone(), uno::UNO_QUERY); + pClipDoc->SetClipOptions(std::move(pOptions)); + } + + rDoc.CopyToClip( aClipParam, pClipDoc, &rMark, false, bIncludeObjects ); + if (ScDrawLayer* pDrawLayer = pClipDoc->GetDrawLayer()) + { + ScClipParam& rClipDocClipParam = pClipDoc->GetClipParam(); + ScRangeListVector& rRangesVector = rClipDocClipParam.maProtectedChartRangesVector; + SCTAB nTabCount = pClipDoc->GetTableCount(); + for ( SCTAB nTab = 0; nTab < nTabCount; ++nTab ) + { + SdrPage* pPage = pDrawLayer->GetPage( static_cast< sal_uInt16 >( nTab ) ); + if ( pPage ) + { + ScChartHelper::FillProtectedChartRangesVector( rRangesVector, rDoc, pPage ); + } + } + } + + if ( pSysClipDoc ) + { + ScDrawLayer::SetGlobalDrawPersist(nullptr); + ScGlobal::SetClipDocName( rDoc.GetDocumentShell()->GetTitle( SFX_TITLE_FULLNAME ) ); + } + pClipDoc->ExtendMerge( aRange, true ); + + if ( pSysClipDoc ) + { + ScDocShell* pDocSh = GetViewData().GetDocShell(); + TransferableObjectDescriptor aObjDesc; + pDocSh->FillTransferableObjectDescriptor( aObjDesc ); + aObjDesc.maDisplayName = pDocSh->GetMedium()->GetURLObject().GetURLNoPass(); + // maSize is set in ScTransferObj ctor + + rtl::Reference<ScTransferObj> pTransferObj(new ScTransferObj( pSysClipDoc, std::move(aObjDesc) )); + if ( ScGlobal::xDrawClipDocShellRef.is() ) + { + SfxObjectShellRef aPersistRef( ScGlobal::xDrawClipDocShellRef.get() ); + pTransferObj->SetDrawPersist( aPersistRef );// keep persist for ole objects alive + } + pTransferObj->CopyToClipboard( GetActiveWin() ); + } + + return true; +} + +bool ScViewFunc::CopyToClipMultiRange( const ScDocument* pInputClipDoc, const ScRangeList& rRanges, bool bCut, bool bApi, bool bIncludeObjects ) +{ + if (bCut) + { + // We don't support cutting of multi-selections. + if (!bApi) + ErrorMessage(STR_NOMULTISELECT); + return false; + } + if (pInputClipDoc) + { + // TODO: What's this for? + if (!bApi) + ErrorMessage(STR_NOMULTISELECT); + return false; + } + + ScClipParam aClipParam( rRanges[0], bCut ); + aClipParam.maRanges = rRanges; + ScDocument& rDoc = GetViewData().GetDocument(); + ScMarkData& rMark = GetViewData().GetMarkData(); + bool bDone = false; + bool bSuccess = false; + aClipParam.mbCutMode = false; + + do + { + ScDocumentUniquePtr pDocClip(new ScDocument(SCDOCMODE_CLIP)); + + // Check for geometrical feasibility of the ranges. + bool bValidRanges = true; + ScRange const * p = &aClipParam.maRanges.front(); + SCCOL nPrevColDelta = 0; + SCROW nPrevRowDelta = 0; + SCCOL nPrevCol = p->aStart.Col(); + SCROW nPrevRow = p->aStart.Row(); + SCCOL nPrevColSize = p->aEnd.Col() - p->aStart.Col() + 1; + SCROW nPrevRowSize = p->aEnd.Row() - p->aStart.Row() + 1; + for ( size_t i = 1; i < aClipParam.maRanges.size(); ++i ) + { + p = &aClipParam.maRanges[i]; + if ( rDoc.HasSelectedBlockMatrixFragment( + p->aStart.Col(), p->aStart.Row(), p->aEnd.Col(), p->aEnd.Row(), rMark) ) + { + if (!bApi) + ErrorMessage(STR_MATRIXFRAGMENTERR); + return false; + } + + SCCOL nColDelta = p->aStart.Col() - nPrevCol; + SCROW nRowDelta = p->aStart.Row() - nPrevRow; + + if ((nColDelta && nRowDelta) || (nPrevColDelta && nRowDelta) || (nPrevRowDelta && nColDelta)) + { + bValidRanges = false; + break; + } + + if (aClipParam.meDirection == ScClipParam::Unspecified) + { + if (nColDelta) + aClipParam.meDirection = ScClipParam::Column; + if (nRowDelta) + aClipParam.meDirection = ScClipParam::Row; + } + + SCCOL nColSize = p->aEnd.Col() - p->aStart.Col() + 1; + SCROW nRowSize = p->aEnd.Row() - p->aStart.Row() + 1; + + if (aClipParam.meDirection == ScClipParam::Column && nRowSize != nPrevRowSize) + { + // column-oriented ranges must have identical row size. + bValidRanges = false; + break; + } + if (aClipParam.meDirection == ScClipParam::Row && nColSize != nPrevColSize) + { + // likewise, row-oriented ranges must have identical + // column size. + bValidRanges = false; + break; + } + + nPrevCol = p->aStart.Col(); + nPrevRow = p->aStart.Row(); + nPrevColDelta = nColDelta; + nPrevRowDelta = nRowDelta; + nPrevColSize = nColSize; + nPrevRowSize = nRowSize; + } + if (!bValidRanges) + break; + rDoc.CopyToClip(aClipParam, pDocClip.get(), &rMark, false, bIncludeObjects ); + + ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack(); + if ( pChangeTrack ) + pChangeTrack->ResetLastCut(); // no more cut-mode + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + TransferableObjectDescriptor aObjDesc; + pDocSh->FillTransferableObjectDescriptor( aObjDesc ); + aObjDesc.maDisplayName = pDocSh->GetMedium()->GetURLObject().GetURLNoPass(); + // maSize is set in ScTransferObj ctor + + rtl::Reference<ScTransferObj> pTransferObj(new ScTransferObj( std::move(pDocClip), std::move(aObjDesc) )); + if ( ScGlobal::xDrawClipDocShellRef.is() ) + { + SfxObjectShellRef aPersistRef( ScGlobal::xDrawClipDocShellRef.get() ); + pTransferObj->SetDrawPersist( aPersistRef ); // keep persist for ole objects alive + } + pTransferObj->CopyToClipboard( GetActiveWin() ); // system clipboard + + bSuccess = true; + } + while (false); + + if (!bSuccess && !bApi) + ErrorMessage(STR_NOMULTISELECT); + + bDone = bSuccess; + + return bDone; +} + +rtl::Reference<ScTransferObj> ScViewFunc::CopyToTransferable() +{ + ScRange aRange; + auto eMarkType = GetViewData().GetSimpleArea( aRange ); + if ( eMarkType == SC_MARK_SIMPLE || eMarkType == SC_MARK_SIMPLE_FILTERED ) + { + ScDocument& rDoc = GetViewData().GetDocument(); + ScMarkData& rMark = GetViewData().GetMarkData(); + if ( !rDoc.HasSelectedBlockMatrixFragment( + aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), + rMark ) ) + { + ScDocumentUniquePtr pClipDoc(new ScDocument( SCDOCMODE_CLIP )); // create one (deleted by ScTransferObj) + + bool bAnyOle = rDoc.HasOLEObjectsInArea( aRange, &rMark ); + ScDrawLayer::SetGlobalDrawPersist( ScTransferObj::SetDrawClipDoc( bAnyOle ) ); + + ScClipParam aClipParam(aRange, false); + rDoc.CopyToClip(aClipParam, pClipDoc.get(), &rMark, false, true); + + ScDrawLayer::SetGlobalDrawPersist(nullptr); + pClipDoc->ExtendMerge( aRange, true ); + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + TransferableObjectDescriptor aObjDesc; + pDocSh->FillTransferableObjectDescriptor( aObjDesc ); + aObjDesc.maDisplayName = pDocSh->GetMedium()->GetURLObject().GetURLNoPass(); + return new ScTransferObj( std::move(pClipDoc), std::move(aObjDesc) ); + } + } + + return nullptr; +} + +// P A S T E + +void ScViewFunc::PasteDraw() +{ + ScViewData& rViewData = GetViewData(); + SCCOL nPosX = rViewData.GetCurX(); + SCROW nPosY = rViewData.GetCurY(); + vcl::Window* pWin = GetActiveWin(); + Point aPos = pWin->PixelToLogic( rViewData.GetScrPos( nPosX, nPosY, + rViewData.GetActivePart() ) ); + const ScDrawTransferObj* pDrawClip = ScDrawTransferObj::GetOwnClipboard(ScTabViewShell::GetClipData(rViewData.GetActiveWin())); + if (pDrawClip) + { + const OUString& aSrcShellID = pDrawClip->GetShellID(); + OUString aDestShellID = SfxObjectShell::CreateShellID(rViewData.GetDocShell()); + PasteDraw(aPos, pDrawClip->GetModel(), false, aSrcShellID, aDestShellID); + } +} + +void ScViewFunc::PasteFromSystem() +{ + UpdateInputLine(); + + vcl::Window* pWin = GetActiveWin(); + css::uno::Reference<css::datatransfer::XTransferable2> xTransferable2(ScTabViewShell::GetClipData(pWin)); + const ScTransferObj* pOwnClip = ScTransferObj::GetOwnClipboard(xTransferable2); + // keep a reference in case the clipboard is changed during PasteFromClip + const ScDrawTransferObj* pDrawClip = ScDrawTransferObj::GetOwnClipboard(xTransferable2); + if (pOwnClip) + { + PasteFromClip( InsertDeleteFlags::ALL, pOwnClip->GetDocument(), + ScPasteFunc::NONE, false, false, false, INS_NONE, InsertDeleteFlags::NONE, + true ); // allow warning dialog + } + else if (pDrawClip) + PasteDraw(); + else + { + TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromSystemClipboard( pWin ) ); + + { + SotClipboardFormatId nBiff8 = SotExchange::RegisterFormatName("Biff8"); + SotClipboardFormatId nBiff5 = SotExchange::RegisterFormatName("Biff5"); + + SotClipboardFormatId nFormat; // output param for GetExchangeAction + sal_uInt8 nEventAction; // output param for GetExchangeAction + + uno::Reference<css::datatransfer::XTransferable> xTransferable( aDataHelper.GetXTransferable() ); + sal_uInt8 nAction = SotExchange::GetExchangeAction( + aDataHelper.GetDataFlavorExVector(), + SotExchangeDest::SCDOC_FREE_AREA, + EXCHG_IN_ACTION_COPY, + EXCHG_IN_ACTION_DEFAULT, + nFormat, nEventAction, SotClipboardFormatId::NONE, + &xTransferable ); + + if ( nAction != EXCHG_INOUT_ACTION_NONE ) + { + switch( nAction ) + { + case EXCHG_OUT_ACTION_INSERT_SVXB: + case EXCHG_OUT_ACTION_INSERT_GDIMETAFILE: + case EXCHG_OUT_ACTION_INSERT_BITMAP: + case EXCHG_OUT_ACTION_INSERT_GRAPH: + // SotClipboardFormatId::BITMAP + // SotClipboardFormatId::PNG + // SotClipboardFormatId::GDIMETAFILE + // SotClipboardFormatId::SVXB + PasteFromSystem(nFormat); + break; + default: + nAction = EXCHG_INOUT_ACTION_NONE; + } + } + + if ( nAction == EXCHG_INOUT_ACTION_NONE ) + { + // first SvDraw-model, then drawing + // (only one drawing is allowed) + + if (aDataHelper.HasFormat( SotClipboardFormatId::DRAWING )) + { + // special case for tables from drawing + if( aDataHelper.HasFormat( SotClipboardFormatId::RTF ) ) + { + PasteFromSystem( SotClipboardFormatId::RTF ); + } + else if( aDataHelper.HasFormat( SotClipboardFormatId::RICHTEXT ) ) + { + PasteFromSystem( SotClipboardFormatId::RICHTEXT ); + } + else + { + PasteFromSystem( SotClipboardFormatId::DRAWING ); + } + } + else if (aDataHelper.HasFormat( SotClipboardFormatId::EMBED_SOURCE )) + { + // If it's a Writer object, insert RTF instead of OLE + + // Else, if the class id is all-zero, and SYLK is available, + // it probably is spreadsheet cells that have been put + // on the clipboard by OOo, so use the SYLK. (fdo#31077) + + bool bDoRtf = false; + TransferableObjectDescriptor aObjDesc; + if( aDataHelper.GetTransferableObjectDescriptor( SotClipboardFormatId::OBJECTDESCRIPTOR, aObjDesc ) ) + { + bDoRtf = ( ( aObjDesc.maClassName == SvGlobalName( SO3_SW_CLASSID ) || + aObjDesc.maClassName == SvGlobalName( SO3_SWWEB_CLASSID ) ) + && ( aDataHelper.HasFormat( SotClipboardFormatId::RTF ) || aDataHelper.HasFormat( SotClipboardFormatId::RICHTEXT ) ) ); + } + if ( bDoRtf ) + PasteFromSystem( aDataHelper.HasFormat( SotClipboardFormatId::RTF ) ? SotClipboardFormatId::RTF : SotClipboardFormatId::RICHTEXT ); + else if ( aObjDesc.maClassName == SvGlobalName( 0,0,0,0,0,0,0,0,0,0,0 ) + && aDataHelper.HasFormat( SotClipboardFormatId::SYLK )) + PasteFromSystem( SotClipboardFormatId::SYLK ); + else + PasteFromSystem( SotClipboardFormatId::EMBED_SOURCE ); + } + else if (aDataHelper.HasFormat( SotClipboardFormatId::LINK_SOURCE )) + PasteFromSystem( SotClipboardFormatId::LINK_SOURCE ); + // the following format can not affect scenario from #89579# + else if (aDataHelper.HasFormat( SotClipboardFormatId::EMBEDDED_OBJ_OLE )) + PasteFromSystem( SotClipboardFormatId::EMBEDDED_OBJ_OLE ); + // SotClipboardFormatId::PRIVATE no longer here (can't work if pOwnClip is NULL) + else if (aDataHelper.HasFormat(nBiff8)) // before xxx_OLE formats + PasteFromSystem(nBiff8); + else if (aDataHelper.HasFormat(nBiff5)) + PasteFromSystem(nBiff5); + else if (aDataHelper.HasFormat(SotClipboardFormatId::RTF)) + PasteFromSystem(SotClipboardFormatId::RTF); + else if (aDataHelper.HasFormat(SotClipboardFormatId::RICHTEXT)) + PasteFromSystem(SotClipboardFormatId::RICHTEXT); + else if (aDataHelper.HasFormat(SotClipboardFormatId::HTML)) + PasteFromSystem(SotClipboardFormatId::HTML); + else if (aDataHelper.HasFormat(SotClipboardFormatId::BITMAP)) + PasteFromSystem(SotClipboardFormatId::BITMAP); + else if (aDataHelper.HasFormat(SotClipboardFormatId::HTML_SIMPLE)) + PasteFromSystem(SotClipboardFormatId::HTML_SIMPLE); + else if (aDataHelper.HasFormat(SotClipboardFormatId::SYLK)) + PasteFromSystem(SotClipboardFormatId::SYLK); + else if (aDataHelper.HasFormat(SotClipboardFormatId::STRING_TSVC)) + PasteFromSystem(SotClipboardFormatId::STRING_TSVC); + else if (aDataHelper.HasFormat(SotClipboardFormatId::STRING)) + PasteFromSystem(SotClipboardFormatId::STRING); + // xxx_OLE formats come last, like in SotExchange tables + else if (aDataHelper.HasFormat( SotClipboardFormatId::EMBED_SOURCE_OLE )) + PasteFromSystem( SotClipboardFormatId::EMBED_SOURCE_OLE ); + else if (aDataHelper.HasFormat( SotClipboardFormatId::LINK_SOURCE_OLE )) + PasteFromSystem( SotClipboardFormatId::LINK_SOURCE_OLE ); + } + } + } + // no exception-> SID_PASTE has FastCall-flag from idl + // will be called in case of empty clipboard (#42531#) +} + +void ScViewFunc::PasteFromTransferable( const uno::Reference<datatransfer::XTransferable>& rxTransferable ) +{ + if (auto pOwnClip = dynamic_cast<ScTransferObj*>(rxTransferable.get())) + { + PasteFromClip( InsertDeleteFlags::ALL, pOwnClip->GetDocument(), + ScPasteFunc::NONE, false, false, false, INS_NONE, InsertDeleteFlags::NONE, + true ); // allow warning dialog + } + else if (auto pDrawClip = dynamic_cast<ScDrawTransferObj*>(rxTransferable.get())) + { + ScViewData& rViewData = GetViewData(); + SCCOL nPosX = rViewData.GetCurX(); + SCROW nPosY = rViewData.GetCurY(); + vcl::Window* pWin = GetActiveWin(); + Point aPos = pWin->PixelToLogic( rViewData.GetScrPos( nPosX, nPosY, rViewData.GetActivePart() ) ); + PasteDraw( + aPos, pDrawClip->GetModel(), false, + pDrawClip->GetShellID(), SfxObjectShell::CreateShellID(rViewData.GetDocShell())); + } + else + { + TransferableDataHelper aDataHelper( rxTransferable ); + SotClipboardFormatId nBiff8 = SotExchange::RegisterFormatName("Biff8"); + SotClipboardFormatId nBiff5 = SotExchange::RegisterFormatName("Biff5"); + SotClipboardFormatId nFormatId = SotClipboardFormatId::NONE; + // first SvDraw-model, then drawing + // (only one drawing is allowed) + + 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; + TransferableObjectDescriptor aObjDesc; + if( aDataHelper.GetTransferableObjectDescriptor( SotClipboardFormatId::OBJECTDESCRIPTOR, aObjDesc ) ) + { + 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; + // the following format can not affect scenario from #89579# + else if (aDataHelper.HasFormat( SotClipboardFormatId::EMBEDDED_OBJ_OLE )) + nFormatId = SotClipboardFormatId::EMBEDDED_OBJ_OLE; + // SotClipboardFormatId::PRIVATE no longer here (can't work if pOwnClip is NULL) + else if (aDataHelper.HasFormat(nBiff8)) // before xxx_OLE formats + nFormatId = nBiff8; + else if (aDataHelper.HasFormat(nBiff5)) + nFormatId = nBiff5; + 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::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::BITMAP)) + nFormatId = SotClipboardFormatId::BITMAP; + // xxx_OLE formats come last, like in SotExchange tables + else if (aDataHelper.HasFormat( SotClipboardFormatId::EMBED_SOURCE_OLE )) + nFormatId = SotClipboardFormatId::EMBED_SOURCE_OLE; + else if (aDataHelper.HasFormat( SotClipboardFormatId::LINK_SOURCE_OLE )) + nFormatId = SotClipboardFormatId::LINK_SOURCE_OLE; + else + return; + + PasteDataFormat( nFormatId, aDataHelper.GetTransferable(), + GetViewData().GetCurX(), GetViewData().GetCurY(), nullptr ); + } +} + +bool ScViewFunc::PasteFromSystem( SotClipboardFormatId nFormatId, bool bApi ) +{ + UpdateInputLine(); + + bool bRet = true; + vcl::Window* pWin = GetActiveWin(); + // keep a reference in case the clipboard is changed during PasteFromClip + const ScTransferObj* pOwnClip = ScTransferObj::GetOwnClipboard(ScTabViewShell::GetClipData(pWin)); + if ( nFormatId == SotClipboardFormatId::NONE && pOwnClip ) + { + PasteFromClip( InsertDeleteFlags::ALL, pOwnClip->GetDocument(), + ScPasteFunc::NONE, false, false, false, INS_NONE, InsertDeleteFlags::NONE, + !bApi ); // allow warning dialog + } + else + { + TransferableDataHelper aDataHelper( TransferableDataHelper::CreateFromSystemClipboard( pWin ) ); + if ( !aDataHelper.GetTransferable().is() ) + return false; + + SCCOL nPosX = 0; + SCROW nPosY = 0; + + ScViewData& rViewData = GetViewData(); + ScRange aRange; + if ( rViewData.GetSimpleArea( aRange ) == SC_MARK_SIMPLE ) + { + nPosX = aRange.aStart.Col(); + nPosY = aRange.aStart.Row(); + } + else + { + nPosX = rViewData.GetCurX(); + nPosY = rViewData.GetCurY(); + } + + bRet = PasteDataFormat( nFormatId, aDataHelper.GetTransferable(), + nPosX, nPosY, + nullptr, false, !bApi ); // allow warning dialog + + if ( !bRet && !bApi ) + { + ErrorMessage(STR_PASTE_ERROR); + } + else if (comphelper::LibreOfficeKit::isActive()) + { + SfxViewShell* pViewShell = rViewData.GetViewShell(); + ScTabViewShell::notifyAllViewsSheetGeomInvalidation(pViewShell, true /* bColumns */, true /* bRows */, + true /* bSizes */, false /* bHidden */, false /* bFiltered */, false /* bGroups */, rViewData.GetTabNo()); + } + } + return bRet; +} + +// P A S T E + +bool ScViewFunc::PasteOnDrawObjectLinked( + const uno::Reference<datatransfer::XTransferable>& rxTransferable, + SdrObject& rHitObj) +{ + TransferableDataHelper aDataHelper( rxTransferable ); + + if ( aDataHelper.HasFormat( SotClipboardFormatId::SVXB ) ) + { + tools::SvRef<SotTempStream> xStm; + ScDrawView* pScDrawView = GetScDrawView(); + + if( pScDrawView && aDataHelper.GetSotStorageStream( SotClipboardFormatId::SVXB, xStm ) ) + { + Graphic aGraphic; + TypeSerializer aSerializer(*xStm); + aSerializer.readGraphic(aGraphic); + + const OUString aBeginUndo(ScResId(STR_UNDO_DRAGDROP)); + + if(pScDrawView->ApplyGraphicToObject( rHitObj, aGraphic, aBeginUndo, "" )) + { + return true; + } + } + } + else if ( aDataHelper.HasFormat( SotClipboardFormatId::GDIMETAFILE ) ) + { + GDIMetaFile aMtf; + ScDrawView* pScDrawView = GetScDrawView(); + + if( pScDrawView && aDataHelper.GetGDIMetaFile( SotClipboardFormatId::GDIMETAFILE, aMtf ) ) + { + const OUString aBeginUndo(ScResId(STR_UNDO_DRAGDROP)); + + if(pScDrawView->ApplyGraphicToObject( rHitObj, Graphic(aMtf), aBeginUndo, "" )) + { + return true; + } + } + } + else if ( aDataHelper.HasFormat( SotClipboardFormatId::BITMAP ) || aDataHelper.HasFormat( SotClipboardFormatId::PNG ) ) + { + BitmapEx aBmpEx; + ScDrawView* pScDrawView = GetScDrawView(); + + if( pScDrawView && aDataHelper.GetBitmapEx( SotClipboardFormatId::BITMAP, aBmpEx ) ) + { + const OUString aBeginUndo(ScResId(STR_UNDO_DRAGDROP)); + + if(pScDrawView->ApplyGraphicToObject( rHitObj, Graphic(aBmpEx), aBeginUndo, "" )) + { + return true; + } + } + } + + return false; +} + +static bool lcl_SelHasAttrib( const ScDocument& rDoc, SCCOL nCol1, SCROW nRow1, SCCOL nCol2, SCROW nRow2, + const ScMarkData& rTabSelection, HasAttrFlags nMask ) +{ + return std::any_of(rTabSelection.begin(), rTabSelection.end(), + [&](const SCTAB& rTab) { return rDoc.HasAttrib( nCol1, nRow1, rTab, nCol2, nRow2, rTab, nMask ); }); +} + +// paste into sheet: + +// internal paste + +namespace { + +bool checkDestRangeForOverwrite(const ScRangeList& rDestRanges, const ScDocument& rDoc, const ScMarkData& rMark, weld::Window* pParentWnd) +{ + bool bIsEmpty = true; + size_t nRangeSize = rDestRanges.size(); + for (const auto& rTab : rMark) + { + for (size_t i = 0; i < nRangeSize && bIsEmpty; ++i) + { + const ScRange& rRange = rDestRanges[i]; + bIsEmpty = rDoc.IsBlockEmpty( + rRange.aStart.Col(), rRange.aStart.Row(), + rRange.aEnd.Col(), rRange.aEnd.Row(), rTab ); + } + if (!bIsEmpty) + break; + } + + if (!bIsEmpty) + { + ScReplaceWarnBox aBox(pParentWnd); + if (aBox.run() != RET_YES) + { + // changing the configuration is within the ScReplaceWarnBox + return false; + } + } + return true; +} + +} + +bool ScViewFunc::PasteFromClip( InsertDeleteFlags nFlags, ScDocument* pClipDoc, + ScPasteFunc nFunction, bool bSkipEmptyCells, + bool bTranspose, bool bAsLink, + InsCellCmd eMoveMode, InsertDeleteFlags nUndoExtraFlags, + bool bAllowDialogs ) +{ + if (!pClipDoc) + { + OSL_FAIL("PasteFromClip: pClipDoc=0 not allowed"); + return false; + } + + if (GetViewData().SelectionForbidsPaste(pClipDoc)) + return false; + + // undo: save all or no content + InsertDeleteFlags nContFlags = InsertDeleteFlags::NONE; + if (nFlags & InsertDeleteFlags::CONTENTS) + nContFlags |= InsertDeleteFlags::CONTENTS; + if (nFlags & InsertDeleteFlags::ATTRIB) + nContFlags |= InsertDeleteFlags::ATTRIB; + // move attributes to undo without copying them from clip to doc + InsertDeleteFlags nUndoFlags = nContFlags; + if (nUndoExtraFlags & InsertDeleteFlags::ATTRIB) + nUndoFlags |= InsertDeleteFlags::ATTRIB; + // do not copy note captions into undo document + nUndoFlags |= InsertDeleteFlags::NOCAPTIONS; + + ScClipParam& rClipParam = pClipDoc->GetClipParam(); + if (rClipParam.isMultiRange()) + { + // Source data is multi-range. + return PasteMultiRangesFromClip(nFlags, pClipDoc, nFunction, bSkipEmptyCells, bTranspose, + bAsLink, bAllowDialogs, eMoveMode, nUndoFlags); + } + + ScMarkData& rMark = GetViewData().GetMarkData(); + if (rMark.IsMultiMarked()) + { + // Source data is single-range but destination is multi-range. + return PasteFromClipToMultiRanges( + nFlags, pClipDoc, nFunction, bSkipEmptyCells, bTranspose, bAsLink, bAllowDialogs, + eMoveMode, nUndoFlags); + } + + bool bCutMode = pClipDoc->IsCutMode(); // if transposing, take from original clipdoc + bool bIncludeFiltered = bCutMode; + + // paste drawing: also if InsertDeleteFlags::NOTE is set (to create drawing layer for note captions) + bool bPasteDraw = ( pClipDoc->GetDrawLayer() && ( nFlags & (InsertDeleteFlags::OBJECTS|InsertDeleteFlags::NOTE) ) ); + + ScDocShellRef aTransShellRef; // for objects in xTransClip - must remain valid as long as xTransClip + ScDocument* pOrigClipDoc = nullptr; + ScDocumentUniquePtr xTransClip; + if ( bTranspose ) + { + SCCOL nX; + SCROW nY; + // include filtered rows until TransposeClip can skip them + pClipDoc->GetClipArea( nX, nY, true ); + if ( nY > static_cast<sal_Int32>(pClipDoc->MaxCol()) ) // too many lines for transpose + { + ErrorMessage(STR_PASTE_FULL); + return false; + } + pOrigClipDoc = pClipDoc; // refs + + if ( bPasteDraw ) + { + aTransShellRef = new ScDocShell; // DocShell needs a Ref immediately + aTransShellRef->DoInitNew(); + } + ScDrawLayer::SetGlobalDrawPersist( aTransShellRef.get() ); + + xTransClip.reset( new ScDocument( SCDOCMODE_CLIP )); + pClipDoc->TransposeClip(xTransClip.get(), nFlags, bAsLink, bIncludeFiltered); + pClipDoc = xTransClip.get(); + + ScDrawLayer::SetGlobalDrawPersist(nullptr); + } + + // TODO: position this call better for performance. + ResetAutoSpellForContentChange(); + + SCCOL nStartCol; + SCROW nStartRow; + SCTAB nStartTab; + SCCOL nEndCol; + SCROW nEndRow; + SCTAB nEndTab; + SCCOL nClipSizeX; + SCROW nClipSizeY; + pClipDoc->GetClipArea( nClipSizeX, nClipSizeY, true ); // size in clipboard doc + + // size in target doc: include filtered rows only if CutMode is set + SCCOL nDestSizeX; + SCROW nDestSizeY; + pClipDoc->GetClipArea( nDestSizeX, nDestSizeY, bIncludeFiltered ); + + ScDocument& rDoc = GetViewData().GetDocument(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + SfxUndoManager* pUndoMgr = pDocSh->GetUndoManager(); + const bool bRecord(rDoc.IsUndoEnabled()); + + ScDocShellModificator aModificator( *pDocSh ); + + ScRange aMarkRange; + ScMarkData aFilteredMark( rMark); // local copy for all modifications + ScMarkType eMarkType = GetViewData().GetSimpleArea( aMarkRange, aFilteredMark); + bool bMarkIsFiltered = (eMarkType == SC_MARK_SIMPLE_FILTERED); + bool bNoPaste = ((eMarkType != SC_MARK_SIMPLE && !bMarkIsFiltered) || + (bMarkIsFiltered && (eMoveMode != INS_NONE || bAsLink))); + + if (!bNoPaste) + { + if (!rMark.IsMarked()) + { + // Create a selection with clipboard row count and check that for + // filtered. + nStartCol = GetViewData().GetCurX(); + nStartRow = GetViewData().GetCurY(); + nStartTab = GetViewData().GetTabNo(); + nEndCol = nStartCol + nDestSizeX; + nEndRow = nStartRow + nDestSizeY; + nEndTab = nStartTab; + aMarkRange = ScRange( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab); + if (ScViewUtil::HasFiltered(aMarkRange, rDoc)) + { + bMarkIsFiltered = true; + // Fit to clipboard's row count unfiltered rows. If there is no + // fit assume that pasting is not possible. Note that nDestSizeY is + // size-1 (difference). + if (!ScViewUtil::FitToUnfilteredRows(aMarkRange, rDoc, nDestSizeY+1)) + bNoPaste = true; + } + aFilteredMark.SetMarkArea( aMarkRange); + } + else + { + // Expand the marked area when the destination area is larger than the + // current selection, to get the undo do the right thing. (i#106711) + ScRange aRange = aFilteredMark.GetMarkArea(); + if( (aRange.aEnd.Col() - aRange.aStart.Col()) < nDestSizeX ) + { + aRange.aEnd.SetCol(aRange.aStart.Col() + nDestSizeX); + aFilteredMark.SetMarkArea(aRange); + } + } + } + + if (bNoPaste) + { + ErrorMessage(STR_MSSG_PASTEFROMCLIP_0); + return false; + } + + SCROW nUnfilteredRows = aMarkRange.aEnd.Row() - aMarkRange.aStart.Row() + 1; + ScRangeList aRangeList; + if (bMarkIsFiltered) + { + ScViewUtil::UnmarkFiltered(aFilteredMark, rDoc); + aFilteredMark.FillRangeListWithMarks( &aRangeList, false); + nUnfilteredRows = 0; + size_t ListSize = aRangeList.size(); + for ( size_t i = 0; i < ListSize; ++i ) + { + ScRange & r = aRangeList[i]; + nUnfilteredRows += r.aEnd.Row() - r.aStart.Row() + 1; + } +#if 0 + /* This isn't needed but could be a desired restriction. */ + // For filtered, destination rows have to be an exact multiple of + // source rows. Note that nDestSizeY is size-1 (difference), so + // nDestSizeY==0 fits always. + if ((nUnfilteredRows % (nDestSizeY+1)) != 0) + { + /* FIXME: this should be a more descriptive error message then. */ + ErrorMessage(STR_MSSG_PASTEFROMCLIP_0); + return false; + } +#endif + } + + // Also for a filtered selection the area is used, for undo et al. + if ( aFilteredMark.IsMarked() || bMarkIsFiltered ) + { + aMarkRange.GetVars( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab); + SCCOL nBlockAddX = nEndCol-nStartCol; + SCROW nBlockAddY = nEndRow-nStartRow; + + // request, if the selection is greater than one row/column, but smaller + // as the Clipboard (then inserting is done beyond the selection) + + // ClipSize is not size, but difference + if ( ( nBlockAddX != 0 && nBlockAddX < nDestSizeX ) || + ( nBlockAddY != 0 && nBlockAddY < nDestSizeY ) || + ( bMarkIsFiltered && nUnfilteredRows < nDestSizeY+1 ) ) + { + ScWaitCursorOff aWaitOff( GetFrameWin() ); + OUString aMessage = ScResId( STR_PASTE_BIGGER ); + + std::unique_ptr<weld::MessageDialog> xQueryBox(Application::CreateMessageDialog(GetViewData().GetDialogParent(), + VclMessageType::Question, VclButtonsType::YesNo, + aMessage)); + xQueryBox->set_default_response(RET_NO); + if (xQueryBox->run() != RET_YES) + { + return false; + } + } + + if (nBlockAddX <= nDestSizeX) + nEndCol = nStartCol + nDestSizeX; + + if (nBlockAddY <= nDestSizeY) + { + nEndRow = nStartRow + nDestSizeY; + if (bMarkIsFiltered || nEndRow > aMarkRange.aEnd.Row()) + { + // Same as above if nothing was marked: re-fit selection to + // unfiltered rows. Extending the selection actually may + // introduce filtered rows where there weren't any before, so + // we also need to test for that. + aMarkRange = ScRange( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab); + if (bMarkIsFiltered || ScViewUtil::HasFiltered(aMarkRange, rDoc)) + { + bMarkIsFiltered = true; + // Worst case: all rows up to the end of the sheet are filtered. + if (!ScViewUtil::FitToUnfilteredRows(aMarkRange, rDoc, nDestSizeY+1)) + { + ErrorMessage(STR_PASTE_FULL); + return false; + } + } + aMarkRange.GetVars( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab); + aFilteredMark.SetMarkArea( aMarkRange); + if (bMarkIsFiltered) + { + ScViewUtil::UnmarkFiltered(aFilteredMark, rDoc); + aFilteredMark.FillRangeListWithMarks( &aRangeList, true); + } + } + } + } + else + { + nStartCol = GetViewData().GetCurX(); + nStartRow = GetViewData().GetCurY(); + nStartTab = GetViewData().GetTabNo(); + nEndCol = nStartCol + nDestSizeX; + nEndRow = nStartRow + nDestSizeY; + nEndTab = nStartTab; + } + + bool bOffLimits = !rDoc.ValidCol(nEndCol) || !rDoc.ValidRow(nEndRow); + + // target-range, as displayed: + ScRange aUserRange( nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab ); + + // should lines be inserted? + // ( too large nEndCol/nEndRow are detected below) + bool bInsertCells = ( eMoveMode != INS_NONE && !bOffLimits ); + if ( bInsertCells ) + { + // Instead of EnterListAction, the paste undo action is merged into the + // insert action, so Repeat can insert the right cells + + MarkRange( aUserRange ); // set through CopyFromClip + + // CutMode is reset on insertion of cols/rows but needed again on cell move + bool bCut = pClipDoc->IsCutMode(); + if (!InsertCells( eMoveMode, bRecord, true )) // is inserting possible? + { + return false; + // #i21036# EnterListAction isn't used, and InsertCells doesn't insert + // its undo action on failure, so no undo handling is needed here + } + if ( bCut ) + pClipDoc->SetCutMode( bCut ); + } + else if (!bOffLimits) + { + bool bAskIfNotEmpty = bAllowDialogs && + ( nFlags & InsertDeleteFlags::CONTENTS ) && + nFunction == ScPasteFunc::NONE && + SC_MOD()->GetInputOptions().GetReplaceCellsWarn(); + if ( bAskIfNotEmpty ) + { + ScRangeList aTestRanges(aUserRange); + if (!checkDestRangeForOverwrite(aTestRanges, rDoc, aFilteredMark, GetViewData().GetDialogParent())) + return false; + } + } + + SCCOL nClipStartX; // enlarge clipboard-range + SCROW nClipStartY; + pClipDoc->GetClipStart( nClipStartX, nClipStartY ); + SCCOL nUndoEndCol = nClipStartX + nClipSizeX; + SCROW nUndoEndRow = nClipStartY + nClipSizeY; // end of source area in clipboard document + bool bClipOver = false; + // #i68690# ExtendMerge for the clip doc must be called with the clipboard's sheet numbers. + // The same end column/row can be used for all calls because the clip doc doesn't contain + // content outside the clip area. + for (SCTAB nClipTab=0; nClipTab < pClipDoc->GetTableCount(); nClipTab++) + if ( pClipDoc->HasTable(nClipTab) ) + if ( pClipDoc->ExtendMerge( nClipStartX,nClipStartY, nUndoEndCol,nUndoEndRow, nClipTab ) ) + bClipOver = true; + nUndoEndCol -= nClipStartX + nClipSizeX; + nUndoEndRow -= nClipStartY + nClipSizeY; // now contains only the difference added by ExtendMerge + nUndoEndCol = sal::static_int_cast<SCCOL>( nUndoEndCol + nEndCol ); + nUndoEndRow = sal::static_int_cast<SCROW>( nUndoEndRow + nEndRow ); // destination area, expanded for merged cells + + if (nUndoEndCol>pClipDoc->MaxCol() || nUndoEndRow>pClipDoc->MaxRow()) + { + ErrorMessage(STR_PASTE_FULL); + return false; + } + + rDoc.ExtendMergeSel( nStartCol,nStartRow, nUndoEndCol,nUndoEndRow, aFilteredMark ); + + // check cell-protection + + ScEditableTester aTester( rDoc, nStartTab, nStartCol,nStartRow, nUndoEndCol,nUndoEndRow ); + if (!aTester.IsEditable()) + { + ErrorMessage(aTester.GetMessageId()); + return false; + } + + //! check overlapping + //! just check truly intersection !!!!!!! + + ScDocFunc& rDocFunc = pDocSh->GetDocFunc(); + if ( bRecord ) + { + OUString aUndo = ScResId( pClipDoc->IsCutMode() ? STR_UNDO_MOVE : STR_UNDO_COPY ); + pUndoMgr->EnterListAction( aUndo, aUndo, 0, GetViewData().GetViewShell()->GetViewShellId() ); + } + + if (bClipOver) + if (lcl_SelHasAttrib( rDoc, nStartCol,nStartRow, nUndoEndCol,nUndoEndRow, aFilteredMark, HasAttrFlags::Overlapped )) + { // "Cell merge not possible if cells already merged" + ScDocAttrIterator aIter( rDoc, nStartTab, nStartCol, nStartRow, nUndoEndCol, nUndoEndRow ); + const ScPatternAttr* pPattern = nullptr; + SCCOL nCol = -1; + SCROW nRow1 = -1; + SCROW nRow2 = -1; + while ( ( pPattern = aIter.GetNext( nCol, nRow1, nRow2 ) ) != nullptr ) + { + const ScMergeAttr& rMergeFlag = pPattern->GetItem(ATTR_MERGE); + const ScMergeFlagAttr& rMergeFlagAttr = pPattern->GetItem(ATTR_MERGE_FLAG); + if (rMergeFlag.IsMerged() || rMergeFlagAttr.IsOverlapped()) + { + ScRange aRange(nCol, nRow1, nStartTab); + rDoc.ExtendOverlapped(aRange); + rDoc.ExtendMerge(aRange, true); + rDocFunc.UnmergeCells(aRange, bRecord, nullptr /*TODO: should pass combined UndoDoc if bRecord*/); + } + } + } + + if ( !bCutMode ) + { + ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack(); + if ( pChangeTrack ) + pChangeTrack->ResetLastCut(); // no more cut-mode + } + + bool bColInfo = ( nStartRow==0 && nEndRow==rDoc.MaxRow() ); + bool bRowInfo = ( nStartCol==0 && nEndCol==rDoc.MaxCol() ); + + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr<ScDocument> pRefUndoDoc; + std::unique_ptr<ScRefUndoData> pUndoData; + + if ( bRecord ) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndoSelected( rDoc, aFilteredMark, bColInfo, bRowInfo ); + + // all sheets - CopyToDocument skips those that don't exist in pUndoDoc + SCTAB nTabCount = rDoc.GetTableCount(); + rDoc.CopyToDocument( nStartCol, nStartRow, 0, nUndoEndCol, nUndoEndRow, nTabCount-1, + nUndoFlags, false, *pUndoDoc ); + + if ( bCutMode ) + { + pRefUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pRefUndoDoc->InitUndo( rDoc, 0, nTabCount-1 ); + + pUndoData.reset(new ScRefUndoData( &rDoc )); + } + } + + sal_uInt16 nExtFlags = 0; + pDocSh->UpdatePaintExt( nExtFlags, nStartCol, nStartRow, nStartTab, + nEndCol, nEndRow, nEndTab ); // content before the change + + if (GetViewData().IsActive()) + { + DoneBlockMode(); + InitOwnBlockMode( aUserRange ); + } + rMark.SetMarkArea( aUserRange ); + MarkDataChanged(); + + // copy from clipboard + // save original data in case of calculation + + ScDocumentUniquePtr pMixDoc; + if (nFunction != ScPasteFunc::NONE) + { + bSkipEmptyCells = false; + if ( nFlags & InsertDeleteFlags::CONTENTS ) + { + pMixDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pMixDoc->InitUndo( rDoc, nStartTab, nEndTab ); + rDoc.CopyToDocument(nStartCol, nStartRow, nStartTab, nEndCol, nEndRow, nEndTab, + InsertDeleteFlags::CONTENTS, false, *pMixDoc); + } + } + + /* Make draw layer and start drawing undo. + - Needed before AdjustBlockHeight to track moved drawing objects. + - Needed before rDoc.CopyFromClip to track inserted note caption objects. + */ + if ( bPasteDraw ) + pDocSh->MakeDrawLayer(); + if ( bRecord ) + rDoc.BeginDrawUndo(); + + InsertDeleteFlags nNoObjFlags = nFlags & ~InsertDeleteFlags::OBJECTS; + if (!bAsLink) + { + // copy normally (original range) + rDoc.CopyFromClip( aUserRange, aFilteredMark, nNoObjFlags, + pRefUndoDoc.get(), pClipDoc, true, false, bIncludeFiltered, + bSkipEmptyCells, (bMarkIsFiltered ? &aRangeList : nullptr) ); + + // adapt refs manually in case of transpose + if ( bTranspose && bCutMode && (nFlags & InsertDeleteFlags::CONTENTS) ) + rDoc.UpdateTranspose( aUserRange.aStart, pOrigClipDoc, aFilteredMark, pRefUndoDoc.get() ); + } + else if (!bTranspose) + { + // copy with bAsLink=TRUE + rDoc.CopyFromClip( aUserRange, aFilteredMark, nNoObjFlags, pRefUndoDoc.get(), pClipDoc, + true, true, bIncludeFiltered, bSkipEmptyCells ); + } + else + { + // copy all content (TransClipDoc contains only formula) + rDoc.CopyFromClip( aUserRange, aFilteredMark, nContFlags, pRefUndoDoc.get(), pClipDoc ); + } + + // skipped rows and merged cells don't mix + if ( !bIncludeFiltered && pClipDoc->HasClipFilteredRows() ) + rDocFunc.UnmergeCells( aUserRange, false, nullptr ); + + rDoc.ExtendMergeSel( nStartCol, nStartRow, nEndCol, nEndRow, aFilteredMark, true ); // refresh + // new range + + if ( pMixDoc ) // calculate with original data? + { + rDoc.MixDocument( aUserRange, nFunction, bSkipEmptyCells, *pMixDoc ); + } + pMixDoc.reset(); + + AdjustBlockHeight(); // update row heights before pasting objects + + ::std::vector< OUString > aExcludedChartNames; + SdrPage* pPage = nullptr; + + if ( nFlags & InsertDeleteFlags::OBJECTS ) + { + ScDrawView* pScDrawView = GetScDrawView(); + SdrModel* pModel = ( pScDrawView ? &pScDrawView->GetModel() : nullptr ); + pPage = ( pModel ? pModel->GetPage( static_cast< sal_uInt16 >( nStartTab ) ) : nullptr ); + if ( pPage ) + { + ScChartHelper::GetChartNames( aExcludedChartNames, pPage ); + } + + // Paste the drawing objects after the row heights have been updated. + + rDoc.CopyFromClip( aUserRange, aFilteredMark, InsertDeleteFlags::OBJECTS, pRefUndoDoc.get(), pClipDoc, + true, false, bIncludeFiltered ); + } + + pDocSh->UpdatePaintExt( nExtFlags, nStartCol, nStartRow, nStartTab, + nEndCol, nEndRow, nEndTab ); // content after the change + + // if necessary, delete autofilter-heads + if (bCutMode) + if (rDoc.RefreshAutoFilter( nClipStartX,nClipStartY, nClipStartX+nClipSizeX, + nClipStartY+nClipSizeY, nStartTab )) + { + pDocSh->PostPaint( + ScRange(nClipStartX, nClipStartY, nStartTab, nClipStartX+nClipSizeX, nClipStartY, nStartTab), + PaintPartFlags::Grid ); + } + + //! remove block-range on RefUndoDoc !!! + + if ( bRecord ) + { + ScDocumentUniquePtr pRedoDoc; + // copy redo data after appearance of the first undo + // don't create Redo-Doc without RefUndoDoc + + if (pRefUndoDoc) + { + pRedoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pRedoDoc->InitUndo( rDoc, nStartTab, nEndTab, bColInfo, bRowInfo ); + + // move adapted refs to Redo-Doc + + SCTAB nTabCount = rDoc.GetTableCount(); + pRedoDoc->AddUndoTab( 0, nTabCount-1 ); + rDoc.CopyUpdated( pRefUndoDoc.get(), pRedoDoc.get() ); + + // move old refs to Undo-Doc + + // not charts? + pUndoDoc->AddUndoTab( 0, nTabCount-1 ); + pRefUndoDoc->DeleteArea( nStartCol, nStartRow, nEndCol, nEndRow, aFilteredMark, InsertDeleteFlags::ALL ); + pRefUndoDoc->CopyToDocument( 0,0,0, pUndoDoc->MaxCol(), pUndoDoc->MaxRow(), nTabCount-1, + InsertDeleteFlags::FORMULA, false, *pUndoDoc ); + pRefUndoDoc.reset(); + } + + // DeleteUnchanged for pUndoData is in ScUndoPaste ctor, + // UndoData for redo is made during first undo + + ScUndoPasteOptions aOptions; // store options for repeat + aOptions.nFunction = nFunction; + aOptions.bSkipEmptyCells = bSkipEmptyCells; + aOptions.bTranspose = bTranspose; + aOptions.bAsLink = bAsLink; + aOptions.eMoveMode = eMoveMode; + + std::unique_ptr<SfxUndoAction> pUndo(new ScUndoPaste( + pDocSh, ScRange(nStartCol, nStartRow, nStartTab, nUndoEndCol, nUndoEndRow, nEndTab), + aFilteredMark, std::move(pUndoDoc), std::move(pRedoDoc), nFlags | nUndoFlags, std::move(pUndoData), + false, &aOptions )); // false = Redo data not yet copied + + if ( bInsertCells ) + { + // Merge the paste undo action into the insert action. + // Use ScUndoWrapper so the ScUndoPaste pointer can be stored in the insert action. + + pUndoMgr->AddUndoAction( std::make_unique<ScUndoWrapper>( std::move(pUndo) ), true ); + } + else + pUndoMgr->AddUndoAction( std::move(pUndo) ); + pUndoMgr->LeaveListAction(); + } + + PaintPartFlags nPaint = PaintPartFlags::Grid; + if (bColInfo) + { + nPaint |= PaintPartFlags::Top; + nUndoEndCol = rDoc.MaxCol(); // just for drawing ! + } + if (bRowInfo) + { + nPaint |= PaintPartFlags::Left; + nUndoEndRow = rDoc.MaxRow(); // just for drawing ! + } + pDocSh->PostPaint( + ScRange(nStartCol, nStartRow, nStartTab, nUndoEndCol, nUndoEndRow, nEndTab), + nPaint, nExtFlags); + // AdjustBlockHeight has already been called above + + aModificator.SetDocumentModified(); + PostPasteFromClip(aUserRange, rMark); + + if ( nFlags & InsertDeleteFlags::OBJECTS ) + { + ScModelObj* pModelObj = pDocSh->GetModel(); + if ( pPage && pModelObj ) + { + bool bSameDoc = ( rClipParam.getSourceDocID() == rDoc.GetDocumentID() ); + const ScRangeListVector& rProtectedChartRangesVector( rClipParam.maProtectedChartRangesVector ); + ScChartHelper::CreateProtectedChartListenersAndNotify( rDoc, pPage, pModelObj, nStartTab, + rProtectedChartRangesVector, aExcludedChartNames, bSameDoc ); + } + } + OUString aStartAddress = aMarkRange.aStart.GetColRowString(); + OUString aEndAddress = aMarkRange.aEnd.GetColRowString(); + collectUIInformation({{"RANGE", aStartAddress + ":" + aEndAddress}}, "PASTE"); + return true; +} + +bool ScViewFunc::PasteMultiRangesFromClip(InsertDeleteFlags nFlags, ScDocument* pClipDoc, + ScPasteFunc nFunction, bool bSkipEmptyCells, + bool bTranspose, bool bAsLink, + bool bAllowDialogs, InsCellCmd eMoveMode, + InsertDeleteFlags nUndoFlags) +{ + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + ScDocShell* pDocSh = rViewData.GetDocShell(); + ScMarkData aMark(rViewData.GetMarkData()); + const ScAddress& rCurPos = rViewData.GetCurPos(); + ScClipParam& rClipParam = pClipDoc->GetClipParam(); + SCCOL nColSize = rClipParam.getPasteColSize(); + SCROW nRowSize = rClipParam.getPasteRowSize(*pClipDoc, /*bIncludeFiltered*/false); + + if (bTranspose) + { + if (static_cast<SCROW>(rCurPos.Col()) + nRowSize-1 > static_cast<SCROW>(pClipDoc->MaxCol())) + { + ErrorMessage(STR_PASTE_FULL); + return false; + } + + ScDocumentUniquePtr pTransClip(new ScDocument(SCDOCMODE_CLIP)); + pClipDoc->TransposeClip(pTransClip.get(), nFlags, bAsLink, /*bIncludeFiltered*/false); + pClipDoc = pTransClip.release(); + SCCOL nTempColSize = nColSize; + nColSize = static_cast<SCCOL>(nRowSize); + nRowSize = static_cast<SCROW>(nTempColSize); + } + + if (!rDoc.ValidCol(rCurPos.Col()+nColSize-1) || !rDoc.ValidRow(rCurPos.Row()+nRowSize-1)) + { + ErrorMessage(STR_PASTE_FULL); + return false; + } + + // Determine the first and last selected sheet numbers. + SCTAB nTab1 = aMark.GetFirstSelected(); + SCTAB nTab2 = aMark.GetLastSelected(); + + ScDocShellModificator aModificator(*pDocSh); + + // For multi-selection paste, we don't support cell duplication for larger + // destination range. In case the destination is marked, we reset it to + // the clip size. + ScRange aMarkedRange(rCurPos.Col(), rCurPos.Row(), nTab1, + rCurPos.Col()+nColSize-1, rCurPos.Row()+nRowSize-1, nTab2); + + // Extend the marked range to account for filtered rows in the destination + // area. + if (ScViewUtil::HasFiltered(aMarkedRange, rDoc)) + { + if (!ScViewUtil::FitToUnfilteredRows(aMarkedRange, rDoc, nRowSize)) + return false; + } + + bool bAskIfNotEmpty = + bAllowDialogs && (nFlags & InsertDeleteFlags::CONTENTS) && + nFunction == ScPasteFunc::NONE && SC_MOD()->GetInputOptions().GetReplaceCellsWarn(); + + if (bAskIfNotEmpty) + { + ScRangeList aTestRanges(aMarkedRange); + if (!checkDestRangeForOverwrite(aTestRanges, rDoc, aMark, GetViewData().GetDialogParent())) + return false; + } + + aMark.SetMarkArea(aMarkedRange); + MarkRange(aMarkedRange); + + bool bInsertCells = (eMoveMode != INS_NONE); + if (bInsertCells) + { + if (!InsertCells(eMoveMode, rDoc.IsUndoEnabled(), true)) + return false; + } + + // TODO: position this call better for performance. + ResetAutoSpellForContentChange(); + + bool bRowInfo = ( aMarkedRange.aStart.Col()==0 && aMarkedRange.aEnd.Col()==pClipDoc->MaxCol() ); + ScDocumentUniquePtr pUndoDoc; + if (rDoc.IsUndoEnabled()) + { + pUndoDoc.reset(new ScDocument(SCDOCMODE_UNDO)); + pUndoDoc->InitUndoSelected(rDoc, aMark, false, bRowInfo); + rDoc.CopyToDocument(aMarkedRange, nUndoFlags, false, *pUndoDoc, &aMark); + } + + ScDocumentUniquePtr pMixDoc; + if ( bSkipEmptyCells || nFunction != ScPasteFunc::NONE) + { + if ( nFlags & InsertDeleteFlags::CONTENTS ) + { + pMixDoc.reset(new ScDocument(SCDOCMODE_UNDO)); + pMixDoc->InitUndoSelected(rDoc, aMark); + rDoc.CopyToDocument(aMarkedRange, InsertDeleteFlags::CONTENTS, false, *pMixDoc, &aMark); + } + } + + /* Make draw layer and start drawing undo. + - Needed before AdjustBlockHeight to track moved drawing objects. + - Needed before rDoc.CopyFromClip to track inserted note caption objects. + */ + if (nFlags & InsertDeleteFlags::OBJECTS) + pDocSh->MakeDrawLayer(); + if (rDoc.IsUndoEnabled()) + rDoc.BeginDrawUndo(); + + InsertDeleteFlags nCopyFlags = nFlags & ~InsertDeleteFlags::OBJECTS; + // in case of transpose, links were added in TransposeClip() + if (bAsLink && bTranspose) + nCopyFlags |= InsertDeleteFlags::FORMULA; + rDoc.CopyMultiRangeFromClip(rCurPos, aMark, nCopyFlags, pClipDoc, true, bAsLink && !bTranspose, + /*bIncludeFiltered*/false, bSkipEmptyCells); + + if (pMixDoc) + rDoc.MixDocument(aMarkedRange, nFunction, bSkipEmptyCells, *pMixDoc); + + AdjustBlockHeight(); // update row heights before pasting objects + + if (nFlags & InsertDeleteFlags::OBJECTS) + { + // Paste the drawing objects after the row heights have been updated. + rDoc.CopyMultiRangeFromClip(rCurPos, aMark, InsertDeleteFlags::OBJECTS, pClipDoc, true, + false, /*bIncludeFiltered*/false, true); + } + + if (bRowInfo) + pDocSh->PostPaint(aMarkedRange.aStart.Col(), aMarkedRange.aStart.Row(), nTab1, pClipDoc->MaxCol(), pClipDoc->MaxRow(), nTab1, PaintPartFlags::Grid|PaintPartFlags::Left); + else + { + ScRange aTmp = aMarkedRange; + aTmp.aStart.SetTab(nTab1); + aTmp.aEnd.SetTab(nTab1); + pDocSh->PostPaint(aTmp, PaintPartFlags::Grid); + } + + if (rDoc.IsUndoEnabled()) + { + SfxUndoManager* pUndoMgr = pDocSh->GetUndoManager(); + OUString aUndo = ScResId( + pClipDoc->IsCutMode() ? STR_UNDO_CUT : STR_UNDO_COPY); + pUndoMgr->EnterListAction(aUndo, aUndo, 0, GetViewData().GetViewShell()->GetViewShellId()); + + ScUndoPasteOptions aOptions; // store options for repeat + aOptions.nFunction = nFunction; + aOptions.bSkipEmptyCells = bSkipEmptyCells; + aOptions.bTranspose = bTranspose; + aOptions.bAsLink = bAsLink; + aOptions.eMoveMode = eMoveMode; + + std::unique_ptr<ScUndoPaste> pUndo(new ScUndoPaste(pDocSh, + aMarkedRange, aMark, std::move(pUndoDoc), nullptr, nFlags|nUndoFlags, nullptr, false, &aOptions)); + + if (bInsertCells) + pUndoMgr->AddUndoAction(std::make_unique<ScUndoWrapper>(std::move(pUndo)), true); + else + pUndoMgr->AddUndoAction(std::move(pUndo)); + + pUndoMgr->LeaveListAction(); + } + + aModificator.SetDocumentModified(); + PostPasteFromClip(aMarkedRange, aMark); + return true; +} + +bool ScViewFunc::PasteFromClipToMultiRanges( + InsertDeleteFlags nFlags, ScDocument* pClipDoc, ScPasteFunc nFunction, + bool bSkipEmptyCells, bool bTranspose, bool bAsLink, bool bAllowDialogs, + InsCellCmd eMoveMode, InsertDeleteFlags nUndoFlags ) +{ + if (bTranspose) + { + // We don't allow transpose for this yet. + ErrorMessage(STR_MSSG_PASTEFROMCLIP_0); + return false; + } + + if (eMoveMode != INS_NONE) + { + // We don't allow insertion mode either. Too complicated. + ErrorMessage(STR_MSSG_PASTEFROMCLIP_0); + return false; + } + + ScViewData& rViewData = GetViewData(); + ScClipParam& rClipParam = pClipDoc->GetClipParam(); + if (rClipParam.mbCutMode) + { + // No cut and paste with this, please. + ErrorMessage(STR_MSSG_PASTEFROMCLIP_0); + return false; + } + + const ScAddress& rCurPos = rViewData.GetCurPos(); + ScDocument& rDoc = rViewData.GetDocument(); + + ScRange aSrcRange = rClipParam.getWholeRange(); + SCROW nRowSize = aSrcRange.aEnd.Row() - aSrcRange.aStart.Row() + 1; + SCCOL nColSize = aSrcRange.aEnd.Col() - aSrcRange.aStart.Col() + 1; + + if (!rDoc.ValidCol(rCurPos.Col()+nColSize-1) || !rDoc.ValidRow(rCurPos.Row()+nRowSize-1)) + { + ErrorMessage(STR_PASTE_FULL); + return false; + } + + ScMarkData aMark(rViewData.GetMarkData()); + + ScRangeList aRanges; + aMark.MarkToSimple(); + aMark.FillRangeListWithMarks(&aRanges, false); + if (!ScClipUtil::CheckDestRanges(rDoc, nColSize, nRowSize, aMark, aRanges)) + { + ErrorMessage(STR_MSSG_PASTEFROMCLIP_0); + return false; + } + + ScDocShell* pDocSh = rViewData.GetDocShell(); + + ScDocShellModificator aModificator(*pDocSh); + + bool bAskIfNotEmpty = + bAllowDialogs && (nFlags & InsertDeleteFlags::CONTENTS) && + nFunction == ScPasteFunc::NONE && SC_MOD()->GetInputOptions().GetReplaceCellsWarn(); + + if (bAskIfNotEmpty) + { + if (!checkDestRangeForOverwrite(aRanges, rDoc, aMark, GetViewData().GetDialogParent())) + return false; + } + + // TODO: position this call better for performance. + ResetAutoSpellForContentChange(); + + ScDocumentUniquePtr pUndoDoc; + if (rDoc.IsUndoEnabled()) + { + pUndoDoc.reset(new ScDocument(SCDOCMODE_UNDO)); + pUndoDoc->InitUndoSelected(rDoc, aMark); + for (size_t i = 0, n = aRanges.size(); i < n; ++i) + { + rDoc.CopyToDocument( + aRanges[i], nUndoFlags, false, *pUndoDoc, &aMark); + } + } + + ScDocumentUniquePtr pMixDoc; + if (bSkipEmptyCells || nFunction != ScPasteFunc::NONE) + { + if (nFlags & InsertDeleteFlags::CONTENTS) + { + pMixDoc.reset(new ScDocument(SCDOCMODE_UNDO)); + pMixDoc->InitUndoSelected(rDoc, aMark); + for (size_t i = 0, n = aRanges.size(); i < n; ++i) + { + rDoc.CopyToDocument( + aRanges[i], InsertDeleteFlags::CONTENTS, false, *pMixDoc, &aMark); + } + } + } + + if (nFlags & InsertDeleteFlags::OBJECTS) + pDocSh->MakeDrawLayer(); + if (rDoc.IsUndoEnabled()) + rDoc.BeginDrawUndo(); + + // First, paste everything but the drawing objects. + for (size_t i = 0, n = aRanges.size(); i < n; ++i) + { + rDoc.CopyFromClip( + aRanges[i], aMark, (nFlags & ~InsertDeleteFlags::OBJECTS), nullptr, pClipDoc, + false, false, true, bSkipEmptyCells); + } + + if (pMixDoc) + { + for (size_t i = 0, n = aRanges.size(); i < n; ++i) + rDoc.MixDocument(aRanges[i], nFunction, bSkipEmptyCells, *pMixDoc); + } + + AdjustBlockHeight(); // update row heights before pasting objects + + // Then paste the objects. + if (nFlags & InsertDeleteFlags::OBJECTS) + { + for (size_t i = 0, n = aRanges.size(); i < n; ++i) + { + rDoc.CopyFromClip( + aRanges[i], aMark, InsertDeleteFlags::OBJECTS, nullptr, pClipDoc, + false, false, true, bSkipEmptyCells); + } + } + + // Refresh the range that includes all pasted ranges. We only need to + // refresh the current sheet. + PaintPartFlags nPaint = PaintPartFlags::Grid; + bool bRowInfo = (aSrcRange.aStart.Col()==0 && aSrcRange.aEnd.Col()==pClipDoc->MaxCol()); + if (bRowInfo) + nPaint |= PaintPartFlags::Left; + pDocSh->PostPaint(aRanges, nPaint); + + if (rDoc.IsUndoEnabled()) + { + SfxUndoManager* pUndoMgr = pDocSh->GetUndoManager(); + OUString aUndo = ScResId( + pClipDoc->IsCutMode() ? STR_UNDO_CUT : STR_UNDO_COPY); + pUndoMgr->EnterListAction(aUndo, aUndo, 0, GetViewData().GetViewShell()->GetViewShellId()); + + ScUndoPasteOptions aOptions; // store options for repeat + aOptions.nFunction = nFunction; + aOptions.bSkipEmptyCells = bSkipEmptyCells; + aOptions.bTranspose = bTranspose; + aOptions.bAsLink = bAsLink; + aOptions.eMoveMode = eMoveMode; + + + pUndoMgr->AddUndoAction( + std::make_unique<ScUndoPaste>( + pDocSh, aRanges, aMark, std::move(pUndoDoc), nullptr, nFlags|nUndoFlags, nullptr, false, &aOptions)); + pUndoMgr->LeaveListAction(); + } + + aModificator.SetDocumentModified(); + PostPasteFromClip(aRanges, aMark); + + return false; +} + +void ScViewFunc::PostPasteFromClip(const ScRangeList& rPasteRanges, const ScMarkData& rMark) +{ + ScViewData& rViewData = GetViewData(); + ScDocShell* pDocSh = rViewData.GetDocShell(); + pDocSh->UpdateOle(rViewData); + + SelectionChanged(true); + + ScModelObj* pModelObj = pDocSh->GetModel(); + + ScRangeList aChangeRanges; + for (size_t i = 0, n = rPasteRanges.size(); i < n; ++i) + { + const ScRange& r = rPasteRanges[i]; + for (const auto& rTab : rMark) + { + ScRange aChangeRange(r); + aChangeRange.aStart.SetTab(rTab); + aChangeRange.aEnd.SetTab(rTab); + aChangeRanges.push_back(aChangeRange); + } + } + + if (HelperNotifyChanges::getMustPropagateChangesModel(pModelObj)) + HelperNotifyChanges::Notify(*pModelObj, aChangeRanges, "paste"); + else if (pModelObj) + HelperNotifyChanges::Notify(*pModelObj, aChangeRanges, "data-area-invalidate"); +} + +// D R A G A N D D R O P + +// inside the doc + +bool ScViewFunc::MoveBlockTo( const ScRange& rSource, const ScAddress& rDestPos, + bool bCut ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + HideAllCursors(); + + ResetAutoSpellForContentChange(); + + bool bSuccess = true; + SCTAB nDestTab = rDestPos.Tab(); + const ScMarkData& rMark = GetViewData().GetMarkData(); + if ( rSource.aStart.Tab() == nDestTab && rSource.aEnd.Tab() == nDestTab && rMark.GetSelectCount() > 1 ) + { + // moving within one table and several tables selected -> apply to all selected tables + + OUString aUndo = ScResId( bCut ? STR_UNDO_MOVE : STR_UNDO_COPY ); + pDocSh->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, GetViewData().GetViewShell()->GetViewShellId() ); + + // collect ranges of consecutive selected tables + + ScRange aLocalSource = rSource; + ScAddress aLocalDest = rDestPos; + SCTAB nTabCount = pDocSh->GetDocument().GetTableCount(); + SCTAB nStartTab = 0; + while ( nStartTab < nTabCount && bSuccess ) + { + while ( nStartTab < nTabCount && !rMark.GetTableSelect(nStartTab) ) + ++nStartTab; + if ( nStartTab < nTabCount ) + { + SCTAB nEndTab = nStartTab; + while ( nEndTab+1 < nTabCount && rMark.GetTableSelect(nEndTab+1) ) + ++nEndTab; + + aLocalSource.aStart.SetTab( nStartTab ); + aLocalSource.aEnd.SetTab( nEndTab ); + aLocalDest.SetTab( nStartTab ); + + bSuccess = pDocSh->GetDocFunc().MoveBlock( + aLocalSource, aLocalDest, bCut, true/*bRecord*/, true/*bPaint*/, true/*bApi*/ ); + + nStartTab = nEndTab + 1; + } + } + + pDocSh->GetUndoManager()->LeaveListAction(); + } + else + { + // move the block as specified + bSuccess = pDocSh->GetDocFunc().MoveBlock( + rSource, rDestPos, bCut, true/*bRecord*/, true/*bPaint*/, true/*bApi*/ ); + } + + ShowAllCursors(); + if (bSuccess) + { + // mark destination range + ScAddress aDestEnd( + rDestPos.Col() + rSource.aEnd.Col() - rSource.aStart.Col(), + rDestPos.Row() + rSource.aEnd.Row() - rSource.aStart.Row(), + nDestTab ); + + bool bIncludeFiltered = bCut; + if ( !bIncludeFiltered ) + { + // find number of non-filtered rows + SCROW nPastedCount = pDocSh->GetDocument().CountNonFilteredRows( + rSource.aStart.Row(), rSource.aEnd.Row(), rSource.aStart.Tab()); + + if ( nPastedCount == 0 ) + nPastedCount = 1; + aDestEnd.SetRow( rDestPos.Row() + nPastedCount - 1 ); + } + + MarkRange( ScRange( rDestPos, aDestEnd ), false ); //! sal_False ??? + + pDocSh->UpdateOle(GetViewData()); + SelectionChanged(); + } + return bSuccess; +} + +// link inside the doc + +bool ScViewFunc::LinkBlock( const ScRange& rSource, const ScAddress& rDestPos ) +{ + // check overlapping + + if ( rSource.aStart.Tab() == rDestPos.Tab() ) + { + SCCOL nDestEndCol = rDestPos.Col() + ( rSource.aEnd.Col() - rSource.aStart.Col() ); + SCROW nDestEndRow = rDestPos.Row() + ( rSource.aEnd.Row() - rSource.aStart.Row() ); + + if ( rSource.aStart.Col() <= nDestEndCol && rDestPos.Col() <= rSource.aEnd.Col() && + rSource.aStart.Row() <= nDestEndRow && rDestPos.Row() <= rSource.aEnd.Row() ) + { + return false; + } + } + + // run with paste + + ScDocument& rDoc = GetViewData().GetDocument(); + ScDocumentUniquePtr pClipDoc(new ScDocument( SCDOCMODE_CLIP )); + rDoc.CopyTabToClip( rSource.aStart.Col(), rSource.aStart.Row(), + rSource.aEnd.Col(), rSource.aEnd.Row(), + rSource.aStart.Tab(), pClipDoc.get() ); + + // mark destination area (set cursor, no marks) + + if ( GetViewData().GetTabNo() != rDestPos.Tab() ) + SetTabNo( rDestPos.Tab() ); + + MoveCursorAbs( rDestPos.Col(), rDestPos.Row(), SC_FOLLOW_NONE, false, false ); + + // Paste + + PasteFromClip( InsertDeleteFlags::ALL, pClipDoc.get(), ScPasteFunc::NONE, false, false, true ); // as a link + + return true; +} + +void ScViewFunc::DataFormPutData( SCROW nCurrentRow , + SCROW nStartRow , SCCOL nStartCol , + SCROW nEndRow , SCCOL nEndCol , + std::vector<std::unique_ptr<ScDataFormFragment>>& rEdits, + sal_uInt16 aColLength ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScMarkData& rMark = GetViewData().GetMarkData(); + ScDocShellModificator aModificator( *pDocSh ); + SfxUndoManager* pUndoMgr = pDocSh->GetUndoManager(); + + const bool bRecord( rDoc.IsUndoEnabled()); + ScDocumentUniquePtr pUndoDoc; + ScDocumentUniquePtr pRedoDoc; + std::unique_ptr<ScRefUndoData> pUndoData; + SCTAB nTab = GetViewData().GetTabNo(); + SCTAB nStartTab = nTab; + SCTAB nEndTab = nTab; + + { + ScChangeTrack* pChangeTrack = rDoc.GetChangeTrack(); + if ( pChangeTrack ) + pChangeTrack->ResetLastCut(); // no more cut-mode + } + ScRange aUserRange( nStartCol, nCurrentRow, nStartTab, nEndCol, nCurrentRow, nEndTab ); + bool bColInfo = ( nStartRow==0 && nEndRow==rDoc.MaxRow() ); + bool bRowInfo = ( nStartCol==0 && nEndCol==rDoc.MaxCol() ); + SCCOL nUndoEndCol = nStartCol+aColLength-1; + SCROW nUndoEndRow = nCurrentRow; + + if ( bRecord ) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndoSelected( rDoc , rMark , bColInfo , bRowInfo ); + rDoc.CopyToDocument( aUserRange , InsertDeleteFlags::VALUE , false, *pUndoDoc ); + } + sal_uInt16 nExtFlags = 0; + pDocSh->UpdatePaintExt( nExtFlags, nStartCol, nStartRow, nStartTab , nEndCol, nEndRow, nEndTab ); // content before the change + rDoc.BeginDrawUndo(); + + for(sal_uInt16 i = 0; i < aColLength; i++) + { + if (rEdits[i] != nullptr) + { + OUString aFieldName = rEdits[i]->m_xEdit->get_text(); + rDoc.SetString( nStartCol + i, nCurrentRow, nTab, aFieldName ); + } + } + pDocSh->UpdatePaintExt( nExtFlags, nStartCol, nCurrentRow, nStartTab, nEndCol, nCurrentRow, nEndTab ); // content after the change + std::unique_ptr<SfxUndoAction> pUndo( new ScUndoDataForm( pDocSh, + nStartCol, nCurrentRow, nStartTab, + nUndoEndCol, nUndoEndRow, nEndTab, rMark, + std::move(pUndoDoc), std::move(pRedoDoc), + std::move(pUndoData) ) ); + pUndoMgr->AddUndoAction( std::make_unique<ScUndoWrapper>( std::move(pUndo) ), true ); + + PaintPartFlags nPaint = PaintPartFlags::Grid; + if (bColInfo) + { + nPaint |= PaintPartFlags::Top; + nUndoEndCol = rDoc.MaxCol(); // just for drawing ! + } + if (bRowInfo) + { + nPaint |= PaintPartFlags::Left; + nUndoEndRow = rDoc.MaxRow(); // just for drawing ! + } + + pDocSh->PostPaint( + ScRange(nStartCol, nCurrentRow, nStartTab, nUndoEndCol, nUndoEndRow, nEndTab), + nPaint, nExtFlags); + pDocSh->UpdateOle(GetViewData()); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/viewfun4.cxx b/sc/source/ui/view/viewfun4.cxx new file mode 100644 index 0000000000..d75541b302 --- /dev/null +++ b/sc/source/ui/view/viewfun4.cxx @@ -0,0 +1,791 @@ +/* -*- 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 <config_features.h> + +#include <memory> +#include <editeng/eeitem.hxx> + +#include <editeng/editobj.hxx> +#include <editeng/editstat.hxx> +#include <editeng/editview.hxx> +#include <editeng/flditem.hxx> +#include <sot/storage.hxx> +#include <svx/hlnkitem.hxx> +#include <editeng/unolingu.hxx> + +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/docfile.hxx> +#include <sfx2/docfilt.hxx> +#include <sfx2/fcontnr.hxx> +#include <svtools/langtab.hxx> +#include <vcl/graphicfilter.hxx> +#include <svl/stritem.hxx> +#include <vcl/transfer.hxx> +#include <svl/urlbmk.hxx> +#include <svl/sharedstringpool.hxx> +#include <vcl/svapp.hxx> +#include <vcl/weld.hxx> +#include <avmedia/mediawindow.hxx> +#include <osl/diagnose.h> + +#include <comphelper/propertyvalue.hxx> +#include <comphelper/storagehelper.hxx> + +#include <viewfunc.hxx> +#include <docsh.hxx> +#include <document.hxx> +#include <globstr.hrc> +#include <global.hxx> +#include <scresid.hxx> +#include <undoblk.hxx> +#include <undocell.hxx> +#include <formulacell.hxx> +#include <scmod.hxx> +#include <spelleng.hxx> +#include <patattr.hxx> +#include <tabvwsh.hxx> +#include <impex.hxx> +#include <editutil.hxx> +#include <editable.hxx> +#include <dociter.hxx> +#include <reffind.hxx> +#include <compiler.hxx> +#include <tokenarray.hxx> +#include <refupdatecontext.hxx> +#include <gridwin.hxx> +#include <refundo.hxx> + +using namespace com::sun::star; + +void ScViewFunc::PasteRTF( SCCOL nStartCol, SCROW nStartRow, + const css::uno::Reference< css::datatransfer::XTransferable >& rxTransferable ) +{ + TransferableDataHelper aDataHelper( rxTransferable ); + if ( aDataHelper.HasFormat( SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT ) ) + { + HideAllCursors(); + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + const bool bRecord (rDoc.IsUndoEnabled()); + + const ScPatternAttr* pPattern = rDoc.GetPattern( nStartCol, nStartRow, nTab ); + std::optional<ScTabEditEngine> pEngine(std::in_place, *pPattern, rDoc.GetEnginePool(), &rDoc ); + pEngine->EnableUndo( false ); + + vcl::Window* pActWin = GetActiveWin(); + if (pActWin) + { + pEngine->SetPaperSize(Size(100000,100000)); + ScopedVclPtrInstance< vcl::Window > aWin( pActWin ); + EditView aEditView( &*pEngine, aWin.get() ); + aEditView.SetOutputArea(tools::Rectangle(0,0,100000,100000)); + + // same method now for clipboard or drag&drop + // mba: clipboard always must contain absolute URLs (could be from alien source) + aEditView.InsertText( rxTransferable, OUString(), true ); + } + + sal_Int32 nParCnt = pEngine->GetParagraphCount(); + if (nParCnt) + { + SCROW nEndRow = nStartRow + static_cast<SCROW>(nParCnt) - 1; + if (nEndRow > rDoc.MaxRow()) + nEndRow = rDoc.MaxRow(); + + ScDocumentUniquePtr pUndoDoc; + if (bRecord) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nTab, nTab ); + rDoc.CopyToDocument( nStartCol,nStartRow,nTab, nStartCol,nEndRow,nTab, InsertDeleteFlags::ALL, false, *pUndoDoc ); + } + + SCROW nRow = nStartRow; + + // Temporarily turn off undo generation for this lot + bool bUndoEnabled = rDoc.IsUndoEnabled(); + rDoc.EnableUndo( false ); + for( sal_Int32 n = 0; n < nParCnt; n++ ) + { + std::unique_ptr<EditTextObject> pObject(pEngine->CreateTextObject(n)); + EnterData(nStartCol, nRow, nTab, *pObject, true); + if( ++nRow > rDoc.MaxRow() ) + break; + } + rDoc.EnableUndo(bUndoEnabled); + + if (bRecord) + { + ScDocumentUniquePtr pRedoDoc(new ScDocument( SCDOCMODE_UNDO )); + pRedoDoc->InitUndo( rDoc, nTab, nTab ); + rDoc.CopyToDocument( nStartCol,nStartRow,nTab, nStartCol,nEndRow,nTab, InsertDeleteFlags::ALL|InsertDeleteFlags::NOCAPTIONS, false, *pRedoDoc ); + + ScRange aMarkRange(nStartCol, nStartRow, nTab, nStartCol, nEndRow, nTab); + ScMarkData aDestMark(rDoc.GetSheetLimits()); + aDestMark.SetMarkArea( aMarkRange ); + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoPaste>( pDocSh, aMarkRange, aDestMark, + std::move(pUndoDoc), std::move(pRedoDoc), InsertDeleteFlags::ALL, nullptr)); + } + } + + pEngine.reset(); + + ShowAllCursors(); + } + else + { + HideAllCursors(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScImportExport aImpEx( pDocSh->GetDocument(), + ScAddress( nStartCol, nStartRow, GetViewData().GetTabNo() ) ); + + OUString aStr; + tools::SvRef<SotTempStream> xStream; + if ( aDataHelper.GetSotStorageStream( SotClipboardFormatId::RTF, xStream ) && xStream.is() ) + // mba: clipboard always must contain absolute URLs (could be from alien source) + aImpEx.ImportStream( *xStream, OUString(), SotClipboardFormatId::RTF ); + else if ( aDataHelper.GetString( SotClipboardFormatId::RTF, aStr ) ) + aImpEx.ImportString( aStr, SotClipboardFormatId::RTF ); + else if ( aDataHelper.GetSotStorageStream( SotClipboardFormatId::RICHTEXT, xStream ) && xStream.is() ) + aImpEx.ImportStream( *xStream, OUString(), SotClipboardFormatId::RICHTEXT ); + else if ( aDataHelper.GetString( SotClipboardFormatId::RICHTEXT, aStr ) ) + aImpEx.ImportString( aStr, SotClipboardFormatId::RICHTEXT ); + + AdjustRowHeight( nStartRow, aImpEx.GetRange().aEnd.Row(), true ); + pDocSh->UpdateOle(GetViewData()); + ShowAllCursors(); + } +} +void ScViewFunc::DoRefConversion() +{ + ScDocument& rDoc = GetViewData().GetDocument(); + ScMarkData& rMark = GetViewData().GetMarkData(); + SCTAB nTabCount = rDoc.GetTableCount(); + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + + ScRange aMarkRange; + rMark.MarkToSimple(); + bool bMulti = rMark.IsMultiMarked(); + if (bMulti) + aMarkRange = rMark.GetMultiMarkArea(); + else if (rMark.IsMarked()) + aMarkRange = rMark.GetMarkArea(); + else + { + aMarkRange = ScRange( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ); + } + ScEditableTester aTester( rDoc, aMarkRange.aStart.Col(), aMarkRange.aStart.Row(), + aMarkRange.aEnd.Col(), aMarkRange.aEnd.Row(),rMark ); + if (!aTester.IsEditable()) + { + ErrorMessage(aTester.GetMessageId()); + return; + } + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + bool bOk = false; + + ScDocumentUniquePtr pUndoDoc; + if (bRecord) + { + pUndoDoc.reset( new ScDocument( SCDOCMODE_UNDO ) ); + SCTAB nTab = aMarkRange.aStart.Tab(); + pUndoDoc->InitUndo( rDoc, nTab, nTab ); + + if ( rMark.GetSelectCount() > 1 ) + { + for (const auto& rTab : rMark) + if ( rTab != nTab ) + pUndoDoc->AddUndoTab( rTab, rTab ); + } + ScRange aCopyRange = aMarkRange; + aCopyRange.aStart.SetTab(0); + aCopyRange.aEnd.SetTab(nTabCount-1); + rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ALL, bMulti, *pUndoDoc, &rMark ); + } + + ScRangeListRef xRanges; + GetViewData().GetMultiArea( xRanges ); + size_t nCount = xRanges->size(); + + for (const SCTAB& i : rMark) + { + for (size_t j = 0; j < nCount; ++j) + { + ScRange aRange = (*xRanges)[j]; + aRange.aStart.SetTab(i); + aRange.aEnd.SetTab(i); + ScCellIterator aIter( rDoc, aRange ); + for (bool bHas = aIter.first(); bHas; bHas = aIter.next()) + { + if (aIter.getType() != CELLTYPE_FORMULA) + continue; + + ScFormulaCell* pCell = aIter.getFormulaCell(); + ScMatrixMode eMatrixMode = pCell->GetMatrixFlag(); + if (eMatrixMode == ScMatrixMode::Reference) + continue; + + OUString aOld = pCell->GetFormula(); + sal_Int32 nLen = aOld.getLength(); + if (eMatrixMode == ScMatrixMode::Formula) + { + assert(nLen >= 2 && aOld[0] == '{' && aOld[nLen-1] == '}'); + nLen -= 2; + aOld = aOld.copy( 1, nLen); + } + ScRefFinder aFinder( aOld, aIter.GetPos(), rDoc, rDoc.GetAddressConvention() ); + aFinder.ToggleRel( 0, nLen ); + if (aFinder.GetFound()) + { + ScAddress aPos = pCell->aPos; + const OUString& aNew = aFinder.GetText(); + ScCompiler aComp( rDoc, aPos, rDoc.GetGrammar()); + std::unique_ptr<ScTokenArray> pArr(aComp.CompileString(aNew)); + ScFormulaCell* pNewCell = + new ScFormulaCell( + rDoc, aPos, *pArr, formula::FormulaGrammar::GRAM_DEFAULT, eMatrixMode); + + rDoc.SetFormulaCell(aPos, pNewCell); + bOk = true; + } + } + } + } + if (bRecord) + { + ScDocumentUniquePtr pRedoDoc(new ScDocument( SCDOCMODE_UNDO )); + SCTAB nTab = aMarkRange.aStart.Tab(); + pRedoDoc->InitUndo( rDoc, nTab, nTab ); + + if ( rMark.GetSelectCount() > 1 ) + { + for (const auto& rTab : rMark) + if ( rTab != nTab ) + pRedoDoc->AddUndoTab( rTab, rTab ); + } + ScRange aCopyRange = aMarkRange; + aCopyRange.aStart.SetTab(0); + aCopyRange.aEnd.SetTab(nTabCount-1); + rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ALL, bMulti, *pRedoDoc, &rMark ); + + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoRefConversion>( pDocSh, + aMarkRange, rMark, std::move(pUndoDoc), std::move(pRedoDoc), bMulti) ); + } + + pDocSh->PostPaint( aMarkRange, PaintPartFlags::Grid ); + pDocSh->UpdateOle(GetViewData()); + pDocSh->SetDocumentModified(); + CellContentChanged(); + + if (!bOk) + ErrorMessage(STR_ERR_NOREF); +} + +// Thesaurus - Undo ok +void ScViewFunc::DoThesaurus() +{ + SCCOL nCol; + SCROW nRow; + SCTAB nTab; + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScMarkData& rMark = GetViewData().GetMarkData(); + ScSplitPos eWhich = GetViewData().GetActivePart(); + EESpellState eState; + EditView* pEditView = nullptr; + std::unique_ptr<ESelection> pEditSel; + std::unique_ptr<ScEditEngineDefaulter> pThesaurusEngine; + bool bIsEditMode = GetViewData().HasEditView(eWhich); + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + if (bIsEditMode) // edit mode active + { + GetViewData().GetEditView(eWhich, pEditView, nCol, nRow); + pEditSel.reset(new ESelection(pEditView->GetSelection())); + SC_MOD()->InputEnterHandler(); + GetViewData().GetBindings().Update(); // otherwise the Sfx becomes mixed-up... + } + else + { + nCol = GetViewData().GetCurX(); + nRow = GetViewData().GetCurY(); + } + nTab = GetViewData().GetTabNo(); + + ScAddress aPos(nCol, nRow, nTab); + ScEditableTester aTester( rDoc, nCol, nRow, nCol, nRow, rMark ); + if (!aTester.IsEditable()) + { + ErrorMessage(aTester.GetMessageId()); + return; + } + + ScCellValue aOldText; + aOldText.assign(rDoc, aPos); + if (aOldText.getType() != CELLTYPE_STRING && aOldText.getType() != CELLTYPE_EDIT) + { + ErrorMessage(STR_THESAURUS_NO_STRING); + return; + } + + uno::Reference<linguistic2::XSpellChecker1> xSpeller = LinguMgr::GetSpellChecker(); + + pThesaurusEngine.reset(new ScEditEngineDefaulter(rDoc.GetEnginePool())); + pThesaurusEngine->SetEditTextObjectPool( rDoc.GetEditPool() ); + pThesaurusEngine->SetRefDevice(GetViewData().GetActiveWin()->GetOutDev()); + pThesaurusEngine->SetSpeller(xSpeller); + MakeEditView(pThesaurusEngine.get(), nCol, nRow ); + SfxItemSet aEditDefaults(pThesaurusEngine->GetEmptyItemSet()); + const ScPatternAttr* pPattern = rDoc.GetPattern(nCol, nRow, nTab); + if (pPattern) + { + pPattern->FillEditItemSet( &aEditDefaults ); + pThesaurusEngine->SetDefaults( aEditDefaults ); + } + + if (aOldText.getType() == CELLTYPE_EDIT) + pThesaurusEngine->SetTextCurrentDefaults(*aOldText.getEditText()); + else + pThesaurusEngine->SetTextCurrentDefaults(aOldText.getString(rDoc)); + + pEditView = GetViewData().GetEditView(GetViewData().GetActivePart()); + if (pEditSel) + pEditView->SetSelection(*pEditSel); + else + pEditView->SetSelection(ESelection(0,0,0,0)); + + pThesaurusEngine->ClearModifyFlag(); + + // language is now in EditEngine attributes -> no longer passed to StartThesaurus + + eState = pEditView->StartThesaurus(GetViewData().GetDialogParent()); + OSL_ENSURE(eState != EESpellState::NoSpeller, "No SpellChecker"); + + if (eState == EESpellState::ErrorFound) // should happen later through Wrapper! + { + LanguageType eLnge = ScViewUtil::GetEffLanguage( rDoc, ScAddress( nCol, nRow, nTab ) ); + OUString aErr = SvtLanguageTable::GetLanguageString(eLnge) + ScResId( STR_SPELLING_NO_LANG ); + + std::unique_ptr<weld::MessageDialog> xInfoBox(Application::CreateMessageDialog(GetViewData().GetDialogParent(), + VclMessageType::Info, VclButtonsType::Ok, + aErr)); + xInfoBox->run(); + } + if (pThesaurusEngine->IsModified()) + { + ScCellValue aNewText; + + if (aOldText.getType() == CELLTYPE_EDIT) + { + // The cell will own the text object instance. + std::unique_ptr<EditTextObject> pText = pThesaurusEngine->CreateTextObject(); + auto tmp = pText.get(); + if (rDoc.SetEditText(ScAddress(nCol,nRow,nTab), std::move(pText))) + aNewText.set(*tmp); + } + else + { + OUString aStr = pThesaurusEngine->GetText(); + aNewText.set(rDoc.GetSharedStringPool().intern(aStr)); + rDoc.SetString(nCol, nRow, nTab, aStr); + } + + pDocSh->SetDocumentModified(); + if (bRecord) + { + GetViewData().GetDocShell()->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoThesaurus>( + GetViewData().GetDocShell(), nCol, nRow, nTab, aOldText, aNewText)); + } + } + + KillEditView(true); + pDocSh->PostPaintGridAll(); +} + +void ScViewFunc::DoHangulHanjaConversion() +{ + ScConversionParam aConvParam( SC_CONVERSION_HANGULHANJA, LANGUAGE_KOREAN, 0, true ); + DoSheetConversion( aConvParam ); +} + +void ScViewFunc::DoSheetConversion( const ScConversionParam& rConvParam ) +{ + SCCOL nCol; + SCROW nRow; + SCTAB nTab; + ScViewData& rViewData = GetViewData(); + ScDocShell* pDocSh = rViewData.GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScMarkData& rMark = rViewData.GetMarkData(); + ScSplitPos eWhich = rViewData.GetActivePart(); + EditView* pEditView = nullptr; + bool bIsEditMode = rViewData.HasEditView(eWhich); + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + if (bIsEditMode) // edit mode active + { + rViewData.GetEditView(eWhich, pEditView, nCol, nRow); + SC_MOD()->InputEnterHandler(); + } + else + { + nCol = rViewData.GetCurX(); + nRow = rViewData.GetCurY(); + + AlignToCursor( nCol, nRow, SC_FOLLOW_JUMP); + } + nTab = rViewData.GetTabNo(); + + rMark.MarkToMulti(); + bool bMarked = rMark.IsMultiMarked(); + if (bMarked) + { + ScEditableTester aTester( rDoc, rMark ); + if (!aTester.IsEditable()) + { + ErrorMessage(aTester.GetMessageId()); + return; + } + } + + ScDocumentUniquePtr pUndoDoc; + ScDocumentUniquePtr pRedoDoc; + if (bRecord) + { + pUndoDoc.reset( new ScDocument( SCDOCMODE_UNDO ) ); + pUndoDoc->InitUndo( rDoc, nTab, nTab ); + pRedoDoc.reset( new ScDocument( SCDOCMODE_UNDO ) ); + pRedoDoc->InitUndo( rDoc, nTab, nTab ); + + if ( rMark.GetSelectCount() > 1 ) + { + for (const auto& rTab : rMark) + if ( rTab != nTab ) + { + pUndoDoc->AddUndoTab( rTab, rTab ); + pRedoDoc->AddUndoTab( rTab, rTab ); + } + } + } + + // from here no return + + bool bOldEnabled = rDoc.IsIdleEnabled(); + rDoc.EnableIdle(false); // stop online spelling + + // *** create and init the edit engine *** -------------------------------- + + std::unique_ptr<ScConversionEngineBase> pEngine; + switch( rConvParam.GetType() ) + { + case SC_CONVERSION_SPELLCHECK: + pEngine.reset(new ScSpellingEngine( + rDoc.GetEnginePool(), rViewData, pUndoDoc.get(), pRedoDoc.get(), LinguMgr::GetSpellChecker() )); + break; + case SC_CONVERSION_HANGULHANJA: + case SC_CONVERSION_CHINESE_TRANSL: + pEngine.reset(new ScTextConversionEngine( + rDoc.GetEnginePool(), rViewData, rConvParam, pUndoDoc.get(), pRedoDoc.get() )); + break; + default: + OSL_FAIL( "ScViewFunc::DoSheetConversion - unknown conversion type" ); + } + + MakeEditView( pEngine.get(), nCol, nRow ); + pEngine->SetRefDevice( rViewData.GetActiveWin()->GetOutDev() ); + // simulate dummy cell: + pEditView = rViewData.GetEditView( rViewData.GetActivePart() ); + rViewData.SetSpellingView( pEditView ); + tools::Rectangle aRect( Point( 0, 0 ), Point( 0, 0 ) ); + pEditView->SetOutputArea( aRect ); + pEngine->SetControlWord( EEControlBits::USECHARATTRIBS ); + pEngine->EnableUndo( false ); + pEngine->SetPaperSize( aRect.GetSize() ); + pEngine->SetTextCurrentDefaults( OUString() ); + + // *** do the conversion *** ---------------------------------------------- + + pEngine->ClearModifyFlag(); + pEngine->ConvertAll(GetViewData().GetDialogParent(), *pEditView); + + // *** undo/redo *** ------------------------------------------------------ + + if( pEngine->IsAnyModified() ) + { + if (bRecord) + { + SCCOL nNewCol = rViewData.GetCurX(); + SCROW nNewRow = rViewData.GetCurY(); + rViewData.GetDocShell()->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoConversion>( + pDocSh, rMark, + nCol, nRow, nTab, std::move(pUndoDoc), + nNewCol, nNewRow, nTab, std::move(pRedoDoc), rConvParam ) ); + } + + sc::SetFormulaDirtyContext aCxt; + rDoc.SetAllFormulasDirty(aCxt); + + pDocSh->SetDocumentModified(); + } + else + { + pUndoDoc.reset(); + pRedoDoc.reset(); + } + + // *** final cleanup *** -------------------------------------------------- + + rViewData.SetSpellingView( nullptr ); + KillEditView(true); + pEngine.reset(); + pDocSh->PostPaintGridAll(); + rViewData.GetViewShell()->UpdateInputHandler(); + rDoc.EnableIdle(bOldEnabled); +} + +// past from SotClipboardFormatId::FILE items +// is not called directly from Drop, but asynchronously -> dialogs are allowed + +bool ScViewFunc::PasteFile( const Point& rPos, const OUString& rFile, bool bLink ) +{ + INetURLObject aURL; + aURL.SetSmartURL( rFile ); + OUString aStrURL = aURL.GetMainURL( INetURLObject::DecodeMechanism::NONE ); + + // is it a media URL? +#if HAVE_FEATURE_AVMEDIA + if( ::avmedia::MediaWindow::isMediaURL( aStrURL, ""/*TODO?*/ ) ) + { + const SfxStringItem aMediaURLItem( SID_INSERT_AVMEDIA, aStrURL ); + const SfxPoolItemHolder aResult(GetViewData().GetDispatcher().ExecuteList( + SID_INSERT_AVMEDIA, SfxCallMode::SYNCHRON, + { &aMediaURLItem })); + return (nullptr != aResult.getItem()); + } +#endif + + if (!bLink) // for bLink only graphics or URL + { + // 1. can I open the file? + std::shared_ptr<const SfxFilter> pFlt; + + // search only for its own filters, without selection box (as in ScDocumentLoader) + SfxFilterMatcher aMatcher( ScDocShell::Factory().GetFilterContainer()->GetName() ); + SfxMedium aSfxMedium( aStrURL, (StreamMode::READ | StreamMode::SHARE_DENYNONE) ); + // #i73992# GuessFilter no longer calls UseInteractionHandler. + // This is UI, so it can be called here. + aSfxMedium.UseInteractionHandler(true); + ErrCode nErr = aMatcher.GuessFilter( aSfxMedium, pFlt ); + + if ( pFlt && !nErr ) + { + // code stolen from the SFX! + SfxDispatcher &rDispatcher = GetViewData().GetDispatcher(); + SfxStringItem aFileNameItem( SID_FILE_NAME, aStrURL ); + SfxStringItem aFilterItem( SID_FILTER_NAME, pFlt->GetName() ); + // #i69524# add target, as in SfxApplication when the Open dialog is used + SfxStringItem aTargetItem( SID_TARGETNAME, "_default" ); + + // Open Asynchronously, because it can also happen from D&D + // and that is not so good for the MAC... + const SfxPoolItemHolder aResult(rDispatcher.ExecuteList(SID_OPENDOC, + SfxCallMode::ASYNCHRON, + { &aFileNameItem, &aFilterItem, &aTargetItem})); + return (nullptr != aResult.getItem()); + } + } + + // 2. can the file be inserted using the graphics filter? + // (as a link, since the Gallery provides it in this way) + + Graphic aGraphic; + GraphicFilter& rGraphicFilter = GraphicFilter::GetGraphicFilter(); + + if (!rGraphicFilter.ImportGraphic(aGraphic, aURL, + GRFILTER_FORMAT_DONTKNOW )) + { + if ( bLink ) + { + return PasteGraphic( rPos, aGraphic, aStrURL ); + } + else + { + // #i76709# if bLink isn't set, pass empty URL/filter, so a non-linked image is inserted + return PasteGraphic( rPos, aGraphic, OUString() ); + } + } + + if (bLink) // for bLink everything, which is not graphics, as URL + { + tools::Rectangle aRect( rPos, Size(0,0) ); + ScRange aRange = GetViewData().GetDocument(). + GetRange( GetViewData().GetTabNo(), aRect ); + SCCOL nPosX = aRange.aStart.Col(); + SCROW nPosY = aRange.aStart.Row(); + + InsertBookmark( aStrURL, aStrURL, nPosX, nPosY ); + return true; + } + else + { + // 3. can the file be inserted as OLE? + // also non-storages, for instance sounds (#38282#) + uno::Reference < embed::XStorage > xStorage = comphelper::OStorageHelper::GetTemporaryStorage(); + + //TODO/LATER: what about "bLink"? + + uno::Sequence < beans::PropertyValue > aMedium{ comphelper::makePropertyValue("URL", + aStrURL) }; + + comphelper::EmbeddedObjectContainer aCnt( xStorage ); + OUString aName; + uno::Reference < embed::XEmbeddedObject > xObj = aCnt.InsertEmbeddedObject( aMedium, aName ); + if( xObj.is() ) + return PasteObject( rPos, xObj, nullptr ); + + // If an OLE object can't be created, insert a URL button + + GetViewData().GetViewShell()->InsertURLButton( aStrURL, aStrURL, OUString(), &rPos ); + return true; + } +} + +bool ScViewFunc::PasteBookmark( SotClipboardFormatId nFormatId, + const css::uno::Reference< css::datatransfer::XTransferable >& rxTransferable, + SCCOL nPosX, SCROW nPosY ) +{ + INetBookmark aBookmark; + TransferableDataHelper aDataHelper( rxTransferable ); + if ( !aDataHelper.GetINetBookmark( nFormatId, aBookmark ) ) + return false; + + InsertBookmark( aBookmark.GetDescription(), aBookmark.GetURL(), nPosX, nPosY ); + return true; +} + +void ScViewFunc::InsertBookmark( const OUString& rDescription, const OUString& rURL, + SCCOL nPosX, SCROW nPosY, const OUString* pTarget, + bool bTryReplace ) +{ + ScViewData& rViewData = GetViewData(); + if ( rViewData.HasEditView( rViewData.GetActivePart() ) && + nPosX >= rViewData.GetEditStartCol() && nPosX <= rViewData.GetEditEndCol() && + nPosY >= rViewData.GetEditStartRow() && nPosY <= rViewData.GetEditEndRow() ) + { + // insert into the cell which just got edited + + OUString aTargetFrame; + if (pTarget) + aTargetFrame = *pTarget; + rViewData.GetViewShell()->InsertURLField( rDescription, rURL, aTargetFrame ); + return; + } + + // insert into not edited cell + + ScDocument& rDoc = GetViewData().GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + ScAddress aCellPos( nPosX, nPosY, nTab ); + EditEngine aEngine( rDoc.GetEnginePool() ); + + const EditTextObject* pOld = rDoc.GetEditText(aCellPos); + if (pOld) + aEngine.SetText(*pOld); + else + { + OUString aOld = rDoc.GetInputString(nPosX, nPosY, nTab); + if (!aOld.isEmpty()) + aEngine.SetText(aOld); + } + + sal_Int32 nPara = aEngine.GetParagraphCount(); + if (nPara) + --nPara; + sal_Int32 nTxtLen = aEngine.GetTextLen(nPara); + ESelection aInsSel( nPara, nTxtLen, nPara, nTxtLen ); + + if ( bTryReplace && HasBookmarkAtCursor( nullptr ) ) + { + // if called from hyperlink slot and cell contains only a URL, + // replace old URL with new one + + aInsSel = ESelection( 0, 0, 0, 1 ); // replace first character (field) + } + + SvxURLField aField( rURL, rDescription, SvxURLFormat::AppDefault ); + if (pTarget) + aField.SetTargetFrame(*pTarget); + aEngine.QuickInsertField( SvxFieldItem( aField, EE_FEATURE_FIELD ), aInsSel ); + + std::unique_ptr<EditTextObject> pData(aEngine.CreateTextObject()); + EnterData(nPosX, nPosY, nTab, *pData); +} + +bool ScViewFunc::HasBookmarkAtCursor( SvxHyperlinkItem* pContent ) +{ + ScAddress aPos( GetViewData().GetCurX(), GetViewData().GetCurY(), GetViewData().GetTabNo() ); + ScDocument& rDoc = GetViewData().GetDocShell()->GetDocument(); + + const EditTextObject* pData = rDoc.GetEditText(aPos); + if (!pData) + return false; + + if (!pData->IsFieldObject()) + // not a field object. + return false; + + const SvxFieldItem* pFieldItem = pData->GetField(); + if (!pFieldItem) + // doesn't have a field item. + return false; + + const SvxFieldData* pField = pFieldItem->GetField(); + if (!pField) + // doesn't have a field item data. + return false; + + if (pField->GetClassId() != css::text::textfield::Type::URL) + // not a URL field. + return false; + + if (pContent) + { + const SvxURLField* pURLField = static_cast<const SvxURLField*>(pField); + pContent->SetName( pURLField->GetRepresentation() ); + pContent->SetURL( pURLField->GetURL() ); + pContent->SetTargetFrame( pURLField->GetTargetFrame() ); + } + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/viewfun5.cxx b/sc/source/ui/view/viewfun5.cxx new file mode 100644 index 0000000000..1098319506 --- /dev/null +++ b/sc/source/ui/view/viewfun5.cxx @@ -0,0 +1,818 @@ +/* -*- 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 <com/sun/star/embed/XEmbedObjectClipboardCreator.hpp> +#include <com/sun/star/embed/Aspects.hpp> +#include <com/sun/star/embed/MSOLEObjectSystemCreator.hpp> + +#include <svx/unomodel.hxx> +#include <unotools/streamwrap.hxx> + +#include <svx/fmmodel.hxx> +#include <svx/svditer.hxx> +#include <svx/svdobj.hxx> +#include <svx/svdogrp.hxx> +#include <svx/svdouno.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svdpage.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/docfile.hxx> +#include <comphelper/classids.hxx> +#include <sot/formats.hxx> +#include <sot/filelist.hxx> +#include <sot/storage.hxx> +#include <svl/stritem.hxx> +#include <vcl/transfer.hxx> +#include <vcl/graph.hxx> +#include <vcl/TypeSerializer.hxx> +#include <osl/thread.h> +#include <o3tl/unit_conversion.hxx> +#include <o3tl/string_view.hxx> + +#include <comphelper/automationinvokedzone.hxx> +#include <comphelper/lok.hxx> +#include <comphelper/processfactory.hxx> +#include <comphelper/storagehelper.hxx> +#include <comphelper/string.hxx> + +#include <viewfunc.hxx> +#include <docsh.hxx> +#include <drawview.hxx> +#include <impex.hxx> +#include <dbdata.hxx> +#include <sc.hrc> +#include <filter.hxx> +#include <globstr.hrc> +#include <global.hxx> +#include <scextopt.hxx> +#include <tabvwsh.hxx> +#include <compiler.hxx> +#include <scmod.hxx> + +#include <asciiopt.hxx> +#include <scabstdlg.hxx> +#include <clipparam.hxx> +#include <markdata.hxx> +#include <sfx2/frame.hxx> +#include <svx/dbaexchange.hxx> +#include <memory> + +using namespace com::sun::star; + +bool ScViewFunc::PasteDataFormat( SotClipboardFormatId nFormatId, + const uno::Reference<datatransfer::XTransferable>& rxTransferable, + SCCOL nPosX, SCROW nPosY, const Point* pLogicPos, bool bLink, bool bAllowDialogs ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + rDoc.SetPastingDrawFromOtherDoc( true ); + + Point aPos; // inserting position (1/100 mm) + if (pLogicPos) + aPos = *pLogicPos; + else + { + // inserting position isn't needed for text formats + bool bIsTextFormat = ( ScImportExport::IsFormatSupported( nFormatId ) || + nFormatId == SotClipboardFormatId::RTF ); + if ( !bIsTextFormat ) + { + // Window MapMode isn't drawing MapMode if DrawingLayer hasn't been created yet + + SCTAB nTab = GetViewData().GetTabNo(); + tools::Long nXT = 0; + for (SCCOL i=0; i<nPosX; i++) + nXT += rDoc.GetColWidth(i,nTab); + if (rDoc.IsNegativePage(nTab)) + nXT = -nXT; + tools::Long nYT = rDoc.GetRowHeight( 0, nPosY-1, nTab); + aPos = Point(o3tl::convert(nXT, o3tl::Length::twip, o3tl::Length::mm100), + o3tl::convert(nYT, o3tl::Length::twip, o3tl::Length::mm100)); + } + } + + TransferableDataHelper aDataHelper( rxTransferable ); + bool bRet = false; + + // handle individual formats + + if ( nFormatId == SotClipboardFormatId::EMBED_SOURCE || + nFormatId == SotClipboardFormatId::LINK_SOURCE || + nFormatId == SotClipboardFormatId::EMBED_SOURCE_OLE || + nFormatId == SotClipboardFormatId::LINK_SOURCE_OLE || + nFormatId == SotClipboardFormatId::EMBEDDED_OBJ_OLE ) + { + uno::Reference < io::XInputStream > xStm; + TransferableObjectDescriptor aObjDesc; + + if (aDataHelper.GetTransferableObjectDescriptor(SotClipboardFormatId::OBJECTDESCRIPTOR, aObjDesc)) + xStm = aDataHelper.GetInputStream(nFormatId, OUString()); + + if (xStm.is()) + { + if ( aObjDesc.maClassName == SvGlobalName( SO3_SC_CLASSID_60 ) ) + { + uno::Reference < embed::XStorage > xStore = ::comphelper::OStorageHelper::GetStorageFromInputStream( xStm ); + + // mba: BaseURL doesn't make sense for clipboard + // #i43716# Medium must be allocated with "new". + // DoLoad stores the pointer and deletes it with the SfxObjectShell. + SfxMedium* pMedium = new SfxMedium( xStore, OUString() ); + + // TODO/LATER: is it a problem that we don't support binary formats here? + ScDocShellRef xDocShRef = new ScDocShell(SfxModelFlags::EMBEDDED_OBJECT); + if (xDocShRef->DoLoad(pMedium)) + { + ScDocument& rSrcDoc = xDocShRef->GetDocument(); + SCTAB nSrcTab = rSrcDoc.GetVisibleTab(); + if (!rSrcDoc.HasTable(nSrcTab)) + nSrcTab = 0; + + ScMarkData aSrcMark(rSrcDoc.GetSheetLimits()); + aSrcMark.SelectOneTable( nSrcTab ); // for CopyToClip + ScDocumentUniquePtr pClipDoc(new ScDocument( SCDOCMODE_CLIP )); + + SCCOL nFirstCol, nLastCol; + SCROW nFirstRow, nLastRow; + if ( rSrcDoc.GetDataStart( nSrcTab, nFirstCol, nFirstRow ) ) + { + rSrcDoc.GetCellArea( nSrcTab, nLastCol, nLastRow ); + if (nLastCol < nFirstCol) + nLastCol = nFirstCol; + if (nLastRow < nFirstRow) + nLastRow = nFirstRow; + } + else + { + nFirstCol = nLastCol = 0; + nFirstRow = nLastRow = 0; + } + + bool bIncludeObjects = false; // include drawing layer objects in CopyToClip ? + + if (nFormatId == SotClipboardFormatId::EMBED_SOURCE) + { + const ScDrawLayer* pDraw = rSrcDoc.GetDrawLayer(); + SCCOL nPrintEndCol = nFirstCol; + SCROW nPrintEndRow = nFirstRow; + bool bHasObjects = pDraw && pDraw->HasObjects(); + // Extend the range to include the drawing layer objects. + if (bHasObjects && rSrcDoc.GetPrintArea(nSrcTab, nPrintEndCol, nPrintEndRow, true)) + { + nLastCol = std::max<SCCOL>(nLastCol, nPrintEndCol); + nLastRow = std::max<SCROW>(nLastRow, nPrintEndRow); + } + + bIncludeObjects = bHasObjects; + } + + ScClipParam aClipParam(ScRange(nFirstCol, nFirstRow, nSrcTab, nLastCol, nLastRow, nSrcTab), false); + rSrcDoc.CopyToClip(aClipParam, pClipDoc.get(), &aSrcMark, false, bIncludeObjects); + ScGlobal::SetClipDocName( xDocShRef->GetTitle( SFX_TITLE_FULLNAME ) ); + + SetCursor( nPosX, nPosY ); + Unmark(); + PasteFromClip( InsertDeleteFlags::ALL, pClipDoc.get(), + ScPasteFunc::NONE, false, false, false, INS_NONE, InsertDeleteFlags::NONE, + bAllowDialogs ); + bRet = true; + } + + xDocShRef->DoClose(); + xDocShRef.clear(); + } + else + { + OUString aName; + uno::Reference < embed::XEmbeddedObject > xObj = GetViewData().GetViewShell()->GetObjectShell()-> + GetEmbeddedObjectContainer().InsertEmbeddedObject( xStm, aName ); + if ( xObj.is() ) + { + // try to get the replacement image from the clipboard + Graphic aGraphic; + SotClipboardFormatId nGrFormat = SotClipboardFormatId::NONE; + + // limit the size of the preview metafile to 100000 actions + GDIMetaFile aMetafile; + if (aDataHelper.GetGDIMetaFile(SotClipboardFormatId::GDIMETAFILE, aMetafile, 100000)) + { + nGrFormat = SotClipboardFormatId::GDIMETAFILE; + aGraphic = aMetafile; + } + + // insert replacement image ( if there is one ) into the object helper + if ( nGrFormat != SotClipboardFormatId::NONE ) + { + datatransfer::DataFlavor aDataFlavor; + SotExchange::GetFormatDataFlavor( nGrFormat, aDataFlavor ); + PasteObject( aPos, xObj, &aObjDesc.maSize, &aGraphic, aDataFlavor.MimeType, aObjDesc.mnViewAspect ); + } + else + PasteObject( aPos, xObj, &aObjDesc.maSize ); + + bRet = true; + } + else + { + OSL_FAIL("Error in CreateAndLoad"); + } + } + } + else + { + if ( aDataHelper.GetTransferableObjectDescriptor( SotClipboardFormatId::OBJECTDESCRIPTOR_OLE, aObjDesc ) ) + { + OUString aName; + uno::Reference < embed::XEmbeddedObject > xObj; + xStm = aDataHelper.GetInputStream(SotClipboardFormatId::EMBED_SOURCE_OLE, OUString()); + if (!xStm.is()) + aDataHelper.GetInputStream(SotClipboardFormatId::EMBEDDED_OBJ_OLE, OUString()); + + if (xStm.is()) + { + xObj = GetViewData().GetDocShell()->GetEmbeddedObjectContainer().InsertEmbeddedObject( xStm, aName ); + } + else + { + try + { + uno::Reference< embed::XStorage > xTmpStor = ::comphelper::OStorageHelper::GetTemporaryStorage(); + uno::Reference < embed::XEmbedObjectClipboardCreator > xClipboardCreator = + embed::MSOLEObjectSystemCreator::create( ::comphelper::getProcessComponentContext() ); + + embed::InsertedObjectInfo aInfo = xClipboardCreator->createInstanceInitFromClipboard( + xTmpStor, + "DummyName", + uno::Sequence< beans::PropertyValue >() ); + + // TODO/LATER: in future InsertedObjectInfo will be used to get container related information + // for example whether the object should be an iconified one + xObj = aInfo.Object; + if ( xObj.is() ) + GetViewData().GetDocShell()->GetEmbeddedObjectContainer().InsertEmbeddedObject( xObj, aName ); + } + catch( uno::Exception& ) + {} + } + + if ( xObj.is() ) + { + // try to get the replacement image from the clipboard + Graphic aGraphic; + SotClipboardFormatId nGrFormat = SotClipboardFormatId::NONE; + +// (for Selection Manager in Trusted Solaris) +#ifndef __sun + if( aDataHelper.GetGraphic( SotClipboardFormatId::SVXB, aGraphic ) ) + nGrFormat = SotClipboardFormatId::SVXB; + else if( aDataHelper.GetGraphic( SotClipboardFormatId::GDIMETAFILE, aGraphic ) ) + nGrFormat = SotClipboardFormatId::GDIMETAFILE; + else if( aDataHelper.GetGraphic( SotClipboardFormatId::BITMAP, aGraphic ) ) + nGrFormat = SotClipboardFormatId::BITMAP; +#endif + + // insert replacement image ( if there is one ) into the object helper + if ( nGrFormat != SotClipboardFormatId::NONE ) + { + datatransfer::DataFlavor aDataFlavor; + SotExchange::GetFormatDataFlavor( nGrFormat, aDataFlavor ); + PasteObject( aPos, xObj, &aObjDesc.maSize, &aGraphic, aDataFlavor.MimeType, aObjDesc.mnViewAspect ); + } + else + PasteObject( aPos, xObj, &aObjDesc.maSize ); + + // let object stay in loaded state after insertion + SdrOle2Obj::Unload( xObj, embed::Aspects::MSOLE_CONTENT ); + bRet = true; + } + else + { + OSL_FAIL("Error creating external OLE object"); + } + } + //TODO/LATER: if format is not available, create picture + } + } + else if ( nFormatId == SotClipboardFormatId::LINK ) // LINK is also in ScImportExport + { + bRet = PasteLink( rxTransferable ); + } + else if ( ScImportExport::IsFormatSupported( nFormatId ) || nFormatId == SotClipboardFormatId::RTF || + nFormatId == SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT ) + { + if ( nFormatId == SotClipboardFormatId::RTF && ( aDataHelper.HasFormat( SotClipboardFormatId::EDITENGINE_ODF_TEXT_FLAT ) ) ) + { + // use EditView's PasteSpecial / Drop + PasteRTF( nPosX, nPosY, rxTransferable ); + bRet = true; + } + else + { + ScAddress aCellPos( nPosX, nPosY, GetViewData().GetTabNo() ); + auto pObj = std::make_shared<ScImportExport>(GetViewData().GetDocument(), aCellPos); + pObj->SetOverwriting( true ); + + + auto pStrBuffer = std::make_shared<OUString>(); + tools::SvRef<SotTempStream> xStream; + if ( aDataHelper.GetSotStorageStream( nFormatId, xStream ) && xStream.is() ) + { + // Static variables for per-session storage. This could be + // changed to longer-term storage in future. + static bool bHaveSavedPreferences = false; + static LanguageType eSavedLanguage; + static bool bSavedDateConversion; + static bool bSavedScientificConversion; + + if (nFormatId == SotClipboardFormatId::HTML && + !comphelper::LibreOfficeKit::isActive()) + { + if (bHaveSavedPreferences) + { + ScAsciiOptions aOptions; + aOptions.SetLanguage(eSavedLanguage); + aOptions.SetDetectSpecialNumber(bSavedDateConversion); + aOptions.SetDetectScientificNumber(bSavedScientificConversion); + pObj->SetExtOptions(aOptions); + } + else + { + // Launch the text import options dialog. For now, we do + // this for html pasting only, but in the future it may + // make sense to do it for other data types too. + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + vcl::Window* pParent = GetActiveWin(); + ScopedVclPtr<AbstractScTextImportOptionsDlg> pDlg( + pFact->CreateScTextImportOptionsDlg(pParent ? pParent->GetFrameWeld() : nullptr)); + + if (pDlg->Execute() == RET_OK) + { + ScAsciiOptions aOptions; + aOptions.SetLanguage(pDlg->GetLanguageType()); + aOptions.SetDetectSpecialNumber(pDlg->IsDateConversionSet()); + aOptions.SetDetectScientificNumber(pDlg->IsScientificConversionSet()); + if (!pDlg->IsKeepAskingSet()) + { + bHaveSavedPreferences = true; + eSavedLanguage = pDlg->GetLanguageType(); + bSavedDateConversion = pDlg->IsDateConversionSet(); + bSavedScientificConversion = pDlg->IsScientificConversionSet(); + } + pObj->SetExtOptions(aOptions); + } + else + { + // prevent error dialog for user cancel action + bRet = true; + } + } + } + if(!bRet) + bRet = pObj->ImportStream( *xStream, OUString(), nFormatId ); + // mba: clipboard always must contain absolute URLs (could be from alien source) + } + else if ((nFormatId == SotClipboardFormatId::STRING || nFormatId == SotClipboardFormatId::STRING_TSVC) + && aDataHelper.GetString( nFormatId, *pStrBuffer )) + { + // Do CSV dialog if more than one line. But not if invoked from Automation. + const SfxViewShell* pViewShell = SfxViewShell::Current(); + sal_Int32 nDelim = pStrBuffer->indexOf('\n'); + if (!(pViewShell && pViewShell->isLOKMobilePhone()) && !comphelper::Automation::AutomationInvokedZone::isActive() + && nDelim >= 0 && nDelim != pStrBuffer->getLength () - 1) + { + vcl::Window* pParent = GetActiveWin(); + + auto pStrm = std::make_shared<ScImportStringStream>(*pStrBuffer); + + ScAbstractDialogFactory* pFact = ScAbstractDialogFactory::Create(); + VclPtr<AbstractScImportAsciiDlg> pDlg( + pFact->CreateScImportAsciiDlg(pParent ? pParent->GetFrameWeld() : nullptr, OUString(), pStrm.get(), SC_PASTETEXT)); + + bAllowDialogs = bAllowDialogs && !SC_MOD()->IsInExecuteDrop(); + + pDlg->StartExecuteAsync([this, pDlg, &rDoc, pStrm, nFormatId, pStrBuffer, pObj, bAllowDialogs](sal_Int32 nResult){ + bool bShowErrorDialog = bAllowDialogs; + if (RET_OK == nResult) + { + ScAsciiOptions aOptions; + pDlg->GetOptions( aOptions ); + pDlg->SaveParameters(); + pObj->SetExtOptions( aOptions ); + pObj->ImportString( *pStrBuffer, nFormatId ); + + // TODO: what if (aObj.IsOverflow()) + // Content was partially pasted, which can be undone by + // the user though. + bShowErrorDialog = bShowErrorDialog && pObj->IsOverflow(); + } + else + { + bShowErrorDialog = false; + // Yes, no failure, don't raise a "couldn't paste" + // dialog if user cancelled. + } + + InvalidateAttribs(); + GetViewData().UpdateInputHandler(); + + rDoc.SetPastingDrawFromOtherDoc( false ); + + if (bShowErrorDialog) + ErrorMessage(STR_PASTE_ERROR); + pDlg->disposeOnce(); + }); + return true; + } + else + bRet = pObj->ImportString( *pStrBuffer, nFormatId ); + } + else if ((nFormatId != SotClipboardFormatId::STRING && nFormatId != SotClipboardFormatId::STRING_TSVC) + && aDataHelper.GetString( nFormatId, *pStrBuffer )) + bRet = pObj->ImportString( *pStrBuffer, nFormatId ); + + InvalidateAttribs(); + GetViewData().UpdateInputHandler(); + } + } + else if (nFormatId == SotClipboardFormatId::SBA_DATAEXCHANGE) + { + // import of database data into table + + const DataFlavorExVector& rVector = aDataHelper.GetDataFlavorExVector(); + if ( svx::ODataAccessObjectTransferable::canExtractObjectDescriptor(rVector) ) + { + // transport the whole ODataAccessDescriptor as slot parameter + svx::ODataAccessDescriptor aDesc = svx::ODataAccessObjectTransferable::extractObjectDescriptor(aDataHelper); + uno::Any aDescAny; + uno::Sequence<beans::PropertyValue> aProperties = aDesc.createPropertyValueSequence(); + aDescAny <<= aProperties; + SfxUnoAnyItem aDataDesc(SID_SBA_IMPORT, aDescAny); + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + SCTAB nTab = GetViewData().GetTabNo(); + + ClickCursor(nPosX, nPosY, false); // set cursor position + + // Creation of database area "Import1" isn't here, but in the DocShell + // slot execute, so it can be added to the undo action + + ScDBData* pDBData = pDocSh->GetDBData( ScRange(nPosX,nPosY,nTab), SC_DB_OLD, ScGetDBSelection::Keep ); + OUString sTarget; + if (pDBData) + sTarget = pDBData->GetName(); + else + { + ScAddress aCellPos( nPosX,nPosY,nTab ); + sTarget = aCellPos.Format(ScRefFlags::ADDR_ABS_3D, &rDoc, rDoc.GetAddressConvention()); + } + SfxStringItem aTarget(FN_PARAM_1, sTarget); + + bool bAreaIsNew = !pDBData; + SfxBoolItem aAreaNew(FN_PARAM_2, bAreaIsNew); + + // asynchronous, to avoid doing the whole import in drop handler + SfxDispatcher& rDisp = GetViewData().GetDispatcher(); + rDisp.ExecuteList(SID_SBA_IMPORT, SfxCallMode::ASYNCHRON, + { &aDataDesc, &aTarget, &aAreaNew }); + + bRet = true; + } + } + else if (nFormatId == SotClipboardFormatId::SBA_FIELDDATAEXCHANGE) + { + // insert database field control + + if ( svx::OColumnTransferable::canExtractColumnDescriptor( aDataHelper.GetDataFlavorExVector(), ColumnTransferFormatFlags::COLUMN_DESCRIPTOR | ColumnTransferFormatFlags::CONTROL_EXCHANGE ) ) + { + MakeDrawLayer(); + ScDrawView* pScDrawView = GetScDrawView(); + rtl::Reference<SdrObject> pObj = pScDrawView->CreateFieldControl( svx::OColumnTransferable::extractColumnDescriptor( aDataHelper ) ); + if (pObj) + { + Point aInsPos = aPos; + tools::Rectangle aRect(pObj->GetLogicRect()); + aInsPos.AdjustX( -(aRect.GetSize().Width() / 2) ); + aInsPos.AdjustY( -(aRect.GetSize().Height() / 2) ); + if ( aInsPos.X() < 0 ) aInsPos.setX( 0 ); + if ( aInsPos.Y() < 0 ) aInsPos.setY( 0 ); + aRect.SetPos(aInsPos); + pObj->SetLogicRect(aRect); + + if ( dynamic_cast<const SdrUnoObj*>( pObj.get() ) != nullptr ) + pObj->NbcSetLayer(SC_LAYER_CONTROLS); + else + pObj->NbcSetLayer(SC_LAYER_FRONT); + if (dynamic_cast<const SdrObjGroup*>( pObj.get() ) != nullptr) + { + SdrObjListIter aIter( *pObj, SdrIterMode::DeepWithGroups ); + SdrObject* pSubObj = aIter.Next(); + while (pSubObj) + { + if ( dynamic_cast<const SdrUnoObj*>( pSubObj) != nullptr ) + pSubObj->NbcSetLayer(SC_LAYER_CONTROLS); + else + pSubObj->NbcSetLayer(SC_LAYER_FRONT); + pSubObj = aIter.Next(); + } + } + + pScDrawView->InsertObjectSafe(pObj.get(), *pScDrawView->GetSdrPageView()); + + GetViewData().GetViewShell()->SetDrawShell( true ); + bRet = true; + } + } + } + else if (nFormatId == SotClipboardFormatId::BITMAP || nFormatId == SotClipboardFormatId::PNG || nFormatId == SotClipboardFormatId::JPEG) + { + BitmapEx aBmpEx; + if( aDataHelper.GetBitmapEx( SotClipboardFormatId::BITMAP, aBmpEx ) ) + bRet = PasteBitmapEx( aPos, aBmpEx ); + } + else if (nFormatId == SotClipboardFormatId::GDIMETAFILE) + { + GDIMetaFile aMtf; + if( aDataHelper.GetGDIMetaFile( SotClipboardFormatId::GDIMETAFILE, aMtf ) ) + bRet = PasteMetaFile( aPos, aMtf ); + } + else if (nFormatId == SotClipboardFormatId::SVXB) + { + tools::SvRef<SotTempStream> xStm; + if( aDataHelper.GetSotStorageStream( SotClipboardFormatId::SVXB, xStm ) ) + { + Graphic aGraphic; + TypeSerializer aSerializer(*xStm); + aSerializer.readGraphic(aGraphic); + bRet = PasteGraphic( aPos, aGraphic, OUString() ); + } + } + else if ( nFormatId == SotClipboardFormatId::DRAWING ) + { + tools::SvRef<SotTempStream> xStm; + if( aDataHelper.GetSotStorageStream( SotClipboardFormatId::DRAWING, xStm ) ) + { + MakeDrawLayer(); // before loading model, so 3D factory has been created + + ScDocShellRef aDragShellRef( new ScDocShell ); + aDragShellRef->MakeDrawLayer(); + aDragShellRef->DoInitNew(); + + ScDrawLayer* pModel = aDragShellRef->GetDocument().GetDrawLayer(); + + xStm->Seek(0); + + css::uno::Reference< css::io::XInputStream > xInputStream( new utl::OInputStreamWrapper( *xStm ) ); + SvxDrawingLayerImport( pModel, xInputStream ); + + // set everything to right layer: + size_t nObjCount = 0; + sal_uInt16 nPages = pModel->GetPageCount(); + for (sal_uInt16 i=0; i<nPages; i++) + { + SdrPage* pPage = pModel->GetPage(i); + SdrObjListIter aIter( pPage, SdrIterMode::DeepWithGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( dynamic_cast<const SdrUnoObj*>( pObject) != nullptr ) + pObject->NbcSetLayer(SC_LAYER_CONTROLS); + else + pObject->NbcSetLayer(SC_LAYER_FRONT); + pObject = aIter.Next(); + } + + nObjCount += pPage->GetObjCount(); // count group object only once + } + + PasteDraw(aPos, pModel, (nObjCount > 1), u"A", u"B"); // grouped if more than 1 object + aDragShellRef->DoClose(); + bRet = true; + } + } + else if ( (nFormatId == SotClipboardFormatId::BIFF_5) || (nFormatId == SotClipboardFormatId::BIFF_8) ) + { + // do excel import into a clipboard document + //TODO/MBA: testing + uno::Reference <io::XInputStream> xStm = aDataHelper.GetInputStream(nFormatId, OUString()); + if (xStm.is()) + { + ScDocument aInsDoc( SCDOCMODE_CLIP ); + SCTAB nSrcTab = 0; // Biff5 in clipboard: always sheet 0 + aInsDoc.ResetClip( &rDoc, nSrcTab ); + + SfxMedium aMed; + aMed.GetItemSet().Put( SfxUnoAnyItem( SID_INPUTSTREAM, uno::Any( xStm ) ) ); + ErrCode eErr = ScFormatFilter::Get().ScImportExcel( aMed, &aInsDoc, EIF_AUTO ); + if ( eErr == ERRCODE_NONE ) + { + ScRange aSource; + const ScExtDocOptions* pExtOpt = aInsDoc.GetExtDocOptions(); + const ScExtTabSettings* pTabSett = pExtOpt ? pExtOpt->GetTabSettings( nSrcTab ) : nullptr; + if( pTabSett && pTabSett->maUsedArea.IsValid() ) + { + aSource = pTabSett->maUsedArea; + // ensure correct sheet indexes + aSource.aStart.SetTab( nSrcTab ); + aSource.aEnd.SetTab( nSrcTab ); +// don't use selection area: if cursor is moved in Excel after Copy, selection +// represents the new cursor position and not the copied area + } + else + { + OSL_FAIL("no dimension"); //! possible? + SCCOL nFirstCol, nLastCol; + SCROW nFirstRow, nLastRow; + if ( aInsDoc.GetDataStart( nSrcTab, nFirstCol, nFirstRow ) ) + aInsDoc.GetCellArea( nSrcTab, nLastCol, nLastRow ); + else + { + nFirstCol = nLastCol = 0; + nFirstRow = nLastRow = 0; + } + aSource = ScRange( nFirstCol, nFirstRow, nSrcTab, + nLastCol, nLastRow, nSrcTab ); + } + + if ( pLogicPos ) + { + // position specified (Drag&Drop) - change selection + MoveCursorAbs( nPosX, nPosY, SC_FOLLOW_NONE, false, false ); + Unmark(); + } + + aInsDoc.SetClipArea( aSource ); + PasteFromClip( InsertDeleteFlags::ALL, &aInsDoc, + ScPasteFunc::NONE, false, false, false, INS_NONE, InsertDeleteFlags::NONE, + bAllowDialogs ); + bRet = true; + } + } + } + else if ( nFormatId == SotClipboardFormatId::SIMPLE_FILE ) + { + OUString aFile; + if ( aDataHelper.GetString( nFormatId, aFile ) ) + bRet = PasteFile( aPos, aFile, bLink ); + } + else if ( nFormatId == SotClipboardFormatId::FILE_LIST ) + { + FileList aFileList; + if ( aDataHelper.GetFileList( nFormatId, aFileList ) ) + { + sal_uLong nCount = aFileList.Count(); + for( sal_uLong i = 0; i < nCount ; i++ ) + { + OUString aFile = aFileList.GetFile( i ); + + PasteFile( aPos, aFile, bLink ); + + aPos.AdjustX(400 ); + aPos.AdjustY(400 ); + } + bRet = true; + } + } + else if ( nFormatId == SotClipboardFormatId::SOLK || + nFormatId == SotClipboardFormatId::UNIFORMRESOURCELOCATOR || + nFormatId == SotClipboardFormatId::NETSCAPE_BOOKMARK || + nFormatId == SotClipboardFormatId::FILEGRPDESCRIPTOR ) + { + bRet = PasteBookmark( nFormatId, rxTransferable, nPosX, nPosY ); + } + + rDoc.SetPastingDrawFromOtherDoc( false ); + + return bRet; +} + +bool ScViewFunc::PasteLink( const uno::Reference<datatransfer::XTransferable>& rxTransferable ) +{ + TransferableDataHelper aDataHelper( rxTransferable ); + + // get link data from transferable before string data, + // so the source knows it will be used for a link + + uno::Sequence<sal_Int8> aSequence = aDataHelper.GetSequence(SotClipboardFormatId::LINK, OUString()); + if (!aSequence.hasElements()) + { + OSL_FAIL("DDE Data not found."); + return false; + } + + // check size (only if string is available in transferable) + + sal_uInt16 nCols = 1; + sal_uInt16 nRows = 1; + if ( aDataHelper.HasFormat( SotClipboardFormatId::STRING ) ) + { + OUString aDataStr; + if ( aDataHelper.GetString( SotClipboardFormatId::STRING, aDataStr ) ) + { + // get size from string the same way as in ScDdeLink::DataChanged + + aDataStr = convertLineEnd(aDataStr, LINEEND_LF); + sal_Int32 nLen = aDataStr.getLength(); + if (nLen && aDataStr[nLen-1] == '\n') + aDataStr = aDataStr.copy(0, nLen-1); + + if (!aDataStr.isEmpty()) + { + nRows = comphelper::string::getTokenCount(aDataStr, '\n'); + std::u16string_view aLine = o3tl::getToken(aDataStr, 0, '\n' ); + if (!aLine.empty()) + nCols = comphelper::string::getTokenCount(aLine, '\t'); + } + } + } + + // create formula + + sal_Int32 nSeqLen = aSequence.getLength(); + const char* p = reinterpret_cast<const char*>(aSequence.getConstArray()); + + rtl_TextEncoding eSysEnc = osl_getThreadTextEncoding(); + + // char array delimited by \0. + // app \0 topic \0 item \0 (extra \0) where the extra is optional. + ::std::vector<OUString> aStrs; + const char* pStart = p; + sal_Int32 nStart = 0; + for (sal_Int32 i = 0; i < nSeqLen; ++i, ++p) + { + if (*p == '\0') + { + sal_Int32 nLen = i - nStart; + aStrs.emplace_back(pStart, nLen, eSysEnc); + nStart = ++i; + pStart = ++p; + } + } + + if (aStrs.size() < 3) + return false; + + const OUString& pApp = aStrs[0]; + const OUString& pTopic = aStrs[1]; + const OUString& pItem = aStrs[2]; + const OUString* pExtra = nullptr; + if (aStrs.size() > 3) + pExtra = &aStrs[3]; + + if ( pExtra && *pExtra == "calc:extref" ) + { + // Paste this as an external reference. Note that paste link always + // uses Calc A1 syntax even when another formula syntax is specified + // in the UI. + EnterMatrix("='" + + ScGlobal::GetAbsDocName(pTopic, GetViewData().GetDocument().GetDocumentShell()) + + "'#" + pItem + , ::formula::FormulaGrammar::GRAM_NATIVE); + return true; + } + else + { + // DDE in all other cases. + + // TODO: we could define ocQuote for " + EnterMatrix("=" + ScCompiler::GetNativeSymbol(ocDde) + + ScCompiler::GetNativeSymbol(ocOpen) + + "\"" + pApp + "\"" + + ScCompiler::GetNativeSymbol(ocSep) + + "\"" + pTopic + "\"" + + ScCompiler::GetNativeSymbol(ocSep) + + "\"" + pItem + "\"" + + ScCompiler::GetNativeSymbol(ocClose) + , ::formula::FormulaGrammar::GRAM_NATIVE); + } + + // mark range + + SCTAB nTab = GetViewData().GetTabNo(); + SCCOL nCurX = GetViewData().GetCurX(); + SCROW nCurY = GetViewData().GetCurY(); + HideAllCursors(); + DoneBlockMode(); + InitBlockMode( nCurX, nCurY, nTab ); + MarkCursor( nCurX+static_cast<SCCOL>(nCols)-1, nCurY+static_cast<SCROW>(nRows)-1, nTab ); + ShowAllCursors(); + CursorPosChanged(); + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/viewfun6.cxx b/sc/source/ui/view/viewfun6.cxx new file mode 100644 index 0000000000..a840670dfc --- /dev/null +++ b/sc/source/ui/view/viewfun6.cxx @@ -0,0 +1,559 @@ +/* -*- 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 <formula/token.hxx> +#include <svx/svdocapt.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/lokhelper.hxx> +#include <svl/stritem.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <svl/zformat.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/uitest/eventdescription.hxx> +#include <editeng/editview.hxx> +#include <rtl/math.hxx> +#include <sal/log.hxx> + +#include <viewfunc.hxx> +#include <viewdata.hxx> +#include <drwlayer.hxx> +#include <docsh.hxx> +#include <futext.hxx> +#include <docfunc.hxx> +#include <sc.hrc> +#include <fusel.hxx> +#include <reftokenhelper.hxx> +#include <externalrefmgr.hxx> +#include <markdata.hxx> +#include <drawview.hxx> +#include <inputhdl.hxx> +#include <tabvwsh.hxx> +#include <scmod.hxx> +#include <postit.hxx> +#include <comphelper/scopeguard.hxx> + +#include <vector> + +namespace +{ + +void collectUIInformation( const OUString& aevent ) +{ + EventDescription aDescription; + aDescription.aID = "grid_window"; + aDescription.aParameters = {{ aevent , ""}}; + aDescription.aAction = "COMMENT"; + aDescription.aParent = "MainWindow"; + aDescription.aKeyWord = "ScGridWinUIObject"; + UITestLogger::getInstance().logEvent(aDescription); +} + +} + +using ::std::vector; + +void ScViewFunc::DetectiveAddPred() +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + pDocSh->GetDocFunc().DetectiveAddPred( GetViewData().GetCurPos() ); + RecalcPPT(); //! use broadcast in DocFunc instead? +} + +void ScViewFunc::DetectiveDelPred() +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + pDocSh->GetDocFunc().DetectiveDelPred( GetViewData().GetCurPos() ); + RecalcPPT(); +} + +void ScViewFunc::DetectiveAddSucc() +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + pDocSh->GetDocFunc().DetectiveAddSucc( GetViewData().GetCurPos() ); + RecalcPPT(); +} + +void ScViewFunc::DetectiveDelSucc() +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + pDocSh->GetDocFunc().DetectiveDelSucc( GetViewData().GetCurPos() ); + RecalcPPT(); +} + +void ScViewFunc::DetectiveAddError() +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + pDocSh->GetDocFunc().DetectiveAddError( GetViewData().GetCurPos() ); + RecalcPPT(); +} + +void ScViewFunc::DetectiveDelAll() +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + pDocSh->GetDocFunc().DetectiveDelAll( GetViewData().GetTabNo() ); + RecalcPPT(); +} + +void ScViewFunc::DetectiveMarkInvalid() +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + pDocSh->GetDocFunc().DetectiveMarkInvalid( GetViewData().GetTabNo() ); + RecalcPPT(); +} + +void ScViewFunc::DetectiveRefresh() +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + pDocSh->GetDocFunc().DetectiveRefresh(); + RecalcPPT(); +} + +static void lcl_jumpToRange(const ScRange& rRange, ScViewData* pView, const ScDocument& rDoc) +{ + OUString aAddrText(rRange.Format(rDoc, ScRefFlags::RANGE_ABS_3D)); + SfxStringItem aPosItem(SID_CURRENTCELL, aAddrText); + SfxBoolItem aUnmarkItem(FN_PARAM_1, true); // remove existing selection + pView->GetDispatcher().ExecuteList( + SID_CURRENTCELL, SfxCallMode::SYNCHRON | SfxCallMode::RECORD, + { &aPosItem, &aUnmarkItem }); +} + +void ScViewFunc::MarkAndJumpToRanges(const ScRangeList& rRanges) +{ + ScViewData& rView = GetViewData(); + ScDocShell* pDocSh = rView.GetDocShell(); + + ScRangeList aRanges(rRanges); + ScRangeList aRangesToMark; + ScAddress aCurPos = rView.GetCurPos(); + size_t ListSize = aRanges.size(); + for ( size_t i = 0; i < ListSize; ++i ) + { + const ScRange & r = aRanges[i]; + // Collect only those ranges that are on the same sheet as the current + // cursor. + if (r.aStart.Tab() == aCurPos.Tab()) + aRangesToMark.push_back(r); + } + + if (aRangesToMark.empty()) + return; + + // Jump to the first range of all precedent ranges. + const ScRange & r = aRangesToMark.front(); + lcl_jumpToRange(r, &rView, pDocSh->GetDocument()); + + ListSize = aRangesToMark.size(); + for ( size_t i = 0; i < ListSize; ++i ) + { + MarkRange(aRangesToMark[i], false, true); + } +} + +void ScViewFunc::DetectiveMarkPred() +{ + ScViewData& rView = GetViewData(); + ScDocShell* pDocSh = rView.GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScMarkData& rMarkData = rView.GetMarkData(); + ScAddress aCurPos = rView.GetCurPos(); + ScRangeList aRanges; + if (rMarkData.IsMarked() || rMarkData.IsMultiMarked()) + rMarkData.FillRangeListWithMarks(&aRanges, false); + else + aRanges.push_back(aCurPos); + + vector<ScTokenRef> aRefTokens; + pDocSh->GetDocFunc().DetectiveCollectAllPreds(aRanges, aRefTokens); + + if (aRefTokens.empty()) + // No precedents found. Nothing to do. + return; + + ScTokenRef p = aRefTokens.front(); + if (ScRefTokenHelper::isExternalRef(p)) + { + // This is external. Open the external document if available, and + // jump to the destination. + + sal_uInt16 nFileId = p->GetIndex(); + ScExternalRefManager* pRefMgr = rDoc.GetExternalRefManager(); + const OUString* pPath = pRefMgr->getExternalFileName(nFileId); + + ScRange aRange; + if (pPath && ScRefTokenHelper::getRangeFromToken(&rDoc, aRange, p, aCurPos, true)) + { + OUString aTabName = p->GetString().getString(); + OUString aRangeStr(aRange.Format(rDoc, ScRefFlags::VALID)); + OUString sUrl = + *pPath + + "#" + + aTabName + + "." + + aRangeStr; + + ScGlobal::OpenURL(sUrl, OUString()); + } + return; + } + else + { + ScRange aRange; + ScRefTokenHelper::getRangeFromToken(&rDoc, aRange, p, aCurPos); + if (aRange.aStart.Tab() != aCurPos.Tab()) + { + // The first precedent range is on a different sheet. Jump to it + // immediately and forget the rest. + lcl_jumpToRange(aRange, &rView, rDoc); + return; + } + } + + ScRangeList aDestRanges; + ScRefTokenHelper::getRangeListFromTokens(&rDoc, aDestRanges, aRefTokens, aCurPos); + MarkAndJumpToRanges(aDestRanges); +} + +void ScViewFunc::DetectiveMarkSucc() +{ + ScViewData& rView = GetViewData(); + ScDocShell* pDocSh = rView.GetDocShell(); + ScMarkData& rMarkData = rView.GetMarkData(); + ScAddress aCurPos = rView.GetCurPos(); + ScRangeList aRanges; + if (rMarkData.IsMarked() || rMarkData.IsMultiMarked()) + rMarkData.FillRangeListWithMarks(&aRanges, false); + else + aRanges.push_back(aCurPos); + + vector<ScTokenRef> aRefTokens; + pDocSh->GetDocFunc().DetectiveCollectAllSuccs(aRanges, aRefTokens); + + if (aRefTokens.empty()) + // No dependents found. Nothing to do. + return; + + ScRangeList aDestRanges; + ScRefTokenHelper::getRangeListFromTokens(&rView.GetDocument(), aDestRanges, aRefTokens, aCurPos); + MarkAndJumpToRanges(aDestRanges); +} + +/** Insert date or time into current cell. + + If cell is in input or edit mode, insert date/time at cursor position, else + create a date or time or date+time cell as follows: + + - key date on time cell => current date + time of cell => date+time formatted cell + - unless time cell was empty or 00:00 time => current date => date formatted cell + - key date on date+time cell => current date + 00:00 time => date+time formatted cell + - unless date was current date => current date => date formatted cell + - key date on other cell => current date => date formatted cell + - key time on date cell => date of cell + current time => date+time formatted cell + - unless date cell was empty => current time => time formatted cell + - key time on date+time cell => current time => time formatted cell + - unless cell was empty => current date+time => date+time formatted cell + - key time on other cell => current time => time formatted cell + */ +void ScViewFunc::InsertCurrentTime(SvNumFormatType nReqFmt, const OUString& rUndoStr) +{ + ScViewData& rViewData = GetViewData(); + + ScInputHandler* pInputHdl = SC_MOD()->GetInputHdl( rViewData.GetViewShell()); + bool bInputMode = (pInputHdl && pInputHdl->IsInputMode()); + + ScDocShell* pDocSh = rViewData.GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScAddress aCurPos = rViewData.GetCurPos(); + const sal_uInt32 nCurNumFormat = rDoc.GetNumberFormat(aCurPos); + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + const SvNumberformat* pCurNumFormatEntry = pFormatter->GetEntry(nCurNumFormat); + const SvNumFormatType nCurNumFormatType = (pCurNumFormatEntry ? + pCurNumFormatEntry->GetMaskedType() : SvNumFormatType::UNDEFINED); + + const int nView(comphelper::LibreOfficeKit::isActive() ? SfxLokHelper::getView() : -1); + if (nView >= 0) + { + const auto [isTimezoneSet, aTimezone] = SfxLokHelper::getViewTimezone(nView); + comphelper::LibreOfficeKit::setTimezone(isTimezoneSet, aTimezone); + } + + comphelper::ScopeGuard aAutoUserTimezone( + [nView]() + { + if (nView >= 0) + { + const auto [isTimezoneSet, aTimezone] = SfxLokHelper::getDefaultTimezone(); + comphelper::LibreOfficeKit::setTimezone(isTimezoneSet, aTimezone); + } + }); + + if (bInputMode) + { + double fVal = 0.0; + sal_uInt32 nFormat = 0; + switch (nReqFmt) + { + case SvNumFormatType::DATE: + { + Date aActDate( Date::SYSTEM ); + fVal = aActDate - pFormatter->GetNullDate(); + if (nCurNumFormatType == SvNumFormatType::DATE) + nFormat = nCurNumFormat; + } + break; + case SvNumFormatType::TIME: + { + tools::Time aActTime( tools::Time::SYSTEM ); + fVal = aActTime.GetTimeInDays(); + if (nCurNumFormatType == SvNumFormatType::TIME) + nFormat = nCurNumFormat; + } + break; + default: + SAL_WARN("sc.ui","unhandled current date/time request"); + nReqFmt = SvNumFormatType::DATETIME; + [[fallthrough]]; + case SvNumFormatType::DATETIME: + { + DateTime aActDateTime( DateTime::SYSTEM ); + fVal = DateTime::Sub( aActDateTime, DateTime( pFormatter->GetNullDate())); + if (nCurNumFormatType == SvNumFormatType::DATETIME) + nFormat = nCurNumFormat; + } + break; + } + + if (!nFormat) + { + const LanguageType nLang = (pCurNumFormatEntry ? pCurNumFormatEntry->GetLanguage() : ScGlobal::eLnge); + nFormat = pFormatter->GetStandardFormat( nReqFmt, nLang); + // This would return a more precise format with seconds and 100th + // seconds for a time request. + //nFormat = pFormatter->GetStandardFormat( fVal, nFormat, nReqFmt, nLang); + } + OUString aString; + const Color* pColor; + pFormatter->GetOutputString( fVal, nFormat, aString, &pColor); + + pInputHdl->DataChanging(); + EditView* pTopView = pInputHdl->GetTopView(); + if (pTopView) + pTopView->InsertText( aString); + EditView* pTableView = pInputHdl->GetTableView(); + if (pTableView) + pTableView->InsertText( aString); + pInputHdl->DataChanged(); + } + else + { + // Clear "Enter pastes" mode. + rViewData.SetPasteMode( ScPasteFlags::NONE ); + // Clear CopySourceOverlay in each window of a split/frozen tabview. + rViewData.GetViewShell()->UpdateCopySourceOverlay(); + + bool bForceReqFmt = false; + const double fCell = rDoc.GetValue( aCurPos); + // Combine requested date/time stamp with existing cell time/date, if any. + switch (nReqFmt) + { + case SvNumFormatType::DATE: + switch (nCurNumFormatType) + { + case SvNumFormatType::TIME: + // An empty cell formatted as time (or 00:00 time) shall + // not result in the current date with 00:00 time, but only + // in current date. + if (fCell != 0.0) + nReqFmt = SvNumFormatType::DATETIME; + break; + case SvNumFormatType::DATETIME: + { + // Force to only date if the existing date+time is the + // current date. This way inserting current date twice + // on an existing date+time cell can be used to force + // date, which otherwise would only be possible by + // applying a date format. + double fDate = rtl::math::approxFloor( fCell); + if (fDate == (Date( Date::SYSTEM) - pFormatter->GetNullDate())) + bForceReqFmt = true; + } + break; + default: break; + } + break; + case SvNumFormatType::TIME: + switch (nCurNumFormatType) + { + case SvNumFormatType::DATE: + // An empty cell formatted as date shall not result in the + // null date and current time, but only in current time. + if (fCell != 0.0) + nReqFmt = SvNumFormatType::DATETIME; + break; + case SvNumFormatType::DATETIME: + // Requesting current time on an empty date+time cell + // inserts both current date+time. + if (fCell == 0.0) + nReqFmt = SvNumFormatType::DATETIME; + else + { + // Add current time to an existing date+time where time is + // zero and date is current date, else force time only. + double fDate = rtl::math::approxFloor( fCell); + double fTime = fCell - fDate; + if (fTime == 0.0 && fDate == (Date( Date::SYSTEM) - pFormatter->GetNullDate())) + nReqFmt = SvNumFormatType::DATETIME; + else + bForceReqFmt = true; + } + break; + default: break; + } + break; + default: + SAL_WARN("sc.ui","unhandled current date/time request"); + nReqFmt = SvNumFormatType::DATETIME; + [[fallthrough]]; + case SvNumFormatType::DATETIME: + break; + } + double fVal = 0.0; + switch (nReqFmt) + { + case SvNumFormatType::DATE: + { + Date aActDate( Date::SYSTEM ); + fVal = aActDate - pFormatter->GetNullDate(); + } + break; + case SvNumFormatType::TIME: + { + tools::Time aActTime( tools::Time::SYSTEM ); + fVal = aActTime.GetTimeInDays(); + } + break; + case SvNumFormatType::DATETIME: + switch (nCurNumFormatType) + { + case SvNumFormatType::DATE: + { + double fDate = rtl::math::approxFloor( fCell); + tools::Time aActTime( tools::Time::SYSTEM ); + fVal = fDate + aActTime.GetTimeInDays(); + } + break; + case SvNumFormatType::TIME: + { + double fTime = fCell - rtl::math::approxFloor( fCell); + Date aActDate( Date::SYSTEM ); + fVal = (aActDate - pFormatter->GetNullDate()) + fTime; + } + break; + default: + { + DateTime aActDateTime( DateTime::SYSTEM ); + fVal = DateTime::Sub( aActDateTime, DateTime( pFormatter->GetNullDate())); + } + } + break; + default: break; + + } + + SfxUndoManager* pUndoMgr = pDocSh->GetUndoManager(); + pUndoMgr->EnterListAction(rUndoStr, rUndoStr, 0, rViewData.GetViewShell()->GetViewShellId()); + + pDocSh->GetDocFunc().SetValueCell(aCurPos, fVal, true); + + // Set the new cell format only when it differs from the current cell + // format type. Preserve a date+time format unless we force a format + // through. + if (bForceReqFmt || (nReqFmt != nCurNumFormatType && nCurNumFormatType != SvNumFormatType::DATETIME)) + SetNumberFormat(nReqFmt); + else + rViewData.UpdateInputHandler(); // update input bar with new value + + pUndoMgr->LeaveListAction(); + } +} + +void ScViewFunc::ShowNote( bool bShow ) +{ + if( bShow ) + HideNoteMarker(); + const ScViewData& rViewData = GetViewData(); + ScAddress aPos( rViewData.GetCurX(), rViewData.GetCurY(), rViewData.GetTabNo() ); + // show note moved to ScDocFunc, to be able to use it in notesuno.cxx + rViewData.GetDocShell()->GetDocFunc().ShowNote( aPos, bShow ); +} + +void ScViewFunc::EditNote() +{ + // for editing display and activate + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + SCCOL nCol = GetViewData().GetCurX(); + SCROW nRow = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + ScAddress aPos( nCol, nRow, nTab ); + + // start drawing undo to catch undo action for insertion of the caption object + pDocSh->MakeDrawLayer(); + ScDrawLayer* pDrawLayer = rDoc.GetDrawLayer(); + pDrawLayer->BeginCalcUndo(true); + // generated undo action is processed in FuText::StopEditMode + + // get existing note or create a new note (including caption drawing object) + ScPostIt* pNote = rDoc.GetOrCreateNote( aPos ); + if(!pNote) + return; + + // hide temporary note caption + HideNoteMarker(); + // show caption object without changing internal visibility state + pNote->ShowCaptionTemp( aPos ); + + /* Drawing object has been created in ScDocument::GetOrCreateNote() or + in ScPostIt::ShowCaptionTemp(), so ScPostIt::GetCaption() should + return a caption object. */ + SdrCaptionObj* pCaption = pNote->GetCaption(); + if( !pCaption ) + return; + + if ( ScDrawView* pScDrawView = GetScDrawView() ) + pScDrawView->SyncForGrid( pCaption ); + + // activate object (as in FuSelection::TestComment) + GetViewData().GetDispatcher().Execute( SID_DRAW_NOTEEDIT, SfxCallMode::SYNCHRON | SfxCallMode::RECORD ); + // now get the created FuText and set into EditMode + FuText* pFuText = dynamic_cast<FuText*>(GetDrawFuncPtr()); + if (pFuText) + { + ScrollToObject( pCaption ); // make object fully visible + pFuText->SetInEditMode( pCaption ); + + ScTabView::OnLOKNoteStateChanged( pNote ); + } + collectUIInformation("OPEN"); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/viewfun7.cxx b/sc/source/ui/view/viewfun7.cxx new file mode 100644 index 0000000000..f704256756 --- /dev/null +++ b/sc/source/ui/view/viewfun7.cxx @@ -0,0 +1,462 @@ +/* -*- 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 <com/sun/star/embed/NoVisualAreaSizeException.hpp> +#include <com/sun/star/embed/XEmbeddedObject.hpp> + +#include <svx/svditer.hxx> +#include <svx/svdograf.hxx> +#include <svx/svdoole2.hxx> +#include <svx/svdouno.hxx> +#include <svx/svdpage.hxx> +#include <svx/svdpagv.hxx> +#include <svx/svdundo.hxx> +#include <svtools/embedhlp.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/ipclient.hxx> +#include <toolkit/helper/vclunohelper.hxx> +#include <com/sun/star/embed/Aspects.hpp> +#include <osl/diagnose.h> + +#include <document.hxx> +#include <viewfunc.hxx> +#include <tabvwsh.hxx> +#include <drawview.hxx> +#include <scmod.hxx> +#include <drwlayer.hxx> +#include <drwtrans.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <docuno.hxx> +#include <docsh.hxx> +#include <dragdata.hxx> +#include <gridwin.hxx> +#include <stlpool.hxx> + +bool bPasteIsMove = false; + +using namespace com::sun::star; + +static void lcl_AdjustInsertPos( ScViewData& rData, Point& rPos, const Size& rSize ) +{ + SdrPage* pPage = rData.GetScDrawView()->GetModel().GetPage( static_cast<sal_uInt16>(rData.GetTabNo()) ); + OSL_ENSURE(pPage,"pPage ???"); + Size aPgSize( pPage->GetSize() ); + if (aPgSize.Width() < 0) + aPgSize.setWidth( -aPgSize.Width() ); + tools::Long x = aPgSize.Width() - rPos.X() - rSize.Width(); + tools::Long y = aPgSize.Height() - rPos.Y() - rSize.Height(); + // if necessary: adjustments (80/200) for pixel approx. errors + if( x < 0 ) + rPos.AdjustX(x + 80 ); + if( y < 0 ) + rPos.AdjustY(y + 200 ); + rPos.AdjustX(rSize.Width() / 2 ); // position at paste is center + rPos.AdjustY(rSize.Height() / 2 ); +} + +void ScViewFunc::PasteDraw( const Point& rLogicPos, SdrModel* pModel, + bool bGroup, std::u16string_view rSrcShellID, std::u16string_view rDestShellID ) +{ + bool bSameDocClipboard = rSrcShellID == rDestShellID; + + MakeDrawLayer(); + Point aPos( rLogicPos ); + + // MapMode at Outliner-RefDevice has to be right (as in FuText::MakeOutliner) + //! merge with FuText::MakeOutliner? + MapMode aOldMapMode; + OutputDevice* pRef = GetViewData().GetDocument().GetDrawLayer()->GetRefDevice(); + if (pRef) + { + aOldMapMode = pRef->GetMapMode(); + pRef->SetMapMode( MapMode(MapUnit::Map100thMM) ); + } + + bool bNegativePage = GetViewData().GetDocument().IsNegativePage( GetViewData().GetTabNo() ); + + SdrView* pDragEditView = nullptr; + ScModule* pScMod = SC_MOD(); + const ScDragData& rData = pScMod->GetDragData(); + ScDrawTransferObj* pDrawTrans = rData.pDrawTransfer; + if (pDrawTrans) + { + pDragEditView = pDrawTrans->GetDragSourceView(); + + aPos -= aDragStartDiff; + if ( bNegativePage ) + { + if (aPos.X() > 0) aPos.setX( 0 ); + } + else + { + if (aPos.X() < 0) aPos.setX( 0 ); + } + if (aPos.Y() < 0) aPos.setY( 0 ); + } + + ScDrawView* pScDrawView = GetScDrawView(); + if (bGroup) + pScDrawView->BegUndo( ScResId( STR_UNDO_PASTE ) ); + + bool bSameDoc = ( pDragEditView && &pDragEditView->GetModel() == &pScDrawView->GetModel() ); + if (bSameDoc) + { + // copy locally - incl. charts + + Point aSourceStart = pDragEditView->GetAllMarkedRect().TopLeft(); + tools::Long nDiffX = aPos.X() - aSourceStart.X(); + tools::Long nDiffY = aPos.Y() - aSourceStart.Y(); + + // move within a page? + + if ( bPasteIsMove && + pScDrawView->GetSdrPageView()->GetPage() == + pDragEditView->GetSdrPageView()->GetPage() ) + { + if ( nDiffX != 0 || nDiffY != 0 ) + pDragEditView->MoveAllMarked(Size(nDiffX,nDiffY)); + } + else + { + SdrModel& rDrawModel = pDragEditView->GetModel(); + SCTAB nTab = GetViewData().GetTabNo(); + SdrPage* pDestPage = rDrawModel.GetPage( static_cast< sal_uInt16 >( nTab ) ); + OSL_ENSURE(pDestPage,"who is this, Page?"); + + ::std::vector< OUString > aExcludedChartNames; + if ( pDestPage ) + { + ScChartHelper::GetChartNames( aExcludedChartNames, pDestPage ); + } + + SdrMarkList aMark = pDragEditView->GetMarkedObjectList(); + aMark.ForceSort(); + const size_t nMarkCnt=aMark.GetMarkCount(); + for (size_t nm=0; nm<nMarkCnt; ++nm) { + const SdrMark* pM=aMark.GetMark(nm); + const SdrObject* pObj=pM->GetMarkedSdrObj(); + + // Directly Clone to target SdrModel + rtl::Reference<SdrObject> pNewObj(pObj->CloneSdrObject(rDrawModel)); + + if (pNewObj!=nullptr) + { + // copy graphics within the same model - always needs new name + if ( dynamic_cast<const SdrGrafObj*>( pNewObj.get()) != nullptr && !bPasteIsMove ) + pNewObj->SetName(static_cast<ScDrawLayer*>(&rDrawModel)->GetNewGraphicName()); + + if (nDiffX!=0 || nDiffY!=0) + pNewObj->NbcMove(Size(nDiffX,nDiffY)); + if (pDestPage) + pDestPage->InsertObject( pNewObj.get() ); + pScDrawView->AddUndo(std::make_unique<SdrUndoInsertObj>( *pNewObj )); + + if (ScDrawLayer::IsCellAnchored(*pNewObj)) + ScDrawLayer::SetCellAnchoredFromPosition(*pNewObj, GetViewData().GetDocument(), nTab, + ScDrawLayer::IsResizeWithCell(*pNewObj)); + } + } + + if (bPasteIsMove) + pDragEditView->DeleteMarked(); + + ScDocument& rDocument = GetViewData().GetDocument(); + ScDocShell* pDocShell = GetViewData().GetDocShell(); + ScModelObj* pModelObj = ( pDocShell ? pDocShell->GetModel() : nullptr ); + if ( pDestPage && pModelObj && pDrawTrans ) + { + const ScRangeListVector& rProtectedChartRangesVector( pDrawTrans->GetProtectedChartRangesVector() ); + ScChartHelper::CreateProtectedChartListenersAndNotify( rDocument, pDestPage, pModelObj, nTab, + rProtectedChartRangesVector, aExcludedChartNames, bSameDoc ); + } + } + } + else + { + bPasteIsMove = false; // no internal move happened + SdrView aView(*pModel); // #i71529# never create a base class of SdrView directly! + SdrPageView* pPv = aView.ShowSdrPage(aView.GetModel().GetPage(0)); + aView.MarkAllObj(pPv); + Size aSize = aView.GetAllMarkedRect().GetSize(); + lcl_AdjustInsertPos( GetViewData(), aPos, aSize ); + + // don't change marking if OLE object is active + // (at Drop from OLE object it would be deactivated in the middle of ExecuteDrag!) + + SdrInsertFlags nOptions = SdrInsertFlags::NONE; + SfxInPlaceClient* pClient = GetViewData().GetViewShell()->GetIPClient(); + if ( pClient && pClient->IsObjectInPlaceActive() ) + nOptions |= SdrInsertFlags::DONTMARK; + + ::std::vector< OUString > aExcludedChartNames; + SCTAB nTab = GetViewData().GetTabNo(); + SdrPage* pPage = pScDrawView->GetModel().GetPage( static_cast< sal_uInt16 >( nTab ) ); + OSL_ENSURE( pPage, "Page?" ); + if ( pPage ) + { + ScChartHelper::GetChartNames( aExcludedChartNames, pPage ); + } + + if ( !bSameDocClipboard ) + { + auto pPool = static_cast<ScStyleSheetPool*>(pScDrawView->GetModel().GetStyleSheetPool()); + pPool->CopyUsedGraphicStylesFrom(pModel->GetStyleSheetPool()); + + // #89247# Set flag for ScDocument::UpdateChartListeners() which is + // called during paste. + GetViewData().GetDocument().SetPastingDrawFromOtherDoc( true ); + } + + pScDrawView->Paste(*pModel, aPos, nullptr, nOptions); + + if ( !bSameDocClipboard ) + GetViewData().GetDocument().SetPastingDrawFromOtherDoc( false ); + + // Paste puts all objects on the active (front) layer + // controls must be on SC_LAYER_CONTROLS + if (pPage) + { + SdrObjListIter aIter( pPage, SdrIterMode::DeepNoGroups ); + SdrObject* pObject = aIter.Next(); + while (pObject) + { + if ( dynamic_cast<const SdrUnoObj*>( pObject) != nullptr && pObject->GetLayer() != SC_LAYER_CONTROLS ) + pObject->NbcSetLayer(SC_LAYER_CONTROLS); + + if (ScDrawLayer::IsCellAnchored(*pObject)) + ScDrawLayer::SetCellAnchoredFromPosition(*pObject, GetViewData().GetDocument(), nTab, + ScDrawLayer::IsResizeWithCell(*pObject)); + + pObject = aIter.Next(); + } + } + + // all graphics objects must have names + GetViewData().GetDocument().EnsureGraphicNames(); + + ScDocument& rDocument = GetViewData().GetDocument(); + ScDocShell* pDocShell = GetViewData().GetDocShell(); + ScModelObj* pModelObj = ( pDocShell ? pDocShell->GetModel() : nullptr ); + const ScDrawTransferObj* pTransferObj = ScDrawTransferObj::GetOwnClipboard(ScTabViewShell::GetClipData(GetViewData().GetActiveWin())); + if ( pPage && pModelObj && ( pTransferObj || pDrawTrans ) ) + { + const ScRangeListVector& rProtectedChartRangesVector( + pTransferObj ? pTransferObj->GetProtectedChartRangesVector() : pDrawTrans->GetProtectedChartRangesVector() ); + ScChartHelper::CreateProtectedChartListenersAndNotify( rDocument, pPage, pModelObj, nTab, + rProtectedChartRangesVector, aExcludedChartNames, bSameDocClipboard ); + } + } + + if (bGroup) + { + pScDrawView->GroupMarked(); + pScDrawView->EndUndo(); + } + + if (pRef) + pRef->SetMapMode( aOldMapMode ); + + // GetViewData().GetViewShell()->SetDrawShell( true ); + // It is not sufficient to just set the DrawShell if we pasted, for + // example, a chart. SetDrawShellOrSub() would only work for D&D in the + // same document but not if inserting from the clipboard, therefore + // MarkListHasChanged() is what we need. + pScDrawView->MarkListHasChanged(); + +} + +bool ScViewFunc::PasteObject( const Point& rPos, const uno::Reference < embed::XEmbeddedObject >& xObj, + const Size* pDescSize, const Graphic* pReplGraph, const OUString& aMediaType, sal_Int64 nAspect ) +{ + MakeDrawLayer(); + if ( xObj.is() ) + { + OUString aName; + //TODO/MBA: is that OK? + comphelper::EmbeddedObjectContainer& aCnt = GetViewData().GetViewShell()->GetObjectShell()->GetEmbeddedObjectContainer(); + if ( !aCnt.HasEmbeddedObject( xObj ) ) + aCnt.InsertEmbeddedObject( xObj, aName ); + else + aName = aCnt.GetEmbeddedObjectName( xObj ); + + svt::EmbeddedObjectRef aObjRef( xObj, nAspect ); + if ( pReplGraph ) + aObjRef.SetGraphic( *pReplGraph, aMediaType ); + + Size aSize; + if ( nAspect == embed::Aspects::MSOLE_ICON ) + { + MapMode aMapMode( MapUnit::Map100thMM ); + aSize = aObjRef.GetSize( &aMapMode ); + } + else + { + // working with visual area can switch object to running state + MapUnit aMapObj = VCLUnoHelper::UnoEmbed2VCLMapUnit( xObj->getMapUnit( nAspect ) ); + MapUnit aMap100 = MapUnit::Map100thMM; + + if ( pDescSize && pDescSize->Width() && pDescSize->Height() ) + { + // use size from object descriptor if given + aSize = OutputDevice::LogicToLogic(*pDescSize, MapMode(aMap100), MapMode(aMapObj)); + awt::Size aSz; + aSz.Width = aSize.Width(); + aSz.Height = aSize.Height(); + xObj->setVisualAreaSize( nAspect, aSz ); + } + + awt::Size aSz; + try + { + aSz = xObj->getVisualAreaSize( nAspect ); + } + catch ( embed::NoVisualAreaSizeException& ) + { + // the default size will be set later + } + + aSize = Size( aSz.Width, aSz.Height ); + aSize = OutputDevice::LogicToLogic(aSize, MapMode(aMapObj), MapMode(aMap100)); // for SdrOle2Obj + + if( aSize.IsEmpty() ) + { + OSL_FAIL("SvObjectDescriptor::GetSize == 0"); + aSize.setWidth( 5000 ); + aSize.setHeight( 5000 ); + aSize = OutputDevice::LogicToLogic(aSize, MapMode(aMap100), MapMode(aMapObj)); + aSz.Width = aSize.Width(); + aSz.Height = aSize.Height(); + xObj->setVisualAreaSize( nAspect, aSz ); + } + } + + // don't call AdjustInsertPos + Point aInsPos = rPos; + if ( GetViewData().GetDocument().IsNegativePage( GetViewData().GetTabNo() ) ) + aInsPos.AdjustX( -(aSize.Width()) ); + tools::Rectangle aRect( aInsPos, aSize ); + + ScDrawView* pDrView = GetScDrawView(); + rtl::Reference<SdrOle2Obj> pSdrObj = new SdrOle2Obj( + pDrView->getSdrModelFromSdrView(), + aObjRef, + aName, + aRect); + + SdrPageView* pPV = pDrView->GetSdrPageView(); + pDrView->InsertObjectSafe( pSdrObj.get(), *pPV ); // don't mark if OLE + GetViewData().GetViewShell()->SetDrawShell( true ); + return true; + } + else + return false; +} + +bool ScViewFunc::PasteBitmapEx( const Point& rPos, const BitmapEx& rBmpEx ) +{ + Graphic aGraphic(rBmpEx); + return PasteGraphic( rPos, aGraphic, "" ); +} + +bool ScViewFunc::PasteMetaFile( const Point& rPos, const GDIMetaFile& rMtf ) +{ + Graphic aGraphic(rMtf); + return PasteGraphic( rPos, aGraphic, "" ); +} + +bool ScViewFunc::PasteGraphic( const Point& rPos, const Graphic& rGraphic, + const OUString& rFile ) +{ + MakeDrawLayer(); + ScDrawView* pScDrawView = GetScDrawView(); + + if (!pScDrawView) + return false; + + // #i123922# check if the drop was over an existing object; if yes, evtl. replace + // the graphic for a SdrGraphObj (including link state updates) or adapt the fill + // style for other objects + SdrPageView* pPageView = pScDrawView->GetSdrPageView(); + if (pPageView) + { + SdrObject* pPickObj = pScDrawView->PickObj(rPos, pScDrawView->getHitTolLog(), pPageView); + if (pPickObj) + { + const OUString aBeginUndo(ScResId(STR_UNDO_DRAGDROP)); + SdrObject* pResult = pScDrawView->ApplyGraphicToObject( + *pPickObj, + rGraphic, + aBeginUndo, + rFile); + + if (pResult) + { + // we are done; mark the modified/new object + pScDrawView->MarkObj(pResult, pScDrawView->GetSdrPageView()); + return true; + } + } + } + + Point aPos( rPos ); + vcl::Window* pWin = GetActiveWin(); + MapMode aSourceMap = rGraphic.GetPrefMapMode(); + MapMode aDestMap( MapUnit::Map100thMM ); + + if (aSourceMap.GetMapUnit() == MapUnit::MapPixel) + { + // consider pixel correction, so bitmap fits to screen + Fraction aScaleX, aScaleY; + pScDrawView->CalcNormScale( aScaleX, aScaleY ); + aDestMap.SetScaleX(aScaleX); + aDestMap.SetScaleY(aScaleY); + } + + Size aSize = pWin->LogicToLogic( rGraphic.GetPrefSize(), &aSourceMap, &aDestMap ); + + if ( GetViewData().GetDocument().IsNegativePage( GetViewData().GetTabNo() ) ) + aPos.AdjustX( -(aSize.Width()) ); + + GetViewData().GetViewShell()->SetDrawShell( true ); + tools::Rectangle aRect(aPos, aSize); + rtl::Reference<SdrGrafObj> pGrafObj = new SdrGrafObj( + pScDrawView->getSdrModelFromSdrView(), + rGraphic, + aRect); + + // path was the name of the graphic in history + + ScDrawLayer* pLayer = static_cast<ScDrawLayer*>(&pScDrawView->GetModel()); + OUString aName = pLayer->GetNewGraphicName(); // "Graphics" + pGrafObj->SetName(aName); + + // don't mark if OLE + bool bSuccess = pScDrawView->InsertObjectSafe(pGrafObj.get(), *pScDrawView->GetSdrPageView()); + + // SetGraphicLink has to be used after inserting the object, + // otherwise an empty graphic is swapped in and the contact stuff crashes. + // See #i37444#. + if (bSuccess && !rFile.isEmpty()) + pGrafObj->SetGraphicLink( rFile ); + + return bSuccess; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/viewfunc.cxx b/sc/source/ui/view/viewfunc.cxx new file mode 100644 index 0000000000..25633cae38 --- /dev/null +++ b/sc/source/ui/view/viewfunc.cxx @@ -0,0 +1,3183 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <config_features.h> + +#include <scitems.hxx> + +#include <sfx2/app.hxx> +#include <svx/algitem.hxx> +#include <editeng/boxitem.hxx> +#include <editeng/editobj.hxx> +#include <editeng/langitem.hxx> +#include <editeng/justifyitem.hxx> +#include <o3tl/unit_conversion.hxx> +#include <sfx2/bindings.hxx> +#include <svl/numformat.hxx> +#include <svl/zforlist.hxx> +#include <svl/zformat.hxx> +#include <vcl/weld.hxx> +#include <vcl/virdev.hxx> +#include <stdlib.h> +#include <unotools/charclass.hxx> +#include <vcl/uitest/logger.hxx> +#include <vcl/uitest/eventdescription.hxx> +#include <osl/diagnose.h> +#include <formula/paramclass.hxx> + +#include <viewfunc.hxx> +#include <tabvwsh.hxx> +#include <docsh.hxx> +#include <attrib.hxx> +#include <patattr.hxx> +#include <docpool.hxx> +#include <sc.hrc> +#include <undocell.hxx> +#include <undoblk.hxx> +#include <refundo.hxx> +#include <olinetab.hxx> +#include <rangenam.hxx> +#include <globstr.hrc> +#include <global.hxx> +#include <stlsheet.hxx> +#include <editutil.hxx> +#include <formulacell.hxx> +#include <scresid.hxx> +#include <inputhdl.hxx> +#include <scmod.hxx> +#include <inputopt.hxx> +#include <compiler.hxx> +#include <docfunc.hxx> +#include <appoptio.hxx> +#include <sizedev.hxx> +#include <editable.hxx> +#include <scui_def.hxx> +#include <funcdesc.hxx> +#include <docuno.hxx> +#include <cellsuno.hxx> +#include <tokenarray.hxx> +#include <rowheightcontext.hxx> +#include <comphelper/lok.hxx> +#include <conditio.hxx> +#include <columnspanset.hxx> +#include <stringutil.hxx> +#include <SparklineList.hxx> + +#include <memory> + +static void ShowFilteredRows(ScDocument& rDoc, SCTAB nTab, SCCOLROW nStartNo, SCCOLROW nEndNo, + bool bShow) +{ + SCROW nFirstRow = nStartNo; + SCROW nLastRow = nStartNo; + do + { + if (!rDoc.RowFiltered(nFirstRow, nTab, nullptr, &nLastRow)) + rDoc.ShowRows(nFirstRow, nLastRow < nEndNo ? nLastRow : nEndNo, nTab, bShow); + nFirstRow = nLastRow + 1; + } while (nFirstRow <= nEndNo); +} + +static void lcl_PostRepaintCondFormat( const ScConditionalFormat *pCondFmt, ScDocShell *pDocSh ) +{ + if( pCondFmt ) + { + const ScRangeList& rRanges = pCondFmt->GetRange(); + + pDocSh->PostPaint( rRanges, PaintPartFlags::All ); + } +} + +static void lcl_PostRepaintSparkLine(sc::SparklineList* pSparklineList, const ScRange& rRange, + ScDocShell* pDocSh) +{ + if (pSparklineList) + { + for (auto& rSparkLineGroup : pSparklineList->getSparklineGroups()) + { + for (auto& rSparkline : pSparklineList->getSparklinesFor(rSparkLineGroup)) + { + if (rSparkline->getInputRange().Contains(rRange)) + { + pDocSh->PostPaint( + ScRange(rSparkline->getColumn(), rSparkline->getRow(), rRange.aStart.Tab()), + PaintPartFlags::All, SC_PF_TESTMERGE); + } + } + } + } +} + +ScViewFunc::ScViewFunc( vcl::Window* pParent, ScDocShell& rDocSh, ScTabViewShell* pViewShell ) : + ScTabView( pParent, rDocSh, pViewShell ), + bFormatValid( false ) +{ +} + +ScViewFunc::~ScViewFunc() +{ +} + +namespace { + +void collectUIInformation(std::map<OUString, OUString>&& aParameters, const OUString& rAction) +{ + EventDescription aDescription; + aDescription.aID = "grid_window"; + aDescription.aAction = rAction; + aDescription.aParameters = std::move(aParameters); + aDescription.aParent = "MainWindow"; + aDescription.aKeyWord = "ScGridWinUIObject"; + + UITestLogger::getInstance().logEvent(aDescription); +} + +} + +void ScViewFunc::StartFormatArea() +{ + // anything to do? + if ( !SC_MOD()->GetInputOptions().GetExtendFormat() ) + return; + + // start only with single cell (marked or cursor position) + ScRange aMarkRange; + bool bOk = (GetViewData().GetSimpleArea( aMarkRange ) == SC_MARK_SIMPLE); + if ( bOk && aMarkRange.aStart != aMarkRange.aEnd ) + bOk = false; + + if (bOk) + { + bFormatValid = true; + aFormatSource = aMarkRange.aStart; + aFormatArea = ScRange( aFormatSource ); + } + else + bFormatValid = false; // discard old range +} + +bool ScViewFunc::TestFormatArea( SCCOL nCol, SCROW nRow, SCTAB nTab, bool bAttrChanged ) +{ + // anything to do? + if ( !SC_MOD()->GetInputOptions().GetExtendFormat() ) + return false; + + // Test: treat input with numberformat (bAttrChanged) always as new Attribute + // (discard old Area ). If not wanted, discard if-statement + if ( bAttrChanged ) + { + StartFormatArea(); + return false; + } + + //! Test if cell empty ??? + + bool bFound = false; + ScRange aNewRange = aFormatArea; + if ( bFormatValid && nTab == aFormatSource.Tab() ) + { + if ( nRow >= aFormatArea.aStart.Row() && nRow <= aFormatArea.aEnd.Row() ) + { + // within range? + if ( nCol >= aFormatArea.aStart.Col() && nCol <= aFormatArea.aEnd.Col() ) + { + bFound = true; // do not change range + } + // left ? + if ( nCol+1 == aFormatArea.aStart.Col() ) + { + bFound = true; + aNewRange.aStart.SetCol( nCol ); + } + // right ? + if ( nCol == aFormatArea.aEnd.Col()+1 ) + { + bFound = true; + aNewRange.aEnd.SetCol( nCol ); + } + } + if ( nCol >= aFormatArea.aStart.Col() && nCol <= aFormatArea.aEnd.Col() ) + { + // top ? + if ( nRow+1 == aFormatArea.aStart.Row() ) + { + bFound = true; + aNewRange.aStart.SetRow( nRow ); + } + // bottom ? + if ( nRow == aFormatArea.aEnd.Row()+1 ) + { + bFound = true; + aNewRange.aEnd.SetRow( nRow ); + } + } + } + + if (bFound) + aFormatArea = aNewRange; // extend + else + bFormatValid = false; // outside of range -> break + + return bFound; +} + +void ScViewFunc::DoAutoAttributes( SCCOL nCol, SCROW nRow, SCTAB nTab, + bool bAttrChanged ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + + const ScPatternAttr* pSource = rDoc.GetPattern( + aFormatSource.Col(), aFormatSource.Row(), nTab ); + if ( !pSource->GetItem(ATTR_MERGE).IsMerged() ) + { + ScRange aRange( nCol, nRow, nTab, nCol, nRow, nTab ); + ScMarkData aMark(rDoc.GetSheetLimits()); + aMark.SetMarkArea( aRange ); + + ScDocFunc &rFunc = GetViewData().GetDocFunc(); + + // pOldPattern is only valid until call to ApplyAttributes! + const ScPatternAttr* pOldPattern = rDoc.GetPattern( nCol, nRow, nTab ); + const ScStyleSheet* pSrcStyle = pSource->GetStyleSheet(); + if ( pSrcStyle && pSrcStyle != pOldPattern->GetStyleSheet() ) + rFunc.ApplyStyle( aMark, pSrcStyle->GetName(), false ); + + rFunc.ApplyAttributes( aMark, *pSource, false ); + } + + if ( bAttrChanged ) // value entered with number format? + aFormatSource.Set( nCol, nRow, nTab ); // then set a new source +} + +// additional routines + +sal_uInt16 ScViewFunc::GetOptimalColWidth( SCCOL nCol, SCTAB nTab, bool bFormula ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScMarkData& rMark = GetViewData().GetMarkData(); + + double nPPTX = GetViewData().GetPPTX(); + double nPPTY = GetViewData().GetPPTY(); + Fraction aZoomX = GetViewData().GetZoomX(); + Fraction aZoomY = GetViewData().GetZoomY(); + + ScSizeDeviceProvider aProv(pDocSh); + if (aProv.IsPrinter()) + { + nPPTX = aProv.GetPPTX(); + nPPTY = aProv.GetPPTY(); + aZoomX = aZoomY = Fraction( 1, 1 ); + } + + sal_uInt16 nTwips = rDoc.GetOptimalColWidth( nCol, nTab, aProv.GetDevice(), + nPPTX, nPPTY, aZoomX, aZoomY, bFormula, &rMark ); + return nTwips; +} + +bool ScViewFunc::SelectionEditable( bool* pOnlyNotBecauseOfMatrix /* = NULL */ ) +{ + bool bRet; + ScDocument& rDoc = GetViewData().GetDocument(); + ScMarkData& rMark = GetViewData().GetMarkData(); + if (rMark.IsMarked() || rMark.IsMultiMarked()) + bRet = rDoc.IsSelectionEditable( rMark, pOnlyNotBecauseOfMatrix ); + else + { + SCCOL nCol = GetViewData().GetCurX(); + SCROW nRow = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + bRet = rDoc.IsBlockEditable( nTab, nCol, nRow, nCol, nRow, + pOnlyNotBecauseOfMatrix ); + } + return bRet; +} + +static bool lcl_FunctionKnown( sal_uInt16 nOpCode ) +{ + const ScFunctionList* pFuncList = ScGlobal::GetStarCalcFunctionList(); + if ( pFuncList ) + { + sal_uLong nCount = pFuncList->GetCount(); + for (sal_uLong i=0; i<nCount; i++) + if ( pFuncList->GetFunction(i)->nFIndex == nOpCode ) + return true; + } + return false; +} + +static bool lcl_AddFunction( ScAppOptions& rAppOpt, sal_uInt16 nOpCode ) +{ + sal_uInt16 nOldCount = rAppOpt.GetLRUFuncListCount(); + sal_uInt16* pOldList = rAppOpt.GetLRUFuncList(); + sal_uInt16 nPos; + for (nPos=0; nPos<nOldCount; nPos++) + if (pOldList[nPos] == nOpCode) // is the function already in the list? + { + if ( nPos == 0 ) + return false; // already at the top -> no change + + // count doesn't change, so the original array is modified + + for (sal_uInt16 nCopy=nPos; nCopy>0; nCopy--) + pOldList[nCopy] = pOldList[nCopy-1]; + pOldList[0] = nOpCode; + + return true; // list has changed + } + + if ( !lcl_FunctionKnown( nOpCode ) ) + return false; // not in function list -> no change + + sal_uInt16 nNewCount = std::min( static_cast<sal_uInt16>(nOldCount + 1), sal_uInt16(LRU_MAX) ); + sal_uInt16 nNewList[LRU_MAX]; + nNewList[0] = nOpCode; + for (nPos=1; nPos<nNewCount; nPos++) + nNewList[nPos] = pOldList[nPos-1]; + rAppOpt.SetLRUFuncList( nNewList, nNewCount ); + + return true; // list has changed +} + +namespace HelperNotifyChanges +{ + static void NotifyIfChangesListeners(const ScDocShell &rDocShell, ScMarkData& rMark, + SCCOL nCol, SCROW nRow, const OUString& rType = "cell-change") + { + ScModelObj* pModelObj = rDocShell.GetModel(); + + ScRangeList aChangeRanges; + for (const auto& rTab : rMark) + aChangeRanges.push_back( ScRange( nCol, nRow, rTab ) ); + + if (getMustPropagateChangesModel(pModelObj)) + Notify(*pModelObj, aChangeRanges, rType); + else + { + Notify(*pModelObj, aChangeRanges, isDataAreaInvalidateType(rType) + ? OUString("data-area-invalidate") : OUString("data-area-extend")); + } + } +} + +namespace +{ + class AutoCorrectQuery : public weld::MessageDialogController + { + private: + std::unique_ptr<weld::TextView> m_xError; + public: + AutoCorrectQuery(weld::Window* pParent, const OUString& rFormula) + : weld::MessageDialogController(pParent, "modules/scalc/ui/warnautocorrect.ui", "WarnAutoCorrect", "grid") + , m_xError(m_xBuilder->weld_text_view("error")) + { + m_xDialog->set_default_response(RET_YES); + + const int nMaxWidth = m_xError->get_approximate_digit_width() * 65; + const int nMaxHeight = m_xError->get_height_rows(6); + m_xError->set_size_request(nMaxWidth, nMaxHeight); + + m_xError->set_text(rFormula); + } + }; +} + +// actual functions + +// input - undo OK +void ScViewFunc::EnterData( SCCOL nCol, SCROW nRow, SCTAB nTab, + const OUString& rString, + const EditTextObject* pData, + bool bMatrixExpand ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + ScMarkData rMark(GetViewData().GetMarkData()); + bool bRecord = rDoc.IsUndoEnabled(); + SCTAB i; + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocFunc &rFunc = GetViewData().GetDocFunc(); + ScDocShellModificator aModificator( *pDocSh ); + + ScEditableTester aTester( rDoc, nCol,nRow, nCol,nRow, rMark ); + if (!aTester.IsEditable()) + { + ErrorMessage(aTester.GetMessageId()); + PaintArea(nCol, nRow, nCol, nRow); // possibly the edit-engine is still painted there + return; + } + + if ( bRecord ) + rFunc.EnterListAction( STR_UNDO_ENTERDATA ); + + bool bFormula = false; + + // do not check formula if it is a text cell + sal_uInt32 format = rDoc.GetNumberFormat( nCol, nRow, nTab ); + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + // a single '=' character is handled as string (needed for special filters) + if ( pFormatter->GetType(format) != SvNumFormatType::TEXT && rString.getLength() > 1 ) + { + if ( rString[0] == '=' ) + { + // handle as formula + bFormula = true; + } + else if ( rString[0] == '+' || rString[0] == '-' ) + { + // if there is more than one leading '+' or '-' character, remove the additional ones + sal_Int32 nIndex = 1; + sal_Int32 nLen = rString.getLength(); + while ( nIndex < nLen && ( rString[ nIndex ] == '+' || rString[ nIndex ] == '-' ) ) + { + ++nIndex; + } + OUString aString = rString.replaceAt( 1, nIndex - 1, u"" ); + + // if the remaining part without the leading '+' or '-' character + // is non-empty and not a number, handle as formula + if ( aString.getLength() > 1 ) + { + double fNumber = 0; + if ( !pFormatter->IsNumberFormat( aString, format, fNumber ) ) + { + bFormula = true; + } + } + } + } + + bool bNumFmtChanged = false; + if ( bFormula ) + { // formula, compile with autoCorrection + i = rMark.GetFirstSelected(); + ScAddress aPos( nCol, nRow, i ); + ScCompiler aComp( rDoc, aPos, rDoc.GetGrammar(), true, false ); +//2do: enable/disable autoCorrection via calcoptions + aComp.SetAutoCorrection( true ); + if ( rString[0] == '+' || rString[0] == '-' ) + { + aComp.SetExtendedErrorDetection( ScCompiler::EXTENDED_ERROR_DETECTION_NAME_BREAK ); + } + OUString aFormula( rString ); + std::unique_ptr< ScTokenArray > pArr; + bool bAgain; + do + { + bAgain = false; + bool bAddEqual = false; + pArr = aComp.CompileString( aFormula ); + bool bCorrected = aComp.IsCorrected(); + std::unique_ptr< ScTokenArray > pArrFirst; + if ( bCorrected ) + { // try to parse with first parser-correction + pArrFirst = std::move( pArr ); + pArr = aComp.CompileString( aComp.GetCorrectedFormula() ); + } + if ( pArr->GetCodeError() == FormulaError::NONE ) + { + bAddEqual = true; + aComp.CompileTokenArray(); + bCorrected |= aComp.IsCorrected(); + } + if ( bCorrected ) + { + OUString aCorrectedFormula; + if ( bAddEqual ) + { + aCorrectedFormula = "=" + aComp.GetCorrectedFormula(); + } + else + aCorrectedFormula = aComp.GetCorrectedFormula(); + short nResult; + if ( aCorrectedFormula.getLength() == 1 ) + nResult = RET_NO; // empty formula, just '=' + else + { + AutoCorrectQuery aQueryBox(GetViewData().GetDialogParent(), aCorrectedFormula); + nResult = aQueryBox.run(); + } + if ( nResult == RET_YES ) + { + aFormula = aCorrectedFormula; + bAgain = true; + } + else + { + if ( pArrFirst ) + pArr = std::move( pArrFirst ); + } + } + } while ( bAgain ); + // to be used in multiple tabs, the formula must be compiled anew + // via ScFormulaCell copy-ctor because of RangeNames, + // the same code-array for all cells is not possible. + // If the array has an error, (it) must be RPN-erased in the newly generated + // cells and the error be set explicitly, so that + // via FormulaCell copy-ctor and Interpreter it will be, when possible, + // ironed out again, too intelligent... e.g.: =1)) + FormulaError nError = pArr->GetCodeError(); + if ( nError == FormulaError::NONE ) + { + // update list of recent functions with all functions that + // are not within parentheses + + ScModule* pScMod = SC_MOD(); + ScAppOptions aAppOpt = pScMod->GetAppOptions(); + bool bOptChanged = false; + + formula::FormulaToken** ppToken = pArr->GetArray(); + sal_uInt16 nTokens = pArr->GetLen(); + sal_uInt16 nLevel = 0; + for (sal_uInt16 nTP=0; nTP<nTokens; nTP++) + { + formula::FormulaToken* pTok = ppToken[nTP]; + OpCode eOp = pTok->GetOpCode(); + if ( eOp == ocOpen ) + ++nLevel; + else if ( eOp == ocClose && nLevel ) + --nLevel; + if ( nLevel == 0 && pTok->IsFunction() && + lcl_AddFunction( aAppOpt, sal::static_int_cast<sal_uInt16>( eOp ) ) ) + bOptChanged = true; + } + + if ( bOptChanged ) + { + pScMod->SetAppOptions(aAppOpt); + } + + if (bMatrixExpand) + { + // If the outer function/operator returns an array/matrix then + // enter a matrix formula. ScViewFunc::EnterMatrix() takes care + // of selection/mark of the result dimensions or preselected + // mark. If the user wanted less or a single cell then should + // mark such prior to entering the formula. + const formula::FormulaToken* pToken = pArr->LastRPNToken(); + if (pToken && (formula::FormulaCompiler::IsMatrixFunction( pToken->GetOpCode()) + || pToken->IsInForceArray())) + { + // Discard this (still empty here) Undo action, + // EnterMatrix() will create its own. + if (bRecord) + rFunc.EndListAction(); + + // Use corrected formula string. + EnterMatrix( aFormula, rDoc.GetGrammar()); + + return; + } + } + } + + ScFormulaCell aCell(rDoc, aPos, std::move( pArr ), formula::FormulaGrammar::GRAM_DEFAULT, ScMatrixMode::NONE); + + for (const auto& rTab : rMark) + { + i = rTab; + aPos.SetTab( i ); + const sal_uInt32 nIndex = rDoc.GetAttr( + nCol, nRow, i, ATTR_VALUE_FORMAT )->GetValue(); + const SvNumFormatType nType = pFormatter->GetType( nIndex); + if (nType == SvNumFormatType::TEXT || + ((rString[0] == '+' || rString[0] == '-') && nError != FormulaError::NONE && rString == aFormula)) + { + if ( pData ) + { + // A clone of pData will be stored in the cell. + rFunc.SetEditCell(aPos, *pData, true); + } + else + rFunc.SetStringCell(aPos, aFormula, true); + } + else + { + ScFormulaCell* pCell = new ScFormulaCell( aCell, rDoc, aPos ); + if ( nError != FormulaError::NONE ) + { + pCell->GetCode()->DelRPN(); + pCell->SetErrCode( nError ); + if(pCell->GetCode()->IsHyperLink()) + pCell->GetCode()->SetHyperLink(false); + } + if (nType == SvNumFormatType::LOGICAL) + { + // Reset to General so the actual format can be determined + // after the cell has been interpreted. A sticky boolean + // number format is highly likely unwanted... see tdf#75650. + // General of same locale as current number format. + const SvNumberformat* pEntry = pFormatter->GetEntry( nIndex); + const LanguageType nLang = (pEntry ? pEntry->GetLanguage() : ScGlobal::eLnge); + const sal_uInt32 nFormat = pFormatter->GetStandardFormat( SvNumFormatType::NUMBER, nLang); + ScPatternAttr aPattern( rDoc.GetPool()); + aPattern.GetItemSet().Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nFormat)); + ScMarkData aMark(rDoc.GetSheetLimits()); + aMark.SelectTable( i, true); + aMark.SetMarkArea( ScRange( aPos)); + rFunc.ApplyAttributes( aMark, aPattern, false); + bNumFmtChanged = true; + } + rFunc.SetFormulaCell(aPos, pCell, true); + } + } + } + else + { + ScFieldEditEngine& rEngine = rDoc.GetEditEngine(); + for (const auto& rTab : rMark) + { + bool bNumFmtSet = false; + const ScAddress aScAddress(nCol, nRow, rTab); + + // tdf#104902 - handle embedded newline + if (ScStringUtil::isMultiline(rString)) + { + rEngine.SetTextCurrentDefaults(rString); + rDoc.SetEditText(aScAddress, rEngine.CreateTextObject()); + pDocSh->AdjustRowHeight(nRow, nRow, rTab); + } + else + { + rFunc.SetNormalString(bNumFmtSet, aScAddress, rString, false); + } + + if (bNumFmtSet) + { + /* FIXME: if set on any sheet results in changed only on + * sheet nTab for TestFormatArea() and DoAutoAttributes() */ + bNumFmtChanged = true; + } + } + } + + bool bAutoFormat = TestFormatArea(nCol, nRow, nTab, bNumFmtChanged); + + if (bAutoFormat) + DoAutoAttributes(nCol, nRow, nTab, bNumFmtChanged); + + pDocSh->UpdateOle(GetViewData()); + + const OUString aType(rString.isEmpty() ? u"delete-content" : u"cell-change"); + HelperNotifyChanges::NotifyIfChangesListeners(*pDocSh, rMark, nCol, nRow, aType); + + if ( bRecord ) + rFunc.EndListAction(); + + aModificator.SetDocumentModified(); + lcl_PostRepaintCondFormat( rDoc.GetCondFormat( nCol, nRow, nTab ), pDocSh ); + lcl_PostRepaintSparkLine(rDoc.GetSparklineList(nTab), ScRange(nCol, nRow, nTab), pDocSh); +} + +// enter value in single cell (on nTab only) + +void ScViewFunc::EnterValue( SCCOL nCol, SCROW nRow, SCTAB nTab, const double& rValue ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + + if (!pDocSh) + return; + + bool bUndo(rDoc.IsUndoEnabled()); + ScDocShellModificator aModificator( *pDocSh ); + + ScEditableTester aTester( rDoc, nTab, nCol,nRow, nCol,nRow ); + if (aTester.IsEditable()) + { + ScAddress aPos( nCol, nRow, nTab ); + ScCellValue aUndoCell; + if (bUndo) + aUndoCell.assign(rDoc, aPos); + + rDoc.SetValue( nCol, nRow, nTab, rValue ); + + // because of ChangeTrack after change in document + if (bUndo) + { + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoEnterValue>(pDocSh, aPos, aUndoCell, rValue)); + } + + pDocSh->PostPaintCell( aPos ); + pDocSh->UpdateOle(GetViewData()); + aModificator.SetDocumentModified(); + } + else + ErrorMessage(aTester.GetMessageId()); +} + +void ScViewFunc::EnterData( SCCOL nCol, SCROW nRow, SCTAB nTab, + const EditTextObject& rData, bool bTestSimple ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScMarkData& rMark = GetViewData().GetMarkData(); + ScDocument& rDoc = pDocSh->GetDocument(); + bool bRecord = rDoc.IsUndoEnabled(); + + ScDocShellModificator aModificator( *pDocSh ); + + ScEditableTester aTester( rDoc, nTab, nCol,nRow, nCol,nRow ); + if (aTester.IsEditable()) + { + + // test for attribute + + bool bSimple = false; + bool bCommon = false; + std::unique_ptr<ScPatternAttr> pCellAttrs; + OUString aString; + + const ScPatternAttr* pOldPattern = rDoc.GetPattern( nCol, nRow, nTab ); + ScTabEditEngine aEngine( *pOldPattern, rDoc.GetEnginePool(), &rDoc ); + aEngine.SetTextCurrentDefaults(rData); + + if (bTestSimple) // test, if simple string without attribute + { + ScEditAttrTester aAttrTester( &aEngine ); + bSimple = !aAttrTester.NeedsObject(); + bCommon = aAttrTester.NeedsCellAttr(); + + // formulas have to be recognized even if they're formatted + // (but common attributes are still collected) + + if (!bSimple) + { + OUString aParStr(aEngine.GetText( 0 )); + if ( aParStr[0] == '=' ) + bSimple = true; + } + + if (bCommon) // attribute for tab + { + pCellAttrs.reset(new ScPatternAttr( *pOldPattern )); + pCellAttrs->GetFromEditItemSet( &aAttrTester.GetAttribs() ); + //! remove common attributes from EditEngine? + } + } + + // #i97726# always get text for "repeat" of undo action + aString = ScEditUtil::GetMultilineString(aEngine); + + // undo + + std::unique_ptr<EditTextObject> pUndoData; + ScUndoEnterData::ValuesType aOldValues; + + if (bRecord && !bSimple) + { + for (const auto& rTab : rMark) + { + ScUndoEnterData::Value aOldValue; + aOldValue.mnTab = rTab; + aOldValue.maCell.assign(rDoc, ScAddress(nCol, nRow, rTab)); + aOldValues.push_back(aOldValue); + } + + pUndoData = rData.Clone(); + } + + // enter data + + if (bCommon) + rDoc.ApplyPattern(nCol,nRow,nTab,*pCellAttrs); //! undo + + if (bSimple) + { + if (bCommon) + AdjustRowHeight(nRow,nRow,true); + + EnterData( nCol, nRow, nTab, aString, nullptr, true /*bMatrixExpand*/); + } + else + { + for (const auto& rTab : rMark) + { + ScAddress aPos(nCol, nRow, rTab); + rDoc.SetEditText(aPos, rData, rDoc.GetEditPool()); + } + + if ( bRecord ) + { // because of ChangeTrack current first + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoEnterData>(pDocSh, ScAddress(nCol,nRow,nTab), aOldValues, aString, std::move(pUndoData))); + } + + HideAllCursors(); + + AdjustRowHeight(nRow,nRow,true); + + for (const auto& rTab : rMark) + pDocSh->PostPaintCell( nCol, nRow, rTab ); + + ShowAllCursors(); + + pDocSh->UpdateOle(GetViewData()); + + bool bIsEmpty = rData.GetParagraphCount() == 0 + || (rData.GetParagraphCount() == 1 && rData.GetText(0).isEmpty()); + const OUString aType(bIsEmpty ? u"delete-content" : u"cell-change"); + HelperNotifyChanges::NotifyIfChangesListeners(*pDocSh, rMark, nCol, nRow, aType); + + aModificator.SetDocumentModified(); + } + lcl_PostRepaintCondFormat( rDoc.GetCondFormat( nCol, nRow, nTab ), pDocSh ); + } + else + { + ErrorMessage(aTester.GetMessageId()); + PaintArea( nCol, nRow, nCol, nRow ); // possibly the edit-engine is still painted there + } +} + +void ScViewFunc::EnterDataAtCursor( const OUString& rString ) +{ + SCCOL nPosX = GetViewData().GetCurX(); + SCROW nPosY = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + + EnterData( nPosX, nPosY, nTab, rString ); + // tdf#154174: update repeated data in the cell + GetViewData().GetViewShell()->UpdateInputHandler(); +} + +void ScViewFunc::EnterMatrix( const OUString& rString, ::formula::FormulaGrammar::Grammar eGram ) +{ + ScViewData& rData = GetViewData(); + const SCCOL nCol = rData.GetCurX(); + const SCROW nRow = rData.GetCurY(); + const ScMarkData& rMark = rData.GetMarkData(); + if ( !rMark.IsMarked() && !rMark.IsMultiMarked() ) + { + // nothing marked -> temporarily calculate block + // with size of result formula to get the size + + ScDocument& rDoc = rData.GetDocument(); + SCTAB nTab = rData.GetTabNo(); + ScFormulaCell aFormCell( rDoc, ScAddress(nCol,nRow,nTab), rString, eGram, ScMatrixMode::Formula ); + + SCSIZE nSizeX; + SCSIZE nSizeY; + aFormCell.GetResultDimensions( nSizeX, nSizeY ); + if ( nSizeX != 0 && nSizeY != 0 && + nCol+nSizeX-1 <= sal::static_int_cast<SCSIZE>(rDoc.MaxCol()) && + nRow+nSizeY-1 <= sal::static_int_cast<SCSIZE>(rDoc.MaxRow()) ) + { + ScRange aResult( nCol, nRow, nTab, + sal::static_int_cast<SCCOL>(nCol+nSizeX-1), + sal::static_int_cast<SCROW>(nRow+nSizeY-1), nTab ); + MarkRange( aResult, false ); + } + } + + ScRange aRange; + if (rData.GetSimpleArea(aRange) == SC_MARK_SIMPLE) + { + ScDocShell* pDocSh = rData.GetDocShell(); + bool bSuccess = pDocSh->GetDocFunc().EnterMatrix( + aRange, &rMark, nullptr, rString, false, false, OUString(), eGram ); + if (bSuccess) + pDocSh->UpdateOle(GetViewData()); + else + PaintArea(nCol, nRow, nCol, nRow); // possibly the edit-engine is still painted there + } + else + ErrorMessage(STR_NOMULTISELECT); +} + +SvtScriptType ScViewFunc::GetSelectionScriptType() +{ + SvtScriptType nScript = SvtScriptType::NONE; + + ScDocument& rDoc = GetViewData().GetDocument(); + const ScMarkData& rMark = GetViewData().GetMarkData(); + if ( !rMark.IsMarked() && !rMark.IsMultiMarked() ) + { + // no selection -> cursor + + nScript = rDoc.GetScriptType( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo()); + } + else + { + ScRangeList aRanges; + rMark.FillRangeListWithMarks( &aRanges, false ); + nScript = rDoc.GetRangeScriptType(aRanges); + } + + if (nScript == SvtScriptType::NONE) + nScript = ScGlobal::GetDefaultScriptType(); + + return nScript; +} + +static void ShrinkToDataArea(ScMarkData& rFuncMark, ScDocument& rDoc); + +const ScPatternAttr* ScViewFunc::GetSelectionPattern() +{ + // Don't use UnmarkFiltered in slot state functions, for performance reasons. + // The displayed state is always that of the whole selection including filtered rows. + + ScMarkData aMark = GetViewData().GetMarkData(); + ScDocument& rDoc = GetViewData().GetDocument(); + + // tdf#155368 if the selection is the whole sheet, we need to shrink the mark area, otherwise + // we will not return a consistent result + // (consistent compared to what happens in ScViewFunc::ApplySelectionPattern) + ShrinkToDataArea( aMark, rDoc ); + + if ( aMark.IsMarked() || aMark.IsMultiMarked() ) + { + // MarkToMulti is no longer necessary for rDoc.GetSelectionPattern + const ScPatternAttr* pAttr = rDoc.GetSelectionPattern( aMark ); + return pAttr; + } + else + { + SCCOL nCol = GetViewData().GetCurX(); + SCROW nRow = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + + // copy sheet selection + aMark.SetMarkArea( ScRange( nCol, nRow, nTab ) ); + const ScPatternAttr* pAttr = rDoc.GetSelectionPattern( aMark ); + return pAttr; + } +} + +void ScViewFunc::GetSelectionFrame( + std::shared_ptr<SvxBoxItem>& rLineOuter, + std::shared_ptr<SvxBoxInfoItem>& rLineInner ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + const ScMarkData& rMark = GetViewData().GetMarkData(); + + if ( rMark.IsMarked() || rMark.IsMultiMarked() ) + { + rDoc.GetSelectionFrame( rMark, *rLineOuter, *rLineInner ); + } + else + { + const ScPatternAttr* pAttrs = + rDoc.GetPattern( GetViewData().GetCurX(), + GetViewData().GetCurY(), + GetViewData().GetTabNo() ); + + rLineOuter.reset(pAttrs->GetItem(ATTR_BORDER).Clone()); + rLineInner.reset(pAttrs->GetItem(ATTR_BORDER_INNER).Clone()); + + rLineInner->SetTable(false); + rLineInner->SetDist(true); + rLineInner->SetMinDist(false); + } +} + +// apply attribute - undo OK +// +// complete set ( ATTR_STARTINDEX, ATTR_ENDINDEX ) + +void ScViewFunc::ApplyAttributes( const SfxItemSet& rDialogSet, + const SfxItemSet& rOldSet, + bool bAdjustBlockHeight) +{ + // not editable because of matrix only? attribute OK nonetheless + bool bOnlyNotBecauseOfMatrix; + if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + ScPatternAttr aOldAttrs(( SfxItemSet(rOldSet) )); + ScPatternAttr aNewAttrs(( SfxItemSet(rDialogSet) )); + aNewAttrs.DeleteUnchanged( &aOldAttrs ); + + if ( rDialogSet.GetItemState( ATTR_VALUE_FORMAT ) == SfxItemState::SET ) + { // don't reset to default SYSTEM GENERAL if not intended + sal_uInt32 nOldFormat = + rOldSet.Get( ATTR_VALUE_FORMAT ).GetValue(); + sal_uInt32 nNewFormat = + rDialogSet.Get( ATTR_VALUE_FORMAT ).GetValue(); + if ( nNewFormat != nOldFormat ) + { + SvNumberFormatter* pFormatter = + GetViewData().GetDocument().GetFormatTable(); + const SvNumberformat* pOldEntry = pFormatter->GetEntry( nOldFormat ); + LanguageType eOldLang = + pOldEntry ? pOldEntry->GetLanguage() : LANGUAGE_DONTKNOW; + const SvNumberformat* pNewEntry = pFormatter->GetEntry( nNewFormat ); + LanguageType eNewLang = + pNewEntry ? pNewEntry->GetLanguage() : LANGUAGE_DONTKNOW; + if ( eNewLang != eOldLang ) + { + aNewAttrs.GetItemSet().Put( + SvxLanguageItem( eNewLang, ATTR_LANGUAGE_FORMAT ) ); + + // only the language has changed -> do not touch numberformat-attribute + sal_uInt32 nNewMod = nNewFormat % SV_COUNTRY_LANGUAGE_OFFSET; + if ( nNewMod == ( nOldFormat % SV_COUNTRY_LANGUAGE_OFFSET ) && + nNewMod <= SV_MAX_COUNT_STANDARD_FORMATS ) + aNewAttrs.GetItemSet().ClearItem( ATTR_VALUE_FORMAT ); + } + } + } + + if (rDialogSet.HasItem(ATTR_FONT_LANGUAGE)) + // font language has changed. Redo the online spelling. + ResetAutoSpell(); + + const SvxBoxItem& rOldOuter = rOldSet.Get(ATTR_BORDER); + const SvxBoxItem& rNewOuter = rDialogSet.Get(ATTR_BORDER); + const SvxBoxInfoItem& rOldInner = rOldSet.Get(ATTR_BORDER_INNER); + const SvxBoxInfoItem& rNewInner = rDialogSet.Get(ATTR_BORDER_INNER); + SfxItemSet& rNewSet = aNewAttrs.GetItemSet(); + SfxItemPool* pNewPool = rNewSet.GetPool(); + + pNewPool->DirectPutItemInPool(rNewOuter); // don't delete yet + pNewPool->DirectPutItemInPool(rNewInner); + rNewSet.ClearItem( ATTR_BORDER ); + rNewSet.ClearItem( ATTR_BORDER_INNER ); + + /* + * establish whether border attribute is to be set: + * 1. new != old + * 2. is one of the borders not-DontCare (since 238.f: IsxxValid()) + * + */ + + bool bFrame = (rDialogSet.GetItemState( ATTR_BORDER ) != SfxItemState::DEFAULT) + || (rDialogSet.GetItemState( ATTR_BORDER_INNER ) != SfxItemState::DEFAULT); + + if (SfxPoolItem::areSame(rNewOuter, rOldOuter) && SfxPoolItem::areSame(rNewInner, rOldInner)) + bFrame = false; + + // this should be intercepted by the pool: ?!??!?? + + if (bFrame && rNewOuter == rOldOuter && rNewInner == rOldInner) + bFrame = false; + + bFrame = bFrame + && ( rNewInner.IsValid(SvxBoxInfoItemValidFlags::LEFT) + || rNewInner.IsValid(SvxBoxInfoItemValidFlags::RIGHT) + || rNewInner.IsValid(SvxBoxInfoItemValidFlags::TOP) + || rNewInner.IsValid(SvxBoxInfoItemValidFlags::BOTTOM) + || rNewInner.IsValid(SvxBoxInfoItemValidFlags::HORI) + || rNewInner.IsValid(SvxBoxInfoItemValidFlags::VERT) ); + + if (!bFrame) + ApplySelectionPattern( aNewAttrs ); // standard only + else + { + // if new items are default-items, overwrite the old items: + + bool bDefNewOuter = IsStaticDefaultItem(&rNewOuter); + bool bDefNewInner = IsStaticDefaultItem(&rNewInner); + + ApplyPatternLines( aNewAttrs, + bDefNewOuter ? rOldOuter : rNewOuter, + bDefNewInner ? &rOldInner : &rNewInner ); + } + + pNewPool->DirectRemoveItemFromPool(rNewOuter); // release + pNewPool->DirectRemoveItemFromPool(rNewInner); + + // adjust height only if needed + if (bAdjustBlockHeight) + AdjustBlockHeight(); + + // CellContentChanged is called in ApplySelectionPattern / ApplyPatternLines +} + +void ScViewFunc::ApplyAttr( const SfxPoolItem& rAttrItem, bool bAdjustBlockHeight ) +{ + // not editable because of matrix only? attribute OK nonetheless + bool bOnlyNotBecauseOfMatrix; + if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + ScPatternAttr aNewAttrs( + SfxItemSetFixed<ATTR_PATTERN_START, ATTR_PATTERN_END>( *GetViewData().GetDocument().GetPool() ) ); + + aNewAttrs.GetItemSet().Put( rAttrItem ); + // if justify is set (with Buttons), always indentation 0 + if ( rAttrItem.Which() == ATTR_HOR_JUSTIFY ) + aNewAttrs.GetItemSet().Put( ScIndentItem( 0 ) ); + ApplySelectionPattern( aNewAttrs ); + + // Prevent useless compute + if (bAdjustBlockHeight) + AdjustBlockHeight(); + + // CellContentChanged is called in ApplySelectionPattern +} + +// patterns and borders + +void ScViewFunc::ApplyPatternLines( const ScPatternAttr& rAttr, const SvxBoxItem& rNewOuter, + const SvxBoxInfoItem* pNewInner ) +{ + ScDocument& rDoc = GetViewData().GetDocument(); + ScMarkData aFuncMark( GetViewData().GetMarkData() ); // local copy for UnmarkFiltered + ScViewUtil::UnmarkFiltered( aFuncMark, rDoc ); + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + + bool bRemoveAdjCellBorder = rNewOuter.IsRemoveAdjacentCellBorder(); + ScRange aMarkRange, aMarkRangeWithEnvelope; + aFuncMark.MarkToSimple(); + bool bMulti = aFuncMark.IsMultiMarked(); + if (bMulti) + aMarkRange = aFuncMark.GetMultiMarkArea(); + else if (aFuncMark.IsMarked()) + aMarkRange = aFuncMark.GetMarkArea(); + else + { + aMarkRange = ScRange( GetViewData().GetCurX(), + GetViewData().GetCurY(), GetViewData().GetTabNo() ); + DoneBlockMode(); + InitOwnBlockMode( aMarkRange ); + aFuncMark.SetMarkArea(aMarkRange); + MarkDataChanged(); + } + if( bRemoveAdjCellBorder ) + aFuncMark.GetSelectionCover( aMarkRangeWithEnvelope ); + else + aMarkRangeWithEnvelope = aMarkRange; + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + + ScDocShellModificator aModificator( *pDocSh ); + + if (bRecord) + { + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + SCTAB nStartTab = aMarkRange.aStart.Tab(); + SCTAB nTabCount = rDoc.GetTableCount(); + bool bCopyOnlyMarked = false; + if( !bRemoveAdjCellBorder ) + bCopyOnlyMarked = bMulti; + pUndoDoc->InitUndo( rDoc, nStartTab, nStartTab ); + for (const auto& rTab : aFuncMark) + if (rTab != nStartTab) + pUndoDoc->AddUndoTab( rTab, rTab ); + + ScRange aCopyRange = aMarkRangeWithEnvelope; + aCopyRange.aStart.SetTab(0); + aCopyRange.aEnd.SetTab(nTabCount-1); + rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ATTRIB, bCopyOnlyMarked, *pUndoDoc, &aFuncMark ); + + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoSelectionAttr>( + pDocSh, aFuncMark, + aMarkRange.aStart.Col(), aMarkRange.aStart.Row(), aMarkRange.aStart.Tab(), + aMarkRange.aEnd.Col(), aMarkRange.aEnd.Row(), aMarkRange.aEnd.Tab(), + std::move(pUndoDoc), bCopyOnlyMarked, &rAttr, &rNewOuter, pNewInner, &aMarkRangeWithEnvelope ) ); + } + + sal_uInt16 nExt = SC_PF_TESTMERGE; + pDocSh->UpdatePaintExt( nExt, aMarkRangeWithEnvelope ); // content before the change + + rDoc.ApplySelectionFrame(aFuncMark, rNewOuter, pNewInner); + + pDocSh->UpdatePaintExt( nExt, aMarkRangeWithEnvelope ); // content after the change + + aFuncMark.MarkToMulti(); + rDoc.ApplySelectionPattern( rAttr, aFuncMark ); + + pDocSh->PostPaint( aMarkRange, PaintPartFlags::Grid, nExt ); + pDocSh->UpdateOle(GetViewData()); + aModificator.SetDocumentModified(); + CellContentChanged(); + + StartFormatArea(); +} + +// tdf#147842 if the marked area is the entire sheet, then shrink it to the data area. +// Otherwise ctrl-A, perform-action, will take a very long time as it tries to modify +// cells that we are not using. +static void ShrinkToDataArea(ScMarkData& rFuncMark, ScDocument& rDoc) +{ + // do not make it marked if it is not already marked + if (!rFuncMark.IsMarked()) + return; + if (rFuncMark.IsMultiMarked()) + return; + ScRange aMarkArea = rFuncMark.GetMarkArea(); + const ScSheetLimits& rLimits = rDoc.GetSheetLimits(); + if (aMarkArea.aStart.Row() != 0 || aMarkArea.aStart.Col() != 0) + return; + if (aMarkArea.aEnd.Row() != rLimits.MaxRow() || aMarkArea.aEnd.Col() != rLimits.MaxCol()) + return; + if (aMarkArea.aStart.Tab() != aMarkArea.aEnd.Tab()) + return; + SCCOL nStartCol = aMarkArea.aStart.Col(); + SCROW nStartRow = aMarkArea.aStart.Row(); + SCCOL nEndCol = aMarkArea.aEnd.Col(); + SCROW nEndRow = aMarkArea.aEnd.Row(); + rDoc.ShrinkToDataArea(aMarkArea.aStart.Tab(), nStartCol, nStartRow, nEndCol, nEndRow); + aMarkArea.aStart.SetCol(nStartCol); + aMarkArea.aStart.SetRow(nStartRow); + aMarkArea.aEnd.SetCol(nEndCol); + aMarkArea.aEnd.SetRow(nEndRow); + rFuncMark.ResetMark(); + rFuncMark.SetMarkArea(aMarkArea); +} + +// pattern only + +void ScViewFunc::ApplySelectionPattern( const ScPatternAttr& rAttr, bool bCursorOnly ) +{ + ScViewData& rViewData = GetViewData(); + ScDocShell* pDocSh = rViewData.GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScMarkData aFuncMark( rViewData.GetMarkData() ); // local copy for UnmarkFiltered + ScViewUtil::UnmarkFiltered( aFuncMark, rDoc ); + + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + + // State from old ItemSet doesn't matter for paint flags, as any change will be + // from SfxItemState::SET in the new ItemSet (default is ignored in ApplyPattern). + // New alignment is checked (check in PostPaint isn't enough) in case a right + // alignment is changed to left. + const SfxItemSet& rNewSet = rAttr.GetItemSet(); + bool bSetLines = rNewSet.GetItemState( ATTR_BORDER ) == SfxItemState::SET || + rNewSet.GetItemState( ATTR_SHADOW ) == SfxItemState::SET; + bool bSetAlign = rNewSet.GetItemState( ATTR_HOR_JUSTIFY ) == SfxItemState::SET; + + sal_uInt16 nExtFlags = 0; + if ( bSetLines ) + nExtFlags |= SC_PF_LINES; + if ( bSetAlign ) + nExtFlags |= SC_PF_WHOLEROWS; + + ScDocShellModificator aModificator( *pDocSh ); + + bool bMulti = aFuncMark.IsMultiMarked(); + aFuncMark.MarkToMulti(); + bool bOnlyTab = (!aFuncMark.IsMultiMarked() && !bCursorOnly && aFuncMark.GetSelectCount() > 1); + if (bOnlyTab) + { + SCCOL nCol = rViewData.GetCurX(); + SCROW nRow = rViewData.GetCurY(); + SCTAB nTab = rViewData.GetTabNo(); + aFuncMark.SetMarkArea(ScRange(nCol,nRow,nTab)); + aFuncMark.MarkToMulti(); + } + + ScRangeList aChangeRanges; + + if (aFuncMark.IsMultiMarked() && !bCursorOnly) + { + const ScRange& aMarkRange = aFuncMark.GetMultiMarkArea(); + SCTAB nTabCount = rDoc.GetTableCount(); + for (const auto& rTab : aFuncMark) + { + ScRange aChangeRange( aMarkRange ); + aChangeRange.aStart.SetTab( rTab ); + aChangeRange.aEnd.SetTab( rTab ); + aChangeRanges.push_back( aChangeRange ); + } + + SCCOL nStartCol = aMarkRange.aStart.Col(); + SCROW nStartRow = aMarkRange.aStart.Row(); + SCTAB nStartTab = aMarkRange.aStart.Tab(); + SCCOL nEndCol = aMarkRange.aEnd.Col(); + SCROW nEndRow = aMarkRange.aEnd.Row(); + SCTAB nEndTab = aMarkRange.aEnd.Tab(); + + ScEditDataArray* pEditDataArray = nullptr; + if (bRecord) + { + ScRange aCopyRange = aMarkRange; + aCopyRange.aStart.SetTab(0); + aCopyRange.aEnd.SetTab(nTabCount-1); + + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nStartTab, nStartTab ); + for (const auto& rTab : aFuncMark) + if (rTab != nStartTab) + pUndoDoc->AddUndoTab( rTab, rTab ); + rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ATTRIB, bMulti, *pUndoDoc, &aFuncMark ); + + aFuncMark.MarkToMulti(); + + ScUndoSelectionAttr* pUndoAttr = new ScUndoSelectionAttr( + pDocSh, aFuncMark, nStartCol, nStartRow, nStartTab, + nEndCol, nEndRow, nEndTab, std::move(pUndoDoc), bMulti, &rAttr ); + pDocSh->GetUndoManager()->AddUndoAction(std::unique_ptr<ScUndoSelectionAttr>(pUndoAttr)); + pEditDataArray = pUndoAttr->GetDataArray(); + } + + rDoc.ApplySelectionPattern( rAttr, aFuncMark, pEditDataArray ); + + pDocSh->PostPaint( nStartCol, nStartRow, nStartTab, + nEndCol, nEndRow, nEndTab, + PaintPartFlags::Grid, nExtFlags | SC_PF_TESTMERGE ); + pDocSh->UpdateOle(GetViewData()); + aModificator.SetDocumentModified(); + CellContentChanged(); + } + else // single cell - simpler undo + { + SCCOL nCol = rViewData.GetCurX(); + SCROW nRow = rViewData.GetCurY(); + SCTAB nTab = rViewData.GetTabNo(); + + std::unique_ptr<EditTextObject> pOldEditData; + std::unique_ptr<EditTextObject> pNewEditData; + ScAddress aPos(nCol, nRow, nTab); + ScRefCellValue aCell(rDoc, aPos); + if (aCell.getType() == CELLTYPE_EDIT) + { + const EditTextObject* pEditObj = aCell.getEditText(); + pOldEditData = pEditObj->Clone(); + rDoc.RemoveEditTextCharAttribs(aPos, rAttr); + pEditObj = rDoc.GetEditText(aPos); + pNewEditData = pEditObj->Clone(); + } + + aChangeRanges.push_back(aPos); + std::optional<ScPatternAttr> pOldPat(*rDoc.GetPattern( nCol, nRow, nTab )); + + rDoc.ApplyPattern( nCol, nRow, nTab, rAttr ); + + const ScPatternAttr* pNewPat = rDoc.GetPattern( nCol, nRow, nTab ); + + if (bRecord) + { + std::unique_ptr<ScUndoCursorAttr> pUndo(new ScUndoCursorAttr( + pDocSh, nCol, nRow, nTab, &*pOldPat, pNewPat, &rAttr )); + pUndo->SetEditData(std::move(pOldEditData), std::move(pNewEditData)); + pDocSh->GetUndoManager()->AddUndoAction(std::move(pUndo)); + } + pOldPat.reset(); // is copied in undo (Pool) + + pDocSh->PostPaint( nCol,nRow,nTab, nCol,nRow,nTab, PaintPartFlags::Grid, nExtFlags | SC_PF_TESTMERGE ); + pDocSh->UpdateOle(GetViewData()); + aModificator.SetDocumentModified(); + CellContentChanged(); + } + + ScModelObj* pModelObj = pDocSh->GetModel(); + + if (HelperNotifyChanges::getMustPropagateChangesModel(pModelObj)) + { + css::uno::Sequence< css::beans::PropertyValue > aProperties; + sal_Int32 nCount = 0; + const SfxItemPropertyMap& rMap = ScCellObj::GetCellPropertyMap(); + for ( sal_uInt16 nWhich = ATTR_PATTERN_START; nWhich <= ATTR_PATTERN_END; ++nWhich ) + { + const SfxPoolItem* pItem = nullptr; + if ( rNewSet.GetItemState( nWhich, true, &pItem ) == SfxItemState::SET && pItem ) + { + for ( const auto pEntry : rMap.getPropertyEntries()) + { + if ( pEntry->nWID == nWhich ) + { + css::uno::Any aVal; + pItem->QueryValue( aVal, pEntry->nMemberId ); + aProperties.realloc( nCount + 1 ); + auto pProperties = aProperties.getArray(); + pProperties[ nCount ].Name = pEntry->aName; + pProperties[ nCount ].Value = aVal; + ++nCount; + } + } + } + } + HelperNotifyChanges::Notify(*pModelObj, aChangeRanges, "attribute", aProperties); + } + + StartFormatArea(); +} + +void ScViewFunc::ApplyUserItemSet( const SfxItemSet& rItemSet ) +{ + // ItemSet from UI, may have different pool + + bool bOnlyNotBecauseOfMatrix; + if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + ScPatternAttr aNewAttrs( GetViewData().GetDocument().GetPool() ); + SfxItemSet& rNewSet = aNewAttrs.GetItemSet(); + rNewSet.Put( rItemSet, false ); + ApplySelectionPattern( aNewAttrs ); + + AdjustBlockHeight(); +} + +const SfxStyleSheet* ScViewFunc::GetStyleSheetFromMarked() +{ + // Don't use UnmarkFiltered in slot state functions, for performance reasons. + // The displayed state is always that of the whole selection including filtered rows. + + const ScStyleSheet* pSheet = nullptr; + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + ScMarkData& rMark = rViewData.GetMarkData(); + + if ( rMark.IsMarked() || rMark.IsMultiMarked() ) + pSheet = rDoc.GetSelectionStyle( rMark ); // MarkToMulti isn't necessary + else + pSheet = rDoc.GetStyle( rViewData.GetCurX(), + rViewData.GetCurY(), + rViewData.GetTabNo() ); + + return pSheet; +} + +void ScViewFunc::SetStyleSheetToMarked( const SfxStyleSheet* pStyleSheet ) +{ + // not editable because of matrix only? attribute OK nonetheless + bool bOnlyNotBecauseOfMatrix; + if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + if ( !pStyleSheet) return; + + ScViewData& rViewData = GetViewData(); + ScDocShell* pDocSh = rViewData.GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScMarkData aFuncMark( rViewData.GetMarkData() ); // local copy for UnmarkFiltered + ScViewUtil::UnmarkFiltered( aFuncMark, rDoc ); + SCTAB nTabCount = rDoc.GetTableCount(); + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + + ScDocShellModificator aModificator( *pDocSh ); + + if ( aFuncMark.IsMarked() || aFuncMark.IsMultiMarked() ) + { + aFuncMark.MarkToMulti(); + const ScRange& aMarkRange = aFuncMark.GetMultiMarkArea(); + + if ( bRecord ) + { + SCTAB nTab = rViewData.GetTabNo(); + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nTab, nTab ); + for (const auto& rTab : aFuncMark) + if (rTab != nTab) + pUndoDoc->AddUndoTab( rTab, rTab ); + + ScRange aCopyRange = aMarkRange; + aCopyRange.aStart.SetTab(0); + aCopyRange.aEnd.SetTab(nTabCount-1); + rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ATTRIB, true, *pUndoDoc, &aFuncMark ); + aFuncMark.MarkToMulti(); + + OUString aName = pStyleSheet->GetName(); + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoSelectionStyle>( pDocSh, aFuncMark, aMarkRange, aName, std::move(pUndoDoc) ) ); + } + + rDoc.ApplySelectionStyle( static_cast<const ScStyleSheet&>(*pStyleSheet), aFuncMark ); + + if (!AdjustBlockHeight()) + rViewData.GetDocShell()->PostPaint( aMarkRange, PaintPartFlags::Grid ); + + aFuncMark.MarkToSimple(); + } + else + { + SCCOL nCol = rViewData.GetCurX(); + SCROW nRow = rViewData.GetCurY(); + SCTAB nTab = rViewData.GetTabNo(); + + if ( bRecord ) + { + ScDocumentUniquePtr pUndoDoc(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nTab, nTab ); + for (const auto& rTab : aFuncMark) + if (rTab != nTab) + pUndoDoc->AddUndoTab( rTab, rTab ); + + ScRange aCopyRange( nCol, nRow, 0, nCol, nRow, nTabCount-1 ); + rDoc.CopyToDocument( aCopyRange, InsertDeleteFlags::ATTRIB, false, *pUndoDoc ); + + ScRange aMarkRange ( nCol, nRow, nTab ); + ScMarkData aUndoMark = aFuncMark; + aUndoMark.SetMultiMarkArea( aMarkRange ); + + OUString aName = pStyleSheet->GetName(); + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoSelectionStyle>( pDocSh, aUndoMark, aMarkRange, aName, std::move(pUndoDoc) ) ); + } + + for (const auto& rTab : aFuncMark) + rDoc.ApplyStyle( nCol, nRow, rTab, static_cast<const ScStyleSheet&>(*pStyleSheet) ); + + if (!AdjustBlockHeight()) + rViewData.GetDocShell()->PostPaintCell( nCol, nRow, nTab ); + + } + + aModificator.SetDocumentModified(); + + StartFormatArea(); +} + +void ScViewFunc::RemoveStyleSheetInUse( const SfxStyleSheetBase* pStyleSheet ) +{ + if ( !pStyleSheet) return; + + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + ScDocShell* pDocSh = rViewData.GetDocShell(); + + ScDocShellModificator aModificator( *pDocSh ); + + ScopedVclPtrInstance< VirtualDevice > pVirtDev; + pVirtDev->SetMapMode(MapMode(MapUnit::MapPixel)); + rDoc.StyleSheetChanged( pStyleSheet, true, pVirtDev, + rViewData.GetPPTX(), + rViewData.GetPPTY(), + rViewData.GetZoomX(), + rViewData.GetZoomY() ); + + pDocSh->PostPaint( 0,0,0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB, PaintPartFlags::Grid|PaintPartFlags::Left ); + aModificator.SetDocumentModified(); + + ScInputHandler* pHdl = SC_MOD()->GetInputHdl(); + if (pHdl) + pHdl->ForgetLastPattern(); +} + +void ScViewFunc::UpdateStyleSheetInUse( const SfxStyleSheetBase* pStyleSheet ) +{ + if ( !pStyleSheet) return; + + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + ScDocShell* pDocSh = rViewData.GetDocShell(); + + ScDocShellModificator aModificator( *pDocSh ); + + ScopedVclPtrInstance< VirtualDevice > pVirtDev; + pVirtDev->SetMapMode(MapMode(MapUnit::MapPixel)); + rDoc.StyleSheetChanged( pStyleSheet, false, pVirtDev, + rViewData.GetPPTX(), + rViewData.GetPPTY(), + rViewData.GetZoomX(), + rViewData.GetZoomY() ); + + pDocSh->PostPaint( 0,0,0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB, PaintPartFlags::Grid|PaintPartFlags::Left ); + aModificator.SetDocumentModified(); + + ScInputHandler* pHdl = SC_MOD()->GetInputHdl(); + if (pHdl) + pHdl->ForgetLastPattern(); +} + + +void ScViewFunc::OnLOKInsertDeleteColumn(SCCOL nStartCol, tools::Long nOffset) +{ + if (!comphelper::LibreOfficeKit::isActive() || nOffset == 0) + return; + + SCTAB nCurrentTabIndex = GetViewData().GetTabNo(); + SfxViewShell* pCurrentViewShell = GetViewData().GetViewShell(); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(pViewShell); + if (pTabViewShell && pTabViewShell->GetDocId() == pCurrentViewShell->GetDocId()) + { + if (ScPositionHelper* pPosHelper = pTabViewShell->GetViewData().GetLOKWidthHelper(nCurrentTabIndex)) + pPosHelper->invalidateByIndex(nStartCol); + + // if we remove a column the cursor position and the current selection + // in other views could need to be moved on the left by one column. + if (pTabViewShell != this) + { + if (pTabViewShell->getPart() == nCurrentTabIndex) + { + SCCOL nX = pTabViewShell->GetViewData().GetCurX(); + if (nX > nStartCol || (nX == nStartCol && nOffset > 0)) + { + ScInputHandler* pInputHdl = pTabViewShell->GetInputHandler(); + SCROW nY = pTabViewShell->GetViewData().GetCurY(); + pTabViewShell->SetCursor(nX + nOffset, nY); + if (pInputHdl && pInputHdl->IsInputMode()) + { + pInputHdl->SetModified(); + } + } + + ScMarkData aMultiMark( pTabViewShell->GetViewData().GetMarkData() ); + aMultiMark.SetMarking( false ); + aMultiMark.MarkToMulti(); + if (aMultiMark.IsMultiMarked()) + { + aMultiMark.ShiftCols(pTabViewShell->GetViewData().GetDocument(), nStartCol, nOffset); + pTabViewShell->SetMarkData(aMultiMark); + } + } + else + { + SCROW nX = pTabViewShell->GetViewData().GetCurXForTab(nCurrentTabIndex); + if (nX > nStartCol || (nX == nStartCol && nOffset > 0)) + { + pTabViewShell->GetViewData().SetCurXForTab(nX + nOffset, nCurrentTabIndex); + } + } + } + } + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +void ScViewFunc::OnLOKInsertDeleteRow(SCROW nStartRow, tools::Long nOffset) +{ + if (!comphelper::LibreOfficeKit::isActive() || nOffset == 0) + return; + + SCTAB nCurrentTabIndex = GetViewData().GetTabNo(); + SfxViewShell* pCurrentViewShell = GetViewData().GetViewShell(); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(pViewShell); + if (pTabViewShell && pTabViewShell->GetDocId() == pCurrentViewShell->GetDocId()) + { + if (ScPositionHelper* pPosHelper = pTabViewShell->GetViewData().GetLOKHeightHelper(nCurrentTabIndex)) + pPosHelper->invalidateByIndex(nStartRow); + + // if we remove a row the cursor position and the current selection + // in other views could need to be moved up by one row. + if (pTabViewShell != this) + { + if (pTabViewShell->getPart() == nCurrentTabIndex) + { + SCROW nY = pTabViewShell->GetViewData().GetCurY(); + if (nY > nStartRow || (nY == nStartRow && nOffset > 0)) + { + ScInputHandler* pInputHdl = pTabViewShell->GetInputHandler(); + SCCOL nX = pTabViewShell->GetViewData().GetCurX(); + pTabViewShell->SetCursor(nX, nY + nOffset); + if (pInputHdl && pInputHdl->IsInputMode()) + { + pInputHdl->SetModified(); + } + } + + ScMarkData aMultiMark( pTabViewShell->GetViewData().GetMarkData() ); + aMultiMark.SetMarking( false ); + aMultiMark.MarkToMulti(); + if (aMultiMark.IsMultiMarked()) + { + aMultiMark.ShiftRows(pTabViewShell->GetViewData().GetDocument(), nStartRow, nOffset); + pTabViewShell->SetMarkData(aMultiMark); + } + } + else + { + SCROW nY = pTabViewShell->GetViewData().GetCurYForTab(nCurrentTabIndex); + if (nY > nStartRow || (nY == nStartRow && nOffset > 0)) + { + pTabViewShell->GetViewData().SetCurYForTab(nY + nOffset, nCurrentTabIndex); + } + } + } + } + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +void ScViewFunc::OnLOKSetWidthOrHeight(SCCOLROW nStart, bool bWidth) +{ + if (!comphelper::LibreOfficeKit::isActive()) + return; + + SCTAB nCurTab = GetViewData().GetTabNo(); + SfxViewShell* pCurrentViewShell = GetViewData().GetViewShell(); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + while (pViewShell) + { + ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(pViewShell); + if (pTabViewShell && pTabViewShell->GetDocId() == pCurrentViewShell->GetDocId()) + { + if (bWidth) + { + if (ScPositionHelper* pPosHelper = pTabViewShell->GetViewData().GetLOKWidthHelper(nCurTab)) + pPosHelper->invalidateByIndex(nStart); + } + else + { + if (ScPositionHelper* pPosHelper = pTabViewShell->GetViewData().GetLOKHeightHelper(nCurTab)) + pPosHelper->invalidateByIndex(nStart); + } + } + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +// insert cells - undo OK + +bool ScViewFunc::InsertCells( InsCellCmd eCmd, bool bRecord, bool bPartOfPaste ) +{ + ScRange aRange; + ScMarkType eMarkType = GetViewData().GetSimpleArea(aRange); + if (eMarkType == SC_MARK_SIMPLE || eMarkType == SC_MARK_SIMPLE_FILTERED) + { + ScDocShell* pDocSh = GetViewData().GetDocShell(); + const ScMarkData& rMark = GetViewData().GetMarkData(); + bool bSuccess = pDocSh->GetDocFunc().InsertCells( aRange, &rMark, eCmd, bRecord, false, bPartOfPaste ); + if (bSuccess) + { + ResetAutoSpellForContentChange(); + bool bInsertCols = ( eCmd == INS_INSCOLS_BEFORE || eCmd == INS_INSCOLS_AFTER); + bool bInsertRows = ( eCmd == INS_INSROWS_BEFORE || eCmd == INS_INSROWS_AFTER ); + + pDocSh->UpdateOle(GetViewData()); + CellContentChanged(); + + if ( bInsertCols || bInsertRows ) + { + OUString aOperation = bInsertRows ? + OUString("insert-rows"): + OUString("insert-columns"); + HelperNotifyChanges::NotifyIfChangesListeners(*pDocSh, aRange, aOperation); + } + + if (comphelper::LibreOfficeKit::isActive()) + { + if (bInsertCols) + ScTabViewShell::notifyAllViewsHeaderInvalidation(GetViewData().GetViewShell(), COLUMN_HEADER, GetViewData().GetTabNo()); + + if (bInsertRows) + ScTabViewShell::notifyAllViewsHeaderInvalidation(GetViewData().GetViewShell(), ROW_HEADER, GetViewData().GetTabNo()); + + ScTabViewShell::notifyAllViewsSheetGeomInvalidation(GetViewData().GetViewShell(), + bInsertCols, bInsertRows, true /* bSizes*/, + true /* bHidden */, true /* bFiltered */, + true /* bGroups */, GetViewData().GetTabNo()); + } + } + else + { + ErrorMessage(STR_ERR_INSERT_CELLS); + } + + OUString aStartAddress = aRange.aStart.GetColRowString(); + OUString aEndAddress = aRange.aEnd.GetColRowString(); + collectUIInformation({{"RANGE", aStartAddress + ":" + aEndAddress}}, "INSERT_CELLS"); + return bSuccess; + } + else + { + ErrorMessage(STR_NOMULTISELECT); + return false; + } +} + +// delete cells - undo OK + +void ScViewFunc::DeleteCells( DelCellCmd eCmd ) +{ + ScRange aRange; + if ( GetViewData().GetSimpleArea( aRange ) == SC_MARK_SIMPLE ) + { + ScDocShell* pDocSh = GetViewData().GetDocShell(); + const ScMarkData& rMark = GetViewData().GetMarkData(); + +#if HAVE_FEATURE_MULTIUSER_ENVIRONMENT + // #i94841# [Collaboration] if deleting rows is rejected, the content is sometimes wrong + if ( pDocSh->IsDocShared() && ( eCmd == DelCellCmd::Rows || eCmd == DelCellCmd::Cols ) ) + { + ScRange aDelRange( aRange.aStart ); + SCCOLROW nCount = 0; + if ( eCmd == DelCellCmd::Rows ) + { + nCount = sal::static_int_cast< SCCOLROW >( aRange.aEnd.Row() - aRange.aStart.Row() + 1 ); + } + else + { + nCount = sal::static_int_cast< SCCOLROW >( aRange.aEnd.Col() - aRange.aStart.Col() + 1 ); + } + while ( nCount > 0 ) + { + pDocSh->GetDocFunc().DeleteCells( aDelRange, &rMark, eCmd, false ); + --nCount; + } + } + else +#endif + { + pDocSh->GetDocFunc().DeleteCells( aRange, &rMark, eCmd, false ); + } + + ResetAutoSpellForContentChange(); + pDocSh->UpdateOle(GetViewData()); + CellContentChanged(); + + if ( eCmd == DelCellCmd::Rows || eCmd == DelCellCmd::Cols ) + { + OUString aOperation = ( eCmd == DelCellCmd::Rows) ? + OUString("delete-rows"): + OUString("delete-columns"); + HelperNotifyChanges::NotifyIfChangesListeners(*pDocSh, aRange, aOperation); + } + + // put cursor directly behind deleted range + SCCOL nCurX = GetViewData().GetCurX(); + SCROW nCurY = GetViewData().GetCurY(); + if ( eCmd==DelCellCmd::CellsLeft || eCmd==DelCellCmd::Cols ) + nCurX = aRange.aStart.Col(); + else + nCurY = aRange.aStart.Row(); + SetCursor( nCurX, nCurY ); + + if (comphelper::LibreOfficeKit::isActive()) + { + bool bColsDeleted = (eCmd == DelCellCmd::Cols); + bool bRowsDeleted = (eCmd == DelCellCmd::Rows); + if (bColsDeleted) + ScTabViewShell::notifyAllViewsHeaderInvalidation(GetViewData().GetViewShell(), COLUMN_HEADER, GetViewData().GetTabNo()); + + if (bRowsDeleted) + ScTabViewShell::notifyAllViewsHeaderInvalidation(GetViewData().GetViewShell(), ROW_HEADER, GetViewData().GetTabNo()); + + ScTabViewShell::notifyAllViewsSheetGeomInvalidation(GetViewData().GetViewShell(), + bColsDeleted, bRowsDeleted, true /* bSizes*/, + true /* bHidden */, true /* bFiltered */, + true /* bGroups */, GetViewData().GetTabNo()); + } + } + else + { + if (eCmd == DelCellCmd::Cols) + DeleteMulti( false ); + else if (eCmd == DelCellCmd::Rows) + DeleteMulti( true ); + else + ErrorMessage(STR_NOMULTISELECT); + } + + OUString aStartAddress = aRange.aStart.GetColRowString(); + OUString aEndAddress = aRange.aEnd.GetColRowString(); + collectUIInformation({{"RANGE", aStartAddress + ":" + aEndAddress}}, "DELETE_CELLS"); + + Unmark(); +} + +void ScViewFunc::DeleteMulti( bool bRows ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocShellModificator aModificator( *pDocSh ); + SCTAB nTab = GetViewData().GetTabNo(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScMarkData aFuncMark( GetViewData().GetMarkData() ); // local copy for UnmarkFiltered + ScViewUtil::UnmarkFiltered( aFuncMark, rDoc ); + + bool bRecord = true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + + std::vector<sc::ColRowSpan> aSpans; + if (bRows) + aSpans = aFuncMark.GetMarkedRowSpans(); + else + aSpans = aFuncMark.GetMarkedColSpans(); + + if (aSpans.empty()) + { + SCCOLROW nCurPos = bRows ? GetViewData().GetCurY() : GetViewData().GetCurX(); + aSpans.emplace_back(nCurPos, nCurPos); + } + + // test if allowed + + TranslateId pErrorId; + bool bNeedRefresh = false; + for (size_t i = 0, n = aSpans.size(); i < n && !pErrorId; ++i) + { + SCCOLROW nStart = aSpans[i].mnStart; + SCCOLROW nEnd = aSpans[i].mnEnd; + + SCCOL nStartCol, nEndCol; + SCROW nStartRow, nEndRow; + if ( bRows ) + { + nStartCol = 0; + nEndCol = rDoc.MaxCol(); + nStartRow = static_cast<SCROW>(nStart); + nEndRow = static_cast<SCROW>(nEnd); + } + else + { + nStartCol = static_cast<SCCOL>(nStart); + nEndCol = static_cast<SCCOL>(nEnd); + nStartRow = 0; + nEndRow = rDoc.MaxRow(); + } + + // cell protection (only needed for first range, as all following cells are moved) + if (i == 0) + { + // test to the end of the sheet + ScEditableTester aTester( rDoc, nTab, nStartCol, nStartRow, rDoc.MaxCol(), rDoc.MaxRow() ); + if (!aTester.IsEditable()) + pErrorId = aTester.GetMessageId(); + } + + // merged cells + SCCOL nMergeStartX = nStartCol; + SCROW nMergeStartY = nStartRow; + SCCOL nMergeEndX = nEndCol; + SCROW nMergeEndY = nEndRow; + rDoc.ExtendMerge( nMergeStartX, nMergeStartY, nMergeEndX, nMergeEndY, nTab ); + rDoc.ExtendOverlapped( nMergeStartX, nMergeStartY, nMergeEndX, nMergeEndY, nTab ); + + if ( nMergeStartX != nStartCol || nMergeStartY != nStartRow ) + { + // Disallow deleting parts of a merged cell. + // Deleting the start is allowed (merge is removed), so the end doesn't have to be checked. + + pErrorId = STR_MSSG_DELETECELLS_0; + } + if ( nMergeEndX != nEndCol || nMergeEndY != nEndRow ) + { + // detect if the start of a merged cell is deleted, so the merge flags can be refreshed + + bNeedRefresh = true; + } + } + + if (pErrorId) + { + ErrorMessage(pErrorId); + return; + } + + // proceed + + weld::WaitObject aWait(GetViewData().GetDialogParent()); // important for TrackFormulas in UpdateReference + + ResetAutoSpellForContentChange(); + + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr<ScRefUndoData> pUndoData; + if (bRecord) + { + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + pUndoDoc->InitUndo( rDoc, nTab, nTab, !bRows, bRows ); // row height + + for (const sc::ColRowSpan & rSpan : aSpans) + { + SCCOLROW nStart = rSpan.mnStart; + SCCOLROW nEnd = rSpan.mnEnd; + if (bRows) + rDoc.CopyToDocument( 0,nStart,nTab, rDoc.MaxCol(), nEnd,nTab, InsertDeleteFlags::ALL,false,*pUndoDoc ); + else + rDoc.CopyToDocument( static_cast<SCCOL>(nStart),0,nTab, + static_cast<SCCOL>(nEnd), rDoc.MaxRow(), nTab, + InsertDeleteFlags::ALL,false,*pUndoDoc ); + } + + // all Formulas because of references + SCTAB nTabCount = rDoc.GetTableCount(); + pUndoDoc->AddUndoTab( 0, nTabCount-1 ); + rDoc.CopyToDocument( 0,0,0, rDoc.MaxCol(), rDoc.MaxRow(), MAXTAB, InsertDeleteFlags::FORMULA,false,*pUndoDoc ); + + pUndoData.reset(new ScRefUndoData( &rDoc )); + + rDoc.BeginDrawUndo(); + } + + std::vector<sc::ColRowSpan>::const_reverse_iterator ri = aSpans.rbegin(), riEnd = aSpans.rend(); + aFuncMark.SelectOneTable(nTab); + for (; ri != riEnd; ++ri) + { + SCCOLROW nEnd = ri->mnEnd; + SCCOLROW nStart = ri->mnStart; + + if (bRows) + { + rDoc.DeleteObjectsInArea(0, nStart, rDoc.MaxCol(), nEnd, aFuncMark, true); + rDoc.DeleteRow(0, nTab, rDoc.MaxCol(), nTab, nStart, static_cast<SCSIZE>(nEnd - nStart + 1)); + } + else + { + rDoc.DeleteObjectsInArea(nStart, 0, nEnd, rDoc.MaxRow(), aFuncMark, true); + rDoc.DeleteCol(0, nTab, rDoc.MaxRow(), nTab, static_cast<SCCOL>(nStart), static_cast<SCSIZE>(nEnd - nStart + 1)); + } + } + + if (bNeedRefresh) + { + SCCOLROW nFirstStart = aSpans[0].mnStart; + SCCOL nStartCol = bRows ? 0 : static_cast<SCCOL>(nFirstStart); + SCROW nStartRow = bRows ? static_cast<SCROW>(nFirstStart) : 0; + SCCOL nEndCol = rDoc.MaxCol(); + SCROW nEndRow = rDoc.MaxRow(); + + rDoc.RemoveFlagsTab( nStartCol, nStartRow, nEndCol, nEndRow, nTab, ScMF::Hor | ScMF::Ver ); + rDoc.ExtendMerge( nStartCol, nStartRow, nEndCol, nEndRow, nTab, true ); + } + + if (bRecord) + { + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoDeleteMulti>( + pDocSh, bRows, bNeedRefresh, nTab, std::vector(aSpans), std::move(pUndoDoc), std::move(pUndoData))); + } + + if (!AdjustRowHeight(0, rDoc.MaxRow(), true)) + { + if (bRows) + { + pDocSh->PostPaint( + 0, aSpans[0].mnStart, nTab, + rDoc.MaxCol(), rDoc.MaxRow(), nTab, (PaintPartFlags::Grid | PaintPartFlags::Left)); + } + else + { + pDocSh->PostPaint( + static_cast<SCCOL>(aSpans[0].mnStart), 0, nTab, + rDoc.MaxCol(), rDoc.MaxRow(), nTab, (PaintPartFlags::Grid | PaintPartFlags::Top)); + } + } + + aModificator.SetDocumentModified(); + + CellContentChanged(); + + // put cursor directly behind the first deleted range + SCCOL nCurX = GetViewData().GetCurX(); + SCROW nCurY = GetViewData().GetCurY(); + if ( bRows ) + nCurY = aSpans[0].mnStart; + else + nCurX = static_cast<SCCOL>(aSpans[0].mnStart); + SetCursor( nCurX, nCurY ); + + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreaLinksChanged ) ); +} + +// delete contents + +void ScViewFunc::DeleteContents( InsertDeleteFlags nFlags ) +{ + ScViewData& rViewData = GetViewData(); + rViewData.SetPasteMode( ScPasteFlags::NONE ); + rViewData.GetViewShell()->UpdateCopySourceOverlay(); + + // not editable because of matrix only? attribute OK nonetheless + bool bOnlyNotBecauseOfMatrix; + bool bEditable = SelectionEditable( &bOnlyNotBecauseOfMatrix ); + if ( !bEditable ) + { + if ( !(bOnlyNotBecauseOfMatrix && + ((nFlags & (InsertDeleteFlags::ATTRIB | InsertDeleteFlags::EDITATTR)) == nFlags)) ) + { + ErrorMessage(bOnlyNotBecauseOfMatrix ? STR_MATRIXFRAGMENTERR : STR_PROTECTIONERR); + return; + } + } + + ScRange aMarkRange; + bool bSimple = false; + + ScDocument& rDoc = GetViewData().GetDocument(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScMarkData aFuncMark( GetViewData().GetMarkData() ); // local copy for UnmarkFiltered + ScViewUtil::UnmarkFiltered( aFuncMark, rDoc ); + + bool bRecord =true; + if (!rDoc.IsUndoEnabled()) + bRecord = false; + + if ( !aFuncMark.IsMarked() && !aFuncMark.IsMultiMarked() ) + { + aMarkRange.aStart.SetCol(GetViewData().GetCurX()); + aMarkRange.aStart.SetRow(GetViewData().GetCurY()); + aMarkRange.aStart.SetTab(GetViewData().GetTabNo()); + aMarkRange.aEnd = aMarkRange.aStart; + if ( rDoc.HasAttrib( aMarkRange, HasAttrFlags::Merged ) ) + { + aFuncMark.SetMarkArea( aMarkRange ); + } + else + bSimple = true; + } + + HideAllCursors(); // for if summary is cancelled + + ScDocFunc& rDocFunc = pDocSh->GetDocFunc(); + + // Can we really be sure that we can pass the bApi parameter as false to DeleteCell() and + // DeleteContents() here? (Meaning that this is interactive use.) Is this never invoked from + // scripting and whatnot? + if (bSimple) + rDocFunc.DeleteCell(aMarkRange.aStart, aFuncMark, nFlags, bRecord, /*bApi=*/ false); + else + rDocFunc.DeleteContents(aFuncMark, nFlags, bRecord, /*bApi=*/ false); + + pDocSh->UpdateOle(GetViewData()); + + if (ScModelObj* pModelObj = pDocSh->GetModel()) + { + ScRangeList aChangeRanges; + if ( bSimple ) + { + aChangeRanges.push_back( aMarkRange ); + } + else + { + aFuncMark.FillRangeListWithMarks( &aChangeRanges, false ); + } + + if (HelperNotifyChanges::getMustPropagateChangesModel(pModelObj)) + HelperNotifyChanges::Notify(*pModelObj, aChangeRanges, "delete-content"); + else if (pModelObj) + HelperNotifyChanges::Notify(*pModelObj, aChangeRanges, "data-area-invalidate"); + } + + CellContentChanged(); + ShowAllCursors(); + + if ( nFlags & InsertDeleteFlags::ATTRIB ) + { + if ( nFlags & InsertDeleteFlags::CONTENTS ) + bFormatValid = false; + else + StartFormatArea(); // delete attribute is also attribute-change + } + OUString aStartAddress = aMarkRange.aStart.GetColRowString(); + OUString aEndAddress = aMarkRange.aEnd.GetColRowString(); + collectUIInformation({{"RANGE", aStartAddress + ":" + aEndAddress}}, "DELETE"); +} + +// column width/row height (via header) - undo OK + +void ScViewFunc::SetWidthOrHeight( + bool bWidth, const std::vector<sc::ColRowSpan>& rRanges, ScSizeMode eMode, + sal_uInt16 nSizeTwips, bool bRecord, const ScMarkData* pMarkData ) +{ + if (rRanges.empty()) + return; + + // Use view's mark if none specified, but do not modify the original data, + // i.e. no MarkToMulti() on that. + ScMarkData aMarkData( pMarkData ? *pMarkData : GetViewData().GetMarkData()); + + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + SCCOL nCurX = GetViewData().GetCurX(); + SCROW nCurY = GetViewData().GetCurY(); + SCTAB nFirstTab = aMarkData.GetFirstSelected(); + SCTAB nCurTab = GetViewData().GetTabNo(); + if (bRecord && !rDoc.IsUndoEnabled()) + bRecord = false; + + ScDocShellModificator aModificator( *pDocSh ); + + bool bAllowed = true; + for (const SCTAB& nTab : aMarkData) + { + bAllowed = std::all_of(rRanges.begin(), rRanges.end(), + [&bWidth, &rDoc, &nTab](const sc::ColRowSpan& rRange) { + bool bOnlyMatrix; + bool bIsBlockEditable; + if (bWidth) + bIsBlockEditable = rDoc.IsBlockEditable(nTab, rRange.mnStart, 0, rRange.mnEnd, rDoc.MaxRow(), &bOnlyMatrix); + else + bIsBlockEditable = rDoc.IsBlockEditable(nTab, 0, rRange.mnStart, rDoc.MaxCol(), rRange.mnEnd, &bOnlyMatrix); + return bIsBlockEditable || bOnlyMatrix; + }); + if (!bAllowed) + break; + } + + // Allow users to resize cols/rows in readonly docs despite the r/o state. + // It is frustrating to be unable to see content in mis-sized cells. + if( !bAllowed && !pDocSh->IsReadOnly() ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + SCCOLROW nStart = rRanges.front().mnStart; + SCCOLROW nEnd = rRanges.back().mnEnd; + + OnLOKSetWidthOrHeight(nStart, bWidth); + + bool bFormula = false; + if ( eMode == SC_SIZE_OPTIMAL ) + { + const ScViewOptions& rOpts = GetViewData().GetOptions(); + bFormula = rOpts.GetOption( VOPT_FORMULAS ); + } + + ScDocumentUniquePtr pUndoDoc; + std::unique_ptr<ScOutlineTable> pUndoTab; + std::vector<sc::ColRowSpan> aUndoRanges; + + if ( bRecord ) + { + rDoc.BeginDrawUndo(); // Drawing Updates + + pUndoDoc.reset(new ScDocument( SCDOCMODE_UNDO )); + for (const SCTAB& nTab : aMarkData) + { + if (bWidth) + { + if ( nTab == nFirstTab ) + pUndoDoc->InitUndo( rDoc, nTab, nTab, true ); + else + pUndoDoc->AddUndoTab( nTab, nTab, true ); + rDoc.CopyToDocument( static_cast<SCCOL>(nStart), 0, nTab, + static_cast<SCCOL>(nEnd), rDoc.MaxRow(), nTab, InsertDeleteFlags::NONE, + false, *pUndoDoc ); + } + else + { + if ( nTab == nFirstTab ) + pUndoDoc->InitUndo( rDoc, nTab, nTab, false, true ); + else + pUndoDoc->AddUndoTab( nTab, nTab, false, true ); + rDoc.CopyToDocument( 0, nStart, nTab, rDoc.MaxCol(), nEnd, nTab, InsertDeleteFlags::NONE, false, *pUndoDoc ); + } + } + + aUndoRanges = rRanges; + + //! outlines from all tab? + ScOutlineTable* pTable = rDoc.GetOutlineTable( nCurTab ); + if (pTable) + pUndoTab.reset(new ScOutlineTable( *pTable )); + } + + if ( eMode==SC_SIZE_OPTIMAL || eMode==SC_SIZE_VISOPT ) + aMarkData.MarkToMulti(); + + bool bShow = nSizeTwips > 0 || eMode != SC_SIZE_DIRECT; + bool bOutline = false; + + for (const SCTAB& nTab : aMarkData) + { + for (const sc::ColRowSpan & rRange : rRanges) + { + SCCOLROW nStartNo = rRange.mnStart; + SCCOLROW nEndNo = rRange.mnEnd; + + if ( !bWidth ) // height always blockwise + { + if ( eMode==SC_SIZE_OPTIMAL || eMode==SC_SIZE_VISOPT ) + { + bool bAll = ( eMode==SC_SIZE_OPTIMAL ); + bool bFiltered = false; + if (!bAll) + { + // delete CRFlags::ManualSize for all in range, + // then SetOptimalHeight with bShrink = FALSE + for (SCROW nRow = nStartNo; nRow <= nEndNo; ++nRow) + { + SCROW nLastRow = nRow; + if (rDoc.RowHidden(nRow, nTab, nullptr, &nLastRow)) + { + nRow = nLastRow; + continue; + } + + CRFlags nOld = rDoc.GetRowFlags(nRow, nTab); + if (nOld & CRFlags::ManualSize) + rDoc.SetRowFlags(nRow, nTab, nOld & ~CRFlags::ManualSize); + } + } + else + { + SCROW nLastRow = nStartNo; + if (rDoc.RowFiltered(nStartNo, nTab, nullptr, &nLastRow) + || nLastRow < nEndNo) + bFiltered = true; + } + + + double nPPTX = GetViewData().GetPPTX(); + double nPPTY = GetViewData().GetPPTY(); + Fraction aZoomX = GetViewData().GetZoomX(); + Fraction aZoomY = GetViewData().GetZoomY(); + + ScSizeDeviceProvider aProv(pDocSh); + if (aProv.IsPrinter()) + { + nPPTX = aProv.GetPPTX(); + nPPTY = aProv.GetPPTY(); + aZoomX = aZoomY = Fraction( 1, 1 ); + } + + sc::RowHeightContext aCxt(rDoc.MaxRow(), nPPTX, nPPTY, aZoomX, aZoomY, aProv.GetDevice()); + aCxt.setForceAutoSize(bAll); + aCxt.setExtraHeight(nSizeTwips); + rDoc.SetOptimalHeight(aCxt, nStartNo, nEndNo, nTab, true); + + if (bFiltered) + ShowFilteredRows(rDoc, nTab, nStartNo, nEndNo, bShow); + + // Manual-Flag already (re)set in SetOptimalHeight in case of bAll=sal_True + // (set for Extra-Height, else reset). + } + else if ( eMode==SC_SIZE_DIRECT ) + { + if (nSizeTwips) + { + rDoc.SetRowHeightRange( nStartNo, nEndNo, nTab, nSizeTwips ); + rDoc.SetManualHeight( nStartNo, nEndNo, nTab, true ); // height was set manually + } + + // tdf#36383 - Skip consecutive rows hidden by AutoFilter + ShowFilteredRows(rDoc, nTab, nStartNo, nEndNo, nSizeTwips != 0); + + if (!bShow && nStartNo <= nCurY && nCurY <= nEndNo && nTab == nCurTab) + { + nCurY = -1; + } + } + else if ( eMode==SC_SIZE_SHOW ) + { + rDoc.ShowRows( nStartNo, nEndNo, nTab, true ); + } + } + else // column width + { + for (SCCOL nCol=static_cast<SCCOL>(nStartNo); nCol<=static_cast<SCCOL>(nEndNo); nCol++) + { + const bool bIsColHidden = rDoc.ColHidden(nCol, nTab); + if ( eMode != SC_SIZE_VISOPT || !bIsColHidden ) + { + sal_uInt16 nThisSize = nSizeTwips; + + if ( eMode==SC_SIZE_OPTIMAL || eMode==SC_SIZE_VISOPT ) + nThisSize = nSizeTwips + GetOptimalColWidth( nCol, nTab, bFormula ); + if ( nThisSize ) + rDoc.SetColWidth( nCol, nTab, nThisSize ); + + // tdf#131073 - Don't show hidden cols after setting optimal col width + if (eMode == SC_SIZE_OPTIMAL) + rDoc.ShowCol(nCol, nTab, !bIsColHidden); + else + rDoc.ShowCol( nCol, nTab, bShow ); + + if (!bShow && nCol == nCurX && nTab == nCurTab) + { + nCurX = -1; + } + } + } + } + + // adjust outline + if (bWidth) + { + if ( rDoc.UpdateOutlineCol( static_cast<SCCOL>(nStartNo), + static_cast<SCCOL>(nEndNo), nTab, bShow ) ) + bOutline = true; + } + else + { + if ( rDoc.UpdateOutlineRow( nStartNo, nEndNo, nTab, bShow ) ) + bOutline = true; + } + } + rDoc.SetDrawPageSize(nTab); + } + + if (!bOutline) + pUndoTab.reset(); + + if (bRecord) + { + pDocSh->GetUndoManager()->AddUndoAction( + std::make_unique<ScUndoWidthOrHeight>( + pDocSh, aMarkData, nStart, nCurTab, nEnd, nCurTab, + std::move(pUndoDoc), std::move(aUndoRanges), std::move(pUndoTab), eMode, nSizeTwips, bWidth)); + } + + if (nCurX < 0) + { + MoveCursorRel( 1, 0, SC_FOLLOW_LINE, false ); + } + + if (nCurY < 0) + { + MoveCursorRel( 0, 1, SC_FOLLOW_LINE, false ); + } + + // fdo#36247 Ensure that the drawing layer's map mode scaling factors match + // the new heights and widths. + GetViewData().GetView()->RefreshZoom(); + + for (const SCTAB& nTab : aMarkData) + rDoc.UpdatePageBreaks( nTab ); + + bool bAffectsVisibility = (eMode != SC_SIZE_ORIGINAL && eMode != SC_SIZE_VISOPT); + ScTabViewShell::notifyAllViewsSheetGeomInvalidation(GetViewData().GetViewShell(), + bWidth /* bColumns */, !bWidth /* bRows */, + true /* bSizes*/, bAffectsVisibility /* bHidden */, bAffectsVisibility /* bFiltered */, + false /* bGroups */, nCurTab); + GetViewData().GetView()->UpdateScrollBars(bWidth ? COLUMN_HEADER : ROW_HEADER); + + { + for (const SCTAB& nTab : aMarkData) + { + if (bWidth) + { + if (rDoc.HasAttrib( static_cast<SCCOL>(nStart),0,nTab, + static_cast<SCCOL>(nEnd), rDoc.MaxRow(), nTab, + HasAttrFlags::Merged | HasAttrFlags::Overlapped )) + nStart = 0; + if (nStart > 0) // go upwards because of Lines and cursor + --nStart; + pDocSh->PostPaint( static_cast<SCCOL>(nStart), 0, nTab, + rDoc.MaxCol(), rDoc.MaxRow(), nTab, PaintPartFlags::Grid | PaintPartFlags::Top ); + } + else + { + if (rDoc.HasAttrib( 0,nStart,nTab, rDoc.MaxCol(), nEnd,nTab, HasAttrFlags::Merged | HasAttrFlags::Overlapped )) + nStart = 0; + if (nStart != 0) + --nStart; + pDocSh->PostPaint( 0, nStart, nTab, rDoc.MaxCol(), rDoc.MaxRow(), nTab, PaintPartFlags::Grid | PaintPartFlags::Left ); + } + } + + pDocSh->UpdateOle(GetViewData()); + if( !pDocSh->IsReadOnly() ) + aModificator.SetDocumentModified(); + } + + if ( !bWidth ) + return; + + ScModelObj* pModelObj = pDocSh->GetModel(); + + if (!HelperNotifyChanges::getMustPropagateChangesModel(pModelObj)) + return; + + ScRangeList aChangeRanges; + for (const SCTAB& nTab : aMarkData) + { + for (const sc::ColRowSpan & rRange : rRanges) + { + SCCOL nStartCol = rRange.mnStart; + SCCOL nEndCol = rRange.mnEnd; + for ( SCCOL nCol = nStartCol; nCol <= nEndCol; ++nCol ) + { + aChangeRanges.push_back( ScRange( nCol, 0, nTab ) ); + } + } + } + HelperNotifyChanges::Notify(*pModelObj, aChangeRanges, "column-resize"); +} + +// column width/row height (via marked range) + +void ScViewFunc::SetMarkedWidthOrHeight( bool bWidth, ScSizeMode eMode, sal_uInt16 nSizeTwips ) +{ + ScMarkData& rMark = GetViewData().GetMarkData(); + + rMark.MarkToMulti(); + if (!rMark.IsMultiMarked()) + { + SCCOL nCol = GetViewData().GetCurX(); + SCROW nRow = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + const ScRange aMarkRange( nCol, nRow, nTab); + DoneBlockMode(); + InitOwnBlockMode( aMarkRange ); + rMark.SetMultiMarkArea( aMarkRange ); + MarkDataChanged(); + } + + std::vector<sc::ColRowSpan> aRanges = + bWidth ? rMark.GetMarkedColSpans() : rMark.GetMarkedRowSpans(); + + SetWidthOrHeight(bWidth, aRanges, eMode, nSizeTwips); + + rMark.MarkToSimple(); +} + +void ScViewFunc::ModifyCellSize( ScDirection eDir, bool bOptimal ) +{ + ScModule* pScMod = SC_MOD(); + bool bAnyEdit = pScMod->IsInputMode(); + SCCOL nCol = GetViewData().GetCurX(); + SCROW nRow = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + + bool bAllowed, bOnlyMatrix; + if ( eDir == DIR_LEFT || eDir == DIR_RIGHT ) + bAllowed = rDoc.IsBlockEditable( nTab, nCol,0, nCol,rDoc.MaxRow(), &bOnlyMatrix ); + else + bAllowed = rDoc.IsBlockEditable( nTab, 0,nRow, rDoc.MaxCol(), nRow, &bOnlyMatrix ); + if ( !bAllowed && !bOnlyMatrix ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + HideAllCursors(); + + //! step size adjustable + // step size is also minimum + constexpr sal_uInt16 nStepX = STD_COL_WIDTH / 5; + const sal_uInt16 nStepY = rDoc.GetSheetOptimalMinRowHeight(nTab); + + sal_uInt16 nWidth = rDoc.GetColWidth( nCol, nTab ); + sal_uInt16 nHeight = rDoc.GetRowHeight( nRow, nTab ); + std::vector<sc::ColRowSpan> aRange(1, sc::ColRowSpan(0,0)); + if ( eDir == DIR_LEFT || eDir == DIR_RIGHT ) + { + if (bOptimal) // width of this single cell + { + if ( bAnyEdit ) + { + // when editing the actual entered width + ScInputHandler* pHdl = pScMod->GetInputHdl( GetViewData().GetViewShell() ); + if (pHdl) + { + tools::Long nEdit = pHdl->GetTextSize().Width(); // in 0.01 mm + + const ScPatternAttr* pPattern = rDoc.GetPattern( nCol, nRow, nTab ); + const SvxMarginItem& rMItem = pPattern->GetItem(ATTR_MARGIN); + sal_uInt16 nMargin = rMItem.GetLeftMargin() + rMItem.GetRightMargin(); + if ( pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue() == SvxCellHorJustify::Left ) + nMargin = sal::static_int_cast<sal_uInt16>( + nMargin + pPattern->GetItem(ATTR_INDENT).GetValue() ); + + nWidth = std::round(o3tl::convert(nEdit * pDocSh->GetOutputFactor(), + o3tl::Length::mm100, o3tl::Length::twip)) + + nMargin + STD_EXTRA_WIDTH; + } + } + else + { + double nPPTX = GetViewData().GetPPTX(); + double nPPTY = GetViewData().GetPPTY(); + Fraction aZoomX = GetViewData().GetZoomX(); + Fraction aZoomY = GetViewData().GetZoomY(); + + ScSizeDeviceProvider aProv(pDocSh); + if (aProv.IsPrinter()) + { + nPPTX = aProv.GetPPTX(); + nPPTY = aProv.GetPPTY(); + aZoomX = aZoomY = Fraction( 1, 1 ); + } + + tools::Long nPixel = rDoc.GetNeededSize( nCol, nRow, nTab, aProv.GetDevice(), + nPPTX, nPPTY, aZoomX, aZoomY, true ); + sal_uInt16 nTwips = static_cast<sal_uInt16>( nPixel / nPPTX ); + if (nTwips != 0) + nWidth = nTwips + STD_EXTRA_WIDTH; + else + nWidth = STD_COL_WIDTH; + } + } + else // increment / decrement + { + if ( eDir == DIR_RIGHT ) + nWidth = sal::static_int_cast<sal_uInt16>( nWidth + nStepX ); + else if ( nWidth > nStepX ) + nWidth = sal::static_int_cast<sal_uInt16>( nWidth - nStepX ); + if ( nWidth < nStepX ) nWidth = nStepX; + if ( nWidth > MAX_COL_WIDTH ) nWidth = MAX_COL_WIDTH; + } + aRange[0].mnStart = nCol; + aRange[0].mnEnd = nCol; + SetWidthOrHeight(true, aRange, SC_SIZE_DIRECT, nWidth); + + // adjust height of this row if width demands/allows this + + if (!bAnyEdit) + { + const ScPatternAttr* pPattern = rDoc.GetPattern( nCol, nRow, nTab ); + bool bNeedHeight = + pPattern->GetItem( ATTR_LINEBREAK ).GetValue() || + pPattern->GetItem( ATTR_HOR_JUSTIFY ).GetValue() == SvxCellHorJustify::Block; + if (bNeedHeight) + AdjustRowHeight( nRow, nRow, true ); + } + } + else + { + ScSizeMode eMode; + if (bOptimal) + { + eMode = SC_SIZE_OPTIMAL; + nHeight = 0; + } + else + { + eMode = SC_SIZE_DIRECT; + if ( eDir == DIR_BOTTOM ) + nHeight = sal::static_int_cast<sal_uInt16>( nHeight + nStepY ); + else if ( nHeight > nStepY ) + nHeight = sal::static_int_cast<sal_uInt16>( nHeight - nStepY ); + if ( nHeight < nStepY ) nHeight = nStepY; + if ( nHeight > MAX_ROW_HEIGHT ) nHeight = MAX_ROW_HEIGHT; + } + aRange[0].mnStart = nRow; + aRange[0].mnEnd = nRow; + SetWidthOrHeight(false, aRange, eMode, nHeight); + } + + if ( bAnyEdit ) + { + UpdateEditView(); + if ( rDoc.HasAttrib( nCol, nRow, nTab, nCol, nRow, nTab, HasAttrFlags::NeedHeight ) ) + { + ScInputHandler* pHdl = pScMod->GetInputHdl( GetViewData().GetViewShell() ); + if (pHdl) + pHdl->SetModified(); // so that the height is adjusted with Enter + } + } + + ShowAllCursors(); +} + +void ScViewFunc::ProtectSheet( SCTAB nTab, const ScTableProtection& rProtect ) +{ + if (nTab == TABLEID_DOC) + return; + + ScMarkData& rMark = GetViewData().GetMarkData(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScDocFunc &rFunc = pDocSh->GetDocFunc(); + bool bUndo(rDoc.IsUndoEnabled()); + + // modifying several tabs is handled here + + if (bUndo) + { + OUString aUndo = ScResId( STR_UNDO_PROTECT_TAB ); + pDocSh->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, GetViewData().GetViewShell()->GetViewShellId() ); + } + + for (const auto& rTab : rMark) + { + rFunc.ProtectSheet(rTab, rProtect); + } + + if (bUndo) + pDocSh->GetUndoManager()->LeaveListAction(); + + UpdateLayerLocks(); //! broadcast to all views +} + +void ScViewFunc::ProtectDoc( const OUString& rPassword ) +{ + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocFunc &rFunc = pDocSh->GetDocFunc(); + + rFunc.Protect( TABLEID_DOC, rPassword ); + + UpdateLayerLocks(); //! broadcast to all views +} + +bool ScViewFunc::Unprotect( SCTAB nTab, const OUString& rPassword ) +{ + ScMarkData& rMark = GetViewData().GetMarkData(); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + ScDocFunc &rFunc = pDocSh->GetDocFunc(); + bool bChanged = false; + bool bUndo (rDoc.IsUndoEnabled()); + + if ( nTab == TABLEID_DOC || rMark.GetSelectCount() <= 1 ) + { + bChanged = rFunc.Unprotect( nTab, rPassword, false ); + if (bChanged && nTab != TABLEID_DOC) + SetTabProtectionSymbol(nTab, false); + } + else + { + // modifying several tabs is handled here + + if (bUndo) + { + OUString aUndo = ScResId( STR_UNDO_UNPROTECT_TAB ); + pDocSh->GetUndoManager()->EnterListAction( aUndo, aUndo, 0, GetViewData().GetViewShell()->GetViewShellId() ); + } + + for (const auto& rTab : rMark) + { + if ( rFunc.Unprotect( rTab, rPassword, false ) ) + { + bChanged = true; + SetTabProtectionSymbol( rTab, false); + } + } + + if (bUndo) + pDocSh->GetUndoManager()->LeaveListAction(); + } + + if (bChanged) + UpdateLayerLocks(); //! broadcast to all views + + return bChanged; +} + +void ScViewFunc::SetNoteText( const ScAddress& rPos, const OUString& rNoteText ) +{ + GetViewData().GetDocShell()->GetDocFunc().SetNoteText( rPos, rNoteText, false ); +} + +void ScViewFunc::ReplaceNote( const ScAddress& rPos, const OUString& rNoteText, const OUString* pAuthor, const OUString* pDate ) +{ + GetViewData().GetDocShell()->GetDocFunc().ReplaceNote( rPos, rNoteText, pAuthor, pDate, false ); +} + +void ScViewFunc::SetNumberFormat( SvNumFormatType nFormatType, sal_uLong nAdd ) +{ + // not editable because of matrix only? attribute OK nonetheless + bool bOnlyNotBecauseOfMatrix; + if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + sal_uInt32 nNumberFormat = 0; + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + SvNumberFormatter* pNumberFormatter = rDoc.GetFormatTable(); + LanguageType eLanguage = ScGlobal::eLnge; + ScPatternAttr aNewAttrs( rDoc.GetPool() ); + + // always take language from cursor position, even if there is a selection + + sal_uInt32 nCurrentNumberFormat = rDoc.GetNumberFormat( rViewData.GetCurX(), + rViewData.GetCurY(), + rViewData.GetTabNo()); + const SvNumberformat* pEntry = pNumberFormatter->GetEntry( nCurrentNumberFormat ); + if (pEntry) + eLanguage = pEntry->GetLanguage(); // else keep ScGlobal::eLnge + + nNumberFormat = pNumberFormatter->GetStandardFormat( nFormatType, eLanguage ) + nAdd; + + SfxItemSet& rSet = aNewAttrs.GetItemSet(); + rSet.Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nNumberFormat ) ); + // ATTR_LANGUAGE_FORMAT not + ApplySelectionPattern( aNewAttrs ); +} + +void ScViewFunc::SetNumFmtByStr( const OUString& rCode ) +{ + // not editable because of matrix only? attribute OK nonetheless + bool bOnlyNotBecauseOfMatrix; + if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + ScViewData& rViewData = GetViewData(); + ScDocument& rDoc = rViewData.GetDocument(); + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + + // language always from cursor position + + sal_uInt32 nCurrentNumberFormat = rDoc.GetNumberFormat( rViewData.GetCurX(), rViewData.GetCurY(), + rViewData.GetTabNo()); + const SvNumberformat* pEntry = pFormatter->GetEntry( nCurrentNumberFormat ); + LanguageType eLanguage = pEntry ? pEntry->GetLanguage() : ScGlobal::eLnge; + + // determine index for String + + bool bOk = true; + sal_uInt32 nNumberFormat = pFormatter->GetEntryKey( rCode, eLanguage ); + if ( nNumberFormat == NUMBERFORMAT_ENTRY_NOT_FOUND ) + { + // enter new + + OUString aFormat = rCode; // will be changed + sal_Int32 nErrPos = 0; + SvNumFormatType nType = SvNumFormatType::ALL; //! ??? + bOk = pFormatter->PutEntry( aFormat, nErrPos, nType, nNumberFormat, eLanguage ); + } + + if ( bOk ) // valid format? + { + ScPatternAttr aNewAttrs( rDoc.GetPool() ); + SfxItemSet& rSet = aNewAttrs.GetItemSet(); + rSet.Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nNumberFormat ) ); + rSet.Put( SvxLanguageItem( eLanguage, ATTR_LANGUAGE_FORMAT ) ); + ApplySelectionPattern( aNewAttrs ); + } + + //! else return error / issue warning ??? +} + +void ScViewFunc::ChangeNumFmtDecimals( bool bIncrement ) +{ + // not editable because of matrix only? attribute OK nonetheless + bool bOnlyNotBecauseOfMatrix; + if ( !SelectionEditable( &bOnlyNotBecauseOfMatrix ) && !bOnlyNotBecauseOfMatrix ) + { + ErrorMessage(STR_PROTECTIONERR); + return; + } + + ScDocument& rDoc = GetViewData().GetDocument(); + SvNumberFormatter* pFormatter = rDoc.GetFormatTable(); + + SCCOL nCol = GetViewData().GetCurX(); + SCROW nRow = GetViewData().GetCurY(); + SCTAB nTab = GetViewData().GetTabNo(); + + sal_uInt32 nOldFormat = rDoc.GetNumberFormat( nCol, nRow, nTab ); + const SvNumberformat* pOldEntry = pFormatter->GetEntry( nOldFormat ); + if (!pOldEntry) + { + OSL_FAIL("numberformat not found !!!"); + return; + } + + // what have we got here? + + sal_uInt32 nNewFormat = nOldFormat; + bool bError = false; + + LanguageType eLanguage = pOldEntry->GetLanguage(); + bool bThousand, bNegRed; + sal_uInt16 nPrecision, nLeading; + pOldEntry->GetFormatSpecialInfo( bThousand, bNegRed, nPrecision, nLeading ); + + SvNumFormatType nOldType = pOldEntry->GetType(); + if ( SvNumFormatType::ALL == ( nOldType & ( + SvNumFormatType::NUMBER | SvNumFormatType::CURRENCY | SvNumFormatType::PERCENT | SvNumFormatType::SCIENTIFIC | SvNumFormatType::TIME ) ) ) + { + // date, fraction, logical, text can not be changed + bError = true; + } + + //! SvNumberformat has a Member bStandard, but doesn't disclose it + bool bWasStandard = ( nOldFormat == pFormatter->GetStandardIndex( eLanguage ) ); + OUString sExponentialStandardFormat = ""; + if (bWasStandard) + { + // with "Standard" the decimal places depend on cell content + // 0 if empty or text -> no decimal places + double nVal = rDoc.GetValue( ScAddress( nCol, nRow, nTab ) ); + + // the ways of the Numberformatters are unfathomable, so try: + OUString aOut; + const Color* pCol; + const_cast<SvNumberformat*>(pOldEntry)->GetOutputString( nVal, aOut, &pCol ); + + nPrecision = 0; + // 'E' for exponential is fixed in Numberformatter + sal_Int32 nIndexE = aOut.indexOf('E'); + if ( nIndexE >= 0 ) + { + sExponentialStandardFormat = aOut.copy( nIndexE ).replace( '-', '+' ); + for ( sal_Int32 i=1 ; i<sExponentialStandardFormat.getLength() ; i++ ) + { + if ( sExponentialStandardFormat[i] >= '1' && sExponentialStandardFormat[i] <= '9' ) + sExponentialStandardFormat = sExponentialStandardFormat.replaceAt( i, 1, u"0" ); + } + aOut = aOut.copy( 0, nIndexE ); // remove exponential part + } + OUString aDecSep( pFormatter->GetFormatDecimalSep( nOldFormat ) ); + sal_Int32 nPos = aOut.indexOf( aDecSep ); + if ( nPos >= 0 ) + nPrecision = aOut.getLength() - nPos - aDecSep.getLength(); + // else keep 0 + } + else + { + if ( (nOldType & SvNumFormatType::SCIENTIFIC) && !bThousand && + (pOldEntry->GetFormatIntegerDigits()%3 == 0) && pOldEntry->GetFormatIntegerDigits() > 0 ) + bThousand = true; + } + + if (!bError) + { + if (bIncrement) + { + if (nPrecision<20) + ++nPrecision; // increment + else + bError = true; // 20 is maximum + } + else + { + if (nPrecision) + --nPrecision; // decrement + else + bError = true; // 0 is minimum + } + } + + if (!bError) + { + OUString aNewPicture = pFormatter->GenerateFormat(nOldFormat, eLanguage, + bThousand, bNegRed, + nPrecision, nLeading) + + sExponentialStandardFormat; + + nNewFormat = pFormatter->GetEntryKey( aNewPicture, eLanguage ); + if ( nNewFormat == NUMBERFORMAT_ENTRY_NOT_FOUND ) + { + sal_Int32 nErrPos = 0; + SvNumFormatType nNewType = SvNumFormatType::ALL; + bool bOk = pFormatter->PutEntry( aNewPicture, nErrPos, + nNewType, nNewFormat, eLanguage ); + OSL_ENSURE( bOk, "incorrect numberformat generated" ); + if (!bOk) + bError = true; + } + } + + if (!bError) + { + ScPatternAttr aNewAttrs( rDoc.GetPool() ); + SfxItemSet& rSet = aNewAttrs.GetItemSet(); + rSet.Put( SfxUInt32Item( ATTR_VALUE_FORMAT, nNewFormat ) ); + // ATTR_LANGUAGE_FORMAT not + ApplySelectionPattern( aNewAttrs ); + } +} + +void ScViewFunc::ChangeIndent( bool bIncrement ) +{ + ScViewData& rViewData = GetViewData(); + ScDocShell* pDocSh = rViewData.GetDocShell(); + ScMarkData& rMark = rViewData.GetMarkData(); + + ScMarkData aWorkMark = rMark; + ScViewUtil::UnmarkFiltered( aWorkMark, pDocSh->GetDocument() ); + aWorkMark.MarkToMulti(); + if (!aWorkMark.IsMultiMarked()) + { + SCCOL nCol = rViewData.GetCurX(); + SCROW nRow = rViewData.GetCurY(); + SCTAB nTab = rViewData.GetTabNo(); + aWorkMark.SetMultiMarkArea( ScRange(nCol,nRow,nTab) ); + } + + bool bSuccess = pDocSh->GetDocFunc().ChangeIndent( aWorkMark, bIncrement, false ); + if (bSuccess) + { + pDocSh->UpdateOle(rViewData); + StartFormatArea(); + + // stuff for sidebar panels + SfxBindings& rBindings = GetViewData().GetBindings(); + rBindings.Invalidate( SID_H_ALIGNCELL ); + rBindings.Invalidate( SID_ATTR_ALIGN_INDENT ); + } +} + +bool ScViewFunc::InsertName( const OUString& rName, const OUString& rSymbol, + const OUString& rType ) +{ + // Type = P,R,C,F (and combinations) + //! undo... + + bool bOk = false; + ScDocShell* pDocSh = GetViewData().GetDocShell(); + ScDocument& rDoc = pDocSh->GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + ScRangeName* pList = rDoc.GetRangeName(); + + ScRangeData::Type nType = ScRangeData::Type::Name; + auto pNewEntry = std::make_unique<ScRangeData>( + rDoc, rName, rSymbol, ScAddress( GetViewData().GetCurX(), + GetViewData().GetCurY(), nTab), nType ); + OUString aUpType = rType.toAsciiUpperCase(); + if ( aUpType.indexOf( 'P' ) != -1 ) + nType |= ScRangeData::Type::PrintArea; + if ( aUpType.indexOf( 'R' ) != -1 ) + nType |= ScRangeData::Type::RowHeader; + if ( aUpType.indexOf( 'C' ) != -1 ) + nType |= ScRangeData::Type::ColHeader; + if ( aUpType.indexOf( 'F' ) != -1 ) + nType |= ScRangeData::Type::Criteria; + pNewEntry->AddType(nType); + + if ( pNewEntry->GetErrCode() == FormulaError::NONE ) // text valid? + { + ScDocShellModificator aModificator( *pDocSh ); + + rDoc.PreprocessRangeNameUpdate(); + + // input available yet? Then remove beforehand (=change) + ScRangeData* pData = pList->findByUpperName(ScGlobal::getCharClass().uppercase(rName)); + if (pData) + { // take old Index + pNewEntry->SetIndex(pData->GetIndex()); + pList->erase(*pData); + } + + // don't delete, insert took ownership, even on failure! + if ( pList->insert( pNewEntry.release() ) ) + bOk = true; + + rDoc.CompileHybridFormula(); + + aModificator.SetDocumentModified(); + SfxGetpApp()->Broadcast( SfxHint( SfxHintId::ScAreasChanged ) ); + } + + return bOk; +} + +void ScViewFunc::CreateNames( CreateNameFlags nFlags ) +{ + bool bDone = false; + ScRange aRange; + if ( GetViewData().GetSimpleArea(aRange) == SC_MARK_SIMPLE ) + bDone = GetViewData().GetDocShell()->GetDocFunc().CreateNames( aRange, nFlags, false ); + + if (!bDone) + ErrorMessage(STR_CREATENAME_MARKERR); +} + +CreateNameFlags ScViewFunc::GetCreateNameFlags() +{ + CreateNameFlags nFlags = CreateNameFlags::NONE; + + SCCOL nStartCol, nEndCol; + SCROW nStartRow, nEndRow; + SCTAB nDummy; + if (GetViewData().GetSimpleArea(nStartCol,nStartRow,nDummy,nEndCol,nEndRow,nDummy) == SC_MARK_SIMPLE) + { + ScDocument& rDoc = GetViewData().GetDocument(); + SCTAB nTab = GetViewData().GetTabNo(); + bool bOk; + SCCOL i; + SCROW j; + + bOk = true; + SCCOL nFirstCol = nStartCol; + SCCOL nLastCol = nEndCol; + if (nStartCol+1 < nEndCol) { ++nFirstCol; --nLastCol; } + for (i=nFirstCol; i<=nLastCol && bOk; i++) + if (!rDoc.HasStringData( i,nStartRow,nTab )) + bOk = false; + if (bOk) + nFlags |= CreateNameFlags::Top; + else // Bottom only if not Top + { + bOk = true; + for (i=nFirstCol; i<=nLastCol && bOk; i++) + if (!rDoc.HasStringData( i,nEndRow,nTab )) + bOk = false; + if (bOk) + nFlags |= CreateNameFlags::Bottom; + } + + bOk = true; + SCROW nFirstRow = nStartRow; + SCROW nLastRow = nEndRow; + if (nStartRow+1 < nEndRow) { ++nFirstRow; --nLastRow; } + for (j=nFirstRow; j<=nLastRow && bOk; j++) + if (!rDoc.HasStringData( nStartCol,j,nTab )) + bOk = false; + if (bOk) + nFlags |= CreateNameFlags::Left; + else // Right only if not Left + { + bOk = true; + for (j=nFirstRow; j<=nLastRow && bOk; j++) + if (!rDoc.HasStringData( nEndCol,j,nTab )) + bOk = false; + if (bOk) + nFlags |= CreateNameFlags::Right; + } + } + + if (nStartCol == nEndCol) + nFlags &= ~CreateNameFlags( CreateNameFlags::Left | CreateNameFlags::Right ); + if (nStartRow == nEndRow) + nFlags &= ~CreateNameFlags( CreateNameFlags::Top | CreateNameFlags::Bottom ); + + return nFlags; +} + +void ScViewFunc::InsertNameList() +{ + ScAddress aPos( GetViewData().GetCurX(), GetViewData().GetCurY(), GetViewData().GetTabNo() ); + ScDocShell* pDocSh = GetViewData().GetDocShell(); + if ( pDocSh->GetDocFunc().InsertNameList( aPos, false ) ) + pDocSh->UpdateOle(GetViewData()); +} + +void ScViewFunc::UpdateSelectionArea( const ScMarkData& rSel, ScPatternAttr* pAttr ) +{ + ScDocShell* pDocShell = GetViewData().GetDocShell(); + ScRange aMarkRange; + if (rSel.IsMultiMarked() ) + aMarkRange = rSel.GetMultiMarkArea(); + else + aMarkRange = rSel.GetMarkArea(); + + bool bSetLines = false; + bool bSetAlign = false; + if ( pAttr ) + { + const SfxItemSet& rNewSet = pAttr->GetItemSet(); + bSetLines = rNewSet.GetItemState( ATTR_BORDER ) == SfxItemState::SET || + rNewSet.GetItemState( ATTR_SHADOW ) == SfxItemState::SET; + bSetAlign = rNewSet.GetItemState( ATTR_HOR_JUSTIFY ) == SfxItemState::SET; + } + + sal_uInt16 nExtFlags = 0; + if ( bSetLines ) + nExtFlags |= SC_PF_LINES; + if ( bSetAlign ) + nExtFlags |= SC_PF_WHOLEROWS; + + SCCOL nStartCol = aMarkRange.aStart.Col(); + SCROW nStartRow = aMarkRange.aStart.Row(); + SCTAB nStartTab = aMarkRange.aStart.Tab(); + SCCOL nEndCol = aMarkRange.aEnd.Col(); + SCROW nEndRow = aMarkRange.aEnd.Row(); + SCTAB nEndTab = aMarkRange.aEnd.Tab(); + pDocShell->PostPaint( nStartCol, nStartRow, nStartTab, + nEndCol, nEndRow, nEndTab, + PaintPartFlags::Grid, nExtFlags | SC_PF_TESTMERGE ); + ScTabViewShell* pTabViewShell = GetViewData().GetViewShell(); + pTabViewShell->AdjustBlockHeight(false, const_cast<ScMarkData*>(&rSel)); +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/viewutil.cxx b/sc/source/ui/view/viewutil.cxx new file mode 100644 index 0000000000..68575ac79f --- /dev/null +++ b/sc/source/ui/view/viewutil.cxx @@ -0,0 +1,426 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <scitems.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/viewsh.hxx> +#include <sfx2/dispatch.hxx> +#include <editeng/fontitem.hxx> +#include <editeng/langitem.hxx> +#include <editeng/scripttypeitem.hxx> +#include <i18nutil/transliteration.hxx> +#include <svl/itempool.hxx> +#include <svl/itemset.hxx> +#include <svl/cjkoptions.hxx> +#include <svl/ctloptions.hxx> +#include <vcl/svapp.hxx> +#include <vcl/settings.hxx> +#include <sfx2/objsh.hxx> +#include <sfx2/viewfrm.hxx> +#include <svl/eitem.hxx> +#include <osl/diagnose.h> + +#include <viewutil.hxx> +#include <chgtrack.hxx> +#include <chgviset.hxx> +#include <markdata.hxx> +#include <document.hxx> +#include <tabvwsh.hxx> + +#include <svx/svxdlg.hxx> +#include <svx/svxids.hrc> +#include <memory> + +void ScViewUtil::PutItemScript( SfxItemSet& rShellSet, const SfxItemSet& rCoreSet, + sal_uInt16 nWhichId, SvtScriptType nScript ) +{ + // take the effective item from rCoreSet according to nScript + // and put in rShellSet under the (base) nWhichId + + SfxItemPool& rPool = *rShellSet.GetPool(); + SvxScriptSetItem aSetItem( rPool.GetSlotId(nWhichId), rPool ); + // use PutExtended with eDefaultAs = SfxItemState::SET, so defaults from rCoreSet + // (document pool) are read and put into rShellSet (MessagePool) + aSetItem.GetItemSet().PutExtended( rCoreSet, SfxItemState::DONTCARE, SfxItemState::SET ); + const SfxPoolItem* pI = aSetItem.GetItemOfScript( nScript ); + if (pI) + { + rShellSet.Put( pI->CloneSetWhich(nWhichId) ); + } + else + rShellSet.InvalidateItem( nWhichId ); +} + +LanguageType ScViewUtil::GetEffLanguage( ScDocument& rDoc, const ScAddress& rPos ) +{ + // used for thesaurus + + SvtScriptType nScript = rDoc.GetScriptType(rPos.Col(), rPos.Row(), rPos.Tab()); + sal_uInt16 nWhich = ( nScript == SvtScriptType::ASIAN ) ? ATTR_CJK_FONT_LANGUAGE : + ( ( nScript == SvtScriptType::COMPLEX ) ? ATTR_CTL_FONT_LANGUAGE : ATTR_FONT_LANGUAGE ); + const SfxPoolItem* pItem = rDoc.GetAttr( rPos.Col(), rPos.Row(), rPos.Tab(), nWhich); + const SvxLanguageItem* pLangIt = dynamic_cast<const SvxLanguageItem*>( pItem ); + LanguageType eLnge; + if (pLangIt) + { + eLnge = pLangIt->GetValue(); + if (eLnge == LANGUAGE_DONTKNOW) //! can this happen? + { + LanguageType eLatin, eCjk, eCtl; + rDoc.GetLanguage( eLatin, eCjk, eCtl ); + eLnge = ( nScript == SvtScriptType::ASIAN ) ? eCjk : + ( ( nScript == SvtScriptType::COMPLEX ) ? eCtl : eLatin ); + } + } + else + eLnge = LANGUAGE_ENGLISH_US; + if ( eLnge == LANGUAGE_SYSTEM ) + eLnge = Application::GetSettings().GetLanguageTag().getLanguageType(); // never use SYSTEM for spelling + + return eLnge; +} + +TransliterationFlags ScViewUtil::GetTransliterationType( sal_uInt16 nSlotID ) +{ + TransliterationFlags nType = TransliterationFlags::NONE; + switch ( nSlotID ) + { + case SID_TRANSLITERATE_SENTENCE_CASE: + nType = TransliterationFlags::SENTENCE_CASE; + break; + case SID_TRANSLITERATE_TITLE_CASE: + nType = TransliterationFlags::TITLE_CASE; + break; + case SID_TRANSLITERATE_TOGGLE_CASE: + nType = TransliterationFlags::TOGGLE_CASE; + break; + case SID_TRANSLITERATE_UPPER: + nType = TransliterationFlags::LOWERCASE_UPPERCASE; + break; + case SID_TRANSLITERATE_LOWER: + nType = TransliterationFlags::UPPERCASE_LOWERCASE; + break; + case SID_TRANSLITERATE_HALFWIDTH: + nType = TransliterationFlags::FULLWIDTH_HALFWIDTH; + break; + case SID_TRANSLITERATE_FULLWIDTH: + nType = TransliterationFlags::HALFWIDTH_FULLWIDTH; + break; + case SID_TRANSLITERATE_HIRAGANA: + nType = TransliterationFlags::KATAKANA_HIRAGANA; + break; + case SID_TRANSLITERATE_KATAKANA: + nType = TransliterationFlags::HIRAGANA_KATAKANA; + break; + } + return nType; +} + +bool ScViewUtil::IsActionShown( const ScChangeAction& rAction, + const ScChangeViewSettings& rSettings, + ScDocument& rDocument ) +{ + // discarded are displayed as inverted accepted action, because of this + // order of ShowRejected/ShowAccepted is important + + if ( !rSettings.IsShowRejected() && rAction.IsRejecting() ) + return false; + + if ( !rSettings.IsShowAccepted() && rAction.IsAccepted() && !rAction.IsRejecting() ) + return false; + + if ( rSettings.HasAuthor() && rAction.GetUser() != rSettings.GetTheAuthorToShow() ) + return false; + + if ( rSettings.HasComment() ) + { + OUString aTmp = rAction.GetDescription(rDocument); + OUString aComStr = rAction.GetComment() + " (" + aTmp + ")"; + + if(!rSettings.IsValidComment(&aComStr)) + return false; + } + + if ( rSettings.HasRange() ) + if ( !rSettings.GetTheRangeList().Intersects( rAction.GetBigRange().MakeRange( rDocument ) ) ) + return false; + + if (rSettings.HasDate() && rSettings.GetTheDateMode() != SvxRedlinDateMode::NONE) + { + DateTime aDateTime = rAction.GetDateTime(); + const DateTime& rFirst = rSettings.GetTheFirstDateTime(); + const DateTime& rLast = rSettings.GetTheLastDateTime(); + switch ( rSettings.GetTheDateMode() ) + { // corresponds with ScHighlightChgDlg::OKBtnHdl + case SvxRedlinDateMode::BEFORE: + if ( aDateTime > rFirst ) + return false; + break; + + case SvxRedlinDateMode::SINCE: + if ( aDateTime < rFirst ) + return false; + break; + + case SvxRedlinDateMode::EQUAL: + case SvxRedlinDateMode::BETWEEN: + if ( aDateTime < rFirst || aDateTime > rLast ) + return false; + break; + + case SvxRedlinDateMode::NOTEQUAL: + if ( aDateTime >= rFirst && aDateTime <= rLast ) + return false; + break; + + case SvxRedlinDateMode::SAVE: + { + ScChangeTrack* pTrack = rDocument.GetChangeTrack(); + if ( !pTrack || pTrack->GetLastSavedActionNumber() >= + rAction.GetActionNumber() ) + return false; + } + break; + + default: + { + // added to avoid warnings + } + } + } + + if ( rSettings.HasActionRange() ) + { + sal_uLong nAction = rAction.GetActionNumber(); + sal_uLong nFirstAction; + sal_uLong nLastAction; + rSettings.GetTheActionRange( nFirstAction, nLastAction ); + if ( nAction < nFirstAction || nAction > nLastAction ) + { + return false; + } + } + + return true; +} + +void ScViewUtil::UnmarkFiltered( ScMarkData& rMark, const ScDocument& rDoc ) +{ + 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.RowFiltered(nRow, nTab, nullptr, &nLastRow)) + { + // use nStartCol/nEndCol, so the multi mark area isn't extended to all columns + // (visible in repaint for indentation) + rMark.SetMultiMarkArea( + ScRange(nStartCol, nRow, nTab, nEndCol, nLastRow, nTab), false); + bChanged = true; + nRow = nLastRow; + } + } + } + + if ( bChanged && !rMark.HasAnyMultiMarks() ) + rMark.ResetMark(); + + rMark.MarkToSimple(); +} + +bool ScViewUtil::FitToUnfilteredRows( ScRange & rRange, const ScDocument& rDoc, size_t nRows ) +{ + SCTAB nTab = rRange.aStart.Tab(); + bool bOneTabOnly = (nTab == rRange.aEnd.Tab()); + // Always fit the range on its first sheet. + OSL_ENSURE( bOneTabOnly, "ScViewUtil::ExtendToUnfilteredRows: works only on one sheet"); + SCROW nStartRow = rRange.aStart.Row(); + SCROW nLastRow = rDoc.LastNonFilteredRow(nStartRow, rDoc.MaxRow(), nTab); + if (rDoc.ValidRow(nLastRow)) + rRange.aEnd.SetRow(nLastRow); + SCROW nCount = rDoc.CountNonFilteredRows(nStartRow, rDoc.MaxRow(), nTab); + return static_cast<size_t>(nCount) == nRows && bOneTabOnly; +} + +bool ScViewUtil::HasFiltered( const ScRange& rRange, const ScDocument& rDoc ) +{ + SCROW nStartRow = rRange.aStart.Row(); + SCROW nEndRow = rRange.aEnd.Row(); + for (SCTAB nTab=rRange.aStart.Tab(); nTab<=rRange.aEnd.Tab(); nTab++) + { + if (rDoc.HasFilteredRows(nStartRow, nEndRow, nTab)) + return true; + } + + return false; +} + +void ScViewUtil::HideDisabledSlot( SfxItemSet& rSet, SfxBindings& rBindings, sal_uInt16 nSlotId ) +{ + SvtCTLOptions aCTLOptions; + bool bEnabled = true; + + switch( nSlotId ) + { + case SID_CHINESE_CONVERSION: + case SID_HANGUL_HANJA_CONVERSION: + bEnabled = SvtCJKOptions::IsAnyEnabled(); + break; + + case SID_TRANSLITERATE_HALFWIDTH: + case SID_TRANSLITERATE_FULLWIDTH: + case SID_TRANSLITERATE_HIRAGANA: + case SID_TRANSLITERATE_KATAKANA: + bEnabled = SvtCJKOptions::IsChangeCaseMapEnabled(); + break; + + case SID_INSERT_RLM: + case SID_INSERT_LRM: + bEnabled = SvtCTLOptions::IsCTLFontEnabled(); + break; + + default: + OSL_FAIL( "ScViewUtil::HideDisabledSlot - unknown slot ID" ); + return; + } + + rBindings.SetVisibleState( nSlotId, bEnabled ); + if( !bEnabled ) + rSet.DisableItem( nSlotId ); +} + +void ScViewUtil::ExecuteCharMap(const SvxFontItem& rOldFont, + const ScTabViewShell& rShell) +{ + SvxAbstractDialogFactory* pFact = SvxAbstractDialogFactory::Create(); + SfxViewFrame& rFrame = rShell.GetViewFrame(); + SfxAllItemSet aSet( rFrame.GetObjectShell()->GetPool() ); + aSet.Put( SfxBoolItem( FN_PARAM_1, false ) ); + aSet.Put( SvxFontItem( rOldFont.GetFamily(), rOldFont.GetFamilyName(), rOldFont.GetStyleName(), rOldFont.GetPitch(), rOldFont.GetCharSet(), aSet.GetPool()->GetWhich( SID_ATTR_CHAR_FONT ) ) ); + auto xFrame = rFrame.GetFrame().GetFrameInterface(); + ScopedVclPtr<SfxAbstractDialog> pDlg(pFact->CreateCharMapDialog(rShell.GetFrameWeld(), aSet, xFrame)); + pDlg->Execute(); +} + +bool ScViewUtil::IsFullScreen( const SfxViewShell& rViewShell ) +{ + SfxBindings& rBindings = rViewShell.GetViewFrame().GetBindings(); + std::unique_ptr<SfxBoolItem> pItem; + bool bIsFullScreen = false; + + if (rBindings.QueryState( SID_WIN_FULLSCREEN, pItem ) >= SfxItemState::DEFAULT) + bIsFullScreen = pItem->GetValue(); + + return bIsFullScreen; +} + +void ScViewUtil::SetFullScreen( const SfxViewShell& rViewShell, bool bSet ) +{ + if( IsFullScreen( rViewShell ) != bSet ) + { + SfxBoolItem aItem( SID_WIN_FULLSCREEN, bSet ); + rViewShell.GetDispatcher()->ExecuteList(SID_WIN_FULLSCREEN, + SfxCallMode::RECORD, { &aItem }); + } +} + +ScUpdateRect::ScUpdateRect( SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2 ) + : nNewStartX(0) + , nNewStartY(0) + , nNewEndX(0) + , nNewEndY(0) +{ + PutInOrder( nX1, nX2 ); + PutInOrder( nY1, nY2 ); + + nOldStartX = nX1; + nOldStartY = nY1; + nOldEndX = nX2; + nOldEndY = nY2; +} + +void ScUpdateRect::SetNew( SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2 ) +{ + PutInOrder( nX1, nX2 ); + PutInOrder( nY1, nY2 ); + + nNewStartX = nX1; + nNewStartY = nY1; + nNewEndX = nX2; + nNewEndY = nY2; +} + +bool ScUpdateRect::GetDiff( SCCOL& rX1, SCROW& rY1, SCCOL& rX2, SCROW& rY2 ) +{ + if ( nNewStartX == nOldStartX && nNewEndX == nOldEndX && + nNewStartY == nOldStartY && nNewEndY == nOldEndY ) + { + rX1 = nNewStartX; + rY1 = nNewStartY; + rX2 = nNewStartX; + rY2 = nNewStartY; + return false; + } + + rX1 = std::min(nNewStartX,nOldStartX); + rY1 = std::min(nNewStartY,nOldStartY); + rX2 = std::max(nNewEndX,nOldEndX); + rY2 = std::max(nNewEndY,nOldEndY); + + if ( nNewStartX == nOldStartX && nNewEndX == nOldEndX ) + { + if ( nNewStartY == nOldStartY ) + { + rY1 = std::min( nNewEndY, nOldEndY ); + rY2 = std::max( nNewEndY, nOldEndY ); + } + else if ( nNewEndY == nOldEndY ) + { + rY1 = std::min( nNewStartY, nOldStartY ); + rY2 = std::max( nNewStartY, nOldStartY ); + } + } + else if ( nNewStartY == nOldStartY && nNewEndY == nOldEndY ) + { + if ( nNewStartX == nOldStartX ) + { + rX1 = std::min( nNewEndX, nOldEndX ); + rX2 = std::max( nNewEndX, nOldEndX ); + } + else if ( nNewEndX == nOldEndX ) + { + rX1 = std::min( nNewStartX, nOldStartX ); + rX2 = std::max( nNewStartX, nOldStartX ); + } + } + + return true; +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/source/ui/view/waitoff.cxx b/sc/source/ui/view/waitoff.cxx new file mode 100644 index 0000000000..94b2728bc0 --- /dev/null +++ b/sc/source/ui/view/waitoff.cxx @@ -0,0 +1,51 @@ +/* -*- 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 <vcl/window.hxx> + +#include <waitoff.hxx> + +ScWaitCursorOff::ScWaitCursorOff( vcl::Window* pWinP ) + : + pWin( pWinP ), + nWaiters(0) +{ + if ( pWin ) + { + while ( pWin->IsWait() ) + { + nWaiters++; + pWin->LeaveWait(); + } + } +} + +ScWaitCursorOff::~ScWaitCursorOff() +{ + if ( pWin ) + { + while ( nWaiters ) + { + nWaiters--; + pWin->EnterWait(); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |