sw/inc/crsrsh.hxx | 14 sw/inc/doc.hxx | 1 sw/qa/extras/odfimport/odfimport.cxx | 4 sw/qa/extras/uiwriter/data/table-at-end-of-cell.fodt | 219 +++++++++++++ sw/qa/extras/uiwriter/uiwriter.cxx | 22 + sw/qa/extras/uiwriter/uiwriter2.cxx | 18 + sw/source/core/crsr/crsrsh.cxx | 315 +++++++++++++++++-- sw/source/core/docnode/ndtbl.cxx | 282 +++++++++-------- sw/source/core/edit/eddel.cxx | 36 +- sw/source/core/edit/edglss.cxx | 14 sw/source/uibase/wrtsh/move.cxx | 46 ++ sw/source/uibase/wrtsh/select.cxx | 36 +- 12 files changed, 807 insertions(+), 200 deletions(-)
New commits: commit 1f1078e0cbcc5879128d20e0748b0dd1583a471e Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Thu Jun 15 13:13:05 2023 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Thu Jun 15 17:37:10 2023 +0200 tdf#155685 sw: ExtendedSelectAll with tables, group the Undo objects Change-Id: I2fba70968c97cd9704212cd799b333d2d158a042 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/153115 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 5ab4fb27f4232fe0f36cfc079acb065d1712a355) diff --git a/sw/source/core/edit/eddel.cxx b/sw/source/core/edit/eddel.cxx index ebb33eca9e43..e16b8256b611 100644 --- a/sw/source/core/edit/eddel.cxx +++ b/sw/source/core/edit/eddel.cxx @@ -106,6 +106,12 @@ void SwEditShell::DeleteSel(SwPaM& rPam, bool const isArtificialSelection, bool SwPaM * pPam = &rPam; if (oSelectAll) { + if (!oSelectAll->second.empty()) + { + SwRewriter aRewriter; + aRewriter.AddRule(UndoArg1, SwResId(STR_MULTISEL)); + GetDoc()->GetIDocumentUndoRedo().StartUndo(SwUndoId::DELETE, &aRewriter); + } // tdf#155685 tables at the end must be deleted separately for (SwTableNode *const pTable : oSelectAll->second) { @@ -122,6 +128,10 @@ void SwEditShell::DeleteSel(SwPaM& rPam, bool const isArtificialSelection, bool GetDoc()->getIDocumentContentOperations().DeleteAndJoin(*pPam, isArtificialSelection ? SwDeleteFlags::ArtificialSelection : SwDeleteFlags::Default); SaveTableBoxContent( pPam->GetPoint() ); + if (oSelectAll && !oSelectAll->second.empty()) + { + GetDoc()->GetIDocumentUndoRedo().EndUndo(SwUndoId::END, nullptr); + } } // Selection is not needed anymore commit 15e494724ab728962f133eb8d81c5fcd475b9197 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Thu Jun 15 12:22:11 2023 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Thu Jun 15 17:37:10 2023 +0200 tdf#155685 sw: fix another ExtendedSelectAll Redo crash w table at end This can be reproduced with a table containing in the last cell a paragraph followed by a table, then ExtendedSelectAll in the cell and delete. On Redo of the SwUndoDelete: warn:legacy.osl:326138:326138:sw/source/core/frmedt/tblsel.cxx:1775: MakeSelUnions with pStart or pEnd not in CellFrame In function: const_reference std::vector<SwTableBox *>::operator[](size_type) const [_Tp = SwTableBox *, _Allocator = std::allocator<SwTableBox *>] Error: attempt to subscript container with out-of-bounds index 0, but container only holds 0 elements. The problem is that DelTable() calls PaMCorrAbs() with a target that is outside of the outer table, so the SwEditShell::DeleteSel() rPam has one end in the last table cell and other end outside the table. Change-Id: Ia2764a4c99ba12102957153e005284a44be04fd0 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/153114 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 4ef548a672658ab164e45e45ebd1b9f0b9282019) diff --git a/sw/qa/extras/uiwriter/data/table-at-end-of-cell.fodt b/sw/qa/extras/uiwriter/data/table-at-end-of-cell.fodt new file mode 100644 index 000000000000..4e18f7dc2ce4 --- /dev/null +++ b/sw/qa/extras/uiwriter/data/table-at-end-of-cell.fodt @@ -0,0 +1,219 @@ +<?xml version='1.0' encoding='UTF-8'?> +<office:document xmlns:css3t="http://www.w3.org/TR/css3-text/" xmlns:grddl="http://www.w3.org/2003/g/data-view#" xmlns:xhtml="http://www.w3.org/1999/xhtml" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xforms="http://www.w3.org/2002/xforms" xmlns:dom="http://www.w3.org/2001/xml-events" xmlns:script="urn:oasis:names:tc:opendocument:xmlns:script:1.0" xmlns:form="urn:oasis:names:tc:opendocument:xmlns:form:1.0" xmlns:math="http://www.w3.org/1998/Math/MathML" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:ooo="http://openoffice.org/2004/office" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:config="urn:oasis:names:tc:opendocument:xmlns:config:1.0" xmlns:ooow="http://openoffice.org/2004/writer" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:drawooo="http://openoffice.org/2010/draw" xmlns:oooc="http://openoffice.org/2004/calc" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:c alcext="urn:org:documentfoundation:names:experimental:calc:xmlns:calcext:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:of="urn:oasis:names:tc:opendocument:xmlns:of:1.2" xmlns:tableooo="http://openoffice.org/2009/table" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:dr3d="urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0" xmlns:rpt="http://openoffice.org/2005/report" xmlns:formx="urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" xmlns:chart="urn:oasis:names:tc:opendocument:xmlns:chart:1.0" xmlns:officeooo="http://openoffice.org/2009/office" xmlns:table="urn:oasis:names:tc:opendocument:xmlns:table:1.0" xmlns:field="urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns: meta:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:meta><meta:creation-date>2023-06-15T12:29:51.722401974</meta:creation-date><dc:date>2023-06-15T12:30:57.494419372</dc:date><meta:editing-duration>PT1M13S</meta:editing-duration><meta:editing-cycles>1</meta:editing-cycles><meta:document-statistic meta:table-count="2" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="0" meta:word-count="0" meta:character-count="0" meta:non-whitespace-character-count="0"/><meta:generator>LibreOfficeDev/7.6.0.0.alpha0$Linux_X86_64 LibreOffice_project/2e5dbe93649ec8043e9da75ddc01c486dbbf18e0</meta:generator></office:meta> + <office:font-face-decls> + <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" style:font-pitch="variable"/> + <style:font-face style:name="Lohit Devanagari1" svg:font-family="'Lohit Devanagari'" style:font-family-generic="system" style:font-pitch="variable"/> + <style:font-face style:name="Noto Sans CJK SC" svg:font-family="'Noto Sans CJK SC'" style:font-family-generic="system" style:font-pitch="variable"/> + </office:font-face-decls> + <office:styles> + <style:default-style style:family="graphic"> + <style:graphic-properties svg:stroke-color="#3465a4" draw:fill-color="#729fcf" fo:wrap-option="no-wrap" draw:shadow-offset-x="0.3cm" draw:shadow-offset-y="0.3cm" draw:start-line-spacing-horizontal="0.283cm" draw:start-line-spacing-vertical="0.283cm" draw:end-line-spacing-horizontal="0.283cm" draw:end-line-spacing-vertical="0.283cm" style:flow-with-text="false"/> + <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" style:writing-mode="lr-tb" style:font-independent-line-spacing="false"> + <style:tab-stops/> + </style:paragraph-properties> + <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Noto Sans CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lohit Devanagari1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN"/> + </style:default-style> + <style:default-style style:family="paragraph"> + <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" style:text-autospace="ideograph-alpha" style:punctuation-wrap="hanging" style:line-break="strict" style:tab-stop-distance="1.251cm" style:writing-mode="page"/> + <style:text-properties style:use-window-font-color="true" loext:opacity="0%" style:font-name="Liberation Serif" fo:font-size="12pt" fo:language="de" fo:country="DE" style:letter-kerning="true" style:font-name-asian="Noto Sans CJK SC" style:font-size-asian="10.5pt" style:language-asian="zh" style:country-asian="CN" style:font-name-complex="Lohit Devanagari1" style:font-size-complex="12pt" style:language-complex="hi" style:country-complex="IN" fo:hyphenate="false" fo:hyphenation-remain-char-count="2" fo:hyphenation-push-char-count="2" loext:hyphenation-no-caps="false" loext:hyphenation-no-last-word="false" loext:hyphenation-word-char-count="5" loext:hyphenation-zone="no-limit"/> + </style:default-style> + <style:default-style style:family="table"> + <style:table-properties table:border-model="collapsing"/> + </style:default-style> + <style:default-style style:family="table-row"> + <style:table-row-properties fo:keep-together="auto"/> + </style:default-style> + <style:style style:name="Standard" style:family="paragraph" style:class="text"/> + <style:style style:name="Table_20_Contents" style:display-name="Table Contents" style:family="paragraph" style:parent-style-name="Standard" style:class="extra"> + <style:paragraph-properties fo:orphans="0" fo:widows="0" text:number-lines="false" text:line-number="0"/> + </style:style> + <text:outline-style style:name="Outline"> + <text:outline-level-style text:level="1" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="2" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="3" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="4" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="5" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="6" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="7" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="8" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="9" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + <text:outline-level-style text:level="10" style:num-format=""> + <style:list-level-properties text:list-level-position-and-space-mode="label-alignment"> + <style:list-level-label-alignment text:label-followed-by="listtab"/> + </style:list-level-properties> + </text:outline-level-style> + </text:outline-style> + <text:notes-configuration text:note-class="footnote" style:num-format="1" text:start-value="0" text:footnotes-position="page" text:start-numbering-at="document"/> + <text:notes-configuration text:note-class="endnote" style:num-format="i" text:start-value="0"/> + <text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/> + <loext:theme loext:name="Office Theme"> + <loext:color-table loext:name="LibreOffice"> + <loext:color loext:name="dk1" loext:color="#000000"/> + <loext:color loext:name="lt1" loext:color="#ffffff"/> + <loext:color loext:name="dk2" loext:color="#000000"/> + <loext:color loext:name="lt2" loext:color="#ffffff"/> + <loext:color loext:name="accent1" loext:color="#18a303"/> + <loext:color loext:name="accent2" loext:color="#0369a3"/> + <loext:color loext:name="accent3" loext:color="#a33e03"/> + <loext:color loext:name="accent4" loext:color="#8e03a3"/> + <loext:color loext:name="accent5" loext:color="#c99c00"/> + <loext:color loext:name="accent6" loext:color="#c9211e"/> + <loext:color loext:name="hlink" loext:color="#0000ee"/> + <loext:color loext:name="folHlink" loext:color="#551a8b"/> + </loext:color-table> + </loext:theme> + </office:styles> + <office:automatic-styles> + <style:style style:name="Table1" style:family="table"> + <style:table-properties style:width="17cm" table:align="margins"/> + </style:style> + <style:style style:name="Table1.A" style:family="table-column"> + <style:table-column-properties style:column-width="8.5cm" style:rel-column-width="32767*"/> + </style:style> + <style:style style:name="Table1.A1" style:family="table-cell"> + <style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="none" fo:border-top="0.5pt solid #000000" fo:border-bottom="0.5pt solid #000000"/> + </style:style> + <style:style style:name="Table1.B1" style:family="table-cell"> + <style:table-cell-properties fo:padding="0.097cm" fo:border="0.5pt solid #000000"/> + </style:style> + <style:style style:name="Table1.A2" style:family="table-cell"> + <style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.5pt solid #000000"/> + </style:style> + <style:style style:name="Table1.B2" style:family="table-cell"> + <style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="0.5pt solid #000000" fo:border-top="none" fo:border-bottom="0.5pt solid #000000"/> + </style:style> + <style:style style:name="Table2" style:family="table"> + <style:table-properties style:width="8.306cm" table:align="margins"/> + </style:style> + <style:style style:name="Table2.A" style:family="table-column"> + <style:table-column-properties style:column-width="4.154cm" style:rel-column-width="32767*"/> + </style:style> + <style:style style:name="Table2.A1" style:family="table-cell"> + <style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="none" fo:border-top="0.5pt solid #000000" fo:border-bottom="0.5pt solid #000000"/> + </style:style> + <style:style style:name="Table2.B1" style:family="table-cell"> + <style:table-cell-properties fo:padding="0.097cm" fo:border="0.5pt solid #000000"/> + </style:style> + <style:style style:name="Table2.A2" style:family="table-cell"> + <style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.5pt solid #000000"/> + </style:style> + <style:style style:name="Table2.B2" style:family="table-cell"> + <style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="0.5pt solid #000000" fo:border-top="none" fo:border-bottom="0.5pt solid #000000"/> + </style:style> + <style:style style:name="Table2" style:family="table"> + <style:table-properties style:width="8.306cm" table:align="margins"/> + </style:style> + <style:style style:name="Table2.A" style:family="table-column"> + <style:table-column-properties style:column-width="4.154cm" style:rel-column-width="32767*"/> + </style:style> + <style:style style:name="Table2.A1" style:family="table-cell"> + <style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="none" fo:border-top="0.5pt solid #000000" fo:border-bottom="0.5pt solid #000000"/> + </style:style> + <style:style style:name="Table2.B1" style:family="table-cell"> + <style:table-cell-properties fo:padding="0.097cm" fo:border="0.5pt solid #000000"/> + </style:style> + <style:style style:name="Table2.A2" style:family="table-cell"> + <style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="none" fo:border-top="none" fo:border-bottom="0.5pt solid #000000"/> + </style:style> + <style:style style:name="Table2.B2" style:family="table-cell"> + <style:table-cell-properties fo:padding="0.097cm" fo:border-left="0.5pt solid #000000" fo:border-right="0.5pt solid #000000" fo:border-top="none" fo:border-bottom="0.5pt solid #000000"/> + </style:style> + <style:page-layout style:name="pm1"> + <style:page-layout-properties fo:page-width="21.001cm" fo:page-height="29.7cm" style:num-format="1" style:print-orientation="portrait" fo:margin-top="2cm" fo:margin-bottom="2cm" fo:margin-left="2cm" fo:margin-right="2cm" style:writing-mode="lr-tb" style:footnote-max-height="0cm" loext:margin-gutter="0cm"> + <style:footnote-sep style:width="0.018cm" style:distance-before-sep="0.101cm" style:distance-after-sep="0.101cm" style:line-style="solid" style:adjustment="left" style:rel-width="25%" style:color="#000000"/> + </style:page-layout-properties> + <style:header-style/> + <style:footer-style/> + </style:page-layout> + </office:automatic-styles> + <office:master-styles> + <style:master-page style:name="Standard" style:page-layout-name="pm1"/> + </office:master-styles> + <office:body> + <office:text> + <text:sequence-decls> + <text:sequence-decl text:display-outline-level="0" text:name="Illustration"/> + <text:sequence-decl text:display-outline-level="0" text:name="Table"/> + <text:sequence-decl text:display-outline-level="0" text:name="Text"/> + <text:sequence-decl text:display-outline-level="0" text:name="Drawing"/> + <text:sequence-decl text:display-outline-level="0" text:name="Figure"/> + </text:sequence-decls> + <table:table table:name="Table1" table:style-name="Table1"> + <table:table-column table:style-name="Table1.A" table:number-columns-repeated="2"/> + <table:table-row> + <table:table-cell table:style-name="Table1.A1" office:value-type="string"> + <text:p text:style-name="Table_20_Contents"/> + </table:table-cell> + <table:table-cell table:style-name="Table1.B1" office:value-type="string"> + <text:p text:style-name="Table_20_Contents"/> + </table:table-cell> + </table:table-row> + <table:table-row> + <table:table-cell table:style-name="Table1.A2" office:value-type="string"> + <text:p text:style-name="Table_20_Contents"/> + </table:table-cell> + <table:table-cell table:style-name="Table1.B2" office:value-type="string"> + <text:p text:style-name="Standard"/> + <table:table table:name="Table2" table:style-name="Table2"> + <table:table-column table:style-name="Table2.A" table:number-columns-repeated="2"/> + <table:table-row> + <table:table-cell table:style-name="Table2.A1" office:value-type="string"> + <text:p text:style-name="Table_20_Contents"/> + </table:table-cell> + <table:table-cell table:style-name="Table2.B1" office:value-type="string"> + <text:p text:style-name="Table_20_Contents"/> + </table:table-cell> + </table:table-row> + <table:table-row> + <table:table-cell table:style-name="Table2.A2" office:value-type="string"> + <text:p text:style-name="Table_20_Contents"/> + </table:table-cell> + <table:table-cell table:style-name="Table2.B2" office:value-type="string"> + <text:p text:style-name="Table_20_Contents"/> + </table:table-cell> + </table:table-row> + </table:table> + </table:table-cell> + </table:table-row> + </table:table> + <text:p text:style-name="Standard"/> + </office:text> + </office:body> +</office:document> \ No newline at end of file diff --git a/sw/qa/extras/uiwriter/uiwriter.cxx b/sw/qa/extras/uiwriter/uiwriter.cxx index 955ed787c570..9a19ed53ca73 100644 --- a/sw/qa/extras/uiwriter/uiwriter.cxx +++ b/sw/qa/extras/uiwriter/uiwriter.cxx @@ -881,6 +881,23 @@ void SwUiWriterTest::testTdf67238() CPPUNIT_ASSERT(!((rTable.GetTableBox("C3"))->GetFrameFormat()->GetProtect()).IsContentProtected()); } +CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf155685) +{ + SwDoc* pDoc = createDoc("table-at-end-of-cell.fodt"); + SwWrtShell* pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->GoNextCell(); + pWrtShell->GoNextCell(); + pWrtShell->GoNextCell(); + pWrtShell->SelAll(); + pWrtShell->Delete(); + // this crashed + pWrtShell->Undo(); + pWrtShell->Undo(); + pWrtShell->Redo(); + // this crashed + pWrtShell->Redo(); +} + void SwUiWriterTest::testFdo75110() { SwDoc* pDoc = createDoc("fdo75110.odt"); diff --git a/sw/source/core/docnode/ndtbl.cxx b/sw/source/core/docnode/ndtbl.cxx index ca634ba914d4..b21acc7020c1 100644 --- a/sw/source/core/docnode/ndtbl.cxx +++ b/sw/source/core/docnode/ndtbl.cxx @@ -1976,15 +1976,28 @@ void SwDoc::DelTable(SwTableNode *const pTableNd) } // Save the cursors (UNO and otherwise) - SwPaM aSavePaM( SwNodeIndex( *pTableNd->EndOfSectionNode() ) ); - if( ! aSavePaM.Move( fnMoveForward, GoInNode ) ) + SwPaM const* pSavePaM(nullptr); + SwPaM forwardPaM{SwNodeIndex(*pTableNd->EndOfSectionNode())}; + if (forwardPaM.Move(fnMoveForward, GoInNode)) { - *aSavePaM.GetMark() = SwPosition( *pTableNd ); - aSavePaM.Move( fnMoveBackward, GoInNode ); + pSavePaM = &forwardPaM; } + SwPaM backwardPaM{SwNodeIndex(*pTableNd)}; + if (backwardPaM.Move(fnMoveBackward, GoInNode)) + { + if (pSavePaM == nullptr + // try to stay in the same outer table cell + || (forwardPaM.GetPoint()->nNode.GetNode().FindTableNode() != pTableNd->StartOfSectionNode()->FindTableNode() + && forwardPaM.GetPoint()->nNode.GetNode().StartOfSectionIndex() + < backwardPaM.GetPoint()->nNode.GetNode().StartOfSectionIndex())) + { + pSavePaM = &backwardPaM; + } + } + assert(pSavePaM); // due to bNewTextNd this must succeed { SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode()); - ::PaMCorrAbs(tmpPaM, *aSavePaM.GetMark()); + ::PaMCorrAbs(tmpPaM, *pSavePaM->GetPoint()); } // Move hard PageBreaks to the succeeding Node @@ -2026,15 +2039,28 @@ void SwDoc::DelTable(SwTableNode *const pTableNd) } // Save the cursors (UNO and otherwise) - SwPaM aSavePaM( SwNodeIndex( *pTableNd->EndOfSectionNode() ) ); - if( ! aSavePaM.Move( fnMoveForward, GoInNode ) ) + SwPaM const* pSavePaM(nullptr); + SwPaM forwardPaM{SwNodeIndex(*pTableNd->EndOfSectionNode())}; + if (forwardPaM.Move(fnMoveForward, GoInNode)) { - *aSavePaM.GetMark() = SwPosition( *pTableNd ); - aSavePaM.Move( fnMoveBackward, GoInNode ); + pSavePaM = &forwardPaM; + } + SwPaM backwardPaM{SwNodeIndex(*pTableNd)}; + if (backwardPaM.Move(fnMoveBackward, GoInNode)) + { + if (pSavePaM == nullptr + // try to stay in the same outer table cell + || (forwardPaM.GetPoint()->nNode.GetNode().FindTableNode() != pTableNd->StartOfSectionNode()->FindTableNode() + && forwardPaM.GetPoint()->nNode.GetNode().StartOfSectionIndex() + < backwardPaM.GetPoint()->nNode.GetNode().StartOfSectionIndex())) + { + pSavePaM = &backwardPaM; + } } + assert(pSavePaM); // due to bNewTextNd this must succeed { SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode()); - ::PaMCorrAbs(tmpPaM, *aSavePaM.GetMark()); + ::PaMCorrAbs(tmpPaM, *pSavePaM->GetPoint()); } // Move hard PageBreaks to the succeeding Node commit a7a1f426374f865ef31f5c5162eac4c1c11efc6c Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Fri Jun 9 13:59:58 2023 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Jun 14 18:00:18 2023 +0200 cool#6580 sw: fix infinite loop when changing document language If there's a footnote in the document, changing the document langauge goes into an infinite loop in FindParentText(), because the selection created by ExtendedSelectAll(true) is actually invalid, apparently the intention is that only very limited functions may be called while it is active. Don't handle this invalid "very" extended selection like one created by ExtendedSelectAll(false). (regression from commit d81379db730a163c5ff75d4f3a3cddbd7b5eddda) Change-Id: Icf1032715cf2e0a05bf485039c483440c08bb6bb Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152797 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit ca9341cf60f3f9350662d30b61f6eadefca24667) diff --git a/sw/qa/extras/uiwriter/uiwriter2.cxx b/sw/qa/extras/uiwriter/uiwriter2.cxx index f2a358d46b25..702efd1c2dff 100644 --- a/sw/qa/extras/uiwriter/uiwriter2.cxx +++ b/sw/qa/extras/uiwriter/uiwriter2.cxx @@ -23,6 +23,10 @@ #include <i18nlangtag/languagetag.hxx> #include <vcl/scheduler.hxx> #include <vcl/settings.hxx> +#include <sfx2/dispatch.hxx> +#include <sfx2/viewfrm.hxx> +#include <svx/svxids.hrc> +#include <view.hxx> #include <ndtxt.hxx> #include <swdtflvr.hxx> #include <wrtsh.hxx> @@ -256,6 +260,20 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testTdf101534) CPPUNIT_ASSERT(aSet.HasItem(RES_LR_SPACE)); } +CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testExtendedSelectAllHang) +{ + SwDoc* const pDoc = createDoc(); + SwWrtShell* const pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + + pWrtShell->InsertFootnote(""); + pWrtShell->StartOfSection(); + SwView* pView = pDoc->GetDocShell()->GetView(); + SfxStringItem aLangString(SID_LANGUAGE_STATUS, "Default_Spanish (Bolivia)"); + // this looped + pView->GetViewFrame()->GetDispatcher()->ExecuteList(SID_LANGUAGE_STATUS, SfxCallMode::SYNCHRON, + { &aLangString }); +} + CPPUNIT_TEST_FIXTURE(SwUiWriterTest2, testRedlineMoveInsertInDelete) { loadURL("private:factory/swriter", nullptr); diff --git a/sw/source/core/crsr/crsrsh.cxx b/sw/source/core/crsr/crsrsh.cxx index 1291a3db2e35..bec74a32816e 100644 --- a/sw/source/core/crsr/crsrsh.cxx +++ b/sw/source/core/crsr/crsrsh.cxx @@ -879,6 +879,16 @@ SwCursorShell::ExtendedSelectedAll() const typename SwCursorShell::StartsWith SwCursorShell::StartsWith_() { SwShellCursor const*const pShellCursor = getShellCursor(false); + // first, check if this is invalid; ExtendedSelectAll(true) may result in + // a) an ordinary selection that is valid + // b) a selection that is extended + // c) a selection that is invalid and will cause FindParentText to loop + SwNode const& rEndOfExtras(GetDoc()->GetNodes().GetEndOfExtras()); + if (pShellCursor->Start()->nNode.GetIndex() <= rEndOfExtras.GetIndex() + && rEndOfExtras.GetIndex() < pShellCursor->End()->nNode.GetIndex()) + { + return StartsWith::None; // *very* extended, no ExtendedSelectedAll handling! + } SwStartNode const*const pStartNode(FindParentText(*pShellCursor)); if (auto const ret = ::StartsWith(*pStartNode); ret != StartsWith::None) { commit a2b206ec80119000c581aa954632a1a9839408ba Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Mon Jun 5 20:24:45 2023 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Jun 14 17:56:35 2023 +0200 tdf#155685 sw: fix crash on undo of ExtendedSelectAll with table at end While the selection with table at the end works for Copy, unfortunately it doesn't work for Delete, because SwUndoDelete can't handle it, and an empty SwTableNode+SwEndNode pair remains. This needs some extra code, extract a SwDoc::DelTable() out of SwDoc::DeleteRowCol() and call it from SwEditShell::DeleteSel() to create separate SwUndoDelete objects per table, which appears to work. (regression from commit d81379db730a163c5ff75d4f3a3cddbd7b5eddda) Change-Id: I1534b100d31bc279bce298d3c2bd235cffc1f9d5 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/152628 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 16e40e8d6e6d606cd239331ad7d4c409b3add8e6) diff --git a/sw/inc/crsrsh.hxx b/sw/inc/crsrsh.hxx index d1cdb2edcfad..26e6895e88cd 100644 --- a/sw/inc/crsrsh.hxx +++ b/sw/inc/crsrsh.hxx @@ -326,7 +326,7 @@ public: // only for usage in special cases allowed! void ExtendedSelectAll(bool bFootnotes = true); /// If ExtendedSelectAll() was called and selection didn't change since then. - SwNode const* ExtendedSelectedAll() const; + ::std::optional<::std::pair<SwNode const*, ::std::vector<SwTableNode*>>> ExtendedSelectedAll() const; enum class StartsWith { None, Table, HiddenPara }; /// If document body starts with a table or starts/ends with hidden paragraph. StartsWith StartsWith_(); diff --git a/sw/inc/doc.hxx b/sw/inc/doc.hxx index 83651fc8fcb2..66ec563e7a14 100644 --- a/sw/inc/doc.hxx +++ b/sw/inc/doc.hxx @@ -1189,6 +1189,7 @@ public: sal_uInt16 nCnt = 1, bool bBehind = true ); // Delete Columns/Rows in table. + void DelTable(SwTableNode * pTable); bool DeleteRowCol( const SwSelBoxes& rBoxes, bool bColumn = false ); void DeleteRow( const SwCursor& rCursor ); void DeleteCol( const SwCursor& rCursor ); diff --git a/sw/source/core/crsr/crsrsh.cxx b/sw/source/core/crsr/crsrsh.cxx index 8aa3ebd86995..1291a3db2e35 100644 --- a/sw/source/core/crsr/crsrsh.cxx +++ b/sw/source/core/crsr/crsrsh.cxx @@ -803,11 +803,12 @@ static typename SwCursorShell::StartsWith EndsWith(SwStartNode const& rStart) // return the node that is the start of the extended selection (to include table // or section start nodes; looks like extending for end nodes is not required) -SwNode const* SwCursorShell::ExtendedSelectedAll() const +::std::optional<::std::pair<SwNode const*, ::std::vector<SwTableNode*>>> +SwCursorShell::ExtendedSelectedAll() const { if (m_pTableCursor) { - return nullptr; + return {}; } SwNodes& rNodes = GetDoc()->GetNodes(); @@ -818,27 +819,48 @@ SwNode const* SwCursorShell::ExtendedSelectedAll() const SwContentNode* pStart = rNodes.GoNext(&nNode); if (!pStart) { - return nullptr; + return {}; } nNode = *pStartNode->EndOfSectionNode(); SwContentNode* pEnd = SwNodes::GoPrevious(&nNode); if (!pEnd) { - return nullptr; + return {}; } SwPosition aStart(*pStart, 0); SwPosition aEnd(*pEnd, pEnd->Len()); if (!(aStart == *pShellCursor->Start() && aEnd == *pShellCursor->End())) { - return nullptr; + return {}; } + auto const ends(::EndsWith(*pStartNode)); if (::StartsWith(*pStartNode) == StartsWith::None - && ::EndsWith(*pStartNode) == StartsWith::None) + && ends == StartsWith::None) { - return nullptr; // "ordinary" selection will work + return {}; // "ordinary" selection will work + } + + ::std::vector<SwTableNode*> tablesAtEnd; + if (ends == StartsWith::Table) + { + SwNode * pLastNode(rNodes[pStartNode->EndOfSectionIndex() - 1]); + while (pLastNode->IsEndNode()) + { + SwNode *const pNode(pLastNode->StartOfSectionNode()); + if (pNode->IsTableNode()) + { + tablesAtEnd.push_back(pNode->GetTableNode()); + pLastNode = rNodes[pNode->GetIndex() - 1]; + } + else if (pNode->IsSectionNode()) + { + pLastNode = rNodes[pLastNode->GetIndex() - 1]; + } + } + assert(!tablesAtEnd.empty()); } // tdf#133990 ensure directly containing section is included in SwUndoDelete @@ -851,7 +873,7 @@ SwNode const* SwCursorShell::ExtendedSelectedAll() const // pStartNode is the node that fully contains the selection - the first // node of the selection is the first node inside pStartNode - return pStartNode->GetNodes()[pStartNode->GetIndex() + 1]; + return ::std::make_pair(rNodes[pStartNode->GetIndex() + 1], tablesAtEnd); } typename SwCursorShell::StartsWith SwCursorShell::StartsWith_() diff --git a/sw/source/core/docnode/ndtbl.cxx b/sw/source/core/docnode/ndtbl.cxx index b12fc72228e2..ca634ba914d4 100644 --- a/sw/source/core/docnode/ndtbl.cxx +++ b/sw/source/core/docnode/ndtbl.cxx @@ -1933,6 +1933,135 @@ void SwDoc::DeleteCol( const SwCursor& rCursor ) GetIDocumentUndoRedo().EndUndo(SwUndoId::COL_DELETE, nullptr); } +void SwDoc::DelTable(SwTableNode *const pTableNd) +{ + bool bNewTextNd = false; + // Is it alone in a FlyFrame? + SwNodeIndex aIdx( *pTableNd, -1 ); + const SwStartNode* pSttNd = aIdx.GetNode().GetStartNode(); + if( pSttNd ) + { + const sal_uLong nTableEnd = pTableNd->EndOfSectionIndex() + 1; + const sal_uLong nSectEnd = pSttNd->EndOfSectionIndex(); + if( nTableEnd == nSectEnd ) + { + if( SwFlyStartNode == pSttNd->GetStartNodeType() ) + { + SwFrameFormat* pFormat = pSttNd->GetFlyFormat(); + if( pFormat ) + { + // That's the FlyFormat we're looking for + getIDocumentLayoutAccess().DelLayoutFormat( pFormat ); + return; + } + } + // No Fly? Thus Header or Footer: always leave a TextNode + // We can forget about Undo then! + bNewTextNd = true; + } + } + + // No Fly? Then it is a Header or Footer, so keep always a TextNode + ++aIdx; + if (GetIDocumentUndoRedo().DoesUndo()) + { + GetIDocumentUndoRedo().ClearRedo(); + SwPaM aPaM( *pTableNd->EndOfSectionNode(), aIdx.GetNode() ); + + if( bNewTextNd ) + { + const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 ); + GetNodes().MakeTextNode( aTmpIdx, + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) ); + } + + // Save the cursors (UNO and otherwise) + SwPaM aSavePaM( SwNodeIndex( *pTableNd->EndOfSectionNode() ) ); + if( ! aSavePaM.Move( fnMoveForward, GoInNode ) ) + { + *aSavePaM.GetMark() = SwPosition( *pTableNd ); + aSavePaM.Move( fnMoveBackward, GoInNode ); + } + { + SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode()); + ::PaMCorrAbs(tmpPaM, *aSavePaM.GetMark()); + } + + // Move hard PageBreaks to the succeeding Node + bool bSavePageBreak = false, bSavePageDesc = false; + sal_uLong nNextNd = pTableNd->EndOfSectionIndex()+1; + SwContentNode* pNextNd = GetNodes()[ nNextNd ]->GetContentNode(); + if( pNextNd ) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + const SfxPoolItem *pItem; + if( SfxItemState::SET == pTableFormat->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + { + pNextNd->SetAttr( *pItem ); + bSavePageDesc = true; + } + + if( SfxItemState::SET == pTableFormat->GetItemState( RES_BREAK, + false, &pItem ) ) + { + pNextNd->SetAttr( *pItem ); + bSavePageBreak = true; + } + } + std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete(aPaM, SwDeleteFlags::Default)); + if( bNewTextNd ) + pUndo->SetTableDelLastNd(); + pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc ); + pUndo->SetTableName(pTableNd->GetTable().GetFrameFormat()->GetName()); + GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); + } + else + { + if( bNewTextNd ) + { + const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 ); + GetNodes().MakeTextNode( aTmpIdx, + getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) ); + } + + // Save the cursors (UNO and otherwise) + SwPaM aSavePaM( SwNodeIndex( *pTableNd->EndOfSectionNode() ) ); + if( ! aSavePaM.Move( fnMoveForward, GoInNode ) ) + { + *aSavePaM.GetMark() = SwPosition( *pTableNd ); + aSavePaM.Move( fnMoveBackward, GoInNode ); + } + { + SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode()); + ::PaMCorrAbs(tmpPaM, *aSavePaM.GetMark()); + } + + // Move hard PageBreaks to the succeeding Node + SwContentNode* pNextNd = GetNodes()[ pTableNd->EndOfSectionIndex()+1 ]->GetContentNode(); + if( pNextNd ) + { + SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); + const SfxPoolItem *pItem; + if( SfxItemState::SET == pTableFormat->GetItemState( RES_PAGEDESC, + false, &pItem ) ) + pNextNd->SetAttr( *pItem ); + + if( SfxItemState::SET == pTableFormat->GetItemState( RES_BREAK, + false, &pItem ) ) + pNextNd->SetAttr( *pItem ); + } + + pTableNd->DelFrames(); + getIDocumentContentOperations().DeleteSection( pTableNd ); + } + + GetDocShell()->GetFEShell()->UpdateTableStyleFormatting(); + + getIDocumentState().SetModified(); + getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); +} + bool SwDoc::DeleteRowCol( const SwSelBoxes& rBoxes, bool bColumn ) { if( ::HasProtectedCells( rBoxes )) @@ -1966,132 +2095,7 @@ bool SwDoc::DeleteRowCol( const SwSelBoxes& rBoxes, bool bColumn ) aSelBoxes[0]->GetSttIdx()-1 == nTmpIdx1 && nTmpIdx2 == pTableNd->EndOfSectionIndex() ) { - bool bNewTextNd = false; - // Is it alone in a FlyFrame? - SwNodeIndex aIdx( *pTableNd, -1 ); - const SwStartNode* pSttNd = aIdx.GetNode().GetStartNode(); - if( pSttNd ) - { - const sal_uLong nTableEnd = pTableNd->EndOfSectionIndex() + 1; - const sal_uLong nSectEnd = pSttNd->EndOfSectionIndex(); - if( nTableEnd == nSectEnd ) - { - if( SwFlyStartNode == pSttNd->GetStartNodeType() ) - { - SwFrameFormat* pFormat = pSttNd->GetFlyFormat(); - if( pFormat ) - { - // That's the FlyFormat we're looking for - getIDocumentLayoutAccess().DelLayoutFormat( pFormat ); - return true; - } - } - // No Fly? Thus Header or Footer: always leave a TextNode - // We can forget about Undo then! - bNewTextNd = true; - } - } - - // No Fly? Then it is a Header or Footer, so keep always a TextNode - ++aIdx; - if (GetIDocumentUndoRedo().DoesUndo()) - { - GetIDocumentUndoRedo().ClearRedo(); - SwPaM aPaM( *pTableNd->EndOfSectionNode(), aIdx.GetNode() ); - - if( bNewTextNd ) - { - const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 ); - GetNodes().MakeTextNode( aTmpIdx, - getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) ); - } - - // Save the cursors (UNO and otherwise) - SwPaM aSavePaM( SwNodeIndex( *pTableNd->EndOfSectionNode() ) ); - if( ! aSavePaM.Move( fnMoveForward, GoInNode ) ) - { - *aSavePaM.GetMark() = SwPosition( *pTableNd ); - aSavePaM.Move( fnMoveBackward, GoInNode ); - } - { - SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode()); - ::PaMCorrAbs(tmpPaM, *aSavePaM.GetMark()); - } - - // Move hard PageBreaks to the succeeding Node - bool bSavePageBreak = false, bSavePageDesc = false; - sal_uLong nNextNd = pTableNd->EndOfSectionIndex()+1; - SwContentNode* pNextNd = GetNodes()[ nNextNd ]->GetContentNode(); - if( pNextNd ) - { - SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); - const SfxPoolItem *pItem; - if( SfxItemState::SET == pTableFormat->GetItemState( RES_PAGEDESC, - false, &pItem ) ) - { - pNextNd->SetAttr( *pItem ); - bSavePageDesc = true; - } - - if( SfxItemState::SET == pTableFormat->GetItemState( RES_BREAK, - false, &pItem ) ) - { - pNextNd->SetAttr( *pItem ); - bSavePageBreak = true; - } - } - std::unique_ptr<SwUndoDelete> pUndo(new SwUndoDelete(aPaM, SwDeleteFlags::Default)); - if( bNewTextNd ) - pUndo->SetTableDelLastNd(); - pUndo->SetPgBrkFlags( bSavePageBreak, bSavePageDesc ); - pUndo->SetTableName(pTableNd->GetTable().GetFrameFormat()->GetName()); - GetIDocumentUndoRedo().AppendUndo( std::move(pUndo) ); - } - else - { - if( bNewTextNd ) - { - const SwNodeIndex aTmpIdx( *pTableNd->EndOfSectionNode(), 1 ); - GetNodes().MakeTextNode( aTmpIdx, - getIDocumentStylePoolAccess().GetTextCollFromPool( RES_POOLCOLL_STANDARD ) ); - } - - // Save the cursors (UNO and otherwise) - SwPaM aSavePaM( SwNodeIndex( *pTableNd->EndOfSectionNode() ) ); - if( ! aSavePaM.Move( fnMoveForward, GoInNode ) ) - { - *aSavePaM.GetMark() = SwPosition( *pTableNd ); - aSavePaM.Move( fnMoveBackward, GoInNode ); - } - { - SwPaM const tmpPaM(*pTableNd, *pTableNd->EndOfSectionNode()); - ::PaMCorrAbs(tmpPaM, *aSavePaM.GetMark()); - } - - // Move hard PageBreaks to the succeeding Node - SwContentNode* pNextNd = GetNodes()[ pTableNd->EndOfSectionIndex()+1 ]->GetContentNode(); - if( pNextNd ) - { - SwFrameFormat* pTableFormat = pTableNd->GetTable().GetFrameFormat(); - const SfxPoolItem *pItem; - if( SfxItemState::SET == pTableFormat->GetItemState( RES_PAGEDESC, - false, &pItem ) ) - pNextNd->SetAttr( *pItem ); - - if( SfxItemState::SET == pTableFormat->GetItemState( RES_BREAK, - false, &pItem ) ) - pNextNd->SetAttr( *pItem ); - } - - pTableNd->DelFrames(); - getIDocumentContentOperations().DeleteSection( pTableNd ); - } - - GetDocShell()->GetFEShell()->UpdateTableStyleFormatting(); - - getIDocumentState().SetModified(); - getIDocumentFieldsAccess().SetFieldsDirty( true, nullptr, 0 ); - + DelTable(pTableNd); return true; } diff --git a/sw/source/core/edit/eddel.cxx b/sw/source/core/edit/eddel.cxx index 7ff1a8d144ea..ebb33eca9e43 100644 --- a/sw/source/core/edit/eddel.cxx +++ b/sw/source/core/edit/eddel.cxx @@ -42,9 +42,9 @@ void SwEditShell::DeleteSel(SwPaM& rPam, bool const isArtificialSelection, bool *const pUndo) { - SwNode const*const pSelectAllStart(StartsWith_() != SwCursorShell::StartsWith::None + auto const oSelectAll(StartsWith_() != SwCursorShell::StartsWith::None ? ExtendedSelectedAll() - : nullptr); + : ::std::optional<::std::pair<SwNode const*, ::std::vector<SwTableNode *>>>{}); // only for selections if (!rPam.HasMark() || (*rPam.GetPoint() == *rPam.GetMark() @@ -60,7 +60,7 @@ void SwEditShell::DeleteSel(SwPaM& rPam, bool const isArtificialSelection, bool // 3. Point and Mark are at the document start and end, Point is in a table: delete selection as usual if( rPam.GetNode().FindTableNode() && rPam.GetNode().StartOfSectionNode() != - rPam.GetNode(false).StartOfSectionNode() && pSelectAllStart == nullptr) + rPam.GetNode(false).StartOfSectionNode() && !oSelectAll) { // group the Undo in the table if( pUndo && !*pUndo ) @@ -104,13 +104,18 @@ void SwEditShell::DeleteSel(SwPaM& rPam, bool const isArtificialSelection, bool { std::unique_ptr<SwPaM> pNewPam; SwPaM * pPam = &rPam; - if (pSelectAllStart) + if (oSelectAll) { + // tdf#155685 tables at the end must be deleted separately + for (SwTableNode *const pTable : oSelectAll->second) + { + GetDoc()->DelTable(pTable); + } assert(dynamic_cast<SwShellCursor*>(&rPam)); // must be corrected pam pNewPam.reset(new SwPaM(*rPam.GetMark(), *rPam.GetPoint())); // Selection starts at the first para of the first cell, but we // want to delete the table node before the first cell as well. - *pNewPam->Start() = SwPosition(*pSelectAllStart); + *pNewPam->Start() = SwPosition(*oSelectAll->first); pPam = pNewPam.get(); } // delete everything diff --git a/sw/source/core/edit/edglss.cxx b/sw/source/core/edit/edglss.cxx index 9da7d1d67ae6..bad2bb719a86 100644 --- a/sw/source/core/edit/edglss.cxx +++ b/sw/source/core/edit/edglss.cxx @@ -203,9 +203,9 @@ bool SwEditShell::CopySelToDoc( SwDoc* pInsDoc ) bool bColSel = GetCursor_()->IsColumnSelection(); if( bColSel && pInsDoc->IsClipBoard() ) pInsDoc->SetColumnSelection( true ); - SwNode const*const pSelectAllStart(StartsWith_() != SwCursorShell::StartsWith::None + auto const oSelectAll(StartsWith_() != SwCursorShell::StartsWith::None ? ExtendedSelectedAll() - : nullptr); + : ::std::optional<::std::pair<SwNode const*, ::std::vector<SwTableNode*>>>{}); { for(SwPaM& rPaM : GetCursor()->GetRingContainer()) { @@ -229,12 +229,12 @@ bool SwEditShell::CopySelToDoc( SwDoc* pInsDoc ) // for the purpose of copying, our shell cursor is not touched. // (Otherwise we would have to restore it.) SwPaM aPaM(*rPaM.GetMark(), *rPaM.GetPoint()); - if (pSelectAllStart) + if (oSelectAll) { // Selection starts at the first para of the first cell, // but we want to copy the table and the start node before // the first cell as well. - *aPaM.Start() = SwPosition(*pSelectAllStart); + *aPaM.Start() = SwPosition(*oSelectAll->first); } bRet = GetDoc()->getIDocumentContentOperations().CopyRange( aPaM, aPos, /*bCopyAll=*/false, /*bCheckPos=*/true, /*bCopyText=*/false ) || bRet; } commit 86d9b8f6f353f5d268f67f2c33681b8fa4f46ea7 Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Wed May 17 14:49:55 2023 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Jun 14 17:17:08 2023 +0200 tdf#155346 sw: fix crash from changing modal mode When closing the dialog, UpdateCursor() creates a table cursor for ExtendedSelectedAll() because mbSelectAll isn't set. 1 SwShellTableCursor::SwShellTableCursor 2 SwCursorShell::UpdateCursor 3 SwCursorShell::ShowCursor 4 SwView::ShowCursor 5 SfxViewFrame::Enable 6 SfxViewFrame::Notify 7 SfxBroadcaster::Broadcast 8 SfxObjectShell::SetModalMode_Impl 9 SfxViewFrame::SetModalMode (regression from commit d81379db730a163c5ff75d4f3a3cddbd7b5eddda) Change-Id: Ie73f8e42f764f8041288eb0850721a530d106a0e Reviewed-on: https://gerrit.libreoffice.org/c/core/+/151880 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit 1458d2f935c50ab7f3e2f1277d1bc7d1b5b5f894) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/151926 Reviewed-by: Xisco Fauli <xiscofa...@libreoffice.org> (cherry picked from commit 8a7fbac52b29da29564e1533030f111ec7e4e9ac) diff --git a/sw/source/core/crsr/crsrsh.cxx b/sw/source/core/crsr/crsrsh.cxx index b3492bb5505e..8aa3ebd86995 100644 --- a/sw/source/core/crsr/crsrsh.cxx +++ b/sw/source/core/crsr/crsrsh.cxx @@ -2631,6 +2631,8 @@ void SwCursorShell::ShowCursor() { if( !m_bBasicHideCursor ) { + comphelper::FlagRestorationGuard g(mbSelectAll, StartsWith_() != StartsWith::None && ExtendedSelectedAll()); + m_bSVCursorVis = true; m_pCurrentCursor->SetShowTextInputFieldOverlay( true ); commit 9667e5ffd18d6167344e102b89a393bc981644ec Author: Michael Stahl <michael.st...@allotropia.de> AuthorDate: Mon May 8 16:38:03 2023 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Jun 14 17:12:23 2023 +0200 tdf#154877 sw: generalise ExtendedSelectAll() This used to work only in the body text; make it work in any text, so also text frame, header/footer, footnote, table cell. (fixes regression from commit 0590cd2857f68f48b8847071a9c1a7dbef135721) This is made much more difficult by table cells, which may contain nested tables; there is already some code to switch between text selection (via m_pCurrentCursor) and table cell selection (via m_pTableCursor). There were also a few things that looked kinda wrong, but i forgot where. One tricky case that can't be handled well is if there are multiple tables in a text but no paragraph (this is impossible in the body but possible in other texts by removing the paragraph via Ctrl+Shift+Delete) ... here the most we get is one table fully selected, because the SwTableCursor can't select cells from multiple tables. Change-Id: I0964baacecb8f10636792a27efa9ea6b18da4709 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/151544 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@allotropia.de> (cherry picked from commit d81379db730a163c5ff75d4f3a3cddbd7b5eddda) Reviewed-on: https://gerrit.libreoffice.org/c/core/+/151766 Reviewed-by: Thorsten Behrens <thorsten.behr...@allotropia.de> (cherry picked from commit ad03ed5106499f74b38da28489a9352ce3f01d1a) diff --git a/sw/inc/crsrsh.hxx b/sw/inc/crsrsh.hxx index a0205ffe4d52..d1cdb2edcfad 100644 --- a/sw/inc/crsrsh.hxx +++ b/sw/inc/crsrsh.hxx @@ -326,7 +326,7 @@ public: // only for usage in special cases allowed! void ExtendedSelectAll(bool bFootnotes = true); /// If ExtendedSelectAll() was called and selection didn't change since then. - bool ExtendedSelectedAll(); + SwNode const* ExtendedSelectedAll() const; enum class StartsWith { None, Table, HiddenPara }; /// If document body starts with a table or starts/ends with hidden paragraph. StartsWith StartsWith_(); @@ -586,8 +586,11 @@ public: // fields etc. OUString GetSelText() const; - // Check of SPoint or Mark of current cursor are placed within a table. - inline const SwTableNode* IsCursorInTable() const; + /// Check if Point of current cursor is placed within a table. + const SwTableNode* IsCursorInTable() const; + bool MoveOutOfTable(); + bool TrySelectOuterTable(); + bool MoveStartText(); bool IsCursorInFootnote() const; @@ -901,11 +904,6 @@ inline bool SwCursorShell::IsMultiSelection() const return m_pCurrentCursor->GetNext() != m_pCurrentCursor; } -inline const SwTableNode* SwCursorShell::IsCursorInTable() const -{ - return m_pCurrentCursor->GetNode().FindTableNode(); -} - inline bool SwCursorShell::IsCursorPtAtEnd() const { return m_pCurrentCursor->End() == m_pCurrentCursor->GetPoint(); diff --git a/sw/qa/extras/odfimport/odfimport.cxx b/sw/qa/extras/odfimport/odfimport.cxx index 3fa5321389e4..dc9bc44679e6 100644 --- a/sw/qa/extras/odfimport/odfimport.cxx +++ b/sw/qa/extras/odfimport/odfimport.cxx @@ -590,6 +590,8 @@ DECLARE_ODFIMPORT_TEST(testFdo37606, "fdo37606.odt") pWrtShell->SelAll(); // Selects the whole table. pWrtShell->SelAll(); // Selects the whole document. + pShellCursor = pWrtShell->getShellCursor(false); + SwTextNode& rStart = dynamic_cast<SwTextNode&>(pShellCursor->Start()->nNode.GetNode()); CPPUNIT_ASSERT_EQUAL(OUString("A1"), rStart.GetText()); @@ -655,11 +657,11 @@ DECLARE_ODFIMPORT_TEST(testFdo69862, "fdo69862.odt") SwXTextDocument* pTextDoc = dynamic_cast<SwXTextDocument *>(mxComponent.get()); CPPUNIT_ASSERT(pTextDoc); SwWrtShell* pWrtShell = pTextDoc->GetDocShell()->GetWrtShell(); - SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); pWrtShell->SelAll(); // Selects A1. pWrtShell->SelAll(); // Selects the whole table. pWrtShell->SelAll(); // Selects the whole document. + SwShellCursor* pShellCursor = pWrtShell->getShellCursor(false); SwTextNode& rStart = dynamic_cast<SwTextNode&>(pShellCursor->Start()->nNode.GetNode()); // This was "Footnote.", as Ctrl-A also selected footnotes, but it should not. CPPUNIT_ASSERT_EQUAL(OUString("A1"), rStart.GetText()); diff --git a/sw/qa/extras/uiwriter/uiwriter.cxx b/sw/qa/extras/uiwriter/uiwriter.cxx index 23aae7c1f9b4..955ed787c570 100644 --- a/sw/qa/extras/uiwriter/uiwriter.cxx +++ b/sw/qa/extras/uiwriter/uiwriter.cxx @@ -7891,6 +7891,7 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf133982) lcl_dispatchCommand(mxComponent, ".uno:SelectAll", {}); lcl_dispatchCommand(mxComponent, ".uno:SelectAll", {}); lcl_dispatchCommand(mxComponent, ".uno:SelectAll", {}); + lcl_dispatchCommand(mxComponent, ".uno:SelectAll", {}); //Without the fix in place, it would have crashed here lcl_dispatchCommand(mxComponent, ".uno:Cut", {}); @@ -7950,10 +7951,12 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest, testTdf114973) { SwDoc* const pDoc = createDoc("tdf114973.fodt"); + SwWrtShell* const pWrtShell = pDoc->GetDocShell()->GetWrtShell(); + pWrtShell->SttEndDoc(true); + lcl_dispatchCommand(mxComponent, ".uno:SelectAll", {}); Scheduler::ProcessEventsToIdle(); - SwWrtShell* const pWrtShell = pDoc->GetDocShell()->GetWrtShell(); // bug: cursor jumped into header CPPUNIT_ASSERT(!pWrtShell->IsInHeaderFooter()); diff --git a/sw/source/core/crsr/crsrsh.cxx b/sw/source/core/crsr/crsrsh.cxx index 2258d39a8422..b3492bb5505e 100644 --- a/sw/source/core/crsr/crsrsh.cxx +++ b/sw/source/core/crsr/crsrsh.cxx @@ -581,54 +581,290 @@ bool SwCursorShell::SttEndDoc( bool bStt ) return bRet; } +const SwTableNode* SwCursorShell::IsCursorInTable() const +{ + if (m_pTableCursor) + { // find the table that has the selected boxes + return m_pTableCursor->GetSelectedBoxes()[0]->GetSttNd()->FindTableNode(); + } + return m_pCurrentCursor->GetNode().FindTableNode(); +} + +// fun cases to consider: +// * outermost table +// - into para => SA/ESA +// - into prev/next table => continue... +// - no prev/next => done +// * inner table +// - into containing cell => SA/ESA +// - into prev/next of containing cell +// + into para +// + into table nested in prev/next cell +// - out of table -> as above +// => iterate in one direction until a node is reached that is a parent or a sibling of a parent of the current table +// - parent reached => SA/ESA depending +// - not in parent but in *prev/next* sibling of outer cell => TrySelectOuterTable +// - not in parent but in *prev/next* sibling of outer table => TrySelectOuterTable +// => select-all cannot select a sequence of table with no para at same level; only 1 table +// - no parent, no prev/next => TrySelectOuterTable + +bool SwCursorShell::MoveOutOfTable() +{ + SwPosition const point(*getShellCursor(false)->GetPoint()); + SwPosition const mark(*getShellCursor(false)->GetMark()); + + for (auto const fnMove : {&fnMoveBackward, &fnMoveForward}) + { + Push(); + SwCursor *const pCursor(getShellCursor(false)); + + pCursor->Normalize(fnMove == &fnMoveBackward); + pCursor->DeleteMark(); + SwTableNode const*const pTable(pCursor->GetPoint()->nNode.GetNode().FindTableNode()); + assert(pTable); + while (MovePara(GoInContent, *fnMove)) + { + SwStartNode const*const pBox(pCursor->GetPoint()->nNode.GetNode().FindTableBoxStartNode()); + if (!pBox) + { + Pop(SwCursorShell::PopMode::DeleteStack); + return true; // moved to paragraph at top-level of text + } + if (pBox->GetIndex() < pTable->GetIndex() + && pTable->EndOfSectionIndex() < pBox->EndOfSectionIndex()) + { + Pop(SwCursorShell::PopMode::DeleteStack); + return true; // pBox contains start position (pTable) + } + } + + Pop(SwCursorShell::PopMode::DeleteCurrent); + // FIXME: Pop doesn't restore original cursor if nested tables + *getShellCursor(false)->GetPoint() = point; + getShellCursor(false)->SetMark(); + *getShellCursor(false)->GetMark() = mark; + } + return false; +} + +bool SwCursorShell::TrySelectOuterTable() +{ + assert(m_pTableCursor); + SwTableNode const& rInnerTable(*m_pTableCursor->GetPoint()->nNode.GetNode().FindTableNode()); + SwNodes const& rNodes(rInnerTable.GetNodes()); + SwTableNode const*const pOuterTable(rInnerTable.GetNodes()[rInnerTable.GetIndex()-1]->FindTableNode()); + if (!pOuterTable) + { + return false; + } + + // manually select boxes of pOuterTable + SwNodeIndex firstCell(*pOuterTable, +1); + SwNodeIndex lastCell(*rNodes[pOuterTable->EndOfSectionIndex()-1]->StartOfSectionNode()); + SwSelBoxes aNew; + pOuterTable->GetTable().CreateSelection(&firstCell.GetNode(), &lastCell.GetNode(), + aNew, SwTable::SEARCH_NONE, false); + // set table cursor to 1st / last content which may be in inner table + SwContentNode *const pStart = rNodes.GoNext(&firstCell); + assert(pStart); // must at least find the previous point node + lastCell = *lastCell.GetNode().EndOfSectionNode(); + SwContentNode *const pEnd = SwNodes::GoPrevious(&lastCell); + assert(pEnd); // must at least find the previous point node + delete m_pTableCursor; + m_pTableCursor = new SwShellTableCursor(*this, SwPosition(*pStart, 0), Point(), + SwPosition(*pEnd, 0), Point()); + m_pTableCursor->ActualizeSelection( aNew ); + m_pTableCursor->IsCursorMovedUpdate(); // clear this so GetCursor() doesn't recreate our SwSelBoxes + + // this will update m_pCurrentCursor based on m_pTableCursor + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + + return true; +} + +/// find XText start node +static SwStartNode const* FindTextStart(SwPosition const& rPos) +{ + SwStartNode const* pStartNode(rPos.nNode.GetNode().StartOfSectionNode()); + while (pStartNode && (pStartNode->IsSectionNode() || pStartNode->IsTableNode())) + { + pStartNode = pStartNode->StartOfSectionNode(); + } + return pStartNode; +} + +static SwStartNode const* FindParentText(SwShellCursor const& rCursor) +{ + // find closest section containing both start and end - ignore Sections + SwStartNode const* pStartNode(FindTextStart(*rCursor.Start())); + SwEndNode const* pEndNode(FindTextStart(*rCursor.End())->EndOfSectionNode()); + while (pStartNode->EndOfSectionNode()->GetIndex() < pEndNode->GetIndex()) + { + pStartNode = pStartNode->StartOfSectionNode(); + } + while (pStartNode->GetIndex() < pEndNode->StartOfSectionNode()->GetIndex()) + { + pEndNode = pEndNode->StartOfSectionNode()->StartOfSectionNode()->EndOfSectionNode(); + } + assert(pStartNode->EndOfSectionNode() == pEndNode); + + return (pStartNode->IsSectionNode() || pStartNode->IsTableNode()) + ? FindTextStart(SwPosition(*pStartNode)) + : pStartNode; +} + +bool SwCursorShell::MoveStartText() +{ + SwPosition const old(*m_pCurrentCursor->GetPoint()); + SwStartNode const*const pStartNode(FindParentText(*getShellCursor(false))); + assert(pStartNode); + SwTableNode const*const pTable(pStartNode->FindTableNode()); + *m_pCurrentCursor->GetPoint() = SwPosition(*pStartNode); + GetDoc()->GetNodes().GoNext(&m_pCurrentCursor->GetPoint()->nNode); + m_pCurrentCursor->GetPoint()->nContent.Assign(m_pCurrentCursor->GetPoint()->nNode.GetNode().GetContentNode(), 0); + while (m_pCurrentCursor->GetPoint()->nNode.GetNode().FindTableNode() != pTable + && (!pTable || pTable->GetIndex() < m_pCurrentCursor->GetPoint()->nNode.GetNode().FindTableNode()->GetIndex()) + && MoveOutOfTable()); + UpdateCursor(SwCursorShell::SCROLLWIN|SwCursorShell::CHKRANGE|SwCursorShell::READONLY); + return old != *m_pCurrentCursor->GetPoint(); +} + +// select all inside the current XText, with table or hidden para at start/end void SwCursorShell::ExtendedSelectAll(bool bFootnotes) { + // find common ancestor node of both ends of cursor + SwStartNode const*const pStartNode(FindParentText(*getShellCursor(false))); + assert(pStartNode); + if (IsTableMode()) + { // convert m_pTableCursor to m_pCurrentCursor after determining pStartNode + TableCursorToCursor(); + } SwNodes& rNodes = GetDoc()->GetNodes(); + m_pCurrentCursor->Normalize(true); SwPosition* pPos = m_pCurrentCursor->GetPoint(); - pPos->nNode = bFootnotes ? rNodes.GetEndOfPostIts() : rNodes.GetEndOfAutotext(); + pPos->nNode = bFootnotes ? rNodes.GetEndOfPostIts() : static_cast<SwNode const&>(*pStartNode); pPos->nContent.Assign( rNodes.GoNext( &pPos->nNode ), 0 ); pPos = m_pCurrentCursor->GetMark(); - pPos->nNode = rNodes.GetEndOfContent(); + pPos->nNode = bFootnotes ? rNodes.GetEndOfContent() : static_cast<SwNode const&>(*pStartNode->EndOfSectionNode()); SwContentNode* pCNd = SwNodes::GoPrevious( &pPos->nNode ); pPos->nContent.Assign( pCNd, pCNd ? pCNd->Len() : 0 ); } -bool SwCursorShell::ExtendedSelectedAll() +static typename SwCursorShell::StartsWith StartsWith(SwStartNode const& rStart) +{ + for (auto i = rStart.GetIndex() + 1; i < rStart.EndOfSectionIndex(); ++i) + { + SwNode const& rNode(*rStart.GetNodes()[i]); + switch (rNode.GetNodeType()) + { + case SwNodeType::Section: + continue; + case SwNodeType::Table: + return SwCursorShell::StartsWith::Table; + case SwNodeType::Text: + if (rNode.GetTextNode()->IsHidden()) + { + return SwCursorShell::StartsWith::HiddenPara; + } + return SwCursorShell::StartsWith::None; + default: + return SwCursorShell::StartsWith::None; + } + } + return SwCursorShell::StartsWith::None; +} + +static typename SwCursorShell::StartsWith EndsWith(SwStartNode const& rStart) { + for (auto i = rStart.EndOfSectionIndex() - 1; rStart.GetIndex() < i; --i) + { + SwNode const& rNode(*rStart.GetNodes()[i]); + switch (rNode.GetNodeType()) + { + case SwNodeType::End: + if (rNode.StartOfSectionNode()->IsTableNode()) + { + return SwCursorShell::StartsWith::Table; + } +//TODO buggy SwUndoRedline in testTdf137503? assert(rNode.StartOfSectionNode()->IsSectionNode()); + break; + case SwNodeType::Text: + if (rNode.GetTextNode()->IsHidden()) + { + return SwCursorShell::StartsWith::HiddenPara; + } + return SwCursorShell::StartsWith::None; + default: + return SwCursorShell::StartsWith::None; + } + } + return SwCursorShell::StartsWith::None; +} + +// return the node that is the start of the extended selection (to include table +// or section start nodes; looks like extending for end nodes is not required) +SwNode const* SwCursorShell::ExtendedSelectedAll() const +{ + if (m_pTableCursor) + { + return nullptr; + } + SwNodes& rNodes = GetDoc()->GetNodes(); - SwNodeIndex nNode = rNodes.GetEndOfAutotext(); + SwShellCursor const*const pShellCursor = getShellCursor(false); + SwStartNode const* pStartNode(FindParentText(*pShellCursor)); + + SwNodeIndex nNode(*pStartNode); SwContentNode* pStart = rNodes.GoNext(&nNode); + if (!pStart) + { + return nullptr; + } - nNode = rNodes.GetEndOfContent(); + nNode = *pStartNode->EndOfSectionNode(); SwContentNode* pEnd = SwNodes::GoPrevious(&nNode); - - if (!pStart || !pEnd) - return false; + if (!pEnd) + { + return nullptr; + } SwPosition aStart(*pStart, 0); SwPosition aEnd(*pEnd, pEnd->Len()); - SwShellCursor* pShellCursor = getShellCursor(false); - return aStart == *pShellCursor->Start() && aEnd == *pShellCursor->End(); + if (!(aStart == *pShellCursor->Start() && aEnd == *pShellCursor->End())) + { + return nullptr; + } + + if (::StartsWith(*pStartNode) == StartsWith::None + && ::EndsWith(*pStartNode) == StartsWith::None) + { + return nullptr; // "ordinary" selection will work + } + + // tdf#133990 ensure directly containing section is included in SwUndoDelete + while (pStartNode->IsSectionNode() + && pStartNode->GetIndex() == pStartNode->StartOfSectionNode()->GetIndex() + 1 + && pStartNode->EndOfSectionNode()->GetIndex() + 1 == pStartNode->StartOfSectionNode()->EndOfSectionNode()->GetIndex()) + { + pStartNode = pStartNode->StartOfSectionNode(); + } + + // pStartNode is the node that fully contains the selection - the first + // node of the selection is the first node inside pStartNode + return pStartNode->GetNodes()[pStartNode->GetIndex() + 1]; } typename SwCursorShell::StartsWith SwCursorShell::StartsWith_() { - SwNodes& rNodes = GetDoc()->GetNodes(); - SwNodeIndex nNode(rNodes.GetEndOfExtras()); - SwContentNode* pContentNode = rNodes.GoNext(&nNode); - if (pContentNode->FindTableNode()) + SwShellCursor const*const pShellCursor = getShellCursor(false); + SwStartNode const*const pStartNode(FindParentText(*pShellCursor)); + if (auto const ret = ::StartsWith(*pStartNode); ret != StartsWith::None) { - return StartsWith::Table; + return ret; } - if (pContentNode->GetTextNode()->IsHidden()) + if (auto const ret = ::EndsWith(*pStartNode); ret != StartsWith::None) { - return StartsWith::HiddenPara; - } - nNode = rNodes.GetEndOfContent(); - pContentNode = SwNodes::GoPrevious(&nNode); - if (pContentNode->GetTextNode()->IsHidden()) - { - return StartsWith::HiddenPara; + return ret; } return StartsWith::None; } @@ -2285,7 +2521,7 @@ bool SwCursorShell::Pop(PopMode const eDelete, if (PopMode::DeleteCurrent == eDelete) { - SwCursorSaveState aSaveState( *m_pCurrentCursor ); + ::std::optional<SwCursorSaveState> oSaveState( *m_pCurrentCursor ); // If the visible SSelection was not changed const Point& rPoint = pOldStack->GetPtPos(); @@ -2313,6 +2549,7 @@ bool SwCursorShell::Pop(PopMode const eDelete, !m_pCurrentCursor->IsSelOvr( SwCursorSelOverFlags::Toggle | SwCursorSelOverFlags::ChangePos ) ) { + oSaveState.reset(); // prevent UAF UpdateCursor(); // update current cursor if (m_pTableCursor) { // tdf#106929 ensure m_pCurrentCursor ring is recreated from table diff --git a/sw/source/core/edit/eddel.cxx b/sw/source/core/edit/eddel.cxx index aa56a85ae43f..7ff1a8d144ea 100644 --- a/sw/source/core/edit/eddel.cxx +++ b/sw/source/core/edit/eddel.cxx @@ -42,7 +42,9 @@ void SwEditShell::DeleteSel(SwPaM& rPam, bool const isArtificialSelection, bool *const pUndo) { - bool bSelectAll = StartsWith_() != SwCursorShell::StartsWith::None && ExtendedSelectedAll(); + SwNode const*const pSelectAllStart(StartsWith_() != SwCursorShell::StartsWith::None + ? ExtendedSelectedAll() + : nullptr); // only for selections if (!rPam.HasMark() || (*rPam.GetPoint() == *rPam.GetMark() @@ -58,7 +60,7 @@ void SwEditShell::DeleteSel(SwPaM& rPam, bool const isArtificialSelection, bool // 3. Point and Mark are at the document start and end, Point is in a table: delete selection as usual if( rPam.GetNode().FindTableNode() && rPam.GetNode().StartOfSectionNode() != - rPam.GetNode(false).StartOfSectionNode() && !bSelectAll ) + rPam.GetNode(false).StartOfSectionNode() && pSelectAllStart == nullptr) { // group the Undo in the table if( pUndo && !*pUndo ) @@ -102,24 +104,13 @@ void SwEditShell::DeleteSel(SwPaM& rPam, bool const isArtificialSelection, bool { std::unique_ptr<SwPaM> pNewPam; SwPaM * pPam = &rPam; - if (bSelectAll) + if (pSelectAllStart) { assert(dynamic_cast<SwShellCursor*>(&rPam)); // must be corrected pam pNewPam.reset(new SwPaM(*rPam.GetMark(), *rPam.GetPoint())); // Selection starts at the first para of the first cell, but we // want to delete the table node before the first cell as well. - while (SwTableNode const* pTableNode = - pNewPam->Start()->nNode.GetNode().StartOfSectionNode()->FindTableNode()) - { - pNewPam->Start()->nNode = *pTableNode; - } - // tdf#133990 ensure section is included in SwUndoDelete - while (SwSectionNode const* pSectionNode = - pNewPam->Start()->nNode.GetNode().StartOfSectionNode()->FindSectionNode()) - { - pNewPam->Start()->nNode = *pSectionNode; - } - pNewPam->Start()->nContent.Assign(nullptr, 0); + *pNewPam->Start() = SwPosition(*pSelectAllStart); pPam = pNewPam.get(); } // delete everything diff --git a/sw/source/core/edit/edglss.cxx b/sw/source/core/edit/edglss.cxx index 8ea52856fd37..9da7d1d67ae6 100644 --- a/sw/source/core/edit/edglss.cxx +++ b/sw/source/core/edit/edglss.cxx @@ -203,7 +203,9 @@ bool SwEditShell::CopySelToDoc( SwDoc* pInsDoc ) bool bColSel = GetCursor_()->IsColumnSelection(); if( bColSel && pInsDoc->IsClipBoard() ) pInsDoc->SetColumnSelection( true ); - bool bSelectAll = StartsWith_() != SwCursorShell::StartsWith::None && ExtendedSelectedAll(); + SwNode const*const pSelectAllStart(StartsWith_() != SwCursorShell::StartsWith::None + ? ExtendedSelectedAll() + : nullptr); { for(SwPaM& rPaM : GetCursor()->GetRingContainer()) { @@ -227,23 +229,12 @@ bool SwEditShell::CopySelToDoc( SwDoc* pInsDoc ) // for the purpose of copying, our shell cursor is not touched. // (Otherwise we would have to restore it.) SwPaM aPaM(*rPaM.GetMark(), *rPaM.GetPoint()); - if (bSelectAll) + if (pSelectAllStart) { // Selection starts at the first para of the first cell, // but we want to copy the table and the start node before // the first cell as well. - // tdf#133982 tables can be nested - while (SwTableNode const* pTableNode = - aPaM.Start()->nNode.GetNode().StartOfSectionNode()->FindTableNode()) - { - aPaM.Start()->nNode = *pTableNode; - } - while (SwSectionNode const* pSectionNode = - aPaM.Start()->nNode.GetNode().StartOfSectionNode()->FindSectionNode()) - { - aPaM.Start()->nNode = *pSectionNode; - } - aPaM.Start()->nContent.Assign(nullptr, 0); + *aPaM.Start() = SwPosition(*pSelectAllStart); } bRet = GetDoc()->getIDocumentContentOperations().CopyRange( aPaM, aPos, /*bCopyAll=*/false, /*bCheckPos=*/true, /*bCopyText=*/false ) || bRet; } diff --git a/sw/source/uibase/wrtsh/move.cxx b/sw/source/uibase/wrtsh/move.cxx index 76f4baedaba5..b0dcdc522de7 100644 --- a/sw/source/uibase/wrtsh/move.cxx +++ b/sw/source/uibase/wrtsh/move.cxx @@ -218,12 +218,25 @@ bool SwWrtShell::GoStart( bool bKeepArea, bool *pMoveTable, *pMoveTable = false; return true; } + SwTableNode const*const pTable(getShellCursor(false)->GetPoint()->nNode.GetNode().FindTableNode()); + assert(pTable); if( MoveTable( GotoCurrTable, fnTableStart ) || bDontMoveRegion ) { if ( pMoveTable ) *pMoveTable = true; return true; } + else if (SwCursor const*const pCursor = getShellCursor(false); + pTable->GetNodes()[pTable->GetIndex()+1]->EndOfSectionIndex() + < pCursor->GetPoint()->nNode.GetNode().GetIndex() + && pMoveTable != nullptr // only set by SelAll() + // problem: cursor isn't inside 1st cell, and didn't move there + // workaround: try to move cursor outside of table for SelAll() + && MoveOutOfTable()) + { + assert(!*pMoveTable); + return true; + } else if( bBoxSelection && pMoveTable ) { // JP 09.01.96: We have a box selection (or an empty cell) @@ -258,15 +271,40 @@ bool SwWrtShell::GoStart( bool bKeepArea, bool *pMoveTable, else if ( bKeepArea ) return true; } - // Regions ??? + + // first try to move to the start of the current SwSection return SwCursorShell::MoveRegion( GotoCurrRegionAndSkip, fnRegionStart ) || - SwCursorShell::SttEndDoc(true); + (pMoveTable != nullptr + // move to start of text - if in different table, move out + ? MoveStartText() + // TODO who needs SttEndDoc for other case? + : SwCursorShell::SttEndDoc(true)); } bool SwWrtShell::GoEnd(bool bKeepArea, const bool *pMoveTable) { - if ( pMoveTable && *pMoveTable ) - return MoveTable( GotoCurrTable, fnTableEnd ); + if (pMoveTable && *pMoveTable) // only in SelAll() + { + SwTableNode const*const pTable(getShellCursor(false)->GetPoint()->nNode.GetNode().FindTableNode()); + assert(pTable); + if (MoveTable(GotoCurrTable, fnTableEnd)) + { + return true; + } + else if (SwCursor const*const pCursor = getShellCursor(false); + pCursor->GetPoint()->nNode.GetNode().GetIndex() + < pTable->GetNodes()[pTable->EndOfSectionIndex()-1]->StartOfSectionIndex() + // problem: cursor isn't inside 1st cell, and didn't move there + // workaround: try to move cursor outside of table for SelAll() + && MoveOutOfTable()) + { + return true; + } + else + { + return false; + } + } if ( IsCursorInTable() ) { diff --git a/sw/source/uibase/wrtsh/select.cxx b/sw/source/uibase/wrtsh/select.cxx index f26bb97bc8f4..17d4c068416a 100644 --- a/sw/source/uibase/wrtsh/select.cxx +++ b/sw/source/uibase/wrtsh/select.cxx @@ -141,7 +141,10 @@ void SwWrtShell::SelAll() bool bHasWholeTabSelection = HasWholeTabSelection(); bool bIsCursorInTable = IsCursorInTable(); - if (!bHasWholeTabSelection) + if (!bHasWholeTabSelection + && ( !bIsCursorInTable + || getShellCursor(false)->GetNode(false).FindTableNode() == nullptr + || !ExtendedSelectedAll())) // ESA inside table -> else branch { if ( IsSelection() && IsCursorPtAtEnd() ) SwapPam(); @@ -157,30 +160,35 @@ void SwWrtShell::SelAll() bIsFullSel &= !MoveSection( GoCurrSection, fnSectionEnd); Pop(SwCursorShell::PopMode::DeleteCurrent); GoStart(true, &bMoveTable, false, !bIsFullSel); + SttSelect(); + GoEnd(true, &bMoveTable); } else { - EnterStdMode(); - SttEndDoc(true); + if (MoveOutOfTable()) + { // select outer text + EnterStdMode(); // delete m_pTableCursor +// GoStart(true, &bMoveTable, false, true); + MoveSection(GoCurrSection, fnSectionStart); // don't move into prev table + SttSelect(); + MoveSection(GoCurrSection, fnSectionEnd); // don't move to different cell + } + else + { + TrySelectOuterTable(); + } } - SttSelect(); - GoEnd(true, &bMoveTable); bool bNeedsExtendedSelectAll = StartsWith_() != StartsWith::None; - // If the cursor was in a table, then we only need the extended select - // all if the whole table is already selected, to still allow selecting - // only a single cell or a single table before selecting the whole - // document. + // the GoEnd() could have created a table selection, if so avoid ESA. if (bNeedsExtendedSelectAll && bIsCursorInTable) - bNeedsExtendedSelectAll = bHasWholeTabSelection; + { + bNeedsExtendedSelectAll = !HasWholeTabSelection(); + } if (bNeedsExtendedSelectAll) { - // Disable table cursor to make sure getShellCursor() returns m_pCurrentCursor, not m_pTableCursor. - if (IsTableMode()) - TableCursorToCursor(); - // Do the extended select all on m_pCurrentCursor. ExtendedSelectAll(/*bFootnotes =*/ false); } commit 53c9990152ef19b0a8d07fc514cef0a73496514c Author: Michael Stahl <michael.st...@cib.de> AuthorDate: Thu Jun 18 18:29:57 2020 +0200 Commit: Michael Stahl <michael.st...@allotropia.de> CommitDate: Wed Jun 14 17:11:34 2023 +0200 sw: SelectAll should copy section before table at start of document Change-Id: I51c9c84073a89402e7e4952c346ad13e1d264622 related: tdf#133982 tdf#133990, see bugdoc of the latter Reviewed-on: https://gerrit.libreoffice.org/c/core/+/96619 Tested-by: Jenkins Reviewed-by: Michael Stahl <michael.st...@cib.de> (cherry picked from commit 0be41ce3002e1ee3c84ec3096c95e72645faa187) diff --git a/sw/source/core/edit/edglss.cxx b/sw/source/core/edit/edglss.cxx index 0d27b7c7a960..8ea52856fd37 100644 --- a/sw/source/core/edit/edglss.cxx +++ b/sw/source/core/edit/edglss.cxx @@ -238,6 +238,11 @@ bool SwEditShell::CopySelToDoc( SwDoc* pInsDoc ) { aPaM.Start()->nNode = *pTableNode; } + while (SwSectionNode const* pSectionNode = + aPaM.Start()->nNode.GetNode().StartOfSectionNode()->FindSectionNode()) + { + aPaM.Start()->nNode = *pSectionNode; + } aPaM.Start()->nContent.Assign(nullptr, 0); } bRet = GetDoc()->getIDocumentContentOperations().CopyRange( aPaM, aPos, /*bCopyAll=*/false, /*bCheckPos=*/true, /*bCopyText=*/false ) || bRet;