diff options
author | Attila Bakos (NISZ) <bakos.attilakaroly@nisz.hu> | 2021-05-07 10:18:01 +0200 |
---|---|---|
committer | László Németh <nemeth@numbertext.org> | 2021-05-25 13:06:22 +0200 |
commit | 0e6d963fbca16f98a3dbb6ef2fee3736a89d055b (patch) | |
tree | e1a413ce8f1307cbbcd8cf96af1d130dcce3a469 /sw | |
parent | b2f7187e04a6b6c6183b42230a0119569f069dcf (diff) |
tdf#138141 sw: fix textbox z-order
Textboxes are implemented as loosely connected
shape-text frame pairs. Missing synchronization
of their z-orders resulted e.g invisible or
only partially visible textbox content using
Arrange options with textboxes (see in local menu
or on Drawing Object Properties toolbar).
Note: because it's not possible to send frames
to the background, Arrange->To Background hasn't
supported, so likely it's worth to remove that
option later from local menu of textboxes.
Change-Id: I1aa50903ba55dd5b9e72ef203c4e30218bee68fb
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/115227
Tested-by: László Németh <nemeth@numbertext.org>
Reviewed-by: László Németh <nemeth@numbertext.org>
Diffstat (limited to 'sw')
-rw-r--r-- | sw/inc/textboxhelper.hxx | 4 | ||||
-rw-r--r-- | sw/qa/extras/ooxmlexport/data/testTextBoxZOrder.docx | bin | 0 -> 17635 bytes | |||
-rw-r--r-- | sw/qa/extras/ooxmlexport/ooxmlexport16.cxx | 31 | ||||
-rw-r--r-- | sw/source/core/doc/textboxhelper.cxx | 70 | ||||
-rw-r--r-- | sw/source/core/frmedt/feshview.cxx | 82 |
5 files changed, 181 insertions, 6 deletions
diff --git a/sw/inc/textboxhelper.hxx b/sw/inc/textboxhelper.hxx index 0db804424332..1c9fdc66e703 100644 --- a/sw/inc/textboxhelper.hxx +++ b/sw/inc/textboxhelper.hxx @@ -107,6 +107,10 @@ public: /// Returns true if the given shape has a valid textframe. static bool isTextBoxShapeHasValidTextFrame(SwFrameFormat* pShape); + // Returns true on success. Synchronize z-order of the text frame of the given textbox + // by setting it one level higher than the z-order of the shape of the textbox. + static bool DoTextBoxZOrderCorrection(SwFrameFormat* pShape); + /** * If we have an associated TextFrame, then return that. * diff --git a/sw/qa/extras/ooxmlexport/data/testTextBoxZOrder.docx b/sw/qa/extras/ooxmlexport/data/testTextBoxZOrder.docx Binary files differnew file mode 100644 index 000000000000..c6dd0b8f30c8 --- /dev/null +++ b/sw/qa/extras/ooxmlexport/data/testTextBoxZOrder.docx diff --git a/sw/qa/extras/ooxmlexport/ooxmlexport16.cxx b/sw/qa/extras/ooxmlexport/ooxmlexport16.cxx index 16cd111e9e54..165f104c2f57 100644 --- a/sw/qa/extras/ooxmlexport/ooxmlexport16.cxx +++ b/sw/qa/extras/ooxmlexport/ooxmlexport16.cxx @@ -26,6 +26,7 @@ #include <com/sun/star/text/XTextViewCursorSupplier.hpp> #include <com/sun/star/text/XTextTable.hpp> #include <com/sun/star/text/XTextTablesSupplier.hpp> +#include <com/sun/star/text/XTextFrame.hpp> #include <com/sun/star/packages/zip/ZipFileAccess.hpp> #include <comphelper/configuration.hxx> #include <comphelper/propertysequence.hxx> @@ -338,6 +339,36 @@ DECLARE_OOXMLEXPORT_TEST(testTdf133473_shadowSize, "tdf133473.docx") CPPUNIT_ASSERT_EQUAL(static_cast<sal_Int32>(200000), nSize1); } +DECLARE_OOXMLEXPORT_TEST(testTextBoxZOrder, "testTextBoxZOrder.docx") +{ + // Is load successful? + CPPUNIT_ASSERT(mxComponent); + // Collect the z-order values of the textboxes + std::vector<sal_uInt64> ShapeZorders; + std::vector<sal_uInt64> FrameZorders; + for (int i = 1; i < 4; i++) + { + uno::Reference<drawing::XShape> xShape(getShape(i)); + CPPUNIT_ASSERT(xShape); + uno::Reference<beans::XPropertySet> xShapeProperties(xShape, uno::UNO_QUERY); + CPPUNIT_ASSERT(xShapeProperties); + uno::Reference<text::XTextFrame> xFrame = SwTextBoxHelper::getUnoTextFrame(xShape); + CPPUNIT_ASSERT(xFrame.is()); + uno::Reference<beans::XPropertySet> const xFrameProperties(xFrame, uno::UNO_QUERY); + CPPUNIT_ASSERT(xFrameProperties); + ShapeZorders.push_back(xShapeProperties->getPropertyValue("ZOrder").get<sal_uInt64>()); + FrameZorders.push_back(xFrameProperties->getPropertyValue("ZOrder").get<sal_uInt64>()); + } + // Check the z-order values. + for (int i = 1; i < 3; i++) + { + CPPUNIT_ASSERT_GREATER(ShapeZorders[i - 1], ShapeZorders[i]); + CPPUNIT_ASSERT_GREATER(FrameZorders[i - 1], FrameZorders[i]); + CPPUNIT_ASSERT_GREATER(ShapeZorders[i - 1], FrameZorders[i - 1]); + } + // Without the fix it failed, because the z-order was wrong. +} + DECLARE_OOXMLEXPORT_TEST(testTdf141550, "tdf141550.docx") { uno::Reference<drawing::XShape> xShape(getShape(1)); diff --git a/sw/source/core/doc/textboxhelper.cxx b/sw/source/core/doc/textboxhelper.cxx index cba67e1bf74d..f901beab461c 100644 --- a/sw/source/core/doc/textboxhelper.cxx +++ b/sw/source/core/doc/textboxhelper.cxx @@ -38,6 +38,9 @@ #include <sal/log.hxx> #include <tools/UnitConversion.hxx> #include <svx/swframetypes.hxx> +#include <drawdoc.hxx> +#include <IDocumentUndoRedo.hxx> +#include <DocumentDrawModelManager.hxx> #include <com/sun/star/document/XActionLockable.hpp> #include <com/sun/star/lang/IndexOutOfBoundsException.hpp> @@ -135,6 +138,7 @@ void SwTextBoxHelper::create(SwFrameFormat* pShape, bool bCopyText) pShape->SetFormatAttr(aSet); } + DoTextBoxZOrderCorrection(pShape); // Also initialize the properties, which are not constant, but inherited from the shape's ones. uno::Reference<drawing::XShape> xShape(pShape->FindRealSdrObject()->getUnoShape(), uno::UNO_QUERY); @@ -1016,6 +1020,8 @@ void SwTextBoxHelper::syncFlyFrameAttr(SwFrameFormat& rShape, SfxItemSet const& if (aTextBoxSet.Count()) pFormat->GetDoc()->SetFlyFrameAttr(*pFormat, aTextBoxSet); + + DoTextBoxZOrderCorrection(&rShape); } void SwTextBoxHelper::updateTextBoxMargin(SdrObject* pObj) @@ -1065,6 +1071,7 @@ bool SwTextBoxHelper::setWrapThrough(SwFrameFormat* pShape) { if (auto pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT)) { + ::sw::UndoGuard const UndoGuard(pShape->GetDoc()->GetIDocumentUndoRedo()); if (auto xFrame = SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat)) try { @@ -1109,6 +1116,7 @@ bool SwTextBoxHelper::changeAnchor(SwFrameFormat* pShape) { try { + ::sw::UndoGuard const UndoGuard(pShape->GetDoc()->GetIDocumentUndoRedo()); uno::Reference<beans::XPropertySet> const xPropertySet( SwXTextFrame::CreateXTextFrame(*pFormat->GetDoc(), pFormat), uno::UNO_QUERY); @@ -1175,7 +1183,7 @@ bool SwTextBoxHelper::changeAnchor(SwFrameFormat* pShape) } } - return doTextBoxPositioning(pShape); + return doTextBoxPositioning(pShape) && DoTextBoxZOrderCorrection(pShape); } } return false; @@ -1187,6 +1195,7 @@ bool SwTextBoxHelper::doTextBoxPositioning(SwFrameFormat* pShape) { if (auto pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT)) { + ::sw::UndoGuard const UndoGuard(pShape->GetDoc()->GetIDocumentUndoRedo()); if (pShape->GetAnchor().GetAnchorId() == RndStdIds::FLY_AS_CHAR) { tools::Rectangle aRect(getTextRectangle(pShape, false)); @@ -1249,19 +1258,68 @@ std::optional<bool> SwTextBoxHelper::isAnchorTypeDifferent(SwFrameFormat* pShape bool SwTextBoxHelper::isTextBoxShapeHasValidTextFrame(SwFrameFormat* pShape) { - OUString sErrMsg; if (pShape && pShape->Which() == RES_DRAWFRMFMT) if (auto pFormat = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT)) if (pFormat && pFormat->Which() == RES_FLYFRMFMT) return true; else - sErrMsg = "Shape do not have valid textframe!"; + SAL_WARN("sw.core", "SwTextBoxHelper::isTextBoxShapeHasValidTextFrame: " + "Shape does not have valid textframe!"); else - sErrMsg = "Shape do not have associated frame!"; + SAL_WARN("sw.core", "SwTextBoxHelper::isTextBoxShapeHasValidTextFrame: " + "Shape does not have associated frame!"); else - sErrMsg = "Not valid shape!"; + SAL_WARN("sw.core", "SwTextBoxHelper::isTextBoxShapeHasValidTextFrame: Not valid shape!"); + return false; +} + +bool SwTextBoxHelper::DoTextBoxZOrderCorrection(SwFrameFormat* pShape) +{ + if (isTextBoxShapeHasValidTextFrame(pShape)) + { + if (SdrObject* pShpObj = pShape->FindRealSdrObject()) + { + if (SdrObject* pFrmObj + = getOtherTextBoxFormat(pShape, RES_DRAWFRMFMT)->FindRealSdrObject()) + { + // Get the draw model from the doc + SwDrawModel* pDrawModel + = pShape->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel(); + if (pDrawModel) + { + // Not really sure this will work all page, but it seems it will. + auto pPage = pDrawModel->GetPage(0); + // Recalc all Zorders + pPage->RecalcObjOrdNums(); + // If the shape is behind the frame, is good, but if there are some objects + // between of them that is wrong so put the frame exactly one level higher + // than the shape. + if (pFrmObj->GetOrdNum() > pShpObj->GetOrdNum()) + pPage->SetObjectOrdNum(pFrmObj->GetOrdNum(), pShpObj->GetOrdNum() + 1); + else + // Else, if the frame is behind the shape, bring to the front of it. + while (pFrmObj->GetOrdNum() <= pShpObj->GetOrdNum()) + { + pPage->SetObjectOrdNum(pFrmObj->GetOrdNum(), pFrmObj->GetOrdNum() + 1); + // If there is any problem with the indexes, do not run over the infinity + if (pPage->GetObjCount() == pFrmObj->GetOrdNum()) + break; + } + pPage->RecalcObjOrdNums(); + return true; // Success + } + SAL_WARN("sw.core", "SwTextBoxHelper::DoTextBoxZOrderCorrection(): " + "No Valid Draw model for SdrObject for the shape!"); + } + SAL_WARN("sw.core", "SwTextBoxHelper::DoTextBoxZOrderCorrection(): " + "No Valid SdrObject for the frame!"); + } + SAL_WARN("sw.core", "SwTextBoxHelper::DoTextBoxZOrderCorrection(): " + "No Valid SdrObject for the shape!"); + } + SAL_WARN("sw.core", "SwTextBoxHelper::DoTextBoxZOrderCorrection(): " + "No Valid TextFrame!"); - SAL_WARN("sw.core", "SwTextBoxHelper::isTextBoxShapeHasValidTextFrame: " << sErrMsg); return false; } diff --git a/sw/source/core/frmedt/feshview.cxx b/sw/source/core/frmedt/feshview.cxx index 31c30e3c31ec..6f4dbf238612 100644 --- a/sw/source/core/frmedt/feshview.cxx +++ b/sw/source/core/frmedt/feshview.cxx @@ -1056,6 +1056,55 @@ void SwFEShell::SelectionToTop( bool bTop ) else Imp()->GetDrawView()->MovMarkedToTop(); ::lcl_NotifyNeighbours( &rMrkList ); + + // Does the selection contain a textbox? + for (size_t i = 0; i < rMrkList.GetMarkCount(); i++) + if (auto pObj = rMrkList.GetMark(i)->GetMarkedSdrObj()) + // Get the textbox-shape + if (auto pFormat = FindFrameFormat(pObj)) + { + // If it has not textframe skip... + if (!SwTextBoxHelper::isTextBoxShapeHasValidTextFrame(pFormat)) + continue; + // If it has a textframe so it is a textbox, get its page + if (auto pDrwModel + = pFormat->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()) + // Not really understood why everything is on page 0... + // but it is easier to handle sdrobjects, thats true + if (auto pPage = pDrwModel->GetPage(0)) + { + // nShift: it means how many layers the pObj have to be shifted up, + // in order not to interfere with other shapes and textboxes. + // Situations: + // - The next shape has textframe: This shape have to shifted with + // two layers. + // - The next shape has not got textframe: This shape have to be + // shifted only one layer up. + // - The next shape is null: + // - This shape is already at heaven: Only the textframe have + // to be adjusted. + sal_uInt32 nShift = 0; + // Get the one level higher object (note: can be nullptr!) + const auto pNextObj = pPage->SetObjectOrdNum(pObj->GetOrdNum() + 1, pObj->GetOrdNum() + 1); + // If there is a higher object (not null)... + if (pNextObj) + { + // One level shift is neccessary + nShift++; + // If this object is a textbox, two level increasing needed + // (one for the shape and one for the frame) + if (auto pNextFormat = FindFrameFormat(pNextObj)) + if (SwTextBoxHelper::isTextBox(pNextFormat, RES_DRAWFRMFMT) + || SwTextBoxHelper::isTextBox(pNextFormat, RES_FLYFRMFMT)) + nShift++; + } + // Set the new z-order. + pPage->SetObjectOrdNum(pObj->GetOrdNum(), pObj->GetOrdNum() + nShift); + } + // The shape is on the right level, correct the layer of the frame + SwTextBoxHelper::DoTextBoxZOrderCorrection(pFormat); + } + GetDoc()->getIDocumentState().SetModified(); EndAllAction(); } @@ -1076,6 +1125,36 @@ void SwFEShell::SelectionToBottom( bool bBottom ) else Imp()->GetDrawView()->MovMarkedToBtm(); ::lcl_NotifyNeighbours( &rMrkList ); + + // If the selection has textbox + for(size_t i = 0; i < rMrkList.GetMarkCount(); i++) + if (auto pObj = rMrkList.GetMark(i)->GetMarkedSdrObj()) + // Get the shape of the textbox + if (auto pFormat = FindFrameFormat(pObj)) + { + // If the shape has not textframes skip. + if (!SwTextBoxHelper::isTextBoxShapeHasValidTextFrame(pFormat)) + continue; + // If has, move the shape to correct level with... + if (auto pDrwModel + = pFormat->GetDoc()->getIDocumentDrawModelAccess().GetDrawModel()) + if (auto pPage = pDrwModel->GetPage(0)) + { + const auto pNextObj = pPage->SetObjectOrdNum(pObj->GetOrdNum() - 1, pObj->GetOrdNum() - 1); + // If there is a lower object (not null)... + if (pNextObj) + { + // If the lower has no textframe, just do nothing, else move by one lower + if (auto pNextFormat = FindFrameFormat(pNextObj)) + if (SwTextBoxHelper::isTextBox(pNextFormat, RES_DRAWFRMFMT) + || SwTextBoxHelper::isTextBox(pNextFormat, RES_FLYFRMFMT)) + pPage->SetObjectOrdNum(pObj->GetOrdNum(), pObj->GetOrdNum() - 1); + } + } + // And set correct layer for the selected textbox. + SwTextBoxHelper::DoTextBoxZOrderCorrection(pFormat); + } + GetDoc()->getIDocumentState().SetModified(); EndAllAction(); } @@ -1139,6 +1218,9 @@ void SwFEShell::ChangeOpaque( SdrLayerID nLayerId ) SvxOpaqueItem aOpa( pFormat->GetOpaque() ); aOpa.SetValue( nLayerId == rIDDMA.GetHellId() ); pFormat->SetFormatAttr( aOpa ); + // If pObj has textframe, put its textframe to the right level + if (auto pTextBx = FindFrameFormat(pObj)) + SwTextBoxHelper::DoTextBoxZOrderCorrection(pTextBx); } } } |