officecfg/registry/data/org/openoffice/Office/UI/WriterCommands.xcu | 14 + sw/inc/cmdid.h | 2 sw/inc/strings.hrc | 2 sw/inc/swundo.hxx | 1 sw/sdi/_viewsh.sdi | 5 sw/sdi/swriter.sdi | 16 + sw/source/core/undo/undobj.cxx | 3 sw/source/uibase/uiview/view2.cxx | 113 ++++++++++ 8 files changed, 156 insertions(+)
New commits: commit 076ad399c286917b67245302fac7c226fafecb89 Author: Jim Raykowski <[email protected]> AuthorDate: Fri Dec 26 00:34:41 2025 -0900 Commit: Jim Raykowski <[email protected]> CommitDate: Wed Dec 31 04:55:33 2025 +0100 tdf#167491 Enhancement to sort chapters alphabetically The UNO command for this feature .uno:SortChapters is set experimental. I found a couple undo redo crashes exposed by this patch. Fixes for these are done in related patches. Change-Id: I75e67b137668be200dce192cfd5cd9a4e1ffdf8e Reviewed-on: https://gerrit.libreoffice.org/c/core/+/196240 Tested-by: Jenkins Reviewed-by: Jim Raykowski <[email protected]> diff --git a/officecfg/registry/data/org/openoffice/Office/UI/WriterCommands.xcu b/officecfg/registry/data/org/openoffice/Office/UI/WriterCommands.xcu index ec26db44a0e1..cd6dbcd61764 100644 --- a/officecfg/registry/data/org/openoffice/Office/UI/WriterCommands.xcu +++ b/officecfg/registry/data/org/openoffice/Office/UI/WriterCommands.xcu @@ -3597,6 +3597,20 @@ <value>1</value> </prop> </node> + <node oor:name=".uno:SortChapters" oor:op="replace"> + <prop oor:name="Label" oor:type="xs:string"> + <value xml:lang="en-US">Sort Chapters</value> + </prop> + <prop oor:name="TooltipLabel" oor:type="xs:string"> + <value xml:lang="en-US">Make chapters in the document alphabetically sorted</value> + </prop> + <prop oor:name="Properties" oor:type="xs:int"> + <value>1</value> + </prop> + <prop oor:name="IsExperimental" oor:type="xs:boolean"> + <value>true</value> + </prop> + </node> </node> <node oor:name="Popups"> <node oor:name=".uno:CharacterMenu" oor:op="replace"> diff --git a/sw/inc/cmdid.h b/sw/inc/cmdid.h index 9a32d2b85d7b..b77f535a6d3d 100644 --- a/sw/inc/cmdid.h +++ b/sw/inc/cmdid.h @@ -213,6 +213,8 @@ class SwUINumRuleItem; #define FN_VIEW_BASELINE_GRID_VISIBLE (FN_VIEW + 74) /* Menu for displaying baseline grid */ +#define FN_SORT_CHAPTERS (FN_VIEW + 75) /* Make chapters alphabetically sorted */ + // Region: Insert #define FN_INSERT_BOOKMARK (FN_INSERT + 2 ) /* Bookmark */ // FN_INSERT + 3 is FN_INSERT_BREAK diff --git a/sw/inc/strings.hrc b/sw/inc/strings.hrc index 0dc4bc9bd2df..0b70e22c6077 100644 --- a/sw/inc/strings.hrc +++ b/sw/inc/strings.hrc @@ -1575,6 +1575,8 @@ #define STR_PAGES NNC_("STR_PAGES", "Page: %1", "Pages: %1-%2") +#define STR_UNDO_SORT_CHAPTERS NC_("STR_UNDO_SORT_CHAPTERS", "Sort chapters") + #endif /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sw/inc/swundo.hxx b/sw/inc/swundo.hxx index e19622a4f783..76af157fd129 100644 --- a/sw/inc/swundo.hxx +++ b/sw/inc/swundo.hxx @@ -185,6 +185,7 @@ enum class SwUndoId CONVERT_FIELD_TO_TEXT = 153, REINSTATE_REDLINE = 154, COPY_HEADER_FOOTER = 155, + SORT_CHAPTERS = 156, }; OUString GetUndoComment(SwUndoId eId); diff --git a/sw/sdi/_viewsh.sdi b/sw/sdi/_viewsh.sdi index 73099317370a..42916cccacbb 100644 --- a/sw/sdi/_viewsh.sdi +++ b/sw/sdi/_viewsh.sdi @@ -1111,4 +1111,9 @@ interface BaseTextEditView StateMethod = StateViewOptions ; ] + FN_SORT_CHAPTERS + [ + ExecMethod = Execute ; + ] + } diff --git a/sw/sdi/swriter.sdi b/sw/sdi/swriter.sdi index 1b2eb90d94fe..a39a202c1cda 100644 --- a/sw/sdi/swriter.sdi +++ b/sw/sdi/swriter.sdi @@ -9069,3 +9069,19 @@ SfxBoolItem BaselineGridVisible FN_VIEW_BASELINE_GRID_VISIBLE ToolBoxConfig = TRUE, GroupId = SfxGroupId::View; ] + +SfxVoidItem SortChapters FN_SORT_CHAPTERS +[ + AutoUpdate = FALSE, + FastCall = FALSE + ReadOnlyDoc = FALSE, + Toggle = FALSE, + Container = FALSE, + RecordAbsolute = FALSE, + RecordPerSet; + + AccelConfig = TRUE, + MenuConfig = TRUE, + ToolBoxConfig = TRUE, + GroupId = SfxGroupId::Edit; +] diff --git a/sw/source/core/undo/undobj.cxx b/sw/source/core/undo/undobj.cxx index 7b83049455fe..c994448a10c6 100644 --- a/sw/source/core/undo/undobj.cxx +++ b/sw/source/core/undo/undobj.cxx @@ -700,6 +700,9 @@ OUString GetUndoComment(SwUndoId eId) case SwUndoId::COPY_HEADER_FOOTER: pId = STR_UNDO_COPY_HEADER_FOOTER; break; + case SwUndoId::SORT_CHAPTERS: + pId = STR_UNDO_SORT_CHAPTERS; + break; } assert(pId); diff --git a/sw/source/uibase/uiview/view2.cxx b/sw/source/uibase/uiview/view2.cxx index 1aa9107700e6..b36157e7663f 100644 --- a/sw/source/uibase/uiview/view2.cxx +++ b/sw/source/uibase/uiview/view2.cxx @@ -164,6 +164,8 @@ #include <svx/dialog/gotodlg.hxx> +#include <set> + const char sStatusDelim[] = " : "; using namespace sfx2; @@ -1707,7 +1709,118 @@ void SwView::Execute(SfxRequest &rReq) } break; } + 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 postition 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(); + } + break; default: OSL_ENSURE(false, "wrong dispatcher"); return;
