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">
