sw/inc/crsrsh.hxx | 2 sw/inc/doc.hxx | 4 sw/inc/editsh.hxx | 3 sw/inc/ndarr.hxx | 15 + sw/inc/ndtxt.hxx | 9 sw/qa/uitest/data/tdf164074.fodt | 214 +++++++++++++++++++ sw/qa/uitest/navigator/movechapterupdown.py | 305 ++++++++++++++++++++++++++++ sw/source/core/crsr/crstrvl.cxx | 31 ++ sw/source/core/doc/docnum.cxx | 64 +++++ sw/source/core/docnode/ndnum.cxx | 53 ++++ sw/source/core/edit/ednumber.cxx | 5 sw/source/core/txtnode/ndtxt.cxx | 38 +++ sw/source/uibase/utlui/content.cxx | 104 +++++++++ 13 files changed, 826 insertions(+), 21 deletions(-)
New commits: commit 32398232e925d18d2ac5a6d467b61e1a84a0df7c Author: László Németh <nem...@numbertext.org> AuthorDate: Wed Nov 27 16:52:10 2024 +0100 Commit: László Németh <nem...@numbertext.org> CommitDate: Thu Nov 28 12:39:47 2024 +0100 tdf#164074 sw inline heading: add up/down outline moving Move inline headings with their outline tree in Navigator, clicking on the Move Heading Up/Down icons. Instead of changing CompareSwOutlineNodes, which breaks the code at other places, add a new SwOutlineNodesInline and CompareSwOutlineNodesInline to sort inline headings (put in Inline Heading frames) with normal headings only for MoveOutlinePara and other part of Navigator's outline moving. Reordering chapters and sections using the Navigator was limited for normal (root) headings, but not for headings in text frames and tables. Recent implementation of inline headings use text frames with Inline Heading frame style, anchored as characters to their paragraphs. Now these inline headings are movable with the Navigator, with their outline tree, i.e. the paragraph where the inline heading anchored as character, the following paragraphs without inline heading, or the following subsections. Note: selecting the inline headings is possible by the Navigator content tree or or by clicking inside the text of the inline heading in the document. Note: according to the fix for tdf#143569, multiple headings in the same text frame or table are ordered alphabetically in the Navigator. This doesn't effect the inline headings, where there is only a single heading in an Inline Heading text frame. Follow-up to commit 7a35f3dc7419d833b8f47069c4df63e900ccb880 "tdf#48459 sw inline heading: apply it on the selected words", commit d87cf67f8f3346a1e380383917a3a4552fd9248e "tdf#131728 sw inline heading: fix missing/broken DOCX export" and commit a1dcbd1d1ce6071d48bb5df26d7839aeb21b75a8 "tdf48459 sw inline heading: add Inline Heading frame style". Change-Id: I7618aa1d4e1ddc20512d81c9a2babfa660053cbf Reviewed-on: https://gerrit.libreoffice.org/c/core/+/177438 Tested-by: Jenkins Reviewed-by: László Németh <nem...@numbertext.org> diff --git a/sw/inc/crsrsh.hxx b/sw/inc/crsrsh.hxx index 2d7f279c8aa3..2fc9598401cc 100644 --- a/sw/inc/crsrsh.hxx +++ b/sw/inc/crsrsh.hxx @@ -663,7 +663,7 @@ public: // select the given range of OutlineNodes. Optionally including the children // the sal_uInt16s are the positions in OutlineNodes-Array (EditShell) void MakeOutlineSel(SwOutlineNodes::size_type nSttPos, SwOutlineNodes::size_type nEndPos, - bool bWithChildren, bool bKillPams = true ); + bool bWithChildren, bool bKillPams = true, SwOutlineNodesInline* pOutlNdsInline = nullptr); bool GotoNextOutline(); bool GotoPrevOutline(); diff --git a/sw/inc/doc.hxx b/sw/inc/doc.hxx index 7d0c2955721e..ca8da4e293e3 100644 --- a/sw/inc/doc.hxx +++ b/sw/inc/doc.hxx @@ -1050,7 +1050,9 @@ public: bool OutlineUpDown(const SwPaM& rPam, short nOffset, SwRootFrame const* pLayout = nullptr); /// Outline - move up / move down. - bool MoveOutlinePara( const SwPaM& rPam, SwOutlineNodes::difference_type nOffset); + bool MoveOutlinePara( const SwPaM& rPam, + SwOutlineNodes::difference_type nOffset, + SwOutlineNodesInline* pOutlNdsInline = nullptr); SW_DLLPUBLIC bool GotoOutline(SwPosition& rPos, const OUString& rName, SwRootFrame const* = nullptr) const; diff --git a/sw/inc/editsh.hxx b/sw/inc/editsh.hxx index 0e9d8064901b..5a16812e89da 100644 --- a/sw/inc/editsh.hxx +++ b/sw/inc/editsh.hxx @@ -498,7 +498,8 @@ public: bool OutlineUpDown( short nOffset = 1 ); - bool MoveOutlinePara( SwOutlineNodes::difference_type nOffset ); + bool MoveOutlinePara( SwOutlineNodes::difference_type nOffset, + SwOutlineNodesInline* pOutlineNodesInline = nullptr ); bool IsProtectedOutlinePara() const; diff --git a/sw/inc/ndarr.hxx b/sw/inc/ndarr.hxx index 7bcebeda22db..5c423e11378c 100644 --- a/sw/inc/ndarr.hxx +++ b/sw/inc/ndarr.hxx @@ -81,6 +81,21 @@ public: static constexpr auto npos = std::numeric_limits<size_type>::max(); bool Seek_Entry(const SwNode* rP, size_type* pnPos) const; + static const SwNode* GetRootNode(const SwNode* pNode); +}; + +struct CompareSwOutlineNodesInline +{ + bool operator()(const SwNode* lhs, const SwNode* rhs) const; +}; + +class SwOutlineNodesInline : public o3tl::sorted_vector<SwNode*, CompareSwOutlineNodesInline> +{ +public: + static constexpr auto npos = std::numeric_limits<size_type>::max(); + + bool Seek_Entry(const SwNode* rP, size_type* pnPos) const; + bool Seek_Entry_By_Anchor(const SwNode* rAnchor, size_type* pnPos) const; }; struct SwTableToTextSave; diff --git a/sw/inc/ndtxt.hxx b/sw/inc/ndtxt.hxx index 8c7e32662aa6..174a88e74c53 100644 --- a/sw/inc/ndtxt.hxx +++ b/sw/inc/ndtxt.hxx @@ -619,6 +619,8 @@ public: /** Returns outline level of this text node. + @param bInlineHeading it can return the outline level of the inline heading + If a text node has an outline number (i.e. it has an SwNodeNum and an outline numbering rule) the outline level is the level of this SwNodeNum. @@ -627,7 +629,10 @@ public: attached the outline level is the outline level of the paragraph style. - Otherwise the text node has no outline level (NO_NUMBERING). + Otherwise the text node has no outline level (NO_NUMBERING), + except if bInlineHeading is true, and there is an inline heading + at the beginning of the paragraph anchored as character and + with a different outline level. NOTE: The outline level of text nodes is subject to change. The plan is to have an SwTextNode::nOutlineLevel member that is @@ -636,7 +641,7 @@ public: @return outline level or NO_NUMBERING if there is no outline level */ - int GetAttrOutlineLevel() const; + int GetAttrOutlineLevel(bool bInlineHeading = false) const; /** Sets the out line level *at* a text node. diff --git a/sw/qa/uitest/data/tdf164074.fodt b/sw/qa/uitest/data/tdf164074.fodt new file mode 100644 index 000000000000..be9f064d77c0 --- /dev/null +++ b/sw/qa/uitest/data/tdf164074.fodt @@ -0,0 +1,214 @@ +<?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.4" office:mimetype="application/vnd.oasis.opendocument.text"> + <office:font-face-decls> + <style:font-face style:name="Liberation Sans" svg:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable"/> + <style:font-face style:name="Liberation Serif" svg:font-family="'Liberation Serif'" style:font-family-generic="roman" 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:writing-mode="lr-tb" style:flow-with-text="false"/> + <style:paragraph-properties style:text-autospace="ideograph-alpha" style:line-break="strict" loext:tab-stop-distance="0cm" 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="en" fo:country="US" style:letter-kerning="true"/> + </style:default-style> + <style:default-style style:family="paragraph"> + <style:paragraph-properties fo:orphans="2" fo:widows="2" fo:hyphenation-ladder-count="no-limit" fo:hyphenation-keep="auto" loext:hyphenation-keep-type="column" 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="en" fo:country="US" style:letter-kerning="true" 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:style style:name="Standard" style:family="paragraph" style:class="text"/> + <style:style style:name="Heading" style:family="paragraph" style:parent-style-name="Standard" style:next-style-name="Text_20_body" style:class="chapter"> + <style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" fo:keep-with-next="always"/> + <style:text-properties style:font-name="Liberation Sans" fo:font-family="'Liberation Sans'" style:font-family-generic="swiss" style:font-pitch="variable" fo:font-size="14pt"/> + </style:style> + <style:style style:name="Text_20_body" style:display-name="Text body" style:family="paragraph" style:parent-style-name="Standard" style:class="text"> + <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0.247cm" style:contextual-spacing="false" fo:line-height="115%"/> + </style:style> + <style:style style:name="Caption" style:family="paragraph" style:parent-style-name="Standard" style:class="extra"> + <style:paragraph-properties fo:margin-top="0.212cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" text:number-lines="false" text:line-number="0"/> + <style:text-properties fo:font-size="12pt" fo:font-style="italic"/> + </style:style> + <style:style style:name="Index" style:family="paragraph" style:parent-style-name="Standard" style:class="index"> + <style:paragraph-properties text:number-lines="false" text:line-number="0"/> + <style:text-properties/> + </style:style> + <style:style style:name="Heading_20_1" style:display-name="Heading 1" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="1" style:class="chapter" style:master-page-name=""> + <style:paragraph-properties fo:margin-top="0.423cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false" style:page-number="auto" fo:break-before="page"/> + <style:text-properties fo:font-size="18pt" fo:font-weight="bold"/> + </style:style> + <style:style style:name="Heading_20_2" style:display-name="Heading 2" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="2" style:class="chapter"> + <style:paragraph-properties fo:margin-top="0.353cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false"/> + <style:text-properties fo:font-size="16pt" fo:font-weight="bold"/> + </style:style> + <style:style style:name="Heading_20_3" style:display-name="Heading 3" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="3" style:class="chapter"> + <style:paragraph-properties fo:margin-top="0.247cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false"/> + <style:text-properties fo:font-size="14pt" fo:font-weight="bold"/> + </style:style> + <style:style style:name="Heading_20_4" style:display-name="Heading 4" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="4" style:class="chapter"> + <style:paragraph-properties fo:margin-top="0.212cm" fo:margin-bottom="0.212cm" style:contextual-spacing="false"/> + <style:text-properties fo:font-size="13pt" fo:font-style="normal" fo:font-weight="bold"/> + </style:style> + <style:style style:name="Frame_20_contents" style:display-name="Frame contents" style:family="paragraph" style:parent-style-name="Standard" style:class="extra"/> + <style:style style:name="Heading_20_5" style:display-name="Heading 5" style:family="paragraph" style:parent-style-name="Heading" style:next-style-name="Text_20_body" style:default-outline-level="5" style:class="chapter"> + <style:paragraph-properties fo:margin-top="0.212cm" fo:margin-bottom="0.106cm" style:contextual-spacing="false"/> + <style:text-properties fo:font-size="12pt" fo:font-style="italic" fo:font-weight="bold"/> + </style:style> + <style:style style:name="Frame" style:family="graphic"> + <style:graphic-properties text:anchor-type="paragraph" svg:x="0cm" svg:y="0cm" fo:margin-left="0.201cm" fo:margin-right="0.201cm" fo:margin-top="0.201cm" fo:margin-bottom="0.201cm" style:wrap="parallel" style:number-wrapped-paragraphs="no-limit" style:wrap-contour="false" style:vertical-pos="top" style:vertical-rel="paragraph-content" style:horizontal-pos="center" style:horizontal-rel="paragraph-content" fo:background-color="transparent" draw:fill="none" draw:fill-color="#729fcf" fo:padding="0.15cm" fo:border="0.06pt solid #000000"/> + </style:style> + <style:style style:name="Inline_20_Heading" style:display-name="Inline Heading" style:family="graphic"> + <style:graphic-properties fo:min-width="0.041cm" fo:min-height="0.041cm" text:anchor-type="as-char" svg:y="0cm" fo:margin-left="0cm" fo:margin-right="0cm" style:vertical-pos="middle" style:vertical-rel="text" fo:background-color="transparent" draw:fill="none" draw:fill-color="#729fcf"/> + </style:style> + <text:linenumbering-configuration text:number-lines="false" text:offset="0.499cm" style:num-format="1" text:number-position="left" text:increment="5"/> + </office:styles> + <office:automatic-styles> + <style:style style:name="P1" style:family="paragraph" style:parent-style-name="Heading_20_1"> + <style:text-properties officeooo:rsid="00155f68" officeooo:paragraph-rsid="00155f68"/> + </style:style> + <style:style style:name="P2" style:family="paragraph" style:parent-style-name="Text_20_body"> + <style:text-properties officeooo:rsid="00155f68" officeooo:paragraph-rsid="00155f68"/> + </style:style> + <style:style style:name="P3" style:family="paragraph" style:parent-style-name="Heading_20_4"> + <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false"/> + </style:style> + <style:style style:name="P4" style:family="paragraph" style:parent-style-name="Heading_20_5"> + <style:paragraph-properties fo:margin-top="0cm" fo:margin-bottom="0cm" style:contextual-spacing="false"/> + </style:style> + <style:style style:name="P5" style:family="paragraph" style:parent-style-name="Text_20_body"> + <style:text-properties officeooo:rsid="00155f68" officeooo:paragraph-rsid="00163552"/> + </style:style> + <style:style style:name="P6" style:family="paragraph" style:parent-style-name="Heading_20_3"> + <style:text-properties officeooo:paragraph-rsid="00163552"/> + </style:style> + <style:style style:name="P7" style:family="paragraph" style:parent-style-name="Heading_20_2"> + <style:text-properties officeooo:paragraph-rsid="00155f68"/> + </style:style> + <style:style style:name="P8" style:family="paragraph" style:parent-style-name="Heading_20_3"> + <style:text-properties officeooo:paragraph-rsid="00155f68"/> + </style:style> + <style:style style:name="P9" style:family="paragraph" style:parent-style-name="Text_20_body"> + <style:text-properties officeooo:paragraph-rsid="00163552"/> + </style:style> + <style:style style:name="T1" style:family="text"> + <style:text-properties officeooo:rsid="00155f68"/> + </style:style> + <style:style style:name="fr1" style:family="graphic" style:parent-style-name="Inline_20_Heading"> + <style:graphic-properties style:vertical-pos="middle" style:vertical-rel="text" style:horizontal-pos="from-left" style:horizontal-rel="paragraph-content"/> + </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:layout-grid-color="#c0c0c0" style:layout-grid-lines="20" style:layout-grid-base-height="0.706cm" style:layout-grid-ruby-height="0.353cm" style:layout-grid-mode="none" style:layout-grid-ruby-below="false" style:layout-grid-print="false" style:layout-grid-display="false" 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> + <style:style style:name="dp1" style:family="drawing-page"> + <style:drawing-page-properties draw:background-size="full"/> + </style:style> + </office:automatic-styles> + <office:master-styles> + <style:master-page style:name="Standard" style:page-layout-name="pm1" draw:style-name="dp1"/> + </office:master-styles> + <office:body> + <office:text text:use-soft-page-breaks="true"> + <text:h text:style-name="P1" text:outline-level="1">1. H1</text:h> + <text:h text:style-name="Heading_20_2" text:outline-level="2">1.1. H<text:span text:style-name="T1">2</text:span></text:h> + <text:h text:style-name="Heading_20_3" text:outline-level="3">1.1.1. H3</text:h> + <text:p text:style-name="P2"><draw:frame draw:style-name="fr1" draw:name="Frame1" text:anchor-type="as-char" draw:z-index="0"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P3" text:outline-level="4">1.1.1.1. Lorem</text:h> + </draw:text-box> + </draw:frame><text:s/>ipsum dolor sit amet, consectetur adipiscing elit.</text:p> + <text:p text:style-name="P2"><draw:frame draw:style-name="fr1" draw:name="Frame3" text:anchor-type="as-char" draw:z-index="1"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P4" text:outline-level="5">1.1.1.1.1. Vestibulum</text:h> + </draw:text-box> + </draw:frame><text:s/>consequat mi quis pretium semper.</text:p> + <text:p text:style-name="P2"><draw:frame draw:style-name="fr1" draw:name="Frame2" text:anchor-type="as-char" draw:z-index="2"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P4" text:outline-level="5">1.1.1.1.2. Integer</text:h> + </draw:text-box> + </draw:frame><text:s/>sodales tincidunt tristique.</text:p> + <text:p text:style-name="P2"><draw:frame draw:style-name="fr1" draw:name="Frame4" text:anchor-type="as-char" draw:z-index="3"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P3" text:outline-level="4">1.1.1.2. Aliquam</text:h> + </draw:text-box> + </draw:frame><text:s/>velit massa, laoreet vel leo nec, volutpat facilisis eros.</text:p> + <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" draw:name="Frame5" text:anchor-type="as-char" draw:z-index="4"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P4" text:outline-level="5">1.1.1.2.1. Donec</text:h> + </draw:text-box> + </draw:frame><text:s/>consequat arcu ut diam tempor luctus.</text:p> + <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" draw:name="Frame11" text:anchor-type="as-char" draw:z-index="5"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P4" text:outline-level="5">1.1.1.2.2. Praesent</text:h> + </draw:text-box> + </draw:frame><text:s/>vitae lacus vel leo sodales pharetra a a nibh.</text:p> + <text:h text:style-name="P6" text:outline-level="3">1.1.2. H3</text:h> + <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" draw:name="Frame14" text:anchor-type="as-char" draw:z-index="6"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P3" text:outline-level="4">1.1.2.1. Lorem</text:h> + </draw:text-box> + </draw:frame><text:s/>ipsum dolor sit amet, consectetur adipiscing elit.</text:p> + <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" draw:name="Frame15" text:anchor-type="as-char" draw:z-index="7"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P4" text:outline-level="5">1.1.2.1.1. Vestibulum</text:h> + </draw:text-box> + </draw:frame><text:s/>consequat mi quis pretium semper.</text:p> + <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" draw:name="Frame16" text:anchor-type="as-char" draw:z-index="8"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P4" text:outline-level="5">1.1.2.1.2. Integer</text:h> + </draw:text-box> + </draw:frame><text:s/>sodales tincidunt tristique.</text:p> + <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" draw:name="Frame17" text:anchor-type="as-char" draw:z-index="9"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P3" text:outline-level="4">1.1.2.2. Aliquam</text:h> + </draw:text-box> + </draw:frame><text:s/>velit massa, laoreet vel leo nec, volutpat facilisis eros.</text:p> + <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" draw:name="Frame18" text:anchor-type="as-char" draw:z-index="10"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P4" text:outline-level="5">1.1.2.2.1. Donec</text:h> + </draw:text-box> + </draw:frame><text:s/>consequat arcu ut diam tempor luctus.</text:p> + <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" draw:name="Frame13" text:anchor-type="as-char" draw:z-index="11"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P4" text:outline-level="5">1.1.2.2.2. Praesent</text:h> + </draw:text-box> + </draw:frame><text:s/>vitae lacus vel leo sodales pharetra a a nibh.</text:p> + <text:h text:style-name="P1" text:outline-level="1">2. H1</text:h> + <text:h text:style-name="P7" text:outline-level="2">2.1. H<text:span text:style-name="T1">2</text:span></text:h> + <text:h text:style-name="P8" text:outline-level="3">2.1.1. H3</text:h> + <text:p text:style-name="P2"><draw:frame draw:style-name="fr1" draw:name="Frame6" text:anchor-type="as-char" draw:z-index="12"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P3" text:outline-level="4">2.1.1.1. Lorem</text:h> + </draw:text-box> + </draw:frame><text:s/>ipsum dolor sit amet, consectetur adipiscing elit.</text:p> + <text:p text:style-name="P2"><draw:frame draw:style-name="fr1" draw:name="Frame7" text:anchor-type="as-char" draw:z-index="13"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P4" text:outline-level="5">2.1.1.1.1. Vestibulum</text:h> + </draw:text-box> + </draw:frame><text:s/>consequat mi quis pretium semper.</text:p> + <text:p text:style-name="P2"><draw:frame draw:style-name="fr1" draw:name="Frame8" text:anchor-type="as-char" draw:z-index="14"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P4" text:outline-level="5">2.1.1.1.2. Integer</text:h> + </draw:text-box> + </draw:frame><text:s/>sodales tincidunt tristique.</text:p> + <text:p text:style-name="P2"><draw:frame draw:style-name="fr1" draw:name="Frame9" text:anchor-type="as-char" draw:z-index="15"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P3" text:outline-level="4">2.1.1.2. Aliquam</text:h> + </draw:text-box> + </draw:frame><text:s/>velit massa, laoreet vel leo nec, volutpat facilisis eros.</text:p> + <text:p text:style-name="P5"><draw:frame draw:style-name="fr1" draw:name="Frame10" text:anchor-type="as-char" draw:z-index="16"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P4" text:outline-level="5">2.1.1.2.1. Donec</text:h> + </draw:text-box> + </draw:frame><text:s/>consequat arcu ut diam tempor luctus.</text:p> + <text:p text:style-name="P9"><text:span text:style-name="T1"><draw:frame draw:style-name="fr1" draw:name="Frame12" text:anchor-type="as-char" draw:z-index="17"> + <draw:text-box fo:min-height="0.041cm" fo:min-width="0.041cm"> + <text:h text:style-name="P4" text:outline-level="5">2.1.1.2.2. Praesent</text:h> + </draw:text-box> + </draw:frame></text:span><text:span text:style-name="T1"><text:s/>vitae lacus vel leo sodales pharetra a a nibh.</text:span></text:p> + </office:text> + </office:body> +</office:document> diff --git a/sw/qa/uitest/navigator/movechapterupdown.py b/sw/qa/uitest/navigator/movechapterupdown.py index d90400ef89b8..c6b6589f7093 100644 --- a/sw/qa/uitest/navigator/movechapterupdown.py +++ b/sw/qa/uitest/navigator/movechapterupdown.py @@ -501,4 +501,309 @@ class movechapterupdown(UITestCase): self.xUITest.executeCommand('.uno:Sidebar') + def test_tdf164074(self): + + with self.ui_test.load_file(get_url_for_data_file('tdf164074.fodt')): + xWriterDoc = self.xUITest.getTopFocusWindow() + xWriterEdit = xWriterDoc.getChild('writer_edit') + + self.xUITest.executeCommand('.uno:Sidebar') + xWriterEdit.executeAction('SIDEBAR', mkPropertyValues({'PANEL': 'SwNavigatorPanel'})) + + # wait until the navigator panel is available + xNavigatorPanel = self.ui_test.wait_until_child_is_available('NavigatorPanel') + + # See the `m_aUpdateTimer.SetTimeout(200)` (to "avoid flickering of buttons") + # in the SwChildWinWrapper ctor in sw/source/uibase/fldui/fldwrap.cxx, where that + # m_aUpdateTimer is started by SwChildWinWrapper::ReInitDlg triggered from the + # xInsert click above. + xToolkit = self.xContext.ServiceManager.createInstance('com.sun.star.awt.Toolkit') + xToolkit.waitUntilAllIdlesDispatched() + + # Given the document chapter structure: + # 1. One H1 + # 1.1. one_A (H2) + # 1.2. one_B (H2) + # 2. Two (H1) + # A heading of level 3 + # 2.1. Two_A (H2) + # 2.1. Two_B (H2) + # 3. Three (H1) + # 3.1. Three_A (H2) + # 3.2. Three_B (H2) + + xNavigatorPanelContentTree = xNavigatorPanel.getChild("contenttree") + + xNavigatorPanelContentTreeHeadings = xNavigatorPanelContentTree.getChild('0') + xNavigatorPanelContentTreeHeadings.executeAction("EXPAND", tuple()) + + # + # test a level 1 chapter move up then move down + # + + # Double click on the "2. Two (H1)" entry to select and set focus + xHeadingsChild1 = xNavigatorPanelContentTreeHeadings.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild1)["Text"], "2. H1") + xHeadingsChild1.executeAction("DOUBLECLICK", tuple()) + + self.ui_test.wait_until_property_is_updated(xNavigatorPanelContentTree, "SelectEntryText", "2. H1") + + # Click on the 'Move chapter up' button in the Navigator tool box + xNavigatorPanel = xWriterEdit.getChild("NavigatorPanel") + xToolBar = xNavigatorPanel.getChild("HeadingsContentFunctionButtonsToolbar") + xToolBar.executeAction("CLICK", mkPropertyValues({"POS": "2"})) + + # Expected chapter order: + # 2. H1 + # 2.1. H2 + # 2.1.1. H3 + # 2.1.1.1. Lorem + # 2.1.1.1.1. Vestibulum + # 2.1.1.1.2. Integer + # 2.1.1.2. Aliquam + + xNavigatorPanelContentTreeHeadings = xNavigatorPanelContentTree.getChild('0') + + xHeadingsChild0 = xNavigatorPanelContentTreeHeadings.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0)["Text"], "2. H1") + xHeadingsChild0Child0 = xHeadingsChild0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0)["Text"], "2.1. H2") + xHeadingsChild0Child0Child0 = xHeadingsChild0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0)["Text"], "2.1.1. H3") + xHeadingsChild0Child0Child0Child0 = xHeadingsChild0Child0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0)["Text"], "2.1.1.1. Lorem") + xHeadingsChild0Child0Child0Child0Child0 = xHeadingsChild0Child0Child0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0Child0)["Text"], "2.1.1.1.1. Vestibulum") + xHeadingsChild0Child0Child0Child0Child1 = xHeadingsChild0Child0Child0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0Child1)["Text"], "2.1.1.1.2. Integer") + xHeadingsChild0Child0Child0Child1 = xHeadingsChild0Child0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child1)["Text"], "2.1.1.2. Aliquam") + + # Click on the 'Move chapter down' button in the Navigator tool box + xNavigatorPanel = xWriterEdit.getChild("NavigatorPanel") + xToolBar = xNavigatorPanel.getChild("HeadingsContentFunctionButtonsToolbar") + xToolBar.executeAction("CLICK", mkPropertyValues({"POS": "3"})) + + # Expected chapter order is the original order + xNavigatorPanelContentTreeHeadings = xNavigatorPanelContentTree.getChild('0') + + xHeadingsChild0 = xNavigatorPanelContentTreeHeadings.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0)["Text"], "1. H1") + xHeadingsChild0Child0 = xHeadingsChild0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0)["Text"], "1.1. H2") + xHeadingsChild0Child0Child0 = xHeadingsChild0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0)["Text"], "1.1.1. H3") + xHeadingsChild0Child0Child0Child0 = xHeadingsChild0Child0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0)["Text"], "1.1.1.1. Lorem") + xHeadingsChild0Child0Child0Child0Child0 = xHeadingsChild0Child0Child0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0Child0)["Text"], "1.1.1.1.1. Vestibulum") + xHeadingsChild0Child0Child0Child0Child1 = xHeadingsChild0Child0Child0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0Child1)["Text"], "1.1.1.1.2. Integer") + xHeadingsChild0Child0Child0Child1 = xHeadingsChild0Child0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child1)["Text"], "1.1.1.2. Aliquam") + + # + # test moving a sub chapter out of and then back into its parent + # + + # Double click on the "1.1.2. (H3)" entry to select and set focus + xHeadingsChild0 = xNavigatorPanelContentTreeHeadings.getChild('0') + xHeadingsChild0Child0 = xHeadingsChild0.getChild('0') + xHeadingsChild0Child0Child1 = xHeadingsChild0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child1)["Text"], "1.1.2. H3") + xHeadingsChild0Child0Child1.executeAction("DOUBLECLICK", tuple()) + + self.ui_test.wait_until_property_is_updated(xNavigatorPanelContentTree, "SelectEntryText", "1.1.2. H3") + + # Click on the 'Move chapter up' button in the Navigator tool box + xNavigatorPanel = xWriterEdit.getChild("NavigatorPanel") + xToolBar = xNavigatorPanel.getChild("HeadingsContentFunctionButtonsToolbar") + xToolBar.executeAction("CLICK", mkPropertyValues({"POS": "2"})) + + # Expected chapter order: + # 1. H1 + # 1.1. H2 + # 1.1.2. H3 <---- + # 1.1.2.1. Lorem + # 1.1.2.1.1. Vestibulum + # 1.1.2.1.2. Integer + # 1.1.2.2. Aliquam + # 1.1.2.2.1. Donec + # 1.1.2.2.2. Praesent + # 1.1.1. H3 + + xNavigatorPanelContentTreeHeadings = xNavigatorPanelContentTree.getChild('0') + + xHeadingsChild0 = xNavigatorPanelContentTreeHeadings.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0)["Text"], "1. H1") + xHeadingsChild0Child0 = xHeadingsChild0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0)["Text"], "1.1. H2") + xHeadingsChild0Child0Child0 = xHeadingsChild0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0)["Text"], "1.1.2. H3") + xHeadingsChild0Child0Child0Child0 = xHeadingsChild0Child0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0)["Text"], "1.1.2.1. Lorem") + xHeadingsChild0Child0Child0Child0Child0 = xHeadingsChild0Child0Child0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0Child0)["Text"], "1.1.2.1.1. Vestibulum") + xHeadingsChild0Child0Child0Child0Child1 = xHeadingsChild0Child0Child0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0Child1)["Text"], "1.1.2.1.2. Integer") + xHeadingsChild0Child0Child0Child1 = xHeadingsChild0Child0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child1)["Text"], "1.1.2.2. Aliquam") + xHeadingsChild0Child0Child0Child1Child0 = xHeadingsChild0Child0Child0Child1.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child1Child0)["Text"], "1.1.2.2.1. Donec") + xHeadingsChild0Child0Child0Child1Child1 = xHeadingsChild0Child0Child0Child1.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child1Child1)["Text"], "1.1.2.2.2. Praesent") + xHeadingsChild0Child0Child1 = xHeadingsChild0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child1)["Text"], "1.1.1. H3") + + # Click on the 'Move chapter down' button in the Navigator tool box + xNavigatorPanel = xWriterEdit.getChild("NavigatorPanel") + xToolBar = xNavigatorPanel.getChild("HeadingsContentFunctionButtonsToolbar") + xToolBar.executeAction("CLICK", mkPropertyValues({"POS": "3"})) + + # Expected chapter order is the original order + # 1. H1 + # 1.1. H2 + # 1.1.1. H3 + # 1.1.1.1. Lorem + # 1.1.1.1.1. Vestibulum + # 1.1.1.1.2. Integer + # 1.1.1.2. Aliquam + # 1.1.1.2.1. Donec + # 1.1.1.2.2. Praesent + # 1.1.2. H3 + + xNavigatorPanelContentTreeHeadings = xNavigatorPanelContentTree.getChild('0') + + xHeadingsChild0 = xNavigatorPanelContentTreeHeadings.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0)["Text"], "1. H1") + xHeadingsChild0Child0 = xHeadingsChild0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0)["Text"], "1.1. H2") + xHeadingsChild0Child0Child0 = xHeadingsChild0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0)["Text"], "1.1.1. H3") + xHeadingsChild0Child0Child0Child0 = xHeadingsChild0Child0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0)["Text"], "1.1.1.1. Lorem") + xHeadingsChild0Child0Child0Child0Child0 = xHeadingsChild0Child0Child0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0Child0)["Text"], "1.1.1.1.1. Vestibulum") + xHeadingsChild0Child0Child0Child0Child1 = xHeadingsChild0Child0Child0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0Child1)["Text"], "1.1.1.1.2. Integer") + xHeadingsChild0Child0Child0Child1 = xHeadingsChild0Child0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child1)["Text"], "1.1.1.2. Aliquam") + xHeadingsChild0Child0Child0Child1Child0 = xHeadingsChild0Child0Child0Child1.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child1Child0)["Text"], "1.1.1.2.1. Donec") + xHeadingsChild0Child0Child0Child1Child1 = xHeadingsChild0Child0Child0Child1.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child1Child1)["Text"], "1.1.1.2.2. Praesent") + xHeadingsChild0Child0Child1 = xHeadingsChild0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child1)["Text"], "1.1.2. H3") + + # + # test moving a sub chapter out of and then back into its parent + # + + # Double click on the "1.1.2. (H3)" entry to select and set focus + xHeadingsChild0 = xNavigatorPanelContentTreeHeadings.getChild('0') + xHeadingsChild0Child0 = xHeadingsChild0.getChild('0') + xHeadingsChild0Child0Child0 = xHeadingsChild0Child0.getChild('0') + xHeadingsChild0Child0Child0Child1 = xHeadingsChild0Child0Child0.getChild('1') + + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child1)["Text"], "1.1.1.2. Aliquam") + xHeadingsChild0Child0Child0Child1.executeAction("DOUBLECLICK", tuple()) + + self.ui_test.wait_until_property_is_updated(xNavigatorPanelContentTree, "SelectEntryText", "1.1.1.2. Aliquam") + + # Click on the 'Move chapter up' button in the Navigator tool box + xNavigatorPanel = xWriterEdit.getChild("NavigatorPanel") + xToolBar = xNavigatorPanel.getChild("HeadingsContentFunctionButtonsToolbar") + xToolBar.executeAction("CLICK", mkPropertyValues({"POS": "2"})) + + # Expected chapter order: + # 1. H1 + # 1.1. H2 + # 1.1.1. H3 + # 1.1.1.2. Aliquam <--- + # 1.1.1.2.1. Donec + # 1.1.1.2.2. Praesent + # 1.1.1.1. Lorem + # 1.1.1.1.1. Vestibulum + # 1.1.1.1.2. Integer + # 1.1.2. H3 + + xNavigatorPanelContentTreeHeadings = xNavigatorPanelContentTree.getChild('0') + + xHeadingsChild0 = xNavigatorPanelContentTreeHeadings.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0)["Text"], "1. H1") + xHeadingsChild0Child0 = xHeadingsChild0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0)["Text"], "1.1. H2") + xHeadingsChild0Child0Child0 = xHeadingsChild0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0)["Text"], "1.1.1. H3") + xHeadingsChild0Child0Child0Child0 = xHeadingsChild0Child0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0)["Text"], "1.1.1.2. Aliquam") + xHeadingsChild0Child0Child0Child0Child0 = xHeadingsChild0Child0Child0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0Child0)["Text"], "1.1.1.2.1. Donec") + xHeadingsChild0Child0Child0Child0Child1 = xHeadingsChild0Child0Child0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0Child1)["Text"], "1.1.1.2.2. Praesent") + xHeadingsChild0Child0Child0Child1 = xHeadingsChild0Child0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child1)["Text"], "1.1.1.1. Lorem") + xHeadingsChild0Child0Child0Child1Child0 = xHeadingsChild0Child0Child0Child1.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child1Child0)["Text"], "1.1.1.1.1. Vestibulum") + xHeadingsChild0Child0Child0Child1Child1 = xHeadingsChild0Child0Child0Child1.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child1Child1)["Text"], "1.1.1.1.2. Integer") + xHeadingsChild0Child0Child1 = xHeadingsChild0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child1)["Text"], "1.1.2. H3") + + # Double click on the "1.1.1.2. Aliquam" entry to select and set focus + # FIXME: restore focus to avoid this + xHeadingsChild0 = xNavigatorPanelContentTreeHeadings.getChild('0') + xHeadingsChild0Child0 = xHeadingsChild0.getChild('0') + xHeadingsChild0Child0Child0 = xHeadingsChild0Child0.getChild('0') + xHeadingsChild0Child0Child0Child0 = xHeadingsChild0Child0Child0.getChild('0') + + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0)["Text"], "1.1.1.2. Aliquam") + xHeadingsChild0Child0Child0Child0.executeAction("DOUBLECLICK", tuple()) + + self.ui_test.wait_until_property_is_updated(xNavigatorPanelContentTree, "SelectEntryText", "1.1.1.2. Aliquam") + + # Click on the 'Move chapter down' button in the Navigator tool box + xNavigatorPanel = xWriterEdit.getChild("NavigatorPanel") + xToolBar = xNavigatorPanel.getChild("HeadingsContentFunctionButtonsToolbar") + xToolBar.executeAction("CLICK", mkPropertyValues({"POS": "3"})) + + # Expected chapter order is the original order + # 1. H1 + # 1.1. H2 + # 1.1.1. H3 + # 1.1.1.1. Lorem + # 1.1.1.1.1. Vestibulum + # 1.1.1.1.2. Integer + # 1.1.1.2. Aliquam + # 1.1.1.2.1. Donec + # 1.1.1.2.2. Praesent + # 1.1.2. H3 + + xNavigatorPanelContentTreeHeadings = xNavigatorPanelContentTree.getChild('0') + + xHeadingsChild0 = xNavigatorPanelContentTreeHeadings.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0)["Text"], "1. H1") + xHeadingsChild0Child0 = xHeadingsChild0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0)["Text"], "1.1. H2") + xHeadingsChild0Child0Child0 = xHeadingsChild0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0)["Text"], "1.1.1. H3") + xHeadingsChild0Child0Child0Child0 = xHeadingsChild0Child0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0)["Text"], "1.1.1.1. Lorem") + xHeadingsChild0Child0Child0Child0Child0 = xHeadingsChild0Child0Child0Child0.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0Child0)["Text"], "1.1.1.1.1. Vestibulum") + xHeadingsChild0Child0Child0Child0Child1 = xHeadingsChild0Child0Child0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child0Child1)["Text"], "1.1.1.1.2. Integer") + xHeadingsChild0Child0Child0Child1 = xHeadingsChild0Child0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child1)["Text"], "1.1.1.2. Aliquam") + xHeadingsChild0Child0Child0Child1Child0 = xHeadingsChild0Child0Child0Child1.getChild('0') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child1Child0)["Text"], "1.1.1.2.1. Donec") + xHeadingsChild0Child0Child0Child1Child1 = xHeadingsChild0Child0Child0Child1.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child0Child1Child1)["Text"], "1.1.1.2.2. Praesent") + xHeadingsChild0Child0Child1 = xHeadingsChild0Child0.getChild('1') + self.assertEqual(get_state_as_dict(xHeadingsChild0Child0Child1)["Text"], "1.1.2. H3") + + self.xUITest.executeCommand('.uno:Sidebar') + + + # vim: set shiftwidth=4 softtabstop=4 expandtab: diff --git a/sw/source/core/crsr/crstrvl.cxx b/sw/source/core/crsr/crstrvl.cxx index f2e4d72ac7cf..ca2c9c371222 100644 --- a/sw/source/core/crsr/crstrvl.cxx +++ b/sw/source/core/crsr/crstrvl.cxx @@ -1364,8 +1364,9 @@ SwOutlineNodes::size_type SwCursorShell::GetOutlinePos(sal_uInt8 nLevel, SwPaM* } void SwCursorShell::MakeOutlineSel(SwOutlineNodes::size_type nSttPos, SwOutlineNodes::size_type nEndPos, - bool bWithChildren , bool bKillPams) + bool bWithChildren , bool bKillPams, SwOutlineNodesInline* pOutlNdsInline) { + SwOutlineNodesInline::size_type nEndPosInline = SwOutlineNodesInline::npos; const SwNodes& rNds = GetDoc()->GetNodes(); const SwOutlineNodes& rOutlNds = rNds.GetOutLineNds(); if( rOutlNds.empty() ) @@ -1383,7 +1384,7 @@ void SwCursorShell::MakeOutlineSel(SwOutlineNodes::size_type nSttPos, SwOutlineN SwNode* pSttNd = rOutlNds[ nSttPos ]; SwNode* pEndNd = rOutlNds[ nEndPos ]; - if( bWithChildren ) + if( bWithChildren && !pOutlNdsInline ) { const int nLevel = pEndNd->GetTextNode()->GetAttrOutlineLevel()-1; for( ++nEndPos; nEndPos < rOutlNds.size(); ++nEndPos ) @@ -1394,11 +1395,33 @@ void SwCursorShell::MakeOutlineSel(SwOutlineNodes::size_type nSttPos, SwOutlineN break; // EndPos is now on the next one } } + // headings in flys + else if( bWithChildren && pOutlNdsInline ) + { + const int nLevel = pEndNd->GetTextNode()->GetAttrOutlineLevel() - 1; + pSttNd = const_cast<SwNode*>(SwOutlineNodes::GetRootNode(pSttNd)); + + pOutlNdsInline->Seek_Entry( pEndNd, &nEndPosInline ); + + for( ++nEndPosInline; nEndPosInline < pOutlNdsInline->size(); ++nEndPosInline ) + { + pEndNd = (*pOutlNdsInline)[ nEndPosInline ]; + const int nNxtLevel = pEndNd->GetTextNode()->GetAttrOutlineLevel()-1; + if( nNxtLevel <= nLevel ) + break; // EndPos is now on the next one + } + // set anchor node of the fly node + if ( nEndPosInline < pOutlNdsInline->size() ) + pEndNd = const_cast<SwNode*>(SwOutlineNodes::GetRootNode(pEndNd)); + } // if without children then set onto next one - else if( ++nEndPos < rOutlNds.size() ) + else if( !pOutlNdsInline && ++nEndPos < rOutlNds.size() ) pEndNd = rOutlNds[ nEndPos ]; + else if( pOutlNdsInline && ++nEndPosInline < pOutlNdsInline->size() ) + pEndNd = const_cast<SwNode*>(SwOutlineNodes::GetRootNode((*pOutlNdsInline)[nEndPosInline])); - if( nEndPos == rOutlNds.size() ) // no end found + if( ( pOutlNdsInline && nEndPosInline == pOutlNdsInline->size() ) || + ( !pOutlNdsInline && nEndPos == rOutlNds.size() ) ) // no end found pEndNd = &rNds.GetEndOfContent(); if( bKillPams ) diff --git a/sw/source/core/doc/docnum.cxx b/sw/source/core/doc/docnum.cxx index ed1a6ab64335..81bc47a8ab47 100644 --- a/sw/source/core/doc/docnum.cxx +++ b/sw/source/core/doc/docnum.cxx @@ -53,6 +53,7 @@ #include <o3tl/string_view.hxx> #include <osl/diagnose.h> #include <tools/datetimeutils.hxx> +//#include <fmtanchr.hxx> #include <map> #include <stdlib.h> @@ -445,7 +446,8 @@ bool SwDoc::OutlineUpDown(const SwPaM& rPam, short nOffset, } // Move up/down -bool SwDoc::MoveOutlinePara( const SwPaM& rPam, SwOutlineNodes::difference_type nOffset ) +bool SwDoc::MoveOutlinePara( const SwPaM& rPam, + SwOutlineNodes::difference_type nOffset, SwOutlineNodesInline* pOutlineNodesInline ) { // Do not move to special sections in the nodes array const SwPosition& rStt = *rPam.Start(), @@ -458,15 +460,20 @@ bool SwDoc::MoveOutlinePara( const SwPaM& rPam, SwOutlineNodes::difference_type } SwOutlineNodes::size_type nCurrentPos = 0; + SwOutlineNodesInline::size_type nCurrentPosInline = 0; SwNodeIndex aSttRg( rStt.GetNode() ), aEndRg( rEnd.GetNode() ); int nOutLineLevel = MAXLEVEL; SwNode* pSrch = &aSttRg.GetNode(); if( pSrch->IsTextNode()) - nOutLineLevel = static_cast<sal_uInt8>(pSrch->GetTextNode()->GetAttrOutlineLevel()-1); + nOutLineLevel = static_cast<sal_uInt8>( + pSrch->GetTextNode()->GetAttrOutlineLevel(/*bInlineHeading=*/true)-1); + SwNode* pEndSrch = &aEndRg.GetNode(); - if( !GetNodes().GetOutLineNds().Seek_Entry( pSrch, &nCurrentPos ) ) + + auto aOutlineNodes = GetNodes().GetOutLineNds(); + if( !pOutlineNodesInline && !GetNodes().GetOutLineNds().Seek_Entry( pSrch, &nCurrentPos ) ) { if( !nCurrentPos ) return false; // Promoting or demoting before the first outline => no. @@ -477,24 +484,63 @@ bool SwDoc::MoveOutlinePara( const SwPaM& rPam, SwOutlineNodes::difference_type else aSttRg = *GetNodes().GetEndOfContent().StartOfSectionNode(); } + else if ( pOutlineNodesInline ) + { + if ( !pOutlineNodesInline->Seek_Entry_By_Anchor(pSrch, &nCurrentPosInline) ) + { + if( !nCurrentPosInline ) + return false; // Promoting or demoting before the first outline => no. + if( --nCurrentPosInline ) + { + aSttRg = *SwOutlineNodes::GetRootNode((*pOutlineNodesInline)[ nCurrentPosInline ]); + } + else if( 0 > nOffset ) + return false; // Promoting at the top of document?! + else + aSttRg = *GetNodes().GetEndOfContent().StartOfSectionNode(); + } + } SwOutlineNodes::size_type nTmpPos = 0; + SwOutlineNodesInline::size_type nTmpPosInline = 0; // If the given range ends at an outlined text node we have to decide if it has to be a part of // the moving range or not. Normally it will be a sub outline of our chapter // and has to be moved, too. But if the chapter ends with a table(or a section end), // the next text node will be chosen and this could be the next outline of the same level. // The criteria has to be the outline level: sub level => incorporate, same/higher level => no. - if( GetNodes().GetOutLineNds().Seek_Entry( pEndSrch, &nTmpPos ) ) + if( !pOutlineNodesInline && GetNodes().GetOutLineNds().Seek_Entry( pEndSrch, &nTmpPos ) ) { if( !pEndSrch->IsTextNode() || pEndSrch == pSrch || nOutLineLevel < pEndSrch->GetTextNode()->GetAttrOutlineLevel()-1 ) ++nTmpPos; // For sub outlines only! } + else if ( pOutlineNodesInline ) + { + if ( pOutlineNodesInline->Seek_Entry_By_Anchor(pEndSrch, &nTmpPosInline) && ( + !pEndSrch->IsTextNode() || pEndSrch == pSrch || nOutLineLevel < + pEndSrch->GetTextNode()->GetAttrOutlineLevel(/*bInlineHeading=*/true)-1 ) ) + { + ++nTmpPosInline; + } + } - aEndRg = nTmpPos < GetNodes().GetOutLineNds().size() + if ( !pOutlineNodesInline ) + { + aEndRg = nTmpPos < GetNodes().GetOutLineNds().size() ? *GetNodes().GetOutLineNds()[ nTmpPos ] : GetNodes().GetEndOfContent(); + } + else + { + aEndRg = nTmpPosInline < pOutlineNodesInline->size() + ? *SwOutlineNodes::GetRootNode((*pOutlineNodesInline)[ nTmpPosInline ]) + : GetNodes().GetEndOfContent(); + } + if( nOffset >= 0 ) + { nCurrentPos = nTmpPos; + nCurrentPosInline = nTmpPosInline; + } if( aEndRg == aSttRg ) { OSL_FAIL( "Moving outlines: Surprising selection" ); @@ -531,7 +577,13 @@ bool SwDoc::MoveOutlinePara( const SwPaM& rPam, SwOutlineNodes::difference_type ++aEndRg; // calculation of the new position - if( nOffset < 0 && nCurrentPos < o3tl::make_unsigned(-nOffset) ) + if( pOutlineNodesInline && nOffset < 0 && nCurrentPosInline < o3tl::make_unsigned(-nOffset) ) + pNd = GetNodes().GetEndOfContent().StartOfSectionNode(); + else if( pOutlineNodesInline && nCurrentPosInline + nOffset >= pOutlineNodesInline->size() ) + pNd = &GetNodes().GetEndOfContent(); + else if ( pOutlineNodesInline ) + pNd = SwOutlineNodes::GetRootNode((*pOutlineNodesInline)[ nCurrentPosInline + nOffset ]); + else if( nOffset < 0 && nCurrentPos < o3tl::make_unsigned(-nOffset) ) pNd = GetNodes().GetEndOfContent().StartOfSectionNode(); else if( nCurrentPos + nOffset >= GetNodes().GetOutLineNds().size() ) pNd = &GetNodes().GetEndOfContent(); diff --git a/sw/source/core/docnode/ndnum.cxx b/sw/source/core/docnode/ndnum.cxx index 58b4cbff6488..89e8cc186311 100644 --- a/sw/source/core/docnode/ndnum.cxx +++ b/sw/source/core/docnode/ndnum.cxx @@ -23,6 +23,28 @@ #include <ndtxt.hxx> #include <fldbas.hxx> #include <osl/diagnose.h> +#include <flyfrm.hxx> +#include <fmtanchr.hxx> +#include <poolfmt.hxx> + +static const SwNode* getNodeOrAnchorNode(const SwNode* pNode) +{ + // if pNode is an inline heading in an Inline Heading + // text frame, return its anchor node instead of pNode + if (const auto pFlyFormat = pNode->GetFlyFormat()) + { + SwFormatAnchor const*const pAnchor = &pFlyFormat->GetAnchor(); + SwNode const*const pAnchorNode = pAnchor->GetAnchorNode(); + const SwFormat* pParent = pFlyFormat->DerivedFrom(); + if ( pAnchorNode && pParent && + RndStdIds::FLY_AS_CHAR == pAnchor->GetAnchorId() && + pParent->GetPoolFormatId() == RES_POOLFRM_INLINE_HEADING ) + { + return pAnchorNode; + } + } + return pNode; +} bool CompareSwOutlineNodes::operator()(const SwNode* lhs, const SwNode* rhs) const { @@ -36,6 +58,37 @@ bool SwOutlineNodes::Seek_Entry(const SwNode* rP, size_type* pnPos) const return it != end() && rP->GetIndex() == (*it)->GetIndex(); } +const SwNode* SwOutlineNodes::GetRootNode(const SwNode* pNode) +{ + return getNodeOrAnchorNode(pNode); +} + +bool CompareSwOutlineNodesInline::operator()(const SwNode* lhs, const SwNode* rhs) const +{ + return getNodeOrAnchorNode(lhs)->GetIndex() < getNodeOrAnchorNode(rhs)->GetIndex(); +} + +bool SwOutlineNodesInline::Seek_Entry(const SwNode* rP, size_type* pnPos) const +{ + const_iterator it = lower_bound(rP); + *pnPos = it - begin(); + return it != end() && getNodeOrAnchorNode(rP)->GetIndex() == getNodeOrAnchorNode(*it)->GetIndex(); +} + +bool SwOutlineNodesInline::Seek_Entry_By_Anchor(const SwNode* rAnchor, SwOutlineNodesInline::size_type* pnPos) const +{ + SwOutlineNodes::size_type nPos; + for( nPos = 0; nPos < size(); ++nPos ) + { + if (getNodeOrAnchorNode(operator[](nPos))->GetIndex() >= rAnchor->GetIndex()) + { + break; + } + } + *pnPos = nPos; + return nPos < size() && getNodeOrAnchorNode(operator[](nPos)) == rAnchor; +} + void SwNodes::UpdateOutlineNode(SwNode & rNd) { assert(IsDocNodes()); // no point in m_pOutlineNodes for undo nodes diff --git a/sw/source/core/edit/ednumber.cxx b/sw/source/core/edit/ednumber.cxx index 6a60cd47dd40..c626c2d9bab0 100644 --- a/sw/source/core/edit/ednumber.cxx +++ b/sw/source/core/edit/ednumber.cxx @@ -540,10 +540,11 @@ bool SwEditShell::OutlineUpDown( short nOffset ) return bRet; } -bool SwEditShell::MoveOutlinePara( SwOutlineNodes::difference_type nOffset ) +bool SwEditShell::MoveOutlinePara( SwOutlineNodes::difference_type nOffset, + SwOutlineNodesInline* pOutlineNodesInline ) { StartAllAction(); - bool bRet = GetDoc()->MoveOutlinePara( *GetCursor(), nOffset ); + bool bRet = GetDoc()->MoveOutlinePara( *GetCursor(), nOffset, pOutlineNodesInline ); EndAllAction(); return bRet; } diff --git a/sw/source/core/txtnode/ndtxt.cxx b/sw/source/core/txtnode/ndtxt.cxx index f9cc2b4f82af..356645f6c9d6 100644 --- a/sw/source/core/txtnode/ndtxt.cxx +++ b/sw/source/core/txtnode/ndtxt.cxx @@ -86,6 +86,8 @@ #include <svl/itemiter.hxx> #include <undobj.hxx> #include <formatflysplit.hxx> +#include <fmtcntnt.hxx> +#include <poolfmt.hxx> using namespace ::com::sun::star; @@ -4186,9 +4188,41 @@ void SwTextNode::UpdateOutlineState() m_bLastOutlineState = IsOutline(); } -int SwTextNode::GetAttrOutlineLevel() const +int SwTextNode::GetAttrOutlineLevel(bool bInlineHeading) const { - return GetAttr(RES_PARATR_OUTLINELEVEL).GetValue(); + sal_uInt16 nLevel = GetAttr(RES_PARATR_OUTLINELEVEL).GetValue(); + // not outline node, so if bIblineHeading = true, look for the + // outline level of the inline heading (i.e the outline node in + // an Inline Heading frame, which frame anchored as character to this node) + if ( !nLevel && bInlineHeading && HasHints() ) + { + // are we in a fly + for ( size_t j = m_pSwpHints->Count(); j; ) + { + SwTextAttr* const pHt = m_pSwpHints->Get( --j ); + if ( RES_TXTATR_FLYCNT == pHt->Which() ) + { + SwFrameFormat* pFrameFormat = pHt->GetFlyCnt().GetFrameFormat(); + const SwFormat* pParent = pFrameFormat->DerivedFrom(); + SwFormatAnchor const& rAnchor(pFrameFormat->GetAnchor()); + bool bInlineHeadingFrame = pParent && + pParent->GetPoolFormatId() == RES_POOLFRM_INLINE_HEADING && + RndStdIds::FLY_AS_CHAR == rAnchor.GetAnchorId(); + const SwNodeIndex* pNdIdx = bInlineHeadingFrame + ? pFrameFormat->GetContent().GetContentIdx() + : nullptr; + const SwNodes* pNodesArray = (pNdIdx != nullptr) + ? &(pNdIdx->GetNodes()) + : nullptr; + const SwTextNode *pTextNode = (pNodesArray != nullptr) + ? (*pNodesArray)[pNdIdx->GetIndex() + 1]->GetTextNode() + : nullptr; + if ( pTextNode ) + return pTextNode->GetAttrOutlineLevel(); + } + } + } + return nLevel; } void SwTextNode::SetAttrOutlineLevel(int nLevel) diff --git a/sw/source/uibase/utlui/content.cxx b/sw/source/uibase/utlui/content.cxx index adddc658fba6..35e281debd83 100644 --- a/sw/source/uibase/utlui/content.cxx +++ b/sw/source/uibase/utlui/content.cxx @@ -3958,6 +3958,7 @@ void SwContentTree::ExecCommand(std::u16string_view rCmd, bool bOutlineWithChild // get first regular document content node outline node position in outline nodes array SwOutlineNodes::size_type nFirstRegularDocContentOutlineNodePos = SwOutlineNodes::npos; SwNodeOffset nEndOfExtrasIndex = rNodes.GetEndOfExtras().GetIndex(); + sal_Int32 nHasInlineHeading = 0; for (SwOutlineNodes::size_type nPos = 0; nPos < nOutlineNdsSize; nPos++) { if (rOutlineNodes[nPos]->GetIndex() > nEndOfExtrasIndex) @@ -3965,6 +3966,8 @@ void SwContentTree::ExecCommand(std::u16string_view rCmd, bool bOutlineWithChild nFirstRegularDocContentOutlineNodePos = nPos; break; } + if ( bUpDown && rOutlineNodes[nPos] != SwOutlineNodes::GetRootNode(rOutlineNodes[nPos]) ) + ++nHasInlineHeading; } for (auto const& pCurrentEntry : selected) @@ -3977,7 +3980,20 @@ void SwContentTree::ExecCommand(std::u16string_view rCmd, bool bOutlineWithChild (bUpDown && (!pShell->IsOutlineMovable(nActPos) || nFirstRegularDocContentOutlineNodePos == SwOutlineNodes::npos))) { - continue; + // except inline headings, i.e. Inline Heading frames with + // single outlines, and achored as characters, which headings + // are movable with their anchor node, if they are + // 1) not in other frames or 2) not in tables and 3) not protected + const SwNode* pRootNode = nHasInlineHeading > 0 + ? SwOutlineNodes::GetRootNode(rOutlineNodes[nActPos]) + : nullptr; + if ( !pRootNode || pRootNode == rOutlineNodes[nActPos] || + pRootNode != SwOutlineNodes::GetRootNode(pRootNode) || // frame in frame + pRootNode->FindTableNode() || // frame in table + pRootNode->IsProtect() ) // write protection + { + continue; + } } if (!bStartedAction) @@ -3990,7 +4006,91 @@ void SwContentTree::ExecCommand(std::u16string_view rCmd, bool bOutlineWithChild pShell->GotoOutline( nActPos); // If text selection != box selection pShell->Push(); - if (bUpDown) + if (nHasInlineHeading && bUpDown) + { + SwOutlineNodesInline aOutlineNodesInline; + // sort inline headings correctly + for (SwNode* pNode : rOutlineNodes) + aOutlineNodesInline.insert(pNode); + const SwOutlineNodes::size_type nOutlineNdsSizeInline = aOutlineNodesInline.size(); + + // move outline position up/down (outline position promote/demote) + SwOutlineNodes::difference_type nDir = bUp ? -1 : 1; + SwOutlineNodesInline::size_type nActPosInline; + aOutlineNodesInline.Seek_Entry(rOutlineNodes[nActPos], &nActPosInline); + if ( (nDir == -1 && nActPosInline > 0) || + (nDir == 1 && nActPosInline < nOutlineNdsSizeInline - 1) ) + { + // make outline selection for use by MoveOutlinePara + pShell->MakeOutlineSel(nActPos, nActPos, bOutlineWithChildren, true, &aOutlineNodesInline); + + int nActPosOutlineLevel = + rOutlineNodes[nActPos]->GetTextNode()->GetAttrOutlineLevel(); + // search for sorted position + SwOutlineNodesInline::size_type nPos; + aOutlineNodesInline.Seek_Entry_By_Anchor(SwOutlineNodes::GetRootNode(rOutlineNodes[nActPos]), &nPos); + if (!bUp) + { + // move down + int nPosOutlineLevel = -1; + while (++nPos < nOutlineNdsSizeInline) + { + nPosOutlineLevel = aOutlineNodesInline[nPos]->GetTextNode()->GetAttrOutlineLevel(); + + // discontinue if moving out of parent or equal level is found + if (nPosOutlineLevel <= nActPosOutlineLevel) + { + break; + } + // count the children of the node when they are not included in the move + if (!bOutlineWithChildren) + nDir++; + } + if (nPosOutlineLevel >= nActPosOutlineLevel) + { + // move past children + while (++nPos < nOutlineNdsSizeInline) + { + nPosOutlineLevel = aOutlineNodesInline[nPos]->GetTextNode()->GetAttrOutlineLevel(); + // discontinue if moving out of parent or equal level is found + if (nPosOutlineLevel <= nActPosOutlineLevel) + break; + nDir++; + } + } + } + else + { + // move up + while (nPos && --nPos >= nFirstRegularDocContentOutlineNodePos - nHasInlineHeading) + { + int nPosOutlineLevel = + aOutlineNodesInline[nPos]->GetTextNode()->GetAttrOutlineLevel(); + // discontinue if equal level is found + if (nPosOutlineLevel == nActPosOutlineLevel) + { + break; + } + // discontinue if moving out of parent + if (nPosOutlineLevel < nActPosOutlineLevel) + { + // Required for expected chapter placement when the chapter being moved + // up has an outline level less than the outline level of chapters it + // is being moved above and then encounters a chapter with an outline + // level that is greater before reaching a chapter with the same + // outline level as itself. + if (nDir < -1) + nDir++; + break; + } + nDir--; + } + } + pShell->MoveOutlinePara(nDir, &aOutlineNodesInline); + } + pShell->ClearMark(); + } + else if (bUpDown) { // move outline position up/down (outline position promote/demote) SwOutlineNodes::difference_type nDir = bUp ? -1 : 1;