diff options
Diffstat (limited to 'sc/source/ui/view/tabview.cxx')
-rw-r--r-- | sc/source/ui/view/tabview.cxx | 3016 |
1 files changed, 3016 insertions, 0 deletions
diff --git a/sc/source/ui/view/tabview.cxx b/sc/source/ui/view/tabview.cxx new file mode 100644 index 000000000..523044c75 --- /dev/null +++ b/sc/source/ui/view/tabview.cxx @@ -0,0 +1,3016 @@ +/* -*- 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<ScrollBar>::Create( pFrameWin, WinBits( WB_VSCROLL | WB_DRAG ) ) ), + aVScrollBottom( VclPtr<ScrollBar>::Create( pFrameWin, WinBits( WB_VSCROLL | WB_DRAG ) ) ), + aHScrollLeft( VclPtr<ScrollBar>::Create( pFrameWin, WinBits( WB_HSCROLL | WB_DRAG ) ) ), + aHScrollRight( VclPtr<ScrollBar>::Create( pFrameWin, WinBits( WB_HSCROLL | WB_DRAG ) ) ), + aCornerButton( VclPtr<ScCornerButton>::Create( pFrameWin, &aViewData ) ), + aTopButton( VclPtr<ScCornerButton>::Create( pFrameWin, &aViewData ) ), + aScrollBarBox( VclPtr<ScrollBarBox>::Create( pFrameWin, WB_SIZEABLE ) ), + aScrollTimer("ScTabView aScrollTimer"), + pTimerWindow( nullptr ), + aExtraEditViewManager( pViewShell, pGridWin ), + nTipVisible( nullptr ), + nTipAlign( QuickHelpFlags::NONE ), + nPrevDragPos( 0 ), + meBlockMode(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( ScrollBar& rScrollBar, tools::Long nMaxVal ) +{ + rScrollBar.SetRange( Range( 0, nMaxVal ) ); + rScrollBar.SetLineSize( 1 ); + rScrollBar.SetPageSize( 1 ); // is queried separately + rScrollBar.SetVisibleSize( 10 ); // is reset by Resize + + rScrollBar.SetScrollHdl( LINK(this, ScTabView, ScrollHdl) ); + rScrollBar.SetEndScrollHdl( LINK(this, ScTabView, EndScrollHdl) ); + + rScrollBar.EnableRTL( aViewData.GetDocument().IsLayoutRTL( aViewData.GetTabNo() ) ); +} + +// 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; + } + + // window at the bottom right + lcl_SetPosSize( *aScrollBarBox, Point( nPosX+nSizeX, nPosY+nSizeY ), Size( nBarX, nBarY ), + nTotalWidth, bLayoutRTL ); + + 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(); + nBarX = aVScrollBottom->GetSizePixel().Width(); + + 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; + lcl_SetPosSize( *aScrollBarBox, Point( nPosX+nSizeX, nPosY+nSizeY ), Size( nBarX, nBarY ), + nTotalWidth, bLayoutRTL ); + + 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 -------------------------------------------------------- + +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(); + tools::Long nOld = static_cast<tools::Long>( rOldY * 100 ); + tools::Long nNew; + if ( pData->GetDelta() < 0 ) + nNew = std::max( tools::Long(MINZOOM), basegfx::zoomtools::zoomOut( nOld )); + else + nNew = std::min( tools::Long(MAXZOOM), basegfx::zoomtools::zoomIn( nOld )); + if ( nNew != nOld ) + { + // scroll wheel doesn't set the AppOptions default + + bool bSyncZoom = SC_MOD()->GetAppOptions().GetSynchronizeZoom(); + SetZoomType( SvxZoomType::PERCENT, bSyncZoom ); + Fraction aFract( nNew, 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); + } + + bDone = true; + } + } + else + { + ScHSplitPos eHPos = WhichH(ePos); + ScVSplitPos eVPos = WhichV(ePos); + ScrollBar* pHScroll = ( eHPos == SC_SPLIT_LEFT ) ? aHScrollLeft.get() : aHScrollRight.get(); + ScrollBar* pVScroll = ( eVPos == SC_SPLIT_TOP ) ? aVScrollTop.get() : aVScrollBottom.get(); + if ( pGridWin[ePos] ) + bDone = pGridWin[ePos]->HandleScrollCommand( rCEvt, pHScroll, pVScroll ); + } + return bDone; +} + +IMPL_LINK_NOARG(ScTabView, EndScrollHdl, ScrollBar*, void) +{ + if ( bDragging ) + { + UpdateScrollBars(); + bDragging = false; + } +} + +IMPL_LINK( ScTabView, ScrollHdl, ScrollBar*, pScroll, void ) +{ + 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->GetType(); + 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); + } + } + + tools::Long nDelta = pScroll->GetDelta(); + 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; + case ScrollType::Drag: + { + // 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; + if ( nScrollPos > nPrevDragPos ) + { + if (nDelta<0) nDelta=0; + } + else if ( nScrollPos < nPrevDragPos ) + { + if (nDelta>0) nDelta=0; + } + else + nDelta = 0; + nPrevDragPos = nScrollPos; + } + break; + default: + { + // added to avoid warnings + } + } + + 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( aScrollBarBox.get(), bVScrollMode || bHScrollMode ); + + 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 ); + tools::Rectangle aObjAbs( pWin->OutputToAbsoluteScreenPixel( aObjPixel.TopLeft() ), + pWin->OutputToAbsoluteScreenPixel( aObjPixel.BottomRight() ) ); + + tools::Rectangle 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() ); + + 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 + 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 + 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(false); + + UpdateShow(); + PaintLeft(); + PaintTop(); + PaintGrid(); + + // SC_FOLLOW_NONE: only update active part + AlignToCursor( aViewData.GetCurX(), aViewData.GetCurY(), SC_FOLLOW_NONE ); + UpdateAutoFillMark(); + + InvalidateSplit(); +} + +void ScTabView::RemoveSplit() +{ + 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* pViewFrame = aViewData.GetViewShell()->GetViewFrame(); + if (pViewFrame) + { + SfxFrame& rFrame = pViewFrame->GetFrame(); + css::uno::Reference<css::frame::XController> xController = rFrame.GetController(); + if (xController.is()) + { + ScTabViewObj* pImp = comphelper::getFromUnoTunnel<ScTabViewObj>( xController ); + 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); + aScrollBarBox->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 ? + comphelper::getFromUnoTunnel<ScModelObj>( 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.getWidth()) || (!bColumn && aNewArea.getHeight())) + { + 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().c_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 getJSONString(aTree).c_str(); + } + + 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 getJSONString(aTree).c_str(); +} + +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: */ |