diff options
Diffstat (limited to 'sc/source/ui/view/gridwin4.cxx')
-rw-r--r-- | sc/source/ui/view/gridwin4.cxx | 2695 |
1 files changed, 2695 insertions, 0 deletions
diff --git a/sc/source/ui/view/gridwin4.cxx b/sc/source/ui/view/gridwin4.cxx new file mode 100644 index 0000000000..3639e82876 --- /dev/null +++ b/sc/source/ui/view/gridwin4.cxx @@ -0,0 +1,2695 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This file incorporates work covered by the following license notice: + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed + * with this work for additional information regarding copyright + * ownership. The ASF licenses this file to you under the Apache + * License, Version 2.0 (the "License"); you may not use this file + * except in compliance with the License. You may obtain a copy of + * the License at http://www.apache.org/licenses/LICENSE-2.0 . + */ + +#include <memory> +#include <scitems.hxx> +#include <editeng/eeitem.hxx> + +#include <svtools/colorcfg.hxx> +#include <editeng/colritem.hxx> +#include <editeng/editview.hxx> +#include <editeng/fhgtitem.hxx> +#include <editeng/brushitem.hxx> +#include <sfx2/bindings.hxx> +#include <sfx2/printer.hxx> +#include <vcl/cursor.hxx> +#include <vcl/settings.hxx> +#include <o3tl/unit_conversion.hxx> +#include <osl/diagnose.h> + +#include <LibreOfficeKit/LibreOfficeKitEnums.h> +#include <comphelper/lok.hxx> +#include <comphelper/scopeguard.hxx> +#include <sfx2/lokhelper.hxx> +#include <sfx2/lokcomponenthelpers.hxx> + +#include <svx/svdview.hxx> +#include <svx/svdpagv.hxx> +#include <svx/sdrpagewindow.hxx> +#include <svx/sdr/contact/objectcontactofpageview.hxx> +#include <svx/sdr/contact/viewobjectcontact.hxx> +#include <svx/sdr/contact/viewcontact.hxx> +#include <tabvwsh.hxx> +#include <vcl/lineinfo.hxx> +#include <vcl/sysdata.hxx> + +#include <gridwin.hxx> +#include <viewdata.hxx> +#include <output.hxx> +#include <document.hxx> +#include <attrib.hxx> +#include <patattr.hxx> +#include <dbdata.hxx> +#include <docoptio.hxx> +#include <notemark.hxx> +#include <dbfunc.hxx> +#include <scmod.hxx> +#include <inputhdl.hxx> +#include <rfindlst.hxx> +#include <hiranges.hxx> +#include <pagedata.hxx> +#include <docpool.hxx> +#include <globstr.hrc> +#include <scresid.hxx> +#include <docsh.hxx> +#include <cbutton.hxx> +#include <invmerge.hxx> +#include <editutil.hxx> +#include <inputopt.hxx> +#include <fillinfo.hxx> +#include <dpcontrol.hxx> +#include <queryparam.hxx> +#include <queryentry.hxx> +#include <markdata.hxx> +#include <sc.hrc> +#include <vcl/virdev.hxx> +#include <svx/sdrpaintwindow.hxx> +#include <drwlayer.hxx> + +static void lcl_LimitRect( tools::Rectangle& rRect, const tools::Rectangle& rVisible ) +{ + if ( rRect.Top() < rVisible.Top()-1 ) rRect.SetTop( rVisible.Top()-1 ); + if ( rRect.Bottom() > rVisible.Bottom()+1 ) rRect.SetBottom( rVisible.Bottom()+1 ); + + // The header row must be drawn also when the inner rectangle is not visible, + // that is why there is no return value anymore. + // When it is far away, then lcl_DrawOneFrame is not even called. +} + +static void lcl_DrawOneFrame( vcl::RenderContext* pDev, const tools::Rectangle& rInnerPixel, + const OUString& rTitle, const Color& rColor, bool bTextBelow, + double nPPTX, double nPPTY, const Fraction& rZoomY, + ScDocument& rDoc, ScViewData& rButtonViewData, bool bLayoutRTL ) +{ + // rButtonViewData is only used to set the button size, + + tools::Rectangle aInner = rInnerPixel; + if ( bLayoutRTL ) + { + aInner.SetLeft( rInnerPixel.Right() ); + aInner.SetRight( rInnerPixel.Left() ); + } + + tools::Rectangle aVisible( Point(0,0), pDev->GetOutputSizePixel() ); + lcl_LimitRect( aInner, aVisible ); + + tools::Rectangle aOuter = aInner; + tools::Long nHor = static_cast<tools::Long>( SC_SCENARIO_HSPACE * nPPTX ); + tools::Long nVer = static_cast<tools::Long>( SC_SCENARIO_VSPACE * nPPTY ); + aOuter.AdjustLeft( -nHor ); + aOuter.AdjustRight(nHor ); + aOuter.AdjustTop( -nVer ); + aOuter.AdjustBottom(nVer ); + + // use ScPatternAttr::GetFont only for font size + vcl::Font aAttrFont; + rDoc.GetPool()->GetDefaultItem(ATTR_PATTERN). + fillFontOnly(aAttrFont, pDev, &rZoomY); + + // everything else from application font + vcl::Font aAppFont = pDev->GetSettings().GetStyleSettings().GetAppFont(); + aAppFont.SetFontSize( aAttrFont.GetFontSize() ); + + aAppFont.SetAlignment( ALIGN_TOP ); + pDev->SetFont( aAppFont ); + + Size aTextSize( pDev->GetTextWidth( rTitle ), pDev->GetTextHeight() ); + + if ( bTextBelow ) + aOuter.AdjustBottom(aTextSize.Height() ); + else + aOuter.AdjustTop( -(aTextSize.Height()) ); + + pDev->SetLineColor(); + pDev->SetFillColor( rColor ); + // left, top, right, bottom + pDev->DrawRect( tools::Rectangle( aOuter.Left(), aOuter.Top(), aInner.Left(), aOuter.Bottom() ) ); + pDev->DrawRect( tools::Rectangle( aOuter.Left(), aOuter.Top(), aOuter.Right(), aInner.Top() ) ); + pDev->DrawRect( tools::Rectangle( aInner.Right(), aOuter.Top(), aOuter.Right(), aOuter.Bottom() ) ); + pDev->DrawRect( tools::Rectangle( aOuter.Left(), aInner.Bottom(), aOuter.Right(), aOuter.Bottom() ) ); + + tools::Long nButtonY = bTextBelow ? aInner.Bottom() : aOuter.Top(); + + ScDDComboBoxButton aComboButton(pDev); + aComboButton.SetOptSizePixel(); + tools::Long nBWidth = tools::Long(aComboButton.GetSizePixel().Width() * rZoomY); + tools::Long nBHeight = nVer + aTextSize.Height() + 1; + Size aButSize( nBWidth, nBHeight ); + tools::Long nButtonPos = bLayoutRTL ? aOuter.Left() : aOuter.Right()-nBWidth+1; + aComboButton.Draw( Point(nButtonPos, nButtonY), aButSize ); + rButtonViewData.SetScenButSize( aButSize ); + + tools::Long nTextStart = bLayoutRTL ? aInner.Right() - aTextSize.Width() + 1 : aInner.Left(); + + bool bWasClip = false; + vcl::Region aOldClip; + bool bClip = ( aTextSize.Width() > aOuter.Right() - nBWidth - aInner.Left() ); + if ( bClip ) + { + if (pDev->IsClipRegion()) + { + bWasClip = true; + aOldClip = pDev->GetActiveClipRegion(); + } + tools::Long nClipStartX = bLayoutRTL ? aOuter.Left() + nBWidth : aInner.Left(); + tools::Long nClipEndX = bLayoutRTL ? aInner.Right() : aOuter.Right() - nBWidth; + pDev->SetClipRegion( vcl::Region(tools::Rectangle( nClipStartX, nButtonY + nVer/2, + nClipEndX, nButtonY + nVer/2 + aTextSize.Height())) ); + } + + pDev->DrawText( Point( nTextStart, nButtonY + nVer/2 ), rTitle ); + + if ( bClip ) + { + if ( bWasClip ) + pDev->SetClipRegion(aOldClip); + else + pDev->SetClipRegion(); + } + + pDev->SetFillColor(); + pDev->SetLineColor( COL_BLACK ); + pDev->DrawRect( aInner ); + pDev->DrawRect( aOuter ); +} + +static void lcl_DrawScenarioFrames( OutputDevice* pDev, ScViewData& rViewData, ScSplitPos eWhich, + SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2 ) +{ + ScDocument& rDoc = rViewData.GetDocument(); + SCTAB nTab = rViewData.GetTabNo(); + SCTAB nTabCount = rDoc.GetTableCount(); + if ( nTab+1 >= nTabCount || !rDoc.IsScenario(nTab+1) || rDoc.IsScenario(nTab) ) + return; + + if ( nX1 > 0 ) --nX1; + if ( nY1>=2 ) nY1 -= 2; // Hack: Header row affects two cells + else if ( nY1 > 0 ) --nY1; + if ( nX2 < rDoc.MaxCol() ) ++nX2; + if ( nY2 < rDoc.MaxRow()-1 ) nY2 += 2; // Hack: Header row affects two cells + else if ( nY2 < rDoc.MaxRow() ) ++nY2; + ScRange aViewRange( nX1,nY1,nTab, nX2,nY2,nTab ); + + //! cache the ranges in table!!!! + + ScMarkData aMarks(rDoc.GetSheetLimits()); + for (SCTAB i=nTab+1; i<nTabCount && rDoc.IsScenario(i); i++) + rDoc.MarkScenario( i, nTab, aMarks, false, ScScenarioFlags::ShowFrame ); + ScRangeListRef xRanges = new ScRangeList; + aMarks.FillRangeListWithMarks( xRanges.get(), false ); + + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + for (size_t j = 0, n = xRanges->size(); j < n; ++j) + { + ScRange aRange = (*xRanges)[j]; + // Always extend scenario frame to merged cells where no new non-covered cells + // are framed + rDoc.ExtendTotalMerge( aRange ); + + //! -> Extend repaint when merging !!! + + if ( aRange.Intersects( aViewRange ) ) //! Space for Text/Button? + { + Point aStartPos = rViewData.GetScrPos( + aRange.aStart.Col(), aRange.aStart.Row(), eWhich, true ); + Point aEndPos = rViewData.GetScrPos( + aRange.aEnd.Col()+1, aRange.aEnd.Row()+1, eWhich, true ); + // on the grid: + aStartPos.AdjustX( -nLayoutSign ); + aStartPos.AdjustY( -1 ); + aEndPos.AdjustX( -nLayoutSign ); + aEndPos.AdjustY( -1 ); + + bool bTextBelow = ( aRange.aStart.Row() == 0 ); + + OUString aCurrent; + Color aColor( COL_LIGHTGRAY ); + for (SCTAB nAct=nTab+1; nAct<nTabCount && rDoc.IsScenario(nAct); nAct++) + if ( rDoc.IsActiveScenario(nAct) && rDoc.HasScenarioRange(nAct,aRange) ) + { + OUString aDummyComment; + ScScenarioFlags nDummyFlags; + rDoc.GetName( nAct, aCurrent ); + rDoc.GetScenarioData( nAct, aDummyComment, aColor, nDummyFlags ); + } + + if (aCurrent.isEmpty()) + aCurrent = ScResId( STR_EMPTYDATA ); + + //! Own text "(None)" instead of "(Empty)" ??? + + lcl_DrawOneFrame( pDev, tools::Rectangle( aStartPos, aEndPos ), + aCurrent, aColor, bTextBelow, + rViewData.GetPPTX(), rViewData.GetPPTY(), rViewData.GetZoomY(), + rDoc, rViewData, bLayoutRTL ); + } + } +} + +static void lcl_DrawHighlight( ScOutputData& rOutputData, const ScViewData& rViewData, + const std::vector<ScHighlightEntry>& rHighlightRanges ) +{ + SCTAB nTab = rViewData.GetTabNo(); + for ( const auto& rHighlightRange : rHighlightRanges) + { + ScRange aRange = rHighlightRange.aRef; + if ( nTab >= aRange.aStart.Tab() && nTab <= aRange.aEnd.Tab() ) + { + rOutputData.DrawRefMark( + aRange.aStart.Col(), aRange.aStart.Row(), + aRange.aEnd.Col(), aRange.aEnd.Row(), + rHighlightRange.aColor, false ); + } + } +} + +// Calculates top-left offset to be applied based on margins and indent. +static void lcl_GetEditAreaTLOffset(tools::Long& nOffsetX, tools::Long& nOffsetY, const ScAddress& rAddr, + const ScViewData& rViewData, ScDocument& rDoc) +{ + tools::Long nLeftMargin = 0; + tools::Long nTopMargin = 0; + tools::Long nIndent = 0; + tools::Long nDummy = 0; + ScEditUtil aEUtil(&rDoc, rAddr.Col(), rAddr.Row(), rAddr.Tab(), + Point(0, 0), nullptr, rViewData.GetPPTX(), + rViewData.GetPPTY(), Fraction(1.0), Fraction(1.0), + false /* bPrintTwips */); + const ScPatternAttr* pPattern = rDoc.GetPattern(rAddr); + if (!rDoc.IsLayoutRTL(rAddr.Tab())) + nIndent = aEUtil.GetIndent(pPattern); + aEUtil.GetMargins(pPattern, nLeftMargin, nTopMargin, nDummy, nDummy); + nOffsetX = nIndent + nLeftMargin; + nOffsetY = nTopMargin; +} + +void ScGridWindow::DoInvertRect( const tools::Rectangle& rPixel ) +{ + if ( rPixel == aInvertRect ) + aInvertRect = tools::Rectangle(); // Cancel + else + { + OSL_ENSURE( aInvertRect.IsEmpty(), "DoInvertRect no pairs" ); + + aInvertRect = rPixel; // Mark new rectangle + } + + UpdateHeaderOverlay(); // uses aInvertRect +} + +void ScGridWindow::PrePaint(vcl::RenderContext& /*rRenderContext*/) +{ + // forward PrePaint to DrawingLayer + ScTabViewShell* pTabViewShell = mrViewData.GetViewShell(); + + if(pTabViewShell) + { + SdrView* pDrawView = pTabViewShell->GetScDrawView(); + + if (pDrawView) + { + pDrawView->PrePaint(); + } + } +} + +bool ScGridWindow::NeedLOKCursorInvalidation(const tools::Rectangle& rCursorRect, + const Fraction aScaleX, const Fraction aScaleY) +{ + // Don't see the need for a map as there will be only a few zoom levels + // and as of now X and Y zooms in online are the same. + for (auto& rEntry : maLOKLastCursor) + { + if (aScaleX == rEntry.aScaleX && aScaleY == rEntry.aScaleY) + { + if (rCursorRect == rEntry.aRect) + return false; // No change + + // Update and allow invalidate. + rEntry.aRect = rCursorRect; + return true; + } + } + + maLOKLastCursor.push_back(LOKCursorEntry{aScaleX, aScaleY, rCursorRect}); + return true; +} + +void ScGridWindow::InvalidateLOKViewCursor(const tools::Rectangle& rCursorRect, + const Fraction aScaleX, const Fraction aScaleY) +{ + if (!NeedLOKCursorInvalidation(rCursorRect, aScaleX, aScaleY)) + return; + + ScTabViewShell* pThisViewShell = mrViewData.GetViewShell(); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + + while (pViewShell) + { + if (pViewShell != pThisViewShell && pViewShell->GetDocId() == pThisViewShell->GetDocId()) + { + ScTabViewShell* pOtherViewShell = dynamic_cast<ScTabViewShell*>(pViewShell); + if (pOtherViewShell) + { + ScViewData& rOtherViewData = pOtherViewShell->GetViewData(); + Fraction aZoomX = rOtherViewData.GetZoomX(); + Fraction aZoomY = rOtherViewData.GetZoomY(); + if (aZoomX == aScaleX && aZoomY == aScaleY) + { + SfxLokHelper::notifyOtherView(pThisViewShell, pOtherViewShell, + LOK_CALLBACK_INVALIDATE_VIEW_CURSOR, "rectangle", rCursorRect.toString()); + } + } + } + + pViewShell = SfxViewShell::GetNext(*pViewShell); + } +} + +void ScGridWindow::Paint( vcl::RenderContext& /*rRenderContext*/, const tools::Rectangle& rRect ) +{ + ScDocument& rDoc = mrViewData.GetDocument(); + if ( rDoc.IsInInterpreter() ) + { + // Via Reschedule, interpreted cells do not trigger Invalidate again, + // otherwise for instance an error box would never appear (bug 36381). + // Later, through bNeedsRepaint everything is painted again. + if ( bNeedsRepaint ) + { + //! Merge Rectangle? + aRepaintPixel = tools::Rectangle(); // multiple -> paint all + } + else + { + bNeedsRepaint = true; + aRepaintPixel = LogicToPixel(rRect); // only affected ranges + } + return; + } + + // #i117893# If GetSizePixel needs to call the resize handler, the resulting nested Paint call + // (possibly for a larger rectangle) has to be allowed. Call GetSizePixel before setting bIsInPaint. + GetSizePixel(); + + if (bIsInPaint) + return; + + bIsInPaint = true; + + tools::Rectangle aPixRect = LogicToPixel( rRect ); + + SCCOL nX1 = mrViewData.GetPosX(eHWhich); + SCROW nY1 = mrViewData.GetPosY(eVWhich); + + SCTAB nTab = mrViewData.GetTabNo(); + + double nPPTX = mrViewData.GetPPTX(); + double nPPTY = mrViewData.GetPPTY(); + + tools::Rectangle aMirroredPixel = aPixRect; + if ( rDoc.IsLayoutRTL( nTab ) ) + { + // mirror and swap + tools::Long nWidth = GetSizePixel().Width(); + aMirroredPixel.SetLeft( nWidth - 1 - aPixRect.Right() ); + aMirroredPixel.SetRight( nWidth - 1 - aPixRect.Left() ); + } + + tools::Long nScrX = ScViewData::ToPixel( rDoc.GetColWidth( nX1, nTab ), nPPTX ); + while ( nScrX <= aMirroredPixel.Left() && nX1 < rDoc.MaxCol() ) + { + ++nX1; + nScrX += ScViewData::ToPixel( rDoc.GetColWidth( nX1, nTab ), nPPTX ); + } + SCCOL nX2 = nX1; + while ( nScrX <= aMirroredPixel.Right() && nX2 < rDoc.MaxCol() ) + { + ++nX2; + nScrX += ScViewData::ToPixel( rDoc.GetColWidth( nX2, nTab ), nPPTX ); + } + + tools::Long nScrY = 0; + ScViewData::AddPixelsWhile( nScrY, aPixRect.Top(), nY1, rDoc.MaxRow(), nPPTY, &rDoc, nTab); + SCROW nY2 = nY1; + if (nScrY <= aPixRect.Bottom() && nY2 < rDoc.MaxRow()) + { + ++nY2; + ScViewData::AddPixelsWhile( nScrY, aPixRect.Bottom(), nY2, rDoc.MaxRow(), nPPTY, &rDoc, nTab); + } + + Draw( nX1,nY1,nX2,nY2, ScUpdateMode::Marks ); // don't continue with painting + + bIsInPaint = false; +} + +void ScGridWindow::Draw( SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2, ScUpdateMode eMode ) +{ + ScDocument& rDoc = mrViewData.GetDocument(); + + // let's ignore the normal Draw() attempts when doing the tiled rendering, + // all the rendering should go through PaintTile() in that case. + // TODO revisit if we can actually turn this into an assert(), and clean + // up the callers + if (comphelper::LibreOfficeKit::isActive()) + return; + + ScModule* pScMod = SC_MOD(); + bool bTextWysiwyg = pScMod->GetInputOptions().GetTextWysiwyg(); + + if (mrViewData.IsMinimized()) + return; + + PutInOrder( nX1, nX2 ); + PutInOrder( nY1, nY2 ); + + OSL_ENSURE( rDoc.ValidCol(nX2) && rDoc.ValidRow(nY2), "GridWin Draw area too big" ); + + UpdateVisibleRange(); + + if (nX2 < maVisibleRange.mnCol1 || nY2 < maVisibleRange.mnRow1) + return; + // invisible + if (nX1 < maVisibleRange.mnCol1) + nX1 = maVisibleRange.mnCol1; + if (nY1 < maVisibleRange.mnRow1) + nY1 = maVisibleRange.mnRow1; + + if (nX1 > maVisibleRange.mnCol2 || nY1 > maVisibleRange.mnRow2) + return; + + if (nX2 > maVisibleRange.mnCol2) + nX2 = maVisibleRange.mnCol2; + if (nY2 > maVisibleRange.mnRow2) + nY2 = maVisibleRange.mnRow2; + + if ( eMode != ScUpdateMode::Marks && nX2 < maVisibleRange.mnCol2) + nX2 = maVisibleRange.mnCol2; // to continue painting + + // point of no return + + ++nPaintCount; // mark that painting is in progress + + SCTAB nTab = mrViewData.GetTabNo(); + rDoc.ExtendHidden( nX1, nY1, nX2, nY2, nTab ); + + Point aScrPos = mrViewData.GetScrPos( nX1, nY1, eWhich ); + tools::Long nMirrorWidth = GetSizePixel().Width(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + if ( bLayoutRTL ) + { + tools::Long nEndPixel = mrViewData.GetScrPos( nX2+1, maVisibleRange.mnRow1, eWhich ).X(); + nMirrorWidth = aScrPos.X() - nEndPixel; + aScrPos.setX( nEndPixel + 1 ); + } + + tools::Long nScrX = aScrPos.X(); + tools::Long nScrY = aScrPos.Y(); + + SCCOL nCurX = mrViewData.GetCurX(); + SCROW nCurY = mrViewData.GetCurY(); + SCCOL nCurEndX = nCurX; + SCROW nCurEndY = nCurY; + rDoc.ExtendMerge( nCurX, nCurY, nCurEndX, nCurEndY, nTab ); + bool bCurVis = nCursorHideCount==0 && + ( nCurEndX+1 >= nX1 && nCurX <= nX2+1 && nCurEndY+1 >= nY1 && nCurY <= nY2+1 ); + + // AutoFill Handles + if ( !bCurVis && nCursorHideCount==0 && bAutoMarkVisible && aAutoMarkPos.Tab() == nTab && + ( aAutoMarkPos.Col() != nCurX || aAutoMarkPos.Row() != nCurY ) ) + { + SCCOL nHdlX = aAutoMarkPos.Col(); + SCROW nHdlY = aAutoMarkPos.Row(); + rDoc.ExtendMerge( nHdlX, nHdlY, nHdlX, nHdlY, nTab ); + // left and top is unaffected + + //! Paint AutoFill handles alone (without Cursor) ??? + } + + double nPPTX = mrViewData.GetPPTX(); + double nPPTY = mrViewData.GetPPTY(); + + const ScViewOptions& rOpts = mrViewData.GetOptions(); + + // data block + + ScTableInfo aTabInfo; + rDoc.FillInfo( aTabInfo, nX1, nY1, nX2, nY2, nTab, + nPPTX, nPPTY, false, rOpts.GetOption(VOPT_FORMULAS), + &mrViewData.GetMarkData() ); + + Fraction aZoomX = mrViewData.GetZoomX(); + Fraction aZoomY = mrViewData.GetZoomY(); + ScOutputData aOutputData( GetOutDev(), OUTTYPE_WINDOW, aTabInfo, &rDoc, nTab, + nScrX, nScrY, nX1, nY1, nX2, nY2, nPPTX, nPPTY, + &aZoomX, &aZoomY ); + + aOutputData.SetMirrorWidth( nMirrorWidth ); // needed for RTL + aOutputData.SetSpellCheckContext(mpSpellCheckCxt.get()); + + ScopedVclPtr< VirtualDevice > xFmtVirtDev; + bool bLogicText = bTextWysiwyg; // call DrawStrings in logic MapMode? + + if ( bTextWysiwyg ) + { + // use printer for text formatting + + OutputDevice* pFmtDev = rDoc.GetPrinter(); + pFmtDev->SetMapMode( mrViewData.GetLogicMode(eWhich) ); + aOutputData.SetFmtDevice( pFmtDev ); + } + else if ( aZoomX != aZoomY && mrViewData.IsOle() ) + { + // #i45033# For OLE inplace editing with different zoom factors, + // use a virtual device with 1/100th mm as text formatting reference + + xFmtVirtDev.disposeAndReset( VclPtr<VirtualDevice>::Create() ); + xFmtVirtDev->SetMapMode(MapMode(MapUnit::Map100thMM)); + aOutputData.SetFmtDevice( xFmtVirtDev.get() ); + + bLogicText = true; // use logic MapMode + } + + DrawContent(*GetOutDev(), aTabInfo, aOutputData, bLogicText); + + // If something was inverted during the Paint (selection changed from Basic Macro) + // then this is now mixed up and has to be repainted + OSL_ENSURE(nPaintCount, "Wrong nPaintCount"); + --nPaintCount; + if (!nPaintCount) + CheckNeedsRepaint(); + + // Flag drawn formula cells "unchanged". + rDoc.ResetChanged(ScRange(nX1, nY1, nTab, nX2, nY2, nTab)); + rDoc.PrepareFormulaCalc(); +} + +namespace { + +class SuppressEditViewMessagesGuard +{ +public: + SuppressEditViewMessagesGuard(EditView& rEditView) : + mrEditView(rEditView), + mbOrigSuppressFlag(rEditView.IsSuppressLOKMessages()) + { + if (!mbOrigSuppressFlag) + mrEditView.SuppressLOKMessages(true); + } + + ~SuppressEditViewMessagesGuard() + { + if (mrEditView.IsSuppressLOKMessages() != mbOrigSuppressFlag) + mrEditView.SuppressLOKMessages(mbOrigSuppressFlag); + } + +private: + EditView& mrEditView; + const bool mbOrigSuppressFlag; +}; + +} + +/** + * Used to store the necessary information about the (combined-)tile + * area relevant to coordinate transformations in RTL mode. + */ +class ScLokRTLContext +{ +public: + ScLokRTLContext(const ScOutputData& rOutputData, const tools::Long nTileDeviceOriginPixelX): + mrOutputData(rOutputData), + mnTileDevOriginX(nTileDeviceOriginPixelX) + {} + + /** + * Converts from document x pixel position to the + * corresponding pixel position w.r.t the tile device origin. + */ + tools::Long docToTilePos(tools::Long nPosX) const + { + tools::Long nMirrorX = (-2 * mnTileDevOriginX) + mrOutputData.GetScrW(); + return nMirrorX - 1 - nPosX; + } + + +private: + const ScOutputData& mrOutputData; + const tools::Long mnTileDevOriginX; +}; + +namespace +{ +int lcl_GetMultiLineHeight(EditEngine* pEditEngine) +{ + int nHeight = 0; + int nParagraphs = pEditEngine->GetParagraphCount(); + if (nParagraphs > 1 || (nParagraphs > 0 && pEditEngine->GetLineCount(0) > 1)) + { + for (int nPara = 0; nPara < nParagraphs; nPara++) + { + nHeight += pEditEngine->GetLineCount(nPara) * pEditEngine->GetLineHeight(nPara); + } + } + + return nHeight; +} +} + +void ScGridWindow::DrawContent(OutputDevice &rDevice, const ScTableInfo& rTableInfo, ScOutputData& aOutputData, + bool bLogicText) +{ + ScModule* pScMod = SC_MOD(); + ScDocument& rDoc = mrViewData.GetDocument(); + const ScViewOptions& rOpts = mrViewData.GetOptions(); + bool bIsTiledRendering = comphelper::LibreOfficeKit::isActive(); + bool bNoBackgroundAndGrid = bIsTiledRendering + && comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scNoGridBackground); + + SCTAB nTab = aOutputData.nTab; + SCCOL nX1 = aOutputData.nX1; + SCROW nY1 = aOutputData.nY1; + SCCOL nX2 = aOutputData.nX2; + SCROW nY2 = aOutputData.nY2; + tools::Long nScrX = aOutputData.nScrX; + tools::Long nScrY = aOutputData.nScrY; + + const svtools::ColorConfig& rColorCfg = pScMod->GetColorConfig(); + Color aGridColor( rColorCfg.GetColorValue( svtools::CALCGRID ).nColor ); + if ( aGridColor == COL_TRANSPARENT ) + { + // use view options' grid color only if color config has "automatic" color + aGridColor = rOpts.GetGridColor(); + } + + aOutputData.SetSyntaxMode ( mrViewData.IsSyntaxMode() ); + aOutputData.SetGridColor ( aGridColor ); + aOutputData.SetShowNullValues ( rOpts.GetOption( VOPT_NULLVALS ) ); + aOutputData.SetShowFormulas ( rOpts.GetOption( VOPT_FORMULAS ) ); + aOutputData.SetShowSpellErrors ( rDoc.GetDocOptions().IsAutoSpell() ); + aOutputData.SetMarkClipped ( SC_MOD()->GetColorConfig().GetColorValue(svtools::CALCTEXTOVERFLOW).bIsVisible ); + + aOutputData.SetUseStyleColor( true ); // always set in table view + + aOutputData.SetViewShell( mrViewData.GetViewShell() ); + + bool bGrid = rOpts.GetOption( VOPT_GRID ) && mrViewData.GetShowGrid(); + bool bGridFirst = !rOpts.GetOption( VOPT_GRID_ONTOP ); + + bool bPage = rOpts.GetOption( VOPT_PAGEBREAKS ) && !bIsTiledRendering; + + bool bPageMode = mrViewData.IsPagebreakMode(); + if (bPageMode) // after FindChanged + { + // SetPagebreakMode also initializes bPrinted Flags + aOutputData.SetPagebreakMode( mrViewData.GetView()->GetPageBreakData() ); + } + + EditView* pEditView = nullptr; + bool bEditMode = mrViewData.HasEditView(eWhich); + if ( bEditMode && mrViewData.GetRefTabNo() == nTab ) + { + SCCOL nEditCol; + SCROW nEditRow; + mrViewData.GetEditView( eWhich, pEditView, nEditCol, nEditRow ); + SCCOL nEditEndCol = mrViewData.GetEditEndCol(); + SCROW nEditEndRow = mrViewData.GetEditEndRow(); + + if ( nEditEndCol >= nX1 && nEditCol <= nX2 && nEditEndRow >= nY1 && nEditRow <= nY2 ) + aOutputData.SetEditCell( nEditCol, nEditRow ); + else + bEditMode = false; + } + + const MapMode aOriginalMode = rDevice.GetMapMode(); + + // define drawing layer map mode and paint rectangle + MapMode aDrawMode = GetDrawMapMode(); + if (bIsTiledRendering) + { + // FIXME this shouldn't be necessary once we change the entire Calc to + // work in the logic coordinates (ideally 100ths of mm - so that it is + // the same as editeng and drawinglayer), and get rid of all the + // SetMapMode's and other unnecessary fun we have with pixels + // See also ScGridWindow::GetDrawMapMode() for the rest of this hack + aDrawMode.SetOrigin(PixelToLogic(Point(nScrX, nScrY), aDrawMode)); + } + tools::Rectangle aDrawingRectLogic; + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + bool bLokRTL = bLayoutRTL && bIsTiledRendering; + std::unique_ptr<ScLokRTLContext> pLokRTLCtxt( + bLokRTL ? + new ScLokRTLContext(aOutputData, o3tl::convert(aOriginalMode.GetOrigin().X(), o3tl::Length::twip, o3tl::Length::px)) : + nullptr); + + { + // get drawing pixel rect + tools::Rectangle aDrawingRectPixel( + bLokRTL ? Point(-(nScrX + aOutputData.GetScrW()), nScrY) : Point(nScrX, nScrY), + Size(aOutputData.GetScrW(), aOutputData.GetScrH())); + + // correct for border (left/right) + if(rDoc.MaxCol() == nX2 && !bLokRTL) + { + if(bLayoutRTL) + { + aDrawingRectPixel.SetLeft( 0 ); + } + else + { + aDrawingRectPixel.SetRight( GetOutputSizePixel().getWidth() ); + } + } + + // correct for border (bottom) + if(rDoc.MaxRow() == nY2) + { + aDrawingRectPixel.SetBottom( GetOutputSizePixel().getHeight() ); + } + + // get logic positions + aDrawingRectLogic = PixelToLogic(aDrawingRectPixel, aDrawMode); + } + + bool bInPlaceEditing = bEditMode && (mrViewData.GetRefTabNo() == mrViewData.GetTabNo()); + vcl::Cursor* pInPlaceCrsr = nullptr; + bool bInPlaceVisCursor = false; + if (bInPlaceEditing) + { + // toggle the cursor off if it's on to ensure the cursor invert + // background logic remains valid after the background is cleared on + // the next cursor flash + pInPlaceCrsr = pEditView->GetCursor(); + bInPlaceVisCursor = pInPlaceCrsr && pInPlaceCrsr->IsVisible(); + if (bInPlaceVisCursor) + pInPlaceCrsr->Hide(); + } + + OutputDevice* pContentDev = &rDevice; // device for document content, used by overlay manager + SdrPaintWindow* pTargetPaintWindow = nullptr; // #i74769# work with SdrPaintWindow directly + + { + // init redraw + ScTabViewShell* pTabViewShell = mrViewData.GetViewShell(); + + if(pTabViewShell) + { + MapMode aCurrentMapMode(pContentDev->GetMapMode()); + pContentDev->SetMapMode(aDrawMode); + SdrView* pDrawView = pTabViewShell->GetScDrawView(); + + if(pDrawView) + { + // #i74769# Use new BeginDrawLayers() interface + vcl::Region aDrawingRegion(aDrawingRectLogic); + pTargetPaintWindow = pDrawView->BeginDrawLayers(pContentDev, aDrawingRegion); + OSL_ENSURE(pTargetPaintWindow, "BeginDrawLayers: Got no SdrPaintWindow (!)"); + + if (!bIsTiledRendering) + { + // #i74769# get target device from SdrPaintWindow, this may be the prerender + // device now, too. + pContentDev = &(pTargetPaintWindow->GetTargetOutputDevice()); + aOutputData.SetContentDevice(pContentDev); + } + } + + pContentDev->SetMapMode(aCurrentMapMode); + } + } + + // app-background / document edge (area) (Pixel) + if ( !bIsTiledRendering && ( nX2 == rDoc.MaxCol() || nY2 == rDoc.MaxRow() ) ) + { + // save MapMode and set to pixel + MapMode aCurrentMapMode(pContentDev->GetMapMode()); + pContentDev->SetMapMode(MapMode(MapUnit::MapPixel)); + + tools::Rectangle aPixRect( Point(), GetOutputSizePixel() ); + pContentDev->SetFillColor( rColorCfg.GetColorValue(svtools::APPBACKGROUND).nColor ); + pContentDev->SetLineColor(); + if ( nX2==rDoc.MaxCol() ) + { + tools::Rectangle aDrawRect( aPixRect ); + if ( bLayoutRTL ) + aDrawRect.SetRight( nScrX - 1 ); + else + aDrawRect.SetLeft( nScrX + aOutputData.GetScrW() ); + if (aDrawRect.Right() >= aDrawRect.Left()) + pContentDev->DrawRect( aDrawRect ); + } + if ( nY2==rDoc.MaxRow() ) + { + tools::Rectangle aDrawRect( aPixRect ); + aDrawRect.SetTop( nScrY + aOutputData.GetScrH() ); + if ( nX2==rDoc.MaxCol() ) + { + // no double painting of the corner + if ( bLayoutRTL ) + aDrawRect.SetLeft( nScrX ); + else + aDrawRect.SetRight( nScrX + aOutputData.GetScrW() - 1 ); + } + if (aDrawRect.Bottom() >= aDrawRect.Top()) + pContentDev->DrawRect( aDrawRect ); + } + + // restore MapMode + pContentDev->SetMapMode(aCurrentMapMode); + } + + if ( rDoc.HasBackgroundDraw( nTab, aDrawingRectLogic ) ) + { + pContentDev->SetMapMode(MapMode(MapUnit::MapPixel)); + aOutputData.DrawClear(); + + // drawing background + + pContentDev->SetMapMode(aDrawMode); + DrawRedraw( aOutputData, SC_LAYER_BACK ); + } + else + aOutputData.SetSolidBackground(!bNoBackgroundAndGrid); + + aOutputData.DrawDocumentBackground(); + + if (bGridFirst && (bGrid || bPage)) + { + // Draw lines in background color cover over lok client grid lines in merged cell areas if bNoBackgroundAndGrid is set. + if (bNoBackgroundAndGrid) + aOutputData.DrawGrid(*pContentDev, false /* bGrid */, false /* bPage */, true /* bMergeCover */); + else + aOutputData.DrawGrid(*pContentDev, bGrid, bPage); + } + + aOutputData.DrawBackground(*pContentDev); + + if (!bGridFirst && (bGrid || bPage) && !bNoBackgroundAndGrid) + aOutputData.DrawGrid(*pContentDev, bGrid, bPage); + + pContentDev->SetMapMode(MapMode(MapUnit::MapPixel)); + + //tdf#128258 - draw a dotted line before hidden columns/rows + DrawHiddenIndicator(nX1,nY1,nX2,nY2, *pContentDev); + + if ( bPageMode ) + { + // DrawPagePreview draws complete lines/page numbers, must always be clipped + if ( aOutputData.SetChangedClip() ) + { + DrawPagePreview(nX1,nY1,nX2,nY2, *pContentDev); + pContentDev->SetClipRegion(); + } + } + + aOutputData.DrawShadow(); + aOutputData.DrawFrame(*pContentDev); + + aOutputData.DrawSparklines(*pContentDev); + + // Show Note Mark + if ( rOpts.GetOption( VOPT_NOTES ) ) + aOutputData.DrawNoteMarks(*pContentDev); + + if ( rOpts.GetOption( VOPT_FORMULAS_MARKS ) ) + aOutputData.DrawFormulaMarks(*pContentDev); + + if ( !bLogicText ) + aOutputData.DrawStrings(); // in pixel MapMode + + // edit cells and printer-metrics text must be before the buttons + // (DataPilot buttons contain labels in UI font) + + pContentDev->SetMapMode(mrViewData.GetLogicMode(eWhich)); + if ( bLogicText ) + aOutputData.DrawStrings(true); // in logic MapMode if bLogicText is set + aOutputData.DrawEdit(true); + + // the buttons are painted in absolute coordinates + if (bIsTiledRendering) + { + // Tiled offset nScrX, nScrY + MapMode aMap( MapUnit::MapPixel ); + Point aOrigin = aOriginalMode.GetOrigin(); + aOrigin.setX(o3tl::convert(aOrigin.getX(), o3tl::Length::twip, o3tl::Length::px) + nScrX); + aOrigin.setY(o3tl::convert(aOrigin.getY(), o3tl::Length::twip, o3tl::Length::px) + nScrY); + aMap.SetOrigin(aOrigin); + pContentDev->SetMapMode(aMap); + } + else + pContentDev->SetMapMode(MapMode(MapUnit::MapPixel)); + + // Autofilter- and Pivot-Buttons + DrawButtons(nX1, nX2, rTableInfo, pContentDev, pLokRTLCtxt.get()); // Pixel + + pContentDev->SetMapMode(MapMode(MapUnit::MapPixel)); + + aOutputData.DrawClipMarks(); + + // In any case, Scenario / ChangeTracking must happen after DrawGrid, also for !bGridFirst + + //! test if ChangeTrack display is active + //! Disable scenario frame via view option? + + SCTAB nTabCount = rDoc.GetTableCount(); + const std::vector<ScHighlightEntry> &rHigh = mrViewData.GetView()->GetHighlightRanges(); + bool bHasScenario = ( nTab+1<nTabCount && rDoc.IsScenario(nTab+1) && !rDoc.IsScenario(nTab) ); + bool bHasChange = ( rDoc.GetChangeTrack() != nullptr ); + + if ( bHasChange || bHasScenario || !rHigh.empty() ) + { + //! Merge SetChangedClip() with DrawMarks() ?? (different MapMode!) + + if ( bHasChange ) + aOutputData.DrawChangeTrack(); + + if ( bHasScenario ) + lcl_DrawScenarioFrames( pContentDev, mrViewData, eWhich, nX1,nY1,nX2,nY2 ); + + lcl_DrawHighlight( aOutputData, mrViewData, rHigh ); + } + + // Drawing foreground + + pContentDev->SetMapMode(aDrawMode); + + // Bitmaps and buttons are in absolute pixel coordinates. + const MapMode aOrig = pContentDev->GetMapMode(); + if (bIsTiledRendering) + { + Point aOrigin = aOriginalMode.GetOrigin(); + tools::Long nXOffset = bLayoutRTL ? + (-o3tl::convert(aOrigin.getX(), o3tl::Length::twip, o3tl::Length::px) + aOutputData.GetScrW()) : + o3tl::convert(aOrigin.getX(), o3tl::Length::twip, o3tl::Length::px); + Size aPixelOffset(nXOffset, o3tl::convert(aOrigin.getY(), o3tl::Length::twip, o3tl::Length::px)); + pContentDev->SetPixelOffset(aPixelOffset); + comphelper::LibreOfficeKit::setLocalRendering(); + } + + DrawRedraw( aOutputData, SC_LAYER_FRONT ); + DrawRedraw( aOutputData, SC_LAYER_INTERN ); + DrawSdrGrid( aDrawingRectLogic, pContentDev ); + + if (bIsTiledRendering) + { + pContentDev->SetPixelOffset(Size()); + pContentDev->SetMapMode(aOrig); + } + + pContentDev->SetMapMode(MapMode(MapUnit::MapPixel)); + + if ( mrViewData.IsRefMode() && nTab >= mrViewData.GetRefStartZ() && nTab <= mrViewData.GetRefEndZ() ) + { + Color aRefColor( rColorCfg.GetColorValue(svtools::CALCREFERENCE).nColor ); + aOutputData.DrawRefMark( mrViewData.GetRefStartX(), mrViewData.GetRefStartY(), + mrViewData.GetRefEndX(), mrViewData.GetRefEndY(), + aRefColor, false ); + } + + // range finder + + ScInputHandler* pHdl = pScMod->GetInputHdl( mrViewData.GetViewShell() ); + if (pHdl) + { + ScDocShell* pDocSh = mrViewData.GetDocShell(); + ScRangeFindList* pRangeFinder = pHdl->GetRangeFindList(); + if ( pRangeFinder && !pRangeFinder->IsHidden() && + pRangeFinder->GetDocName() == pDocSh->GetTitle() ) + { + sal_uInt16 nCount = static_cast<sal_uInt16>(pRangeFinder->Count()); + for (sal_uInt16 i=0; i<nCount; i++) + { + ScRangeFindData& rData = pRangeFinder->GetObject(i); + + ScRange aRef = rData.aRef; + aRef.PutInOrder(); + if ( aRef.aStart.Tab() >= nTab && aRef.aEnd.Tab() <= nTab ) + aOutputData.DrawRefMark( aRef.aStart.Col(), aRef.aStart.Row(), + aRef.aEnd.Col(), aRef.aEnd.Row(), + rData.nColor, + true ); + } + } + } + + { + // end redraw + ScTabViewShell* pTabViewShell = mrViewData.GetViewShell(); + + if(pTabViewShell) + { + MapMode aCurrentMapMode(pContentDev->GetMapMode()); + pContentDev->SetMapMode(aDrawMode); + + if (bIsTiledRendering) + { + Point aOrigin = aOriginalMode.GetOrigin(); + if (bLayoutRTL) + aOrigin.setX(-o3tl::convert(aOrigin.getX(), o3tl::Length::twip, o3tl::Length::px) + + aOutputData.nScrX + aOutputData.GetScrW()); + else + aOrigin.setX(o3tl::convert(aOrigin.getX(), o3tl::Length::twip, o3tl::Length::px) + + aOutputData.nScrX); + + aOrigin.setY(o3tl::convert(aOrigin.getY(), o3tl::Length::twip, o3tl::Length::px) + + aOutputData.nScrY); + const double twipFactor = 15 * 1.76388889; // 26.45833335 + aOrigin = Point(aOrigin.getX() * twipFactor, + aOrigin.getY() * twipFactor); + MapMode aNew = rDevice.GetMapMode(); + aNew.SetOrigin(aOrigin); + rDevice.SetMapMode(aNew); + } + + SdrView* pDrawView = pTabViewShell->GetScDrawView(); + + if(pDrawView) + { + // #i74769# work with SdrPaintWindow directly + pDrawView->EndDrawLayers(*pTargetPaintWindow, true); + } + + pContentDev->SetMapMode(aCurrentMapMode); + } + } + + // paint in-place editing + if (bIsTiledRendering) + { + ScTabViewShell* pThisViewShell = mrViewData.GetViewShell(); + SfxViewShell* pViewShell = SfxViewShell::GetFirst(); + + while (pViewShell) + { + bool bEnterLoop = bIsTiledRendering || pViewShell != pThisViewShell; + if (bEnterLoop && pViewShell->GetDocId() == pThisViewShell->GetDocId()) + { + ScTabViewShell* pTabViewShell = dynamic_cast<ScTabViewShell*>(pViewShell); + if (pTabViewShell) + { + ScViewData& rOtherViewData = pTabViewShell->GetViewData(); + ScSplitPos eOtherWhich = rOtherViewData.GetEditActivePart(); + + bool bOtherEditMode = rOtherViewData.HasEditView(eOtherWhich); + SCCOL nCol1 = rOtherViewData.GetEditStartCol(); + SCROW nRow1 = rOtherViewData.GetEditStartRow(); + SCCOL nCol2 = rOtherViewData.GetEditEndCol(); + SCROW nRow2 = rOtherViewData.GetEditEndRow(); + bOtherEditMode = bOtherEditMode + && ( nCol2 >= nX1 && nCol1 <= nX2 && nRow2 >= nY1 && nRow1 <= nY2 ); + if (bOtherEditMode && rOtherViewData.GetRefTabNo() == nTab) + { + EditView* pOtherEditView = rOtherViewData.GetEditView(eOtherWhich); + if (pOtherEditView) + { + tools::Long nScreenX = aOutputData.nScrX; + tools::Long nScreenY = aOutputData.nScrY; + + rDevice.SetLineColor(); + SfxViewShell* pSfxViewShell = SfxViewShell::Current(); + ScTabViewShell* pCurrentViewShell = dynamic_cast<ScTabViewShell*>(pSfxViewShell); + if (pCurrentViewShell) + { + const ScViewData& pViewData = pCurrentViewShell->GetViewData(); + const ScViewOptions& aViewOptions = pViewData.GetOptions(); + const ScPatternAttr* pPattern = rDoc.GetPattern( nCol1, nRow1, nTab ); + Color aCellColor = pPattern->GetItem(ATTR_BACKGROUND).GetColor(); + if (aCellColor.IsTransparent()) + { + aCellColor = aViewOptions.GetDocColor(); + } + rDevice.SetFillColor(aCellColor); + pOtherEditView->SetBackgroundColor(aCellColor); + } + Point aStart = mrViewData.GetScrPos( nCol1, nRow1, eOtherWhich ); + Point aEnd = mrViewData.GetScrPos( nCol2+1, nRow2+1, eOtherWhich ); + + if (bIsTiledRendering) + { + EditEngine* pEditEngine = pOtherEditView->GetEditEngine(); + if (pEditEngine) + aEnd.AdjustY(lcl_GetMultiLineHeight(pEditEngine)); + } + + if (bLokRTL) + { + // Transform the cell range X coordinates such that the edit cell area is + // horizontally mirrored w.r.t the (combined-)tile. + aStart.setX(pLokRTLCtxt->docToTilePos(aStart.X())); + aEnd.setX(pLokRTLCtxt->docToTilePos(aEnd.X())); + } + + // don't overwrite grid + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + aEnd.AdjustX( -(2 * nLayoutSign) ); + aEnd.AdjustY( -2 ); + + tools::Rectangle aBackground(aStart, aEnd); + if (bLokRTL) + aBackground.Normalize(); + + // Need to draw the background in absolute coords. + Point aOrigin = aOriginalMode.GetOrigin(); + aOrigin.setX( + o3tl::convert(aOrigin.getX(), o3tl::Length::twip, o3tl::Length::px) + + nScreenX); + aOrigin.setY( + o3tl::convert(aOrigin.getY(), o3tl::Length::twip, o3tl::Length::px) + + nScreenY); + aBackground += aOrigin; + rDevice.SetMapMode(aDrawMode); + + static const double twipFactor = 15 * 1.76388889; // 26.45833335 + // keep into account the zoom factor + aOrigin = Point((aOrigin.getX() * twipFactor) / static_cast<double>(aDrawMode.GetScaleX()), + (aOrigin.getY() * twipFactor) / static_cast<double>(aDrawMode.GetScaleY())); + + MapMode aNew = rDevice.GetMapMode(); + aNew.SetOrigin(aOrigin); + rDevice.SetMapMode(aNew); + + // paint the background + rDevice.DrawRect(rDevice.PixelToLogic(aBackground)); + tools::Rectangle aBGAbs(aBackground); + + tools::Rectangle aEditRect(aBackground); + tools::Long nOffsetX = 0, nOffsetY = 0; + // Get top-left offset because of margin and indent. + lcl_GetEditAreaTLOffset(nOffsetX, nOffsetY, ScAddress(nCol1, nRow1, nTab), mrViewData, rDoc); + aEditRect.AdjustLeft(nOffsetX + 1); + aEditRect.AdjustRight(1); + aEditRect.AdjustTop(nOffsetY + 1); + aEditRect.AdjustBottom(1); + + // EditView has an 'output area' which is used to clip the 'paint area' we provide below. + // So they need to be in the same coordinates/units. This is tied to the mapmode of the gridwin + // attached to the EditView, so we have to change its mapmode too (temporarily). We save the + // original mapmode and 'output area' and roll them back when we finish painting to rDevice. + OutputDevice& rOtherWin = pOtherEditView->GetOutputDevice(); + const tools::Rectangle aOrigOutputArea(pOtherEditView->GetOutputArea()); // Not in pixels. + const MapMode aOrigMapMode = rOtherWin.GetMapMode(); + rOtherWin.SetMapMode(rDevice.GetMapMode()); + + // Avoid sending wrong cursor/selection messages by the 'other' view, as the output-area is going + // to be tweaked temporarily to match the current view's zoom. + SuppressEditViewMessagesGuard aGuard(*pOtherEditView); + comphelper::ScopeGuard aOutputGuard( + [pOtherEditView, aOrigOutputArea, bLokRTL] { + if (bLokRTL && aOrigOutputArea != pOtherEditView->GetOutputArea()) + pOtherEditView->SetOutputArea(aOrigOutputArea); + }); + + aEditRect = rDevice.PixelToLogic(aEditRect); + if (bIsTiledRendering) + pOtherEditView->SetOutputArea(aEditRect); + else + aEditRect.Intersection(pOtherEditView->GetOutputArea()); + pOtherEditView->Paint(aEditRect, &rDevice); + + // EditView will do the cursor notifications correctly if we're in + // print-twips messaging mode. + if (bIsTiledRendering && !comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs)) + { + // Now we need to get relative cursor position within the editview. + // This is for sending the pixel-aligned twips position of the cursor to the specific views with + // the same given zoom level. + tools::Rectangle aCursorRect = pOtherEditView->GetEditCursor(); + Point aCursPos = OutputDevice::LogicToLogic(aCursorRect.TopLeft(), + MapMode(MapUnit::Map100thMM), MapMode(MapUnit::MapTwip)); + + const MapMode& rDevMM = rDevice.GetMapMode(); + MapMode aMM(MapUnit::MapTwip); + aMM.SetScaleX(rDevMM.GetScaleX()); + aMM.SetScaleY(rDevMM.GetScaleY()); + + aBGAbs.AdjustLeft(1); + aBGAbs.AdjustTop(1); + aCursorRect = GetOutDev()->PixelToLogic(aBGAbs, aMM); + aCursorRect.setWidth(0); + aCursorRect.Move(aCursPos.getX(), 0); + // Sends view cursor position to views of all matching zooms if needed (avoids duplicates). + InvalidateLOKViewCursor(aCursorRect, aMM.GetScaleX(), aMM.GetScaleY()); + } + + // Rollback the mapmode and 'output area'. + rOtherWin.SetMapMode(aOrigMapMode); + if (!bIsTiledRendering) + pOtherEditView->SetOutputArea(aOrigOutputArea); + rDevice.SetMapMode(MapMode(MapUnit::MapPixel)); + } + } + } + } + + pViewShell = SfxViewShell::GetNext(*pViewShell); + } + + } + + // In-place editing - when the user is typing, we need to paint the text + // using the editeng. + // It's being done after EndDrawLayers() to get it outside the overlay + // buffer and on top of everything. + if (bInPlaceEditing) + { + // get the coordinates of the area we need to clear (overpaint by + // the background) + SCCOL nCol1 = mrViewData.GetEditStartCol(); + SCROW nRow1 = mrViewData.GetEditStartRow(); + SCCOL nCol2 = mrViewData.GetEditEndCol(); + SCROW nRow2 = mrViewData.GetEditEndRow(); + rDevice.SetLineColor(); + rDevice.SetFillColor(pEditView->GetBackgroundColor()); + Point aStart = mrViewData.GetScrPos( nCol1, nRow1, eWhich ); + Point aEnd = mrViewData.GetScrPos( nCol2+1, nRow2+1, eWhich ); + + if (bLokRTL) + { + // Transform the cell range X coordinates such that the edit cell area is + // horizontally mirrored w.r.t the (combined-)tile. + aStart.setX(pLokRTLCtxt->docToTilePos(aStart.X())); + aEnd.setX(pLokRTLCtxt->docToTilePos(aEnd.X())); + } + + // don't overwrite grid + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + aEnd.AdjustX( -(2 * nLayoutSign) ); + aEnd.AdjustY( -2 ); + + // set the correct mapmode + tools::Rectangle aBackground(aStart, aEnd); + if (bLokRTL) + aBackground.Normalize(); + tools::Rectangle aBGAbs(aBackground); + + if (bIsTiledRendering) + { + // Need to draw the background in absolute coords. + Point aOrigin = aOriginalMode.GetOrigin(); + aOrigin.setX(o3tl::convert(aOrigin.getX(), o3tl::Length::twip, o3tl::Length::px) + + nScrX); + aOrigin.setY(o3tl::convert(aOrigin.getY(), o3tl::Length::twip, o3tl::Length::px) + + nScrY); + aBackground += aOrigin; + rDevice.SetMapMode(aDrawMode); + } + else + rDevice.SetMapMode(mrViewData.GetLogicMode()); + + if (bIsTiledRendering) + { + Point aOrigin = aOriginalMode.GetOrigin(); + aOrigin.setX(o3tl::convert(aOrigin.getX(), o3tl::Length::twip, o3tl::Length::px) + + nScrX); + aOrigin.setY(o3tl::convert(aOrigin.getY(), o3tl::Length::twip, o3tl::Length::px) + + nScrY); + static const double twipFactor = 15 * 1.76388889; // 26.45833335 + // keep into account the zoom factor + aOrigin = Point((aOrigin.getX() * twipFactor) / static_cast<double>(aDrawMode.GetScaleX()), + (aOrigin.getY() * twipFactor) / static_cast<double>(aDrawMode.GetScaleY())); + MapMode aNew = rDevice.GetMapMode(); + aNew.SetOrigin(aOrigin); + rDevice.SetMapMode(aNew); + } + + // paint the editeng text + if (bIsTiledRendering) + { + // EditView has an 'output area' which is used to clip the paint area we provide below. + // So they need to be in the same coordinates/units. This is tied to the mapmode of the gridwin + // attached to the EditView, so we have to change its mapmode too (temporarily). We save the + // original mapmode and 'output area' and roll them back when we finish painting to rDevice. + const MapMode aOrigMapMode = GetMapMode(); + SetMapMode(rDevice.GetMapMode()); + + // Avoid sending wrong cursor/selection messages by the current view, as the output-area is going + // to be tweaked temporarily to match other view's zoom. (This does not affect the manual + // cursor-messaging done in the non print-twips mode) + SuppressEditViewMessagesGuard aGuard(*pEditView); + + // EditView will do the cursor notifications correctly if we're in + // print-twips messaging mode. + if (!comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs)) + { + // Now we need to get relative cursor position within the editview. + // This is for sending the pixel-aligned twips position of the cursor to the specific views with + // the same given zoom level. + tools::Rectangle aCursorRect = pEditView->GetEditCursor(); + Point aCursPos = o3tl::toTwips(aCursorRect.TopLeft(), o3tl::Length::mm100); + + const MapMode& rDevMM = rDevice.GetMapMode(); + MapMode aMM(MapUnit::MapTwip); + aMM.SetScaleX(rDevMM.GetScaleX()); + aMM.SetScaleY(rDevMM.GetScaleY()); + + aBGAbs.AdjustLeft(1); + aBGAbs.AdjustTop(1); + aCursorRect = GetOutDev()->PixelToLogic(aBGAbs, aMM); + aCursorRect.setWidth(0); + aCursorRect.Move(aCursPos.getX(), 0); + // Sends view cursor position to views of all matching zooms if needed (avoids duplicates). + InvalidateLOKViewCursor(aCursorRect, aMM.GetScaleX(), aMM.GetScaleY()); + } + + // Rollback the mapmode and 'output area'. + SetMapMode(aOrigMapMode); + } + else + { + // paint the background + tools::Rectangle aLogicRect(rDevice.PixelToLogic(aBackground)); + //tdf#100925, rhbz#1283420, Draw some text here, to get + //X11CairoTextRender::getCairoContext called, so that the forced read + //from the underlying X Drawable gets it to sync. + rDevice.DrawText(aLogicRect.BottomLeft(), " "); + rDevice.DrawRect(aLogicRect); + + tools::Rectangle aEditRect(Point(nScrX, nScrY), Size(aOutputData.GetScrW(), aOutputData.GetScrH())); + pEditView->Paint(rDevice.PixelToLogic(aEditRect), &rDevice); + } + + rDevice.SetMapMode(MapMode(MapUnit::MapPixel)); + + // restore the cursor it was originally visible + if (bInPlaceVisCursor) + pInPlaceCrsr->Show(); + } + + if (mrViewData.HasEditView(eWhich)) + { + // flush OverlayManager before changing the MapMode + flushOverlayManager(); + + // set MapMode for text edit + rDevice.SetMapMode(mrViewData.GetLogicMode()); + } + else + rDevice.SetMapMode(aDrawMode); + + if (mpNoteMarker) + mpNoteMarker->Draw(); // Above the cursor, in drawing map mode + + if (bPage && bInitialPageBreaks) + SetupInitialPageBreaks(rDoc, nTab); +} + + +void ScGridWindow::SetupInitialPageBreaks(const ScDocument& rDoc, SCTAB nTab) +{ + // tdf#124983, if option LibreOfficeDev Calc/View/Visual Aids/Page breaks + // is enabled, breaks should be visible. If the document is opened the first + // time, the breaks are not calculated yet, so for this initialization + // a timer will be triggered here. + std::set<SCCOL> aColBreaks; + std::set<SCROW> aRowBreaks; + rDoc.GetAllColBreaks(aColBreaks, nTab, true, false); + rDoc.GetAllRowBreaks(aRowBreaks, nTab, true, false); + if (aColBreaks.size() == 0 || aRowBreaks.size() == 0) + { + maShowPageBreaksTimer.SetPriority(TaskPriority::DEFAULT_IDLE); + maShowPageBreaksTimer.Start(); + } + bInitialPageBreaks = false; +} + +namespace +{ + template<typename IndexType> + void lcl_getBoundingRowColumnforTile(ScViewData& rViewData, + tools::Long nTileStartPosPx, tools::Long nTileEndPosPx, + sal_Int32& nTopLeftTileOffset, sal_Int32& nTopLeftTileOrigin, + sal_Int32& nTopLeftTileIndex, sal_Int32& nBottomRightTileIndex) + { + const bool bColumnHeader = std::is_same<IndexType, SCCOL>::value; + + SCTAB nTab = rViewData.GetTabNo(); + + IndexType nStartIndex = -1; + IndexType nEndIndex = -1; + tools::Long nStartPosPx = 0; + tools::Long nEndPosPx = 0; + + ScPositionHelper& rPositionHelper = + bColumnHeader ? rViewData.GetLOKWidthHelper() : rViewData.GetLOKHeightHelper(); + const auto& rStartNearest = rPositionHelper.getNearestByPosition(nTileStartPosPx); + const auto& rEndNearest = rPositionHelper.getNearestByPosition(nTileEndPosPx); + + ScBoundsProvider aBoundsProvider(rViewData, nTab, bColumnHeader); + aBoundsProvider.Compute(rStartNearest, rEndNearest, nTileStartPosPx, nTileEndPosPx); + aBoundsProvider.GetStartIndexAndPosition(nStartIndex, nStartPosPx); ++nStartIndex; + aBoundsProvider.GetEndIndexAndPosition(nEndIndex, nEndPosPx); + + nTopLeftTileOffset = nTileStartPosPx - nStartPosPx; + nTopLeftTileOrigin = nStartPosPx; + nTopLeftTileIndex = nStartIndex; + nBottomRightTileIndex = nEndIndex; + } + + void lcl_RTLAdjustTileColOffset(ScViewData& rViewData, sal_Int32& nTileColOffset, + tools::Long nTileEndPx, sal_Int32 nEndCol, SCTAB nTab, + const ScDocument& rDoc, double fPPTX) + { + auto GetColWidthPx = [&rDoc, nTab, fPPTX](SCCOL nCol) { + const sal_uInt16 nSize = rDoc.GetColWidth(nCol, nTab); + const tools::Long nSizePx = ScViewData::ToPixel(nSize, fPPTX); + return nSizePx; + }; + + ScPositionHelper rHelper = rViewData.GetLOKWidthHelper(); + tools::Long nEndColPos = rHelper.computePosition(nEndCol, GetColWidthPx); + + nTileColOffset += (nEndColPos - nTileEndPx - nTileColOffset); + } + + class ScLOKProxyObjectContact final : public sdr::contact::ObjectContactOfPageView + { + private: + ScDrawView* mpScDrawView; + + public: + explicit ScLOKProxyObjectContact( + ScDrawView* pDrawView, + SdrPageWindow& rPageWindow, + const char* pDebugName) : + ObjectContactOfPageView(rPageWindow, pDebugName), + mpScDrawView(pDrawView) + { + } + + virtual bool supportsGridOffsets() const override { return true; } + + virtual void calculateGridOffsetForViewObjectContact( + basegfx::B2DVector& rTarget, + const sdr::contact::ViewObjectContact& rClient) const override + { + if (!mpScDrawView) + return; + + SdrPageView* pPageView(mpScDrawView->GetSdrPageView()); + if (!pPageView) + return; + + SdrPageWindow* pSdrPageWindow = nullptr; + if (pPageView->PageWindowCount() > 0) + pSdrPageWindow = pPageView->GetPageWindow(0); + if (!pSdrPageWindow) + return; + + sdr::contact::ObjectContact& rObjContact(pSdrPageWindow->GetObjectContact()); + + SdrObject* pTargetSdrObject(rClient.GetViewContact().TryToGetSdrObject()); + if (pTargetSdrObject) + rTarget = pTargetSdrObject->GetViewContact().GetViewObjectContact(rObjContact).getGridOffset(); + } + }; + + class ScLOKDrawView : public FmFormView + { + public: + ScLOKDrawView(OutputDevice* pOut, ScViewData& rData) : + FmFormView(*rData.GetDocument().GetDrawLayer(), pOut), + mpScDrawView(rData.GetScDrawView()) + { + } + + virtual sdr::contact::ObjectContact* createViewSpecificObjectContact( + SdrPageWindow& rPageWindow, const char* pDebugName) const override + { + if (!mpScDrawView) + return SdrView::createViewSpecificObjectContact(rPageWindow, pDebugName); + + return new ScLOKProxyObjectContact(mpScDrawView, rPageWindow, pDebugName); + } + + private: + ScDrawView* mpScDrawView; + }; +} // anonymous namespace + +void ScGridWindow::PaintTile( VirtualDevice& rDevice, + int nOutputWidth, int nOutputHeight, + int nTilePosX, int nTilePosY, + tools::Long nTileWidth, tools::Long nTileHeight, + SCCOL nTiledRenderingAreaEndCol, SCROW nTiledRenderingAreaEndRow ) +{ + Fraction origZoomX = mrViewData.GetZoomX(); + Fraction origZoomY = mrViewData.GetZoomY(); + + // Output size is in pixels while tile position and size are in logical units (twips). + + // Assumption: always paint the whole sheet i.e. "visible" range is always + // from (0,0) to last data position. + + // Tile geometry is independent of the zoom level, but the output size is + // dependent of the zoom level. Determine the correct zoom level before + // we start. + + // FIXME the painting works using a mixture of drawing with coordinates in + // pixels and in logic coordinates; it should be cleaned up to use logic + // coords only, and avoid all the SetMapMode()'s. + // Similarly to Writer, we should set the mapmode once on the rDevice, and + // not care about any zoom settings. + + Fraction aFracX(o3tl::convert(nOutputWidth, o3tl::Length::px, o3tl::Length::twip), nTileWidth); + Fraction aFracY(o3tl::convert(nOutputHeight, o3tl::Length::px, o3tl::Length::twip), nTileHeight); + + const bool bChangeZoom = (aFracX != origZoomX || aFracY != origZoomY); + + // page break zoom, and aLogicMode in ScViewData + // FIXME: there are issues when SetZoom is called conditionally. + mrViewData.SetZoom(aFracX, aFracY, true); + if (bChangeZoom) + { + if (ScDrawView* pDrawView = mrViewData.GetScDrawView()) + pDrawView->resetGridOffsetsForAllSdrPageViews(); + } + + const double fTilePosXPixel = static_cast<double>(nTilePosX) * nOutputWidth / nTileWidth; + const double fTilePosYPixel = static_cast<double>(nTilePosY) * nOutputHeight / nTileHeight; + const double fTileBottomPixel = static_cast<double>(nTilePosY + nTileHeight) * nOutputHeight / nTileHeight; + const double fTileRightPixel = static_cast<double>(nTilePosX + nTileWidth) * nOutputWidth / nTileWidth; + + SCTAB nTab = mrViewData.GetTabNo(); + ScDocument& rDoc = mrViewData.GetDocument(); + + const double fPPTX = mrViewData.GetPPTX(); + const double fPPTY = mrViewData.GetPPTY(); + + // find approximate col/row offsets of nearby. + sal_Int32 nTopLeftTileRowOffset = 0; + sal_Int32 nTopLeftTileColOffset = 0; + sal_Int32 nTopLeftTileRowOrigin = 0; + sal_Int32 nTopLeftTileColOrigin = 0; + + sal_Int32 nTopLeftTileRow = 0; + sal_Int32 nTopLeftTileCol = 0; + sal_Int32 nBottomRightTileRow = 0; + sal_Int32 nBottomRightTileCol = 0; + + lcl_getBoundingRowColumnforTile<SCROW>(mrViewData, + fTilePosYPixel, fTileBottomPixel, + nTopLeftTileRowOffset, nTopLeftTileRowOrigin, + nTopLeftTileRow, nBottomRightTileRow); + + lcl_getBoundingRowColumnforTile<SCCOL>(mrViewData, + fTilePosXPixel, fTileRightPixel, + nTopLeftTileColOffset, nTopLeftTileColOrigin, + nTopLeftTileCol, nBottomRightTileCol); + + // Enlarge + nBottomRightTileCol++; + nBottomRightTileRow++; + + if (nBottomRightTileCol > rDoc.MaxCol()) + nBottomRightTileCol = rDoc.MaxCol(); + + if (nBottomRightTileRow > MAXTILEDROW) + nBottomRightTileRow = MAXTILEDROW; + + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + + if (bLayoutRTL) + { + lcl_RTLAdjustTileColOffset(mrViewData, nTopLeftTileColOffset, + fTileRightPixel, nBottomRightTileCol, nTab, + rDoc, fPPTX); + } + + // size of the document including drawings, charts, etc. + SCCOL nEndCol = nTiledRenderingAreaEndCol; + SCROW nEndRow = nTiledRenderingAreaEndRow; + + if (nEndCol < nBottomRightTileCol) + nEndCol = nBottomRightTileCol; + + if (nEndRow < nBottomRightTileRow) + nEndRow = nBottomRightTileRow; + + nTopLeftTileCol = std::max<sal_Int32>(nTopLeftTileCol, 0); + nTopLeftTileRow = std::max<sal_Int32>(nTopLeftTileRow, 0); + nTopLeftTileColOrigin = o3tl::convert(nTopLeftTileColOrigin, o3tl::Length::px, o3tl::Length::twip); + nTopLeftTileRowOrigin = o3tl::convert(nTopLeftTileRowOrigin, o3tl::Length::px, o3tl::Length::twip); + + // Checkout -> 'rDoc.ExtendMerge' ... if we miss merged cells. + + // Origin must be the offset of the first col and row + // containing our top-left pixel. + const MapMode aOriginalMode = rDevice.GetMapMode(); + MapMode aAbsMode = aOriginalMode; + const Point aOrigin(-nTopLeftTileColOrigin, -nTopLeftTileRowOrigin); + aAbsMode.SetOrigin(aOrigin); + rDevice.SetMapMode(aAbsMode); + + ScTableInfo aTabInfo(nEndRow + 3); + rDoc.FillInfo(aTabInfo, nTopLeftTileCol, nTopLeftTileRow, + nBottomRightTileCol, nBottomRightTileRow, + nTab, fPPTX, fPPTY, false, false); + +// FIXME: is this called some +// Point aScrPos = mrViewData.GetScrPos( nX1, nY1, eWhich ); + + ScOutputData aOutputData(&rDevice, OUTTYPE_WINDOW, aTabInfo, &rDoc, nTab, + -nTopLeftTileColOffset, + -nTopLeftTileRowOffset, + nTopLeftTileCol, nTopLeftTileRow, + nBottomRightTileCol, nBottomRightTileRow, + fPPTX, fPPTY, nullptr, nullptr); + + // setup the SdrPage so that drawinglayer works correctly + ScDrawLayer* pModel = rDoc.GetDrawLayer(); + if (pModel) + { + bool bPrintTwipsMsgs = comphelper::LibreOfficeKit::isCompatFlagSet( + comphelper::LibreOfficeKit::Compat::scPrintTwipsMsgs); + if (!mpLOKDrawView) + { + mpLOKDrawView.reset(bPrintTwipsMsgs ? + new ScLOKDrawView( + &rDevice, + mrViewData) : + new FmFormView( + *pModel, + &rDevice)); + } + + mpLOKDrawView->SetNegativeX(bLayoutRTL); + mpLOKDrawView->ShowSdrPage(mpLOKDrawView->GetModel().GetPage(nTab)); + aOutputData.SetDrawView(mpLOKDrawView.get()); + aOutputData.SetSpellCheckContext(mpSpellCheckCxt.get()); + } + + // draw the content + DrawContent(rDevice, aTabInfo, aOutputData, true); + rDevice.SetMapMode(aOriginalMode); + + // Paint the chart(s) in edit mode. + LokChartHelper::PaintAllChartsOnTile(rDevice, nOutputWidth, nOutputHeight, + nTilePosX, nTilePosY, nTileWidth, nTileHeight, bLayoutRTL); + + rDevice.SetMapMode(aOriginalMode); + + // Flag drawn formula cells "unchanged". + rDoc.ResetChanged(ScRange(nTopLeftTileCol, nTopLeftTileRow, nTab, nBottomRightTileCol, nBottomRightTileRow, nTab)); + rDoc.PrepareFormulaCalc(); + + mrViewData.SetZoom(origZoomX, origZoomY, true); + if (bChangeZoom) + { + if (ScDrawView* pDrawView = mrViewData.GetScDrawView()) + pDrawView->resetGridOffsetsForAllSdrPageViews(); + } + + if (bLayoutRTL) + { + Bitmap aCellBMP = rDevice.GetBitmap(Point(0, 0), Size(nOutputWidth, nOutputHeight)); + aCellBMP.Mirror(BmpMirrorFlags::Horizontal); + rDevice.DrawBitmap(Point(0, 0), Size(nOutputWidth, nOutputHeight), aCellBMP); + } +} + +void ScGridWindow::LogicInvalidatePart(const tools::Rectangle* pRectangle, int nPart) +{ + tools::Rectangle aRectangle; + tools::Rectangle* pResultRectangle; + if (!pRectangle) + pResultRectangle = nullptr; + else + { + aRectangle = *pRectangle; + // When dragging shapes the map mode is disabled. + if (IsMapModeEnabled()) + { + if (GetMapMode().GetMapUnit() == MapUnit::Map100thMM) + { + aRectangle = o3tl::convert(aRectangle, o3tl::Length::mm100, o3tl::Length::twip); + } + } + else + aRectangle = PixelToLogic(aRectangle, MapMode(MapUnit::MapTwip)); + pResultRectangle = &aRectangle; + } + + // Trim invalidation rectangle overlapping negative X region in RTL mode. + if (pResultRectangle && pResultRectangle->Left() < 0 + && mrViewData.GetDocument().IsLayoutRTL(mrViewData.GetTabNo())) + { + pResultRectangle->SetLeft(0); + if (pResultRectangle->Right() < 0) + pResultRectangle->SetRight(0); + } + + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + SfxLokHelper::notifyInvalidation(pViewShell, nPart, pResultRectangle); +} + +void ScGridWindow::LogicInvalidate(const tools::Rectangle* pRectangle) +{ + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + LogicInvalidatePart(pRectangle, pViewShell->getPart()); +} + +void ScGridWindow::SetCellSelectionPixel(int nType, int nPixelX, int nPixelY) +{ + ScTabView* pTabView = mrViewData.GetView(); + ScTabViewShell* pViewShell = mrViewData.GetViewShell(); + ScInputHandler* pInputHandler = SC_MOD()->GetInputHdl(pViewShell); + + if (pInputHandler && pInputHandler->IsInputMode()) + { + // we need to switch off the editeng + ScTabView::UpdateInputLine(); + pViewShell->UpdateInputHandler(); + } + + if (nType == LOK_SETTEXTSELECTION_RESET) + { + pTabView->DoneBlockMode(); + return; + } + + // obtain the current selection + ScRangeList aRangeList = mrViewData.GetMarkData().GetMarkedRanges(); + + SCCOL nCol1, nCol2; + SCROW nRow1, nRow2; + SCTAB nTab1, nTab2; + + bool bWasEmpty = false; + if (aRangeList.empty()) + { + nCol1 = nCol2 = mrViewData.GetCurX(); + nRow1 = nRow2 = mrViewData.GetCurY(); + bWasEmpty = true; + } + else + aRangeList.Combine().GetVars(nCol1, nRow1, nTab1, nCol2, nRow2, nTab2); + + // convert the coordinates to column/row + SCCOL nNewPosX; + SCROW nNewPosY; + SCTAB nTab = mrViewData.GetTabNo(); + mrViewData.GetPosFromPixel(nPixelX, nPixelY, eWhich, nNewPosX, nNewPosY); + + // change the selection + switch (nType) + { + case LOK_SETTEXTSELECTION_START: + if (nNewPosX != nCol1 || nNewPosY != nRow1 || bWasEmpty) + { + pTabView->SetCursor(nNewPosX, nNewPosY); + pTabView->DoneBlockMode(); + pTabView->InitBlockMode(nNewPosX, nNewPosY, nTab, true); + pTabView->MarkCursor(nCol2, nRow2, nTab); + } + break; + case LOK_SETTEXTSELECTION_END: + if (nNewPosX != nCol2 || nNewPosY != nRow2 || bWasEmpty) + { + pTabView->SetCursor(nCol1, nRow1); + pTabView->DoneBlockMode(); + pTabView->InitBlockMode(nCol1, nRow1, nTab, true); + pTabView->MarkCursor(nNewPosX, nNewPosY, nTab); + } + break; + default: + assert(false); + break; + } +} + +void ScGridWindow::CheckNeedsRepaint() +{ + // called at the end of painting, and from timer after background text width calculation + + if (!bNeedsRepaint) + return; + + bNeedsRepaint = false; + if (aRepaintPixel.IsEmpty()) + Invalidate(); + else + Invalidate(PixelToLogic(aRepaintPixel)); + aRepaintPixel = tools::Rectangle(); + + // selection function in status bar might also be invalid + SfxBindings& rBindings = mrViewData.GetBindings(); + rBindings.Invalidate( SID_STATUS_SUM ); + rBindings.Invalidate( SID_ATTR_SIZE ); + rBindings.Invalidate( SID_TABLE_CELL ); +} + +void ScGridWindow::DrawHiddenIndicator( SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2, vcl::RenderContext& rRenderContext) +{ + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + const svtools::ColorConfig& rColorCfg = SC_MOD()->GetColorConfig(); + const svtools::ColorConfigValue aColorValue = rColorCfg.GetColorValue(svtools::CALCHIDDENROWCOL); + if (aColorValue.bIsVisible) { + rRenderContext.SetLineColor(aColorValue.nColor); + LineInfo aLineInfo(LineStyle::Dash, 2); + aLineInfo.SetDashCount(0); + aLineInfo.SetDotCount(1); + aLineInfo.SetDistance(15); + // round caps except when running VCL_PLUGIN=gen due to a performance issue + // https://bugs.documentfoundation.org/show_bug.cgi?id=128258#c14 + if (mrViewData.GetActiveWin()->GetSystemData()->toolkit != SystemEnvData::Toolkit::Gen) + aLineInfo.SetLineCap(css::drawing::LineCap_ROUND); + aLineInfo.SetDotLen(1); + for (int i=nX1; i<nX2; i++) { + if (rDoc.ColHidden(i,nTab) && (i<rDoc.MaxCol() ? !rDoc.ColHidden(i+1,nTab) : true)) { + Point aStart = mrViewData.GetScrPos(i, nY1, eWhich, true ); + Point aEnd = mrViewData.GetScrPos(i, nY2, eWhich, true ); + rRenderContext.DrawLine(aStart,aEnd,aLineInfo); + } + } + for (int i=nY1; i<nY2; i++) { + if (rDoc.RowHidden(i,nTab) && (i<rDoc.MaxRow() ? !rDoc.RowHidden(i+1,nTab) : true)) { + Point aStart = mrViewData.GetScrPos(nX1, i, eWhich, true ); + Point aEnd = mrViewData.GetScrPos(nX2, i, eWhich, true ); + rRenderContext.DrawLine(aStart,aEnd,aLineInfo); + } + } + } //visible +} + +void ScGridWindow::DrawPagePreview( SCCOL nX1, SCROW nY1, SCCOL nX2, SCROW nY2, vcl::RenderContext& rRenderContext) +{ + ScPageBreakData* pPageData = mrViewData.GetView()->GetPageBreakData(); + if (!pPageData) + return; + + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + Size aWinSize = GetOutputSizePixel(); + const svtools::ColorConfig& rColorCfg = SC_MOD()->GetColorConfig(); + Color aManual( rColorCfg.GetColorValue(svtools::CALCPAGEBREAKMANUAL).nColor ); + Color aAutomatic( rColorCfg.GetColorValue(svtools::CALCPAGEBREAK).nColor ); + + OUString aPageStr = ScResId( STR_PGNUM ); + if ( nPageScript == SvtScriptType::NONE ) + { + // get script type of translated "Page" string only once + nPageScript = rDoc.GetStringScriptType( aPageStr ); + if (nPageScript == SvtScriptType::NONE) + nPageScript = ScGlobal::GetDefaultScriptType(); + } + + vcl::Font aFont; + std::unique_ptr<ScEditEngineDefaulter> pEditEng; + const ScPatternAttr& rDefPattern = rDoc.GetPool()->GetDefaultItem(ATTR_PATTERN); + if ( nPageScript == SvtScriptType::LATIN ) + { + // use single font and call DrawText directly + rDefPattern.fillFontOnly(aFont); + aFont.SetColor(COL_LIGHTGRAY); + // font size is set as needed + } + else + { + // use EditEngine to draw mixed-script string + pEditEng.reset(new ScEditEngineDefaulter( EditEngine::CreatePool().get(), true )); + pEditEng->SetRefMapMode(rRenderContext.GetMapMode()); + auto pEditDefaults = std::make_unique<SfxItemSet>( pEditEng->GetEmptyItemSet() ); + rDefPattern.FillEditItemSet( pEditDefaults.get() ); + pEditDefaults->Put( SvxColorItem( COL_LIGHTGRAY, EE_CHAR_COLOR ) ); + pEditEng->SetDefaults( std::move(pEditDefaults) ); + } + + sal_uInt16 nCount = sal::static_int_cast<sal_uInt16>( pPageData->GetCount() ); + for (sal_uInt16 nPos=0; nPos<nCount; nPos++) + { + ScPrintRangeData& rData = pPageData->GetData(nPos); + ScRange aRange = rData.GetPrintRange(); + if ( aRange.aStart.Col() <= nX2+1 && aRange.aEnd.Col()+1 >= nX1 && + aRange.aStart.Row() <= nY2+1 && aRange.aEnd.Row()+1 >= nY1 ) + { + // 3 pixel frame around the print area + // (middle pixel on the grid lines) + + rRenderContext.SetLineColor(); + if (rData.IsAutomatic()) + rRenderContext.SetFillColor( aAutomatic ); + else + rRenderContext.SetFillColor( aManual ); + + Point aStart = mrViewData.GetScrPos( + aRange.aStart.Col(), aRange.aStart.Row(), eWhich, true ); + Point aEnd = mrViewData.GetScrPos( + aRange.aEnd.Col() + 1, aRange.aEnd.Row() + 1, eWhich, true ); + aStart.AdjustX( -2 ); + aStart.AdjustY( -2 ); + + // Prevent overflows: + if ( aStart.X() < -10 ) aStart.setX( -10 ); + if ( aStart.Y() < -10 ) aStart.setY( -10 ); + if ( aEnd.X() > aWinSize.Width() + 10 ) + aEnd.setX( aWinSize.Width() + 10 ); + if ( aEnd.Y() > aWinSize.Height() + 10 ) + aEnd.setY( aWinSize.Height() + 10 ); + + rRenderContext.DrawRect( tools::Rectangle( aStart, Point(aEnd.X(),aStart.Y()+2) ) ); + rRenderContext.DrawRect( tools::Rectangle( aStart, Point(aStart.X()+2,aEnd.Y()) ) ); + rRenderContext.DrawRect( tools::Rectangle( Point(aStart.X(),aEnd.Y()-2), aEnd ) ); + rRenderContext.DrawRect( tools::Rectangle( Point(aEnd.X()-2,aStart.Y()), aEnd ) ); + + // Page breaks + //! Display differently (dashed ????) + + size_t nColBreaks = rData.GetPagesX(); + const SCCOL* pColEnd = rData.GetPageEndX(); + size_t nColPos; + for (nColPos=0; nColPos+1<nColBreaks; nColPos++) + { + SCCOL nBreak = pColEnd[nColPos]+1; + if ( nBreak >= nX1 && nBreak <= nX2+1 ) + { + //! Search for hidden + if (rDoc.HasColBreak(nBreak, nTab) & ScBreakType::Manual) + rRenderContext.SetFillColor( aManual ); + else + rRenderContext.SetFillColor( aAutomatic ); + Point aBreak = mrViewData.GetScrPos( + nBreak, aRange.aStart.Row(), eWhich, true ); + rRenderContext.DrawRect( tools::Rectangle( aBreak.X()-1, aStart.Y(), aBreak.X(), aEnd.Y() ) ); + } + } + + size_t nRowBreaks = rData.GetPagesY(); + const SCROW* pRowEnd = rData.GetPageEndY(); + size_t nRowPos; + for (nRowPos=0; nRowPos+1<nRowBreaks; nRowPos++) + { + SCROW nBreak = pRowEnd[nRowPos]+1; + if ( nBreak >= nY1 && nBreak <= nY2+1 ) + { + //! Search for hidden + if (rDoc.HasRowBreak(nBreak, nTab) & ScBreakType::Manual) + rRenderContext.SetFillColor( aManual ); + else + rRenderContext.SetFillColor( aAutomatic ); + Point aBreak = mrViewData.GetScrPos( + aRange.aStart.Col(), nBreak, eWhich, true ); + rRenderContext.DrawRect( tools::Rectangle( aStart.X(), aBreak.Y()-1, aEnd.X(), aBreak.Y() ) ); + } + } + + // Page numbers + + SCROW nPrStartY = aRange.aStart.Row(); + for (nRowPos=0; nRowPos<nRowBreaks; nRowPos++) + { + SCROW nPrEndY = pRowEnd[nRowPos]; + if ( nPrEndY >= nY1 && nPrStartY <= nY2 ) + { + SCCOL nPrStartX = aRange.aStart.Col(); + for (nColPos=0; nColPos<nColBreaks; nColPos++) + { + SCCOL nPrEndX = pColEnd[nColPos]; + if ( nPrEndX >= nX1 && nPrStartX <= nX2 ) + { + Point aPageStart = mrViewData.GetScrPos( + nPrStartX, nPrStartY, eWhich, true ); + Point aPageEnd = mrViewData.GetScrPos( + nPrEndX+1,nPrEndY+1, eWhich, true ); + + tools::Long nPageNo = rData.GetFirstPage(); + if ( rData.IsTopDown() ) + nPageNo += static_cast<tools::Long>(nColPos)*nRowBreaks+nRowPos; + else + nPageNo += static_cast<tools::Long>(nRowPos)*nColBreaks+nColPos; + + OUString aThisPageStr = aPageStr.replaceFirst("%1", OUString::number(nPageNo)); + + if ( pEditEng ) + { + // find right font size with EditEngine + tools::Long nHeight = 100; + pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT ) ); + pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT_CJK ) ); + pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT_CTL ) ); + pEditEng->SetTextCurrentDefaults( aThisPageStr ); + Size aSize100( pEditEng->CalcTextWidth(), pEditEng->GetTextHeight() ); + + // 40% of width or 60% of height + tools::Long nSizeX = 40 * ( aPageEnd.X() - aPageStart.X() ) / aSize100.Width(); + tools::Long nSizeY = 60 * ( aPageEnd.Y() - aPageStart.Y() ) / aSize100.Height(); + nHeight = std::min(nSizeX,nSizeY); + pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT ) ); + pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT_CJK ) ); + pEditEng->SetDefaultItem( SvxFontHeightItem( nHeight, 100, EE_CHAR_FONTHEIGHT_CTL ) ); + + // centered output with EditEngine + Size aTextSize( pEditEng->CalcTextWidth(), pEditEng->GetTextHeight() ); + Point aPos( (aPageStart.X()+aPageEnd.X()-aTextSize.Width())/2, + (aPageStart.Y()+aPageEnd.Y()-aTextSize.Height())/2 ); + pEditEng->Draw(rRenderContext, aPos); + } + else + { + // find right font size for DrawText + aFont.SetFontSize( Size( 0,100 ) ); + rRenderContext.SetFont( aFont ); + + Size aSize100(rRenderContext.GetTextWidth( aThisPageStr ), rRenderContext.GetTextHeight() ); + if (aSize100.Width() && aSize100.Height()) + { + // 40% of width or 60% of height + tools::Long nSizeX = 40 * ( aPageEnd.X() - aPageStart.X() ) / aSize100.Width(); + tools::Long nSizeY = 60 * ( aPageEnd.Y() - aPageStart.Y() ) / aSize100.Height(); + aFont.SetFontSize( Size( 0,std::min(nSizeX,nSizeY) ) ); + rRenderContext.SetFont( aFont ); + } + + // centered output with DrawText + Size aTextSize(rRenderContext.GetTextWidth( aThisPageStr ), rRenderContext.GetTextHeight() ); + Point aPos( (aPageStart.X()+aPageEnd.X()-aTextSize.Width())/2, + (aPageStart.Y()+aPageEnd.Y()-aTextSize.Height())/2 ); + rRenderContext.DrawText( aPos, aThisPageStr ); + } + } + nPrStartX = nPrEndX + 1; + } + } + nPrStartY = nPrEndY + 1; + } + } + } +} + +void ScGridWindow::DrawButtons(SCCOL nX1, SCCOL nX2, const ScTableInfo& rTabInfo, OutputDevice* pContentDev, const ScLokRTLContext* pLokRTLContext) +{ + aComboButton.SetOutputDevice( pContentDev ); + + ScDocument& rDoc = mrViewData.GetDocument(); + ScDPFieldButton aCellBtn(pContentDev, &GetSettings().GetStyleSettings(), &mrViewData.GetZoomY(), &rDoc); + + SCCOL nCol; + SCROW nRow; + SCSIZE nArrY; + SCSIZE nQuery; + SCTAB nTab = mrViewData.GetTabNo(); + ScDBData* pDBData = nullptr; + std::unique_ptr<ScQueryParam> pQueryParam; + + RowInfo* pRowInfo = rTabInfo.mpRowInfo.get(); + sal_uInt16 nArrCount = rTabInfo.mnArrCount; + + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + + Point aOldPos = aComboButton.GetPosPixel(); // store state for MouseDown/Up + Size aOldSize = aComboButton.GetSizePixel(); + + for (nArrY=1; nArrY+1<nArrCount; nArrY++) + { + if ( pRowInfo[nArrY].bAutoFilter && pRowInfo[nArrY].bChanged ) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + + nRow = pThisRowInfo->nRowNo; + + for (nCol=nX1; nCol<=nX2; nCol++) + { + ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nCol); + //if several columns merged on a row, there should be only one auto button at the end of the columns. + //if several rows merged on a column, the button may be in the middle, so "!pInfo->bVOverlapped" should not be used + if ( pInfo->bAutoFilter && !pInfo->bHOverlapped ) + { + if (!pQueryParam) + pQueryParam.reset(new ScQueryParam); + + bool bNewData = true; + if (pDBData) + { + SCCOL nStartCol; + SCROW nStartRow; + SCCOL nEndCol; + SCROW nEndRow; + SCTAB nAreaTab; + pDBData->GetArea( nAreaTab, nStartCol, nStartRow, nEndCol, nEndRow ); + if ( nCol >= nStartCol && nCol <= nEndCol && + nRow >= nStartRow && nRow <= nEndRow ) + bNewData = false; + } + if (bNewData) + { + pDBData = rDoc.GetDBAtCursor( nCol, nRow, nTab, ScDBDataPortion::AREA ); + if (pDBData) + pDBData->GetQueryParam( *pQueryParam ); + else + { + // can also be part of DataPilot table + } + } + + // pQueryParam can only include MAXQUERY entries + + bool bArrowState = false; + if (pQueryParam->bInplace) + { + SCSIZE nCount = pQueryParam->GetEntryCount(); + for (nQuery = 0; nQuery < nCount; ++nQuery) + { + // Do no restrict to EQUAL here + // (Column head should become blue also when ">1") + const ScQueryEntry& rEntry = pQueryParam->GetEntry(nQuery); + if (rEntry.bDoQuery && rEntry.nField == nCol) + { + bArrowState = true; + break; + } + } + } + + tools::Long nSizeX; + tools::Long nSizeY; + SCCOL nStartCol= nCol; + SCROW nStartRow = nRow; + //if address(nCol,nRow) is not the start pos of the merge area, the value of the nSizeX will be incorrect, it will be the length of the cell. + //should first get the start pos of the merge area, then get the nSizeX through the start pos. + rDoc.ExtendOverlapped(nStartCol, nStartRow,nCol, nRow, nTab);//get nStartCol,nStartRow + mrViewData.GetMergeSizePixel( nStartCol, nStartRow, nSizeX, nSizeY );//get nSizeX + nSizeY = ScViewData::ToPixel(rDoc.GetRowHeight(nRow, nTab), mrViewData.GetPPTY()); + Point aScrPos = mrViewData.GetScrPos( nCol, nRow, eWhich ); + if (pLokRTLContext) + aScrPos.setX(pLokRTLContext->docToTilePos(aScrPos.X())); + + aCellBtn.setBoundingBox(aScrPos, Size(nSizeX-1, nSizeY-1), bLayoutRTL); + aCellBtn.setPopupLeft(bLayoutRTL); // #i114944# AutoFilter button is left-aligned in RTL + aCellBtn.setDrawBaseButton(false); + aCellBtn.setDrawPopupButton(true); + aCellBtn.setHasHiddenMember(bArrowState); + aCellBtn.draw(); + } + } + } + + if ( (pRowInfo[nArrY].bPivotToggle || pRowInfo[nArrY].bPivotButton) && pRowInfo[nArrY].bChanged ) + { + RowInfo* pThisRowInfo = &pRowInfo[nArrY]; + nRow = pThisRowInfo->nRowNo; + for (nCol=nX1; nCol<=nX2; nCol++) + { + ScCellInfo* pInfo = &pThisRowInfo->cellInfo(nCol); + if (pInfo->bHOverlapped || pInfo->bVOverlapped) + continue; + + Point aScrPos = mrViewData.GetScrPos( nCol, nRow, eWhich ); + tools::Long nSizeX; + tools::Long nSizeY; + mrViewData.GetMergeSizePixel( nCol, nRow, nSizeX, nSizeY ); + tools::Long nPosX = aScrPos.X(); + tools::Long nPosY = aScrPos.Y(); + // bLayoutRTL is handled in setBoundingBox + + bool bDrawToggle = pInfo->bPivotCollapseButton || pInfo->bPivotExpandButton; + if (!bDrawToggle) + { + OUString aStr = rDoc.GetString(nCol, nRow, nTab); + aCellBtn.setText(aStr); + } + + sal_uInt16 nIndent = 0; + if (const ScIndentItem* pIndentItem = rDoc.GetAttr(nCol, nRow, nTab, ATTR_INDENT)) + nIndent = pIndentItem->GetValue(); + aCellBtn.setBoundingBox(Point(nPosX, nPosY), Size(nSizeX-1, nSizeY-1), bLayoutRTL); + aCellBtn.setPopupLeft(false); // DataPilot popup is always right-aligned for now + aCellBtn.setDrawBaseButton(pInfo->bPivotButton); + aCellBtn.setDrawPopupButton(pInfo->bPivotPopupButton); + aCellBtn.setDrawPopupButtonMulti(pInfo->bPivotPopupButtonMulti); + aCellBtn.setDrawToggleButton(bDrawToggle, pInfo->bPivotCollapseButton, nIndent); + aCellBtn.setHasHiddenMember(pInfo->bFilterActive); + aCellBtn.draw(); + } + } + + if ( !comphelper::LibreOfficeKit::isActive() && bListValButton && pRowInfo[nArrY].nRowNo == aListValPos.Row() && pRowInfo[nArrY].bChanged ) + { + tools::Rectangle aRect = GetListValButtonRect( aListValPos ); + aComboButton.SetPosPixel( aRect.TopLeft() ); + aComboButton.SetSizePixel( aRect.GetSize() ); + pContentDev->SetClipRegion(vcl::Region(aRect)); + aComboButton.Draw(); + pContentDev->SetClipRegion(); // always called from Draw() without clip region + aComboButton.SetPosPixel( aOldPos ); // restore old state + aComboButton.SetSizePixel( aOldSize ); // for MouseUp/Down (AutoFilter) + } + } + + pQueryParam.reset(); + aComboButton.SetOutputDevice( GetOutDev() ); +} + +tools::Rectangle ScGridWindow::GetListValButtonRect( const ScAddress& rButtonPos ) +{ + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; + + ScDDComboBoxButton aButton( GetOutDev() ); // for optimal size + Size aBtnSize = aButton.GetSizePixel(); + + SCCOL nCol = rButtonPos.Col(); + SCROW nRow = rButtonPos.Row(); + + tools::Long nCellSizeX; // width of this cell, including merged + tools::Long nDummy; + mrViewData.GetMergeSizePixel( nCol, nRow, nCellSizeX, nDummy ); + + // for height, only the cell's row is used, excluding merged cells + tools::Long nCellSizeY = ScViewData::ToPixel( rDoc.GetRowHeight( nRow, nTab ), mrViewData.GetPPTY() ); + tools::Long nAvailable = nCellSizeX; + + // left edge of next cell if there is a non-hidden next column + SCCOL nNextCol = nCol + 1; + const ScMergeAttr* pMerge = rDoc.GetAttr( nCol,nRow,nTab, ATTR_MERGE ); + if ( pMerge->GetColMerge() > 1 ) + nNextCol = nCol + pMerge->GetColMerge(); // next cell after the merged area + while ( nNextCol <= rDoc.MaxCol() && rDoc.ColHidden(nNextCol, nTab) ) + ++nNextCol; + bool bNextCell = ( nNextCol <= rDoc.MaxCol() ); + if ( bNextCell ) + nAvailable = ScViewData::ToPixel( rDoc.GetColWidth( nNextCol, nTab ), mrViewData.GetPPTX() ); + + if ( nAvailable < aBtnSize.Width() ) + aBtnSize.setWidth( nAvailable ); + if ( nCellSizeY < aBtnSize.Height() ) + aBtnSize.setHeight( nCellSizeY ); + + Point aPos = mrViewData.GetScrPos( nCol, nRow, eWhich, true ); + aPos.AdjustX(nCellSizeX * nLayoutSign ); // start of next cell + if (!bNextCell) + aPos.AdjustX( -(aBtnSize.Width() * nLayoutSign) ); // right edge of cell if next cell not available + aPos.AdjustY(nCellSizeY - aBtnSize.Height() ); + // X remains at the left edge + + if ( bLayoutRTL ) + aPos.AdjustX( -(aBtnSize.Width()-1) ); // align right edge of button with cell border + + return tools::Rectangle( aPos, aBtnSize ); +} + +bool ScGridWindow::IsAutoFilterActive( SCCOL nCol, SCROW nRow, SCTAB nTab ) +{ + ScDocument& rDoc = mrViewData.GetDocument(); + ScDBData* pDBData = rDoc.GetDBAtCursor( nCol, nRow, nTab, ScDBDataPortion::AREA ); + ScQueryParam aQueryParam; + + if ( pDBData ) + pDBData->GetQueryParam( aQueryParam ); + else + { + OSL_FAIL("Auto filter button without DBData"); + } + + bool bSimpleQuery = true; + bool bColumnFound = false; + SCSIZE nQuery; + + if ( !aQueryParam.bInplace ) + bSimpleQuery = false; + + // aQueryParam can only include MAXQUERY entries + + SCSIZE nCount = aQueryParam.GetEntryCount(); + for (nQuery = 0; nQuery < nCount && bSimpleQuery; ++nQuery) + if ( aQueryParam.GetEntry(nQuery).bDoQuery ) + { + if (aQueryParam.GetEntry(nQuery).nField == nCol) + bColumnFound = true; + + if (nQuery > 0) + if (aQueryParam.GetEntry(nQuery).eConnect != SC_AND) + bSimpleQuery = false; + } + + return ( bSimpleQuery && bColumnFound ); +} + +void ScGridWindow::GetSelectionRects( ::std::vector< tools::Rectangle >& rPixelRects ) const +{ + GetPixelRectsFor( mrViewData.GetMarkData(), rPixelRects ); +} + +void ScGridWindow::GetSelectionRectsPrintTwips(::std::vector< tools::Rectangle >& rRects) const +{ + GetRectsAnyFor(mrViewData.GetMarkData(), rRects, true); +} + +/// convert rMarkData into pixel rectangles for this view +void ScGridWindow::GetPixelRectsFor( const ScMarkData &rMarkData, + ::std::vector< tools::Rectangle >& rPixelRects ) const +{ + GetRectsAnyFor(rMarkData, rPixelRects, false); +} + +void ScGridWindow::GetRectsAnyFor(const ScMarkData &rMarkData, + ::std::vector< tools::Rectangle >& rRects, + bool bInPrintTwips) const +{ + ScDocument& rDoc = mrViewData.GetDocument(); + SCTAB nTab = mrViewData.GetTabNo(); + double nPPTX = mrViewData.GetPPTX(); + double nPPTY = mrViewData.GetPPTY(); + bool bLayoutRTL = rDoc.IsLayoutRTL( nTab ); + // LOK clients needs exact document coordinates, so don't horizontally mirror them. + tools::Long nLayoutSign = (!comphelper::LibreOfficeKit::isActive() && bLayoutRTL) ? -1 : 1; + + ScMarkData aMultiMark( rMarkData ); + aMultiMark.SetMarking( false ); + + if (!aMultiMark.IsMultiMarked()) + { + // simple range case - simplify calculation + const ScRange& aSimpleRange = aMultiMark.GetMarkArea(); + + aMultiMark.MarkToMulti(); + if ( !aMultiMark.IsMultiMarked() ) + return; + + SCCOL nX1 = aSimpleRange.aStart.Col(); + SCROW nY1 = aSimpleRange.aStart.Row(); + SCCOL nX2 = aSimpleRange.aEnd.Col(); + SCROW nY2 = aSimpleRange.aEnd.Row(); + + PutInOrder( nX1, nX2 ); + PutInOrder( nY1, nY2 ); + + SCCOL nPosX = mrViewData.GetPosX( eHWhich ); + SCROW nPosY = mrViewData.GetPosY( eVWhich ); + // is the selection visible at all? + if (nX2 < nPosX || nY2 < nPosY) + return; + + Point aScrStartPos = bInPrintTwips ? mrViewData.GetPrintTwipsPos(nX1, nY1) : + mrViewData.GetScrPos(nX1, nY1, eWhich); + + tools::Long nStartX = aScrStartPos.X(); + tools::Long nStartY = aScrStartPos.Y(); + + Point aScrEndPos = bInPrintTwips ? mrViewData.GetPrintTwipsPos(nX2, nY2) : + mrViewData.GetScrPos(nX2, nY2, eWhich); + + tools::Long nWidthTwips = rDoc.GetColWidth(nX2, nTab); + const tools::Long nWidth = bInPrintTwips ? + nWidthTwips : ScViewData::ToPixel(nWidthTwips, nPPTX); + tools::Long nEndX = aScrEndPos.X() + (nWidth - 1) * nLayoutSign; + + sal_uInt16 nHeightTwips = rDoc.GetRowHeight( nY2, nTab ); + const tools::Long nHeight = bInPrintTwips ? + nHeightTwips : ScViewData::ToPixel(nHeightTwips, nPPTY); + tools::Long nEndY = aScrEndPos.Y() + nHeight - 1; + + ScInvertMerger aInvert( &rRects ); + aInvert.AddRect( tools::Rectangle( nStartX, nStartY, nEndX, nEndY ) ); + + return; + } + + aMultiMark.MarkToMulti(); + if ( !aMultiMark.IsMultiMarked() ) + return; + const ScRange& aMultiRange = aMultiMark.GetMultiMarkArea(); + SCCOL nX1 = aMultiRange.aStart.Col(); + SCROW nY1 = aMultiRange.aStart.Row(); + SCCOL nX2 = aMultiRange.aEnd.Col(); + SCROW nY2 = aMultiRange.aEnd.Row(); + + PutInOrder( nX1, nX2 ); + PutInOrder( nY1, nY2 ); + + SCCOL nTestX2 = nX2; + SCROW nTestY2 = nY2; + + rDoc.ExtendMerge( nX1,nY1, nTestX2,nTestY2, nTab ); + + SCCOL nPosX = mrViewData.GetPosX( eHWhich ); + SCROW nPosY = mrViewData.GetPosY( eVWhich ); + // is the selection visible at all? + if (nTestX2 < nPosX || nTestY2 < nPosY) + return; + SCCOL nRealX1 = nX1; + if (nX1 < nPosX) + nX1 = nPosX; + if (nY1 < nPosY) + nY1 = nPosY; + + if (!comphelper::LibreOfficeKit::isActive()) + { + // limit the selection to only what is visible on the screen + SCCOL nXRight = nPosX + mrViewData.VisibleCellsX(eHWhich); + if (nXRight > rDoc.MaxCol()) + nXRight = rDoc.MaxCol(); + + SCROW nYBottom = nPosY + mrViewData.VisibleCellsY(eVWhich); + if (nYBottom > rDoc.MaxRow()) + nYBottom = rDoc.MaxRow(); + + // is the selection visible at all? + if (nX1 > nXRight || nY1 > nYBottom) + return; + + if (nX2 > nXRight) + nX2 = nXRight; + if (nY2 > nYBottom) + nY2 = nYBottom; + } + else + { + SCCOL nMaxTiledCol; + SCROW nMaxTiledRow; + rDoc.GetTiledRenderingArea(nTab, nMaxTiledCol, nMaxTiledRow); + + if (nX2 > nMaxTiledCol) + nX2 = nMaxTiledCol; + if (nY2 > nMaxTiledRow) + nY2 = nMaxTiledRow; + } + + ScInvertMerger aInvert( &rRects ); + + Point aScrPos = bInPrintTwips ? mrViewData.GetPrintTwipsPos(nX1, nY1) : + mrViewData.GetScrPos(nX1, nY1, eWhich); + tools::Long nScrY = aScrPos.Y(); + bool bWasHidden = false; + for (SCROW nY=nY1; nY<=nY2; nY++) + { + bool bFirstRow = ( nY == nPosY ); // first visible row? + bool bDoHidden = false; // repeat hidden ? + sal_uInt16 nHeightTwips = rDoc.GetRowHeight( nY,nTab ); + bool bDoRow = ( nHeightTwips != 0 ); + if (bDoRow) + { + if (bWasHidden) // test hidden merge + { + bDoHidden = true; + bDoRow = true; + } + + bWasHidden = false; + } + else + { + bWasHidden = true; + if (nY==nY2) + bDoRow = true; // last cell of the block + } + + if ( bDoRow ) + { + SCCOL nLoopEndX = nX2; + if (nX2 < nX1) // the rest of the merge + { + SCCOL nStartX = nX1; + while ( rDoc.GetAttr(nStartX,nY,nTab,ATTR_MERGE_FLAG)->IsHorOverlapped() ) + --nStartX; + if (nStartX <= nX2) + nLoopEndX = nX1; + } + + const tools::Long nHeight = bInPrintTwips ? + nHeightTwips : ScViewData::ToPixel(nHeightTwips, nPPTY); + tools::Long nEndY = nScrY + nHeight - 1; + tools::Long nScrX = aScrPos.X(); + for (SCCOL nX=nX1; nX<=nLoopEndX; nX++) + { + tools::Long nWidth = rDoc.GetColWidth(nX, nTab); + if (!bInPrintTwips) + nWidth = ScViewData::ToPixel(nWidth, nPPTX); + + if ( nWidth > 0 ) + { + tools::Long nEndX = nScrX + ( nWidth - 1 ) * nLayoutSign; + + SCROW nThisY = nY; + const ScPatternAttr* pPattern = rDoc.GetPattern( nX, nY, nTab ); + const ScMergeFlagAttr* pMergeFlag = &pPattern->GetItem(ATTR_MERGE_FLAG); + if ( pMergeFlag->IsVerOverlapped() && ( bDoHidden || bFirstRow ) ) + { + while ( pMergeFlag->IsVerOverlapped() && nThisY > 0 && + (rDoc.RowHidden(nThisY-1, nTab) || bFirstRow) ) + { + --nThisY; + pPattern = rDoc.GetPattern( nX, nThisY, nTab ); + pMergeFlag = &pPattern->GetItem(ATTR_MERGE_FLAG); + } + } + + // only the rest of the merged is seen ? + SCCOL nThisX = nX; + if ( pMergeFlag->IsHorOverlapped() && nX == nPosX && nX > nRealX1 ) + { + while ( pMergeFlag->IsHorOverlapped() ) + { + --nThisX; + pPattern = rDoc.GetPattern( nThisX, nThisY, nTab ); + pMergeFlag = &pPattern->GetItem(ATTR_MERGE_FLAG); + } + } + + if ( aMultiMark.IsCellMarked( nThisX, nThisY, true ) ) + { + if ( !pMergeFlag->IsOverlapped() ) + { + const ScMergeAttr* pMerge = &pPattern->GetItem(ATTR_MERGE); + if (pMerge->GetColMerge() > 0 || pMerge->GetRowMerge() > 0) + { + const SCCOL nEndColMerge = nThisX + pMerge->GetColMerge(); + const SCROW nEndRowMerge = nThisY + pMerge->GetRowMerge(); + Point aEndPos = bInPrintTwips ? + mrViewData.GetPrintTwipsPos(nEndColMerge, nEndRowMerge) : + mrViewData.GetScrPos(nEndColMerge, nEndRowMerge, eWhich); + if ( aEndPos.X() * nLayoutSign > nScrX * nLayoutSign && aEndPos.Y() > nScrY ) + { + aInvert.AddRect( tools::Rectangle( nScrX,nScrY, + aEndPos.X()-nLayoutSign,aEndPos.Y()-1 ) ); + } + } + else if ( nEndX * nLayoutSign >= nScrX * nLayoutSign && nEndY >= nScrY ) + { + aInvert.AddRect( tools::Rectangle( nScrX,nScrY,nEndX,nEndY ) ); + } + } + } + + nScrX = nEndX + nLayoutSign; + } + } + nScrY = nEndY + 1; + } + } +} + +void ScGridWindow::DataChanged( const DataChangedEvent& rDCEvt ) +{ + Window::DataChanged(rDCEvt); + + if ( !((rDCEvt.GetType() == DataChangedEventType::PRINTER) || + (rDCEvt.GetType() == DataChangedEventType::DISPLAY) || + (rDCEvt.GetType() == DataChangedEventType::FONTS) || + (rDCEvt.GetType() == DataChangedEventType::FONTSUBSTITUTION) || + ((rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE))) ) + return; + + if ( rDCEvt.GetType() == DataChangedEventType::FONTS && eWhich == mrViewData.GetActivePart() ) + mrViewData.GetDocShell()->UpdateFontList(); + + if ( (rDCEvt.GetType() == DataChangedEventType::SETTINGS) && + (rDCEvt.GetFlags() & AllSettingsFlags::STYLE) ) + { + if ( eWhich == mrViewData.GetActivePart() ) // only once for the view + { + ScTabView* pView = mrViewData.GetView(); + + pView->RecalcPPT(); + + // RepeatResize in case scroll bar sizes have changed + pView->RepeatResize(); + pView->UpdateAllOverlays(); + + // invalidate cell attribs in input handler, in case the + // EditEngine BackgroundColor has to be changed + if ( mrViewData.IsActive() ) + { + ScInputHandler* pHdl = SC_MOD()->GetInputHdl(); + if (pHdl) + pHdl->ForgetLastPattern(); + } + } + } + + Invalidate(); +} + +void ScGridWindow::initiatePageBreaks() +{ + bInitialPageBreaks = true; +} + +IMPL_LINK(ScGridWindow, InitiatePageBreaksTimer, Timer*, pTimer, void) +{ + if (pTimer != &maShowPageBreaksTimer) + return; + + const ScViewOptions& rOpts = mrViewData.GetOptions(); + const bool bPage = rOpts.GetOption(VOPT_PAGEBREAKS); + // tdf#124983, if option LibreOfficeDev Calc/View/Visual Aids/Page + // breaks is enabled, breaks should be visible. If the document is + // opened the first time or a tab is activated the first time, the + // breaks are not calculated yet, so this initialization is done here. + if (bPage) + { + const SCTAB nCurrentTab = mrViewData.GetTabNo(); + ScDocument& rDoc = mrViewData.GetDocument(); + const Size aPageSize = rDoc.GetPageSize(nCurrentTab); + // Do not attempt to calculate a page size here if it is empty if + // that involves counting pages. + // An earlier implementation did + // ScPrintFunc(pDocSh, pDocSh->GetPrinter(), nCurrentTab); + // rDoc.SetPageSize(nCurrentTab, rDoc.GetPageSize(nCurrentTab)); + // which resulted in tremendous waiting times after having loaded + // larger documents i.e. imported from CSV, in which UI is entirely + // blocked. All time is spent under ScPrintFunc::CountPages() in + // ScTable::ExtendPrintArea() in the loop that calls + // MaybeAddExtraColumn() to do stuff for each text string content + // cell (each row in each column). Maybe that can be optimized, or + // obtaining page size without that overhead would be possible, but + // as is calling that from here is a no-no so this is a quick + // disable things. + if (!aPageSize.IsEmpty()) + { + ScDocShell* pDocSh = mrViewData.GetDocShell(); + const bool bModified = pDocSh->IsModified(); + // Even setting the same size sets page size valid, so + // UpdatePageBreaks() actually does something. + rDoc.SetPageSize( nCurrentTab, aPageSize); + rDoc.UpdatePageBreaks(nCurrentTab); + pDocSh->PostPaint(0, 0, nCurrentTab, rDoc.MaxCol(), rDoc.MaxRow(), nCurrentTab, PaintPartFlags::Grid); + pDocSh->SetModified(bModified); + } + } +} + + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |