sw/qa/uitest/navigator/tdf40427.py | 22 +++- sw/source/uibase/inc/content.hxx | 4 sw/source/uibase/inc/conttree.hxx | 1 sw/source/uibase/utlui/content.cxx | 188 ++++++++++++++++++++++++++++++------- 4 files changed, 174 insertions(+), 41 deletions(-)
New commits: commit 468c5110226c7f5b3079852a27adde45dc32dc76 Author: Jim Raykowski <rayk...@gmail.com> AuthorDate: Wed Nov 22 00:00:57 2023 -0900 Commit: Jim Raykowski <rayk...@gmail.com> CommitDate: Mon Nov 27 18:56:38 2023 +0100 tdf#158103 Enhancement to display Sections as an expandable/ collapsible hierarchy in the Writer Navigator Change-Id: I86bc17d11b4c5bf0ca0496e4ab62a0d77ddb625f Reviewed-on: https://gerrit.libreoffice.org/c/core/+/159812 Tested-by: Jenkins Reviewed-by: Mike Kaganski <mike.kagan...@collabora.com> Reviewed-by: Jim Raykowski <rayk...@gmail.com> diff --git a/sw/qa/uitest/navigator/tdf40427.py b/sw/qa/uitest/navigator/tdf40427.py index f394bf37aa06..aaab63ffdd4d 100644 --- a/sw/qa/uitest/navigator/tdf40427.py +++ b/sw/qa/uitest/navigator/tdf40427.py @@ -19,6 +19,20 @@ class tdf40427(UITestCase): if name == get_state_as_dict(xItem)['Text']: return xItem + def expand_all(self, xTreeItem): + count = len(xTreeItem.getChildren()) + for i in xTreeItem.getChildren(): + xTreeItem.getChild(i).executeAction("EXPAND", ()) + count += self.expand_all(xTreeItem.getChild(i)) + return count + + def get_names(self, xTreeItem): + names = [] + for i in xTreeItem.getChildren(): + names.append(get_state_as_dict(xTreeItem.getChild(str(i)))['Text']) + names += self.get_names(xTreeItem.getChild(i)) + return names + def test_tdf40427(self): with self.ui_test.load_file(get_url_for_data_file("tdf40427_SectionPositions.odt")) as document: xMainWindow = self.xUITest.getTopFocusWindow() @@ -53,6 +67,7 @@ class tdf40427(UITestCase): xSections = self.get_item(xContentTree, 'Sections') self.assertEqual('Sections', get_state_as_dict(xSections)['Text']) xSections.executeAction("EXPAND", ()) + totalSectionsCount = self.expand_all(xSections) refSectionNames = [ 'SectionZ', @@ -69,11 +84,10 @@ class tdf40427(UITestCase): 'SectionB', # High on screen, but late in list because it's on second page 'SectionC', ] - self.assertEqual(len(refSectionNames), len(xSections.getChildren())) + self.assertEqual(len(refSectionNames), totalSectionsCount) + + actSectionNames = self.get_names(xSections) - actSectionNames = [] - for i in range(len(refSectionNames)): - actSectionNames.append(get_state_as_dict(xSections.getChild(str(i)))['Text']) # Without the fix in place, this would fail with # AssertionError: Lists differ: ['SectionZ', 'SectionY', 'SectionT3', 'SectionT1', 'SectionT2'[100 chars]onC'] != ['SectionZ', 'SectionB', 'SectionF3', 'SectionFinF3', 'Section[100 chars]onA'] self.assertEqual(refSectionNames, actSectionNames) diff --git a/sw/source/uibase/inc/content.hxx b/sw/source/uibase/inc/content.hxx index c2a922189894..77cb156b744c 100644 --- a/sw/source/uibase/inc/content.hxx +++ b/sw/source/uibase/inc/content.hxx @@ -212,8 +212,8 @@ public: Invalidate(); } - bool GetSortType() const {return m_bAlphabeticSort;} - void SetSortType(bool bAlphabetic) {m_bAlphabeticSort = bAlphabetic;} + bool IsAlphabeticSort() const {return m_bAlphabeticSort;} + void SetAlphabeticSort(bool bAlphabetic) {m_bAlphabeticSort = bAlphabetic;} void Invalidate(); // only nMemberCount is read again diff --git a/sw/source/uibase/inc/conttree.hxx b/sw/source/uibase/inc/conttree.hxx index 9c6db5b5a5eb..a8094c40303a 100644 --- a/sw/source/uibase/inc/conttree.hxx +++ b/sw/source/uibase/inc/conttree.hxx @@ -107,6 +107,7 @@ class SwContentTree final : public SfxListener SwNavigationConfig* m_pConfig; std::map< void*, bool > mOutLineNodeMap; + std::map<const void*, bool> m_aRegionNodeExpandMap; // stores expand state of nodes with children sal_Int32 m_nActiveBlock; // used to restore content types/categories expand state sal_Int32 m_nHiddenBlock; diff --git a/sw/source/uibase/utlui/content.cxx b/sw/source/uibase/utlui/content.cxx index 770f8b41e705..07fd64dc3bf9 100644 --- a/sw/source/uibase/utlui/content.cxx +++ b/sw/source/uibase/utlui/content.cxx @@ -217,6 +217,11 @@ namespace rPos = *pPos; } } + + bool lcl_IsLowerRegionContent(const weld::TreeIter& rEntry, const weld::TreeView& rTreeView, sal_uInt8 nLevel) + { + return weld::fromId<const SwRegionContent*>(rTreeView.get_id(rEntry))->GetRegionLevel() < nLevel; + } } // Content, contains names and reference at the content type. @@ -1727,7 +1732,7 @@ IMPL_LINK(SwContentTree, CommandHdl, const CommandEvent&, rCEvt, bool) && nContentType != ContentTypeId::POSTIT) { bRemoveSortEntry = false; - xPop->set_active("sort", pType->GetSortType()); + xPop->set_active("sort", pType->IsAlphabeticSort()); } OUString aIdent; @@ -2130,26 +2135,19 @@ void SwContentTree::InsertContent(const weld::TreeIter& rParent) assert(dynamic_cast<SwContentType*>(weld::fromId<SwTypeNumber*>(m_xTreeView->get_id(rParent)))); SwContentType* pCntType = weld::fromId<SwContentType*>(m_xTreeView->get_id(rParent)); bool bGraphic = pCntType->GetType() == ContentTypeId::GRAPHIC; - bool bRegion = pCntType->GetType() == ContentTypeId::REGION; std::unique_ptr<weld::TreeIter> xChild = m_xTreeView->make_iterator(); const size_t nCount = pCntType->GetMemberCount(); for(size_t i = 0; i < nCount; ++i) { const SwContent* pCnt = pCntType->GetMember(i); - if (pCnt) - { - OUString sEntry = pCnt->GetName(); - if (sEntry.isEmpty()) - sEntry = m_sSpace; - OUString sId(weld::toId(pCnt)); - insert(&rParent, sEntry, sId, false, xChild.get()); - m_xTreeView->set_sensitive(*xChild, !pCnt->IsInvisible()); - if (bGraphic && !static_cast<const SwGraphicContent*>(pCnt)->GetLink().isEmpty()) - m_xTreeView->set_image(*xChild, RID_BMP_NAVI_GRAPHIC_LINK); - else if (bRegion) - m_xTreeView->set_extra_row_indent(*xChild, - static_cast<const SwRegionContent*>(pCnt)->GetRegionLevel()); - } + OUString sEntry = pCnt->GetName(); + if (sEntry.isEmpty()) + sEntry = m_sSpace; + OUString sId(weld::toId(pCnt)); + insert(&rParent, sEntry, sId, false, xChild.get()); + m_xTreeView->set_sensitive(*xChild, !pCnt->IsInvisible()); + if (bGraphic && !static_cast<const SwGraphicContent*>(pCnt)->GetLink().isEmpty()) + m_xTreeView->set_image(*xChild, RID_BMP_NAVI_GRAPHIC_LINK); } } @@ -2226,6 +2224,72 @@ bool SwContentTree::RequestingChildren(const weld::TreeIter& rParent) } } } + else if (pCntType->GetType() == ContentTypeId::REGION) + { + if (pCntType->IsAlphabeticSort()) + { + for(size_t i = 0; i < nCount; ++i) + { + const SwRegionContent* pCnt = + static_cast<const SwRegionContent*>(pCntType->GetMember(i)); + + OUString sEntry = pCnt->GetName(); + OUString sId(weld::toId(pCnt)); + + const auto nLevel = pCnt->GetRegionLevel(); + insert(&rParent, sEntry, sId, false, xChild.get()); + + m_xTreeView->set_sensitive(*xChild, !pCnt->IsInvisible()); + m_xTreeView->set_extra_row_indent(*xChild, nLevel); + + bool bHidden = pCnt->GetSectionFormat()->GetSection()->IsHidden(); + if (pCnt->IsProtect()) + m_xTreeView->set_image(*xChild, bHidden ? RID_BMP_PROT_HIDE : RID_BMP_PROT_NO_HIDE); + else + m_xTreeView->set_image(*xChild, bHidden ? RID_BMP_HIDE : RID_BMP_NO_HIDE); + } + } + else + { + std::vector<std::unique_ptr<weld::TreeIter>> aParentCandidates; + for(size_t i = 0; i < nCount; ++i) + { + const SwRegionContent* pCnt = + static_cast<const SwRegionContent*>(pCntType->GetMember(i)); + + OUString sEntry = pCnt->GetName(); + OUString sId(weld::toId(pCnt)); + + const auto nLevel = pCnt->GetRegionLevel(); + auto lambda = [nLevel, this](const std::unique_ptr<weld::TreeIter>& xEntry) + { + return lcl_IsLowerRegionContent(*xEntry, *m_xTreeView, nLevel); + }; + + // if there is a preceding region node candidate with a lower region level use + // that as a parent, otherwise use the root node + auto aFind = std::find_if(aParentCandidates.rbegin(), aParentCandidates.rend(), lambda); + if (aFind != aParentCandidates.rend()) + insert(aFind->get(), sEntry, sId, false, xChild.get()); + else + insert(&rParent, sEntry, sId, false, xChild.get()); + m_xTreeView->set_sensitive(*xChild, !pCnt->IsInvisible()); + + bool bHidden = pCnt->GetSectionFormat()->GetSection()->IsHidden(); + if (pCnt->IsProtect()) + m_xTreeView->set_image(*xChild, bHidden ? RID_BMP_PROT_HIDE : RID_BMP_PROT_NO_HIDE); + else + m_xTreeView->set_image(*xChild, bHidden ? RID_BMP_HIDE : RID_BMP_NO_HIDE); + + // remove any parent candidates equal to or higher than this node + aParentCandidates.erase(std::remove_if(aParentCandidates.begin(), aParentCandidates.end(), + std::not_fn(lambda)), aParentCandidates.end()); + + // add this node as a parent candidate for any following nodes at a higher region level + aParentCandidates.emplace_back(m_xTreeView->make_iterator(xChild.get())); + } + } + } else InsertContent(rParent); @@ -2283,15 +2347,14 @@ void SwContentTree::Expand(const weld::TreeIter& rParent, std::vector<std::uniqu if (!(m_xTreeView->iter_has_child(rParent) || m_xTreeView->get_children_on_demand(rParent))) return; - if (!m_bIsRoot - || (lcl_IsContentType(rParent, *m_xTreeView) && - weld::fromId<SwContentType*>(m_xTreeView->get_id(rParent))->GetType() == ContentTypeId::OUTLINE) - || (m_nRootType == ContentTypeId::OUTLINE)) + if (m_nRootType == ContentTypeId::UNKNOWN || m_nRootType == ContentTypeId::OUTLINE || + m_nRootType == ContentTypeId::REGION) { if (lcl_IsContentType(rParent, *m_xTreeView)) { - SwContentType* pCntType = weld::fromId<SwContentType*>(m_xTreeView->get_id(rParent)); - const sal_Int32 nOr = 1 << static_cast<int>(pCntType->GetType()); //linear -> Bitposition + ContentTypeId eContentTypeId = + weld::fromId<SwContentType*>(m_xTreeView->get_id(rParent))->GetType(); + const sal_Int32 nOr = 1 << static_cast<int>(eContentTypeId); //linear -> Bitposition if (State::HIDDEN != m_eState) { m_nActiveBlock |= nOr; @@ -2299,7 +2362,7 @@ void SwContentTree::Expand(const weld::TreeIter& rParent, std::vector<std::uniqu } else m_nHiddenBlock |= nOr; - if (pCntType->GetType() == ContentTypeId::OUTLINE) + if (eContentTypeId == ContentTypeId::OUTLINE) { std::map< void*, bool > aCurrOutLineNodeMap; @@ -2335,18 +2398,60 @@ void SwContentTree::Expand(const weld::TreeIter& rParent, std::vector<std::uniqu mOutLineNodeMap = aCurrOutLineNodeMap; return; } + if (eContentTypeId == ContentTypeId::REGION) + { + std::map<const void*, bool> aCurrentRegionNodeExpandMap; + + bool bParentHasChild = RequestingChildren(rParent); + if (pNodesToExpand) + pNodesToExpand->emplace_back(m_xTreeView->make_iterator(&rParent)); + if (bParentHasChild) + { + std::unique_ptr<weld::TreeIter> xChild(m_xTreeView->make_iterator(&rParent)); + bool bChild = m_xTreeView->iter_next(*xChild); + while (bChild && lcl_IsContent(*xChild, *m_xTreeView)) + { + if (m_xTreeView->iter_has_child(*xChild)) + { + assert(dynamic_cast<SwRegionContent*>(weld::fromId<SwTypeNumber*>(m_xTreeView->get_id(*xChild)))); + const void* key = static_cast<const void*>( + weld::fromId<SwRegionContent*>(m_xTreeView->get_id(*xChild)) + ->GetSectionFormat()); + aCurrentRegionNodeExpandMap.emplace(key, false); + if (m_aRegionNodeExpandMap.contains(key) && m_aRegionNodeExpandMap[key]) + { + aCurrentRegionNodeExpandMap[key] = true; + RequestingChildren(*xChild); + if (pNodesToExpand) + pNodesToExpand->emplace_back(m_xTreeView->make_iterator(xChild.get())); + m_xTreeView->set_children_on_demand(*xChild, false); + } + } + bChild = m_xTreeView->iter_next(*xChild); + } + } + m_aRegionNodeExpandMap = aCurrentRegionNodeExpandMap; + return; + } } - else + else // content entry { - if (lcl_IsContent(rParent, *m_xTreeView)) + ContentTypeId eContentTypeId = + weld::fromId<SwContent*>(m_xTreeView->get_id(rParent))->GetParent()->GetType(); + if (eContentTypeId == ContentTypeId::OUTLINE) { SwWrtShell* pShell = GetWrtShell(); - // paranoid assert now that outline type is checked assert(dynamic_cast<SwOutlineContent*>(weld::fromId<SwTypeNumber*>(m_xTreeView->get_id(rParent)))); auto const nPos = weld::fromId<SwOutlineContent*>(m_xTreeView->get_id(rParent))->GetOutlinePos(); void* key = static_cast<void*>(pShell->getIDocumentOutlineNodesAccess()->getOutlineNode( nPos )); mOutLineNodeMap[key] = true; } + else if(eContentTypeId == ContentTypeId::REGION) + { + assert(dynamic_cast<SwRegionContent*>(weld::fromId<SwTypeNumber*>(m_xTreeView->get_id(rParent)))); + const void* key = static_cast<const void*>(weld::fromId<SwRegionContent*>(m_xTreeView->get_id(rParent))->GetSectionFormat()); + m_aRegionNodeExpandMap[key] = true; + } } } @@ -2382,8 +2487,9 @@ IMPL_LINK(SwContentTree, CollapseHdl, const weld::TreeIter&, rParent, bool) } return false; // return false to notify caller not to do collapse } - SwContentType* pCntType = weld::fromId<SwContentType*>(m_xTreeView->get_id(rParent)); - const sal_Int32 nAnd = ~(1 << static_cast<int>(pCntType->GetType())); + ContentTypeId eContentTypeId = + weld::fromId<SwContentType*>(m_xTreeView->get_id(rParent))->GetType(); + const sal_Int32 nAnd = ~(1 << static_cast<int>(eContentTypeId)); if (State::HIDDEN != m_eState) { m_nActiveBlock &= nAnd; @@ -2392,13 +2498,24 @@ IMPL_LINK(SwContentTree, CollapseHdl, const weld::TreeIter&, rParent, bool) else m_nHiddenBlock &= nAnd; } - else if (lcl_IsContent(rParent, *m_xTreeView)) + else // content entry { SwWrtShell* pShell = GetWrtShell(); - assert(dynamic_cast<SwOutlineContent*>(weld::fromId<SwTypeNumber*>(m_xTreeView->get_id(rParent)))); - auto const nPos = weld::fromId<SwOutlineContent*>(m_xTreeView->get_id(rParent))->GetOutlinePos(); - void* key = static_cast<void*>(pShell->getIDocumentOutlineNodesAccess()->getOutlineNode( nPos )); - mOutLineNodeMap[key] = false; + ContentTypeId eContentTypeId = + weld::fromId<SwContent*>(m_xTreeView->get_id(rParent))->GetParent()->GetType(); + if (eContentTypeId == ContentTypeId::OUTLINE) + { + assert(dynamic_cast<SwOutlineContent*>(weld::fromId<SwTypeNumber*>(m_xTreeView->get_id(rParent)))); + auto const nPos = weld::fromId<SwOutlineContent*>(m_xTreeView->get_id(rParent))->GetOutlinePos(); + void* key = static_cast<void*>(pShell->getIDocumentOutlineNodesAccess()->getOutlineNode( nPos )); + mOutLineNodeMap[key] = false; + } + else if(eContentTypeId == ContentTypeId::REGION) + { + assert(dynamic_cast<SwRegionContent*>(weld::fromId<SwTypeNumber*>(m_xTreeView->get_id(rParent)))); + const void* key = static_cast<const void*>(weld::fromId<SwRegionContent*>(m_xTreeView->get_id(rParent))->GetSectionFormat()); + m_aRegionNodeExpandMap[key] = false; + } } return true; @@ -2690,7 +2807,8 @@ void SwContentTree::Display( bool bActive ) if(!rpRootContentT) rpRootContentT.reset(new SwContentType(pShell, m_nRootType, m_nOutlineLevel )); OUString aImage(GetImageIdForContentTypeId(m_nRootType)); - bool bChOnDemand = m_nRootType == ContentTypeId::OUTLINE; + bool bChOnDemand(m_nRootType == ContentTypeId::OUTLINE || + m_nRootType == ContentTypeId::REGION); OUString sId(weld::toId(rpRootContentT.get())); insert(nullptr, rpRootContentT->GetName(), sId, bChOnDemand, xEntry.get()); m_xTreeView->set_image(*xEntry, aImage); @@ -4701,7 +4819,7 @@ void SwContentTree::ExecuteContextMenuAction(const OUString& rSelectedPopupEntry pCntType = weld::fromId<SwContentType*>(rId); else pCntType = const_cast<SwContentType*>(weld::fromId<SwContent*>(rId)->GetParent()); - pCntType->SetSortType(!pCntType->GetSortType()); + pCntType->SetAlphabeticSort(!pCntType->IsAlphabeticSort()); pCntType->FillMemberList(); Display(true); return;