sw/source/uibase/inc/wrtsh.hxx | 2 sw/source/uibase/uiview/view2.cxx | 111 --------------------- sw/source/uibase/utlui/content.cxx | 52 ++++++--- sw/source/uibase/wrtsh/wrtsh3.cxx | 132 +++++++++++++++++++++++++ sw/uiconfig/swriter/ui/navigatorcontextmenu.ui | 10 + 5 files changed, 179 insertions(+), 128 deletions(-)
New commits: commit 9be7dacd773ee32a3d50dba77115fbee92506cea Author: Jim Raykowski <[email protected]> AuthorDate: Tue Jan 13 20:28:45 2026 -0900 Commit: Jim Raykowski <[email protected]> CommitDate: Wed Jan 14 23:03:54 2026 +0100 tdf#169674 Sort headings and retain their contents while moving them Enhancement patch to do this from the Writer Navigator Headings (Outline) context menu. Currently experimental mode must be on to see the 'Sort Alphabetically in Document' menu item which is only shown for headings with sub headings. Change-Id: Iec0006d1309b3c96bc7019d009b9dc4f9146df62 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/197230 Tested-by: Jenkins Reviewed-by: Jim Raykowski <[email protected]> diff --git a/sw/source/uibase/inc/wrtsh.hxx b/sw/source/uibase/inc/wrtsh.hxx index 077d03aebfde..821a321f7a65 100644 --- a/sw/source/uibase/inc/wrtsh.hxx +++ b/sw/source/uibase/inc/wrtsh.hxx @@ -539,6 +539,8 @@ typedef bool (SwWrtShell::*FNSimpleMove)(); std::optional<OString> getLOKPayload(int nType, int nViewId) const; + void SortChapters(const SwOutlineNodes::size_type nOutlineNodePos = SwOutlineNodes::npos); + private: void OpenMark(); diff --git a/sw/source/uibase/uiview/view2.cxx b/sw/source/uibase/uiview/view2.cxx index 31a288b82136..08dae34969c7 100644 --- a/sw/source/uibase/uiview/view2.cxx +++ b/sw/source/uibase/uiview/view2.cxx @@ -165,8 +165,6 @@ #include <svx/dialog/gotodlg.hxx> #include <unotools/tempfile.hxx> -#include <set> - const char sStatusDelim[] = " : "; using namespace sfx2; @@ -1712,114 +1710,7 @@ void SwView::Execute(SfxRequest &rReq) } case FN_SORT_CHAPTERS: { - auto sort_chapters = [this](const SwNode* pParentNode, int nOutlineLevel) - { - const SwNode* pEndNode; - - std::vector<const SwTextNode*> vLevelOutlineNodes; - auto GetLevelOutlineNodesAndEndNode = [&]() - { - vLevelOutlineNodes.clear(); - bool bParentFound = false; - pEndNode = &m_pWrtShell->GetNodes().GetEndOfContent(); - for (const SwNode* pNode : m_pWrtShell->GetNodes().GetOutLineNds()) - { - if (!pNode->IsTextNode()) - continue; - if (pParentNode && !bParentFound) - { - bParentFound = pNode == pParentNode; - continue; - } - if (pNode->GetTextNode()->GetAttrOutlineLevel() < nOutlineLevel) - { - pEndNode = pNode; - break; - } - if (pNode->GetTextNode()->GetAttrOutlineLevel() == nOutlineLevel) - vLevelOutlineNodes.emplace_back(pNode->GetTextNode()); - } - }; - - GetLevelOutlineNodesAndEndNode(); - - std::vector<const SwTextNode*> vSortedLevelOutlineNodes = vLevelOutlineNodes; - std::stable_sort(vSortedLevelOutlineNodes.begin(), vSortedLevelOutlineNodes.end(), - [](const SwTextNode* a, const SwTextNode* b) - { - const OUString& raText = a->GetText(); - const OUString& rbText = b->GetText(); - return raText < rbText; - }); - - for (size_t i = 0, nSize = vLevelOutlineNodes.size(); i < nSize; i++) - { - // Find the position that the sorted node is at in the unsorted vector. - // This is the position of the node in the unsorted vector that is used for the start of - // the range of nodes to be moved in this iteration. - size_t j = 0; - for (; j < nSize; j++) - { - if (vSortedLevelOutlineNodes[i] == vLevelOutlineNodes[j]) - break; - } - - // The end node in the range is the next entry in the unsorted vector or the pEndNode set - // by GetLevelOutlineNodesAndEndNode or the end of the document. - const SwNode* pEndRangeNode; - if (j + 1 < nSize) - pEndRangeNode = vLevelOutlineNodes[j + 1]; - else - pEndRangeNode = pEndNode; - - SwNodeRange aNodeRange(*vLevelOutlineNodes[j], SwNodeOffset(0), *pEndRangeNode, - SwNodeOffset(0)); - - // Move the range of nodes to before the node in the unsorted outline vector at the - // current iteration index to match the position of the outline node in the sorted vector. - m_pWrtShell->getIDocumentContentOperations().MoveNodeRange( - aNodeRange, *const_cast<SwTextNode*>(vLevelOutlineNodes[i]), - SwMoveFlags::DEFAULT | SwMoveFlags::CREATEUNDOOBJ); - - GetLevelOutlineNodesAndEndNode(); - } - }; - - const SwOutlineNodes& rOutlineNodes = m_pWrtShell->GetNodes().GetOutLineNds(); - - if (rOutlineNodes.empty()) - return; - - m_pWrtShell->StartAction(); - m_pWrtShell->StartUndo(SwUndoId::SORT_CHAPTERS); - - // Create an ordered set of outline levels in the outline nodes for use to determine - // the lowest level to use for first sort and to only iterate over higher levels used. - std::set<int> aOutlineLevelSet; - for (const SwNode* pNode : rOutlineNodes) - { - int nOutlineLevel = pNode->GetTextNode()->GetAttrOutlineLevel(); - aOutlineLevelSet.emplace(nOutlineLevel); - } - - // No parent node for the lowest outline level nodes sort. - sort_chapters(nullptr /*pParentNode*/, - aOutlineLevelSet.extract(aOutlineLevelSet.begin()).value()); - - for (int nOutlineLevel : aOutlineLevelSet) - for (size_t i = 0, nSize = rOutlineNodes.size(); i < nSize; i++) - { - const SwNode* pParentNode = rOutlineNodes[i]; - if (i + 1 < nSize - && rOutlineNodes[i + 1]->GetTextNode()->GetAttrOutlineLevel() - == nOutlineLevel) - { - sort_chapters(pParentNode, nOutlineLevel); - } - } - - m_pWrtShell->EndUndo(SwUndoId::SORT_CHAPTERS); - m_pWrtShell->EndAction(); + m_pWrtShell->SortChapters(); } break; default: diff --git a/sw/source/uibase/utlui/content.cxx b/sw/source/uibase/utlui/content.cxx index 5ae78dc29249..0d729fc38625 100644 --- a/sw/source/uibase/utlui/content.cxx +++ b/sw/source/uibase/utlui/content.cxx @@ -135,6 +135,8 @@ #include <sfx2/passwd.hxx> #include <svl/PasswordHelper.hxx> +#include <officecfg/Office/Common.hxx> + #define CTYPE_CNT 0 #define CTYPE_CTT 1 @@ -1945,28 +1947,40 @@ IMPL_LINK(SwContentTree, CommandHdl, const CommandEvent&, rCEvt, bool) bool bRemoveFootnoteTracking = true; bool bRemoveEndnoteTracking = true; + // display the content type content in alphabetical order (does not change the document) bool bRemoveSortEntry = true; + // sort outline content in alphabetical order (changes the document) + bool bRemoveSortOutlineAlphabeticallyInDocument = true; bool bRemoveProtectSection = true; bool bRemoveHideSection = true; if (xEntry) { + bool bIsContentType; const SwContentType* pType; - if (lcl_IsContentType(*xEntry, *m_xTreeView)) + if ((bIsContentType = lcl_IsContentType(*xEntry, *m_xTreeView))) pType = weld::fromId<SwContentType*>(m_xTreeView->get_id(*xEntry)); else pType = weld::fromId<SwContent*>( m_xTreeView->get_id(*xEntry))->GetParent(); const ContentTypeId nContentType = pType->GetType(); - if (nContentType != ContentTypeId::FOOTNOTE && nContentType != ContentTypeId::ENDNOTE - && nContentType != ContentTypeId::POSTIT && nContentType != ContentTypeId::UNKNOWN) + if (bIsContentType && nContentType != ContentTypeId::FOOTNOTE + && nContentType != ContentTypeId::ENDNOTE && nContentType != ContentTypeId::POSTIT + && nContentType != ContentTypeId::UNKNOWN) { bRemoveSortEntry = false; xPop->set_active(u"sort"_ustr, pType->IsAlphabeticSort()); } + if (officecfg::Office::Common::Misc::ExperimentalMode::get() + && nContentType == ContentTypeId::OUTLINE) + { + if (bIsContentType || m_xTreeView->iter_has_child(*xEntry)) + bRemoveSortOutlineAlphabeticallyInDocument = false; + } + OUString aIdent; switch (nContentType) { @@ -2406,19 +2420,11 @@ IMPL_LINK(SwContentTree, CommandHdl, const CommandEvent&, rCEvt, bool) if (bRemoveCopyEntry) xPop->remove(u"copy"_ustr); - if (bRemoveGotoEntry && - bRemoveCopyEntry && - bRemoveSelectEntry && - bRemoveDeleteEntry && - bRemoveMakeFootnotesEndnotesViceVersaEntry && - bRemoveChapterEntries && - bRemovePostItEntries && - bRemoveRenameEntry && - bRemoveIndexEntry && - bRemoveUpdateIndexEntry && - bRemoveReadonlyIndexEntry && - bRemoveUnprotectEntry && - bRemoveEditEntry) + if (bRemoveGotoEntry && bRemoveCopyEntry && bRemoveSelectEntry && bRemoveDeleteEntry + && bRemoveMakeFootnotesEndnotesViceVersaEntry && bRemoveChapterEntries + && bRemovePostItEntries && bRemoveRenameEntry && bRemoveIndexEntry + && bRemoveUpdateIndexEntry && bRemoveReadonlyIndexEntry && bRemoveUnprotectEntry + && bRemoveEditEntry && bRemoveSortOutlineAlphabeticallyInDocument) xPop->remove(u"separator2"_ustr); if (!bOutline) @@ -2470,6 +2476,8 @@ IMPL_LINK(SwContentTree, CommandHdl, const CommandEvent&, rCEvt, bool) xPop->remove(u"endnotetracking"_ustr); if (bRemoveSortEntry) xPop->remove(u"sort"_ustr); + if (bRemoveSortOutlineAlphabeticallyInDocument) + xPop->remove(u"sortalphabeticallyindocument"_ustr); if (bRemoveProtectSection) xPop->remove(u"protectsection"_ustr); if (bRemoveHideSection) @@ -6104,7 +6112,17 @@ void SwContentTree::ExecuteContextMenuAction(const OUString& rSelectedPopupEntry EditEntry(*xFirst, EditEntryMode::TEXT_ALTERNATIVE); return; } - + else if (rSelectedPopupEntry == "sortalphabeticallyindocument") + { + SwOutlineNodes::size_type nOutlineNodePos = SwOutlineNodes::npos; + if (lcl_IsContent(*xFirst, *m_xTreeView)) + { + nOutlineNodePos + = weld::fromId<SwOutlineContent*>(m_xTreeView->get_id(*xFirst))->GetOutlinePos(); + } + m_pActiveShell->SortChapters(nOutlineNodePos); + return; + } auto nSelectedPopupEntry = rSelectedPopupEntry.toUInt32(); switch (nSelectedPopupEntry) diff --git a/sw/source/uibase/wrtsh/wrtsh3.cxx b/sw/source/uibase/wrtsh/wrtsh3.cxx index 67ba8e22b6e7..324b3e7a3507 100644 --- a/sw/source/uibase/wrtsh/wrtsh3.cxx +++ b/sw/source/uibase/wrtsh/wrtsh3.cxx @@ -44,6 +44,11 @@ #include <strings.hrc> #include <textcontentcontrol.hxx> +#include <vector> +#include <set> +#include <IDocumentContentOperations.hxx> +#include <ndtxt.hxx> + using namespace ::com::sun::star; bool SwWrtShell::MoveBookMark( BookMarkMove eFuncId, const ::sw::mark::MarkBase* const pMark) @@ -395,4 +400,131 @@ SwPostItMgr* SwWrtShell::GetPostItMgr() return m_rView.GetPostItMgr(); } +void SwWrtShell::SortChapters(const SwOutlineNodes::size_type nOutlineNodePos) +{ + auto sort_chapters = [this](const SwNode* pParentNode, int nOutlineLevel) + { + const SwNode* pEndNode; + + std::vector<const SwTextNode*> vLevelOutlineNodes; + auto GetLevelOutlineNodesAndEndNode = [&]() + { + vLevelOutlineNodes.clear(); + bool bParentFound = false; + pEndNode = &GetNodes().GetEndOfContent(); + for (const SwNode* pNode : GetNodes().GetOutLineNds()) + { + if (!pNode->IsTextNode()) + continue; + if (pParentNode && !bParentFound) + { + bParentFound = pNode == pParentNode; + continue; + } + if (pNode->GetTextNode()->GetAttrOutlineLevel() < nOutlineLevel) + { + pEndNode = pNode; + break; + } + if (pNode->GetTextNode()->GetAttrOutlineLevel() == nOutlineLevel) + vLevelOutlineNodes.emplace_back(pNode->GetTextNode()); + } + }; + + GetLevelOutlineNodesAndEndNode(); + + std::vector<const SwTextNode*> vSortedLevelOutlineNodes = vLevelOutlineNodes; + std::stable_sort(vSortedLevelOutlineNodes.begin(), vSortedLevelOutlineNodes.end(), + [](const SwTextNode* a, const SwTextNode* b) + { + const OUString& raText = a->GetText(); + const OUString& rbText = b->GetText(); + return raText < rbText; + }); + + for (size_t i = 0, nSize = vLevelOutlineNodes.size(); i < nSize; i++) + { + // Find the position that the sorted node is at in the unsorted vector. + // This is the position of the node in the unsorted vector that is used for the start of + // the range of nodes to be moved in this iteration. + size_t j = 0; + for (; j < nSize; j++) + { + if (vSortedLevelOutlineNodes[i] == vLevelOutlineNodes[j]) + break; + } + + // The end node in the range is the next entry in the unsorted vector or the pEndNode set + // by GetLevelOutlineNodesAndEndNode or the end of the document. + const SwNode* pEndRangeNode; + if (j + 1 < nSize) + pEndRangeNode = vLevelOutlineNodes[j + 1]; + else + pEndRangeNode = pEndNode; + + SwNodeRange aNodeRange(*vLevelOutlineNodes[j], SwNodeOffset(0), *pEndRangeNode, + SwNodeOffset(0)); + + // Move the range of nodes to before the node in the unsorted outline vector at the + // current iteration index to match the position of the outline node in the sorted vector. + getIDocumentContentOperations().MoveNodeRange( + aNodeRange, *const_cast<SwTextNode*>(vLevelOutlineNodes[i]), + SwMoveFlags::DEFAULT | SwMoveFlags::CREATEUNDOOBJ); + + GetLevelOutlineNodesAndEndNode(); + } + }; + + const SwOutlineNodes& rOutlineNodes = GetNodes().GetOutLineNds(); + + if (rOutlineNodes.empty()) + return; + + StartAction(); + StartUndo(SwUndoId::SORT_CHAPTERS); + + // Create an ordered set of outline levels in the outline nodes for use to determine + // the lowest level to use for first sort and to only iterate over higher levels used. + std::set<int> aOutlineLevelSet; + for (const SwNode* pNode : rOutlineNodes) + { + int nOutlineLevel = pNode->GetTextNode()->GetAttrOutlineLevel(); + aOutlineLevelSet.emplace(nOutlineLevel); + } + + // No parent node for the lowest outline level nodes sort. + if (nOutlineNodePos == SwOutlineNodes::npos) + { + sort_chapters(nullptr /*pParentNode*/, + aOutlineLevelSet.extract(aOutlineLevelSet.begin()).value()); + for (int nOutlineLevel : aOutlineLevelSet) + { + for (size_t i = 0, nSize = rOutlineNodes.size(); i + 1 < nSize; i++) + { + if (rOutlineNodes[i]->GetTextNode()->GetAttrOutlineLevel() < nOutlineLevel + && rOutlineNodes[i + 1]->GetTextNode()->GetAttrOutlineLevel() == nOutlineLevel) + { + const SwNode* pParentNode = rOutlineNodes[i]; + sort_chapters(pParentNode, nOutlineLevel); + } + } + } + } + else + { + for (int nOutlineLevel : aOutlineLevelSet) + { + if (rOutlineNodes[nOutlineNodePos]->GetTextNode()->GetAttrOutlineLevel() + < nOutlineLevel) + { + const SwNode* pParentNode = rOutlineNodes[nOutlineNodePos]; + sort_chapters(pParentNode, nOutlineLevel); + } + } + } + + EndUndo(SwUndoId::SORT_CHAPTERS); + EndAction(); +} + /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/uiconfig/swriter/ui/navigatorcontextmenu.ui b/sw/uiconfig/swriter/ui/navigatorcontextmenu.ui index 596b03845523..30d3ba84b83f 100644 --- a/sw/uiconfig/swriter/ui/navigatorcontextmenu.ui +++ b/sw/uiconfig/swriter/ui/navigatorcontextmenu.ui @@ -98,6 +98,14 @@ <accelerator key="Right" signal="activate" modifiers="GDK_CONTROL_MASK"/> </object> </child> + <child> + <object class="GtkMenuItem" id="sortalphabeticallyindocument"> + <property name="visible">True</property> + <property name="can-focus">False</property> + <property name="label" translatable="yes" context="navigatorcontextmenu|STR_SORT_ALPHABETICALLY_IN_DOCUMENT">Sort Alphabetically in Document</property> + <property name="use-underline">True</property> + </object> + </child> <child> <object class="GtkMenuItem" id="401"> <property name="visible">True</property> @@ -608,7 +616,7 @@ <object class="GtkCheckMenuItem" id="sort"> <property name="visible">True</property> <property name="can-focus">False</property> - <property name="label" translatable="yes" context="navigatorcontextmenu|STR_SORT_ALPHABETICALLY">Sort Alphabetically</property> + <property name="label" translatable="yes" context="navigatorcontextmenu|STR_SORT_ALPHABETICALLY">Display Alphabetically</property> <property name="use-underline">True</property> </object> </child>
