officecfg/registry/data/org/openoffice/Office/UI/CalcCommands.xcu |    8 
 sc/inc/globstr.hrc                                                |    1 
 sc/inc/sc.hrc                                                     |    4 
 sc/inc/sortparam.hxx                                              |    9 
 sc/qa/unit/helper/qahelper.cxx                                    |   44 ++
 sc/qa/unit/helper/qahelper.hxx                                    |    4 
 sc/qa/unit/ucalc_sort.cxx                                         |  196 
++++++++++
 sc/sdi/cellsh.sdi                                                 |    1 
 sc/sdi/scalc.sdi                                                  |   17 
 sc/source/core/data/sortparam.cxx                                 |    6 
 sc/source/core/data/table3.cxx                                    |   38 +
 sc/source/ui/docshell/dbdocfun.cxx                                |   12 
 sc/source/ui/undo/undosort.cxx                                    |    2 
 sc/source/ui/view/cellsh2.cxx                                     |   24 +
 sc/uiconfig/scalc/menubar/menubar.xml                             |    1 
 15 files changed, 351 insertions(+), 16 deletions(-)

New commits:
commit a8ae0abbd646443a65dc2e5f44c395fb5a454a29
Author:     Tomaž Vajngerl <[email protected]>
AuthorDate: Mon Feb 23 13:39:24 2026 +0900
Commit:     Tomaž Vajngerl <[email protected]>
CommitDate: Wed Feb 25 02:24:28 2026 +0100

    tdf#158196 Shuffle operation to randomly shuffle a cell range
    
    Useful for statistics where it is needed to shuffle the entries
    or to have a randomly shuffled range of numbers.
    
    Implementation reuses what was already there to sort a cell range
    but uses its own random reordering function.
    
    Change-Id: Ibfceb0825c2b2a3e63b0e6a45963dd043464b77a
    Reviewed-on: https://gerrit.libreoffice.org/c/core/+/22963
    Reviewed-by: Tomaž Vajngerl <[email protected]>
    Tested-by: Jenkins

diff --git a/officecfg/registry/data/org/openoffice/Office/UI/CalcCommands.xcu 
b/officecfg/registry/data/org/openoffice/Office/UI/CalcCommands.xcu
index e7b584b32d3f..3dbc69f5dfe3 100644
--- a/officecfg/registry/data/org/openoffice/Office/UI/CalcCommands.xcu
+++ b/officecfg/registry/data/org/openoffice/Office/UI/CalcCommands.xcu
@@ -2192,6 +2192,14 @@
           <value>1</value>
         </prop>
       </node>
+      <node oor:name=".uno:Shuffle" oor:op="replace">
+        <prop oor:name="Label" oor:type="xs:string">
+          <value xml:lang="en-US">Shuffle</value>
+        </prop>
+        <prop oor:name="Properties" oor:type="xs:int">
+          <value>1</value>
+        </prop>
+      </node>
       <node oor:name=".uno:RenameTable" oor:op="replace">
         <prop oor:name="Label" oor:type="xs:string">
           <value xml:lang="en-US">Rename S~heet...</value>
diff --git a/sc/inc/globstr.hrc b/sc/inc/globstr.hrc
index f386f4220adb..6a24c9224314 100644
--- a/sc/inc/globstr.hrc
+++ b/sc/inc/globstr.hrc
@@ -69,6 +69,7 @@
 #define STR_UNDO_SUBTOTALS                      NC_("STR_UNDO_SUBTOTALS", 
"Subtotals")
 #define STR_UNDO_TABLETOTALS                    NC_("STR_UNDO_TABLETOTALS", 
"Table Total Row")
 #define STR_UNDO_SORT                           NC_("STR_UNDO_SORT", "Sort")
+#define STR_UNDO_SHUFFLE                        NC_("STR_UNDO_SHUFFLE", 
"Shuffle")
 #define STR_UNDO_QUERY                          NC_("STR_UNDO_QUERY", "Filter")
 #define STR_UNDO_DBADDTABLE                     NC_("STR_UNDO_DBTABLE", 
"Create table")
 #define STR_UNDO_DBREMTABLE                     NC_("STR_UNDO_DBTABLE", 
"Delete table")
diff --git a/sc/inc/sc.hrc b/sc/inc/sc.hrc
index 9dc545881550..86a239b531f4 100644
--- a/sc/inc/sc.hrc
+++ b/sc/inc/sc.hrc
@@ -386,8 +386,8 @@ class SvxZoomSliderItem;
 #define SID_OUTLINE_SHOW        (DATA_MENU_START + 26)
 #define SID_OUTLINE_MAKE        TypedWhichId<SfxStringItem>(DATA_MENU_START + 
27)
 #define SID_OUTLINE_REMOVE      TypedWhichId<SfxStringItem>(DATA_MENU_START + 
28)
-
-#define DATA_MENU_END           (DATA_MENU_START + 29)
+#define SID_SHUFFLE             (DATA_MENU_START + 29)
+#define DATA_MENU_END           (DATA_MENU_START + 30)
 
 #define TAB_POPUP_START         (DATA_MENU_END)
 #define FID_TAB_MENU_RENAME     (TAB_POPUP_START)
diff --git a/sc/inc/sortparam.hxx b/sc/inc/sortparam.hxx
index 0de0e3bbdb1b..44a8a50fbdc7 100644
--- a/sc/inc/sortparam.hxx
+++ b/sc/inc/sortparam.hxx
@@ -37,6 +37,12 @@ struct ScQueryParam;
 class SdrObject;
 class ScPostIt;
 
+enum class SortOrderType
+{
+    Ordered,
+    Random
+};
+
 /** Sort by which color */
 enum class ScColorSortMode {
     None,
@@ -156,6 +162,7 @@ struct SC_DLLPUBLIC ScSortParam
     css::lang::Locale aCollatorLocale;
     OUString    aCollatorAlgorithm;
     sal_uInt16  nCompatHeader;
+    SortOrderType meSortOrderType = SortOrderType::Ordered;
 
     ScSortParam();
     ScSortParam( const ScSortParam& r );
@@ -363,6 +370,7 @@ struct ReorderParam
     bool mbHiddenFiltered;
     bool mbUpdateRefs;
     bool mbHasHeaders;
+    bool mbShuffle;
 
     /**
      * Reorder the position indices such that it can be used to undo the
@@ -375,6 +383,7 @@ struct ReorderParam
         , mbHiddenFiltered(false)
         , mbUpdateRefs(false)
         , mbHasHeaders(false)
+        , mbShuffle(false)
     {
     }
 };
diff --git a/sc/qa/unit/helper/qahelper.cxx b/sc/qa/unit/helper/qahelper.cxx
index 73fa003745e7..6e513ba7ff65 100644
--- a/sc/qa/unit/helper/qahelper.cxx
+++ b/sc/qa/unit/helper/qahelper.cxx
@@ -786,6 +786,50 @@ ScRange ScUcalcTestBase::insertRangeData(
     return aRange;
 }
 
+ScRange ScUcalcTestBase::insertRangeData(ScDocument* pDoc, const ScAddress& 
rPos, const std::vector<std::vector<OUString>>& rData)
+{
+    if (rData.empty())
+        return ScRange(ScAddress::INITIALIZE_INVALID);
+
+    ScAddress aPos = rPos;
+
+    SCCOL nColWidth = 1;
+    for (auto const& rRow : rData)
+        nColWidth = std::max<SCCOL>(nColWidth, rRow.size());
+
+    ScRange aRange(aPos);
+    aRange.aEnd.IncCol(nColWidth-1);
+    aRange.aEnd.IncRow(rData.size()-1);
+
+    clearRange(pDoc, aRange);
+
+    for (auto const& rRow : rData)
+    {
+        aPos.SetCol(rPos.Col());
+
+        for (OUString const& rString : rRow)
+        {
+            if (rString.isEmpty())
+            {
+                aPos.IncCol();
+                continue;
+            }
+
+            ScSetStringParam aParam; // Leave default.
+            aParam.meStartListening = sc::NoListening;
+            pDoc->SetString(aPos, rString, &aParam);
+
+            aPos.IncCol();
+        }
+
+        aPos.IncRow();
+    }
+
+    pDoc->StartAllListeners(aRange);
+    printRange(pDoc, aRange, "Range data content");
+    return aRange;
+}
+
 ScUndoCut* ScUcalcTestBase::cutToClip(ScDocShell& rDocSh, const ScRange& 
rRange, ScDocument* pClipDoc, bool bCreateUndo)
 {
     ScDocument* pSrcDoc = &rDocSh.GetDocument();
diff --git a/sc/qa/unit/helper/qahelper.hxx b/sc/qa/unit/helper/qahelper.hxx
index 84a32d1ba9bd..6e3584aa230f 100644
--- a/sc/qa/unit/helper/qahelper.hxx
+++ b/sc/qa/unit/helper/qahelper.hxx
@@ -107,8 +107,8 @@ public:
     virtual void setUp() override;
     virtual void tearDown() override;
 
-    ScRange insertRangeData(ScDocument* pDoc, const ScAddress& rPos,
-                                       const std::vector<std::vector<const 
char*>>& rData);
+    ScRange insertRangeData(ScDocument* pDoc, const ScAddress& rPos, const 
std::vector<std::vector<const char*>>& rData);
+    ScRange insertRangeData(ScDocument* pDoc, const ScAddress& rPos, const 
std::vector<std::vector<OUString>>& rData);
     void copyToClip(ScDocument* pSrcDoc, const ScRange& rRange, ScDocument* 
pClipDoc);
     void pasteFromClip(ScDocument* pDestDoc, const ScRange& rDestRange,
                                         ScDocument* pClipDoc);
diff --git a/sc/qa/unit/ucalc_sort.cxx b/sc/qa/unit/ucalc_sort.cxx
index 5cd3fed5f0db..3d3c94bd1662 100644
--- a/sc/qa/unit/ucalc_sort.cxx
+++ b/sc/qa/unit/ucalc_sort.cxx
@@ -2372,6 +2372,202 @@ CPPUNIT_TEST_FIXTURE(TestSort, 
testSortEmbeddedNumberTypes)
     m_pDoc->DeleteTab(0);
 }
 
+CPPUNIT_TEST_FIXTURE(TestSort, testShuffle)
+{
+    // Check we shuffle the range, the data is still the same, and undo / redo 
work correctly
+
+    m_pDoc->InsertTab(0, u"Test"_ustr);
+
+    const std::vector<std::vector<OUString>> aData = {
+        { u"A"_ustr, u"B"_ustr },
+        { u"0"_ustr, u"1"_ustr },
+        { u"4"_ustr, u"3"_ustr },
+        { u"2"_ustr, u"4"_ustr },
+        { u"9"_ustr, u"8"_ustr },
+        { u"6"_ustr, u"9"_ustr },
+    };
+
+    // Insert data
+    ScRange aDataRange = insertRangeData(m_pDoc, {0, 0, 0}, aData);
+    CPPUNIT_ASSERT_EQUAL(ScAddress(0, 0, 0), aDataRange.aStart);
+    CPPUNIT_ASSERT_EQUAL(ScAddress(1, 5, 0), aDataRange.aEnd);
+
+    // Check Data
+    {
+        size_t i = 0;
+        for (auto const& rRow : aData)
+        {
+            CPPUNIT_ASSERT_EQUAL(rRow[0], m_pDoc->GetString(ScAddress(0, i, 
0)));
+            CPPUNIT_ASSERT_EQUAL(rRow[1], m_pDoc->GetString(ScAddress(1, i, 
0)));
+            i++;
+        }
+    }
+
+    // Define A1:B6 as sheet-local anonymous database range.
+    m_pDoc->SetAnonymousDBData(0, std::unique_ptr<ScDBData>(new 
ScDBData(STR_DB_LOCAL_NONAME, 0, 0, 0, 1, 5)));
+
+    // Set up sort param with for shuffle
+    ScSortParam aSortData;
+    aSortData.nCol1 = 0;
+    aSortData.nCol2 = 1;
+    aSortData.nRow1 = 0;
+    aSortData.nRow2 = 5;
+    aSortData.bHasHeader = true;
+    aSortData.bByRow = true;
+    aSortData.aDataAreaExtras.mbCellFormats = true;
+    aSortData.meSortOrderType = SortOrderType::Random;
+
+    ScDBDocFunc aFunc(*m_xDocShell);
+    bool bSorted = aFunc.Sort(0, aSortData, true, true, true);
+    CPPUNIT_ASSERT(bSorted);
+
+    // First check the header, which should be untouched
+    CPPUNIT_ASSERT_EQUAL(u"A"_ustr, m_pDoc->GetString(ScAddress(0, 0, 0)));
+    CPPUNIT_ASSERT_EQUAL(u"B"_ustr, m_pDoc->GetString(ScAddress(1, 0, 0)));
+
+    // Check Data
+    std::vector<size_t> aIndices;
+    std::vector<std::vector<OUString>> aShuffledData(6);
+    for (SCROW nRow = 0; nRow <= 5; nRow++)
+    {
+        OUString sValueA = m_pDoc->GetString(ScAddress(0, nRow, 0));
+        OUString sValueB = m_pDoc->GetString(ScAddress(1, nRow, 0));
+        aShuffledData[nRow] = { sValueA, sValueB };
+
+        std::optional<size_t> oIndex;
+        size_t i = 0;
+        for (auto const& rRow : aData)
+        {
+            if (rRow[0] == sValueA && rRow[1] == sValueB)
+            {
+                oIndex = i;
+                aIndices.push_back(i);
+                continue;
+            }
+            i++;
+        }
+        CPPUNIT_ASSERT(oIndex);
+    }
+    std::sort(aIndices.begin(), aIndices.end());
+    // Should be all unique values
+    bool bIsUnique = std::unique(aIndices.begin(), aIndices.end()) == 
aIndices.end();
+    std::vector<size_t> aExpected{0, 1, 2, 3, 4, 5};
+
+    CPPUNIT_ASSERT(bIsUnique);
+    CPPUNIT_ASSERT(std::ranges::equal(aIndices, aExpected));
+
+    // Verify undo
+    SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
+    CPPUNIT_ASSERT(pUndoMgr);
+    CPPUNIT_ASSERT_EQUAL(u"Shuffle"_ustr, pUndoMgr->GetUndoActionComment());
+
+    pUndoMgr->Undo();
+
+    // Check Data
+    {
+        size_t i = 0;
+        for (auto const& rRow : aData)
+        {
+            CPPUNIT_ASSERT_EQUAL(rRow[0], m_pDoc->GetString(ScAddress(0, i, 
0)));
+            CPPUNIT_ASSERT_EQUAL(rRow[1], m_pDoc->GetString(ScAddress(1, i, 
0)));
+            i++;
+        }
+    }
+
+    // Redo and verify data is still consistent.
+    pUndoMgr->Redo();
+
+    for (SCROW nRow = 0; nRow <= 5; nRow++)
+    {
+        OUString sValueA = m_pDoc->GetString(ScAddress(0, nRow, 0));
+        OUString sValueB = m_pDoc->GetString(ScAddress(1, nRow, 0));
+
+        CPPUNIT_ASSERT_EQUAL(aShuffledData[nRow][0], sValueA);
+        CPPUNIT_ASSERT_EQUAL(aShuffledData[nRow][1], sValueB);
+    }
+}
+
+CPPUNIT_TEST_FIXTURE(TestSort, testShuffleAndThenSort)
+{
+    m_pDoc->InsertTab(0, u"Test"_ustr);
+
+    ScDBDocFunc aFunc(*m_xDocShell);
+
+    const std::vector<std::vector<OUString>> aData = {
+        { u"A"_ustr, u"B"_ustr },
+        { u"0"_ustr, u"1"_ustr },
+        { u"4"_ustr, u"3"_ustr },
+        { u"2"_ustr, u"4"_ustr },
+        { u"9"_ustr, u"8"_ustr },
+        { u"6"_ustr, u"9"_ustr },
+    };
+
+    // Insert data
+    ScRange aDataRange = insertRangeData(m_pDoc, {0, 0, 0}, aData);
+    CPPUNIT_ASSERT_EQUAL(ScAddress(0, 0, 0), aDataRange.aStart);
+    CPPUNIT_ASSERT_EQUAL(ScAddress(1, 5, 0), aDataRange.aEnd);
+
+    // Define A1:B6 as sheet-local anonymous database range.
+    m_pDoc->SetAnonymousDBData(0, std::unique_ptr<ScDBData>(new 
ScDBData(STR_DB_LOCAL_NONAME, 0, 0, 0, 1, 5)));
+
+    // Set up sort param with for shuffle
+    {
+        ScSortParam aSortData;
+        aSortData.nCol1 = 0;
+        aSortData.nCol2 = 1;
+        aSortData.nRow1 = 0;
+        aSortData.nRow2 = 5;
+        aSortData.bHasHeader = true;
+        aSortData.bByRow = true;
+        aSortData.aDataAreaExtras.mbCellFormats = true;
+        aSortData.meSortOrderType = SortOrderType::Random;
+
+        bool bSorted = aFunc.Sort(0, aSortData, true, true, true);
+        CPPUNIT_ASSERT(bSorted);
+    }
+
+    // Verify a sort after shuffle works correctly
+    {
+        ScSortParam aSortData;
+        aSortData.nCol1 = 0;
+        aSortData.nCol2 = 1;
+        aSortData.nRow1 = 0;
+        aSortData.nRow2 = 5;
+        aSortData.bHasHeader = true;
+        aSortData.bByRow = true;
+        aSortData.aDataAreaExtras.mbCellFormats = true;
+        aSortData.maKeyState[0].bDoSort = true;
+        aSortData.maKeyState[0].nField = 1;
+        aSortData.maKeyState[0].bAscending = true;
+        aSortData.maKeyState[0].aColorSortMode = ScColorSortMode::None;
+
+        bool bSorted = aFunc.Sort(0, aSortData, true, true, true);
+        CPPUNIT_ASSERT(bSorted);
+    }
+
+    // Check Header - should be untouched
+    CPPUNIT_ASSERT_EQUAL(u"A"_ustr, m_pDoc->GetString(ScAddress(0, 0, 0)));
+    CPPUNIT_ASSERT_EQUAL(u"B"_ustr, m_pDoc->GetString(ScAddress(1, 0, 0)));
+
+    // Check Data
+    {
+        size_t i = 0;
+        for (auto const& rRow : aData)
+        {
+            CPPUNIT_ASSERT_EQUAL(rRow[0], m_pDoc->GetString(ScAddress(0, i, 
0)));
+            CPPUNIT_ASSERT_EQUAL(rRow[1], m_pDoc->GetString(ScAddress(1, i, 
0)));
+            i++;
+        }
+    }
+
+    // Verify undo
+    SfxUndoManager* pUndoMgr = m_pDoc->GetUndoManager();
+    CPPUNIT_ASSERT(pUndoMgr);
+    CPPUNIT_ASSERT_EQUAL(u"Sort"_ustr, pUndoMgr->GetUndoActionComment());
+
+    m_pDoc->DeleteTab(0);
+}
+
 CPPUNIT_PLUGIN_IMPLEMENT();
 
 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/sc/sdi/cellsh.sdi b/sc/sdi/cellsh.sdi
index f038689890a3..0791b88617d2 100644
--- a/sc/sdi/cellsh.sdi
+++ b/sc/sdi/cellsh.sdi
@@ -52,6 +52,7 @@ interface CellSelection
     SID_DATA_PROVIDER       [ ExecMethod = ExecuteDB; StateMethod = 
GetDBState; ]
     SID_DATA_PROVIDER_REFRESH [ ExecMethod = ExecuteDB; StateMethod = 
GetDBState; ]
     SID_MANAGE_XML_SOURCE   [ ExecMethod = ExecuteDB; StateMethod = 
GetDBState; ]
+    SID_SHUFFLE             [ ExecMethod = ExecuteDB; StateMethod = 
GetDBState; ]
     SID_SORT                [ ExecMethod = ExecuteDB; StateMethod = 
GetDBState; ]
     SID_DATA_FORM           [ ExecMethod = ExecuteDB; StateMethod = 
GetDBState; ]
     FID_FILTER_OK           [ ExecMethod = ExecuteDB; StateMethod = 
GetDBState; ]
diff --git a/sc/sdi/scalc.sdi b/sc/sdi/scalc.sdi
index 485aadb47c1e..4533e85bb625 100644
--- a/sc/sdi/scalc.sdi
+++ b/sc/sdi/scalc.sdi
@@ -1194,6 +1194,23 @@ SfxVoidItem DataSort SID_SORT
     GroupId = SfxGroupId::Data;
 ]
 
+SfxVoidItem Shuffle SID_SHUFFLE
+()
+[
+    AutoUpdate = FALSE,
+    FastCall = FALSE,
+    ReadOnlyDoc = TRUE,
+    Toggle = FALSE,
+    Container = FALSE,
+    RecordAbsolute = FALSE,
+    RecordPerSet;
+
+    AccelConfig = TRUE,
+    MenuConfig = TRUE,
+    ToolBoxConfig = FALSE,
+    GroupId = SfxGroupId::Data;
+]
+
 
 SfxVoidItem DataForm SID_DATA_FORM
 ()
diff --git a/sc/source/core/data/sortparam.cxx 
b/sc/source/core/data/sortparam.cxx
index 9b6f483192e1..b2a67f297810 100644
--- a/sc/source/core/data/sortparam.cxx
+++ b/sc/source/core/data/sortparam.cxx
@@ -44,7 +44,8 @@ ScSortParam::ScSortParam( const ScSortParam& r ) :
         nDestTab(r.nDestTab),nDestCol(r.nDestCol),nDestRow(r.nDestRow),
         maKeyState( r.maKeyState ),
         aCollatorLocale( r.aCollatorLocale ), aCollatorAlgorithm( 
r.aCollatorAlgorithm ),
-        nCompatHeader( r.nCompatHeader )
+        nCompatHeader( r.nCompatHeader ),
+        meSortOrderType(r.meSortOrderType)
 {
 }
 
@@ -64,6 +65,7 @@ void ScSortParam::Clear()
     bHasHeader=bCaseSens=bUserDef = false;
     eSortNumberBehavior = ScSortNumberBehavior::ALPHA_NUMERIC;
     bByRow = bInplace = true;
+    meSortOrderType = SortOrderType::Ordered;
     aCollatorLocale = css::lang::Locale();
     aCollatorAlgorithm.clear();
 
@@ -93,6 +95,7 @@ ScSortParam& ScSortParam::operator=( const ScSortParam& r )
     aCollatorLocale         = r.aCollatorLocale;
     aCollatorAlgorithm      = r.aCollatorAlgorithm;
     nCompatHeader   = r.nCompatHeader;
+    meSortOrderType = r.meSortOrderType;
 
     return *this;
 }
@@ -138,6 +141,7 @@ bool ScSortParam::operator==( const ScSortParam& rOther ) 
const
         && (aCollatorLocale.Country     == rOther.aCollatorLocale.Country)
         && (aCollatorLocale.Variant     == rOther.aCollatorLocale.Variant)
         && (aCollatorAlgorithm          == rOther.aCollatorAlgorithm)
+        && (meSortOrderType == rOther.meSortOrderType)
         && ( !maKeyState.empty() || !rOther.maKeyState.empty() )
         )
     {
diff --git a/sc/source/core/data/table3.cxx b/sc/source/core/data/table3.cxx
index 1cfeaef2ed62..648670c4c1d9 100644
--- a/sc/source/core/data/table3.cxx
+++ b/sc/source/core/data/table3.cxx
@@ -319,8 +319,18 @@ void initDataRows(
     }
 }
 
+// Shuffle - swap every cell with a random cell
+void lclShuffleArray(ScSortInfoArray* pArray, SCCOLROW nLow, SCCOLROW nHigh)
+{
+    for (SCCOLROW i = nHigh - nLow; i > 0; --i)
+    {
+        int nRandom = comphelper::rng::uniform_int_distribution(0, i);
+        pArray->Swap(nLow + i, nLow + nRandom);
+    }
 }
 
+} // anonymous namespace
+
 std::unique_ptr<ScSortInfoArray> ScTable::CreateSortInfoArray( const 
sc::ReorderParam& rParam )
 {
     std::unique_ptr<ScSortInfoArray> pArray;
@@ -1754,8 +1764,12 @@ void ScTable::Sort(
     const ScSortParam& rSortParam, bool bKeepQuery, bool bUpdateRefs,
     ScProgress* pProgress, sc::ReorderParam* pUndo )
 {
+    const bool bSortOrdered = rSortParam.meSortOrderType == 
SortOrderType::Ordered;
+
     sc::DelayDeletingBroadcasters delayDeletingBroadcasters(GetDoc());
-    InitSortCollator( rSortParam );
+    if (bSortOrdered)
+        InitSortCollator(rSortParam);
+
     bGlobalKeepQuery = bKeepQuery;
 
     if (pUndo)
@@ -1766,6 +1780,7 @@ void ScTable::Sort(
         pUndo->mbHiddenFiltered = bKeepQuery;
         pUndo->mbUpdateRefs = bUpdateRefs;
         pUndo->mbHasHeaders = rSortParam.bHasHeader;
+        pUndo->mbShuffle = !bSortOrdered;
     }
 
     // It is assumed that the data area has already been trimmed as necessary.
@@ -1775,7 +1790,8 @@ void ScTable::Sort(
     {
         const SCROW nLastRow = rSortParam.nRow2;
         const SCROW nRow1 = (rSortParam.bHasHeader ? rSortParam.nRow1 + 1 : 
rSortParam.nRow1);
-        if (nRow1 < nLastRow && !IsSorted(nRow1, nLastRow))
+        if (nRow1 < nLastRow
+            && (!bSortOrdered || !IsSorted(nRow1, nLastRow)))
         {
             if(pProgress)
                 pProgress->SetState( 0, nLastRow-nRow1 );
@@ -1786,7 +1802,11 @@ void ScTable::Sort(
             if ( nLastRow - nRow1 > 255 )
                 DecoladeRow(pArray.get(), nRow1, nLastRow);
 
-            QuickSort(pArray.get(), nRow1, nLastRow);
+            if (bSortOrdered)
+                QuickSort(pArray.get(), nRow1, nLastRow);
+            else
+                lclShuffleArray(pArray.get(), nRow1, nLastRow);
+
             if (pArray->IsUpdateRefs())
                 SortReorderByRowRefUpdate(pArray.get(), aSortParam.nCol1, 
aSortParam.nCol2, pProgress);
             else
@@ -1824,7 +1844,8 @@ void ScTable::Sort(
     {
         const SCCOL nLastCol = rSortParam.nCol2;
         const SCCOL nCol1 = (rSortParam.bHasHeader ? rSortParam.nCol1 + 1 : 
rSortParam.nCol1);
-        if (nCol1 < nLastCol && !IsSorted(nCol1, nLastCol))
+        if (nCol1 < nLastCol
+            && (!bSortOrdered || !IsSorted(nCol1, nLastCol)))
         {
             if(pProgress)
                 pProgress->SetState( 0, nLastCol-nCol1 );
@@ -1832,7 +1853,11 @@ void ScTable::Sort(
             std::unique_ptr<ScSortInfoArray> pArray( CreateSortInfoArray(
                         aSortParam, nCol1, nLastCol, bKeepQuery, bUpdateRefs));
 
-            QuickSort(pArray.get(), nCol1, nLastCol);
+            if (bSortOrdered)
+                QuickSort(pArray.get(), nCol1, nLastCol);
+            else
+                lclShuffleArray(pArray.get(), nCol1, nLastCol);
+
             SortReorderByColumn(pArray.get(), rSortParam.nRow1, 
rSortParam.nRow2,
                     rSortParam.aDataAreaExtras.mbCellFormats, pProgress);
             if (rSortParam.aDataAreaExtras.anyExtrasWanted() && 
!pArray->IsUpdateRefs())
@@ -1848,7 +1873,8 @@ void ScTable::Sort(
             }
         }
     }
-    DestroySortCollator();
+    if (bSortOrdered)
+        DestroySortCollator();
 }
 
 void ScTable::Reorder( const sc::ReorderParam& rParam )
diff --git a/sc/source/ui/docshell/dbdocfun.cxx 
b/sc/source/ui/docshell/dbdocfun.cxx
index 642ef0426f75..90d20da0f41f 100644
--- a/sc/source/ui/docshell/dbdocfun.cxx
+++ b/sc/source/ui/docshell/dbdocfun.cxx
@@ -753,8 +753,10 @@ bool ScDBDocFunc::Sort( SCTAB nTab, const ScSortParam& 
rSortParam,
 
     sc::ReorderParam aUndoParam;
 
-    // don't call ScDocument::Sort with an empty SortParam (may be empty here 
if bCopy is set)
-    if (aLocalParam.GetSortKeyCount() && aLocalParam.maKeyState[0].bDoSort)
+    // don't call ScDocument::Sort with an empty SortParam (may be empty here 
if bCopy is set),
+    // but always call it for random shuffle which doesn't need sort keys
+    if (aLocalParam.meSortOrderType == SortOrderType::Random
+        || (aLocalParam.GetSortKeyCount() && 
aLocalParam.maKeyState[0].bDoSort))
     {
         ScProgress aProgress(&rDocShell, ScResId(STR_PROGRESS_SORTING), 0, 
true);
         if (!bRepeatQuery)
@@ -769,10 +771,12 @@ bool ScDBDocFunc::Sort( SCTAB nTab, const ScSortParam& 
rSortParam,
             std::make_unique<sc::UndoSort>(rDocShell, aUndoParam));
     }
 
-    pDBData->SetSortParam(rSortParam);
+    ScSortParam aSortParamData(rSortParam);
+    aSortParamData.meSortOrderType = SortOrderType::Ordered;
+    pDBData->SetSortParam(aSortParamData);
     // Remember additional settings on anonymous database ranges.
     if (pDBData == rDoc.GetAnonymousDBData( nTab) || 
rDoc.GetDBCollection()->getAnonDBs().has( pDBData))
-        pDBData->UpdateFromSortParam( rSortParam);
+        pDBData->UpdateFromSortParam(aSortParamData);
 
     if (SfxViewShell* pKitSomeViewForThisDoc = 
comphelper::LibreOfficeKit::isActive() ?
                                                
rDocShell.GetBestViewShell(false) : nullptr)
diff --git a/sc/source/ui/undo/undosort.cxx b/sc/source/ui/undo/undosort.cxx
index abeb012ba4b9..15f4eabffbfc 100644
--- a/sc/source/ui/undo/undosort.cxx
+++ b/sc/source/ui/undo/undosort.cxx
@@ -21,7 +21,7 @@ UndoSort::UndoSort( ScDocShell& rDocSh, ReorderParam aParam ) 
:
 
 OUString UndoSort::GetComment() const
 {
-    return ScResId(STR_UNDO_SORT);
+    return ScResId(maParam.mbShuffle ? STR_UNDO_SHUFFLE : STR_UNDO_SORT);
 }
 
 void UndoSort::Undo()
diff --git a/sc/source/ui/view/cellsh2.cxx b/sc/source/ui/view/cellsh2.cxx
index 63b7e53dd10c..6bb34d55b74d 100644
--- a/sc/source/ui/view/cellsh2.cxx
+++ b/sc/source/ui/view/cellsh2.cxx
@@ -54,6 +54,7 @@
 #include <validate.hxx>
 #include <datamapper.hxx>
 #include <datafdlg.hxx>
+#include <undosort.hxx>
 
 #include <scui_def.hxx>
 #include <scabstdlg.hxx>
@@ -429,7 +430,30 @@ void ScCellShell::ExecuteDB( SfxRequest& rReq )
                 }
             }
             break;
+        case SID_SHUFFLE:
+            {
+                ScSortParam aSortParam;
+                ScViewData& rData = GetViewData();
+                pTabViewShell->GetDBData()->GetSortParam(aSortParam);
 
+                if (lcl_GetSortParam(rData, aSortParam))
+                {
+                    pTabViewShell->GetDBData()->GetSortParam(aSortParam);
+
+                    ScDocument& rDoc = rData.GetDocument();
+                    SCTAB nTab = rData.CurrentTabForData();
+                    bool bHasHeader = rDoc.HasColHeader(
+                        aSortParam.nCol1, aSortParam.nRow1,
+                        aSortParam.nCol2, aSortParam.nRow2, nTab);
+                    aSortParam.bHasHeader = bHasHeader;
+                    aSortParam.bByRow = true;
+                    aSortParam.meSortOrderType = SortOrderType::Random;
+
+                    pTabViewShell->Sort(aSortParam);
+                    rReq.Done();
+                }
+            }
+            break;
         case SID_SORT:
             {
                 if (ScDBData* pDBData = pTabViewShell->GetDBData())
diff --git a/sc/uiconfig/scalc/menubar/menubar.xml 
b/sc/uiconfig/scalc/menubar/menubar.xml
index 05da857006d1..eac700187f7e 100644
--- a/sc/uiconfig/scalc/menubar/menubar.xml
+++ b/sc/uiconfig/scalc/menubar/menubar.xml
@@ -595,6 +595,7 @@
       <menu:menuitem menu:id=".uno:DataSort"/>
       <menu:menuitem menu:id=".uno:SortAscending"/>
       <menu:menuitem menu:id=".uno:SortDescending"/>
+      <menu:menuitem menu:id=".uno:Shuffle"/>
       <menu:menuseparator/>
       <menu:menuitem menu:id=".uno:DataFilterAutoFilter"/>
       <menu:menu menu:id=".uno:FilterMenu" menu:style="text">

Reply via email to