From 8a732d9ff15ba73835c3fa60208f954b772f5a9c Mon Sep 17 00:00:00 2001 From: Marco Cecchetti Date: Mon, 4 Dec 2023 09:31:23 +0100 Subject: calc: on editing invalidation of view with different zoom is wrong MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch fixes the following invalidation issue: There are 2 views with different zoom levels. In a view text editing for a cell occurs. The other view is not invalidated properly: the computed invalidation rectangle is misplaced. Change-Id: I72db61486647640ee68e6cb2db96b2902de5b997 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/160303 Tested-by: Jenkins CollaboraOffice Tested-by: Caolán McNamara Reviewed-by: Caolán McNamara --- editeng/source/editeng/editview.cxx | 5 +- include/vcl/window.hxx | 2 + sc/qa/unit/tiledrendering/tiledrendering.cxx | 115 +++++++++++++++++++++++++++ sc/source/ui/inc/gridwin.hxx | 1 + sc/source/ui/view/gridwin4.cxx | 26 ++++++ vcl/source/window/paint.cxx | 5 ++ 6 files changed, 153 insertions(+), 1 deletion(-) diff --git a/editeng/source/editeng/editview.cxx b/editeng/source/editeng/editview.cxx index 0f53a53de045..6cb2913be3da 100644 --- a/editeng/source/editeng/editview.cxx +++ b/editeng/source/editeng/editview.cxx @@ -231,7 +231,10 @@ void EditView::InvalidateOtherViewWindows( const tools::Rectangle& rInvRect ) for (auto& pWin : pImpEditView->aOutWindowSet) { if (pWin) - pWin->Invalidate( bNegativeX ? lcl_negateRectX(rInvRect) : rInvRect ); + { + if (!pWin->InvalidateByForeignEditView(this)) + pWin->Invalidate( bNegativeX ? lcl_negateRectX(rInvRect) : rInvRect ); + } } } } diff --git a/include/vcl/window.hxx b/include/vcl/window.hxx index d03555bd5b99..829b9f174b57 100644 --- a/include/vcl/window.hxx +++ b/include/vcl/window.hxx @@ -62,6 +62,7 @@ class VclWindowEvent; class AllSettings; class InputContext; class VclEventListeners; +class EditView; enum class ImplPaintFlags; enum class VclEventId; enum class PointerStyle; @@ -970,6 +971,7 @@ public: */ virtual void LogicInvalidate(const tools::Rectangle* pRectangle); + virtual bool InvalidateByForeignEditView(EditView* ); /** * Notification about some rectangle of the output device got invalidated. Used for the * dialogs and floating windows (e.g. context menu, popup). diff --git a/sc/qa/unit/tiledrendering/tiledrendering.cxx b/sc/qa/unit/tiledrendering/tiledrendering.cxx index 1f18401a970c..7d89cb787c85 100644 --- a/sc/qa/unit/tiledrendering/tiledrendering.cxx +++ b/sc/qa/unit/tiledrendering/tiledrendering.cxx @@ -9,6 +9,7 @@ #include #include +#include #include #include @@ -60,6 +61,31 @@ static std::ostream& operator<<(std::ostream& os, ViewShellId const & id) os << static_cast(id); return os; } +// for passing data to testInvalidateOnTextEditWithDifferentZoomLevels +struct ColRowZoom +{ + SCCOL col; + SCROW row; + int zoom; +}; + +CPPUNIT_NS_BEGIN +namespace StringHelper +{ +// used by CPPUNIT_TEST_PARAMETERIZED for testInvalidateOnTextEditWithDifferentZoomLevels +template<> +inline std::string toString(const ColRowZoom& item) +{ + std::ostringstream ss; + ss << "zoom level: " << item.zoom << ", " + << "col: " << item.col << ", " + << "row: " << item.row; + return ss.str(); +} + +} +CPPUNIT_NS_END + namespace { @@ -129,6 +155,7 @@ public: void testUndoReorderingRedo(); void testUndoReorderingMulti(); void testGetViewRenderState(); + void testInvalidateOnTextEditWithDifferentZoomLevels(const ColRowZoom& rData); CPPUNIT_TEST_SUITE(ScTiledRenderingTest); CPPUNIT_TEST(testRowColumnHeaders); @@ -190,6 +217,14 @@ public: CPPUNIT_TEST(testUndoReorderingRedo); CPPUNIT_TEST(testUndoReorderingMulti); CPPUNIT_TEST(testGetViewRenderState); + CPPUNIT_TEST_PARAMETERIZED(testInvalidateOnTextEditWithDifferentZoomLevels, + std::initializer_list + { + // zoom level 120% + {0, 999, 1}, {99, 0, 1}, + // zoom level 40% + {0, 999, -5}, {99, 0, -5} + }); CPPUNIT_TEST_SUITE_END(); private: @@ -3286,6 +3321,86 @@ void ScTiledRenderingTest::testGetViewRenderState() CPPUNIT_ASSERT_EQUAL(OString(";Default"), pModelObj->getViewRenderState()); } +/* + * testInvalidateOnTextEditWithDifferentZoomLevels + * steps: + * set view 1 zoom to the passed zoom level + * in view 1 type a char at the passed cell address + * store invalidation rectangle + * exit from in place editing (press esc) + * create view 2 (keep 100% zoom) + * go to the same cell address used in view 1 + * type a char into the cell + * get invalidation rectangle for view 1 + * check if the invalidation rectangle is equal to the one stored previously +*/ +void ScTiledRenderingTest::testInvalidateOnTextEditWithDifferentZoomLevels(const ColRowZoom& rData) +{ + ScModelObj* pModelObj = createDoc("empty.ods"); + CPPUNIT_ASSERT(pModelObj); + ScDocument* pDoc = pModelObj->GetDocument(); + CPPUNIT_ASSERT(pDoc); + + OUString sZoomUnoCmd = ".uno:ZoomPlus"; + int nZoomLevel = rData.zoom; + if (nZoomLevel < 0) + { + nZoomLevel = -nZoomLevel; + sZoomUnoCmd = ".uno:ZoomMinus"; + } + + // view #1 + ViewCallback aView1; + // set zoom level + for (int i = 0; i < nZoomLevel; ++i) + dispatchCommand(mxComponent, sZoomUnoCmd, {}); + Scheduler::ProcessEventsToIdle(); + + auto* pTabViewShell1 = dynamic_cast(SfxViewShell::Current()); + CPPUNIT_ASSERT(pTabViewShell1); + + // enable in place editing in view 1 + auto& rInvalidations = aView1.m_aInvalidations; + pTabViewShell1->SetCursor(rData.col, rData.row); + Scheduler::ProcessEventsToIdle(); + aView1.m_bInvalidateTiles = false; + rInvalidations.clear(); + pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0); + pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(aView1.m_bInvalidateTiles); + CPPUNIT_ASSERT(!rInvalidations.empty()); + tools::Rectangle aInvRect1 = rInvalidations[0]; + + // end editing + pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 0, awt::Key::ESCAPE); + pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 0, awt::Key::ESCAPE); + Scheduler::ProcessEventsToIdle(); + + // view #2 + SfxLokHelper::createView(); + pModelObj->initializeForTiledRendering(uno::Sequence()); + ViewCallback aView2; + Scheduler::ProcessEventsToIdle(); + + auto* pTabViewShell2 = dynamic_cast(SfxViewShell::Current()); + CPPUNIT_ASSERT(pTabViewShell2); + pTabViewShell2->SetCursor(rData.col, rData.row); + Scheduler::ProcessEventsToIdle(); + + // text edit in view #2 + aView1.m_bInvalidateTiles = false; + rInvalidations.clear(); + pModelObj->postKeyEvent(LOK_KEYEVENT_KEYINPUT, 'x', 0); + pModelObj->postKeyEvent(LOK_KEYEVENT_KEYUP, 'x', 0); + Scheduler::ProcessEventsToIdle(); + CPPUNIT_ASSERT(aView1.m_bInvalidateTiles); + CPPUNIT_ASSERT(!rInvalidations.empty()); + tools::Rectangle aInvRect2 = rInvalidations[0]; + + CPPUNIT_ASSERT_EQUAL_MESSAGE("Invalidation rectangle is wrong.", aInvRect1, aInvRect2); +} + } CPPUNIT_TEST_SUITE_REGISTRATION(ScTiledRenderingTest); diff --git a/sc/source/ui/inc/gridwin.hxx b/sc/source/ui/inc/gridwin.hxx index 8334e5fd0d51..048d2d3d9359 100644 --- a/sc/source/ui/inc/gridwin.hxx +++ b/sc/source/ui/inc/gridwin.hxx @@ -380,6 +380,7 @@ public: void LogicInvalidate(const tools::Rectangle* pRectangle) override; void LogicInvalidatePart(const tools::Rectangle* pRectangle, int nPart); + bool InvalidateByForeignEditView(EditView* pEditView) override; /// Update the cell selection according to what handles have been dragged. /// @see vcl::ITiledRenderable::setTextSelection() for the values of nType. /// Coordinates are in pixels. diff --git a/sc/source/ui/view/gridwin4.cxx b/sc/source/ui/view/gridwin4.cxx index 1f235fcd6aa4..390c4b2bdb63 100644 --- a/sc/source/ui/view/gridwin4.cxx +++ b/sc/source/ui/view/gridwin4.cxx @@ -671,6 +671,11 @@ int lcl_GetMultiLineHeight(EditEngine* pEditEngine) return nHeight; } + +tools::Rectangle lcl_negateRectX(const tools::Rectangle& rRect) +{ + return tools::Rectangle(-rRect.Right(), rRect.Top(), -rRect.Left(), rRect.Bottom()); +} } void ScGridWindow::DrawContent(OutputDevice &rDevice, const ScTableInfo& rTableInfo, ScOutputData& aOutputData, @@ -1765,6 +1770,27 @@ void ScGridWindow::LogicInvalidate(const tools::Rectangle* pRectangle) LogicInvalidatePart(pRectangle, pViewShell->getPart()); } +bool ScGridWindow::InvalidateByForeignEditView(EditView* pEditView) +{ + if (!pEditView) + return false; + + auto* pGridWin = dynamic_cast(pEditView->GetWindow()); + if (!pGridWin) + return false; + + const ScViewData& rViewData = pGridWin->getViewData(); + tools::Long nRefTabNo = rViewData.GetRefTabNo(); + tools::Long nX = rViewData.GetCurXForTab(nRefTabNo); + tools::Long nY = rViewData.GetCurYForTab(nRefTabNo); + + tools::Rectangle aPixRect = getViewData().GetEditArea(eWhich, nX, nY, this, nullptr, true); + tools::Rectangle aLogicRect = PixelToLogic(aPixRect, getViewData().GetLogicMode()); + Invalidate(pEditView->IsNegativeX() ? lcl_negateRectX(aLogicRect) : aLogicRect); + + return true; +} + void ScGridWindow::SetCellSelectionPixel(int nType, int nPixelX, int nPixelY) { ScTabView* pTabView = mrViewData.GetView(); diff --git a/vcl/source/window/paint.cxx b/vcl/source/window/paint.cxx index a70f1c0e4004..2e9b153bc961 100644 --- a/vcl/source/window/paint.cxx +++ b/vcl/source/window/paint.cxx @@ -1198,6 +1198,11 @@ void Window::LogicInvalidate(const tools::Rectangle* pRectangle) PixelInvalidate(nullptr); } +bool Window::InvalidateByForeignEditView(EditView* ) +{ + return false; +} + void Window::PixelInvalidate(const tools::Rectangle* pRectangle) { if (comphelper::LibreOfficeKit::isDialogPainting() || !comphelper::LibreOfficeKit::isActive()) -- cgit v1.2.3