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;

Reply via email to