sc/qa/uitest/calc_tests7/tdf150044.py | 108 +- sw/CppunitTest_sw_core_edit.mk | 74 + sw/CppunitTest_sw_core_text.mk | 2 sw/Module_sw.mk | 1 sw/inc/IDocumentContentOperations.hxx | 11 sw/inc/crsrsh.hxx | 13 sw/inc/editsh.hxx | 4 sw/inc/ndarr.hxx | 8 sw/inc/ndtxt.hxx | 2 sw/inc/unobaseclass.hxx | 19 sw/inc/unosett.hxx | 5 sw/inc/unotextcursor.hxx | 4 sw/inc/unotextrange.hxx | 2 sw/qa/core/edit/data/redline-hidden.fodt | 32 sw/qa/core/edit/edit.cxx | 57 + sw/qa/core/text/text.cxx | 51 sw/qa/core/uwriter.cxx | 2 sw/qa/extras/odfexport/data/tdf137199.docx |binary sw/qa/extras/odfexport/odfexport.cxx | 11 sw/qa/extras/uiwriter/data/tdf133982.docx |binary sw/qa/extras/uiwriter/data/tdf134021.docx |binary sw/qa/extras/uiwriter/data/tdf139843.odt |binary sw/qa/extras/uiwriter/data/tdf148868.odt |binary sw/qa/extras/uiwriter/data2/tdf135061.odt |binary sw/qa/extras/uiwriter/data2/tdf136452.fodt | 80 + sw/qa/extras/uiwriter/data2/tdf141175.odt |binary sw/qa/extras/uiwriter/data2/try2.fodt | 66 + sw/qa/extras/uiwriter/uiwriter.cxx | 262 ++++- sw/qa/extras/uiwriter/uiwriter2.cxx | 679 +++++++++++++ sw/qa/extras/unowriter/unowriter.cxx | 33 sw/qa/extras/ww8export/data/tdf138302_restartNumbering.odt |binary sw/qa/extras/ww8export/ww8export3.cxx | 6 sw/qa/inc/swmodeltestbase.hxx | 8 sw/qa/uitest/data/tdf39721.fodt | 42 sw/qa/uitest/writer_tests7/tdf145093.py | 34 sw/source/core/crsr/crbm.cxx | 15 sw/source/core/crsr/crsrsh.cxx | 9 sw/source/core/crsr/crstrvl.cxx | 41 sw/source/core/doc/DocumentContentOperationsManager.cxx | 110 +- sw/source/core/doc/DocumentRedlineManager.cxx | 6 sw/source/core/doc/docedt.cxx | 11 sw/source/core/doc/docnew.cxx | 27 sw/source/core/docnode/ndsect.cxx | 2 sw/source/core/docnode/ndtbl.cxx | 2 sw/source/core/docnode/node.cxx | 6 sw/source/core/docnode/nodes.cxx | 182 ++- sw/source/core/edit/acorrect.cxx | 5 sw/source/core/edit/autofmt.cxx | 29 sw/source/core/edit/edatmisc.cxx | 2 sw/source/core/edit/eddel.cxx | 9 sw/source/core/edit/edglbldc.cxx | 2 sw/source/core/edit/editsh.cxx | 4 sw/source/core/edit/edlingu.cxx | 19 sw/source/core/edit/edws.cxx | 6 sw/source/core/fields/reffld.cxx | 4 sw/source/core/frmedt/fecopy.cxx | 2 sw/source/core/inc/DocumentContentOperationsManager.hxx | 9 sw/source/core/inc/UndoDelete.hxx | 3 sw/source/core/inc/UndoRedline.hxx | 8 sw/source/core/inc/mvsave.hxx | 5 sw/source/core/inc/rootfrm.hxx | 2 sw/source/core/layout/atrfrm.cxx | 2 sw/source/core/layout/frmtool.cxx | 13 sw/source/core/layout/tabfrm.cxx | 7 sw/source/core/text/frmform.cxx | 18 sw/source/core/text/txtfrm.cxx | 1 sw/source/core/txtnode/ndtxt.cxx | 70 - sw/source/core/undo/undel.cxx | 27 sw/source/core/undo/undobj.cxx | 19 sw/source/core/undo/unins.cxx | 7 sw/source/core/undo/unredln.cxx | 38 sw/source/core/undo/untbl.cxx | 14 sw/source/core/undo/untblk.cxx | 2 sw/source/core/unocore/unobkm.cxx | 2 sw/source/core/unocore/unoobj.cxx | 10 sw/source/core/unocore/unoobj2.cxx | 9 sw/source/core/unocore/unosett.cxx | 20 sw/source/core/unocore/unotext.cxx | 22 sw/source/filter/ww8/wrtw8num.cxx | 11 sw/source/filter/ww8/wrtww8.hxx | 1 sw/source/filter/ww8/ww8atr.cxx | 7 sw/source/uibase/config/StoredChapterNumbering.cxx | 2 sw/source/uibase/dochdl/swdtflvr.cxx | 2 sw/source/uibase/docvw/edtwin.cxx | 29 sw/source/uibase/inc/wrtsh.hxx | 3 sw/source/uibase/lingu/hhcwrp.cxx | 4 sw/source/uibase/ribbar/inputwin.cxx | 4 sw/source/uibase/shells/textfld.cxx | 2 sw/source/uibase/uitest/uiobject.cxx | 25 sw/source/uibase/wrtsh/delete.cxx | 28 sw/source/uibase/wrtsh/select.cxx | 4 sw/source/uibase/wrtsh/wrtsh1.cxx | 20 xmloff/source/style/xmlnume.cxx | 19 93 files changed, 2210 insertions(+), 368 deletions(-)
New commits: commit 2404401551cb4f07710f9659e975d3573090a27a Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Wed Aug 24 13:26:34 2022 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Aug 24 13:26:34 2022 +0200 sc: disable tdf150044.save_shared_readonly_with_password.test_save_to_shared_ods Unfortunately can't figure out why it locks up without creating xFilePath. Change-Id: I6b875135e762f4323910664ab98accb68b36c1b6 diff --git a/sc/qa/uitest/calc_tests7/tdf150044.py b/sc/qa/uitest/calc_tests7/tdf150044.py index 6e42358d7b81..e3a400f617da 100644 --- a/sc/qa/uitest/calc_tests7/tdf150044.py +++ b/sc/qa/uitest/calc_tests7/tdf150044.py @@ -9,7 +9,6 @@ from uitest.framework import UITestCase from libreoffice.uno.propertyvalue import mkPropertyValues from org.libreoffice.unotest import systemPathToFileUrl -from uitest.uihelper.common import select_by_text from tempfile import TemporaryDirectory import os.path @@ -17,53 +16,70 @@ class save_shared_readonly_with_password(UITestCase): def test_save_to_shared_ods(self): + return # FIXME the test deadlocks after saving the file with TemporaryDirectory() as tempdir: xFilePath = os.path.join(tempdir, "shared_readonly_with_password_tmp.ods") - with self.ui_test.create_doc_in_start_center("calc"): - with self.ui_test.execute_dialog_through_command(".uno:ShareDocument", close_button="") as xShareDocumentDialog: - xShareCheckButton = xShareDocumentDialog.getChild("share") - xShareCheckButton.executeAction("CLICK", tuple()) - xOk = xShareDocumentDialog.getChild("ok") - # Save the document - with self.ui_test.execute_dialog_through_action(xOk, "CLICK", close_button="") as xSaveDialog: - xFileName = xSaveDialog.getChild("file_name") - xFileName.executeAction("TYPE", mkPropertyValues({"KEYCODE":"CTRL+A"})) - xFileName.executeAction("TYPE", mkPropertyValues({"KEYCODE":"BACKSPACE"})) - xFileName.executeAction("TYPE", mkPropertyValues({"TEXT": xFilePath})) - xPasswordCheckButton = xSaveDialog.getChild("password") - xPasswordCheckButton.executeAction("CLICK", tuple()) - xOpen = xSaveDialog.getChild("open") - - with self.ui_test.execute_dialog_through_action(xOpen, "CLICK") as xPasswordDialog: - xReadonly = xPasswordDialog.getChild("readonly") - xReadonly.executeAction("CLICK", tuple()) - xNewPassword = xPasswordDialog.getChild("newpassroEntry") - xNewPassword.executeAction("TYPE", mkPropertyValues({"TEXT": "password"})) - xConfirmPassword = xPasswordDialog.getChild("confirmropassEntry") - xConfirmPassword.executeAction("TYPE", mkPropertyValues({"TEXT": "password"})) - - self.ui_test.wait_until_file_is_available(xFilePath) - - with self.ui_test.execute_dialog_through_command(".uno:Open", close_button="") as xOpenDialog: - # Open document - xFileName = xOpenDialog.getChild("file_name") - xFileName.executeAction("TYPE", mkPropertyValues({"TEXT": xFilePath})) - xOpenBtn = xOpenDialog.getChild("open") - xOpenBtn.executeAction("CLICK", tuple()) - - xDialog = self.ui_test.wait_for_top_focus_window('SharedWarningDialog') - xOk = xDialog.getChild("ok") - xOk.executeAction("CLICK", tuple()) - - document = self.ui_test.get_component() - self.assertTrue(document.isReadonly()) - - with self.ui_test.execute_dialog_through_command(".uno:EditDoc") as xDialog: - # check that we have a password dialog for editing the shared document - xPassword = xDialog.getChild("newpassEntry") - xPassword.executeAction("TYPE", mkPropertyValues({"TEXT": "password"})) - - self.assertFalse(document.isReadonly()) + self.ui_test.create_doc_in_start_center("calc") + + self.ui_test.execute_dialog_through_command(".uno:ShareDocument") + xShareDocumentDialog = self.xUITest.getTopFocusWindow() + xShareCheckButton = xShareDocumentDialog.getChild("share") + xShareCheckButton.executeAction("CLICK", tuple()) + xOk = xShareDocumentDialog.getChild("ok") + + # Save the document + self.ui_test.execute_dialog_through_action(xOk, "CLICK") + xSaveDialog = self.xUITest.getTopFocusWindow() + xFileName = xSaveDialog.getChild("file_name") + xFileName.executeAction("TYPE", mkPropertyValues({"KEYCODE":"CTRL+A"})) + xFileName.executeAction("TYPE", mkPropertyValues({"KEYCODE":"BACKSPACE"})) + xFileName.executeAction("TYPE", mkPropertyValues({"TEXT": xFilePath})) + xPasswordCheckButton = xSaveDialog.getChild("password") + xPasswordCheckButton.executeAction("CLICK", tuple()) + xOpen = xSaveDialog.getChild("open") + + self.ui_test.execute_dialog_through_action(xOpen, "CLICK") + xPasswordDialog = self.xUITest.getTopFocusWindow() + xReadonly = xPasswordDialog.getChild("readonly") + xReadonly.executeAction("CLICK", tuple()) + xNewPassword = xPasswordDialog.getChild("newpassroEntry") + xNewPassword.executeAction("TYPE", mkPropertyValues({"TEXT": "password"})) + xConfirmPassword = xPasswordDialog.getChild("confirmropassEntry") + xConfirmPassword.executeAction("TYPE", mkPropertyValues({"TEXT": "password"})) + + xOKButton = xPasswordDialog.getChild("ok") + xOKButton.executeAction("CLICK", tuple()) + + while True: + if not os.path.isfile(xFilePath): + time.sleep(DEFAULT_SLEEP) + + self.ui_test.close_doc() + + self.ui_test.execute_dialog_through_command(".uno:Open") + xOpenDialog = self.xUITest.getTopFocusWindow() + # Open document + xFileName = xOpenDialog.getChild("file_name") + xFileName.executeAction("TYPE", mkPropertyValues({"TEXT": xFilePath})) + xOpenBtn = xOpenDialog.getChild("open") + xOpenBtn.executeAction("CLICK", tuple()) + + xDialog = self.ui_test.wait_for_top_focus_window('SharedWarningDialog') + xOk = xDialog.getChild("ok") + xOk.executeAction("CLICK", tuple()) + + document = self.ui_test.get_component() + self.assertTrue(document.isReadonly()) + + self.ui_test.execute_dialog_through_command(".uno:EditDoc") + xDialog = self.xUITest.getTopFocusWindow() + # check that we have a password dialog for editing the shared document + xPassword = xDialog.getChild("newpassEntry") + xPassword.executeAction("TYPE", mkPropertyValues({"TEXT": "password"})) + xOKButton = xDialog.getChild("ok") + xOKButton.executeAction("CLICK", tuple()) + + self.assertFalse(document.isReadonly()) # vim: set shiftwidth=4 softtabstop=4 expandtab: commit 41ec4c4255667089f1f28aa9f29ff2941368e482 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Fri Jul 22 19:34:12 2022 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Tue Aug 23 18:57:04 2022 +0200 sw: delete bookmark if paragraph is fully selected testTdf96479 requires inserting with absorb=true to be treated as Replace, and there is of course no string in insertTextContent(). testDeleteFlyAtCharAtStart requires setString("") to be treated as Delete. Annoyingly this requires API setString() call to be replaced with internal call, do this for SwXTextRange and SwXTextCursor which are the 2 classes typically used in practice. Change-Id: I87caa1aa11abe298cdd3d9a9bbb602e547c7b443 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/137370 Reviewed-by: Michael Stahl <michael.st...@allotropia.de> Tested-by: Jenkins (cherry picked from commit baf8d2c1c16cb3bdc4edad2560f95fea807a034f) diff --git a/sw/inc/unobaseclass.hxx b/sw/inc/unobaseclass.hxx index 289d5f0b7ee1..9f7db995de53 100644 --- a/sw/inc/unobaseclass.hxx +++ b/sw/inc/unobaseclass.hxx @@ -26,6 +26,9 @@ #include <comphelper/servicehelper.hxx> #include <cppuhelper/implbase.hxx> + +#include <o3tl/typed_flags_set.hxx> + #include <vcl/svapp.hxx> class SfxPoolItem; @@ -54,6 +57,22 @@ enum class CursorType Meta, // meta/meta-field }; +namespace sw { + +enum class DeleteAndInsertMode +{ + Default = 0, + ForceExpandHints = (1<<0), + ForceReplace = (1<<1), +}; + +} // namespace sw + +namespace o3tl +{ + template<> struct typed_flags<::sw::DeleteAndInsertMode> : is_typed_flags<::sw::DeleteAndInsertMode, 0x03> {}; +} + /* Start/EndAction or Start/EndAllAction */ diff --git a/sw/inc/unotextcursor.hxx b/sw/inc/unotextcursor.hxx index 25cdb7d102d7..4585e79e72ab 100644 --- a/sw/inc/unotextcursor.hxx +++ b/sw/inc/unotextcursor.hxx @@ -91,9 +91,7 @@ public: SwUnoCursor& GetCursor(); bool IsAtEndOfMeta() const; - void DeleteAndInsert(OUString const& rText, - const bool bForceExpandHints); - + void DeleteAndInsert(OUString const& rText, ::sw::DeleteAndInsertMode eMode); // OTextCursorHelper virtual const SwPaM* GetPaM() const override; virtual SwPaM* GetPaM() override; diff --git a/sw/inc/unotextrange.hxx b/sw/inc/unotextrange.hxx index 41a69049fc5e..36dc36e643db 100644 --- a/sw/inc/unotextrange.hxx +++ b/sw/inc/unotextrange.hxx @@ -101,7 +101,7 @@ private: //TODO: new exception type for protected content /// @throws css::uno::RuntimeException void DeleteAndInsert( - const OUString& rText, const bool bForceExpandHints); + const OUString& rText, ::sw::DeleteAndInsertMode eMode); void Invalidate(); virtual ~SwXTextRange() override; diff --git a/sw/source/core/undo/undel.cxx b/sw/source/core/undo/undel.cxx index cb819d5216c8..bee925546d75 100644 --- a/sw/source/core/undo/undel.cxx +++ b/sw/source/core/undo/undel.cxx @@ -1304,6 +1304,7 @@ void SwUndoDelete::RedoImpl(::sw::UndoRedoContext & rContext) rDoc.getIDocumentContentOperations().DelFullPara( rPam ); } else + // FIXME: this ends up calling DeleteBookmarks() on the entire rPam which deletes too many! rDoc.getIDocumentContentOperations().DeleteAndJoin(rPam, m_DeleteFlags); } diff --git a/sw/source/core/undo/undobj.cxx b/sw/source/core/undo/undobj.cxx index d611cb4a496b..098c277d9155 100644 --- a/sw/source/core/undo/undobj.cxx +++ b/sw/source/core/undo/undobj.cxx @@ -1112,7 +1112,12 @@ void SwUndoSaveContent::DelContentIndex( const SwPosition& rMark, && ( type == IDocumentMarkAccess::MarkType::TEXT_FIELDMARK || type == IDocumentMarkAccess::MarkType::CHECKBOX_FIELDMARK || type == IDocumentMarkAccess::MarkType::DROPDOWN_FIELDMARK - || type == IDocumentMarkAccess::MarkType::DATE_FIELDMARK))) + || type == IDocumentMarkAccess::MarkType::DATE_FIELDMARK)) + || (bMaybe + && !(nDelContentType & DelContentType::Replace) + && type == IDocumentMarkAccess::MarkType::BOOKMARK + && pStt->nContent == 0 // entire paragraph deleted? + && pEnd->nContent == pEnd->nNode.GetNode().GetTextNode()->Len())) { if( bMaybe ) bSavePos = true; diff --git a/sw/source/core/unocore/unoobj.cxx b/sw/source/core/unocore/unoobj.cxx index 1f8b4d02edfd..e3ac0563be12 100644 --- a/sw/source/core/unocore/unoobj.cxx +++ b/sw/source/core/unocore/unoobj.cxx @@ -765,7 +765,7 @@ SwXTextCursor::~SwXTextCursor() } void SwXTextCursor::DeleteAndInsert(const OUString& rText, - const bool bForceExpandHints) + ::sw::DeleteAndInsertMode const eMode) { auto pUnoCursor = static_cast<SwCursor*>(m_pImpl->m_pUnoCursor.get()); if (pUnoCursor) @@ -780,13 +780,15 @@ void SwXTextCursor::DeleteAndInsert(const OUString& rText, { if (pCurrent->HasMark()) { - pDoc->getIDocumentContentOperations().DeleteAndJoin(*pCurrent); + pDoc->getIDocumentContentOperations().DeleteAndJoin(*pCurrent, + // is it "delete" or "replace"? + (nTextLen != 0 || eMode & ::sw::DeleteAndInsertMode::ForceReplace) ? SwDeleteFlags::ArtificialSelection : SwDeleteFlags::Default); } if(nTextLen) { const bool bSuccess( SwUnoCursorHelper::DocInsertStringSplitCR( - *pDoc, *pCurrent, rText, bForceExpandHints ) ); + *pDoc, *pCurrent, rText, bool(eMode & ::sw::DeleteAndInsertMode::ForceExpandHints)) ); OSL_ENSURE( bSuccess, "Doc->Insert(Str) failed." ); SwUnoCursorHelper::SelectPam(*pUnoCursor, true); @@ -1754,7 +1756,7 @@ SwXTextCursor::setString(const OUString& aString) const bool bForceExpandHints( (CursorType::Meta == m_pImpl->m_eType) && dynamic_cast<SwXMeta*>(m_pImpl->m_xParentText.get()) ->CheckForOwnMemberMeta(*GetPaM(), true) ); - DeleteAndInsert(aString, bForceExpandHints); + DeleteAndInsert(aString, bForceExpandHints ? ::sw::DeleteAndInsertMode::ForceExpandHints : ::sw::DeleteAndInsertMode::Default); } uno::Any SwUnoCursorHelper::GetPropertyValue( diff --git a/sw/source/core/unocore/unoobj2.cxx b/sw/source/core/unocore/unoobj2.cxx index 502e2e74c2dd..62180c0bc230 100644 --- a/sw/source/core/unocore/unoobj2.cxx +++ b/sw/source/core/unocore/unoobj2.cxx @@ -791,7 +791,7 @@ void SwXTextRange::SetPositions(const SwPaM& rPam) } void SwXTextRange::DeleteAndInsert( - const OUString& rText, const bool bForceExpandHints) + const OUString& rText, ::sw::DeleteAndInsertMode const eMode) { if (RANGE_IS_TABLE == m_pImpl->m_eRangePosition) { @@ -807,13 +807,14 @@ void SwXTextRange::DeleteAndInsert( m_pImpl->m_rDoc.GetIDocumentUndoRedo().StartUndo(SwUndoId::INSERT, nullptr); if (aCursor.HasMark()) { - m_pImpl->m_rDoc.getIDocumentContentOperations().DeleteAndJoin(aCursor); + m_pImpl->m_rDoc.getIDocumentContentOperations().DeleteAndJoin(aCursor, + (!rText.isEmpty() || eMode & ::sw::DeleteAndInsertMode::ForceReplace) ? SwDeleteFlags::ArtificialSelection : SwDeleteFlags::Default); } if (!rText.isEmpty()) { SwUnoCursorHelper::DocInsertStringSplitCR( - m_pImpl->m_rDoc, aCursor, rText, bForceExpandHints); + m_pImpl->m_rDoc, aCursor, rText, bool(eMode & ::sw::DeleteAndInsertMode::ForceExpandHints)); SwUnoCursorHelper::SelectPam(aCursor, true); aCursor.Left(rText.getLength()); @@ -961,7 +962,7 @@ void SAL_CALL SwXTextRange::setString(const OUString& rString) { SolarMutexGuard aGuard; - DeleteAndInsert(rString, false); + DeleteAndInsert(rString, ::sw::DeleteAndInsertMode::Default); } bool SwXTextRange::GetPositions(SwPaM& rToFill) const diff --git a/sw/source/core/unocore/unotext.cxx b/sw/source/core/unocore/unotext.cxx index a4dfe5422af1..a8f91620622c 100644 --- a/sw/source/core/unocore/unotext.cxx +++ b/sw/source/core/unocore/unotext.cxx @@ -364,7 +364,8 @@ SwXText::insertString(const uno::Reference< text::XTextRange >& xTextRange, dynamic_cast<SwXTextCursor*>(pCursor) ); if (pTextCursor) { - pTextCursor->DeleteAndInsert(rString, bForceExpandHints); + pTextCursor->DeleteAndInsert(rString, ::sw::DeleteAndInsertMode::ForceReplace + | (bForceExpandHints ? ::sw::DeleteAndInsertMode::ForceExpandHints : ::sw::DeleteAndInsertMode::Default)); } else { @@ -373,7 +374,8 @@ SwXText::insertString(const uno::Reference< text::XTextRange >& xTextRange, } else { - pRange->DeleteAndInsert(rString, bForceExpandHints); + pRange->DeleteAndInsert(rString, ::sw::DeleteAndInsertMode::ForceReplace + | (bForceExpandHints ? ::sw::DeleteAndInsertMode::ForceExpandHints : ::sw::DeleteAndInsertMode::Default)); } } else @@ -605,7 +607,21 @@ SwXText::insertTextContent( if (bAbsorb && !bAttribute) { - xRange->setString(OUString()); + uno::Reference<lang::XUnoTunnel> const xRangeTunnel(xRange, uno::UNO_QUERY); + if (SwXTextRange *const pRange = ::sw::UnoTunnelGetImplementation<SwXTextRange>(xRangeTunnel)) + { + pRange->DeleteAndInsert(OUString(), ::sw::DeleteAndInsertMode::ForceReplace + | (bForceExpandHints ? ::sw::DeleteAndInsertMode::ForceExpandHints : ::sw::DeleteAndInsertMode::Default)); + } + else if (SwXTextCursor *const pCursor = dynamic_cast<SwXTextCursor*>(::sw::UnoTunnelGetImplementation<OTextCursorHelper>(xRangeTunnel))) + { + pCursor->DeleteAndInsert(OUString(), ::sw::DeleteAndInsertMode::ForceReplace + | (bForceExpandHints ? ::sw::DeleteAndInsertMode::ForceExpandHints : ::sw::DeleteAndInsertMode::Default)); + } + else + { + xRange->setString(OUString()); + } } uno::Reference< text::XTextRange > xTempRange = (bAttribute && bAbsorb) ? xRange : xRange->getStart(); commit c17bcef73f17d022352d410e841e510e75d88cc0 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Mon Aug 8 16:15:36 2022 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Tue Aug 23 18:32:35 2022 +0200 sw: fix infinite recursion in SwEditWin::GetSurroundingTextSelection() The unexpected problem of calling SwCursorShell::Pop() from there ending up calling the function itself again, with gtk3 UI, on loading forum-mso-en-4034.docx and clicking somewhere or scrolling: 466 SwEditWin::GetSurroundingTextSelection() const at sw/source/uibase/docvw/edtwin.cxx:6656 467 ImplHandleSurroundingTextRequest(vcl::Window*, rtl::OUString&, Selection&) at vcl/source/window/winproc.cxx:2487 468 ImplHandleSalSurroundingTextRequest(vcl::Window*, SalSurroundingTextRequestEvent*) at vcl/source/window/winproc.cxx:2497 469 ImplWindowFrameProc(vcl::Window*, SalEvent, void const*) at vcl/source/window/winproc.cxx:2826 470 SalFrame::CallCallback(SalEvent, void const*) const at vcl/inc/salframe.hxx:306 471 GtkSalFrame::IMHandler::signalIMRetrieveSurrounding(_GtkIMContext*, void*) at vcl/unx/gtk3/gtkframe.cxx:5707 472 _gtk_marshal_BOOLEAN__VOIDv () at /lib64/libgtk-3.so.0 473 g_signal_emit_valist () at /lib64/libgobject-2.0.so.0 474 g_signal_emit_by_name () at /lib64/libgobject-2.0.so.0 475 gtk_im_multicontext_retrieve_surrounding_cb () at /lib64/libgtk-3.so.0 476 _gtk_marshal_BOOLEAN__VOIDv () at /lib64/libgtk-3.so.0 477 g_signal_emit_valist () at /lib64/libgobject-2.0.so.0 478 g_signal_emit_by_name () at /lib64/libgobject-2.0.so.0 479 enable () at /lib64/libgtk-3.so.0 480 GtkSalFrame::IMHandler::createIMContext() at vcl/unx/gtk3/gtkframe.cxx:5187 481 GtkSalFrame::IMHandler::IMHandler(GtkSalFrame*) at vcl/unx/gtk3/gtkframe.cxx:5153 482 GtkSalFrame::SetInputContext(SalInputContext*) at vcl/unx/gtk3/gtkframe.cxx:2711 483 vcl::Window::ImplNewInputContext() () at vcl/source/window/window.cxx:1781 484 vcl::Window::SetInputContext(InputContext const&) at vcl/source/window/window.cxx:2083 485 SwView::CheckReadonlySelection() at sw/source/uibase/uiview/view.cxx:699 486 SwView::AttrChangedNotify(LinkParamNone*) at sw/source/uibase/uiview/view.cxx:510 487 SwView::LinkStubAttrChangedNotify(void*, LinkParamNone*) at sw/source/uibase/uiview/view.cxx:499 488 Link<LinkParamNone*, void>::Call(LinkParamNone*) const at include/tools/link.hxx:111 489 SwCursorShell::CallChgLnk() at sw/source/core/crsr/crsrsh.cxx:2544 490 SwCallLink::~SwCallLink() at sw/source/core/crsr/callnk.cxx:149 491 SwCursorShell::Pop(SwCursorShell::PopMode) at sw/source/core/crsr/crsrsh.cxx:2327 492 SwWrtShell::Pop(SwCursorShell::PopMode) at sw/source/uibase/wrtsh/wrtsh1.cxx:2016 493 SwEditWin::GetSurroundingTextSelection() const at sw/source/uibase/docvw/edtwin.cxx:6656 This SwCallLink looks unnecessary here, but it's triggered because it compares the state before Pop() to the state after Pop(), instead of the state before Push() to the state after Pop(). This problem could probably benefit from being solved more generally, but with 2 functions involved it gets rather ugly as it can't easily be encapsulated in SwCursorShell. (probably regression from aac9bd235e65b27faf63e64bba3ecd94837381d6) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/137987 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 581ba395222e04e43697484bef9181c877d1fd61) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/138027 Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de> (cherry picked from commit d7a427d9c869729c21285df7c32106f934f66a04) Change-Id: Ief176b54daf96da378d2e5d57f3dd5b4a0817299 diff --git a/sw/inc/crsrsh.hxx b/sw/inc/crsrsh.hxx index 4a9d8dc2497d..3a5d395733e8 100644 --- a/sw/inc/crsrsh.hxx +++ b/sw/inc/crsrsh.hxx @@ -45,6 +45,7 @@ class SfxItemSet; class SfxPoolItem; +class SwCallLink; class SwContentFrame; class SwUnoCursor; class SwFormatField; @@ -437,6 +438,7 @@ public: * stack * @return <true> if there was one on the stack, <false> otherwise */ + bool Pop(PopMode, ::std::unique_ptr<SwCallLink> pLink); bool Pop(PopMode); /* * Combine 2 Cursors. diff --git a/sw/source/core/crsr/crsrsh.cxx b/sw/source/core/crsr/crsrsh.cxx index fce0ca5219c2..f7ddd3a3f1d7 100644 --- a/sw/source/core/crsr/crsrsh.cxx +++ b/sw/source/core/crsr/crsrsh.cxx @@ -2243,7 +2243,14 @@ void SwCursorShell::Push() */ bool SwCursorShell::Pop(PopMode const eDelete) { - SwCallLink aLk( *this ); // watch Cursor-Moves; call Link if needed + ::std::unique_ptr<SwCallLink> pLink(::std::make_unique<SwCallLink>(*this)); // watch Cursor-Moves; call Link if needed + return Pop(eDelete, ::std::move(pLink)); +} + +bool SwCursorShell::Pop(PopMode const eDelete, + [[maybe_unused]] ::std::unique_ptr<SwCallLink> const pLink) +{ + assert(pLink); // parameter exists only to be deleted before return // are there any left? if (nullptr == m_pStackCursor) diff --git a/sw/source/uibase/docvw/edtwin.cxx b/sw/source/uibase/docvw/edtwin.cxx index 9dfcf20f7c83..08bfd444b27b 100644 --- a/sw/source/uibase/docvw/edtwin.cxx +++ b/sw/source/uibase/docvw/edtwin.cxx @@ -163,6 +163,9 @@ #include <sfx2/event.hxx> #include <memory> +#include "../../core/crsr/callnk.hxx" + + using namespace sw::mark; using namespace ::com::sun::star; @@ -6318,13 +6321,15 @@ Selection SwEditWin::GetSurroundingTextSelection() const // around the visible cursor. TextFrameIndex const nPos(rSh.GetCursorPointAsViewIndex()); + // store shell state *before* Push + ::std::unique_ptr<SwCallLink> pLink(::std::make_unique<SwCallLink>(rSh)); rSh.Push(); rSh.HideCursor(); rSh.GoStartSentence(); TextFrameIndex const nStartPos(rSh.GetCursorPointAsViewIndex()); - rSh.Pop(SwCursorShell::PopMode::DeleteCurrent); + rSh.Pop(SwCursorShell::PopMode::DeleteCurrent, ::std::move(pLink)); rSh.ShowCursor(); return Selection(sal_Int32(nPos - nStartPos), sal_Int32(nPos - nStartPos)); diff --git a/sw/source/uibase/inc/wrtsh.hxx b/sw/source/uibase/inc/wrtsh.hxx index bf6e9d961a13..7a653bf55490 100644 --- a/sw/source/uibase/inc/wrtsh.hxx +++ b/sw/source/uibase/inc/wrtsh.hxx @@ -140,6 +140,7 @@ public: // is there a text- or frameselection? bool HasSelection() const { return SwCursorShell::HasSelection() || IsMultiSelection() || IsSelFrameMode() || IsObjSelected(); } + bool Pop(SwCursorShell::PopMode, ::std::unique_ptr<SwCallLink> const pLink); bool Pop(SwCursorShell::PopMode = SwCursorShell::PopMode::DeleteStack); void EnterStdMode(); diff --git a/sw/source/uibase/wrtsh/wrtsh1.cxx b/sw/source/uibase/wrtsh/wrtsh1.cxx index 53e8ae7a2fbd..c240c630df3a 100644 --- a/sw/source/uibase/wrtsh/wrtsh1.cxx +++ b/sw/source/uibase/wrtsh/wrtsh1.cxx @@ -118,6 +118,9 @@ #include <comphelper/lok.hxx> #include <memory> +#include "../../core/crsr/callnk.hxx" + + using namespace sw::mark; using namespace com::sun::star; namespace { @@ -1730,7 +1733,13 @@ SwWrtShell::~SwWrtShell() bool SwWrtShell::Pop(SwCursorShell::PopMode const eDelete) { - bool bRet = SwCursorShell::Pop(eDelete); + ::std::unique_ptr<SwCallLink> pLink(::std::make_unique<SwCallLink>(*this)); + return Pop(eDelete, ::std::move(pLink)); +} + +bool SwWrtShell::Pop(SwCursorShell::PopMode const eDelete, ::std::unique_ptr<SwCallLink> pLink) +{ + bool bRet = SwCursorShell::Pop(eDelete, ::std::move(pLink)); if( bRet && IsSelection() ) { m_fnSetCursor = &SwWrtShell::SetCursorKillSel; commit 1f7143a45bd7ec89d16055879bf3f34b1b1e6cf3 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Fri Mar 4 21:53:38 2022 +0100 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Tue Aug 23 18:31:54 2022 +0200 sw_redlinehide: more issues with SwEditWin Surrounding functions .. and SwEditWinUIObject. These need to work with TextFrameIndex. Add some functions to SwCursorShell to make it possible. Reviewed-on: https://gerrit.libreoffice.org/c/core/+/131042 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 112592ac580108998a2cd99ae9bbf376c80c10d8) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/131109 Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de> (cherry picked from commit 26830d91d2f2a2d50f989fa3f36d18d55ea0eafd) Change-Id: I884c4822a0e7ecf254ea09a893762e1e6d539534 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/131625 Tested-by: Thorsten Behrens <thorsten.behr...@allotropia.de> Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de> diff --git a/sw/inc/crsrsh.hxx b/sw/inc/crsrsh.hxx index 189fd3ba5061..4a9d8dc2497d 100644 --- a/sw/inc/crsrsh.hxx +++ b/sw/inc/crsrsh.hxx @@ -26,6 +26,9 @@ #include <vcl/keycod.hxx> #include <o3tl/typed_flags_set.hxx> +#ifdef SW_DLLIMPLEMENTATION +#include <TextFrameIndex.hxx> +#endif #include "IShellCursorSupplier.hxx" #include "swdllapi.h" #include "docary.hxx" @@ -792,8 +795,12 @@ public: bool GotoINetAttr( const SwTextINetFormat& rAttr ); const SwFormatINetFormat* FindINetAttr( const OUString& rName ) const; - bool SelectText( const sal_Int32 nStart, - const sal_Int32 nEnd ); + bool SelectTextModel(sal_Int32 nStart, sal_Int32 nEnd); +#ifdef SW_DLLIMPLEMENTATION + bool SelectTextView(TextFrameIndex nStart, TextFrameIndex nEnd); + // result is only valid while cursor isn't moved! + TextFrameIndex GetCursorPointAsViewIndex() const; +#endif bool CheckTableBoxContent( const SwPosition* pPos = nullptr ); void SaveTableBoxContent( const SwPosition* pPos = nullptr ); diff --git a/sw/qa/extras/uiwriter/uiwriter.cxx b/sw/qa/extras/uiwriter/uiwriter.cxx index 79a1026e38b3..1e0b5b46114e 100644 --- a/sw/qa/extras/uiwriter/uiwriter.cxx +++ b/sw/qa/extras/uiwriter/uiwriter.cxx @@ -3825,7 +3825,7 @@ void SwUiWriterTest::testUndoDelAsCharTdf107512() CPPUNIT_ASSERT(rIDCO.InsertGraphicObject(*pShell->GetCursor(), grf, &frameSet, &grfSet)); CPPUNIT_ASSERT_EQUAL(size_t(2), pDoc->GetFlyCount(FLYCNTTYPE_GRF)); SvxCharHiddenItem hidden(true, RES_CHRATR_HIDDEN); - pShell->SelectText(1, 4); + pShell->SelectTextModel(1, 4); rIDCO.InsertPoolItem(*pShell->GetCursor(), hidden); // now we have "\1foo\1" with the "foo" hidden CPPUNIT_ASSERT(pShell->GetCursor()->GetNode().GetTextNode()->GetTextAttrForCharAt(0, RES_TXTATR_FLYCNT)); @@ -3839,7 +3839,7 @@ void SwUiWriterTest::testUndoDelAsCharTdf107512() query.ClearItem(RES_CHRATR_HIDDEN); // delete from the start - pShell->SelectText(0, 4); + pShell->SelectTextModel(0, 4); rIDCO.DeleteAndJoin(*pShell->GetCursor()); CPPUNIT_ASSERT(pShell->GetCursor()->GetNode().GetTextNode()->GetTextAttrForCharAt(0, RES_TXTATR_FLYCNT)); CPPUNIT_ASSERT_EQUAL(size_t(1), pDoc->GetFlyCount(FLYCNTTYPE_GRF)); @@ -3882,7 +3882,7 @@ void SwUiWriterTest::testUndoDelAsCharTdf107512() query.ClearItem(RES_CHRATR_HIDDEN); // delete from the end - pShell->SelectText(1, 5); + pShell->SelectTextModel(1, 5); rIDCO.DeleteAndJoin(*pShell->GetCursor()); CPPUNIT_ASSERT(pShell->GetCursor()->GetNode().GetTextNode()->GetTextAttrForCharAt(0, RES_TXTATR_FLYCNT)); CPPUNIT_ASSERT_EQUAL(size_t(1), pDoc->GetFlyCount(FLYCNTTYPE_GRF)); diff --git a/sw/source/core/crsr/crstrvl.cxx b/sw/source/core/crsr/crstrvl.cxx index 9153b8b34e85..11fb5db02df6 100644 --- a/sw/source/core/crsr/crstrvl.cxx +++ b/sw/source/core/crsr/crstrvl.cxx @@ -1930,7 +1930,7 @@ bool SwContentAtPos::IsInRTLText()const return bRet; } -bool SwCursorShell::SelectText( const sal_Int32 nStart, +bool SwCursorShell::SelectTextModel( const sal_Int32 nStart, const sal_Int32 nEnd ) { SET_CURR_SHELL( this ); @@ -1954,6 +1954,43 @@ bool SwCursorShell::SelectText( const sal_Int32 nStart, return bRet; } +TextFrameIndex SwCursorShell::GetCursorPointAsViewIndex() const +{ + SwPosition const*const pPos(GetCursor()->GetPoint()); + SwTextNode const*const pTextNode(pPos->nNode.GetNode().GetTextNode()); + assert(pTextNode); + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(pTextNode->getLayoutFrame(GetLayout()))); + assert(pFrame); + return pFrame->MapModelToViewPos(*pPos); +} + +bool SwCursorShell::SelectTextView(TextFrameIndex const nStart, + TextFrameIndex const nEnd) +{ + CurrShell aCurr( this ); + bool bRet = false; + + SwCallLink aLk( *this ); + SwCursorSaveState aSaveState( *m_pCurrentCursor ); + + SwPosition& rPos = *m_pCurrentCursor->GetPoint(); + m_pCurrentCursor->DeleteMark(); + // indexes must correspond to cursor point! + SwTextFrame const*const pFrame(static_cast<SwTextFrame const*>(m_pCurrentCursor->GetPoint()->nNode.GetNode().GetTextNode()->getLayoutFrame(GetLayout()))); + assert(pFrame); + rPos = pFrame->MapViewToModelPos(nStart); + m_pCurrentCursor->SetMark(); + rPos = pFrame->MapViewToModelPos(nEnd); + + if (!m_pCurrentCursor->IsSelOvr()) + { + UpdateCursor(); + bRet = true; + } + + return bRet; +} + bool SwCursorShell::SelectTextAttr( sal_uInt16 nWhich, bool bExpand, const SwTextAttr* pTextAttr ) @@ -1977,7 +2014,7 @@ bool SwCursorShell::SelectTextAttr( sal_uInt16 nWhich, if( pTextAttr ) { const sal_Int32* pEnd = pTextAttr->End(); - bRet = SelectText( pTextAttr->GetStart(), ( pEnd ? *pEnd : pTextAttr->GetStart() + 1 ) ); + bRet = SelectTextModel(pTextAttr->GetStart(), (pEnd ? *pEnd : pTextAttr->GetStart() + 1)); } } return bRet; diff --git a/sw/source/uibase/docvw/edtwin.cxx b/sw/source/uibase/docvw/edtwin.cxx index 12c0519ed9d8..9dfcf20f7c83 100644 --- a/sw/source/uibase/docvw/edtwin.cxx +++ b/sw/source/uibase/docvw/edtwin.cxx @@ -3700,7 +3700,7 @@ void SwEditWin::MouseButtonDown(const MouseEvent& _rMEvt) // select content of Input Field, but exclude CH_TXT_ATR_INPUTFIELDSTART // and CH_TXT_ATR_INPUTFIELDEND rSh.SttSelect(); - rSh.SelectText( aFieldAtPos.pFndTextAttr->GetStart() + 1, + rSh.SelectTextModel( aFieldAtPos.pFndTextAttr->GetStart() + 1, *(aFieldAtPos.pFndTextAttr->End()) - 1 ); } // don't reset here any longer so that, in case through MouseMove @@ -3730,8 +3730,8 @@ void SwEditWin::MouseButtonDown(const MouseEvent& _rMEvt) // select content of Input Field, but exclude CH_TXT_ATR_INPUTFIELDSTART // and CH_TXT_ATR_INPUTFIELDEND rSh.SttSelect(); - rSh.SelectText( aFieldAtPos.pFndTextAttr->GetStart() + 1, - *(aFieldAtPos.pFndTextAttr->End()) - 1 ); + rSh.SelectTextModel( aFieldAtPos.pFndTextAttr->GetStart() + 1, + *(aFieldAtPos.pFndTextAttr->End()) - 1 ); } } @@ -6316,18 +6316,18 @@ Selection SwEditWin::GetSurroundingTextSelection() const { // Return the position of the visible cursor in the sentence // around the visible cursor. - SwPosition *pPos = rSh.GetCursor()->GetPoint(); - const sal_Int32 nPos = pPos->nContent.GetIndex(); + TextFrameIndex const nPos(rSh.GetCursorPointAsViewIndex()); + rSh.Push(); rSh.HideCursor(); rSh.GoStartSentence(); - const sal_Int32 nStartPos = rSh.GetCursor()->GetPoint()->nContent.GetIndex(); + TextFrameIndex const nStartPos(rSh.GetCursorPointAsViewIndex()); rSh.Pop(SwCursorShell::PopMode::DeleteCurrent); rSh.ShowCursor(); - return Selection( nPos - nStartPos, nPos - nStartPos ); + return Selection(sal_Int32(nPos - nStartPos), sal_Int32(nPos - nStartPos)); } } diff --git a/sw/source/uibase/shells/textfld.cxx b/sw/source/uibase/shells/textfld.cxx index c65d5d65da6a..072a38ab81b5 100644 --- a/sw/source/uibase/shells/textfld.cxx +++ b/sw/source/uibase/shells/textfld.cxx @@ -199,7 +199,7 @@ void SwTextShell::ExecField(SfxRequest &rReq) SwCursorShell::GetTextFieldAtCursor(rSh.GetCursor(), true)))) { rSh.SttSelect(); - rSh.SelectText( + rSh.SelectTextModel( SwCursorShell::StartOfInputFieldAtPos( *(rSh.GetCursor()->Start()) ) + 1, SwCursorShell::EndOfInputFieldAtPos( *(rSh.GetCursor()->Start()) ) - 1 ); } diff --git a/sw/source/uibase/uitest/uiobject.cxx b/sw/source/uibase/uitest/uiobject.cxx index ecdfd68ad6a8..f44f2b5a70fb 100644 --- a/sw/source/uibase/uitest/uiobject.cxx +++ b/sw/source/uibase/uitest/uiobject.cxx @@ -13,6 +13,7 @@ #include <view.hxx> #include <wrtsh.hxx> #include <navipi.hxx> +#include <ndtxt.hxx> #include <sfx2/sidebar/Sidebar.hxx> #include <sfx2/viewfrm.hxx> @@ -89,14 +90,30 @@ void SwEditWinUIObject::execute(const OUString& rAction, { auto itr = rParameters.find("START_POS"); OUString aStartPos = itr->second; - sal_Int32 nStartPos = aStartPos.toInt32(); + TextFrameIndex const nStartPos(aStartPos.toInt32()); itr = rParameters.find("END_POS"); assert(itr != rParameters.end()); OUString aEndPos = itr->second; - sal_Int32 nEndPos = aEndPos.toInt32(); - - getWrtShell(mxEditWin).SelectText(nStartPos, nEndPos); + TextFrameIndex const nEndPos(aEndPos.toInt32()); + + auto & shell = getWrtShell(mxEditWin); + if (shell.GetCursor_()->GetPoint()->nNode.GetNode().GetTextNode()) + { + shell.Push(); + shell.MovePara(GoCurrPara, fnParaEnd); + TextFrameIndex const len(shell.GetCursorPointAsViewIndex()); + shell.Pop(SwCursorShell::PopMode::DeleteCurrent); + SAL_WARN_IF( + sal_Int32(nStartPos) < 0 || nStartPos > len || sal_Int32(nEndPos) < 0 || nEndPos > len, "sw.ui", + "SELECT START/END_POS " << sal_Int32(nStartPos) << ".." << sal_Int32(nEndPos) << " outside 0.." << sal_Int32(len)); + shell.SelectTextView( + std::clamp(nStartPos, TextFrameIndex(0), len), std::clamp(nEndPos, TextFrameIndex(0), len)); + } + else + { + SAL_WARN("sw.ui", "SELECT without SwTextNode"); + } } } else if (rAction == "SIDEBAR") commit 6538e62b4aee3df747dd47a8ce77b86537d9783c Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Fri Mar 4 20:05:03 2022 +0100 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Tue Aug 23 18:31:47 2022 +0200 sw_redlinehide: fix crashes in SwEditWin Surrounding functions These foolish functions write into the shell cursor! But the shell cursor's node isn't necessarily the same as before with merged paragraphs, so could crash with out of bounds indexes. Better use Push()/Pop(). Reviewed-on: https://gerrit.libreoffice.org/c/core/+/131041 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit aac9bd235e65b27faf63e64bba3ecd94837381d6) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/131127 Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de> (cherry picked from commit 08690fe5c4e5028ccaebc80145be650caf62ea76) Change-Id: I4fd0e2aa915b6c5448772a2c766848607bbf5aaa Reviewed-on: https://gerrit.libreoffice.org/c/core/+/131624 Tested-by: Thorsten Behrens <thorsten.behr...@allotropia.de> Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de> diff --git a/sw/source/uibase/docvw/edtwin.cxx b/sw/source/uibase/docvw/edtwin.cxx index 20f5761f1361..12c0519ed9d8 100644 --- a/sw/source/uibase/docvw/edtwin.cxx +++ b/sw/source/uibase/docvw/edtwin.cxx @@ -6287,8 +6287,7 @@ OUString SwEditWin::GetSurroundingText() const rSh.GetSelectedText( sReturn, ParaBreakType::ToOnlyCR ); else if( !rSh.HasSelection() ) { - SwPosition *pPos = rSh.GetCursor()->GetPoint(); - const sal_Int32 nPos = pPos->nContent.GetIndex(); + rSh.Push(); // get the sentence around the cursor rSh.HideCursor(); @@ -6297,8 +6296,7 @@ OUString SwEditWin::GetSurroundingText() const rSh.GoEndSentence(); rSh.GetSelectedText( sReturn, ParaBreakType::ToOnlyCR ); - pPos->nContent = nPos; - rSh.ClearMark(); + rSh.Pop(SwCursorShell::PopMode::DeleteCurrent); rSh.HideCursor(); } @@ -6320,13 +6318,13 @@ Selection SwEditWin::GetSurroundingTextSelection() const // around the visible cursor. SwPosition *pPos = rSh.GetCursor()->GetPoint(); const sal_Int32 nPos = pPos->nContent.GetIndex(); + rSh.Push(); rSh.HideCursor(); rSh.GoStartSentence(); const sal_Int32 nStartPos = rSh.GetCursor()->GetPoint()->nContent.GetIndex(); - pPos->nContent = nPos; - rSh.ClearMark(); + rSh.Pop(SwCursorShell::PopMode::DeleteCurrent); rSh.ShowCursor(); return Selection( nPos - nStartPos, nPos - nStartPos ); commit 7b7c622f544237a4a60c2a97b4a2c2fbef204ad1 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Wed Jul 27 16:06:05 2022 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Tue Aug 23 18:19:55 2022 +0200 sw: SolarMutexGuard missing in SwXBookmark::setPropertyValue() Change-Id: I41187b02e6b0545529e2a2c5b07da671eae89079 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/137506 Reviewed-by: Michael Stahl <michael.st...@allotropia.de> Tested-by: Jenkins (cherry picked from commit c7a76952b6fa0e6688028047726ac794fdd5cca3) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/137519 Tested-by: Michael Stahl <michael.st...@allotropia.de> diff --git a/sw/source/core/unocore/unobkm.cxx b/sw/source/core/unocore/unobkm.cxx index ddeaccf1966b..a4d719016f68 100644 --- a/sw/source/core/unocore/unobkm.cxx +++ b/sw/source/core/unocore/unobkm.cxx @@ -410,6 +410,8 @@ void SAL_CALL SwXBookmark::setPropertyValue(const OUString& PropertyName, const uno::Any& rValue) { + SolarMutexGuard g; + if (PropertyName == UNO_NAME_BOOKMARK_HIDDEN) { bool bNewValue = false; commit a4ea1f361288086e1137ac91e05fefa7317bd023 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Wed Jun 15 15:06:59 2022 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Tue Aug 23 18:16:38 2022 +0200 tdf#135976 sw: preserve flys on backspace/delete with redlining enabled This is a continuation of commit 85376a02348810812d515ee72140dbf56f2b6040 for the case when redlining is turned on. Also try to restore the anchors in SwUndoRedlineDelete. (regression from commit 3345feb67f2c49a1b76639965b56968e1c5f03ee) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135909 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 932a8efce878547bfd81521d0cf1ddfe8dc33ec6) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135968 Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de> (cherry picked from commit 03834f003c56e6646e3b274c418acd4fc344fd8e) sw: fix odd m_bCanGroup check in SwUndoRedlineDelete This looks like copypasta, presumably both flags must be true to allow grouping. Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135908 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit f31f11f3222933dbc96dc672e6fa52233cda12be) Change-Id: I4199f5755398d469a606618c037ad9756cb7aeba Reviewed-on: https://gerrit.libreoffice.org/c/core/+/136223 Tested-by: Thorsten Behrens <thorsten.behr...@allotropia.de> Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de> diff --git a/sw/qa/extras/uiwriter/uiwriter2.cxx b/sw/qa/extras/uiwriter/uiwriter2.cxx index 284deb537640..f2a358d46b25 100644 --- a/sw/qa/extras/uiwriter/uiwriter2.cxx +++ b/sw/qa/extras/uiwriter/uiwriter2.cxx @@ -28,6 +28,7 @@ #include <wrtsh.hxx> #include <IDocumentRedlineAccess.hxx> #include <flyfrm.hxx> +#include <pagefrm.hxx> #include <fmtanchr.hxx> #include <UndoManager.hxx> #include <sortedobjs.hxx> @@ -958,6 +959,100 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testTdf139982) CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetFlyCount(FLYCNTTYPE_FRM)); } +CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testTdf135976) +{ + SwDoc* const pDoc = createDoc(); + SwWrtShell* const pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + + pWrtShell->Insert("foobar"); + + pWrtShell->Left(CRSR_SKIP_CHARS, /*bSelect=*/false, 2, /*bBasicCall=*/false); + SwFormatAnchor anchor(RndStdIds::FLY_AT_CHAR); + anchor.SetAnchor(pWrtShell->GetCursor()->GetPoint()); + SfxItemSet flySet(pDoc->GetAttrPool(), svl::Items<RES_ANCHOR, RES_ANCHOR>{}); + flySet.Put(anchor); + SwFrameFormat const* pFly = pWrtShell->NewFlyFrame(flySet, /*bAnchValid=*/true); + CPPUNIT_ASSERT(pFly != nullptr); + + // turn on redlining and show changes + pDoc->getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::On | RedlineFlags::ShowDelete + | RedlineFlags::ShowInsert); + lcl_dispatchCommand(mxComponent, ".uno:ShowTrackedChanges", {}); + CPPUNIT_ASSERT_MESSAGE("redlining should be on", + pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + CPPUNIT_ASSERT_MESSAGE( + "redlines should be visible", + IDocumentRedlineAccess::IsShowChanges(pDoc->getIDocumentRedlineAccess().GetRedlineFlags())); + CPPUNIT_ASSERT(pWrtShell->GetLayout()->IsHideRedlines()); + + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetFlyCount(FLYCNTTYPE_FRM)); + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetLayout()->GetLastPage()->GetSortedObjs()->size()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pFly->GetAnchor().GetContentAnchor()->nContent.GetIndex()); + + pWrtShell->UnSelectFrame(); + pWrtShell->SttEndDoc(/*bStart=*/false); + pWrtShell->Left(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false); + + pWrtShell->DelLeft(); + pWrtShell->DelLeft(); + + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetFlyCount(FLYCNTTYPE_FRM)); + // the problem was that the fly was deleted from the layout + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetLayout()->GetLastPage()->GetSortedObjs()->size()); + // check that the anchor was moved outside the redline + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), pFly->GetAnchor().GetContentAnchor()->nContent.GetIndex()); + + pWrtShell->Undo(2); + + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetFlyCount(FLYCNTTYPE_FRM)); + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetLayout()->GetLastPage()->GetSortedObjs()->size()); + // check that the anchor was restored + CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pFly->GetAnchor().GetContentAnchor()->nContent.GetIndex()); + + pWrtShell->Redo(2); + + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetFlyCount(FLYCNTTYPE_FRM)); + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetLayout()->GetLastPage()->GetSortedObjs()->size()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(3), pFly->GetAnchor().GetContentAnchor()->nContent.GetIndex()); + + pWrtShell->Undo(2); + + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetFlyCount(FLYCNTTYPE_FRM)); + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetLayout()->GetLastPage()->GetSortedObjs()->size()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pFly->GetAnchor().GetContentAnchor()->nContent.GetIndex()); + + // now again in the other direction: + + pWrtShell->SttEndDoc(/*bStart=*/false); + pWrtShell->Left(CRSR_SKIP_CHARS, /*bSelect=*/false, 3, /*bBasicCall=*/false); + + pWrtShell->DelRight(); + pWrtShell->DelRight(); + + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetFlyCount(FLYCNTTYPE_FRM)); + // the problem was that the fly was deleted from the layout + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetLayout()->GetLastPage()->GetSortedObjs()->size()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(5), pFly->GetAnchor().GetContentAnchor()->nContent.GetIndex()); + + pWrtShell->Undo(2); + + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetFlyCount(FLYCNTTYPE_FRM)); + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetLayout()->GetLastPage()->GetSortedObjs()->size()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pFly->GetAnchor().GetContentAnchor()->nContent.GetIndex()); + + pWrtShell->Redo(2); + + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetFlyCount(FLYCNTTYPE_FRM)); + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetLayout()->GetLastPage()->GetSortedObjs()->size()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(5), pFly->GetAnchor().GetContentAnchor()->nContent.GetIndex()); + + pWrtShell->Undo(2); + + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetFlyCount(FLYCNTTYPE_FRM)); + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetLayout()->GetLastPage()->GetSortedObjs()->size()); + CPPUNIT_ASSERT_EQUAL(sal_Int32(4), pFly->GetAnchor().GetContentAnchor()->nContent.GetIndex()); +} + CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testTdf54819) { load(DATA_DIRECTORY, "tdf54819.fodt"); diff --git a/sw/source/core/doc/DocumentContentOperationsManager.cxx b/sw/source/core/doc/DocumentContentOperationsManager.cxx index b6dc4158b936..315cf5cdeefa 100644 --- a/sw/source/core/doc/DocumentContentOperationsManager.cxx +++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx @@ -3915,7 +3915,7 @@ DocumentContentOperationsManager::~DocumentContentOperationsManager() } //Private methods -bool DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl(SwPaM & rPam, SwDeleteFlags const /*flags*/, const bool) +bool DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl(SwPaM & rPam, SwDeleteFlags const flags, const bool) { assert(m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()); @@ -3995,7 +3995,7 @@ bool DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl(SwPaM & rPam { assert(pRedline->HasValidRange()); undos.emplace_back(std::make_unique<SwUndoRedlineDelete>( - *pRedline, SwUndoId::DELETE)); + *pRedline, SwUndoId::DELETE, flags)); } const SwRewriter aRewriter = undos.front()->GetRewriter(); // can only group a single undo action diff --git a/sw/source/core/doc/docedt.cxx b/sw/source/core/doc/docedt.cxx index 398b5ae1a065..d6072b4b3725 100644 --- a/sw/source/core/doc/docedt.cxx +++ b/sw/source/core/doc/docedt.cxx @@ -28,6 +28,7 @@ #include <mdiexp.hxx> #include <mvsave.hxx> #include <redline.hxx> +#include <rolbck.hxx> #include <rootfrm.hxx> #include <splargs.hxx> #include <swcrsr.hxx> @@ -130,7 +131,7 @@ void SaveFlyInRange( const SwNodeRange& rRg, SaveFlyArr& rArr ) } void SaveFlyInRange( const SwPaM& rPam, const SwPosition& rInsPos, - SaveFlyArr& rArr, bool bMoveAllFlys ) + SaveFlyArr& rArr, bool bMoveAllFlys, SwHistory *const pHistory) { SwFrameFormats& rFormats = *rPam.GetPoint()->nNode.GetNode().GetDoc()->GetSpzFrameFormats(); SwFrameFormat* pFormat; @@ -176,6 +177,10 @@ void SaveFlyInRange( const SwPaM& rPam, const SwPosition& rInsPos, || (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId() && (bInsPos = (rInsPos == *pAPos)))) { + if (pHistory) + { + pHistory->AddChangeFlyAnchor(*pFormat); + } SaveFly aSave( pAPos->nNode.GetIndex() - rSttNdIdx.GetIndex(), (RndStdIds::FLY_AT_CHAR == pAnchor->GetAnchorId()) ? (pAPos->nNode == rSttNdIdx) diff --git a/sw/source/core/inc/UndoRedline.hxx b/sw/source/core/inc/UndoRedline.hxx index 38ecd86314cb..ada3c34fad7f 100644 --- a/sw/source/core/inc/UndoRedline.hxx +++ b/sw/source/core/inc/UndoRedline.hxx @@ -22,6 +22,7 @@ #include <memory> #include <undobj.hxx> +#include <IDocumentContentOperations.hxx> struct SwSortOptions; class SwRangeRedline; @@ -52,17 +53,22 @@ public: class SwUndoRedlineDelete : public SwUndoRedline { +private: + std::unique_ptr<SwHistory> m_pHistory; ///< for moved fly anchors + // bool bCanGroup : 1; bool bIsDelim : 1; bool bIsBackspace : 1; OUString m_sRedlineText; + void InitHistory(SwPaM const& rRange); + virtual void UndoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) override; virtual void RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) override; public: - SwUndoRedlineDelete( const SwPaM& rRange, SwUndoId nUserId ); + SwUndoRedlineDelete(const SwPaM& rRange, SwUndoId nUserId, SwDeleteFlags flags = SwDeleteFlags::Default); virtual SwRewriter GetRewriter() const override; bool CanGrouping( const SwUndoRedlineDelete& rPrev ); diff --git a/sw/source/core/inc/mvsave.hxx b/sw/source/core/inc/mvsave.hxx index d6cde5520ecb..5b84b5e10819 100644 --- a/sw/source/core/inc/mvsave.hxx +++ b/sw/source/core/inc/mvsave.hxx @@ -34,6 +34,7 @@ class SwDoc; class SwFormatAnchor; class SwFrameFormat; class SwIndex; +class SwHistory; class SwNodeIndex; class SwNodeRange; class SwPaM; @@ -119,7 +120,7 @@ void RestFlyInRange( SaveFlyArr& rArr, const SwPosition& rSttIdx, const SwNodeIndex* pInsPos, bool isForceToStartPos = false); void SaveFlyInRange( const SwNodeRange& rRg, SaveFlyArr& rArr ); void SaveFlyInRange( const SwPaM& rPam, const SwPosition& rInsPos, - SaveFlyArr& rArr, bool bMoveAllFlys ); + SaveFlyArr& rArr, bool bMoveAllFlys, SwHistory * pHistory = nullptr); void DelFlyInRange( const SwNodeIndex& rMkNdIdx, const SwNodeIndex& rPtNdIdx, diff --git a/sw/source/core/undo/unredln.cxx b/sw/source/core/undo/unredln.cxx index 117a7992ff50..c66d8eed133e 100644 --- a/sw/source/core/undo/unredln.cxx +++ b/sw/source/core/undo/unredln.cxx @@ -26,6 +26,8 @@ #include <pam.hxx> #include <ndtxt.hxx> #include <txtfrm.hxx> +#include <mvsave.hxx> +#include <rolbck.hxx> #include <UndoCore.hxx> #include <UndoDelete.hxx> #include <strings.hrc> @@ -153,7 +155,8 @@ void SwUndoRedline::RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any); } -SwUndoRedlineDelete::SwUndoRedlineDelete( const SwPaM& rRange, SwUndoId nUsrId ) +SwUndoRedlineDelete::SwUndoRedlineDelete( + const SwPaM& rRange, SwUndoId const nUsrId, SwDeleteFlags const flags) : SwUndoRedline( nUsrId != SwUndoId::EMPTY ? nUsrId : SwUndoId::DELETE, rRange ), bCanGroup( false ), bIsDelim( false ), bIsBackspace( false ) { @@ -174,6 +177,24 @@ SwUndoRedlineDelete::SwUndoRedlineDelete( const SwPaM& rRange, SwUndoId nUsrId ) } m_bCacheComment = false; + if (flags & SwDeleteFlags::ArtificialSelection) + { + InitHistory(rRange); + } +} + +void SwUndoRedlineDelete::InitHistory(SwPaM const& rRedline) +{ + m_pHistory.reset(new SwHistory); + // try to rely on direction of rPam here so it works for + // backspacing/deleting consecutive characters + SaveFlyArr flys; + SaveFlyInRange(rRedline, *rRedline.GetMark(), flys, false, m_pHistory.get()); + RestFlyInRange(flys, *rRedline.GetPoint(), &rRedline.GetPoint()->nNode, true); + if (m_pHistory->Count()) + { + bCanGroup = false; // how to group history? + } } // bit of a hack, replace everything... @@ -197,12 +218,21 @@ void SwUndoRedlineDelete::SetRedlineText(const OUString & rText) void SwUndoRedlineDelete::UndoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) { rDoc.getIDocumentRedlineAccess().DeleteRedline(rPam, true, RedlineType::Any); + if (m_pHistory) + { + m_pHistory->TmpRollback(&rDoc, 0); + } } void SwUndoRedlineDelete::RedoRedlineImpl(SwDoc & rDoc, SwPaM & rPam) { if (rPam.GetPoint() != rPam.GetMark()) { + if (m_pHistory) // if it was created before, it must be recreated now + { + rPam.Normalize(bIsBackspace); // to check the correct edge + InitHistory(rPam); + } rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline(*mpRedlData, rPam), false ); } sw::UpdateFramesForAddDeleteRedline(rDoc, rPam); @@ -212,7 +242,7 @@ bool SwUndoRedlineDelete::CanGrouping( const SwUndoRedlineDelete& rNext ) { bool bRet = false; if( SwUndoId::DELETE == mnUserId && mnUserId == rNext.mnUserId && - bCanGroup == rNext.bCanGroup && + bCanGroup && rNext.bCanGroup && bIsDelim == rNext.bIsDelim && bIsBackspace == rNext.bIsBackspace && m_nSttNode == m_nEndNode && commit e4d6b7b76a399c462ffd3dbf8efe8bab713bdbf6 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Tue Jun 14 17:19:23 2022 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Tue Aug 23 18:16:08 2022 +0200 sw_redlinehide: skip unnecessary updates when undoing redlined delete When reproducing tdf#135976 and then Undo, an UAF crash happens here: assert(!pFrame->GetDrawObjs() || !pObjs->Contains(*pObj)); The pObjs was actually deleted and then re-created, because the pObj was removed from the frame and added again to the same frame. This is a bit unexpected, so prevent it by taking a shortcut in the caller UpdateFramesForRemoveDeleteRedline() to insert a check that had been removed in commit 14e87a4b15d31a34e6053f6194688f3aa23af991. If the rPam is inside a single node, the sw::RedlineUnDelText hint that was sent to the SwTextFrame should be sufficient to update it and the rest of the code in the loop that deals with newly split paragraph can be skipped. Change-Id: I5f36eb91bc20003887ee0bad03ea4a6e67135de9 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135907 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit cf9a16caf5012d65b2a45a5525e36e40585dd35c) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135892 Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de> (cherry picked from commit 918f435d48de3f29814f187c8621c1a564c5b835) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/136222 Tested-by: Thorsten Behrens <thorsten.behr...@allotropia.de> diff --git a/sw/source/core/doc/DocumentRedlineManager.cxx b/sw/source/core/doc/DocumentRedlineManager.cxx index 8ae32f266bdb..e11c1c6f2c4b 100644 --- a/sw/source/core/doc/DocumentRedlineManager.cxx +++ b/sw/source/core/doc/DocumentRedlineManager.cxx @@ -283,6 +283,12 @@ void UpdateFramesForRemoveDeleteRedline(SwDoc & rDoc, SwPaM const& rPam) break; } + // no nodes can be unmerged by this - skip MakeFrames() etc. + if (rPam.GetPoint()->nNode == rPam.GetMark()->nNode) + { + break; // continue with AppendAllObjs() + } + // first, call CheckParaRedlineMerge on the first paragraph, // to init flag on new merge range (if any) + 1st node post the merge auto eMode(sw::FrameMode::Existing); commit 8fa5f170f206793b9a6c98ea04df554fe422d186 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Fri Jun 10 18:25:05 2022 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Tue Aug 23 18:15:19 2022 +0200 tdf#148868 sw: handle selection then Insert similar to Replace ... if the selection is inside one paragraph, to avoid deleting flys anchored in the selection. From a code point of view it's a bit inconsistent to do this, but from user point of view there are some ways to conveniently create a selection such as by double clicking a word. (see also tdf#133957) Also in SwWrtShell::AutoCorrect(), which oddly enough might get called with a selection from textsh*.cxx, at least in theory. Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135606 Tested-by: Michael Stahl <michael.st...@allotropia.de> Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit d72bee64a97650507d042f17846b6fc427b8434c) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135839 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> (cherry picked from commit 67eec140556edb42280def88e987448aaa221c5e) tdf#148868: sw_uiwriter3: Add unittest Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135827 Tested-by: Jenkins Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> (cherry picked from commit 4e8295638e68295d73b49ddb80e23c3509a49b3e) Change-Id: I8cf9459e5a7ec7754ce8fe323cd158c7e84a5c93 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/136221 Tested-by: Michael Stahl <michael.st...@allotropia.de> Reviewed-by: Michael Stahl <michael.st...@allotropia.de> diff --git a/sw/qa/extras/uiwriter/data/tdf148868.odt b/sw/qa/extras/uiwriter/data/tdf148868.odt new file mode 100644 index 000000000000..7ebf68a82ea3 Binary files /dev/null and b/sw/qa/extras/uiwriter/data/tdf148868.odt differ diff --git a/sw/qa/extras/uiwriter/uiwriter.cxx b/sw/qa/extras/uiwriter/uiwriter.cxx index 355fa17b3fff..79a1026e38b3 100644 --- a/sw/qa/extras/uiwriter/uiwriter.cxx +++ b/sw/qa/extras/uiwriter/uiwriter.cxx @@ -7754,6 +7754,25 @@ void SwUiWriterTest::testTdf38394() CPPUNIT_ASSERT_EQUAL(sReplaced, static_cast<SwTextNode*>(pDoc->GetNodes()[nIndex])->GetText()); } +CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf148868) +{ + SwDoc* pDoc = createDoc("tdf148868.odt"); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + + CPPUNIT_ASSERT_EQUAL(1, getShapes()); + CPPUNIT_ASSERT_EQUAL(1, getPages()); + + pWrtShell->EndPg(/*bSelect=*/false); + pWrtShell->Left(CRSR_SKIP_CHARS, /*bSelect=*/true, 5, /*bBasicCall=*/false); + pWrtShell->Insert("X"); + + // Without the fix in place, this test would have failed with + // - Expected: 1 + // - Actual : 0 + CPPUNIT_ASSERT_EQUAL(1, getShapes()); + CPPUNIT_ASSERT_EQUAL(1, getPages()); +} + CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf134021) { load(DATA_DIRECTORY, "tdf134021.docx"); diff --git a/sw/source/uibase/inc/wrtsh.hxx b/sw/source/uibase/inc/wrtsh.hxx index 6ec17d5b0e78..bf6e9d961a13 100644 --- a/sw/source/uibase/inc/wrtsh.hxx +++ b/sw/source/uibase/inc/wrtsh.hxx @@ -279,7 +279,7 @@ typedef bool (SwWrtShell:: *FNSimpleMove)(); bool DelLeft(); // also deletes the frame or sets the cursor in the frame when bDelFrame == false - bool DelRight(); + bool DelRight(bool isReplaceHeuristic = false); void DelToEndOfPara(); void DelToStartOfPara(); bool DelToEndOfSentence(); diff --git a/sw/source/uibase/wrtsh/delete.cxx b/sw/source/uibase/wrtsh/delete.cxx index b71d1b112404..0a8a46d344ac 100644 --- a/sw/source/uibase/wrtsh/delete.cxx +++ b/sw/source/uibase/wrtsh/delete.cxx @@ -288,7 +288,7 @@ bool SwWrtShell::DelLeft() return bRet; } -bool SwWrtShell::DelRight() +bool SwWrtShell::DelRight(bool const isReplaceHeuristic) { // Will be or'ed, if a tableselection exists; // will here be implemented on SelectionType::Table @@ -315,7 +315,7 @@ bool SwWrtShell::DelRight() { SwActContext aActContext(this); ResetCursorStack(); - Delete(false); + Delete(isReplaceHeuristic); UpdateAttr(); } if( IsBlockMode() ) diff --git a/sw/source/uibase/wrtsh/wrtsh1.cxx b/sw/source/uibase/wrtsh/wrtsh1.cxx index 0840ed3cd407..53e8ae7a2fbd 100644 --- a/sw/source/uibase/wrtsh/wrtsh1.cxx +++ b/sw/source/uibase/wrtsh/wrtsh1.cxx @@ -244,7 +244,8 @@ void SwWrtShell::Insert( const OUString &rStr ) StartUndo(SwUndoId::REPLACE, &aRewriter); bStarted = true; Push(); - bDeleted = DelRight(); + // let's interpret a selection within the same node as "replace" + bDeleted = DelRight(GetCursor()->GetPoint()->nNode == GetCursor()->GetMark()->nNode); Pop(SwCursorShell::PopMode::DeleteCurrent); // Restore selection (if tracking changes) NormalizePam(false); // tdf#127635 put point at the end of deletion ClearMark(); @@ -1667,7 +1668,7 @@ void SwWrtShell::AutoCorrect( SvxAutoCorrect& rACorr, sal_Unicode cChar ) StartUndo( SwUndoId::REPLACE, &aRewriter ); bStarted = true; - DelRight(); + DelRight(true); } SwEditShell::AutoCorrect( rACorr, IsInsMode(), cChar ); commit f85a5b4d5cafc80e01e2891cb1427ee94abe1d41 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Fri Jun 10 16:26:40 2022 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Tue Aug 23 18:14:49 2022 +0200 tdf#139982 sw: preserve flys in Replace with redlining enabled The problem is that there isn't a redline type "Replace" so it's represented as Delete+Insert. To prevent the flys anchored in the text from being deleted, move the anchors to the point between the old (deleted) and new (inserted) text. (regression from commit 28b77c89dfcafae82cf2a6d85731b643ff9290e5) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135604 Tested-by: Michael Stahl <michael.st...@allotropia.de> Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 646c6ddd91a98afddf914e3889cb269fc814c060) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135737 Tested-by: Jenkins Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de> (cherry picked from commit b7ab83bd96c70932c2223c8d0b3bc0f24327cef2) Change-Id: Ib600c9dbfb9421917e4b8d61195c48cf0b364f06 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/136220 Tested-by: Michael Stahl <michael.st...@allotropia.de> Reviewed-by: Michael Stahl <michael.st...@allotropia.de> diff --git a/sw/qa/extras/uiwriter/uiwriter2.cxx b/sw/qa/extras/uiwriter/uiwriter2.cxx index f742288bf2db..284deb537640 100644 --- a/sw/qa/extras/uiwriter/uiwriter2.cxx +++ b/sw/qa/extras/uiwriter/uiwriter2.cxx @@ -908,6 +908,56 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testTdf140007) pDoc->GetNodes()[SwNodeOffset(11)]->GetTextNode()->GetText()); } +CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testTdf139982) +{ + SwDoc* const pDoc = createDoc(); + SwWrtShell* const pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + + // turn on redlining and show changes + pDoc->getIDocumentRedlineAccess().SetRedlineFlags(RedlineFlags::On | RedlineFlags::ShowDelete + | RedlineFlags::ShowInsert); + CPPUNIT_ASSERT_MESSAGE("redlining should be on", + pDoc->getIDocumentRedlineAccess().IsRedlineOn()); + CPPUNIT_ASSERT_MESSAGE( + "redlines should be visible", + IDocumentRedlineAccess::IsShowChanges(pDoc->getIDocumentRedlineAccess().GetRedlineFlags())); + + pWrtShell->Insert("helloo"); + + pWrtShell->Left(CRSR_SKIP_CHARS, /*bSelect=*/false, 1, /*bBasicCall=*/false); + { + SwFormatAnchor anchor(RndStdIds::FLY_AT_CHAR); + anchor.SetAnchor(pWrtShell->GetCursor()->GetPoint()); + SfxItemSet flySet(pDoc->GetAttrPool(), svl::Items<RES_ANCHOR, RES_ANCHOR>{}); + flySet.Put(anchor); + SwFrameFormat const* pFly = pWrtShell->NewFlyFrame(flySet, /*bAnchValid=*/true); + CPPUNIT_ASSERT(pFly != nullptr); + } + + pWrtShell->SttEndDoc(true); + pWrtShell->EndPara(/*bSelect=*/true); + + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetFlyCount(FLYCNTTYPE_FRM)); + + pWrtShell->Replace("hello", true); + + // the problem was that a redline delete with the same author as redline + // insert has its text deleted immediately, including anchored flys. + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetFlyCount(FLYCNTTYPE_FRM)); + + pWrtShell->Undo(); + + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetFlyCount(FLYCNTTYPE_FRM)); + + pWrtShell->Redo(); + + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetFlyCount(FLYCNTTYPE_FRM)); + + pWrtShell->Undo(); + + CPPUNIT_ASSERT_EQUAL(size_t(1), pWrtShell->GetFlyCount(FLYCNTTYPE_FRM)); +} + CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testTdf54819) { load(DATA_DIRECTORY, "tdf54819.fodt"); diff --git a/sw/source/core/doc/DocumentContentOperationsManager.cxx b/sw/source/core/doc/DocumentContentOperationsManager.cxx index 2db40c2e5d57..b6dc4158b936 100644 --- a/sw/source/core/doc/DocumentContentOperationsManager.cxx +++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx @@ -4421,6 +4421,14 @@ bool DocumentContentOperationsManager::ReplaceRangeImpl( SwPaM& rPam, const OUSt InsertItemSet( aTmpRange, aSet ); } + // tdf#139982: Appending the redline may immediately delete flys + // anchored in the previous text if it's inside an insert redline. + // Also flys will be deleted if the redline is accepted. Move them + // to the position between the previous text and the new text, + // there the chance of surviving both accept and reject is best. + SaveFlyArr flys; + SaveFlyInRange(aDelPam, *aDelPam.End(), flys, false); + if (m_rDoc.GetIDocumentUndoRedo().DoesUndo()) { m_rDoc.GetIDocumentUndoRedo().AppendUndo( @@ -4431,6 +4439,7 @@ bool DocumentContentOperationsManager::ReplaceRangeImpl( SwPaM& rPam, const OUSt pCursor->SetMark(); *pCursor->GetPoint() = *aDelPam.GetPoint(); m_rDoc.getIDocumentRedlineAccess().AppendRedline( new SwRangeRedline( RedlineType::Delete, aDelPam ), true); + RestFlyInRange(flys, *aDelPam.End(), &aDelPam.End()->nNode, true); sw::UpdateFramesForAddDeleteRedline(m_rDoc, *pCursor); *rPam.GetMark() = *aDelPam.GetMark(); diff --git a/sw/source/core/doc/docedt.cxx b/sw/source/core/doc/docedt.cxx index 1b93a7a56a78..398b5ae1a065 100644 --- a/sw/source/core/doc/docedt.cxx +++ b/sw/source/core/doc/docedt.cxx @@ -48,7 +48,7 @@ using namespace ::com::sun::star::i18n; void RestFlyInRange( SaveFlyArr & rArr, const SwPosition& rStartPos, - const SwNodeIndex* pInsertPos ) + const SwNodeIndex* pInsertPos, bool const isForceToStartPos) { SwPosition aPos(rStartPos); for(const SaveFly & rSave : rArr) @@ -57,7 +57,7 @@ void RestFlyInRange( SaveFlyArr & rArr, const SwPosition& rStartPos, SwFrameFormat* pFormat = rSave.pFrameFormat; SwFormatAnchor aAnchor( pFormat->GetAnchor() ); - if (rSave.isAtInsertNode) + if (rSave.isAtInsertNode || isForceToStartPos) { if( pInsertPos != nullptr ) { diff --git a/sw/source/core/inc/mvsave.hxx b/sw/source/core/inc/mvsave.hxx index c472b6f7bc1a..d6cde5520ecb 100644 --- a/sw/source/core/inc/mvsave.hxx +++ b/sw/source/core/inc/mvsave.hxx @@ -116,7 +116,7 @@ struct SaveFly typedef std::deque< SaveFly > SaveFlyArr; void RestFlyInRange( SaveFlyArr& rArr, const SwPosition& rSttIdx, - const SwNodeIndex* pInsPos ); + const SwNodeIndex* pInsPos, bool isForceToStartPos = false); void SaveFlyInRange( const SwNodeRange& rRg, SaveFlyArr& rArr ); void SaveFlyInRange( const SwPaM& rPam, const SwPosition& rInsPos, SaveFlyArr& rArr, bool bMoveAllFlys ); commit 986fffa7f3d3eb4a49179230b7e39626f74771a4 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Thu Jun 9 18:58:06 2022 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Tue Aug 23 18:13:34 2022 +0200 tdf#140007 sw: fix SwUndoReplace (regression from commit d6b0e84b236b78f4b21bd16e46dda3fa0876096d) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135585 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 45613274794636ba98d0e978fe872511297d275d) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135549 Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> (cherry picked from commit 7df50ecd9dea623058dc7bf9095fd13d9bb49860) Change-Id: I1facf1584a349d1d087438f4e6fd3a63a80c6f7e Reviewed-on: https://gerrit.libreoffice.org/c/core/+/136219 Tested-by: Thorsten Behrens <thorsten.behr...@allotropia.de> Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de> diff --git a/sw/qa/extras/uiwriter/uiwriter2.cxx b/sw/qa/extras/uiwriter/uiwriter2.cxx index 9e6c81bc769f..f742288bf2db 100644 --- a/sw/qa/extras/uiwriter/uiwriter2.cxx +++ b/sw/qa/extras/uiwriter/uiwriter2.cxx @@ -820,6 +820,94 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testTdf131912) CPPUNIT_ASSERT_EQUAL(OUString("foo"), pWrtShell->GetCursor()->GetText()); } +CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testTdf140007) +{ + typedef sal_uLong SwNodeOffset; + SwDoc* const pDoc = createDoc(); + SwWrtShell* const pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + + pWrtShell->Insert("foo"); + pWrtShell->SplitNode(); + pWrtShell->Insert("bar"); + pWrtShell->SplitNode(); + pWrtShell->Insert("baz"); + CPPUNIT_ASSERT_EQUAL(sal_uLong(13), pDoc->GetNodes().Count()); + CPPUNIT_ASSERT_EQUAL(OUString("foo"), + pDoc->GetNodes()[SwNodeOffset(9)]->GetTextNode()->GetText()); + CPPUNIT_ASSERT_EQUAL(OUString("bar"), + pDoc->GetNodes()[SwNodeOffset(10)]->GetTextNode()->GetText()); + CPPUNIT_ASSERT_EQUAL(OUString("baz"), + pDoc->GetNodes()[SwNodeOffset(11)]->GetTextNode()->GetText()); + + pWrtShell->SttEndDoc(true); + pWrtShell->EndPara(false); + pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/true, 1, /*bBasicCall=*/false); + pWrtShell->Replace(" ", true); + CPPUNIT_ASSERT_EQUAL(SwNodeOffset(12), pDoc->GetNodes().Count()); + CPPUNIT_ASSERT_EQUAL(OUString("foo bar"), + pDoc->GetNodes()[SwNodeOffset(9)]->GetTextNode()->GetText()); + CPPUNIT_ASSERT_EQUAL(OUString("baz"), + pDoc->GetNodes()[SwNodeOffset(10)]->GetTextNode()->GetText()); + + pWrtShell->SttEndDoc(true); + pWrtShell->EndPara(false); + pWrtShell->Right(CRSR_SKIP_CHARS, /*bSelect=*/true, 1, /*bBasicCall=*/false); + pWrtShell->Replace(" ", true); + CPPUNIT_ASSERT_EQUAL(OUString("foo bar baz"), + pDoc->GetNodes()[SwNodeOffset(9)]->GetTextNode()->GetText()); + CPPUNIT_ASSERT_EQUAL(SwNodeOffset(11), pDoc->GetNodes().Count()); + + pWrtShell->Undo(); + + CPPUNIT_ASSERT_EQUAL(SwNodeOffset(12), pDoc->GetNodes().Count()); + CPPUNIT_ASSERT_EQUAL(OUString("foo bar"), + pDoc->GetNodes()[SwNodeOffset(9)]->GetTextNode()->GetText()); + CPPUNIT_ASSERT_EQUAL(OUString("baz"), + pDoc->GetNodes()[SwNodeOffset(10)]->GetTextNode()->GetText()); + + pWrtShell->Undo(); + + CPPUNIT_ASSERT_EQUAL(SwNodeOffset(13), pDoc->GetNodes().Count()); + CPPUNIT_ASSERT_EQUAL(OUString("foo"), + pDoc->GetNodes()[SwNodeOffset(9)]->GetTextNode()->GetText()); + CPPUNIT_ASSERT_EQUAL(OUString("bar"), + pDoc->GetNodes()[SwNodeOffset(10)]->GetTextNode()->GetText()); + CPPUNIT_ASSERT_EQUAL(OUString("baz"), + pDoc->GetNodes()[SwNodeOffset(11)]->GetTextNode()->GetText()); + + pWrtShell->Redo(); + + CPPUNIT_ASSERT_EQUAL(SwNodeOffset(12), pDoc->GetNodes().Count()); + CPPUNIT_ASSERT_EQUAL(OUString("foo bar"), + pDoc->GetNodes()[SwNodeOffset(9)]->GetTextNode()->GetText()); + CPPUNIT_ASSERT_EQUAL(OUString("baz"), + pDoc->GetNodes()[SwNodeOffset(10)]->GetTextNode()->GetText()); + + pWrtShell->Redo(); + + CPPUNIT_ASSERT_EQUAL(OUString("foo bar baz"), + pDoc->GetNodes()[SwNodeOffset(9)]->GetTextNode()->GetText()); + CPPUNIT_ASSERT_EQUAL(SwNodeOffset(11), pDoc->GetNodes().Count()); + + pWrtShell->Undo(); + + CPPUNIT_ASSERT_EQUAL(SwNodeOffset(12), pDoc->GetNodes().Count()); + CPPUNIT_ASSERT_EQUAL(OUString("foo bar"), + pDoc->GetNodes()[SwNodeOffset(9)]->GetTextNode()->GetText()); + CPPUNIT_ASSERT_EQUAL(OUString("baz"), + pDoc->GetNodes()[SwNodeOffset(10)]->GetTextNode()->GetText()); + + pWrtShell->Undo(); + + CPPUNIT_ASSERT_EQUAL(SwNodeOffset(13), pDoc->GetNodes().Count()); + CPPUNIT_ASSERT_EQUAL(OUString("foo"), + pDoc->GetNodes()[SwNodeOffset(9)]->GetTextNode()->GetText()); + CPPUNIT_ASSERT_EQUAL(OUString("bar"), + pDoc->GetNodes()[SwNodeOffset(10)]->GetTextNode()->GetText()); + CPPUNIT_ASSERT_EQUAL(OUString("baz"), + pDoc->GetNodes()[SwNodeOffset(11)]->GetTextNode()->GetText()); +} + CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testTdf54819) { load(DATA_DIRECTORY, "tdf54819.fodt"); diff --git a/sw/source/core/undo/unins.cxx b/sw/source/core/undo/unins.cxx index 6135466bf73d..f1f7f48188aa 100644 --- a/sw/source/core/undo/unins.cxx +++ b/sw/source/core/undo/unins.cxx @@ -689,7 +689,8 @@ void SwUndoReplace::Impl::UndoImpl(::sw::UndoRedoContext & rContext) if( m_bSplitNext ) { - SwPosition aPos(*pNd, pNd->Len()); + assert(m_nSttCnt + m_sOld.getLength() <= pNd->Len()); + SwPosition aPos(*pNd, m_nSttCnt + m_sOld.getLength()); pDoc->getIDocumentContentOperations().SplitNode( aPos, false ); pNd->RestoreMetadata(m_pMetadataUndoEnd); pNd = pDoc->GetNodes()[ m_nSttNd - m_nOffset ]->GetTextNode(); @@ -723,7 +724,7 @@ void SwUndoReplace::Impl::UndoImpl(::sw::UndoRedoContext & rContext) } rPam.GetPoint()->nNode = m_nSttNd; - rPam.GetPoint()->nContent = m_nSttCnt; + rPam.GetPoint()->nContent.Assign(rPam.GetPoint()->nNode.GetNode().GetTextNode(), m_nSttCnt); } void SwUndoReplace::Impl::RedoImpl(::sw::UndoRedoContext & rContext) commit f8aea76d216b1c70a03ed204bc0a4dea4edb4c5b Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Wed Jun 8 17:34:32 2022 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Tue Aug 23 18:12:33 2022 +0200 (related: tdf#139514) sw: fix Undo of delete with at-para fly Nonobviously, there are situations where the anchor node must be preserved and restored when it's not on the node that is being deleted. (probably regression from commit 91b2325808a75174f284c48c8b8afc118fad74e4) Change-Id: I39f09ddb631204c8ad522f9ec7068d235ca94ad2 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135509 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 12acdce71dd6b6af2c52ba8fa3248d3166418543) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135518 Reviewed-by: Miklos Vajna <vmik...@collabora.com> (cherry picked from commit 054c248127c78521b4a4e7aacd8936bd54259996) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/136218 Tested-by: Michael Stahl <michael.st...@allotropia.de> diff --git a/sw/source/core/undo/undobj.cxx b/sw/source/core/undo/undobj.cxx index 3fe7f107f052..d611cb4a496b 100644 --- a/sw/source/core/undo/undobj.cxx +++ b/sw/source/core/undo/undobj.cxx @@ -981,10 +981,14 @@ void SwUndoSaveContent::DelContentIndex( const SwPosition& rMark, // Moving the anchor? else if (!((DelContentType::CheckNoCntnt|DelContentType::ExcludeFlyAtStartEnd) & nDelContentType) && - // at least for calls from SwUndoDelete, - // this should work - other Undos don't - // remember the order of the cursor - (rPoint.nNode.GetIndex() == pAPos->nNode.GetIndex()) + // for SwUndoDelete: rPoint is the node that + // will be Joined - so anchor should be moved + // off it - but UndoImpl() split will insert + // new node *before* existing one so a no-op + // may need to be done here to add it to + // history for Undo. + (rPoint.nNode.GetIndex() == pAPos->nNode.GetIndex() + || pStt->nNode.GetIndex() == pAPos->nNode.GetIndex()) // Do not try to move the anchor to a table! && rMark.nNode.GetNode().IsTextNode()) { commit 3506fab4554238da59d22788cffe4e140c175215 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Tue Jun 7 19:01:24 2022 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Tue Aug 23 18:12:09 2022 +0200 tdf#133957 sw: don't delete flys on Backspace/Delete keys Also fixes: tdf#134007 tdf#138835 tdf#139514 When a character is deleted via the keyboard by Backspace or Delete key, an artificial selection is created in SwWrtShell::DelLeft()/DelRight(). Ideally this should not delete flys that may be anchored to the paragraphs, but unfortunately this may happen if there are only 2 empty paragraphs in the section, because then the artificial selection cannot be distinguished by the SwDoc implementation from a selection from Ctrl+A (Select All), which *should* delete the flys. So introduce a new flag that needs to be passed down multiple layers so that SwUndoDelete can use it to determine if flys should be deleted, and translating it to a flag that had been introduced to preserve flys in ReplaceRange() previously. There are a couple more callers that look like they want to "replace" some text, so guess a bit at where to set this new flag. (note: of course fly anchored *as char* must be deleted via keys.) (regression from commit e75dd1fc992f168f24d66595265a978071cdd277) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135476 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 85376a02348810812d515ee72140dbf56f2b6040) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/135517 Reviewed-by: Miklos Vajna <vmik...@collabora.com> (cherry picked from commit 5192cd430e8cab0ed04f8c70c5194397455ac705) Change-Id: Ib4467476b12a12aefbbcb74ab9802f9318cf9aa0 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/136217 Tested-by: Michael Stahl <michael.st...@allotropia.de> Reviewed-by: Michael Stahl <michael.st...@allotropia.de> diff --git a/sw/inc/IDocumentContentOperations.hxx b/sw/inc/IDocumentContentOperations.hxx index b6857c346a33..6e6b3c1bad3e 100644 --- a/sw/inc/IDocumentContentOperations.hxx +++ b/sw/inc/IDocumentContentOperations.hxx @@ -69,6 +69,16 @@ namespace o3tl template<> struct typed_flags<SwInsertFlags> : is_typed_flags<SwInsertFlags, 0x07> {}; } +enum class SwDeleteFlags +{ + Default = 0, + ArtificialSelection = (1<<0), ///< keyboard delete, artificial selection, avoid deleting flys +}; +namespace o3tl +{ + template<> struct typed_flags<SwDeleteFlags> : is_typed_flags<SwDeleteFlags, 0x01> {}; +} + /** Text operation/manipulation interface */ class IDocumentContentOperations @@ -130,6 +140,7 @@ public: Needed for hiding of deletion redlines */ virtual bool DeleteAndJoin( SwPaM&, + SwDeleteFlags flags = SwDeleteFlags::Default, const bool bForceJoinNext = false ) = 0; virtual bool MoveRange(SwPaM&, SwPosition&, SwMoveFlags) = 0; diff --git a/sw/inc/editsh.hxx b/sw/inc/editsh.hxx index 53b91f44db98..3f2d34d66c2e 100644 --- a/sw/inc/editsh.hxx +++ b/sw/inc/editsh.hxx @@ -152,7 +152,7 @@ class SW_DLLPUBLIC SwEditShell : public SwCursorShell that will be used by GetGraphic() and GetGraphicSize(). */ SAL_DLLPRIVATE SwGrfNode *GetGrfNode_() const ; - SAL_DLLPRIVATE void DeleteSel( SwPaM& rPam, bool* pUndo = nullptr ); + SAL_DLLPRIVATE void DeleteSel(SwPaM& rPam, bool isArtificialSelection, bool* pUndo = nullptr); SAL_DLLPRIVATE void SetSectionAttr_( SwSectionFormat& rSectFormat, const SfxItemSet& rSet ); @@ -174,7 +174,7 @@ public: /** Delete content of all ranges. If whole nodes are selected, these nodes get deleted. */ - bool Delete(); + bool Delete(bool isArtificialSelection = false); /// Remove a complete paragraph. bool DelFullPara(); diff --git a/sw/qa/core/uwriter.cxx b/sw/qa/core/uwriter.cxx index 22cd289c7f27..ec18a86241bf 100644 --- a/sw/qa/core/uwriter.cxx +++ b/sw/qa/core/uwriter.cxx @@ -1149,7 +1149,7 @@ void SwDocTest::randomTest() break; case 2: *pCrs->GetMark() = getRandomPosition(m_pDoc, 42); - m_pDoc->getIDocumentContentOperations().DeleteAndJoin(*pCrs, !!getRand(1)); + m_pDoc->getIDocumentContentOperations().DeleteAndJoin(*pCrs, SwDeleteFlags::Default, !!getRand(1)); break; case 3: default: diff --git a/sw/source/core/doc/DocumentContentOperationsManager.cxx b/sw/source/core/doc/DocumentContentOperationsManager.cxx index aefb3f438cef..2db40c2e5d57 100644 --- a/sw/source/core/doc/DocumentContentOperationsManager.cxx +++ b/sw/source/core/doc/DocumentContentOperationsManager.cxx @@ -618,8 +618,9 @@ namespace sw namespace { - bool lcl_DoWithBreaks(::sw::DocumentContentOperationsManager & rDocumentContentOperations, SwPaM & rPam, - bool (::sw::DocumentContentOperationsManager::*pFunc)(SwPaM&, bool), const bool bForceJoinNext = false) + bool lcl_DoWithBreaks(::sw::DocumentContentOperationsManager & rDocumentContentOperations, + SwPaM & rPam, SwDeleteFlags const flags, + bool (::sw::DocumentContentOperationsManager::*pFunc)(SwPaM&, SwDeleteFlags, bool), const bool bForceJoinNext = false) { std::vector<std::pair<sal_uLong, sal_Int32>> Breaks; @@ -627,7 +628,7 @@ namespace if (Breaks.empty()) { - return (rDocumentContentOperations.*pFunc)(rPam, bForceJoinNext); + return (rDocumentContentOperations.*pFunc)(rPam, flags, bForceJoinNext); } // Deletion must be split into several parts if the text node @@ -651,7 +652,7 @@ namespace rStart = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second + 1); if (rStart < rEnd) // check if part is empty { - bRet &= (rDocumentContentOperations.*pFunc)(aPam, bForceJoinNext); + bRet &= (rDocumentContentOperations.*pFunc)(aPam, flags, bForceJoinNext); nOffset = iter->first - rStart.nNode.GetIndex(); // deleted fly nodes... } rEnd = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second); @@ -661,7 +662,7 @@ namespace rStart = *rPam.Start(); // set to original start if (rStart < rEnd) // check if part is empty { - bRet &= (rDocumentContentOperations.*pFunc)(aPam, bForceJoinNext); + bRet &= (rDocumentContentOperations.*pFunc)(aPam, flags, bForceJoinNext); } return bRet; @@ -1997,7 +1998,7 @@ void DocumentContentOperationsManager::DeleteDummyChar( assert(aPam.GetText().getLength() == 1 && aPam.GetText()[0] == cDummy); (void) cDummy; - DeleteRangeImpl(aPam); + DeleteRangeImpl(aPam, SwDeleteFlags::Default); if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) @@ -2008,7 +2009,7 @@ void DocumentContentOperationsManager::DeleteDummyChar( void DocumentContentOperationsManager::DeleteRange( SwPaM & rPam ) { - lcl_DoWithBreaks( *this, rPam, &DocumentContentOperationsManager::DeleteRangeImpl ); + lcl_DoWithBreaks(*this, rPam, SwDeleteFlags::Default, &DocumentContentOperationsManager::DeleteRangeImpl); if (!m_rDoc.getIDocumentRedlineAccess().IsIgnoreRedline() && !m_rDoc.getIDocumentRedlineAccess().GetRedlineTable().empty()) @@ -2111,7 +2112,7 @@ bool DocumentContentOperationsManager::DelFullPara( SwPaM& rPam ) ::PaMCorrAbs( aDelPam, aTmpPos ); } - std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete( aDelPam, true )); + std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete(aDelPam, SwDeleteFlags::Default, true)); *rPam.GetPoint() = *aDelPam.GetPoint(); pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc ); @@ -2206,13 +2207,13 @@ bool DocumentContentOperationsManager::DelFullPara( SwPaM& rPam ) } // #i100466# Add handling of new optional parameter <bForceJoinNext> -bool DocumentContentOperationsManager::DeleteAndJoin( SwPaM & rPam, +bool DocumentContentOperationsManager::DeleteAndJoin(SwPaM & rPam, SwDeleteFlags const flags, const bool bForceJoinNext ) { if ( lcl_StrLenOverflow( rPam ) ) return false; - bool const ret = lcl_DoWithBreaks( *this, rPam, (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()) + bool const ret = lcl_DoWithBreaks( *this, rPam, flags, (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()) ? &DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl : &DocumentContentOperationsManager::DeleteAndJoinImpl, bForceJoinNext ); @@ -3345,8 +3346,8 @@ bool DocumentContentOperationsManager::ReplaceRange( SwPaM& rPam, const OUString if (rStart < rEnd) // check if part is empty { bRet &= (m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()) - ? DeleteAndJoinWithRedlineImpl(aPam) - : DeleteAndJoinImpl(aPam, false); + ? DeleteAndJoinWithRedlineImpl(aPam, SwDeleteFlags::Default) + : DeleteAndJoinImpl(aPam, SwDeleteFlags::Default, false); nOffset = iter->first - rStart.nNode.GetIndex(); // deleted fly nodes... } rEnd = SwPosition(*rNodes[iter->first - nOffset]->GetTextNode(), iter->second); @@ -3914,7 +3915,7 @@ DocumentContentOperationsManager::~DocumentContentOperationsManager() } //Private methods -bool DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl( SwPaM & rPam, const bool ) +bool DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl(SwPaM & rPam, SwDeleteFlags const /*flags*/, const bool) { assert(m_rDoc.getIDocumentRedlineAccess().IsRedlineOn()); @@ -4055,7 +4056,7 @@ bool DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl( SwPaM & rPa return true; } -bool DocumentContentOperationsManager::DeleteAndJoinImpl( SwPaM & rPam, +bool DocumentContentOperationsManager::DeleteAndJoinImpl(SwPaM & rPam, SwDeleteFlags const flags, const bool bForceJoinNext ) { bool bJoinText, bJoinPrev; @@ -4067,7 +4068,7 @@ bool DocumentContentOperationsManager::DeleteAndJoinImpl( SwPaM & rPam, } { - bool const bSuccess( DeleteRangeImpl( rPam ) ); + bool const bSuccess( DeleteRangeImpl(rPam, flags) ); if (!bSuccess) return false; } @@ -4086,14 +4087,14 @@ bool DocumentContentOperationsManager::DeleteAndJoinImpl( SwPaM & rPam, return true; } -bool DocumentContentOperationsManager::DeleteRangeImpl(SwPaM & rPam, const bool) +bool DocumentContentOperationsManager::DeleteRangeImpl(SwPaM & rPam, SwDeleteFlags const flags, const bool) { // Move all cursors out of the deleted range, but first copy the // passed PaM, because it could be a cursor that would be moved! SwPaM aDelPam( *rPam.GetMark(), *rPam.GetPoint() ); ::PaMCorrAbs( aDelPam, *aDelPam.GetPoint() ); - bool const bSuccess( DeleteRangeImplImpl( aDelPam ) ); + bool const bSuccess( DeleteRangeImplImpl(aDelPam, flags) ); if (bSuccess) { // now copy position from temp copy to given PaM *rPam.GetPoint() = *aDelPam.GetPoint(); @@ -4102,7 +4103,7 @@ bool DocumentContentOperationsManager::DeleteRangeImpl(SwPaM & rPam, const bool) return bSuccess; } -bool DocumentContentOperationsManager::DeleteRangeImplImpl(SwPaM & rPam) +bool DocumentContentOperationsManager::DeleteRangeImplImpl(SwPaM & rPam, SwDeleteFlags const flags) { SwPosition *pStt = rPam.Start(), *pEnd = rPam.End(); @@ -4167,7 +4168,7 @@ bool DocumentContentOperationsManager::DeleteRangeImplImpl(SwPaM & rPam) } if (!bMerged) { - m_rDoc.GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoDelete>( rPam ) ); + m_rDoc.GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoDelete>(rPam, flags)); } m_rDoc.getIDocumentState().SetModified(); @@ -4179,8 +4180,11 @@ bool DocumentContentOperationsManager::DeleteRangeImplImpl(SwPaM & rPam) m_rDoc.getIDocumentRedlineAccess().DeleteRedline( rPam, true, RedlineType::Any ); // Delete and move all "Flys at the paragraph", which are within the Selection - DelFlyInRange(rPam.GetMark()->nNode, rPam.GetPoint()->nNode, - &rPam.GetMark()->nContent, &rPam.GetPoint()->nContent); + if (!(flags & SwDeleteFlags::ArtificialSelection)) + { + DelFlyInRange(rPam.GetMark()->nNode, rPam.GetPoint()->nNode, + &rPam.GetMark()->nContent, &rPam.GetPoint()->nContent); + } DelBookmarks( pStt->nNode, pEnd->nNode, diff --git a/sw/source/core/docnode/ndsect.cxx b/sw/source/core/docnode/ndsect.cxx index 8c2efee0eb7e..6154cfc7d776 100644 --- a/sw/source/core/docnode/ndsect.cxx +++ b/sw/source/core/docnode/ndsect.cxx @@ -535,7 +535,7 @@ void SwDoc::DelSectionFormat( SwSectionFormat *pFormat, bool bDelNodes ) { SwNodeIndex aUpdIdx( *pIdx ); SwPaM aPaM( *pSectNd->EndOfSectionNode(), *pSectNd ); - GetIDocumentUndoRedo().AppendUndo( std::make_unique<SwUndoDelete>( aPaM )); + GetIDocumentUndoRedo().AppendUndo(std::make_unique<SwUndoDelete>(aPaM, SwDeleteFlags::Default)); if( pFootnoteEndAtTextEnd ) GetFootnoteIdxs().UpdateFootnote( aUpdIdx ); getIDocumentState().SetModified(); diff --git a/sw/source/core/docnode/ndtbl.cxx b/sw/source/core/docnode/ndtbl.cxx index 15a49729ce51..b12fc72228e2 100644 --- a/sw/source/core/docnode/ndtbl.cxx +++ b/sw/source/core/docnode/ndtbl.cxx @@ -2040,7 +2040,7 @@ bool SwDoc::DeleteRowCol( const SwSelBoxes& rBoxes, bool bColumn ) bSavePageBreak = true; } } - std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete( aPaM )); + std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete(aPaM, SwDeleteFlags::Default)); if( bNewTextNd ) pUndo->SetTableDelLastNd(); pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc ); diff --git a/sw/source/core/edit/autofmt.cxx b/sw/source/core/edit/autofmt.cxx index cb905187faed..6e4bccb55bcc 100644 --- a/sw/source/core/edit/autofmt.cxx +++ b/sw/source/core/edit/autofmt.cxx @@ -1196,7 +1196,7 @@ void SwAutoFormat::DeleteSelImpl(SwPaM & rDelPam, SwPaM & rPamToCorrect) SwPaM* pPrev = rPamToCorrect.GetPrev(); rPamToCorrect.GetRingContainer().merge( pShCursor->GetRingContainer() ); - m_pEditShell->DeleteSel( rDelPam ); + m_pEditShell->DeleteSel(rDelPam, true); // and remove Pam again: SwPaM* p; @@ -1212,7 +1212,7 @@ void SwAutoFormat::DeleteSelImpl(SwPaM & rDelPam, SwPaM & rPamToCorrect) m_pCurTextFrame = GetFrame(*m_pCurTextNd); // keep it up to date } else - m_pEditShell->DeleteSel( rDelPam ); + m_pEditShell->DeleteSel(rDelPam, true); } bool SwAutoFormat::DeleteJoinCurNextPara(SwTextFrame const*const pNextFrame, diff --git a/sw/source/core/edit/eddel.cxx b/sw/source/core/edit/eddel.cxx index 6389ddc78987..ac60b7343ac4 100644 --- a/sw/source/core/edit/eddel.cxx +++ b/sw/source/core/edit/eddel.cxx @@ -38,7 +38,7 @@ #include <strings.hrc> #include <vector> -void SwEditShell::DeleteSel( SwPaM& rPam, bool* pUndo ) +void SwEditShell::DeleteSel(SwPaM& rPam, bool const isArtificialSelection, bool *const pUndo) { bool bSelectAll = StartsWithTable() && ExtendedSelectedAll(); // only for selections @@ -121,7 +121,8 @@ void SwEditShell::DeleteSel( SwPaM& rPam, bool* pUndo ) pPam = pNewPam.get(); } // delete everything - GetDoc()->getIDocumentContentOperations().DeleteAndJoin(*pPam); + GetDoc()->getIDocumentContentOperations().DeleteAndJoin(*pPam, + isArtificialSelection ? SwDeleteFlags::ArtificialSelection : SwDeleteFlags::Default); SaveTableBoxContent( pPam->GetPoint() ); } @@ -129,7 +130,7 @@ void SwEditShell::DeleteSel( SwPaM& rPam, bool* pUndo ) rPam.DeleteMark(); } -bool SwEditShell::Delete() +bool SwEditShell::Delete(bool const isArtificialSelection) { SET_CURR_SHELL( this ); bool bRet = false; @@ -148,7 +149,7 @@ bool SwEditShell::Delete() for(SwPaM& rPaM : GetCursor()->GetRingContainer()) { - DeleteSel( rPaM, &bUndo ); + DeleteSel(rPaM, isArtificialSelection, &bUndo); } // If undo container then close here diff --git a/sw/source/core/edit/edglbldc.cxx b/sw/source/core/edit/edglbldc.cxx index 3d916edc5fe0..c5ea9081d043 100644 --- a/sw/source/core/edit/edglbldc.cxx +++ b/sw/source/core/edit/edglbldc.cxx @@ -272,7 +272,7 @@ void SwEditShell::DeleteGlobalDocContent( const SwGlblDocContents& rArr , rPos.nNode = pMyDoc->GetNodes().GetEndOfContent(); --rPos.nNode; if( !pMyDoc->getIDocumentContentOperations().DelFullPara( *pCursor ) ) - Delete(); + Delete(false); } break; diff --git a/sw/source/core/edit/editsh.cxx b/sw/source/core/edit/editsh.cxx index 8f84ce42ed75..699997003daf 100644 --- a/sw/source/core/edit/editsh.cxx +++ b/sw/source/core/edit/editsh.cxx @@ -663,7 +663,7 @@ bool SwEditShell::InsertURL( const SwFormatINetFormat& rFormat, const OUString& bDelText = bInsText = false; if( bDelText ) - Delete(); + Delete(true); } else if( pCursor->IsMultiSelection() && rFormat.GetValue() == rStr ) bInsText = false; @@ -732,7 +732,7 @@ void SwEditShell::DelINetAttrWithText() { bool bRet = SelectTextAttr( RES_TXTATR_INETFMT, false ); if( bRet ) - DeleteSel( *GetCursor() ); + DeleteSel(*GetCursor(), true); } /// Set the DontExpand flag at the text character attributes diff --git a/sw/source/core/frmedt/fecopy.cxx b/sw/source/core/frmedt/fecopy.cxx index c2470b997a93..8589b8eed579 100644 --- a/sw/source/core/frmedt/fecopy.cxx +++ b/sw/source/core/frmedt/fecopy.cxx @@ -1021,7 +1021,7 @@ bool SwFEShell::Paste( SwDoc* pClpDoc, bool bNestedTable ) { if( bDelTable && IsTableMode() ) { - SwEditShell::Delete(); + SwEditShell::Delete(false); bDelTable = false; } diff --git a/sw/source/core/inc/DocumentContentOperationsManager.hxx b/sw/source/core/inc/DocumentContentOperationsManager.hxx index 2d600b6ff8ba..994812dc14b4 100644 --- a/sw/source/core/inc/DocumentContentOperationsManager.hxx +++ b/sw/source/core/inc/DocumentContentOperationsManager.hxx @@ -48,6 +48,7 @@ public: // Add optional parameter <bForceJoinNext>, default value <false> // Needed for hiding of deletion redlines bool DeleteAndJoin( SwPaM&, + SwDeleteFlags flags = SwDeleteFlags::Default, const bool bForceJoinNext = false ) override; bool MoveRange(SwPaM&, SwPosition&, SwMoveFlags) override; @@ -159,10 +160,10 @@ public: private: SwDoc& m_rDoc; ... etc. - the rest is truncated