tdf#159565: make sure to handle leading hidden section correctly

Change-Id: I41c7d2b6e765f03c72a968fd05e8de7047f1ce41
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/163371
Tested-by: Jenkins
Reviewed-by: Mike Kaganski <mike.kaganski@collabora.com>
diff --git a/sw/inc/crsrsh.hxx b/sw/inc/crsrsh.hxx
index eafc6a0..ecf2532 100644
--- a/sw/inc/crsrsh.hxx
+++ b/sw/inc/crsrsh.hxx
@@ -334,7 +334,7 @@ public:
    void ExtendedSelectAll(bool bFootnotes = true);
    /// If ExtendedSelectAll() was called and selection didn't change since then.
    ::std::optional<::std::pair<SwNode const*, ::std::vector<SwTableNode*>>> ExtendedSelectedAll() const;
    enum class StartsWith { None, Table, HiddenPara };
    enum class StartsWith { None, Table, HiddenPara, HiddenSection };
    /// If document body starts with a table or starts/ends with hidden paragraph.
    StartsWith StartsWith_();

diff --git a/sw/inc/ndarr.hxx b/sw/inc/ndarr.hxx
index 7afe8d2..7383c25 100644
--- a/sw/inc/ndarr.hxx
+++ b/sw/inc/ndarr.hxx
@@ -131,6 +131,11 @@ class SW_DLLPUBLIC SwNodes final

    SwNodes(SwDoc& rDoc);

    // Returns start of the document section (PostIts/Inserts/Autotext/Redlines/Content),
    // or of a specific fly / header / footer / footnote, where this node is, which must not
    // be crossed when moving backwards
    SwNodeOffset StartOfGlobalSection(const SwNode& node) const;

public:
    ~SwNodes();

@@ -188,8 +193,8 @@ public:

    SwContentNode* GoNext(SwNodeIndex *) const;
    SwContentNode* GoNext(SwPosition *) const;
    static SwContentNode* GoPrevious(SwNodeIndex *);
    static SwContentNode* GoPrevious(SwPosition *);
    static SwContentNode* GoPrevious(SwNodeIndex *, bool canCrossBoundary = false);
    static SwContentNode* GoPrevious(SwPosition *, bool canCrossBoundary = false);

    /** Go to next content-node that is not protected or hidden
       (Both set FALSE ==> GoNext/GoPrevious!!!). */
diff --git a/sw/qa/extras/uiwriter/data/FrameInHiddenSection.fodt b/sw/qa/extras/uiwriter/data/FrameInHiddenSection.fodt
new file mode 100644
index 0000000..2095c71
--- /dev/null
+++ b/sw/qa/extras/uiwriter/data/FrameInHiddenSection.fodt
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>

<office:document xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:draw="urn:oasis:names:tc:opendocument:xmlns:drawing:1.0" xmlns:svg="urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
 <office:body>
  <office:text>
   <text:section text:name="Section1">
    <text:section text:name="Section2Hidden" text:display="none">
     <text:p><draw:frame text:anchor-type="paragraph" svg:x="1cm" svg:y="1cm" svg:width="1cm">
      <draw:text-box/>
     </draw:frame>lorem</text:p>
    </text:section>
    <text:section text:name="Section3"/>
    <text:section text:name="Section4"/>
    <text:section text:name="Section5">
     <text:p>ipsum</text:p>
    </text:section>
   </text:section>
  </office:text>
 </office:body>
</office:document>
\ No newline at end of file
diff --git a/sw/qa/extras/uiwriter/uiwriter9.cxx b/sw/qa/extras/uiwriter/uiwriter9.cxx
index 7b5103bc..cafda2a 100644
--- a/sw/qa/extras/uiwriter/uiwriter9.cxx
+++ b/sw/qa/extras/uiwriter/uiwriter9.cxx
@@ -16,6 +16,8 @@
#include <com/sun/star/text/XTextTable.hpp>
#include <com/sun/star/text/XTextViewCursorSupplier.hpp>
#include <com/sun/star/text/XPageCursor.hpp>
#include <com/sun/star/view/XSelectionSupplier.hpp>

#include <comphelper/propertysequence.hxx>
#include <swdtflvr.hxx>
#include <o3tl/string_view.hxx>
@@ -251,6 +253,25 @@ CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testHiddenSectionsAroundPageBreak)
    CPPUNIT_ASSERT_EQUAL(u"Landscape"_ustr, getProperty<OUString>(xCursor, "PageStyleName"));
}

CPPUNIT_TEST_FIXTURE(SwUiWriterTest9, testTdf159565)
{
    // Given a document with a hidden section in the beginning, additionally containing a frame
    createSwDoc("FrameInHiddenSection.fodt");

    dispatchCommand(mxComponent, u".uno:SelectAll"_ustr, {});

    // Check that the selection covers the whole visible text
    auto xModel(mxComponent.queryThrow<css::frame::XModel>());
    auto xSelSupplier(xModel->getCurrentController().queryThrow<css::view::XSelectionSupplier>());
    auto xSelections(xSelSupplier->getSelection().queryThrow<css::container::XIndexAccess>());
    CPPUNIT_ASSERT_EQUAL(sal_Int32(1), xSelections->getCount());
    auto xSelection(xSelections->getByIndex(0).queryThrow<css::text::XTextRange>());

    // Without the fix, this would fail - there was no selection
    CPPUNIT_ASSERT_EQUAL(u"" SAL_NEWLINE_STRING SAL_NEWLINE_STRING "ipsum"_ustr,
                         xSelection->getString());
}

} // end of anonymous namespace
CPPUNIT_PLUGIN_IMPLEMENT();

diff --git a/sw/source/core/crsr/crsrsh.cxx b/sw/source/core/crsr/crsrsh.cxx
index 13d2776..06cc3a4 100644
--- a/sw/source/core/crsr/crsrsh.cxx
+++ b/sw/source/core/crsr/crsrsh.cxx
@@ -775,6 +775,8 @@ static typename SwCursorShell::StartsWith StartsWith(SwStartNode const& rStart)
        switch (rNode.GetNodeType())
        {
            case SwNodeType::Section:
                if (rNode.GetSectionNode()->GetSection().IsHidden())
                    return SwCursorShell::StartsWith::HiddenSection;
                continue;
            case SwNodeType::Table:
                return SwCursorShell::StartsWith::Table;
@@ -799,11 +801,16 @@ static typename SwCursorShell::StartsWith EndsWith(SwStartNode const& rStart)
        switch (rNode.GetNodeType())
        {
            case SwNodeType::End:
                if (rNode.StartOfSectionNode()->IsTableNode())
                if (auto pStartNode = rNode.StartOfSectionNode(); pStartNode->IsTableNode())
                {
                    return SwCursorShell::StartsWith::Table;
                }
//TODO buggy SwUndoRedline in testTdf137503?                assert(rNode.StartOfSectionNode()->IsSectionNode());
                else if (pStartNode->IsSectionNode())
                {
                    if (pStartNode->GetSectionNode()->GetSection().IsHidden())
                        return SwCursorShell::StartsWith::HiddenSection;
                }
                    //TODO buggy SwUndoRedline in testTdf137503?                assert(rNode.StartOfSectionNode()->IsSectionNode());
            break;
            case SwNodeType::Text:
                if (rNode.GetTextNode()->IsHidden())
@@ -3473,7 +3480,7 @@ bool SwCursorShell::FindValidContentNode( bool bOnlyText )
        GetDoc()->GetDocShell()->IsReadOnlyUI() )
        return true;

    if( m_pCurrentCursor->HasMark() )
    if( m_pCurrentCursor->HasMark() && !mbSelectAll )
        ClearMark();

    // first check for frames
diff --git a/sw/source/core/crsr/swcrsr.cxx b/sw/source/core/crsr/swcrsr.cxx
index 0f0b6d4..1c29c23 100644
--- a/sw/source/core/crsr/swcrsr.cxx
+++ b/sw/source/core/crsr/swcrsr.cxx
@@ -913,7 +913,7 @@ static bool lcl_MakeSelFwrd( const SwNode& rSttNd, const SwNode& rEndNd,

    rPam.SetMark();
    rPam.GetPoint()->Assign(rEndNd);
    pCNd = SwNodes::GoPrevious( rPam.GetPoint() );
    pCNd = SwNodes::GoPrevious(rPam.GetPoint(), true);
    if( !pCNd )
        return false;
    rPam.GetPoint()->AssignEndIndex(*pCNd);
@@ -933,7 +933,7 @@ static bool lcl_MakeSelBkwrd( const SwNode& rSttNd, const SwNode& rEndNd,
    if( !bFirst )
    {
        rPam.GetPoint()->Assign(rSttNd);
        pCNd = SwNodes::GoPrevious( rPam.GetPoint() );
        pCNd = SwNodes::GoPrevious(rPam.GetPoint(), true);
        if( !pCNd )
            return false;
        rPam.GetPoint()->AssignEndIndex(*pCNd);
diff --git a/sw/source/core/docnode/nodes.cxx b/sw/source/core/docnode/nodes.cxx
index 1808e42..f61a2c0 100644
--- a/sw/source/core/docnode/nodes.cxx
+++ b/sw/source/core/docnode/nodes.cxx
@@ -1336,34 +1336,68 @@ SwContentNode* SwNodes::GoNext(SwPosition *pIdx) const
    return static_cast<SwContentNode*>(pNd);
}

SwContentNode* SwNodes::GoPrevious(SwNodeIndex *pIdx)
SwNodeOffset SwNodes::StartOfGlobalSection(const SwNode& node) const
{
    const SwNodeOffset pos = node.GetIndex();
    if (GetEndOfExtras().GetIndex() < pos)
        // Regular ContentSection
        return GetEndOfExtras().GetIndex() + SwNodeOffset(1);
    if (GetEndOfAutotext().GetIndex() < pos)
        // Redlines
        return GetEndOfAutotext().GetIndex() + SwNodeOffset(1);
    if (GetEndOfInserts().GetIndex() < pos)
    {
        // Flys/Headers/Footers
        if (auto* p = node.FindFlyStartNode())
            return p->GetIndex();
        if (auto* p = node.FindHeaderStartNode())
            return p->GetIndex();
        if (auto* p = node.FindFooterStartNode())
            return p->GetIndex();
        return GetEndOfInserts().GetIndex() + SwNodeOffset(1);
    }
    if (GetEndOfPostIts().GetIndex() < pos)
    {
        // Footnotes
        if (auto* p = node.FindFootnoteStartNode())
            return p->GetIndex();
        return GetEndOfPostIts().GetIndex() + SwNodeOffset(1);
    }
    return SwNodeOffset(0);
}

SwContentNode* SwNodes::GoPrevious(SwNodeIndex* pIdx, bool canCrossBoundary)
{
    if( !pIdx->GetIndex() )
        return nullptr;

    SwNodeIndex aTmp( *pIdx, -1 );
    SwNodeOffset aGlobalStart(
        canCrossBoundary ? SwNodeOffset(0) : aTmp.GetNodes().StartOfGlobalSection(pIdx->GetNode()));
    SwNode* pNd = nullptr;
    while( aTmp.GetIndex() && !( pNd = &aTmp.GetNode())->IsContentNode() )
    while (aTmp > aGlobalStart && !(pNd = &aTmp.GetNode())->IsContentNode())
        --aTmp;

    if( !aTmp.GetIndex() )
    if (aTmp <= aGlobalStart)
        pNd = nullptr;
    else
        (*pIdx) = aTmp;
    return static_cast<SwContentNode*>(pNd);
}

SwContentNode* SwNodes::GoPrevious(SwPosition *pIdx)
SwContentNode* SwNodes::GoPrevious(SwPosition* pIdx, bool canCrossBoundary)
{
    if( !pIdx->GetNodeIndex() )
        return nullptr;

    SwNodeIndex aTmp( pIdx->GetNode(), -1 );
    SwNodeOffset aGlobalStart(
        canCrossBoundary ? SwNodeOffset(0) : aTmp.GetNodes().StartOfGlobalSection(pIdx->GetNode()));
    SwNode* pNd = nullptr;
    while( aTmp.GetIndex() && !( pNd = &aTmp.GetNode())->IsContentNode() )
    while( aTmp > aGlobalStart && !( pNd = &aTmp.GetNode())->IsContentNode() )
        --aTmp;

    if( !aTmp.GetIndex() )
    if (aTmp <= aGlobalStart)
        pNd = nullptr;
    else
        pIdx->Assign(aTmp);
@@ -2072,8 +2106,9 @@ SwContentNode* SwNodes::GoPrevSection( SwNodeIndex * pIdx,
{
    bool bFirst = true;
    SwNodeIndex aTmp( *pIdx );
    SwNodeOffset aGlobalStart(aTmp.GetNodes().StartOfGlobalSection(pIdx->GetNode()));
    const SwNode* pNd;
    while( aTmp > SwNodeOffset(0) )
    while (aTmp > aGlobalStart)
    {
        pNd = & aTmp.GetNode();
        if (SwNodeType::End == pNd->GetNodeType())
@@ -2129,8 +2164,9 @@ SwContentNode* SwNodes::GoPrevSection( SwPosition * pIdx,
{
    bool bFirst = true;
    SwNodeIndex aTmp( pIdx->GetNode() );
    SwNodeOffset aGlobalStart(aTmp.GetNodes().StartOfGlobalSection(pIdx->GetNode()));
    const SwNode* pNd;
    while( aTmp > SwNodeOffset(0) )
    while (aTmp > aGlobalStart)
    {
        pNd = & aTmp.GetNode();
        if (SwNodeType::End == pNd->GetNodeType())