offapi/com/sun/star/sheet/SortNumberBehavior.idl | 27 ++- sc/inc/sc.hrc | 1 sc/inc/sortparam.hxx | 14 + sc/qa/extras/macros-test.cxx | 37 ++++ sc/qa/extras/testdocuments/tdf161948_NaturalSort_OldWay.ods |binary sc/qa/uitest/sort/naturalSort.py | 16 +- sc/qa/uitest/sort/tdf95192.py | 9 - sc/qa/uitest/sort/tdf99208.py | 5 sc/qa/uitest/sort/tdf99627.py | 8 - sc/qa/uitest/sort/tdf99773.py | 3 sc/qa/unit/subsequent_filters_test5.cxx | 8 - sc/qa/unit/ucalc_sort.cxx | 96 +++++++++++- sc/sdi/scalc.sdi | 2 sc/source/core/data/sortparam.cxx | 14 + sc/source/core/data/table3.cxx | 68 ++++++-- sc/source/filter/xml/XMLExportDatabaseRanges.cxx | 7 sc/source/filter/xml/xmlsorti.cxx | 5 sc/source/ui/dbgui/tpsort.cxx | 40 ++++- sc/source/ui/inc/tpsort.hxx | 2 sc/source/ui/unoobj/datauno.cxx | 12 - sc/source/ui/view/cellsh2.cxx | 19 +- sc/source/ui/view/gridwin.cxx | 4 sc/uiconfig/scalc/ui/sortoptionspage.ui | 66 +++++++- 23 files changed, 388 insertions(+), 75 deletions(-)
New commits: commit 45e8e48301c100e35da4e5da3d643a92ab06ba17 Author: Regina Henschel <[email protected]> AuthorDate: Thu Sep 18 13:44:34 2025 +0200 Commit: Regina Henschel <[email protected]> CommitDate: Sun Oct 5 12:37:49 2025 +0200 tdf#161948 support embedded-number-behavior='integer' The element <table:sort> has the values 'alpha-numeric', 'double' and 'integer' in its attribute embedded-number-behavior in ODF. The member bNaturalSort in ScSortParam covers only 'alpha-numeric' and 'double'. The patch replaces the member bNaturalSort of ScSortParam with a new eSortNumberBehavior. That takes the three values from enum ScSortNumberBehavior. Only the parameter 'SfxBoolItem NaturalSort SID_SORT_NATURALSORT' in SID_SORT is kept so that existing macros that use it still work. Change-Id: I2a346223767a5a4aae79bac5fa08e608b0cea503 Reviewed-on: https://gerrit.libreoffice.org/c/core/+/191159 Reviewed-by: Regina Henschel <[email protected]> Tested-by: Jenkins Reviewed-by: Heiko Tietze <[email protected]> diff --git a/offapi/com/sun/star/sheet/SortNumberBehavior.idl b/offapi/com/sun/star/sheet/SortNumberBehavior.idl index 61c5f0937964..e868e062e86a 100644 --- a/offapi/com/sun/star/sheet/SortNumberBehavior.idl +++ b/offapi/com/sun/star/sheet/SortNumberBehavior.idl @@ -11,10 +11,8 @@ module com { module sun { module star { module sheet { /** Describes how numbers inside text are handled in text comparisons. - The constants correspond to the ODF attribute - table:embedded-number-behavior (19.628, part 3 ODF 1.4). - That has values 'alpha-numeric', 'double' and 'integer'. - Value 'integer' is not yet implemented. + <p>The constants correspond to the ODF attribute + table:embedded-number-behavior (19.628, part 3 ODF 1.4).</p> @since LibreOffice 26.2 */ @@ -22,18 +20,27 @@ published constants SortNumberBehavior { /** Digits inside text are compared alphanumerically. - "K10" < "K2" < "K3", for example. + <p>"K10" < "K2" < "K3", for example.</p> */ const long ALPHA_NUMERIC = 0; - /** Comparison of text uses natural sort. + /** Comparison of text uses natural sort with decimal numbers. - "K2" < "K3" < "K10", for example. The number parts inside the text - may be decimal numbers. Which character is considered a decimal - separator, depends on the language of the text. - Read ODF standard for details. + <p>Example: "K2" < "K3" < "K10".<br/> + The number parts inside the text may be decimal numbers. + Which character is considered a decimal separator, depends on + the locale. Read ODF standard for details.<br/> + Example with dot as decimal separator: "K2.40" < "K2.5"</p> */ const long DOUBLE = 1; + + /** Comparison of text uses natural sort with integer numbers. + + <p>Any decimal separator is treated as ordinary character. + A fractional part is handled as separate number.<br/> + Example: "K2.5" < "K2.40", because 5 < 40.</p> + */ + const long INTEGER = 2; }; }; }; }; }; diff --git a/sc/inc/sc.hrc b/sc/inc/sc.hrc index c79f87ea08b1..2a1181655748 100644 --- a/sc/inc/sc.hrc +++ b/sc/inc/sc.hrc @@ -644,6 +644,7 @@ static_assert(SID_PREVIEW_END < SID_KEYFUNC_START, "calc slots ids trampling inf #define SID_SORT_NATURALSORT TypedWhichId<SfxBoolItem>(SC_PARAM_START+5) #define SID_SORT_INCCOMMENTS TypedWhichId<SfxBoolItem>(SC_PARAM_START+6) #define SID_SORT_INCIMAGES TypedWhichId<SfxBoolItem>(SC_PARAM_START+7) +#define SID_SORT_NUMBERBEHAVIOR TypedWhichId<SfxInt32Item>(SC_PARAM_START+8) // Sidebar ------------------------------------------------------------- diff --git a/sc/inc/sortparam.hxx b/sc/inc/sortparam.hxx index 1dd105b6d9be..3f266984bd47 100644 --- a/sc/inc/sortparam.hxx +++ b/sc/inc/sortparam.hxx @@ -25,6 +25,7 @@ #include "address.hxx" #include <com/sun/star/lang/Locale.hpp> +#include <com/sun/star/sheet/SortNumberBehavior.hpp> #include "scdllapi.h" #include "celltextattr.hxx" #include "cellvalue.hxx" @@ -114,6 +115,17 @@ struct ScDataAreaExtras } }; +/** Specifies how numbers embedded in text are treated in text + comparisons. The values correspond to the ODF attribute + table:embedded-number-behavior (19.628, part 3 ODF 1.4). +*/ +enum class ScSortNumberBehavior : sal_Int32 +{ + ALPHA_NUMERIC = css::sheet::SortNumberBehavior::ALPHA_NUMERIC, // 0 + DOUBLE = css::sheet::SortNumberBehavior::DOUBLE, // 1 + INTEGER = css::sheet::SortNumberBehavior::INTEGER, // 2 +}; + struct SC_DLLPUBLIC ScSortParam { SCCOL nCol1; @@ -126,7 +138,7 @@ struct SC_DLLPUBLIC ScSortParam bool bHasHeader; bool bByRow; bool bCaseSens; - bool bNaturalSort; + ScSortNumberBehavior eSortNumberBehavior; bool bUserDef; bool bInplace; SCTAB nDestTab; diff --git a/sc/qa/extras/macros-test.cxx b/sc/qa/extras/macros-test.cxx index dd5a178a2ec7..50d98027d4dd 100644 --- a/sc/qa/extras/macros-test.cxx +++ b/sc/qa/extras/macros-test.cxx @@ -1004,7 +1004,7 @@ CPPUNIT_TEST_FIXTURE(ScMacrosTest, testTdf161948NaturalSortAPI) // The source has "K3", "K10", "K104", "K23", "K2" in Range A2:A6 and label in A1. // The result goes to range C1:C6. - // Enable natural sorting and sort. Examine cell C2 + // Enable natural sorting "double" and sort. Examine cell C2 executeMacro(u"vnd.sun.star.script:Standard.SortTest.EnableNaturalSort" "?language=Basic&location=document"_ustr); OUString sCellContent = pDoc->GetString(2, 1, 0); @@ -1034,6 +1034,41 @@ CPPUNIT_TEST_FIXTURE(ScMacrosTest, testTdf81003_DateCellToVbaUDF) CPPUNIT_ASSERT_EQUAL(45898.0, pDoc->GetValue(1, 0, 0)); } +CPPUNIT_TEST_FIXTURE(ScMacrosTest, testTdf161948NaturalSortDispatcher) +{ + // Since LibreOffice 26.2 the feature natural sort is available in the API. Prior to that, + // natural sort by macro was only possible by using the dispatcher. + // Here we test with a Basic macro, that the old way still works. + createScDoc("tdf161948_NaturalSort_OldWay.ods"); + ScDocument* pDoc = getScDoc(); + + // The source has "ID", "K3", "K10", "K104", "K23", "K2" in Range A1:A6. + + // The macro enables natural sorting via parameter NaturalSort=true of SID_SORT. + executeMacro(u"vnd.sun.star.script:Standard.SortTest.Dispatcher_NaturalSort" + "?language=Basic&location=document"_ustr); + + // Verify sort result. Sorting via dispatcher is always 'inplace' thus results are in A1:A6. + const std::array<OUString, 6> aExpectedNaturalSort + = { u"ID"_ustr, u"K2"_ustr, u"K3"_ustr, u"K10"_ustr, u"K23"_ustr, u"K104"_ustr }; + for (SCROW nRow = 0; nRow <= 5; nRow++) // ScAddress(col, row, tab) + { + CPPUNIT_ASSERT_EQUAL(aExpectedNaturalSort[nRow], pDoc->GetString(ScAddress(0, nRow, 0))); + } + + // Same test for alpha-numeric sort, that is NaturalSort=false. + executeMacro(u"vnd.sun.star.script:Standard.SortTest.Dispatcher_AlphaNumeric" + "?language=Basic&location=document"_ustr); + + // Verify sort result + const std::array<OUString, 6> aExpectedAlphaNumeric + = { u"ID"_ustr, u"K10"_ustr, u"K104"_ustr, u"K2"_ustr, u"K23"_ustr, u"K3"_ustr }; + for (SCROW nRow = 0; nRow <= 5; nRow++) + { + CPPUNIT_ASSERT_EQUAL(aExpectedAlphaNumeric[nRow], pDoc->GetString(ScAddress(0, nRow, 0))); + } +} + ScMacrosTest::ScMacrosTest() : ScModelTestBase(u"/sc/qa/extras/testdocuments"_ustr) { diff --git a/sc/qa/extras/testdocuments/tdf161948_NaturalSort_OldWay.ods b/sc/qa/extras/testdocuments/tdf161948_NaturalSort_OldWay.ods new file mode 100644 index 000000000000..54698e57fa36 Binary files /dev/null and b/sc/qa/extras/testdocuments/tdf161948_NaturalSort_OldWay.ods differ diff --git a/sc/qa/uitest/sort/naturalSort.py b/sc/qa/uitest/sort/naturalSort.py index 91c2f35c3f71..e26d6371a11c 100644 --- a/sc/qa/uitest/sort/naturalSort.py +++ b/sc/qa/uitest/sort/naturalSort.py @@ -30,9 +30,14 @@ class CalcNaturalSorting(UITestCase): #Open sort dialog by DATA - SORT with self.ui_test.execute_dialog_through_command(".uno:DataSort") as xDialog: xTabs = xDialog.getChild("tabcontrol") - select_pos(xTabs, "0") + select_pos(xTabs, "1") xNatural = xDialog.getChild("naturalsort") - xNatural.executeAction("CLICK", tuple()) + if (get_state_as_dict(xNatural)["Selected"]) == "false": + xNatural.executeAction("CLICK", tuple()) + xNumberBehaviorDouble = xDialog.getChild("doublenaturalsortrb") + if (get_state_as_dict(xNumberBehaviorDouble)["Checked"]) == "false": + xNumberBehaviorDouble.executeAction("CLICK", tuple()) + #Verify self.assertEqual(get_cell_by_position(document, 0, 0, 0).getString(), "MW100SSMOU456.996JIL4") self.assertEqual(get_cell_by_position(document, 0, 0, 1).getString(), "MW101SSMOU456.996JIL4") @@ -62,6 +67,9 @@ class CalcNaturalSorting(UITestCase): xNatural = xDialog.getChild("naturalsort") if (get_state_as_dict(xNatural)["Selected"]) == "false": xNatural.executeAction("CLICK", tuple()) + xNumberBehaviorDouble = xDialog.getChild("doublenaturalsortrb") + if (get_state_as_dict(xNumberBehaviorDouble)["Checked"]) == "false": + xNumberBehaviorDouble.executeAction("CLICK", tuple()) self.assertEqual(get_cell_by_position(document, 0, 3, 0).getString(), "MW-1") self.assertEqual(get_cell_by_position(document, 0, 3, 1).getString(), "MW-2") @@ -88,6 +96,10 @@ class CalcNaturalSorting(UITestCase): xNatural = xDialog.getChild("naturalsort") if (get_state_as_dict(xNatural)["Selected"]) == "false": xNatural.executeAction("CLICK", tuple()) + xNumberBehaviorDouble = xDialog.getChild("doublenaturalsortrb") + if (get_state_as_dict(xNumberBehaviorDouble)["Checked"]) == "false": + xNumberBehaviorDouble.executeAction("CLICK", tuple()) + select_pos(xTabs, "0") xleftright = xDialog.getChild("rbLeftRight") xleftright.executeAction("CLICK", tuple()) diff --git a/sc/qa/uitest/sort/tdf95192.py b/sc/qa/uitest/sort/tdf95192.py index 1c33f56f7bf6..c5fe9f057592 100644 --- a/sc/qa/uitest/sort/tdf95192.py +++ b/sc/qa/uitest/sort/tdf95192.py @@ -7,7 +7,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # from uitest.framework import UITestCase -from uitest.uihelper.common import get_url_for_data_file, select_pos +from uitest.uihelper.common import get_url_for_data_file, select_pos, get_state_as_dict from libreoffice.calc.document import get_cell_by_position from libreoffice.uno.propertyvalue import mkPropertyValues @@ -27,7 +27,12 @@ class tdf95192(UITestCase): xTabs = xDialog.getChild("tabcontrol") select_pos(xTabs, "1") xNatural = xDialog.getChild("naturalsort") - xNatural.executeAction("CLICK", tuple()) + if (get_state_as_dict(xNatural)["Selected"]) == "false": + xNatural.executeAction("CLICK", tuple()) + xNumberBehaviorDouble = xDialog.getChild("doublenaturalsortrb") + if (get_state_as_dict(xNumberBehaviorDouble)["Checked"]) == "false": + xNumberBehaviorDouble.executeAction("CLICK", tuple()) + #Verify self.assertEqual(get_cell_by_position(calc_doc, 0, 0, 0).getString(), "Sal. Capra 1/17") self.assertEqual(get_cell_by_position(calc_doc, 0, 0, 1).getString(), "Sal. Capra 1/20") diff --git a/sc/qa/uitest/sort/tdf99208.py b/sc/qa/uitest/sort/tdf99208.py index e740a9c4a596..9e6870630180 100644 --- a/sc/qa/uitest/sort/tdf99208.py +++ b/sc/qa/uitest/sort/tdf99208.py @@ -29,9 +29,12 @@ class tdf99208(UITestCase): xTabs = xDialog.getChild("tabcontrol") select_pos(xTabs, "1") xNatural = xDialog.getChild("naturalsort") - xFormats = xDialog.getChild("formats") if (get_state_as_dict(xNatural)["Selected"]) == "false": xNatural.executeAction("CLICK", tuple()) + xNumberBehaviorDouble = xDialog.getChild("doublenaturalsortrb") + if (get_state_as_dict(xNumberBehaviorDouble)["Checked"]) == "false": + xNumberBehaviorDouble.executeAction("CLICK", tuple()) + xFormats = xDialog.getChild("formats") if (get_state_as_dict(xFormats)["Selected"]) == "false": xFormats.executeAction("CLICK", tuple()) select_pos(xTabs, "0") diff --git a/sc/qa/uitest/sort/tdf99627.py b/sc/qa/uitest/sort/tdf99627.py index 281f4340de46..25dc3b75268d 100644 --- a/sc/qa/uitest/sort/tdf99627.py +++ b/sc/qa/uitest/sort/tdf99627.py @@ -7,7 +7,7 @@ # file, You can obtain one at http://mozilla.org/MPL/2.0/. # from uitest.framework import UITestCase -from uitest.uihelper.common import get_url_for_data_file, select_pos +from uitest.uihelper.common import get_url_for_data_file, select_pos, get_state_as_dict from libreoffice.calc.document import get_cell_by_position from libreoffice.uno.propertyvalue import mkPropertyValues @@ -27,8 +27,12 @@ class tdf99627(UITestCase): xTabs = xDialog.getChild("tabcontrol") select_pos(xTabs, "1") xNatural = xDialog.getChild("naturalsort") + if (get_state_as_dict(xNatural)["Selected"]) == "false": + xNatural.executeAction("CLICK", tuple()) + xNumberBehaviorDouble = xDialog.getChild("doublenaturalsortrb") + if (get_state_as_dict(xNumberBehaviorDouble)["Checked"]) == "false": + xNumberBehaviorDouble.executeAction("CLICK", tuple()) xdown = xDialog.getChild("down") - xNatural.executeAction("CLICK", tuple()) select_pos(xTabs, "0") xdown.executeAction("CLICK", tuple()) #Verify diff --git a/sc/qa/uitest/sort/tdf99773.py b/sc/qa/uitest/sort/tdf99773.py index 49775899fb02..3367fa132f66 100644 --- a/sc/qa/uitest/sort/tdf99773.py +++ b/sc/qa/uitest/sort/tdf99773.py @@ -36,6 +36,9 @@ class tdf99773(UITestCase): xNatural = xDialog.getChild("naturalsort") if (get_state_as_dict(xNatural)["Selected"]) == "false": xNatural.executeAction("CLICK", tuple()) + xNumberBehaviorDouble = xDialog.getChild("doublenaturalsortrb") + if (get_state_as_dict(xNumberBehaviorDouble)["Checked"]) == "false": + xNumberBehaviorDouble.executeAction("CLICK", tuple()) #Verify self.assertEqual(get_cell_by_position(document, 0, 0, 0).getString(), "A 2") self.assertEqual(get_cell_by_position(document, 0, 0, 1).getString(), "A 5") diff --git a/sc/qa/unit/subsequent_filters_test5.cxx b/sc/qa/unit/subsequent_filters_test5.cxx index 7bcb4e9bc32a..8f16c130fb7b 100644 --- a/sc/qa/unit/subsequent_filters_test5.cxx +++ b/sc/qa/unit/subsequent_filters_test5.cxx @@ -224,7 +224,7 @@ CPPUNIT_TEST_FIXTURE(ScFiltersTest5, testTdf161948_NaturalSortSaveLoad) ScDBData* pDBData = pDoc->GetDBAtArea(0, 0, 0, 0, 5); // tab, col1, row1, col2, row2 ScSortParam aSortParam; // that is a struct pDBData->GetSortParam(aSortParam); - aSortParam.bNaturalSort = true; + aSortParam.eSortNumberBehavior = ScSortNumberBehavior::DOUBLE; // The output range and the ScDBData are only updated, when you actual sort using the new // parameters. @@ -248,10 +248,10 @@ CPPUNIT_TEST_FIXTURE(ScFiltersTest5, testTdf161948_NaturalSortSaveLoad) pDoc = getScDoc(); pDBData = pDoc->GetDBAtArea(0, 0, 0, 0, 5); pDBData->GetSortParam(aSortParam); - CPPUNIT_ASSERT(aSortParam.bNaturalSort); + CPPUNIT_ASSERT_EQUAL(ScSortNumberBehavior::DOUBLE, aSortParam.eSortNumberBehavior); // disable natural sorted - aSortParam.bNaturalSort = false; + aSortParam.eSortNumberBehavior = ScSortNumberBehavior::ALPHA_NUMERIC; pDocSh = getScDocShell(); ScDBDocFunc aFunc2(*pDocSh); bSorted = aFunc2.Sort(0, aSortParam, true, true, true); @@ -273,7 +273,7 @@ CPPUNIT_TEST_FIXTURE(ScFiltersTest5, testTdf161948_NaturalSortSaveLoad) pDoc = getScDoc(); pDBData = pDoc->GetDBAtArea(0, 0, 0, 0, 5); pDBData->GetSortParam(aSortParam); - CPPUNIT_ASSERT(!aSortParam.bNaturalSort); + CPPUNIT_ASSERT_EQUAL(ScSortNumberBehavior::ALPHA_NUMERIC, aSortParam.eSortNumberBehavior); } CPPUNIT_TEST_FIXTURE(ScFiltersTest5, testTdf168589) diff --git a/sc/qa/unit/ucalc_sort.cxx b/sc/qa/unit/ucalc_sort.cxx index 86e44d9f9699..07cd73792d64 100644 --- a/sc/qa/unit/ucalc_sort.cxx +++ b/sc/qa/unit/ucalc_sort.cxx @@ -2254,7 +2254,7 @@ CPPUNIT_TEST_FIXTURE(TestSort, testLanguageDependentNaturalSort) aSortParam.nRow1 = 0; aSortParam.nRow2 = 6; aSortParam.bHasHeader = true; - aSortParam.bNaturalSort = true; // needs to be adapted when mode 'integer' is implemented + aSortParam.eSortNumberBehavior = ScSortNumberBehavior::DOUBLE; aSortParam.bInplace = false; aSortParam.nDestTab = 0; aSortParam.nDestCol = 2; @@ -2283,6 +2283,100 @@ CPPUNIT_TEST_FIXTURE(TestSort, testLanguageDependentNaturalSort) m_pDoc->DeleteTab(0); } +CPPUNIT_TEST_FIXTURE(TestSort, testSortEmbeddedNumberTypes) +{ + // LibreOffice 26.2 introduces a new type INTEGER for sorting with numbers embedded in text. It + // is a natural sort in principle. But decimal separators are treated as normal characters. Thus + // even it would be a valid decimal number for the current local, integer part and fractional + // part are treated as separate integer values. + // The previous "natural sort" is type DOUBLE now. The previous non natural sort is type + // ALPHA_NUMERIC now. + + // Force the system locale to "en-US" for to have a dot as well defined decimal separator + SvtSysLocaleOptions aOptions; + OUString sLocaleConfigString = aOptions.GetLanguageTag().getBcp47(); + aOptions.SetLocaleConfigString(u"en-US"_ustr); + aOptions.Commit(); + comphelper::ScopeGuard g([&aOptions, &sLocaleConfigString] { + aOptions.SetLocaleConfigString(sLocaleConfigString); + aOptions.Commit(); + }); + + // Generate test data + m_pDoc->InsertTab(0, u"NaturalSortTest"_ustr); + m_pDoc->SetString(ScAddress(0,0,0),u"Item"_ustr); // ScAddress(col, row, tab) + m_pDoc->SetString(ScAddress(0,1,0),u"K2.5"_ustr); + m_pDoc->SetString(ScAddress(0,2,0),u"K2.501"_ustr); + m_pDoc->SetString(ScAddress(0,3,0),u"K10"_ustr); + m_pDoc->SetString(ScAddress(0,4,0),u"K1.104"_ustr); + m_pDoc->SetString(ScAddress(0,5,0),u"K1.2"_ustr); + m_pDoc->SetString(ScAddress(0,6,0),u"K2.40"_ustr); + m_pDoc->SetAnonymousDBData( + 0, std::unique_ptr<ScDBData>(new ScDBData(STR_DB_LOCAL_NONAME, 0, 0, 0, 0, 6))); + + // Create sort parameters + ScSortParam aSortParam; + aSortParam.nCol1 = 0; + aSortParam.nCol2 = 0; + aSortParam.nRow1 = 0; + aSortParam.nRow2 = 6; + aSortParam.bHasHeader = true; + aSortParam.eSortNumberBehavior = ScSortNumberBehavior::INTEGER; + aSortParam.bInplace = false; + aSortParam.nDestTab = 0; + aSortParam.nDestCol = 2; + aSortParam.nDestRow = 0; + // no aSortParam.aCollatorLocale. If no local is set, the global local is used, here en_US + aSortParam.maKeyState[0].bDoSort = true; + aSortParam.maKeyState[0].nField = 0; + aSortParam.maKeyState[0].bAscending = true; + aSortParam.maKeyState[0].aColorSortMode = ScColorSortMode::None; + + // Actually sort + ScDBDocFunc aFunc(*m_xDocShell); + bool bSorted = aFunc.Sort(0, aSortParam, true, true, true); + CPPUNIT_ASSERT(bSorted); + + // Verify sort result: Item | K1.2 | K1.104 | K2.5 | K2.40 | K2.501 | K10 + const std::array<OUString, 7> aExpectedInt + = { u"Item"_ustr, u"K1.2"_ustr, u"K1.104"_ustr, u"K2.5"_ustr, + u"K2.40"_ustr, u"K2.501"_ustr, u"K10"_ustr }; + for (SCROW nRow = 0; nRow <= 6; nRow++) + { + CPPUNIT_ASSERT_EQUAL(aExpectedInt[nRow], m_pDoc->GetString(ScAddress(2, nRow, 0))); + } + + // Make sure that type ScSortNumberBehavior::DOUBLE works as well + // It sorts according the values of the decimal numbers. + aSortParam.eSortNumberBehavior = ScSortNumberBehavior::DOUBLE; + bSorted = aFunc.Sort(0, aSortParam, true, true, true); + CPPUNIT_ASSERT(bSorted); + + const std::array<OUString, 7> aExpectedDbl + = { u"Item"_ustr, u"K1.104"_ustr, u"K1.2"_ustr, u"K2.40"_ustr, + u"K2.5"_ustr, u"K2.501"_ustr, u"K10"_ustr }; + for (SCROW nRow = 0; nRow <= 6; nRow++) + { + CPPUNIT_ASSERT_EQUAL(aExpectedDbl[nRow], m_pDoc->GetString(ScAddress(2, nRow, 0))); + } + + // And same for type ScSortNumberBehavior::ALPHA_NUMERIC + // It treats digits and decimal separator of the numbers as normal characters. + aSortParam.eSortNumberBehavior = ScSortNumberBehavior::ALPHA_NUMERIC; + bSorted = aFunc.Sort(0, aSortParam, true, true, true); + CPPUNIT_ASSERT(bSorted); + + const std::array<OUString, 7> aExpectedAlpha + = { u"Item"_ustr, u"K1.104"_ustr, u"K1.2"_ustr, u"K10"_ustr, + u"K2.40"_ustr, u"K2.5"_ustr, u"K2.501"_ustr }; + for (SCROW nRow = 0; nRow <= 6; nRow++) + { + CPPUNIT_ASSERT_EQUAL(aExpectedAlpha[nRow], m_pDoc->GetString(ScAddress(2, nRow, 0))); + } + + m_pDoc->DeleteTab(0); +} + CPPUNIT_PLUGIN_IMPLEMENT(); /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ diff --git a/sc/sdi/scalc.sdi b/sc/sdi/scalc.sdi index 740ab2693f71..98f4c02599ed 100644 --- a/sc/sdi/scalc.sdi +++ b/sc/sdi/scalc.sdi @@ -1178,7 +1178,7 @@ SfxVoidItem DataSelect SID_DATA_SELECT SfxVoidItem DataSort SID_SORT -(SfxBoolItem ByRows SID_SORT_BYROW,SfxBoolItem HasHeader SID_SORT_HASHEADER,SfxBoolItem CaseSensitive SID_SORT_CASESENS,SfxBoolItem NaturalSort SID_SORT_NATURALSORT,SfxBoolItem IncludeAttribs SID_SORT_ATTRIBS,SfxUInt16Item UserDefIndex SID_SORT_USERDEF,SfxInt32Item Col1 FN_PARAM_1,SfxBoolItem Ascending1 FN_PARAM_2,SfxInt32Item Col2 FN_PARAM_3,SfxBoolItem Ascending2 FN_PARAM_4,SfxInt32Item Col3 FN_PARAM_5,SfxBoolItem Ascending3 FN_PARAM_6,SfxBoolItem IncludeComments SID_SORT_INCCOMMENTS, SfxBoolItem IncludeImages SID_SORT_INCIMAGES) +(SfxBoolItem ByRows SID_SORT_BYROW,SfxBoolItem HasHeader SID_SORT_HASHEADER,SfxBoolItem CaseSensitive SID_SORT_CASESENS,SfxBoolItem NaturalSort SID_SORT_NATURALSORT,SfxBoolItem IncludeAttribs SID_SORT_ATTRIBS,SfxUInt16Item UserDefIndex SID_SORT_USERDEF,SfxInt32Item Col1 FN_PARAM_1,SfxBoolItem Ascending1 FN_PARAM_2,SfxInt32Item Col2 FN_PARAM_3,SfxBoolItem Ascending2 FN_PARAM_4,SfxInt32Item Col3 FN_PARAM_5,SfxBoolItem Ascending3 FN_PARAM_6,SfxBoolItem IncludeComments SID_SORT_INCCOMMENTS, SfxBoolItem IncludeImages SID_SORT_INCIMAGES, SfxInt32Item NumberBehavior SID_SORT_NUMBERBEHAVIOR) [ AutoUpdate = FALSE, FastCall = FALSE, diff --git a/sc/source/core/data/sortparam.cxx b/sc/source/core/data/sortparam.cxx index 868cf38a8df8..9e583bbeb5d2 100644 --- a/sc/source/core/data/sortparam.cxx +++ b/sc/source/core/data/sortparam.cxx @@ -38,7 +38,7 @@ ScSortParam::ScSortParam( const ScSortParam& r ) : aDataAreaExtras(r.aDataAreaExtras), nUserIndex(r.nUserIndex), bHasHeader(r.bHasHeader),bByRow(r.bByRow),bCaseSens(r.bCaseSens), - bNaturalSort(r.bNaturalSort), + eSortNumberBehavior(r.eSortNumberBehavior), bUserDef(r.bUserDef), bInplace(r.bInplace), nDestTab(r.nDestTab),nDestCol(r.nDestCol),nDestRow(r.nDestRow), @@ -63,7 +63,8 @@ void ScSortParam::Clear() nCompatHeader = 2; nDestTab = 0; nUserIndex = 0; - bHasHeader=bCaseSens=bUserDef=bNaturalSort = false; + bHasHeader=bCaseSens=bUserDef = false; + eSortNumberBehavior = ScSortNumberBehavior::ALPHA_NUMERIC; bByRow = bInplace = true; aCollatorLocale = css::lang::Locale(); aCollatorAlgorithm.clear(); @@ -89,7 +90,7 @@ ScSortParam& ScSortParam::operator=( const ScSortParam& r ) bHasHeader = r.bHasHeader; bByRow = r.bByRow; bCaseSens = r.bCaseSens; - bNaturalSort = r.bNaturalSort; + eSortNumberBehavior = r.eSortNumberBehavior; bUserDef = r.bUserDef; bInplace = r.bInplace; nDestTab = r.nDestTab; @@ -133,7 +134,7 @@ bool ScSortParam::operator==( const ScSortParam& rOther ) const && (bHasHeader == rOther.bHasHeader) && (bByRow == rOther.bByRow) && (bCaseSens == rOther.bCaseSens) - && (bNaturalSort == rOther.bNaturalSort) + && (eSortNumberBehavior == rOther.eSortNumberBehavior) && (bUserDef == rOther.bUserDef) && (nUserIndex == rOther.nUserIndex) && (bInplace == rOther.bInplace) @@ -163,7 +164,8 @@ ScSortParam::ScSortParam( const ScSubTotalParam& rSub, const ScSortParam& rOld ) nSourceTab(0), aDataAreaExtras(rOld.aDataAreaExtras), nUserIndex(rSub.nUserIndex), - bHasHeader(true),bByRow(true),bCaseSens(rSub.bCaseSens),bNaturalSort(rOld.bNaturalSort), + bHasHeader(true),bByRow(true),bCaseSens(rSub.bCaseSens), + eSortNumberBehavior(rOld.eSortNumberBehavior), bUserDef(rSub.bUserDef), bInplace(true), nDestTab(0),nDestCol(0),nDestRow(0), @@ -212,7 +214,7 @@ ScSortParam::ScSortParam( const ScQueryParam& rParam, SCCOL nCol ) : nSourceTab(rParam.nTab), nUserIndex(0), bHasHeader(rParam.bHasHeader),bByRow(true),bCaseSens(rParam.bCaseSens), - bNaturalSort(false), + eSortNumberBehavior(ScSortNumberBehavior::ALPHA_NUMERIC), //TODO: what about Locale and Algorithm? bUserDef(false), bInplace(true), diff --git a/sc/source/core/data/table3.cxx b/sc/source/core/data/table3.cxx index a8eb314f935b..a2e09a087c1c 100644 --- a/sc/source/core/data/table3.cxx +++ b/sc/source/core/data/table3.cxx @@ -100,11 +100,15 @@ using namespace ::com::sun::star::i18n; Number converted from the middle number string If no number was found, fNum is unchanged. + @param bInteger + If value is true, any decimal separator is treated as ordinary character. Use value true + in case of ScSortNumberBehavior::INTEGER. + @return Returns TRUE if a numeral element is found in a given string, or FALSE if no numeral element is found. */ static bool SplitString(const OUString &sWhole, const LanguageTag& rLanguageTag, OUString &sPrefix, - OUString &sSuffix, double &fNum) + OUString &sSuffix, double &fNum, const bool &bInteger = false) { // Get prefix element, search for any digit and stop. sal_Int32 nPos = 0; @@ -134,6 +138,23 @@ static bool SplitString(const OUString &sWhole, const LanguageTag& rLanguageTag, return false; } + if (bInteger) + { + // ScSortNumberBehavior::INTEGER treats decimal separators as ordinary character. If we have + // a separator in the string number part, we need to recalculate value and start of sSuffix. + const OUString sNumber = sWhole.copy(nPos, aPRNum.EndPos - nPos); + if (sNumber.indexOf(sUser.toChar(), 0) >=0) + { + ParseResult aPRNumInt = aCharClass.parsePredefinedToken( + KParseType::ANY_NUMBER, sNumber.getToken(0, sUser.toChar()), 0, + KParseTokens::ANY_NUMBER, u""_ustr, KParseTokens::ANY_NUMBER, sUser); + sPrefix = sWhole.copy(0, nPos); + fNum = aPRNumInt.Value; + sSuffix = sWhole.copy(nPos + aPRNumInt.EndPos); + return true; + } + } + sPrefix = sWhole.copy( 0, nPos ); fNum = aPRNum.Value; sSuffix = sWhole.copy( aPRNum.EndPos ); @@ -165,20 +186,24 @@ static bool SplitString(const OUString &sWhole, const LanguageTag& rLanguageTag, @param pCW Pointer to collator wrapper for normal string comparison + @param bInteger + Use value true in case of ScSortNumberBehavior::INTEGER. + @return Returns 1 if sInput1 is greater, 0 if sInput1 == sInput2, and -1 if sInput2 is greater. */ static short Compare(const OUString &sInput1, const OUString &sInput2, const LanguageTag& rLanguageTag, const bool bCaseSens, - const ScUserListData* pData, const CollatorWrapper *pCW) + const ScUserListData* pData, const CollatorWrapper *pCW, + const bool& bInteger = false) { OUString sStr1( sInput1 ), sStr2( sInput2 ), sPre1, sSuf1, sPre2, sSuf2; do { double nNum1, nNum2; - bool bNumFound1 = SplitString( sStr1, rLanguageTag, sPre1, sSuf1, nNum1 ); - bool bNumFound2 = SplitString( sStr2, rLanguageTag, sPre2, sSuf2, nNum2 ); + bool bNumFound1 = SplitString( sStr1, rLanguageTag, sPre1, sSuf1, nNum1, bInteger); + bool bNumFound2 = SplitString( sStr2, rLanguageTag, sPre2, sSuf2, nNum2, bInteger); short nPreRes; // Prefix comparison result if ( pData ) @@ -226,7 +251,7 @@ static short Compare(const OUString &sInput1, const OUString &sInput2, return 0; } -} +} // namespace naturalsort // Assume that we can handle 512MB, which with a ~100 bytes // ScSortInfoArray::Cell element for 500MB are about 5 million cells plus @@ -1502,34 +1527,45 @@ short ScTable::CompareCell( aStr2 = GetString(nCell2Col, nCell2Row); bool bUserDef = aSortParam.bUserDef; // custom sort order - bool bNaturalSort = aSortParam.bNaturalSort; // natural sort bool bCaseSens = aSortParam.bCaseSens; // case sensitivity LanguageTag aSortLanguageTag(aSortParam.aCollatorLocale); ScUserList& rList = ScGlobal::GetUserList(); if (bUserDef && rList.size() > aSortParam.nUserIndex) { + // ToDo: ScUserListData uses internally always ScGlobal. A comparison using the + // sort locale would require an extended version of ScUserListData. So for now in + // case of bUserDef the sort locale is not evaluated. const ScUserListData& rData = rList[aSortParam.nUserIndex]; - - if ( bNaturalSort ) - nRes = naturalsort::Compare(aStr1, aStr2, aSortLanguageTag, bCaseSens, - &rData, pSortCollator); - else + if (aSortParam.eSortNumberBehavior == ScSortNumberBehavior::ALPHA_NUMERIC) { if ( bCaseSens ) nRes = sal::static_int_cast<short>( rData.Compare(aStr1, aStr2) ); else nRes = sal::static_int_cast<short>( rData.ICompare(aStr1, aStr2) ); } - + else if (aSortParam.eSortNumberBehavior == ScSortNumberBehavior::DOUBLE) + nRes = naturalsort::Compare(aStr1, aStr2, aSortLanguageTag, bCaseSens, + &rData, pSortCollator); + else // ScSortNumberBehavior::INTEGER + { + nRes = naturalsort::Compare(aStr1, aStr2, aSortLanguageTag, bCaseSens, + &rData, pSortCollator, true /*bInteger*/); + } } if (!bUserDef) { - if ( bNaturalSort ) + if (aSortParam.eSortNumberBehavior == ScSortNumberBehavior::ALPHA_NUMERIC) + nRes = sal::static_int_cast<short>( + pSortCollator->compareString(aStr1, aStr2)); + else if (aSortParam.eSortNumberBehavior == ScSortNumberBehavior::DOUBLE) nRes = naturalsort::Compare(aStr1, aStr2, aSortLanguageTag, bCaseSens, - nullptr, pSortCollator ); - else - nRes = static_cast<short>( pSortCollator->compareString( aStr1, aStr2 ) ); + nullptr, pSortCollator); + else // ScSortNumberBehavior::INTEGER + { + nRes = naturalsort::Compare(aStr1, aStr2, aSortLanguageTag, bCaseSens, + nullptr, pSortCollator, true /*bInteger*/); + } } } else if ( bStr1 ) // String <-> Number or Error diff --git a/sc/source/filter/xml/XMLExportDatabaseRanges.cxx b/sc/source/filter/xml/XMLExportDatabaseRanges.cxx index f030fd81ac20..b93d54be867e 100644 --- a/sc/source/filter/xml/XMLExportDatabaseRanges.cxx +++ b/sc/source/filter/xml/XMLExportDatabaseRanges.cxx @@ -85,10 +85,13 @@ void writeSort(ScXMLExport& mrExport, const ScSortParam& aParam, const ScRange& if (aParam.bCaseSens) mrExport.AddAttribute(XML_NAMESPACE_TABLE, XML_CASE_SENSITIVE, XML_TRUE); - if (aParam.bNaturalSort) + if (aParam.eSortNumberBehavior != ScSortNumberBehavior::ALPHA_NUMERIC + && mrExport.getSaneDefaultVersion() >= SvtSaveOptions::ODFSVER_012) { - if (mrExport.getSaneDefaultVersion() >= SvtSaveOptions::ODFSVER_012) + if (aParam.eSortNumberBehavior == ScSortNumberBehavior::DOUBLE) mrExport.AddAttribute(XML_NAMESPACE_TABLE, XML_EMBEDDED_NUMBER_BEHAVIOR, XML_DOUBLE); + else + mrExport.AddAttribute(XML_NAMESPACE_TABLE, XML_EMBEDDED_NUMBER_BEHAVIOR, XML_INTEGER); } mrExport.AddLanguageTagAttributes( XML_NAMESPACE_TABLE, XML_NAMESPACE_TABLE, aParam.aCollatorLocale, false); diff --git a/sc/source/filter/xml/xmlsorti.cxx b/sc/source/filter/xml/xmlsorti.cxx index 5122911e1839..230e46ab7e6c 100644 --- a/sc/source/filter/xml/xmlsorti.cxx +++ b/sc/source/filter/xml/xmlsorti.cxx @@ -158,9 +158,10 @@ void SAL_CALL ScXMLSortContext::endFastElement( sal_Int32 /*nElement*/ ) pSortDescriptor[6].Name = SC_UNONAME_SORTFLD; pSortDescriptor[6].Value <<= aSortFields; pSortDescriptor[7].Name = SC_UNONAME_NUMBERBEHAVIOR; - // value 'integer' is not yet implemented. Map it to 'double'. - if (msEmbeddedNumberBehavior.equals(u"double"_ustr) || msEmbeddedNumberBehavior.equals(u"integer"_ustr)) + if (msEmbeddedNumberBehavior.equals(u"double"_ustr)) pSortDescriptor[7].Value <<= sheet::SortNumberBehavior::DOUBLE; + else if (msEmbeddedNumberBehavior.equals(u"integer"_ustr)) + pSortDescriptor[7].Value <<= sheet::SortNumberBehavior::INTEGER; else pSortDescriptor[7].Value <<= sheet::SortNumberBehavior::ALPHA_NUMERIC; if (!maLanguageTagODF.isEmpty()) diff --git a/sc/source/ui/dbgui/tpsort.cxx b/sc/source/ui/dbgui/tpsort.cxx index f19c821ac76e..0ac10173d1ff 100644 --- a/sc/source/ui/dbgui/tpsort.cxx +++ b/sc/source/ui/dbgui/tpsort.cxx @@ -513,6 +513,8 @@ ScTabPageSortOptions::ScTabPageSortOptions(weld::Container* pPage, weld::DialogC , m_xLbAlgorithm(m_xBuilder->weld_combo_box(u"algorithmlb"_ustr)) , m_xBtnIncComments(m_xBuilder->weld_check_button(u"includenotes"_ustr)) , m_xBtnIncImages(m_xBuilder->weld_check_button(u"includeimages"_ustr)) + , m_xRBDoubleNaturalSort(m_xBuilder->weld_radio_button(u"doublenaturalsortrb"_ustr)) + , m_xRBIntegerNaturalSort(m_xBuilder->weld_radio_button(u"integernaturalsortrb"_ustr)) { m_xLbSortUser->set_size_request(m_xLbSortUser->get_approximate_digit_width() * 50, -1); m_xLbSortUser->set_accessible_description(ScResId(STR_A11Y_DESC_SORTUSER)); @@ -533,6 +535,7 @@ void ScTabPageSortOptions::Init() m_xLbOutPos->connect_changed( LINK( this, ScTabPageSortOptions, SelOutPosHdl ) ); m_xBtnCopyResult->connect_toggled( LINK( this, ScTabPageSortOptions, EnableHdl ) ); m_xBtnSortUser->connect_toggled( LINK( this, ScTabPageSortOptions, EnableHdl ) ); + m_xBtnNaturalSort->connect_toggled( LINK( this, ScTabPageSortOptions, EnableHdl ) ); m_xLbLanguage->connect_changed( LINK( this, ScTabPageSortOptions, FillAlgorHdl ) ); pViewData = rSortItem.GetViewData(); @@ -594,7 +597,6 @@ void ScTabPageSortOptions::Reset( const SfxItemSet* /* rArgSet */ ) m_xBtnCase->set_active( aSortData.bCaseSens ); m_xBtnFormats->set_active( aSortData.aDataAreaExtras.mbCellFormats ); - m_xBtnNaturalSort->set_active( aSortData.bNaturalSort ); m_xBtnIncComments->set_active( aSortData.aDataAreaExtras.mbCellNotes ); m_xBtnIncImages->set_active( aSortData.aDataAreaExtras.mbCellDrawObjects ); @@ -632,6 +634,30 @@ void ScTabPageSortOptions::Reset( const SfxItemSet* /* rArgSet */ ) m_xEdOutPos->set_sensitive(false); m_xEdOutPos->set_text( OUString() ); } + + m_xBtnNaturalSort->set_sensitive(true); + if (aSortData.eSortNumberBehavior == ScSortNumberBehavior::ALPHA_NUMERIC) + { + m_xBtnNaturalSort->set_active(false); + m_xRBDoubleNaturalSort->set_sensitive(false); + m_xRBIntegerNaturalSort->set_sensitive(false); + } + else + { + m_xBtnNaturalSort->set_active(true); + m_xRBDoubleNaturalSort->set_sensitive(true); + m_xRBIntegerNaturalSort->set_sensitive(true); + if (aSortData.eSortNumberBehavior == ScSortNumberBehavior::DOUBLE) + { + m_xRBDoubleNaturalSort->set_active(true); + m_xRBIntegerNaturalSort->set_active(false); + } + else + { + m_xRBDoubleNaturalSort->set_active(false); + m_xRBIntegerNaturalSort->set_active(true); + } + } } bool ScTabPageSortOptions::FillItemSet( SfxItemSet* rArgSet ) @@ -646,7 +672,6 @@ bool ScTabPageSortOptions::FillItemSet( SfxItemSet* rArgSet ) aNewSortData = pSortItem->GetSortData(); } aNewSortData.bCaseSens = m_xBtnCase->get_active(); - aNewSortData.bNaturalSort = m_xBtnNaturalSort->get_active(); aNewSortData.aDataAreaExtras.mbCellNotes = m_xBtnIncComments->get_active(); aNewSortData.aDataAreaExtras.mbCellDrawObjects = m_xBtnIncImages->get_active(); aNewSortData.aDataAreaExtras.mbCellFormats = m_xBtnFormats->get_active(); @@ -675,6 +700,12 @@ bool ScTabPageSortOptions::FillItemSet( SfxItemSet* rArgSet ) } aNewSortData.aCollatorAlgorithm = sAlg; + if (m_xBtnNaturalSort->get_active()) + aNewSortData.eSortNumberBehavior = m_xRBDoubleNaturalSort->get_active() + ? ScSortNumberBehavior::DOUBLE : ScSortNumberBehavior::INTEGER; + else + aNewSortData.eSortNumberBehavior = ScSortNumberBehavior::ALPHA_NUMERIC; + rArgSet->Put( ScSortItem( SCITEM_SORTDATA, pViewData, &aNewSortData ) ); return true; @@ -786,6 +817,11 @@ IMPL_LINK( ScTabPageSortOptions, EnableHdl, weld::Toggleable&, rButton, void ) else m_xLbSortUser->set_sensitive(false); } + else if (&rButton == m_xBtnNaturalSort.get()) + { + m_xRBDoubleNaturalSort->set_sensitive(rButton.get_active()); + m_xRBIntegerNaturalSort->set_sensitive(rButton.get_active()); + } } IMPL_LINK(ScTabPageSortOptions, SelOutPosHdl, weld::ComboBox&, rLb, void) diff --git a/sc/source/ui/inc/tpsort.hxx b/sc/source/ui/inc/tpsort.hxx index b8c630aa9315..c5569308f3a2 100644 --- a/sc/source/ui/inc/tpsort.hxx +++ b/sc/source/ui/inc/tpsort.hxx @@ -140,6 +140,8 @@ private: std::unique_ptr<weld::ComboBox> m_xLbAlgorithm; std::unique_ptr<weld::CheckButton> m_xBtnIncComments; std::unique_ptr<weld::CheckButton> m_xBtnIncImages; + std::unique_ptr<weld::RadioButton> m_xRBDoubleNaturalSort; + std::unique_ptr<weld::RadioButton> m_xRBIntegerNaturalSort; private: void Init (); diff --git a/sc/source/ui/unoobj/datauno.cxx b/sc/source/ui/unoobj/datauno.cxx index 052133d19bba..e182e8622a7e 100644 --- a/sc/source/ui/unoobj/datauno.cxx +++ b/sc/source/ui/unoobj/datauno.cxx @@ -320,14 +320,14 @@ void ScSortDescriptor::FillProperties( uno::Sequence<beans::PropertyValue>& rSeq pArray[8].Value <<= static_cast<sal_Int32>( rParam.nUserIndex ); pArray[9].Name = SC_UNONAME_NUMBERBEHAVIOR; - pArray[9].Value <<= rParam.bNaturalSort ? SortNumberBehavior::DOUBLE - : SortNumberBehavior::ALPHA_NUMERIC; + pArray[9].Value <<= static_cast<sal_Int32>(rParam.eSortNumberBehavior); } void ScSortDescriptor::FillSortParam( ScSortParam& rParam, const uno::Sequence<beans::PropertyValue>& rSeq ) { sal_Int32 nSortSize = static_cast<sal_Int32>(rParam.GetSortKeyCount()); - rParam.bNaturalSort = false; // default if optional SC_UNONAME_NUMBERBEHAVIOR does not exist + // default if optional SC_UNONAME_NUMBERBEHAVIOR does not exist + rParam.eSortNumberBehavior = ScSortNumberBehavior::ALPHA_NUMERIC; for (const beans::PropertyValue& rProp : rSeq) { @@ -445,9 +445,11 @@ void ScSortDescriptor::FillSortParam( ScSortParam& rParam, const uno::Sequence<b } else if (aPropName == SC_UNONAME_NUMBERBEHAVIOR) { - sal_Int32 nVal = SortNumberBehavior::ALPHA_NUMERIC; + sal_Int32 nVal = css::sheet::SortNumberBehavior::ALPHA_NUMERIC; if (rProp.Value >>= nVal) - rParam.bNaturalSort = nVal == SortNumberBehavior::DOUBLE; + { + rParam.eSortNumberBehavior = static_cast<ScSortNumberBehavior>(nVal <= 0 || nVal > 2 ? 0 : nVal); + } } } } diff --git a/sc/source/ui/view/cellsh2.cxx b/sc/source/ui/view/cellsh2.cxx index c11ebf00ad08..a2d407f5e365 100644 --- a/sc/source/ui/view/cellsh2.cxx +++ b/sc/source/ui/view/cellsh2.cxx @@ -410,7 +410,7 @@ void ScCellShell::ExecuteDB( SfxRequest& rReq ) aSortParam.bHasHeader = bHasHeader; aSortParam.bByRow = true; aSortParam.bCaseSens = false; - aSortParam.bNaturalSort = false; + aSortParam.eSortNumberBehavior = ScSortNumberBehavior::ALPHA_NUMERIC; aSortParam.aDataAreaExtras.mbCellNotes = false; aSortParam.aDataAreaExtras.mbCellDrawObjects = true; aSortParam.aDataAreaExtras.mbCellFormats = true; @@ -464,7 +464,12 @@ void ScCellShell::ExecuteDB( SfxRequest& rReq ) if ( const SfxBoolItem* pItem = pArgs->GetItemIfSet( SID_SORT_CASESENS ) ) aSortParam.bCaseSens = pItem->GetValue(); if ( const SfxBoolItem* pItem = pArgs->GetItemIfSet( SID_SORT_NATURALSORT ) ) - aSortParam.bNaturalSort = pItem->GetValue(); + { + // For to keep old macros working. Its value will be overwritten by + // SID_SORT_NUMBERBEHAVIOR if that one is set. + sal_Int32 nVal = pItem->GetValue() ? 1 : 0; + aSortParam.eSortNumberBehavior = static_cast<ScSortNumberBehavior>(nVal); + } if ( const SfxBoolItem* pItem = pArgs->GetItemIfSet( SID_SORT_INCCOMMENTS ) ) aSortParam.aDataAreaExtras.mbCellNotes = pItem->GetValue(); if ( const SfxBoolItem* pItem = pArgs->GetItemIfSet( SID_SORT_INCIMAGES ) ) @@ -478,7 +483,13 @@ void ScCellShell::ExecuteDB( SfxRequest& rReq ) if ( nUserIndex ) aSortParam.nUserIndex = nUserIndex - 1; // Basic: 1-based } - + if (const SfxInt32Item* pItem = pArgs->GetItemIfSet( SID_SORT_NUMBERBEHAVIOR)) + { + sal_Int32 nVal = pItem->GetValue(); + if (nVal < 0 || nVal >2) + nVal = 0; // invalid value, use default + aSortParam.eSortNumberBehavior = static_cast<ScSortNumberBehavior>(nVal); + } SCCOLROW nField0 = 0; const SfxPoolItem* pItem = nullptr; if ( pArgs->GetItemState( FN_PARAM_1, true, &pItem ) == SfxItemState::SET ) @@ -554,8 +565,6 @@ void ScCellShell::ExecuteDB( SfxRequest& rReq ) rOutParam.bHasHeader ) ); aRequest.AppendItem( SfxBoolItem( SID_SORT_CASESENS, rOutParam.bCaseSens ) ); - aRequest.AppendItem( SfxBoolItem( SID_SORT_NATURALSORT, - rOutParam.bNaturalSort ) ); aRequest.AppendItem( SfxBoolItem( SID_SORT_INCCOMMENTS, rOutParam.aDataAreaExtras.mbCellNotes ) ); aRequest.AppendItem( SfxBoolItem( SID_SORT_INCIMAGES, diff --git a/sc/source/ui/view/gridwin.cxx b/sc/source/ui/view/gridwin.cxx index 0f1b7a36ed33..3ee29d55bc59 100644 --- a/sc/source/ui/view/gridwin.cxx +++ b/sc/source/ui/view/gridwin.cxx @@ -700,7 +700,7 @@ public: aSortParam.bHasHeader = bHasHeader; aSortParam.bByRow = true; aSortParam.bCaseSens = false; - aSortParam.bNaturalSort = false; + aSortParam.eSortNumberBehavior = ScSortNumberBehavior::ALPHA_NUMERIC; aSortParam.aDataAreaExtras.mbCellNotes = false; aSortParam.aDataAreaExtras.mbCellDrawObjects = true; aSortParam.aDataAreaExtras.mbCellFormats = true; @@ -1201,7 +1201,7 @@ void ScGridWindow::UpdateAutoFilterFromMenu(AutoFilterMode eMode) aSortParam.bHasHeader = bHasHeader; aSortParam.bByRow = true; aSortParam.bCaseSens = false; - aSortParam.bNaturalSort = false; + aSortParam.eSortNumberBehavior = ScSortNumberBehavior::ALPHA_NUMERIC; aSortParam.aDataAreaExtras.mbCellNotes = false; aSortParam.aDataAreaExtras.mbCellDrawObjects = true; aSortParam.aDataAreaExtras.mbCellFormats = true; diff --git a/sc/uiconfig/scalc/ui/sortoptionspage.ui b/sc/uiconfig/scalc/ui/sortoptionspage.ui index 9ba51748c246..b7467e3fcec4 100644 --- a/sc/uiconfig/scalc/ui/sortoptionspage.ui +++ b/sc/uiconfig/scalc/ui/sortoptionspage.ui @@ -63,17 +63,64 @@ </packing> </child> <child> - <object class="GtkCheckButton" id="naturalsort"> - <property name="label" translatable="yes" context="sortoptionspage|naturalsort">Enable natural sort</property> + <object class="GtkBox"> <property name="visible">True</property> - <property name="can-focus">True</property> - <property name="receives-default">False</property> - <property name="use-underline">True</property> - <property name="draw-indicator">True</property> - <child internal-child="accessible"> - <object class="AtkObject" id="naturalsort-atkobject"> - <property name="AtkObject::accessible-description" translatable="yes" context="sortoptionspage|extended_tip|naturalsort">Natural sort is a sort algorithm that sorts string-prefixed numbers based on the value of the numerical element in each sorted number, instead of the traditional way of sorting them as ordinary strings.</property> + <property name="can-focus">False</property> + <property name="orientation">vertical</property> + <property name="spacing">3</property> + <child> + <object class="GtkCheckButton" id="naturalsort"> + <property name="label" translatable="yes" context="sortoptionspage|naturalsort">Recognize numbers within strings</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="has-tooltip">True</property> + <property name="tooltip-text" translatable="yes" context="sortoptionspage|naturalsort">For numbers embedded in text, sorting uses their values instead of their sequence of digit characters. Example: A1, A2, A10 instead of A1, A10, A2</property> + <property name="use-underline">True</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">True</property> + <property name="position">0</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="doublenaturalsortrb"> + <property name="label" translatable="yes" context="sortoptionspage|naturalsort|doublenaturalsortrb">Use decimal numbers as a whole</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="has-tooltip">True</property> + <property name="tooltip-text" translatable="yes" context="sortoptionspage|naturalsort|doublenaturalsortrb">Decimal numbers are detected. Example: A1.14, A1.2, A2.5, A10 (with dot as separator)</property> + <property name="margin-start">20</property> + <property name="use-underline">True</property> + <property name="draw-indicator">True</property> + </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">1</property> + </packing> + </child> + <child> + <object class="GtkRadioButton" id="integernaturalsortrb"> + <property name="label" translatable="yes" context="sortoptionspage|naturalsort|integernaturalsortrb">Split in integer and fractions</property> + <property name="visible">True</property> + <property name="can-focus">True</property> + <property name="receives-default">False</property> + <property name="has-tooltip">True</property> + <property name="tooltip-text" translatable="yes" context="sortoptionspage|naturalsort|integernaturalsortrb">Decimal separator is treated as ordinary character. Example: A1.2, A1.14, A2.5, A10</property> + <property name="margin-start">20</property> + <property name="use-underline">True</property> + <property name="draw-indicator">True</property> + <property name="group">doublenaturalsortrb</property> </object> + <packing> + <property name="expand">False</property> + <property name="fill">False</property> + <property name="position">2</property> + </packing> </child> </object> <packing> @@ -266,7 +313,6 @@ <property name="has-entry">True</property> <child internal-child="entry"> <object class="GtkEntry"> - <property name="truncate-multiline">True</property> <property name="can-focus">False</property> </object> </child>
