editeng/source/editeng/editview.cxx | 5 editeng/source/editeng/impedit.hxx | 1 editeng/source/editeng/impedit3.cxx | 33 + include/editeng/editview.hxx | 1 sc/source/ui/inc/gridwin.hxx | 1 sc/source/ui/inc/tabview.hxx | 2 sc/source/ui/view/gridwin4.cxx | 38 - sc/source/ui/view/tabview.cxx | 4 sc/source/ui/view/tabview3.cxx | 420 ++++++++++++++++++++-- sc/source/ui/view/viewdata.cxx | 9 svx/inc/sdr/overlay/overlaymanagerbuffered.hxx | 7 svx/source/sdr/overlay/overlaymanagerbuffered.cxx | 40 +- 12 files changed, 470 insertions(+), 91 deletions(-)
New commits: commit f678ec9a65c4d7ae2d4dda4599a06fc4b66a27d8 Author: Armin Le Grand (Collabora) <armin.le.gr...@me.com> AuthorDate: Wed Apr 30 14:16:25 2025 +0200 Commit: Armin Le Grand <armin.le.gr...@me.com> CommitDate: Wed May 7 10:54:30 2025 +0200 CEOO: CellEditOnOverlay Instead of using EditView's Paint directly to Window I changed this now to using the Overlay that is available in all applications. This allows to have less repaints of the Calc View since the Overlay has a copy of the area it is working overlayed. It also allows to get away from one of the last areas where XOR selection was used since EditView/EditEngine have no better way to do this. Selection is now visualized using the OS'es choosen SelectionColor, as in all other apps. I could also get rid of the flush() of the OverlayManager that was needed before and caused some problems (see tdf#165621 and associated tasks). A failing UnitTest showed that OverlayManagerBuffered needs to be aware when no CompleteRedraw was done yet and thus no background data exists and refresh makes no sense. This may happen when there is no redraw/UI e.g. UnitTests, but still an OverlayManager is used and together with a Reschedule (many possible reasons) that refresh might be triggered. Made access to EditView in OverlayObject no longer dependent on ActiveView, that is not always the one with the EditView. Now using a combination of ScTabView and ScSplitPos for accesses. Asserting now if no EditView -> is mandatory. Continuing going through CppunitTest_sc_tiledrendering UTs, one more identified. Have to re-activate EditViewInvalidate rect forwarding since the Invalidate is needed for sc tiled rendering tests, but not for sd tiled rendering tests. Added that to the impl of EditViewCallbacks using LibreOfficeKit::isActive(). Change-Id: I41f5448bfbabfaae4858c7617478771053c2cd77 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/184830 Tested-by: Jenkins Reviewed-by: Armin Le Grand <armin.le.gr...@me.com> diff --git a/editeng/source/editeng/editview.cxx b/editeng/source/editeng/editview.cxx index 4791c5e7c0fb..29f0065ffeda 100644 --- a/editeng/source/editeng/editview.cxx +++ b/editeng/source/editeng/editview.cxx @@ -347,6 +347,11 @@ void EditView::GetSelectionRectangles(std::vector<tools::Rectangle>& rLogicRects return getImpl().GetSelectionRectangles(getImpl().GetEditSelection(), rLogicRects); } +Point EditView::CalculateTextPaintStartPosition() const +{ + return getImpEditEngine().CalculateTextPaintStartPosition(getImpl()); +} + void EditView::Paint( const tools::Rectangle& rRect, OutputDevice* pTargetDevice ) { getImpEditEngine().Paint(&getImpl(), rRect, pTargetDevice); diff --git a/editeng/source/editeng/impedit.hxx b/editeng/source/editeng/impedit.hxx index 86bbb9a6b9da..f725ffbb170b 100644 --- a/editeng/source/editeng/impedit.hxx +++ b/editeng/source/editeng/impedit.hxx @@ -989,6 +989,7 @@ public: void Draw( OutputDevice& rOutDev, const Point& rStartPos, Degree10 nOrientation ); void Draw( OutputDevice& rOutDev, const tools::Rectangle& rOutRect, const Point& rStartDocPos, bool bClip ); void UpdateViews( EditView* pCurView = nullptr ); + Point CalculateTextPaintStartPosition(ImpEditView& rView) const; void Paint( ImpEditView* pView, const tools::Rectangle& rRect, OutputDevice* pTargetDevice ); void Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Point aStartPos, Degree10 nOrientation = 0_deg10, diff --git a/editeng/source/editeng/impedit3.cxx b/editeng/source/editeng/impedit3.cxx index 57f2a1043a96..ba01e1c3584e 100644 --- a/editeng/source/editeng/impedit3.cxx +++ b/editeng/source/editeng/impedit3.cxx @@ -4135,6 +4135,26 @@ void ImpEditEngine::Paint( OutputDevice& rOutDev, tools::Rectangle aClipRect, Po } } +Point ImpEditEngine::CalculateTextPaintStartPosition(ImpEditView& rView) const +{ + Point aStartPos; + + if ( !IsEffectivelyVertical() ) + aStartPos = rView.GetOutputArea().TopLeft(); + else + { + if( IsTopToBottom() ) + aStartPos = rView.GetOutputArea().TopRight(); + else + aStartPos = rView.GetOutputArea().BottomLeft(); + } + + adjustXDirectionAware(aStartPos, -(rView.GetVisDocLeft())); + adjustYDirectionAware(aStartPos, -(rView.GetVisDocTop())); + + return aStartPos; +} + void ImpEditEngine::Paint( ImpEditView* pView, const tools::Rectangle& rRect, OutputDevice* pTargetDevice ) { if ( !IsUpdateLayout() || IsInUndo() ) @@ -4148,18 +4168,7 @@ void ImpEditEngine::Paint( ImpEditView* pView, const tools::Rectangle& rRect, Ou OutputDevice& rTarget = pTargetDevice ? *pTargetDevice : *pView->GetWindow()->GetOutDev(); - Point aStartPos; - if ( !IsEffectivelyVertical() ) - aStartPos = pView->GetOutputArea().TopLeft(); - else - { - if( IsTopToBottom() ) - aStartPos = pView->GetOutputArea().TopRight(); - else - aStartPos = pView->GetOutputArea().BottomLeft(); - } - adjustXDirectionAware(aStartPos, -(pView->GetVisDocLeft())); - adjustYDirectionAware(aStartPos, -(pView->GetVisDocTop())); + const Point aStartPos(CalculateTextPaintStartPosition(*pView)); // If Doc-width < Output Area,Width and not wrapped fields, // the fields usually protrude if > line. diff --git a/include/editeng/editview.hxx b/include/editeng/editview.hxx index 9672506dc9a4..1ac391151a02 100644 --- a/include/editeng/editview.hxx +++ b/include/editeng/editview.hxx @@ -194,6 +194,7 @@ public: bool AddOtherViewWindow( vcl::Window* pWin ); bool RemoveOtherViewWindow( vcl::Window* pWin ); + Point CalculateTextPaintStartPosition() const; void Paint( const tools::Rectangle& rRect, OutputDevice* pTargetDevice = nullptr ); tools::Rectangle GetInvalidateRect() const; SAL_DLLPRIVATE void InvalidateWindow(const tools::Rectangle& rClipRect); diff --git a/sc/source/ui/inc/gridwin.hxx b/sc/source/ui/inc/gridwin.hxx index 4ca1f2337aaa..0e101f641fed 100644 --- a/sc/source/ui/inc/gridwin.hxx +++ b/sc/source/ui/inc/gridwin.hxx @@ -441,7 +441,6 @@ public: /// Draw content of the gridwindow; shared between the desktop and the tiled rendering. void DrawContent(OutputDevice &rDevice, const ScTableInfo& rTableInfo, ScOutputData& aOutputData, bool bLogicText); - void DrawEditView(OutputDevice &rDevice, EditView *pEditView); void CreateAnchorHandle(SdrHdlList& rHdl, const ScAddress& rAddress); diff --git a/sc/source/ui/inc/tabview.hxx b/sc/source/ui/inc/tabview.hxx index 69f87b16f737..2a6827b87b1a 100644 --- a/sc/source/ui/inc/tabview.hxx +++ b/sc/source/ui/inc/tabview.hxx @@ -124,6 +124,7 @@ private: ScHeaderFunctionSet aHdrFunc; std::unique_ptr<ScDrawView> pDrawView; + sdr::overlay::OverlayObjectList maTextEditOverlayGroup; Size aFrameSize; // passed on as for DoResize Point aBorderPos; @@ -529,6 +530,7 @@ public: void MakeEditView( ScEditEngineDefaulter* pEngine, SCCOL nCol, SCROW nRow ); void KillEditView( bool bNoPaint ); void UpdateEditView(); + void RefeshTextEditOverlay(); // Blocks diff --git a/sc/source/ui/view/gridwin4.cxx b/sc/source/ui/view/gridwin4.cxx index 7b7d05a58779..3d9d559e649a 100644 --- a/sc/source/ui/view/gridwin4.cxx +++ b/sc/source/ui/view/gridwin4.cxx @@ -665,43 +665,6 @@ Fraction GetZoom(const ScViewData& rViewData, int i) } } - -void ScGridWindow::DrawEditView(OutputDevice &rDevice, EditView *pEditView) -{ - 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 ); - - // don't overwrite grid - bool bLayoutRTL = mrViewData.GetDocument().IsLayoutRTL(mrViewData.GetTabNo()); - tools::Long nLayoutSign = bLayoutRTL ? -1 : 1; - aEnd.AdjustX( -(2 * nLayoutSign) ); - aEnd.AdjustY( -2 ); - - // set the correct mapmode - tools::Rectangle aBackground(aStart, aEnd); - - // paint the background - rDevice.SetMapMode(mrViewData.GetLogicMode()); - - 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(), u" "_ustr); - rDevice.DrawRect(aLogicRect); - - // paint the editeng text - pEditView->Paint(rDevice.PixelToLogic(aEditRectangle), &rDevice); - rDevice.SetMapMode(MapMode(MapUnit::MapPixel)); -} - void ScGridWindow::DrawContent(OutputDevice &rDevice, const ScTableInfo& rTableInfo, ScOutputData& aOutputData, bool bLogicText) { @@ -1343,7 +1306,6 @@ void ScGridWindow::DrawContent(OutputDevice &rDevice, const ScTableInfo& rTableI if (bInPlaceEditing && !bIsTiledRendering) { aEditRectangle = tools::Rectangle(Point(nScrX, nScrY), Size(aOutputData.GetScrW(), aOutputData.GetScrH())); - DrawEditView(rDevice, pEditView); if (bInPlaceVisCursor) pInPlaceCrsr->Show(); diff --git a/sc/source/ui/view/tabview.cxx b/sc/source/ui/view/tabview.cxx index 652e6b5a244f..7a0691349684 100644 --- a/sc/source/ui/view/tabview.cxx +++ b/sc/source/ui/view/tabview.cxx @@ -2406,9 +2406,7 @@ void ScTabView::SetNewVisArea() if (bInPlaceVisCursor) pInPlaceCrsr->Hide(); - ScGridWindow *pGridWindow = GetViewData().GetActiveWin(); - pGridWindow->DrawEditView(*pGridWindow->GetOutDev(), pEditView); - pGridWindow->GetOutDev()->SetMapMode(GetViewData().GetLogicMode()); + RefeshTextEditOverlay(); if (bInPlaceVisCursor) pInPlaceCrsr->Show(); diff --git a/sc/source/ui/view/tabview3.cxx b/sc/source/ui/view/tabview3.cxx index 6cc15283d272..cd060978c5d3 100644 --- a/sc/source/ui/view/tabview3.cxx +++ b/sc/source/ui/view/tabview3.cxx @@ -34,6 +34,14 @@ #include <sal/log.hxx> #include <osl/diagnose.h> +#include <basegfx/polygon/b2dpolygontools.hxx> +#include <drawinglayer/primitive2d/PolyPolygonRGBAPrimitive2D.hxx> +#include <drawinglayer/primitive2d/unifiedtransparenceprimitive2d.hxx> +#include <drawinglayer/primitive2d/modifiedcolorprimitive2d.hxx> +#include <drawinglayer/primitive2d/transformprimitive2d.hxx> +#include <svx/sdr/overlay/overlayselection.hxx> +#include <svtools/optionsdrawinglayer.hxx> + #include <IAnyRefDialog.hxx> #include <tabview.hxx> #include <tabvwsh.hxx> @@ -2189,7 +2197,354 @@ void ScTabView::OnLibreOfficeKitTabChanged() pThisViewShell->GetInputHandler()->UpdateLokReferenceMarks(); } -// paint functions - only for this View +// TextEditOverlayObject for TextOnOverlay TextEdit. It directly +// implements needed EditViewCallbacks and also hosts the +// OverlaySelection +namespace +{ +class ScTextEditOverlayObject : public sdr::overlay::OverlayObject, public EditViewCallbacks +{ + // the ScTabView the TextEdit is running at and the ScSplitPos to + // identify the associated data + ScTabView& mrScTabView; + ScSplitPos maScSplitPos; + + // this separate OverlayObject holds and creates the selection + // visualization, so it can be changed/refreshed without changing + // the Text or TextEditBackground + std::unique_ptr<sdr::overlay::OverlaySelection> mxOverlayTransparentSelection; + + // geometry creation for OverlayObject, in this case the extraction + // of edited Text from the setup EditEngine + virtual drawinglayer::primitive2d::Primitive2DContainer createOverlayObjectPrimitive2DSequence() override; + + // EditView overrides + virtual void EditViewInvalidate(const tools::Rectangle& rRect) override; + virtual void EditViewSelectionChange() override; + virtual OutputDevice& EditViewOutputDevice() const override; + virtual Point EditViewPointerPosPixel() const override; + virtual css::uno::Reference<css::datatransfer::clipboard::XClipboard> GetClipboard() const override; + virtual css::uno::Reference<css::datatransfer::dnd::XDropTarget> GetDropTarget() override; + virtual void EditViewInputContext(const InputContext& rInputContext) override; + virtual void EditViewCursorRect(const tools::Rectangle& rRect, int nExtTextInputWidth) override; + +public: + // create using system selection color & ScTabView + ScTextEditOverlayObject( + const Color& rColor, + ScTabView& rScTabView, + ScSplitPos aScSplitPos); + virtual ~ScTextEditOverlayObject() override; + + // override to mix in TextEditBackground and the Text transformed + // as needed + virtual drawinglayer::primitive2d::Primitive2DContainer getOverlayObjectPrimitive2DSequence() const override; + + // access to OverlaySelection to add to OverlayManager + sdr::overlay::OverlayObject* getOverlaySelection() + { + return mxOverlayTransparentSelection.get(); + } + + void RefeshTextEditOverlay() + { + // currently just deletes all created stuff, this may + // be fine-tuned later if needed + objectChange(); + } +}; + +ScTextEditOverlayObject::ScTextEditOverlayObject( + const Color& rColor, + ScTabView& rScTabView, + ScSplitPos aScSplitPos) +: OverlayObject(rColor) +, mrScTabView(rScTabView) +, maScSplitPos(aScSplitPos) +{ + // no AA for TextEdit overlay + allowAntiAliase(false); + + // establish EditViewCallbacks + const ScViewData& rScViewData(mrScTabView.GetViewData()); + EditView* pEditView(rScViewData.GetEditView(maScSplitPos)); + DBG_ASSERT(nullptr != pEditView, "NO access to EditView in ScTextEditOverlayObject!"); + pEditView->setEditViewCallbacks(this); + + // initialize empty OverlaySelection + std::vector<basegfx::B2DRange> aEmptySelection{}; + mxOverlayTransparentSelection.reset(new sdr::overlay::OverlaySelection( + sdr::overlay::OverlayType::Transparent, rColor, std::move(aEmptySelection), true)); +} + +ScTextEditOverlayObject::~ScTextEditOverlayObject() +{ + // delete OverlaySelection - this will also remove itself from + // OverlayManager already + mxOverlayTransparentSelection.reset(); + + // shutdown EditViewCallbacks + const ScViewData& rScViewData(mrScTabView.GetViewData()); + EditView* pEditView(rScViewData.GetEditView(maScSplitPos)); + DBG_ASSERT(nullptr != pEditView, "NO access to EditView in ScTextEditOverlayObject!"); + pEditView->setEditViewCallbacks(nullptr); + + // remove myself + if (getOverlayManager()) + getOverlayManager()->remove(*this); +} + +drawinglayer::primitive2d::Primitive2DContainer ScTextEditOverlayObject::createOverlayObjectPrimitive2DSequence() +{ + // extract primitive representation from active EditEngine + drawinglayer::primitive2d::Primitive2DContainer aRetval; + + ScViewData& rScViewData(mrScTabView.GetViewData()); + const EditView* pEditView(rScViewData.GetEditView(maScSplitPos)); + DBG_ASSERT(nullptr != pEditView, "NO access to EditView in ScTextEditOverlayObject!"); + + // use no transformations. The result will be in logic coordinates + // based on aEditRectangle and the EditEngine setup, see + // ScViewData::SetEditEngine + basegfx::B2DHomMatrix aNewTransformA; + basegfx::B2DHomMatrix aNewTransformB; + + // get text data in LogicMode + OutputDevice& rOutDev(pEditView->GetOutputDevice()); + const MapMode aOrig(rOutDev.GetMapMode()); + rOutDev.SetMapMode(rScViewData.GetLogicMode()); + + pEditView->getEditEngine().StripPortions( + [&aRetval, &aNewTransformA, &aNewTransformB](const DrawPortionInfo& rInfo){ + CreateTextPortionPrimitivesFromDrawPortionInfo( + aRetval, + aNewTransformA, + aNewTransformB, + rInfo); + }, + [&aRetval, &aNewTransformA, &aNewTransformB](const DrawBulletInfo& rInfo){ + CreateDrawBulletPrimitivesFromDrawBulletInfo( + aRetval, + aNewTransformA, + aNewTransformB, + rInfo); + }); + + rOutDev.SetMapMode(aOrig); + return aRetval; +} + +void ScTextEditOverlayObject::EditViewInvalidate(const tools::Rectangle& rRect) +{ + if (comphelper::LibreOfficeKit::isActive()) + { + // UT testPageDownInvalidation from CppunitTest_sc_tiledrendering + // *needs* the direct invalidates formerly done in + // EditView::InvalidateWindow when no EditViewCallbacks are set + ScGridWindow* pActiveWin(static_cast<ScGridWindow*>(mrScTabView.GetWindowByPos(maScSplitPos))); + pActiveWin->Invalidate(rRect); + } + + RefeshTextEditOverlay(); +} + +void ScTextEditOverlayObject::EditViewSelectionChange() +{ + ScViewData& rScViewData(mrScTabView.GetViewData()); + EditView* pEditView(rScViewData.GetEditView(maScSplitPos)); + DBG_ASSERT(nullptr != pEditView, "NO access to EditView in ScTextEditOverlayObject!"); + + // get the selection rectangles + std::vector<tools::Rectangle> aRects; + pEditView->GetSelectionRectangles(aRects); + std::vector<basegfx::B2DRange> aLogicRanges; + + if (aRects.empty()) + { + // if none, reset selection at OverlayObject and we are done + mxOverlayTransparentSelection->setRanges(std::move(aLogicRanges)); + return; + } + + // create needed transformations + // LogicMode -> DiscreteViewCoordinates (Pixels) + basegfx::B2DHomMatrix aTransformToPixels; + OutputDevice& rOutDev(pEditView->GetOutputDevice()); + const MapMode aOrig(rOutDev.GetMapMode()); + rOutDev.SetMapMode(rScViewData.GetLogicMode()); + aTransformToPixels = rOutDev.GetViewTransformation(); + + // DiscreteViewCoordinates (Pixels) -> LogicDrawCoordinates + basegfx::B2DHomMatrix aTransformToDrawCoordinates; + ScGridWindow* pActiveWin(static_cast<ScGridWindow*>(mrScTabView.GetWindowByPos(maScSplitPos))); + rOutDev.SetMapMode(pActiveWin->GetDrawMapMode()); + aTransformToDrawCoordinates = rOutDev.GetInverseViewTransformation(); + rOutDev.SetMapMode(aOrig); + + for (const auto& aRect : aRects) + { + basegfx::B2DRange aRange(aRect.Left(), aRect.Top(), aRect.Right(), aRect.Bottom()); + + // range to pixels + aRange.transform(aTransformToPixels); + + // grow by 1px for slight distance/overlap + aRange.grow(1.0); + + // to drawinglayer coordinates & add + aRange.transform(aTransformToDrawCoordinates); + aLogicRanges.emplace_back(aRange); + } + + mxOverlayTransparentSelection->setRanges(std::move(aLogicRanges)); +} + +OutputDevice& ScTextEditOverlayObject::EditViewOutputDevice() const +{ + ScGridWindow* pActiveWin(static_cast<ScGridWindow*>(mrScTabView.GetWindowByPos(maScSplitPos))); + return *pActiveWin->GetOutDev(); +} + +Point ScTextEditOverlayObject::EditViewPointerPosPixel() const +{ + ScGridWindow* pActiveWin(static_cast<ScGridWindow*>(mrScTabView.GetWindowByPos(maScSplitPos))); + return pActiveWin->GetPointerPosPixel(); +} + +css::uno::Reference<css::datatransfer::clipboard::XClipboard> ScTextEditOverlayObject::GetClipboard() const +{ + ScGridWindow* pActiveWin(static_cast<ScGridWindow*>(mrScTabView.GetWindowByPos(maScSplitPos))); + return pActiveWin->GetClipboard(); +} + +css::uno::Reference<css::datatransfer::dnd::XDropTarget> ScTextEditOverlayObject::GetDropTarget() +{ + ScGridWindow* pActiveWin(static_cast<ScGridWindow*>(mrScTabView.GetWindowByPos(maScSplitPos))); + return pActiveWin->GetDropTarget(); +} + +void ScTextEditOverlayObject::EditViewInputContext(const InputContext& rInputContext) +{ + ScGridWindow* pActiveWin(static_cast<ScGridWindow*>(mrScTabView.GetWindowByPos(maScSplitPos))); + pActiveWin->SetInputContext(rInputContext); +} + +void ScTextEditOverlayObject::EditViewCursorRect(const tools::Rectangle& rRect, int nExtTextInputWidth) +{ + ScGridWindow* pActiveWin(static_cast<ScGridWindow*>(mrScTabView.GetWindowByPos(maScSplitPos))); + pActiveWin->SetCursorRect(&rRect, nExtTextInputWidth); +} + +drawinglayer::primitive2d::Primitive2DContainer ScTextEditOverlayObject::getOverlayObjectPrimitive2DSequence() const +{ + drawinglayer::primitive2d::Primitive2DContainer aRetval; + + + ScViewData& rScViewData(mrScTabView.GetViewData()); + EditView* pEditView(rScViewData.GetEditView(maScSplitPos)); + DBG_ASSERT(nullptr != pEditView, "NO access to EditView in ScTextEditOverlayObject!"); + + // call base implementation to get TextPrimitives in logic coordinates + // directly from the setup EditEngine + drawinglayer::primitive2d::Primitive2DContainer aText( + OverlayObject::getOverlayObjectPrimitive2DSequence()); + + if (aText.empty()) + // no Text, done, return result + return aRetval; + + // remember MapMode and restore at exit - we do not want + // to change it outside this method + OutputDevice& rOutDev(pEditView->GetOutputDevice()); + const MapMode aOrig(rOutDev.GetMapMode()); + + // create text edit background based on pixel coordinates + // of involved Cells and append + { + const SCCOL nCol1(rScViewData.GetEditStartCol()); + const SCROW nRow1(rScViewData.GetEditStartRow()); + const SCCOL nCol2(rScViewData.GetEditEndCol()); + const SCROW nRow2(rScViewData.GetEditEndRow()); + const Point aStart(rScViewData.GetScrPos(nCol1, nRow1, maScSplitPos)); + const Point aEnd(rScViewData.GetScrPos(nCol2+1, nRow2+1, maScSplitPos)); + + if (aStart != aEnd) + { + basegfx::B2DPolyPolygon aOutline(basegfx::utils::createPolygonFromRect( + basegfx::B2DRange( + aStart.X(), aStart.Y(), + aEnd.X(), aEnd.Y()))); + + // transform from Pixels to LogicDrawCoordinates + ScGridWindow* pActiveWin(static_cast<ScGridWindow*>(mrScTabView.GetWindowByPos(maScSplitPos))); + rOutDev.SetMapMode(pActiveWin->GetDrawMapMode()); + aOutline.transform(rOutDev.GetInverseViewTransformation()); + + // on overlay we can now design the look more freely, it + // uses the CellBackgroundColor combined with 50% transparence + // to not completely hide what may be behind. + // Also currently no need to shrink by 2px (?) right/bottom + // to not kill cell separation lines - may have to be re-added + // when transparency is not wanted + aRetval.push_back( + rtl::Reference<drawinglayer::primitive2d::PolyPolygonRGBAPrimitive2D>( + new drawinglayer::primitive2d::PolyPolygonRGBAPrimitive2D( + aOutline, + pEditView->GetBackgroundColor().getBColor(), + 0.5))); + } + } + + // create embedding transformation for Text itself and + // append Text + { + basegfx::B2DHomMatrix aTransform; + + // transform by TextPaint StartPosition (Top-Left). This corresponds + // to aEditRectangle and the EditEngine setup, see + // ScViewData::SetEditEngine. Offset is in LogicCoordinates/LogicMode + const Point aStartPosition(pEditView->CalculateTextPaintStartPosition()); + aTransform.translate(aStartPosition.X(), aStartPosition.Y()); + + // LogicMode -> DiscreteViewCoordinates (Pixels) + rOutDev.SetMapMode(rScViewData.GetLogicMode()); + aTransform *= rOutDev.GetViewTransformation(); + + // DiscreteViewCoordinates (Pixels) -> LogicDrawCoordinates + ScGridWindow* pActiveWin(static_cast<ScGridWindow*>(mrScTabView.GetWindowByPos(maScSplitPos))); + rOutDev.SetMapMode(pActiveWin->GetDrawMapMode()); + aTransform *= rOutDev.GetInverseViewTransformation(); + + // add text embedded to created transformation + aRetval.push_back(rtl::Reference<drawinglayer::primitive2d::TransformPrimitive2D>( + new drawinglayer::primitive2d::TransformPrimitive2D( + aTransform, std::move(aText)))); + } + + rOutDev.SetMapMode(aOrig); + return aRetval; +} +} // end of anonymos namespace + +void ScTabView::RefeshTextEditOverlay() +{ + // find the ScTextEditOverlayObject in the OverlayGroup and + // call refresh there. It is also possible to have separate + // holders of that data, so no find/identification would be + // needed, but having all associated OverlayObjects in one + // group makes handling simple(r). It may also be that the + // cursor visualization will be added to that group later + for (sal_uInt32 a(0); a < maTextEditOverlayGroup.count(); a++) + { + sdr::overlay::OverlayObject& rOverlayObject(maTextEditOverlayGroup.getOverlayObject(a)); + ScTextEditOverlayObject* pScTextEditOverlayObject(dynamic_cast<ScTextEditOverlayObject*>(&rOverlayObject)); + + if (nullptr != pScTextEditOverlayObject) + { + pScTextEditOverlayObject->RefeshTextEditOverlay(); + } + } +} void ScTabView::MakeEditView( ScEditEngineDefaulter* pEngine, SCCOL nCol, SCROW nRow ) { @@ -2204,8 +2559,9 @@ void ScTabView::MakeEditView( ScEditEngineDefaulter* pEngine, SCCOL nCol, SCROW { if (pGridWin[i] && pGridWin[i]->IsVisible() && !aViewData.HasEditView(ScSplitPos(i))) { - ScHSplitPos eHWhich = WhichH( static_cast<ScSplitPos>(i) ); - ScVSplitPos eVWhich = WhichV( static_cast<ScSplitPos>(i) ); + const ScSplitPos aScSplitPos(static_cast<ScSplitPos>(i)); + ScHSplitPos eHWhich = WhichH(aScSplitPos); + ScVSplitPos eVWhich = WhichV(aScSplitPos); SCCOL nScrX = aViewData.GetPosX( eHWhich ); SCROW nScrY = aViewData.GetPosY( eVWhich ); @@ -2217,39 +2573,47 @@ void ScTabView::MakeEditView( ScEditEngineDefaulter* pEngine, SCCOL nCol, SCROW // so input isn't lost (and the edit view may be scrolled into the visible area) // #i26433# during spelling, the spelling view must be active - if ( bPosVisible || aViewData.GetActivePart() == static_cast<ScSplitPos>(i) || - ( pSpellingView && aViewData.GetEditView(static_cast<ScSplitPos>(i)) == pSpellingView ) ) + if ( bPosVisible || aViewData.GetActivePart() == aScSplitPos || + ( pSpellingView && aViewData.GetEditView(aScSplitPos) == pSpellingView ) ) { - pGridWin[i]->HideCursor(); - - pGridWin[i]->DeleteCursorOverlay(); - pGridWin[i]->DeleteAutoFillOverlay(); - pGridWin[i]->DeleteCopySourceOverlay(); - - // tdf#165621 allow the Overlay to quickly update, necessary - // for clean graphical refreshes - // NOTE: This also works using Application::Reschedule(true), but - // triggers CppunitTest_desktop_lib "DesktopLOKTest::testRedlineCalc", - // probably due to not only the timer triggering but 'other' stuff - // that better runs later (...?). Not doing it - as long as we need - // to do it - is safer, but forces to keep that flush just for this - // single usage - rtl::Reference<sdr::overlay::OverlayManager> xOverlayManager = pGridWin[i]->getOverlayManager(); - if (xOverlayManager.is()) - xOverlayManager->flush(); + VclPtr<ScGridWindow> pScGridWindow(pGridWin[aScSplitPos]); + pScGridWindow->HideCursor(); + pScGridWindow->DeleteCursorOverlay(); + pScGridWindow->DeleteAutoFillOverlay(); + pScGridWindow->DeleteCopySourceOverlay(); // MapMode must be set after HideCursor - pGridWin[i]->SetMapMode(aViewData.GetLogicMode()); + pScGridWindow->SetMapMode(aViewData.GetLogicMode()); if ( !bPosVisible ) { // move the edit view area to the real (possibly negative) position, // or hide if completely above or left of the window - pGridWin[i]->UpdateEditViewPos(); + pScGridWindow->UpdateEditViewPos(); } - aViewData.SetEditEngine(static_cast<ScSplitPos>(i), pEngine, pGridWin[i], nCol, + aViewData.SetEditEngine(aScSplitPos, pEngine, pScGridWindow, nCol, nRow); + + // get OverlayManager and initialize TextEditOnOverlay for it + rtl::Reference<sdr::overlay::OverlayManager> xOverlayManager = pScGridWindow->getOverlayManager(); + + if (xOverlayManager.is() && aViewData.HasEditView(aScSplitPos)) + { + const Color aHilightColor(SvtOptionsDrawinglayer::getHilightColor()); + std::unique_ptr<ScTextEditOverlayObject> pNewScTextEditOverlayObject( + new ScTextEditOverlayObject( + aHilightColor, + *this, + aScSplitPos)); + + // add TextEditOverlayObject and the OverlaySelection hosted by it + // to the OverlayManager (to make visible) and to the local + // reference TextEditOverlayGroup (to access if needed) + xOverlayManager->add(*pNewScTextEditOverlayObject); + xOverlayManager->add(*pNewScTextEditOverlayObject->getOverlaySelection()); + maTextEditOverlayGroup.append(std::move(pNewScTextEditOverlayObject)); + } } } } @@ -2282,6 +2646,8 @@ void ScTabView::UpdateEditView() pEditView->ShowCursor( false ); } } + + RefeshTextEditOverlay(); } void ScTabView::KillEditView( bool bNoPaint ) @@ -2312,6 +2678,10 @@ void ScTabView::KillEditView( bool bNoPaint ) } } + // this cleans up all used OverlayObjects for TextEdit, they get deleted + // and removed from the OverlayManager what makes them optically disappear + maTextEditOverlayGroup.clear(); + // notify accessibility before all things happen if (bNotifyAcc && aViewData.GetViewShell()->HasAccessibilityObjects()) aViewData.GetViewShell()->BroadcastAccessibility(SfxHint(SfxHintId::ScAccLeaveEditMode)); diff --git a/sc/source/ui/view/viewdata.cxx b/sc/source/ui/view/viewdata.cxx index 41cd00126f81..469aaeacc5a2 100644 --- a/sc/source/ui/view/viewdata.cxx +++ b/sc/source/ui/view/viewdata.cxx @@ -1867,8 +1867,13 @@ void ScViewData::SetEditEngine( ScSplitPos eWhich, } pEditView[eWhich]->SetBackgroundColor( aBackCol ); - pEditView[eWhich]->Invalidate(); // needed? - // needed, if position changed + if (comphelper::LibreOfficeKit::isActive()) + { + // now only needed when not CEOO (CellEditOnOverlay) + // needed? + // needed, if position changed + pEditView[eWhich]->Invalidate(); + } } IMPL_LINK( ScViewData, EditEngineHdl, EditStatus&, rStatus, void ) diff --git a/svx/inc/sdr/overlay/overlaymanagerbuffered.hxx b/svx/inc/sdr/overlay/overlaymanagerbuffered.hxx index a5acd6abc3ee..485ab992e39f 100644 --- a/svx/inc/sdr/overlay/overlaymanagerbuffered.hxx +++ b/svx/inc/sdr/overlay/overlaymanagerbuffered.hxx @@ -44,6 +44,10 @@ namespace sdr::overlay // Range for buffering (in pixel to be independent from mapMode) basegfx::B2IRange maBufferRememberedRangePixel; + // remember MapMode from last completeRedraw, that is what + // a timer-based refresh will be based on + mutable MapMode maMapModeLastCompleteRedraw; + // link for timer DECL_LINK(ImpBufferTimerHandler, Timer*, void); @@ -62,9 +66,6 @@ namespace sdr::overlay // complete redraw virtual void completeRedraw(const vcl::Region& rRegion, OutputDevice* pPreRenderDevice = nullptr) const override; - // flush. Do buffered updates. - virtual void flush() override; - // invalidate the given range at local OutputDevice virtual void invalidateRange(const basegfx::B2DRange& rRange) override; }; diff --git a/svx/source/sdr/overlay/overlaymanagerbuffered.cxx b/svx/source/sdr/overlay/overlaymanagerbuffered.cxx index a58ec8192004..f32b87b2898e 100644 --- a/svx/source/sdr/overlay/overlaymanagerbuffered.cxx +++ b/svx/source/sdr/overlay/overlaymanagerbuffered.cxx @@ -196,8 +196,29 @@ namespace sdr::overlay maBufferIdle.Stop(); if(maBufferRememberedRangePixel.isEmpty()) + // nothing to refresh, done return; + if (maMapModeLastCompleteRedraw == MapMode()) + // there was no CompleteRedraw yet, done + return; + + // in calc i stumbled over the situation that MapModes for OutDevs + // were different for ::CompleteRedraw and this timer-based refresh, + // probably due to Calc's massive MapMode changes - as soon as an + // overlay invalidate happened the next reschedule in calc *will* + // trigger this refresh, despite what MapMode may be set at the + // target device currently. + // These updates only make sense in combination with the saved + // background, so this is an error (and happens only in Calc due + // to these always changing MapModes). + // Thus it is plausible to remember the MapMode of the last full + // CompleteRedraw and force local usage to it. + const MapMode aPrevMapMode(getOutputDevice().GetMapMode()); + const bool bPatchMapMode(aPrevMapMode != maMapModeLastCompleteRedraw); + if (bPatchMapMode) + getOutputDevice().SetMapMode(maMapModeLastCompleteRedraw); + // logic size for impDrawMember call basegfx::B2DRange aBufferRememberedRangeLogic( maBufferRememberedRangePixel.getMinX(), maBufferRememberedRangePixel.getMinY(), @@ -344,6 +365,11 @@ namespace sdr::overlay // forget remembered Region maBufferRememberedRangePixel.reset(); + + // restore evtl. temporarily patched MapMode (see explanation + // above) + if (bPatchMapMode) + getOutputDevice().SetMapMode(aPrevMapMode); } OverlayManagerBuffered::OverlayManagerBuffered( @@ -351,7 +377,8 @@ namespace sdr::overlay : OverlayManager(rOutputDevice), mpBufferDevice(VclPtr<VirtualDevice>::Create()), mpOutputBufferDevice(VclPtr<VirtualDevice>::Create()), - maBufferIdle( "sdr::overlay::OverlayManagerBuffered maBufferIdle" ) + maBufferIdle( "sdr::overlay::OverlayManagerBuffered maBufferIdle" ), + maMapModeLastCompleteRedraw() { // Init timer maBufferIdle.SetPriority( TaskPriority::POST_PAINT ); @@ -378,6 +405,11 @@ namespace sdr::overlay void OverlayManagerBuffered::completeRedraw(const vcl::Region& rRegion, OutputDevice* pPreRenderDevice) const { + // remember MapMode of last full completeRedraw to + // have it available in timer-based updates (see + // ImpBufferTimerHandler) + maMapModeLastCompleteRedraw = getOutputDevice().GetMapMode(); + if(!rRegion.IsEmpty()) { // save new background @@ -388,12 +420,6 @@ namespace sdr::overlay OverlayManager::completeRedraw(rRegion, pPreRenderDevice); } - void OverlayManagerBuffered::flush() - { - // call timer handler direct - ImpBufferTimerHandler(nullptr); - } - void OverlayManagerBuffered::invalidateRange(const basegfx::B2DRange& rRange) { if(rRange.isEmpty())