tdf#154545 sw Navigator: select & track nested bookmarks

After selecting a nested bookmark in the Navigator with
double click or Enter, the selected item changed to the
item of the main bookmark in the tree list. This jumping
was very annoying especially with multiple nested bookmarks
inside the same bookmark.

Navigator didn't track nested bookmarks, i.e. it always
showed the main bookmark instead of the actual nested one
under the text cursor.

Change-Id: Ic3f1e8321454d4ad892708e76c001da6eca30cf9
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/149915
Tested-by: Jenkins
Reviewed-by: László Németh <nemeth@numbertext.org>
diff --git a/sw/qa/uitest/navigator/tdf154521.py b/sw/qa/uitest/navigator/tdf154521.py
index 0dca5037..ac3c21d 100644
--- a/sw/qa/uitest/navigator/tdf154521.py
+++ b/sw/qa/uitest/navigator/tdf154521.py
@@ -38,12 +38,6 @@ class tdf154521(UITestCase):
        global selectionChangedResult
        with self.ui_test.create_doc_in_start_center("writer") as xDoc:

            # type "foo", and create a bookmark on it

            self.xUITest.executeCommand(".uno:Escape")

            # click on the bookmark name in the Navigator

            xWriterDoc = self.xUITest.getTopFocusWindow()
            xWriterEdit = xWriterDoc.getChild("writer_edit")

@@ -54,6 +48,10 @@ class tdf154521(UITestCase):
            xToolBar = xNavigatorPanel.getChild("content5")
            xToolBar.executeAction("CLICK", mkPropertyValues({"POS": "0"})) # 'root' button

            # type "foo", and create a bookmark on it

            self.xUITest.executeCommand(".uno:Escape")

            xDoc.Text.insertString(xDoc.Text.getStart(), "foo", False)
            self.xUITest.executeCommand(".uno:SelectAll")

diff --git a/sw/qa/uitest/navigator/tdf154545.py b/sw/qa/uitest/navigator/tdf154545.py
new file mode 100644
index 0000000..718f689
--- /dev/null
+++ b/sw/qa/uitest/navigator/tdf154545.py
@@ -0,0 +1,98 @@
# -*- tab-width: 4; indent-tabs-mode: nil; py-indent-offset: 4 -*-
#
# This file is part of the LibreOffice project.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#

from uitest.framework import UITestCase
from libreoffice.uno.propertyvalue import mkPropertyValues
from uitest.uihelper.common import get_state_as_dict
import unohelper

class tdf154545(UITestCase):

    def test_tdf154545(self):
        global selectionChangedResult
        with self.ui_test.create_doc_in_start_center("writer") as xDoc:

            # click on the bookmark name in the Navigator

            xWriterDoc = self.xUITest.getTopFocusWindow()
            xWriterEdit = xWriterDoc.getChild("writer_edit")

            self.xUITest.executeCommand(".uno:Sidebar")
            xWriterEdit.executeAction("SIDEBAR", mkPropertyValues({"PANEL": "SwNavigatorPanel"}))

            xNavigatorPanel = xWriterEdit.getChild("NavigatorPanel")
            xToolBar = xNavigatorPanel.getChild("content5")
            xToolBar.executeAction("CLICK", mkPropertyValues({"POS": "0"})) # 'root' button

            # type "foo", and create a bookmark on it

            xDoc.Text.insertString(xDoc.Text.getStart(), "foo", False)
            self.xUITest.executeCommand(".uno:SelectAll")

            with self.ui_test.execute_dialog_through_command(".uno:InsertBookmark", close_button="insert"):
                pass

            # check selected bookmark in Navigator

            xWriterEdit.executeAction("FOCUS", tuple())

            xContentTree = xNavigatorPanel.getChild("contenttree")

            self.ui_test.wait_until_property_is_updated(xContentTree, "SelectEntryText", "Bookmark 1")
            self.assertEqual(get_state_as_dict(xContentTree)["SelectEntryText"], "Bookmark 1")
            self.assertEqual(get_state_as_dict(xContentTree)["SelectionCount"], "1")

            self.xUITest.executeCommand(".uno:Escape")

            # create a nested bookmark on the last "o"

            cursor = xDoc.getCurrentController().getViewCursor()
            cursor.goLeft(1, True)

            with self.ui_test.execute_dialog_through_command(".uno:InsertBookmark", close_button="insert"):
                pass

            self.xUITest.executeCommand(".uno:Escape")

            # check selected nested bookmark in Navigator

            # This never occured: Navigator didn't track nested bookmarks
            self.ui_test.wait_until_property_is_updated(xContentTree, "SelectEntryText", "Bookmark 2")
            # This was Bookmark 1
            self.assertEqual(get_state_as_dict(xContentTree)["SelectEntryText"], "Bookmark 2")
            self.assertEqual(get_state_as_dict(xContentTree)["SelectionCount"], "1")

            # Select nested bookmark in Navigator

            xContentTree.executeAction("TYPE", mkPropertyValues({"KEYCODE": "RETURN"}))

            # This jumped to Bookmark 1 after selection
            self.ui_test.wait_until_property_is_updated(xContentTree, "SelectEntryText", "Bookmark 2")
            # This was Bookmark 1
            self.assertEqual(get_state_as_dict(xContentTree)["SelectEntryText"], "Bookmark 2")
            self.assertEqual(get_state_as_dict(xContentTree)["SelectionCount"], "1")

            # Try the same selection with Bookmark 1
            xContentTree.executeAction("TYPE", mkPropertyValues({"KEYCODE": "UP"}))
            self.ui_test.wait_until_property_is_updated(xContentTree, "SelectEntryText", "Bookmark 1")
            self.assertEqual(get_state_as_dict(xContentTree)["SelectEntryText"], "Bookmark 1")
            self.assertEqual(get_state_as_dict(xContentTree)["SelectionCount"], "1")
            xContentTree.executeAction("TYPE", mkPropertyValues({"KEYCODE": "RETURN"}))
            self.ui_test.wait_until_property_is_updated(xContentTree, "SelectEntryText", "Bookmark 1")
            self.assertEqual(get_state_as_dict(xContentTree)["SelectEntryText"], "Bookmark 1")
            self.assertEqual(get_state_as_dict(xContentTree)["SelectionCount"], "1")

            # go to the previous item
            xContentTree.executeAction("TYPE", mkPropertyValues({"KEYCODE": "UP"}))
            self.ui_test.wait_until_property_is_updated(xContentTree, "SelectEntryText", "Bookmarks")
            self.assertEqual(get_state_as_dict(xContentTree)["SelectEntryText"], "Bookmarks")

            self.xUITest.executeCommand(".uno:Sidebar")

# vim: set shiftwidth=4 softtabstop=4 expandtab:
diff --git a/sw/source/uibase/inc/conttree.hxx b/sw/source/uibase/inc/conttree.hxx
index 587f1ce..5bcdc90 100644
--- a/sw/source/uibase/inc/conttree.hxx
+++ b/sw/source/uibase/inc/conttree.hxx
@@ -100,6 +100,7 @@ class SwContentTree final : public SfxListener
    o3tl::enumarray<ContentTypeId,std::unique_ptr<SwContentType>>  m_aHiddenContentArr;
    OUString            m_aContextStrings[CONTEXT_COUNT + 1];
    OUString            m_sInvisible;
    OUString            m_sSelectedItem;  // last selected item (only bookmarks yet)

    SwWrtShell*         m_pHiddenShell;   // dropped Doc
    SwWrtShell*         m_pActiveShell;   // the active or a const. open view
diff --git a/sw/source/uibase/utlui/content.cxx b/sw/source/uibase/utlui/content.cxx
index 806b207..9d9732a 100644
--- a/sw/source/uibase/utlui/content.cxx
+++ b/sw/source/uibase/utlui/content.cxx
@@ -3917,6 +3917,7 @@ void SwContentTree::UpdateTracking()
            if (pCursor && ppBookmark != pMarkAccess->getBookmarksEnd() &&
                    !(m_bIsRoot && m_nRootType != ContentTypeId::BOOKMARK))
            {
                OUString sBookmarkName;
                SwPosition* pCursorPoint = pCursor->GetPoint();
                while (ppBookmark != pMarkAccess->getBookmarksEnd())
                {
@@ -3924,13 +3925,32 @@ void SwContentTree::UpdateTracking()
                            *pCursorPoint >= (*ppBookmark)->GetMarkStart() &&
                            *pCursorPoint <= (*ppBookmark)->GetMarkEnd())
                    {
                        lcl_SelectByContentTypeAndName(this, *m_xTreeView,
                                                       SwResId(STR_CONTENT_TYPE_BOOKMARK),
                                                       (*ppBookmark)->GetName());
                        return;
                        sBookmarkName = (*ppBookmark)->GetName();
                        // keep previously selected bookmark instead
                        // of selecting a different bookmark inside of it
                        if (sBookmarkName == m_sSelectedItem)
                            break;
                    }
                    else if (!sBookmarkName.isEmpty() &&
                        *pCursorPoint < (*ppBookmark)->GetMarkStart())
                    {
                        // don't search a different bookmark inside the
                        // previous one, if the starting position of the next bookmarks
                        // is after the cursor position (assuming that the
                        // bookmark iterator jumps inside the same text by positions)
                        break;
                    }
                    ++ppBookmark;
                }

                if (!sBookmarkName.isEmpty())
                {
                    // select the bookmark
                    lcl_SelectByContentTypeAndName(this, *m_xTreeView,
                                                       SwResId(STR_CONTENT_TYPE_BOOKMARK),
                                                       sBookmarkName);
                    return;
                }
            }
        }
        // references
@@ -5342,6 +5362,7 @@ void SwContentTree::CopyOutlineSelections()
void SwContentTree::GotoContent(const SwContent* pCnt)
{
    m_nLastGotoContentWasOutlinePos = SwOutlineNodes::npos;
    m_sSelectedItem = "";
    lcl_AssureStdModeAtShell(m_pActiveShell);
    switch(m_nLastSelType = pCnt->GetParent()->GetType())
    {
@@ -5376,6 +5397,7 @@ void SwContentTree::GotoContent(const SwContent* pCnt)
            m_pActiveShell->StartAction();
            m_pActiveShell->GotoMark(pCnt->GetName());
            m_pActiveShell->EndAction();
            m_sSelectedItem = pCnt->GetName();
        }
        break;
        case ContentTypeId::REGION    :